// settings.js — the settings page's three enhancements. None of them
// adds a control the page lacks without script (RFC 0001 Principle 3):
// the rail marks the section in view, a destructive button asks once
// before its form posts, and copy buttons — hidden in the markup —
// are shown, because a button that cannot copy is a lie.
(function () {
"use strict";
// The rail follows the reader. Each section reports how much of it
// is visible; the link of the most visible one carries aria-current.
// A section taller than the viewport never reaches 100%, so the
// ratio is compared, not thresholded.
var rail = document.querySelector(".set-rail");
if (rail && "IntersectionObserver" in window) {
var seen = {};
var mark = function () {
var best = null;
for (var id in seen) {
if (best === null || seen[id] > seen[best]) best = id;
}
rail.querySelectorAll("a[aria-current]").forEach(function (a) {
a.removeAttribute("aria-current");
});
var a = best && rail.querySelector('a[href="#' + best + '"]');
if (a) a.setAttribute("aria-current", "true");
};
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) { seen[e.target.id] = e.intersectionRatio; });
mark();
}, { threshold: [0, 0.25, 0.5, 0.75, 1] });
document.querySelectorAll("main section[id]").forEach(function (s) { io.observe(s); });
}
// One dialog, shared. The submit button that opened it keeps its own
// label inside the dialog, so "Revoke" is confirmed by "Revoke".
// Confirming re-submits the form with the same button, flagged so the
// interception lets it through this once.
var dlg = document.getElementById("confirm");
if (dlg && typeof dlg.showModal === "function") {
var pending = null;
document.addEventListener("submit", function (ev) {
var b = ev.submitter;
if (!b || !b.hasAttribute("data-confirm") || ev.target._confirmed) return;
ev.preventDefault();
pending = { form: ev.target, button: b };
dlg.querySelector(".confirm-why").textContent = b.getAttribute("data-confirm");
dlg.querySelector(".confirm-do").textContent = b.textContent;
dlg.showModal();
});
dlg.querySelector(".confirm-do").addEventListener("click", function () {
var p = pending;
dlg.close();
if (!p) return;
p.form._confirmed = true;
p.form.requestSubmit(p.button);
});
dlg.querySelector(".confirm-no").addEventListener("click", function () {
pending = null;
dlg.close();
});
}
// Copy buttons: data-copy names the element whose text is copied.
// Selection first, so a phone without the Clipboard API still ends
// up with the text selected for its native gesture.
document.querySelectorAll("button[data-copy]").forEach(function (b) { b.hidden = false; });
document.addEventListener("click", function (ev) {
var b = ev.target.closest && ev.target.closest("button[data-copy]");
if (!b) return;
var el = document.getElementById(b.getAttribute("data-copy"));
if (!el) return;
var range = document.createRange();
range.selectNodeContents(el);
var sel = getSelection();
sel.removeAllRanges();
sel.addRange(range);
var done = function () {
b.classList.add("done");
clearTimeout(b._t);
b._t = setTimeout(function () { b.classList.remove("done"); }, 4000);
};
if (navigator.clipboard) {
navigator.clipboard.writeText(el.textContent).then(done, function () {});
}
});
})();