diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md
index 2cf7dad..4954fdc 100644
--- a/docs/DECISIONS.md
+++ b/docs/DECISIONS.md
@@ -329,3 +329,37 @@ Not eine deutsche Oberfläche. Die Erkennung greift auch **nach dem Reset**
(CLAUDE: neue UI-Texte zuerst auf Deutsch) — das betrifft die Autoren-/Pflege
seite und ist unabhängig vom Anzeige-Default für Besucher. Eine bewusste
Sprachwahl überschreibt die Erkennung dauerhaft (Persistenz in `werkbaum-lang`).
+
+## D22 — Mehrere Dokumente client-seitig, Wähler in der Editor-Titelzeile
+Der Editor kann mehrere Notationstexte halten, zwischen denen umgeschaltet wird
+(z. B. verschiedene Projekte/Bäume). Umgesetzt **ohne Backend** (noch keins,
+D13): die Dokumente liegen als `[{id, name, text}]` im localStorage
+(`werkbaum-docs`), das aktive per `id` in `werkbaum-active`. Der aktive Text
+wird zusätzlich in `werkbaum-src` gespiegelt (Abwärtskompatibilität + Migration).
+
+**Platzierung: Dropdown in der Editor-Titelzeile.** Der Name des aktiven
+Dokuments **ersetzt** die statische Beschriftung „Struktur (Text)" und ist
+zugleich der Auslöser eines Dropdowns zum Wechseln, Anlegen (`+ Neu`),
+Umbenennen und Löschen. Begründung: Der Wähler bestimmt, *welchen Text* man
+bearbeitet — er gehört auf das Textpanel, nicht ins Diagramm. Verworfen:
+**Kopfzeile oben** (schon eng, besonders mobil) und **Tab-Leiste** (kostet eine
+ganze Zeile Höhe, D17, und skaliert nicht über ~5 Dokumente). Ein Dropdown
+skaliert und passt zum bereits etablierten Overlay-Idiom (Sprache/Download, D17).
+Auf kleinem Bildschirm öffnet dasselbe Menü als absolut positioniertes Overlay
+unter der Titelzeile.
+
+**Vereinbarkeit mit D14 (Text ist das führende Format):** Jedes Dokument ist nur
+ein Notationstext plus `name` (Metadatum) — **kein** erfundenes Strukturformat.
+Damit ist das Modell vorwärtskompatibel zum geplanten Persistenz-/Taiga-Backend
+(D13/D14: „Text als Ganzes + explizite Metadaten"): „mehrere Dokumente" bildet
+1:1 auf „mehrere gespeicherte Notationstexte mit Name/id" ab; der localStorage-
+Array ist der client-seitige Platzhalter, bis das Backend existiert.
+
+**Migration & Reset:** Fehlt `werkbaum-docs`, wird der bestehende Einzeltext
+(oder `INITIAL`) verlustfrei in **ein** Dokument gepackt (Name in der erkannten
+UI-Sprache, `docDefaultName`). Der Reset löscht wie bisher alle `werkbaum-*`-
+Schlüssel → beim nächsten Laden greift dieselbe Migration. Wird das letzte
+Dokument gelöscht, entsteht wieder das Beispiel-Dokument (INITIAL). Die
+GUI-Ansichts-Einstellungen (Modus, Zoom, Aufteilung; `werkbaum-ui`) bleiben
+bewusst **global** über alle Dokumente — pro-Dokument-Ansichtszustand wäre eine
+spätere Erweiterung.
diff --git a/frontend/CLAUDE.md b/frontend/CLAUDE.md
index a8d023d..8a84c28 100644
--- a/frontend/CLAUDE.md
+++ b/frontend/CLAUDE.md
@@ -81,13 +81,24 @@ verworfene Elemente. Quelle sind ES-Module unter `src/`; `index.html` ist der
(`a11y*`) in **allen 9 Sprachen** anlegen. Knoten sind `tabindex="0"`
(Fokus = Lesereihenfolge), `#warn` ist eine Live-Region.
- Zustand wird im `localStorage` gehalten (noch kein Backend): `werkbaum-lang`
- (Sprache), `werkbaum-src` (Editortext), `werkbaum-ui` (JSON: Modus,
- verworfene, günstigster Pfad, Split-Zustand inkl. `--col`/`--drow`, Zoom,
- Vollbild). Neue
+ (Sprache), `werkbaum-docs` (JSON-Array der Dokumente `[{id,name,text}]`),
+ `werkbaum-active` (id des aktiven Dokuments), `werkbaum-src` (Spiegel des
+ aktiven Texts, Abwärtskompatibilität), `werkbaum-ui` (JSON: Modus, verworfene,
+ günstigster Pfad, Split-Zustand inkl. `--col`/`--drow`, Zoom, Vollbild). Neue
GUI-Einstellungen in `saveUI()`/`restoreState()` mitführen; `saveUI` liefert
während `restoring===true` nichts, damit das Wiederherstellen nicht sofort
- zurückschreibt. Fehlender `werkbaum-src` fällt auf `INITIAL` zurück, ein
- leerer String bleibt jedoch leer.
+ zurückschreibt.
+- Dokumente (D22): mehrere umschaltbare Notationstexte. `loadDocs()` migriert bei
+ fehlendem `werkbaum-docs` den bestehenden `werkbaum-src` (oder `INITIAL`) in
+ **ein** Dokument; `initDocs()` (Aufruf **nach** `applyLang`, damit der
+ Standardname in der erkannten UI-Sprache steht) holt den aktiven Text in den
+ Editor. `saveSrc()` schreibt den Editortext ins aktive Dokument. Der Wähler ist
+ ein Dropdown in der Editor-Titelzeile (`#docTrigger`/`#docMenu`, ersetzt die
+ frühere feste „Struktur (Text)"-Beschriftung); Wechseln/Neu/Umbenennen/Löschen
+ in `switchDoc/newDoc/renameDoc/deleteDoc`. Jedes Dokument ist nur Text + Name
+ (kein Strukturformat, D14) — vorwärtskompatibel zum Backend (D13). Ansichts-
+ State (`werkbaum-ui`) bleibt global über alle Dokumente. Ein leerer Editortext
+ bleibt leer; das letzte gelöschte Dokument wird als `INITIAL` neu gesät.
- Kleiner Bildschirm: `body.mobile` (per `matchMedia`, ≤ 640 px) stapelt
Diagramm/Editor mit **stufenlosem** Splitter (kein Snap/Collapse wie auf
Desktop): der Gutter-Drag ruft `setMobileDrow()` (klemmt `--drow` zwischen den
diff --git a/frontend/index.html b/frontend/index.html
index a1b71bf..69e36e6 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -46,7 +46,18 @@
-
Struktur (Text)
+
+
+
+
+
diff --git a/frontend/src/app.js b/frontend/src/app.js
index c332726..6402267 100644
--- a/frontend/src/app.js
+++ b/frontend/src/app.js
@@ -572,6 +572,10 @@ const I18N = {
fullscreenTooltip:"Vollbild – Panels nutzen die ganze Fensterbreite",
brandTooltip:"„Werkbaum“ bedeutet so viel wie ‚Werk-Baum‘ — der Baum des Projektstrukturplans (WBS).",
editorTitle:"Struktur (Text)", diagramTitle:"Diagramm",
+ docSwitchTooltip:"Dokument wählen oder verwalten", docMenuAria:"Dokumente",
+ docNew:"Neues Dokument", docRename:"Umbenennen", docDelete:"Löschen",
+ docDefaultName:"Beispiel", docNewName:"Unbenannt",
+ docRenamePrompt:"Dokument umbenennen:", docDeleteConfirm:"Dokument „{name}“ löschen?",
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)",
@@ -613,6 +617,10 @@ const I18N = {
fullscreenTooltip:"Full screen – panels use the full window width",
brandTooltip:"“Werkbaum” means roughly ‘work tree’ — the tree of the work breakdown structure (WBS).",
editorTitle:"Structure (text)", diagramTitle:"Diagram",
+ docSwitchTooltip:"Choose or manage document", docMenuAria:"Documents",
+ docNew:"New document", docRename:"Rename", docDelete:"Delete",
+ docDefaultName:"Example", docNewName:"Untitled",
+ docRenamePrompt:"Rename document:", docDeleteConfirm:"Delete document “{name}”?",
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)",
@@ -654,6 +662,10 @@ const I18N = {
fullscreenTooltip:"Pantalla completa – los paneles usan todo el ancho de la ventana",
brandTooltip:"«Werkbaum» significa algo así como ‘árbol de trabajo’ — el árbol de la estructura de desglose del trabajo (EDT).",
editorTitle:"Estructura (texto)", diagramTitle:"Diagrama",
+ docSwitchTooltip:"Elegir o gestionar documento", docMenuAria:"Documentos",
+ docNew:"Nuevo documento", docRename:"Renombrar", docDelete:"Eliminar",
+ docDefaultName:"Ejemplo", docNewName:"Sin título",
+ docRenamePrompt:"Renombrar documento:", docDeleteConfirm:"¿Eliminar el documento «{name}»?",
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)",
@@ -695,6 +707,10 @@ const I18N = {
fullscreenTooltip:"Plein écran – les panneaux occupent toute la largeur de la fenêtre",
brandTooltip:"« Werkbaum » signifie à peu près « arbre de travail » — l’arbre de l’organigramme des tâches (WBS).",
editorTitle:"Structure (texte)", diagramTitle:"Diagramme",
+ docSwitchTooltip:"Choisir ou gérer le document", docMenuAria:"Documents",
+ docNew:"Nouveau document", docRename:"Renommer", docDelete:"Supprimer",
+ docDefaultName:"Exemple", docNewName:"Sans titre",
+ docRenamePrompt:"Renommer le document :", docDeleteConfirm:"Supprimer le document « {name} » ?",
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)",
@@ -736,6 +752,10 @@ const I18N = {
fullscreenTooltip:"Pełny ekran – panele wykorzystują całą szerokość okna",
brandTooltip:"„Werkbaum” znaczy mniej więcej ‚drzewo pracy’ — drzewo struktury podziału pracy (WBS).",
editorTitle:"Struktura (tekst)", diagramTitle:"Diagram",
+ docSwitchTooltip:"Wybierz lub zarządzaj dokumentem", docMenuAria:"Dokumenty",
+ docNew:"Nowy dokument", docRename:"Zmień nazwę", docDelete:"Usuń",
+ docDefaultName:"Przykład", docNewName:"Bez nazwy",
+ docRenamePrompt:"Zmień nazwę dokumentu:", docDeleteConfirm:"Usunąć dokument „{name}”?",
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)",
@@ -777,6 +797,10 @@ const I18N = {
fullscreenTooltip:"Полный экран – панели занимают всю ширину окна",
brandTooltip:"«Werkbaum» примерно означает ‚дерево работ’ — дерево структуры декомпозиции работ (СДР).",
editorTitle:"Структура (текст)", diagramTitle:"Диаграмма",
+ docSwitchTooltip:"Выбрать документ или управлять им", docMenuAria:"Документы",
+ docNew:"Новый документ", docRename:"Переименовать", docDelete:"Удалить",
+ docDefaultName:"Пример", docNewName:"Без названия",
+ docRenamePrompt:"Переименовать документ:", docDeleteConfirm:"Удалить документ «{name}»?",
copy:"копировать", copyDone:"скопировано ✓", copyTooltip:"Скопировать текст в буфер обмена",
copyDiagramTooltip:"Скопировать диаграмму как изображение PNG в буфер обмена",
downloadDiagramTooltip:"Скачать диаграмму как файл SVG (напр. для LibreOffice: Вставка → Изображение)",
@@ -818,6 +842,10 @@ const I18N = {
fullscreenTooltip:"पूर्ण स्क्रीन – पैनल पूरी विंडो चौड़ाई का उपयोग करते हैं",
brandTooltip:"„Werkbaum“ का अर्थ लगभग ‚कार्य-वृक्ष‘ है — कार्य विभाजन संरचना (WBS) का वृक्ष।",
editorTitle:"संरचना (टेक्स्ट)", diagramTitle:"आरेख",
+ docSwitchTooltip:"दस्तावेज़ चुनें या प्रबंधित करें", docMenuAria:"दस्तावेज़",
+ docNew:"नया दस्तावेज़", docRename:"नाम बदलें", docDelete:"हटाएँ",
+ docDefaultName:"उदाहरण", docNewName:"बिना शीर्षक",
+ docRenamePrompt:"दस्तावेज़ का नाम बदलें:", docDeleteConfirm:"दस्तावेज़ „{name}“ हटाएँ?",
copy:"कॉपी करें", copyDone:"कॉपी हो गया ✓", copyTooltip:"टेक्स्ट को क्लिपबोर्ड पर कॉपी करें",
copyDiagramTooltip:"आरेख को PNG छवि के रूप में क्लिपबोर्ड पर कॉपी करें",
downloadDiagramTooltip:"आरेख को SVG फ़ाइल के रूप में डाउनलोड करें (जैसे LibreOffice: सम्मिलित करें → छवि)",
@@ -859,6 +887,10 @@ const I18N = {
ghostTooltip:"从 M 号起,元素应进一步细分。",
riskTooltip:"高风险 – 工作量尚不明确。",
editorTitle:"结构(文本)", diagramTitle:"图表",
+ docSwitchTooltip:"选择或管理文档", docMenuAria:"文档",
+ docNew:"新建文档", docRename:"重命名", docDelete:"删除",
+ docDefaultName:"示例", docNewName:"未命名",
+ docRenamePrompt:"重命名文档:", docDeleteConfirm:"删除文档“{name}”?",
copy:"复制", copyDone:"已复制 ✓", copyTooltip:"将文本复制到剪贴板",
copyDiagramTooltip:"将图表作为 PNG 图片复制到剪贴板",
downloadDiagramTooltip:"将图表下载为 SVG 文件(例如用于 LibreOffice:插入 → 图像)",
@@ -900,6 +932,10 @@ const I18N = {
ghostTooltip:"サイズ M 以上の要素はさらに分解すべきです。",
riskTooltip:"高リスク – 規模はまだ不明。",
editorTitle:"構造(テキスト)", diagramTitle:"ダイアグラム",
+ docSwitchTooltip:"ドキュメントを選択・管理", docMenuAria:"ドキュメント",
+ docNew:"新規ドキュメント", docRename:"名前を変更", docDelete:"削除",
+ docDefaultName:"例", docNewName:"無題",
+ docRenamePrompt:"ドキュメントの名前を変更:", docDeleteConfirm:"ドキュメント「{name}」を削除しますか?",
copy:"コピー", copyDone:"コピーしました ✓", copyTooltip:"テキストをクリップボードにコピー",
copyDiagramTooltip:"ダイアグラムを PNG 画像としてクリップボードにコピー",
downloadDiagramTooltip:"ダイアグラムを SVG ファイルとしてダウンロード(例:LibreOffice の 挿入 → 画像)",
@@ -1026,11 +1062,56 @@ fsBtn.addEventListener('click', () => {
Vollbild) und der Editortext bleiben per localStorage über Neuladen
erhalten. Sprache liegt weiterhin in 'werkbaum-lang'. */
const LS_UI = 'werkbaum-ui', LS_SRC = 'werkbaum-src';
+const LS_DOCS = 'werkbaum-docs', LS_ACTIVE = 'werkbaum-active';
let restoring = false; /* unterdrückt Speichern während des Wiederherstellens */
let hadStoredUI = false; /* gab es beim Laden schon gespeicherte GUI-Einstellungen? */
+
+/* ---------- Dokumente (mehrere umschaltbare Notationstexte) ----------
+ Noch kein Backend: mehrere Dokumente liegen als [{id,name,text}] im
+ localStorage (LS_DOCS), das aktive per id in LS_ACTIVE. Jedes Dokument ist
+ nur ein Notationstext + Name (Metadatum) — kein eigenes Strukturformat (D14).
+ Der aktive Text wird zusätzlich in LS_SRC gespiegelt (Abwärtskompatibilität
+ + Migration bestehender Einzeltexte). */
+let docs = []; /* [{id, name, text}] */
+let activeId = null;
+function uid(){ return 'd' + Date.now().toString(36) + Math.random().toString(36).slice(2, 7); }
+function activeDoc(){ return docs.find(d => d.id === activeId) || null; }
+function uniqueName(base){
+ const taken = new Set(docs.map(d => d.name));
+ if(!taken.has(base)) return base;
+ let i = 2;
+ while(taken.has(base + ' ' + i)) i++;
+ return base + ' ' + i;
+}
+function persistDocs(){
+ try{
+ localStorage.setItem(LS_DOCS, JSON.stringify(docs));
+ localStorage.setItem(LS_ACTIVE, activeId || '');
+ const d = activeDoc();
+ if(d) localStorage.setItem(LS_SRC, d.text); /* Spiegel für Fallback/Migration */
+ }catch(_){}
+}
+/* Aus dem localStorage laden; bei fehlender Dokumentenliste den bestehenden
+ Einzeltext (oder INITIAL) als erstes Dokument migrieren. */
+function loadDocs(){
+ let arr = null;
+ try{ arr = JSON.parse(localStorage.getItem(LS_DOCS) || 'null'); }catch(_){}
+ if(!Array.isArray(arr) || !arr.length ||
+ !arr.every(d => d && typeof d.id === 'string' && typeof d.text === 'string')){
+ let legacy = null;
+ try{ legacy = localStorage.getItem(LS_SRC); }catch(_){}
+ arr = [{ id: uid(), name: t('docDefaultName'), text: (legacy !== null) ? legacy : INITIAL }];
+ }
+ docs = arr;
+ let a = null;
+ try{ a = localStorage.getItem(LS_ACTIVE); }catch(_){}
+ activeId = docs.some(d => d.id === a) ? a : docs[0].id;
+}
function saveSrc(){
if(restoring) return;
- try{ localStorage.setItem(LS_SRC, src.value); }catch(_){}
+ const d = activeDoc();
+ if(d) d.text = src.value;
+ persistDocs();
}
function saveUI(){
if(restoring) return;
@@ -1050,11 +1131,11 @@ function saveUI(){
}
function restoreState(){
restoring = true;
- let savedSrc = null, ui = null;
- try{ savedSrc = localStorage.getItem(LS_SRC); }catch(_){}
+ let ui = null;
try{ ui = JSON.parse(localStorage.getItem(LS_UI) || 'null'); }catch(_){}
hadStoredUI = !!ui;
- src.value = (savedSrc !== null) ? savedSrc : INITIAL; /* leerer String bleibt leer */
+ /* Editortext gehört jetzt den Dokumenten (initDocs, nach applyLang), damit der
+ Standard-Dokumentname in der erkannten UI-Sprache steht. */
const mode = (ui && ui.mode) || 'horizontal';
const modeEl = document.querySelector('input[name="layout"][value="' + mode + '"]');
if(modeEl) modeEl.checked = true;
@@ -1081,6 +1162,107 @@ function restoreState(){
restoring = false;
}
+/* ---------- Dokument-Wähler: UI (Dropdown in der Editor-Titelzeile) ---------- */
+const docTrigger = document.getElementById('docTrigger');
+const docMenu = document.getElementById('docMenu');
+const docNameEl = document.getElementById('docName');
+const docList = document.getElementById('docList');
+function updateDocName(){
+ const d = activeDoc();
+ if(docNameEl) docNameEl.textContent = d ? d.name : '';
+}
+function renderDocMenu(){
+ docList.innerHTML = docs.map(d =>
+ `` +
+ `✓ ` +
+ `${esc(d.name)} `
+ ).join('');
+}
+function openDocMenu(){ renderDocMenu(); docMenu.hidden = false; docTrigger.setAttribute('aria-expanded', 'true'); }
+function closeDocMenu(){ 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
+ Dokument sichern, dann das Ziel laden und neu rendern. */
+function flushActive(){ const d = activeDoc(); if(d) d.text = src.value; }
+function loadActiveIntoEditor(){ const d = activeDoc(); src.value = d ? d.text : ''; render(); updateDocName(); }
+function switchDoc(id){
+ if(id === activeId) return;
+ flushActive();
+ activeId = id;
+ loadActiveIntoEditor();
+ persistDocs();
+}
+function newDoc(){
+ flushActive();
+ const d = { id: uid(), name: uniqueName(t('docNewName')), text: '' };
+ docs.push(d);
+ activeId = d.id;
+ loadActiveIntoEditor();
+ persistDocs();
+ src.focus();
+}
+function renameDoc(){
+ const d = activeDoc();
+ if(!d) return;
+ const name = window.prompt(t('docRenamePrompt'), d.name);
+ if(name === null) return;
+ const trimmed = name.trim();
+ if(!trimmed) return;
+ d.name = trimmed;
+ persistDocs();
+ updateDocName();
+ renderDocMenu();
+}
+function deleteDoc(){
+ const d = activeDoc();
+ if(!d) return;
+ if(!window.confirm(t('docDeleteConfirm', {name: d.name}))) return;
+ docs = docs.filter(x => x.id !== d.id);
+ if(!docs.length) docs = [{ id: uid(), name: t('docDefaultName'), text: INITIAL }];
+ activeId = docs[0].id;
+ loadActiveIntoEditor();
+ persistDocs();
+ closeDocMenu();
+}
+/* Dokumente laden + aktiven Text in den Editor holen. Nach applyLang aufgerufen,
+ damit der migrierte Standardname in der erkannten UI-Sprache steht. */
+function initDocs(){
+ restoring = true;
+ loadDocs();
+ const d = activeDoc();
+ src.value = d ? d.text : '';
+ restoring = false;
+ persistDocs(); /* migrierte/geladene Liste festschreiben (stabil über Reload) */
+ updateDocName();
+ render();
+}
+docTrigger.addEventListener('click', e => {
+ /* Desktop: ist der Editor minimiert, stellt ein Klick ihn wieder her
+ (Bubbling zur Titelzeile) statt das Menü zu öffnen. */
+ if(!isMobile() && editorPanel.classList.contains('collapsed')) return;
+ e.stopPropagation();
+ toggleDocMenu();
+});
+docTrigger.addEventListener('keydown', e => {
+ if(e.key === 'Enter' || e.key === ' '){ e.preventDefault(); toggleDocMenu(); }
+ else if(e.key === 'Escape') closeDocMenu();
+});
+docList.addEventListener('click', e => {
+ const btn = e.target.closest('.docitem');
+ if(!btn) return;
+ e.stopPropagation();
+ switchDoc(btn.dataset.id);
+ closeDocMenu();
+});
+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(); });
+/* Klick außerhalb schließt das Menü. */
+document.addEventListener('click', e => {
+ if(!docMenu.hidden && !docMenu.contains(e.target) && !docTrigger.contains(e.target)) closeDocMenu();
+});
+
/* ---------- Kleiner Bildschirm: nur ein Panel, schlanke Sprachwahl, Vollbild ---------- */
const mqMobile = window.matchMedia('(max-width:640px)');
function isMobile(){ return mqMobile.matches; }
@@ -1162,6 +1344,7 @@ function detectLang(){
let startLang = 'de';
try{ startLang = localStorage.getItem('werkbaum-lang') || detectLang(); }catch(_){ startLang = detectLang(); }
applyLang(I18N[startLang] ? startLang : 'de'); /* setzt Texte + rendert */
+initDocs(); /* Dokumente laden + aktiven Text in den Editor (nach Sprache) */
applyMobile(); /* Mobil-Verhalten (nach Sprache/Restore) anwenden */
/* ---------- Build-Hinweis (Vorschau/Dev + „latest build") ----------
diff --git a/frontend/src/style.css b/frontend/src/style.css
index 5868b10..0af724d 100644
--- a/frontend/src/style.css
+++ b/frontend/src/style.css
@@ -239,7 +239,7 @@
display:flex;flex-direction:column;min-height:0;min-width:0;
}
.panel-head{
- flex:0 0 auto;
+ flex:0 0 auto;position:relative;
padding:10px 16px;
display:flex;justify-content:space-between;align-items:center;gap:14px;
font-family:'IBM Plex Mono',monospace;
@@ -308,6 +308,49 @@
.dlmenu{display:inline-flex;gap:3px}
.dltrigger{display:none}
.legendbtn{display:none} /* nur auf kleinem Bildschirm sichtbar */
+ /* 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). */
+ .doctrigger{
+ display:inline-flex;align-items:center;gap:5px;cursor:pointer;
+ margin-right:auto;max-width:60%;color:var(--muted);
+ padding:2px 6px;margin-left:-6px;border-radius:6px;
+ text-transform:none;letter-spacing:0;font-family:'IBM Plex Mono',monospace;
+ }
+ .doctrigger:hover,.doctrigger[aria-expanded="true"]{color:var(--or)}
+ .doctrigger:focus-visible{outline:2px solid var(--or);outline-offset:2px}
+ .docname{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600}
+ .doc-caret{width:13px;height:13px;flex:0 0 auto;opacity:.7}
+ .panel.collapsed .doc-caret{display:none}
+ .docmenu{
+ position:absolute;top:calc(100% + 4px);left:12px;z-index:50;min-width:210px;
+ background:var(--card);border:1px solid rgba(36,52,71,.15);border-radius:10px;
+ padding:6px;box-shadow:0 8px 24px rgba(36,52,71,.22);
+ text-transform:none;letter-spacing:0;font-family:'IBM Plex Sans',sans-serif;
+ }
+ .docmenu[hidden]{display:none}
+ .docmenu-list{display:flex;flex-direction:column;gap:2px;max-height:260px;overflow:auto}
+ .docitem{
+ display:flex;align-items:center;gap:8px;width:100%;
+ border:0;background:transparent;cursor:pointer;text-align:left;
+ padding:7px 9px;border-radius:7px;color:var(--ink);
+ font-family:inherit;font-size:.86rem;
+ }
+ .docitem:hover{background:rgba(15,118,110,.08)}
+ .docitem[aria-checked="true"]{color:var(--or);font-weight:600}
+ .docitem .doccheck{flex:0 0 auto;width:14px;text-align:center;opacity:0}
+ .docitem[aria-checked="true"] .doccheck{opacity:1}
+ .docmenu-actions{
+ display:flex;flex-wrap:wrap;gap:4px;margin-top:6px;padding-top:6px;
+ border-top:1px solid rgba(36,52,71,.12);
+ }
+ .docact{
+ flex:1 1 auto;border:1px solid rgba(36,52,71,.2);border-radius:7px;
+ background:var(--card);color:var(--muted);cursor:pointer;
+ padding:5px 8px;font-family:inherit;font-size:.78rem;white-space:nowrap;
+ }
+ .docact:hover{border-color:var(--or);color:var(--or)}
+ .docdel:hover{border-color:#D897A8;color:#B4415E}
.togglebtn[aria-pressed="true"]{background:var(--or);color:#fff;border-color:var(--or)}
.togglebtn[aria-pressed="true"]:hover{color:#fff}
textarea{