]> BookStack Code Mirror - hacks/commitdiff
refactor(mermaid-viewer): Bind event handlers to class instance
authorAlexander Wilms <redacted>
Thu, 26 Jun 2025 21:42:33 +0000 (23:42 +0200)
committerAlexander Wilms <redacted>
Thu, 26 Jun 2025 21:42:33 +0000 (23:42 +0200)
This ensures proper addition and removal of event listeners and fixes a memory leak. Also increases the z-index of the notification.

content/mermaid-viewer/head.html

index 24702318639597916a8c66e61675908e9f290d06..68962ca288aa2a94563b64414f302c41416197cc 100644 (file)
             this.zoomOutBtn = null;
             this.zoomResetBtn = null;
 
+            // Bind event handlers for proper addition and removal
             this.boundMouseMoveHandler = this.handleMouseMove.bind(this);
             this.boundMouseUpHandler = this.handleMouseUp.bind(this);
+            this.boundToggleInteraction = this.toggleInteraction.bind(this);
+            this.boundCopyCode = this.copyCode.bind(this);
+            this.boundZoomIn = () => {
+                const { clientX, clientY } = this._getViewportCenterClientCoords();
+                this.zoom(1, clientX, clientY);
+            };
+            this.boundZoomOut = () => {
+                const { clientX, clientY } = this._getViewportCenterClientCoords();
+                this.zoom(-1, clientX, clientY);
+            };
+            this.boundResetZoom = this.resetZoom.bind(this);
+            this.boundHandleWheel = this.handleWheel.bind(this);
+            this.boundHandleMouseDown = this.handleMouseDown.bind(this);
+            this.boundPreventDefault = e => e.preventDefault();
+            this.boundPreventSelect = e => { if (this.isDragging || this.interactionEnabled) e.preventDefault(); };
+
             this.setupViewer();
             this.setupEventListeners();
         }
         }
 
         setupEventListeners() {
-            this.toggleInteractionBtn.addEventListener('click', () => this.toggleInteraction());
-            this.copyCodeBtn.addEventListener('click', () => this.copyCode());
-            this.zoomInBtn.addEventListener('click', () => {
-                const { clientX, clientY } = this._getViewportCenterClientCoords();
-                this.zoom(1, clientX, clientY);
-            });
-            this.zoomOutBtn.addEventListener('click', () => {
-                const { clientX, clientY } = this._getViewportCenterClientCoords();
-                this.zoom(-1, clientX, clientY);
-            });
-            this.zoomResetBtn.addEventListener('click', () => this.resetZoom());
-
-            this.viewport.addEventListener('wheel', (e) => {
-                if (!this.interactionEnabled) return;
-                // Prevent default browser scroll/zoom behavior when wheeling over the diagram
-                e.preventDefault();
-                this.content.classList.add(CSS_CLASSES.ZOOMING);
-                const clientX = e.clientX;
-                const clientY = e.clientY;
-                if (e.deltaY > 0) this.zoom(-1, clientX, clientY);
-                else this.zoom(1, clientX, clientY);
-                setTimeout(() => this.content.classList.remove(CSS_CLASSES.ZOOMING), ZOOM_ANIMATION_CLASS_TIMEOUT_MS);
-            }, { passive: false });
+            this.toggleInteractionBtn.addEventListener('click', this.boundToggleInteraction);
+            this.copyCodeBtn.addEventListener('click', this.boundCopyCode);
+            this.zoomInBtn.addEventListener('click', this.boundZoomIn);
+            this.zoomOutBtn.addEventListener('click', this.boundZoomOut);
+            this.zoomResetBtn.addEventListener('click', this.boundResetZoom);
 
-            this.viewport.addEventListener('mousedown', (e) => {
-                if (!this.interactionEnabled || e.button !== 0) return;
-                e.preventDefault();
-                this.isDragging = true;
-                this.dragStarted = false;
-                this.startX = e.clientX;
-                this.startY = e.clientY;
-                this.dragBaseTranslateX = this.translateX;
-                this.dragBaseTranslateY = this.translateY;
-                this.viewport.classList.add(CSS_CLASSES.DRAGGING);
-                this.viewport.classList.remove(CSS_CLASSES.INTERACTIVE_HOVER);
-                this.viewport.classList.add(CSS_CLASSES.INTERACTIVE_PAN);
-                this.content.classList.remove(CSS_CLASSES.ZOOMING);
-            });
+            this.viewport.addEventListener('wheel', this.boundHandleWheel, { passive: false });
+            this.viewport.addEventListener('mousedown', this.boundHandleMouseDown);
 
             // Listen on document for mousemove to handle dragging outside viewport
             document.addEventListener('mousemove', this.boundMouseMoveHandler);
             // Listen on window for mouseup to ensure drag ends even if mouse is released outside
             window.addEventListener('mouseup', this.boundMouseUpHandler, true); // Use capture phase
 
-            this.viewport.addEventListener('contextmenu', (e) => e.preventDefault());
-            this.viewport.addEventListener('selectstart', (e) => { if (this.isDragging || this.interactionEnabled) e.preventDefault(); });
+            this.viewport.addEventListener('contextmenu', this.boundPreventDefault);
+            this.viewport.addEventListener('selectstart', this.boundPreventSelect);
         }
 
         toggleInteraction() {
             this.content.style.transform = `translate(${this.translateX}px, ${this.translateY}px) scale(${this.scale})`;
         }
 
