"use strict"; const api = { async get(url) { const r = await fetch(url, { credentials: "same-origin" }); return r.json(); }, async post(url, formData) { const r = await fetch(url, { method: "POST", body: formData, credentials: "same-origin", }); return { ok: r.ok, status: r.status, data: await r.json().catch(() => ({})) }; }, }; // --- Headerbar (eingeloggt?) --- let loggedIn = false; async function refreshHeader() { const h = await api.get("/auth/headerbar"); loggedIn = !!h.loggedin; const bar = document.getElementById("headerbar"); document.getElementById("auth-box").hidden = loggedIn; document.getElementById("compose").hidden = !loggedIn; document.getElementById("account").hidden = !loggedIn; if (loggedIn) { bar.innerHTML = ``; document.getElementById("logout-btn").addEventListener("click", async () => { await api.get("/auth/logout"); await refreshHeader(); }); loadAccount(); } else { bar.innerHTML = ""; showAuth("login"); } } async function loadAccount() { const info = await api.get("/user/info"); const since = new Date(info.created_at * 1000).toLocaleDateString("de-DE"); document.getElementById("account-info").textContent = `Angemeldet als ${info.username} · Mitglied seit ${since}`; } document.getElementById("delete-form").addEventListener("submit", async (e) => { e.preventDefault(); if (!confirm("Konto wirklich unwiderruflich löschen?")) return; const res = await api.post("/user/delete", new FormData(e.target)); if (res.ok) { await refreshHeader(); } else { document.getElementById("delete-msg").textContent = res.data.error || "Löschen fehlgeschlagen."; } }); // --- Login / Registrierung --- const loginForm = document.getElementById("login-form"); const registerForm = document.getElementById("register-form"); const authMsg = document.getElementById("auth-msg"); function showAuth(which) { loginForm.hidden = which !== "login"; registerForm.hidden = which !== "register"; authMsg.textContent = ""; } document.getElementById("show-register").addEventListener("click", (e) => { e.preventDefault(); showAuth("register"); }); document.getElementById("show-login").addEventListener("click", (e) => { e.preventDefault(); showAuth("login"); }); loginForm.addEventListener("submit", async (e) => { e.preventDefault(); const res = await api.post("/auth/login", new FormData(e.target)); if (res.ok) { await refreshHeader(); } else { authMsg.textContent = res.data.error || "Login fehlgeschlagen."; } }); registerForm.addEventListener("submit", async (e) => { e.preventDefault(); const data = new FormData(e.target); const res = await api.post("/auth/newuser", data); if (!res.ok) { // newuser liefert eine Fehlerliste ({"errors": [...]}). const errors = res.data.errors || [res.data.error || "Registrierung fehlgeschlagen."]; authMsg.textContent = errors.join(" "); return; } // Direkt mit denselben Daten einloggen, damit der Nutzer gleich drin ist. const login = new FormData(); login.append("user", data.get("user")); login.append("pass", data.get("pass1")); login.append("timeout", "86400"); await api.post("/auth/login", login); await refreshHeader(); }); // --- Neuer Beitrag --- document.getElementById("entry-form").addEventListener("submit", async (e) => { e.preventDefault(); const res = await api.post("/entry/create", new FormData(e.target)); const msg = document.getElementById("entry-msg"); if (res.ok) { msg.textContent = ""; e.target.reset(); resetFeed(); } else { msg.textContent = res.data.error || "Beitrag fehlgeschlagen."; } }); // --- Feed mit Infinite Scroll --- let page = 0; let loading = false; let done = false; function renderEntry(e) { const el = document.createElement("article"); el.className = "card"; const when = new Date(e.created_at * 1000).toLocaleString("de-DE"); let media = ""; if (e.filepath) { media = ``; } el.innerHTML = `
${escapeHtml(e.username)} · ${when}
${linkify(e.content)}
${media}
– / –
`; setupVoting(el, e.pid); return el; } function setupVoting(el, pid) { const box = el.querySelector(".row"); const counts = box.querySelector("span"); const buttons = box.querySelectorAll(".ghost"); function render(state) { if (state.error) { counts.textContent = state.error; return; } counts.textContent = `${state.left} / ${state.right}`; buttons.forEach((b) => b.classList.toggle("selected", b.dataset.mode === state.selected) ); } buttons.forEach((b) => b.addEventListener("click", async () => { const fd = new FormData(); fd.append("mode", b.dataset.mode); const res = await api.post(`/entry/${pid}/vote`, fd); render(res.data); }) ); // Anfangszustand laden (read-only). api.get(`/entry/${pid}/votes`).then(render); } async function loadMore() { if (loading || done) return; loading = true; const entries = await api.get(`/entry/feed/${page}`); const feed = document.getElementById("feed"); if (!entries.length) { done = true; const end = document.createElement("p"); end.className = "muted end"; end.textContent = "YOU REACHED THE END!"; feed.appendChild(end); } else { entries.forEach((e) => feed.appendChild(renderEntry(e))); page++; } loading = false; } function resetFeed() { page = 0; done = false; document.getElementById("feed").innerHTML = ""; loadMore(); } const observer = new IntersectionObserver((items) => { if (items.some((i) => i.isIntersecting)) loadMore(); }); observer.observe(document.getElementById("sentinel")); // --- Helpers --- function escapeHtml(s) { const d = document.createElement("div"); d.textContent = s ?? ""; return d.innerHTML; } function linkify(s) { const escaped = escapeHtml(s); const url = /https?:\/\/[^\s<]+/g; return escaped .replace(url, (m) => `${m}`) .replace(/\n/g, "
"); } // --- Start --- refreshHeader(); loadMore();