// Minimal vanilla-JS client for the suggestions page. Talks to the JSON API // exposed by routes_flask.py / routes_fastapi.py. No build step, no framework. const $ = (id) => document.getElementById(id); async function loadIssues() { const list = $("s-list"); if (!list) return; try { const res = await fetch("/api/suggestions?state=open"); const data = await res.json(); const issues = data.issues || []; if (!issues.length) { list.innerHTML = '
No open suggestions yet.
'; return; } list.innerHTML = issues.map((i) => `Could not load suggestions.
'; } } function escapeHtml(s) { return (s || "").replace(/[&<>"']/g, (c) => ( { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c] )); } async function uploadImage(file) { const fd = new FormData(); fd.append("file", file); const res = await fetch("/api/suggestions/upload", { method: "POST", body: fd }); if (!res.ok) throw new Error("upload failed"); return (await res.json()).url; } function wireComposer() { const send = $("s-send"); const body = $("s-body"); if (!send || !body) return; // Paste-to-attach: drop an image embed into the body. body.addEventListener("paste", async (ev) => { const item = [...(ev.clipboardData?.items || [])].find((i) => i.type.startsWith("image/")); if (!item) return; ev.preventDefault(); $("s-status").textContent = "Uploading image…"; try { const url = await uploadImage(item.getAsFile()); body.value += `\n\n`; $("s-status").textContent = "Image attached."; } catch { $("s-status").textContent = "Image upload failed."; } }); send.addEventListener("click", async () => { const title = $("s-title").value.trim(); if (title.length < 3) { $("s-status").textContent = "Add a longer title."; return; } send.disabled = true; $("s-status").textContent = "Sending…"; try { const res = await fetch("/api/suggestions", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ title, body: body.value }), }); if (!res.ok) throw new Error(); $("s-title").value = ""; body.value = ""; $("s-status").textContent = "Thanks! Sent."; loadIssues(); } catch { $("s-status").textContent = "Could not send — try again."; } finally { send.disabled = false; } }); } wireComposer(); loadIssues();