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 = `
- <div class="${CSS_CLASSES.CONTROLS}">
- <div class="${CSS_CLASSES.BUTTON_BASE} mermaid-btn toggle-interaction" title="Toggle interaction">
- <i class="${CSS_CLASSES.LOCK_ICON}" aria-hidden="true"></i>
- </div>
- <div class="${CSS_CLASSES.BUTTON_BASE} mermaid-btn" title="Copy code">
- <i class="fa fa-copy" aria-hidden="true"></i>
- </div>
- </div>
- <div class="${CSS_CLASSES.ZOOM_CONTROLS}">
- <div class="${CSS_CLASSES.BUTTON_BASE} mermaid-zoom-btn zoom-in" title="Zoom in"><i class="fa fa-search-plus" aria-hidden="true"></i></div>
- <div class="${CSS_CLASSES.BUTTON_BASE} mermaid-zoom-btn zoom-out" title="Zoom out"><i class="fa fa-search-minus" aria-hidden="true"></i></div>
- <div class="${CSS_CLASSES.BUTTON_BASE} mermaid-zoom-btn zoom-reset" title="Reset"><i class="fa fa-refresh" aria-hidden="true"></i></div>
- </div>
- <div class="${CSS_CLASSES.VIEWPORT}">
- <div class="${CSS_CLASSES.CONTENT}">
- <div class="${CSS_CLASSES.DIAGRAM}">${this.mermaidCode}</div>
- </div>
- </div>
- `;
- 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 = () => {