Files
Moon-Household-Budget/moon-household-budget-handoff/suggestions/static/suggestions.js
2026-06-06 14:22:57 -05:00

88 lines
2.8 KiB
JavaScript

// 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 = '<p class="muted">No open suggestions yet.</p>';
return;
}
list.innerHTML = issues.map((i) => `
<div class="card">
<div class="issue-title">#${i.number} ${escapeHtml(i.title)}</div>
<div class="issue-meta">${i.comments} comment(s) ·
<a href="${i.html_url}" target="_blank" rel="noopener">view on Gitea</a></div>
</div>`).join("");
} catch (e) {
list.innerHTML = '<p class="muted">Could not load suggestions.</p>';
}
}
function escapeHtml(s) {
return (s || "").replace(/[&<>"']/g, (c) => (
{ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[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![pasted image](${url})`;
$("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();