feat(diagram): #-Umschalter blendet die Knoten-IDs vor dem Titel ein
Die ID ist die Adresse eines Knotens — Ziel der Abhängigkeiten, Schlüssel der Beschreibungsblöcke —, stand im Diagramm aber nur im Tooltip (D36). Jetzt umschaltbar und geschrieben wie im Text: `#some.id: Titel`. - Als RENDERER-Option, nicht per CSS versteckt: `diagramToSvg()` zieht den Knotentext aus `textContent`, ein `display:none` hätte die IDs trotzdem in jeden Export geschrieben. Nachgemessen im SVG: `>#not.line: Line format<` mit Umschalter, `>Line format<` ohne. - Zurückgenommen dargestellt (Mono wie im Textfeld, --muted), `aria-hidden` — der Screenreader bekommt die ID über a11yId (D36). Zustand persistiert. Das zehnte Bedienelement hat die Kopfzeile gekippt, zweimal dieselbe Rechnung wie D17-Nachtrag 5: - Schreibtisch: 738 nötig, 728 da -> 82 statt 49 px. Nicht die Knöpfe (566 px für elf Elemente), sondern zehn Lücken à 14 px = 140 px. `gap:10` gibt 40 px zurück; danach 698 von 728 und wieder 49 px mit UND ohne Knopf. - Telefon (375 px): alle Ziele schon auf 29 px, Lücken auf 6 — ein zehntes passt arithmetisch nicht (386 gegen 359). D50s Umbruch-Schwelle wandert von 360 auf 440 px. Greift nur mit dem „Was ist neu?"-Knopf: mit allen zehn 78 px/zwei Reihen, ohne ihn 49 px/eine Reihe. SPEC §9 + D56; Plan: #ed.showids [x] (160 Knoten, 0 Warnungen). 293 Tests grün (288+5); i18n `idsTooltip` in 9 Sprachen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
285d86a2e1
commit
f0fe877d45
@@ -3922,3 +3922,55 @@ erste `#`-Token, zeichengenaue Erhaltung von Einrückung, Zeichen, Statusbox
|
|||||||
und Faltmarke, und dass das Ergebnis denselben Baum ergibt wie die von Hand
|
und Faltmarke, und dass das Ergebnis denselben Baum ergibt wie die von Hand
|
||||||
ausgeschriebene Fassung). Das Zusammenspiel mit dem Textfeld bleibt
|
ausgeschriebene Fassung). Das Zusammenspiel mit dem Textfeld bleibt
|
||||||
Browser-Sache — die Lehre aus D54-Nachtrag 3.
|
Browser-Sache — die Lehre aus D54-Nachtrag 3.
|
||||||
|
|
||||||
|
## D56 — `#`-Umschalter: die Knoten-ID vor dem Titel, geschrieben wie im Text
|
||||||
|
Die ID ist die **Adresse** eines Knotens — Ziel der Abhängigkeiten (§1), Schlüssel
|
||||||
|
der Beschreibungsblöcke, und seit D55 auch das, was man beim Tippen abkürzt. Im
|
||||||
|
Diagramm stand sie bisher nur im Tooltip (D36: „eine eigene Darstellung hat sie
|
||||||
|
(noch) nicht"). Wer Text und Bild nebeneinander liest, musste jeden Knoten
|
||||||
|
antippen, um zu wissen, welcher er ist.
|
||||||
|
|
||||||
|
Ein Umschalter im Diagramm-Kopf blendet sie ein, **geschrieben wie im Text**:
|
||||||
|
`#some.id: Titel`, mit Doppelpunkt und Leerzeichen. Dieselbe Schreibweise auf
|
||||||
|
beiden Seiten ist der ganze Zweck — eine eigene Darstellung (Badge in der Ecke,
|
||||||
|
Klammern, Kapitälchen) wäre eine zweite Konvention für dieselbe Sache.
|
||||||
|
|
||||||
|
**Als Renderer-Option, nicht per CSS versteckt.** Der naheliegende Weg wäre eine
|
||||||
|
Klasse an `#out` und `display:none` gewesen. Dann aber stünde die ID trotzdem im
|
||||||
|
`textContent` — und genau daraus zieht `diagramToSvg()` den Knotentext. Der
|
||||||
|
Export hätte die IDs also **immer** enthalten, egal was auf dem Schirm steht.
|
||||||
|
Als Option in `renderTreeHtml(roots, {…, showIds})` folgt er von selbst;
|
||||||
|
nachgemessen im ausgegebenen SVG: `>#not.line: Line format</text>` mit
|
||||||
|
Umschalter, `>Line format</text>` ohne. Das entspricht der Hausregel, UI-State
|
||||||
|
als **Parameter** zu übergeben (frontend/CLAUDE.md), und der Linie von D38/D44:
|
||||||
|
Export und Druck zeigen, was sichtbar ist.
|
||||||
|
|
||||||
|
**Zurückgenommen dargestellt:** Mono-Schrift wie im Textfeld, 0,86 em, in
|
||||||
|
`--muted` (am dunklen Wurzelknoten in Weiß mit 75 %). Die ID ist die Adresse,
|
||||||
|
nicht der Name — der Titel soll die Zeile weiter anführen. `aria-hidden`, weil
|
||||||
|
der Screenreader sie über `a11yId` ohnehin bekommt (D36) und sie sonst doppelt
|
||||||
|
vorläse. Der Zustand gehört zur Ansicht und wird wie Modus, Zoom und Aufteilung
|
||||||
|
global gemerkt (D22).
|
||||||
|
|
||||||
|
**Der Knopf trägt das Zeichen selbst.** Ein gezeichnetes Icon sagte hier
|
||||||
|
weniger als `#` — dasselbe Argument wie beim Fenster-Wähler in D31, nur
|
||||||
|
umgekehrt: Wo es ein etabliertes Schriftzeichen gibt, ist es das beste Symbol.
|
||||||
|
|
||||||
|
**Das zehnte Bedienelement hat die Kopfzeile gekippt — zweimal dieselbe
|
||||||
|
Rechnung wie D17-Nachtrag 5.** Nachgemessen statt geschätzt:
|
||||||
|
|
||||||
|
- **Schreibtisch:** 738 px nötig, 728 verfügbar — 10 px zu wenig, die Zeile ging
|
||||||
|
auf 82 statt 49 px. Nicht die Knöpfe waren das Problem (566 px für elf
|
||||||
|
Elemente), sondern der Weißraum: zehn Lücken à 14 px sind 140 px. `gap:10`
|
||||||
|
statt `14` gibt 40 px zurück, also fast genau die Breite des neuen Knopfes;
|
||||||
|
danach 698 von 728 und wieder 49 px — mit **und** ohne den Knopf.
|
||||||
|
- **Telefon (375 px):** Dort sind alle Ziele schon auf dem 29-px-Fingerminimum
|
||||||
|
und die Lücken auf 6 px; ein zehntes passt arithmetisch nicht (386 gegen
|
||||||
|
359 px). Die Umbruch-Schwelle aus D50 wandert deshalb von 360 auf 440 px —
|
||||||
|
dieselbe Rechnung, ein Element mehr. Sie greift auch nur dann wirklich: Der
|
||||||
|
„Was ist neu?"-Knopf (44 px) erscheint nur bei Neuigkeiten, ohne ihn bleiben
|
||||||
|
336 px und damit eine Reihe. Gemessen: mit allen zehn 78 px und zwei Reihen,
|
||||||
|
alles innerhalb; ohne den Neuigkeiten-Knopf 49 px und eine Reihe.
|
||||||
|
|
||||||
|
Ein `overflow` an der Kopfzeile bleibt weiterhin ausgeschlossen — es klippt die
|
||||||
|
beiden Aufklapp-Menüs (D50).
|
||||||
|
|||||||
@@ -703,6 +703,17 @@ verknüpft (siehe D25):
|
|||||||
- Die Hervorhebung ist eine reine Editierhilfe: nicht im Grafikexport, nicht im
|
- Die Hervorhebung ist eine reine Editierhilfe: nicht im Grafikexport, nicht im
|
||||||
Druck.
|
Druck.
|
||||||
|
|
||||||
|
### Knoten-IDs im Diagramm einblenden (`#`)
|
||||||
|
Ein Umschalter im Diagramm-Kopf stellt die Knoten-ID **vor den Titel**, in
|
||||||
|
derselben Schreibweise wie im Text (§1/D36): `#some.id: Titel`. Dargestellt in
|
||||||
|
der Mono-Schrift des Textfelds und zurückgenommen gefärbt — die ID ist die
|
||||||
|
Adresse, der Titel führt die Zeile weiter an. Der Zustand wird gemerkt.
|
||||||
|
|
||||||
|
**Grafikexport und Druck folgen dem Umschalter** (wie beim „verworfene
|
||||||
|
einblenden"-Filter und der Faltung): Ist er an, stehen die IDs auch im
|
||||||
|
ausgegebenen Bild. Für Screenreader ändert sich nichts — sie bekommen die ID
|
||||||
|
ohnehin über das `aria-label`. Siehe D56.
|
||||||
|
|
||||||
### Kurzschreibweise der Knoten-ID beim Tippen (`#.name`)
|
### Kurzschreibweise der Knoten-ID beim Tippen (`#.name`)
|
||||||
Eine **Eingabehilfe**, keine Notation: Wer `#.kc` unter einem Knoten mit der ID
|
Eine **Eingabehilfe**, keine Notation: Wer `#.kc` unter einem Knoten mit der ID
|
||||||
`#prod-stage` schreibt, findet beim **Verlassen der Zeile** `#prod-stage.kc` im
|
`#prod-stage` schreibt, findet beim **Verlassen der Zeile** `#prod-stage.kc` im
|
||||||
|
|||||||
@@ -68,6 +68,7 @@
|
|||||||
- [^] #ed.jump: Jump between diagram and text (S)
|
- [^] #ed.jump: Jump between diagram and text (S)
|
||||||
- [^] #ed.lineno: Line numbers in the text editor (XS) %% the warnings name them
|
- [^] #ed.lineno: Line numbers in the text editor (XS) %% the warnings name them
|
||||||
- [x] #ed.shortid: Type #.kc under #prod-stage, get #prod-stage.kc (XS) %% an input aid, not notation
|
- [x] #ed.shortid: Type #.kc under #prod-stage, get #prod-stage.kc (XS) %% an input aid, not notation
|
||||||
|
- [x] #ed.showids: A # button shows the ids before the titles (XS) %% written as in the text, with a colon
|
||||||
- [^] #ed.lineno.tip: The warning is the tooltip of its number (XS) %% no hunting in the list
|
- [^] #ed.lineno.tip: The warning is the tooltip of its number (XS) %% no hunting in the list
|
||||||
- [^] #ed.fold: Collapsible subtrees (M)
|
- [^] #ed.fold: Collapsible subtrees (M)
|
||||||
- [^] #ed.fold.marks: Initial state from the text: > collapses, < re-opens (S)
|
- [^] #ed.fold.marks: Initial state from the text: > collapses, < re-opens (S)
|
||||||
@@ -498,6 +499,11 @@
|
|||||||
typing, not while parsing: the file keeps the full id, so it stays greppable
|
typing, not while parsing: the file keeps the full id, so it stays greppable
|
||||||
and survives re-ordering, which is what the :#... references rely on.
|
and survives re-ordering, which is what the :#... references rely on.
|
||||||
|
|
||||||
|
#ed.showids
|
||||||
|
The id is the address of a node, but the diagram only carried it in the
|
||||||
|
tooltip. The switch writes it before the title the same way the text does,
|
||||||
|
so both can be read side by side; the graphic export follows the switch.
|
||||||
|
|
||||||
#ed.fold
|
#ed.fold
|
||||||
A large plan is unreadable unless parts of it can be put away. Folding is
|
A large plan is unreadable unless parts of it can be put away. Folding is
|
||||||
view only — warnings from folded subtrees are still reported, because they
|
view only — warnings from folded subtrees are still reported, because they
|
||||||
|
|||||||
@@ -192,6 +192,12 @@
|
|||||||
<button type="button" class="copybtn togglebtn" id="foldSmallBtn" aria-pressed="false" data-i18n-title="foldSmallTooltip" data-i18n-aria="foldSmallTooltip" title="Knoten der Größe M und kleiner zuklappen – erneut drücken klappt alle wieder auf" aria-label="Knoten der Größe M und kleiner zuklappen – erneut drücken klappt alle wieder auf">
|
<button type="button" class="copybtn togglebtn" id="foldSmallBtn" aria-pressed="false" data-i18n-title="foldSmallTooltip" data-i18n-aria="foldSmallTooltip" title="Knoten der Größe M und kleiner zuklappen – erneut drücken klappt alle wieder auf" aria-label="Knoten der Größe M und kleiner zuklappen – erneut drücken klappt alle wieder auf">
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 21V3M8 8l4 4 4-4M8 16l4-4 4 4"/></svg>
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 21V3M8 8l4 4 4-4M8 16l4-4 4 4"/></svg>
|
||||||
</button>
|
</button>
|
||||||
|
<!-- Knoten-IDs vor dem Titel einblenden (SPEC §9, D56) — geschrieben wie
|
||||||
|
im Text: `#some.id: Titel`. Das Zeichen selbst ist das Symbol; ein
|
||||||
|
gezeichnetes Icon sagte hier weniger als das `#`. -->
|
||||||
|
<button type="button" class="copybtn togglebtn idsbtn" id="idsBtn" aria-pressed="false" data-i18n-title="idsTooltip" data-i18n-aria="idsTooltip" title="Knoten-IDs vor dem Titel einblenden" aria-label="Knoten-IDs vor dem Titel einblenden">
|
||||||
|
<span aria-hidden="true">#</span>
|
||||||
|
</button>
|
||||||
<div class="seg" role="radiogroup" aria-label="Darstellung">
|
<div class="seg" role="radiogroup" aria-label="Darstellung">
|
||||||
<label data-i18n-title="modeHorizontal" title="Horizontal – Organigramm, Diagramm über dem Editor">
|
<label data-i18n-title="modeHorizontal" title="Horizontal – Organigramm, Diagramm über dem Editor">
|
||||||
<input type="radio" name="layout" value="horizontal" checked data-i18n-aria="modeHorizontal" aria-label="Horizontal – Organigramm">
|
<input type="radio" name="layout" value="horizontal" checked data-i18n-aria="modeHorizontal" aria-label="Horizontal – Organigramm">
|
||||||
|
|||||||
+28
-1
@@ -53,6 +53,7 @@ const lineNoInner = lineNoBox.firstElementChild;
|
|||||||
Pfad) lebt headless in model.js, das HTML-Erzeugen in render.js. Hier bleibt
|
Pfad) lebt headless in model.js, das HTML-Erzeugen in render.js. Hier bleibt
|
||||||
nur der UI-State des Günstigster-Pfad-Toggles (persistiert). */
|
nur der UI-State des Günstigster-Pfad-Toggles (persistiert). */
|
||||||
let cheapPathOn = true;
|
let cheapPathOn = true;
|
||||||
|
let showIds = false; /* Knoten-IDs im Diagramm einblenden (D56) */
|
||||||
/* Ansicht bei einem Pad-Dokument (D31): beide | nur Pad | nur Spiegel. Hier oben
|
/* Ansicht bei einem Pad-Dokument (D31): beide | nur Pad | nur Spiegel. Hier oben
|
||||||
deklariert, weil saveUI() sie liest und schon aus applySplit() heraus laufen
|
deklariert, weil saveUI() sie liest und schon aus applySplit() heraus laufen
|
||||||
kann — weiter unten stünde sie dann noch in der temporalen Todeszone. */
|
kann — weiter unten stünde sie dann noch in der temporalen Todeszone. */
|
||||||
@@ -148,7 +149,7 @@ function render(){
|
|||||||
„alles offen" derselbe Zustand — der Knopf säße dann gedrückt da, ohne
|
„alles offen" derselbe Zustand — der Knopf säße dann gedrückt da, ohne
|
||||||
dass etwas zugeklappt ist. */
|
dass etwas zugeklappt ist. */
|
||||||
foldSmallBtn.setAttribute('aria-pressed', foldSmallExact && foldSmallAny ? 'true' : 'false');
|
foldSmallBtn.setAttribute('aria-pressed', foldSmallExact && foldSmallAny ? 'true' : 'false');
|
||||||
const r = renderTreeHtml(roots, {t, showDiscarded, cheapPath: cheapPathOn, cheapSet,
|
const r = renderTreeHtml(roots, {t, showDiscarded, cheapPath: cheapPathOn, cheapSet, showIds,
|
||||||
freshSet, collapsedSet,
|
freshSet, collapsedSet,
|
||||||
effStatus: effectiveStatus(roots)});
|
effStatus: effectiveStatus(roots)});
|
||||||
out.innerHTML = r.html;
|
out.innerHTML = r.html;
|
||||||
@@ -1662,6 +1663,18 @@ cheapBtn.addEventListener('click', () => {
|
|||||||
saveUI();
|
saveUI();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* Knoten-IDs im Diagramm an/aus (D56). Wie die Nachbarn ein Umschalter mit
|
||||||
|
`aria-pressed`; der Zustand gehört zur Ansicht und wird wie Modus, Zoom und
|
||||||
|
Aufteilung global gemerkt (D22). Neu gerendert statt per CSS versteckt —
|
||||||
|
dann folgt der Grafikexport von selbst. */
|
||||||
|
const idsBtn = document.getElementById('idsBtn');
|
||||||
|
idsBtn.addEventListener('click', () => {
|
||||||
|
showIds = !showIds;
|
||||||
|
idsBtn.setAttribute('aria-pressed', showIds ? 'true' : 'false');
|
||||||
|
render();
|
||||||
|
saveUI();
|
||||||
|
});
|
||||||
|
|
||||||
/* ---------- Von Station zu Station (SPEC §9, D47) ----------
|
/* ---------- Von Station zu Station (SPEC §9, D47) ----------
|
||||||
Kein Umschalter, sondern eine Bewegung: Jeder Druck holt die nächste noch
|
Kein Umschalter, sondern eine Bewegung: Jeder Druck holt die nächste noch
|
||||||
offene Station des günstigsten Pfads in die Mitte — beim ersten Druck die
|
offene Station des günstigsten Pfads in die Mitte — beim ersten Druck die
|
||||||
@@ -1802,6 +1815,7 @@ const I18N = {
|
|||||||
snapTooltip:"Frühere Stände dieses Dokuments", snapMenuAria:"Frühere Stände",
|
snapTooltip:"Frühere Stände dieses Dokuments", snapMenuAria:"Frühere Stände",
|
||||||
snapNone:"Noch keine früheren Stände – gesichert wird auf Knopfdruck und alle 10 Minuten, sobald sich etwas geändert hat.",
|
snapNone:"Noch keine früheren Stände – gesichert wird auf Knopfdruck und alle 10 Minuten, sobald sich etwas geändert hat.",
|
||||||
snapAddTooltip:"Aktuellen Stand jetzt sichern",
|
snapAddTooltip:"Aktuellen Stand jetzt sichern",
|
||||||
|
idsTooltip:"Knoten-IDs vor dem Titel einblenden",
|
||||||
snapLines:"{n} Zeilen",
|
snapLines:"{n} Zeilen",
|
||||||
docNew:"Neues Dokument", docRename:"Umbenennen", docDelete:"Löschen",
|
docNew:"Neues Dokument", docRename:"Umbenennen", docDelete:"Löschen",
|
||||||
docNewName:"Unbenannt",
|
docNewName:"Unbenannt",
|
||||||
@@ -1891,6 +1905,7 @@ const I18N = {
|
|||||||
snapTooltip:"Earlier states of this document", snapMenuAria:"Earlier states",
|
snapTooltip:"Earlier states of this document", snapMenuAria:"Earlier states",
|
||||||
snapNone:"No earlier states yet – one is kept on demand and every 10 minutes, once something has changed.",
|
snapNone:"No earlier states yet – one is kept on demand and every 10 minutes, once something has changed.",
|
||||||
snapAddTooltip:"Save the current state now",
|
snapAddTooltip:"Save the current state now",
|
||||||
|
idsTooltip:"Show node ids before the title",
|
||||||
snapLines:"{n} lines",
|
snapLines:"{n} lines",
|
||||||
docNew:"New document", docRename:"Rename", docDelete:"Delete",
|
docNew:"New document", docRename:"Rename", docDelete:"Delete",
|
||||||
docNewName:"Untitled",
|
docNewName:"Untitled",
|
||||||
@@ -1980,6 +1995,7 @@ const I18N = {
|
|||||||
snapTooltip:"Estados anteriores de este documento", snapMenuAria:"Estados anteriores",
|
snapTooltip:"Estados anteriores de este documento", snapMenuAria:"Estados anteriores",
|
||||||
snapNone:"Aún no hay estados anteriores: se guarda uno al pulsar el botón y cada 10 minutos, cuando algo ha cambiado.",
|
snapNone:"Aún no hay estados anteriores: se guarda uno al pulsar el botón y cada 10 minutos, cuando algo ha cambiado.",
|
||||||
snapAddTooltip:"Guardar ahora el estado actual",
|
snapAddTooltip:"Guardar ahora el estado actual",
|
||||||
|
idsTooltip:"Mostrar los id de nodo antes del título",
|
||||||
snapLines:"{n} líneas",
|
snapLines:"{n} líneas",
|
||||||
docNew:"Nuevo documento", docRename:"Renombrar", docDelete:"Eliminar",
|
docNew:"Nuevo documento", docRename:"Renombrar", docDelete:"Eliminar",
|
||||||
docNewName:"Sin título",
|
docNewName:"Sin título",
|
||||||
@@ -2069,6 +2085,7 @@ const I18N = {
|
|||||||
snapTooltip:"États antérieurs de ce document", snapMenuAria:"États antérieurs",
|
snapTooltip:"États antérieurs de ce document", snapMenuAria:"États antérieurs",
|
||||||
snapNone:"Pas encore d’état antérieur : un état est conservé sur demande et toutes les 10 minutes, dès que quelque chose a changé.",
|
snapNone:"Pas encore d’état antérieur : un état est conservé sur demande et toutes les 10 minutes, dès que quelque chose a changé.",
|
||||||
snapAddTooltip:"Enregistrer l’état actuel maintenant",
|
snapAddTooltip:"Enregistrer l’état actuel maintenant",
|
||||||
|
idsTooltip:"Afficher les identifiants de nœud avant le titre",
|
||||||
snapLines:"{n} lignes",
|
snapLines:"{n} lignes",
|
||||||
docNew:"Nouveau document", docRename:"Renommer", docDelete:"Supprimer",
|
docNew:"Nouveau document", docRename:"Renommer", docDelete:"Supprimer",
|
||||||
docNewName:"Sans titre",
|
docNewName:"Sans titre",
|
||||||
@@ -2158,6 +2175,7 @@ const I18N = {
|
|||||||
snapTooltip:"Wcześniejsze stany tego dokumentu", snapMenuAria:"Wcześniejsze stany",
|
snapTooltip:"Wcześniejsze stany tego dokumentu", snapMenuAria:"Wcześniejsze stany",
|
||||||
snapNone:"Brak wcześniejszych stanów – zapisywany jest na żądanie i co 10 minut, gdy coś się zmieni.",
|
snapNone:"Brak wcześniejszych stanów – zapisywany jest na żądanie i co 10 minut, gdy coś się zmieni.",
|
||||||
snapAddTooltip:"Zapisz bieżący stan teraz",
|
snapAddTooltip:"Zapisz bieżący stan teraz",
|
||||||
|
idsTooltip:"Pokaż identyfikatory węzłów przed tytułem",
|
||||||
snapLines:"wierszy: {n}",
|
snapLines:"wierszy: {n}",
|
||||||
docNew:"Nowy dokument", docRename:"Zmień nazwę", docDelete:"Usuń",
|
docNew:"Nowy dokument", docRename:"Zmień nazwę", docDelete:"Usuń",
|
||||||
docNewName:"Bez nazwy",
|
docNewName:"Bez nazwy",
|
||||||
@@ -2247,6 +2265,7 @@ const I18N = {
|
|||||||
snapTooltip:"Прежние состояния этого документа", snapMenuAria:"Прежние состояния",
|
snapTooltip:"Прежние состояния этого документа", snapMenuAria:"Прежние состояния",
|
||||||
snapNone:"Прежних состояний пока нет — они сохраняются по нажатию кнопки и каждые 10 минут, если что-то изменилось.",
|
snapNone:"Прежних состояний пока нет — они сохраняются по нажатию кнопки и каждые 10 минут, если что-то изменилось.",
|
||||||
snapAddTooltip:"Сохранить текущее состояние сейчас",
|
snapAddTooltip:"Сохранить текущее состояние сейчас",
|
||||||
|
idsTooltip:"Показывать идентификаторы узлов перед заголовком",
|
||||||
snapLines:"строк: {n}",
|
snapLines:"строк: {n}",
|
||||||
docNew:"Новый документ", docRename:"Переименовать", docDelete:"Удалить",
|
docNew:"Новый документ", docRename:"Переименовать", docDelete:"Удалить",
|
||||||
docNewName:"Без названия",
|
docNewName:"Без названия",
|
||||||
@@ -2336,6 +2355,7 @@ const I18N = {
|
|||||||
snapTooltip:"इस दस्तावेज़ की पिछली स्थितियाँ", snapMenuAria:"पिछली स्थितियाँ",
|
snapTooltip:"इस दस्तावेज़ की पिछली स्थितियाँ", snapMenuAria:"पिछली स्थितियाँ",
|
||||||
snapNone:"अभी कोई पिछली स्थिति नहीं — बटन दबाने पर और कुछ बदलने पर हर 10 मिनट में एक सहेजी जाती है।",
|
snapNone:"अभी कोई पिछली स्थिति नहीं — बटन दबाने पर और कुछ बदलने पर हर 10 मिनट में एक सहेजी जाती है।",
|
||||||
snapAddTooltip:"मौजूदा स्थिति अभी सहेजें",
|
snapAddTooltip:"मौजूदा स्थिति अभी सहेजें",
|
||||||
|
idsTooltip:"शीर्षक से पहले नोड आईडी दिखाएँ",
|
||||||
snapLines:"{n} पंक्तियाँ",
|
snapLines:"{n} पंक्तियाँ",
|
||||||
docNew:"नया दस्तावेज़", docRename:"नाम बदलें", docDelete:"हटाएँ",
|
docNew:"नया दस्तावेज़", docRename:"नाम बदलें", docDelete:"हटाएँ",
|
||||||
docNewName:"बिना शीर्षक",
|
docNewName:"बिना शीर्षक",
|
||||||
@@ -2425,6 +2445,7 @@ const I18N = {
|
|||||||
snapTooltip:"此文档的早期状态", snapMenuAria:"早期状态",
|
snapTooltip:"此文档的早期状态", snapMenuAria:"早期状态",
|
||||||
snapNone:"暂无早期状态——可随时手动保存;内容有变动时,也会每 10 分钟保存一次。",
|
snapNone:"暂无早期状态——可随时手动保存;内容有变动时,也会每 10 分钟保存一次。",
|
||||||
snapAddTooltip:"立即保存当前状态",
|
snapAddTooltip:"立即保存当前状态",
|
||||||
|
idsTooltip:"在标题前显示节点 ID",
|
||||||
snapLines:"{n} 行",
|
snapLines:"{n} 行",
|
||||||
docNew:"新建文档", docRename:"重命名", docDelete:"删除",
|
docNew:"新建文档", docRename:"重命名", docDelete:"删除",
|
||||||
docNewName:"未命名",
|
docNewName:"未命名",
|
||||||
@@ -2514,6 +2535,7 @@ const I18N = {
|
|||||||
snapTooltip:"このドキュメントの以前の状態", snapMenuAria:"以前の状態",
|
snapTooltip:"このドキュメントの以前の状態", snapMenuAria:"以前の状態",
|
||||||
snapNone:"以前の状態はまだありません — ボタンを押したとき、および変更があれば 10 分ごとに保存されます。",
|
snapNone:"以前の状態はまだありません — ボタンを押したとき、および変更があれば 10 分ごとに保存されます。",
|
||||||
snapAddTooltip:"現在の状態を今すぐ保存",
|
snapAddTooltip:"現在の状態を今すぐ保存",
|
||||||
|
idsTooltip:"タイトルの前にノード ID を表示",
|
||||||
snapLines:"{n} 行",
|
snapLines:"{n} 行",
|
||||||
docNew:"新規ドキュメント", docRename:"名前を変更", docDelete:"削除",
|
docNew:"新規ドキュメント", docRename:"名前を変更", docDelete:"削除",
|
||||||
docNewName:"無題",
|
docNewName:"無題",
|
||||||
@@ -2808,6 +2830,7 @@ function saveUI(){
|
|||||||
mode: modeEl ? modeEl.value : 'horizontal',
|
mode: modeEl ? modeEl.value : 'horizontal',
|
||||||
discarded: discardedShown(),
|
discarded: discardedShown(),
|
||||||
cheapPath: cheapPathOn,
|
cheapPath: cheapPathOn,
|
||||||
|
showIds,
|
||||||
split: splitState,
|
split: splitState,
|
||||||
col: app.style.getPropertyValue('--col') || null,
|
col: app.style.getPropertyValue('--col') || null,
|
||||||
drow: app.style.getPropertyValue('--drow') || null,
|
drow: app.style.getPropertyValue('--drow') || null,
|
||||||
@@ -2846,6 +2869,10 @@ function restoreState(){
|
|||||||
cheapPathOn = ui.cheapPath;
|
cheapPathOn = ui.cheapPath;
|
||||||
cheapBtn.setAttribute('aria-pressed', cheapPathOn ? 'true' : 'false');
|
cheapBtn.setAttribute('aria-pressed', cheapPathOn ? 'true' : 'false');
|
||||||
}
|
}
|
||||||
|
if(typeof ui.showIds === 'boolean'){
|
||||||
|
showIds = ui.showIds;
|
||||||
|
idsBtn.setAttribute('aria-pressed', showIds ? 'true' : 'false');
|
||||||
|
}
|
||||||
if(typeof ui.zoom === 'number') zoom = ui.zoom;
|
if(typeof ui.zoom === 'number') zoom = ui.zoom;
|
||||||
if(ui.split) splitState = ui.split;
|
if(ui.split) splitState = ui.split;
|
||||||
if(ui.fullscreen){
|
if(ui.fullscreen){
|
||||||
|
|||||||
+10
-1
@@ -90,7 +90,7 @@ function nodeAria(n, opts, fold){
|
|||||||
}
|
}
|
||||||
|
|
||||||
function nodeHtml(n, extra, opts, fold){
|
function nodeHtml(n, extra, opts, fold){
|
||||||
const { t, cheapPath } = opts;
|
const { t, cheapPath, showIds } = opts;
|
||||||
const need = needsBreakdown(n);
|
const need = needsBreakdown(n);
|
||||||
/* Die Knotenfarbe zeigt den EFFEKTIVEN Status (SPEC §9/D39); bei Diskrepanz
|
/* Die Knotenfarbe zeigt den EFFEKTIVEN Status (SPEC §9/D39); bei Diskrepanz
|
||||||
trägt die Marke unten links die eigene Statusbox in den eigenen Farben. */
|
trägt die Marke unten links die eigene Statusbox in den eigenen Farben. */
|
||||||
@@ -163,7 +163,16 @@ function nodeHtml(n, extra, opts, fold){
|
|||||||
const ownChip = effKey
|
const ownChip = effKey
|
||||||
? `<span class="chip ownst st-${n.status.key}" aria-hidden="true">[${n.status.code}]</span>`
|
? `<span class="chip ownst st-${n.status.key}" aria-hidden="true">[${n.status.code}]</span>`
|
||||||
: '';
|
: '';
|
||||||
|
/* ID vor dem Titel, mit `: ` abgetrennt — dieselbe Schreibweise wie im Text
|
||||||
|
(SPEC §1/D36), damit man beides nebeneinander lesen kann. Umschaltbar im
|
||||||
|
Diagramm-Kopf; als Renderer-Option und nicht per CSS versteckt, damit der
|
||||||
|
Grafikexport (er liest den Knotentext) von selbst folgt. `aria-hidden`:
|
||||||
|
Der Screenreader bekommt die ID schon über `a11yId` (D56). */
|
||||||
|
const idHtml = showIds && n.id
|
||||||
|
? `<span class="nid" aria-hidden="true">#${esc(n.id)}:</span> `
|
||||||
|
: '';
|
||||||
const inner = foldHtml +
|
const inner = foldHtml +
|
||||||
|
idHtml +
|
||||||
esc(n.label) +
|
esc(n.label) +
|
||||||
/* ”-Marke (D40): macht die sonst unsichtbare Beschreibung
|
/* ”-Marke (D40): macht die sonst unsichtbare Beschreibung
|
||||||
auffindbar (Lehre aus D25) — spiegelt das "-Zeichen der
|
auffindbar (Lehre aus D25) — spiegelt das "-Zeichen der
|
||||||
|
|||||||
+31
-6
@@ -305,7 +305,12 @@
|
|||||||
.panel-head{
|
.panel-head{
|
||||||
flex:0 0 auto;position:relative;
|
flex:0 0 auto;position:relative;
|
||||||
padding:10px 16px;
|
padding:10px 16px;
|
||||||
display:flex;justify-content:space-between;align-items:center;gap:14px;
|
/* gap:10 statt 14 — dieselbe Rechnung wie in D17-Nachtrag 5, nur für den
|
||||||
|
Schreibtisch: Beim zehnten Bedienelement (D56) fehlten 10 px, und zehn
|
||||||
|
Lücken à 14 px machen 140 px Weißraum für 566 px Knöpfe. Verkleinert
|
||||||
|
wird der Zwischenraum, nicht das Ziel; 4 px weniger je Lücke geben die
|
||||||
|
Breite des neuen Knopfes zurück. */
|
||||||
|
display:flex;justify-content:space-between;align-items:center;gap:10px;
|
||||||
font-family:'IBM Plex Mono',monospace;
|
font-family:'IBM Plex Mono',monospace;
|
||||||
font-size:.7rem;letter-spacing:.08em;text-transform:uppercase;
|
font-size:.7rem;letter-spacing:.08em;text-transform:uppercase;
|
||||||
color:var(--muted);
|
color:var(--muted);
|
||||||
@@ -635,6 +640,21 @@
|
|||||||
nachlesbar. Spiegelt das "-Zeichen der Notation; gedeckt, kein Klickziel. */
|
nachlesbar. Spiegelt das "-Zeichen der Notation; gedeckt, kein Klickziel. */
|
||||||
.node .desc-mark{color:var(--muted);margin-left:4px;font-weight:600}
|
.node .desc-mark{color:var(--muted);margin-left:4px;font-weight:600}
|
||||||
.root-node .desc-mark{color:rgba(255,255,255,.75)}
|
.root-node .desc-mark{color:rgba(255,255,255,.75)}
|
||||||
|
/* Knoten-ID vor dem Titel (D56). Zurückgenommen und in der Mono-Schrift des
|
||||||
|
Textfelds: Sie ist die Adresse, nicht der Name — der Titel soll die Zeile
|
||||||
|
weiter anführen. Kein `margin-right`; das trennende Leerzeichen steht im
|
||||||
|
Markup, damit der Grafikexport (er liest den Knotentext) es mitbekommt. */
|
||||||
|
.node .nid{
|
||||||
|
font-family:'IBM Plex Mono',monospace;font-size:.86em;font-weight:500;
|
||||||
|
color:var(--muted);
|
||||||
|
}
|
||||||
|
.root-node .nid{color:rgba(255,255,255,.75)}
|
||||||
|
/* Der Knopf trägt das Zeichen selbst statt eines Icons — in der Größe der
|
||||||
|
SVGs daneben, damit die Kopfzeile ihre Höhe behält. */
|
||||||
|
.copybtn.idsbtn span{
|
||||||
|
display:block;width:15px;height:15px;line-height:15px;text-align:center;
|
||||||
|
font-family:'IBM Plex Mono',monospace;font-size:14px;font-weight:600;
|
||||||
|
}
|
||||||
/* Sichtbarer Tastatur-Fokus (Knoten sind fokussierbar: Fokusreihenfolge =
|
/* Sichtbarer Tastatur-Fokus (Knoten sind fokussierbar: Fokusreihenfolge =
|
||||||
Lese-/Dokumentreihenfolge; Screenreader liest den aria-label). */
|
Lese-/Dokumentreihenfolge; Screenreader liest den aria-label). */
|
||||||
.node:focus-visible{outline:2px solid var(--or);outline-offset:2px;box-shadow:0 2px 8px rgba(36,52,71,.22)}
|
.node:focus-visible{outline:2px solid var(--or);outline-offset:2px;box-shadow:0 2px 8px rgba(36,52,71,.22)}
|
||||||
@@ -1235,17 +1255,22 @@
|
|||||||
ein sichtbarer Überlauf (D17-Nachtrag 5). */
|
ein sichtbarer Überlauf (D17-Nachtrag 5). */
|
||||||
body.mobile .panel-head{gap:6px;flex-wrap:nowrap;padding-inline:8px}
|
body.mobile .panel-head{gap:6px;flex-wrap:nowrap;padding-inline:8px}
|
||||||
body.mobile .panel-head>*{flex:0 0 auto}
|
body.mobile .panel-head>*{flex:0 0 auto}
|
||||||
/* Unterhalb von rund 360 px ist es reine Arithmetik: neun Bedienelemente à
|
/* Unterhalb von rund 440 px ist es reine Arithmetik: **zehn** Bedienelemente
|
||||||
29 px plus Lücken brauchen mehr, als dort überhaupt da ist (bei 320 px
|
à 29 px (D56 hat den ID-Umschalter hinzugefügt) plus Lücken brauchen
|
||||||
fehlen 41 px). Kleiner als 29 px darf ein Fingerziel nicht werden
|
386 px, bei 375 px stehen 359 zur Verfügung. Kleiner als 29 px darf ein
|
||||||
(D17-Nachtrag 5) — dort darf die Zeile deshalb umbrechen.
|
Fingerziel nicht werden (D17-Nachtrag 5) — dort darf die Zeile deshalb
|
||||||
|
umbrechen. Sie tut es aber nur, wenn wirklich alle zehn da sind: Der
|
||||||
|
„Was ist neu?"-Knopf (44 px) erscheint nur bei Neuigkeiten, ohne ihn
|
||||||
|
bleiben 336 px und damit eine Reihe. Die Schwelle stand vorher bei 360 px
|
||||||
|
und ist mit dem zehnten Element mitgewandert — dieselbe Rechnung, ein
|
||||||
|
Element mehr.
|
||||||
**Kein `overflow` an dieser Stelle** (D50): Die Kopfzeile trägt die beiden
|
**Kein `overflow` an dieser Stelle** (D50): Die Kopfzeile trägt die beiden
|
||||||
Aufklapp-Menüs (Dokumente, Download) als absolut positionierte Kinder. Ein
|
Aufklapp-Menüs (Dokumente, Download) als absolut positionierte Kinder. Ein
|
||||||
Scroll-Container klippt sie — und weil `overflow-x` das `visible` der
|
Scroll-Container klippt sie — und weil `overflow-x` das `visible` der
|
||||||
anderen Achse auf `auto` hebt, klippt er auch nach unten, wo die Menüs
|
anderen Achse auf `auto` hebt, klippt er auch nach unten, wo die Menüs
|
||||||
stehen. Sie waren damit auf **jeder** Mobilbreite unerreichbar, nicht nur
|
stehen. Sie waren damit auf **jeder** Mobilbreite unerreichbar, nicht nur
|
||||||
auf den schmalen. */
|
auf den schmalen. */
|
||||||
@media (max-width:360px){
|
@media (max-width:440px){
|
||||||
body.mobile .panel-head{flex-wrap:wrap}
|
body.mobile .panel-head{flex-wrap:wrap}
|
||||||
}
|
}
|
||||||
/* Inhalte rund 25 % kleiner, damit mehr Plan auf die Fläche passt
|
/* Inhalte rund 25 % kleiner, damit mehr Plan auf die Fläche passt
|
||||||
|
|||||||
@@ -141,3 +141,42 @@ describe('renderTreeHtml — data-line je Knoten (D25)', () => {
|
|||||||
expect(html).not.toMatch(/ghost-node[^>]*data-line/);
|
expect(html).not.toMatch(/ghost-node[^>]*data-line/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* Knoten-IDs im Diagramm (D56). Als Renderer-Option gebaut und nicht per CSS
|
||||||
|
versteckt — nur so folgt der Grafikexport, der den Knotentext ausliest. */
|
||||||
|
describe('showIds — die ID vor dem Titel, geschrieben wie im Text', () => {
|
||||||
|
const ids = (text, showIds) => renderTreeHtml(parse(text).roots,
|
||||||
|
{t, showDiscarded: false, cheapPath: false, cheapSet: new Set(), showIds}).html;
|
||||||
|
|
||||||
|
it('bleibt ohne Umschalter aus dem Knotentext heraus', () => {
|
||||||
|
const html = ids('#auth: Backend', false);
|
||||||
|
expect(html).toContain('>Backend<');
|
||||||
|
expect(html).not.toContain('nid');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('stellt die ID mit Doppelpunkt und Leerzeichen davor', () => {
|
||||||
|
expect(ids('#auth: Backend', true))
|
||||||
|
.toContain('<span class="nid" aria-hidden="true">#auth:</span> Backend');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('lässt Knoten ohne ID unangetastet', () => {
|
||||||
|
const html = ids('- [x] Ohne ID', true);
|
||||||
|
expect(html).toContain('>Ohne ID<');
|
||||||
|
expect(html).not.toContain('nid');
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Die ID steht ohnehin im aria-label (D36) — sichtbar doppelt vorgelesen
|
||||||
|
würde sie sonst. */
|
||||||
|
it('versteckt die sichtbare ID vor dem Screenreader', () => {
|
||||||
|
expect(ids('#auth: Backend', true)).toContain('<span class="nid" aria-hidden="true">');
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Markup kann in einer ID gar nicht vorkommen — `<`, `&` und `"` gehören
|
||||||
|
nicht zur Zeichenmenge (SPEC §1), die ID endet dort. Geprüft wird deshalb
|
||||||
|
genau das: `#a<b` ergibt die ID `a`, der Rest bleibt (escaped) im Label. */
|
||||||
|
it('endet an einem Zeichen außerhalb der ID-Zeichenmenge', () => {
|
||||||
|
const html = ids('#a<b: X', true);
|
||||||
|
expect(html).toContain('<span class="nid" aria-hidden="true">#a:</span>');
|
||||||
|
expect(html).toContain('<b: X');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user