feat: Strg+S speichert das Dokument direkt als Datei (D74)
Die Geste, die jeder zuerst versucht, tat bisher das Falsche (Browser-Dialog „Seite speichern"). Jetzt ruft sie saveLocalFile(): mit gemerktem Handle dialogfrei in dieselbe Datei, sichtbar bestätigt durch einen Petrol-Haken am Dokumentnamen; ohne Handle wie der Menü-Eintrag. Legenden-Zeile hint_save in neun Sprachen. Nebenbefund behoben: adoptFile() wartet jetzt auf die aus IndexedDB zurückgeholten Handles — ein Doppelklick unmittelbar nach dem App-Start legte sonst ein Duplikat an statt dasselbe Dokument zu aktualisieren. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
45a52c15f9
commit
72ab9746d5
@@ -19,6 +19,8 @@ reverse.
|
|||||||
|
|
||||||
## 2026-08-25
|
## 2026-08-25
|
||||||
|
|
||||||
|
- Ctrl+S saves the document as a file — with a remembered file it writes straight back in place, and the document name flashes as confirmation
|
||||||
|
- Fix: a file double-clicked right after the app starts could open as a duplicate document instead of updating its own
|
||||||
- The installed app registers for `.werkbaum` files — a double-click in the file manager opens them straight into the editor
|
- The installed app registers for `.werkbaum` files — a double-click in the file manager opens them straight into the editor
|
||||||
- A service worker starts the editor offline; navigations stay network-first, so the update notice keeps telling the truth
|
- A service worker starts the editor offline; navigations stay network-first, so the update notice keeps telling the truth
|
||||||
- Werkbaum installs as an app: a web app manifest with standalone display and icons rendered from the brand mark
|
- Werkbaum installs as an app: a web app manifest with standalone display and icons rendered from the brand mark
|
||||||
|
|||||||
@@ -5476,3 +5476,70 @@ auf eine `.werkbaum`-Datei und die persistente Schreibberechtigung der
|
|||||||
installierten App sind Betriebssystem-Dialoge und bleiben ein Handtest auf
|
installierten App sind Betriebssystem-Dialoge und bleiben ein Handtest auf
|
||||||
echter Hardware; gemessen ist alles bis an diese Kante (Manifest gültig,
|
echter Hardware; gemessen ist alles bis an diese Kante (Manifest gültig,
|
||||||
Consumer registriert, `adoptFile()`-Weg seit D72 geprüft).
|
Consumer registriert, `adoptFile()`-Weg seit D72 geprüft).
|
||||||
|
|
||||||
|
## D74 — Strg+S speichert direkt: die D72-Zurückstellung ist umgekehrt
|
||||||
|
D72-Nachtrag hielt fest: „Kein Strg+S — erwogen und zurückgestellt", mit zwei
|
||||||
|
Einwänden — der Browser-Default (Seite speichern) müsste abgefangen werden,
|
||||||
|
und ohne Handle öffnete die Geste unvermittelt einen Dialog. Mit der
|
||||||
|
installierten PWA (D73) kippt die Abwägung, und der Nutzer hat es benannt:
|
||||||
|
Ein lokal geladenes Dokument soll **direkt** speicherbar sein, ohne Dialog.
|
||||||
|
Genau dafür ist Strg+S die Geste, die jeder zuerst versucht — und ohne
|
||||||
|
eigenen Handler tut sie das Schlimmstmögliche: Sie öffnet den
|
||||||
|
„Seite speichern"-Dialog des Browsers, also einen Dialog, der nicht einmal
|
||||||
|
das Dokument speichert.
|
||||||
|
|
||||||
|
**Die beiden D72-Einwände, neu bewertet:**
|
||||||
|
|
||||||
|
- **Der Browser-Default ist kein Preis, sondern der Anlass.** `preventDefault`
|
||||||
|
ist eine Zeile; was sie unterdrückt, war vorher der einzige Effekt der
|
||||||
|
Geste — und der falsche.
|
||||||
|
- **„Ohne Handle unvermittelt ein Dialog" trägt nicht mehr.** Die Geste
|
||||||
|
**heißt** Speichern; ein Speicher-Dialog auf eine Speichern-Geste ist keine
|
||||||
|
Überraschung, sondern das Verhalten jedes Editors beim ersten Strg+S.
|
||||||
|
Danach ist das Handle gemerkt und jede weitere Geste dialogfrei.
|
||||||
|
|
||||||
|
**Verhalten = der Menü-Eintrag, nur als Geste.** Strg+S (macOS auch Cmd+S)
|
||||||
|
ruft dasselbe `saveLocalFile()`: mit gemerktem Handle in dieselbe Datei
|
||||||
|
(die einmalige Schreibberechtigungs-Nachfrage des Browsers bleibt — der
|
||||||
|
Tastendruck ist die dafür nötige Nutzergeste; die installierte App kann sie
|
||||||
|
mit „Bei jedem Besuch zulassen" dauerhaft erteilen), ohne Handle der
|
||||||
|
Speichern-Dialog (Chromium) bzw. Download (Firefox/Safari). Pad-Dokumente
|
||||||
|
dürfen wie über das Menü gespeichert werden (D72: der Schreibschutz gilt dem
|
||||||
|
Textfeld, nicht dem Export). `e.repeat` ist ausgefiltert — eine gehaltene
|
||||||
|
Taste speichert einmal.
|
||||||
|
|
||||||
|
**Stilles Speichern braucht eine sichtbare Antwort.** Der In-Place-Weg zeigt
|
||||||
|
sonst nichts — die Geste wirkte tot, und niemand wüsste, ob gespeichert ist.
|
||||||
|
Rückmeldung im Haus-Idiom (`flashBtn`, D54: 1,5 s Petrol samt Haken), am
|
||||||
|
**Dokumentnamen** in der Editor-Titelzeile — der benennt, was gespeichert
|
||||||
|
wurde. Dialog und Download sind selbst sichtbar und brauchen keine.
|
||||||
|
|
||||||
|
**Die Legenden-Zeile, die D72 zur Bedingung machte, ist da:** `hint_save` in
|
||||||
|
allen neun Sprachen, als zweite Zeile der Bedienungs-Zeile am Ende der
|
||||||
|
Legende (D25-Idiom: Auffindbarkeit gehört zur Geste).
|
||||||
|
|
||||||
|
**Nebenbefund, mitbehoben: der launchQueue-Empfänger wartete nicht auf die
|
||||||
|
gemerkten Handles.** `idbLoadHandles()` läuft asynchron beim Start; ein
|
||||||
|
Doppelklick, der die App erst startet, konnte seinen Consumer **vor** dem
|
||||||
|
Laden der Handle-Map erreichen — der `isSameEntry`-Abgleich lief dann über
|
||||||
|
eine leere Map, und dieselbe Datei wurde als Duplikat angelegt statt ihr
|
||||||
|
Dokument zu aktualisieren (genau die Zusage aus D73, „dieselbe Datei landet
|
||||||
|
im selben Dokument", wäre im häufigsten PWA-Startweg gebrochen).
|
||||||
|
`adoptFile()` wartet jetzt auf das `handlesReady`-Promise, bevor es
|
||||||
|
abgleicht — an der einen Stelle, die die Map braucht, statt in jedem
|
||||||
|
Aufrufer. Der Race ist zeitabhängig und im Werkzeug nicht deterministisch
|
||||||
|
auslösbar; der Fix ist eine Ordnungszusage im Code, geprüft per Review und
|
||||||
|
dadurch, dass der Picker-Weg (der dasselbe `adoptFile()` nimmt) unverändert
|
||||||
|
funktioniert.
|
||||||
|
|
||||||
|
**Nachgemessen** am gebauten Stand (dist auf localhost, Picker gestubbt —
|
||||||
|
die Werkzeuggrenze aus D72: den nativen Dialog kann die Automatisierung
|
||||||
|
nicht bedienen, die Logik dahinter schon): Erster Strg+S ruft den
|
||||||
|
Speichern-Dialog genau einmal (richtiger `suggestedName`) und schreibt;
|
||||||
|
zweiter Strg+S schreibt **ohne** Dialog in dasselbe Handle (0 weitere
|
||||||
|
Picker-Aufrufe, 2 Schreibvorgänge). Beide waren **echte** Tastendrücke
|
||||||
|
(CDP), und kein Browser-Dialog erschien — `preventDefault` greift. Den
|
||||||
|
Petrol-Haken am Dokumentnamen hat erst ein synthetischer Strg+S nach 400 ms
|
||||||
|
gezeigt (Klasse `done`, ✓, `#0F766E`): Der Werkzeug-Umlauf ist langsamer als
|
||||||
|
die 1,5 s des Blitzes — eine Messgrenze, kein Befund. Die Legende zeigt die
|
||||||
|
neue Zeile in DE und EN.
|
||||||
|
|||||||
@@ -88,6 +88,7 @@
|
|||||||
- [^] #ed.snaps.manual: Save a state by hand, before a larger change (XS) %% ten minutes is the wrong beat for that moment
|
- [^] #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.files: Open and save .werkbaum files (S)
|
||||||
- [^] #ed.files.inplace: Saving writes back into the opened file (S) %% File System Access, Chromium
|
- [^] #ed.files.inplace: Saving writes back into the opened file (S) %% File System Access, Chromium
|
||||||
|
- [x] #ed.files.ctrls: Ctrl+S saves the document as a file (XS) %% in place with a remembered handle
|
||||||
+ [^] #ed.fresh: Show what is new since your last visit (S)
|
+ [^] #ed.fresh: Show what is new since your last visit (S)
|
||||||
- [^] #ed.fresh.news: A star in the header, with the last few days (S) %% see D58
|
- [^] #ed.fresh.news: A star in the header, with the last few days (S) %% see D58
|
||||||
+ [?] #ed.percolor: A pastel colour per person (S)
|
+ [?] #ed.percolor: A pastel colour per person (S)
|
||||||
@@ -615,6 +616,12 @@
|
|||||||
handle: saving writes back into the same file, and the same file reopens
|
handle: saving writes back into the same file, and the same file reopens
|
||||||
into the same document. Firefox and Safari keep the plain download.
|
into the same document. Firefox and Safari keep the plain download.
|
||||||
|
|
||||||
|
#ed.files.ctrls
|
||||||
|
The save gesture everyone tries first. Without it the browser offers to
|
||||||
|
save the page — the one dialog nobody wants. With a remembered handle it
|
||||||
|
writes straight back in place and flashes the document name as its answer;
|
||||||
|
otherwise it behaves like the menu entry.
|
||||||
|
|
||||||
#ed.fresh
|
#ed.fresh
|
||||||
For documents that come from outside, nodes that went to production since
|
For documents that come from outside, nodes that went to production since
|
||||||
your last visit get a yellow halo. New means live, not "line added" — a line
|
your last visit get a yellow halo. New means live, not "line added" — a line
|
||||||
|
|||||||
+47
-12
@@ -2252,7 +2252,8 @@ const I18N = {
|
|||||||
hint_eff:"Die Knotenfarbe zeigt den effektiven Status (mit Abhängigkeiten); ist der eigene weiter, steht er als Marke unten links.",
|
hint_eff:"Die Knotenfarbe zeigt den effektiven Status (mit Abhängigkeiten); ist der eigene weiter, steht er als Marke unten links.",
|
||||||
hint_desc:"Beschreibungen: \" Zeile unter dem Knoten; Langtext hinter --- als eingerückter #id-Block — beides im Tooltip (”).",
|
hint_desc:"Beschreibungen: \" Zeile unter dem Knoten; Langtext hinter --- als eingerückter #id-Block — beides im Tooltip (”).",
|
||||||
hint_fold:"Falten: - [x] > … startet eingeklappt, < holt hervor; ▾/▸ am Knoten klappt um (Tastatur: ←/→).",
|
hint_fold:"Falten: - [x] > … startet eingeklappt, < holt hervor; ▾/▸ am Knoten klappt um (Tastatur: ←/→).",
|
||||||
hint_jump:"Alt+Klick auf einen Knoten (mobil: langer Druck) springt zur zugehörigen Textzeile; Alt+Klick im Text holt den Knoten ins Bild; Strg+Klick auf eine Abhängigkeit :#id springt zur Zeile dieser ID."
|
hint_jump:"Alt+Klick auf einen Knoten (mobil: langer Druck) springt zur zugehörigen Textzeile; Alt+Klick im Text holt den Knoten ins Bild; Strg+Klick auf eine Abhängigkeit :#id springt zur Zeile dieser ID.",
|
||||||
|
hint_save:"Strg+S speichert das Dokument als Datei — mit gemerkter Datei (Chromium) direkt an Ort und Stelle."
|
||||||
},
|
},
|
||||||
en: {
|
en: {
|
||||||
subtitle:"Werkbaum – Work Breakdown Structure / Lean Pathfinding · Project structure editor (also feature-tree & requirements)",
|
subtitle:"Werkbaum – Work Breakdown Structure / Lean Pathfinding · Project structure editor (also feature-tree & requirements)",
|
||||||
@@ -2358,7 +2359,8 @@ const I18N = {
|
|||||||
hint_eff:"Node colour shows the effective status (with dependencies); if its own is further along, it appears as a mark at the bottom left.",
|
hint_eff:"Node colour shows the effective status (with dependencies); if its own is further along, it appears as a mark at the bottom left.",
|
||||||
hint_desc:"Descriptions: a \" line below the node; long text behind --- as an indented #id block — both in the tooltip (”).",
|
hint_desc:"Descriptions: a \" line below the node; long text behind --- as an indented #id block — both in the tooltip (”).",
|
||||||
hint_fold:"Folding: - [x] > … starts collapsed, < brings it back; ▾/▸ on a node toggles (keyboard: ←/→).",
|
hint_fold:"Folding: - [x] > … starts collapsed, < brings it back; ▾/▸ on a node toggles (keyboard: ←/→).",
|
||||||
hint_jump:"Alt+click a node (long press on touch) jumps to its line in the text; Alt+click in the text brings the node into view; Ctrl+click a dependency :#id jumps to that ID's line."
|
hint_jump:"Alt+click a node (long press on touch) jumps to its line in the text; Alt+click in the text brings the node into view; Ctrl+click a dependency :#id jumps to that ID's line.",
|
||||||
|
hint_save:"Ctrl+S saves the document as a file — with a remembered file (Chromium) right back in place."
|
||||||
},
|
},
|
||||||
es: {
|
es: {
|
||||||
subtitle:"Werkbaum – EDT / Lean Pathfinding · Editor de estructura de proyectos (también árboles de características y requisitos)",
|
subtitle:"Werkbaum – EDT / Lean Pathfinding · Editor de estructura de proyectos (también árboles de características y requisitos)",
|
||||||
@@ -2464,7 +2466,8 @@ const I18N = {
|
|||||||
hint_eff:"El color del nodo muestra el estado efectivo (con dependencias); si el propio va más adelante, aparece como marca abajo a la izquierda.",
|
hint_eff:"El color del nodo muestra el estado efectivo (con dependencias); si el propio va más adelante, aparece como marca abajo a la izquierda.",
|
||||||
hint_desc:"Descripciones: línea \" bajo el nodo; texto largo tras --- como bloque #id sangrado — ambos en el tooltip (”).",
|
hint_desc:"Descripciones: línea \" bajo el nodo; texto largo tras --- como bloque #id sangrado — ambos en el tooltip (”).",
|
||||||
hint_fold:"Plegado: - [x] > … empieza plegado, < lo recupera; ▾/▸ en el nodo alterna (teclado: ←/→).",
|
hint_fold:"Plegado: - [x] > … empieza plegado, < lo recupera; ▾/▸ en el nodo alterna (teclado: ←/→).",
|
||||||
hint_jump:"Alt+clic en un nodo (pulsación larga en táctil) salta a su línea en el texto; Alt+clic en el texto trae el nodo a la vista; Ctrl+clic en una dependencia :#id salta a la línea de esa ID."
|
hint_jump:"Alt+clic en un nodo (pulsación larga en táctil) salta a su línea en el texto; Alt+clic en el texto trae el nodo a la vista; Ctrl+clic en una dependencia :#id salta a la línea de esa ID.",
|
||||||
|
hint_save:"Ctrl+S guarda el documento como archivo — con un archivo recordado (Chromium), directamente en el mismo lugar."
|
||||||
},
|
},
|
||||||
fr: {
|
fr: {
|
||||||
subtitle:"Werkbaum – WBS / Lean Pathfinding · Éditeur de structure de projet (aussi pour arbres de fonctionnalités et d'exigences)",
|
subtitle:"Werkbaum – WBS / Lean Pathfinding · Éditeur de structure de projet (aussi pour arbres de fonctionnalités et d'exigences)",
|
||||||
@@ -2570,7 +2573,8 @@ const I18N = {
|
|||||||
hint_eff:"La couleur du nœud montre le statut effectif (avec dépendances) ; si le sien est plus avancé, il apparaît en marque en bas à gauche.",
|
hint_eff:"La couleur du nœud montre le statut effectif (avec dépendances) ; si le sien est plus avancé, il apparaît en marque en bas à gauche.",
|
||||||
hint_desc:"Descriptions : ligne \" sous le nœud ; texte long après --- en bloc #id indenté — les deux dans l’infobulle (”).",
|
hint_desc:"Descriptions : ligne \" sous le nœud ; texte long après --- en bloc #id indenté — les deux dans l’infobulle (”).",
|
||||||
hint_fold:"Pliage : - [x] > … démarre replié, < le fait ressortir ; ▾/▸ sur le nœud bascule (clavier : ←/→).",
|
hint_fold:"Pliage : - [x] > … démarre replié, < le fait ressortir ; ▾/▸ sur le nœud bascule (clavier : ←/→).",
|
||||||
hint_jump:"Alt+clic sur un nœud (appui long sur tactile) saute à sa ligne dans le texte ; Alt+clic dans le texte amène le nœud à l’écran ; Ctrl+clic sur une dépendance :#id saute à la ligne de cet ID."
|
hint_jump:"Alt+clic sur un nœud (appui long sur tactile) saute à sa ligne dans le texte ; Alt+clic dans le texte amène le nœud à l’écran ; Ctrl+clic sur une dépendance :#id saute à la ligne de cet ID.",
|
||||||
|
hint_save:"Ctrl+S enregistre le document comme fichier — avec un fichier mémorisé (Chromium), directement sur place."
|
||||||
},
|
},
|
||||||
pl: {
|
pl: {
|
||||||
subtitle:"Werkbaum – WBS / Lean Pathfinding · Edytor struktury projektów (również dla drzew funkcji i wymagań)",
|
subtitle:"Werkbaum – WBS / Lean Pathfinding · Edytor struktury projektów (również dla drzew funkcji i wymagań)",
|
||||||
@@ -2676,7 +2680,8 @@ const I18N = {
|
|||||||
hint_eff:"Kolor węzła pokazuje status efektywny (z zależnościami); jeśli własny jest dalej, widnieje jako znacznik u dołu po lewej.",
|
hint_eff:"Kolor węzła pokazuje status efektywny (z zależnościami); jeśli własny jest dalej, widnieje jako znacznik u dołu po lewej.",
|
||||||
hint_desc:"Opisy: wiersz \" pod węzłem; dłuższy tekst za --- jako wcięty blok #id — oba w podpowiedzi (”).",
|
hint_desc:"Opisy: wiersz \" pod węzłem; dłuższy tekst za --- jako wcięty blok #id — oba w podpowiedzi (”).",
|
||||||
hint_fold:"Zwijanie: - [x] > … zaczyna zwinięte, < przywraca; ▾/▸ na węźle przełącza (klawiatura: ←/→).",
|
hint_fold:"Zwijanie: - [x] > … zaczyna zwinięte, < przywraca; ▾/▸ na węźle przełącza (klawiatura: ←/→).",
|
||||||
hint_jump:"Alt+kliknięcie węzła (długie naciśnięcie na dotyku) przechodzi do jego wiersza w tekście; Alt+kliknięcie w tekście pokazuje węzeł na diagramie; Ctrl+kliknięcie zależności :#id przechodzi do wiersza tego ID."
|
hint_jump:"Alt+kliknięcie węzła (długie naciśnięcie na dotyku) przechodzi do jego wiersza w tekście; Alt+kliknięcie w tekście pokazuje węzeł na diagramie; Ctrl+kliknięcie zależności :#id przechodzi do wiersza tego ID.",
|
||||||
|
hint_save:"Ctrl+S zapisuje dokument jako plik — przy zapamiętanym pliku (Chromium) bezpośrednio w tym samym miejscu."
|
||||||
},
|
},
|
||||||
ru: {
|
ru: {
|
||||||
subtitle:"Werkbaum – СДР / Lean Pathfinding · Редактор структуры проектов (также для деревьев функций и требований)",
|
subtitle:"Werkbaum – СДР / Lean Pathfinding · Редактор структуры проектов (также для деревьев функций и требований)",
|
||||||
@@ -2782,7 +2787,8 @@ const I18N = {
|
|||||||
hint_eff:"Цвет узла показывает фактический статус (с учётом зависимостей); если собственный дальше, он показан меткой слева внизу.",
|
hint_eff:"Цвет узла показывает фактический статус (с учётом зависимостей); если собственный дальше, он показан меткой слева внизу.",
|
||||||
hint_desc:"Описания: строка \" под узлом; длинный текст после --- как блок #id с отступом — оба в подсказке (”).",
|
hint_desc:"Описания: строка \" под узлом; длинный текст после --- как блок #id с отступом — оба в подсказке (”).",
|
||||||
hint_fold:"Сворачивание: - [x] > … открывается свёрнутым, < возвращает; ▾/▸ на узле переключает (клавиши: ←/→).",
|
hint_fold:"Сворачивание: - [x] > … открывается свёрнутым, < возвращает; ▾/▸ на узле переключает (клавиши: ←/→).",
|
||||||
hint_jump:"Alt+клик по узлу (долгое нажатие на сенсоре) переходит к его строке в тексте; Alt+клик в тексте показывает узел на диаграмме; Ctrl+клик по зависимости :#id переходит к строке этого ID."
|
hint_jump:"Alt+клик по узлу (долгое нажатие на сенсоре) переходит к его строке в тексте; Alt+клик в тексте показывает узел на диаграмме; Ctrl+клик по зависимости :#id переходит к строке этого ID.",
|
||||||
|
hint_save:"Ctrl+S сохраняет документ как файл — с запомненным файлом (Chromium) прямо на месте."
|
||||||
},
|
},
|
||||||
hi: {
|
hi: {
|
||||||
subtitle:"Werkbaum – WBS / Lean Pathfinding · परियोजना संरचना संपादक (फ़ीचर और रिक्वायरमेंट ट्री के लिए भी)",
|
subtitle:"Werkbaum – WBS / Lean Pathfinding · परियोजना संरचना संपादक (फ़ीचर और रिक्वायरमेंट ट्री के लिए भी)",
|
||||||
@@ -2888,7 +2894,8 @@ const I18N = {
|
|||||||
hint_eff:"नोड का रंग प्रभावी स्थिति दिखाता है (निर्भरताओं सहित); यदि अपनी स्थिति आगे है, तो वह नीचे-बाएँ चिह्न के रूप में दिखती है।",
|
hint_eff:"नोड का रंग प्रभावी स्थिति दिखाता है (निर्भरताओं सहित); यदि अपनी स्थिति आगे है, तो वह नीचे-बाएँ चिह्न के रूप में दिखती है।",
|
||||||
hint_desc:"विवरण: नोड के नीचे \" पंक्ति; --- के बाद #id ब्लॉक में लंबा पाठ — दोनों टूलटिप में (”)।",
|
hint_desc:"विवरण: नोड के नीचे \" पंक्ति; --- के बाद #id ब्लॉक में लंबा पाठ — दोनों टूलटिप में (”)।",
|
||||||
hint_fold:"फ़ोल्डिंग: - [x] > … समेटा हुआ खुलता है, < वापस लाता है; नोड पर ▾/▸ टॉगल करता है (कीबोर्ड: ←/→)।",
|
hint_fold:"फ़ोल्डिंग: - [x] > … समेटा हुआ खुलता है, < वापस लाता है; नोड पर ▾/▸ टॉगल करता है (कीबोर्ड: ←/→)।",
|
||||||
hint_jump:"किसी नोड पर Alt+क्लिक (टच पर लंबा दबाव) टेक्स्ट में उसकी पंक्ति पर ले जाता है; टेक्स्ट में Alt+क्लिक उस नोड को आरेख में दिखाता है; :#id निर्भरता पर Ctrl+क्लिक उस ID की पंक्ति पर ले जाता है।"
|
hint_jump:"किसी नोड पर Alt+क्लिक (टच पर लंबा दबाव) टेक्स्ट में उसकी पंक्ति पर ले जाता है; टेक्स्ट में Alt+क्लिक उस नोड को आरेख में दिखाता है; :#id निर्भरता पर Ctrl+क्लिक उस ID की पंक्ति पर ले जाता है।",
|
||||||
|
hint_save:"Ctrl+S दस्तावेज़ को फ़ाइल के रूप में सहेजता है — याद रखी गई फ़ाइल (Chromium) में सीधे उसी जगह।"
|
||||||
},
|
},
|
||||||
zh: {
|
zh: {
|
||||||
subtitle:"Werkbaum – WBS / Lean Pathfinding · 项目结构编辑器(也支持功能树和需求树)",
|
subtitle:"Werkbaum – WBS / Lean Pathfinding · 项目结构编辑器(也支持功能树和需求树)",
|
||||||
@@ -2994,7 +3001,8 @@ const I18N = {
|
|||||||
hint_eff:"节点颜色显示实际状态(含依赖);若自身状态更靠前,会以左下角标记显示。",
|
hint_eff:"节点颜色显示实际状态(含依赖);若自身状态更靠前,会以左下角标记显示。",
|
||||||
hint_desc:"描述:节点下方的 \" 行;--- 之后的缩进 #id 块为长文本——均显示在提示中(”)。",
|
hint_desc:"描述:节点下方的 \" 行;--- 之后的缩进 #id 块为长文本——均显示在提示中(”)。",
|
||||||
hint_fold:"折叠:- [x] > … 打开时即折叠,< 将其展开;节点上的 ▾/▸ 切换(键盘:←/→)。",
|
hint_fold:"折叠:- [x] > … 打开时即折叠,< 将其展开;节点上的 ▾/▸ 切换(键盘:←/→)。",
|
||||||
hint_jump:"Alt+点击节点(触摸屏为长按)可跳转到文本中对应的行;在文本中 Alt+点击则把该节点带入视野;在依赖 :#id 上 Ctrl+点击可跳转到该 ID 所在的行。"
|
hint_jump:"Alt+点击节点(触摸屏为长按)可跳转到文本中对应的行;在文本中 Alt+点击则把该节点带入视野;在依赖 :#id 上 Ctrl+点击可跳转到该 ID 所在的行。",
|
||||||
|
hint_save:"Ctrl+S 将文档保存为文件——若已记住文件(Chromium),则直接就地写回。"
|
||||||
},
|
},
|
||||||
ja: {
|
ja: {
|
||||||
subtitle:"Werkbaum – WBS / Lean Pathfinding · プロジェクト構造エディター(フィーチャーツリーと要件ツリーにも対応)",
|
subtitle:"Werkbaum – WBS / Lean Pathfinding · プロジェクト構造エディター(フィーチャーツリーと要件ツリーにも対応)",
|
||||||
@@ -3100,7 +3108,8 @@ const I18N = {
|
|||||||
hint_eff:"ノードの色は実効ステータス(依存関係込み)を示します。自身が先行している場合は左下のマークで表示されます。",
|
hint_eff:"ノードの色は実効ステータス(依存関係込み)を示します。自身が先行している場合は左下のマークで表示されます。",
|
||||||
hint_desc:"説明:ノード直下の \" 行。--- 以降は #id ブロック(字下げ)で長文 — どちらもツールチップに表示(”)。",
|
hint_desc:"説明:ノード直下の \" 行。--- 以降は #id ブロック(字下げ)で長文 — どちらもツールチップに表示(”)。",
|
||||||
hint_fold:"折りたたみ:- [x] > … は折りたたんだ状態で開き、< は呼び戻します。ノードの ▾/▸ で切替(キー:←/→)。",
|
hint_fold:"折りたたみ:- [x] > … は折りたたんだ状態で開き、< は呼び戻します。ノードの ▾/▸ で切替(キー:←/→)。",
|
||||||
hint_jump:"ノードを Alt+クリック(タッチでは長押し)すると、テキストの該当行へ移動します。テキスト内で Alt+クリックすると、そのノードが図の中央に表示されます。依存関係 :#id を Ctrl+クリックすると、その ID の行へ移動します。"
|
hint_jump:"ノードを Alt+クリック(タッチでは長押し)すると、テキストの該当行へ移動します。テキスト内で Alt+クリックすると、そのノードが図の中央に表示されます。依存関係 :#id を Ctrl+クリックすると、その ID の行へ移動します。",
|
||||||
|
hint_save:"Ctrl+S は文書をファイルとして保存します。記憶されたファイル(Chromium)には、そのまま直接書き戻します。"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let lang = 'de';
|
let lang = 'de';
|
||||||
@@ -3140,7 +3149,7 @@ function buildHint(){
|
|||||||
${esc(t('hint_desc'))}
|
${esc(t('hint_desc'))}
|
||||||
${esc(t('hint_fold'))}
|
${esc(t('hint_fold'))}
|
||||||
<code>!!!</code> ${esc(t('hint_focus'))}
|
<code>!!!</code> ${esc(t('hint_focus'))}
|
||||||
<div class="hint-op">${esc(t('hint_jump'))}</div>`;
|
<div class="hint-op">${esc(t('hint_jump'))}<br>${esc(t('hint_save'))}</div>`;
|
||||||
}
|
}
|
||||||
function applyLang(l){
|
function applyLang(l){
|
||||||
lang = l;
|
lang = l;
|
||||||
@@ -3891,6 +3900,11 @@ fileOpenInput.addEventListener('change', async () => {
|
|||||||
Stufe-1-Weg; die Bedienung ist in beiden Fällen dieselbe. */
|
Stufe-1-Weg; die Bedienung ist in beiden Fällen dieselbe. */
|
||||||
const hasFsAccess = typeof window.showOpenFilePicker === 'function';
|
const hasFsAccess = typeof window.showOpenFilePicker === 'function';
|
||||||
const fileHandles = new Map(); /* docId → FileSystemFileHandle */
|
const fileHandles = new Map(); /* docId → FileSystemFileHandle */
|
||||||
|
/* Beim Start werden die gemerkten Handles asynchron zurückgeholt; wer die
|
||||||
|
Map fürs isSameEntry-Abgleichen braucht (adoptFile), wartet auf dieses
|
||||||
|
Promise — sonst legt ein Doppelklick unmittelbar nach dem App-Start ein
|
||||||
|
Duplikat an statt dasselbe Dokument zu aktualisieren (D74). */
|
||||||
|
let handlesReady = Promise.resolve();
|
||||||
/* Handles überleben den Neustart nur in IndexedDB (localStorage kann sie
|
/* Handles überleben den Neustart nur in IndexedDB (localStorage kann sie
|
||||||
nicht halten — sie sind nicht JSON-serialisierbar). Alles hier ist Komfort,
|
nicht halten — sie sind nicht JSON-serialisierbar). Alles hier ist Komfort,
|
||||||
keine Pflicht: Scheitert IndexedDB, funktioniert Speichern weiter über den
|
keine Pflicht: Scheitert IndexedDB, funktioniert Speichern weiter über den
|
||||||
@@ -3941,6 +3955,7 @@ async function adoptFile(handle, name, text){
|
|||||||
flushActive();
|
flushActive();
|
||||||
let targetId = null;
|
let targetId = null;
|
||||||
if(handle){
|
if(handle){
|
||||||
|
await handlesReady; /* gemerkte Handles müssen da sein (D74) */
|
||||||
for(const [id, h] of fileHandles){
|
for(const [id, h] of fileHandles){
|
||||||
try{ if(await handle.isSameEntry(h) && docs.some(d => d.id === id)){ targetId = id; break; } }
|
try{ if(await handle.isSameEntry(h) && docs.some(d => d.id === id)){ targetId = id; break; } }
|
||||||
catch(_){}
|
catch(_){}
|
||||||
@@ -4008,13 +4023,33 @@ async function saveLocalFile(){
|
|||||||
flushActive();
|
flushActive();
|
||||||
const d = activeDoc();
|
const d = activeDoc();
|
||||||
if(!d) return;
|
if(!d) return;
|
||||||
|
let inPlace = false;
|
||||||
if(hasFsAccess){
|
if(hasFsAccess){
|
||||||
if(!(await saveToKnownFile(d))) await saveWithPicker(d);
|
inPlace = await saveToKnownFile(d);
|
||||||
|
if(!inPlace) await saveWithPicker(d);
|
||||||
} else {
|
} else {
|
||||||
saveBlob(new Blob([d.text], {type:'text/plain;charset=utf-8'}), saveFileName(d.name));
|
saveBlob(new Blob([d.text], {type:'text/plain;charset=utf-8'}), saveFileName(d.name));
|
||||||
}
|
}
|
||||||
closeDocMenu();
|
closeDocMenu();
|
||||||
|
/* Stilles In-Place-Speichern braucht eine sichtbare Antwort — sonst wirkt
|
||||||
|
die Geste tot. Haus-Idiom flashBtn (Petrol-Blitz, D54), am Dokumentnamen:
|
||||||
|
Der ist das, was gespeichert wurde. Dialog und Download sind selbst
|
||||||
|
sichtbar und brauchen keinen. */
|
||||||
|
if(inPlace) flashBtn(docTrigger);
|
||||||
}
|
}
|
||||||
|
/* Strg+S / Cmd+S speichert als Datei (D74) — die Geste, die jeder zuerst
|
||||||
|
versucht. Ohne preventDefault öffnet der Browser „Seite speichern": genau
|
||||||
|
der Dialog, den niemand will. Ohne gemerktes Handle verhält sich die Geste
|
||||||
|
wie der Menü-Eintrag (Dialog in Chromium, Download sonst) — ein
|
||||||
|
Speichern-Dialog auf eine Speichern-Geste ist keine Überraschung. Der
|
||||||
|
Tastendruck ist zugleich die Nutzergeste, die requestPermission braucht. */
|
||||||
|
document.addEventListener('keydown', e => {
|
||||||
|
if((e.ctrlKey || e.metaKey) && !e.altKey && !e.shiftKey
|
||||||
|
&& (e.key === 's' || e.key === 'S')){
|
||||||
|
e.preventDefault();
|
||||||
|
if(!e.repeat) saveLocalFile();
|
||||||
|
}
|
||||||
|
});
|
||||||
/* Dokumente laden + aktiven Text in den Editor holen (nach applyLang). */
|
/* Dokumente laden + aktiven Text in den Editor holen (nach applyLang). */
|
||||||
function initDocs(){
|
function initDocs(){
|
||||||
restoring = true;
|
restoring = true;
|
||||||
@@ -4567,7 +4602,7 @@ let startLang = 'de';
|
|||||||
try{ startLang = localStorage.getItem('werkbaum-lang') || detectLang(); }catch(_){ startLang = detectLang(); }
|
try{ startLang = localStorage.getItem('werkbaum-lang') || detectLang(); }catch(_){ startLang = detectLang(); }
|
||||||
applyLang(I18N[startLang] ? startLang : 'de'); /* setzt Texte + rendert */
|
applyLang(I18N[startLang] ? startLang : 'de'); /* setzt Texte + rendert */
|
||||||
initDocs(); /* Dokumente laden + aktiven Text in den Editor (nach Sprache) */
|
initDocs(); /* Dokumente laden + aktiven Text in den Editor (nach Sprache) */
|
||||||
if(hasFsAccess) idbLoadHandles(); /* gemerkte Datei-Handles zurückholen (D72, Stufe 2) */
|
if(hasFsAccess) handlesReady = idbLoadHandles(); /* gemerkte Datei-Handles zurückholen (D72, Stufe 2) */
|
||||||
|
|
||||||
/* PWA-Dateihandling (D73): Die installierte App registriert sich über das
|
/* PWA-Dateihandling (D73): Die installierte App registriert sich über das
|
||||||
Manifest (`file_handlers`) für .werkbaum/.txt; ein Doppelklick im
|
Manifest (`file_handlers`) für .werkbaum/.txt; ein Doppelklick im
|
||||||
|
|||||||
@@ -502,6 +502,10 @@
|
|||||||
}
|
}
|
||||||
.doctrigger:hover,.doctrigger[aria-expanded="true"]{color:var(--or)}
|
.doctrigger:hover,.doctrigger[aria-expanded="true"]{color:var(--or)}
|
||||||
.doctrigger:focus-visible{outline:2px solid var(--or);outline-offset:2px}
|
.doctrigger:focus-visible{outline:2px solid var(--or);outline-offset:2px}
|
||||||
|
/* Rückmeldung fürs stille In-Place-Speichern (Strg+S, D74): Petrol-Blitz
|
||||||
|
samt Haken am Dokumentnamen — das Haus-Idiom von flashBtn (D54). */
|
||||||
|
.doctrigger.done{color:var(--or)}
|
||||||
|
.doctrigger.done::before{content:"✓ ";flex:0 0 auto}
|
||||||
.docname{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600}
|
.docname{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600}
|
||||||
.doc-caret{width:13px;height:13px;flex:0 0 auto;opacity:.7}
|
.doc-caret{width:13px;height:13px;flex:0 0 auto;opacity:.7}
|
||||||
.panel.collapsed .doc-caret{display:none}
|
.panel.collapsed .doc-caret{display:none}
|
||||||
|
|||||||
Reference in New Issue
Block a user