mirror of
https://github.com/hugo-fixit/FixIt.git
synced 2026-08-24 15:28:57 +00:00
refactor(mermaid): performance optimization and support pan and zoom etc. (#737)
* perf(mermaid): optimize rendering by deferring non-critical tasks and enhancing viewport observation * feat(mermaid): add panzoom and download svg actions for mermaid * feat(mermaid): enhance diagram controls and add fullscreen action for code tabs * feat(i18n): update translation keys for diagram and tab actions in multiple languages * fix(mermaid): move mermaid-neutral inside diagram-container in wrapper mode Agent-Logs-Url: https://github.com/hugo-fixit/FixIt/sessions/e79e837e-be0b-445c-8bd3-8c03cdce2651 Co-authored-by: Lruihao <33419593+Lruihao@users.noreply.github.com> * fix(mermaid): update download filename for SVG export in diagram controls * refactor(mermaid): refactor rendering logic and enhance task queue for improved performance --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -85,7 +85,6 @@ FixIt/
|
||||
|
||||
```scss
|
||||
@include border-radius;
|
||||
@include transition(all 0.2s ease);
|
||||
```
|
||||
|
||||
### 代码质量要求
|
||||
|
||||
Vendored
+2
-1
@@ -3,6 +3,7 @@
|
||||
"budparr.language-hugo-vscode",
|
||||
"kaellarkin.hugo-shortcode-syntax",
|
||||
"lruihao.hugo-partials-refs",
|
||||
"tamasfe.even-better-toml"
|
||||
"tamasfe.even-better-toml",
|
||||
"casualjim.gotemplate"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -192,6 +192,175 @@ mode = 'classic'
|
||||
wrapper = true
|
||||
```
|
||||
|
||||
## Mermaid
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Start] --> B{Is it?}
|
||||
B -->|Yes| C[OK]
|
||||
C --> D[Rethink]
|
||||
D --> B
|
||||
B ---->|No| E[End]
|
||||
```
|
||||
|
||||
## ECharts
|
||||
|
||||
```echarts
|
||||
[title]
|
||||
text = "Summary Line Chart"
|
||||
top = "2%"
|
||||
left = "center"
|
||||
|
||||
[tooltip]
|
||||
trigger = "axis"
|
||||
|
||||
[legend]
|
||||
data = [
|
||||
"Email Marketing",
|
||||
"Affiliate Advertising",
|
||||
"Video Advertising",
|
||||
"Direct View",
|
||||
"Search Engine"
|
||||
]
|
||||
top = "10%"
|
||||
|
||||
[grid]
|
||||
left = "5%"
|
||||
right = "5%"
|
||||
bottom = "5%"
|
||||
top = "20%"
|
||||
containLabel = true
|
||||
|
||||
[toolbox]
|
||||
left = "left"
|
||||
[toolbox.feature]
|
||||
[toolbox.feature.saveAsImage]
|
||||
title = "Save as Image"
|
||||
|
||||
[xAxis]
|
||||
type = "category"
|
||||
boundaryGap = false
|
||||
data = [
|
||||
"Monday",
|
||||
"Tuesday",
|
||||
"Wednesday",
|
||||
"Thursday",
|
||||
"Friday",
|
||||
"Saturday",
|
||||
"Sunday"
|
||||
]
|
||||
|
||||
[yAxis]
|
||||
type = "value"
|
||||
|
||||
[[series]]
|
||||
name = "Email Marketing"
|
||||
type = "line"
|
||||
stack = "Total"
|
||||
data = [
|
||||
120.0,
|
||||
132.0,
|
||||
101.0,
|
||||
134.0,
|
||||
90.0,
|
||||
230.0,
|
||||
210.0
|
||||
]
|
||||
|
||||
[[series]]
|
||||
name = "Affiliate Advertising"
|
||||
type = "line"
|
||||
stack = "Total"
|
||||
data = [
|
||||
220.0,
|
||||
182.0,
|
||||
191.0,
|
||||
234.0,
|
||||
290.0,
|
||||
330.0,
|
||||
310.0
|
||||
]
|
||||
|
||||
[[series]]
|
||||
name = "Video Advertising"
|
||||
type = "line"
|
||||
stack = "Total"
|
||||
data = [
|
||||
150.0,
|
||||
232.0,
|
||||
201.0,
|
||||
154.0,
|
||||
190.0,
|
||||
330.0,
|
||||
410.0
|
||||
]
|
||||
|
||||
[[series]]
|
||||
name = "Direct View"
|
||||
type = "line"
|
||||
stack = "Total"
|
||||
data = [
|
||||
320.0,
|
||||
332.0,
|
||||
301.0,
|
||||
334.0,
|
||||
390.0,
|
||||
330.0,
|
||||
320.0
|
||||
]
|
||||
|
||||
[[series]]
|
||||
name = "Search Engine"
|
||||
type = "line"
|
||||
stack = "Total"
|
||||
data = [
|
||||
820.0,
|
||||
932.0,
|
||||
901.0,
|
||||
934.0,
|
||||
1290.0,
|
||||
1330.0,
|
||||
1320.0
|
||||
]
|
||||
```
|
||||
|
||||
## Timeline
|
||||
|
||||
```timeline
|
||||
events:
|
||||
- timestamp: 2024-07-11
|
||||
content: Success
|
||||
- timestamp: 2024-07-13
|
||||
content: Approved
|
||||
- timestamp: 2024-07-15
|
||||
content: Event start
|
||||
```
|
||||
|
||||
## JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "FixIt",
|
||||
"version": "0.4.0",
|
||||
"description": "A clean, elegant but advanced blog theme for Hugo",
|
||||
"keywords": ["Hugo", "theme", "FixIt"]
|
||||
}
|
||||
```
|
||||
|
||||
## File Tree
|
||||
|
||||
{{< file-tree >}}
|
||||
- name: src
|
||||
type: dir
|
||||
children:
|
||||
- name: index.ts
|
||||
type: file
|
||||
- name: app.ts
|
||||
type: file
|
||||
- name: package.json
|
||||
type: file
|
||||
{{< /file-tree >}}
|
||||
|
||||
## Code block inside other blocks
|
||||
|
||||
> [!NOTE]+
|
||||
|
||||
@@ -13,6 +13,17 @@ tags:
|
||||
|
||||
The FixIt theme supports two ways to use Mermaid: code block syntax and shortcode syntax.
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
theme: 'forest'
|
||||
---
|
||||
graph TD
|
||||
a --> b
|
||||
```
|
||||
|
||||
<!--more-->
|
||||
|
||||
## Flowchart
|
||||
|
||||
```mermaid
|
||||
@@ -24,8 +35,6 @@ flowchart TD
|
||||
B ---->|No| E[End]
|
||||
```
|
||||
|
||||
<!--more-->
|
||||
|
||||
## Sequence Diagram
|
||||
|
||||
```mermaid
|
||||
|
||||
@@ -63,6 +63,8 @@ libFiles:
|
||||
paceJS: pace-js@1.2.4
|
||||
# pangu.js@4.0.7 https://github.com/vinta/pangu.js
|
||||
panguJS: pangu@4.0.7/dist/browser/pangu.min.js
|
||||
# @panzoom/panzoom@4.6.2 https://github.com/timmywil/panzoom
|
||||
panzoomJS: '@panzoom/panzoom@4.6.2/dist/panzoom.min.js'
|
||||
# sharer.js@0.5.1 https://github.com/ellisonleao/sharer.js
|
||||
sharerJS: sharer.js@0.5.1/sharer.min.js
|
||||
# @github/tab-container-element@4.8.2 https://github.com/github/tab-container-element
|
||||
|
||||
@@ -63,6 +63,8 @@ libFiles:
|
||||
paceJS: pace-js@1.2.4
|
||||
# pangu.js@4.0.7 https://github.com/vinta/pangu.js
|
||||
panguJS: pangu@4.0.7/dist/browser/pangu.min.js
|
||||
# @panzoom/panzoom@4.6.2 https://github.com/timmywil/panzoom
|
||||
panzoomJS: '@panzoom/panzoom@4.6.2/dist/panzoom.min.js'
|
||||
# sharer.js@0.5.1 https://github.com/ellisonleao/sharer.js
|
||||
sharerJS: sharer.js@0.5.1/sharer.min.js
|
||||
# @github/tab-container-element@4.8.2 https://github.com/github/tab-container-element
|
||||
|
||||
+653
-86
@@ -1,6 +1,7 @@
|
||||
{{- /* Page level params is not supported */ -}}
|
||||
{{- $mermaid := .Site.Params.mermaid -}}
|
||||
{{- $mermaidCDN := $mermaid.cdn | default "https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.esm.min.mjs" -}}
|
||||
{{- $wrapped := $mermaid.wrapper | default true -}}
|
||||
|
||||
import mermaid from "{{ $mermaidCDN }}"
|
||||
{{- with $mermaid.zenuml }}
|
||||
@@ -16,109 +17,675 @@ await mermaid.registerExternalDiagrams([zenuml])
|
||||
{{- end }}
|
||||
const loaders = []
|
||||
for(const item of {{ $loadersArr }}) {
|
||||
if(Array.isArray(item)) {
|
||||
loaders.push(...item)
|
||||
} else {
|
||||
loaders.push(item)
|
||||
}
|
||||
if(Array.isArray(item)) {
|
||||
loaders.push(...item)
|
||||
} else {
|
||||
loaders.push(item)
|
||||
}
|
||||
}
|
||||
mermaid.registerLayoutLoaders(loaders)
|
||||
mermaid.startOnLoad = false;
|
||||
const mermaidConfig = {{ $mermaid | jsonify }}
|
||||
let processing = false
|
||||
let delayTask = null
|
||||
const config = {{ $mermaid | jsonify }}
|
||||
let tabContainerEventBound = false
|
||||
let mermaidThemeRerenderBound = false
|
||||
let mermaidContainerObserver = null
|
||||
let mermaidRenderLock = Promise.resolve()
|
||||
let mermaidConfigKey = ''
|
||||
let mermaidIdSeq = 0
|
||||
{{- if $wrapped }}
|
||||
let panzoomInstances = null
|
||||
let panzoomWheelHosts = null
|
||||
let panzoomEventBound = false
|
||||
let mermaidThemeSyncBound = false
|
||||
let mermaidPanzoomGroups = null
|
||||
{{- end }}
|
||||
|
||||
/**
|
||||
* Get current/next theme
|
||||
* @param {Boolean} [next] whether to get the next theme
|
||||
* @returns
|
||||
* Waits for the next animation frame.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
function getTheme(next) {
|
||||
const darkMode = document.documentElement.dataset.theme === 'dark'
|
||||
return (next ? !darkMode : darkMode)
|
||||
? mermaidConfig.themes[1]
|
||||
: mermaidConfig.themes[0]
|
||||
function nextFrame() {
|
||||
return new Promise((resolve) => requestAnimationFrame(resolve))
|
||||
}
|
||||
|
||||
{{- if $wrapped }}
|
||||
/**
|
||||
* Gets (and initializes) the panzoom shared state for a Mermaid diagram container.
|
||||
*
|
||||
* The group is used to keep zoom/pan transform in sync across theme layers
|
||||
* (e.g. `.mermaid` vs `.mermaid-dark`) within the same `.diagram-container`.
|
||||
*
|
||||
* @param {SVGElement} svg
|
||||
*/
|
||||
function getMermaidPanzoomGroup(svg) {
|
||||
if (!svg?.closest?.('.diagram-view')) return null
|
||||
const container = svg?.closest?.('.diagram-container')
|
||||
if (!container) return null
|
||||
if (!mermaidPanzoomGroups) mermaidPanzoomGroups = new WeakMap()
|
||||
let group = mermaidPanzoomGroups.get(container)
|
||||
if (!group) {
|
||||
group = { container, transform: null, svgs: new Set(), lastSvg: null }
|
||||
mermaidPanzoomGroups.set(container, group)
|
||||
}
|
||||
const svgs = container.querySelectorAll('.mermaid svg, .mermaid-dark svg')
|
||||
svgs.forEach((s) => group.svgs.add(s))
|
||||
return group
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* Applies a saved pan/zoom transform via CSS transform.
|
||||
* @param {SVGElement} svg
|
||||
* @param {any} transform
|
||||
* @param {number} transform.x
|
||||
* @param {number} transform.y
|
||||
* @param {number} transform.scale
|
||||
*/
|
||||
async function loadMermaid({ theme, darkMode, selector }) {
|
||||
if (processing) {
|
||||
console.warn('Mermaid is still processing, delaying the reload.')
|
||||
delayTask = () => loadMermaid({ theme, darkMode, selector })
|
||||
function applyPanzoomTransformToSvg(svg, transform) {
|
||||
if (!svg || !transform) return
|
||||
svg.style.transform = `scale(${transform.scale}) translate(${transform.x}px, ${transform.y}px)`
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a saved pan/zoom transform via Panzoom instance methods.
|
||||
* @param {any} instance
|
||||
* @param {any} transform
|
||||
* @param {number} transform.x
|
||||
* @param {number} transform.y
|
||||
* @param {number} transform.scale
|
||||
*/
|
||||
function applyPanzoomTransform(instance, transform) {
|
||||
if (!instance || !transform) return
|
||||
try { instance.zoom(transform.scale, { animate: false, force: true }) } catch {}
|
||||
try { instance.pan(transform.x, transform.y, { animate: false, force: true }) } catch {}
|
||||
}
|
||||
{{- end }}
|
||||
|
||||
/**
|
||||
* Schedule task at idle time to avoid blocking critical rendering.
|
||||
* @param {Function} task
|
||||
* @param {number} [timeout=0] - Timeout in milliseconds for the task to run.
|
||||
*/
|
||||
function runWhenIdle(task, timeout = 0) {
|
||||
if (typeof window.requestIdleCallback === 'function') {
|
||||
window.requestIdleCallback(task, { timeout })
|
||||
return
|
||||
}
|
||||
const isDarkMode = darkMode ?? (document.documentElement.dataset.theme === 'dark')
|
||||
const currentTheme = theme ?? getTheme()
|
||||
const querySelector = selector ?? (isDarkMode ? '.mermaid-dark' : '.mermaid')
|
||||
const nodes = Array.from(document.querySelectorAll(querySelector))
|
||||
.filter((node) => {
|
||||
const p = node.closest('.tab-panel')
|
||||
if (!p) return true
|
||||
const style = getComputedStyle(p)
|
||||
return style.height !== 'auto' || style.width !== 'auto'
|
||||
})
|
||||
|
||||
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,
|
||||
layout: mermaidConfig.layout,
|
||||
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');
|
||||
}
|
||||
window.setTimeout(task, 80)
|
||||
}
|
||||
|
||||
async function initMermaid() {
|
||||
const mermaidElements = document.querySelectorAll('.mermaid, .mermaid-dark, .mermaid-neutral')
|
||||
/**
|
||||
* Checks whether an element is in the viewport (vertically).
|
||||
* @param {Element} node
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isNodeInViewport(node) {
|
||||
const rect = node.getBoundingClientRect()
|
||||
return rect.top < window.innerHeight && rect.bottom > 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a node is currently visible and should be rendered first.
|
||||
* @param {Element} node
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isNodeVisuallyActive(node) {
|
||||
if (!isRenderContextActive(node)) return false
|
||||
const nodeStyle = getComputedStyle(node)
|
||||
if (nodeStyle.display === 'none' || nodeStyle.visibility === 'hidden') return false
|
||||
const rect = node.getBoundingClientRect()
|
||||
return rect.width > 0 && rect.height > 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether an element is inside an active renderable context.
|
||||
*
|
||||
* This is stricter than "is in the DOM": it excludes content hidden by
|
||||
* tab panels, diagram/code toggles, or common hidden containers.
|
||||
*
|
||||
* @param {Element} el
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isRenderContextActive(el) {
|
||||
if (!el?.isConnected) return false
|
||||
if (el.closest?.('[hidden], .d-none, [aria-hidden="true"]')) return false
|
||||
const diagramView = el.closest?.('.diagram-view')
|
||||
if (diagramView?.closest?.('.diagram-tabs') && !diagramView.classList.contains('active')) return false
|
||||
const panel = el.closest?.('.tab-panel')
|
||||
if (panel?.hidden) return false
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the current effective theme is dark.
|
||||
* In auto mode, this follows the system color scheme preference.
|
||||
* @returns {boolean} Whether dark mode is currently active.
|
||||
*/
|
||||
function isDarkMode() {
|
||||
const themeMode = document.documentElement.dataset.themeMode || 'auto';
|
||||
return themeMode === 'auto'
|
||||
? window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
: themeMode === 'dark';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets Mermaid theme name for the current effective color scheme.
|
||||
* `.mermaid` uses themes[0] (default), `.mermaid-dark` uses themes[1] (dark).
|
||||
* @returns {string}
|
||||
*/
|
||||
function getTheme() {
|
||||
return isDarkMode() ? config.themes[1] : config.themes[0]
|
||||
}
|
||||
|
||||
/**
|
||||
* Mermaid uses global runtime config; `initialize()` changes subsequent `render()` behavior.
|
||||
* This helper serializes all render work to avoid concurrent initialize/render cross-talk.
|
||||
* @param {Function} task
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
function withMermaidLock(task) {
|
||||
mermaidRenderLock = mermaidRenderLock.then(task, task)
|
||||
return mermaidRenderLock
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes Mermaid only when (theme, darkMode) changes to reduce work and keep it stable.
|
||||
* @param {string} theme
|
||||
* @param {boolean} darkMode
|
||||
*/
|
||||
function ensureMermaidInitialized(theme, darkMode) {
|
||||
const key = `${String(theme)}|${darkMode ? '1' : '0'}`
|
||||
if (key === mermaidConfigKey) return
|
||||
mermaidConfigKey = key
|
||||
mermaid.initialize({
|
||||
startOnLoad: false,
|
||||
darkMode,
|
||||
theme,
|
||||
securityLevel: config.securitylevel,
|
||||
look: config.look,
|
||||
layout: config.layout,
|
||||
fontFamily: config.fontfamily,
|
||||
altFontFamily: config.fontfamily
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a single Mermaid host element (`.mermaid`, `.mermaid-dark`, `.mermaid-neutral`).
|
||||
*
|
||||
* Notes:
|
||||
* - Source uses `textContent` to avoid HTML entities (e.g. `>`) breaking Mermaid parsing.
|
||||
* - Uses `data-processing` / `data-processed` as state flags.
|
||||
* - Emits `fixit:mermaid-rendered` to hook Panzoom and other post-render behaviors.
|
||||
*
|
||||
* @param {Element} el
|
||||
* @param {Object} options
|
||||
* @param {string} options.theme
|
||||
* @param {boolean} options.darkMode
|
||||
*/
|
||||
async function renderMermaidElement(el, { theme, darkMode } = {}) {
|
||||
if (!el || el.hasAttribute('data-processed') || el.hasAttribute('data-processing')) return
|
||||
if (!isRenderContextActive(el)) return
|
||||
const source = el.textContent.trim()
|
||||
if (!source) {
|
||||
el.setAttribute('data-processed', '')
|
||||
return
|
||||
}
|
||||
|
||||
await withMermaidLock(async () => {
|
||||
if (!el || el.hasAttribute('data-processed') || !isRenderContextActive(el)) return
|
||||
if (!el.hasAttribute('data-processing')) el.setAttribute('data-processing', '')
|
||||
ensureMermaidInitialized(theme, darkMode)
|
||||
const id = `fixit-mermaid-${++mermaidIdSeq}`
|
||||
try {
|
||||
const result = await mermaid.render(id, source)
|
||||
const svg = result?.svg || ''
|
||||
el.innerHTML = svg
|
||||
if (typeof result?.bindFunctions === 'function') {
|
||||
try { result.bindFunctions(el) } catch {}
|
||||
}
|
||||
const svgEl = el.querySelector('svg')
|
||||
if (svgEl && !svgEl.id) svgEl.id = id
|
||||
el.removeAttribute('data-processing')
|
||||
el.setAttribute('data-processed', '')
|
||||
const svgId = svgEl?.id || id
|
||||
if (svgId) {
|
||||
const group = svgEl ? getMermaidPanzoomGroup(svgEl) : null
|
||||
if (svgEl && group?.transform) applyPanzoomTransformToSvg(svgEl, group.transform)
|
||||
document.dispatchEvent(new CustomEvent('fixit:mermaid-rendered', { detail: { svgId } }))
|
||||
}
|
||||
} catch (e) {
|
||||
el.removeAttribute('data-processing')
|
||||
console.warn('FixIt Mermaid render failed:', e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function collectMermaidContainers(root = document) {
|
||||
return Array.from(root.querySelectorAll('.diagram-container'))
|
||||
.filter((container) => container && container.querySelector('.mermaid, .mermaid-dark, .mermaid-neutral'))
|
||||
}
|
||||
|
||||
/**
|
||||
* Core rule: render only when the active layer is visible and not rendered yet.
|
||||
* - "Active layer" means `.mermaid` in light mode or `.mermaid-dark` in dark mode.
|
||||
* - We treat presence of a rendered `<svg>` as "already rendered" even if flags are missing.
|
||||
*/
|
||||
async function renderActiveLayerInContainer(container) {
|
||||
if (!container) return
|
||||
const el = container.querySelector(isDarkMode() ? '.mermaid-dark' : '.mermaid')
|
||||
if (!el) return
|
||||
if (el.hasAttribute('data-processed') || el.querySelector('svg')) return
|
||||
if (!isNodeVisuallyActive(el)) return
|
||||
const darkMode = isDarkMode()
|
||||
await renderMermaidElement(el, { theme: getTheme(), darkMode })
|
||||
}
|
||||
|
||||
/**
|
||||
* Neutral theme is not part of the live light/dark switching UI.
|
||||
* Render it in idle time for print / fallback scenarios.
|
||||
*/
|
||||
function scheduleNeutralRender() {
|
||||
runWhenIdle(() => {
|
||||
const neutralNodes = Array.from(document.querySelectorAll('.mermaid-neutral'))
|
||||
.filter((el) => el && !el.hasAttribute('data-processed'))
|
||||
.filter(isRenderContextActive)
|
||||
neutralNodes.forEach((el) => {
|
||||
renderMermaidElement(el, { theme: 'neutral', darkMode: false })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy rendering via IntersectionObserver.
|
||||
* When a `.diagram-container` gets close to viewport, attempt to render its active layer.
|
||||
*/
|
||||
function bindMermaidIntersectionObserver() {
|
||||
if (mermaidContainerObserver) return
|
||||
mermaidContainerObserver = new IntersectionObserver((entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (!entry.isIntersecting) return
|
||||
const container = entry.target
|
||||
if (!container || !isRenderContextActive(container)) return
|
||||
renderActiveLayerInContainer(container)
|
||||
})
|
||||
}, {
|
||||
root: null,
|
||||
// 200px margin to allow partial visibility
|
||||
rootMargin: '200px 0px',
|
||||
threshold: 0.01,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers all Mermaid containers under root into the observer.
|
||||
* Idempotent via `data-mermaid-observed`.
|
||||
*/
|
||||
function observeMermaidContainers(root = document, refresh = false) {
|
||||
bindMermaidIntersectionObserver()
|
||||
const containers = collectMermaidContainers(root)
|
||||
containers.forEach((container) => {
|
||||
if (!container || container.hasAttribute('data-mermaid-observed')) return
|
||||
container.setAttribute('data-mermaid-observed', '')
|
||||
mermaidContainerObserver.observe(container)
|
||||
})
|
||||
if (!refresh) return
|
||||
containers.forEach((container) => {
|
||||
if (!container) return
|
||||
mermaidContainerObserver.unobserve(container)
|
||||
mermaidContainerObserver.observe(container)
|
||||
})
|
||||
}
|
||||
|
||||
{{- if $wrapped }}
|
||||
/**
|
||||
* Gets the currently visible SVG for a diagram block that contains multiple Mermaid layers.
|
||||
* @param {Element} root
|
||||
* @returns {SVGElement|null}
|
||||
*/
|
||||
function getActiveMermaidSvg(root) {
|
||||
const nodes = root?.querySelectorAll?.('.mermaid, .mermaid-dark, .mermaid-neutral')
|
||||
if (!nodes?.length) return null
|
||||
const visible = Array.from(nodes).find((el) => {
|
||||
const style = getComputedStyle(el)
|
||||
if (style.display === 'none' || style.visibility === 'hidden') return false
|
||||
const rect = el.getBoundingClientRect()
|
||||
return rect.width > 0 && rect.height > 0
|
||||
})
|
||||
return visible?.querySelector?.('svg') || null
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables pan/zoom for a Mermaid SVG inside `.diagram-view`.
|
||||
* Requires `window.Panzoom`.
|
||||
*
|
||||
* @param {SVGElement} svg
|
||||
* @returns {void}
|
||||
*/
|
||||
function bindMermaidPanzoom(svg) {
|
||||
if (!svg) return
|
||||
if (!svg.closest?.('.diagram-view')) return
|
||||
if (!svg.closest?.('.mermaid, .mermaid-dark')) return
|
||||
if (!panzoomInstances) panzoomInstances = new WeakMap()
|
||||
if (!panzoomWheelHosts) panzoomWheelHosts = new WeakSet()
|
||||
if (panzoomInstances.has(svg)) return
|
||||
if (typeof window.Panzoom !== 'function') return
|
||||
|
||||
const group = getMermaidPanzoomGroup(svg)
|
||||
|
||||
const panzoom = window.Panzoom(svg, {
|
||||
maxScale: 6,
|
||||
minScale: 0.2,
|
||||
step: 0.1,
|
||||
})
|
||||
panzoomInstances.set(svg, panzoom)
|
||||
|
||||
if (group?.transform) {
|
||||
applyPanzoomTransform(panzoom, group.transform)
|
||||
}
|
||||
|
||||
const host = svg.closest('.mermaid, .mermaid-dark')
|
||||
if (!host) return
|
||||
if (panzoomWheelHosts.has(host)) return
|
||||
panzoomWheelHosts.add(host)
|
||||
|
||||
host.addEventListener('pointerdown', () => {
|
||||
const currentSvg = host.querySelector('svg')
|
||||
if (group && currentSvg) group.lastSvg = currentSvg
|
||||
})
|
||||
|
||||
host.addEventListener('wheel', (event) => {
|
||||
if (!event.ctrlKey) return
|
||||
const currentSvg = host.querySelector('svg')
|
||||
const instance = currentSvg && panzoomInstances.get(currentSvg)
|
||||
if (!instance) return
|
||||
if (group && currentSvg) group.lastSvg = currentSvg
|
||||
event.preventDefault()
|
||||
instance.zoomWithWheel(event)
|
||||
}, { passive: false })
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes diagram tabs (`.diagram-tabs[data-diagram="mermaid"]`) controls:
|
||||
* - Diagram/Code tab switching
|
||||
* - Zoom/reset/download buttons wiring
|
||||
* - Ensures Mermaid renders when the diagram tab is active
|
||||
*/
|
||||
function initDiagramControls() {
|
||||
document.querySelectorAll('.diagram-tabs[data-diagram="mermaid"]:not([data-diagram-init])').forEach((tabs) => {
|
||||
const actions = tabs.querySelector('.tabs-actions')
|
||||
const diagramTabBtn = tabs.querySelector('.tab-item[data-tab="diagram"]')
|
||||
const codeTabBtn = tabs.querySelector('.tab-item[data-tab="code"]')
|
||||
const diagramBlock = tabs.querySelector('.diagram-view')
|
||||
const codeBlock = Array.from(tabs.querySelectorAll('.tabs-content > .code-block'))
|
||||
.find((block) => !block.classList.contains('diagram-view'))
|
||||
if (!actions || !diagramTabBtn || !codeTabBtn || !diagramBlock || !codeBlock) return
|
||||
|
||||
const zoomInBtn = tabs.querySelector('.diagram-zoom-in-btn')
|
||||
const zoomOutBtn = tabs.querySelector('.diagram-zoom-out-btn')
|
||||
const resetBtn = tabs.querySelector('.diagram-reset-btn')
|
||||
const downloadBtn = tabs.querySelector('.diagram-download-btn')
|
||||
tabs.dataset.diagramInit = 'true'
|
||||
|
||||
const diagramActionButtons = [zoomOutBtn, zoomInBtn, resetBtn, downloadBtn].filter(Boolean)
|
||||
|
||||
const movedActionRestore = new WeakMap()
|
||||
let codeActionCache = []
|
||||
|
||||
const moveToActions = (elements) => {
|
||||
elements.forEach((el) => {
|
||||
if (!el || el.parentElement === actions) return
|
||||
movedActionRestore.set(el, { parent: el.parentElement, next: el.nextSibling })
|
||||
if (pinnedFullscreenBtn && pinnedFullscreenBtn.parentElement === actions) {
|
||||
actions.insertBefore(el, pinnedFullscreenBtn)
|
||||
} else {
|
||||
actions.appendChild(el)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const restoreMoved = (elements) => {
|
||||
elements.forEach((el) => {
|
||||
const restore = movedActionRestore.get(el)
|
||||
if (!restore?.parent) return
|
||||
if (restore.next && restore.next.parentNode === restore.parent) {
|
||||
restore.parent.insertBefore(el, restore.next)
|
||||
} else {
|
||||
restore.parent.appendChild(el)
|
||||
}
|
||||
movedActionRestore.delete(el)
|
||||
})
|
||||
}
|
||||
|
||||
const pinnedFullscreenBtn = codeBlock.querySelector('.code-header .fullscreen-btn')
|
||||
if (pinnedFullscreenBtn && pinnedFullscreenBtn.parentElement !== actions) {
|
||||
actions.appendChild(pinnedFullscreenBtn)
|
||||
}
|
||||
|
||||
const collectCodeActions = () => {
|
||||
const codeHeader = codeBlock.querySelector('.code-header')
|
||||
if (codeHeader) {
|
||||
return Array.from(codeHeader.querySelectorAll('.action-btn'))
|
||||
.filter((btn) => btn !== pinnedFullscreenBtn)
|
||||
}
|
||||
return Array.from(codeBlock.querySelectorAll('.code-copy-btn'))
|
||||
}
|
||||
|
||||
const getCodeActions = () => {
|
||||
if (codeActionCache.length && codeActionCache.some((el) => el.isConnected)) return codeActionCache
|
||||
codeActionCache = collectCodeActions()
|
||||
return codeActionCache
|
||||
}
|
||||
|
||||
const setDiagramActionsVisible = (visible) => {
|
||||
diagramActionButtons.forEach((btn) => {
|
||||
btn.style.display = visible ? '' : 'none'
|
||||
})
|
||||
}
|
||||
|
||||
const switchTo = (target) => {
|
||||
const showDiagram = target === 'diagram'
|
||||
diagramTabBtn.classList.toggle('active', showDiagram)
|
||||
codeTabBtn.classList.toggle('active', !showDiagram)
|
||||
diagramBlock.classList.toggle('active', showDiagram)
|
||||
codeBlock.classList.toggle('active', !showDiagram)
|
||||
const codeActions = getCodeActions()
|
||||
if (showDiagram) {
|
||||
setDiagramActionsVisible(true)
|
||||
restoreMoved(codeActions)
|
||||
// observeMermaidContainers(tabs, true)
|
||||
} else {
|
||||
setDiagramActionsVisible(false)
|
||||
moveToActions(codeActions)
|
||||
}
|
||||
}
|
||||
|
||||
const resolvePanzoom = () => {
|
||||
const svg = getActiveMermaidSvg(diagramBlock)
|
||||
return svg && panzoomInstances ? panzoomInstances.get(svg) : null
|
||||
}
|
||||
|
||||
diagramTabBtn.addEventListener('click', () => switchTo('diagram'))
|
||||
codeTabBtn.addEventListener('click', () => switchTo('code'))
|
||||
|
||||
zoomInBtn?.addEventListener('click', () => {
|
||||
const panzoom = resolvePanzoom()
|
||||
if (!panzoom?.zoomIn) return
|
||||
try { panzoom.zoomIn({ animate: true }) } catch { panzoom.zoomIn() }
|
||||
})
|
||||
zoomOutBtn?.addEventListener('click', () => {
|
||||
const panzoom = resolvePanzoom()
|
||||
if (!panzoom?.zoomOut) return
|
||||
try { panzoom.zoomOut({ animate: true }) } catch { panzoom.zoomOut() }
|
||||
})
|
||||
resetBtn?.addEventListener('click', () => {
|
||||
const panzoom = resolvePanzoom()
|
||||
if (!panzoom?.reset) return
|
||||
try { panzoom.reset({ animate: true }) } catch { panzoom.reset() }
|
||||
})
|
||||
downloadBtn?.addEventListener('click', () => {
|
||||
const svg = getActiveMermaidSvg(diagramBlock)
|
||||
if (!svg) return
|
||||
const clonedSvg = svg.cloneNode(true)
|
||||
clonedSvg.style.removeProperty('transform')
|
||||
const xml = new XMLSerializer().serializeToString(clonedSvg)
|
||||
const blob = new Blob([xml], { type: 'image/svg+xml;charset=utf-8' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = diagramBlock.dataset.filename.split('.')[0]
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
URL.revokeObjectURL(url)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds a listener to attach Panzoom after Mermaid renders an SVG.
|
||||
* @returns {void}
|
||||
*/
|
||||
function bindRenderedPanzoom() {
|
||||
if (panzoomEventBound) return
|
||||
panzoomEventBound = true
|
||||
document.addEventListener('fixit:mermaid-rendered', (event) => {
|
||||
const svgId = event?.detail?.svgId
|
||||
if (!svgId) return
|
||||
bindMermaidPanzoom(document.getElementById(svgId))
|
||||
})
|
||||
}
|
||||
{{- end }}
|
||||
|
||||
/**
|
||||
* Binds a listener so Mermaid diagrams inside a tab panel render after tab switches.
|
||||
* @returns {void}
|
||||
*/
|
||||
function bindTabContainerChanged() {
|
||||
if (tabContainerEventBound) return
|
||||
tabContainerEventBound = true
|
||||
document.addEventListener('tab-container-changed', async (event) => {
|
||||
const panel = event?.panel || event?.detail?.relatedTarget
|
||||
if (!panel) return
|
||||
// Wait for layout/visibility to settle after tab switch before measuring visibility.
|
||||
await nextFrame()
|
||||
await nextFrame()
|
||||
observeMermaidContainers(panel, true)
|
||||
})
|
||||
}
|
||||
|
||||
{{- if $wrapped }}
|
||||
/**
|
||||
* Binds theme switch synchronization for Panzoom transforms across Mermaid layers.
|
||||
* @returns {void}
|
||||
*/
|
||||
function bindThemeSync() {
|
||||
if (mermaidThemeSyncBound) return
|
||||
mermaidThemeSyncBound = true
|
||||
|
||||
const getActivePanzoomSvg = (container) => {
|
||||
const nodes = container?.querySelectorAll?.('.mermaid, .mermaid-dark')
|
||||
if (!nodes?.length) return null
|
||||
const visible = Array.from(nodes).find((el) => {
|
||||
const style = getComputedStyle(el)
|
||||
if (style.display === 'none' || style.visibility === 'hidden') return false
|
||||
const rect = el.getBoundingClientRect()
|
||||
return rect.width > 0 && rect.height > 0
|
||||
})
|
||||
return visible?.querySelector?.('svg') || null
|
||||
}
|
||||
|
||||
const safeGetTransformFromSvg = (svg) => {
|
||||
const instance = svg && panzoomInstances?.get?.(svg)
|
||||
if (instance?.getPan && instance?.getScale) {
|
||||
try {
|
||||
const pan = instance.getPan()
|
||||
const scale = instance.getScale()
|
||||
if (pan && typeof scale === 'number') return { x: pan.x, y: pan.y, scale }
|
||||
} catch {}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const resolveSourceSvg = (container) => {
|
||||
const active = getActivePanzoomSvg(container)
|
||||
const nowDark = isDarkMode()
|
||||
const fallback = nowDark
|
||||
? container.querySelector('.mermaid svg')
|
||||
: container.querySelector('.mermaid-dark svg')
|
||||
const group = active ? getMermaidPanzoomGroup(active) : null
|
||||
const last = group?.lastSvg
|
||||
if (last?.isConnected) return last
|
||||
return fallback || active
|
||||
}
|
||||
|
||||
const sync = () => {
|
||||
if (!mermaidPanzoomGroups) return
|
||||
document.querySelectorAll('.diagram-view .diagram-container').forEach((container) => {
|
||||
const currentActive = getActivePanzoomSvg(container)
|
||||
if (!currentActive) return
|
||||
const group = getMermaidPanzoomGroup(currentActive)
|
||||
if (!group) return
|
||||
|
||||
const sourceSvg = resolveSourceSvg(container)
|
||||
const sourceTransform = safeGetTransformFromSvg(sourceSvg)
|
||||
if (sourceTransform) group.transform = sourceTransform
|
||||
|
||||
if (!group.transform) return
|
||||
|
||||
const instance = panzoomInstances?.get?.(currentActive)
|
||||
if (instance) {
|
||||
applyPanzoomTransform(instance, group.transform)
|
||||
} else {
|
||||
applyPanzoomTransformToSvg(currentActive, group.transform)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (window.fixit?.switchThemeEventSet?.add) {
|
||||
window.fixit.switchThemeEventSet.add(async () => {
|
||||
await nextFrame()
|
||||
await nextFrame()
|
||||
sync()
|
||||
})
|
||||
}
|
||||
}
|
||||
{{- end }}
|
||||
|
||||
/**
|
||||
* Initializes Mermaid rendering, events, lazy loading and diagram controls.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function init() {
|
||||
const mermaidElements = document.querySelectorAll('.mermaid, .mermaid-dark, .mermaid-neutral')
|
||||
if (!mermaidElements.length) return
|
||||
console.log(
|
||||
`%c💫 FixIt Mermaid`,
|
||||
'color: #FF3670; font-weight: bold; font-size: 16px; text-shadow: 1px 1px 2px rgba(0,0,0,0.1);',
|
||||
)
|
||||
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' })
|
||||
console.log(
|
||||
`%c💫 FixIt Mermaid`,
|
||||
'color: #FF3670; font-weight: bold; font-size: 16px; text-shadow: 1px 1px 2px rgba(0,0,0,0.1);',
|
||||
)
|
||||
// Core: observe containers and render only when they become visible.
|
||||
observeMermaidContainers()
|
||||
// Neutral is non-critical, defer to idle time.
|
||||
scheduleNeutralRender()
|
||||
|
||||
bindTabContainerChanged()
|
||||
{{- if $wrapped }}
|
||||
initDiagramControls()
|
||||
bindRenderedPanzoom()
|
||||
bindThemeSync()
|
||||
{{- end }}
|
||||
|
||||
if (!mermaidThemeRerenderBound && window.fixit?.switchThemeEventSet?.add) {
|
||||
mermaidThemeRerenderBound = true
|
||||
window.fixit.switchThemeEventSet.add(async () => {
|
||||
await nextFrame()
|
||||
await nextFrame()
|
||||
// Theme switch doesn't change container intersection, so refresh IO to trigger callbacks.
|
||||
observeMermaidContainers(document, true)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
window.FixItMermaid = {
|
||||
config: mermaidConfig,
|
||||
load: loadMermaid,
|
||||
init: initMermaid,
|
||||
config,
|
||||
init,
|
||||
}
|
||||
window.mermaid = mermaid
|
||||
|
||||
+45
-25
@@ -606,47 +606,64 @@ class FixIt {
|
||||
}, false);
|
||||
}
|
||||
|
||||
_getCodeFullscreenTarget(codeBlock) {
|
||||
return codeBlock.closest('.code-tabs') || codeBlock;
|
||||
}
|
||||
|
||||
_setCodeFullscreenState(codeBlock, show) {
|
||||
const fullscreenBtn = codeBlock.querySelector('.code-header .fullscreen-btn');
|
||||
const hasExpandBtn = !!codeBlock.querySelector('.code-expand-btn');
|
||||
if (show && hasExpandBtn) {
|
||||
const target = this._getCodeFullscreenTarget(codeBlock);
|
||||
const expandBtn = codeBlock.querySelector('.code-expand-btn');
|
||||
|
||||
if (show && expandBtn) {
|
||||
codeBlock.dataset.fullscreenExpanded = codeBlock.classList.contains('is-expanded') ? 'true' : 'false';
|
||||
codeBlock.classList.add('is-expanded');
|
||||
}
|
||||
if (!show && codeBlock.classList.contains('is-fullscreen')) {
|
||||
codeBlock.classList.add('instant-height');
|
||||
if (hasExpandBtn && codeBlock.dataset.fullscreenExpanded === 'false') {
|
||||
|
||||
if (!show && target.classList.contains('is-fullscreen')) {
|
||||
target.classList.add('instant-height');
|
||||
window.requestAnimationFrame(() => target.classList.remove('instant-height'));
|
||||
|
||||
if (expandBtn && codeBlock.dataset.fullscreenExpanded === 'false') {
|
||||
codeBlock.classList.remove('is-expanded');
|
||||
}
|
||||
delete codeBlock.dataset.fullscreenExpanded;
|
||||
window.requestAnimationFrame(() => {
|
||||
codeBlock.classList.remove('instant-height');
|
||||
});
|
||||
}
|
||||
codeBlock.classList.toggle('is-fullscreen', show);
|
||||
fullscreenBtn?.toggleAttribute('data-fullscreen', show);
|
||||
const hasFullscreenCode = !!document.querySelector('.code-block.highlight.is-fullscreen');
|
||||
document.documentElement.style.overflow = hasFullscreenCode ? 'hidden' : '';
|
||||
|
||||
// update button tooltip
|
||||
target.classList.toggle('is-fullscreen', show);
|
||||
const btn = target.querySelector('.tabs-actions .fullscreen-btn')
|
||||
|| codeBlock.querySelector('.code-header .fullscreen-btn');
|
||||
if (!btn) return;
|
||||
const exitTitle = btn.dataset.exitTitle || btn.getAttribute('data-exit-title') || btn.title;
|
||||
const originalTitle = btn.dataset.ctOriginalTitle || btn.dataset.ctTitle || btn.title;
|
||||
btn.dataset.ctOriginalTitle = originalTitle;
|
||||
btn.dataset.ctTitle = show ? exitTitle : originalTitle;
|
||||
const instance = window.CellTooltip.getOrCreateInstance(btn);
|
||||
instance.hide();
|
||||
}
|
||||
|
||||
closeCodeFullscreen() {
|
||||
const $activeCodeBlock = document.querySelector('.code-block.highlight.is-fullscreen');
|
||||
if (!$activeCodeBlock) return;
|
||||
this._setCodeFullscreenState($activeCodeBlock, false);
|
||||
const $activeTabs = document.querySelector('.code-tabs.is-fullscreen');
|
||||
if ($activeTabs) {
|
||||
const $activeBlock = $activeTabs.querySelector('.code-block.active') || $activeTabs.querySelector('.code-block');
|
||||
if ($activeBlock) this._setCodeFullscreenState($activeBlock, false);
|
||||
return;
|
||||
}
|
||||
const $activeBlock = document.querySelector('.code-block.highlight.is-fullscreen');
|
||||
if ($activeBlock) this._setCodeFullscreenState($activeBlock, false);
|
||||
}
|
||||
|
||||
initFullscreenCode(codeBlock) {
|
||||
const fullscreenBtn = codeBlock.querySelector('.code-header .fullscreen-btn');
|
||||
if (!fullscreenBtn) return;
|
||||
fullscreenBtn.addEventListener('click', () => {
|
||||
const isFullscreen = codeBlock.classList.contains('is-fullscreen');
|
||||
if (isFullscreen) {
|
||||
this._setCodeFullscreenState(codeBlock, false);
|
||||
return;
|
||||
const target = this._getCodeFullscreenTarget(codeBlock);
|
||||
const show = !target.classList.contains('is-fullscreen');
|
||||
if (show) {
|
||||
this.closeCodeFullscreen();
|
||||
codeBlock.classList.remove('is-collapsed');
|
||||
}
|
||||
this.closeCodeFullscreen();
|
||||
codeBlock.classList.remove('is-collapsed');
|
||||
this._setCodeFullscreenState(codeBlock, true);
|
||||
this._setCodeFullscreenState(codeBlock, show);
|
||||
}, false);
|
||||
if (!this._codeFullscreenOnEsc) {
|
||||
this._codeFullscreenOnEsc = (event) => {
|
||||
@@ -1567,7 +1584,6 @@ class FixIt {
|
||||
initTabEvents(target = document) {
|
||||
target.addEventListener('tab-container-changed', () => {
|
||||
FileTree.updateLineHeight(target);
|
||||
window.FixItMermaid?.init?.();
|
||||
}, false);
|
||||
}
|
||||
|
||||
@@ -1612,7 +1628,9 @@ class FixIt {
|
||||
placement: 'right',
|
||||
});
|
||||
// code block action buttons tooltip
|
||||
window.CellTooltip.initAll('.action-btn[title]');
|
||||
window.CellTooltip.initAll('.action-btn[title]', {
|
||||
placement: 'bottom',
|
||||
});
|
||||
// footnote refs tooltip
|
||||
this.initFootnotes();
|
||||
}
|
||||
@@ -1922,6 +1940,8 @@ class FixIt {
|
||||
if (printConfig.expandCode) {
|
||||
// revert code tabs to code blocks for better printing support
|
||||
forEach($content.querySelectorAll('.code-tabs'), ($codeTabs) => {
|
||||
// skip diagrams
|
||||
if ($codeTabs.dataset.diagram) return;
|
||||
// restore action buttons to the active tab's code-header before reverting
|
||||
const $actions = $codeTabs.querySelector('.tabs-actions');
|
||||
const $activeBlock = $codeTabs.querySelector('.code-block.active');
|
||||
|
||||
Vendored
+16
File diff suppressed because one or more lines are too long
@@ -59,6 +59,14 @@
|
||||
@include border-radius(full);
|
||||
}
|
||||
|
||||
.is-fullscreen {
|
||||
.fa-expand {
|
||||
--fa: "\f066";
|
||||
font-weight: 900;
|
||||
color: fi-var(global-link-hover-color);
|
||||
}
|
||||
}
|
||||
|
||||
// auto adapt to dark mode by inverting the color and hue
|
||||
.auto-dark-mode {
|
||||
@include dark-mode {
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
container-type: scroll-state;
|
||||
container-name: scroller;
|
||||
|
||||
&:has(dialog[open]) {
|
||||
&:has(dialog[open], .is-fullscreen) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
@@ -137,10 +137,6 @@ pre {
|
||||
@include box-shadow(hover);
|
||||
}
|
||||
|
||||
&.instant-height .code-wrapper {
|
||||
transition: height 0s !important;
|
||||
}
|
||||
|
||||
.code-wrapper {
|
||||
overflow: hidden;
|
||||
height: auto;
|
||||
@@ -256,12 +252,6 @@ pre {
|
||||
color: fi-var(global-link-hover-color);
|
||||
}
|
||||
|
||||
.fullscreen-btn[data-fullscreen] .fa-expand {
|
||||
--fa: "\f066";
|
||||
font-weight: 900;
|
||||
color: fi-var(global-link-hover-color);
|
||||
}
|
||||
|
||||
@each $type, $text in $code-type-map {
|
||||
&.language-#{to-lower-case($type)} {
|
||||
// e.g., --fi-code-type: "JavaScript";
|
||||
@@ -635,9 +625,9 @@ pre {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
translate: 0 -50%;
|
||||
width: 1px;
|
||||
height: 36%;
|
||||
height: fi-var(code-block-font-size);
|
||||
background-color: fi-var(global-border-color-weight);
|
||||
}
|
||||
|
||||
@@ -696,10 +686,6 @@ pre {
|
||||
font-weight: 900;
|
||||
color: fi-var(global-link-hover-color);
|
||||
}
|
||||
|
||||
&.fullscreen-btn {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -725,4 +711,43 @@ pre {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.is-fullscreen {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
margin: 0 !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
@include z-index(fixed, 1);
|
||||
@include border-radius(0);
|
||||
|
||||
.tabs-header {
|
||||
top: 0 !important;
|
||||
@include border-radius(0);
|
||||
}
|
||||
|
||||
.tabs-content {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
|
||||
.code-block.active {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.code-wrapper {
|
||||
overflow: auto;
|
||||
height: calc(100dvh - 1.4em - 0.8rem);
|
||||
}
|
||||
}
|
||||
|
||||
.code-expand-btn {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// prevent transition when code block height changed by fullscreen toggle
|
||||
.instant-height .code-wrapper {
|
||||
transition: height 0s !important;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
.mermaid,
|
||||
.mermaid-dark,
|
||||
.mermaid-neutral {
|
||||
@mixin mermaid-base {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
overflow: hidden !important;
|
||||
|
||||
&[data-processed] {
|
||||
text-align: center;
|
||||
background-color: transparent;
|
||||
|
||||
svg {
|
||||
display: block;
|
||||
margin-inline: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&:not([data-processed])::before {
|
||||
@@ -20,36 +23,87 @@
|
||||
}
|
||||
}
|
||||
|
||||
@mixin mermaid-hidden-layer {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@mixin mermaid-visible-layer {
|
||||
position: relative;
|
||||
inset: auto;
|
||||
visibility: visible;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
:is(.mermaid, .mermaid-dark, .mermaid-neutral) {
|
||||
@include mermaid-base;
|
||||
}
|
||||
|
||||
.diagram-tabs {
|
||||
.tabs-actions .fullscreen-btn {
|
||||
position: relative;
|
||||
margin-inline-start: 4px;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
width: 1px;
|
||||
translate: -2px -50%;
|
||||
height: fi-var(code-block-font-size);
|
||||
background-color: fi-var(global-border-color-weight);
|
||||
}
|
||||
}
|
||||
|
||||
.diagram-container {
|
||||
margin-block: 0;
|
||||
}
|
||||
|
||||
&.is-fullscreen {
|
||||
.diagram-container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:is(.mermaid, .mermaid-dark)[data-processed] {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:is(.mermaid-dark, .mermaid-neutral) {
|
||||
@include mermaid-hidden-layer;
|
||||
}
|
||||
|
||||
.mermaid {
|
||||
@include dark-mode {
|
||||
height: 0;
|
||||
padding: 0;
|
||||
@include mermaid-hidden-layer;
|
||||
}
|
||||
}
|
||||
|
||||
.mermaid-dark {
|
||||
height: 0;
|
||||
padding: 0;
|
||||
|
||||
@include dark-mode {
|
||||
height: auto;
|
||||
padding: 0.5rem 0.75rem;
|
||||
@include mermaid-visible-layer;
|
||||
}
|
||||
}
|
||||
|
||||
// for print view
|
||||
.mermaid-neutral {
|
||||
height: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@include media('print') {
|
||||
.diagram-tabs {
|
||||
border: none !important;
|
||||
background-color: transparent !important;
|
||||
|
||||
.tabs-header {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
.mermaid,
|
||||
.mermaid-dark {
|
||||
display: none !important;
|
||||
display: none;
|
||||
}
|
||||
.mermaid-neutral {
|
||||
height: auto;
|
||||
padding: 0.5rem 0.75rem;
|
||||
@include mermaid-visible-layer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -841,6 +841,12 @@ loop = false
|
||||
# FixIt 0.2.15 | NEW Mermaid config
|
||||
# See http://fixit.lruihao.cn/documentation/content-management/diagrams-support/mermaid/
|
||||
[params.mermaid]
|
||||
# FixIt 1.0.0 | NEW whether to enable the Mermaid wrapper
|
||||
# When enabled, Mermaid diagrams are wrapped with diagram tabs and actions:
|
||||
# - pan/zoom (drag & ctrl+wheel)
|
||||
# - reset view
|
||||
# - download as SVG
|
||||
wrapper = true
|
||||
# FixIt 0.4.0 | NEW Mermaid ESM module CDN source
|
||||
# default is https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.esm.min.mjs
|
||||
cdn = ""
|
||||
|
||||
+16
-1
@@ -105,8 +105,9 @@ toggleLineNumbers = "Zeilennummern umschalten"
|
||||
toggleLineWrap = "Zeilenumbruch umschalten"
|
||||
toggleCodeEditable = "Codeblock editierbar umschalten"
|
||||
downloadCode = "Code herunterladen"
|
||||
toggleCodeFullscreen = "Codeblock-Vollbild umschalten"
|
||||
expandCode = "Codeblock erweitern oder reduzieren"
|
||||
fullscreen = "Vollbild"
|
||||
exitFullscreen = "Vollbild verlassen"
|
||||
cookieconsentMessage = "Diese Website verwendet Cookies, um Ihre Erfahrung zu verbessern."
|
||||
cookieconsentDismiss = "Zustimmen"
|
||||
cookieconsentLink = "Erfahren Sie mehr"
|
||||
@@ -236,3 +237,17 @@ changed = "GEÄNDERT"
|
||||
deleted = "GELÖSCHT"
|
||||
deprecated = "VERALTET"
|
||||
# === shortcodes/version.html ===
|
||||
|
||||
# === diagram actions ===
|
||||
[diagram]
|
||||
zoomIn = "Vergrößern"
|
||||
zoomOut = "Verkleinern"
|
||||
reset = "Zurücksetzen"
|
||||
download = "SVG herunterladen"
|
||||
# === diagram actions ===
|
||||
|
||||
# === tabs related ===
|
||||
[tabs]
|
||||
diagram = "Diagramm"
|
||||
code = "Code"
|
||||
# === tabs related ===
|
||||
|
||||
+16
-1
@@ -105,8 +105,9 @@ toggleLineNumbers = "Toggle line numbers"
|
||||
toggleLineWrap = "Toggle code wrap"
|
||||
toggleCodeEditable = "Toggle code block editable"
|
||||
downloadCode = "Download code"
|
||||
toggleCodeFullscreen = "Toggle code block fullscreen"
|
||||
expandCode = "Expand or collapse code block"
|
||||
fullscreen = "Fullscreen"
|
||||
exitFullscreen = "Exit fullscreen"
|
||||
cookieconsentMessage = "This website uses Cookies to improve your experience."
|
||||
cookieconsentDismiss = "Got it!"
|
||||
cookieconsentLink = "Learn more"
|
||||
@@ -236,3 +237,17 @@ changed = "CHANGED"
|
||||
deleted = "DELETED"
|
||||
deprecated = "DEPRECATED"
|
||||
# === shortcodes/version.html ===
|
||||
|
||||
# === diagram actions ===
|
||||
[diagram]
|
||||
zoomIn = "Zoom in"
|
||||
zoomOut = "Zoom out"
|
||||
reset = "Reset"
|
||||
download = "Download SVG"
|
||||
# === diagram actions ===
|
||||
|
||||
# === tabs related ===
|
||||
[tabs]
|
||||
diagram = "Diagram"
|
||||
code = "Code"
|
||||
# === tabs related ===
|
||||
|
||||
+16
-1
@@ -105,8 +105,9 @@ toggleLineNumbers = "Alternar números de línea"
|
||||
toggleLineWrap = "Alternar ajuste de línea"
|
||||
toggleCodeEditable = "Alternar bloque de código editable"
|
||||
downloadCode = "Descargar código"
|
||||
toggleCodeFullscreen = "Alternar pantalla completa del bloque de código"
|
||||
expandCode = "Expandir o contraer bloque de código"
|
||||
fullscreen = "Pantalla completa"
|
||||
exitFullscreen = "Salir de pantalla completa"
|
||||
cookieconsentMessage = "Este sitio web utiliza Cookies para mejorar su experiencia."
|
||||
cookieconsentDismiss = "De acuerdo"
|
||||
cookieconsentLink = "Aprende más"
|
||||
@@ -236,3 +237,17 @@ changed = "MODIFICADO"
|
||||
deleted = "ELIMINADO"
|
||||
deprecated = "OBSOLETO"
|
||||
# === shortcodes/version.html ===
|
||||
|
||||
# === diagram actions ===
|
||||
[diagram]
|
||||
zoomIn = "Acercar"
|
||||
zoomOut = "Alejar"
|
||||
reset = "Restablecer"
|
||||
download = "Descargar SVG"
|
||||
# === diagram actions ===
|
||||
|
||||
# === tabs related ===
|
||||
[tabs]
|
||||
diagram = "Diagrama"
|
||||
code = "Código"
|
||||
# === tabs related ===
|
||||
|
||||
+16
-1
@@ -105,8 +105,9 @@ toggleLineNumbers = "Basculer les numéros de ligne"
|
||||
toggleLineWrap = "Basculer le retour à la ligne"
|
||||
toggleCodeEditable = "Basculer le bloc de code modifiable"
|
||||
downloadCode = "Télécharger le code"
|
||||
toggleCodeFullscreen = "Basculer le plein écran du bloc de code"
|
||||
expandCode = "Développer ou réduire le bloc de code"
|
||||
fullscreen = "Plein écran"
|
||||
exitFullscreen = "Quitter le plein écran"
|
||||
cookieconsentMessage = "Ce site Web utilise des Cookies pour améliorer votre expérience."
|
||||
cookieconsentDismiss = "J'ai compris !"
|
||||
cookieconsentLink = "En apprendre plus"
|
||||
@@ -236,3 +237,17 @@ changed = "MODIFIÉ"
|
||||
deleted = "SUPPRIMÉ"
|
||||
deprecated = "OBSOLÈTE"
|
||||
# === shortcodes/version.html ===
|
||||
|
||||
# === diagram actions ===
|
||||
[diagram]
|
||||
zoomIn = "Zoom avant"
|
||||
zoomOut = "Zoom arrière"
|
||||
reset = "Réinitialiser"
|
||||
download = "Télécharger le SVG"
|
||||
# === diagram actions ===
|
||||
|
||||
# === tabs related ===
|
||||
[tabs]
|
||||
diagram = "Diagramme"
|
||||
code = "Code"
|
||||
# === tabs related ===
|
||||
|
||||
+16
-1
@@ -106,8 +106,9 @@ toggleLineNumbers = "लाइन नंबर टॉगल करें"
|
||||
toggleLineWrap = "लाइन रैप टॉगल करें"
|
||||
toggleCodeEditable = "कोड ब्लॉक संपादन योग्य टॉगल करें"
|
||||
downloadCode = "कोड डाउनलोड करें"
|
||||
toggleCodeFullscreen = "कोड ब्लॉक फ़ुलस्क्रीन टॉगल करें"
|
||||
expandCode = "कोड ब्लॉक का विस्तार या संकुचित करें"
|
||||
fullscreen = "पूर्ण स्क्रीन"
|
||||
exitFullscreen = "पूर्ण स्क्रीन से बाहर निकलें"
|
||||
cookieconsentMessage = "यह वेबसाइट आपके अनुभव को बेहतर बनाने के लिए कुकीज़ का उपयोग करती है।"
|
||||
cookieconsentDismiss = "समझ गया!"
|
||||
cookieconsentLink = "और अधिक जानें"
|
||||
@@ -237,3 +238,17 @@ changed = "बदला हुआ"
|
||||
deleted = "हटाए गए"
|
||||
deprecated = "अप्रचलित"
|
||||
# === shortcodes/version.html ===
|
||||
|
||||
# === diagram actions ===
|
||||
[diagram]
|
||||
zoomIn = "ज़ूम इन"
|
||||
zoomOut = "ज़ूम आउट"
|
||||
reset = "रीसेट"
|
||||
download = "SVG डाउनलोड करें"
|
||||
# === diagram actions ===
|
||||
|
||||
# === tabs related ===
|
||||
[tabs]
|
||||
diagram = "आरेख"
|
||||
code = "कोड"
|
||||
# === tabs related ===
|
||||
|
||||
+16
-1
@@ -105,8 +105,9 @@ toggleLineNumbers = "Attiva/disattiva i numeri di linea"
|
||||
toggleLineWrap = "Attiva/disattiva il ritorno a capo del codice"
|
||||
toggleCodeEditable = "Attiva/disattiva la modifica del blocco di codice"
|
||||
downloadCode = "Scarica codice"
|
||||
toggleCodeFullscreen = "Attiva/disattiva schermo intero del blocco di codice"
|
||||
expandCode = "Espandi o riduci il blocco di codice"
|
||||
fullscreen = "Schermo intero"
|
||||
exitFullscreen = "Esci da schermo intero"
|
||||
cookieconsentMessage = "Questo sito Web utilizza i Cookies per migliorare la tua esperienza."
|
||||
cookieconsentDismiss = "Essere d'accordo"
|
||||
cookieconsentLink = "Per saperne di più"
|
||||
@@ -236,3 +237,17 @@ changed = "CAMBIATO"
|
||||
deleted = "CANCELLATO"
|
||||
deprecated = "DEPRECATO"
|
||||
# === shortcodes/version.html ===
|
||||
|
||||
# === diagram actions ===
|
||||
[diagram]
|
||||
zoomIn = "Zoom avanti"
|
||||
zoomOut = "Zoom indietro"
|
||||
reset = "Reimposta"
|
||||
download = "Scarica SVG"
|
||||
# === diagram actions ===
|
||||
|
||||
# === tabs related ===
|
||||
[tabs]
|
||||
diagram = "Diagramma"
|
||||
code = "Codice"
|
||||
# === tabs related ===
|
||||
|
||||
+16
-1
@@ -103,8 +103,9 @@ toggleLineNumbers = "行番号の切り替え"
|
||||
toggleLineWrap = "自動折り返しの切り替え"
|
||||
toggleCodeEditable = "コードブロックの編集切り替え"
|
||||
downloadCode = "コードをダウンロード"
|
||||
toggleCodeFullscreen = "コードブロックの全画面表示を切り替え"
|
||||
expandCode = "コードブロックの展開/折りたたみ"
|
||||
fullscreen = "全画面"
|
||||
exitFullscreen = "全画面を終了"
|
||||
cookieconsentMessage = "このウェブサイトはクッキーを使用して体験を向上させます。"
|
||||
cookieconsentDismiss = "同意する"
|
||||
cookieconsentLink = "詳細はこちら"
|
||||
@@ -232,3 +233,17 @@ changed = "変更"
|
||||
deleted = "削除"
|
||||
deprecated = "非推奨"
|
||||
# === shortcodes/version.html ===
|
||||
|
||||
# === diagram actions ===
|
||||
[diagram]
|
||||
zoomIn = "拡大"
|
||||
zoomOut = "縮小"
|
||||
reset = "リセット"
|
||||
download = "SVG をダウンロード"
|
||||
# === diagram actions ===
|
||||
|
||||
# === tabs related ===
|
||||
[tabs]
|
||||
diagram = "図"
|
||||
code = "コード"
|
||||
# === tabs related ===
|
||||
|
||||
+16
-1
@@ -103,8 +103,9 @@ toggleLineNumbers = "줄 번호 전환"
|
||||
toggleLineWrap = "자동 줄 바꿈 전환"
|
||||
toggleCodeEditable = "코드 블록 편집 전환"
|
||||
downloadCode = "코드 다운로드"
|
||||
toggleCodeFullscreen = "코드 블록 전체 화면 전환"
|
||||
expandCode = "코드 블록 확장/축소"
|
||||
fullscreen = "전체 화면"
|
||||
exitFullscreen = "전체 화면 종료"
|
||||
cookieconsentMessage = "이 웹사이트는 쿠키를 사용하여 경험을 향상시킵니다."
|
||||
cookieconsentDismiss = "동의"
|
||||
cookieconsentLink = "자세히 알아보기"
|
||||
@@ -232,3 +233,17 @@ changed = "변경됨"
|
||||
deleted = "삭제됨"
|
||||
deprecated = "더 이상 사용되지 않음"
|
||||
# === shortcodes/version.html ===
|
||||
|
||||
# === diagram actions ===
|
||||
[diagram]
|
||||
zoomIn = "확대"
|
||||
zoomOut = "축소"
|
||||
reset = "재설정"
|
||||
download = "SVG 다운로드"
|
||||
# === diagram actions ===
|
||||
|
||||
# === tabs related ===
|
||||
[tabs]
|
||||
diagram = "다이어그램"
|
||||
code = "코드"
|
||||
# === tabs related ===
|
||||
|
||||
+16
-1
@@ -105,8 +105,9 @@ toggleLineNumbers = "Przełącz numery linii"
|
||||
toggleLineWrap = "Przełącz zawijanie linii"
|
||||
toggleCodeEditable = "Przełącz edytowalny blok kodu"
|
||||
downloadCode = "Pobierz kod"
|
||||
toggleCodeFullscreen = "Przełącz pełny ekran bloku kodu"
|
||||
expandCode = "Rozwiń lub zwiń blok kodu"
|
||||
fullscreen = "Pełny ekran"
|
||||
exitFullscreen = "Wyjdź z pełnego ekranu"
|
||||
cookieconsentMessage = "Ta strona korzysta z plików Cookies, aby poprawić komfort użytkowania."
|
||||
cookieconsentDismiss = "Zgodzić się"
|
||||
cookieconsentLink = "Ucz się więcej"
|
||||
@@ -236,3 +237,17 @@ changed = "Zmieniono"
|
||||
deleted = "Usunięte"
|
||||
deprecated = "Przestarzałe"
|
||||
# === shortcodes/version.html ===
|
||||
|
||||
# === diagram actions ===
|
||||
[diagram]
|
||||
zoomIn = "Powiększ"
|
||||
zoomOut = "Pomniejsz"
|
||||
reset = "Resetuj"
|
||||
download = "Pobierz SVG"
|
||||
# === diagram actions ===
|
||||
|
||||
# === tabs related ===
|
||||
[tabs]
|
||||
diagram = "Diagram"
|
||||
code = "Kod"
|
||||
# === tabs related ===
|
||||
|
||||
+16
-1
@@ -106,8 +106,9 @@ toggleLineNumbers = "Alternar números de linha"
|
||||
toggleLineWrap = "Alternar quebra de linha"
|
||||
toggleCodeEditable = "Alternar bloco de código editável"
|
||||
downloadCode = "Baixar código"
|
||||
toggleCodeFullscreen = "Alternar tela cheia do bloco de código"
|
||||
expandCode = "Expandir ou recolher bloco de código"
|
||||
fullscreen = "Tela cheia"
|
||||
exitFullscreen = "Sair da tela cheia"
|
||||
cookieconsentMessage = "Este site usa Cookies para melhorar sua experiência."
|
||||
cookieconsentDismiss = "Aceitar"
|
||||
cookieconsentLink = "Saber mais"
|
||||
@@ -237,3 +238,17 @@ changed = "ALTERADO"
|
||||
deleted = "EXCLUÍDO"
|
||||
deprecated = "OBSOLETO"
|
||||
# === shortcodes/version.html ===
|
||||
|
||||
# === diagram actions ===
|
||||
[diagram]
|
||||
zoomIn = "Ampliar"
|
||||
zoomOut = "Reduzir"
|
||||
reset = "Redefinir"
|
||||
download = "Baixar SVG"
|
||||
# === diagram actions ===
|
||||
|
||||
# === tabs related ===
|
||||
[tabs]
|
||||
diagram = "Diagrama"
|
||||
code = "Código"
|
||||
# === tabs related ===
|
||||
|
||||
+16
-1
@@ -105,8 +105,9 @@ toggleLineNumbers = "Comută numerele de linie"
|
||||
toggleLineWrap = "Comută înfășurarea liniei"
|
||||
toggleCodeEditable = "Comută blocul de cod editabil"
|
||||
downloadCode = "Descarcă codul"
|
||||
toggleCodeFullscreen = "Comută ecran complet pentru blocul de cod"
|
||||
expandCode = "Extinde sau restrânge blocul de cod"
|
||||
fullscreen = "Ecran complet"
|
||||
exitFullscreen = "Ieși din ecran complet"
|
||||
cookieconsentMessage = "Acest site web utilizează Cookies pentru a vă îmbunătăți experiența."
|
||||
cookieconsentDismiss = "De acord"
|
||||
cookieconsentLink = "Aflați mai multe"
|
||||
@@ -236,3 +237,17 @@ changed = "SCHIMBAT"
|
||||
deleted = "ȘTERS"
|
||||
deprecated = "DEPRECIAȚI"
|
||||
# === shortcodes/version.html ===
|
||||
|
||||
# === diagram actions ===
|
||||
[diagram]
|
||||
zoomIn = "Mărește"
|
||||
zoomOut = "Micșorează"
|
||||
reset = "Resetează"
|
||||
download = "Descarcă SVG"
|
||||
# === diagram actions ===
|
||||
|
||||
# === tabs related ===
|
||||
[tabs]
|
||||
diagram = "Diagramă"
|
||||
code = "Cod"
|
||||
# === tabs related ===
|
||||
|
||||
+16
-1
@@ -105,8 +105,9 @@ toggleLineNumbers = "Переключить номера строк"
|
||||
toggleLineWrap = "Переключить перенос строк"
|
||||
toggleCodeEditable = "Переключить редактируемый блок кода"
|
||||
downloadCode = "Скачать код"
|
||||
toggleCodeFullscreen = "Переключить полноэкранный режим блока кода"
|
||||
expandCode = "Развернуть или свернуть блок кода"
|
||||
fullscreen = "Полный экран"
|
||||
exitFullscreen = "Выйти из полного экрана"
|
||||
cookieconsentMessage = "Этот сайт использует Cookies для улучшения вашего опыта."
|
||||
cookieconsentDismiss = "Согласен"
|
||||
cookieconsentLink = "Учить больше"
|
||||
@@ -236,3 +237,17 @@ changed = "ИЗМЕНЕН"
|
||||
deleted = "УДАЛЕН"
|
||||
deprecated = "УСТАРЕЛ"
|
||||
# === shortcodes/version.html ===
|
||||
|
||||
# === diagram actions ===
|
||||
[diagram]
|
||||
zoomIn = "Увеличить"
|
||||
zoomOut = "Уменьшить"
|
||||
reset = "Сбросить"
|
||||
download = "Скачать SVG"
|
||||
# === diagram actions ===
|
||||
|
||||
# === tabs related ===
|
||||
[tabs]
|
||||
diagram = "Диаграмма"
|
||||
code = "Код"
|
||||
# === tabs related ===
|
||||
|
||||
+16
-1
@@ -105,8 +105,9 @@ toggleLineNumbers = "Прекидач за бројеве линија"
|
||||
toggleLineWrap = "Прекидач за оматање линија"
|
||||
toggleCodeEditable = "Пребаци уређивање блока кода"
|
||||
downloadCode = "Преузми код"
|
||||
toggleCodeFullscreen = "Пребаци цео екран блока кода"
|
||||
expandCode = "Прошири или смањи блок кода"
|
||||
fullscreen = "Цео екран"
|
||||
exitFullscreen = "Изађи из целог екрана"
|
||||
cookieconsentMessage = "Ова веб локација користи Cookies да би побољшала ваше искуство."
|
||||
cookieconsentDismiss = "Договорити се"
|
||||
cookieconsentLink = "Сазнајте више"
|
||||
@@ -236,3 +237,17 @@ changed = "ПРОМЕЊЕНО"
|
||||
deleted = "ОБРИСАНО"
|
||||
deprecated = "ЗАСТАРЕЛО"
|
||||
# === shortcodes/version.html ===
|
||||
|
||||
# === diagram actions ===
|
||||
[diagram]
|
||||
zoomIn = "Увећај"
|
||||
zoomOut = "Умањи"
|
||||
reset = "Ресетуј"
|
||||
download = "Преузми SVG"
|
||||
# === diagram actions ===
|
||||
|
||||
# === tabs related ===
|
||||
[tabs]
|
||||
diagram = "Дијаграм"
|
||||
code = "Код"
|
||||
# === tabs related ===
|
||||
|
||||
+16
-1
@@ -104,8 +104,9 @@ toggleLineNumbers = "Chuyển đổi số dòng"
|
||||
toggleLineWrap = "Chuyển đổi xuống dòng"
|
||||
toggleCodeEditable = "Chuyển đổi khối mã có thể chỉnh sửa"
|
||||
downloadCode = "Tải mã"
|
||||
toggleCodeFullscreen = "Chuyển đổi toàn màn hình khối mã"
|
||||
expandCode = "Mở rộng hoặc thu gọn khối mã"
|
||||
fullscreen = "Toàn màn hình"
|
||||
exitFullscreen = "Thoát toàn màn hình"
|
||||
cookieconsentMessage = "Trang web này sử dụng Cookies để cải thiện trải nghiệm của bạn."
|
||||
cookieconsentDismiss = "Đã hiểu!"
|
||||
cookieconsentLink = "Tìm hiểu thêm"
|
||||
@@ -235,3 +236,17 @@ changed = "THAY ĐỔI"
|
||||
deleted = "XOÁ"
|
||||
deprecated = "KHÔNG CÒN SỬ DỤNG"
|
||||
# === shortcodes/version.html ===
|
||||
|
||||
# === diagram actions ===
|
||||
[diagram]
|
||||
zoomIn = "Phóng to"
|
||||
zoomOut = "Thu nhỏ"
|
||||
reset = "Đặt lại"
|
||||
download = "Tải SVG"
|
||||
# === diagram actions ===
|
||||
|
||||
# === tabs related ===
|
||||
[tabs]
|
||||
diagram = "Sơ đồ"
|
||||
code = "Mã"
|
||||
# === tabs related ===
|
||||
|
||||
+16
-1
@@ -103,8 +103,9 @@ toggleLineNumbers = "切换行号"
|
||||
toggleLineWrap = "切换自动换行"
|
||||
toggleCodeEditable = "切换可编辑代码块"
|
||||
downloadCode = "下载代码"
|
||||
toggleCodeFullscreen = "切换代码块全屏"
|
||||
expandCode = "展开或折叠代码块"
|
||||
fullscreen = "全屏"
|
||||
exitFullscreen = "退出全屏"
|
||||
cookieconsentMessage = "本网站使用 Cookies 来改善你的浏览体验。"
|
||||
cookieconsentDismiss = "同意"
|
||||
cookieconsentLink = "了解更多"
|
||||
@@ -232,3 +233,17 @@ changed = "更改"
|
||||
deleted = "删除"
|
||||
deprecated = "弃用"
|
||||
# === shortcodes/version.html ===
|
||||
|
||||
# === diagram actions ===
|
||||
[diagram]
|
||||
zoomIn = "放大"
|
||||
zoomOut = "缩小"
|
||||
reset = "重置"
|
||||
download = "下载 SVG"
|
||||
# === diagram actions ===
|
||||
|
||||
# === tabs related ===
|
||||
[tabs]
|
||||
diagram = "图表"
|
||||
code = "代码"
|
||||
# === tabs related ===
|
||||
|
||||
+16
-1
@@ -103,8 +103,9 @@ toggleLineNumbers = "切換行號"
|
||||
toggleLineWrap = "切換自動換行"
|
||||
toggleCodeEditable = "切換可編輯程式碼區塊"
|
||||
downloadCode = "下載程式碼"
|
||||
toggleCodeFullscreen = "切換程式碼區塊全螢幕"
|
||||
expandCode = "展開或摺疊程式碼區塊"
|
||||
fullscreen = "全螢幕"
|
||||
exitFullscreen = "退出全螢幕"
|
||||
cookieconsentMessage = "本網站使用 Cookies 來改善你的流覽體驗。"
|
||||
cookieconsentDismiss = "同意"
|
||||
cookieconsentLink = "瞭解更多"
|
||||
@@ -232,3 +233,17 @@ changed = "更改"
|
||||
deleted = "刪除"
|
||||
deprecated = "棄用"
|
||||
# === shortcodes/version.html ===
|
||||
|
||||
# === diagram actions ===
|
||||
[diagram]
|
||||
zoomIn = "放大"
|
||||
zoomOut = "縮小"
|
||||
reset = "重置"
|
||||
download = "下載 SVG"
|
||||
# === diagram actions ===
|
||||
|
||||
# === tabs related ===
|
||||
[tabs]
|
||||
diagram = "圖表"
|
||||
code = "程式碼"
|
||||
# === tabs related ===
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
{{- end -}}
|
||||
{{- if $config.fullscreen -}}
|
||||
{{- $fullscreenIcon := dict "Class" "fa-solid fa-expand" | partial "plugin/icon.html" -}}
|
||||
{{- $fullscreenBtn = printf `<button type="button" class="action-btn fullscreen-btn" aria-label="%s" title="%s">%s</button>` (T "assets.toggleCodeFullscreen") (T "assets.toggleCodeFullscreen") $fullscreenIcon -}}
|
||||
{{- $fullscreenBtn = printf `<button type="button" class="action-btn fullscreen-btn" aria-label="%s" title="%s" data-exit-title="%s">%s</button>` (T "assets.fullscreen") (T "assets.fullscreen") (T "assets.exitFullscreen") $fullscreenIcon -}}
|
||||
{{- end -}}
|
||||
{{- with $config.name -}}
|
||||
{{- $titleEl = replace $titleEl `<span class="code-title">` (printf `<span class="code-title" data-name="%s">` .) -}}
|
||||
|
||||
@@ -348,6 +348,10 @@ FixIt theme assets partial
|
||||
|
||||
{{- /* Mermaid plugin */ -}}
|
||||
{{- if .Store.Get "hasMermaid" | and (not $isArchivesOrOffline) -}}
|
||||
{{- if .Site.Params.mermaid.wrapper -}}
|
||||
{{- $source := $cdn.panzoomJS | default "lib/panzoom/panzoom.min.js" -}}
|
||||
{{- dict "Source" $source "Fingerprint" $fingerprint "Defer" true | dict "Page" $ "Data" | partial "store/script.html" -}}
|
||||
{{- end -}}
|
||||
{{- $options := dict "Source" "js/lib/mermaid.js" "Template" "js/lib/mermaid.js" -}}
|
||||
{{- $options = dict "Context" . "Minify" hugo.IsProduction "Fingerprint" $fingerprint "Attr" `type="module"` | merge $options -}}
|
||||
{{- $options | dict "Page" $ "Data" | partial "store/script.html" -}}
|
||||
|
||||
@@ -1,9 +1,56 @@
|
||||
{{- /* Mermaid support for code fences extended and shortcodes. */ -}}
|
||||
<div class="diagram-container">
|
||||
<pre class="mermaid">{{ .Inner | safeHTML }}</pre>
|
||||
<pre class="mermaid-dark">{{ .Inner | safeHTML }}</pre>
|
||||
<pre class="mermaid-neutral">{{ .Inner | safeHTML }}</pre>
|
||||
{{- dict "Label" "Copy Mermaid code" | partial "plugin/diagram-copy-btn.html" -}}
|
||||
<template><pre>{{ .Inner | safeHTML }}</pre></template>
|
||||
</div>
|
||||
{{- /* EOF */ -}}
|
||||
{{- $code := strings.TrimSpace .Inner -}}
|
||||
{{- $wrapped := .Options.wrapper | default site.Params.mermaid.wrapper -}}
|
||||
|
||||
{{- if $wrapped -}}
|
||||
{{- $labelDiagram := T "tabs.diagram" -}}
|
||||
{{- $labelCode := T "tabs.code" -}}
|
||||
{{- $labelZoomIn := T "diagram.zoomIn" -}}
|
||||
{{- $labelZoomOut := T "diagram.zoomOut" -}}
|
||||
{{- $labelReset := T "diagram.reset" -}}
|
||||
{{- $labelDownload := T "diagram.download" -}}
|
||||
{{- $filename := .Options.filename | default "mermaid.mmd" -}}
|
||||
{{- $codeAttrs := printf `{copyable=true, fullscreen=true, filename="%s", class="diagram-code"}` $filename -}}
|
||||
{{- $codeFence := printf "```text %s\n%s\n```" $codeAttrs $code -}}
|
||||
|
||||
<div class="code-tabs diagram-tabs" data-diagram="mermaid">
|
||||
<div class="tabs-header">
|
||||
<div class="tabs-items">
|
||||
<span class="tab-item active" data-tab="diagram" title="{{ $labelDiagram }}">{{ $labelDiagram }}</span>
|
||||
<span class="tab-item" data-tab="code" title="{{ $labelCode }}">{{ $labelCode }}</span>
|
||||
</div>
|
||||
<div class="tabs-actions">
|
||||
<button type="button" class="action-btn diagram-zoom-out-btn" aria-label="{{ $labelZoomOut }}" title="{{ $labelZoomOut }}">
|
||||
{{- dict "Class" "fa-solid fa-magnifying-glass-minus" | partial "plugin/icon.html" -}}
|
||||
</button>
|
||||
<button type="button" class="action-btn diagram-zoom-in-btn" aria-label="{{ $labelZoomIn }}" title="{{ $labelZoomIn }}">
|
||||
{{- dict "Class" "fa-solid fa-magnifying-glass-plus" | partial "plugin/icon.html" -}}
|
||||
</button>
|
||||
<button type="button" class="action-btn diagram-reset-btn" aria-label="{{ $labelReset }}" title="{{ $labelReset }}">
|
||||
{{- dict "Class" "fa-solid fa-arrows-rotate" | partial "plugin/icon.html" -}}
|
||||
</button>
|
||||
<button type="button" class="action-btn diagram-download-btn" aria-label="{{ $labelDownload }}" title="{{ $labelDownload }}">
|
||||
{{- dict "Class" "fa-solid fa-download" | partial "plugin/icon.html" -}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tabs-content">
|
||||
<div class="code-block diagram-view active" data-filename="{{ $filename }}">
|
||||
<div class="diagram-container">
|
||||
<pre class="mermaid">{{ $code | safeHTML }}</pre>
|
||||
<pre class="mermaid-dark">{{ $code | safeHTML }}</pre>
|
||||
<pre class="mermaid-neutral">{{ $code | safeHTML }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
{{- $codeFence | page.RenderString (dict "display" "block") -}}
|
||||
</div>
|
||||
</div>
|
||||
{{- else -}}
|
||||
<div class="diagram-container">
|
||||
<pre class="mermaid">{{ $code | safeHTML }}</pre>
|
||||
<pre class="mermaid-dark">{{ $code | safeHTML }}</pre>
|
||||
<pre class="mermaid-neutral">{{ $code | safeHTML }}</pre>
|
||||
{{- dict "Label" "Copy Mermaid code" | partial "plugin/diagram-copy-btn.html" -}}
|
||||
<template><pre>{{ $code | safeHTML }}</pre></template>
|
||||
</div>
|
||||
{{- end -}}
|
||||
|
||||
@@ -131,6 +131,12 @@ libraries:
|
||||
items:
|
||||
- from: dist/browser/pangu.umd.js
|
||||
to: pangu/pangu.umd.js
|
||||
- npm: '@panzoom/panzoom'
|
||||
version: 4.6.2
|
||||
local:
|
||||
items:
|
||||
- from: dist/panzoom.min.js
|
||||
to: panzoom/panzoom.min.js
|
||||
- npm: sharer.js
|
||||
version: 0.5.1
|
||||
local:
|
||||
|
||||
Reference in New Issue
Block a user