From 37ac6a21716c650177e1cddabba6bec3e3ca8623 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hern=C3=A2ni=20Marques?= Date: Sat, 10 Oct 2026 00:49:11 +0200 Subject: [PATCH] feat/dossiers-ngi: dated tip/at bar keeps caller path on edition switch --- .../dossiers-ngi/caddy-dossiers-ngi.snippet | 5 +- .../_cascade/bar.css | 21 +++ .../_cascade/bar.html | 1 + .../_cascade/bar.js | 121 +++++++++++++++++- .../_cascade/index.html | 1 + .../_cascade/missing.html | 17 +++ .../cascade/_cascade/bar.css | 21 +++ .../cascade/_cascade/bar.html | 1 + .../cascade/_cascade/bar.js | 121 +++++++++++++++++- .../cascade/_cascade/index.html | 1 + 10 files changed, 303 insertions(+), 7 deletions(-) create mode 100644 sites/dossiers.ngi.hacktivism.ch/_cascade/missing.html diff --git a/scripts/dossiers-ngi/caddy-dossiers-ngi.snippet b/scripts/dossiers-ngi/caddy-dossiers-ngi.snippet index c746406..7e02a41 100644 --- a/scripts/dossiers-ngi/caddy-dossiers-ngi.snippet +++ b/scripts/dossiers-ngi/caddy-dossiers-ngi.snippet @@ -19,7 +19,10 @@ handle @use { header Set-Cookie "tdpd_edition={re.use.1}; Path=/; Max-Age=31536000; SameSite=Lax" header Cache-Control "private, no-store" - redir * / 302 + # Keep the caller's path (?next=). Never bounce to /. No next → bar page. + @next query_regexp next ^(/|/[^/].*)$ + redir @next {query.next} 302 + redir * /_cascade/ 302 } handle /_cascade/* { diff --git a/sites/dossiers.ngi.hacktivism.ch/_cascade/bar.css b/sites/dossiers.ngi.hacktivism.ch/_cascade/bar.css index d0a638e..feefe10 100644 --- a/sites/dossiers.ngi.hacktivism.ch/_cascade/bar.css +++ b/sites/dossiers.ngi.hacktivism.ch/_cascade/bar.css @@ -118,3 +118,24 @@ a.cascade-tree-row.is-active:hover { color: #8a96a3; font-weight: 600; } +.cascade-hint { + width: 100%; + max-width: 45rem; + margin: 0.15rem auto 0; + box-sizing: border-box; + padding: 0.2rem 0.35rem; + color: #f0c674; + text-align: center; +} +.cascade-hint[hidden] { + display: none; +} +.cascade-missing-msg { + max-width: 45rem; + margin: 1.5rem auto; + padding: 0 0.75rem; + color: #c8d0d8; + font-family: ui-sans-serif, system-ui, sans-serif; + font-size: 1rem; + line-height: 1.4; +} diff --git a/sites/dossiers.ngi.hacktivism.ch/_cascade/bar.html b/sites/dossiers.ngi.hacktivism.ch/_cascade/bar.html index ed51952..d81564d 100644 --- a/sites/dossiers.ngi.hacktivism.ch/_cascade/bar.html +++ b/sites/dossiers.ngi.hacktivism.ch/_cascade/bar.html @@ -1,5 +1,6 @@ diff --git a/sites/dossiers.ngi.hacktivism.ch/_cascade/bar.js b/sites/dossiers.ngi.hacktivism.ch/_cascade/bar.js index 8fe4765..8db6e4a 100644 --- a/sites/dossiers.ngi.hacktivism.ch/_cascade/bar.js +++ b/sites/dossiers.ngi.hacktivism.ch/_cascade/bar.js @@ -25,6 +25,99 @@ } } + function setEditionCookie(edition) { + document.cookie = + "tdpd_edition=" + + encodeURIComponent(edition) + + "; Path=/; Max-Age=31536000; SameSite=Lax"; + } + + function keepPath() { + return location.pathname + location.search; + } + + function useHref(edition) { + var path = keepPath(); + var href = "/_cascade/use/" + encodeURIComponent(edition); + if (!path || path.indexOf("/_cascade/use/") === 0) return href; + return href + "?next=" + encodeURIComponent(path); + } + + function hintEl() { + return document.getElementById("cascade-hint"); + } + + function showHint(msg) { + var el = hintEl(); + if (!el) { + var bar = document.getElementById("cascade-bar"); + if (!bar) return; + el = document.createElement("div"); + el.id = "cascade-hint"; + el.className = "cascade-hint"; + el.setAttribute("role", "status"); + bar.appendChild(el); + } + el.hidden = false; + el.textContent = msg; + pinBar(); + } + + function clearHint() { + var el = hintEl(); + if (!el) return; + el.hidden = true; + el.textContent = ""; + pinBar(); + } + + function responseKeepsBar(res, text) { + if (res && res.ok) return true; + var body = text || ""; + return ( + body.indexOf("cascade-bar") !== -1 || + body.indexOf("cascade-missing") !== -1 + ); + } + + function switchEdition(edition, branchLabel) { + var prev = cookieEdition(); + var here = keepPath() || "/"; + var label = branchLabel || edition; + if (!edition) return; + if (edition === prev) { + clearHint(); + return; + } + setEditionCookie(edition); + fetch(here, { cache: "no-store", credentials: "same-origin" }) + .then(function (res) { + return res.text().then(function (text) { + return { res: res, text: text }; + }); + }) + .then(function (got) { + if (responseKeepsBar(got.res, got.text)) { + location.replace(here + location.hash); + return; + } + setEditionCookie(prev); + showHint( + "This path is not in " + + label + + ". Choose another branch above." + ); + }) + .catch(function () { + setEditionCookie(prev); + showHint( + "This path is not in " + + label + + ". Choose another branch above." + ); + }); + } + function esc(s) { return String(s) .replace(/&/g, "&") @@ -276,8 +369,8 @@ var parent = row.parent || ""; var isDefault = branch === defaultBranch; html.push( - 'flask' + + 'flask' + ' (branch-status unavailable)'; markActive(mount, cookieEdition()); pinBar(); @@ -341,6 +436,26 @@ }); } + document.addEventListener("click", function (ev) { + var a = ev.target.closest + ? ev.target.closest("a.cascade-tree-row[data-edition]") + : null; + if (!a || ev.defaultPrevented) return; + if (ev.button !== 0 || ev.metaKey || ev.ctrlKey || ev.shiftKey || ev.altKey) + return; + ev.preventDefault(); + switchEdition( + a.getAttribute("data-edition"), + a.getAttribute("data-branch") || a.getAttribute("data-edition") + ); + }); + + if (document.body && document.body.classList.contains("cascade-missing")) { + showHint( + "This path is not in the current branch. Choose another branch above." + ); + } + loadStatus(false); setInterval(function () { loadStatus(true); diff --git a/sites/dossiers.ngi.hacktivism.ch/_cascade/index.html b/sites/dossiers.ngi.hacktivism.ch/_cascade/index.html index 036ead3..db0bc96 100644 --- a/sites/dossiers.ngi.hacktivism.ch/_cascade/index.html +++ b/sites/dossiers.ngi.hacktivism.ch/_cascade/index.html @@ -9,6 +9,7 @@

Stuck on an edition without a bar? Pick a build above, or reset to flask.

diff --git a/sites/dossiers.ngi.hacktivism.ch/_cascade/missing.html b/sites/dossiers.ngi.hacktivism.ch/_cascade/missing.html new file mode 100644 index 0000000..449d470 --- /dev/null +++ b/sites/dossiers.ngi.hacktivism.ch/_cascade/missing.html @@ -0,0 +1,17 @@ + + + + + +path not in this branch + + + + +

This address is not in the current branch. Choose another branch above.

+ + + diff --git a/sites/dossiers.ngi.hacktivism.ch/cascade/_cascade/bar.css b/sites/dossiers.ngi.hacktivism.ch/cascade/_cascade/bar.css index d0a638e..feefe10 100644 --- a/sites/dossiers.ngi.hacktivism.ch/cascade/_cascade/bar.css +++ b/sites/dossiers.ngi.hacktivism.ch/cascade/_cascade/bar.css @@ -118,3 +118,24 @@ a.cascade-tree-row.is-active:hover { color: #8a96a3; font-weight: 600; } +.cascade-hint { + width: 100%; + max-width: 45rem; + margin: 0.15rem auto 0; + box-sizing: border-box; + padding: 0.2rem 0.35rem; + color: #f0c674; + text-align: center; +} +.cascade-hint[hidden] { + display: none; +} +.cascade-missing-msg { + max-width: 45rem; + margin: 1.5rem auto; + padding: 0 0.75rem; + color: #c8d0d8; + font-family: ui-sans-serif, system-ui, sans-serif; + font-size: 1rem; + line-height: 1.4; +} diff --git a/sites/dossiers.ngi.hacktivism.ch/cascade/_cascade/bar.html b/sites/dossiers.ngi.hacktivism.ch/cascade/_cascade/bar.html index ed51952..d81564d 100644 --- a/sites/dossiers.ngi.hacktivism.ch/cascade/_cascade/bar.html +++ b/sites/dossiers.ngi.hacktivism.ch/cascade/_cascade/bar.html @@ -1,5 +1,6 @@ diff --git a/sites/dossiers.ngi.hacktivism.ch/cascade/_cascade/bar.js b/sites/dossiers.ngi.hacktivism.ch/cascade/_cascade/bar.js index 8fe4765..8db6e4a 100644 --- a/sites/dossiers.ngi.hacktivism.ch/cascade/_cascade/bar.js +++ b/sites/dossiers.ngi.hacktivism.ch/cascade/_cascade/bar.js @@ -25,6 +25,99 @@ } } + function setEditionCookie(edition) { + document.cookie = + "tdpd_edition=" + + encodeURIComponent(edition) + + "; Path=/; Max-Age=31536000; SameSite=Lax"; + } + + function keepPath() { + return location.pathname + location.search; + } + + function useHref(edition) { + var path = keepPath(); + var href = "/_cascade/use/" + encodeURIComponent(edition); + if (!path || path.indexOf("/_cascade/use/") === 0) return href; + return href + "?next=" + encodeURIComponent(path); + } + + function hintEl() { + return document.getElementById("cascade-hint"); + } + + function showHint(msg) { + var el = hintEl(); + if (!el) { + var bar = document.getElementById("cascade-bar"); + if (!bar) return; + el = document.createElement("div"); + el.id = "cascade-hint"; + el.className = "cascade-hint"; + el.setAttribute("role", "status"); + bar.appendChild(el); + } + el.hidden = false; + el.textContent = msg; + pinBar(); + } + + function clearHint() { + var el = hintEl(); + if (!el) return; + el.hidden = true; + el.textContent = ""; + pinBar(); + } + + function responseKeepsBar(res, text) { + if (res && res.ok) return true; + var body = text || ""; + return ( + body.indexOf("cascade-bar") !== -1 || + body.indexOf("cascade-missing") !== -1 + ); + } + + function switchEdition(edition, branchLabel) { + var prev = cookieEdition(); + var here = keepPath() || "/"; + var label = branchLabel || edition; + if (!edition) return; + if (edition === prev) { + clearHint(); + return; + } + setEditionCookie(edition); + fetch(here, { cache: "no-store", credentials: "same-origin" }) + .then(function (res) { + return res.text().then(function (text) { + return { res: res, text: text }; + }); + }) + .then(function (got) { + if (responseKeepsBar(got.res, got.text)) { + location.replace(here + location.hash); + return; + } + setEditionCookie(prev); + showHint( + "This path is not in " + + label + + ". Choose another branch above." + ); + }) + .catch(function () { + setEditionCookie(prev); + showHint( + "This path is not in " + + label + + ". Choose another branch above." + ); + }); + } + function esc(s) { return String(s) .replace(/&/g, "&") @@ -276,8 +369,8 @@ var parent = row.parent || ""; var isDefault = branch === defaultBranch; html.push( - 'flask' + + 'flask' + ' (branch-status unavailable)'; markActive(mount, cookieEdition()); pinBar(); @@ -341,6 +436,26 @@ }); } + document.addEventListener("click", function (ev) { + var a = ev.target.closest + ? ev.target.closest("a.cascade-tree-row[data-edition]") + : null; + if (!a || ev.defaultPrevented) return; + if (ev.button !== 0 || ev.metaKey || ev.ctrlKey || ev.shiftKey || ev.altKey) + return; + ev.preventDefault(); + switchEdition( + a.getAttribute("data-edition"), + a.getAttribute("data-branch") || a.getAttribute("data-edition") + ); + }); + + if (document.body && document.body.classList.contains("cascade-missing")) { + showHint( + "This path is not in the current branch. Choose another branch above." + ); + } + loadStatus(false); setInterval(function () { loadStatus(true); diff --git a/sites/dossiers.ngi.hacktivism.ch/cascade/_cascade/index.html b/sites/dossiers.ngi.hacktivism.ch/cascade/_cascade/index.html index 036ead3..db0bc96 100644 --- a/sites/dossiers.ngi.hacktivism.ch/cascade/_cascade/index.html +++ b/sites/dossiers.ngi.hacktivism.ch/cascade/_cascade/index.html @@ -9,6 +9,7 @@

Stuck on an edition without a bar? Pick a build above, or reset to flask.