feat(bausteine): Abschnitte, die mehrere Vorlagen teilen (#57)
Prüfung / pruefung (push) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s

This commit was merged in pull request #57.
This commit is contained in:
2026-10-04 17:07:12 +00:00
parent b688e2ebc8
commit 80870133b8
24 changed files with 2862 additions and 46 deletions
+84 -1
View File
@@ -41,6 +41,18 @@ export type EditorAbschnitt = {
readonly kennung: string;
readonly titel: string;
readonly schritte: readonly EditorSchritt[];
/**
* Gesetzt, wenn dieser Abschnitt ein eingesetzter **Baustein** ist (#030).
*
* Dann sind `titel` und `schritte` nur Anzeige: beim Veroeffentlichen setzt
* der Dienst die Schritte des Bausteins ein. Bearbeiten laesst sich ein
* solcher Abschnitt hier nicht — das geschieht am Baustein.
*/
readonly baustein?: {
readonly versionId: string;
readonly name: string;
readonly version: number;
};
};
export type EditorVariable = {
@@ -207,6 +219,63 @@ export function neuerAbschnitt(): EditorAbschnitt {
return { kennung: neueKennung("abschnitt"), titel: "", schritte: [] };
}
/**
* Ein Abschnitt, der einen Baustein einsetzt (#030).
*
* Titel und Schritte kommen zur Anzeige mit; gespeichert wird nur der Verweis,
* und beim Veroeffentlichen setzt der Dienst die Schritte des Bausteins ein.
*/
export function neuerBausteinAbschnitt(baustein: {
readonly bausteinVersionId: string;
readonly name: string;
readonly titel: string;
readonly version: number;
readonly anzahlSchritte: number;
}): EditorAbschnitt {
return {
kennung: neueKennung("abschnitt"),
titel: baustein.titel,
baustein: { versionId: baustein.bausteinVersionId, name: baustein.name, version: baustein.version },
// Platzhalter, damit die Anzahl stimmt. Gespeichert werden sie nicht.
schritte: Array.from({ length: baustein.anzahlSchritte }, () => neuerSchritt()),
};
}
/**
* Traegt Name und Fassung der verwiesenen Bausteine nach.
*
* Die gespeicherte Struktur kennt nur die Kennung; die Seite holt die Liste der
* einsetzbaren Bausteine getrennt und reicht sie hier ein. Ein Verweis, dessen
* Baustein nicht mehr einsetzbar ist (archiviert, oder eine aeltere Fassung),
* behaelt seine Kennung und bekommt einen erkennbaren Namen — verschwinden darf
* er nicht, sonst waere er beim naechsten Speichern still weg.
*/
export function bausteineAnreichern(
aufbau: EditorAufbau,
bekannte: readonly {
readonly bausteinVersionId: string;
readonly name: string;
readonly version: number;
}[],
): EditorAufbau {
const nachId = new Map(bekannte.map((eintrag) => [eintrag.bausteinVersionId, eintrag]));
return {
...aufbau,
abschnitte: aufbau.abschnitte.map((abschnitt) => {
if (abschnitt.baustein === undefined) return abschnitt;
const treffer = nachId.get(abschnitt.baustein.versionId);
return {
...abschnitt,
baustein: {
versionId: abschnitt.baustein.versionId,
name: treffer?.name ?? "(nicht mehr verfügbar)",
version: treffer?.version ?? 0,
},
};
}),
};
}
export function neueVariable(): EditorVariable {
return {
kennung: neueKennung("variable"),
@@ -233,6 +302,11 @@ export function alsEditorAufbau(struktur: Struktur): EditorAufbau {
kennung: neueKennung("abschnitt"),
// Die Nummer gehoert zur Position, nicht in den Titel — siehe titelOhneNummer.
titel: titelOhneNummer(abschnitt.titel),
// Den Namen und die Fassung kennt die Struktur nicht; die Seite reicht sie
// getrennt nach und setzt sie ueber `bausteineAnreichern` ein (#030).
...(abschnitt.bausteinVersionId === undefined
? {}
: { baustein: { versionId: abschnitt.bausteinVersionId, name: "", version: 0 } }),
schritte: abschnitt.schritte.map((schritt) => ({
kennung: neueKennung("schritt"),
titel: schritt.titel,
@@ -266,7 +340,16 @@ export function alsStruktur(aufbau: EditorAufbau): Struktur {
})),
abschnitte: aufbau.abschnitte.map((abschnitt) => ({
titel: abschnitt.titel,
schritte: abschnitt.schritte.map((schritt) => ({
...(abschnitt.baustein === undefined ? {} : { bausteinVersionId: abschnitt.baustein.versionId }),
/*
* Ein Baustein-Abschnitt gibt **keine** Schritte mit: sie kommen beim
* Veroeffentlichen aus dem Baustein. Die Schritte im Zustand sind nur
* Platzhalter, damit die Anzahl in der Karte stimmt — und sie haben keinen
* Titel. Wuerden sie mitgeschickt, wiese `SchrittSchema` den ganzen
* Entwurf ab, und das Speichern scheiterte (#030, gefunden beim
* Durchklicken des gebauten Stands).
*/
schritte: (abschnitt.baustein === undefined ? abschnitt.schritte : []).map((schritt) => ({
titel: schritt.titel,
hilfetext: schritt.hilfetext.trim() === "" ? undefined : schritt.hilfetext,
typ: schritt.typ,