feat: Umklappen im Diagramm schreibt die Faltmarke in den Text zurück
Erster Schritt der Schreibrichtung Diagramm -> Text. Damit ist der Text auch für
die Faltung die eine Quelle der Wahrheit (D14): Was man sieht, steht
geschrieben, und ein Neuladen stellt es wieder her. Kehrt die bisherige
Festlegung aus D34/D38 um („die Marken bestimmen nur den Anfangszustand").
Verfahren: minimal patchen, dann nachrechnen. Die Ableitung Text -> Zustand ist
nicht umkehrbar — mehrere Markensätze ergeben denselben Zustand, und `<` faltet
Knoten, die gar keine Marke tragen. Statt zu invertieren wird der Kandidat
befragt: `>` an die eine Zeile, initialCollapsed() darauf, mit dem Soll
vergleichen. Passt es, bleibt alles andere unangetastet — so überleben von Hand
gesetzte `<`. Passt es nicht, werden alle Marken neu gesetzt (das `<` löst sich
dabei auf). Passt auch das nicht (ein `!!!` holt seinen Knoten immer wieder
hervor), wird gar nicht geschrieben: Ein Text, der etwas anderes sagt als das
Bild, wäre schlimmer als eine fehlende Marke. initialCollapsed() bleibt die
einzige Stelle, die die Marken versteht.
Undo-fähig über execCommand('insertText') — gemessen: value= UND setRangeText
machen Strg+Z wirkungslos, wer tippt und dann faltet käme an sein Getipptes
nicht mehr heran. Falten ist damit ein eigener Undo-Schritt (geprüft: einmal
Strg+Z nimmt die Faltung, ein zweites das Getippte).
Zwei gemessene Fallen: Ein Textfeld mit display:none-Vorfahr nimmt kein
execCommand an (liefert false, obwohl activeElement es meldet) — auf Mobil ist
der Editor beim Falten genau das, deshalb schaltet ihn `writing-fold` für die
Dauer des synchronen Schreibens aus dem Bild heraus sichtbar. Und der nötige
Fokus zöge dort die Tastatur hoch, dagegen inputmode="none" wie beim Sprung
(D25); danach geht der Fokus an den Knoten zurück.
Bei schreibgeschütztem Text (Pad, D31) wird nicht geschrieben — dort trägt
weiter die Sitzungs-Überlagerung. Schreibmarke und Scrollstand bleiben erhalten.
Im Browser geprüft: `<` bleibt stehen, wenn es noch stimmt, und löst sich auf,
wenn nicht; Falten bei verborgenem Editor auf Mobil; Pad-Fall sitzungsweise;
Hin- und Herklappen führt zeichengenau zum Ausgangstext zurück.
7 neue Tests für setFoldMark (215 gesamt). SPEC §1/§9, D38 (Nachtrag 2) und
frontend/CLAUDE.md nachgezogen.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
38104a4c5d
commit
321bdc9dfa
@@ -2232,6 +2232,65 @@ per CSS zu verstecken). Die Kennzeichnung „▸ n“ ist Teil des Knotentexts u
|
||||
wandert von selbst in den SVG-Export; das ▾ offener Knoten wird dort und im
|
||||
Druck entfernt — es ist Bedienelement, keine Aussage über den Plan.
|
||||
|
||||
**Nachtrag 2 — Umklappen im Diagramm schreibt jetzt in den Text.** Damit wird
|
||||
die oben getroffene Festlegung („der Eingriff gilt für die Sitzung; er wird
|
||||
nicht gespeichert — die dauerhafte Aussage steht im Text") **umgekehrt**, und
|
||||
mit ihr die D34-Formulierung, die Marken bestimmten nur den Anfangszustand.
|
||||
Anlass ist der Wunsch nach bidirektionaler Interaktion zwischen Diagramm und
|
||||
Text; der Faltzustand ist dafür der richtige erste Fall: verlustfrei umkehrbar,
|
||||
ohne inhaltliche Aussage, und die Notation dafür gibt es schon.
|
||||
|
||||
Die Entscheidungen im Einzelnen, alle vom Nutzer getroffen:
|
||||
|
||||
- **Bei jedem Umschalten**, nicht auf Ansage. Der direkteste Zusammenhang
|
||||
zwischen Bild und Text; der Preis (unten) wurde bewusst in Kauf genommen.
|
||||
- **`<` bleibt erhalten, wo es noch stimmt.** Nicht auf reine `>`
|
||||
normalisieren: Handgeschriebene Marken sollen stehen bleiben.
|
||||
- **Geschrieben wird, wo der Text beschreibbar ist** — auch bei `?sourceUrl=`,
|
||||
wo es wie jede lokale Änderung bis zum nächsten Laden hält (D23). Nur beim
|
||||
Pad bleibt es sitzungsweise, weil das Textfeld dort schreibgeschützt ist
|
||||
(D31).
|
||||
- **Falten gilt als Änderung.** Ein mitgeliefertes Dokument wird dadurch als
|
||||
bearbeitet geführt, bekommt also keine neuen Fassungen mehr und zeigt
|
||||
„Original wiederherstellen" (D27). Bewusst keine Sonderregel: Wer faltet,
|
||||
ändert den Text, und der Text ist der Vertrag (D14).
|
||||
- **Undo-fähig geschrieben.** Nachgemessen: `value =` **und** `setRangeText`
|
||||
machen Strg+Z wirkungslos — wer tippt und dann faltet, käme an sein
|
||||
Getipptes nicht mehr heran. Nur `document.execCommand('insertText')` erhält
|
||||
die Historie; die API gilt als veraltet, funktioniert aber überall. Falten
|
||||
ist damit ein eigener Undo-Schritt (geprüft: Strg+Z nimmt die Faltung
|
||||
zurück, ein zweites das Getippte).
|
||||
|
||||
**Das Verfahren: minimal patchen, dann nachrechnen.** Die Ableitung Text →
|
||||
Zustand ist **nicht umkehrbar** — mehrere Markensätze ergeben denselben
|
||||
Zustand, und `<` erzeugt Faltungen an Knoten, die gar keine Marke tragen
|
||||
(oben). Statt sie zu invertieren, wird der Kandidat schlicht **befragt**:
|
||||
`>` an der einen Zeile setzen oder entfernen, `initialCollapsed()` darauf
|
||||
laufen lassen, mit dem Sollzustand vergleichen. Stimmt es, bleibt alles andere
|
||||
unangetastet — das ist der `<`-Erhalt. Stimmt es nicht, werden alle Marken neu
|
||||
gesetzt und erneut geprüft. Stimmt auch das nicht — etwa weil ein `!!!` seinen
|
||||
Knoten immer wieder hervorholt und „eingeklappt" dort gar nicht ausdrückbar
|
||||
ist —, wird **nicht geschrieben** und die Sitzungs-Überlagerung bleibt stehen.
|
||||
So kann nie ein Text entstehen, der etwas anderes sagt als das Bild.
|
||||
`initialCollapsed()` bleibt dadurch die einzige Stelle, die die Bedeutung der
|
||||
Marken kennt.
|
||||
|
||||
**Zwei Fallen, beide gemessen statt vermutet.** Erstens: Auf kleinem
|
||||
Bildschirm ist der Editor `display:none`, wenn das Diagramm vorn ist — und
|
||||
`execCommand` tut dann **nichts**, es liefert `false`, obwohl `activeElement`
|
||||
das Textfeld meldet. Für die Dauer des synchronen Schreibens wird der Editor
|
||||
deshalb absolut positioniert aus dem Bild geschoben sichtbar geschaltet
|
||||
(Klasse `writing-fold`, derselbe Griff wie `exporting` im Grafikexport);
|
||||
gezeichnet wird davon nichts. Zweitens: `execCommand` braucht den Fokus im
|
||||
Textfeld, und der zöge auf dem Telefon die Bildschirmtastatur hoch — dagegen
|
||||
`inputmode="none"` wie beim Sprung (D25), das der erste echte Tipp ins Feld
|
||||
wieder aufhebt. Anschließend geht der Fokus an den Knoten zurück.
|
||||
|
||||
Schreibmarke und Scrollstand werden gesichert und zurückgesetzt; verschoben
|
||||
wird nur, was **hinter** der Änderung lag. Gelingt das Schreiben, werden die
|
||||
Sitzungs-Überlagerungen geleert — sonst könnten sie den Text maskieren, der
|
||||
jetzt die Wahrheit ist.
|
||||
|
||||
**Nachtrag — der eingeklappte Knoten vertritt seinen Teilbaum auch auf dem
|
||||
günstigsten Pfad.** Oben steht, ein eingeklappter Zweig dürfe Pfadknoten
|
||||
verbergen, „die Inversion an den sichtbaren Knoten bleibt richtig". Das war zu
|
||||
|
||||
+20
-6
@@ -34,9 +34,10 @@ dieser Reihenfolge (wichtig für Kollisionsfreiheit):
|
||||
- `>` heißt: der Teilbaum dieses Knotens ist beim Öffnen **eingeklappt**.
|
||||
- `<` innerhalb eines eingeklappten Bereichs holt den **eigenen Teilbaum**
|
||||
gezielt wieder hervor (Mechanik: §9).
|
||||
- Die Marken bestimmen nur den **Anfangszustand** — im Diagramm wird danach
|
||||
unabhängig vom Text gefaltet (§9). Sie sagen nichts über Fortschritt (§4)
|
||||
oder Notwendigkeit (§3) und ändern weder Kosten noch Warnungen.
|
||||
- Die Marken beschreiben den **Faltzustand des Dokuments**: Beim Öffnen stellen
|
||||
sie ihn her, und Umklappen im Diagramm schreibt sie zurück (§9) — Text und
|
||||
Bild sagen dasselbe. Sie sagen nichts über Fortschritt (§4) oder
|
||||
Notwendigkeit (§3) und ändern weder Kosten noch Warnungen.
|
||||
|
||||
**Knoten-ID `#name`** — benennt einen Knoten im **ganzen Dokument** eindeutig;
|
||||
sie ist die Adresse für Abhängigkeiten und Beschreibungsblöcke (§11).
|
||||
@@ -665,9 +666,22 @@ fokussierten Knoten (WAI-ARIA-Baum-Idiom).
|
||||
hervorgeholten Teilbaums bleibt respektiert.
|
||||
- Auch ein **`!!!`-markierter Knoten** (§1) holt sich auf diese Weise hervor —
|
||||
ein Zeigefinger auf etwas Unsichtbares zeigte ins Leere.
|
||||
- Der interaktive Eingriff gilt **je Knoten** (Identität = Label-Pfad, wie bei
|
||||
„Was ist neu?“) und **für die Sitzung**; er wird nicht gespeichert — die
|
||||
dauerhafte Aussage steht im Text (D34). Ein Dokumentwechsel setzt zurück.
|
||||
- **Umklappen im Diagramm schreibt die Marke in den Text zurück.** Damit ist
|
||||
der Text auch für die Faltung die eine Quelle der Wahrheit: Was du siehst,
|
||||
steht geschrieben, und ein Neuladen stellt es wieder her. Die Änderung ist
|
||||
eine gewöhnliche Textänderung — sie lässt sich mit **Rückgängig** zurücknehmen
|
||||
und macht ein mitgeliefertes Dokument zu einem bearbeiteten (§9, D27).
|
||||
Geschrieben wird **minimal**: Nur die Zeile des umgeklappten Knotens wird
|
||||
angefasst, solange die übrigen Marken den Zustand noch richtig beschreiben —
|
||||
ein von Hand gesetztes `<` bleibt also stehen. Trifft es nicht mehr zu, werden
|
||||
alle Marken neu gesetzt und das `<` dabei aufgelöst.
|
||||
- Wo der Text **nicht beschreibbar** ist — bei einem Pad-Dokument (§9) —, gilt
|
||||
der Eingriff **je Knoten** (Identität = Label-Pfad, wie bei „Was ist neu?“)
|
||||
und **nur für die Sitzung**; ein Dokumentwechsel setzt ihn zurück. Dasselbe
|
||||
gilt für einen Zustand, der sich in Marken gar nicht ausdrücken lässt (etwa
|
||||
weil eine Fokusmarke `!!!` ihren Knoten immer wieder hervorholt): Dann wird
|
||||
lieber nichts geschrieben, als einen Text zu hinterlassen, der etwas anderes
|
||||
sagt als das Bild.
|
||||
- Faltung ist **reine Ansicht**: Warnungen aus eingeklappten Teilbäumen werden
|
||||
weiter gemeldet (sie gelten dem Text), und der günstigste Pfad rechnet
|
||||
unverändert über den ganzen Baum.
|
||||
|
||||
+17
-1
@@ -311,7 +311,23 @@ verworfene Elemente. Quelle sind ES-Module unter `src/`; `index.html` ist der
|
||||
Pfad-Mitgliedschaft (ein per `:#…` gezogenes Ziel unter einem `+`-Knoten):
|
||||
Dann ist er der einzige sichtbare Griff darauf und zählt als `cheap`.
|
||||
`extraCls()` muss dieselbe Eingeklappt-Bedingung bilden wie `itemHtml`
|
||||
(nur mit sichtbaren Kindern). Umklappen: Klick aufs
|
||||
(nur mit sichtbaren Kindern).
|
||||
**Umklappen schreibt in den TEXT zurück** (`writeFoldToText`, D38-Nachtrag 2):
|
||||
Die Ableitung Text → Zustand ist nicht umkehrbar (mehrere Markensätze ergeben
|
||||
denselben Zustand; `<` faltet Knoten ohne eigene Marke), deshalb **minimal
|
||||
patchen und nachrechnen** — `setFoldMark()` auf die eine Zeile, dann
|
||||
`initialCollapsed()` auf dem Kandidaten gegen den Soll vergleichen; erst bei
|
||||
Abweichung alle Marken neu setzen; passt auch das nicht (z. B. `!!!` holt den
|
||||
Knoten immer hervor), gar nicht schreiben. Nie eine eigene Umkehrfunktion
|
||||
bauen — `initialCollapsed()` bleibt die einzige Stelle, die die Marken
|
||||
versteht. Geschrieben wird mit `execCommand('insertText')`: `value =` und
|
||||
`setRangeText` zerstören die Undo-Historie (gemessen). Zwei Fallen: Ein
|
||||
Textfeld mit `display:none`-Vorfahr nimmt **kein** `execCommand` an (Klasse
|
||||
`writing-fold` schaltet es kurz sichtbar), und der nötige Fokus zieht auf
|
||||
Mobil die Tastatur hoch (`keyboardOnJump(true)`). Bei `src.readOnly` (Pad,
|
||||
D31) wird nicht geschrieben, dort trägt weiter `foldOverrides` — die
|
||||
Überlagerungen werden nach erfolgreichem Schreiben geleert, sonst maskieren
|
||||
sie den Text. Umklappen: Klick aufs
|
||||
`.fold`-Zeichen (preventDefault — es sitzt bei Link-Knoten im `<a>`) oder
|
||||
←/→ am fokussierten Knoten; nach `render()` den Fokus per `data-line`
|
||||
wiederherstellen. Export/Druck: „▸ n" bleibt, das ▾ offener Knoten fällt weg
|
||||
|
||||
+127
-6
@@ -1,5 +1,5 @@
|
||||
import './style.css';
|
||||
import { parse } from './parser.js';
|
||||
import { parse, setFoldMark } from './parser.js';
|
||||
import { computeCheapPlan, freshProdSet, initialCollapsed, nodeKeys, effectiveStatus } from './model.js';
|
||||
import { esc, renderTreeHtml } from './render.js';
|
||||
import { formatWarning } from './warnings.js';
|
||||
@@ -856,15 +856,136 @@ function nodeFromEvent(e){
|
||||
return el && out.contains(el) ? el : null;
|
||||
}
|
||||
|
||||
/* Faltung umklappen (SPEC §9, D38). Der Eingriff überlagert den Anfangszustand
|
||||
aus dem Text; nach dem Neubau bekommt derselbe Knoten den Fokus zurück,
|
||||
/* ---------- Faltung zurück in den Text (D38-Nachtrag 2) ----------
|
||||
Umklappen im Diagramm ändert den Notationstext. Der Text ist damit auch für
|
||||
die Faltung die eine Quelle der Wahrheit (D14) — die Sitzungs-Überlagerung
|
||||
`foldOverrides` bleibt nur für Dokumente, in die nicht geschrieben werden
|
||||
kann (Pad, schreibgeschützt nach D31). */
|
||||
|
||||
/* Der Sollzustand als Menge von Label-Pfad-Schlüsseln: heutiger Anfangszustand
|
||||
aus dem Text, überlagert von den Eingriffen — dieselbe Rechnung wie in
|
||||
render(), damit beide nie auseinanderlaufen. */
|
||||
function desiredFoldKeys(roots){
|
||||
const initFold = initialCollapsed(roots, true);
|
||||
const keys = nodeKeys(roots);
|
||||
const want = new Set();
|
||||
keys.forEach((key, n) => {
|
||||
const ov = foldOverrides.get(key);
|
||||
if((ov !== undefined ? ov : initFold.has(n)) && n.children.length > 0) want.add(key);
|
||||
});
|
||||
return want;
|
||||
}
|
||||
|
||||
/* Erzeugt ein Text ZWEIMAL denselben Faltzustand? Statt die Ableitung
|
||||
Text -> Zustand umzukehren (sie ist nicht eindeutig: mehrere Markensätze
|
||||
ergeben denselben Zustand), wird der Kandidat schlicht nachgerechnet.
|
||||
`initialCollapsed()` bleibt so die einzige Wahrheit über die Bedeutung der
|
||||
Marken — die Umkehrung muss sie nicht kennen, nur befragen. */
|
||||
function foldStateMatches(txt, want){
|
||||
const r = parse(txt).roots;
|
||||
const keys = nodeKeys(r);
|
||||
const got = new Set();
|
||||
initialCollapsed(r, true).forEach(n => { if(n.children.length > 0) got.add(keys.get(n)); });
|
||||
if(got.size !== want.size) return false;
|
||||
for(const k of want) if(!got.has(k)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Schreibt den Text undo-fähig. `execCommand` ist die EINZIGE Art, ein Textfeld
|
||||
programmatisch zu ändern, ohne dessen Rückgängig-Historie zu zerstören
|
||||
(nachgemessen: `value =` und `setRangeText` machen Strg+Z wirkungslos). Sie
|
||||
ersetzt die Auswahl — geändert wird deshalb nur das wirklich abweichende
|
||||
Stück zwischen gemeinsamem Anfang und Ende. */
|
||||
function replaceTextUndoable(neu){
|
||||
const alt = src.value;
|
||||
if(alt === neu) return true;
|
||||
let s = 0;
|
||||
while(s < alt.length && s < neu.length && alt[s] === neu[s]) s++;
|
||||
let e = 0;
|
||||
while(e < alt.length - s && e < neu.length - s &&
|
||||
alt[alt.length-1-e] === neu[neu.length-1-e]) e++;
|
||||
const von = s, bis = alt.length - e, ein = neu.slice(s, neu.length - e);
|
||||
const cs = src.selectionStart, ce = src.selectionEnd, top = src.scrollTop;
|
||||
/* Der Fokus muss ins Textfeld — sonst greift execCommand nicht. Auf dem
|
||||
Telefon zöge das die Bildschirmtastatur hoch; `inputmode="none"` hält sie
|
||||
unten (derselbe Griff wie beim Sprung, D25), und der erste echte Tipp ins
|
||||
Feld hebt die Sperre wieder auf. */
|
||||
keyboardOnJump(true);
|
||||
src.focus({preventScroll: true});
|
||||
src.setSelectionRange(von, bis);
|
||||
let ok = false;
|
||||
try{ ok = document.execCommand('insertText', false, ein); }catch(_){}
|
||||
if(!ok){
|
||||
/* Rückfall: Der richtige Zustand geht vor der Undo-Historie (D14). */
|
||||
src.value = neu;
|
||||
src.dispatchEvent(new Event('input', {bubbles: true}));
|
||||
}
|
||||
/* Schreibmarke und Scrollstand zurück — sonst risse das Falten den Nutzer
|
||||
aus seiner Textstelle. Nur was HINTER der Änderung lag, verschiebt sich. */
|
||||
const d = ein.length - (bis - von);
|
||||
const fix = p => p >= bis ? p + d : Math.min(p, von + ein.length);
|
||||
src.setSelectionRange(fix(cs), fix(ce));
|
||||
src.scrollTop = top;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Auf kleinem Bildschirm ist der Editor `display:none`, wenn das Diagramm vorn
|
||||
ist — und dann tut `execCommand` NICHTS (gemessen: liefert `false`, obwohl
|
||||
`activeElement` das Feld meldet). Für die Dauer des synchronen Schreibens
|
||||
wird er deshalb aus dem Fluss heraus sichtbar geschaltet; gezeichnet wird
|
||||
davon nichts, wie bei `exporting` im Grafikexport. */
|
||||
function withEditorWritable(fn){
|
||||
const versteckt = editorPanel.offsetParent === null;
|
||||
if(versteckt) document.body.classList.add('writing-fold');
|
||||
try{ return fn(); }
|
||||
finally{ if(versteckt) document.body.classList.remove('writing-fold'); }
|
||||
}
|
||||
|
||||
/* Schreibt den Faltzustand in den Text. Liefert false, wenn nicht geschrieben
|
||||
werden konnte — dann bleibt die Sitzungs-Überlagerung stehen. */
|
||||
function writeFoldToText(line, collapsed){
|
||||
if(src.readOnly) return false; /* Pad-Dokument (D31) */
|
||||
const roots = parse(src.value).roots;
|
||||
if(!roots.length) return false;
|
||||
const want = desiredFoldKeys(roots);
|
||||
const zeilen = src.value.split('\n');
|
||||
|
||||
/* 1) Minimal: nur die umgeklappte Zeile anfassen. So bleiben von Hand
|
||||
gesetzte `<` stehen, solange sie den Zustand noch richtig beschreiben. */
|
||||
const klein = zeilen.slice();
|
||||
klein[line-1] = setFoldMark(klein[line-1], collapsed ? '>' : null);
|
||||
let neu = klein.join('\n');
|
||||
|
||||
if(!foldStateMatches(neu, want)){
|
||||
/* 2) Vollständig: alle Marken neu setzen. Nötig, wenn ein `<` den Zustand
|
||||
nicht mehr trifft — dann wird es hier aufgelöst. */
|
||||
const keys = nodeKeys(roots);
|
||||
const ganz = zeilen.slice();
|
||||
keys.forEach((key, n) => {
|
||||
ganz[n.line-1] = setFoldMark(ganz[n.line-1], want.has(key) ? '>' : null);
|
||||
});
|
||||
neu = ganz.join('\n');
|
||||
/* 3) Nicht ausdrückbar — etwa weil ein `!!!` im Zweig seinen Knoten immer
|
||||
wieder hervorholt (SPEC §9). Dann NICHT schreiben: ein Text, der
|
||||
etwas anderes sagt als das Bild, wäre schlimmer als keine Marke. */
|
||||
if(!foldStateMatches(neu, want)) return false;
|
||||
}
|
||||
return withEditorWritable(() => replaceTextUndoable(neu));
|
||||
}
|
||||
|
||||
/* Faltung umklappen (SPEC §9, D38). Gelingt das Zurückschreiben, ist der Text
|
||||
der Zustand — die Überlagerungen werden dann geleert, damit sie ihn nicht
|
||||
maskieren können. Nach dem Neubau bekommt derselbe Knoten den Fokus zurück,
|
||||
sonst risse die Tastaturbedienung ab (das alte Element ist weg). */
|
||||
function toggleFold(el){
|
||||
const st = foldByLine.get(+el.dataset.line);
|
||||
const line = +el.dataset.line;
|
||||
const st = foldByLine.get(line);
|
||||
if(!st || !st.canFold) return;
|
||||
foldOverrides.set(st.key, !st.collapsed);
|
||||
render();
|
||||
const again = out.querySelector('.node[data-line="' + el.dataset.line + '"]');
|
||||
/* Das Schreiben löst per `input`-Ereignis schon ein render() aus. */
|
||||
if(writeFoldToText(line, !st.collapsed)) foldOverrides.clear();
|
||||
else render();
|
||||
const again = out.querySelector('.node[data-line="' + line + '"]');
|
||||
if(again) again.focus({preventScroll: true});
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,18 @@ export const STATUS_BY_CODE = {
|
||||
'!': {code:'!', key:'highrisk', name:'High Risk – Aufwand unklar'}
|
||||
};
|
||||
|
||||
/* Setzt (`'>'`) oder entfernt (`null`) die Faltmarke einer Zeile — die Umkehrung
|
||||
der Extraktion aus §1, gebraucht fürs Zurückschreiben aus dem Diagramm
|
||||
(D38-Nachtrag). Angefasst wird NUR die Marke samt ihrem Leerraum: Einrückung,
|
||||
Zerlegungszeichen, Statusbox und Label bleiben zeichengenau stehen, auch bei
|
||||
ungewöhnlicher Spaltung wie `- [x] X`. Wurzelzeilen (ohne Zeichen) bekommen
|
||||
die Marke am Zeilenanfang, wie SPEC §1 es verlangt. */
|
||||
export function setFoldMark(line, mark){
|
||||
const m = line.match(/^([ \t]*(?:[-|+]|=(?=[ \t]))?[ \t]*)((?:[><](?=[ \t])[ \t]*)?)/);
|
||||
const head = m[1], alt = m[2];
|
||||
return head + (mark ? mark + ' ' : '') + line.slice(head.length + alt.length);
|
||||
}
|
||||
|
||||
/* Status, die als „realisiert" zählen (XOR-Regel, SPEC §3/D35): Kosten sind
|
||||
investiert oder mehr. Absicht (`[?]`, `[ ]`, `[!]`), Ablehnung (`[-]`) und
|
||||
neutrale Knoten zählen nicht. */
|
||||
|
||||
@@ -399,6 +399,18 @@
|
||||
.dltrigger{display:none}
|
||||
.legendbtn{display:none} /* nur auf kleinem Bildschirm sichtbar */
|
||||
.panetoggle{display:none} /* ebenso: dort gibt es nur einen Bereich */
|
||||
/* Zurückschreiben der Faltung (D38-Nachtrag 2): Für die Dauer des SYNCHRONEN
|
||||
Schreibens braucht das Textfeld einen Kasten — ohne ihn tut `execCommand`
|
||||
nichts (gemessen: liefert `false`, obwohl `activeElement` das Feld meldet).
|
||||
Auf kleinem Bildschirm ist der Editor `display:none`, wenn das Diagramm
|
||||
vorn ist. Absolut aus dem Fluss genommen und aus dem Bild geschoben, damit
|
||||
der sichtbare Bereich seine Größe behält; gezeichnet wird davon nichts —
|
||||
derselbe Griff wie `exporting` im Grafikexport. */
|
||||
body.writing-fold .panel.editor{
|
||||
display:flex!important;
|
||||
position:absolute!important;left:-10000px!important;top:0!important;
|
||||
width:360px!important;height:400px!important;
|
||||
}
|
||||
/* Dokument-Wähler: der Name des aktiven Dokuments in der Editor-Titelzeile ist
|
||||
zugleich der Auslöser eines Dropdowns zum Wechseln/Anlegen/Umbenennen/Löschen
|
||||
(localStorage-Dokumente, noch kein Backend). */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { parse } from '../src/parser.js';
|
||||
import { parse, setFoldMark } from '../src/parser.js';
|
||||
import { initialCollapsed, computeCheapSet } from '../src/model.js';
|
||||
import { renderTreeHtml } from '../src/render.js';
|
||||
|
||||
@@ -43,6 +43,44 @@ describe('Parser — Faltmarke zwischen Zeichen und Statusbox', () => {
|
||||
});
|
||||
});
|
||||
|
||||
/* Umkehrung der Marken-Extraktion fürs Zurückschreiben aus dem Diagramm
|
||||
(SPEC §1, D38-Nachtrag 2): Angefasst wird nur die Marke samt Leerraum. */
|
||||
describe('setFoldMark — Faltmarke setzen und entfernen', () => {
|
||||
it('setzt `>` hinter das Zerlegungszeichen', () => {
|
||||
expect(setFoldMark(' - [x] Concept (M)', '>')).toBe(' - > [x] Concept (M)');
|
||||
});
|
||||
|
||||
it('entfernt eine vorhandene Marke', () => {
|
||||
expect(setFoldMark(' - > [x] Concept (M)', null)).toBe(' - [x] Concept (M)');
|
||||
});
|
||||
|
||||
it('ersetzt `<` durch `>`', () => {
|
||||
expect(setFoldMark(' - < [ ] B1', '>')).toBe(' - > [ ] B1');
|
||||
});
|
||||
|
||||
it('setzt die Marke bei Wurzelzeilen an den Zeilenanfang', () => {
|
||||
expect(setFoldMark('[~] Wurzel', '>')).toBe('> [~] Wurzel');
|
||||
expect(setFoldMark('> [~] Wurzel', null)).toBe('[~] Wurzel');
|
||||
});
|
||||
|
||||
it('lässt ungewöhnliche Spaltung stehen', () => {
|
||||
expect(setFoldMark(' - [x] X', '>')).toBe(' - > [x] X');
|
||||
expect(setFoldMark('\t= [ ] Cloud', '>')).toBe('\t= > [ ] Cloud');
|
||||
});
|
||||
|
||||
it('fasst ein `>` im Label nicht an (Leerraum-Regel)', () => {
|
||||
/* `- >Achtung` ist ein Label, keine Marke — die neue Marke kommt davor. */
|
||||
expect(setFoldMark(' - >Achtung', '>')).toBe(' - > >Achtung');
|
||||
expect(setFoldMark(' - >Achtung', null)).toBe(' - >Achtung');
|
||||
});
|
||||
|
||||
it('ist verlustfrei umkehrbar', () => {
|
||||
for(const l of ['[ ] W', ' - [x] A (M) @anna', ' | [?] B', ' + [-] C']){
|
||||
expect(setFoldMark(setFoldMark(l, '>'), null)).toBe(l);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('initialCollapsed — `>` klappt ein, `<` wandert die Faltung hinunter', () => {
|
||||
it('klappt `>`-Knoten ein', () => {
|
||||
expect(collapsedLabels(`[ ] W\n - > [ ] A\n - [ ] A1\n - [ ] B`))
|
||||
|
||||
Reference in New Issue
Block a user