diff --git a/timeline/index.html b/timeline/index.html index 3c3fb8d..9b832f0 100644 --- a/timeline/index.html +++ b/timeline/index.html @@ -987,11 +987,28 @@

How to read it

// began while a role ran still did. The selection is state, not a place: remembered in // the browser like the card's width, never written into the address — and a link to a // row of a kind that is off turns that kind back on, because a link must land. + // + // A link can still set it. ?kinds= names the kinds to show, by name and in any case, + // comma-separated: those are on and the rest off, and the page reads it the way it reads + // ?lang= — once, on arrival, then strips it from the address and remembers the selection + // as if the menu had been used, so the link hands the visitor a view and nothing they + // copy afterwards carries it. A name the model does not know is dropped, and a list naming + // no kind at all is ignored whole rather than emptying the ledger. var KINDS = data.entities.filter(function(e){ return e.type === "experience-kind"; }) .sort(function(a, b){ return a.name.localeCompare(b.name, "en"); }); var KIND_KEY = "timeline-kinds", on = {}; KINDS.forEach(function(k){ on[k.name] = true; }); try { var saved = JSON.parse(localStorage.getItem(KIND_KEY) || "null"); if (saved) KINDS.forEach(function(k){ if (k.name in saved) on[k.name] = !!saved[k.name]; }); } catch (e) {} + var linked = /[?&]kinds=([^&#]*)(&|$)/.exec(location.search), linkedKinds = null; + if (linked) { + var named = decodeURIComponent(linked[1]).toLowerCase().split(","); + var known = KINDS.filter(function(k){ return named.indexOf(k.name.toLowerCase()) >= 0; }); + if (known.length) { linkedKinds = {}; KINDS.forEach(function(k){ linkedKinds[k.name] = known.indexOf(k) >= 0; }); } + try { + var q = location.search.replace(/([?&])kinds=[^&#]*(&|$)/, "$1").replace(/[?&]$/, ""); + history.replaceState(null, "", location.pathname + q + location.hash); + } catch (e) {} + } var kinds = document.getElementById("kinds"), kindsLabel = document.getElementById("kindslabel"), kindsMenu = document.getElementById("kindsmenu"); var none = h("li", null, "none"); none.hidden = true; ol.appendChild(none); var boxes = []; @@ -1087,7 +1104,7 @@

How to read it

window.addEventListener("hashchange", arrive); new MutationObserver(render).observe(document.documentElement, { attributes: true, attributeFilter: ["lang"] }); - applyKinds(); + if (linkedKinds) { on = linkedKinds; applyKinds(true); } else applyKinds(); arrive(); } diff --git a/timeline/og.sha b/timeline/og.sha index b6aa91b..e8f01e1 100644 --- a/timeline/og.sha +++ b/timeline/og.sha @@ -1 +1 @@ -4e61cc3847555fee53e2bee1397ed745291be6cddfabbc66b882562466fbc59a +c82190396ef09ea5f16cd25f466022be5b674e4828d9e1e9172acee71c1c1f10 diff --git a/verify/check.mjs b/verify/check.mjs index f0fbcbf..f715552 100644 --- a/verify/check.mjs +++ b/verify/check.mjs @@ -311,7 +311,27 @@ const CHECKS = { await page.goto(BASE + spec.path + "#" + target); const opened = await page.evaluate((id) => document.getElementById(id).open, target); if (!opened) return `arriving at #${target} did not open that row`; - // Leave the page as it was found — at rest, no row open — for whatever check runs next. + // Arriving with ?kinds= sets the selection from the address: the kinds named are on and + // the rest off, the parameter leaves the address the way ?lang= does, and the selection + // is remembered as if the menu had been used. A name the model does not know is ignored, + // and a list naming no kind at all is ignored whole. + await page.goto(BASE + spec.path + "?kinds=" + kindOff.toLowerCase() + ",nosuchkind"); + const linked = await page.evaluate((kind) => ({ + hidden: [...document.querySelectorAll("#ledger li.k-" + kind.toLowerCase())].filter(li => li.hidden).length, + others: [...document.querySelectorAll("#ledger li:has(details)")].filter(li => !li.classList.contains("k-" + kind.toLowerCase()) && !li.hidden).length, + label: document.getElementById("kindslabel").textContent, search: location.search, + stored: (() => { try { return JSON.parse(localStorage.getItem("timeline-kinds")); } catch (e) { return null; } })() }), kindOff); + if (linked.hidden) return `?kinds=${kindOff} hid ${linked.hidden} rows of that kind`; + if (linked.others) return `?kinds=${kindOff} left ${linked.others} rows of other kinds showing`; + if (linked.label !== kindOff) return `with ?kinds=${kindOff} the filter reads ${JSON.stringify(linked.label)}`; + if (linked.search) return `the address still carries ${linked.search}`; + if (!linked.stored || linked.stored[kindOff] !== true || Object.keys(linked.stored).some(k => k !== kindOff && linked.stored[k])) return `?kinds=${kindOff} was not remembered: ${JSON.stringify(linked.stored)}`; + await page.goto(BASE + spec.path + "?kinds=nosuchkind"); + const ignored = await page.evaluate(() => ({ hidden: [...document.querySelectorAll("#ledger li:has(details)")].filter(li => li.hidden).length, label: document.getElementById("kindslabel").textContent })); + if (ignored.label !== kindOff) return `?kinds naming no kind changed the selection to ${JSON.stringify(ignored.label)}`; + // Leave the page as it was found — at rest, every kind on, no row open — for whatever + // check runs next. + await page.evaluate(() => { try { localStorage.removeItem("timeline-kinds"); } catch (e) {} }); await page.goto(BASE + spec.path); return null; },