frontend: Diagramm als Text-Baum in die Zwischenablage kopieren
Neue Icon-Schaltfläche im Diagramm-Kopf exportiert die sichtbare Struktur als einfachen, überall einsetzbaren ASCII-Baum: ├─/└─ für all-of, ├◇/└◇ für any-of-Alternativen, je Knoten [Status] Label (Größe) @tags. Respektiert den verworfene-Filter. Clipboard-Logik in writeClipboard/ flashCopied ausgelagert und von beiden Kopieren-Buttons genutzt. SPEC §9 um den Textexport ergänzt. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
1479bd9717
commit
6e33591b84
@@ -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
|
+ halbe Knotenhöhe). Abzweige zielen auf den **Knoten**, nie auf die Mitte
|
||||||
des Teilbaums.
|
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)
|
## 10. Beispiel (kanonisch)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
+71
-16
@@ -530,6 +530,10 @@
|
|||||||
<span><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2.5" y="9.5" width="5" height="5" rx="1"/><rect x="16" y="3" width="6" height="4" rx="1"/><rect x="16" y="17" width="6" height="4" rx="1"/><path d="M7.5 12H12M12 5V19M12 5H16M12 19H16"/></svg></span>
|
<span><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2.5" y="9.5" width="5" height="5" rx="1"/><rect x="16" y="3" width="6" height="4" rx="1"/><rect x="16" y="17" width="6" height="4" rx="1"/><path d="M7.5 12H12M12 5V19M12 5H16M12 19H16"/></svg></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<button type="button" class="copybtn" id="copyDiagram" data-i18n-title="copyDiagramTitle" data-i18n-aria="copyDiagramTitle" title="Diagramm als Textbaum kopieren" aria-label="Diagramm als Textbaum kopieren">
|
||||||
|
<svg class="ic-copy" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="12" height="12" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></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>
|
||||||
<div class="winbtns" aria-label="Diagramm">
|
<div class="winbtns" aria-label="Diagramm">
|
||||||
<button type="button" class="winbtn" data-state="a" data-i18n-title="minimize" title="minimieren">—</button>
|
<button type="button" class="winbtn" data-state="a" data-i18n-title="minimize" title="minimieren">—</button>
|
||||||
<button type="button" class="winbtn" data-state="normal" data-i18n-title="normal" title="normal">▢</button>
|
<button type="button" class="winbtn" data-state="normal" data-i18n-title="normal" title="normal">▢</button>
|
||||||
@@ -577,6 +581,9 @@ const STATUS_BY_CODE = {
|
|||||||
'-': {key:'verworfen', name:'verworfen'}
|
'-': {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 src = document.getElementById('src');
|
||||||
const out = document.getElementById('out');
|
const out = document.getElementById('out');
|
||||||
const warnBox = document.getElementById('warn');
|
const warnBox = document.getElementById('warn');
|
||||||
@@ -696,6 +703,43 @@ function render(){
|
|||||||
warnBox.innerHTML = warnings.map(w => `<div>⚠ ${w}</div>`).join('');
|
warnBox.innerHTML = warnings.map(w => `<div>⚠ ${w}</div>`).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 */
|
/* Tab-Taste rückt ein statt den Fokus zu wechseln */
|
||||||
src.addEventListener('keydown', e => {
|
src.addEventListener('keydown', e => {
|
||||||
if(e.key === 'Tab'){
|
if(e.key === 'Tab'){
|
||||||
@@ -834,25 +878,32 @@ diagramEl.addEventListener('wheel', e => {
|
|||||||
const showc = document.getElementById('showc');
|
const showc = document.getElementById('showc');
|
||||||
showc.addEventListener('change', render);
|
showc.addEventListener('change', render);
|
||||||
|
|
||||||
/* ---------- Text in die Zwischenablage kopieren ---------- */
|
/* ---------- In die Zwischenablage kopieren ---------- */
|
||||||
const copyBtn = document.getElementById('copy');
|
async function writeClipboard(text){
|
||||||
copyBtn.addEventListener('click', async () => {
|
try{ await navigator.clipboard.writeText(text); return; }catch(_){}
|
||||||
try{
|
const ta = document.createElement('textarea'); /* Fallback ohne Clipboard-API */
|
||||||
await navigator.clipboard.writeText(src.value);
|
ta.value = text; ta.style.position = 'fixed'; ta.style.opacity = '0';
|
||||||
}catch(_){
|
document.body.appendChild(ta); ta.select();
|
||||||
src.select();
|
try{ document.execCommand('copy'); }catch(_){}
|
||||||
document.execCommand('copy');
|
document.body.removeChild(ta);
|
||||||
src.setSelectionRange(0, 0);
|
|
||||||
src.blur();
|
|
||||||
}
|
}
|
||||||
copyBtn.classList.add('done');
|
function flashCopied(btn, restoreTitleKey){
|
||||||
copyBtn.title = t('copyDone');
|
btn.classList.add('done');
|
||||||
copyBtn.setAttribute('aria-label', t('copyDone'));
|
btn.title = t('copyDone');
|
||||||
|
btn.setAttribute('aria-label', t('copyDone'));
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
copyBtn.classList.remove('done');
|
btn.classList.remove('done');
|
||||||
copyBtn.title = t('copyTitle');
|
btn.title = t(restoreTitleKey);
|
||||||
copyBtn.setAttribute('aria-label', t('copyTitle'));
|
btn.setAttribute('aria-label', t(restoreTitleKey));
|
||||||
}, 1500);
|
}, 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) ---------- */
|
/* ---------- Internationalisierung (DE/EN/ES/FR) ---------- */
|
||||||
@@ -861,6 +912,7 @@ const I18N = {
|
|||||||
subtitle:"Werkbaum – Ein Text-basierter WBS/Projektstrukturplan/Feature-Tree Diagramm-Editor.",
|
subtitle:"Werkbaum – Ein Text-basierter WBS/Projektstrukturplan/Feature-Tree Diagramm-Editor.",
|
||||||
editorTitle:"Struktur (Text)", diagramTitle:"Diagramm",
|
editorTitle:"Struktur (Text)", diagramTitle:"Diagramm",
|
||||||
copy:"kopieren", copyDone:"kopiert ✓", copyTitle:"Text in die Zwischenablage kopieren",
|
copy:"kopieren", copyDone:"kopiert ✓", copyTitle:"Text in die Zwischenablage kopieren",
|
||||||
|
copyDiagramTitle:"Diagramm als Textbaum kopieren",
|
||||||
minimize:"minimieren", normal:"normal", maximize:"maximieren",
|
minimize:"minimieren", normal:"normal", maximize:"maximieren",
|
||||||
agenda:"Agenda", discarded:"verworfene",
|
agenda:"Agenda", discarded:"verworfene",
|
||||||
modeHorizontal:"Horizontal – Organigramm, Diagramm über dem Editor",
|
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.",
|
subtitle:"Werkbaum – A text-based WBS / work breakdown structure / feature-tree diagram editor.",
|
||||||
editorTitle:"Structure (text)", diagramTitle:"Diagram",
|
editorTitle:"Structure (text)", diagramTitle:"Diagram",
|
||||||
copy:"copy", copyDone:"copied ✓", copyTitle:"Copy text to clipboard",
|
copy:"copy", copyDone:"copied ✓", copyTitle:"Copy text to clipboard",
|
||||||
|
copyDiagramTitle:"Copy diagram as a text tree",
|
||||||
minimize:"minimize", normal:"normal", maximize:"maximize",
|
minimize:"minimize", normal:"normal", maximize:"maximize",
|
||||||
agenda:"Legend", discarded:"discarded",
|
agenda:"Legend", discarded:"discarded",
|
||||||
modeHorizontal:"Horizontal – org chart, diagram above the editor",
|
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.",
|
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",
|
editorTitle:"Estructura (texto)", diagramTitle:"Diagrama",
|
||||||
copy:"copiar", copyDone:"copiado ✓", copyTitle:"Copiar el texto al portapapeles",
|
copy:"copiar", copyDone:"copiado ✓", copyTitle:"Copiar el texto al portapapeles",
|
||||||
|
copyDiagramTitle:"Copiar el diagrama como árbol de texto",
|
||||||
minimize:"minimizar", normal:"normal", maximize:"maximizar",
|
minimize:"minimizar", normal:"normal", maximize:"maximizar",
|
||||||
agenda:"Leyenda", discarded:"descartados",
|
agenda:"Leyenda", discarded:"descartados",
|
||||||
modeHorizontal:"Horizontal – organigrama, diagrama sobre el editor",
|
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.",
|
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",
|
editorTitle:"Structure (texte)", diagramTitle:"Diagramme",
|
||||||
copy:"copier", copyDone:"copié ✓", copyTitle:"Copier le texte dans le presse-papiers",
|
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",
|
minimize:"réduire", normal:"normal", maximize:"agrandir",
|
||||||
agenda:"Légende", discarded:"abandonnés",
|
agenda:"Légende", discarded:"abandonnés",
|
||||||
modeHorizontal:"Horizontal – organigramme, diagramme au-dessus de l'éditeur",
|
modeHorizontal:"Horizontal – organigramme, diagramme au-dessus de l'éditeur",
|
||||||
|
|||||||
Reference in New Issue
Block a user