feat(editor): Knopf, der einen Stand von Hand sichert

Zehn Minuten sind der falsche Takt für den einen Moment, in dem man einen
Stand wirklich will: unmittelbar vor einer größeren Änderung. Dann ist der
letzte selbsttätige Stand im Zweifel neun Minuten alt.

- Kamera statt Plus: Ein `+` neben dem Dokument-Namen läse sich als „neues
  Dokument“, ein Lesezeichen als „Favorit“. Gegen Uhr (Verlauf) und Caret
  (Dokumentwahl) ist die Kamera eindeutig.
- Die Rückmeldung kommt IMMER, auch wenn nichts angelegt wurde: Der Knopf
  verspricht „dein Stand ist gesichert“, nicht „ein Eintrag wurde erzeugt“.
  Bei unverändertem Text steht er schon oben in der Liste — einen doppelten
  Eintrag zu erzwingen kostete nur einen der 20 Plätze.
- Gezeigt per vorhandenem Haus-Idiom (`flashBtn`, 1,5 s Haken); dafür nur
  `.copybtn.done .ic-copy` um `.ic-main` erweitert.
- Pad-Fall folgt von selbst: beide Knöpfe in EINER Zuweisung versteckt.
- i18n `snapAddTooltip` in 9 Sprachen; der Leer-Text des Menüs nennt jetzt
  beide Wege.

Nachgemessen: 0 -> Klick -> 1 (Haken sichtbar, nach 1,7 s wieder Kamera);
zweiter Klick ohne Änderung -> weiterhin 1, Rückmeldung trotzdem; nach einer
Änderung -> 2. Im schmalen Layout kostet der Knopf nichts (Kopfhöhe 44 px
und Zeilenzahl mit und ohne ihn identisch).

