@barzhsieh/nuxt-content-mermaid 2.1.1 → 2.1.3

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
@@ -1,5 +1,7 @@
1
1
  [English](./README.md) | [中文](./README.zh-TW.md)
2
2
 
3
+ [![nuxt-content-social-card](https://raw.githubusercontent.com/andy820621/nuxt-content-mermaid/main/src/assets/nuxt-content-mermaid.webp)](https://www.npmjs.com/package/@barzhsieh/nuxt-content-mermaid)
4
+
3
5
  # nuxt-content-mermaid
4
6
 
5
7
  [![npm version][npm-version-src]][npm-version-href]
package/README.zh-TW.md CHANGED
@@ -1,4 +1,6 @@
1
- [English](./README.md) | [中文](./README.zh-TW.md)
1
+ [中文](./README.zh-TW.md) | [English](./README.md)
2
+
3
+ [![nuxt-content-social-card](https://raw.githubusercontent.com/andy820621/nuxt-content-mermaid/main/src/assets/nuxt-content-mermaid.webp)](https://www.npmjs.com/package/@barzhsieh/nuxt-content-mermaid)
2
4
 
3
5
  # nuxt-content-mermaid
4
6
 
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": "^3.20.1 || ^4.1.0"
6
6
  },
7
- "version": "2.1.1",
7
+ "version": "2.1.3",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -2,8 +2,8 @@ import { defineNuxtModule, useLogger, createResolver, addVitePlugin, addPlugin,
2
2
  import { defu } from 'defu';
3
3
  import { DEFAULT_MERMAID_CONFIG, DEFAULT_DARK_THEME, DEFAULT_LIGHT_THEME, DEFAULT_FRONTMATTER_CONFIG_KEY } from '../dist/runtime/constants.js';
4
4
 
5
- const MERMAID_BLOCK = /```mermaid([\s\S]*?)```/gi;
6
5
  const SANITIZE_URL_PACKAGE = "@braintree/sanitize-url";
6
+ const MERMAID_FENCE_RE = /^[ \t]*(?:`{3,}|~{3,})[ \t]*mermaid(?:$|[ \t{[])/im;
7
7
  const DEFAULTS = {
8
8
  enabled: true,
9
9
  loader: {
@@ -106,7 +106,7 @@ export {}
106
106
  nuxt.hook("content:file:beforeParse", (ctx) => {
107
107
  const { file } = ctx;
108
108
  if (!file.id?.endsWith(".md")) return;
109
- if (!file.body.toLowerCase().includes("```mermaid")) return;
109
+ if (!MERMAID_FENCE_RE.test(file.body)) return;
110
110
  file.body = transformMermaidCodeBlocks(
111
111
  file.body,
112
112
  baseMermaidComponentName
@@ -115,12 +115,106 @@ export {}
115
115
  }
116
116
  });
117
117
  function transformMermaidCodeBlocks(body, componentName, frontmatterConfigKey = DEFAULT_FRONTMATTER_CONFIG_KEY) {
118
- return body.replace(MERMAID_BLOCK, (_, rawCode = "") => {
118
+ const newline = body.includes("\r\n") ? "\r\n" : "\n";
119
+ const lines = body.split(newline);
120
+ const output = [];
121
+ let inFence = false;
122
+ let fenceChar = null;
123
+ let fenceLength = 0;
124
+ const isClosingFence = (line) => {
125
+ if (!fenceChar) return false;
126
+ const trimmed = line.trim();
127
+ if (trimmed.length < fenceLength) return false;
128
+ for (const char of trimmed) {
129
+ if (char !== fenceChar) return false;
130
+ }
131
+ return true;
132
+ };
133
+ const isFenceMarkerChar = (char) => char === "`" || char === "~";
134
+ const parseFence = (line) => {
135
+ let index = 0;
136
+ while (index < line.length && (line[index] === " " || line[index] === " "))
137
+ index++;
138
+ const indent = line.slice(0, index);
139
+ const markerChar = line[index];
140
+ if (!isFenceMarkerChar(markerChar))
141
+ return null;
142
+ let markerEnd = index;
143
+ while (markerEnd < line.length && line[markerEnd] === markerChar)
144
+ markerEnd++;
145
+ const markerLength = markerEnd - index;
146
+ if (markerLength < 3)
147
+ return null;
148
+ return {
149
+ indent,
150
+ markerChar,
151
+ markerLength,
152
+ info: line.slice(markerEnd).trim()
153
+ };
154
+ };
155
+ const isMermaidFence = (info) => {
156
+ if (!info) return false;
157
+ const lower = info.toLowerCase();
158
+ if (!lower.startsWith("mermaid")) return false;
159
+ const next = lower.slice("mermaid".length, "mermaid".length + 1);
160
+ return next === "" || /\s/.test(next) || next === "{" || next === "[";
161
+ };
162
+ const buildComponentTag = (encoded) => `<${componentName} :config="${frontmatterConfigKey}" code="${encoded}"></${componentName}>`;
163
+ for (let index = 0; index < lines.length; index++) {
164
+ const line = lines[index];
165
+ if (inFence) {
166
+ output.push(line);
167
+ if (isClosingFence(line)) {
168
+ inFence = false;
169
+ fenceChar = null;
170
+ fenceLength = 0;
171
+ }
172
+ continue;
173
+ }
174
+ const fence = parseFence(line);
175
+ if (!fence) {
176
+ output.push(line);
177
+ continue;
178
+ }
179
+ if (!isMermaidFence(fence.info)) {
180
+ inFence = true;
181
+ fenceChar = fence.markerChar;
182
+ fenceLength = fence.markerLength;
183
+ output.push(line);
184
+ continue;
185
+ }
186
+ let closingIndex = -1;
187
+ for (let j = index + 1; j < lines.length; j++) {
188
+ const trimmed = lines[j].trim();
189
+ if (trimmed.length < fence.markerLength) continue;
190
+ let isFence = true;
191
+ for (const char of trimmed) {
192
+ if (char !== fence.markerChar) {
193
+ isFence = false;
194
+ break;
195
+ }
196
+ }
197
+ if (isFence) {
198
+ closingIndex = j;
199
+ break;
200
+ }
201
+ }
202
+ if (closingIndex === -1) {
203
+ output.push(line);
204
+ continue;
205
+ }
206
+ const rawCode = lines.slice(index + 1, closingIndex).join(newline);
119
207
  const code = rawCode.trim();
120
- if (!code) return _;
208
+ if (!code) {
209
+ output.push(...lines.slice(index, closingIndex + 1));
210
+ index = closingIndex;
211
+ continue;
212
+ }
121
213
  const encoded = encodeURIComponent(code);
122
- return `<${componentName} :config="${frontmatterConfigKey}" code="${encoded}"></${componentName}>`;
123
- });
214
+ output.push(`${fence.indent}${buildComponentTag(encoded)}`);
215
+ index = closingIndex;
216
+ }
217
+ return output.join(newline);
124
218
  }
125
219
 
126
220
  export { module$1 as default, transformMermaidCodeBlocks };
@@ -298,6 +298,7 @@ watch(decodedCode, (newCode) => {
298
298
  <div
299
299
  v-else
300
300
  class="mermaid-wrapper"
301
+ :class="{ 'ncm-is-loading': isLoading && !hasRenderedOnce }"
301
302
  >
302
303
  <div ref="mermaidContainer">
303
304
  <!-- Initially show the slot's <pre><code> for SSR; client-side rendering will replace it with the mermaid SVG -->
@@ -337,5 +338,5 @@ watch(decodedCode, (newCode) => {
337
338
  </template>
338
339
 
339
340
  <style scoped>
340
- .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-wrapper :deep(svg){display:inline-block;vertical-align:middle}.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}
341
+ .mermaid-wrapper{--ncm-loading-min-height:160px;position:relative;text-align:center}.mermaid-wrapper.ncm-is-loading{min-height:var(--ncm-loading-min-height,160px)}.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-wrapper :deep(svg){display:inline-block;vertical-align:middle}.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}
341
342
  </style>
@@ -1,15 +1,19 @@
1
1
  <template>
2
2
  <svg
3
- class="spinner"
3
+ class="ncm-spinner"
4
4
  viewBox="0 0 50 50"
5
5
  >
6
6
  <circle
7
- class="path"
7
+ class="ncm-spinner__path"
8
8
  cx="25"
9
9
  cy="25"
10
10
  r="20"
11
11
  fill="none"
12
12
  stroke-width="5"
13
+ stroke="rgb(197, 197, 197)"
14
+ stroke-linecap="round"
15
+ stroke-dasharray="1 150"
16
+ stroke-dashoffset="0"
13
17
  />
14
18
  </svg>
15
19
  </template>
@@ -20,6 +24,6 @@ export default {
20
24
  };
21
25
  </script>
22
26
 
23
- <style>
24
- .spinner{--radius:20px;animation:rotate 2s linear infinite;height:calc(var(--radius)*2);left:50%;margin:calc(var(--radius)*-1) 0 0 calc(var(--radius)*-1);position:absolute;top:50%;width:calc(var(--radius)*2);z-index:2}.spinner .path{animation:dash 1.5s ease-in-out infinite;stroke:#c5c5c5;stroke-linecap:round}@keyframes rotate{to{transform:rotate(1turn)}}@keyframes dash{0%{stroke-dasharray:1,150;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-35}to{stroke-dasharray:90,150;stroke-dashoffset:-124}}
27
+ <style scoped>
28
+ .ncm-spinner{--radius:20px;animation:rotate 2s linear infinite;height:calc(var(--radius)*2);left:50%;margin:calc(var(--radius)*-1) 0 0 calc(var(--radius)*-1);position:absolute;top:50%;transform-origin:50% 50%;width:calc(var(--radius)*2);z-index:2}.ncm-spinner__path{animation:dash 1.5s ease-in-out infinite}@keyframes rotate{to{transform:rotate(1turn)}}@keyframes dash{0%{stroke-dasharray:1,150;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-35}to{stroke-dasharray:90,150;stroke-dashoffset:-124}}
25
29
  </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barzhsieh/nuxt-content-mermaid",
3
- "version": "2.1.1",
3
+ "version": "2.1.3",
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",