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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
e856349cd4
commit
1af5613e9b
@@ -0,0 +1,74 @@
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user