From ea2c7eef4aee42703d26907aee89c84f6bc25a94 Mon Sep 17 00:00:00 2001 From: mhoennig Date: Sat, 22 Aug 2026 20:04:10 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20=E2=80=9EOriginal=20wiederherstellen"?= =?UTF-8?q?=20im=20Dokumenten-Men=C3=BC=20(D22-Nachtrag)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Auf der Prod-Instanz gab es keinen Weg, ein bearbeitetes mitgeliefertes Dokument wieder auf den Auslieferungsstand zu bringen — der Reset ist ein Debug-Knopf, und das Fingerabdruck-Nachziehen (D27) fasst bearbeitete Texte nie an. Stilles Überschreiben beim Laden wäre überraschender Datenverlust (verworfen); stattdessen ein Menü-Eintrag, sichtbar nur für mitgelieferte Dokumente und nur bei Abweichung vom Auslieferungsstand, mit Rückfrage. Stellt Text und Name wieder her, verwirft Falt-Eingriffe. i18n in allen 9 Sprachen; im Browser durchgespielt (erscheint nach Edit, stellt her, verschwindet wieder). Co-Authored-By: Claude Fable 5 --- docs/DECISIONS.md | 15 ++++++++++ docs/examples/werkbaum.werkbaum | 1 + frontend/CLAUDE.md | 5 +++- frontend/index.html | 1 + frontend/src/app.js | 49 ++++++++++++++++++++++++++++++++- 5 files changed, 69 insertions(+), 2 deletions(-) diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md index 41a4aa7..b5b5539 100644 --- a/docs/DECISIONS.md +++ b/docs/DECISIONS.md @@ -387,6 +387,21 @@ Die GUI-Ansichts-Einstellungen (Modus, Zoom, Aufteilung; `werkbaum-ui`) bleiben bewusst **global** über alle Dokumente — pro-Dokument-Ansichtszustand wäre eine spätere Erweiterung. +**Nachtrag — „Original wiederherstellen“ ist jetzt ein Produkt-Feature im +Dokumenten-Menü.** Auslöser: Auf der Prod-Instanz gab es **keinen** Weg, ein +bearbeitetes mitgeliefertes Dokument wieder auf den Auslieferungsstand zu +bringen — der Reset ist ein Debug-Knopf außerhalb des Prod-Builds, und das +Fingerabdruck-Nachziehen (D27) fasst bearbeitete Texte grundsätzlich nicht an. +Wer im Beispiel herumprobiert hatte, sah neue Beispiel-Fassungen also nie. +Erwogen und **verworfen**: die Alternative, dass eine neue Fassung lokale +Änderungen still überschreibt — das wäre überraschender Datenverlust und +bräche die D22/D27-Linie („bearbeitete Inhalte nie anfassen“). Stattdessen ein +Menü-Eintrag, sichtbar nur für die **mitgelieferten** Dokumente und nur, wenn +Text oder Name vom Auslieferungsstand abweichen; mit Rückfrage, die den +Verlust benennt. Wiederhergestellt werden Text **und** Name (der Eintrag sagt +„Original“); Falt-Eingriffe werden mit verworfen. Der Debug-Reset bleibt +daneben bestehen — er setzt zusätzlich Einstellungen und Merker zurück. + ## D23 — Notationstext per `?sourceUrl=` von einer URL laden Der Editor kann den Notationstext aus einer **externen Textdatei** beziehen: `…/index.html?sourceUrl=https://example.org/plan.txt`. Damit lässt sich ein diff --git a/docs/examples/werkbaum.werkbaum b/docs/examples/werkbaum.werkbaum index d02be3d..d04867a 100644 --- a/docs/examples/werkbaum.werkbaum +++ b/docs/examples/werkbaum.werkbaum @@ -58,6 +58,7 @@ - [^] Several documents in one editor (M) - [^] Switcher in the editor title bar (S) - [^] Load a document from ?sourceUrl= (S) + - [x] Restore a shipped document from the menu (XS) - [^] Jump between diagram and text (S) - [^] Line numbers in the text editor (XS) %% the warnings name them - [^] Collapsible subtrees (M) diff --git a/frontend/CLAUDE.md b/frontend/CLAUDE.md index fe0d013..48ef758 100644 --- a/frontend/CLAUDE.md +++ b/frontend/CLAUDE.md @@ -105,7 +105,10 @@ verworfene Elemente. Quelle sind ES-Module unter `src/`; `index.html` ist der `text === INITIAL` (nie echte Nutzerinhalte). Eine neue INITIAL-Fassung wird wie beim Werkbaum-Dokument per Fingerabdruck nachgezogen (`werkbaum-seeded-example`, D27-Nachtrag) — nur bei unverändertem Text; der - Reset setzt beide Merker. `resetToDefaults()` setzt **nur** + Reset setzt beide Merker. Bearbeitete mitgelieferte Dokumente holt der + Nutzer über „Original wiederherstellen" im Dokumenten-Menü zurück + (`restoreDoc()`, D22-Nachtrag) — sichtbar nur bei Abweichung vom + Auslieferungsstand, stellt Text UND Name wieder her. `resetToDefaults()` setzt **nur** das Beispiel-Dokument (id `example`) auf `INITIAL`/„Example" zurück und verwirft `werkbaum-ui`/`werkbaum-lang`/Update-Flags — **andere Dokumente bleiben stehen** (nicht mehr pauschal alle `werkbaum-*` löschen!). Das letzte gelöschte Dokument diff --git a/frontend/index.html b/frontend/index.html index a3fbdb2..13c2518 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -55,6 +55,7 @@
+
diff --git a/frontend/src/app.js b/frontend/src/app.js index 424bfe2..92ca5b2 100644 --- a/frontend/src/app.js +++ b/frontend/src/app.js @@ -1273,6 +1273,8 @@ const I18N = { docNew:"Neues Dokument", docRename:"Umbenennen", docDelete:"Löschen", docNewName:"Unbenannt", docDeleteConfirm:"Dokument „{name}“ löschen?", + docRestore:"Original wiederherstellen", + docRestoreConfirm:"„{name}“ auf den mitgelieferten Stand zurücksetzen? Eigene Änderungen gehen verloren.", copy:"kopieren", copyDone:"kopiert ✓", copyTooltip:"Text in die Zwischenablage kopieren", copyDiagramTooltip:"Diagramm als PNG-Bild in die Zwischenablage kopieren", downloadDiagramTooltip:"Diagramm als SVG-Datei herunterladen (z. B. für LibreOffice: Einfügen → Bild)", @@ -1349,6 +1351,8 @@ const I18N = { docNew:"New document", docRename:"Rename", docDelete:"Delete", docNewName:"Untitled", docDeleteConfirm:"Delete document “{name}”?", + docRestore:"Restore original", + docRestoreConfirm:"Reset “{name}” to the shipped version? Your changes will be lost.", copy:"copy", copyDone:"copied ✓", copyTooltip:"Copy text to clipboard", copyDiagramTooltip:"Copy diagram as a PNG image to the clipboard", downloadDiagramTooltip:"Download diagram as an SVG file (e.g. for LibreOffice: Insert → Image)", @@ -1425,6 +1429,8 @@ const I18N = { docNew:"Nuevo documento", docRename:"Renombrar", docDelete:"Eliminar", docNewName:"Sin título", docDeleteConfirm:"¿Eliminar el documento «{name}»?", + docRestore:"Restaurar original", + docRestoreConfirm:"¿Restablecer «{name}» a la versión incluida? Tus cambios se perderán.", copy:"copiar", copyDone:"copiado ✓", copyTooltip:"Copiar el texto al portapapeles", copyDiagramTooltip:"Copiar el diagrama como imagen PNG al portapapeles", downloadDiagramTooltip:"Descargar el diagrama como archivo SVG (p. ej. para LibreOffice: Insertar → Imagen)", @@ -1501,6 +1507,8 @@ const I18N = { docNew:"Nouveau document", docRename:"Renommer", docDelete:"Supprimer", docNewName:"Sans titre", docDeleteConfirm:"Supprimer le document « {name} » ?", + docRestore:"Restaurer l’original", + docRestoreConfirm:"Réinitialiser « {name} » à la version livrée ? Vos modifications seront perdues.", copy:"copier", copyDone:"copié ✓", copyTooltip:"Copier le texte dans le presse-papiers", copyDiagramTooltip:"Copier le diagramme comme image PNG dans le presse-papiers", downloadDiagramTooltip:"Télécharger le diagramme en fichier SVG (p. ex. pour LibreOffice : Insertion → Image)", @@ -1577,6 +1585,8 @@ const I18N = { docNew:"Nowy dokument", docRename:"Zmień nazwę", docDelete:"Usuń", docNewName:"Bez nazwy", docDeleteConfirm:"Usunąć dokument „{name}”?", + docRestore:"Przywróć oryginał", + docRestoreConfirm:"Przywrócić „{name}” do dostarczonej wersji? Twoje zmiany zostaną utracone.", copy:"kopiuj", copyDone:"skopiowano ✓", copyTooltip:"Kopiuj tekst do schowka", copyDiagramTooltip:"Kopiuj diagram jako obraz PNG do schowka", downloadDiagramTooltip:"Pobierz diagram jako plik SVG (np. dla LibreOffice: Wstaw → Obraz)", @@ -1653,6 +1663,8 @@ const I18N = { docNew:"Новый документ", docRename:"Переименовать", docDelete:"Удалить", docNewName:"Без названия", docDeleteConfirm:"Удалить документ «{name}»?", + docRestore:"Восстановить оригинал", + docRestoreConfirm:"Вернуть «{name}» к поставляемой версии? Ваши изменения будут потеряны.", copy:"копировать", copyDone:"скопировано ✓", copyTooltip:"Скопировать текст в буфер обмена", copyDiagramTooltip:"Скопировать диаграмму как изображение PNG в буфер обмена", downloadDiagramTooltip:"Скачать диаграмму как файл SVG (напр. для LibreOffice: Вставка → Изображение)", @@ -1729,6 +1741,8 @@ const I18N = { docNew:"नया दस्तावेज़", docRename:"नाम बदलें", docDelete:"हटाएँ", docNewName:"बिना शीर्षक", docDeleteConfirm:"दस्तावेज़ „{name}“ हटाएँ?", + docRestore:"मूल पुनर्स्थापित करें", + docRestoreConfirm:"„{name}“ को मूल संस्करण पर लौटाएँ? आपके परिवर्तन खो जाएँगे।", copy:"कॉपी करें", copyDone:"कॉपी हो गया ✓", copyTooltip:"टेक्स्ट को क्लिपबोर्ड पर कॉपी करें", copyDiagramTooltip:"आरेख को PNG छवि के रूप में क्लिपबोर्ड पर कॉपी करें", downloadDiagramTooltip:"आरेख को SVG फ़ाइल के रूप में डाउनलोड करें (जैसे LibreOffice: सम्मिलित करें → छवि)", @@ -1805,6 +1819,8 @@ const I18N = { docNew:"新建文档", docRename:"重命名", docDelete:"删除", docNewName:"未命名", docDeleteConfirm:"删除文档“{name}”?", + docRestore:"恢复原始版本", + docRestoreConfirm:"将“{name}”重置为随附版本?您的更改将丢失。", copy:"复制", copyDone:"已复制 ✓", copyTooltip:"将文本复制到剪贴板", copyDiagramTooltip:"将图表作为 PNG 图片复制到剪贴板", downloadDiagramTooltip:"将图表下载为 SVG 文件(例如用于 LibreOffice:插入 → 图像)", @@ -1881,6 +1897,8 @@ const I18N = { docNew:"新規ドキュメント", docRename:"名前を変更", docDelete:"削除", docNewName:"無題", docDeleteConfirm:"ドキュメント「{name}」を削除しますか?", + docRestore:"オリジナルを復元", + docRestoreConfirm:"「{name}」を同梱版に戻しますか?変更内容は失われます。", copy:"コピー", copyDone:"コピーしました ✓", copyTooltip:"テキストをクリップボードにコピー", copyDiagramTooltip:"ダイアグラムを PNG 画像としてクリップボードにコピー", downloadDiagramTooltip:"ダイアグラムを SVG ファイルとしてダウンロード(例:LibreOffice の 挿入 → 画像)", @@ -2275,7 +2293,35 @@ function renderDocMenu(){ } } } -function openDocMenu(){ renderDocMenu(); docMenu.hidden = false; docTrigger.setAttribute('aria-expanded', 'true'); } +/* „Original wiederherstellen" (D22-Nachtrag): nur für die mitgelieferten + Dokumente, und nur wenn sie vom Auslieferungsstand abweichen — der einzige + Weg auf der Prod-Instanz (ohne Debug-Reset), ein bearbeitetes Beispiel + wieder frisch zu bekommen. Bewusste Handlung mit Rückfrage statt stillem + Überschreiben beim Laden (D22/D27: bearbeitete Inhalte nie anfassen). */ +function shippedStateOf(id){ + if(id === EXAMPLE_ID) return {name: EXAMPLE_NAME, text: INITIAL}; + if(id === WERKBAUM_ID) return {name: WERKBAUM_NAME, text: WERKBAUM_DOC}; + return null; +} +function updateRestoreBtn(){ + const d = activeDoc(); + const shipped = d && shippedStateOf(d.id); + document.getElementById('docRestore').hidden = + !shipped || (d.text === shipped.text && d.name === shipped.name); +} +function restoreDoc(){ + const d = activeDoc(); + const shipped = d && shippedStateOf(d.id); + if(!shipped) return; + if(!window.confirm(t('docRestoreConfirm', {name: d.name}))) return; + d.text = shipped.text; + d.name = shipped.name; + foldOverrides.clear(); + loadActiveIntoEditor(); + persistDocs(); + closeDocMenu(); +} +function openDocMenu(){ renderDocMenu(); updateRestoreBtn(); docMenu.hidden = false; docTrigger.setAttribute('aria-expanded', 'true'); } function closeDocMenu(){ renamingId = null; docMenu.hidden = true; docTrigger.setAttribute('aria-expanded', 'false'); } function toggleDocMenu(){ docMenu.hidden ? openDocMenu() : closeDocMenu(); } /* Beim Wechseln/Anlegen/Löschen zuerst den aktuellen Editortext ins aktive @@ -2706,6 +2752,7 @@ docList.addEventListener('click', e => { document.getElementById('docNew').addEventListener('click', e => { e.stopPropagation(); newDoc(); renderDocMenu(); }); document.getElementById('docRename').addEventListener('click', e => { e.stopPropagation(); renameDoc(); }); document.getElementById('docDelete').addEventListener('click', e => { e.stopPropagation(); deleteDoc(); }); +document.getElementById('docRestore').addEventListener('click', e => { e.stopPropagation(); restoreDoc(); }); /* Klick außerhalb schließt das Menü. */ document.addEventListener('click', e => { if(!docMenu.hidden && !docMenu.contains(e.target) && !docTrigger.contains(e.target)) closeDocMenu();