@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.
Files changed (62) hide show
  1. package/README.md +47 -502
  2. package/README.zh-TW.md +46 -509
  3. package/dist/module.d.mts +39 -53
  4. package/dist/module.json +3 -3
  5. package/dist/module.mjs +600 -155
  6. package/dist/runtime/built-in-renderer/BuiltInRenderer.d.vue.ts +0 -0
  7. package/dist/runtime/built-in-renderer/BuiltInRenderer.vue +597 -0
  8. package/dist/runtime/built-in-renderer/BuiltInRenderer.vue.d.ts +0 -0
  9. package/dist/runtime/component-configuration-error.d.ts +3 -0
  10. package/dist/runtime/component-configuration-error.js +7 -0
  11. package/dist/runtime/component-configuration.d.ts +23 -0
  12. package/dist/runtime/component-configuration.js +78 -0
  13. package/dist/runtime/components/ContentMermaidTransport.d.vue.ts +10 -0
  14. package/dist/runtime/components/ContentMermaidTransport.vue +21 -0
  15. package/dist/runtime/components/ContentMermaidTransport.vue.d.ts +10 -0
  16. package/dist/runtime/components/Mermaid.d.vue.ts +0 -31
  17. package/dist/runtime/components/Mermaid.vue +136 -743
  18. package/dist/runtime/components/Mermaid.vue.d.ts +0 -31
  19. package/dist/runtime/components/MermaidExpandOverlay.d.vue.ts +13 -16
  20. package/dist/runtime/components/MermaidExpandOverlay.vue +61 -53
  21. package/dist/runtime/components/MermaidExpandOverlay.vue.d.ts +13 -16
  22. package/dist/runtime/components/MermaidFullscreenPresentation.d.vue.ts +18 -0
  23. package/dist/runtime/components/MermaidFullscreenPresentation.vue +56 -0
  24. package/dist/runtime/components/MermaidFullscreenPresentation.vue.d.ts +18 -0
  25. package/dist/runtime/composables/useMermaidExpand.d.ts +16 -32
  26. package/dist/runtime/composables/useMermaidExpand.js +232 -75
  27. package/dist/runtime/composables/useMermaidFullscreen.d.ts +18 -0
  28. package/dist/runtime/composables/useMermaidFullscreen.js +242 -0
  29. package/dist/runtime/composables/useMermaidZoom.d.ts +5 -1
  30. package/dist/runtime/composables/useMermaidZoom.js +54 -28
  31. package/dist/runtime/configuration/core.d.ts +27 -0
  32. package/dist/runtime/configuration/core.js +340 -0
  33. package/dist/runtime/configuration/module.d.ts +17 -0
  34. package/dist/runtime/configuration/module.js +233 -0
  35. package/dist/runtime/configuration/runtime-options.d.ts +4 -0
  36. package/dist/runtime/configuration/runtime-options.js +57 -0
  37. package/dist/runtime/constants.d.ts +24 -0
  38. package/dist/runtime/constants.js +60 -2
  39. package/dist/runtime/direct-mermaid-config.d.ts +5 -0
  40. package/dist/runtime/direct-mermaid-config.js +216 -0
  41. package/dist/runtime/mermaid-config.d.ts +9 -5
  42. package/dist/runtime/mermaid-config.js +30 -19
  43. package/dist/runtime/mermaid-rendering.d.ts +28 -0
  44. package/dist/runtime/mermaid-rendering.js +140 -0
  45. package/dist/runtime/plugins/content-mermaid-transport.d.ts +2 -0
  46. package/dist/runtime/plugins/content-mermaid-transport.js +5 -0
  47. package/dist/runtime/plugins/mermaid.client.d.ts +0 -3
  48. package/dist/runtime/plugins/mermaid.client.js +14 -32
  49. package/dist/runtime/plugins/runtime-config.d.ts +2 -0
  50. package/dist/runtime/plugins/runtime-config.js +9 -0
  51. package/dist/runtime/rendererSelection.d.ts +43 -0
  52. package/dist/runtime/rendererSelection.js +36 -0
  53. package/dist/runtime/rendererSelectionOrchestration.d.ts +12 -0
  54. package/dist/runtime/rendererSelectionOrchestration.js +13 -0
  55. package/dist/runtime/runtime-snapshot.d.ts +3 -0
  56. package/dist/runtime/runtime-snapshot.js +26 -0
  57. package/dist/runtime/utils/index.d.ts +0 -4
  58. package/dist/runtime/utils/index.js +0 -37
  59. package/dist/types.d.mts +2 -2
  60. package/package.json +38 -32
  61. package/dist/runtime/utils/mermaid-transform.d.ts +0 -18
  62. package/dist/runtime/utils/mermaid-transform.js +0 -345
