---
import type { CollectionEntry } from 'astro:content';
import { getAllPosts, postUrl } from '../lib/posts';
+import { getActiveProject, getVisibleProjectNavTrees, type ProjectNavNode } from '../lib/projects';
interface Props {
currentUrl: string;
return !(isCurrent || isAncestor || isSibling || isChild);
}
+// ── Projects section ──────────────────────────────────────────────────────────
+const activeProject = await getActiveProject(currentUrl);
+const onProjectsSection = currentUrl === '/projects/' || activeProject !== null;
+// When on a SNAPSHOT page, render that version's tree; otherwise all visible trees.
+const projectTrees = onProjectsSection && activeProject && !activeProject.isCurrent
+ ? [activeProject]
+ : await getVisibleProjectNavTrees();
+
+// Nav helpers for project trees
+function inProjectSubtree(node: ProjectNavNode, url: string): boolean {
+ if (node.url === url) return true;
+ return node.children.some(c => inProjectSubtree(c, url));
+}
+
+// Find the parent node of a URL within a tree
+function findProjectParent(node: ProjectNavNode, url: string): ProjectNavNode | null {
+ for (const c of node.children) {
+ if (c.url === url) return node;
+ const found = findProjectParent(c, url);
+ if (found) return found;
+ }
+ return null;
+}
+
+// Find a node by URL
+function findProjectNode(node: ProjectNavNode, url: string): ProjectNavNode | null {
+ if (node.url === url) return node;
+ for (const c of node.children) {
+ const found = findProjectNode(c, url);
+ if (found) return found;
+ }
+ return null;
+}
+
+// Determine the "active" project URL — either from visible page or SNAPSHOT page
+const activeProjectUrl = activeProject?.navRoot.url ?? null;
+// The active project's current node and its parent (for off-logic)
+const _activeProjectRoot = activeProject?.navRoot ?? null;
+const activeProjectCurrentNode = _activeProjectRoot ? findProjectNode(_activeProjectRoot, currentUrl) : null;
+const activeProjectCurrentParent = _activeProjectRoot ? findProjectParent(_activeProjectRoot, currentUrl) : null;
+const activeProjectCurrentIsNode = activeProjectCurrentNode?.isNode ?? false;
+
+// off-logic mirroring tree.html for a node within a project tree
+function projectNodeOff(node: ProjectNavNode, parentUrl: string): boolean {
+ const isCurrent = node.url === currentUrl;
+ const isAncestor = !isCurrent && inProjectSubtree(node, currentUrl);
+ // Sibling: same parent as current page, and current is NOT a section node
+ const isSibling = parentUrl === (activeProjectCurrentParent?.url ?? '') && !activeProjectCurrentIsNode;
+ // Child: this node's parent is the current page
+ const isChild = parentUrl === currentUrl;
+ return !(isCurrent || isAncestor || isSibling || isChild);
+}
+
// ── Blog URL classification ────────────────────────────────────────────────────
const mainSections = [
{ name: 'blog', url: '/blog/', title: 'Blog' },
// Active section for #menu highlight
const activeSection = mainSections.find(s => {
- if (s.name === 'blog') return blogSelected;
- if (s.name === 'about') return onAboutSection;
+ if (s.name === 'blog') return blogSelected;
+ if (s.name === 'about') return onAboutSection;
+ if (s.name === 'projects') return onProjectsSection;
return currentUrl.startsWith(s.url);
})?.name ?? '';
-// Top-level section off-logic (Hugo blog.html vs default.html + tree.html):
-// Blog section hidden when viewing any About-section page (and later: Projects pages).
-// About section hidden only when on Blog pages.
-const blogSectionOff = onAboutSection;
-const aboutOff = blogSelected;
+// Top-level section off-logic
+const blogSectionOff = onAboutSection || onProjectsSection;
+const projectsSectionOff = blogSelected || onAboutSection;
+const aboutOff = blogSelected || onProjectsSection;
// Active year / taxonomy term helpers
function isActiveYear(year: string) {
</ul>
</li>
+ {/* ── Projects subtree ─────────────────────────────────────────────────── */}
+ <li class={`s sub${projectsSectionOff ? ' off' : ''}`}>
+ <a href="/projects/" class={onProjectsSection ? 's selected' : 's'}>
+ {onProjectsSection ? <strong>Projects</strong> : 'Projects'}
+ </a>
+ <ul class={`s${onProjectsSection ? ' active' : ''}`}>
+ {projectTrees.map(info => {
+ const root = info.navRoot;
+ const rootOff = projectNodeOff(root, '/projects/');
+ const rootInPath = inProjectSubtree(root, currentUrl);
+ return (
+ <li class={`s sub${rootOff ? ' off' : ''}`}>
+ <a href={root.url} class={rootInPath ? 's selected' : 's'}>
+ {rootInPath ? <strong>{root.title}</strong> : root.title}
+ </a>
+ <ul class={`s${rootInPath ? ' active' : ''}`}>
+ {root.children.map(child => {
+ const childOff = projectNodeOff(child, root.url);
+ const childInPath = inProjectSubtree(child, currentUrl);
+ const childIsNode = child.isNode;
+ return (
+ <li class={`s${childIsNode ? ' sub' : ''}${childOff ? ' off' : ''}`}>
+ <a href={child.url} class={childInPath ? 's selected' : 's'}>
+ {childInPath ? <strong>{child.title}</strong> : child.title}
+ </a>
+ {childIsNode && child.children.length > 0 && (
+ <ul class={`s${childInPath ? ' active' : ''}`}>
+ {child.children.map(grand => {
+ const grandOff = projectNodeOff(grand, child.url);
+ const grandInPath = inProjectSubtree(grand, currentUrl);
+ const grandIsNode = grand.isNode;
+ return (
+ <li class={`s${grandIsNode ? ' sub' : ''}${grandOff ? ' off' : ''}`}>
+ <a href={grand.url} class={grandInPath ? 's selected' : 's'}>
+ {grandInPath ? <strong>{grand.title}</strong> : grand.title}
+ </a>
+ {grandIsNode && grand.children.length > 0 && (
+ <ul class={`s${grandInPath ? ' active' : ''}`}>
+ {grand.children.map(ggrand => {
+ const ggOff = projectNodeOff(ggrand, grand.url);
+ const ggActive = ggrand.url === currentUrl;
+ return (
+ <li class={`s${ggOff ? ' off' : ''}`}>
+ <a href={ggrand.url} class={ggActive ? 's selected' : 's'}>
+ {ggActive ? <strong>{ggrand.title}</strong> : ggrand.title}
+ </a>
+ </li>
+ );
+ })}
+ </ul>
+ )}
+ </li>
+ );
+ })}
+ </ul>
+ )}
+ </li>
+ );
+ })}
+ </ul>
+ </li>
+ );
+ })}
+ </ul>
+ </li>
+
{/* ── About subtree ────────────────────────────────────────────────────── */}
<li class={`s sub${aboutOff ? ' off' : ''}`}>
<a href="/about.html" class={onAboutSection ? 's selected' : 's'}>