diff --git a/docs/SPEC.md b/docs/SPEC.md index 4d132d6..ff221f1 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -134,6 +134,17 @@ ist grau (`#6B7A8C`) — kein Petrol mehr im Diagramm. Der Modus ändert nur die + halbe Knotenhöhe). Abzweige zielen auf den **Knoten**, nie auf die Mitte des Teilbaums. +### Textexport des Diagramms +Das Diagramm ist per Icon-Schaltfläche als einfacher, überall einsetzbarer +ASCII-Baum in die Zwischenablage kopierbar. Format: + +- Eingerückter Baum mit `├─`/`└─` (all-of-Abzweige) bzw. `├◇`/`└◇` + (any-of-Alternativen); Fortsetzungsspalten mit `│`. +- Je Knoten: `[Status] Label (Größe) @tags` — jeder Teil optional, Reihenfolge + wie hier. Der Status-Code entspricht §4 (`[ ]`, `[x]`, `[~]`, …). +- Es wird genau die **sichtbare** Struktur exportiert (der „verworfene + einblenden“-Filter wirkt auch hier). Mehrere Wurzeln durch Leerzeile getrennt. + ## 10. Beispiel (kanonisch) ``` diff --git a/frontend/index.html b/frontend/index.html index 49c76a8..ee79dec 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -530,6 +530,10 @@ +
@@ -577,6 +581,9 @@ const STATUS_BY_CODE = { '-': {key:'verworfen', name:'verworfen'} }; +/* Rück-Abbildung Status-Key → Code (für den Text-Export des Diagramms) */ +const CODE_BY_KEY = {idee:'?', geplant:' ', arbeit:'~', durchstich:'/', fertig:'x', prod:'^', verworfen:'-'}; + const src = document.getElementById('src'); const out = document.getElementById('out'); const warnBox = document.getElementById('warn'); @@ -696,6 +703,43 @@ function render(){ warnBox.innerHTML = warnings.map(w => `
⚠ ${w}
`).join(''); } +/* ---------- Diagramm als einfacher Text-Baum ---------- */ +/* Einfachstes, aber überall einsetzbares Format: eingerückter ASCII-Baum. + all-of-Abzweige mit ├─/└─, any-of-Alternativen mit ├◇/└◇. Je Knoten: + [Status] Label (Größe) @tags. Respektiert den „verworfene“-Filter, gibt + also genau die sichtbare Struktur wieder. */ +function nodeLabelText(n){ + let s = n.status ? '[' + (CODE_BY_KEY[n.status.key] ?? ' ') + '] ' : ''; + s += n.label; + if(n.size) s += ' (' + n.size + ')'; + if(n.tags && n.tags.length) s += ' ' + n.tags.map(x => '@' + x).join(' '); + return s; +} +function walkText(node, prefix, lines){ + const kids = visibleChildren(node); + const gate = gateOf(kids); + kids.forEach((k, i) => { + const last = i === kids.length - 1; + const conn = gate === 'or' + ? (last ? '└◇ ' : '├◇ ') + : (last ? '└─ ' : '├─ '); + lines.push(prefix + conn + nodeLabelText(k)); + walkText(k, prefix + (last ? ' ' : '│ '), lines); + }); +} +function treeToText(){ + let {roots} = parse(src.value); + if(!showc.checked){ + roots = roots.filter(r => !r.status || r.status.key !== 'verworfen'); + } + const blocks = roots.map(root => { + const lines = [nodeLabelText(root)]; + walkText(root, '', lines); + return lines.join('\n'); + }); + return blocks.join('\n\n'); /* mehrere Wurzeln durch Leerzeile trennen */ +} + /* Tab-Taste rückt ein statt den Fokus zu wechseln */ src.addEventListener('keydown', e => { if(e.key === 'Tab'){ @@ -834,25 +878,32 @@ diagramEl.addEventListener('wheel', e => { const showc = document.getElementById('showc'); showc.addEventListener('change', render); -/* ---------- Text in die Zwischenablage kopieren ---------- */ -const copyBtn = document.getElementById('copy'); -copyBtn.addEventListener('click', async () => { - try{ - await navigator.clipboard.writeText(src.value); - }catch(_){ - src.select(); - document.execCommand('copy'); - src.setSelectionRange(0, 0); - src.blur(); - } - copyBtn.classList.add('done'); - copyBtn.title = t('copyDone'); - copyBtn.setAttribute('aria-label', t('copyDone')); +/* ---------- In die Zwischenablage kopieren ---------- */ +async function writeClipboard(text){ + try{ await navigator.clipboard.writeText(text); return; }catch(_){} + const ta = document.createElement('textarea'); /* Fallback ohne Clipboard-API */ + ta.value = text; ta.style.position = 'fixed'; ta.style.opacity = '0'; + document.body.appendChild(ta); ta.select(); + try{ document.execCommand('copy'); }catch(_){} + document.body.removeChild(ta); +} +function flashCopied(btn, restoreTitleKey){ + btn.classList.add('done'); + btn.title = t('copyDone'); + btn.setAttribute('aria-label', t('copyDone')); setTimeout(() => { - copyBtn.classList.remove('done'); - copyBtn.title = t('copyTitle'); - copyBtn.setAttribute('aria-label', t('copyTitle')); + btn.classList.remove('done'); + btn.title = t(restoreTitleKey); + btn.setAttribute('aria-label', t(restoreTitleKey)); }, 1500); +} +document.getElementById('copy').addEventListener('click', async () => { + await writeClipboard(src.value); + flashCopied(document.getElementById('copy'), 'copyTitle'); +}); +document.getElementById('copyDiagram').addEventListener('click', async () => { + await writeClipboard(treeToText()); + flashCopied(document.getElementById('copyDiagram'), 'copyDiagramTitle'); }); /* ---------- Internationalisierung (DE/EN/ES/FR) ---------- */ @@ -861,6 +912,7 @@ const I18N = { subtitle:"Werkbaum – Ein Text-basierter WBS/Projektstrukturplan/Feature-Tree Diagramm-Editor.", editorTitle:"Struktur (Text)", diagramTitle:"Diagramm", copy:"kopieren", copyDone:"kopiert ✓", copyTitle:"Text in die Zwischenablage kopieren", + copyDiagramTitle:"Diagramm als Textbaum kopieren", minimize:"minimieren", normal:"normal", maximize:"maximieren", agenda:"Agenda", discarded:"verworfene", modeHorizontal:"Horizontal – Organigramm, Diagramm über dem Editor", @@ -885,6 +937,7 @@ const I18N = { subtitle:"Werkbaum – A text-based WBS / work breakdown structure / feature-tree diagram editor.", editorTitle:"Structure (text)", diagramTitle:"Diagram", copy:"copy", copyDone:"copied ✓", copyTitle:"Copy text to clipboard", + copyDiagramTitle:"Copy diagram as a text tree", minimize:"minimize", normal:"normal", maximize:"maximize", agenda:"Legend", discarded:"discarded", modeHorizontal:"Horizontal – org chart, diagram above the editor", @@ -909,6 +962,7 @@ const I18N = { subtitle:"Werkbaum – Un editor de diagramas basado en texto para EDT / estructura de desglose del trabajo / árbol de características.", editorTitle:"Estructura (texto)", diagramTitle:"Diagrama", copy:"copiar", copyDone:"copiado ✓", copyTitle:"Copiar el texto al portapapeles", + copyDiagramTitle:"Copiar el diagrama como árbol de texto", minimize:"minimizar", normal:"normal", maximize:"maximizar", agenda:"Leyenda", discarded:"descartados", modeHorizontal:"Horizontal – organigrama, diagrama sobre el editor", @@ -933,6 +987,7 @@ const I18N = { subtitle:"Werkbaum – Un éditeur de diagrammes basé sur le texte pour WBS / organigramme des tâches / arbre de fonctionnalités.", editorTitle:"Structure (texte)", diagramTitle:"Diagramme", copy:"copier", copyDone:"copié ✓", copyTitle:"Copier le texte dans le presse-papiers", + copyDiagramTitle:"Copier le diagramme en arbre de texte", minimize:"réduire", normal:"normal", maximize:"agrandir", agenda:"Légende", discarded:"abandonnés", modeHorizontal:"Horizontal – organigramme, diagramme au-dessus de l'éditeur",