@barzhsieh/nuxt-content-mermaid 1.0.4 → 2.0.0

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 CHANGED
@@ -22,7 +22,7 @@ It automatically converts `mermaid` code blocks in Markdown into responsive char
22
22
 
23
23
  ## Requirements
24
24
 
25
- - Nuxt 3.19+ or 4.1+ (`nuxt@^3.20.1 || ^4.1.0`)
25
+ - `nuxt@^3.20.1 || ^4.1.0`
26
26
  - `@nuxt/content@>=3.5.0`
27
27
 
28
28
  ## Quick Setup
@@ -79,20 +79,20 @@ The module will automatically transform the block into an SVG chart component.
79
79
 
80
80
  ## Configuration
81
81
 
82
- You can configure the module globally through the `mermaidContent` option.
82
+ You can configure the module globally through the `contentMermaid` option.
83
+ (`mermaidContent` is still accepted for backward compatibility, but will be removed in a future release.)
83
84
 
84
85
  ```ts
85
86
  // nuxt.config.ts
86
87
  export default defineNuxtConfig({
87
- mermaidContent: {
88
+ contentMermaid: {
88
89
  enabled: true,
89
90
  loader: {
90
- importSource:
91
- "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs",
92
91
  init: {
93
92
  securityLevel: "strict",
94
93
  // additional options passed to mermaid.initialize()
95
94
  },
95
+ lazy: true,
96
96
  },
97
97
  theme: {
98
98
  useColorModeTheme: true,
@@ -115,13 +115,14 @@ export default defineNuxtConfig({
115
115
  | Option | Type | Default | Description |
116
116
  | :-------- | :-------- | :------ | :--------------------------------------------------- |
117
117
  | `enabled` | `boolean` | `true` | Whether the module and its conversion logic are on. |
118
+ | `debug` | `boolean` | `false` | Enable verbose diagnostics; see Debug section below. |
118
119
 
119
120
  **loader**
120
121
 
121
- | Option | Type | Default | Description |
122
- | :--------------------- | :----------------- | :----------- | :--------------------------------------------------------------------- |
123
- | `loader.importSource` | `string` | jsDelivr CDN | Source to load Mermaid ESM from (supports CDN or a local path). |
124
- | `loader.init` | `MermaidConfig` | `{ startOnLoad: false }` | Raw options object passed to `mermaid.initialize`. |
122
+ | Option | Type | Default | Description |
123
+ | :---------------- | :------------------------------------ | :--------------------- | :------------------------------------------------------------------------------- |
124
+ | `loader.init` | `MermaidConfig` | `{ startOnLoad: false }` | Raw options object passed to `mermaid.initialize`. |
125
+ | `loader.lazy` | `boolean \| { threshold?: number }` | `true` | Lazy load Mermaid when the component enters the viewport; set `false` to preload. |
125
126
 
126
127
  **theme**
127
128
 
@@ -139,10 +140,19 @@ export default defineNuxtConfig({
139
140
  | `components.spinner` | `string` | `undefined` | Optional: global loading spinner component name. |
140
141
  | `components.error` | `string` | `undefined` | Optional: global error component name when Mermaid rendering fails. |
141
142
 
142
- > **Note**: All options can be overridden at runtime via `runtimeConfig.public.mermaidContent`.
143
+ > **Note**: All options can be overridden at runtime via `runtimeConfig.public.contentMermaid` (`runtimeConfig.public.mermaidContent` remains supported but deprecated).
143
144
 
144
145
  ## Advanced Usage
145
146
 
147
+ ### Debug mode
148
+
149
+ **`contentMermaid.debug`** (default `false`):
150
+ - **Auto-config**: If you did **not** set `loader.init.logLevel` or `suppressErrorRendering`, debug defaults them to `logLevel: 1` and `suppressErrorRendering: false` (Mermaid shows errors in the DOM). If you set them explicitly, your values win.
151
+ - **Runtime behavior**:
152
+ - **Debug on**: `mermaid.run` uses `suppressErrors: false`, errors throw with full stack traces for debugging.
153
+ - **Debug off**: `mermaid.run` uses `suppressErrors: true`, so one failing chart won't block others.
154
+ - **Console output**: Adds queue diagnostics and render-time stats.
155
+
146
156
  ### Theme & Color Mode
147
157
 
148
158
  The module determines the active Mermaid theme with the following priority:
@@ -154,6 +164,62 @@ The module determines the active Mermaid theme with the following priority:
154
164
  - Use `loader.init.theme` if provided.
155
165
  - Else fall back to `theme.light`, then `theme.dark`.
156
166
 
167
+ ### Override Per-Page Settings with Frontmatter
168
+
169
+ Each Markdown file can override module settings by adding a `config` field in the frontmatter.
170
+ ````markdown
171
+ ---
172
+ title: Example of Overriding Mermaid Settings Per Page
173
+ config:
174
+ theme: forest
175
+ flowchart:
176
+ htmlLabels: false
177
+ ---
178
+ ```mermaid
179
+ flowchart LR
180
+ A["<b>Allow HTML labels?</b>"] --> B{Not allowed}
181
+ ```
182
+ ````
183
+
184
+ > **Important: If you want to control settings via frontmatter, declare it in the collection schema in `content.config.ts`, for example:**
185
+ >
186
+ > ```ts
187
+ > import { defineContentConfig, defineCollection, z } from '@nuxt/content'
188
+ >
189
+ > export default defineContentConfig({
190
+ > collections: {
191
+ > content: defineCollection({
192
+ > type: 'page',
193
+ > source: '**',
194
+ > schema: z.object({
195
+ > config: z.record(z.unknown()).optional(), // Declare config field
196
+ > }).passthrough(),
197
+ > }),
198
+ > },
199
+ > })
200
+ > ```
201
+
202
+ ### Priority Order of `%%{init}%%` Syntax, Frontmatter, and Module Settings
203
+
204
+ Mermaid itself also supports overriding settings within diagrams using the `%%{init: ...}%%` syntax, for example:
205
+ ````markdown
206
+ ```mermaid
207
+ %%{init: { 'theme': 'forest', 'flowchart': { 'curve': 'step' } }}%%
208
+ graph TD
209
+ A[Input] --> B{Valid?}
210
+ B -- Yes --> C[Persist]
211
+ B -- No --> D[Error]
212
+ ```
213
+ ````
214
+
215
+ > For details, refer to the [official Mermaid documentation](https://mermaid.js.org/config/directives.html#declaring-directives)
216
+
217
+ The actual priority order when settings take effect is as follows:
218
+
219
+ 1. **`%%{init: ...}%%` within the diagram** — Highest priority, processed directly by Mermaid.
220
+ 2. **Frontmatter `config`** — merged on top of the module's `loader.init`.
221
+ 3. **Module-level `contentMermaid.loader.init`** — Project default settings.
222
+
157
223
  ### Custom Rendering Component
158
224
 
159
225
  If you want full control over rendering (for example to add a border, zoom controls, or other UI), you can provide a custom component via `components.renderer`.
@@ -161,7 +227,7 @@ If you want full control over rendering (for example to add a border, zoom contr
161
227
  1. Specify the component name in `nuxt.config.ts`:
162
228
 
163
229
  ```ts
