window.pageBoot = async (me) => { let settings = [], cat = null; const dirty = new Set(); async function load(){ const d = await GET("/settings/list"); settings = d.settings || []; const cats = [...new Set(settings.map(s => s.category))]; if (!cat && cats.length) cat = cats[0]; $("#cats").innerHTML = cats.map(c => `
${esc(c)} ${settings.filter(s=>s.category===c).length}
`).join(""); renderRows(); } function renderRows(){ $("#catTitle").textContent = cat || "—"; const rows = settings.filter(s => s.category === cat); $("#rows").innerHTML = rows.map(s => { const val = s.value === null || s.value === undefined ? "" : String(s.value); const input = (s.type === "bool") ? `` : ``; return `
${esc(s.label)} key: ${esc(s.key)} · default: ${esc(String(s.default))}${s.is_default ? " (using default)" : " (customized)"}
${input}
`; }).join(""); dirty.clear(); } $("#cats").addEventListener("click", (e) => { const it = e.target.closest(".cat-item"); if (!it) return; cat = it.dataset.cat; load(); }); $("#rows").addEventListener("input", (e) => { const inp = e.target.closest("[data-key]"); if (inp) dirty.add(inp.dataset.key); }); $("#rows").addEventListener("change", (e) => { const inp = e.target.closest("[data-key]"); if (inp) dirty.add(inp.dataset.key); }); $("#btnSave").addEventListener("click", async () => { if (!dirty.size) { toast("No changes to save"); return; } const values = {}; for (const k of dirty) { const inp = $(`#rows [data-key="${CSS.escape(k)}"]`); values[k] = inp.value; } try { const d = await POST("/settings/save-batch", { values }); const errs = d.errors || {}; const nApplied = Object.keys(d.applied || {}).length; if (nApplied) toast(`Saved ${nApplied} setting(s)`, "ok"); if (Object.keys(errs).length) toast("Some values rejected: " + JSON.stringify(errs), "err", 8000); await load(); } catch (err) { toast("Save failed: " + err.message, "err"); } }); $("#rows").addEventListener("click", (e) => { const b = e.target.closest("button"); if (!b || !b.dataset.reset) return; confirmedAction(`Reset “${b.dataset.reset}” to its default value?`, async () => { await POST("/settings/reset", { key: b.dataset.reset, value: "" }); toast("Reset to default", "ok"); await load(); }); }); $("#btnResetCat").addEventListener("click", () => { const keys = settings.filter(s => s.category === cat && !s.is_default).map(s => s.key); if (!keys.length) { toast("All settings in this category already use defaults"); return; } confirmedAction(`Reset all customized settings in “${cat}”?`, async () => { for (const k of keys) await POST("/settings/reset", { key: k, value: "" }); toast(`Reset ${keys.length} setting(s)`, "ok"); await load(); }); }); === HTML controls === 24:
25:
32: 33: 39:
42:

—

43: