From: Kai Moritz Date: Sun, 14 Jun 2026 14:35:54 +0000 (+0000) Subject: BlogNav + projects: Code bereinigt und vereinfacht X-Git-Url: https://juplo.de/gitweb/?a=commitdiff_plain;h=e5b70514ed2529075a4f82951063a6b83e292211;p=website BlogNav + projects: Code bereinigt und vereinfacht Totes Code entfernt: - activeProjectUrl, isReports (nie benutzt) - dirname, basename in content.config.ts (unused imports) - visibleRoot-Branch in getActiveProject (identisch mit visiblePage) Duplikate zusammengeführt: - inSubtree + inProjectSubtree → generische Funktion (funktioniert für NavPage und ProjectNavNode dank struktureller Typisierung) - aboutPageOff + projectNodeOff → nodeOff(nodeUrl, parentUrl, inPath, currentParentUrl, currentIsNode); Partial Applications aboutNodeOff und projNodeOff binden den section-spezifischen Kontext - findProjectNode + findProjectParent → findNodeAndParent (ein DFS-Pass statt zwei); _activeProjectRoot-Alias dadurch obsolet Wartbarkeit: - aboutParentOf Map + aboutSectionIndexUrls Set durch findInTree ersetzt (leitet parentUrl und isNode direkt aus dem aboutSection-Baum ab statt beide separat zu pflegen) - inPath einmal pro Node berechnet, für off-Logik und selected-Klasse wiederverwendet (war vorher doppelter Traversal) - versionPrefix in buildNavTree aus makeNode herausgezogen Co-Authored-By: Claude Sonnet 4.6 --- diff --git a/src/components/BlogNav.astro b/src/components/BlogNav.astro index 5c05a463..5cde6a22 100644 --- a/src/components/BlogNav.astro +++ b/src/components/BlogNav.astro @@ -46,118 +46,104 @@ const tags = [...tagSet.entries()].map(([name, count]) => ({ count, })).sort((a, b) => a.displayName.localeCompare(b.displayName)); -// ── About section: static tree matching Hugo content/about/ structure ────────── -// Sorted by weight (Hugo default), then by title. -// isNode = true means the page is a section index (has children in Hugo). +// ── Shared nav helpers ──────────────────────────────────────────────────────── + +// Generic tree traversal — works for both NavPage and ProjectNavNode +function inSubtree(node: T, url: string): boolean { + if (node.url === url) return true; + return (node.children ?? []).some(c => inSubtree(c, url)); +} + +// A nav item is visible (not off) when it is: current, ancestor of current, +// sibling of current (only when current is not a section-index node), or child of current. +// `inPath` = inSubtree(node, currentUrl), pre-computed by the caller to avoid double traversal. +function nodeOff( + nodeUrl: string, + parentUrl: string, + inPath: boolean, + currentParentUrl: string, + currentIsNode: boolean, +): boolean { + const isCurrent = nodeUrl === currentUrl; + const isAncestor = inPath && !isCurrent; + const isSibling = parentUrl === currentParentUrl && !currentIsNode; + const isChild = parentUrl === currentUrl; + return !(isCurrent || isAncestor || isSibling || isChild); +} + +// ── About section: static tree matching content/about/ structure ────────────── +// isNode = true means the page is a section index (has children). interface NavPage { url: string; title: string; isNode?: boolean; children?: NavPage[] } const aboutSection: NavPage = { url: '/about.html', title: 'About', isNode: true, children: [ - { url: '/contact.html', title: 'Contact' }, // weight 10 - { // weight 10 (alpha: C < I) + { url: '/contact.html', title: 'Contact' }, // weight 10 + { // weight 10 (alpha: C < I) url: '/impressum.html', title: 'Impressum', isNode: true, children: [ - { url: '/urheberrechte.html', title: 'Urheberrecht' }, // weight 10 - { url: '/datenschutz.html', title: 'Datenschutz' }, // weight 20 - { url: '/haftung-inhalte.html', title: 'Haftung für Inhalte' }, // weight 30 - { url: '/haftung-links.html', title: 'Haftung für Links' }, // weight 40 - { url: '/google-analytics.html', title: 'Google Analytics' }, // no weight → last + { url: '/urheberrechte.html', title: 'Urheberrecht' }, // weight 10 + { url: '/datenschutz.html', title: 'Datenschutz' }, // weight 20 + { url: '/haftung-inhalte.html', title: 'Haftung für Inhalte' }, // weight 30 + { url: '/haftung-links.html', title: 'Haftung für Links' }, // weight 40 + { url: '/google-analytics.html', title: 'Google Analytics' }, // no weight → last ] }, - { url: '/agb.html', title: 'AGB' }, // weight 30 + { url: '/agb.html', title: 'AGB' }, // weight 30 ] }; -// True if `url` is anywhere inside `node`'s subtree (inclusive) -function inSubtree(node: NavPage, url: string): boolean { - if (node.url === url) return true; - return (node.children ?? []).some(c => inSubtree(c, url)); +// Walk the About tree to find currentUrl — returns parent URL + isNode flag. +// Replaces the separate aboutParentOf Map and aboutSectionIndexUrls Set. +function findInTree(node: NavPage, url: string, parentUrl = ''): { parentUrl: string; isNode: boolean } | null { + if (node.url === url) return { parentUrl, isNode: node.isNode ?? false }; + for (const c of node.children ?? []) { + const r = findInTree(c, url, node.url); + if (r) return r; + } + return null; } -const onAboutSection = inSubtree(aboutSection, currentUrl); - -// Parent URL of each About-section page (needed for sibling/child off-logic) -const aboutParentOf = new Map([ - ['/about.html', ''], - ['/contact.html', '/about.html'], - ['/impressum.html', '/about.html'], - ['/agb.html', '/about.html'], - ['/urheberrechte.html', '/impressum.html'], - ['/datenschutz.html', '/impressum.html'], - ['/haftung-inhalte.html', '/impressum.html'], - ['/haftung-links.html', '/impressum.html'], - ['/google-analytics.html','/impressum.html'], -]); +const _currentAbout = findInTree(aboutSection, currentUrl); +const onAboutSection = _currentAbout !== null; +const currentAboutParent = _currentAbout?.parentUrl ?? ''; +const currentAboutIsNode = _currentAbout?.isNode ?? false; -// Section-index pages within About (= have children in Hugo) -const aboutSectionIndexUrls = new Set(['/about.html', '/impressum.html']); -const currentAboutIsNode = aboutSectionIndexUrls.has(currentUrl); -const currentAboutParent = aboutParentOf.get(currentUrl) ?? ''; - -// off-logic mirroring Hugo tree.html for a page inside the About subtree. -// A page is visible (not off) if it is current, ancestor-of-current, -// sibling-of-current (only when current is not a section-index), or child-of-current. -function aboutPageOff(page: NavPage, parentUrl: string): boolean { - const isCurrent = page.url === currentUrl; - const isAncestor = !isCurrent && inSubtree(page, currentUrl); - const isSibling = parentUrl === currentAboutParent && !currentAboutIsNode; - const isChild = parentUrl === currentUrl; - return !(isCurrent || isAncestor || isSibling || isChild); -} +// Partial application of nodeOff bound to the About subtree's current-page context +const aboutNodeOff = (nodeUrl: string, parentUrl: string, inPath: boolean) => + nodeOff(nodeUrl, parentUrl, inPath, currentAboutParent, currentAboutIsNode); // ── Projects section ────────────────────────────────────────────────────────── -const activeProject = await getActiveProject(currentUrl); -const onProjectsSection = currentUrl === '/projects/' || activeProject !== null; +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; +// Single DFS pass: finds both the current node and its parent in a project tree +function findNodeAndParent( + node: ProjectNavNode, + url: string, + parent: ProjectNavNode | null = null, +): { node: ProjectNavNode; parent: ProjectNavNode | null } | null { + if (node.url === url) return { node, parent }; for (const c of node.children) { - const found = findProjectNode(c, url); - if (found) return found; + const r = findNodeAndParent(c, url, node); + if (r) return r; } 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 _found = activeProject?.navRoot ? findNodeAndParent(activeProject.navRoot, currentUrl) : null; +const activeProjectCurrentNode = _found?.node ?? null; +const activeProjectCurrentParent = _found?.parent ?? 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); -} +// Partial application of nodeOff bound to the Projects subtree's current-page context +const _projCurrentParentUrl = activeProjectCurrentParent?.url ?? ''; +const projNodeOff = (nodeUrl: string, parentUrl: string, inPath: boolean) => + nodeOff(nodeUrl, parentUrl, inPath, _projCurrentParentUrl, activeProjectCurrentIsNode); // ── Blog URL classification ──────────────────────────────────────────────────── const mainSections = [ @@ -332,9 +318,9 @@ const activeTagSlug = onTagTerm ? currentUrl.replace(/^\/tags\//, '').repl