package/dist/module.mjs CHANGED
@@ -1,152 +1,448 @@
1
- import { defineNuxtModule, useLogger, createResolver, addVitePlugin, addPlugin, addComponent, addImports, addTypeTemplate } from '@nuxt/kit';
2
- import { defu } from 'defu';
3
- import { DEFAULT_MERMAID_CONFIG, DEFAULT_TOOLBAR_OPTIONS, DEFAULT_EXPAND_OPTIONS, DEFAULT_DARK_THEME, DEFAULT_LIGHT_THEME, DEFAULT_FRONTMATTER_CONFIG_KEY } from '../dist/runtime/constants.js';
4
- import { parseInlineAttrs, parseMermaidFrontmatter, extractMermaidInlineOverrides, applyMermaidFrontmatterOverrides, mergeToolbarProps, extractToolbarProps } from '../dist/runtime/utils/mermaid-transform.js';
5
- import { isNonEmptyRecord, stringifyInlineValue } from '../dist/runtime/utils/index.js';
1
+ import { defineNuxtModule, createResolver, addVitePlugin, addPlugin, addComponent, addImports, addTypeTemplate } from '@nuxt/kit';
2
+ import { cloneOwnedData, mergeByPresence, assertStrictData, ContentMermaidConfigurationError } from '../dist/runtime/configuration/core.js';
3
+ import { resolveModuleConfiguration } from '../dist/runtime/configuration/module.js';
4
+ import destr from 'destr';
5
+ import { parse, stringify } from 'yaml';
6
+ import { isRecord, isNonEmptyRecord } from '../dist/runtime/utils/is.js';
6
7
 