+        handleWheel(e) {
+            if (!this.interactionEnabled) return;
+            // Prevent default browser scroll/zoom behavior when wheeling over the diagram
+            e.preventDefault();
+            this.content.classList.add(CSS_CLASSES.ZOOMING);
+            const clientX = e.clientX;
+            const clientY = e.clientY;
+            if (e.deltaY > 0) this.zoom(-1, clientX, clientY);
+            else this.zoom(1, clientX, clientY);
+            setTimeout(() => this.content.classList.remove(CSS_CLASSES.ZOOMING), ZOOM_ANIMATION_CLASS_TIMEOUT_MS);
+        }
+
+        handleMouseDown(e) {
+            if (!this.interactionEnabled || e.button !== 0) return;
+            e.preventDefault();
+            this.isDragging = true;
+            this.dragStarted = false;
+            this.startX = e.clientX;
+            this.startY = e.clientY;
+            this.dragBaseTranslateX = this.translateX;
+            this.dragBaseTranslateY = this.translateY;
+            this.viewport.classList.add(CSS_CLASSES.DRAGGING);
+            this.viewport.classList.remove(CSS_CLASSES.INTERACTIVE_HOVER);
+            this.viewport.classList.add(CSS_CLASSES.INTERACTIVE_PAN);
+            this.content.classList.remove(CSS_CLASSES.ZOOMING);
+        }
+
         handleMouseMove(e) {
             if (!this.isDragging) return;
             // e.preventDefault() is called only after dragStarted is true to allow clicks if threshold isn't met.
 
         destroy() {
             // Remove event listeners specific to this instance
-            this.toggleInteractionBtn.removeEventListener('click', this.toggleInteraction); // Need to ensure this is the same function reference
-            this.copyCodeBtn.removeEventListener('click', this.copyCode);
-            this.zoomInBtn.removeEventListener('click', this.zoom); // These would need bound versions or careful handling
-            this.zoomOutBtn.removeEventListener('click', this.zoom);
-            this.zoomResetBtn.removeEventListener('click', this.resetZoom);
+            this.toggleInteractionBtn.removeEventListener('click', this.boundToggleInteraction);
+            this.copyCodeBtn.removeEventListener('click', this.boundCopyCode);
+            this.zoomInBtn.removeEventListener('click', this.boundZoomIn);
+            this.zoomOutBtn.removeEventListener('click', this.boundZoomOut);
+            this.zoomResetBtn.removeEventListener('click', this.boundResetZoom);
 
-            this.viewport.removeEventListener('wheel', this.handleWheel); // Assuming handleWheel is the actual handler
-            this.viewport.removeEventListener('mousedown', this.handleMouseDown); // Assuming handleMouseDown
-            this.viewport.removeEventListener('contextmenu', this.handleContextMenu);
-            this.viewport.removeEventListener('selectstart', this.handleSelectStart);
+            this.viewport.removeEventListener('wheel', this.boundHandleWheel, { passive: false });
+            this.viewport.removeEventListener('mousedown', this.boundHandleMouseDown);
+            this.viewport.removeEventListener('contextmenu', this.boundPreventDefault);
+            this.viewport.removeEventListener('selectstart', this.boundPreventSelect);
 
             document.removeEventListener('mousemove', this.boundMouseMoveHandler);
             window.removeEventListener('mouseup', this.boundMouseUpHandler, true);
         border-radius: 6px;
         transform: translateX(400px);
         transition: transform 0.3s ease;
-        z-index: 1000;
+        z-index: 10000;
+        /* Increased z-index to appear above site header */
     }
 
     .mermaid-notification.show {