5b8042b173
Replace the visible create button with a secret tap gesture that still opens the PIN-protected recap flow. Co-authored-by: Cursor <cursoragent@cursor.com>
194 lines
5.2 KiB
JavaScript
194 lines
5.2 KiB
JavaScript
import {
|
|
api,
|
|
VERDICT_LABELS,
|
|
formatTimestamp,
|
|
getToken,
|
|
setToken,
|
|
isLoggedIn,
|
|
} from "./api.js";
|
|
|
|
const createModal = document.getElementById("createModal");
|
|
const pinModal = document.getElementById("pinModal");
|
|
const recapList = document.getElementById("recapList");
|
|
const emptyState = document.getElementById("emptyState");
|
|
const listError = document.getElementById("listError");
|
|
|
|
function escapeHtml(text) {
|
|
const div = document.createElement("div");
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
function renderRecapCard(item) {
|
|
const verdict = VERDICT_LABELS[item.watch_verdict] || VERDICT_LABELS.skim;
|
|
const href = `/r/${encodeURIComponent(item.video_id)}`;
|
|
return `
|
|
<a class="recapCard" href="${href}">
|
|
<img class="recapThumb" src="${escapeHtml(item.thumbnail_url || "")}" alt="" loading="lazy">
|
|
<div class="recapCardBody">
|
|
<span class="verdictBadge ${verdict.className}">${verdict.emoji} ${verdict.text}</span>
|
|
<h3>${escapeHtml(item.title)}</h3>
|
|
<p class="recapTldr">${escapeHtml(item.tldr)}</p>
|
|
<span class="recapMeta">${escapeHtml(item.channel || "")}</span>
|
|
</div>
|
|
</a>
|
|
`;
|
|
}
|
|
|
|
async function loadRecaps() {
|
|
listError.hidden = true;
|
|
try {
|
|
const data = await api.listRecaps(30);
|
|
const items = data.items || [];
|
|
if (!items.length) {
|
|
recapList.innerHTML = "";
|
|
emptyState.hidden = false;
|
|
return;
|
|
}
|
|
emptyState.hidden = true;
|
|
recapList.innerHTML = items.map(renderRecapCard).join("");
|
|
} catch (err) {
|
|
listError.textContent = err.message || "Liste konnte nicht geladen werden.";
|
|
listError.hidden = false;
|
|
}
|
|
}
|
|
|
|
function openPinModal() {
|
|
document.getElementById("pinError").hidden = true;
|
|
document.getElementById("pinInput").value = "";
|
|
pinModal.showModal();
|
|
document.getElementById("pinInput").focus();
|
|
}
|
|
|
|
function waitForPinLogin() {
|
|
return new Promise((resolve, reject) => {
|
|
if (isLoggedIn()) {
|
|
resolve();
|
|
return;
|
|
}
|
|
openPinModal();
|
|
pinModal.addEventListener(
|
|
"close",
|
|
() => {
|
|
if (isLoggedIn()) resolve();
|
|
else reject(new Error("Abgebrochen"));
|
|
},
|
|
{ once: true }
|
|
);
|
|
});
|
|
}
|
|
|
|
function openCreateModal() {
|
|
document.getElementById("createError").hidden = true;
|
|
document.getElementById("createProgress").hidden = true;
|
|
document.getElementById("urlInput").value = "";
|
|
document.getElementById("forceCheckbox").checked = false;
|
|
document.getElementById("createSubmit").disabled = false;
|
|
createModal.showModal();
|
|
document.getElementById("urlInput").focus();
|
|
}
|
|
|
|
async function handleCreate(e) {
|
|
e.preventDefault();
|
|
const url = document.getElementById("urlInput").value.trim();
|
|
const force = document.getElementById("forceCheckbox").checked;
|
|
const errEl = document.getElementById("createError");
|
|
const progress = document.getElementById("createProgress");
|
|
const submit = document.getElementById("createSubmit");
|
|
|
|
errEl.hidden = true;
|
|
progress.hidden = false;
|
|
submit.disabled = true;
|
|
|
|
try {
|
|
const result = await api.createRecap(url, force);
|
|
createModal.close();
|
|
window.location.href = `/r/${encodeURIComponent(result.recap.video_id)}`;
|
|
} catch (err) {
|
|
errEl.textContent = err.message || "Recap fehlgeschlagen.";
|
|
errEl.hidden = false;
|
|
progress.hidden = true;
|
|
submit.disabled = false;
|
|
}
|
|
}
|
|
|
|
function wirePinForm() {
|
|
document.getElementById("pinCancel").addEventListener("click", () => pinModal.close());
|
|
document.getElementById("pinForm").addEventListener("submit", async (e) => {
|
|
e.preventDefault();
|
|
const pin = document.getElementById("pinInput").value.trim();
|
|
const errEl = document.getElementById("pinError");
|
|
errEl.hidden = true;
|
|
try {
|
|
const { token } = await api.login(pin);
|
|
setToken(token);
|
|
pinModal.close();
|
|
} catch {
|
|
errEl.textContent = "Falscher PIN. Dein Goldfisch auch.";
|
|
errEl.hidden = false;
|
|
}
|
|
});
|
|
}
|
|
|
|
const GOLDFISH_TAPS = 5;
|
|
const GOLDFISH_RESET_MS = 2000;
|
|
|
|
async function openCreateFlow() {
|
|
try {
|
|
await waitForPinLogin();
|
|
openCreateModal();
|
|
} catch {
|
|
/* cancelled */
|
|
}
|
|
}
|
|
|
|
function wireGoldfishSecret() {
|
|
const logo = document.getElementById("goldfishLogo");
|
|
let taps = 0;
|
|
let resetTimer = null;
|
|
|
|
logo.addEventListener("click", () => {
|
|
taps += 1;
|
|
logo.classList.add("logoWiggle");
|
|
window.setTimeout(() => logo.classList.remove("logoWiggle"), 300);
|
|
|
|
if (resetTimer) window.clearTimeout(resetTimer);
|
|
resetTimer = window.setTimeout(() => {
|
|
taps = 0;
|
|
}, GOLDFISH_RESET_MS);
|
|
|
|
if (taps >= GOLDFISH_TAPS) {
|
|
taps = 0;
|
|
if (resetTimer) window.clearTimeout(resetTimer);
|
|
openCreateFlow();
|
|
}
|
|
});
|
|
}
|
|
|
|
function wireCreate() {
|
|
wireGoldfishSecret();
|
|
document.getElementById("createCancel").addEventListener("click", () => createModal.close());
|
|
document.getElementById("createForm").addEventListener("submit", async (e) => {
|
|
e.preventDefault();
|
|
if (!isLoggedIn()) {
|
|
try {
|
|
await waitForPinLogin();
|
|
} catch {
|
|
return;
|
|
}
|
|
}
|
|
await handleCreate(e);
|
|
});
|
|
}
|
|
|
|
function registerSw() {
|
|
if ("serviceWorker" in navigator) {
|
|
navigator.serviceWorker.register("/sw.js").catch(() => {});
|
|
}
|
|
}
|
|
|
|
wirePinForm();
|
|
wireCreate();
|
|
loadRecaps();
|
|
registerSw();
|