From: Alexander Wilms Date: Thu, 26 Jun 2025 22:06:42 +0000 (+0200) Subject: refactor(mermaid-viewer): Construct viewer DOM programmatically X-Git-Url: http://source.bookstackapp.com/hacks/commitdiff_plain/dd21d9a16bdfae0c336427c9cafe000fe60f6b4c refactor(mermaid-viewer): Construct viewer DOM programmatically --- diff --git a/content/mermaid-viewer/head.html b/content/mermaid-viewer/head.html index 3037a8a..aeec262 100644 --- a/content/mermaid-viewer/head.html +++ b/content/mermaid-viewer/head.html @@ -94,37 +94,52 @@ this.setupEventListeners(); } + /** + * Creates the DOM structure for the viewer programmatically. + * This is safer and more maintainable than using innerHTML with a large template string. + */ setupViewer() { - this.container.innerHTML = ` -
-
- -
-
- -
-
-
-
-
-
-
-
-
-
${this.mermaidCode}
-
-
- `; - this.viewport = this.container.querySelector(`.${CSS_CLASSES.VIEWPORT}`); - this.content = this.container.querySelector(`.${CSS_CLASSES.CONTENT}`); - this.diagram = this.container.querySelector(`.${CSS_CLASSES.DIAGRAM}`); - - // Cache control elements - this.toggleInteractionBtn = this.container.querySelector('.toggle-interaction'); - this.copyCodeBtn = this.container.querySelector('.mermaid-btn:not(.toggle-interaction)'); - this.zoomInBtn = this.container.querySelector('.zoom-in'); - this.zoomOutBtn = this.container.querySelector('.zoom-out'); - this.zoomResetBtn = this.container.querySelector('.zoom-reset'); + const createButton = (title, iconClass, ...extraClasses) => { + const button = document.createElement('div'); + button.className = `${CSS_CLASSES.BUTTON_BASE} ${extraClasses.join(' ')}`; + button.title = title; + const icon = document.createElement('i'); + icon.className = iconClass; + icon.setAttribute('aria-hidden', 'true'); + button.append(icon); + return button; + }; + + const controls = document.createElement('div'); + controls.className = CSS_CLASSES.CONTROLS; + this.toggleInteractionBtn = createButton('Toggle interaction', CSS_CLASSES.LOCK_ICON, 'mermaid-btn', 'toggle-interaction'); + this.copyCodeBtn = createButton('Copy code', 'fa fa-copy', 'mermaid-btn'); + controls.append(this.toggleInteractionBtn, this.copyCodeBtn); + + const zoomControls = document.createElement('div'); + zoomControls.className = CSS_CLASSES.ZOOM_CONTROLS; + this.zoomInBtn = createButton('Zoom in', 'fa fa-search-plus', 'mermaid-zoom-btn', 'zoom-in'); + this.zoomOutBtn = createButton('Zoom out', 'fa fa-search-minus', 'mermaid-zoom-btn', 'zoom-out'); + this.zoomResetBtn = createButton('Reset', 'fa fa-refresh', 'mermaid-zoom-btn', 'zoom-reset'); + zoomControls.append(this.zoomInBtn, this.zoomOutBtn, this.zoomResetBtn); + + this.diagram = document.createElement('div'); + this.diagram.className = CSS_CLASSES.DIAGRAM; + // Use textContent for security, preventing any potential HTML injection. + // Mermaid will parse the text content safely. + this.diagram.textContent = this.mermaidCode; + + this.content = document.createElement('div'); + this.content.className = CSS_CLASSES.CONTENT; + this.content.append(this.diagram); + + this.viewport = document.createElement('div'); + this.viewport.className = CSS_CLASSES.VIEWPORT; + this.viewport.append(this.content); + + // Clear the container and append the new structure + this.container.innerHTML = ''; + this.container.append(controls, zoomControls, this.viewport); // Function to render the diagram and perform post-render setup const renderAndSetup = () => {