164
- mermaidContent: {
230
+ contentMermaid: {
165
231
  components: {
166
232
  renderer: "MyCustomMermaid",
167
233
  },
@@ -193,7 +259,7 @@ Minimal example that uses the provided spinner while waiting for your own loadin
193
259
  ```ts
194
260
  // nuxt.config.ts
195
261
  export default defineNuxtConfig({
196
- mermaidContent: {
262
+ contentMermaid: {
197
263
  components: {
198
264
  renderer: 'MyCustomMermaid',
199
265
  spinner: 'MySpinner', // optional: globally replace the default spinner
@@ -288,7 +354,7 @@ To reuse a global error view everywhere, register it once and reference it in co
288
354
  ```ts
289
355
  // nuxt.config.ts
290
356
  export default defineNuxtConfig({
291
- mermaidContent: {
357
+ contentMermaid: {
292
358
  components: {
293
359
  error: 'MermaidError', // globally registered component name
294
360
  },
package/README.zh-TW.md CHANGED
@@ -10,7 +10,7 @@
10
10
  [![Nuxt Content](https://img.shields.io/badge/Nuxt%20Content-3.x-00DC82?logo=nuxt.js)](https://content.nuxt.com/)
11
11
 
12
12
  專為 [**Nuxt Content v3**](https://content.nuxt.com/docs/getting-started) 整合 [**Mermaid**](https://mermaid.js.org/) 的模組。
13
- 能自動將 Markdown 中的 ```mermaid 區塊轉換為響應式的圖表元件,並支援 Lazy Loading 與深色模式切換。
13
+ 能自動將 Markdown 中的 \`\`\`mermaid 區塊轉換為響應式的圖表元件,並支援 Lazy Loading 與深色模式切換。
14
14
 
15
15
  ## Features
16
16
 
@@ -22,7 +22,7 @@
22
22
 
23
23
  ## 使用前提
24
24
 
25
- - Nuxt 3.19 以上或 4.1 以上(`nuxt@^3.20.1 || ^4.1.0`)
25
+ - `nuxt@^3.20.1 || ^4.1.0`
26
26
  - `@nuxt/content@>=3.5.0`
27
27
 
28
28
  ## Quick Setup
@@ -35,7 +35,7 @@
35
35
  npx nuxi module add @barzhsieh/nuxt-content-mermaid
36
36
  ```
37
37
 
38
- > Nuxt CLI 對第三方模組預設會裝到 `dependencies`。若想放 `devDependencies`,請裝完後移動或改用下方手動安裝。
38
+ > Nuxt CLI 對第三方模組預設會裝到 `dependencies`。若你偏好放在 `devDependencies`,請安裝完後移動或改用下方的方式手動安裝。
39
39
 
40
40
  **裝到 `devDependencies`(需手動加入 `modules`):**
41
41
 
@@ -79,20 +79,20 @@ graph LR
79
79
 
80
80
  ## Configuration
81
81
 
82
- 你可以透過 `mermaidContent` 選項進行全域設定。
82
+ 你可以透過 `contentMermaid` 選項進行全域設定。
83
+ (`mermaidContent` 仍會被接受以維持相容,但已棄用,未來版本會移除。)
83
84
 
84
85
  ```ts
85
86
  // nuxt.config.ts
86
87
  export default defineNuxtConfig({
87
- mermaidContent: {
88
+ contentMermaid: {
88
89
  enabled: true,
89
90
  loader: {
90
- importSource:
91
- "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs",
92
91
  init: {
93
92
  securityLevel: "strict",
94
93
  // 其他傳遞給 mermaid.initialize() 的參數
95
94
  },
95
+ lazy: true,
96
96
  },
97
97
  theme: {
98
98
  useColorModeTheme: true,
@@ -115,13 +115,14 @@ export default defineNuxtConfig({
115
115
  | 參數 | 類型 | 預設值 | 說明 |
116
116
  | :-------- | :-------- | :----- | :----------------------------- |
117
117
  | `enabled` | `boolean` | `true` | 是否啟用模組與轉換邏輯。 |
118
+ | `debug` | `boolean` | `false` | 啟用除錯模式;見下方 Debug 說明。 |
118
119
 
119
120
  **loader**
120
121
 
121
- | 參數 | 類型 | 預設值 | 說明 |
122
- | :-------------------- | :-------------- | :---------------------- | :--------------------------------------------------------- |
123
- | `loader.importSource` | `string` | jsDelivr CDN | 指定 Mermaid ESM 的載入來源(支援 CDN 或本地路徑)。 |
124
- | `loader.init` | `MermaidConfig` | `{ startOnLoad: false }` | 直接傳遞給 `mermaid.initialize` 的原始設定。 |
122
+ | 參數 | 類型 | 預設值 | 說明 |
123
+ | :--------------- | :------------------------------------- | :----------------------- | :---------------------------------------------------------------------- |
124
+ | `loader.init` | `MermaidConfig` | `{ startOnLoad: false }` | 直接傳遞給 `mermaid.initialize` 的原始設定。 |
125
+ | `loader.lazy` | `boolean \| { threshold?: number }` | `true` | 元件進入 viewport 時才載入 Mermaid;設為 `false` 會在前一刻就載入。 |
125
126
 
126
127
  **theme**
127
128
 
@@ -139,10 +140,19 @@ export default defineNuxtConfig({
139
140
  | `components.spinner` | `string` | `undefined` | 指定全域的 Loading 元件名稱。 |
140
141
  | `components.error` | `string` | `undefined` | 指定全域的錯誤顯示元件名稱,渲染失敗時會使用。 |
141
142
 
142
- > **Note**: 所有設定皆可透過 `runtimeConfig.public.mermaidContent` 在部署時進行覆寫。
143
+ > **Note**: 所有設定皆可透過 `runtimeConfig.public.contentMermaid` 在部署時進行覆寫(`runtimeConfig.public.mermaidContent` 仍支援但已棄用)。
143
144
 
144
145
  ## Advanced Usage
145
146
 
147
+ ### Debug 模式
148
+
149
+ **`contentMermaid.debug`**(預設 `false`):
150
+ - **自動配置**:若未手動設定 `loader.init.logLevel` 或 `suppressErrorRendering`,開啟 debug 模式會自動將 `logLevel` 設為 `1` (Info),並將 `suppressErrorRendering` 設為 `false`(允許 Mermaid 在 DOM 中顯示錯誤訊息)。
151
+ - **執行行為**:
152
+ - **Debug 開啟**:`mermaid.run` 使用 `suppressErrors: false`,發生錯誤時會拋出完整堆疊以便除錯。
153
+ - **Debug 關閉**:`mermaid.run` 使用 `suppressErrors: true`,避免單一圖表錯誤中斷其他圖表的渲染。
154
+ - **主控台輸出**:模組會額外輸出渲染佇列的診斷資訊與執行時間統計。
155
+
146
156
  ### 主題與顏色模式 (Color Mode)
147
157
 
148
158
  模組會依據以下優先順序決定主題:
@@ -154,6 +164,65 @@ export default defineNuxtConfig({
154
164
  - 優先使用 `loader.init.theme`。
155
165
  - 若未提供,依序回退至 `theme.light`,若沒有設置則會回退至 `theme.dark`。
156
166
 
167
+ ### 以 frontmatter 覆寫單頁設定
168
+
169
+ 每篇 Markdown 都可以透過於 frontmatter 中加入 `config` 欄位來覆寫模組設定。
170
+
171
+ ````markdown
172
+ ---
173
+ title: 單篇覆寫 Mermaid 設定範例
174
+ config:
175
+ theme: forest
176
+ flowchart:
177
+ htmlLabels: false
178
+ ---
179
+
180
+ ```mermaid
181
+ flowchart LR
182
+ A["<b>允許 HTML labels?</b>"] --> B{不允許}
183
+ ```
184
+ ````
185
+
186
+ > **重要:如果要使用 frontmatter 控制設定,請在 `content.config.ts` 的 collection schema 中宣告它,例如:**
187
+ >
188
+ > ```ts
189
+ > import { defineContentConfig, defineCollection, z } from '@nuxt/content'
190
+ >
191
+ > export default defineContentConfig({
192
+ > collections: {
193
+ > content: defineCollection({
194
+ > type: 'page',
195
+ > source: '**',
196
+ > schema: z.object({
197
+ > config: z.record(z.unknown()).optional(), // 宣告 config 欄位
198
+ > }).passthrough(),
199
+ > }),
200
+ > },
201
+ > })
202
+ > ```
203
+
204
+ ### `%%{init}%%` 語法、frontmatter 與模組設定的優先順序
205
+
206
+ Mermaid 本身也支援在圖表內透過 `%%{init: ...}%%` 語法覆寫設定,例如:
207
+
208
+ ````markdown
209
+ ```mermaid
210
+ %%{init: { 'theme': 'forest', 'flowchart': { 'curve': 'step' } }}%%
211
+ graph TD
212
+ A[Input] --> B{Valid?}
213
+ B -- Yes --> C[Persist]
214
+ B -- No --> D[Error]
215
+ ```
216
+ ````
217
+
218
+ > 細節可參考 [Mermaid 官方文件](https://mermaid.js.org/config/directives.html#declaring-directives)
219
+
220
+ 實際生效時的優先順序如下:
221
+
222
+ 1. **圖表內的 `%%{init: ...}%%`** —— 最優先,直接由 Mermaid 處理。
223
+ 2. **frontmatter `config`** —— 深度合併在模組的 `loader.init` 之上。
224
+ 3. **模組層級的 `contentMermaid.loader.init`** —— 專案的全域預設。
225
+
157
226
  ### 自訂渲染元件 (Custom Component)
158
227
 
159
228
  若需完全接管 Mermaid 的渲染行為(例如:加入外框、ZoomIn/Out 功能),可指定 `components.renderer`。
@@ -161,7 +230,7 @@ export default defineNuxtConfig({
161
230
  1. 在 `nuxt.config.ts` 中指定元件名稱:
162
231
 
163
232
  ```ts
164
- mermaidContent: {
233
+ contentMermaid: {
165
234
  components: {
166
235
  renderer: "MyCustomMermaid",
167
236
  }
@@ -193,7 +262,7 @@ export default defineNuxtConfig({
193
262
  ```ts
194
263
  // nuxt.config.ts
195
264
  export default defineNuxtConfig({
196
- mermaidContent: {
265
+ contentMermaid: {
197
266
  components: {
198
267
  renderer: 'MyCustomMermaid',
199
268
  spinner: 'MySpinner', // 選填:改用自己的全域 Loading 元件
@@ -287,7 +356,7 @@ onMounted(() => { loading.value = false })
287
356
  ```ts
288
357
  // nuxt.config.ts
289
358
  export default defineNuxtConfig({
290
- mermaidContent: {
359
+ contentMermaid: {
291
360
  components: {
292
361
  error: 'MermaidError', // 全域註冊的元件名稱
293
362
  },
package/dist/module.d.mts CHANGED
@@ -7,14 +7,15 @@ interface ModuleOptions {
7
7
  * @default true
8
8
  */
9
9
  enabled?: boolean;
10
+ /**
11
+ * Enable debug mode for detailed logging and error reporting
12
+ * @default false
13
+ */
14
+ debug?: boolean;
10
15
  /**
11
16
  * Options related to loading mermaid
12
17
  */
13
18
  loader?: {
14
- /**
15
- * Mermaid ESM source, can be a CDN URL or local /public path
16
- */
17
- importSource?: string;
18
19
  /**
19
20
  * Pass-through to `mermaid.initialize`
20
21
  */
@@ -66,5 +67,7 @@ interface ModuleOptions {
66
67
  }
67
68
  declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
68
69
 
69
- export { _default as default };
70
+ declare function transformMermaidCodeBlocks(body: string, componentName: string, frontmatterConfigKey?: string): string;
71
+
72
+ export { _default as default, transformMermaidCodeBlocks };
70
73
  export type { ModuleOptions };
package/dist/module.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@barzhsieh/nuxt-content-mermaid",
3
- "configKey": "mermaidContent",
3
+ "configKey": "contentMermaid",
4
4
  "compatibility": {
5
5
  "nuxt": "^3.20.1 || ^4.1.0"
6
6
  },
7
- "version": "1.0.4",
7
+ "version": "2.0.0",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -1,13 +1,12 @@
1
- import { defineNuxtModule, createResolver, addPlugin, addComponent, addTypeTemplate } from '@nuxt/kit';
1
+ import { defineNuxtModule, useLogger, createResolver, addPlugin, addComponent, addTypeTemplate } from '@nuxt/kit';
2
2
  import { defu } from 'defu';
3
- import { DEFAULT_MERMAID_INIT, DEFAULT_DARK_THEME, DEFAULT_LIGHT_THEME, DEFAULT_IMPORT_SOURCE } from '../dist/runtime/constants.js';
3
+ import { DEFAULT_MERMAID_CONFIG, DEFAULT_DARK_THEME, DEFAULT_LIGHT_THEME, DEFAULT_FRONTMATTER_CONFIG_KEY } from '../dist/runtime/constants.js';
4
4
 
5
5
  const MERMAID_BLOCK = /```mermaid([\s\S]*?)```/gi;
6
6
  const DEFAULTS = {
7
7
  enabled: true,
8
8
  loader: {
9
- importSource: DEFAULT_IMPORT_SOURCE,
10
- init: { ...DEFAULT_MERMAID_INIT },
9
+ init: { ...DEFAULT_MERMAID_CONFIG },
11
10
  lazy: true
12
11
  },
13
12
  theme: {
@@ -24,7 +23,7 @@ const DEFAULTS = {
24
23
  const module$1 = defineNuxtModule({
25
24
  meta: {
26
25
  name: "@barzhsieh/nuxt-content-mermaid",
27
- configKey: "mermaidContent",
26
+ configKey: "contentMermaid",
28
27
  compatibility: {
29
28
  nuxt: "^3.20.1 || ^4.1.0"
30
29
  }
@@ -33,15 +32,30 @@ const module$1 = defineNuxtModule({
33
32
  ...DEFAULTS
34
33
  },
35
34
  setup(options, nuxt) {
36
- const resolvedOptions = defu(options, DEFAULTS);
35
+ const logger = useLogger("nuxt-content-mermaid");
36
+ const warn = (message) => logger.warn(message);
37
+ const deprecatedOptions = nuxt.options.mermaidContent;
38
+ const hasDeprecatedOptions = deprecatedOptions && typeof deprecatedOptions === "object" && Object.keys(deprecatedOptions).length > 0;
39
+ if (hasDeprecatedOptions)
40
+ 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.");
41
+ const resolvedOptions = defu(
42
+ {},
43
+ options,
44
+ deprecatedOptions,
45
+ DEFAULTS
46
+ );
37
47
  const resolver = createResolver(import.meta.url);
38
48
  const runtimeDir = resolver.resolve("./runtime");
39
49
  const publicRuntimeConfig = nuxt.options.runtimeConfig.public;
40
- const runtimeOverrides = publicRuntimeConfig.mermaidContent || {};
50
+ const runtimeOverrides = publicRuntimeConfig.contentMermaid || publicRuntimeConfig.mermaidContent || {};
51
+ if (!publicRuntimeConfig.contentMermaid && publicRuntimeConfig.mermaidContent)
52
+ warn("[nuxt-content-mermaid] `runtimeConfig.public.mermaidContent` is deprecated, please use `runtimeConfig.public.contentMermaid` instead.");
41
53
  const runtimeMermaidConfig = defu(
54
+ {},
42
55
  runtimeOverrides,
43
56
  resolvedOptions
44
57
  );
58
+ publicRuntimeConfig.contentMermaid = runtimeMermaidConfig;
45
59
  publicRuntimeConfig.mermaidContent = runtimeMermaidConfig;
46
60
  const baseMermaidComponentName = "Mermaid";
47
61
  const isEnabled = runtimeMermaidConfig.enabled !== false;
@@ -83,19 +97,13 @@ export {}
83
97
  });
84
98
  }
85
99
  });
86
- function escapeHtml(text) {
87
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
88
- }
89
- function transformMermaidCodeBlocks(body, componentName) {
100
+ function transformMermaidCodeBlocks(body, componentName, frontmatterConfigKey = DEFAULT_FRONTMATTER_CONFIG_KEY) {
90
101
  return body.replace(MERMAID_BLOCK, (_, rawCode = "") => {
91
102
  const code = rawCode.trim();
92
103
  if (!code) return _;
93
- const escaped = escapeHtml(code);
94
- return `<${componentName}>
95
- <pre><code>${escaped}
96
- </code></pre>
97
- </${componentName}>`;
104
+ const encoded = encodeURIComponent(code);
105
+ return `<${componentName} :config="${frontmatterConfigKey}" code="${encoded}"></${componentName}>`;
98
106
  });
99
107
  }
100
108
 
101
- export { module$1 as default };
109
+ export { module$1 as default, transformMermaidCodeBlocks };
@@ -1,3 +1,8 @@
1
+ import type { MermaidConfig } from 'mermaid';
2
+ type __VLS_Props = {
3
+ config?: MermaidConfig | string | unknown;
4
+ code?: string;
5
+ };
1
6
  declare var __VLS_1: {}, __VLS_9: {}, __VLS_11: {}, __VLS_13: {}, __VLS_21: {
2
7
  error: unknown;
3
8
  source: string;
@@ -13,7 +18,7 @@ type __VLS_Slots = {} & {
13
18
  } & {
14
19
  error?: (props: typeof __VLS_21) => any;
15
20
  };
16
- declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
21
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
17
22
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
18
23
  declare const _default: typeof __VLS_export;
19
24
  export default _default;
@@ -10,15 +10,21 @@ import {
10
10
  shallowRef,
11
11
  useTemplateRef
12
12
  } from "vue";
13
+ import { mergeMermaidConfig, resolveMermaidTheme } from "../mermaid-config";
13
14
  import { enqueueRender } from "../utils";
14
15
  import Spinner from "./Spinner.vue";
16
+ const props = defineProps({
17
+ config: { type: null, required: false },
18
+ code: { type: String, required: false }
19
+ });
15
20
  const nuxtApp = useNuxtApp();
16
21
  const runtimeConfig = useRuntimeConfig();
17
- const mermaidContent = runtimeConfig.public?.mermaidContent || {};
18
- const isEnabled = mermaidContent.enabled !== false;
19
- const loaderOptions = mermaidContent.loader || {};
20
- const themeOptions = mermaidContent.theme || {};
21
- const componentOptions = mermaidContent.components || {};
22
+ const contentMermaidOptions = runtimeConfig.public?.contentMermaid || runtimeConfig.public?.mermaidContent || {};
23
+ const isEnabled = contentMermaidOptions.enabled !== false;
24
+ const debug = contentMermaidOptions.debug || false;
25
+ const loaderOptions = contentMermaidOptions.loader || {};
26
+ const themeOptions = contentMermaidOptions.theme || {};
27
+ const componentOptions = contentMermaidOptions.components || {};
22
28
  const lazyOption = loaderOptions.lazy;
23
29
  const isLazy = lazyOption !== false;
24
30
  const lazyThreshold = typeof lazyOption === "object" && typeof lazyOption.threshold === "number" ? lazyOption.threshold : 0.1;
@@ -33,12 +39,40 @@ const hasRenderedOnce = ref(false);
33
39
  const isLoading = ref(false);
34
40
  const hasError = ref(false);
35
41
  const errorContent = shallowRef(null);
36
- let mermaidDefinition = "";
42
+ const decodedCode = computed(() => props.code ? decodeURIComponent(props.code) : "");
43
+ const mermaidDefinition = ref(decodedCode.value);
37
44
  let observer = null;
45
+ const frontmatterConfig = computed(() => {
46
+ const value = props.config;
47
+ if (!value)
48
+ return void 0;
49
+ if (typeof value !== "object") {
50
+ if (import.meta.dev) {
51
+ console.warn(
52
+ "[nuxt-content-mermaid] Ignoring 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.",
53
+ value
54
+ );
55
+ }
56
+ return void 0;
57
+ }
58
+ return value;
59
+ });
38
60
  const mermaidTheme = computed(() => {
39
- if (useColorModeTheme && colorMode)
40
- return colorMode.value === "dark" ? darkTheme : lightTheme;
41
- return baseMermaidInit.theme ?? lightTheme ?? darkTheme;
61
+ return resolveMermaidTheme({
62
+ useColorModeTheme,
63
+ colorModeValue: colorMode?.value,
64
+ frontmatterTheme: frontmatterConfig.value?.theme,
65
+ baseTheme: baseMermaidInit.theme,
66
+ lightTheme,
67
+ darkTheme
68
+ });
69
+ });
70
+ const effectiveMermaidInit = computed(() => {
71
+ return mergeMermaidConfig({
72
+ baseConfig: baseMermaidInit,
73
+ overrideConfig: frontmatterConfig.value,
74
+ theme: mermaidTheme.value
75
+ });
42
76
  });
43
77
  const configuredSpinnerName = computed(() => componentOptions.spinner?.trim() || "");
44
78
  const customSpinner = shallowRef(null);
@@ -108,46 +142,78 @@ function serializeMermaidFromNode(root) {
108
142
  if (root.nodeType === ELEMENT_NODE) {
109
143
  const el = root;
110
144
  const tag = el.tagName?.toUpperCase();
111
- if (tag === "BR") return "<br/>";
145
+ if (tag === "BR") return "\n";
146
+ const blockTags = [
147
+ "DIV",
148
+ "P",
149
+ "LI",
150
+ "TR",
151
+ "H1",
152
+ "H2",
153
+ "H3",
154
+ "H4",
155
+ "H5",
156
+ "H6",
157
+ "BLOCKQUOTE",
158
+ "PRE",
159
+ "UL",
160
+ "OL",
161
+ "DL",
162
+ "DT",
163
+ "DD",
164
+ "TABLE",
165
+ "THEAD",
166
+ "TBODY",
167
+ "TFOOT",
168
+ "SECTION",
169
+ "ARTICLE",
170
+ "ASIDE",
171
+ "HEADER",
172
+ "FOOTER",
173
+ "MAIN"
174
+ ];
175
+ const isBlock = blockTags.includes(tag) || el.classList.contains("line") || el.classList.contains("code-line");
112
176
  let out = "";
113
177
  el.childNodes.forEach((child) => {
114
178
  out += serializeMermaidFromNode(child);
115
179
  });
180
+ if (isBlock) return "\n" + out + "\n";
116
181
  return out;
117
182
  }
118
183
  return "";
119
184
  }
120
185
  function extractMermaidDefinition(container) {
186
+ const codeNodes = Array.from(container.querySelectorAll("code"));
187
+ if (codeNodes.length > 0) {
188
+ return codeNodes.map((node) => serializeMermaidFromNode(node)).join("\n").trim();
189
+ }
121
190
  const pre = container.querySelector("pre");
122
- const code = container.querySelector("code");
123
- if (code) return code.textContent?.trim() || "";
124
- if (pre) return pre.textContent?.trim() || "";
191
+ if (pre) return serializeMermaidFromNode(pre).trim();
125
192
  return serializeMermaidFromNode(container).trim();
126
193
  }
127
194
  async function renderMermaid() {
128
- if (!mermaidContainer.value || !mermaidDefinition) return;
195
+ if (!mermaidContainer.value || !mermaidDefinition.value) return;
129
196
  isLoading.value = true;
130
197
  const performRender = async () => {
131
198
  if (!mermaidContainer.value) return;
132
199
  hasError.value = false;
133
200
  errorContent.value = null;
201
+ const startTime = performance.now();
134
202
  try {
135
203
  const mermaid = await $mermaid();
136
- const initOptions = {
137
- startOnLoad: false,
138
- ...baseMermaidInit,
139
- theme: mermaidTheme.value
140
- };
141
- mermaid.initialize(initOptions);
204
+ mermaid.initialize(effectiveMermaidInit.value);
142
205
  mermaidContainer.value.removeAttribute("data-processed");
143
- mermaidContainer.value.textContent = mermaidDefinition;
206
+ mermaidContainer.value.textContent = mermaidDefinition.value;
144
207
  await nextTick();
145
208
  await mermaid.run({
146
209
  nodes: [mermaidContainer.value],
147
- suppressErrors: true
210
+ suppressErrors: !debug
148
211
  });
149
212
  hasRenderedOnce.value = true;
150
- await new Promise((resolve) => setTimeout(resolve, 5));
213
+ if (debug) {
214
+ const endTime = performance.now();
215
+ console.log(`[nuxt-content-mermaid] \u23F1\uFE0F Rendered in ${(endTime - startTime).toFixed(2)}ms`);
216
+ }
151
217
  } catch (error) {
152
218
  console.error("[nuxt-content-mermaid]", error);
153
219
  hasError.value = true;
@@ -158,13 +224,11 @@ async function renderMermaid() {
158
224
  isLoading.value = false;
159
225
  }
160
226
  };
161
- await enqueueRender(performRender);
227
+ await enqueueRender(performRender, debug);
162
228
  }
163
229
  function setupMermaidContainer() {
164
230
  const container = mermaidContainer.value;
165
231
  if (!container) return;
166
- if (!mermaidDefinition)
167
- mermaidDefinition = extractMermaidDefinition(container);
168
232
  if (!isLazy) {
169
233
  renderMermaid();
170
234
  return;
@@ -182,8 +246,8 @@ function setupMermaidContainer() {
182
246
  }
183
247
  onMounted(() => {
184
248
  if (!isEnabled) return;
185
- if (mermaidContainer.value)
186
- mermaidDefinition = extractMermaidDefinition(mermaidContainer.value);
249
+ if (!mermaidDefinition.value && mermaidContainer.value)
250
+ mermaidDefinition.value = extractMermaidDefinition(mermaidContainer.value);
187
251
  nextTick(() => setupMermaidContainer());
188
252
  });
189
253
  onUnmounted(() => {
@@ -193,6 +257,19 @@ watch(mermaidTheme, () => {
193
257
  if (!isEnabled) return;
194
258
  if (hasRenderedOnce.value) renderMermaid();
195
259
  });
260
+ watch(
261
+ frontmatterConfig,
262
+ () => {
263
+ if (!isEnabled) return;
264
+ if (hasRenderedOnce.value) renderMermaid();
265
+ },
266
+ { deep: true }
267
+ );
268
+ watch(decodedCode, (newCode) => {
269
+ if (!isEnabled) return;
270
+ mermaidDefinition.value = newCode;
271
+ if (hasRenderedOnce.value) renderMermaid();
272
+ });
196
273
  </script>
197
274
 
198
275
  <template>
@@ -210,8 +287,11 @@ watch(mermaidTheme, () => {
210
287
  :is="customMermaidImpl"
211
288
  v-else-if="customMermaidImpl"
212
289
  :spinner="spinnerComponent"
290
+ :code="decodedCode"
213
291
  >
214
- <slot />
292
+ <slot>
293
+ <pre v-if="decodedCode"><code>{{ decodedCode }}</code></pre>
294
+ </slot>
215
295
  </component>
216
296
 
217
297
  <div
@@ -220,7 +300,9 @@ watch(mermaidTheme, () => {
220
300
  >
221
301
  <div ref="mermaidContainer">
222
302
  <!-- Initially show the slot's <pre><code> for SSR; client-side rendering will replace it with the mermaid SVG -->
223
- <slot />
303
+ <slot>
304
+ <pre v-if="decodedCode"><code>{{ decodedCode }}</code></pre>
305
+ </slot>
224
306
  </div>
225
307
 
226
308
  <template v-if="isLoading && !hasRenderedOnce">
@@ -254,5 +336,5 @@ watch(mermaidTheme, () => {
254
336
  </template>
255
337
 
256
338
  <style scoped>
257
- .mermaid-wrapper{align-items:center;display:flex;justify-content:center;position:relative}.mermaid-wrapper :deep(code),.mermaid-wrapper :deep(pre){margin:0;max-height:160px;opacity:0;overflow:hidden;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.mermaid-error-default{text-align:center}.mermaid:not([data-processed]){color:transparent;min-height:10px}.mermaid{display:flex;justify-content:center}.mermaid .edgeLabel,.mermaid .label,.mermaid .nodeLabel{overflow:visible!important;white-space:normal!important}.mermaid foreignObject{overflow:visible!important}
339
+ .mermaid-wrapper{position:relative;text-align:center}.mermaid-wrapper :deep(code),.mermaid-wrapper :deep(pre){margin:0;max-height:160px;opacity:0;overflow:hidden;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.mermaid-error-default{text-align:center}.mermaid:not([data-processed]){color:transparent;min-height:10px}.mermaid-wrapper :deep(.edgeLabel),.mermaid-wrapper :deep(.label),.mermaid-wrapper :deep(.nodeLabel){overflow:visible!important;white-space:normal!important}.mermaid-wrapper :deep(foreignObject){overflow:visible!important}
258
340
  </style>
@@ -1,3 +1,8 @@
1
+ import type { MermaidConfig } from 'mermaid';
2
+ type __VLS_Props = {
3
+ config?: MermaidConfig | string | unknown;
4
+ code?: string;
5
+ };
1
6
  declare var __VLS_1: {}, __VLS_9: {}, __VLS_11: {}, __VLS_13: {}, __VLS_21: {
2
7
  error: unknown;
3
8
  source: string;
@@ -13,7 +18,7 @@ type __VLS_Slots = {} & {
13
18
  } & {
14
19
  error?: (props: typeof __VLS_21) => any;
15
20
  };
16
- declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
21
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
17
22
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
18
23
  declare const _default: typeof __VLS_export;
19
24
  export default _default;
@@ -1,5 +1,5 @@
1
1
  import type { MermaidConfig } from 'mermaid';
2
- export declare const DEFAULT_IMPORT_SOURCE = "https://cdn.jsdelivr.net/npm/mermaid@11.12.1/dist/mermaid.esm.min.mjs";
3
2
  export declare const DEFAULT_LIGHT_THEME: MermaidConfig['theme'];
4
3
  export declare const DEFAULT_DARK_THEME: MermaidConfig['theme'];
5
- export declare const DEFAULT_MERMAID_INIT: MermaidConfig;
4
+ export declare const DEFAULT_MERMAID_CONFIG: MermaidConfig;
5
+ export declare const DEFAULT_FRONTMATTER_CONFIG_KEY = "config";
@@ -1,6 +1,10 @@
1
- export const DEFAULT_IMPORT_SOURCE = "https://cdn.jsdelivr.net/npm/mermaid@11.12.1/dist/mermaid.esm.min.mjs";
2
1
  export const DEFAULT_LIGHT_THEME = "default";
3
2
  export const DEFAULT_DARK_THEME = "dark";
4
- export const DEFAULT_MERMAID_INIT = {
5
- startOnLoad: false
3
+ export const DEFAULT_MERMAID_CONFIG = {
4
+ startOnLoad: false,
5
+ theme: "default",
6
+ fontFamily: "Arial, sans-serif, \u5FAE\u8EDF\u6B63\u9ED1\u9AD4",
7
+ securityLevel: "strict",
8
+ logLevel: "error"
6
9
  };
10
+ export const DEFAULT_FRONTMATTER_CONFIG_KEY = "config";
@@ -0,0 +1,17 @@
1
+ import type { MermaidConfig } from 'mermaid';
2
+ interface ThemeOptions {
3
+ useColorModeTheme?: boolean;
4
+ colorModeValue?: string;
5
+ baseTheme?: MermaidConfig['theme'];
6
+ lightTheme?: MermaidConfig['theme'];
7
+ darkTheme?: MermaidConfig['theme'];
8
+ frontmatterTheme?: MermaidConfig['theme'];
9
+ }
10
+ export declare function resolveMermaidTheme(options: ThemeOptions): "default" | "base" | "dark" | "forest" | "neutral" | "null" | undefined;
11
+ interface MergeOptions extends MermaidConfig {
12
+ baseConfig?: MermaidConfig;
13
+ overrideConfig?: MermaidConfig;
14
+ theme?: MermaidConfig['theme'];
15
+ }
16
+ export declare function mergeMermaidConfig(options: MergeOptions): MermaidConfig;
17
+ export {};
@@ -0,0 +1,36 @@
1
+ import { defu } from "defu";
2
+ export function resolveMermaidTheme(options) {
3
+ const {
4
+ useColorModeTheme,
5
+ colorModeValue,
6
+ frontmatterTheme,
7
+ baseTheme,
8
+ lightTheme,
9
+ darkTheme
10
+ } = options;
11
+ if (frontmatterTheme) return frontmatterTheme;
12
+ if (useColorModeTheme && colorModeValue) {
13
+ if (colorModeValue === "dark" && darkTheme) return darkTheme;
14
+ if (colorModeValue !== "dark" && lightTheme) return lightTheme;
15
+ }
16
+ return baseTheme ?? lightTheme ?? darkTheme;
17
+ }
18
+ export function mergeMermaidConfig(options) {
19
+ const {
20
+ baseConfig,
21
+ overrideConfig,
22
+ theme,
23
+ ...overrides
24
+ } = options;
25
+ const merged = defu(
26
+ {},
27
+ overrides || {},
28
+ overrideConfig || {},
29
+ baseConfig || {}
30
+ );
31
+ return {
32
+ startOnLoad: false,
33
+ ...merged,
34
+ theme: theme ?? merged.theme
35
+ };
36
+ }
@@ -1,27 +1,9 @@
1
1
  import { defineNuxtPlugin, useRuntimeConfig } from "#app";
2
- import {
3
- DEFAULT_IMPORT_SOURCE,
4
- DEFAULT_MERMAID_INIT
5
- } from "../constants.js";
2
+ import { DEFAULT_MERMAID_CONFIG } from "../constants.js";
6
3
  const globalWithLoader = globalThis;
7
- const ABSOLUTE_URL_REGEX = /^(?:https?:)?\/\//;
8
- function resolveSource(source) {
9
- if (ABSOLUTE_URL_REGEX.test(source)) return source;
10
- if (source.startsWith("/")) return source;
11
- if (typeof window !== "undefined")
12
- return new URL(source, window.location.origin).href;
13
- return source;
14
- }
15
- async function loadMermaidFrom(source) {
16
- const mod = await import(
17
- /* @vite-ignore */
18
- resolveSource(source)
19
- );
20
- return mod.default ?? mod;
21
- }
22
4
  export default defineNuxtPlugin(() => {
23
5
  const runtimeConfig = useRuntimeConfig();
24
- const mermaidConfig = runtimeConfig.public?.mermaidContent;
6
+ const mermaidConfig = runtimeConfig.public?.contentMermaid ?? runtimeConfig.public?.mermaidContent ?? {};
25
7
  if (mermaidConfig?.enabled === false) {
26
8
  return {
27
9
  provide: {
@@ -36,18 +18,27 @@ export default defineNuxtPlugin(() => {
36
18
  const loadMermaid = async () => {
37
19
  if (globalWithLoader.__nuxtMermaidLoader__)
38
20
  return globalWithLoader.__nuxtMermaidLoader__;
39
- const importSource = mermaidConfig?.loader?.importSource || DEFAULT_IMPORT_SOURCE;
21
+ const debug = mermaidConfig?.debug || false;
22
+ const userInit = mermaidConfig?.loader?.init || {};
23
+ const hasUserLogLevel = Object.prototype.hasOwnProperty.call(userInit, "logLevel");
24
+ const hasUserSuppressErrorRendering = Object.prototype.hasOwnProperty.call(userInit, "suppressErrorRendering");
40
25
  const initOptions = {
41
- ...DEFAULT_MERMAID_INIT,
42
- ...mermaidConfig?.loader?.init
26
+ ...DEFAULT_MERMAID_CONFIG,
27
+ ...userInit,
28
+ logLevel: hasUserLogLevel ? userInit.logLevel : debug ? 1 : 5,
29
+ suppressErrorRendering: hasUserSuppressErrorRendering ? userInit.suppressErrorRendering : !debug
43
30
  };
44
- globalWithLoader.__nuxtMermaidLoader__ = loadMermaidFrom(importSource).then((mermaid) => {
45
- mermaid.initialize(initOptions);
46
- return mermaid;
47
- }).catch((error) => {
48
- globalWithLoader.__nuxtMermaidLoader__ = void 0;
49
- throw error;
50
- });
31
+ globalWithLoader.__nuxtMermaidLoader__ = (async () => {
32
+ try {
33
+ const mermaid = await import("mermaid");
34
+ const mermaidInstance = mermaid.default ?? mermaid;
35
+ mermaidInstance.initialize(initOptions);
36
+ return mermaidInstance;
37
+ } catch (error) {
38
+ globalWithLoader.__nuxtMermaidLoader__ = void 0;
39
+ throw error;
40
+ }
41
+ })();
51
42
  return globalWithLoader.__nuxtMermaidLoader__;
52
43
  };
53
44
  return {
@@ -1 +1 @@
1
- export declare function enqueueRender(task: () => Promise<void>): Promise<void>;
1
+ export declare function enqueueRender(task: () => Promise<void>, debug?: boolean): Promise<void>;
@@ -1,7 +1,30 @@
1
1
  let renderQueue = Promise.resolve();
2
- export function enqueueRender(task) {
3
- renderQueue = renderQueue.then(task).catch((e) => {
4
- console.error("[nuxt-content-mermaid] Render error:", e);
2
+ let queueSize = 0;
3
+ export function enqueueRender(task, debug = false) {
4
+ queueSize++;
5
+ if (debug) {
6
+ console.log(`[nuxt-content-mermaid] \u{1F535} Enqueueing task. Queue size: ${queueSize}`);
7
+ }
8
+ const wrappedTask = async () => {
9
+ if (debug) {
10
+ console.log(`[nuxt-content-mermaid] \u{1F7E2} Starting render task. Pending: ${queueSize - 1}`);
11
+ }
12
+ try {
13
+ await task();
14
+ } catch (e) {
15
+ console.error("[nuxt-content-mermaid] Render error:", e);
16
+ throw e;
17
+ } finally {
18
+ queueSize--;
19
+ if (debug) {
20
+ console.log(`[nuxt-content-mermaid] \u2705 Task finished. Remaining: ${queueSize}`);
21
+ }
22
+ }
23
+ };
24
+ renderQueue = renderQueue.then(wrappedTask).catch((e) => {
25
+ if (debug) {
26
+ console.error("[nuxt-content-mermaid] \u274C Queue chain caught error:", e);
27
+ }
5
28
  });
6
29
  return renderQueue;
7
30
  }
package/dist/types.d.mts CHANGED
@@ -1,3 +1,3 @@
1
- export { default } from './module.mjs'
1
+ export { default, type transformMermaidCodeBlocks } from './module.mjs'
2
2
 
3
3
  export { type ModuleOptions } from './module.mjs'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barzhsieh/nuxt-content-mermaid",
3
- "version": "1.0.4",
3
+ "version": "2.0.0",
4
4
  "description": "A Nuxt module designed for integrating Mermaid with `@nuxt/content`. It automatically converts `mermaid` code blocks in Markdown into responsive chart components, and supports lazy loading and dark/light theme switching.",
5
5
  "keywords": [
6
6
  "nuxt",
@@ -37,28 +37,29 @@
37
37
  ],
38
38
  "dependencies": {
39
39
  "@nuxt/kit": "4.2.1",
40
- "defu": "^6.1.4"
40
+ "defu": "^6.1.4",
41
+ "mermaid": "^11.12.2"
41
42
  },
42
43
  "peerDependencies": {
43
- "nuxt": "^3.20.1 || ^4.1.0",
44
- "@nuxt/content": ">=3.5.0"
44
+ "@nuxt/content": ">=3.5.0",
45
+ "nuxt": "^3.20.1 || ^4.1.0"
45
46
  },
46
47
  "devDependencies": {
47
- "@nuxt/content": "3.8.0",
48
+ "@nuxt/content": "^3.9.0",
48
49
  "@nuxt/devtools": "3.1.1",
49
50
  "@nuxt/eslint-config": "1.11.0",
50
51
  "@nuxt/module-builder": "1.0.2",
51
52
  "@nuxt/schema": "4.2.1",
52
- "@nuxt/test-utils": "3.20.1",
53
+ "@nuxt/test-utils": "^3.21.0",
53
54
  "@nuxtjs/color-mode": "4.0.0",
54
- "@types/node": "latest",
55
+ "@types/node": "^24.10.1",
55
56
  "changelogen": "^0.6.2",
56
57
  "eslint": "^9.39.1",
57
- "mermaid": "^11.12.1",
58
58
  "nuxt": "4.2.1",
59
+ "playwright": "^1.57.0",
59
60
  "typescript": "~5.9.3",
60
- "vitest": "^4.0.14",
61
- "vue-tsc": "^3.1.4"
61
+ "vitest": "^4.0.15",
62
+ "vue-tsc": "^3.1.7"
62
63
  },
63
64
  "volta": {
64
65
  "node": "22.21.1"