D54-Nachtrag; Plan: #ed.snaps.manual [x] (158 Knoten, 0 Warnungen).
243 Tests grün.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-08-24 11:21:06 +02:00
co-authored by Claude Opus 5
parent 7abdd3bc45
commit f7d963809a
5 changed files with 100 additions and 11 deletions
+48
View File
@@ -3690,3 +3690,51 @@ schließt das Menü, das Diagramm zeichnet neu (30 Knoten) — und ein `undo` ho
den vorherigen Stand zeichengenau zurück. Der **Pad-Fall** ist durch die
`readOnly`-Wächter im Code abgedeckt, aber nicht live durchgespielt (er
bräuchte ein echtes Pad).
**Nachtrag — ein Knopf, der von Hand sichert.** Zehn Minuten sind der falsche
Takt für den einen Moment, in dem man einen Stand wirklich will: unmittelbar
**vor** einer größeren Änderung. Genau dann ist der letzte selbsttätige Stand
im Zweifel neun Minuten alt und enthält die Arbeit nicht, die man gleich
aufs Spiel setzt. Der Knopf steht rechts neben dem Verlaufs-Knopf, in
derselben Gruppe wie der Dokument-Wähler.
**Kamera, nicht Plus.** Ein `+` unmittelbar neben dem Dokument-Namen liest
sich als „neues Dokument" — das gibt es schon, im Menü daneben. Ein
Lesezeichen läse sich als „Favorit". Die Kamera ist gegen beide Nachbarn
eindeutig (Uhr mit Pfeil = Verlauf, Caret = Dokumentwahl) und trifft die
Vorstellung, die der Nutzer selbst benannt hat („einen Snapshot manuell
anfertigen"). Die UI-Texte bleiben bei „Stand", weil das Menü daneben so
heißt.
**Die Rückmeldung kommt immer — auch wenn nichts angelegt wurde.** Ein Knopf,
der bei unverändertem Text stumm bleibt, wirkt kaputt; genau das passiert,
denn `snapshotNow()` legt keinen doppelten Eintrag an. Der Ausweg ist keine
zweite Meldung, sondern eine genauere Zusage: Der Knopf verspricht **„dein
aktueller Stand ist gesichert"**, nicht „ein Eintrag wurde erzeugt". Bei
unverändertem Text steht er bereits oben in der Liste — die Zusage stimmt
also in beiden Fällen, und es braucht weder eine Fallunterscheidung noch
einen zehnten i18n-Text. Einen Eintrag zu erzwingen wäre die schlechtere
Wahl: Er kostete einen der zwanzig Plätze für Text, der schon da ist.
Gezeigt wird das mit dem **vorhandenen** Haus-Idiom: `flashBtn()` setzt für
1,5 s die Klasse `done`, die Petrol färbt und das Icon gegen einen Haken
tauscht — dieselbe Rückmeldung wie bei „kopieren" und „herunterladen". Dafür
musste nur die CSS-Regel `.copybtn.done .ic-copy` um `.ic-main` erweitert
werden: die neutrale Form desselben Platzes für Knöpfe, die nichts kopieren.
Ein **offenes Menü bleibt offen** und zeichnet neu — dort sieht man den
Eintrag entstehen. Der Pad-Fall folgt von selbst: Beide Knöpfe werden in
**einer** Zuweisung versteckt (`snapBtn.hidden = snapAddBtn.hidden =
src.readOnly`), sie können also nicht auseinanderlaufen.
Der Leer-Text des Menüs nennt jetzt beide Wege („auf Knopfdruck und alle 10
Minuten") — er ist die Stelle, an der jemand fragt, wie man überhaupt zu
einem Stand kommt.
**Nachgemessen** (Werkbaum-Plan, Stände zuvor leer): 0 → Klick → **1**,
Haken sichtbar (`ic-done` block, `ic-main` none), nach 1,7 s wieder das
Kamera-Icon. Zweiter Klick **ohne** Änderung → weiterhin 1, Rückmeldung
trotzdem. Text geändert, Klick → **2**; Menü zeigt beide, neueste zuoberst,
und liegt vollständig im Panel. Im schmalen Layout kostet der Knopf nichts:
Kopfhöhe 44 px und Zeilenzahl sind mit und ohne ihn identisch, die Breite des
Dokument-Namens bleibt bei 54 px.
+7
View File
@@ -74,6 +74,7 @@
- [^] #ed.fold.write: Folding writes the mark back into the text (S) %% one undo step
- [^] #ed.fold.preset: One switch folds everything from size M downward (S) %% see D44
- [^] #ed.snaps: Earlier states of a document, every ten minutes (S) %% only when something changed
- [x] #ed.snaps.manual: Save a state by hand, before a larger change (XS) %% ten minutes is the wrong beat for that moment
- [ ] #ed.files: Open and save .werkbaum files (S)
+ [^] #ed.fresh: Show what is new since your last visit (S)
+ [?] #ed.percolor: A pastel colour per person (S)
@@ -521,6 +522,12 @@
beside the document picker. Loading one is a single undo step, so a wrong
pick costs nothing.
#ed.snaps.manual
Ten minutes is the wrong beat for the one moment a state is really wanted:
right before a larger change. The button promises that the current state is
kept, not that an entry was added, so it confirms even when the text is
unchanged and already at the top of the list.
#ed.files
Open a .werkbaum file and save one. Today the leading data format is the one
thing that cannot leave the browser as a file, while the diagram can.
+7
View File
@@ -74,6 +74,13 @@
</button>
<div class="snapmenu" id="snapMenu" role="menu" data-i18n-aria="snapMenuAria" aria-label="Frühere Stände" hidden></div>
</span>
<!-- Von Hand sichern, z. B. vor einer größeren Änderung. Kamera statt
Plus: ein Plus neben dem Dokument-Namen läse sich als „neues
Dokument“ (D54-Nachtrag). -->
<button type="button" class="copybtn snapaddbtn" id="snapAddBtn" data-i18n-title="snapAddTooltip" data-i18n-aria="snapAddTooltip" title="Aktuellen Stand jetzt sichern" aria-label="Aktuellen Stand jetzt sichern">
<svg class="ic-main" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 9a2 2 0 0 1 2-2h2.2l1.3-2h6.9l1.3 2H19a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z"/><circle cx="12" cy="13" r="3.1"/></svg>
<svg class="ic-done" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6L9 17l-5-5"/></svg>
</button>
</span>
<button type="button" class="copybtn padview" id="padViewBtn" data-view="text" hidden>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect class="frame" x="2.5" y="5" width="19" height="14" rx="2"/><line class="ic-both" x1="12" y1="5" x2="12" y2="19"/><rect class="ic-pad" x="3.5" y="6" width="8" height="12" rx="1" fill="currentColor" stroke="none"/><rect class="ic-text" x="12.5" y="6" width="8" height="12" rx="1" fill="currentColor" stroke="none"/></svg>
+35 -10
View File
@@ -1761,7 +1761,8 @@ const I18N = {
docSwitchTooltip:"Dokument wählen oder verwalten", docMenuAria:"Dokumente",
/* Frühere Stände (D54) — alle 10 Minuten, nur bei Änderung. */
snapTooltip:"Frühere Stände dieses Dokuments", snapMenuAria:"Frühere Stände",
snapNone:"Noch keine früheren Stände gesichert wird 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",
snapLines:"{n} Zeilen",
docNew:"Neues Dokument", docRename:"Umbenennen", docDelete:"Löschen",
docNewName:"Unbenannt",
@@ -1849,7 +1850,8 @@ const I18N = {
editorTitle:"Structure (text)", diagramTitle:"Diagram",
docSwitchTooltip:"Choose or manage document", docMenuAria:"Documents",
snapTooltip:"Earlier states of this document", snapMenuAria:"Earlier states",
snapNone:"No earlier states yet one is kept 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",
snapLines:"{n} lines",
docNew:"New document", docRename:"Rename", docDelete:"Delete",
docNewName:"Untitled",
@@ -1937,7 +1939,8 @@ const I18N = {
editorTitle:"Estructura (texto)", diagramTitle:"Diagrama",
docSwitchTooltip:"Elegir o gestionar documento", docMenuAria:"Documentos",
snapTooltip:"Estados anteriores de este documento", snapMenuAria:"Estados anteriores",
snapNone:"Aún no hay estados anteriores: se guarda uno 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",
snapLines:"{n} líneas",
docNew:"Nuevo documento", docRename:"Renombrar", docDelete:"Eliminar",
docNewName:"Sin título",
@@ -2025,7 +2028,8 @@ const I18N = {
editorTitle:"Structure (texte)", diagramTitle:"Diagramme",
docSwitchTooltip:"Choisir ou gérer le document", docMenuAria:"Documents",
snapTooltip:"États antérieurs de ce document", snapMenuAria:"États antérieurs",
snapNone:"Pas encore d’état antérieur : un état est conservé 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",
snapLines:"{n} lignes",
docNew:"Nouveau document", docRename:"Renommer", docDelete:"Supprimer",
docNewName:"Sans titre",
@@ -2113,7 +2117,8 @@ const I18N = {
editorTitle:"Struktura (tekst)", diagramTitle:"Diagram",
docSwitchTooltip:"Wybierz lub zarządzaj dokumentem", docMenuAria:"Dokumenty",
snapTooltip:"Wcześniejsze stany tego dokumentu", snapMenuAria:"Wcześniejsze stany",
snapNone:"Brak wcześniejszych stanów zapisywany jest 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",
snapLines:"wierszy: {n}",
docNew:"Nowy dokument", docRename:"Zmień nazwę", docDelete:"Usuń",
docNewName:"Bez nazwy",
@@ -2201,7 +2206,8 @@ const I18N = {
editorTitle:"Структура (текст)", diagramTitle:"Диаграмма",
docSwitchTooltip:"Выбрать документ или управлять им", docMenuAria:"Документы",
snapTooltip:"Прежние состояния этого документа", snapMenuAria:"Прежние состояния",
snapNone:"Прежних состояний пока нет — они сохраняются каждые 10 минут, если что-то изменилось.",
snapNone:"Прежних состояний пока нет — они сохраняются по нажатию кнопки и каждые 10 минут, если что-то изменилось.",
snapAddTooltip:"Сохранить текущее состояние сейчас",
snapLines:"строк: {n}",
docNew:"Новый документ", docRename:"Переименовать", docDelete:"Удалить",
docNewName:"Без названия",
@@ -2289,7 +2295,8 @@ const I18N = {
editorTitle:"संरचना (टेक्स्ट)", diagramTitle:"आरेख",
docSwitchTooltip:"दस्तावेज़ चुनें या प्रबंधित करें", docMenuAria:"दस्तावेज़",
snapTooltip:"इस दस्तावेज़ की पिछली स्थितियाँ", snapMenuAria:"पिछली स्थितियाँ",
snapNone:"अभी कोई पिछली स्थिति नहीं — कुछ बदलने पर हर 10 मिनट में एक सहेजी जाती है।",
snapNone:"अभी कोई पिछली स्थिति नहीं — बटन दबाने पर और कुछ बदलने पर हर 10 मिनट में एक सहेजी जाती है।",
snapAddTooltip:"मौजूदा स्थिति अभी सहेजें",
snapLines:"{n} पंक्तियाँ",
docNew:"नया दस्तावेज़", docRename:"नाम बदलें", docDelete:"हटाएँ",
docNewName:"बिना शीर्षक",
@@ -2377,7 +2384,8 @@ const I18N = {
editorTitle:"结构(文本)", diagramTitle:"图表",
docSwitchTooltip:"选择或管理文档", docMenuAria:"文档",
snapTooltip:"此文档的早期状态", snapMenuAria:"早期状态",
snapNone:"暂无早期状态——内容有变动时,每 10 分钟保存一次。",
snapNone:"暂无早期状态——可随时手动保存;内容有变动时,也会每 10 分钟保存一次。",
snapAddTooltip:"立即保存当前状态",
snapLines:"{n} 行",
docNew:"新建文档", docRename:"重命名", docDelete:"删除",
docNewName:"未命名",
@@ -2465,7 +2473,8 @@ const I18N = {
editorTitle:"構造(テキスト)", diagramTitle:"ダイアグラム",
docSwitchTooltip:"ドキュメントを選択・管理", docMenuAria:"ドキュメント",
snapTooltip:"このドキュメントの以前の状態", snapMenuAria:"以前の状態",
snapNone:"以前の状態はまだありません — 変更があれば 10 分ごとに保存されます。",
snapNone:"以前の状態はまだありません — ボタンを押したとき、および変更があれば 10 分ごとに保存されます。",
snapAddTooltip:"現在の状態を今すぐ保存",
snapLines:"{n} 行",
docNew:"新規ドキュメント", docRename:"名前を変更", docDelete:"削除",
docNewName:"無題",
@@ -2972,6 +2981,7 @@ function snapshotNow(){
setInterval(snapshotNow, SNAP_EVERY);
const snapBtn = document.getElementById('snapBtn');
const snapAddBtn = document.getElementById('snapAddBtn');
const snapMenu = document.getElementById('snapMenu');
function snapLabel(ms){
@@ -3027,11 +3037,26 @@ function closeSnapMenu(){
snapMenu.hidden = true;
snapBtn.setAttribute('aria-expanded', 'false');
}
function updateSnapBtn(){ snapBtn.hidden = src.readOnly; if(src.readOnly) closeSnapMenu(); }
function updateSnapBtn(){
snapBtn.hidden = snapAddBtn.hidden = src.readOnly;
if(src.readOnly) closeSnapMenu();
}
snapBtn.addEventListener('click', e => {
e.stopPropagation();
snapMenu.hidden ? openSnapMenu() : closeSnapMenu();
});
/* Von Hand sichern der Fall gleich ändere ich viel, für den zehn Minuten
zu lang sind. Die Rückmeldung kommt **immer**, auch wenn `snapshotNow()`
nichts angelegt hat: Der Knopf verspricht dein Stand ist gesichert, nicht
ein Eintrag wurde erzeugt bei unverändertem Text steht er schon oben in
der Liste. Einen doppelten Eintrag zu erzwingen kostete nur einen der 20
Plätze (D54-Nachtrag). Ein offenes Menü bleibt offen und zeichnet neu, dort
sieht man den neuen Eintrag entstehen. */
snapAddBtn.addEventListener('click', e => {
e.stopPropagation();
snapshotNow();
flashBtn(snapAddBtn);
});
document.addEventListener('click', e => {
if(!snapMenu.hidden && !snapMenu.contains(e.target) && !snapBtn.contains(e.target)) closeSnapMenu();
});
+3 -1
View File
@@ -362,7 +362,9 @@
.copybtn:hover,.copybtn.done{border-color:var(--or);color:var(--or)}
.copybtn svg{display:block;width:15px;height:15px}
.copybtn .ic-done{display:none}
.copybtn.done .ic-copy{display:none}
/* `.ic-main` ist die neutrale Form von `.ic-copy` für Knöpfe, die nichts
kopieren, aber dieselbe Erfolgs-Rückmeldung geben (Stand sichern, D54). */
.copybtn.done .ic-copy,.copybtn.done .ic-main{display:none}
.copybtn.done .ic-done{display:block}
/* im Pad bearbeiten" (D31) nur bei einem ?etherpad=-Dokument sichtbar.
Das [hidden] braucht die eigene Regel, weil .copybtn ein display setzt. */