2026-09-27 18:02:09 +02:00
<!doctype html>
< html lang = "en" >
< head >
< meta charset = "utf-8" >
< title > Dialog< / title >
< style >
/* Same surface tokens as approval.html so page dialogs and add-on approvals
read as one family; tones follow the notification-card pattern (icon in a
tinted circle, tinted primary action, quiet dismiss). */
:root { color-scheme: light dark;
--surface:#1c222c; --surface2:#0f1621; --line:rgba(255,255,255,.12);
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d;
--ok:#3ddc84; --warn:#f5a524; --danger:#f6768a; --info:#8b98a9; }
@media (prefers-color-scheme: light) {
:root { --surface:#ffffff; --surface2:#f1f4fa; --line:rgba(0,0,0,.12);
--ink:#1a1f2b; --mut:#5c6577; --dim:#8a93a5; --acid:#0AC18E;
--ok:#16a34a; --warn:#d97706; --danger:#dc2626; --info:#5c6577; }
}
* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; background: transparent; }
body { font: 13px/1.5 system-ui, -apple-system, Segoe UI, Roboto, sans-serif; color: var(--ink); }
.promptmask { position: fixed; inset: 0; background: rgba(0,0,0,.35);
display: grid; place-items: start center; padding-top: 14px; }
.card { --tone: var(--info);
position: relative; background: var(--surface); border: 1px solid var(--line); border-radius: 14px;
padding: 16px 16px 14px 16px; width: min(440px, calc(100vw - 32px)); max-height: calc(100vh - 28px); overflow: auto;
box-shadow: 0 18px 50px #000b; animation: drop .14s ease-out; }
.card.ok { --tone: var(--ok); } .card.warn { --tone: var(--warn); } .card.danger { --tone: var(--danger); }
@keyframes drop { from { transform: translateY(-8px); opacity: 0; } to { transform: none; opacity: 1; } }
.row { display: flex; gap: 12px; align-items: flex-start; }
.icon { flex: none; width: 30px; height: 30px; border-radius: 50%; display: grid; place-items: center;
background: rgb(from var(--tone) r g b / .16); color: var(--tone); margin-top: 1px; }
.icon svg { width: 17px; height: 17px; }
.text { flex: 1; min-width: 0; padding-right: 22px; }
.who { color: var(--dim); font-size: 11.5px; margin: 0 0 3px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.who .src { color: var(--mut); font-weight: 600; }
.who .src.site { font: 600 11.5px/1.3 ui-monospace, "Cascadia Code", Consolas, monospace; }
.body { white-space: pre-wrap; overflow-wrap: anywhere; color: var(--ink); font-size: 13.5px; font-weight: 500; max-height: 40vh; overflow: auto; }
.close { position: absolute; top: 10px; right: 10px; width: 26px; height: 26px; border: none; border-radius: 7px;
background: transparent; color: var(--dim); cursor: pointer; font-size: 15px; line-height: 1; }
.close:hover { background: var(--surface2); color: var(--ink); }
input.pin { display: block; width: 100%; margin: 10px 0 0; padding: 8px 10px; border-radius: 8px;
background: var(--surface2); color: var(--ink); border: 1px solid var(--line); font: inherit; font-size: 13px; }
input.pin:focus { outline: none; border-color: rgb(from var(--tone) r g b / .7); }
.pact { display: flex; gap: 8px; align-items: center; margin: 12px 0 0 42px; }
.pbtn { padding: 7px 16px; border-radius: 8px; border: 1px solid transparent; cursor: pointer; font: inherit; font-size: 12.5px; font-weight: 600; min-width: 84px; }
.pbtn.primary { background: rgb(from var(--tone) r g b / .18); color: var(--tone); border-color: rgb(from var(--tone) r g b / .35); }
.pbtn.primary:hover { background: rgb(from var(--tone) r g b / .28); }
.card.info .pbtn.primary { background: var(--acid); color: #0b0e14; border-color: transparent; }
.card.info .pbtn.primary:hover { filter: brightness(1.05); }
.pbtn.quiet { background: transparent; color: var(--mut); }
.pbtn.quiet:hover { background: var(--surface2); color: var(--ink); }
.pbtn:focus-visible { outline: 2px solid rgb(from var(--tone) r g b / .6); outline-offset: 1px; }
< / style >
< / head >
< body >
< script >
const esc = (s) => String(s ?? "").replace(/[& < >"']/g, (c) => ({ "&":"& ","< ":"< ",">":"> ",'"':"" ","'":"' " })[c]);
// The page only gives us a sentence; read its tone from the words so a
// "Delete…?" looks destructive and a "Saved." looks like good news.
function toneFor(msg) {
const m = String(msg || "").toLowerCase();
if (/\b(error|fail|failed|failure|incorrect|invalid|wrong|cannot|can't|couldn't|unable|denied|delete|remove|discard|permanently|undone|lost|reject)\b/.test(m)) return "danger";
if (/\b(warning|caution|required|please provide|must|expire|expiring|expired|unsaved|leave|overwrite|replace)\b/.test(m)) return "warn";
if (/\b(success|successfully|saved|completed|complete|done|created|copied|sent|installed|updated|ready)\b/.test(m)) return "ok";
return "info";
}
const ICONS = {
ok: '< svg viewBox = "0 0 16 16" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" > < path d = "M3.5 8.5 L6.5 11.5 L12.5 5" / > < / svg > ',
warn: '< svg viewBox = "0 0 16 16" fill = "none" stroke = "currentColor" stroke-width = "1.8" stroke-linecap = "round" stroke-linejoin = "round" > < path d = "M8 2.2 L14.3 13.3 H1.7 Z" / > < path d = "M8 6.2 V9.6" / > < circle cx = "8" cy = "11.8" r = ".6" fill = "currentColor" / > < / svg > ',
danger: '< svg viewBox = "0 0 16 16" fill = "none" stroke = "currentColor" stroke-width = "1.8" stroke-linecap = "round" > < circle cx = "8" cy = "8" r = "6.3" / > < path d = "M8 4.6 V8.8" / > < circle cx = "8" cy = "11.3" r = ".6" fill = "currentColor" / > < / svg > ',
info: '< svg viewBox = "0 0 16 16" fill = "none" stroke = "currentColor" stroke-width = "1.8" stroke-linecap = "round" > < circle cx = "8" cy = "8" r = "6.3" / > < path d = "M8 7.2 V11.4" / > < circle cx = "8" cy = "4.9" r = ".6" fill = "currentColor" / > < / svg > ',
};
let current = null;
function finish(ok) {
if (!current) return;
const { reqId, kind } = current;
const input = document.getElementById("pin");
const value = kind === "prompt" ? (ok ? (input ? input.value : "") : null) : null;
current = null;
document.body.innerHTML = "";
window.jsdialog.answer(reqId, !!ok, value);
}
window.jsdialog.onShow((req) => {
current = req;
const who = req.who || { label: "This page", kind: "site" };
const verb = req.kind === "prompt" ? "asks" : "says";
const tone = toneFor(req.message);
const okLabel = req.kind === "alert" ? "OK" : tone === "danger" & & /\?\s*$/.test(String(req.message || "")) ? "Yes" : "OK";
document.body.innerHTML =
`< div class = "promptmask" > < div class = "card ${tone}" role = "${req.kind === " alert " ? " alertdialog " : " dialog " } " aria-modal = "true" >
< button class = "close" id = "close" type = "button" title = "${req.kind === " alert " ? " OK " : " Cancel " } " aria-label = "Close" > ✕< / button >
< div class = "row" >
< div class = "icon" > ${ICONS[tone]}< / div >
< div class = "text" >
< div class = "who" > < span class = "src ${esc(who.kind)}" title = "${esc(who.label)}" > ${esc(who.label)}< / span > ${verb}< / div >
< div class = "body" > ${esc(req.message)}< / div >
${req.kind === "prompt" ? `< input class = "pin" id = "pin" type = "text" value = "${esc(req.def ?? " " ) } " spellcheck = "false" > ` : ""}
< / div >
< / div >
< div class = "pact" >
< button class = "pbtn primary" type = "button" id = "ok" > ${okLabel}< / button >
${req.kind === "alert" ? "" : `< button class = "pbtn quiet" type = "button" id = "cancel" > Cancel< / button > `}
< / div >
< / div > < / div > `;
document.getElementById("ok").addEventListener("click", () => finish(true));
const c = document.getElementById("cancel"); if (c) c.addEventListener("click", () => finish(false));
document.getElementById("close").addEventListener("click", () => finish(req.kind === "alert"));
const mask = document.querySelector(".promptmask");
mask.addEventListener("mousedown", (e) => { if (e.target === mask) finish(req.kind === "alert"); });
const input = document.getElementById("pin");
setTimeout(() => { if (input) { input.focus(); input.select(); } else document.getElementById("ok").focus(); }, 0);
});
document.addEventListener("keydown", (e) => {
if (!current) return;
if (e.key === "Escape") { e.preventDefault(); finish(current.kind === "alert"); }
else if (e.key === "Enter" & & !(e.target & & (e.target.id === "cancel" || e.target.id === "close"))) { e.preventDefault(); finish(true); }
});
< / script >
< / body >
< / html >