@barzhsieh/nuxt-content-mermaid 2.2.3 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -502
- package/README.zh-TW.md +46 -509
- package/dist/module.d.mts +39 -53
- package/dist/module.json +3 -3
- package/dist/module.mjs +600 -155
- package/dist/runtime/built-in-renderer/BuiltInRenderer.d.vue.ts +0 -0
- package/dist/runtime/built-in-renderer/BuiltInRenderer.vue +597 -0
- package/dist/runtime/built-in-renderer/BuiltInRenderer.vue.d.ts +0 -0
- package/dist/runtime/component-configuration-error.d.ts +3 -0
- package/dist/runtime/component-configuration-error.js +7 -0
- package/dist/runtime/component-configuration.d.ts +23 -0
- package/dist/runtime/component-configuration.js +78 -0
- package/dist/runtime/components/ContentMermaidTransport.d.vue.ts +10 -0
- package/dist/runtime/components/ContentMermaidTransport.vue +21 -0
- package/dist/runtime/components/ContentMermaidTransport.vue.d.ts +10 -0
- package/dist/runtime/components/Mermaid.d.vue.ts +0 -31
- package/dist/runtime/components/Mermaid.vue +136 -743
- package/dist/runtime/components/Mermaid.vue.d.ts +0 -31
- package/dist/runtime/components/MermaidExpandOverlay.d.vue.ts +13 -16
- package/dist/runtime/components/MermaidExpandOverlay.vue +61 -53
- package/dist/runtime/components/MermaidExpandOverlay.vue.d.ts +13 -16
- package/dist/runtime/components/MermaidFullscreenPresentation.d.vue.ts +18 -0
- package/dist/runtime/components/MermaidFullscreenPresentation.vue +56 -0
- package/dist/runtime/components/MermaidFullscreenPresentation.vue.d.ts +18 -0
- package/dist/runtime/composables/useMermaidExpand.d.ts +16 -32
- package/dist/runtime/composables/useMermaidExpand.js +232 -75
- package/dist/runtime/composables/useMermaidFullscreen.d.ts +18 -0
- package/dist/runtime/composables/useMermaidFullscreen.js +242 -0
- package/dist/runtime/composables/useMermaidZoom.d.ts +5 -1
- package/dist/runtime/composables/useMermaidZoom.js +54 -28
- package/dist/runtime/configuration/core.d.ts +27 -0
- package/dist/runtime/configuration/core.js +340 -0
- package/dist/runtime/configuration/module.d.ts +17 -0
- package/dist/runtime/configuration/module.js +233 -0
- package/dist/runtime/configuration/runtime-options.d.ts +4 -0
- package/dist/runtime/configuration/runtime-options.js +57 -0
- package/dist/runtime/constants.d.ts +24 -0
- package/dist/runtime/constants.js +60 -2
- package/dist/runtime/direct-mermaid-config.d.ts +5 -0
- package/dist/runtime/direct-mermaid-config.js +216 -0
- package/dist/runtime/mermaid-config.d.ts +9 -5
- package/dist/runtime/mermaid-config.js +30 -19
- package/dist/runtime/mermaid-rendering.d.ts +28 -0
- package/dist/runtime/mermaid-rendering.js +140 -0
- package/dist/runtime/plugins/content-mermaid-transport.d.ts +2 -0
- package/dist/runtime/plugins/content-mermaid-transport.js +5 -0
- package/dist/runtime/plugins/mermaid.client.d.ts +0 -3
- package/dist/runtime/plugins/mermaid.client.js +14 -32
- package/dist/runtime/plugins/runtime-config.d.ts +2 -0
- package/dist/runtime/plugins/runtime-config.js +9 -0
- package/dist/runtime/rendererSelection.d.ts +43 -0
- package/dist/runtime/rendererSelection.js +36 -0
- package/dist/runtime/rendererSelectionOrchestration.d.ts +12 -0
- package/dist/runtime/rendererSelectionOrchestration.js +13 -0
- package/dist/runtime/runtime-snapshot.d.ts +3 -0
- package/dist/runtime/runtime-snapshot.js +26 -0
- package/dist/runtime/utils/index.d.ts +0 -4
- package/dist/runtime/utils/index.js +0 -37
- package/dist/types.d.mts +2 -2
- package/package.json +38 -32
- package/dist/runtime/utils/mermaid-transform.d.ts +0 -18
- package/dist/runtime/utils/mermaid-transform.js +0 -345
|
@@ -1,622 +1,152 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { useNuxtApp
|
|
2
|
+
import { useNuxtApp } from "#app";
|
|
3
|
+
import { computed, shallowRef, watch } from "vue";
|
|
3
4
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
} from "
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import { enqueueRender, parseSizeToPx, isRecord } from "../utils";
|
|
16
|
-
import { useMermaidTheme } from "../composables/useMermaidTheme";
|
|
17
|
-
import { useMermaidExpand } from "../composables/useMermaidExpand";
|
|
18
|
-
import { useMermaidCursors } from "../composables/useMermaidCursors";
|
|
19
|
-
import { useFullscreen } from "../composables/useFullscreen";
|
|
20
|
-
import { useMermaidZoom } from "../composables/useMermaidZoom";
|
|
21
|
-
import { useEventListener } from "../composables/useEventListener";
|
|
22
|
-
import { DEFAULT_TOOLBAR_OPTIONS, DEFAULT_EXPAND_OPTIONS } from "../constants";
|
|
5
|
+
collectMermaidComponentSourceDependencies,
|
|
6
|
+
resolveMermaidComponentSource
|
|
7
|
+
} from "../component-configuration";
|
|
8
|
+
import { MERMAID_LOG_PREFIX } from "../constants";
|
|
9
|
+
import BuiltInRenderer from "../built-in-renderer/BuiltInRenderer.vue";
|
|
10
|
+
import {
|
|
11
|
+
createRendererResolutionFailureHandoff,
|
|
12
|
+
selectRenderer
|
|
13
|
+
} from "../rendererSelection";
|
|
14
|
+
import { createRendererSelectionAttemptCoordinator } from "../rendererSelectionOrchestration";
|
|
15
|
+
import { getRuntimeMermaidSnapshot } from "../runtime-snapshot";
|
|
23
16
|
import Spinner from "./Spinner.vue";
|
|
24
|
-
import IconClipboard from "./icons/IconClipboard.vue";
|
|
25
|
-
import IconClipboardHover from "./icons/IconClipboardHover.vue";
|
|
26
|
-
import IconClipboardSuccess from "./icons/IconClipboardSuccess.vue";
|
|
27
|
-
import IconFullScreen from "./icons/iconFullScreen.vue";
|
|
28
|
-
import IconFullScreenExit from "./icons/iconFullScreenExit.vue";
|
|
29
|
-
import MermaidExpandOverlay from "./MermaidExpandOverlay.vue";
|
|
30
|
-
import IconExpand from "./icons/IconExpand.vue";
|
|
31
|
-
import IconCollapse from "./icons/IconCollapse.vue";
|
|
32
|
-
import MermaidZoomToolbar from "./MermaidZoomToolbar.vue";
|
|
33
|
-
const isMac = import.meta.client ? /Mac|iPhone|iPad|iPod/.test(navigator.userAgent) : false;
|
|
34
17
|
const props = defineProps({
|
|
35
|
-
config: { type: null, required: false },
|
|
36
18
|
toolbar: { type: Object, required: false },
|
|
37
|
-
code: { type: String, required: false }
|
|
38
|
-
}
|
|
19
|
+
code: { type: String, required: false },
|
|
20
|
+
pageConfig: { type: null, required: false },
|
|
21
|
+
config: { type: null, required: false }
|
|
22
|
+
});
|
|
23
|
+
function resolveCurrentComponentSource() {
|
|
24
|
+
return resolveMermaidComponentSource({
|
|
25
|
+
pageConfig: props.pageConfig,
|
|
26
|
+
config: props.config
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
const initialComponentSource = resolveCurrentComponentSource();
|
|
30
|
+
if (initialComponentSource.kind === "conflict") throw initialComponentSource.error;
|
|
31
|
+
const componentSource = shallowRef(initialComponentSource);
|
|
39
32
|
const nuxtApp = useNuxtApp();
|
|
40
|
-
const
|
|
41
|
-
const contentMermaidOptions = runtimeConfig.public?.contentMermaid || runtimeConfig.public?.mermaidContent || {};
|
|
42
|
-
const isEnabled = contentMermaidOptions.enabled !== false;
|
|
43
|
-
const debug = contentMermaidOptions.debug || false;
|
|
44
|
-
const loaderOptions = contentMermaidOptions.loader || {};
|
|
45
|
-
const themeOptions = contentMermaidOptions.theme || {};
|
|
33
|
+
const contentMermaidOptions = getRuntimeMermaidSnapshot(nuxtApp);
|
|
46
34
|
const componentOptions = contentMermaidOptions.components || {};
|
|
47
|
-
function resolveExpandOptions(expand) {
|
|
48
|
-
if (expand === false)
|
|
49
|
-
return { ...DEFAULT_EXPAND_OPTIONS, enabled: false };
|
|
50
|
-
if (expand === true || !expand || typeof expand !== "object")
|
|
51
|
-
return DEFAULT_EXPAND_OPTIONS;
|
|
52
|
-
return defu({}, expand, DEFAULT_EXPAND_OPTIONS);
|
|
53
|
-
}
|
|
54
|
-
const expandOptions = resolveExpandOptions(contentMermaidOptions.expand);
|
|
55
|
-
const expandEnabled = expandOptions.enabled !== false;
|
|
56
|
-
const expandOpenOptions = expandOptions.invokeOpenOn || {};
|
|
57
|
-
const expandCloseOptions = expandOptions.invokeCloseOn || {};
|
|
58
|
-
const expandMargin = typeof expandOptions.margin === "number" && Number.isFinite(expandOptions.margin) ? Math.max(0, expandOptions.margin) : 0;
|
|
59
|
-
const allowOpenDiagramClick = expandOpenOptions.diagramClick !== false;
|
|
60
|
-
const allowOverlayClose = expandCloseOptions.overlayClick !== false;
|
|
61
|
-
const allowCloseButtonClose = expandCloseOptions.closeButtonClick !== false;
|
|
62
|
-
const allowEscClose = expandCloseOptions.esc !== false;
|
|
63
|
-
const allowWheelClose = expandCloseOptions.wheel !== false;
|
|
64
|
-
const allowSwipeClose = expandCloseOptions.swipe !== false;
|
|
65
|
-
const lazyOption = loaderOptions.lazy;
|
|
66
|
-
const isLazy = lazyOption !== false;
|
|
67
|
-
const lazyThreshold = typeof lazyOption === "object" && typeof lazyOption.threshold === "number" ? lazyOption.threshold : 0.1;
|
|
68
|
-
const baseMermaidInit = loaderOptions.init || {};
|
|
69
|
-
const lightTheme = themeOptions.light;
|
|
70
|
-
const darkTheme = themeOptions.dark;
|
|
71
|
-
const colorMode = nuxtApp.$colorMode;
|
|
72
|
-
const { $mermaid } = nuxtApp;
|
|
73
|
-
const { currentTheme: manualThemeMode } = useMermaidTheme();
|
|
74
|
-
const mermaidBlock = useTemplateRef("mermaidBlock");
|
|
75
|
-
const mermaidContainer = useTemplateRef("mermaidContainer");
|
|
76
|
-
const hasRenderedOnce = ref(false);
|
|
77
|
-
const isLoading = ref(false);
|
|
78
|
-
const hasError = ref(false);
|
|
79
|
-
const errorContent = shallowRef(null);
|
|
80
35
|
const decodedCode = computed(() => props.code ? decodeURIComponent(props.code) : "");
|
|
81
|
-
const mermaidDefinition = ref(decodedCode.value);
|
|
82
|
-
let observer = null;
|
|
83
|
-
const defaultToolbarTitle = DEFAULT_TOOLBAR_OPTIONS.title ?? "mermaid";
|
|
84
|
-
const defaultToolbarFontSize = DEFAULT_TOOLBAR_OPTIONS.fontSize ?? "14px";
|
|
85
|
-
const defaultFullscreenToolbarScale = DEFAULT_TOOLBAR_OPTIONS.fullscreenToolbarScale ?? 1.25;
|
|
86
|
-
const baseToolbarDefaults = DEFAULT_TOOLBAR_OPTIONS;
|
|
87
|
-
const runtimeToolbarDefaults = computed(() => {
|
|
88
|
-
const toolbar = contentMermaidOptions.toolbar;
|
|
89
|
-
return toolbar && typeof toolbar === "object" ? toolbar : {};
|
|
90
|
-
});
|
|
91
|
-
const resolvedToolbar = computed(() => {
|
|
92
|
-
return defu({}, props.toolbar ?? {}, runtimeToolbarDefaults.value, baseToolbarDefaults);
|
|
93
|
-
});
|
|
94
|
-
const toolbarButtons = computed(() => {
|
|
95
|
-
const buttons = resolvedToolbar.value.buttons;
|
|
96
|
-
return isRecord(buttons) ? buttons : {};
|
|
97
|
-
});
|
|
98
|
-
const toolbarTitle = computed(() => resolvedToolbar.value.title ?? defaultToolbarTitle);
|
|
99
|
-
const toolbarFontSize = computed(() => {
|
|
100
|
-
const value = resolvedToolbar.value.fontSize ?? defaultToolbarFontSize;
|
|
101
|
-
if (typeof value === "number")
|
|
102
|
-
return `${value}px`;
|
|
103
|
-
if (typeof value === "string") {
|
|
104
|
-
const trimmed = value.trim();
|
|
105
|
-
if (/^\d+(?:\.\d+)?$/.test(trimmed))
|
|
106
|
-
return `${trimmed}px`;
|
|
107
|
-
return trimmed;
|
|
108
|
-
}
|
|
109
|
-
return defaultToolbarFontSize;
|
|
110
|
-
});
|
|
111
|
-
const showCopyButton = computed(() => toolbarButtons.value.copy !== false);
|
|
112
|
-
const copySource = computed(() => decodedCode.value || mermaidDefinition.value || "");
|
|
113
|
-
const hasCopySource = computed(() => !!copySource.value);
|
|
114
|
-
const copyState = ref("idle");
|
|
115
|
-
const copyLabel = computed(() => {
|
|
116
|
-
if (copyState.value === "copied") return "Copied";
|
|
117
|
-
if (copyState.value === "error") return "Copy failed";
|
|
118
|
-
return "Copy";
|
|
119
|
-
});
|
|
120
|
-
let copyResetTimer = null;
|
|
121
|
-
const isCopyHovered = ref(false);
|
|
122
|
-
const copyIcon = computed(() => {
|
|
123
|
-
if (copyState.value === "copied") return IconClipboardSuccess;
|
|
124
|
-
if (isCopyHovered.value && hasCopySource.value) return IconClipboardHover;
|
|
125
|
-
return IconClipboard;
|
|
126
|
-
});
|
|
127
|
-
const { isSupported: isFullscreenSupported, isFullscreen, toggle: toggleFullscreen } = useFullscreen(mermaidBlock, {
|
|
128
|
-
autoExit: true
|
|
129
|
-
});
|
|
130
|
-
const showFullscreenButton = computed(() => toolbarButtons.value.fullscreen !== false && isFullscreenSupported.value);
|
|
131
|
-
const fullscreenLabel = computed(() => isFullscreen.value ? "Exit fullscreen" : "Enter fullscreen");
|
|
132
|
-
const fullscreenToolbarScale = computed(() => {
|
|
133
|
-
const raw = resolvedToolbar.value.fullscreenToolbarScale ?? defaultFullscreenToolbarScale;
|
|
134
|
-
if (typeof raw === "number" && Number.isFinite(raw))
|
|
135
|
-
return raw;
|
|
136
|
-
if (typeof raw === "string") {
|
|
137
|
-
const parsed = Number.parseFloat(raw);
|
|
138
|
-
if (Number.isFinite(parsed))
|
|
139
|
-
return parsed;
|
|
140
|
-
}
|
|
141
|
-
return defaultFullscreenToolbarScale;
|
|
142
|
-
});
|
|
143
|
-
const toolbarFontSizePx = computed(() => {
|
|
144
|
-
return parseSizeToPx(resolvedToolbar.value.fontSize ?? defaultToolbarFontSize, 14);
|
|
145
|
-
});
|
|
146
|
-
const fullscreenToolbarFontSize = computed(() => {
|
|
147
|
-
const scaled = toolbarFontSizePx.value * fullscreenToolbarScale.value;
|
|
148
|
-
return `${Math.round(scaled * 100) / 100}px`;
|
|
149
|
-
});
|
|
150
|
-
const effectiveToolbarFontSize = computed(() => {
|
|
151
|
-
return isFullscreen.value ? fullscreenToolbarFontSize.value : toolbarFontSize.value;
|
|
152
|
-
});
|
|
153
|
-
const effectiveToolbarFontSizePx = computed(() => {
|
|
154
|
-
const baseSize = toolbarFontSizePx.value;
|
|
155
|
-
return isFullscreen.value ? baseSize * fullscreenToolbarScale.value : baseSize;
|
|
156
|
-
});
|
|
157
|
-
const pageConfig = computed(() => {
|
|
158
|
-
const value = props.config;
|
|
159
|
-
if (!value)
|
|
160
|
-
return void 0;
|
|
161
|
-
if (typeof value !== "object") {
|
|
162
|
-
if (import.meta.dev && value !== null && value !== void 0) {
|
|
163
|
-
console.warn(
|
|
164
|
-
"[nuxt-content-mermaid] Received non-object `config` prop on <Mermaid> component. This usually means your `content.config.ts` collection schema did not declare `config` as a JSON field. See README section about per-page overrides via frontmatter.",
|
|
165
|
-
value
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
return void 0;
|
|
169
|
-
}
|
|
170
|
-
return value;
|
|
171
|
-
});
|
|
172
|
-
const mermaidTheme = computed(() => {
|
|
173
|
-
return resolveMermaidTheme({
|
|
174
|
-
colorModeValue: colorMode?.value,
|
|
175
|
-
manualThemeMode: manualThemeMode.value,
|
|
176
|
-
frontmatterTheme: pageConfig.value?.theme,
|
|
177
|
-
baseTheme: baseMermaidInit.theme,
|
|
178
|
-
lightTheme,
|
|
179
|
-
darkTheme
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
const effectiveMermaidInit = computed(() => {
|
|
183
|
-
return mergeMermaidConfig({
|
|
184
|
-
baseConfig: baseMermaidInit,
|
|
185
|
-
overrideConfig: pageConfig.value,
|
|
186
|
-
theme: mermaidTheme.value
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
36
|
const configuredSpinnerName = computed(() => componentOptions.spinner?.trim() || "");
|
|
190
37
|
const customSpinner = shallowRef(null);
|
|
191
38
|
const configuredMermaidImplName = computed(() => componentOptions.renderer?.trim() || "");
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
39
|
+
const rendererSelectionState = shallowRef(
|
|
40
|
+
configuredMermaidImplName.value ? { status: "pending" } : { status: "built-in" }
|
|
41
|
+
);
|
|
195
42
|
const spinnerComponent = computed(() => customSpinner.value || Spinner);
|
|
196
|
-
const iconSize = computed(() => {
|
|
197
|
-
return Math.round(effectiveToolbarFontSizePx.value * 1.2);
|
|
198
|
-
});
|
|
199
|
-
const isExpandBlocked = computed(() => !isEnabled || !expandEnabled || isLoading.value || hasError.value || isFullscreen.value);
|
|
200
|
-
const fullscreenZoom = useMermaidZoom({
|
|
201
|
-
active: isFullscreen,
|
|
202
|
-
minScale: 0.1,
|
|
203
|
-
maxScale: 10
|
|
204
|
-
});
|
|
205
|
-
const showFullscreenZoomHint = ref(false);
|
|
206
|
-
const fullscreenHintDurationMs = 3e3;
|
|
207
|
-
let fullscreenHintTimeout;
|
|
208
|
-
let hasShownFullscreenZoomHint = false;
|
|
209
|
-
watch(isFullscreen, (value) => {
|
|
210
|
-
if (value) {
|
|
211
|
-
hasShownFullscreenZoomHint = false;
|
|
212
|
-
nextTick(() => {
|
|
213
|
-
const el = mermaidContainer.value;
|
|
214
|
-
if (el) {
|
|
215
|
-
const rect = el.getBoundingClientRect();
|
|
216
|
-
fullscreenZoom.init({
|
|
217
|
-
scale: 1,
|
|
218
|
-
translateX: 0,
|
|
219
|
-
translateY: 0,
|
|
220
|
-
top: rect.top,
|
|
221
|
-
left: rect.left
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
});
|
|
225
|
-
} else {
|
|
226
|
-
showFullscreenZoomHint.value = false;
|
|
227
|
-
clearTimeout(fullscreenHintTimeout);
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
function refreshFullscreenOrigin() {
|
|
231
|
-
if (!import.meta.client || !isFullscreen.value) return;
|
|
232
|
-
const el = mermaidContainer.value;
|
|
233
|
-
if (!el) return;
|
|
234
|
-
requestAnimationFrame(() => {
|
|
235
|
-
requestAnimationFrame(() => {
|
|
236
|
-
const rect = el.getBoundingClientRect();
|
|
237
|
-
fullscreenZoom.setMetrics({
|
|
238
|
-
scale: fullscreenZoom.scale.value,
|
|
239
|
-
translateX: fullscreenZoom.translateX.value,
|
|
240
|
-
translateY: fullscreenZoom.translateY.value,
|
|
241
|
-
top: rect.top,
|
|
242
|
-
left: rect.left
|
|
243
|
-
});
|
|
244
|
-
});
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
function handleFullscreenWheel(event) {
|
|
248
|
-
if (!isFullscreen.value) return;
|
|
249
|
-
const handled = fullscreenZoom.handleWheel(event);
|
|
250
|
-
if (handled) {
|
|
251
|
-
if (fullscreenHintTimeout && showFullscreenZoomHint.value) {
|
|
252
|
-
showFullscreenZoomHint.value = false;
|
|
253
|
-
clearTimeout(fullscreenHintTimeout);
|
|
254
|
-
}
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
if (!hasShownFullscreenZoomHint) {
|
|
258
|
-
hasShownFullscreenZoomHint = true;
|
|
259
|
-
showFullscreenZoomHint.value = true;
|
|
260
|
-
clearTimeout(fullscreenHintTimeout);
|
|
261
|
-
fullscreenHintTimeout = setTimeout(() => {
|
|
262
|
-
showFullscreenZoomHint.value = false;
|
|
263
|
-
}, fullscreenHintDurationMs);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
function handleFullscreenKeyDown(event) {
|
|
267
|
-
if (!isFullscreen.value) return;
|
|
268
|
-
if (event.defaultPrevented) return;
|
|
269
|
-
const target = event.target;
|
|
270
|
-
if (target) {
|
|
271
|
-
const tagName = target.tagName;
|
|
272
|
-
if (target.isContentEditable || tagName === "INPUT" || tagName === "TEXTAREA" || tagName === "SELECT")
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
const browserZoomKeys = /* @__PURE__ */ new Set(["+", "=", "-", "_"]);
|
|
276
|
-
if (event.ctrlKey || event.metaKey) {
|
|
277
|
-
if (browserZoomKeys.has(event.key))
|
|
278
|
-
event.preventDefault();
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
const moveStep = 20 / fullscreenZoom.scale.value;
|
|
282
|
-
const keyHandlers = {
|
|
283
|
-
"+": () => fullscreenZoom.zoomIn(),
|
|
284
|
-
"=": () => fullscreenZoom.zoomIn(),
|
|
285
|
-
"-": () => fullscreenZoom.zoomOut(),
|
|
286
|
-
"_": () => fullscreenZoom.zoomOut(),
|
|
287
|
-
"0": () => fullscreenZoom.reset(),
|
|
288
|
-
"ArrowUp": () => {
|
|
289
|
-
fullscreenZoom.translateY.value += moveStep;
|
|
290
|
-
},
|
|
291
|
-
"ArrowDown": () => {
|
|
292
|
-
fullscreenZoom.translateY.value -= moveStep;
|
|
293
|
-
},
|
|
294
|
-
"ArrowLeft": () => {
|
|
295
|
-
fullscreenZoom.translateX.value += moveStep;
|
|
296
|
-
},
|
|
297
|
-
"ArrowRight": () => {
|
|
298
|
-
fullscreenZoom.translateX.value -= moveStep;
|
|
299
|
-
}
|
|
300
|
-
};
|
|
301
|
-
const handler = keyHandlers[event.key];
|
|
302
|
-
if (handler) {
|
|
303
|
-
event.preventDefault();
|
|
304
|
-
handler();
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
if (import.meta.client) {
|
|
308
|
-
const fullscreenDocument = computed(() => isFullscreen.value ? document : null);
|
|
309
|
-
const fullscreenWindow = computed(() => isFullscreen.value ? window : null);
|
|
310
|
-
const fullscreenVisualViewport = computed(() => isFullscreen.value ? window.visualViewport : null);
|
|
311
|
-
useEventListener(fullscreenWindow, "wheel", handleFullscreenWheel, { passive: false });
|
|
312
|
-
useEventListener(fullscreenDocument, "keydown", handleFullscreenKeyDown);
|
|
313
|
-
useEventListener(fullscreenWindow, "focus", refreshFullscreenOrigin);
|
|
314
|
-
useEventListener(fullscreenDocument, "fullscreenchange", refreshFullscreenOrigin);
|
|
315
|
-
useEventListener(fullscreenVisualViewport, "resize", refreshFullscreenOrigin, { passive: true });
|
|
316
|
-
useEventListener(fullscreenDocument, "visibilitychange", () => {
|
|
317
|
-
if (document.visibilityState === "visible") refreshFullscreenOrigin();
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
const {
|
|
321
|
-
setExpandTargetWrap,
|
|
322
|
-
expandTargetStyle,
|
|
323
|
-
isExpandActive,
|
|
324
|
-
isVisible,
|
|
325
|
-
closeExpand,
|
|
326
|
-
toggleExpand,
|
|
327
|
-
handleExpandTransitionEnd,
|
|
328
|
-
handleMermaidClick,
|
|
329
|
-
resetExpand,
|
|
330
|
-
zoom,
|
|
331
|
-
showZoomHint
|
|
332
|
-
} = useMermaidExpand({
|
|
333
|
-
getExpandTarget: getMermaidSvg,
|
|
334
|
-
expandMargin,
|
|
335
|
-
isBlocked: isExpandBlocked,
|
|
336
|
-
invokeOpenOn: {
|
|
337
|
-
diagramClick: allowOpenDiagramClick
|
|
338
|
-
},
|
|
339
|
-
invokeCloseOn: {
|
|
340
|
-
esc: allowEscClose,
|
|
341
|
-
wheel: allowWheelClose,
|
|
342
|
-
swipe: allowSwipeClose
|
|
343
|
-
}
|
|
344
|
-
});
|
|
345
|
-
const showExpandToolbarButton = computed(() => {
|
|
346
|
-
if (!expandEnabled) return false;
|
|
347
|
-
if (toolbarButtons.value.expand === false) return false;
|
|
348
|
-
if (isExpandActive.value) return false;
|
|
349
|
-
if (isFullscreen.value) return false;
|
|
350
|
-
return true;
|
|
351
|
-
});
|
|
352
43
|
function normalizeIdentifier(value) {
|
|
353
44
|
return value.replace(/\.vue$/i, "").split(/[\\/]/).pop()?.replace(/[\s_-]+/g, "").toLowerCase() || "";
|
|
354
45
|
}
|
|
355
|
-
async function
|
|
46
|
+
async function loadAppComponent(name, appComponents) {
|
|
356
47
|
const target = normalizeIdentifier(name);
|
|
357
48
|
const matchEntry = Object.entries(appComponents).find(([path]) => {
|
|
358
49
|
const base = path.split(/[\\/]/).pop() || "";
|
|
359
|
-
|
|
360
|
-
return normalized === target;
|
|
50
|
+
return normalizeIdentifier(base) === target;
|
|
361
51
|
});
|
|
362
|
-
if (!matchEntry)
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
52
|
+
if (!matchEntry) return null;
|
|
53
|
+
const [, componentLoader] = matchEntry;
|
|
54
|
+
const mod = await componentLoader();
|
|
55
|
+
return mod.default || mod;
|
|
56
|
+
}
|
|
57
|
+
async function resolveSpinnerAppComponent(name, appComponents) {
|
|
369
58
|
try {
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
|
|
59
|
+
const component = await loadAppComponent(name, appComponents);
|
|
60
|
+
if (!component) {
|
|
61
|
+
console.warn(
|
|
62
|
+
"[nuxt-content-mermaid] Cannot find spinner component:",
|
|
63
|
+
name
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
return component;
|
|
373
67
|
} catch (error) {
|
|
374
68
|
console.error(
|
|
375
|
-
|
|
69
|
+
"[nuxt-content-mermaid] Failed to load spinner component:",
|
|
376
70
|
error
|
|
377
71
|
);
|
|
378
72
|
return null;
|
|
379
73
|
}
|
|
380
74
|
}
|
|
381
|
-
if (import.meta.client
|
|
382
|
-
let watchCustomAppComponent = function(nameRef, targetRef, label) {
|
|
383
|
-
watch(
|
|
384
|
-
nameRef,
|
|
385
|
-
async (name) => {
|
|
386
|
-
if (!name) {
|
|
387
|
-
targetRef.value = null;
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
390
|
-
targetRef.value = await resolveAppComponent(
|
|
391
|
-
name,
|
|
392
|
-
appComponents,
|
|
393
|
-
label
|
|
394
|
-
);
|
|
395
|
-
},
|
|
396
|
-
{ immediate: true }
|
|
397
|
-
);
|
|
398
|
-
};
|
|
75
|
+
if (import.meta.client) {
|
|
399
76
|
const appComponents = import.meta.glob("~/components/**/*.{vue,js,ts}");
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
const ELEMENT_NODE = 1;
|
|
407
|
-
if (root.nodeType === TEXT_NODE) {
|
|
408
|
-
return root.data;
|
|
409
|
-
}
|
|
410
|
-
if (root.nodeType === ELEMENT_NODE) {
|
|
411
|
-
const el = root;
|
|
412
|
-
const tag = el.tagName?.toUpperCase();
|
|
413
|
-
if (tag === "BR") return "\n";
|
|
414
|
-
const blockTags = [
|
|
415
|
-
"DIV",
|
|
416
|
-
"P",
|
|
417
|
-
"LI",
|
|
418
|
-
"TR",
|
|
419
|
-
"H1",
|
|
420
|
-
"H2",
|
|
421
|
-
"H3",
|
|
422
|
-
"H4",
|
|
423
|
-
"H5",
|
|
424
|
-
"H6",
|
|
425
|
-
"BLOCKQUOTE",
|
|
426
|
-
"PRE",
|
|
427
|
-
"UL",
|
|
428
|
-
"OL",
|
|
429
|
-
"DL",
|
|
430
|
-
"DT",
|
|
431
|
-
"DD",
|
|
432
|
-
"TABLE",
|
|
433
|
-
"THEAD",
|
|
434
|
-
"TBODY",
|
|
435
|
-
"TFOOT",
|
|
436
|
-
"SECTION",
|
|
437
|
-
"ARTICLE",
|
|
438
|
-
"ASIDE",
|
|
439
|
-
"HEADER",
|
|
440
|
-
"FOOTER",
|
|
441
|
-
"MAIN"
|
|
442
|
-
];
|
|
443
|
-
const isBlock = blockTags.includes(tag) || el.classList.contains("line") || el.classList.contains("code-line");
|
|
444
|
-
let out = "";
|
|
445
|
-
el.childNodes.forEach((child) => {
|
|
446
|
-
out += serializeMermaidFromNode(child);
|
|
447
|
-
});
|
|
448
|
-
if (isBlock) return "\n" + out + "\n";
|
|
449
|
-
return out;
|
|
450
|
-
}
|
|
451
|
-
return "";
|
|
452
|
-
}
|
|
453
|
-
function extractMermaidDefinition(container) {
|
|
454
|
-
const codeNodes = Array.from(container.querySelectorAll("code"));
|
|
455
|
-
if (codeNodes.length > 0) {
|
|
456
|
-
return codeNodes.map((node) => serializeMermaidFromNode(node)).join("\n").trim();
|
|
457
|
-
}
|
|
458
|
-
const pre = container.querySelector("pre");
|
|
459
|
-
if (pre) return serializeMermaidFromNode(pre).trim();
|
|
460
|
-
return serializeMermaidFromNode(container).trim();
|
|
461
|
-
}
|
|
462
|
-
function getMermaidSvg() {
|
|
463
|
-
if (!mermaidContainer.value) return null;
|
|
464
|
-
const svg = mermaidContainer.value.querySelector("svg");
|
|
465
|
-
return svg instanceof SVGSVGElement ? svg : null;
|
|
466
|
-
}
|
|
467
|
-
async function renderMermaid() {
|
|
468
|
-
if (!mermaidContainer.value || !mermaidDefinition.value) return;
|
|
469
|
-
isLoading.value = true;
|
|
470
|
-
const performRender = async () => {
|
|
471
|
-
if (!mermaidContainer.value) return;
|
|
472
|
-
if (import.meta.client && isExpandActive.value) {
|
|
473
|
-
resetExpand();
|
|
474
|
-
}
|
|
475
|
-
hasError.value = false;
|
|
476
|
-
errorContent.value = null;
|
|
477
|
-
const startTime = performance.now();
|
|
478
|
-
try {
|
|
479
|
-
const mermaid = await $mermaid();
|
|
480
|
-
mermaid.initialize(effectiveMermaidInit.value);
|
|
481
|
-
mermaidContainer.value.removeAttribute("data-processed");
|
|
482
|
-
mermaidContainer.value.textContent = mermaidDefinition.value;
|
|
483
|
-
await nextTick();
|
|
484
|
-
await mermaid.run({
|
|
485
|
-
nodes: [mermaidContainer.value],
|
|
486
|
-
suppressErrors: !debug
|
|
487
|
-
});
|
|
488
|
-
hasRenderedOnce.value = true;
|
|
489
|
-
const svg = getMermaidSvg();
|
|
490
|
-
if (svg) ensureViewBox(svg);
|
|
491
|
-
if (debug) {
|
|
492
|
-
const endTime = performance.now();
|
|
493
|
-
console.log(`[nuxt-content-mermaid] \u23F1\uFE0F Rendered in ${(endTime - startTime).toFixed(2)}ms`);
|
|
77
|
+
watch(
|
|
78
|
+
configuredSpinnerName,
|
|
79
|
+
async (name) => {
|
|
80
|
+
if (!name) {
|
|
81
|
+
customSpinner.value = null;
|
|
82
|
+
return;
|
|
494
83
|
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
if (!isLazy) {
|
|
511
|
-
renderMermaid();
|
|
512
|
-
return;
|
|
513
|
-
}
|
|
514
|
-
observer = new IntersectionObserver(
|
|
515
|
-
(entries) => {
|
|
516
|
-
if (entries[0]?.isIntersecting && !hasRenderedOnce.value) {
|
|
517
|
-
renderMermaid();
|
|
518
|
-
if (observer) observer.disconnect();
|
|
84
|
+
customSpinner.value = await resolveSpinnerAppComponent(name, appComponents);
|
|
85
|
+
},
|
|
86
|
+
{ immediate: true }
|
|
87
|
+
);
|
|
88
|
+
const beginRendererSelectionAttempt = createRendererSelectionAttemptCoordinator();
|
|
89
|
+
watch(
|
|
90
|
+
configuredMermaidImplName,
|
|
91
|
+
async (name) => {
|
|
92
|
+
const commitRendererSelectionAttempt = beginRendererSelectionAttempt();
|
|
93
|
+
const outcome = selectRenderer(name, {
|
|
94
|
+
loadComponent: (candidate) => loadAppComponent(candidate, appComponents)
|
|
95
|
+
});
|
|
96
|
+
if (outcome.status === "no-candidate") {
|
|
97
|
+
rendererSelectionState.value = { status: "built-in" };
|
|
98
|
+
return;
|
|
519
99
|
}
|
|
100
|
+
rendererSelectionState.value = { status: "pending" };
|
|
101
|
+
const commitResolutionFailure = createRendererResolutionFailureHandoff({
|
|
102
|
+
reportDiagnostic: (diagnostic) => {
|
|
103
|
+
const failureContext = diagnostic.reason === "load-failed" ? [diagnostic.error] : [];
|
|
104
|
+
console.warn(
|
|
105
|
+
MERMAID_LOG_PREFIX,
|
|
106
|
+
`Custom Renderer Candidate "${diagnostic.candidate}" resolution failed (${diagnostic.reason}).`,
|
|
107
|
+
diagnostic,
|
|
108
|
+
...failureContext
|
|
109
|
+
);
|
|
110
|
+
},
|
|
111
|
+
commitBuiltInOwnership: () => {
|
|
112
|
+
rendererSelectionState.value = { status: "built-in" };
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
const resolvedOutcome = await outcome.resolution;
|
|
116
|
+
commitRendererSelectionAttempt(resolvedOutcome, {
|
|
117
|
+
commitCustomOwnership: (component) => {
|
|
118
|
+
rendererSelectionState.value = {
|
|
119
|
+
status: "custom",
|
|
120
|
+
component
|
|
121
|
+
};
|
|
122
|
+
},
|
|
123
|
+
commitResolutionFailure
|
|
124
|
+
});
|
|
520
125
|
},
|
|
521
|
-
{
|
|
126
|
+
{ immediate: true }
|
|
522
127
|
);
|
|
523
|
-
observer.observe(container);
|
|
524
128
|
}
|
|
525
|
-
async function copyMermaidSource() {
|
|
526
|
-
if (!copySource.value) return;
|
|
527
|
-
const copied = await copyToClipboard(copySource.value);
|
|
528
|
-
copyState.value = copied ? "copied" : "error";
|
|
529
|
-
if (copyResetTimer)
|
|
530
|
-
clearTimeout(copyResetTimer);
|
|
531
|
-
copyResetTimer = setTimeout(() => {
|
|
532
|
-
copyState.value = "idle";
|
|
533
|
-
}, 2e3);
|
|
534
|
-
}
|
|
535
|
-
async function copyToClipboard(text) {
|
|
536
|
-
if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
|
|
537
|
-
try {
|
|
538
|
-
await navigator.clipboard.writeText(text);
|
|
539
|
-
return true;
|
|
540
|
-
} catch {
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
if (typeof document === "undefined")
|
|
544
|
-
return false;
|
|
545
|
-
const textarea = document.createElement("textarea");
|
|
546
|
-
textarea.value = text;
|
|
547
|
-
textarea.setAttribute("readonly", "");
|
|
548
|
-
textarea.style.position = "fixed";
|
|
549
|
-
textarea.style.top = "-9999px";
|
|
550
|
-
textarea.style.opacity = "0";
|
|
551
|
-
document.body.appendChild(textarea);
|
|
552
|
-
textarea.select();
|
|
553
|
-
let copied = false;
|
|
554
|
-
try {
|
|
555
|
-
copied = document.execCommand("copy");
|
|
556
|
-
} catch {
|
|
557
|
-
copied = false;
|
|
558
|
-
} finally {
|
|
559
|
-
document.body.removeChild(textarea);
|
|
560
|
-
}
|
|
561
|
-
return copied;
|
|
562
|
-
}
|
|
563
|
-
onMounted(() => {
|
|
564
|
-
if (!isEnabled) return;
|
|
565
|
-
if (!mermaidDefinition.value && mermaidContainer.value)
|
|
566
|
-
mermaidDefinition.value = extractMermaidDefinition(mermaidContainer.value);
|
|
567
|
-
nextTick(() => setupMermaidContainer());
|
|
568
|
-
});
|
|
569
|
-
onUnmounted(() => {
|
|
570
|
-
if (observer) observer.disconnect();
|
|
571
|
-
if (copyResetTimer) clearTimeout(copyResetTimer);
|
|
572
|
-
});
|
|
573
|
-
watch(mermaidTheme, () => {
|
|
574
|
-
if (!isEnabled) return;
|
|
575
|
-
if (hasRenderedOnce.value) renderMermaid();
|
|
576
|
-
});
|
|
577
129
|
watch(
|
|
578
|
-
|
|
130
|
+
() => collectMermaidComponentSourceDependencies({
|
|
131
|
+
pageConfig: props.pageConfig,
|
|
132
|
+
config: props.config
|
|
133
|
+
}),
|
|
579
134
|
() => {
|
|
580
|
-
|
|
581
|
-
|
|
135
|
+
const wasConflict = componentSource.value.kind === "conflict";
|
|
136
|
+
const source = resolveCurrentComponentSource();
|
|
137
|
+
componentSource.value = source;
|
|
138
|
+
if (source.kind === "conflict" && !wasConflict)
|
|
139
|
+
throw source.error;
|
|
582
140
|
},
|
|
583
|
-
{
|
|
141
|
+
{ flush: "post" }
|
|
584
142
|
);
|
|
585
|
-
watch(decodedCode, (newCode) => {
|
|
586
|
-
if (!isEnabled) return;
|
|
587
|
-
mermaidDefinition.value = newCode;
|
|
588
|
-
if (hasRenderedOnce.value) renderMermaid();
|
|
589
|
-
});
|
|
590
|
-
function ensureViewBox(svg) {
|
|
591
|
-
if (svg.hasAttribute("viewBox"))
|
|
592
|
-
return;
|
|
593
|
-
try {
|
|
594
|
-
const bbox = svg.getBBox();
|
|
595
|
-
if (bbox.width > 0 && bbox.height > 0)
|
|
596
|
-
svg.setAttribute("viewBox", `${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}`);
|
|
597
|
-
} catch {
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
const { cursorVariables } = useMermaidCursors(iconSize, expandEnabled);
|
|
601
143
|
</script>
|
|
602
144
|
|
|
603
145
|
<template>
|
|
604
146
|
<div class="mermaid-outer-wrapper">
|
|
605
|
-
<div
|
|
606
|
-
v-if="!isEnabled"
|
|
607
|
-
class="mermaid-wrapper"
|
|
608
|
-
>
|
|
609
|
-
<div
|
|
610
|
-
ref="mermaidContainer"
|
|
611
|
-
class="mermaid"
|
|
612
|
-
>
|
|
613
|
-
<slot />
|
|
614
|
-
</div>
|
|
615
|
-
</div>
|
|
616
|
-
|
|
617
147
|
<component
|
|
618
|
-
:is="
|
|
619
|
-
v-
|
|
148
|
+
:is="rendererSelectionState?.component"
|
|
149
|
+
v-if="rendererSelectionState?.status === 'custom'"
|
|
620
150
|
:spinner="spinnerComponent"
|
|
621
151
|
:code="decodedCode"
|
|
622
152
|
>
|
|
@@ -625,174 +155,37 @@ const { cursorVariables } = useMermaidCursors(iconSize, expandEnabled);
|
|
|
625
155
|
</slot>
|
|
626
156
|
</component>
|
|
627
157
|
|
|
628
|
-
<
|
|
158
|
+
<template v-else-if="rendererSelectionState?.status === 'pending'">
|
|
159
|
+
<slot>
|
|
160
|
+
<pre v-if="decodedCode"><code>{{ decodedCode }}</code></pre>
|
|
161
|
+
</slot>
|
|
162
|
+
</template>
|
|
163
|
+
|
|
164
|
+
<BuiltInRenderer
|
|
629
165
|
v-else
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
:
|
|
166
|
+
v-bind="props"
|
|
167
|
+
:component-source="componentSource"
|
|
168
|
+
:spinner-component="spinnerComponent"
|
|
633
169
|
>
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
<span v-if="toolbarTitle">{{ toolbarTitle }}</span>
|
|
641
|
-
</div>
|
|
642
|
-
<div class="mermaid-actions">
|
|
643
|
-
<button
|
|
644
|
-
v-if="showCopyButton"
|
|
645
|
-
type="button"
|
|
646
|
-
class="mermaid-btn"
|
|
647
|
-
:title="copyLabel"
|
|
648
|
-
:aria-label="copyLabel"
|
|
649
|
-
:disabled="!hasCopySource"
|
|
650
|
-
@click="copyMermaidSource"
|
|
651
|
-
@mouseenter="isCopyHovered = true"
|
|
652
|
-
@mouseleave="isCopyHovered = false"
|
|
653
|
-
>
|
|
654
|
-
<component
|
|
655
|
-
:is="copyIcon"
|
|
656
|
-
:size="iconSize"
|
|
657
|
-
/>
|
|
658
|
-
</button>
|
|
659
|
-
<button
|
|
660
|
-
v-if="showExpandToolbarButton"
|
|
661
|
-
type="button"
|
|
662
|
-
class="mermaid-btn"
|
|
663
|
-
:title="isExpandActive ? 'Collapse' : 'Expand'"
|
|
664
|
-
:aria-label="isExpandActive ? 'Collapse diagram' : 'Expand diagram'"
|
|
665
|
-
@click="toggleExpand"
|
|
666
|
-
>
|
|
667
|
-
<IconExpand
|
|
668
|
-
v-if="!isExpandActive"
|
|
669
|
-
:size="iconSize"
|
|
670
|
-
/>
|
|
671
|
-
<IconCollapse
|
|
672
|
-
v-else
|
|
673
|
-
:size="iconSize"
|
|
674
|
-
/>
|
|
675
|
-
</button>
|
|
676
|
-
<button
|
|
677
|
-
v-if="showFullscreenButton"
|
|
678
|
-
type="button"
|
|
679
|
-
class="mermaid-btn"
|
|
680
|
-
:title="fullscreenLabel"
|
|
681
|
-
:aria-label="fullscreenLabel"
|
|
682
|
-
@click="toggleFullscreen"
|
|
683
|
-
>
|
|
684
|
-
<IconFullScreen
|
|
685
|
-
v-if="!isFullscreen"
|
|
686
|
-
:size="iconSize"
|
|
687
|
-
/>
|
|
688
|
-
<IconFullScreenExit
|
|
689
|
-
v-else
|
|
690
|
-
:size="iconSize"
|
|
691
|
-
/>
|
|
692
|
-
</button>
|
|
693
|
-
</div>
|
|
694
|
-
</div>
|
|
695
|
-
|
|
696
|
-
<MermaidZoomToolbar
|
|
697
|
-
v-if="isFullscreen"
|
|
698
|
-
variant="fullscreen"
|
|
699
|
-
:scale="fullscreenZoom.scale.value"
|
|
700
|
-
:icon-size="iconSize"
|
|
701
|
-
@zoom-out="fullscreenZoom.zoomOut()"
|
|
702
|
-
@zoom-in="fullscreenZoom.zoomIn()"
|
|
703
|
-
@reset="fullscreenZoom.reset()"
|
|
704
|
-
/>
|
|
705
|
-
|
|
706
|
-
<!-- Fullscreen Zoom Hint -->
|
|
707
|
-
<Transition name="ncm-hint-fade">
|
|
708
|
-
<div
|
|
709
|
-
v-if="showFullscreenZoomHint && isFullscreen"
|
|
710
|
-
class="ncm-fullscreen-zoom-hint"
|
|
711
|
-
>
|
|
712
|
-
{{ isMac ? "\u2318" : "Ctrl" }} + Scroll to zoom
|
|
713
|
-
</div>
|
|
714
|
-
</Transition>
|
|
715
|
-
|
|
716
|
-
<!-- Diagram Container -->
|
|
717
|
-
<div
|
|
718
|
-
class="mermaid-wrapper"
|
|
719
|
-
:class="{
|
|
720
|
-
'ncm-is-loading': isLoading && !hasRenderedOnce,
|
|
721
|
-
'ncm-expand-hidden': isExpandActive,
|
|
722
|
-
'ncm-expand-clickable': expandEnabled && allowOpenDiagramClick && !isFullscreen,
|
|
723
|
-
'ncm-fullscreen-zoom': isFullscreen
|
|
724
|
-
}"
|
|
725
|
-
:style="cursorVariables"
|
|
726
|
-
@click="handleMermaidClick"
|
|
727
|
-
@mousedown="isFullscreen && fullscreenZoom.handleDragStart($event)"
|
|
728
|
-
@mousemove="isFullscreen && fullscreenZoom.handleDragMove($event)"
|
|
729
|
-
@touchstart="isFullscreen && fullscreenZoom.handleDragStart($event)"
|
|
730
|
-
@touchmove="isFullscreen && fullscreenZoom.handleDragMove($event)"
|
|
731
|
-
@touchend="isFullscreen && fullscreenZoom.handleDragEnd()"
|
|
170
|
+
<slot>
|
|
171
|
+
<pre v-if="decodedCode"><code>{{ decodedCode }}</code></pre>
|
|
172
|
+
</slot>
|
|
173
|
+
<template
|
|
174
|
+
v-if="$slots.loading"
|
|
175
|
+
#loading
|
|
732
176
|
>
|
|
733
|
-
<
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
</div>
|
|
747
|
-
|
|
748
|
-
<template v-if="isLoading && !hasRenderedOnce">
|
|
749
|
-
<slot name="loading">
|
|
750
|
-
<component :is="spinnerComponent" />
|
|
751
|
-
</slot>
|
|
752
|
-
</template>
|
|
753
|
-
<template v-else-if="hasError">
|
|
754
|
-
<slot
|
|
755
|
-
name="error"
|
|
756
|
-
:error="errorContent"
|
|
757
|
-
:source="mermaidDefinition"
|
|
758
|
-
>
|
|
759
|
-
<component
|
|
760
|
-
:is="errorComponent"
|
|
761
|
-
v-if="errorComponent"
|
|
762
|
-
:error="errorContent"
|
|
763
|
-
:source="mermaidDefinition"
|
|
764
|
-
/>
|
|
765
|
-
|
|
766
|
-
<div
|
|
767
|
-
v-else
|
|
768
|
-
class="mermaid-error-default"
|
|
769
|
-
>
|
|
770
|
-
⚠️ Mermaid Diagram Error
|
|
771
|
-
</div>
|
|
772
|
-
</slot>
|
|
773
|
-
</template>
|
|
774
|
-
</div>
|
|
775
|
-
|
|
776
|
-
<MermaidExpandOverlay
|
|
777
|
-
:active="isExpandActive"
|
|
778
|
-
:is-visable="isVisible"
|
|
779
|
-
:overlay-style="cursorVariables"
|
|
780
|
-
:content-style="cursorVariables"
|
|
781
|
-
:target-style="expandTargetStyle"
|
|
782
|
-
:target-ref="setExpandTargetWrap"
|
|
783
|
-
:allow-overlay-close="allowOverlayClose"
|
|
784
|
-
:allow-close-button="allowCloseButtonClose"
|
|
785
|
-
:target-has-margin="expandMargin > 0"
|
|
786
|
-
:icon-size="iconSize"
|
|
787
|
-
:zoom="zoom"
|
|
788
|
-
:show-hint="showZoomHint"
|
|
789
|
-
@close="closeExpand"
|
|
790
|
-
@transitionend="handleExpandTransitionEnd"
|
|
791
|
-
/>
|
|
792
|
-
</div>
|
|
177
|
+
<slot name="loading" />
|
|
178
|
+
</template>
|
|
179
|
+
<template
|
|
180
|
+
v-if="$slots.error"
|
|
181
|
+
#error="{ error, source }"
|
|
182
|
+
>
|
|
183
|
+
<slot
|
|
184
|
+
name="error"
|
|
185
|
+
:error="error"
|
|
186
|
+
:source="source"
|
|
187
|
+
/>
|
|
188
|
+
</template>
|
|
189
|
+
</BuiltInRenderer>
|
|
793
190
|
</div>
|
|
794
191
|
</template>
|
|
795
|
-
|
|
796
|
-
<style scoped>
|
|
797
|
-
.mermaid-block{border:var(--ncm-border);border-radius:4px;flex-direction:column;margin:1rem 0;overflow:hidden;--ncm-toolbar-height:calc(var(--ncm-icon-size, 17px) + 20px)}.mermaid-block,.mermaid-toolbar{background-color:var(--ncm-code-bg);display:flex}.mermaid-toolbar{align-items:center;border-bottom:var(--ncm-border-bottom);border-top-left-radius:4px;border-top-right-radius:4px;justify-content:space-between;padding:.375rem .375rem .375rem .75rem;z-index:20}.mermaid-title{color:var(--ncm-text-muted);font-weight:500;letter-spacing:0;line-height:1.25;max-width:80%;overflow:hidden;padding:4px 0;text-overflow:ellipsis;white-space:nowrap}:fullscreen .mermaid-title{max-width:80%}.mermaid-actions{display:flex;gap:0;justify-self:flex-end}.mermaid-btn{align-items:center;background:var(--ncm-code-bg);border:none;border-radius:4px;color:var(--ncm-text-xmuted);cursor:pointer;display:flex;justify-content:center;line-height:1.25;padding:4px;transition:color .2s,background-color .2s}.mermaid-btn:hover{background-color:var(--ncm-code-bg-hover);color:var(--ncm-text)}.mermaid-btn:disabled{color:var(--ncm-text-xmuted);cursor:not-allowed;opacity:.6}.mermaid-wrapper{align-items:center;display:flex;justify-content:center;overflow:auto;padding:1rem;position:relative;--sb-thumb:rgba(0,0,0,.2);--sb-thumb-hover:rgba(0,0,0,.35)}.mermaid-wrapper>div{height:100%;width:100%}.mermaid-wrapper>div>:deep(svg){margin:0 auto}.mermaid-wrapper :deep(code),.mermaid-wrapper :deep(pre){height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;clip:rect(0,0,0,0);border:0;clip-path:inset(50%);white-space:nowrap}.mermaid:not([data-processed]){color:transparent;min-height:100px}:fullscreen .mermaid-wrapper{flex:1;overflow:hidden;touch-action:none}:fullscreen .mermaid-wrapper::-webkit-scrollbar{display:none}:fullscreen .mermaid-wrapper :deep(svg){height:100%!important;max-height:none!important;max-width:none!important;width:100%!important}:global(.dark .mermaid-wrapper){--sb-thumb:hsla(0,0%,100%,.2);--sb-thumb-hover:hsla(0,0%,100%,.35)}.mermaid-wrapper::-webkit-scrollbar{width:14px}.mermaid-wrapper::-webkit-scrollbar-track{background:hsla(0,0%,94%,.08)}:global(.dark .mermaid-wrapper::-webkit-scrollbar-track){background:hsla(0,0%,9%,.08)}.mermaid-wrapper::-webkit-scrollbar-corner{background:transparent}.mermaid-wrapper::-webkit-scrollbar-thumb{background-clip:content-box;background-color:var(--sb-thumb);border:3px solid transparent;border-radius:10px}.mermaid-wrapper::-webkit-scrollbar-thumb:hover{background-color:var(--sb-thumb-hover)}.mermaid-wrapper.ncm-is-loading{align-items:center;display:flex;justify-content:center;min-height:150px}.mermaid-wrapper.ncm-expand-hidden{opacity:0;pointer-events:none}.mermaid-wrapper.ncm-expand-clickable :deep(svg){cursor:var(--ncm-cursor-expand)}.ncm-fullscreen-zoom-hint{display:none}:fullscreen .ncm-fullscreen-zoom-hint{background-color:var(--ncm-hint-bg,rgba(0,0,0,.75));border-radius:var(--ncm-hint-radius,8px);color:var(--ncm-hint-text,#fff);display:block;font-size:20px;font-weight:500;left:50%;padding:12px 24px;pointer-events:none;position:absolute;top:50%;transform:translate(-50%,-50%);-webkit-user-select:none;-moz-user-select:none;user-select:none;white-space:nowrap;z-index:20}.ncm-hint-fade-enter-active,.ncm-hint-fade-leave-active{transition:opacity .2s ease}.ncm-hint-fade-enter-from,.ncm-hint-fade-leave-to{opacity:0}@media (prefers-reduced-motion:reduce){.ncm-hint-fade-enter-active,.ncm-hint-fade-leave-active{transition-duration:.01ms!important}}.mermaid-error-default{color:#f55;font-family:monospace;padding:1rem;white-space:pre-wrap}
|
|
798
|
-
</style>
|