\s*([\s\S]*?)\s*<\/blockquote>/g, (_, inhalt) => { let klasse = 'callout'; const markierung = inhalt.match(/^\s*\[!([A-ZÄÖÜ]+)\]\s*/); if (markierung && ALERTS[markierung[1]]) { klasse = ALERTS[markierung[1]]; inhalt = inhalt.replace(markierung[0], '
'); } // Eine einzelne Textzeile braucht kein
— das spart eine Zeile Abstand in der Box. const einzeln = inhalt.match(/^
([\s\S]*?)<\/p>$/); return `
${einzeln ? einzeln[1] : inhalt}`; }); } /** Markdown-Sprungziele wie #7-drucken auf die Abschnitts-IDs #s7 umschreiben. */ function ankerUmschreiben(html) { return html.replace(/href="#(\d+)-[^"]*"/g, (_, nummer) => `href="#s${nummer}"`); } function einrücken(html, stufe = ' ') { return html.split('\n').map((z) => (z.trim() ? stufe + z : z)).join('\n'); } const markdown = readFileSync(QUELLE, 'utf8'); // 1. Seitentitel aus der H1 const h1 = markdown.match(/^#\s+(.+)$/m); if (!h1) throw new Error('ANLEITUNG.md: H1-Überschrift (# Titel) fehlt'); const [titelKopf, titelUnter = ''] = h1[1].split(/\s+[–—-]\s+/, 2); // 2. In Blöcke je H2 zerlegen const bloecke = markdown .split(/^##\s+/m) .slice(1) .map((b) => { const umbruch = b.indexOf('\n'); return { titel: b.slice(0, umbruch).trim(), rumpf: b.slice(umbruch + 1).replace(/^\s*---\s*$/gm, '').trim(), }; }); // 3. Inhaltsverzeichnis: die Nummern der Listenpunkte werden zu #sconst tocBlock = bloecke.find((b) => b.titel.toLowerCase() === 'inhaltsverzeichnis'); if (!tocBlock) throw new Error('ANLEITUNG.md: Abschnitt „## Inhaltsverzeichnis" fehlt'); const toc = [...tocBlock.rumpf.matchAll(/^\s*(\d+)\.\s*\[([^\]]+)\]/gm)] .map(([, nr, text]) => ` ${text} `) .join('\n'); if (!toc) throw new Error('ANLEITUNG.md: Inhaltsverzeichnis enthält keine Einträge'); // 4. Nummerierte Abschnitte const abschnitte = []; for (const block of bloecke) { const kopf = block.titel.match(/^(\d+)\.\s+(.*)$/); if (!kopf) continue; // Inhaltsverzeichnis und alles ohne Nummer const [, nummer, titel] = kopf; const rumpf = ankerUmschreiben(calloutsUmschreiben(marked.parse(block.rumpf))); abschnitte.push( ` \n` + `\n` + ` ` ); } if (!abschnitte.length) throw new Error('ANLEITUNG.md: keine nummerierten Abschnitte (## 1. …) gefunden'); const html = readFileSync(VORLAGE, 'utf8') .replace('{{H1}}', titelKopf.trim()) .replace('{{SUBTITLE}}', titelUnter.trim()) .replace('{{TOC}}', toc) .replace('{{SECTIONS}}', abschnitte.join('\n\n')); writeFileSync(ZIEL, html, 'utf8'); console.log(`anleitung.html erzeugt — ${abschnitte.length} Abschnitte, ${html.length} Zeichen`);${nummer} ${titel}
\n` + `${einrücken(rumpf.trim())}\n` + `