Render the folder tree on the server

The sidebar tree is part of the page (chain to the current page
expanded, active row marked), so it no longer paints late and the
sessionStorage snapshot is gone; only the rail's scroll position is
kept. Folders are native <details> that lazy-load their children as
HTML via htmx. The tree picker hosts the same server-rendered rows with
radio inputs, so ?tree returns HTML and the JSON listing is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-30 16:51:20 +02:00
co-authored by Claude Opus 5.5
parent 353df29ee6
commit 4365fa2567
11 changed files with 357 additions and 567 deletions
+14 -15
View File
@@ -17,8 +17,7 @@
<script src="{{asset "search-suggest.js"}}" defer></script>
<script src="{{asset "tree-picker.js"}}"></script>
<script src="{{asset "companion.js"}}" defer></script>
{{if not .EditMode}}<script src="{{asset "overlay.js"}}" defer></script>
<script src="{{asset "tree-sidebar.js"}}" defer></script>{{end}}
{{if not .EditMode}}<script src="{{asset "overlay.js"}}" defer></script>{{end}}
{{block "headScripts" .}}{{end}}
</head>
<body>
@@ -37,7 +36,9 @@
<span class="dropdown actions">
<button type="button" class="btn actions-toggle" data-action="actions-menu" aria-haspopup="true" aria-expanded="false" title="Actions">ACTIONS</button>
<div class="dropdown-menu align-right actions-menu">
<div class="actions-special"></div>
<div class="actions-special">
<button type="button" class="btn btn-block" title="Folders" onclick="openOverlay(document.querySelector('.tree-scroll'))">FOLDERS</button>
</div>
{{template "headerActions" . }}
</div>
</span>
@@ -46,20 +47,18 @@
</header>
<div class="shell">
{{if not .EditMode}}<aside class="tree-sidebar col">
<div class="tree-scroll"></div>
<nav class="tree-scroll" aria-label="Folders">{{.Tree}}</nav>
<script>
// Paint the previous page's tree before first render so the rail
// doesn't flash empty; tree-sidebar.js swaps in the live tree.
// Keep the rail's scroll position across page loads (before
// first paint, so it doesn't jump), then reveal the active row.
(function () {
try {
var s = JSON.parse(sessionStorage.getItem("tree-snapshot"));
if (!s) return;
var el = document.currentScript.previousElementSibling;
el.innerHTML = s.html;
var a = el.querySelector('a.tree-name[href="' + CSS.escape(location.pathname.replace(/\/*$/, "/")) + '"]');
if (a) a.parentNode.classList.add("is-active");
el.scrollTop = s.top;
} catch (e) {}
var el = document.currentScript.previousElementSibling;
try { el.scrollTop = +sessionStorage.getItem("tree-scroll") || 0; } catch (e) {}
var active = el.querySelector(".tree-row.is-active");
if (active) active.scrollIntoView({ block: "nearest" });
addEventListener("pagehide", function () {
try { sessionStorage.setItem("tree-scroll", el.scrollTop); } catch (e) {}
});
})();
</script>
</aside>{{end}}