astro-mermaid 1.1.0 → 1.3.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 +1 -1
- package/astro-mermaid-integration.js +207 -130
- package/package.json +18 -3
package/README.md
CHANGED
|
@@ -218,7 +218,7 @@ All mermaid diagram types are supported:
|
|
|
218
218
|
|
|
219
219
|
## Version
|
|
220
220
|
|
|
221
|
-
**Current:** `v1.0
|
|
221
|
+
**Current:** `v1.2.0` - Enhanced universal compatibility with dual plugin system
|
|
222
222
|
|
|
223
223
|
See [changelog](https://github.com/joesaby/astro-mermaid/releases) for version history.
|
|
224
224
|
|
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import { resolve } from 'import-meta-resolve';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Helper function to HTML-escape text content
|
|
5
|
+
* This ensures HTML tags in mermaid diagrams are preserved as text
|
|
6
|
+
*/
|
|
7
|
+
function escapeHtml(text) {
|
|
8
|
+
const htmlEntities = {
|
|
9
|
+
'&': '&',
|
|
10
|
+
'<': '<',
|
|
11
|
+
'>': '>',
|
|
12
|
+
'"': '"',
|
|
13
|
+
"'": '''
|
|
14
|
+
};
|
|
15
|
+
return text.replace(/[&<>"']/g, char => htmlEntities[char]);
|
|
16
|
+
}
|
|
17
|
+
|
|
3
18
|
/**
|
|
4
19
|
* Remark plugin to transform mermaid code blocks at the markdown level
|
|
5
20
|
*/
|
|
@@ -13,10 +28,10 @@ function remarkMermaidPlugin(options = {}) {
|
|
|
13
28
|
if (node.lang === 'mermaid') {
|
|
14
29
|
mermaidCount++;
|
|
15
30
|
|
|
16
|
-
// Transform to html node with pre.mermaid
|
|
31
|
+
// Transform to html node with pre.mermaid, escaping HTML content
|
|
17
32
|
const htmlNode = {
|
|
18
33
|
type: 'html',
|
|
19
|
-
value: `<pre class="mermaid">${node.value}</pre>`
|
|
34
|
+
value: `<pre class="mermaid">${escapeHtml(node.value)}</pre>`
|
|
20
35
|
};
|
|
21
36
|
|
|
22
37
|
// Replace the code node with html node
|
|
@@ -36,6 +51,49 @@ function remarkMermaidPlugin(options = {}) {
|
|
|
36
51
|
};
|
|
37
52
|
}
|
|
38
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Helper function to serialize HAST nodes back to HTML text
|
|
56
|
+
* This preserves HTML tags within the mermaid content
|
|
57
|
+
*/
|
|
58
|
+
function serializeHastChildren(children) {
|
|
59
|
+
let result = '';
|
|
60
|
+
|
|
61
|
+
for (const child of children) {
|
|
62
|
+
if (child.type === 'text') {
|
|
63
|
+
result += child.value;
|
|
64
|
+
} else if (child.type === 'element') {
|
|
65
|
+
// Reconstruct the HTML tag
|
|
66
|
+
const tagName = child.tagName;
|
|
67
|
+
const selfClosing = ['br', 'hr', 'img', 'input', 'meta', 'link'].includes(tagName);
|
|
68
|
+
|
|
69
|
+
result += `<${tagName}`;
|
|
70
|
+
|
|
71
|
+
// Add attributes if any
|
|
72
|
+
if (child.properties) {
|
|
73
|
+
for (const [key, value] of Object.entries(child.properties)) {
|
|
74
|
+
if (key !== 'className') {
|
|
75
|
+
result += ` ${key}="${value}"`;
|
|
76
|
+
} else if (Array.isArray(value)) {
|
|
77
|
+
result += ` class="${value.join(' ')}"`;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (selfClosing) {
|
|
83
|
+
result += '/>';
|
|
84
|
+
} else {
|
|
85
|
+
result += '>';
|
|
86
|
+
if (child.children && child.children.length > 0) {
|
|
87
|
+
result += serializeHastChildren(child.children);
|
|
88
|
+
}
|
|
89
|
+
result += `</${tagName}>`;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
|
|
39
97
|
/**
|
|
40
98
|
* Rehype plugin to transform mermaid code blocks
|
|
41
99
|
* Converts ```mermaid code blocks to <pre class="mermaid">
|
|
@@ -43,7 +101,6 @@ function remarkMermaidPlugin(options = {}) {
|
|
|
43
101
|
function rehypeMermaidPlugin(options = {}) {
|
|
44
102
|
return async function transformer(tree, file) {
|
|
45
103
|
const { visit } = await import('unist-util-visit');
|
|
46
|
-
const { toString } = await import('mdast-util-to-string');
|
|
47
104
|
|
|
48
105
|
let mermaidCount = 0;
|
|
49
106
|
|
|
@@ -59,8 +116,8 @@ function rehypeMermaidPlugin(options = {}) {
|
|
|
59
116
|
|
|
60
117
|
if (Array.isArray(className) && className.includes('language-mermaid')) {
|
|
61
118
|
mermaidCount++;
|
|
62
|
-
// Get the mermaid diagram content
|
|
63
|
-
const diagramContent =
|
|
119
|
+
// Get the mermaid diagram content, preserving HTML tags
|
|
120
|
+
const diagramContent = serializeHastChildren(codeNode.children || []);
|
|
64
121
|
|
|
65
122
|
// Transform to <pre class="mermaid">
|
|
66
123
|
node.properties = {
|
|
@@ -68,9 +125,10 @@ function rehypeMermaidPlugin(options = {}) {
|
|
|
68
125
|
className: ['mermaid']
|
|
69
126
|
};
|
|
70
127
|
|
|
128
|
+
// Escape HTML to preserve it as text content
|
|
71
129
|
node.children = [{
|
|
72
130
|
type: 'text',
|
|
73
|
-
value: diagramContent
|
|
131
|
+
value: escapeHtml(diagramContent)
|
|
74
132
|
}];
|
|
75
133
|
|
|
76
134
|
if (options.logger) {
|
|
@@ -166,12 +224,16 @@ const hasMermaidDiagrams = () => {
|
|
|
166
224
|
return document.querySelectorAll('pre.mermaid').length > 0;
|
|
167
225
|
};
|
|
168
226
|
|
|
169
|
-
//
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
227
|
+
// Shared mermaid initialization function
|
|
228
|
+
let mermaidPromise = null;
|
|
229
|
+
let mermaidInstance = null;
|
|
230
|
+
|
|
231
|
+
async function loadMermaid() {
|
|
232
|
+
if (mermaidPromise) return mermaidPromise;
|
|
233
|
+
|
|
234
|
+
console.log('[astro-mermaid] Loading mermaid.js...');
|
|
235
|
+
|
|
236
|
+
mermaidPromise = import('mermaid').then(async ({ default: mermaid }) => {
|
|
175
237
|
// Register icon packs if provided
|
|
176
238
|
const iconPacks = ${JSON.stringify(iconPacksConfig)};
|
|
177
239
|
if (iconPacks && iconPacks.length > 0) {
|
|
@@ -192,132 +254,147 @@ if (elkModule?.default) {
|
|
|
192
254
|
}
|
|
193
255
|
` : ``}
|
|
194
256
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const id = 'mermaid-' + Math.random().toString(36).slice(2, 11);
|
|
255
|
-
|
|
256
|
-
console.log('[astro-mermaid] Rendering diagram:', id);
|
|
257
|
-
|
|
258
|
-
try {
|
|
259
|
-
// Clear any existing error state
|
|
260
|
-
const existingGraph = document.getElementById(id);
|
|
261
|
-
if (existingGraph) {
|
|
262
|
-
existingGraph.remove();
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const { svg } = await mermaid.render(id, diagramDefinition);
|
|
266
|
-
diagram.innerHTML = svg;
|
|
267
|
-
diagram.setAttribute('data-processed', 'true');
|
|
268
|
-
console.log('[astro-mermaid] Successfully rendered diagram:', id);
|
|
269
|
-
} catch (error) {
|
|
270
|
-
console.error('[astro-mermaid] Mermaid rendering error for diagram:', id, error);
|
|
271
|
-
diagram.innerHTML = \`<div style="color: red; padding: 1rem; border: 1px solid red; border-radius: 0.5rem;">
|
|
272
|
-
<strong>Error rendering diagram:</strong><br/>
|
|
273
|
-
\${error.message || 'Unknown error'}
|
|
274
|
-
</div>\`;
|
|
275
|
-
diagram.setAttribute('data-processed', 'true');
|
|
276
|
-
}
|
|
277
|
-
}
|
|
257
|
+
mermaidInstance = mermaid;
|
|
258
|
+
return mermaid;
|
|
259
|
+
}).catch(error => {
|
|
260
|
+
console.error('[astro-mermaid] Failed to load mermaid:', error);
|
|
261
|
+
mermaidPromise = null;
|
|
262
|
+
throw error;
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
return mermaidPromise;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Mermaid configuration
|
|
269
|
+
const defaultConfig = ${JSON.stringify({
|
|
270
|
+
startOnLoad: false,
|
|
271
|
+
theme: theme,
|
|
272
|
+
...mermaidConfig
|
|
273
|
+
})};
|
|
274
|
+
|
|
275
|
+
// Theme mapping for auto-theme switching
|
|
276
|
+
const themeMap = {
|
|
277
|
+
'light': 'default',
|
|
278
|
+
'dark': 'dark'
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
// Initialize all mermaid diagrams
|
|
282
|
+
async function initMermaid() {
|
|
283
|
+
console.log('[astro-mermaid] Initializing mermaid diagrams...');
|
|
284
|
+
const diagrams = document.querySelectorAll('pre.mermaid');
|
|
285
|
+
|
|
286
|
+
console.log('[astro-mermaid] Found', diagrams.length, 'mermaid diagrams');
|
|
287
|
+
|
|
288
|
+
if (diagrams.length === 0) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Load mermaid if not already loaded
|
|
293
|
+
const mermaid = await loadMermaid();
|
|
294
|
+
|
|
295
|
+
// Get current theme from multiple sources
|
|
296
|
+
let currentTheme = defaultConfig.theme;
|
|
297
|
+
|
|
298
|
+
if (${autoTheme}) {
|
|
299
|
+
// Check both html and body for data-theme attribute
|
|
300
|
+
const htmlTheme = document.documentElement.getAttribute('data-theme');
|
|
301
|
+
const bodyTheme = document.body.getAttribute('data-theme');
|
|
302
|
+
const dataTheme = htmlTheme || bodyTheme;
|
|
303
|
+
currentTheme = themeMap[dataTheme] || defaultConfig.theme;
|
|
304
|
+
console.log('[astro-mermaid] Using theme:', currentTheme, 'from', htmlTheme ? 'html' : 'body');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Configure mermaid with gitGraph support
|
|
308
|
+
mermaid.initialize({
|
|
309
|
+
...defaultConfig,
|
|
310
|
+
theme: currentTheme,
|
|
311
|
+
gitGraph: {
|
|
312
|
+
mainBranchName: 'main',
|
|
313
|
+
showCommitLabel: true,
|
|
314
|
+
showBranches: true,
|
|
315
|
+
rotateCommitLabel: true
|
|
278
316
|
}
|
|
317
|
+
});
|
|
279
318
|
|
|
280
|
-
|
|
281
|
-
|
|
319
|
+
// Render each diagram
|
|
320
|
+
for (const diagram of diagrams) {
|
|
321
|
+
// Skip if already processed
|
|
322
|
+
if (diagram.hasAttribute('data-processed')) continue;
|
|
282
323
|
|
|
283
|
-
//
|
|
284
|
-
if (
|
|
285
|
-
|
|
286
|
-
for (const mutation of mutations) {
|
|
287
|
-
if (mutation.type === 'attributes' && mutation.attributeName === 'data-theme') {
|
|
288
|
-
// Reset processed state and re-render
|
|
289
|
-
document.querySelectorAll('pre.mermaid[data-processed]').forEach(diagram => {
|
|
290
|
-
diagram.removeAttribute('data-processed');
|
|
291
|
-
});
|
|
292
|
-
initMermaid();
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
// Observe both html and body for data-theme changes
|
|
298
|
-
observer.observe(document.documentElement, {
|
|
299
|
-
attributes: true,
|
|
300
|
-
attributeFilter: ['data-theme']
|
|
301
|
-
});
|
|
302
|
-
observer.observe(document.body, {
|
|
303
|
-
attributes: true,
|
|
304
|
-
attributeFilter: ['data-theme']
|
|
305
|
-
});
|
|
324
|
+
// Store original content
|
|
325
|
+
if (!diagram.hasAttribute('data-diagram')) {
|
|
326
|
+
diagram.setAttribute('data-diagram', diagram.textContent || '');
|
|
306
327
|
}
|
|
307
328
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
329
|
+
const diagramDefinition = diagram.getAttribute('data-diagram') || '';
|
|
330
|
+
const id = 'mermaid-' + Math.random().toString(36).slice(2, 11);
|
|
331
|
+
|
|
332
|
+
console.log('[astro-mermaid] Rendering diagram:', id);
|
|
333
|
+
|
|
334
|
+
try {
|
|
335
|
+
// Clear any existing error state
|
|
336
|
+
const existingGraph = document.getElementById(id);
|
|
337
|
+
if (existingGraph) {
|
|
338
|
+
existingGraph.remove();
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const { svg } = await mermaid.render(id, diagramDefinition);
|
|
342
|
+
diagram.innerHTML = svg;
|
|
343
|
+
diagram.setAttribute('data-processed', 'true');
|
|
344
|
+
console.log('[astro-mermaid] Successfully rendered diagram:', id);
|
|
345
|
+
} catch (error) {
|
|
346
|
+
console.error('[astro-mermaid] Mermaid rendering error for diagram:', id, error);
|
|
347
|
+
diagram.innerHTML = \`<div style="color: red; padding: 1rem; border: 1px solid red; border-radius: 0.5rem;">
|
|
348
|
+
<strong>Error rendering diagram:</strong><br/>
|
|
349
|
+
\${error.message || 'Unknown error'}
|
|
350
|
+
</div>\`;
|
|
351
|
+
diagram.setAttribute('data-processed', 'true');
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Initialize on first load if there are diagrams
|
|
357
|
+
if (hasMermaidDiagrams()) {
|
|
358
|
+
console.log('[astro-mermaid] Mermaid diagrams detected on initial load');
|
|
359
|
+
initMermaid();
|
|
360
|
+
} else {
|
|
361
|
+
console.log('[astro-mermaid] No mermaid diagrams found on initial load');
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Re-render on theme change if auto-theme is enabled
|
|
365
|
+
if (${autoTheme}) {
|
|
366
|
+
const observer = new MutationObserver((mutations) => {
|
|
367
|
+
for (const mutation of mutations) {
|
|
368
|
+
if (mutation.type === 'attributes' && mutation.attributeName === 'data-theme') {
|
|
369
|
+
// Reset processed state and re-render
|
|
370
|
+
document.querySelectorAll('pre.mermaid[data-processed]').forEach(diagram => {
|
|
371
|
+
diagram.removeAttribute('data-processed');
|
|
372
|
+
});
|
|
312
373
|
initMermaid();
|
|
313
374
|
}
|
|
314
|
-
}
|
|
315
|
-
})
|
|
316
|
-
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// Observe both html and body for data-theme changes
|
|
379
|
+
observer.observe(document.documentElement, {
|
|
380
|
+
attributes: true,
|
|
381
|
+
attributeFilter: ['data-theme']
|
|
382
|
+
});
|
|
383
|
+
observer.observe(document.body, {
|
|
384
|
+
attributes: true,
|
|
385
|
+
attributeFilter: ['data-theme']
|
|
317
386
|
});
|
|
318
|
-
} else {
|
|
319
|
-
console.log('[astro-mermaid] No mermaid diagrams found on this page, skipping mermaid.js load');
|
|
320
387
|
}
|
|
388
|
+
|
|
389
|
+
// Handle view transitions (for Astro View Transitions API)
|
|
390
|
+
// This is registered ALWAYS, not just when initial page has diagrams
|
|
391
|
+
document.addEventListener('astro:after-swap', () => {
|
|
392
|
+
console.log('[astro-mermaid] View transition detected');
|
|
393
|
+
// Check if new page has diagrams
|
|
394
|
+
if (hasMermaidDiagrams()) {
|
|
395
|
+
initMermaid();
|
|
396
|
+
}
|
|
397
|
+
});
|
|
321
398
|
`;
|
|
322
399
|
|
|
323
400
|
injectScript('page', mermaidScriptContent);
|
|
@@ -407,4 +484,4 @@ if (elkModule?.default) {
|
|
|
407
484
|
}
|
|
408
485
|
}
|
|
409
486
|
};
|
|
410
|
-
}
|
|
487
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "astro-mermaid",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "An Astro integration for rendering Mermaid diagrams with automatic theme switching and client-side rendering",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./astro-mermaid-integration.js",
|
|
@@ -43,11 +43,26 @@
|
|
|
43
43
|
"mdast-util-to-string": "^4.0.0",
|
|
44
44
|
"unist-util-visit": "^5.0.0"
|
|
45
45
|
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"claude": "claude",
|
|
48
|
+
"test": "vitest",
|
|
49
|
+
"test:ui": "vitest --ui",
|
|
50
|
+
"test:coverage": "vitest --coverage"
|
|
51
|
+
},
|
|
46
52
|
"devDependencies": {
|
|
53
|
+
"@anthropic-ai/claude-code": "^2.0.71",
|
|
47
54
|
"@types/hast": "^3.0.4",
|
|
55
|
+
"@vitest/ui": "^3.2.4",
|
|
48
56
|
"astro": "^5.0.0",
|
|
57
|
+
"hast-util-from-html": "^2.0.3",
|
|
49
58
|
"mermaid": "^11.0.0",
|
|
50
|
-
"
|
|
59
|
+
"rehype-parse": "^9.0.1",
|
|
60
|
+
"rehype-stringify": "^10.0.1",
|
|
61
|
+
"remark-mermaid": "^0.2.0",
|
|
62
|
+
"remark-parse": "^11.0.0",
|
|
63
|
+
"typescript": "^5.0.0",
|
|
64
|
+
"unified": "^11.0.5",
|
|
65
|
+
"vitest": "^3.2.4"
|
|
51
66
|
},
|
|
52
67
|
"repository": {
|
|
53
68
|
"type": "git",
|
|
@@ -57,4 +72,4 @@
|
|
|
57
72
|
"bugs": {
|
|
58
73
|
"url": "https://github.com/joesaby/astro-mermaid/issues"
|
|
59
74
|
}
|
|
60
|
-
}
|
|
75
|
+
}
|