/* Progressive enhancement only: every page works without this file. */ (function () { "use strict"; /* The band's active tab: the longest link that prefixes the path. */ var p = location.pathname, best = null; document.querySelectorAll(".band a").forEach(function (a) { var h = a.getAttribute("href"); if (h !== "/" && (p === h || p.indexOf(h + "/") === 0) && (!best || h.length > best.getAttribute("href").length)) best = a; }); if (best) best.classList.add("on"); /* Off-the-shelf editors, loaded only where a page asks for them: CodeMirror 6 over textarea.code, EasyMDE over textarea.md. */ function load(src, css) { return new Promise(function (ok) { if (css) { var l = document.createElement("link"); l.rel = "stylesheet"; l.href = css; document.head.appendChild(l); } var s = document.createElement("script"); s.src = src; s.onload = ok; document.head.appendChild(s); }); } if (document.querySelector("textarea.code")) load("/static/vendor/editor.js"); var mds = document.querySelectorAll("textarea.md"); if (mds.length) load("/static/vendor/easymde.min.js", "/static/vendor/easymde.min.css").then(function () { var E = window.EasyMDE; function b(name, action, text, title) { return { name: name, action: action, text: text, title: title, className: "mde-btn" }; } var toolbar = [b("bold", E.toggleBold, "B", "Bold"), b("italic", E.toggleItalic, "I", "Italic"), b("heading", E.toggleHeadingSmaller, "H", "Heading"), "|", b("code", E.toggleCodeBlock, "", "Code"), b("quote", E.toggleBlockquote, "“", "Quote"), b("ul", E.toggleUnorderedList, "•", "List"), b("ol", E.toggleOrderedList, "1.", "Numbered list"), "|", b("link", E.drawLink, "link", "Link"), "|", b("preview", E.togglePreview, "preview", "Preview")]; mds.forEach(function (t) { t._mde = new E({ element: t, spellChecker: false, status: false, forceSync: true, autoDownloadFontAwesome: false, toolbar: toolbar }); }); /* An editor shown later (a :target edit form) lays itself out again. */ window.addEventListener("hashchange", function () { mds.forEach(function (t) { if (t._mde) setTimeout(function () { t._mde.codemirror.refresh(); }, 0); }); }); }); /* Theme switch in the header: dark, light, auto (follows the system); kept in the theme cookie. */ var toggle = document.querySelector("[data-theme-toggle]"); if (toggle) { var root = document.documentElement, order = ["light", "dark", "auto"], glyph = { dark: "◐", light: "☀", auto: "◑" }; var show = function () { var p = root.dataset.themePref || "light"; toggle.title = "Theme: " + p + " (click to change)"; toggle.setAttribute("aria-label", toggle.title); toggle.firstElementChild.textContent = glyph[p]; }; show(); toggle.addEventListener("click", function () { var p = order[(order.indexOf(root.dataset.themePref || "light") + 1) % order.length]; document.cookie = "theme=" + p + "; path=/; max-age=31536000; samesite=lax"; root.dataset.themePref = p; root.dataset.theme = p === "auto" ? (window.matchMedia && matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark") : p; show(); }); } /* Diffs: unified or split (remembered in the diff cookie), collapse all. */ var diffs = document.querySelectorAll("table.code.diff"); if (diffs.length) { var pane = function (tr, col, side) { var n = document.createElement("td"), t = document.createElement("td"); n.className = "n"; t.className = "t" + (side === "right" ? " mid" : ""); if (!tr) { n.className += " none"; t.className += " none"; return [n, t]; } var kind = tr.classList.contains("add") ? "add" : tr.classList.contains("del") ? "del" : "ctx"; if (kind !== "ctx") { n.className += " " + kind; t.className += " " + kind; } n.textContent = tr.cells[col].textContent; t.innerHTML = tr.cells[3].innerHTML; // Line comments belong to the projected (right) side only. if (side === "left") t.querySelectorAll(".linecomment").forEach(function (d) { d.remove(); }); return [n, t]; }; var split = function (table) { var s = document.createElement("table"); s.className = "code diff split"; s.innerHTML = ''; var rows = [].slice.call(table.rows), i = 0; var add = function (l, r) { var tr = s.insertRow(); pane(l, 0, "left").concat(pane(r, 1, "right")).forEach(function (c) { tr.appendChild(c); }); }; while (i < rows.length) { var r = rows[i]; if (r.classList.contains("del") || r.classList.contains("add")) { var dels = [], adds = []; while (i < rows.length && rows[i].classList.contains("del")) dels.push(rows[i++]); while (i < rows.length && rows[i].classList.contains("add")) adds.push(rows[i++]); for (var k = 0; k < Math.max(dels.length, adds.length); k++) add(dels[k], adds[k]); continue; } if (r.classList.contains("gap") || r.querySelector("td.thread")) { var tr = s.insertRow(), td = tr.insertCell(); tr.className = r.className; td.colSpan = 4; td.className = r.cells[r.cells.length - 1].className; td.innerHTML = r.cells[r.cells.length - 1].innerHTML; } else { add(r, r); } i++; } return s; }; var cookieMode = (document.cookie.match(/(?:^|; )diff=(split|unified)/) || [])[1] || "unified"; var apply = function (mode) { diffs.forEach(function (t) { if (mode === "split" && !t._split) { t._split = split(t); t.parentNode.insertBefore(t._split, t.nextSibling); } t.hidden = mode === "split"; if (t._split) t._split.hidden = mode !== "split"; }); document.querySelectorAll("[data-diff-mode]").forEach(function (a) { a.classList.toggle("on", a.getAttribute("data-diff-mode") === mode); }); }; document.querySelectorAll("[data-diff-modes], [data-diff-collapse]").forEach(function (e) { e.hidden = false; }); document.querySelectorAll("[data-diff-mode]").forEach(function (a) { a.addEventListener("click", function (ev) { ev.preventDefault(); var mode = a.getAttribute("data-diff-mode"); document.cookie = "diff=" + mode + "; path=/; max-age=31536000; samesite=lax"; apply(mode); }); }); document.querySelectorAll("[data-diff-collapse]").forEach(function (a) { a.addEventListener("click", function (ev) { ev.preventDefault(); var files = document.querySelectorAll("details.file-diff"), anyOpen = [].some.call(files, function (d) { return d.open; }); files.forEach(function (d) { d.open = !anyOpen; }); a.textContent = anyOpen ? "expand all" : "collapse all"; }); }); apply(cookieMode); } /* Settings pages: mark the section in view in the list of sections. */ var navLinks = document.querySelectorAll(".section-nav a[href^='#']"); if (navLinks.length && "IntersectionObserver" in window) { var byId = {}; navLinks.forEach(function (a) { byId[a.getAttribute("href").slice(1)] = a; }); var io = new IntersectionObserver(function (entries) { entries.forEach(function (e) { if (e.isIntersecting && byId[e.target.id]) { navLinks.forEach(function (a) { a.classList.remove("on"); }); byId[e.target.id].classList.add("on"); } }); }, { rootMargin: "0px 0px -70% 0px" }); Object.keys(byId).forEach(function (id) { var el = document.getElementById(id); if (el) io.observe(el); }); } /* Copy buttons. */ document.querySelectorAll("[data-copy]").forEach(function (b) { b.addEventListener("click", function () { navigator.clipboard.writeText(b.getAttribute("data-copy")).then(function () { var t = b.textContent; b.textContent = "copied"; setTimeout(function () { b.textContent = t; }, 1200); }); }); }); /* Tree rows: the last change per entry, fetched after the page paints. */ var tree = document.querySelector("table.tree[data-last]"); if (tree) { var paths = [].map.call(tree.querySelectorAll("tr[data-path]"), function (r) { return r.getAttribute("data-path"); }); if (paths.length) { fetch(tree.getAttribute("data-last") + "?paths=" + encodeURIComponent(paths.join(",")), { credentials: "same-origin" }) .then(function (r) { return r.json(); }) .then(function (rows) { rows.forEach(function (c) { var tr = tree.querySelector('tr[data-path="' + CSS.escape(c.path) + '"]'); if (!tr) return; var a = tr.querySelector("td.last a"), t = tr.querySelector("td.age"); if (a) { a.textContent = c.title; a.href = tree.getAttribute("data-change") + c.hash; } if (t) { t.textContent = c.ago; t.title = c.when; } }); }).catch(function () {}); } } /* Line anchors: #L12 or #L12-L20 highlight a range and clicking a number selects. */ function mark() { var m = location.hash.match(/^#L(\d+)(?:-L(\d+))?$/); document.querySelectorAll("tr.hl").forEach(function (r) { r.classList.remove("hl"); }); if (!m) { showBar(0, 0); return; } var a = +m[1], b = +(m[2] || m[1]); for (var i = Math.min(a, b); i <= Math.max(a, b); i++) { var row = document.getElementById("L" + i); if (row) row.classList.add("hl"); } var first = document.getElementById("L" + Math.min(a, b)); if (first && !mark.done) { first.scrollIntoView({ block: "center" }); mark.done = true; } showBar(Math.min(a, b), Math.max(a, b)); } /* The bar for a selected range: copy its text, or a permalink to it. */ var bar = document.querySelector(".linebar"); function showBar(a, b) { if (!bar) return; bar.hidden = !a; if (!a) return; bar.querySelector("[data-linebar-label]").textContent = a === b ? "Line " + a : "Lines " + a + "–" + b; bar.setAttribute("data-range", a + "-" + b); } function flash(btn) { var t = btn.textContent; btn.textContent = "Copied"; setTimeout(function () { btn.textContent = t; }, 1200); } if (bar) { bar.querySelector("[data-copy-lines]").addEventListener("click", function (ev) { var r = bar.getAttribute("data-range").split("-"), out = []; for (var i = +r[0]; i <= +r[1]; i++) { var row = document.getElementById("L" + i); if (row) out.push(row.querySelector("td.t").textContent); } navigator.clipboard.writeText(out.join("\n") + "\n").then(function () { flash(ev.target); }); }); var pl = bar.querySelector("[data-copy-permalink]"); if (pl) pl.addEventListener("click", function () { navigator.clipboard.writeText(new URL(document.body.getAttribute("data-permalink"), location.href).href + location.hash).then(function () { flash(pl); }); }); } function permalink() { var p = document.body.getAttribute("data-permalink"); return p ? p + location.hash : null; } window.addEventListener("hashchange", mark); mark(); document.querySelectorAll("table.code td.n").forEach(function (td) { td.addEventListener("click", function (e) { var n = td.parentNode.id; if (e.shiftKey && location.hash.match(/^#L(\d+)/)) location.hash = location.hash.replace(/^#(L\d+).*/, "$1-" + n); else location.hash = n; }); }); /* Flow run page: statuses and new log lines while the run is live. */ var liveUrl = document.body.getAttribute("data-flow-live"); if (liveUrl) { var tick = function () { var after = [].map.call(document.querySelectorAll("details[data-job]"), function (d) { return d.getAttribute("data-job") + ":" + d.querySelector("[data-log]").getAttribute("data-seq"); }).join(","); fetch(liveUrl + "?after=" + after, { headers: { accept: "application/json" } }) .then(function (r) { return r.json(); }) .then(function (s) { s.jobs.forEach(function (j) { var cls = "st-" + j.status; var node = document.querySelector("[data-job-node='" + j.id + "']"); if (node) { node.setAttribute("class", "node " + cls); node.querySelector("[data-glyph]").textContent = j.glyph; node.querySelector("[data-meta]").textContent = j.status + " · " + j.duration; } var d = document.querySelector("details[data-job='" + j.id + "']"); if (!d) return; d.className = "panel flow-job " + cls; var g = d.querySelector("summary [data-glyph]"); g.className = "flow-glyph " + cls; g.textContent = j.glyph; d.querySelector("summary [data-meta]").textContent = j.status + (j.exit_code !== null ? " · exit " + j.exit_code : "") + " · " + j.duration; if (j.status === "running" || j.status === "failed") d.open = true; var log = d.querySelector("[data-log]"); var stick = log.scrollTop + log.clientHeight >= log.scrollHeight - 4; j.lines.forEach(function (l) { var ln = document.createElement("span"); ln.className = "ln s-" + l.stream; var t = document.createElement("span"); t.className = "t"; t.textContent = (l.t / 1000).toFixed(1) + "s"; ln.appendChild(t); ln.appendChild(document.createTextNode(l.text)); log.appendChild(ln); log.appendChild(document.createTextNode("\n")); log.setAttribute("data-seq", l.seq); }); if (stick && j.lines.length) log.scrollTop = log.scrollHeight; }); var st = document.querySelector("[data-run-status]"); if (st) st.textContent = s.status; if (s.live) setTimeout(tick, 1500); else setTimeout(function () { location.reload(); }, 800); }) .catch(function () { setTimeout(tick, 5000); }); }; setTimeout(tick, 1500); } /* Keyboard: / focuses search; g c / g p / g i jump; ? shows the sheet. */ var pending = null; document.addEventListener("keydown", function (e) { if (e.target && /^(INPUT|TEXTAREA|SELECT)$/.test(e.target.tagName)) return; if (e.key === "/") { var s = document.querySelector("input[name=q]"); if (s) { e.preventDefault(); s.focus(); } return; } if (e.key === "y" && !e.ctrlKey && !e.metaKey && !e.altKey && permalink()) { history.replaceState(null, "", permalink()); pending = null; return; } if (e.key === "?") { var k = document.getElementById("keys"); if (k) k.hidden = !k.hidden; return; } var repo = document.body.getAttribute("data-repo"); if (pending === "g" && repo) { var to = { c: "", p: "/proposals", i: "/issues", h: "/changes/" + (document.body.getAttribute("data-ref") || "main"), b: "/bookmarks", t: "/trust" }[e.key]; if (to !== undefined) location.href = repo + to; pending = null; return; } pending = e.key === "g" ? "g" : null; }); // Live conflict resolution (hub spec §6.10): who else is resolving this // conflict, their choices region by region (the last one wins), and the // resolution when someone commits it. var live = document.querySelector("[data-resolve-live]"); if (live && window.EventSource && window.fetch) { var url = live.getAttribute("data-resolve-live"), csrf = live.getAttribute("data-csrf"); var client = Math.random().toString(36).slice(2) + Date.now().toString(36); var present = document.querySelector("[data-live-present]"), landed = document.querySelector("[data-live-landed]"); var applying = false; var fieldsOf = function (region) { return region === "keep" ? { radios: live.querySelectorAll('input[name="keep"]'), text: null } : { radios: live.querySelectorAll('input[name="r' + region + '"]'), text: live.querySelector('textarea[name="t' + region + '"]') }; }; var apply = function (region, c) { var f = fieldsOf(region); applying = true; f.radios.forEach(function (r) { r.checked = r.value === c.choice; }); if (f.text && c.choice === "text") f.text.value = c.text; applying = false; var sec = f.radios.length ? f.radios[0].closest("section") : null; if (sec) sec.setAttribute("title", "chosen by " + c.by); }; var send = function (region, choice, text) { fetch(url, { method: "POST", headers: { "content-type": "application/json" }, credentials: "same-origin", body: JSON.stringify({ _csrf: csrf, client: client, region: region, choice: choice, text: text || "" }) }); }; var timers = {}; live.addEventListener("change", function (ev) { var t = ev.target; if (applying || !t.name || t.type !== "radio") return; var region = t.name === "keep" ? "keep" : t.name.slice(1); var ta = live.querySelector('textarea[name="t' + region + '"]'); send(region, t.value, t.value === "text" && ta ? ta.value : ""); }); live.addEventListener("input", function (ev) { var t = ev.target; if (applying || t.tagName !== "TEXTAREA") return; var region = t.name.slice(1); live.querySelectorAll('input[name="r' + region + '"]').forEach(function (r) { r.checked = r.value === "text"; }); clearTimeout(timers[region]); timers[region] = setTimeout(function () { send(region, "text", t.value); }, 400); }); var es = new EventSource(url + "?client=" + encodeURIComponent(client)); var showPresent = function (names) { present.hidden = names.length < 2; present.textContent = "Resolving together: " + names.join(", ") + ". Choices are shared as you make them; the last choice for a conflict wins."; }; es.addEventListener("snapshot", function (e) { var d = JSON.parse(e.data); showPresent(d.present); Object.keys(d.choices).forEach(function (r) { apply(r, d.choices[r]); }); if (d.keep) apply("keep", d.keep); }); es.addEventListener("presence", function (e) { showPresent(JSON.parse(e.data).present); }); es.addEventListener("choice", function (e) { var d = JSON.parse(e.data); if (d.client !== client) apply(d.region, d.choice); }); es.addEventListener("landed", function (e) { var d = JSON.parse(e.data); es.close(); landed.hidden = false; landed.textContent = ""; landed.appendChild(document.createTextNode(d.by + " committed the resolution: ")); var a = document.createElement("a"); a.href = d.url; a.textContent = "see the change"; landed.appendChild(a); live.querySelectorAll("input, textarea, button").forEach(function (x) { x.disabled = true; }); }); } // Swimlanes of a proposal's bookmark (hub spec §6.7): drag a change onto // another to plan a squash or a rebase. The hub only writes the plan; // the author's client carries it out and signs it. var plan = document.querySelector("[data-plan]"); if (plan && window.fetch) { var panel = document.querySelector("[data-plan-panel]"), from = null, moved = false; plan.querySelectorAll("[data-change]").forEach(function (a) { a.style.cursor = "grab"; a.addEventListener("pointerdown", function (ev) { from = a.getAttribute("data-change"); moved = false; ev.preventDefault(); }); a.addEventListener("pointermove", function () { if (from) moved = true; }); a.addEventListener("click", function (ev) { if (moved) ev.preventDefault(); }); }); document.addEventListener("pointerup", function (ev) { var start = from; from = null; if (!start) return; var el = document.elementFromPoint(ev.clientX, ev.clientY), onto = el && el.closest ? el.closest("[data-change]") : null; if (!onto || onto.getAttribute("data-change") === start) return; var body = new URLSearchParams({ _csrf: plan.getAttribute("data-csrf"), change: start, target: onto.getAttribute("data-change") }); fetch(plan.getAttribute("data-plan"), { method: "POST", credentials: "same-origin", body: body }) .then(function (r) { return r.json(); }) .then(function (d) { panel.hidden = false; panel.textContent = ""; if (d.error) { panel.textContent = d.error; return; } d.options.forEach(function (o) { var p = document.createElement("p"); p.appendChild(document.createTextNode(o.summary + " ")); var a = document.createElement("a"); a.href = o.vscode; a.textContent = "Apply in VS Code"; p.appendChild(a); var code = document.createElement("pre"); code.className = "snippet"; code.textContent = o.command; panel.appendChild(p); panel.appendChild(code); }); }); }); } /* Sidebar menus (oxygenhub#82): type to filter a menu's items; a click outside an open menu closes it. */ document.querySelectorAll("[data-menu-filter]").forEach(function (input) { var items = input.closest(".menu-pop").querySelectorAll(".menu-item"); input.addEventListener("input", function () { var q = input.value.trim().toLowerCase(); items.forEach(function (it) { it.hidden = q !== "" && it.textContent.toLowerCase().indexOf(q) < 0; }); }); }); document.querySelectorAll("details.menu").forEach(function (d) { d.addEventListener("toggle", function () { var f = d.querySelector("[data-menu-filter]"); if (d.open && f) f.focus(); }); }); document.addEventListener("click", function (ev) { document.querySelectorAll("details.menu[open]").forEach(function (d) { if (!d.contains(ev.target)) d.open = false; }); }); /* ··· menus: copy a link to a part of the page, or some text. */ function copied(btn, text) { if (navigator.clipboard) navigator.clipboard.writeText(text).then(function () { var was = btn.textContent; btn.textContent = "Copied"; setTimeout(function () { btn.textContent = was; }, 1200); }); var d = btn.closest("details"); if (d) d.open = false; } document.querySelectorAll("[data-copy-link]").forEach(function (b) { b.addEventListener("click", function () { copied(b, location.origin + location.pathname + location.search + b.getAttribute("data-copy-link")); }); }); document.querySelectorAll("[data-copy-text]").forEach(function (b) { b.addEventListener("click", function () { copied(b, b.getAttribute("data-copy-text")); }); }); document.querySelectorAll('.menu-actions a[href^="#"]').forEach(function (a) { a.addEventListener("click", function () { var d = a.closest("details"); if (d) d.open = false; }); }); })();