content/
blog/ ← Blog-Artikel als Markdown (Content Collection, glob-Loader)
projects/ ← Projektdokumentation als HTML + YAML-Frontmatter (Custom Loader)
- components/ ← Wiederverwendbare Astro-Komponenten (BlogNav.astro, ProjectPage.astro, …)
+ components/ ← Wiederverwendbare Astro-Komponenten (SitemapNav.astro, ProjectPage.astro, …)
lib/ ← Shared TypeScript-Helpers (posts.ts, projects.ts)
styles/ ← SCSS-Quellen
public/
└── footer#footer Copyright + Footer-Links
```
-Seiten befüllen die Slots mit `<Fragment slot="…">`. Den `menu`-Slot übernimmt immer `BlogNav.astro`.
+Seiten befüllen die Slots mit `<Fragment slot="…">`. Den `menu`-Slot übernimmt immer `SitemapNav.astro`.
---
Ein `ul.s` ohne `active` blendet sich **nicht selbst** aus — nur `li.s.off` ist hidden.
-### Implementierung: `BlogNav.astro`
+### Implementierung: `SitemapNav.astro`
-`BlogNav.astro` empfängt `currentUrl` als Parameter und berechnet daraus vollständig in TypeScript alle Navigationszustände (`onBlog`, `onArchive`, `onCategoriesIndex`, `onProjectsSection` etc.). Alle CSS-Klassen werden direkt aus diesen Variablen abgeleitet. Nie werden Items per `{condition && <li>}` weggelassen — nur die `off`-Klasse steuert die Sichtbarkeit via CSS.
+`SitemapNav.astro` empfängt `currentUrl` als Parameter und berechnet daraus vollständig in TypeScript alle Navigationszustände (`onBlog`, `onArchive`, `onCategoriesIndex`, `onProjectsSection` etc.). Alle CSS-Klassen werden direkt aus diesen Variablen abgeleitet. Nie werden Items per `{condition && <li>}` weggelassen — nur die `off`-Klasse steuert die Sichtbarkeit via CSS.
### Section-`off`-Logik
### About-Bereich: Seitenstruktur
-Der About-Seitenbaum ist statisch in `BlogNav.astro` als TypeScript-Konstante (`NavPage[]`) definiert. Sortiert nach weight:
+Der About-Seitenbaum ist statisch in `SitemapNav.astro` als TypeScript-Konstante (`NavPage[]`) definiert. Sortiert nach weight:
```
/about.html
**Alias-Einträge (`params.alias: true`):** Seiten, die in `stili-json` in mehreren `childs`-Listen auftauchen, werden vom Import-Skript an mehreren Stellen im Content-Tree abgelegt. Die primäre Datei (kanonischer Ort, mit Body + Routing) folgt den `crumbs`/`path`-Feldern. Zusätzliche **Stub-Dateien** (leer, kein Routing) entstehen an den weiteren Positionen und erhalten `params.alias: true`.
-In `BlogNav.astro` werden Alias-Nodes mit der CSS-Klasse `alias` versehen. In der `off`-Berechnung überspringen Alias-Nodes `isCurrent` und `isAncestor` — dadurch sind sie im Classic-Layout versteckt, wenn die Seite aktiv ist (der kanonische Eintrag übernimmt die Darstellung). Sichtbar sind sie als Geschwister (normale `isSibling`-Logik).
+In `SitemapNav.astro` werden Alias-Nodes mit der CSS-Klasse `alias` versehen. In der `off`-Berechnung überspringen Alias-Nodes `isCurrent` und `isAncestor` — dadurch sind sie im Classic-Layout versteckt, wenn die Seite aktiv ist (der kanonische Eintrag übernimmt die Darstellung). Sichtbar sind sie als Geschwister (normale `isSibling`-Logik).
Alias-Nodes erhalten zusätzlich einen Barrierefreiheits-Hinweis im Markup: `<span class="alias-hint"> (Schnellzugriff)</span>` direkt nach dem Titel-Element (`<strong>` oder `<a>`). Der Hinweis steht **außerhalb** von `<strong>` und ist damit nie fett — auch nicht wenn die Seite aktiv ist. Im Layout „None" (kein CSS) erscheint der Hinweis, damit Nutzende von Text-Browsern und Screen-Readern erkennen, dass dieser Eintrag ein Zusatzzugang zu einer auch anderswo gelisteten Seite ist. Im CSS (`base/navigation.scss`: `#submenu .alias-hint { display: none }`) wird er im Classic-Layout ausgeblendet (da `navigation.scss` unter `:root[data-layout="classic"]` eingebunden ist — siehe Abschnitt „CSS-Layouts" unten). Der kanonische Eintrag erhält keinen Hinweis.
Die Projektdokumentation in `src/content/projects/` wird **nicht manuell gepflegt**, sondern mit dem **StILi Maven Skin** generiert und per `import-in-astro.sh` importiert.
-Die importierten Dateien sind HTML mit YAML-Frontmatter. Ein Custom Loader in `src/content.config.ts` (mit `js-yaml`) liest diese ein; Routing und Nav-Logik werden in `src/lib/projects.ts` und `BlogNav.astro` verwaltet.
+Die importierten Dateien sind HTML mit YAML-Frontmatter. Ein Custom Loader in `src/content.config.ts` (mit `js-yaml`) liest diese ein; Routing und Nav-Logik werden in `src/lib/projects.ts` und `SitemapNav.astro` verwaltet.
**Verzeichnisstruktur (Beispiel hibernate-maven-plugin):**
```
### Shared Helpers
- `src/lib/posts.ts` — `getAllPosts`, `postUrl`, `getSummary`, `wordCount` für Blog-Seiten und RSS-Feeds
-- `src/lib/projects.ts` — `getActiveProject`, `getVisibleProjectNavTrees`, `buildNavTree`, `generatedDocNodes` für die Projects-Nav-Logik in `BlogNav.astro`
+- `src/lib/projects.ts` — `getActiveProject`, `getVisibleProjectNavTrees`, `buildNavTree`, `generatedDocNodes` für die Projects-Nav-Logik in `SitemapNav.astro`
### Content Collections
+++ /dev/null
----
-import type { CollectionEntry } from 'astro:content';
-import { getAllPosts, postUrl } from '../lib/posts';
-import { getActiveProject, getVisibleProjectNavTrees, type ProjectNavNode } from '../lib/projects';
-import { aboutSection, findAboutPath } from '../lib/about';
-
-interface Props {
- currentUrl: string;
-}
-
-const { currentUrl } = Astro.props;
-
-const allPosts = await getAllPosts();
-
-// Group posts by year, years sorted descending
-const postsByYear: Record<string, CollectionEntry<'blog'>[]> = {};
-for (const post of allPosts) {
- const year = post.data.date.getFullYear().toString();
- if (!postsByYear[year]) postsByYear[year] = [];
- postsByYear[year].push(post);
-}
-const years = Object.keys(postsByYear).sort((a, b) => Number(b) - Number(a));
-
-// Collect unique categories and tags from all posts
-const categorySet = new Map<string, number>();
-const tagSet = new Map<string, number>();
-for (const post of allPosts) {
- for (const cat of post.data.categories ?? []) {
- categorySet.set(cat, (categorySet.get(cat) ?? 0) + 1);
- }
- for (const tag of post.data.tags ?? []) {
- tagSet.set(tag, (tagSet.get(tag) ?? 0) + 1);
- }
-}
-
-const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
-const categories = [...categorySet.entries()].map(([name, count]) => ({
- name,
- displayName: capitalize(name),
- url: `/categories/${name}/`,
- count,
-})).sort((a, b) => a.displayName.localeCompare(b.displayName));
-const tags = [...tagSet.entries()].map(([name, count]) => ({
- name,
- displayName: capitalize(name),
- url: `/tags/${name}/`,
- count,
-})).sort((a, b) => a.displayName.localeCompare(b.displayName));
-
-// ── Shared nav helpers ────────────────────────────────────────────────────────
-
-// Generic tree traversal — works for both NavPage and ProjectNavNode
-function inSubtree<T extends { url: string; children?: T[] }>(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.
-// `isAlias` = true for nav-only stub entries: isCurrent and isAncestor are suppressed so the
-// alias is hidden in Classic layout when its page is active (the canonical entry handles that).
-function nodeOff(
- nodeUrl: string,
- parentUrl: string,
- inPath: boolean,
- currentParentUrl: string,
- currentIsNode: boolean,
- isAlias = false,
-): boolean {
- const isCurrent = !isAlias && nodeUrl === currentUrl;
- const isAncestor = !isAlias && inPath && !isCurrent;
- const isSibling = parentUrl === currentParentUrl && !currentIsNode;
- const isChild = parentUrl === currentUrl;
- return !(isCurrent || isAncestor || isSibling || isChild);
-}
-
-// ── About section: static tree — see src/lib/about.ts ───────────────────────
-const _aboutPath = findAboutPath(aboutSection, currentUrl);
-const onAboutSection = _aboutPath !== null;
-const currentAboutParent = _aboutPath && _aboutPath.length >= 2 ? _aboutPath.at(-2)!.url : '';
-const currentAboutIsNode = _aboutPath ? (_aboutPath.at(-1)!.isNode ?? false) : false;
-
-// 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;
-// When on a SNAPSHOT page, render that version's tree; otherwise all visible trees.
-const projectTrees = onProjectsSection && activeProject && !activeProject.isCurrent
- ? [activeProject]
- : await getVisibleProjectNavTrees();
-
-// 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 r = findNodeAndParent(c, url, node);
- if (r) return r;
- }
- return 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;
-
-// 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, isAlias = false) =>
- nodeOff(nodeUrl, parentUrl, inPath, _projCurrentParentUrl, activeProjectCurrentIsNode, isAlias);
-
-// ── Blog URL classification ────────────────────────────────────────────────────
-const mainSections = [
- { name: 'blog', url: '/blog/', title: 'Blog' },
- { name: 'projects', url: '/projects/', title: 'Projects' },
- { name: 'about', url: '/about.html', title: 'About' },
-];
-
-const onBlog = currentUrl === '/blog/';
-const isOnBlogPost = allPosts.some(p => (p.data.url ?? `/${p.id}/`) === currentUrl);
-const onArchive = currentUrl.startsWith('/blog/archive/') || isOnBlogPost;
-const onCategoriesIndex = currentUrl === '/blog/categories/';
-const onCategoryTerm = currentUrl.startsWith('/categories/');
-const onTagsIndex = currentUrl === '/blog/tags/';
-const onTagTerm = currentUrl.startsWith('/tags/');
-const blogSelected = onBlog || onArchive || onCategoriesIndex || onCategoryTerm || onTagsIndex || onTagTerm;
-
-const categoriesSelected = onCategoriesIndex || onCategoryTerm;
-const tagsSelected = onTagsIndex || onTagTerm;
-
-const archiveOff = !blogSelected || onCategoryTerm || onTagTerm;
-const categoriesOff = !blogSelected || onArchive || onTagTerm;
-const tagsOff = !blogSelected || onArchive || onCategoryTerm;
-
-// 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 === 'projects') return onProjectsSection;
- return currentUrl.startsWith(s.url);
-})?.name ?? '';
-
-// 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) {
- return currentUrl === `/blog/archive/${year}/` ||
- postsByYear[year]?.some(p => postUrl(p) === currentUrl) === true;
-}
-const activeCatSlug = onCategoryTerm ? currentUrl.replace(/^\/categories\//, '').replace(/\/$/, '') : '';
-const activeTagSlug = onTagTerm ? currentUrl.replace(/^\/tags\//, '').replace(/\/$/, '') : '';
----
-
-<nav id="nav">
- <hr class="n"/>
- <a class="hide" href="#top" title="Show Content">Jump back to the top of the page</a>
- <h1 class="nav">Navigation</h1>
- <div>
- <h2 id="sitedescription" class="nav menu">Description of the website structure</h2>
- <ul id="menu" class="cf">
- {mainSections.map(s => (
- <li class={`m ${s.name}`}>
- <a href={s.url} class={activeSection === s.name ? 'm selected' : 'm'}>{s.title}</a><span class="sep">:</span>
- <span class="description">Wechselnde passende Beschreibung des Bereichs</span>
- </li>
- ))}
- </ul>
- </div>
- <div id="sitemap">
- <h2 class="nav">Sitemap</h2>
- <div class="home">
- {currentUrl === '/'
- ? <strong class="s">
- <span class="home-surrounding">You are at the </span>Home<span class="home-surrounding">-page of the site</span>
- </strong>
- : <a class="s selected" href="/">
- <span class="home-surrounding">Go back to the </span>Home<span class="home-surrounding">-page of the site</span>
- </a>
- }
- </div>
- <ul id="submenu" class="s active">
-
- {/* ── Blog subtree ─────────────────────────────────────────────────────── */}
- <li class={`s sub${blogSectionOff ? ' off' : ''}`}>
- {onBlog
- ? <strong class="s">Blog</strong>
- : <a href="/blog/" class={blogSelected ? 's selected' : 's'}>
- {blogSelected ? <strong>Blog</strong> : 'Blog'}
- </a>
- }
- <ul class={blogSelected ? 's active' : 's'}>
- {/* Archive subtree — always rendered; off-class controls visibility via CSS */}
- <li class={`s sub${archiveOff ? ' off' : ''}`}>
- {currentUrl === '/blog/archive/'
- ? <strong class="s">Archive</strong>
- : <a href="/blog/archive/" class={onArchive ? 's selected' : 's'}>
- {onArchive ? <strong>Archive</strong> : 'Archive'}
- </a>
- }
- <ul class={onArchive ? 's active' : 's'}>
- {years.map(year => {
- const active = isActiveYear(year);
- // Year visible only when: on Archive root (all years are children) or this year is active.
- // On Blog root, Categories, Tags, outside Blog: all years are off.
- const yearOff = currentUrl !== '/blog/archive/' && !active;
- return (
- <li class={`s${yearOff ? ' off' : ''}`}>
- {currentUrl === `/blog/archive/${year}/`
- ? <strong class="s">{year}</strong>
- : <a href={`/blog/archive/${year}/`} class={active ? 's selected' : 's'}>
- {active ? <strong>{year}</strong> : year}
- </a>
- }
- <ul class="s">
- {postsByYear[year].map(post => {
- const pUrl = postUrl(post);
- const current = pUrl === currentUrl;
- return (
- <li class="s nav-leaf">
- {current
- ? <strong class="s">{post.data.title}</strong>
- : <a href={pUrl} class="s">{post.data.title}</a>
- }
- </li>
- );
- })}
- </ul>
- </li>
- );
- })}
- </ul>
- </li>
- {/* Categories subtree */}
- {categories.length > 0 && (
- <li class={`s${categoriesOff ? ' off' : ''}${categoriesSelected ? ' sub' : ''}`}>
- {onCategoriesIndex
- ? <strong class="s">Categories</strong>
- : <a href="/blog/categories/" class={categoriesSelected ? 's selected' : 's'}>
- {categoriesSelected ? <strong>Categories</strong> : 'Categories'}
- </a>
- }
- {categoriesSelected ? (
- <ul class="s active">
- {categories.map(cat => {
- const active = cat.name === activeCatSlug;
- return (
- <li class="s">
- {active
- ? <strong class="s">{cat.displayName}</strong>
- : <a href={cat.url} class="s">{cat.displayName}</a>
- }
- </li>
- );
- })}
- </ul>
- ) : (
- <ul class="s">
- {categories.map(cat => (
- <li class="s nav-leaf">
- <a href={cat.url} class="s">{cat.displayName}</a>
- </li>
- ))}
- </ul>
- )}
- </li>
- )}
- {/* Tags subtree */}
- {tags.length > 0 && (
- <li class={`s${tagsOff ? ' off' : ''}${tagsSelected ? ' sub' : ''}`}>
- {onTagsIndex
- ? <strong class="s">Tags</strong>
- : <a href="/blog/tags/" class={tagsSelected ? 's selected' : 's'}>
- {tagsSelected ? <strong>Tags</strong> : 'Tags'}
- </a>
- }
- {tagsSelected ? (
- <ul class="s active">
- {tags.map(tag => {
- const active = tag.name === activeTagSlug;
- return (
- <li class="s">
- {active
- ? <strong class="s">{tag.displayName}</strong>
- : <a href={tag.url} class="s">{tag.displayName}</a>
- }
- </li>
- );
- })}
- </ul>
- ) : (
- <ul class="s">
- {tags.map(tag => (
- <li class="s nav-leaf">
- <a href={tag.url} class="s">{tag.displayName}</a>
- </li>
- ))}
- </ul>
- )}
- </li>
- )}
- </ul>
- </li>
-
- {/* ── Projects subtree ─────────────────────────────────────────────────── */}
- <li class={`s sub${projectsSectionOff ? ' off' : ''}`}>
- {currentUrl === '/projects/'
- ? <strong class="s">Projects</strong>
- : <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 rootInPath = inSubtree(root, currentUrl);
- const rootOff = projNodeOff(root.url, '/projects/', rootInPath);
- return (
- <li class={`s sub${rootOff ? ' off' : ''}`}>
- {root.url === currentUrl
- ? <strong class="s">{root.title}</strong>
- : <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 childInPath = inSubtree(child, currentUrl);
- const childOff = projNodeOff(child.url, root.url, childInPath, child.isAlias);
- const childIsNode = child.isNode;
- return (
- <li class={`s${childIsNode ? ' sub' : ''}${child.isAlias ? ' alias' : ''}${childOff ? ' off' : ''}`}>
- {child.url === currentUrl
- ? <strong class="s">{child.title}</strong>
- : <a href={child.url} class={childInPath ? 's selected' : 's'}>
- {childInPath ? <strong>{child.title}</strong> : child.title}
- </a>
- }
- {child.isAlias && <span class="alias-hint"> (Schnellzugriff)</span>}
- {childIsNode && child.children.length > 0 && (
- <ul class={`s${childInPath ? ' active' : ''}`}>
- {child.children.map(grand => {
- const grandInPath = inSubtree(grand, currentUrl);
- const grandOff = projNodeOff(grand.url, child.url, grandInPath, grand.isAlias);
- const grandIsNode = grand.isNode;
- return (
- <li class={`s${grandIsNode ? ' sub' : ''}${grand.isAlias ? ' alias' : ''}${grandOff ? ' off' : ''}`}>
- {grand.url === currentUrl
- ? <strong class="s">{grand.title}</strong>
- : <a href={grand.url} class={grandInPath ? 's selected' : 's'}>
- {grandInPath ? <strong>{grand.title}</strong> : grand.title}
- </a>
- }
- {grand.isAlias && <span class="alias-hint"> (Schnellzugriff)</span>}
- {grandIsNode && grand.children.length > 0 && (
- <ul class={`s${grandInPath ? ' active' : ''}`}>
- {grand.children.map(ggrand => {
- const ggInPath = ggrand.url === currentUrl;
- const ggOff = projNodeOff(ggrand.url, grand.url, ggInPath, ggrand.isAlias);
- return (
- <li class={`s${ggrand.isAlias ? ' alias' : ''}${ggOff ? ' off' : ''}`}>
- {ggInPath
- ? <strong class="s">{ggrand.title}</strong>
- : <a href={ggrand.url} class="s">{ggrand.title}</a>
- }
- {ggrand.isAlias && <span class="alias-hint"> (Schnellzugriff)</span>}
- </li>
- );
- })}
- </ul>
- )}
- </li>
- );
- })}
- </ul>
- )}
- </li>
- );
- })}
- </ul>
- </li>
- );
- })}
- </ul>
- </li>
-
- {/* ── About subtree ────────────────────────────────────────────────────── */}
- <li class={`s sub${aboutOff ? ' off' : ''}`}>
- {currentUrl === '/about.html'
- ? <strong class="s">About</strong>
- : <a href="/about.html" class={onAboutSection ? 's selected' : 's'}>
- {onAboutSection ? <strong>About</strong> : 'About'}
- </a>
- }
- <ul class={`s${onAboutSection ? ' active' : ''}`}>
- {aboutSection.children!.map(child => {
- const childInPath = inSubtree(child, currentUrl);
- const childOff = aboutNodeOff(child.url, '/about.html', childInPath);
- const childHasSubs = (child.children ?? []).length > 0;
- return (
- <li class={`s${childHasSubs ? ' sub' : ''}${childOff ? ' off' : ''}`}>
- {child.url === currentUrl
- ? <strong class="s">{child.title}</strong>
- : <a href={child.url} class={childInPath ? 's selected' : 's'}>
- {childInPath ? <strong>{child.title}</strong> : child.title}
- </a>
- }
- {childHasSubs && (
- <ul class={`s${childInPath ? ' active' : ''}`}>
- {child.children!.map(grand => {
- const grandActive = grand.url === currentUrl;
- const grandOff = aboutNodeOff(grand.url, child.url, grandActive);
- return (
- <li class={`s${grandOff ? ' off' : ''}`}>
- {grandActive
- ? <strong class="s">{grand.title}</strong>
- : <a href={grand.url} class="s">{grand.title}</a>
- }
- </li>
- );
- })}
- </ul>
- )}
- </li>
- );
- })}
- </ul>
- </li>
-
- </ul>
- </div>
- <hr class="n"/>
-</nav>
-
-<script>
- const descriptions: Record<string, string[]> = {
- blog: [
- 'Java-Tipps und Tricks aus dem Entwickleralltag',
- 'Erfahrungsberichte aus Open-Source-Projekten',
- 'Technische Einblicke für Java-Entwickler',
- ],
- projects: [
- 'Open-Source-Werkzeuge für Maven und Java',
- 'Nützliche Plugins für den Entwickleralltag',
- 'Bewährte Lösungen für typische Java-Probleme',
- ],
- about: [
- 'Wer steckt hinter juplo.de?',
- 'Kontakt, Impressum und rechtliche Hinweise',
- 'Über den Autor und seine Arbeit',
- ],
- };
-
- const indices: Record<string, number> = {};
- const menuItems = document.querySelectorAll<HTMLElement>('#menu > li.m');
-
- menuItems.forEach(li => {
- const section = Array.from(li.classList).find(c => c !== 'm');
- if (!section || !descriptions[section]) return;
- const span = li.querySelector<HTMLElement>('span.description');
- if (!span) return;
- indices[section] = 0;
- span.textContent = descriptions[section][0];
- });
-
- setInterval(() => {
- menuItems.forEach(li => {
- const section = Array.from(li.classList).find(c => c !== 'm');
- if (!section || !descriptions[section]) return;
- const span = li.querySelector<HTMLElement>('span.description');
- if (!span) return;
- indices[section] = (indices[section] + 1) % descriptions[section].length;
- span.textContent = descriptions[section][indices[section]];
- });
- }, 5000);
-</script>
import hljs from 'highlight.js';
import BaseLayout from '../layouts/BaseLayout.astro';
import Breadcrumb from '../components/Breadcrumb.astro';
-import BlogNav from '../components/BlogNav.astro';
+import SitemapNav from '../components/SitemapNav.astro';
import { getActiveProject, findCanonicalPath } from '../lib/projects';
interface Props {
</Fragment>
<Fragment slot="menu">
- <BlogNav currentUrl={url} />
+ <SitemapNav currentUrl={url} />
</Fragment>
</BaseLayout>
--- /dev/null
+---
+import type { CollectionEntry } from 'astro:content';
+import { getAllPosts, postUrl } from '../lib/posts';
+import { getActiveProject, getVisibleProjectNavTrees, type ProjectNavNode } from '../lib/projects';
+import { aboutSection, findAboutPath } from '../lib/about';
+
+interface Props {
+ currentUrl: string;
+}
+
+const { currentUrl } = Astro.props;
+
+const allPosts = await getAllPosts();
+
+// Group posts by year, years sorted descending
+const postsByYear: Record<string, CollectionEntry<'blog'>[]> = {};
+for (const post of allPosts) {
+ const year = post.data.date.getFullYear().toString();
+ if (!postsByYear[year]) postsByYear[year] = [];
+ postsByYear[year].push(post);
+}
+const years = Object.keys(postsByYear).sort((a, b) => Number(b) - Number(a));
+
+// Collect unique categories and tags from all posts
+const categorySet = new Map<string, number>();
+const tagSet = new Map<string, number>();
+for (const post of allPosts) {
+ for (const cat of post.data.categories ?? []) {
+ categorySet.set(cat, (categorySet.get(cat) ?? 0) + 1);
+ }
+ for (const tag of post.data.tags ?? []) {
+ tagSet.set(tag, (tagSet.get(tag) ?? 0) + 1);
+ }
+}
+
+const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
+const categories = [...categorySet.entries()].map(([name, count]) => ({
+ name,
+ displayName: capitalize(name),
+ url: `/categories/${name}/`,
+ count,
+})).sort((a, b) => a.displayName.localeCompare(b.displayName));
+const tags = [...tagSet.entries()].map(([name, count]) => ({
+ name,
+ displayName: capitalize(name),
+ url: `/tags/${name}/`,
+ count,
+})).sort((a, b) => a.displayName.localeCompare(b.displayName));
+
+// ── Shared nav helpers ────────────────────────────────────────────────────────
+
+// Generic tree traversal — works for both NavPage and ProjectNavNode
+function inSubtree<T extends { url: string; children?: T[] }>(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.
+// `isAlias` = true for nav-only stub entries: isCurrent and isAncestor are suppressed so the
+// alias is hidden in Classic layout when its page is active (the canonical entry handles that).
+function nodeOff(
+ nodeUrl: string,
+ parentUrl: string,
+ inPath: boolean,
+ currentParentUrl: string,
+ currentIsNode: boolean,
+ isAlias = false,
+): boolean {
+ const isCurrent = !isAlias && nodeUrl === currentUrl;
+ const isAncestor = !isAlias && inPath && !isCurrent;
+ const isSibling = parentUrl === currentParentUrl && !currentIsNode;
+ const isChild = parentUrl === currentUrl;
+ return !(isCurrent || isAncestor || isSibling || isChild);
+}
+
+// ── About section: static tree — see src/lib/about.ts ───────────────────────
+const _aboutPath = findAboutPath(aboutSection, currentUrl);
+const onAboutSection = _aboutPath !== null;
+const currentAboutParent = _aboutPath && _aboutPath.length >= 2 ? _aboutPath.at(-2)!.url : '';
+const currentAboutIsNode = _aboutPath ? (_aboutPath.at(-1)!.isNode ?? false) : false;
+
+// 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;
+// When on a SNAPSHOT page, render that version's tree; otherwise all visible trees.
+const projectTrees = onProjectsSection && activeProject && !activeProject.isCurrent
+ ? [activeProject]
+ : await getVisibleProjectNavTrees();
+
+// 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 r = findNodeAndParent(c, url, node);
+ if (r) return r;
+ }
+ return 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;
+
+// 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, isAlias = false) =>
+ nodeOff(nodeUrl, parentUrl, inPath, _projCurrentParentUrl, activeProjectCurrentIsNode, isAlias);
+
+// ── Blog URL classification ────────────────────────────────────────────────────
+const mainSections = [
+ { name: 'blog', url: '/blog/', title: 'Blog' },
+ { name: 'projects', url: '/projects/', title: 'Projects' },
+ { name: 'about', url: '/about.html', title: 'About' },
+];
+
+const onBlog = currentUrl === '/blog/';
+const isOnBlogPost = allPosts.some(p => (p.data.url ?? `/${p.id}/`) === currentUrl);
+const onArchive = currentUrl.startsWith('/blog/archive/') || isOnBlogPost;
+const onCategoriesIndex = currentUrl === '/blog/categories/';
+const onCategoryTerm = currentUrl.startsWith('/categories/');
+const onTagsIndex = currentUrl === '/blog/tags/';
+const onTagTerm = currentUrl.startsWith('/tags/');
+const blogSelected = onBlog || onArchive || onCategoriesIndex || onCategoryTerm || onTagsIndex || onTagTerm;
+
+const categoriesSelected = onCategoriesIndex || onCategoryTerm;
+const tagsSelected = onTagsIndex || onTagTerm;
+
+const archiveOff = !blogSelected || onCategoryTerm || onTagTerm;
+const categoriesOff = !blogSelected || onArchive || onTagTerm;
+const tagsOff = !blogSelected || onArchive || onCategoryTerm;
+
+// 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 === 'projects') return onProjectsSection;
+ return currentUrl.startsWith(s.url);
+})?.name ?? '';
+
+// 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) {
+ return currentUrl === `/blog/archive/${year}/` ||
+ postsByYear[year]?.some(p => postUrl(p) === currentUrl) === true;
+}
+const activeCatSlug = onCategoryTerm ? currentUrl.replace(/^\/categories\//, '').replace(/\/$/, '') : '';
+const activeTagSlug = onTagTerm ? currentUrl.replace(/^\/tags\//, '').replace(/\/$/, '') : '';
+---
+
+<nav id="nav">
+ <hr class="n"/>
+ <a class="hide" href="#top" title="Show Content">Jump back to the top of the page</a>
+ <h1 class="nav">Navigation</h1>
+ <div>
+ <h2 id="sitedescription" class="nav menu">Description of the website structure</h2>
+ <ul id="menu" class="cf">
+ {mainSections.map(s => (
+ <li class={`m ${s.name}`}>
+ <a href={s.url} class={activeSection === s.name ? 'm selected' : 'm'}>{s.title}</a><span class="sep">:</span>
+ <span class="description">Wechselnde passende Beschreibung des Bereichs</span>
+ </li>
+ ))}
+ </ul>
+ </div>
+ <div id="sitemap">
+ <h2 class="nav">Sitemap</h2>
+ <div class="home">
+ {currentUrl === '/'
+ ? <strong class="s">
+ <span class="home-surrounding">You are at the </span>Home<span class="home-surrounding">-page of the site</span>
+ </strong>
+ : <a class="s selected" href="/">
+ <span class="home-surrounding">Go back to the </span>Home<span class="home-surrounding">-page of the site</span>
+ </a>
+ }
+ </div>
+ <ul id="submenu" class="s active">
+
+ {/* ── Blog subtree ─────────────────────────────────────────────────────── */}
+ <li class={`s sub${blogSectionOff ? ' off' : ''}`}>
+ {onBlog
+ ? <strong class="s">Blog</strong>
+ : <a href="/blog/" class={blogSelected ? 's selected' : 's'}>
+ {blogSelected ? <strong>Blog</strong> : 'Blog'}
+ </a>
+ }
+ <ul class={blogSelected ? 's active' : 's'}>
+ {/* Archive subtree — always rendered; off-class controls visibility via CSS */}
+ <li class={`s sub${archiveOff ? ' off' : ''}`}>
+ {currentUrl === '/blog/archive/'
+ ? <strong class="s">Archive</strong>
+ : <a href="/blog/archive/" class={onArchive ? 's selected' : 's'}>
+ {onArchive ? <strong>Archive</strong> : 'Archive'}
+ </a>
+ }
+ <ul class={onArchive ? 's active' : 's'}>
+ {years.map(year => {
+ const active = isActiveYear(year);
+ // Year visible only when: on Archive root (all years are children) or this year is active.
+ // On Blog root, Categories, Tags, outside Blog: all years are off.
+ const yearOff = currentUrl !== '/blog/archive/' && !active;
+ return (
+ <li class={`s${yearOff ? ' off' : ''}`}>
+ {currentUrl === `/blog/archive/${year}/`
+ ? <strong class="s">{year}</strong>
+ : <a href={`/blog/archive/${year}/`} class={active ? 's selected' : 's'}>
+ {active ? <strong>{year}</strong> : year}
+ </a>
+ }
+ <ul class="s">
+ {postsByYear[year].map(post => {
+ const pUrl = postUrl(post);
+ const current = pUrl === currentUrl;
+ return (
+ <li class="s nav-leaf">
+ {current
+ ? <strong class="s">{post.data.title}</strong>
+ : <a href={pUrl} class="s">{post.data.title}</a>
+ }
+ </li>
+ );
+ })}
+ </ul>
+ </li>
+ );
+ })}
+ </ul>
+ </li>
+ {/* Categories subtree */}
+ {categories.length > 0 && (
+ <li class={`s${categoriesOff ? ' off' : ''}${categoriesSelected ? ' sub' : ''}`}>
+ {onCategoriesIndex
+ ? <strong class="s">Categories</strong>
+ : <a href="/blog/categories/" class={categoriesSelected ? 's selected' : 's'}>
+ {categoriesSelected ? <strong>Categories</strong> : 'Categories'}
+ </a>
+ }
+ {categoriesSelected ? (
+ <ul class="s active">
+ {categories.map(cat => {
+ const active = cat.name === activeCatSlug;
+ return (
+ <li class="s">
+ {active
+ ? <strong class="s">{cat.displayName}</strong>
+ : <a href={cat.url} class="s">{cat.displayName}</a>
+ }
+ </li>
+ );
+ })}
+ </ul>
+ ) : (
+ <ul class="s">
+ {categories.map(cat => (
+ <li class="s nav-leaf">
+ <a href={cat.url} class="s">{cat.displayName}</a>
+ </li>
+ ))}
+ </ul>
+ )}
+ </li>
+ )}
+ {/* Tags subtree */}
+ {tags.length > 0 && (
+ <li class={`s${tagsOff ? ' off' : ''}${tagsSelected ? ' sub' : ''}`}>
+ {onTagsIndex
+ ? <strong class="s">Tags</strong>
+ : <a href="/blog/tags/" class={tagsSelected ? 's selected' : 's'}>
+ {tagsSelected ? <strong>Tags</strong> : 'Tags'}
+ </a>
+ }
+ {tagsSelected ? (
+ <ul class="s active">
+ {tags.map(tag => {
+ const active = tag.name === activeTagSlug;
+ return (
+ <li class="s">
+ {active
+ ? <strong class="s">{tag.displayName}</strong>
+ : <a href={tag.url} class="s">{tag.displayName}</a>
+ }
+ </li>
+ );
+ })}
+ </ul>
+ ) : (
+ <ul class="s">
+ {tags.map(tag => (
+ <li class="s nav-leaf">
+ <a href={tag.url} class="s">{tag.displayName}</a>
+ </li>
+ ))}
+ </ul>
+ )}
+ </li>
+ )}
+ </ul>
+ </li>
+
+ {/* ── Projects subtree ─────────────────────────────────────────────────── */}
+ <li class={`s sub${projectsSectionOff ? ' off' : ''}`}>
+ {currentUrl === '/projects/'
+ ? <strong class="s">Projects</strong>
+ : <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 rootInPath = inSubtree(root, currentUrl);
+ const rootOff = projNodeOff(root.url, '/projects/', rootInPath);
+ return (
+ <li class={`s sub${rootOff ? ' off' : ''}`}>
+ {root.url === currentUrl
+ ? <strong class="s">{root.title}</strong>
+ : <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 childInPath = inSubtree(child, currentUrl);
+ const childOff = projNodeOff(child.url, root.url, childInPath, child.isAlias);
+ const childIsNode = child.isNode;
+ return (
+ <li class={`s${childIsNode ? ' sub' : ''}${child.isAlias ? ' alias' : ''}${childOff ? ' off' : ''}`}>
+ {child.url === currentUrl
+ ? <strong class="s">{child.title}</strong>
+ : <a href={child.url} class={childInPath ? 's selected' : 's'}>
+ {childInPath ? <strong>{child.title}</strong> : child.title}
+ </a>
+ }
+ {child.isAlias && <span class="alias-hint"> (Schnellzugriff)</span>}
+ {childIsNode && child.children.length > 0 && (
+ <ul class={`s${childInPath ? ' active' : ''}`}>
+ {child.children.map(grand => {
+ const grandInPath = inSubtree(grand, currentUrl);
+ const grandOff = projNodeOff(grand.url, child.url, grandInPath, grand.isAlias);
+ const grandIsNode = grand.isNode;
+ return (
+ <li class={`s${grandIsNode ? ' sub' : ''}${grand.isAlias ? ' alias' : ''}${grandOff ? ' off' : ''}`}>
+ {grand.url === currentUrl
+ ? <strong class="s">{grand.title}</strong>
+ : <a href={grand.url} class={grandInPath ? 's selected' : 's'}>
+ {grandInPath ? <strong>{grand.title}</strong> : grand.title}
+ </a>
+ }
+ {grand.isAlias && <span class="alias-hint"> (Schnellzugriff)</span>}
+ {grandIsNode && grand.children.length > 0 && (
+ <ul class={`s${grandInPath ? ' active' : ''}`}>
+ {grand.children.map(ggrand => {
+ const ggInPath = ggrand.url === currentUrl;
+ const ggOff = projNodeOff(ggrand.url, grand.url, ggInPath, ggrand.isAlias);
+ return (
+ <li class={`s${ggrand.isAlias ? ' alias' : ''}${ggOff ? ' off' : ''}`}>
+ {ggInPath
+ ? <strong class="s">{ggrand.title}</strong>
+ : <a href={ggrand.url} class="s">{ggrand.title}</a>
+ }
+ {ggrand.isAlias && <span class="alias-hint"> (Schnellzugriff)</span>}
+ </li>
+ );
+ })}
+ </ul>
+ )}
+ </li>
+ );
+ })}
+ </ul>
+ )}
+ </li>
+ );
+ })}
+ </ul>
+ </li>
+ );
+ })}
+ </ul>
+ </li>
+
+ {/* ── About subtree ────────────────────────────────────────────────────── */}
+ <li class={`s sub${aboutOff ? ' off' : ''}`}>
+ {currentUrl === '/about.html'
+ ? <strong class="s">About</strong>
+ : <a href="/about.html" class={onAboutSection ? 's selected' : 's'}>
+ {onAboutSection ? <strong>About</strong> : 'About'}
+ </a>
+ }
+ <ul class={`s${onAboutSection ? ' active' : ''}`}>
+ {aboutSection.children!.map(child => {
+ const childInPath = inSubtree(child, currentUrl);
+ const childOff = aboutNodeOff(child.url, '/about.html', childInPath);
+ const childHasSubs = (child.children ?? []).length > 0;
+ return (
+ <li class={`s${childHasSubs ? ' sub' : ''}${childOff ? ' off' : ''}`}>
+ {child.url === currentUrl
+ ? <strong class="s">{child.title}</strong>
+ : <a href={child.url} class={childInPath ? 's selected' : 's'}>
+ {childInPath ? <strong>{child.title}</strong> : child.title}
+ </a>
+ }
+ {childHasSubs && (
+ <ul class={`s${childInPath ? ' active' : ''}`}>
+ {child.children!.map(grand => {
+ const grandActive = grand.url === currentUrl;
+ const grandOff = aboutNodeOff(grand.url, child.url, grandActive);
+ return (
+ <li class={`s${grandOff ? ' off' : ''}`}>
+ {grandActive
+ ? <strong class="s">{grand.title}</strong>
+ : <a href={grand.url} class="s">{grand.title}</a>
+ }
+ </li>
+ );
+ })}
+ </ul>
+ )}
+ </li>
+ );
+ })}
+ </ul>
+ </li>
+
+ </ul>
+ </div>
+ <hr class="n"/>
+</nav>
+
+<script>
+ const descriptions: Record<string, string[]> = {
+ blog: [
+ 'Java-Tipps und Tricks aus dem Entwickleralltag',
+ 'Erfahrungsberichte aus Open-Source-Projekten',
+ 'Technische Einblicke für Java-Entwickler',
+ ],
+ projects: [
+ 'Open-Source-Werkzeuge für Maven und Java',
+ 'Nützliche Plugins für den Entwickleralltag',
+ 'Bewährte Lösungen für typische Java-Probleme',
+ ],
+ about: [
+ 'Wer steckt hinter juplo.de?',
+ 'Kontakt, Impressum und rechtliche Hinweise',
+ 'Über den Autor und seine Arbeit',
+ ],
+ };
+
+ const indices: Record<string, number> = {};
+ const menuItems = document.querySelectorAll<HTMLElement>('#menu > li.m');
+
+ menuItems.forEach(li => {
+ const section = Array.from(li.classList).find(c => c !== 'm');
+ if (!section || !descriptions[section]) return;
+ const span = li.querySelector<HTMLElement>('span.description');
+ if (!span) return;
+ indices[section] = 0;
+ span.textContent = descriptions[section][0];
+ });
+
+ setInterval(() => {
+ menuItems.forEach(li => {
+ const section = Array.from(li.classList).find(c => c !== 'm');
+ if (!section || !descriptions[section]) return;
+ const span = li.querySelector<HTMLElement>('span.description');
+ if (!span) return;
+ indices[section] = (indices[section] + 1) % descriptions[section].length;
+ span.textContent = descriptions[section][indices[section]];
+ });
+ }, 5000);
+</script>
import { render } from 'astro:content';
import BaseLayout from '../layouts/BaseLayout.astro';
import Breadcrumb from '../components/Breadcrumb.astro';
-import BlogNav from '../components/BlogNav.astro';
+import SitemapNav from '../components/SitemapNav.astro';
import TaxonomyAside from '../components/TaxonomyAside.astro';
import { getAllPosts, postUrl as getPostUrl, getSummary } from '../lib/posts';
</Fragment>
<Fragment slot="menu">
- <BlogNav currentUrl={postUrl} />
+ <SitemapNav currentUrl={postUrl} />
</Fragment>
<Fragment slot="marginalcontent">
---
import BaseLayout from '../layouts/BaseLayout.astro';
import Breadcrumb from '../components/Breadcrumb.astro';
-import BlogNav from '../components/BlogNav.astro';
+import SitemapNav from '../components/SitemapNav.astro';
import { aboutSection, findAboutPath } from '../lib/about';
const _path = findAboutPath(aboutSection, '/about.html')!;
</Fragment>
<Fragment slot="menu">
- <BlogNav currentUrl="/about.html" />
+ <SitemapNav currentUrl="/about.html" />
</Fragment>
<Fragment slot="marginalcontent">
---
import BaseLayout from '../layouts/BaseLayout.astro';
import Breadcrumb from '../components/Breadcrumb.astro';
-import BlogNav from '../components/BlogNav.astro';
+import SitemapNav from '../components/SitemapNav.astro';
import { aboutSection, findAboutPath } from '../lib/about';
const _path = findAboutPath(aboutSection, '/agb.html')!;
</Fragment>
<Fragment slot="menu">
- <BlogNav currentUrl="/agb.html" />
+ <SitemapNav currentUrl="/agb.html" />
</Fragment>
</BaseLayout>
---
import BaseLayout from '../../../layouts/BaseLayout.astro';
import Breadcrumb from '../../../components/Breadcrumb.astro';
-import BlogNav from '../../../components/BlogNav.astro';
+import SitemapNav from '../../../components/SitemapNav.astro';
import { getAllPosts, postUrl, wordCount } from '../../../lib/posts';
export async function getStaticPaths() {
</Fragment>
<Fragment slot="menu">
- <BlogNav currentUrl={currentUrl} />
+ <SitemapNav currentUrl={currentUrl} />
</Fragment>
</BaseLayout>
---
import BaseLayout from '../../../layouts/BaseLayout.astro';
import Breadcrumb from '../../../components/Breadcrumb.astro';
-import BlogNav from '../../../components/BlogNav.astro';
+import SitemapNav from '../../../components/SitemapNav.astro';
import { getAllPosts, postUrl, wordCount } from '../../../lib/posts';
const currentUrl = '/blog/archive/';
</Fragment>
<Fragment slot="menu">
- <BlogNav currentUrl={currentUrl} />
+ <SitemapNav currentUrl={currentUrl} />
</Fragment>
</BaseLayout>
import { getCollection } from 'astro:content';
import BaseLayout from '../../../layouts/BaseLayout.astro';
import Breadcrumb from '../../../components/Breadcrumb.astro';
-import BlogNav from '../../../components/BlogNav.astro';
+import SitemapNav from '../../../components/SitemapNav.astro';
const currentUrl = '/blog/categories/';
</Fragment>
<Fragment slot="menu">
- <BlogNav currentUrl={currentUrl} />
+ <SitemapNav currentUrl={currentUrl} />
</Fragment>
</BaseLayout>
---
import BaseLayout from '../../layouts/BaseLayout.astro';
import Breadcrumb from '../../components/Breadcrumb.astro';
-import BlogNav from '../../components/BlogNav.astro';
+import SitemapNav from '../../components/SitemapNav.astro';
import { getAllPosts, postUrl, getSummary, wordCount } from '../../lib/posts';
const currentUrl = '/blog/';
</Fragment>
<Fragment slot="menu">
- <BlogNav currentUrl={currentUrl} />
+ <SitemapNav currentUrl={currentUrl} />
</Fragment>
</BaseLayout>
import { getCollection } from 'astro:content';
import BaseLayout from '../../../layouts/BaseLayout.astro';
import Breadcrumb from '../../../components/Breadcrumb.astro';
-import BlogNav from '../../../components/BlogNav.astro';
+import SitemapNav from '../../../components/SitemapNav.astro';
const currentUrl = '/blog/tags/';
</Fragment>
<Fragment slot="menu">
- <BlogNav currentUrl={currentUrl} />
+ <SitemapNav currentUrl={currentUrl} />
</Fragment>
</BaseLayout>
---
import BaseLayout from '../../layouts/BaseLayout.astro';
import Breadcrumb from '../../components/Breadcrumb.astro';
-import BlogNav from '../../components/BlogNav.astro';
+import SitemapNav from '../../components/SitemapNav.astro';
import { getAllPosts, postUrl, wordCount } from '../../lib/posts';
export async function getStaticPaths() {
</Fragment>
<Fragment slot="menu">
- <BlogNav currentUrl={currentUrl} />
+ <SitemapNav currentUrl={currentUrl} />
</Fragment>
</BaseLayout>
---
import BaseLayout from '../layouts/BaseLayout.astro';
import Breadcrumb from '../components/Breadcrumb.astro';
-import BlogNav from '../components/BlogNav.astro';
+import SitemapNav from '../components/SitemapNav.astro';
import { aboutSection, findAboutPath } from '../lib/about';
const _path = findAboutPath(aboutSection, '/contact.html')!;
</Fragment>
<Fragment slot="menu">
- <BlogNav currentUrl="/contact.html" />
+ <SitemapNav currentUrl="/contact.html" />
</Fragment>
</BaseLayout>
---
import BaseLayout from '../layouts/BaseLayout.astro';
import Breadcrumb from '../components/Breadcrumb.astro';
-import BlogNav from '../components/BlogNav.astro';
+import SitemapNav from '../components/SitemapNav.astro';
import { aboutSection, findAboutPath } from '../lib/about';
const _path = findAboutPath(aboutSection, '/datenschutz.html')!;
</Fragment>
<Fragment slot="menu">
- <BlogNav currentUrl="/datenschutz.html" />
+ <SitemapNav currentUrl="/datenschutz.html" />
</Fragment>
<Fragment slot="footerlinks">
---
import BaseLayout from '../layouts/BaseLayout.astro';
import Breadcrumb from '../components/Breadcrumb.astro';
-import BlogNav from '../components/BlogNav.astro';
+import SitemapNav from '../components/SitemapNav.astro';
import { aboutSection, findAboutPath } from '../lib/about';
const _path = findAboutPath(aboutSection, '/google-analytics.html')!;
</Fragment>
<Fragment slot="menu">
- <BlogNav currentUrl="/google-analytics.html" />
+ <SitemapNav currentUrl="/google-analytics.html" />
</Fragment>
</BaseLayout>
---
import BaseLayout from '../layouts/BaseLayout.astro';
import Breadcrumb from '../components/Breadcrumb.astro';
-import BlogNav from '../components/BlogNav.astro';
+import SitemapNav from '../components/SitemapNav.astro';
import { aboutSection, findAboutPath } from '../lib/about';
const _path = findAboutPath(aboutSection, '/haftung-inhalte.html')!;
</Fragment>
<Fragment slot="menu">
- <BlogNav currentUrl="/haftung-inhalte.html" />
+ <SitemapNav currentUrl="/haftung-inhalte.html" />
</Fragment>
</BaseLayout>
---
import BaseLayout from '../layouts/BaseLayout.astro';
import Breadcrumb from '../components/Breadcrumb.astro';
-import BlogNav from '../components/BlogNav.astro';
+import SitemapNav from '../components/SitemapNav.astro';
import { aboutSection, findAboutPath } from '../lib/about';
const _path = findAboutPath(aboutSection, '/haftung-links.html')!;
</Fragment>
<Fragment slot="menu">
- <BlogNav currentUrl="/haftung-links.html" />
+ <SitemapNav currentUrl="/haftung-links.html" />
</Fragment>
</BaseLayout>
---
import BaseLayout from '../layouts/BaseLayout.astro';
import Breadcrumb from '../components/Breadcrumb.astro';
-import BlogNav from '../components/BlogNav.astro';
+import SitemapNav from '../components/SitemapNav.astro';
import { aboutSection, findAboutPath } from '../lib/about';
const _path = findAboutPath(aboutSection, '/impressum.html')!;
</Fragment>
<Fragment slot="menu">
- <BlogNav currentUrl="/impressum.html" />
+ <SitemapNav currentUrl="/impressum.html" />
</Fragment>
<Fragment slot="footerlinks">
---
import BaseLayout from '../layouts/BaseLayout.astro';
-import BlogNav from '../components/BlogNav.astro';
+import SitemapNav from '../components/SitemapNav.astro';
import { getAllPosts, postUrl } from '../lib/posts';
const currentUrl = '/';
</Fragment>
<Fragment slot="menu">
- <BlogNav currentUrl={currentUrl} />
+ <SitemapNav currentUrl={currentUrl} />
</Fragment>
</BaseLayout>
---
import BaseLayout from '../../layouts/BaseLayout.astro';
import Breadcrumb from '../../components/Breadcrumb.astro';
-import BlogNav from '../../components/BlogNav.astro';
+import SitemapNav from '../../components/SitemapNav.astro';
import { getVisibleProjectNavTrees } from '../../lib/projects';
const projects = await getVisibleProjectNavTrees();
</Fragment>
<Fragment slot="menu">
- <BlogNav currentUrl="/projects/" />
+ <SitemapNav currentUrl="/projects/" />
</Fragment>
</BaseLayout>
---
import BaseLayout from '../../layouts/BaseLayout.astro';
import Breadcrumb from '../../components/Breadcrumb.astro';
-import BlogNav from '../../components/BlogNav.astro';
+import SitemapNav from '../../components/SitemapNav.astro';
import { getAllPosts, postUrl, wordCount } from '../../lib/posts';
export async function getStaticPaths() {
</Fragment>
<Fragment slot="menu">
- <BlogNav currentUrl={currentUrl} />
+ <SitemapNav currentUrl={currentUrl} />
</Fragment>
</BaseLayout>
---
import BaseLayout from '../layouts/BaseLayout.astro';
import Breadcrumb from '../components/Breadcrumb.astro';
-import BlogNav from '../components/BlogNav.astro';
+import SitemapNav from '../components/SitemapNav.astro';
import { aboutSection, findAboutPath } from '../lib/about';
const _path = findAboutPath(aboutSection, '/urheberrechte.html')!;
</Fragment>
<Fragment slot="menu">
- <BlogNav currentUrl="/urheberrechte.html" />
+ <SitemapNav currentUrl="/urheberrechte.html" />
</Fragment>
</BaseLayout>