diff --git a/src/public/app/services/utils.js b/src/public/app/services/utils.js index e8c92351ef..bcc936228e 100644 --- a/src/public/app/services/utils.js +++ b/src/public/app/services/utils.js @@ -527,6 +527,31 @@ function downloadSvg(nameWithoutExtension, svgContent) { document.body.removeChild(element); } +function downloadPng(nameWithoutExtension, pngBlob) { + console.log('downloadPng called with:', nameWithoutExtension, pngBlob); + + if (!pngBlob) { + console.error('PNG blob is null or undefined'); + return; + } + + const filename = `${nameWithoutExtension}.png`; + const url = URL.createObjectURL(pngBlob); + console.log('Blob URL:', url); + const element = document.createElement('a'); + element.setAttribute('href', url); + element.setAttribute('download', filename); + + element.style.display = 'none'; + document.body.appendChild(element); + + element.click(); + + document.body.removeChild(element); + URL.revokeObjectURL(url); + console.log('PNG download triggered'); +} + export default { reloadFrontendApp, parseDate, @@ -567,5 +592,6 @@ export default { areObjectsEqual, copyHtmlToClipboard, createImageSrcUrl, - downloadSvg + downloadSvg, + downloadPng }; diff --git a/src/public/app/widgets/mermaid.js b/src/public/app/widgets/mermaid.js index a05561ac0d..9ac22f46d3 100644 --- a/src/public/app/widgets/mermaid.js +++ b/src/public/app/widgets/mermaid.js @@ -33,6 +33,10 @@ const TPL = `
+
+ + +
`; let idCounter = 1; @@ -51,6 +55,9 @@ export default class MermaidWidget extends NoteContextAwareWidget { this.$display = this.$widget.find('.mermaid-render'); this.$errorContainer = this.$widget.find(".mermaid-error"); this.$errorMessage = this.$errorContainer.find(".error-content"); + + this.$widget.find('.export-svg').on('click', () => this.exportSvgEvent({ format: 'svg' })); + this.$widget.find('.export-png').on('click', () => this.exportSvgEvent({ format: 'png' })); } async refreshWithNote(note) { @@ -134,12 +141,54 @@ export default class MermaidWidget extends NoteContextAwareWidget { } } - async exportSvgEvent({ntxId}) { - if (!this.isNoteContext(ntxId) || this.note.type !== "mermaid") { - return; - } - + async exportSvgEvent({ format = 'svg' }) { + console.log('Export event triggered with format:', format); const svg = await this.renderSvg(); - utils.downloadSvg(this.note.title, svg); + console.log('SVG rendered:', svg); + + if (format === 'png') { + try { + const png = await this.convertSvgToPng(svg); + console.log('PNG converted:', png); + utils.downloadPng(this.note.title, png); + console.log('PNG download triggered'); + } catch (error) { + console.error('Error converting SVG to PNG:', error); + } + } else { + utils.downloadSvg(this.note.title, svg); + console.log('SVG download triggered'); + } + } + + async convertSvgToPng(svg) { + return new Promise((resolve, reject) => { + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + const img = new Image(); + + img.onload = () => { + console.log('Image loaded'); + canvas.width = img.width; + canvas.height = img.height; + ctx.drawImage(img, 0, 0); + canvas.toBlob((blob) => { + if (blob) { + resolve(blob); + } else { + reject(new Error('Canvas toBlob conversion failed')); + } + }, 'image/png'); + }; + + img.onerror = (e) => { + console.error('Image load error:', e); + reject(e); + }; + + const svgDataUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`; + console.log('SVG Data URL:', svgDataUrl); + img.src = svgDataUrl; + }); } -} +} \ No newline at end of file