Sidebar nav: full garden tree on every page

- buildNavTree: every non-draft page nested by vault folder; folders link
  to their (real or synthetic) index page and take its title when set
- NavTree component: recursive details/summary tree, no JS — folders on
  the current path render open, current page gets aria-current styling
- layout: sticky left sidebar >=72rem, flows after the footer as a site
  index on narrow screens

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wes
2026-07-15 14:54:04 -04:00
co-authored by Claude Opus 4.8
parent 04c9aa2fb2
commit 13b23ace3d
3 changed files with 202 additions and 16 deletions
+55
View File
@@ -0,0 +1,55 @@
// Sidebar navigation tree: every non-draft page in the garden, nested by
// vault folder. Folders link to their index page (real or synthetic).
import { urlForId, slugForId } from './quartz-compat.mjs';
export function buildNavTree(entries) {
const root = { children: new Map() };
const dirNode = (parts) => {
let node = root;
let acc = '';
for (const seg of parts) {
acc = acc ? `${acc}/${seg}` : seg;
if (!node.children.has(seg)) {
node.children.set(seg, {
name: seg,
url: '/' + slugForId(acc) + '/',
children: new Map(),
});
}
node = node.children.get(seg);
}
return node;
};
for (const e of entries) {
if (e.data.draft || e.id === 'index') continue;
const parts = e.id.split('/');
const base = parts.pop();
const folder = dirNode(parts);
if (base === 'index') {
// folder's own page: use its title for the folder label if set
if (e.data.title) folder.name = e.data.title;
continue;
}
folder.children.set(base, {
name: e.data.title ?? base,
url: urlForId(e.id),
children: null,
});
}
const finalize = (node) => {
const kids = [...node.children.values()].map((k) =>
k.children ? finalize(k) : k,
);
kids.sort((a, b) => {
const af = a.children ? 0 : 1;
const bf = b.children ? 0 : 1;
return af - bf || a.name.localeCompare(b.name, 'en', { sensitivity: 'base' });
});
return { ...node, children: kids };
};
return finalize(root).children;
}