From b5fd3d69cd517d4566a5378dc29bce103b9a0507 Mon Sep 17 00:00:00 2001 From: "shentong.martin" Date: Mon, 1 Dec 2025 11:03:46 +0800 Subject: [PATCH] feat: enable mermaid with zoom, pan and download Change-Id: I6063e0e66e0787cc55299fbf08c33d97f687198b --- assets/js/mermaid.js | 114 +++++++++++++++++++++++++++++++ assets/scss/_styles_project.scss | 34 +++++++++ assets/scss/markdown.scss | 6 ++ config.toml | 6 ++ 4 files changed, 160 insertions(+) diff --git a/assets/js/mermaid.js b/assets/js/mermaid.js index 821e5678729..501d0ffa8e2 100644 --- a/assets/js/mermaid.js +++ b/assets/js/mermaid.js @@ -33,6 +33,120 @@ var settings = norm(mermaid.mermaidAPI.defaultConfig, params); settings.startOnLoad = true; mermaid.initialize(settings); + + function ensureModal() { + if (!$('#mermaidModal').length) { + var html = '' + + ''; + $('body').append(html); + } + } + + var scale = 1; + function applyScale() { + $('.mermaid-zoom-content').css('transform', 'scale(' + scale + ')'); + } + + $(document).on('click', 'pre.mermaid, .mermaid', function() { + ensureModal(); + var $svg = $(this).find('svg').clone(); + var $content = $('#mermaidModal .mermaid-zoom-content'); + $content.empty().append($svg); + scale = 1; + applyScale(); + $('#mermaidModal').modal('show'); + }); + + $(document).on('click', '#mermaidZoomIn', function() { + scale = Math.min(scale + 0.2, 5); + applyScale(); + }); + $(document).on('click', '#mermaidZoomOut', function() { + scale = Math.max(scale - 0.2, 0.2); + applyScale(); + }); + $(document).on('click', '#mermaidZoomReset', function() { + scale = 1; + applyScale(); + }); + $(document).on('wheel', '.mermaid-zoom-wrapper', function(e) { + e.preventDefault(); + var dy = e.originalEvent.deltaY; + scale += (dy > 0 ? -0.1 : 0.1); + scale = Math.max(0.2, Math.min(5, scale)); + applyScale(); + }); + + var isDragging = false; + var startX = 0; + var startY = 0; + var scrollLeft = 0; + var scrollTop = 0; + + $(document).on('mousedown touchstart', '.mermaid-zoom-wrapper', function(e) { + var $wrap = $(this); + isDragging = true; + $wrap.addClass('dragging'); + var ev = e.type === 'touchstart' ? e.originalEvent.touches[0] : e; + startX = ev.pageX - $wrap.offset().left; + startY = ev.pageY - $wrap.offset().top; + scrollLeft = $wrap.scrollLeft(); + scrollTop = $wrap.scrollTop(); + }); + + $(document).on('mousemove touchmove', '.mermaid-zoom-wrapper', function(e) { + if (!isDragging) return; + var $wrap = $(this); + var ev = e.type === 'touchmove' ? e.originalEvent.touches[0] : e; + var x = ev.pageX - $wrap.offset().left; + var y = ev.pageY - $wrap.offset().top; + var dx = x - startX; + var dy = y - startY; + $wrap.scrollLeft(scrollLeft - dx); + $wrap.scrollTop(scrollTop - dy); + }); + + $(document).on('mouseup mouseleave touchend', '.mermaid-zoom-wrapper', function() { + isDragging = false; + $(this).removeClass('dragging'); + }); + + $(document).on('click', '#mermaidDownload', function() { + var $svg = $('#mermaidModal .mermaid-zoom-content svg'); + if (!$svg.length) return; + var node = $svg.get(0); + if (!node.getAttribute('xmlns')) { + node.setAttribute('xmlns', 'http://www.w3.org/2000/svg'); + } + var serializer = new XMLSerializer(); + var source = serializer.serializeToString(node); + var blob = new Blob([source], { type: 'image/svg+xml;charset=utf-8' }); + var url = URL.createObjectURL(blob); + var a = document.createElement('a'); + var name = (document.title || 'diagram') + '-' + Date.now() + '.svg'; + a.href = url; + a.download = name; + document.body.appendChild(a); + a.click(); + setTimeout(function() { + URL.revokeObjectURL(url); + a.remove(); + }, 0); + }); })(jQuery); {{ end }} {{ end }} diff --git a/assets/scss/_styles_project.scss b/assets/scss/_styles_project.scss index 9a6ec356c75..d9f6d793e1d 100644 --- a/assets/scss/_styles_project.scss +++ b/assets/scss/_styles_project.scss @@ -23,3 +23,37 @@ assets/scss/_styles_project.scss margin-bottom: initial; } } + +pre.mermaid { + padding: 0; + margin: 0; + background: transparent; + border: 0; +} + +.mermaid svg { + width: 100%; + height: auto; +} + +.mermaid-zoom-wrapper { + overflow: auto; + max-height: 85vh; + cursor: grab; + user-select: none; +} + +.mermaid-zoom-content { + transform-origin: 0 0; +} + +.mermaid-zoom-wrapper.dragging { + cursor: grabbing; +} + +.mermaid-zoom-controls { + position: absolute; + right: 1rem; + top: 1rem; + z-index: 10; +} diff --git a/assets/scss/markdown.scss b/assets/scss/markdown.scss index 85d8e9e3b59..f903a83f79f 100644 --- a/assets/scss/markdown.scss +++ b/assets/scss/markdown.scss @@ -39,6 +39,12 @@ overflow: auto } +.markdown pre.mermaid { + padding: 0; + margin: 0; + overflow: visible +} + .markdown div[class*=language-] pre code { color: rgba(0,0,0,.5) } diff --git a/config.toml b/config.toml index 1749593ccd9..35257deb1b0 100644 --- a/config.toml +++ b/config.toml @@ -161,6 +161,12 @@ offlineSearch = false # Enable syntax highlighting and copy buttons on code blocks with Prism prism_syntax_highlighting = true +# Mermaid diagrams configuration +[params.mermaid] +enable = true +theme = "default" +securitylevel = "strict" + # User interface configuration [params.ui] # Enable to show the side bar menu in its compact state.