7
- const MODULE_NAME = "@barzhsieh/nuxt-content-mermaid";
8
- const MERMAID_OPTIMIZE_DEPS = [
9
- "mermaid",
10
- "@braintree/sanitize-url",
11
- "dayjs",
12
- "dayjs/plugin/isoWeek.js",
13
- "dayjs/plugin/customParseFormat.js",
14
- "dayjs/plugin/advancedFormat.js",
15
- "dayjs/plugin/duration.js"
16
- ];
17
- const MERMAID_FENCE_RE = /^[ \t]*(?:`{3,}|~{3,})[ \t]*mermaid(?:$|[ \t{[])/im;
18
- const DEFAULTS = {
19
- enabled: true,
20
- loader: {
21
- init: { ...DEFAULT_MERMAID_CONFIG },
22
- lazy: true
23
- },
24
- theme: {
25
- light: DEFAULT_LIGHT_THEME,
26
- dark: DEFAULT_DARK_THEME
27
- },
28
- components: {
29
- renderer: void 0,
30
- spinner: void 0,
31
- error: void 0
32
- },
33
- expand: DEFAULT_EXPAND_OPTIONS,
34
- toolbar: DEFAULT_TOOLBAR_OPTIONS
35
- };
36
- const module$1 = defineNuxtModule({
37
- meta: {
38
- name: MODULE_NAME,
39
- configKey: "contentMermaid",
40
- compatibility: {
41
- nuxt: "^3.20.1 || ^4.1.0"
8
+ const UNSAFE_INLINE_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
9
+ function inspectMermaidFrontmatter(rawCode, newline) {
10
+ const lines = rawCode.split(newline);
11
+ let startIndex = 0;
12
+ while (startIndex < lines.length) {
13
+ const line = lines[startIndex];
14
+ const trimmed = line.trim();
15
+ if (!trimmed) {
16
+ startIndex += 1;
17
+ continue;
42
18
  }
43
- },
44
- defaults: {
45
- ...DEFAULTS
46
- },
47
- setup(options, nuxt) {
48
- const logger = useLogger("nuxt-content-mermaid");
49
- const warn = (message) => logger.warn(message);
50
- const deprecatedOptions = nuxt.options.mermaidContent;
51
- const hasDeprecatedOptions = deprecatedOptions && typeof deprecatedOptions === "object" && Object.keys(deprecatedOptions).length > 0;
52
- if (hasDeprecatedOptions)
53
- warn("[nuxt-content-mermaid] `mermaidContent` is deprecated, please switch to `contentMermaid`. The old key is still read for now but will be removed in a future release.");
54
- const resolvedOptions = defu(
55
- {},
56
- options,
57
- deprecatedOptions,
58
- DEFAULTS
59
- );
60
- const resolver = createResolver(import.meta.url);
61
- const runtimeDir = resolver.resolve("./runtime");
62
- nuxt.options.css ||= [];
63
- nuxt.options.css.push(resolver.resolve("./runtime/styles.css"));
64
- const publicRuntimeConfig = nuxt.options.runtimeConfig.public;
65
- const runtimeOverrides = publicRuntimeConfig.contentMermaid || publicRuntimeConfig.mermaidContent || {};
66
- if (!publicRuntimeConfig.contentMermaid && publicRuntimeConfig.mermaidContent)
67
- warn("[nuxt-content-mermaid] `runtimeConfig.public.mermaidContent` is deprecated, please use `runtimeConfig.public.contentMermaid` instead.");
68
- const runtimeMermaidConfig = defu(
69
- {},
70
- runtimeOverrides,
71
- resolvedOptions
72
- );
73
- publicRuntimeConfig.contentMermaid = runtimeMermaidConfig;
74
- publicRuntimeConfig.mermaidContent = runtimeMermaidConfig;
75
- const baseMermaidComponentName = "Mermaid";
76
- const isEnabled = runtimeMermaidConfig.enabled !== false;
77
- if (!isEnabled) return;
78
- addVitePlugin(() => ({
79
- name: "nuxt-content-mermaid:optimize-deps",
80
- configEnvironment(name, config) {
81
- if (name === "client") {
82
- config.optimizeDeps ||= {};
83
- config.optimizeDeps.include ||= [];
84
- config.optimizeDeps.include.push(
85
- ...MERMAID_OPTIMIZE_DEPS.map((dep) => `${MODULE_NAME} > ${dep}`)
86
- );
87
- }
19
+ if (isMermaidDirectiveStart(line)) {
20
+ startIndex = skipMermaidDirective(lines, startIndex);
21
+ continue;
22
+ }
23
+ if (isMermaidCommentLine(line)) {
24
+ startIndex += 1;
25
+ continue;
26
+ }
27
+ break;
28
+ }
29
+ if (startIndex >= lines.length)
30
+ return { kind: "absent" };
31
+ const startLine = lines[startIndex];
32
+ if (startLine.trim() !== "---")
33
+ return { kind: "absent" };
34
+ const indentMatch = startLine.match(/^\s*/);
35
+ const indent = indentMatch ? indentMatch[0] : "";
36
+ let endIndex = -1;
37
+ for (let i = startIndex + 1; i < lines.length; i++) {
38
+ if (lines[i].trim() === "---") {
39
+ endIndex = i;
40
+ break;
41
+ }
42
+ }
43
+ if (endIndex === -1)
44
+ return { kind: "invalid" };
45
+ const yamlLines = lines.slice(startIndex + 1, endIndex).map((line) => line.startsWith(indent) ? line.slice(indent.length) : line);
46
+ const yamlRaw = yamlLines.join("\n").trim();
47
+ try {
48
+ const data = yamlRaw ? parse(yamlRaw) : {};
49
+ if (!isRecord(data))
50
+ return { kind: "invalid" };
51
+ return {
52
+ kind: "valid",
53
+ info: {
54
+ data,
55
+ startIndex,
56
+ endIndex,
57
+ indent
88
58
  }
89
- }));
90
- addPlugin({
91
- src: resolver.resolve(runtimeDir, "plugins/mermaid.client"),
92
- mode: "client"
93
- });
94
- addComponent({
95
- name: baseMermaidComponentName,
96
- filePath: resolver.resolve(runtimeDir, "components/Mermaid.vue")
97
- });
98
- addImports([
99
- {
100
- name: "useMermaidTheme",
101
- as: "useMermaidTheme",
102
- from: resolver.resolve(runtimeDir, "composables/useMermaidTheme")
59
+ };
60
+ } catch {
61
+ return { kind: "invalid" };
62
+ }
63
+ }
64
+ function isMermaidDirectiveStart(line) {
65
+ return line.trim().startsWith("%%{");
66
+ }
67
+ function isMermaidCommentLine(line) {
68
+ const trimmed = line.trim();
69
+ return trimmed.startsWith("%%") && !trimmed.startsWith("%%{");
70
+ }
71
+ function skipMermaidDirective(lines, startIndex) {
72
+ let index = startIndex;
73
+ while (index < lines.length) {
74
+ if (lines[index].includes("}%%"))
75
+ return index + 1;
76
+ index += 1;
77
+ }
78
+ return lines.length;
79
+ }
80
+ function parseInlineAttrs(info) {
81
+ const attrsBlock = extractInlineAttrsBlock(info);
82
+ if (attrsBlock === null)
83
+ return null;
84
+ return parseInlineAttrsBlock(attrsBlock);
85
+ }
86
+ function extractInlineAttrsBlock(info) {
87
+ const startIndex = info.indexOf("{");
88
+ if (startIndex === -1)
89
+ return null;
90
+ let depth = 0;
91
+ let quote = null;
92
+ for (let i = startIndex; i < info.length; i++) {
93
+ const char = info[i];
94
+ if (quote) {
95
+ if (char === quote && info[i - 1] !== "\\")
96
+ quote = null;
97
+ continue;
98
+ }
99
+ if (char === '"' || char === "'") {
100
+ quote = char;
101
+ continue;
102
+ }
103
+ if (char === "{") {
104
+ depth += 1;
105
+ continue;
106
+ }
107
+ if (char === "}") {
108
+ depth -= 1;
109
+ if (depth === 0)
110
+ return info.slice(startIndex + 1, i);
111
+ }
112
+ }
113
+ return null;
114
+ }
115
+ function parseInlineAttrsBlock(raw) {
116
+ const result = {};
117
+ let index = 0;
118
+ while (index < raw.length) {
119
+ index = skipSeparators(raw, index);
120
+ if (index >= raw.length)
121
+ break;
122
+ const keyStart = index;
123
+ while (index < raw.length && isAttrKeyChar(raw[index]))
124
+ index += 1;
125
+ const key = raw.slice(keyStart, index).trim();
126
+ if (!key)
127
+ break;
128
+ index = skipWhitespace(raw, index);
129
+ let value = true;
130
+ if (index < raw.length && (raw[index] === "=" || raw[index] === ":")) {
131
+ index += 1;
132
+ index = skipWhitespace(raw, index);
133
+ const { value: rawValue, nextIndex, quoted } = readAttributeValue(raw, index);
134
+ index = nextIndex;
135
+ value = parseAttributeValue(rawValue, quoted);
136
+ }
137
+ if (!setNestedValue(result, key, value))
138
+ return null;
139
+ }
140
+ return result;
141
+ }
142
+ function skipWhitespace(input, index) {
143
+ let cursor = index;
144
+ while (cursor < input.length) {
145
+ const char = input[cursor];
146
+ if (char !== " " && char !== " " && char !== "\n" && char !== "\r")
147
+ break;
148
+ cursor += 1;
149
+ }
150
+ return cursor;
151
+ }
152
+ function skipSeparators(input, index) {
153
+ let cursor = index;
154
+ while (cursor < input.length) {
155
+ const char = input[cursor];
156
+ if (char !== " " && char !== " " && char !== "\n" && char !== "\r" && char !== ";")
157
+ break;
158
+ cursor += 1;
159
+ }
160
+ return cursor;
161
+ }
162
+ function isAttrKeyChar(char) {
163
+ return /[\w.-]/.test(char);
164
+ }
165
+ function readAttributeValue(input, start) {
166
+ const char = input[start];
167
+ if (char === '"' || char === "'")
168
+ return readQuotedValue(input, start);
169
+ if (char === "{" || char === "[")
170
+ return readBalancedValue(input, start);
171
+ let end = start;
172
+ while (end < input.length) {
173
+ const current = input[end];
174
+ if (current === ";" || current === " " || current === " " || current === "\n" || current === "\r")
175
+ break;
176
+ end += 1;
177
+ }
178
+ return { value: input.slice(start, end), nextIndex: end, quoted: false };
179
+ }
180
+ function readQuotedValue(input, start) {
181
+ const quote = input[start];
182
+ let cursor = start + 1;
183
+ let result = "";
184
+ while (cursor < input.length) {
185
+ const char = input[cursor];
186
+ if (char === "\\" && cursor + 1 < input.length) {
187
+ result += input[cursor + 1];
188
+ cursor += 2;
189
+ continue;
190
+ }
191
+ if (char === quote) {
192
+ return { value: result, nextIndex: cursor + 1, quoted: true };
193
+ }
194
+ result += char;
195
+ cursor += 1;
196
+ }
197
+ return { value: result, nextIndex: cursor, quoted: true };
198
+ }
199
+ function readBalancedValue(input, start) {
200
+ const open = input[start];
201
+ const close = open === "{" ? "}" : "]";
202
+ let depth = 0;
203
+ let quote = null;
204
+ for (let i = start; i < input.length; i++) {
205
+ const char = input[i];
206
+ if (quote) {
207
+ if (char === quote && input[i - 1] !== "\\")
208
+ quote = null;
209
+ continue;
210
+ }
211
+ if (char === '"' || char === "'") {
212
+ quote = char;
213
+ continue;
214
+ }
215
+ if (char === open) {
216
+ depth += 1;
217
+ continue;
218
+ }
219
+ if (char === close) {
220
+ depth -= 1;
221
+ if (depth === 0) {
222
+ return { value: input.slice(start, i + 1), nextIndex: i + 1, quoted: false };
103
223
  }
104
- ]);
105
- addTypeTemplate({
106
- filename: "types/mermaid-content.d.ts",
107
- getContents: () => `
108
- import type { Mermaid } from 'mermaid'
224
+ }
225
+ }
226
+ return { value: input.slice(start), nextIndex: input.length, quoted: false };
227
+ }
228
+ function parseAttributeValue(raw, quoted) {
229
+ const value = raw.trim();
230
+ if (!value)
231
+ return "";
232
+ const isStructured = value.startsWith("{") || value.startsWith("[");
233
+ if (quoted) {
234
+ if (!isStructured)
235
+ return value;
236
+ return parseStructuredValue(value);
237
+ }
238
+ if (isStructured)
239
+ return parseStructuredValue(value);
240
+ return destr(value);
241
+ }
242
+ function parseStructuredValue(value) {
243
+ const parsed = destr(value);
244
+ if (parsed !== value)
245
+ return parsed;
246
+ try {
247
+ const yamlParsed = parse(value);
248
+ if (isRecord(yamlParsed) || Array.isArray(yamlParsed))
249
+ return yamlParsed;
250
+ } catch {
251
+ }
252
+ return value;
253
+ }
254
+ function setNestedValue(target, path, value) {
255
+ const keys = path.split(".").filter(Boolean);
256
+ if (!keys.length)
257
+ return false;
258
+ let current = target;
259
+ for (let i = 0; i < keys.length; i++) {
260
+ const key = keys[i];
261
+ if (UNSAFE_INLINE_KEYS.has(key))
262
+ return false;
263
+ if (i === keys.length - 1) {
264
+ current[key] = value;
265
+ return true;
266
+ }
267
+ const existing = current[key];
268
+ if (!isRecord(existing)) {
269
+ current[key] = {};
270
+ }
271
+ current = current[key];
272
+ }
273
+ return true;
274
+ }
275
+ function applyResolvedMermaidFrontmatter(rawCode, newline, frontmatterInfo, frontmatter, fallbackIndent) {
276
+ const frontmatterLines = serializeMermaidFrontmatter(
277
+ frontmatter,
278
+ frontmatterInfo?.indent ?? fallbackIndent
279
+ );
280
+ const lines = rawCode.split(newline);
281
+ if (frontmatterInfo) {
282
+ const before = lines.slice(0, frontmatterInfo.startIndex);
283
+ const after = lines.slice(frontmatterInfo.endIndex + 1);
284
+ return [...frontmatterLines, ...before, ...after].join(newline);
285
+ }
286
+ return [
287
+ ...frontmatterLines,
288
+ ...lines
289
+ ].join(newline);
290
+ }
291
+ function serializeMermaidFrontmatter(frontmatter, indent) {
292
+ const yaml = stringify(frontmatter).trim();
293
+ const yamlLines = yaml ? yaml.split("\n").map((line) => `${indent}${line}`) : [];
294
+ const delimiter = `${indent}---`;
295
+ return [
296
+ delimiter,
297
+ ...yamlLines,
298
+ delimiter
299
+ ];
300
+ }
109
301
 
