feat/dossiers-ngi: dated tip/at bar keeps caller path on edition switch

This commit is contained in:
Hernâni Marques 2026-10-10 00:49:11 +02:00
parent 197e0ab19a
commit 37ac6a2171
No known key found for this signature in database
10 changed files with 303 additions and 7 deletions

View file

@ -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/* {

View file

@ -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;
}

View file

@ -1,5 +1,6 @@
<link rel="stylesheet" href="/_cascade/bar.css">
<nav class="cascade-bar" aria-label="cascade builds" id="cascade-bar">
<div class="cascade-tree" id="cascade-tree" role="tree">Loading builds…</div>
<div class="cascade-hint" id="cascade-hint" role="status" hidden></div>
</nav>
<script src="/_cascade/bar.js" defer></script>

View file

@ -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, "&amp;")
@ -276,8 +369,8 @@
var parent = row.parent || "";
var isDefault = branch === defaultBranch;
html.push(
'<a class="cascade-tree-row" role="treeitem" href="/_cascade/use/' +
esc(edition) +
'<a class="cascade-tree-row" role="treeitem" href="' +
esc(useHref(edition)) +
'" data-edition="' +
esc(edition) +
'" data-branch="' +
@ -333,7 +426,9 @@
.catch(function () {
if (!soft && mount && !mount.querySelector("a[data-edition]")) {
mount.innerHTML =
'<a class="cascade-tree-row" href="/_cascade/use/flask" data-edition="flask" data-branch="flask"><span class="cascade-name"><code>flask</code></span></a>' +
'<a class="cascade-tree-row" href="' +
esc(useHref("flask")) +
'" data-edition="flask" data-branch="flask"><span class="cascade-name"><code>flask</code></span></a>' +
'<span class="tip"> (branch-status unavailable)</span>';
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);

View file

@ -9,6 +9,7 @@
<body>
<nav class="cascade-bar" aria-label="cascade builds" id="cascade-bar">
<div class="cascade-tree" id="cascade-tree" role="tree">Loading builds…</div>
<div class="cascade-hint" id="cascade-hint" role="status" hidden></div>
</nav>
<p>Stuck on an edition without a bar? Pick a build above, or <a href="/_cascade/use/flask">reset to flask</a>.</p>
<script src="/_cascade/bar.js" defer></script>

View file

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>path not in this branch</title>
<link rel="stylesheet" href="/_cascade/bar.css">
</head>
<body class="cascade-missing">
<nav class="cascade-bar" aria-label="cascade builds" id="cascade-bar">
<div class="cascade-tree" id="cascade-tree" role="tree">Loading builds…</div>
<div class="cascade-hint" id="cascade-hint" role="status" hidden></div>
</nav>
<p class="cascade-missing-msg">This address is not in the current branch. Choose another branch above.</p>
<script src="/_cascade/bar.js" defer></script>
</body>
</html>

View file

@ -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;
}

View file

@ -1,5 +1,6 @@
<link rel="stylesheet" href="/_cascade/bar.css">
<nav class="cascade-bar" aria-label="cascade builds" id="cascade-bar">
<div class="cascade-tree" id="cascade-tree" role="tree">Loading builds…</div>
<div class="cascade-hint" id="cascade-hint" role="status" hidden></div>
</nav>
<script src="/_cascade/bar.js" defer></script>

View file

@ -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, "&amp;")
@ -276,8 +369,8 @@
var parent = row.parent || "";
var isDefault = branch === defaultBranch;
html.push(
'<a class="cascade-tree-row" role="treeitem" href="/_cascade/use/' +
esc(edition) +
'<a class="cascade-tree-row" role="treeitem" href="' +
esc(useHref(edition)) +
'" data-edition="' +
esc(edition) +
'" data-branch="' +
@ -333,7 +426,9 @@
.catch(function () {
if (!soft && mount && !mount.querySelector("a[data-edition]")) {
mount.innerHTML =
'<a class="cascade-tree-row" href="/_cascade/use/flask" data-edition="flask" data-branch="flask"><span class="cascade-name"><code>flask</code></span></a>' +
'<a class="cascade-tree-row" href="' +
esc(useHref("flask")) +
'" data-edition="flask" data-branch="flask"><span class="cascade-name"><code>flask</code></span></a>' +
'<span class="tip"> (branch-status unavailable)</span>';
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);

View file

@ -9,6 +9,7 @@
<body>
<nav class="cascade-bar" aria-label="cascade builds" id="cascade-bar">
<div class="cascade-tree" id="cascade-tree" role="tree">Loading builds…</div>
<div class="cascade-hint" id="cascade-hint" role="status" hidden></div>
</nav>
<p>Stuck on an edition without a bar? Pick a build above, or <a href="/_cascade/use/flask">reset to flask</a>.</p>
<script src="/_cascade/bar.js" defer></script>