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
+72
-17
@@ -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>
|
||||
</label>
|
||||
</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">
|
||||
<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>
|
||||
@@ -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 => `<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 */
|
||||
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",
|
||||
|
||||
Reference in New Issue
Block a user