astro-mermaid 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -2
- package/astro-mermaid-integration.js +166 -106
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# astro-mermaid
|
|
2
2
|
|
|
3
|
-
An Astro integration for rendering Mermaid diagrams with automatic theme switching and client-side rendering.
|
|
3
|
+
An Astro integration for rendering Mermaid diagrams with automatic theme switching and client-side rendering. This follows the mermaid integration in [cloudflare-docs](https://github.com/cloudflare/cloudflare-docs)
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
@@ -10,6 +10,11 @@ An Astro integration for rendering Mermaid diagrams with automatic theme switchi
|
|
|
10
10
|
- ⚡ Vite optimization for fast development
|
|
11
11
|
- 🔧 Customizable mermaid configuration
|
|
12
12
|
- 🎯 TypeScript support
|
|
13
|
+
- 🔒 Privacy-focused with no external server dependencies
|
|
14
|
+
- 🌐 Offline-capable rendering
|
|
15
|
+
- ⚡ Zero network latency for diagram generation
|
|
16
|
+
- 📦 Conditional loading - mermaid.js only loads on pages with diagrams
|
|
17
|
+
- 🎭 Smooth loading animations to prevent layout shifts
|
|
13
18
|
|
|
14
19
|
## Installation
|
|
15
20
|
|
|
@@ -27,7 +32,28 @@ import mermaid from 'astro-mermaid';
|
|
|
27
32
|
|
|
28
33
|
export default defineConfig({
|
|
29
34
|
integrations: [
|
|
30
|
-
mermaid(
|
|
35
|
+
mermaid({
|
|
36
|
+
theme: 'forest'
|
|
37
|
+
})
|
|
38
|
+
]
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Important: Integration Order
|
|
43
|
+
|
|
44
|
+
When using with Starlight or other integrations that process markdown, make sure to place the mermaid integration **before** them:
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
import { defineConfig } from 'astro/config';
|
|
48
|
+
import starlight from '@astrojs/starlight';
|
|
49
|
+
import mermaid from 'astro-mermaid';
|
|
50
|
+
|
|
51
|
+
export default defineConfig({
|
|
52
|
+
integrations: [
|
|
53
|
+
mermaid(), // Must come BEFORE starlight
|
|
54
|
+
starlight({
|
|
55
|
+
title: 'My Docs'
|
|
56
|
+
})
|
|
31
57
|
]
|
|
32
58
|
});
|
|
33
59
|
```
|
|
@@ -70,6 +96,39 @@ If `autoTheme` is enabled (default), the integration will automatically switch b
|
|
|
70
96
|
- `data-theme="light"` → uses 'default' mermaid theme
|
|
71
97
|
- `data-theme="dark"` → uses 'dark' mermaid theme
|
|
72
98
|
|
|
99
|
+
## Client-Side Rendering & Security
|
|
100
|
+
|
|
101
|
+
### 🔒 Privacy & Security Benefits
|
|
102
|
+
|
|
103
|
+
This integration uses **100% client-side rendering** with zero external dependencies at runtime:
|
|
104
|
+
|
|
105
|
+
- **No Data Transmission**: Your diagram content never leaves your browser
|
|
106
|
+
- **No External Servers**: No calls to mermaid.live or any external services
|
|
107
|
+
- **Offline Capable**: Works completely offline after initial page load
|
|
108
|
+
- **Zero Network Latency**: Instant diagram rendering without network delays
|
|
109
|
+
- **Corporate Firewall Friendly**: No external domains need to be whitelisted
|
|
110
|
+
|
|
111
|
+
### ⚡ How It Works
|
|
112
|
+
|
|
113
|
+
1. **Build Time**: Mermaid code blocks are transformed to `<pre class="mermaid">` elements
|
|
114
|
+
2. **Runtime**: The bundled Mermaid JavaScript library renders diagrams locally
|
|
115
|
+
3. **Output**: Pure SVG generated entirely in your browser
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
// All rendering happens locally - no network calls
|
|
119
|
+
import mermaid from 'mermaid';
|
|
120
|
+
const { svg } = await mermaid.render(id, diagramDefinition);
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 🛡️ Enterprise & Compliance
|
|
124
|
+
|
|
125
|
+
Perfect for:
|
|
126
|
+
- Corporate environments with strict security policies
|
|
127
|
+
- GDPR/privacy-compliant applications
|
|
128
|
+
- Air-gapped or restricted network environments
|
|
129
|
+
- Applications requiring data sovereignty
|
|
130
|
+
- High-security environments where external requests are prohibited
|
|
131
|
+
|
|
73
132
|
## Supported Diagrams
|
|
74
133
|
|
|
75
134
|
All mermaid diagram types are supported:
|
|
@@ -90,6 +149,14 @@ All mermaid diagram types are supported:
|
|
|
90
149
|
- Quadrant charts
|
|
91
150
|
- And more!
|
|
92
151
|
|
|
152
|
+
## Demo
|
|
153
|
+
|
|
154
|
+
Check out the [live demo](https://starlight-mermaid-demo.netlify.app/) built with Starlight.
|
|
155
|
+
|
|
156
|
+
## Contributing
|
|
157
|
+
|
|
158
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
159
|
+
|
|
93
160
|
## License
|
|
94
161
|
|
|
95
162
|
MIT
|
|
@@ -92,129 +92,147 @@ export default function astroMermaid(options = {}) {
|
|
|
92
92
|
}
|
|
93
93
|
});
|
|
94
94
|
|
|
95
|
-
// Inject client-side mermaid script
|
|
95
|
+
// Inject client-side mermaid script with conditional loading
|
|
96
96
|
const mermaidScriptContent = `
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const defaultConfig = ${JSON.stringify({
|
|
101
|
-
startOnLoad: false,
|
|
102
|
-
theme: theme,
|
|
103
|
-
...mermaidConfig
|
|
104
|
-
})};
|
|
105
|
-
|
|
106
|
-
// Theme mapping for auto-theme switching
|
|
107
|
-
const themeMap = {
|
|
108
|
-
'light': 'default',
|
|
109
|
-
'dark': 'dark'
|
|
97
|
+
// Check if page has mermaid diagrams
|
|
98
|
+
const hasMermaidDiagrams = () => {
|
|
99
|
+
return document.querySelectorAll('pre.mermaid').length > 0;
|
|
110
100
|
};
|
|
111
101
|
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
console.log('[astro-mermaid]
|
|
115
|
-
const diagrams = document.querySelectorAll('pre.mermaid');
|
|
116
|
-
|
|
117
|
-
console.log('[astro-mermaid] Found', diagrams.length, 'mermaid diagrams');
|
|
118
|
-
|
|
119
|
-
if (diagrams.length === 0) {
|
|
120
|
-
console.log('[astro-mermaid] No mermaid diagrams found. Looking for code blocks...');
|
|
121
|
-
const codeBlocks = document.querySelectorAll('pre code.language-mermaid');
|
|
122
|
-
console.log('[astro-mermaid] Found', codeBlocks.length, 'mermaid code blocks');
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Get current theme
|
|
127
|
-
let currentTheme = defaultConfig.theme;
|
|
128
|
-
|
|
129
|
-
if (${autoTheme}) {
|
|
130
|
-
const dataTheme = document.documentElement.getAttribute('data-theme');
|
|
131
|
-
currentTheme = themeMap[dataTheme] || defaultConfig.theme;
|
|
132
|
-
console.log('[astro-mermaid] Using theme:', currentTheme);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Configure mermaid
|
|
136
|
-
mermaid.initialize({
|
|
137
|
-
...defaultConfig,
|
|
138
|
-
theme: currentTheme
|
|
139
|
-
});
|
|
102
|
+
// Only proceed if there are mermaid diagrams on the page
|
|
103
|
+
if (hasMermaidDiagrams()) {
|
|
104
|
+
console.log('[astro-mermaid] Mermaid diagrams detected, loading mermaid.js...');
|
|
140
105
|
|
|
141
|
-
//
|
|
142
|
-
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
106
|
+
// Dynamically import mermaid only when needed
|
|
107
|
+
import('mermaid').then(({ default: mermaid }) => {
|
|
108
|
+
// Mermaid configuration
|
|
109
|
+
const defaultConfig = ${JSON.stringify({
|
|
110
|
+
startOnLoad: false,
|
|
111
|
+
theme: theme,
|
|
112
|
+
...mermaidConfig
|
|
113
|
+
})};
|
|
114
|
+
|
|
115
|
+
// Theme mapping for auto-theme switching
|
|
116
|
+
const themeMap = {
|
|
117
|
+
'light': 'default',
|
|
118
|
+
'dark': 'dark'
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// Initialize all mermaid diagrams
|
|
122
|
+
async function initMermaid() {
|
|
123
|
+
console.log('[astro-mermaid] Initializing mermaid diagrams...');
|
|
124
|
+
const diagrams = document.querySelectorAll('pre.mermaid');
|
|
125
|
+
|
|
126
|
+
console.log('[astro-mermaid] Found', diagrams.length, 'mermaid diagrams');
|
|
127
|
+
|
|
128
|
+
if (diagrams.length === 0) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Get current theme
|
|
133
|
+
let currentTheme = defaultConfig.theme;
|
|
134
|
+
|
|
135
|
+
if (${autoTheme}) {
|
|
136
|
+
const dataTheme = document.documentElement.getAttribute('data-theme');
|
|
137
|
+
currentTheme = themeMap[dataTheme] || defaultConfig.theme;
|
|
138
|
+
console.log('[astro-mermaid] Using theme:', currentTheme);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Configure mermaid with gitGraph support
|
|
142
|
+
mermaid.initialize({
|
|
143
|
+
...defaultConfig,
|
|
144
|
+
theme: currentTheme,
|
|
145
|
+
gitGraph: {
|
|
146
|
+
mainBranchName: 'main',
|
|
147
|
+
showCommitLabel: true,
|
|
148
|
+
showBranches: true,
|
|
149
|
+
rotateCommitLabel: true
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Render each diagram
|
|
154
|
+
for (const diagram of diagrams) {
|
|
155
|
+
// Skip if already processed
|
|
156
|
+
if (diagram.hasAttribute('data-processed')) continue;
|
|
157
|
+
|
|
158
|
+
// Store original content
|
|
159
|
+
if (!diagram.hasAttribute('data-diagram')) {
|
|
160
|
+
diagram.setAttribute('data-diagram', diagram.textContent || '');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const diagramDefinition = diagram.getAttribute('data-diagram') || '';
|
|
164
|
+
const id = 'mermaid-' + Math.random().toString(36).slice(2, 11);
|
|
165
|
+
|
|
166
|
+
console.log('[astro-mermaid] Rendering diagram:', id);
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
// Clear any existing error state
|
|
170
|
+
const existingGraph = document.getElementById(id);
|
|
171
|
+
if (existingGraph) {
|
|
172
|
+
existingGraph.remove();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const { svg } = await mermaid.render(id, diagramDefinition);
|
|
176
|
+
diagram.innerHTML = svg;
|
|
177
|
+
diagram.setAttribute('data-processed', 'true');
|
|
178
|
+
console.log('[astro-mermaid] Successfully rendered diagram:', id);
|
|
179
|
+
} catch (error) {
|
|
180
|
+
console.error('[astro-mermaid] Mermaid rendering error for diagram:', id, error);
|
|
181
|
+
diagram.innerHTML = \`<div style="color: red; padding: 1rem; border: 1px solid red; border-radius: 0.5rem;">
|
|
182
|
+
<strong>Error rendering diagram:</strong><br/>
|
|
183
|
+
\${error.message || 'Unknown error'}
|
|
184
|
+
</div>\`;
|
|
185
|
+
diagram.setAttribute('data-processed', 'true');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
164
188
|
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
189
|
|
|
168
|
-
// Initialize
|
|
169
|
-
|
|
170
|
-
document.addEventListener('DOMContentLoaded', initMermaid);
|
|
171
|
-
} else {
|
|
172
|
-
initMermaid();
|
|
173
|
-
}
|
|
190
|
+
// Initialize immediately since DOM is ready
|
|
191
|
+
initMermaid();
|
|
174
192
|
|
|
175
|
-
// Re-render on theme change if auto-theme is enabled
|
|
176
|
-
if (${autoTheme}) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
193
|
+
// Re-render on theme change if auto-theme is enabled
|
|
194
|
+
if (${autoTheme}) {
|
|
195
|
+
const observer = new MutationObserver((mutations) => {
|
|
196
|
+
for (const mutation of mutations) {
|
|
197
|
+
if (mutation.type === 'attributes' && mutation.attributeName === 'data-theme') {
|
|
198
|
+
// Reset processed state and re-render
|
|
199
|
+
document.querySelectorAll('pre.mermaid[data-processed]').forEach(diagram => {
|
|
200
|
+
diagram.removeAttribute('data-processed');
|
|
201
|
+
});
|
|
202
|
+
initMermaid();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
observer.observe(document.documentElement, {
|
|
208
|
+
attributes: true,
|
|
209
|
+
attributeFilter: ['data-theme']
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Handle view transitions (for Astro View Transitions API)
|
|
214
|
+
document.addEventListener('astro:after-swap', () => {
|
|
215
|
+
// Check again if new page has diagrams
|
|
216
|
+
if (hasMermaidDiagrams()) {
|
|
184
217
|
initMermaid();
|
|
185
218
|
}
|
|
186
|
-
}
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
observer.observe(document.documentElement, {
|
|
190
|
-
attributes: true,
|
|
191
|
-
attributeFilter: ['data-theme']
|
|
219
|
+
});
|
|
220
|
+
}).catch(error => {
|
|
221
|
+
console.error('[astro-mermaid] Failed to load mermaid:', error);
|
|
192
222
|
});
|
|
223
|
+
} else {
|
|
224
|
+
console.log('[astro-mermaid] No mermaid diagrams found on this page, skipping mermaid.js load');
|
|
193
225
|
}
|
|
194
|
-
|
|
195
|
-
// Handle view transitions (for Astro View Transitions API)
|
|
196
|
-
document.addEventListener('astro:after-swap', initMermaid);
|
|
197
226
|
`;
|
|
198
227
|
|
|
199
228
|
injectScript('page', mermaidScriptContent);
|
|
200
229
|
|
|
201
|
-
// Add CSS to the page
|
|
230
|
+
// Add CSS to the page with layout shift prevention
|
|
202
231
|
injectScript('page', `
|
|
203
232
|
// Add CSS for mermaid diagrams
|
|
204
233
|
const style = document.createElement('style');
|
|
205
234
|
style.textContent = \`
|
|
206
|
-
/*
|
|
207
|
-
pre.mermaid:not([data-processed]) {
|
|
208
|
-
opacity: 0;
|
|
209
|
-
transition: opacity 0.3s ease-in-out;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/* Show processed diagrams */
|
|
213
|
-
pre.mermaid[data-processed] {
|
|
214
|
-
opacity: 1;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/* Center mermaid diagrams and add spacing */
|
|
235
|
+
/* Prevent layout shifts by setting minimum height */
|
|
218
236
|
pre.mermaid {
|
|
219
237
|
display: flex;
|
|
220
238
|
justify-content: center;
|
|
@@ -224,6 +242,37 @@ document.addEventListener('astro:after-swap', initMermaid);
|
|
|
224
242
|
background-color: transparent;
|
|
225
243
|
border: none;
|
|
226
244
|
overflow: auto;
|
|
245
|
+
min-height: 200px; /* Prevent layout shift */
|
|
246
|
+
position: relative;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/* Loading state with skeleton loader */
|
|
250
|
+
pre.mermaid:not([data-processed]) {
|
|
251
|
+
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
|
252
|
+
background-size: 200% 100%;
|
|
253
|
+
animation: shimmer 1.5s infinite;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/* Dark mode skeleton loader */
|
|
257
|
+
[data-theme="dark"] pre.mermaid:not([data-processed]) {
|
|
258
|
+
background: linear-gradient(90deg, #2a2a2a 25%, #3a3a3a 50%, #2a2a2a 75%);
|
|
259
|
+
background-size: 200% 100%;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
@keyframes shimmer {
|
|
263
|
+
0% {
|
|
264
|
+
background-position: -200% 0;
|
|
265
|
+
}
|
|
266
|
+
100% {
|
|
267
|
+
background-position: 200% 0;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/* Show processed diagrams with smooth transition */
|
|
272
|
+
pre.mermaid[data-processed] {
|
|
273
|
+
animation: none;
|
|
274
|
+
background: transparent;
|
|
275
|
+
min-height: auto; /* Allow natural height after render */
|
|
227
276
|
}
|
|
228
277
|
|
|
229
278
|
/* Ensure responsive sizing for mermaid SVGs */
|
|
@@ -234,18 +283,29 @@ document.addEventListener('astro:after-swap', initMermaid);
|
|
|
234
283
|
|
|
235
284
|
/* Optional: Add subtle background for better visibility */
|
|
236
285
|
@media (prefers-color-scheme: dark) {
|
|
237
|
-
pre.mermaid {
|
|
286
|
+
pre.mermaid[data-processed] {
|
|
238
287
|
background-color: rgba(255, 255, 255, 0.02);
|
|
239
288
|
border-radius: 0.5rem;
|
|
240
289
|
}
|
|
241
290
|
}
|
|
242
291
|
|
|
243
292
|
@media (prefers-color-scheme: light) {
|
|
244
|
-
pre.mermaid {
|
|
293
|
+
pre.mermaid[data-processed] {
|
|
245
294
|
background-color: rgba(0, 0, 0, 0.02);
|
|
246
295
|
border-radius: 0.5rem;
|
|
247
296
|
}
|
|
248
297
|
}
|
|
298
|
+
|
|
299
|
+
/* Respect user's color scheme preference */
|
|
300
|
+
[data-theme="dark"] pre.mermaid[data-processed] {
|
|
301
|
+
background-color: rgba(255, 255, 255, 0.02);
|
|
302
|
+
border-radius: 0.5rem;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
[data-theme="light"] pre.mermaid[data-processed] {
|
|
306
|
+
background-color: rgba(0, 0, 0, 0.02);
|
|
307
|
+
border-radius: 0.5rem;
|
|
308
|
+
}
|
|
249
309
|
\`;
|
|
250
310
|
document.head.appendChild(style);
|
|
251
311
|
`);
|
package/package.json
CHANGED