| |
| |
| |
| |
|
|
| console.log("🚀 Mermaid Zoom Script v16.0 loaded - OPTIMIZED for many diagrams"); |
|
|
| |
| const imageCache = new Map(); |
|
|
| |
| function applyMermaidStylesToSvg(svgElement) { |
| try { |
| const isDark = document.documentElement.getAttribute("data-theme") === "dark"; |
|
|
| |
| const colors = isDark ? { |
| nodeFill: '#1a1a1a', |
| nodeStroke: '#ffffff', |
| clusterFill: '#2a2a2a', |
| clusterStroke: '#ffffff', |
| pathStroke: '#ffffff', |
| textColor: '#ffffff' |
| } : { |
| nodeFill: '#ffffff', |
| nodeStroke: '#333333', |
| clusterFill: '#f9f9f9', |
| clusterStroke: '#333333', |
| pathStroke: '#333333', |
| textColor: '#333333' |
| }; |
|
|
| |
| const rects = svgElement.querySelectorAll('rect:not(.flowchart-link), .node rect, .nodeLabel rect'); |
| rects.forEach(rect => { |
| rect.setAttribute('rx', '8'); |
| rect.setAttribute('ry', '8'); |
| rect.setAttribute('fill', colors.nodeFill); |
| rect.setAttribute('stroke', colors.nodeStroke); |
| }); |
|
|
| const clusterRects = svgElement.querySelectorAll('.cluster rect'); |
| clusterRects.forEach(rect => { |
| rect.setAttribute('rx', '8'); |
| rect.setAttribute('ry', '8'); |
| rect.setAttribute('fill', colors.clusterFill); |
| rect.setAttribute('stroke', colors.clusterStroke); |
| }); |
|
|
| const paths = svgElement.querySelectorAll('.edgePath'); |
| paths.forEach(path => { |
| path.setAttribute('stroke', colors.pathStroke); |
| }); |
|
|
| const textElements = svgElement.querySelectorAll('text, .nodeLabel text, .edgeLabel text'); |
| textElements.forEach(text => { |
| text.setAttribute('fill', colors.textColor); |
| }); |
|
|
| } catch (error) { |
| console.error('❌ Error applying styles to SVG:', error); |
| } |
| } |
|
|
| |
| function getCacheKey(svgElement, theme) { |
| const svgContent = svgElement.outerHTML; |
| const hash = svgContent.length + svgContent.slice(0, 100); |
| return `${hash}-${theme}`; |
| } |
|
|
| |
| function convertSvgToImageCached(svgElement, wrapper, theme) { |
| const cacheKey = getCacheKey(svgElement, theme); |
|
|
| |
| if (imageCache.has(cacheKey)) { |
| console.log(`📦 Using cached image for theme: ${theme}`); |
| return imageCache.get(cacheKey); |
| } |
|
|
| try { |
| const wrapperRect = wrapper.getBoundingClientRect(); |
| const wrapperWidth = Math.round(wrapperRect.width); |
| const wrapperHeight = Math.round(wrapperRect.height); |
|
|
| |
| const clonedSvg = svgElement.cloneNode(true); |
| applyMermaidStylesToSvg(clonedSvg); |
|
|
| |
| const zoomFactor = 2; |
| const imageWidth = wrapperWidth * zoomFactor; |
| const imageHeight = wrapperHeight * zoomFactor; |
|
|
| clonedSvg.setAttribute('width', imageWidth); |
| clonedSvg.setAttribute('height', imageHeight); |
|
|
| |
| const svgData = new XMLSerializer().serializeToString(clonedSvg); |
| const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' }); |
| const svgUrl = URL.createObjectURL(svgBlob); |
|
|
| |
| const imgElement = document.createElement('img'); |
| imgElement.src = svgUrl; |
| imgElement.style.width = `${wrapperWidth}px`; |
| imgElement.style.height = `${wrapperHeight}px`; |
| imgElement.style.display = 'block'; |
| imgElement.setAttribute('data-zoomable', '1'); |
| imgElement.classList.add('mermaid-zoom-image'); |
|
|
| const result = { |
| imgElement, |
| svgUrl, |
| dimensions: { wrapperWidth, wrapperHeight, imageWidth, imageHeight } |
| }; |
|
|
| |
| imageCache.set(cacheKey, result); |
| console.log(`💾 Cached image for theme: ${theme}`); |
|
|
| return result; |
|
|
| } catch (error) { |
| console.error("❌ Error converting SVG to image:", error); |
| return null; |
| } |
| } |
|
|
| |
| function initMermaidZoom(mermaidEl, index) { |
| |
| if (mermaidEl.parentElement?.classList.contains("mermaid-zoom-wrapper")) { |
| return; |
| } |
|
|
| console.log(`📦 Setting up zoom for Mermaid ${index}`); |
|
|
| |
| const wrapper = document.createElement("div"); |
| wrapper.className = "mermaid-zoom-wrapper"; |
| wrapper.setAttribute("data-zoomable", "1"); |
| wrapper.style.display = "block"; |
| wrapper.style.width = "100%"; |
| wrapper.style.maxWidth = "100%"; |
|
|
| |
| mermaidEl.parentNode.insertBefore(wrapper, mermaidEl); |
| wrapper.appendChild(mermaidEl); |
|
|
| |
| wrapper.addEventListener("click", (e) => { |
| e.preventDefault(); |
| e.stopPropagation(); |
|
|
| const svgElement = mermaidEl.querySelector("svg"); |
| if (!svgElement) return; |
|
|
| const currentTheme = document.documentElement.getAttribute("data-theme"); |
| console.log(`🖱️ Mermaid ${index} clicked, converting for zoom`); |
|
|
| |
| const result = convertSvgToImageCached(svgElement, wrapper, currentTheme); |
| if (!result) return; |
|
|
| |
| wrapper.innerHTML = ''; |
| wrapper.appendChild(result.imgElement); |
|
|
| |
| const isDark = currentTheme === "dark"; |
| const background = isDark ? "rgba(0,0,0,.9)" : "rgba(0,0,0,.85)"; |
|
|
| const zoomInstance = window.mediumZoom(result.imgElement, { |
| background, |
| margin: 24, |
| scrollOffset: 0, |
| }); |
|
|
| console.log(`🎉 Zoom initialized for Mermaid ${index}`); |
|
|
| |
| const forceZIndex = () => { |
| const overlay = document.querySelector('.medium-zoom-overlay'); |
| const zoomedImage = document.querySelector('.medium-zoom-image--opened'); |
|
|
| if (overlay) { |
| overlay.style.zIndex = '9999999'; |
| overlay.style.setProperty('z-index', '9999999', 'important'); |
| } |
|
|
| if (zoomedImage) { |
| zoomedImage.style.zIndex = '10000000'; |
| zoomedImage.style.setProperty('z-index', '10000000', 'important'); |
| zoomedImage.style.filter = 'none'; |
| zoomedImage.style.setProperty('filter', 'none', 'important'); |
| } |
| }; |
|
|
| |
| result.imgElement.addEventListener('zoom:open', forceZIndex); |
| result.imgElement.addEventListener('zoom:opened', forceZIndex); |
|
|
| |
| const observer = new MutationObserver(forceZIndex); |
| observer.observe(document.body, { childList: true, subtree: true }); |
|
|
| |
| setTimeout(() => observer.disconnect(), 5000); |
| }); |
| } |
|
|
| |
| function setupMermaidZoomOptimized() { |
| const mermaidElements = document.querySelectorAll(".mermaid"); |
| console.log(`🔍 Found ${mermaidElements.length} Mermaid elements`); |
|
|
| mermaidElements.forEach((mermaidEl, index) => { |
| initMermaidZoom(mermaidEl, index); |
| }); |
| } |
|
|
| |
| function setupGlobalObserver() { |
| const observer = new MutationObserver((mutations) => { |
| let shouldUpdate = false; |
|
|
| mutations.forEach((mutation) => { |
| if (mutation.type === 'childList') { |
| |
| mutation.addedNodes.forEach(node => { |
| if (node.nodeType === 1) { |
| if (node.classList?.contains('mermaid') || |
| node.querySelector?.('.mermaid')) { |
| shouldUpdate = true; |
| } |
| } |
| }); |
| } |
| }); |
|
|
| if (shouldUpdate) { |
| console.log(`🔄 New Mermaid diagrams detected, updating...`); |
| setTimeout(setupMermaidZoomOptimized, 100); |
| } |
| }); |
|
|
| observer.observe(document.body, { |
| childList: true, |
| subtree: true |
| }); |
|
|
| console.log(`👁️ Global observer started`); |
| } |
|
|
| |
| function initMermaidZoomOptimized() { |
| console.log("🎯 Initializing optimized Mermaid zoom"); |
|
|
| |
| const checkMediumZoom = () => { |
| if (window.mediumZoom) { |
| console.log("✅ mediumZoom found, initializing optimized Mermaid zoom"); |
| setupMermaidZoomOptimized(); |
| setupGlobalObserver(); |
| } else { |
| console.log("⏳ Waiting for mediumZoom..."); |
| setTimeout(checkMediumZoom, 100); |
| } |
| }; |
|
|
| checkMediumZoom(); |
| } |
|
|
| |
| if (document.readyState === "loading") { |
| document.addEventListener("DOMContentLoaded", initMermaidZoomOptimized); |
| } else { |
| initMermaidZoomOptimized(); |
| } |
|
|
| |
| window.addEventListener("load", () => { |
| setTimeout(initMermaidZoomOptimized, 1000); |
| }); |
|
|
| |
| setInterval(() => { |
| if (imageCache.size > 20) { |
| console.log(`🧹 Cleaning image cache (${imageCache.size} items)`); |
| imageCache.clear(); |
| } |
| }, 60000); |
|
|