Initial commit — Moon Household Budget
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
// 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) => (
|
||||
{ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[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();
|
||||
Reference in New Issue
Block a user