110
- declare module '#app' {
111
- interface NuxtApp {
112
- $mermaid: () => Promise<Mermaid>
302
+ const MARKDOWN_METADATA_PHASE = {
303
+ name: "Markdown Mermaid Metadata",
304
+ root: "mermaid"
305
+ };
306
+ const UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
307
+ const INLINE_ATTRIBUTE_KEYS = /* @__PURE__ */ new Set(["title", "config", "toolbar"]);
308
+ const TOOLBAR_KEYS = /* @__PURE__ */ new Set(["title", "fontSize", "fullscreenToolbarScale", "buttons"]);
309
+ const TOOLBAR_BUTTON_KEYS = /* @__PURE__ */ new Set(["copy", "fullscreen", "expand"]);
310
+ function hasOwn(value, key) {
311
+ return Object.prototype.hasOwnProperty.call(value, key);
312
+ }
313
+ function valueAt(value, key) {
314
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
315
+ return descriptor && "value" in descriptor ? descriptor.value : void 0;
316
+ }
317
+ function isPlainRecord(value) {
318
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
319
+ const prototype = Object.getPrototypeOf(value);
320
+ return prototype === Object.prototype || prototype === null;
321
+ }
322
+ function hasUnsafeKeys(value) {
323
+ if (value === null || typeof value !== "object") return false;
324
+ const descriptors = Object.getOwnPropertyDescriptors(value);
325
+ for (const key of Object.keys(descriptors)) {
326
+ if (UNSAFE_KEYS.has(key)) return true;
327
+ const descriptor = descriptors[key];
328
+ if (descriptor && "value" in descriptor && hasUnsafeKeys(descriptor.value))
329
+ return true;
113
330
  }
331
+ return false;
114
332
  }
115
- declare module 'vue' {
116
- interface ComponentCustomProperties {
117
- $mermaid: () => Promise<Mermaid>
333
+ function normalizeRecord(value) {
334
+ try {
335
+ assertStrictData(value, MARKDOWN_METADATA_PHASE);
336
+ } catch (error) {
337
+ if (error instanceof ContentMermaidConfigurationError) return null;
338
+ throw error;
118
339
  }
340
+ if (!isPlainRecord(value) || hasUnsafeKeys(value)) return null;
341
+ return cloneOwnedData(value);
119
342
  }
120
- export {}
121
- `
122
- });
123
- nuxt.hook("content:file:beforeParse", (ctx) => {
124
- const { file } = ctx;
125
- if (!file.id?.endsWith(".md")) return;
126
- if (!MERMAID_FENCE_RE.test(file.body)) return;
127
- file.body = transformMermaidCodeBlocks(
128
- file.body,
129
- baseMermaidComponentName
130
- );
131
- });
343
+ function hasOnlyKeys(value, allowedKeys) {
344
+ return Object.keys(Object.getOwnPropertyDescriptors(value)).every((key) => allowedKeys.has(key));
345
+ }
346
+ function isStringifiableScalar(value) {
347
+ return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
348
+ }
349
+ function normalizeToolbar(value) {
350
+ const toolbar = normalizeRecord(value);
351
+ if (!toolbar || !hasOnlyKeys(toolbar, TOOLBAR_KEYS)) return null;
352
+ const title = valueAt(toolbar, "title");
353
+ if (title !== void 0 && typeof title !== "string") return null;
354
+ const fontSize = valueAt(toolbar, "fontSize");
355
+ if (fontSize !== void 0 && typeof fontSize !== "string" && typeof fontSize !== "number") return null;
356
+ const fullscreenToolbarScale = valueAt(toolbar, "fullscreenToolbarScale");
357
+ if (fullscreenToolbarScale !== void 0 && typeof fullscreenToolbarScale !== "number") return null;
358
+ const buttons = valueAt(toolbar, "buttons");
359
+ if (buttons !== void 0) {
360
+ if (!isPlainRecord(buttons) || !hasOnlyKeys(buttons, TOOLBAR_BUTTON_KEYS)) return null;
361
+ for (const key of TOOLBAR_BUTTON_KEYS) {
362
+ const button = valueAt(buttons, key);
363
+ if (button !== void 0 && typeof button !== "boolean") return null;
364
+ }
132
365
  }
133
- });
134
- function transformMermaidCodeBlocks(body, componentName, frontmatterConfigKey = DEFAULT_FRONTMATTER_CONFIG_KEY) {
366
+ return toolbar;
367
+ }
368
+ function resolveDiagramMermaidConfig(value) {
369
+ return normalizeRecord(value);
370
+ }
371
+ function resolveFenceInlineAttributes(value) {
372
+ if (value === null) return null;
373
+ const attrs = normalizeRecord(value);
374
+ if (!attrs || !hasOnlyKeys(attrs, INLINE_ATTRIBUTE_KEYS)) return null;
375
+ const normalized = cloneOwnedData(attrs);
376
+ for (const key of ["title"]) {
377
+ if (!hasOwn(attrs, key)) continue;
378
+ const attribute = valueAt(attrs, key);
379
+ if (attribute === void 0 || !isStringifiableScalar(attribute)) return null;
380
+ normalized[key] = String(attribute);
381
+ }
382
+ if (hasOwn(attrs, "config")) {
383
+ const config = resolveDiagramMermaidConfig(valueAt(attrs, "config"));
384
+ if (!config) return null;
385
+ normalized.config = config;
386
+ }
387
+ if (hasOwn(attrs, "toolbar")) {
388
+ const toolbar = resolveMarkdownToolbar([valueAt(attrs, "toolbar")]);
389
+ if (toolbar === null || toolbar === void 0) return null;
390
+ normalized.toolbar = toolbar;
391
+ }
392
+ return normalized;
393
+ }
394
+ function resolveMarkdownFrontmatter(layers) {
395
+ const normalizedLayers = [];
396
+ for (const layer of layers) {
397
+ if (layer === void 0) continue;
398
+ const normalized = normalizeRecord(layer);
399
+ if (!normalized) return null;
400
+ if (hasOwn(normalized, "config")) {
401
+ const config = resolveDiagramMermaidConfig(valueAt(normalized, "config"));
402
+ if (!config) return null;
403
+ normalized.config = config;
404
+ }
405
+ if (hasOwn(normalized, "toolbar")) {
406
+ const toolbar = resolveMarkdownToolbar([valueAt(normalized, "toolbar")]);
407
+ if (toolbar === null || toolbar === void 0) return null;
408
+ normalized.toolbar = toolbar;
409
+ }
410
+ normalizedLayers.push(normalized);
411
+ }
412
+ return mergeByPresence(normalizedLayers);
413
+ }
414
+ function resolveMarkdownToolbar(layers) {
415
+ const normalizedLayers = [];
416
+ for (const layer of layers) {
417
+ if (layer === void 0) continue;
418
+ const toolbar = normalizeToolbar(layer);
419
+ if (!toolbar) return null;
420
+ normalizedLayers.push(toolbar);
421
+ }
422
+ if (!normalizedLayers.length) return void 0;
423
+ return mergeByPresence(normalizedLayers);
424
+ }
425
+
426
+ const MERMAID_COMPONENT_NAME = "ContentMermaidTransport";
427
+ const PAGE_MERMAID_CONFIG_EXPRESSION = "config";
428
+ function serializeInlineAttributeRecord(value) {
429
+ return JSON.stringify(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/'/g, "&#39;");
430
+ }
431
+ function isClosingFence(line, markerChar, markerLength) {
432
+ const trimmed = line.trim();
433
+ if (trimmed.length < markerLength) return false;
434
+ for (const char of trimmed) {
435
+ if (char !== markerChar) return false;
436
+ }
437
+ return true;
438
+ }
439
+ function transformMarkdownDiagrams(body) {
135
440
  const newline = body.includes("\r\n") ? "\r\n" : "\n";
136
441
  const lines = body.split(newline);
137
442
  const output = [];
138
443
  let inFence = false;
139
444
  let fenceChar = null;
140
445
  let fenceLength = 0;
141
- const isClosingFence = (line) => {
142
- if (!fenceChar) return false;
143
- const trimmed = line.trim();
144
- if (trimmed.length < fenceLength) return false;
145
- for (const char of trimmed) {
146
- if (char !== fenceChar) return false;
147
- }
148
- return true;
149
- };
150
446
  const isFenceMarkerChar = (char) => char === "`" || char === "~";
151
447
  const parseFence = (line) => {
152
448
  let index = 0;
@@ -178,19 +474,19 @@ function transformMermaidCodeBlocks(body, componentName, frontmatterConfigKey =
178
474
  };
179
475
  const buildComponentTag = (encoded, toolbar) => {
180
476
  const attrs = [
181
- `:config="${frontmatterConfigKey}"`
477
+ `:page-config="${PAGE_MERMAID_CONFIG_EXPRESSION}"`
182
478
  ];
183
479
  if (toolbar && isNonEmptyRecord(toolbar)) {
184
- attrs.push(`:toolbar='${stringifyInlineValue(toolbar)}'`);
480
+ attrs.push(`:toolbar='${serializeInlineAttributeRecord(toolbar)}'`);
185
481
  }
186
482
  attrs.push(`code="${encoded}"`);
187
- return `<${componentName} ${attrs.join(" ")}></${componentName}>`;
483
+ return `<${MERMAID_COMPONENT_NAME} ${attrs.join(" ")}></${MERMAID_COMPONENT_NAME}>`;
188
484
  };
189
485
  for (let index = 0; index < lines.length; index++) {
190
486
  const line = lines[index];
191
487
  if (inFence) {
192
488
  output.push(line);
193
- if (isClosingFence(line)) {
489
+ if (fenceChar && isClosingFence(line, fenceChar, fenceLength)) {
194
490
  inFence = false;
195
491
  fenceChar = null;
196
492
  fenceLength = 0;
@@ -211,16 +507,7 @@ function transformMermaidCodeBlocks(body, componentName, frontmatterConfigKey =
211
507
  }
212
508
  let closingIndex = -1;
213
509
  for (let j = index + 1; j < lines.length; j++) {
214
- const trimmed = lines[j].trim();
215
- if (trimmed.length < fence.markerLength) continue;
216
- let isFence = true;
217
- for (const char of trimmed) {
218
- if (char !== fence.markerChar) {
219
- isFence = false;
220
- break;
221
- }
222
- }
223
- if (isFence) {
510
+ if (isClosingFence(lines[j], fence.markerChar, fence.markerLength)) {
224
511
  closingIndex = j;
225
512
  break;
226
513
  }
@@ -237,13 +524,39 @@ function transformMermaidCodeBlocks(body, componentName, frontmatterConfigKey =
237
524
  continue;
238
525
  }
239
526
  const inlineAttrs = parseInlineAttrs(fence.info);
240
- const frontmatterInfo = parseMermaidFrontmatter(rawCode, newline);
241
- const inlineOverrides = extractMermaidInlineOverrides(inlineAttrs);
242
- const mergedCode = inlineOverrides ? applyMermaidFrontmatterOverrides(rawCode, newline, frontmatterInfo, inlineOverrides, fence.indent) : rawCode;
243
- const toolbarProps = mergeToolbarProps(
244
- extractToolbarProps(frontmatterInfo?.data),
245
- extractToolbarProps(inlineAttrs)
246
- );
527
+ if (fence.info.includes("{") && !inlineAttrs) {
528
+ output.push(...lines.slice(index, closingIndex + 1));
529
+ index = closingIndex;
530
+ continue;
531
+ }
532
+ const frontmatterResult = inspectMermaidFrontmatter(rawCode, newline);
533
+ if (frontmatterResult.kind === "invalid") {
534
+ output.push(...lines.slice(index, closingIndex + 1));
535
+ index = closingIndex;
536
+ continue;
537
+ }
538
+ const frontmatterInfo = frontmatterResult.kind === "valid" ? frontmatterResult.info : null;
539
+ const inlineOverrides = resolveFenceInlineAttributes(inlineAttrs);
540
+ if (inlineAttrs && !inlineOverrides) {
541
+ output.push(...lines.slice(index, closingIndex + 1));
542
+ index = closingIndex;
543
+ continue;
544
+ }
545
+ const resolvedFrontmatter = resolveMarkdownFrontmatter([
546
+ frontmatterInfo?.data,
547
+ inlineOverrides ?? void 0
548
+ ]);
549
+ const toolbarProps = resolveMarkdownToolbar([
550
+ frontmatterInfo?.data.toolbar,
551
+ inlineOverrides?.toolbar
552
+ ]);
553
+ if (resolvedFrontmatter === null || toolbarProps === null) {
554
+ output.push(...lines.slice(index, closingIndex + 1));
555
+ index = closingIndex;
556
+ continue;
557
+ }
558
+ const hasInlineOverrides = inlineOverrides && Object.keys(inlineOverrides).length > 0;
559
+ const mergedCode = frontmatterInfo || hasInlineOverrides ? applyResolvedMermaidFrontmatter(rawCode, newline, frontmatterInfo, resolvedFrontmatter, fence.indent) : rawCode;
247
560
  const encoded = encodeURIComponent(mergedCode.trim());
248
561
  output.push(`${fence.indent}${buildComponentTag(encoded, toolbarProps)}`);
249
562
  index = closingIndex;
@@ -251,4 +564,136 @@ function transformMermaidCodeBlocks(body, componentName, frontmatterConfigKey =
251
564
  return output.join(newline);
252
565
  }
253
566
 
254
- export { module$1 as default, transformMermaidCodeBlocks };
567
+ const MODULE_NAME = "@barzhsieh/nuxt-content-mermaid";
568
+ const MERMAID_OPTIMIZE_DEPS = [
569
+ "mermaid",
570
+ "@braintree/sanitize-url",
571
+ "dayjs",
572
+ "dayjs/plugin/isoWeek.js",
573
+ "dayjs/plugin/customParseFormat.js",
574
+ "dayjs/plugin/advancedFormat.js",
575
+ "dayjs/plugin/duration.js"
576
+ ];
577
+ const LEGACY_ALIAS_PHASE = {
578
+ name: "Nuxt Module Configuration",
579
+ root: "nuxt.options"
580
+ };
581
+ function throwMigrationError(path, expected, received) {
582
+ throw new ContentMermaidConfigurationError(LEGACY_ALIAS_PHASE, [{
583
+ path,
584
+ code: "UNEXPECTED_PROPERTY",
585
+ expected,
586
+ received
587
+ }], false);
588
+ }
589
+ function assertNoLegacyModuleAlias(nuxtOptions) {
590
+ if (Object.getOwnPropertyDescriptor(nuxtOptions, "mermaidContent")) {
591
+ throwMigrationError(["mermaidContent"], "the supported contentMermaid key", "removed legacy alias");
592
+ }
593
+ }
594
+ function readRuntimeOverrides(publicRuntimeConfig) {
595
+ const descriptor = Object.getOwnPropertyDescriptor(publicRuntimeConfig, "contentMermaid");
596
+ if (!descriptor) return void 0;
597
+ if (!("value" in descriptor)) {
598
+ throwMigrationError(
599
+ ["runtimeConfig", "public", "contentMermaid"],
600
+ "an enumerable data property",
601
+ "accessor"
602
+ );
603
+ }
604
+ if (!descriptor.enumerable) {
605
+ throwMigrationError(
606
+ ["runtimeConfig", "public", "contentMermaid"],
607
+ "an enumerable data property",
608
+ "non-enumerable-property"
609
+ );
610
+ }
611
+ return descriptor.value;
612
+ }
613
+ const module$1 = defineNuxtModule({
614
+ meta: {
615
+ name: MODULE_NAME,
616
+ configKey: "contentMermaid",
617
+ compatibility: {
618
+ nuxt: "^4.1.0"
619
+ }
620
+ },
621
+ moduleDependencies: {
622
+ "@nuxt/content": {
623
+ version: ">=3.5.0 <4.0.0"
624
+ }
625
+ },
626
+ setup(options, nuxt) {
627
+ assertNoLegacyModuleAlias(nuxt.options);
628
+ const publicRuntimeConfig = nuxt.options.runtimeConfig.public;
629
+ const resolved = resolveModuleConfiguration({
630
+ nuxtResolvedOptions: options,
631
+ runtimeOverrides: readRuntimeOverrides(publicRuntimeConfig)
632
+ });
633
+ if (!resolved.enabled) return;
634
+ publicRuntimeConfig.contentMermaid = resolved.runtimeOptions;
635
+ const resolver = createResolver(import.meta.url);
636
+ const runtimeDir = resolver.resolve("./runtime");
637
+ const baseMermaidComponentName = "Mermaid";
638
+ nuxt.options.css ||= [];
639
+ nuxt.options.css.push(resolver.resolve("./runtime/styles.css"));
640
+ addVitePlugin(() => ({
641
+ name: "nuxt-content-mermaid:optimize-deps",
642
+ configEnvironment(name, config) {
643
+ if (name === "client") {
644
+ config.optimizeDeps ||= {};
645
+ config.optimizeDeps.include ||= [];
646
+ config.optimizeDeps.include.push(
647
+ ...MERMAID_OPTIMIZE_DEPS.map((dep) => `${MODULE_NAME} > ${dep}`)
648
+ );
649
+ }
650
+ }
651
+ }));
652
+ addPlugin({
653
+ src: resolver.resolve(runtimeDir, "plugins/mermaid.client"),
654
+ mode: "client"
655
+ });
656
+ addPlugin({
657
+ src: resolver.resolve(runtimeDir, "plugins/content-mermaid-transport")
658
+ });
659
+ addPlugin({
660
+ src: resolver.resolve(runtimeDir, "plugins/runtime-config")
661
+ });
662
+ addComponent({
663
+ name: baseMermaidComponentName,
664
+ filePath: resolver.resolve(runtimeDir, "components/Mermaid.vue")
665
+ });
666
+ addImports([
667
+ {
668
+ name: "useMermaidTheme",
669
+ as: "useMermaidTheme",
670
+ from: resolver.resolve(runtimeDir, "composables/useMermaidTheme")
671
+ }
672
+ ]);
673
+ addTypeTemplate({
674
+ filename: "types/mermaid-content.d.ts",
675
+ getContents: () => `
676
+ import type { Mermaid } from 'mermaid'
677
+
678
+ declare module '#app' {
679
+ interface NuxtApp {
680
+ $mermaid: () => Promise<Mermaid>
681
+ }
682
+ }
683
+ declare module 'vue' {
684
+ interface ComponentCustomProperties {
685
+ $mermaid: () => Promise<Mermaid>
686
+ }
687
+ }
688
+ export {}
689
+ `
690
+ });
691
+ nuxt.hook("content:file:beforeParse", ((ctx) => {
692
+ const { file } = ctx;
693
+ if (!file.id?.endsWith(".md")) return;
694
+ file.body = transformMarkdownDiagrams(file.body);
695
+ }));
696
+ }
697
+ });
698
+
699
+ export { module$1 as default };