From 8d35611f4cf647304b621c74a88511e1360e015c Mon Sep 17 00:00:00 2001 From: mhoennig Date: Tue, 21 Jul 2026 12:56:17 +0200 Subject: [PATCH] frontend: Diagramm als SVG-Datei herunterladen (LibreOffice-tauglich) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LibreOffice Writer erkennt das Browser-Bild-Clipboard nicht zuverlässig (sein HTML-Filter ignoriert data-URI-Bilder, und die Async-Clipboard-API erlaubt kein SVG als Bildtyp). Neue Download-Schaltfläche im Diagramm-Kopf speichert das Diagramm als eigenständige SVG-Datei (Vektor), die per Einfügen → Bild überall zuverlässig importierbar ist. Der bisherige PNG-Clipboard-Button bleibt für Apps, die ihn lesen. Tooltip in allen neun Sprachen; SPEC §9 ergänzt. Co-Authored-By: Claude Opus 4.8 --- docs/SPEC.md | 21 +++++++++++---------- frontend/index.html | 30 ++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/docs/SPEC.md b/docs/SPEC.md index 052de3a..faa3732 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -135,17 +135,18 @@ ist grau (`#6B7A8C`) — kein Petrol mehr im Diagramm. Der Modus ändert nur die des Teilbaums. ### Grafikexport des Diagramms -Das Diagramm ist per Icon-Schaltfläche als **Bild** in die Zwischenablage -kopierbar (grafisches Format, in Dokumente/Chats einfügbar): +Das Diagramm wird aus der Live-Geometrie in ein eigenständiges SVG (nur Formen ++ Text, keine externen Ressourcen) nachgezeichnet. Zwei Icon-Schaltflächen: -- Das gerenderte Diagramm wird aus der Live-Geometrie in ein eigenständiges - SVG (nur Formen + Text, keine externen Ressourcen) nachgezeichnet und als - **PNG** in die Zwischenablage gelegt. Es werden zwei Flavors geschrieben: - `image/png` (das eigentliche Bild) und `text/html` mit eingebettetem PNG - (``) — Office-Programme wie LibreOffice Writer - bevorzugen den HTML-Flavor und betten das Bild dann korrekt ein. Fällt der - Bild-Clipboard ganz aus (fehlende `ClipboardItem`-Unterstützung), wird der - **SVG-Quelltext** kopiert (ebenfalls ein Grafikformat). +- **Kopieren** — als **PNG** in die Zwischenablage. Es werden zwei Flavors + geschrieben: `image/png` (das eigentliche Bild) und `text/html` mit + eingebettetem PNG. Fällt der Bild-Clipboard ganz aus (fehlende + `ClipboardItem`-Unterstützung), wird der **SVG-Quelltext** kopiert. +- **Herunterladen** — als **SVG-Datei** (`werkbaum-diagramm.svg`, Vektor). + Verlässlicher Weg für Programme, die das Browser-Bild-Clipboard nicht + erkennen (z. B. LibreOffice Writer: Einfügen → Bild → Datei). Das + Browser-Clipboard erlaubt nur `image/png`/Text als Bildtypen, kein SVG — + daher der Datei-Weg für Vektor. - Übernommen werden Knotenfarben (Status §4), Größen-Badge, Tags und der Geister-Knoten; die Verbindungslinien werden je Gate neu gezogen (und = durchgezogen Tinte, oder = gestrichelt Grau) und treffen die Knoten diff --git a/frontend/index.html b/frontend/index.html index ab64737..faf0f72 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -572,6 +572,9 @@ +
@@ -863,6 +866,18 @@ async function copyDiagramImage(){ } await writeClipboard(svg); /* Fallback: SVG-Quelltext (ebenfalls Grafik) */ } +/* Als Datei speichern (SVG, Vektor). Verlässlicher Weg z. B. für LibreOffice + Writer: Einfügen → Bild → die Datei; das Bild-Clipboard aus dem Browser + erkennt LibreOffice nicht zuverlässig. */ +function downloadDiagramSvg(){ + const {svg} = diagramToSvg(); + const blob = new Blob([`\n` + svg], {type:'image/svg+xml'}); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; a.download = 'werkbaum-diagramm.svg'; + document.body.appendChild(a); a.click(); a.remove(); + setTimeout(() => URL.revokeObjectURL(url), 1000); +} /* Tab-Taste rückt ein statt den Fokus zu wechseln */ src.addEventListener('keydown', e => { @@ -1029,6 +1044,12 @@ document.getElementById('copyDiagram').addEventListener('click', async () => { await copyDiagramImage(); flashCopied(document.getElementById('copyDiagram'), 'copyDiagramTooltip'); }); +document.getElementById('dlDiagram').addEventListener('click', () => { + downloadDiagramSvg(); + const b = document.getElementById('dlDiagram'); /* kurzer Petrol-Blitz, Tooltip bleibt */ + b.classList.add('done'); + setTimeout(() => b.classList.remove('done'), 1500); +}); /* ---------- Internationalisierung (DE/EN/ES/FR) ---------- */ const I18N = { @@ -1041,6 +1062,7 @@ const I18N = { editorTitle:"Struktur (Text)", diagramTitle:"Diagramm", 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)", minimize:"minimieren", normal:"normal", maximize:"maximieren", agenda:"Agenda", discarded:"verworfene", modeHorizontal:"Horizontal – Organigramm, Diagramm über dem Editor", @@ -1070,6 +1092,7 @@ const I18N = { editorTitle:"Structure (text)", diagramTitle:"Diagram", 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)", minimize:"minimize", normal:"normal", maximize:"maximize", agenda:"Legend", discarded:"discarded", modeHorizontal:"Horizontal – org chart, diagram above the editor", @@ -1099,6 +1122,7 @@ const I18N = { editorTitle:"Estructura (texto)", diagramTitle:"Diagrama", 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)", minimize:"minimizar", normal:"normal", maximize:"maximizar", agenda:"Leyenda", discarded:"descartados", modeHorizontal:"Horizontal – organigrama, diagrama sobre el editor", @@ -1128,6 +1152,7 @@ const I18N = { editorTitle:"Structure (texte)", diagramTitle:"Diagramme", 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)", minimize:"réduire", normal:"normal", maximize:"agrandir", agenda:"Légende", discarded:"abandonnés", modeHorizontal:"Horizontal – organigramme, diagramme au-dessus de l'éditeur", @@ -1157,6 +1182,7 @@ const I18N = { editorTitle:"Struktura (tekst)", diagramTitle:"Diagram", 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)", minimize:"minimalizuj", normal:"normalny", maximize:"maksymalizuj", agenda:"Legenda", discarded:"odrzucone", modeHorizontal:"Poziomo – schemat organizacyjny, diagram nad edytorem", @@ -1186,6 +1212,7 @@ const I18N = { editorTitle:"Структура (текст)", diagramTitle:"Диаграмма", copy:"копировать", copyDone:"скопировано ✓", copyTooltip:"Скопировать текст в буфер обмена", copyDiagramTooltip:"Скопировать диаграмму как изображение PNG в буфер обмена", + downloadDiagramTooltip:"Скачать диаграмму как файл SVG (напр. для LibreOffice: Вставка → Изображение)", minimize:"свернуть", normal:"обычный", maximize:"развернуть", agenda:"Легенда", discarded:"отклонённые", modeHorizontal:"Горизонтально – оргсхема, диаграмма над редактором", @@ -1215,6 +1242,7 @@ const I18N = { editorTitle:"संरचना (टेक्स्ट)", diagramTitle:"आरेख", copy:"कॉपी करें", copyDone:"कॉपी हो गया ✓", copyTooltip:"टेक्स्ट को क्लिपबोर्ड पर कॉपी करें", copyDiagramTooltip:"आरेख को PNG छवि के रूप में क्लिपबोर्ड पर कॉपी करें", + downloadDiagramTooltip:"आरेख को SVG फ़ाइल के रूप में डाउनलोड करें (जैसे LibreOffice: सम्मिलित करें → छवि)", minimize:"छोटा करें", normal:"सामान्य", maximize:"बड़ा करें", agenda:"लेजेंड", discarded:"अस्वीकृत", modeHorizontal:"क्षैतिज – संगठन-चार्ट, संपादक के ऊपर आरेख", @@ -1244,6 +1272,7 @@ const I18N = { editorTitle:"结构(文本)", diagramTitle:"图表", copy:"复制", copyDone:"已复制 ✓", copyTooltip:"将文本复制到剪贴板", copyDiagramTooltip:"将图表作为 PNG 图片复制到剪贴板", + downloadDiagramTooltip:"将图表下载为 SVG 文件(例如用于 LibreOffice:插入 → 图像)", minimize:"最小化", normal:"正常", maximize:"最大化", agenda:"图例", discarded:"已放弃", modeHorizontal:"横向——组织结构图,图表在编辑器上方", @@ -1273,6 +1302,7 @@ const I18N = { editorTitle:"構造(テキスト)", diagramTitle:"ダイアグラム", copy:"コピー", copyDone:"コピーしました ✓", copyTooltip:"テキストをクリップボードにコピー", copyDiagramTooltip:"ダイアグラムを PNG 画像としてクリップボードにコピー", + downloadDiagramTooltip:"ダイアグラムを SVG ファイルとしてダウンロード(例:LibreOffice の 挿入 → 画像)", minimize:"最小化", normal:"標準", maximize:"最大化", agenda:"凡例", discarded:"破棄", modeHorizontal:"横 — 組織図、ダイアグラムはエディターの上",