Files
lernbar/src/lib/inhalte/markdown.ts
T
Florian WildeandClaude Opus 5 1af5613e9b Baustein 2: Inhalte einlesen und pruefen, Handouts, Suche, Glossar
Die fachlichen Inhalte liegen als Dateien unter content/ und werden beim
Start eingelesen, geprueft und in den Speicher geladen.

- Inhaltslader mit Zod-Schemata und Querverweispruefung
- Fehlermeldungen nennen Datei und Zeile, gesammelt statt beim ersten
  Fehler geworfen; die Anwendung startet bei Fehlern nicht
- npm run inhalte:pruefen prueft ohne Start
- Handout-Ansicht mit festem Aufbau, Uebersicht nach Themenbloecken
- Volltextsuche mit Gewichtung und Trefferausschnitt
- Gelesen-Markierung je Handout
- Glossar mit Suche und automatischer Verlinkung in den Handouts
- Beispielinhalte: 4 Wochen, 12 Handouts, 24 Merkkarten, 4 Aufgaben,
  4 Checklisten, 2 Lernzielkontrollen, 24 Glossareintraege
- rahmenplan.yaml aus FIAusbV 2020 und KMK-Rahmenlehrplan, mit Fundstellen
- gray-matter entfernt, YAML-Kopf wird selbst herausgeloest
- npm run screenshots nimmt die Bilder fuer die Anleitung auf
- 39 neue Tests, insgesamt 68

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 07:56:38 +02:00

75 lines
2.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import MarkdownIt from 'markdown-it';
/**
* Markdown wird beim Einlesen einmal nach HTML übersetzt und liegt dann fertig
* im Speicher. Die Inhalte stammen aus dem Repository, sind also vertrauenswürdig
* – trotzdem bleibt rohes HTML abgeschaltet, damit sich niemand daran gewöhnt.
*/
const uebersetzer = new MarkdownIt({
html: false,
linkify: false,
typographer: false,
breaks: false,
});
export function nachHtml(markdown: string): string {
return uebersetzer.render(markdown).trim();
}
export function nachHtmlInline(markdown: string): string {
return uebersetzer.renderInline(markdown).trim();
}
/** Entfernt Auszeichnung, damit der Text durchsucht werden kann. */
export function nurText(markdown: string): string {
return markdown
.replace(/```[\s\S]*?```/g, ' ')
.replace(/`([^`]*)`/g, '$1')
.replace(/!?\[([^\]]*)\]\([^)]*\)/g, '$1')
.replace(/^[#>\-*+|]+\s?/gm, ' ')
.replace(/[*_~]/g, '')
.replace(/\s+/g, ' ')
.trim();
}
export type Abschnitt = { titel: string; markdown: string; html: string };
/**
* Zerlegt den Rumpf eines Handouts an den Überschriften der zweiten Ebene.
* Der feste Aufbau eines Handouts – Das Wesentliche, Übersicht, Prüfungsfrage –
* wird dadurch für die Darstellung einzeln greifbar.
*/
export function inAbschnitte(rumpf: string): Abschnitt[] {
const zeilen = rumpf.split('\n');
const abschnitte: Abschnitt[] = [];
let titel = '';
let puffer: string[] = [];
const ablegen = () => {
const markdown = puffer.join('\n').trim();
if (titel || markdown) {
abschnitte.push({ titel, markdown, html: nachHtml(markdown) });
}
puffer = [];
};
for (const zeile of zeilen) {
const treffer = /^##\s+(.+?)\s*$/.exec(zeile);
if (treffer) {
ablegen();
titel = treffer[1]!;
} else {
puffer.push(zeile);
}
}
ablegen();
return abschnitte.filter((a) => a.titel !== '' || a.markdown !== '');
}
/** Sucht einen Abschnitt anhand seiner Überschrift, unabhängig von Groß- und Kleinschreibung. */
export function abschnitt(abschnitte: Abschnitt[], titel: string): Abschnitt | undefined {
const gesucht = titel.toLocaleLowerCase('de-DE');
return abschnitte.find((a) => a.titel.toLocaleLowerCase('de-DE') === gesucht);
}