Navigation Script – So funktioniert's
Eine verständliche Erklärung, wie das generate-navigation.js Script alle Navigationen automatisch erstellt und aktualisiert.
Was macht das Script?
Das generate-navigation.js Script ist das Herzstück der automatischen
Navigation auf DevPanicZone. Es durchsucht alle Tutorial-Dateien und erstellt automatisch:
- Header & Footer auf allen Seiten
- Main Navigation (Dropdown-Menü mit allen Kategorien)
- Breadcrumbs (Navigationspfad: Home › Kategorie › Tutorial)
- Prev/Next Navigation (Vorheriges/Nächstes Tutorial)
- Sidebar TOC (Inhaltsverzeichnis aus Überschriften)
- Sidebar Tutorial-Liste (Alle Tutorials der Kategorie)
- Tutorial-Buttons (Übersicht auf Kategorieseiten)
Stell dir vor, du müsstest bei jedem neuen Tutorial ALLE anderen Tutorial-Seiten manuell
aktualisieren! Das Script macht das automatisch mit einem einzigen Befehl: npm run build
Aufbau des Scripts
Das Script ist in logische Abschnitte unterteilt. Lass uns jeden einzelnen durchgehen!
1. Konfiguration & Setup
Ganz am Anfang werden die wichtigsten Einstellungen definiert:
const fs = require('fs');
const path = require('path');
const { JSDOM } = require('jsdom');
// Pfade vom scripts/ Ordner aus
const tutorialsDir = path.join(__dirname, '..', 'tutorials');
const assetsDir = path.join(__dirname, '..', 'assets');
// Blacklist: Diese Ordner werden ignoriert
const BLACKLIST_FOLDERS = ['noupload', 'node_modules', '.git', 'assets'];
Was passiert hier?
fs→ Zum Lesen und Schreiben von Dateienpath→ Zum Arbeiten mit DateipfadenJSDOM→ Zum Parsen und Manipulieren von HTML
tutorialsDir→ Zeigt auf den/tutorialsOrdnerBLACKLIST_FOLDERS→ Ordner, die das Script ignorieren soll
Das Script liegt in /scripts/generate-navigation.js. Um auf
/tutorials zu kommen, muss es einen Ordner HOCH .. und dann in
den tutorials Ordner.
2. Deine Konfiguration
Diese Konstanten definierst DU – sie steuern, wie die Navigationen aussehen:
// Kategorie-Namen für Main-Navigation
const CATEGORY_NAMES = {
'html': 'HTML',
'css': 'CSS',
'javascript': 'JavaScript'
};
// Reihenfolge der Tutorials
const TUTORIAL_ORDER = {
'tutorials/html/html-basics': [
'html-basics.html',
'html-grundgeruest.html'
]
};
// Eigene Titel für Links
const CUSTOM_TITLES = {
'tutorials/html/html-basics/html-basics.html': 'Was ist HTML?'
};
Das bedeutet:
CATEGORY_NAMES→ Welche Kategorien im Dropdown erscheinenTUTORIAL_ORDER→ In welcher Reihenfolge Tutorials erscheinen (wichtig für Prev/Next!)CUSTOM_TITLES→ Alternative Namen für Links (z.B. statt "html-basics" → "Was ist HTML?")
3. Header & Footer Templates
Das Script hat fertige HTML-Templates für Header und Footer gespeichert:
const HEADER_TEMPLATE = `<!-- Header -->
<header class="site-header">
<div class="container">
<!-- Logo, Theme Toggle, Navigation... -->
</div>
</header>`;
const FOOTER_TEMPLATE = `<footer class="site-footer">
<div class="container">
<!-- Copyright, Links... -->
</div>
</footer>`;
Vorteil: Wenn du den Header ändern willst, änderst du ihn nur HIER – dann wird er auf ALLEN Seiten aktualisiert!
Hilfsfunktionen
Das Script hat kleine Helferlein, die bestimmte Aufgaben übernehmen:
findHtmlFiles() – HTML-Dateien finden
function findHtmlFiles(dir, fileList = []) {
const files = fs.readdirSync(dir);
files.forEach(file => {
const filePath = path.join(dir, file);
const stat = fs.statSync(filePath);
if (stat.isDirectory()) {
// Blacklist-Ordner ignorieren
if (BLACKLIST_FOLDERS.includes(file)) {
return;
}
// Rekursiv in Unterordner gehen
findHtmlFiles(filePath, fileList);
} else if (file.endsWith('.html')) {
fileList.push(filePath);
}
});
return fileList;
}
Was macht die Funktion?
- Liest alle Dateien in einem Ordner
- Ist es ein Ordner? → Prüfe, ob er auf der Blacklist steht
- Ist es keine Blacklist? → Gehe rekursiv in den Ordner (Funktion ruft sich selbst auf!)
- Ist es eine HTML-Datei? → Füge sie zur Liste hinzu
Die Funktion ruft sich selbst auf! Das ermöglicht es, durch ALLE Unterordner zu gehen, egal wie tief verschachtelt.
getCategoryFromPath() – Kategorie ermitteln
function getCategoryFromPath(filePath) {
const relativePath = path.relative(tutorialsDir, filePath);
const parts = relativePath.split(path.sep);
return parts[0]; // Erste Ebene (html, css, etc.)
}
Beispiel:
- Pfad:
/tutorials/html/html-basics/html-basics.html - Relative Pfad:
html/html-basics/html-basics.html
- Aufteilen:
['html', 'html-basics', 'html-basics.html'] - Erste Ebene:
html← Das ist die Kategorie!
getSubcategoryFromPath() – Unterkategorie ermitteln
function getSubcategoryFromPath(filePath) {
const relativePath = path.relative(tutorialsDir, filePath);
const parts = relativePath.split(path.sep);
if (parts.length >= 2) {
return 'tutorials/' + parts.slice(0, 2).join('/');
}
return 'tutorials/' + parts[0];
}
Beispiel:
- Pfad:
/tutorials/html/html-basics/html-basics.html - Parts:
['html', 'html-basics', 'html-basics.html']
- Erste 2 Ebenen:
['html', 'html-basics'] - Ergebnis:
tutorials/html/html-basics
Warum wichtig?
Das Script braucht diese Info, um in TUTORIAL_ORDER
die richtige Reihenfolge zu finden!
Navigation-Generierung
Jetzt wird's interessant! Diese Funktionen erstellen die eigentlichen Navigationen:
generateMainNavigation() – Dropdown-Menü
function generateMainNavigation() {
const categories = Object.keys(CATEGORY_NAMES);
const navItems = [];
categories.forEach(category => {
const categoryName = CATEGORY_NAMES[category];
// Hat diese Kategorie Tutorials?
const hasEntry = Object.keys(TUTORIAL_ORDER).some(key =>
key.startsWith('tutorials/' + category + '/')
);
if (hasEntry) {
navItems.push(
`<li><a href="/tutorials/${category}/">${categoryName}</a></li>`
);
}
});
return navItems;
}
Schritt für Schritt:
- Hole alle Kategorien aus
CATEGORY_NAMES - Für jede Kategorie: Prüfe, ob es Tutorials dafür gibt (in
TUTORIAL_ORDER) - Wenn ja: Erstelle einen Link für das Dropdown
- Gib alle Links zurück
generateBreadcrumbs() – Navigationspfad
function generateBreadcrumbs(filePath, currentTitle) {
const category = getCategoryFromPath(filePath);
const categoryName = CATEGORY_NAMES[category] || category;
const basename = path.basename(filePath);
// Kategorieseite: Home › Kategorie
if (basename === 'index.html') {
return `<a href="/">Home</a>
<span>›</span>
<span>${categoryName}</span>`;
}
// Tutorial-Seite: Home › Kategorie › Tutorial
return `<a href="/">Home</a>
<span>›</span>
<a href="/tutorials/${category}/">${categoryName}</a>
<span>›</span>
<span>${currentTitle}</span>`;
}
Unterschied:
- Kategorieseite:
Home › CSS(CSS ist nicht klickbar) - Tutorial-Seite:
Home › CSS › Flexbox(CSS ist klickbar!)
generatePrevNextNav() – Vorheriges/Nächstes
function generatePrevNextNav(filePath) {
const subcategory = getSubcategoryFromPath(filePath);
const tutorials = getOrderedTutorials(subcategory);
const currentIndex = tutorials.findIndex(t => t.filePath === filePath);
if (currentIndex === -1) return '';
const prev = currentIndex > 0 ? tutorials[currentIndex - 1] : null;
const next = currentIndex < tutorials.length - 1 ?
tutorials[currentIndex + 1] : null;
let navHtml = '';
// Vorheriges Tutorial
if (prev) {
navHtml += `<div class="tutorial-nav-prev">
<a href="${prev.url}">
<span>← Vorheriges</span>
<span>${prev.title}</span>
</a>
</div>`;
} else {
navHtml += '<div class="tutorial-nav-prev"></div>';
}
// Nächstes Tutorial (DIREKT anschließend ohne Leerzeichen!)
if (next) {
navHtml += `<div class="tutorial-nav-next">
<a href="${next.url}">
<span>Nächstes →</span>
<span>${next.title}</span>
</a>
</div>`;
} else {
navHtml += '<div class="tutorial-nav-next"></div>';
}
return navHtml;
}
Logik:
- Finde die Unterkategorie (z.B.
tutorials/html/html-basics) - Hole die Tutorial-Liste aus
TUTORIAL_ORDER - Finde die Position des aktuellen Tutorials in der Liste
- Vorheriges = Index - 1, Nächstes = Index + 1
- Erstelle HTML für beide Buttons (oder leere divs, wenn es keinen Prev/Next gibt)
Die beiden <div>-Elemente müssen DIREKT aneinander ohne
Leerzeichen, damit
das CSS sie nebeneinander anzeigt (Flexbox)!
addIdsToHeadingsAndGenerateSidebar() – Sidebar TOC
Diese Funktion macht mehrere Dinge auf einmal:
- Findet alle
<h2>und<h3>Überschriften - Fügt ihnen IDs hinzu (falls keine vorhanden)
- Prüft ob Custom Titles in TOC_CUSTOM_TITLES definiert sind
- Erstellt die Sidebar-Links mit Custom oder Original-Titel
function addIdsToHeadingsAndGenerateSidebar(html, filename) {
const dom = new JSDOM(html);
const doc = dom.window.document;
const headings = doc.querySelectorAll('h2, h3');
const anchors = [];
const usedIds = new Set();
headings.forEach(heading => {
// Überschriften mit class="no-toc" überspringen
if (heading.classList.contains('no-toc')) return;
let id = heading.id;
// ID generieren wenn keine vorhanden
if (!id) {
id = heading.textContent.trim().toLowerCase()
.replace(/[äöüÄÖÜß]/g, match => {
const map = {
'ä': 'ae', 'ö': 'oe', 'ü': 'ue',
'Ä': 'Ae', 'Ö': 'Oe', 'Ü': 'Ue', 'ß': 'ss'
};
return map[match];
})
.replace(/[^\w\s-]/g, '') // Sonderzeichen entfernen
.replace(/\s+/g, '-'); // Leerzeichen → Bindestriche
// Duplikate vermeiden
let finalId = id;
let counter = 1;
while (usedIds.has(finalId)) {
finalId = `${id}-${counter}`;
counter++;
}
// ID ins Heading einfügen!
heading.id = finalId;
id = finalId;
}
usedIds.add(id);
const level = heading.tagName === 'H2' ? 'toc-level-1' : 'toc-level-2';
// Custom Title verwenden falls vorhanden, sonst Original-Text
let displayTitle = heading.textContent.trim();
if (TOC_CUSTOM_TITLES[filename] && TOC_CUSTOM_TITLES[filename][id]) {
displayTitle = TOC_CUSTOM_TITLES[filename][id];
}
// Sidebar-Link erstellen
anchors.push(
`<li class="${level}"><a href="#${id}">${displayTitle}</a></li>`
);
});
return {
html: dom.serialize(), // HTML mit IDs!
anchors: anchors.join('\n')
};
}
Beispiel Normal:
- Überschrift:
<h2>Was ist CSS?</h2> - Wird zu:
<h2 id="was-ist-css">Was ist CSS?</h2> - Sidebar-Link:
<a href="#was-ist-css">Was ist CSS?</a>
Beispiel mit Custom Title:
- Überschrift:
<h2 id="die-top-15-tags">HTML Cheat Sheet – Die Top 15 Tags</h2> - Custom Title in TOC_CUSTOM_TITLES:
'die-top-15-tags': 'Top 15 Tags' - Sidebar-Link:
<a href="#die-top-15-tags">Top 15 Tags</a>(gekürzt!)
Lange Überschriften können in der Sidebar zu breit werden. Mit TOC_CUSTOM_TITLES kannst du kurze, prägnante Titel für die Sidebar definieren, während die Original-Überschrift im Haupttext erhalten bleibt:
const TOC_CUSTOM_TITLES = {
'html-basics.html': {
'die-top-15-tags': 'Top 15 Tags',
'html-attribute-erklaert': 'HTML Attribute'
},
'css-farben.html': {
'rgb-rgba-hex': 'Farbformate'
}
};
Wichtig:
Die ID muss die finale ID sein (nach
Slug-Generierung), also z.B. 'die-top-15-tags' nicht
'die-Top-15-Tags'.
IDs wie #für-anfänger können in URLs Probleme machen. Besser:
#fuer-anfaenger
HTML aktualisieren
Die Hauptfunktion updateHtmlFile() koordiniert alle Updates:
function updateHtmlFile(filePath) {
// 1. HTML-Datei einlesen
let html = fs.readFileSync(filePath, 'utf8');
// 2. Metadaten sammeln
const category = getCategoryFromPath(filePath);
const basename = path.basename(filePath);
const currentTitle = CUSTOM_TITLES[basename] || extractTitle(html);
// 3. IDs zu Headings hinzufügen (ZUERST!)
let sidebarAnchorsHtml = '';
if (basename !== 'index.html') {
const result = addIdsToHeadingsAndGenerateSidebar(html);
html = result.html; // Nutze das HTML mit IDs!
sidebarAnchorsHtml = result.anchors;
}
// 4. Header aktualisieren
html = updateHeader(html);
// 5. Footer aktualisieren
html = updateFooter(html);
// 6. Tutorial-Buttons (nur für Kategorieseiten)
if (basename === 'index.html') {
html = updateTutorialButtons(html, category);
}
// 7. Main Navigation einfügen
const mainNavItems = generateMainNavigation();
html = html.replace(
/<!-- Main-Nav-Start -->.*?<!-- Main-Nav-End -->/s,
`<!-- Main-Nav-Start -->\n${mainNavItems.join('\n')}\n<!-- Main-Nav-End -->`
);
// 8. Breadcrumbs einfügen
if (isInTutorials) {
const breadcrumbs = generateBreadcrumbs(filePath, currentTitle);
html = html.replace(
/<nav class="breadcrumbs">.*?<\/nav>/s,
`<nav class="breadcrumbs">${breadcrumbs}</nav>`
);
}
// 9. Prev/Next Navigation
if (basename !== 'index.html') {
const prevNext = generatePrevNextNav(filePath);
html = html.replace(
/<nav class="tutorial-nav">.*?<\/nav>/s,
`<nav class="tutorial-nav">${prevNext}</nav>`
);
}
// 10. Sidebar Anchors
if (basename !== 'index.html') {
html = html.replace(
/<!-- Sidebar-Anchor-Start -->.*?<!-- Sidebar-Anchor-End -->/s,
`<!-- Sidebar-Anchor-Start -->\n${sidebarAnchorsHtml}\n<!-- Sidebar-Anchor-End -->`
);
}
// 11. Sidebar Navigation List
if (basename !== 'index.html') {
const navList = generateSidebarNavList(filePath);
html = html.replace(
/<ul class="sidebar-nav-list">.*?<\/ul>/s,
`<ul class="sidebar-nav-list">\n${navList}\n</ul>`
);
}
// 12. Datei speichern
fs.writeFileSync(filePath, html, 'utf8');
}
- Zuerst IDs zu Headings hinzufügen
- Dann Header/Footer ersetzen
- Danach alle Navigationen einfügen
- Zum Schluss Datei speichern
Die Sidebar-Links verweisen auf IDs (#meine-ueberschrift). Diese
IDs müssen also
existieren, BEVOR die Sidebar generiert wird!
Regex Patterns erklärt
Das Script nutzt Regular Expressions (Regex), um HTML zu finden und zu ersetzen. Das sieht kompliziert aus, ist aber logisch:
Pattern für Kommentar-Marker
/<!-- Main-Nav-Start -->.*?<!-- Main-Nav-End -->/s
Aufschlüsselung:
/→ Start des Regex<!-- Main-Nav-Start -->→ Findet den Start-Kommentar.*?→ Findet ALLES dazwischen (aber so wenig wie möglich)<!-- Main-Nav-End -->→ Findet den End-Kommentar/s→ "Dotall" Mode –.matched auch Zeilenumbrüche
Beispiel:
<!-- Main-Nav-Start -->
<li><a href="/tutorials/html/">HTML</a></li>
<li><a href="/tutorials/css/">CSS</a></li>
<!-- Main-Nav-End -->
Das Regex findet ALLES zwischen den Markern und ersetzt es mit neuem HTML!
Pattern für HTML-Tags
/<nav class="breadcrumbs">.*?<\/nav>/s
Aufschlüsselung:
<nav class="breadcrumbs">→ Findet das öffnende Tag.*?→ Findet den Inhalt<\/nav>→ Findet das schließende Tag (/muss escaped werden!)/s→ Dotall Mode
Die Hauptlogik
Ganz am Ende des Scripts steht die Funktion, die alles startet:
function generateAllNavigation() {
console.log('🚀 Starte Navigation-Generierung...\n');
// 1. Alle HTML-Dateien finden
const tutorialFiles = findHtmlFiles(tutorialsDir);
console.log(`📄 Gefundene Tutorial-Dateien: ${tutorialFiles.length}\n`);
// 2. Jede Datei aktualisieren
tutorialFiles.forEach(filePath => {
const relativePath = path.relative(tutorialsDir, filePath);
console.log(` ✓ Aktualisiere: ${relativePath}`);
updateHtmlFile(filePath);
});
console.log('\n✅ Navigation erfolgreich generiert!');
}
// Script ausführen!
generateAllNavigation();
Ablauf:
- Finde alle HTML-Dateien im
/tutorialsOrdner - Für jede gefundene Datei: Rufe
updateHtmlFile()auf - Zeige Fortschritt im Terminal
- Fertig!
Mit npm run build führst du dieses Script aus und es aktualisiert
ALLE Seiten
automatisch! 🎉
Best Practices & Tipps
1. Immer vor dem Committen ausführen
Gewöhne dir an, vor jedem Git-Commit npm run build auszuführen. So
sind alle
Navigationen garantiert aktuell!
2. Teste nach Änderungen
Nach größeren Änderungen am Script:
- Führe
npm run buildaus - Öffne mehrere Tutorial-Seiten
- Prüfe Prev/Next, Sidebar, Breadcrumbs
- Teste die Links!
3. Backup vor Script-Änderungen
Wenn du das Script änderst:
- Erstelle eine Kopie:
generate-navigation-backup.js - Teste deine Änderungen auf einer Datei
- Erst dann auf allen Dateien ausführen
4. Console-Logs nutzen
Füge bei Problemen Console-Logs hinzu:
console.log('Aktuelle Kategorie:', category);
console.log('Gefundene Tutorials:', tutorials);
console.log('Current Index:', currentIndex);
Häufige Probleme
Problem: Script findet keine Dateien
Ursache:
- Blacklist oder Pfad falsch
Lösung:
- Prüfe
BLACKLIST_FOLDERS - Prüfe ob Script in
/scripts/liegt - Teste:
console.log(tutorialsDir)
Problem: Prev/Next zeigt falsches Tutorial
Ursache:
- Reihenfolge in
TUTORIAL_ORDERfalsch
Lösung:
- Öffne
generate-navigation.js - Finde die richtige Unterkategorie
- Korrigiere die Reihenfolge
- Führe
npm run buildaus
Problem: Sidebar-Links führen nirgends hin
Ursache:
- IDs wurden nicht zu Headings hinzugefügt
Lösung:
- Prüfe ob
addIdsToHeadingsAndGenerateSidebarausgeführt wird - Prüfe ob
html = result.htmlverwendet wird - Führe
npm run buildnochmal aus
Problem: Dropdown fehlt eine Kategorie
Ursache:
- Keine Tutorials in
TUTORIAL_ORDERfür diese Kategorie
Lösung:
- Füge Tutorials zu
TUTORIAL_ORDERhinzu - Oder: Entferne die Kategorie aus
CATEGORY_NAMES
Zusammenfassung
Das generate-navigation.js Script ist ein mächtiges Werkzeug, das dir
viel manuelle
Arbeit abnimmt:
Was es macht:
- Findet alle HTML-Dateien
- Erstellt Header & Footer
- Generiert alle Navigationen
- Fügt IDs zu Überschriften hinzu
- Aktualisiert alle Seiten
Was du tun musst:
- Neue Tutorials in
TUTORIAL_ORDEReintragen - Optional: Custom-Titel in
CUSTOM_TITLES npm run buildausführen- Fertig!
Mehr aus DevPanicZone!
Tutorials werden geladen...