From 77fcd7fa93bd173055e1604c0054aecd7488ac10 Mon Sep 17 00:00:00 2001 From: Cell <1024@lruihao.cn> Date: Sun, 17 Aug 2025 14:32:51 +0800 Subject: [PATCH] :recycle: Refactor: Mermaid theme switching again (#623) --- assets/css/_partials/_single/_code.scss | 2 +- assets/css/_shortcodes/_mermaid.scss | 41 ++++++++- assets/js/lib/mermaid.js | 105 +++++++++++++++++++++++- assets/js/theme.js | 79 ++---------------- layouts/_partials/assets.html | 7 -- layouts/_partials/plugin/mermaid.html | 6 +- 6 files changed, 151 insertions(+), 89 deletions(-) diff --git a/assets/css/_partials/_single/_code.scss b/assets/css/_partials/_single/_code.scss index fb40dec4..ff96c79a 100644 --- a/assets/css/_partials/_single/_code.scss +++ b/assets/css/_partials/_single/_code.scss @@ -4,7 +4,7 @@ code { } // indented code -pre:not(.mermaid[data-processed]) { +pre:not([data-processed]) { margin: 0; line-height: 1.45em; padding: 0.5rem; diff --git a/assets/css/_shortcodes/_mermaid.scss b/assets/css/_shortcodes/_mermaid.scss index 670fc222..a4c80399 100644 --- a/assets/css/_shortcodes/_mermaid.scss +++ b/assets/css/_shortcodes/_mermaid.scss @@ -1,9 +1,12 @@ -.mermaid { +.mermaid, +.mermaid-dark, +.mermaid-neutral { position: relative; overflow: hidden !important; &[data-processed] { text-align: center; + margin-block: 0.5rem; } &:not([data-processed])::before { @@ -13,7 +16,7 @@ left: 0; width: 100%; height: 100%; - backdrop-filter: blur(0.5rem); + backdrop-filter: blur(1rem); background-position: center; background-repeat: no-repeat; background-image: var(#{$rootPrefix}loading-img); @@ -25,3 +28,37 @@ height: auto; } } + +.mermaid { + [data-theme='dark'] & { + height: 0; + padding: 0; + } +} + +.mermaid-dark { + height: 0; + padding: 0 !important; + + [data-theme='dark'] & { + height: auto; + padding: 0.5rem !important; + } +} + +// for print view +.mermaid-neutral { + height: 0; + padding: 0 !important; +} + +@media only print { + .mermaid, + .mermaid-dark { + display: none; + } + .mermaid-neutral { + height: auto; + padding: 0.5rem !important; + } +} diff --git a/assets/js/lib/mermaid.js b/assets/js/lib/mermaid.js index e34bad6b..e716fc0c 100644 --- a/assets/js/lib/mermaid.js +++ b/assets/js/lib/mermaid.js @@ -1,10 +1,107 @@ {{- $mermaid := .Param "mermaid" -}} {{- $mermaidCDN := $mermaid.cdn | default "https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.esm.min.mjs" -}} -import mermaid from "{{ $mermaidCDN }}"; +import mermaid from "{{ $mermaidCDN }}" {{- with $mermaid.zenuml }} -import zenuml from "{{ . }}"; -await mermaid.registerExternalDiagrams([zenuml]); +import zenuml from "{{ . }}" +await mermaid.registerExternalDiagrams([zenuml]) {{- end }} mermaid.startOnLoad = false; -window.mermaid = mermaid; +const mermaidConfig = {{ $mermaid | jsonify }} +let processing = false +let delayTask = null + +/** + * Get current/next theme + * @param {Boolean} [next] whether to get the next theme + * @returns + */ +function getTheme(next) { + const darkMode = document.documentElement.dataset.theme === 'dark' + return (next ? !darkMode : darkMode) + ? mermaidConfig.themes[1] + : mermaidConfig.themes[0] +} + +/** + * Initialize Mermaid by loading the specified theme + * @param {String} [theme] specific theme to load + * @param {Boolean} [darkMode] whether to use dark mode + * @param {String} [selector] specific selector for the elements to process + * @returns + */ +async function loadMermaid({ theme, darkMode, selector }) { + if (processing) { + console.warn('Mermaid is still processing, delaying the reload.') + delayTask = () => loadMermaid({ theme, darkMode, selector }) + return + } + const isDarkMode = darkMode ?? (document.documentElement.dataset.theme === 'dark') + const currentTheme = theme ?? getTheme() + const querySelector = selector ?? (isDarkMode ? '.mermaid-dark' : '.mermaid') + const nodes = document.querySelectorAll(querySelector) + + if (!nodes.length) return + + console.time(`Loaded [${currentTheme}]`) + processing = true + nodes.forEach((node) => { + node.toggleAttribute('data-processing', true) + }) + + // https://mermaid.js.org/config/schema-docs/config.html + mermaid.initialize({ + startOnLoad: false, + darkMode: isDarkMode, + theme: currentTheme, + securityLevel: mermaidConfig.securityLevel, + look: mermaidConfig.look, + fontFamily: mermaidConfig.fontFamily, + altFontFamily: mermaidConfig.fontFamily + }) + // skip data-processed elements in Mermaid run function, so this won't re-render + await mermaid.run({ + nodes, + suppressErrors: true, + postRenderCallback: (svgId) => { + document.getElementById(svgId).parentElement.toggleAttribute('data-processing', false) + }, + }) + processing = false + console.timeEnd(`Loaded [${currentTheme}]`) + + if (delayTask && typeof delayTask === 'function') { + delayTask(); + delayTask = null; + // console.log('Delayed task executed'); + } +} + +async function initMermaid() { + const contentEl = document.getElementById('content'); + if (!contentEl || !contentEl.innerHTML) return + const currentTheme = getTheme() + const nextTheme = getTheme(true) + const darkMode = document.documentElement.dataset.theme === 'dark' + await loadMermaid({ theme: currentTheme, darkMode }) + await loadMermaid({ theme: nextTheme, darkMode: !darkMode }) + await loadMermaid({ theme: 'neutral', darkMode: false, selector: '.mermaid-neutral' }) +} + +window.FixItMermaid = { + config: mermaidConfig, + load: loadMermaid, + init: initMermaid, +} +window.mermaid = mermaid + +console.log( + `%c💫 FixIt Mermaid`, + 'color: #FF3670; font-weight: bold; font-size: 16px; text-shadow: 1px 1px 2px rgba(0,0,0,0.1);', +) + +if (document.readyState !== 'loading') { + initMermaid() +} else { + document.addEventListener('DOMContentLoaded', initMermaid, false) +} diff --git a/assets/js/theme.js b/assets/js/theme.js index d8259689..f6551b7e 100644 --- a/assets/js/theme.js +++ b/assets/js/theme.js @@ -523,11 +523,11 @@ class FixIt { * init diagram copy button */ initDiagramCopyBtn() { + const stagingDOM = this.util.getStagingDOM() this.util.forEach(document.querySelectorAll('.diagram-copy-btn'), ($btn) => { $btn.addEventListener('click', () => { - const _tempEl = document.createElement('div'); - _tempEl.appendChild($btn.parentElement.querySelector('template').content.cloneNode(true)); - const code = _tempEl.innerText.trim(); + stagingDOM.stage($btn.parentElement.querySelector('template').content.cloneNode(true)) + const code = stagingDOM.contentAsText(); this.util.copyText(code).then(() => { $btn.toggleAttribute('data-copied', true); setTimeout(() => { @@ -538,6 +538,7 @@ class FixIt { }); }, false); }); + stagingDOM.destroy(); } initTable(target = document) { @@ -656,73 +657,6 @@ class FixIt { }); } } - initMermaid() { - if (!this.config.mermaid) return; - - const themes = this.config.mermaid.themes ?? ['default', 'dark']; - let processing = false; - let delayTask = null; - - const loadMermaid = async () => { - processing = true; - // https://mermaid.js.org/config/schema-docs/config.html - window.mermaid.initialize({ - startOnLoad: false, - darkMode: this.isDark, - theme: this.isDark ? themes[1] : themes[0], - securityLevel: this.config.mermaid.securityLevel, - look: this.config.mermaid.look, - fontFamily: this.config.mermaid.fontFamily, - altFontFamily: this.config.mermaid.fontFamily - }); - await window.mermaid.run({ - querySelector: '.mermaid', - suppressErrors: true, - }); - processing = false; - if (delayTask && typeof delayTask === 'function') { - delayTask(); - delayTask = null; - // console.log('Delayed task executed'); - } - }; - - const reloadMermaid = async () => { - await this.util.forEach(document.querySelectorAll('.mermaid[data-processed]'), (el) => { - el.removeAttribute('data-processed'); - el.parentElement.replaceChild(el.nextElementSibling.content.cloneNode(true), el); - }); - await loadMermaid(); - }; - - const waitForMermaid = () => { - return new Promise((resolve) => { - const timer = setInterval(() => { - if (window.mermaid && window.mermaid.initialize) { - clearInterval(timer); - resolve(); - } - }, 100); - }); - }; - - waitForMermaid().then(() => { - loadMermaid(); - this.switchThemeEventSet.add(() => { - if (processing) { - console.warn('Mermaid is still processing, delaying the reload.'); - delayTask = reloadMermaid; - return; - } - // console.log('reload immediately'); - reloadMermaid().catch(console.error); - }); - - this.beforeprintEventSet.add(() => { - // Optionally set theme to 'neutral' for printing if required - }); - }) - } initEcharts() { if (!this.config.echarts) { @@ -1167,7 +1101,6 @@ class FixIt { this.initCodeWrapper(); this.initDiagramCopyBtn(); this.initTable(); - this.initMermaid(); this.initEcharts(); this.initTypeit(); this.initMapbox(); @@ -1176,6 +1109,7 @@ class FixIt { this.initTocListener(); this.initPangu(); this.initMathJax(); + window.FixItMermaid?.init?.(); this.util.forEach(document.querySelectorAll('.encrypted-hidden'), ($element) => { $element.classList.replace('encrypted-hidden', 'decrypted-shown'); }); @@ -1187,12 +1121,12 @@ class FixIt { this.initCodeWrapper(); this.initDiagramCopyBtn(); this.initTable($content); - this.initMermaid(); this.initEcharts(); this.initTypeit($content); this.initMapbox(); this.initPangu(); this.initMathJax(); + window.FixItMermaid?.init?.(); this.util.forEach($content.querySelectorAll('.encrypted-hidden'), ($element) => { $element.classList.replace('encrypted-hidden', 'decrypted-shown'); }); @@ -1389,7 +1323,6 @@ class FixIt { this.initCodeWrapper(); this.initDiagramCopyBtn(); this.initTable(); - this.initMermaid(); this.initEcharts(); this.initTypeit(); this.initMapbox(); diff --git a/layouts/_partials/assets.html b/layouts/_partials/assets.html index 359af760..20e7e3a0 100644 --- a/layouts/_partials/assets.html +++ b/layouts/_partials/assets.html @@ -129,13 +129,6 @@ {{- /* mermaid */ -}} {{- if .Store.Get "hasMermaid" -}} - {{- $mermaid := .Param "mermaid" -}} - {{- $config = dict - "themes" $mermaid.themes - "securityLevel" $mermaid.securityLevel - "look" $mermaid.look - "fontFamily" $mermaid.fontFamily - | dict "mermaid" | merge $config -}} {{- $options := dict "Source" "js/lib/mermaid.js" "Template" "js/lib/mermaid.min.js" -}} {{- $options = dict "Context" . "Minify" true "Fingerprint" $fingerprint "Attr" `type="module"` | merge $options -}} {{- $options | dict "Page" $ "Data" | partial "store/script.html" -}} diff --git a/layouts/_partials/plugin/mermaid.html b/layouts/_partials/plugin/mermaid.html index a5dcd9ac..b7be8510 100644 --- a/layouts/_partials/plugin/mermaid.html +++ b/layouts/_partials/plugin/mermaid.html @@ -1,7 +1,9 @@ {{- /* Mermaid support for code fences extended and shortcodes. */ -}}
{{ .Inner | safeHTML }}
- {{ .Inner | safeHTML }}
+ {{ .Inner | safeHTML }}
+ {{ .Inner | safeHTML }}
+ {{- dict "Label" "Copy Mermaid code" | partial "plugin/diagram-copy-btn.html" -}}
+ {{ .Inner | safeHTML }}