$("#gal").innerHTML = `
❑

${all.length ? "No templates match the filters." : "No saved templates yet. Create or upload templates on the HTML Templates page."}

`; return; } $("#gal").innerHTML = rows.map(t => `

${esc(t.name)}

${esc(t.source || "—")}
${t.subject_hint ? `
${esc(t.subject_hint)}
` : ""}
${thumb(t)}
${(t.variables || []).slice(0, 10).map(v => `${esc(v)}`).join("")} ${(t.variables || []).length > 10 ? `+${t.variables.length - 10} more` : ""} ${(t.unknown_variables || []).length ? `${t.unknown_variables.length} unknown` : ""}
`).join(""); $$("#gal [data-prev]").forEach(b => b.onclick = () => previewTpl(+b.dataset.prev)); $$("#gal [data-pdf]").forEach(b => b.onclick = () => testPdf(+b.dataset.pdf)); $$("#gal [data-dup]").forEach(b => b.onclick = async () => { try { const d = await POST(`/templates/${b.dataset.dup}/duplicate`); toast(`Duplicated — new template #${d.id}`, "ok"); load(); } catch (e) { toast(e.message, "err", 6000); } }); $$("#gal [data-del]").forEach(b => b.onclick = () => confirmedAction(`Delete this template? Campaigns that reference it will show a warning at start.`, async () => { await DEL(`/templates/${b.dataset.del}`); load(); return "Template deleted"; }, { title: "Delete template", confirmLabel: "Delete" })); } async function previewTpl(id) { let d; try { d = await POST(`/templates/${id}/preview`, {}); } catch (e) { toast(e.message, "err", 6000); return; } const node = el("div", {}, (d.unknown_variables || []).length || (d.missing_variables || []).length || (d.warnings || []).length ? el("div", { class: "notice warn mb-1", html: `⚠
` + [].concat(d.unknown_variables || []).map(v => `Unknown variable: ${esc(v)}`).join("
") + [].concat(d.missing_variables || []).map(v => `Missing data: ${esc(v)}`).join("
") + [].concat(d.warnings || []).map(w => `Validation: ${esc(String(w))}`).join("
") + `
` }) : null, el("div", { class: "kv-inline" }, d.generated && d.generated.invoice_number ? el("span", { class: "badge" }, esc(d.generated.invoice_number)) : null, d.generated && d.generated.amount != null ? el("span", { class: "badge" }, fmtMoney(d.generated.amount)) : null, d.generated && d.generated.shipping != null ? el("span", { class: "badge" }, "ship " + fmtMoney(d.generated.shipping)) : null), el("iframe", { class: "preview-frame", srcdoc: d.rendered_html })); openModal(`Preview — ${((all.find(t => t.id === id) || {}).name) || "#" + id}`, node, { xw: true }); } async function testPdf(id) { toast("Rendering PDF via WeasyPrint…", "info"); try { const d = await POST(`/templates/${id}/test-pdf`, {}); const blob = b64Blob(d.pdf_base64, "application/pdf"); dlBlob(blob, `template_${id}_test.pdf`); toast(`PDF generated — ${fmtBytes(d.size_bytes)} (${d.engine})`, "ok"); } catch (e) { toast(e.message, "err", 6000); } } $("#fSearch").oninput = debounce(() => { q = $("#fSearch").value.trim(); render(); }, 250); $("#fSrc").onchange = () => { src = $("#fSrc").value; render(); }; $("#btnClear").onclick = () => confirmedAction("Clear ALL saved templates? Every template will be permanently deleted. Campaigns referencing them will fail validation.", async () => { const r = await POST("/templates/clear-all"); load(); return `Cleared ${r.deleted ?? 0} template(s)`; }, { title: "Clear all templates", confirmLabel: "Delete everything" }); load(); };