@eventcatalog/core 3.4.0 → 3.4.1
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/dist/analytics/analytics.cjs +1 -1
- package/dist/analytics/analytics.js +2 -2
- package/dist/analytics/log-build.cjs +1 -1
- package/dist/analytics/log-build.js +3 -3
- package/dist/{chunk-MJRHV77M.js → chunk-56E6QDHD.js} +1 -1
- package/dist/{chunk-Q4DKMESA.js → chunk-6AW5YJSJ.js} +1 -1
- package/dist/{chunk-KFZIBXRQ.js → chunk-7CV7NFRY.js} +1 -1
- package/dist/{chunk-VAGFX36R.js → chunk-IVLW66F7.js} +1 -1
- package/dist/{chunk-GLMX3ZTY.js → chunk-SPL7HGIZ.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +1 -1
- package/dist/eventcatalog.js +5 -5
- package/dist/generate.cjs +1 -1
- package/dist/generate.js +3 -3
- package/dist/utils/cli-logger.cjs +1 -1
- package/dist/utils/cli-logger.js +2 -2
- package/eventcatalog/src/enterprise/custom-documentation/pages/docs/custom/index.astro +21 -143
- package/eventcatalog/src/enterprise/mcp/mcp-server.ts +4 -4
- package/eventcatalog/src/env.d.ts +11 -0
- package/eventcatalog/src/pages/diagrams/[id]/[version]/embed.astro +433 -10
- package/eventcatalog/src/pages/diagrams/[id]/[version]/index.astro +21 -100
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +26 -143
- package/eventcatalog/src/types/mcp-modules.d.ts +66 -0
- package/eventcatalog/src/utils/mermaid-zoom.ts +751 -0
- package/package.json +2 -1
|
@@ -33,6 +33,9 @@ const diagramId = props.data.id;
|
|
|
33
33
|
--ec-page-text-muted: 100 116 139;
|
|
34
34
|
--ec-page-border: 226 232 240;
|
|
35
35
|
--ec-content-hover: 241 245 249;
|
|
36
|
+
--ec-card-bg: 255 255 255;
|
|
37
|
+
--ec-icon-color: 100 116 139;
|
|
38
|
+
--ec-icon-hover: 15 23 42;
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
:root[data-theme='dark'] {
|
|
@@ -41,6 +44,9 @@ const diagramId = props.data.id;
|
|
|
41
44
|
--ec-page-text-muted: 139 148 158;
|
|
42
45
|
--ec-page-border: 33 38 45;
|
|
43
46
|
--ec-content-hover: 33 38 45;
|
|
47
|
+
--ec-card-bg: 22 27 34;
|
|
48
|
+
--ec-icon-color: 139 148 158;
|
|
49
|
+
--ec-icon-hover: 240 246 252;
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
body {
|
|
@@ -66,6 +72,16 @@ const diagramId = props.data.id;
|
|
|
66
72
|
display: block;
|
|
67
73
|
}
|
|
68
74
|
|
|
75
|
+
/* Zoom controls styling - must stay as CSS since svg-pan-zoom injects them */
|
|
76
|
+
.svg-pan-zoom-control {
|
|
77
|
+
fill: rgb(var(--ec-page-text-muted)) !important;
|
|
78
|
+
cursor: pointer;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.svg-pan-zoom-control:hover {
|
|
82
|
+
fill: rgb(var(--ec-page-text)) !important;
|
|
83
|
+
}
|
|
84
|
+
|
|
69
85
|
/* Prose styles */
|
|
70
86
|
.prose {
|
|
71
87
|
max-width: none;
|
|
@@ -172,6 +188,17 @@ const diagramId = props.data.id;
|
|
|
172
188
|
</div>
|
|
173
189
|
|
|
174
190
|
<script is:inline define:vars={{ config, baseUrl: import.meta.env.BASE_URL }}>
|
|
191
|
+
/**
|
|
192
|
+
* IMPORTANT: Diagram zoom functionality is intentionally duplicated here from mermaid-zoom.ts.
|
|
193
|
+
*
|
|
194
|
+
* This embed page is designed to be loaded in iframes and must be fully self-contained.
|
|
195
|
+
* It uses CDN imports (e.g., https://cdn.jsdelivr.net/npm/mermaid@10) instead of npm
|
|
196
|
+
* package imports to ensure isolation from the parent application's build process.
|
|
197
|
+
*
|
|
198
|
+
* If you need to update the zoom controls or diagram rendering logic, please update
|
|
199
|
+
* both this file AND eventcatalog/src/utils/mermaid-zoom.ts to keep them in sync.
|
|
200
|
+
*/
|
|
201
|
+
|
|
175
202
|
window.eventcatalog = { mermaid: config?.mermaid };
|
|
176
203
|
|
|
177
204
|
// Set the go-to-diagram link by parsing URL: /diagrams/{id}/{version}/embed
|
|
@@ -185,6 +212,356 @@ const diagramId = props.data.id;
|
|
|
185
212
|
goToBtn.href = `${base}/diagrams/${diagramId}/${version}`;
|
|
186
213
|
}
|
|
187
214
|
|
|
215
|
+
// Store zoom instances for cleanup
|
|
216
|
+
const zoomInstances = new Map();
|
|
217
|
+
|
|
218
|
+
function createZoomControls(onZoomIn, onZoomOut, onFitView) {
|
|
219
|
+
// Detect dark mode
|
|
220
|
+
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
|
|
221
|
+
|
|
222
|
+
// Use explicit colors based on theme
|
|
223
|
+
const bgColor = isDark ? '#161b22' : '#ffffff';
|
|
224
|
+
const borderColor = isDark ? '#30363d' : '#e2e8f0';
|
|
225
|
+
const iconColor = isDark ? '#8b949e' : '#64748b';
|
|
226
|
+
const iconHoverColor = isDark ? '#f0f6fc' : '#0f172a';
|
|
227
|
+
const hoverBgColor = isDark ? '#21262d' : '#f1f5f9';
|
|
228
|
+
|
|
229
|
+
const controls = document.createElement('div');
|
|
230
|
+
controls.className = 'mermaid-zoom-controls';
|
|
231
|
+
controls.style.cssText = `
|
|
232
|
+
position: absolute;
|
|
233
|
+
bottom: 12px;
|
|
234
|
+
left: 12px;
|
|
235
|
+
display: flex;
|
|
236
|
+
flex-direction: column;
|
|
237
|
+
background: ${bgColor};
|
|
238
|
+
border-radius: 6px;
|
|
239
|
+
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
|
240
|
+
border: 1px solid ${borderColor};
|
|
241
|
+
overflow: hidden;
|
|
242
|
+
z-index: 10;
|
|
243
|
+
`;
|
|
244
|
+
|
|
245
|
+
const createButton = (svg, title, onClick, isLast = false) => {
|
|
246
|
+
const btn = document.createElement('button');
|
|
247
|
+
btn.type = 'button';
|
|
248
|
+
btn.title = title;
|
|
249
|
+
btn.innerHTML = svg;
|
|
250
|
+
btn.onclick = onClick;
|
|
251
|
+
btn.style.cssText = `
|
|
252
|
+
display: flex;
|
|
253
|
+
justify-content: center;
|
|
254
|
+
align-items: center;
|
|
255
|
+
width: 26px;
|
|
256
|
+
height: 26px;
|
|
257
|
+
padding: 0;
|
|
258
|
+
margin: 0;
|
|
259
|
+
border: none;
|
|
260
|
+
background: ${bgColor};
|
|
261
|
+
color: ${iconColor};
|
|
262
|
+
cursor: pointer;
|
|
263
|
+
transition: background-color 0.15s, color 0.15s;
|
|
264
|
+
line-height: 1;
|
|
265
|
+
${!isLast ? `border-bottom: 1px solid ${borderColor};` : ''}
|
|
266
|
+
`;
|
|
267
|
+
// Ensure SVG is properly centered
|
|
268
|
+
const svgEl = btn.querySelector('svg');
|
|
269
|
+
if (svgEl) {
|
|
270
|
+
svgEl.style.display = 'block';
|
|
271
|
+
}
|
|
272
|
+
btn.onmouseenter = () => {
|
|
273
|
+
btn.style.backgroundColor = hoverBgColor;
|
|
274
|
+
btn.style.color = iconHoverColor;
|
|
275
|
+
};
|
|
276
|
+
btn.onmouseleave = () => {
|
|
277
|
+
btn.style.backgroundColor = bgColor;
|
|
278
|
+
btn.style.color = iconColor;
|
|
279
|
+
};
|
|
280
|
+
return btn;
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const plusSvg = `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>`;
|
|
284
|
+
const minusSvg = `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"></line></svg>`;
|
|
285
|
+
const fitSvg = `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"></path></svg>`;
|
|
286
|
+
|
|
287
|
+
controls.appendChild(createButton(plusSvg, 'Zoom in', onZoomIn));
|
|
288
|
+
controls.appendChild(createButton(minusSvg, 'Zoom out', onZoomOut));
|
|
289
|
+
controls.appendChild(createButton(fitSvg, 'Fit view', onFitView, true));
|
|
290
|
+
|
|
291
|
+
return controls;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Icons for toolbar
|
|
295
|
+
const ICONS = {
|
|
296
|
+
presentation: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3.75 3v11.25A2.25 2.25 0 006 16.5h2.25M3.75 3h-1.5m1.5 0h16.5m0 0h1.5m-1.5 0v11.25A2.25 2.25 0 0118 16.5h-2.25m-7.5 0h7.5m-7.5 0l-1 3m8.5-3l1 3m0 0l.5 1.5m-.5-1.5h-9.5m0 0l-.5 1.5m.75-9l3-3 2.148 2.148A12.061 12.061 0 0116.5 7.605"></path></svg>`,
|
|
297
|
+
copy: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M8.25 7.5V6.108c0-1.135.845-2.098 1.976-2.192.373-.03.748-.057 1.123-.08M15.75 18H18a2.25 2.25 0 002.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 00-1.123-.08M15.75 18.75v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5A3.375 3.375 0 006.375 7.5H5.25m11.9-3.664A2.251 2.251 0 0015 2.25h-1.5a2.251 2.251 0 00-2.15 1.586m5.8 0c.065.21.1.433.1.664v.75h-6V4.5c0-.231.035-.454.1-.664M6.75 7.5H4.875c-.621 0-1.125.504-1.125 1.125v12c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V16.5a9 9 0 00-9-9z"></path></svg>`,
|
|
298
|
+
check: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>`,
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
function createToolbarButton(icon, tooltipText, onClick, tooltipPosition = 'bottom') {
|
|
302
|
+
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
|
|
303
|
+
const bgColor = isDark ? '#161b22' : '#ffffff';
|
|
304
|
+
const iconColor = isDark ? '#8b949e' : '#64748b';
|
|
305
|
+
const iconHoverColor = isDark ? '#f0f6fc' : '#0f172a';
|
|
306
|
+
const hoverBgColor = isDark ? '#21262d' : '#f1f5f9';
|
|
307
|
+
|
|
308
|
+
const wrapper = document.createElement('div');
|
|
309
|
+
wrapper.style.cssText = 'position: relative;';
|
|
310
|
+
|
|
311
|
+
const btn = document.createElement('button');
|
|
312
|
+
btn.type = 'button';
|
|
313
|
+
btn.innerHTML = icon;
|
|
314
|
+
btn.style.cssText = `
|
|
315
|
+
all: unset;
|
|
316
|
+
box-sizing: border-box;
|
|
317
|
+
display: flex;
|
|
318
|
+
justify-content: center;
|
|
319
|
+
align-items: center;
|
|
320
|
+
width: 40px;
|
|
321
|
+
height: 40px;
|
|
322
|
+
min-width: 40px;
|
|
323
|
+
min-height: 40px;
|
|
324
|
+
padding: 0;
|
|
325
|
+
margin: 0;
|
|
326
|
+
border: none;
|
|
327
|
+
border-radius: 6px;
|
|
328
|
+
background: ${bgColor};
|
|
329
|
+
color: ${iconColor};
|
|
330
|
+
cursor: pointer;
|
|
331
|
+
transition: background-color 0.15s, color 0.15s;
|
|
332
|
+
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
|
333
|
+
`;
|
|
334
|
+
|
|
335
|
+
const svgEl = btn.querySelector('svg');
|
|
336
|
+
if (svgEl) svgEl.style.cssText = 'display: block; width: 20px; height: 20px;';
|
|
337
|
+
|
|
338
|
+
btn.onmouseenter = () => {
|
|
339
|
+
btn.style.backgroundColor = hoverBgColor;
|
|
340
|
+
btn.style.color = iconHoverColor;
|
|
341
|
+
};
|
|
342
|
+
btn.onmouseleave = () => {
|
|
343
|
+
btn.style.backgroundColor = bgColor;
|
|
344
|
+
btn.style.color = iconColor;
|
|
345
|
+
};
|
|
346
|
+
btn.onclick = onClick;
|
|
347
|
+
|
|
348
|
+
const tooltip = document.createElement('div');
|
|
349
|
+
tooltip.textContent = tooltipText;
|
|
350
|
+
|
|
351
|
+
// Position tooltip based on tooltipPosition parameter
|
|
352
|
+
let tooltipStyles = `
|
|
353
|
+
position: absolute;
|
|
354
|
+
padding: 4px 8px;
|
|
355
|
+
background: #1f2937;
|
|
356
|
+
color: white;
|
|
357
|
+
font-size: 12px;
|
|
358
|
+
border-radius: 4px;
|
|
359
|
+
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
|
|
360
|
+
white-space: nowrap;
|
|
361
|
+
pointer-events: none;
|
|
362
|
+
opacity: 0;
|
|
363
|
+
transition: opacity 0.15s;
|
|
364
|
+
z-index: 50;
|
|
365
|
+
`;
|
|
366
|
+
|
|
367
|
+
if (tooltipPosition === 'right') {
|
|
368
|
+
tooltipStyles += `
|
|
369
|
+
top: 50%;
|
|
370
|
+
left: 100%;
|
|
371
|
+
transform: translateY(-50%);
|
|
372
|
+
margin-left: 8px;
|
|
373
|
+
`;
|
|
374
|
+
} else if (tooltipPosition === 'left') {
|
|
375
|
+
tooltipStyles += `
|
|
376
|
+
top: 50%;
|
|
377
|
+
right: 100%;
|
|
378
|
+
transform: translateY(-50%);
|
|
379
|
+
margin-right: 8px;
|
|
380
|
+
`;
|
|
381
|
+
} else {
|
|
382
|
+
// Default: bottom
|
|
383
|
+
tooltipStyles += `
|
|
384
|
+
top: 100%;
|
|
385
|
+
left: 50%;
|
|
386
|
+
transform: translateX(-50%);
|
|
387
|
+
margin-top: 8px;
|
|
388
|
+
`;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
tooltip.style.cssText = tooltipStyles;
|
|
392
|
+
|
|
393
|
+
wrapper.onmouseenter = () => (tooltip.style.opacity = '1');
|
|
394
|
+
wrapper.onmouseleave = () => (tooltip.style.opacity = '0');
|
|
395
|
+
|
|
396
|
+
wrapper.appendChild(btn);
|
|
397
|
+
wrapper.appendChild(tooltip);
|
|
398
|
+
return { wrapper, btn, tooltip, iconColor };
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function createFullscreenButton(onClick) {
|
|
402
|
+
const { wrapper } = createToolbarButton(ICONS.presentation, 'Presentation Mode', onClick, 'right');
|
|
403
|
+
wrapper.style.cssText = 'position: absolute; top: 12px; left: 12px; z-index: 10;';
|
|
404
|
+
return wrapper;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function createCopyButton(onCopy) {
|
|
408
|
+
const copy = createToolbarButton(
|
|
409
|
+
ICONS.copy,
|
|
410
|
+
'Copy diagram code',
|
|
411
|
+
() => {
|
|
412
|
+
onCopy();
|
|
413
|
+
copy.btn.innerHTML = ICONS.check;
|
|
414
|
+
copy.btn.style.color = '#10b981';
|
|
415
|
+
copy.tooltip.textContent = 'Copied!';
|
|
416
|
+
const svgEl = copy.btn.querySelector('svg');
|
|
417
|
+
if (svgEl) svgEl.style.cssText = 'display: block; width: 20px; height: 20px;';
|
|
418
|
+
|
|
419
|
+
setTimeout(() => {
|
|
420
|
+
copy.btn.innerHTML = ICONS.copy;
|
|
421
|
+
copy.btn.style.color = copy.iconColor;
|
|
422
|
+
copy.tooltip.textContent = 'Copy diagram code';
|
|
423
|
+
const svgEl = copy.btn.querySelector('svg');
|
|
424
|
+
if (svgEl) svgEl.style.cssText = 'display: block; width: 20px; height: 20px;';
|
|
425
|
+
}, 2000);
|
|
426
|
+
},
|
|
427
|
+
'left'
|
|
428
|
+
);
|
|
429
|
+
copy.wrapper.style.cssText = 'position: absolute; top: 12px; right: 12px; z-index: 10;';
|
|
430
|
+
return copy.wrapper;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function toggleFullscreen(container) {
|
|
434
|
+
if (!document.fullscreenElement) {
|
|
435
|
+
container.requestFullscreen().catch((err) => console.warn('Fullscreen error:', err));
|
|
436
|
+
} else {
|
|
437
|
+
document.exitFullscreen();
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
async function initMermaidZoom(svgElement, container, id, diagramContent) {
|
|
442
|
+
const { default: svgPanZoom } = await import('https://cdn.jsdelivr.net/npm/svg-pan-zoom@3.6.2/+esm');
|
|
443
|
+
|
|
444
|
+
// Get the natural dimensions from viewBox or getBBox
|
|
445
|
+
let width, height;
|
|
446
|
+
const viewBox = svgElement.getAttribute('viewBox');
|
|
447
|
+
if (viewBox) {
|
|
448
|
+
const parts = viewBox.split(/[\s,]+/).map(Number);
|
|
449
|
+
width = parts[2];
|
|
450
|
+
height = parts[3];
|
|
451
|
+
} else {
|
|
452
|
+
const bbox = svgElement.getBBox();
|
|
453
|
+
width = bbox.width;
|
|
454
|
+
height = bbox.height;
|
|
455
|
+
if (width > 0 && height > 0) {
|
|
456
|
+
svgElement.setAttribute('viewBox', `${bbox.x} ${bbox.y} ${width} ${height}`);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Set container height based on SVG aspect ratio, capped for usability
|
|
461
|
+
if (width > 0 && height > 0) {
|
|
462
|
+
const containerWidth = container.clientWidth || 800;
|
|
463
|
+
const aspectRatio = height / width;
|
|
464
|
+
const calculatedHeight = Math.min(Math.max(containerWidth * aspectRatio, 200), 500);
|
|
465
|
+
container.style.height = `${calculatedHeight}px`;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// SVG needs to fill the container for svg-pan-zoom
|
|
469
|
+
svgElement.style.width = '100%';
|
|
470
|
+
svgElement.style.height = '100%';
|
|
471
|
+
svgElement.removeAttribute('height');
|
|
472
|
+
svgElement.removeAttribute('width');
|
|
473
|
+
|
|
474
|
+
try {
|
|
475
|
+
const instance = svgPanZoom(svgElement, {
|
|
476
|
+
zoomEnabled: true,
|
|
477
|
+
controlIconsEnabled: false,
|
|
478
|
+
fit: true,
|
|
479
|
+
center: true,
|
|
480
|
+
minZoom: 0.5,
|
|
481
|
+
maxZoom: 10,
|
|
482
|
+
zoomScaleSensitivity: 0.15,
|
|
483
|
+
dblClickZoomEnabled: true,
|
|
484
|
+
mouseWheelZoomEnabled: false, // Disabled to avoid hijacking page scroll
|
|
485
|
+
preventMouseEventsDefault: true,
|
|
486
|
+
panEnabled: true,
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
zoomInstances.set(id, instance);
|
|
490
|
+
|
|
491
|
+
// Update cursor during pan
|
|
492
|
+
container.addEventListener('mousedown', () => {
|
|
493
|
+
container.style.cursor = 'grabbing';
|
|
494
|
+
});
|
|
495
|
+
container.addEventListener('mouseup', () => {
|
|
496
|
+
container.style.cursor = 'grab';
|
|
497
|
+
});
|
|
498
|
+
container.addEventListener('mouseleave', () => {
|
|
499
|
+
container.style.cursor = 'grab';
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
// Add custom controls
|
|
503
|
+
const controls = createZoomControls(
|
|
504
|
+
() => instance.zoomIn(),
|
|
505
|
+
() => instance.zoomOut(),
|
|
506
|
+
() => {
|
|
507
|
+
instance.fit();
|
|
508
|
+
instance.center();
|
|
509
|
+
}
|
|
510
|
+
);
|
|
511
|
+
container.appendChild(controls);
|
|
512
|
+
|
|
513
|
+
// Add fullscreen button (top-left)
|
|
514
|
+
const fullscreenBtn = createFullscreenButton(() => toggleFullscreen(container));
|
|
515
|
+
container.appendChild(fullscreenBtn);
|
|
516
|
+
|
|
517
|
+
// Add copy button (top-right) if diagram content is available
|
|
518
|
+
if (diagramContent) {
|
|
519
|
+
const copyBtn = createCopyButton(() => {
|
|
520
|
+
navigator.clipboard.writeText(diagramContent).catch((err) => {
|
|
521
|
+
console.warn('Failed to copy:', err);
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
container.appendChild(copyBtn);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Handle fullscreen changes
|
|
528
|
+
document.addEventListener('fullscreenchange', () => {
|
|
529
|
+
const isFullscreen = document.fullscreenElement === container;
|
|
530
|
+
if (isFullscreen) {
|
|
531
|
+
instance.enableMouseWheelZoom();
|
|
532
|
+
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
|
|
533
|
+
container.style.background = isDark ? '#0d1117' : '#ffffff';
|
|
534
|
+
} else {
|
|
535
|
+
instance.disableMouseWheelZoom();
|
|
536
|
+
container.style.background = '';
|
|
537
|
+
}
|
|
538
|
+
setTimeout(() => {
|
|
539
|
+
if (container.clientWidth > 0 && container.clientHeight > 0) {
|
|
540
|
+
try {
|
|
541
|
+
instance.resize();
|
|
542
|
+
instance.fit();
|
|
543
|
+
instance.center();
|
|
544
|
+
} catch (e) {}
|
|
545
|
+
}
|
|
546
|
+
}, 100);
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
// Resize handler for responsiveness
|
|
550
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
551
|
+
if (container.clientWidth > 0 && container.clientHeight > 0) {
|
|
552
|
+
try {
|
|
553
|
+
instance.resize();
|
|
554
|
+
instance.fit();
|
|
555
|
+
instance.center();
|
|
556
|
+
} catch (e) {}
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
resizeObserver.observe(container);
|
|
560
|
+
} catch (e) {
|
|
561
|
+
console.warn('Failed to initialize zoom on mermaid diagram:', e);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
188
565
|
async function renderMermaid() {
|
|
189
566
|
const graphs = document.getElementsByClassName('mermaid');
|
|
190
567
|
if (graphs.length === 0) return;
|
|
@@ -204,7 +581,24 @@ const diagramId = props.data.id;
|
|
|
204
581
|
const id = 'mermaid-' + Math.round(Math.random() * 100000);
|
|
205
582
|
try {
|
|
206
583
|
const result = await mermaid.render(id, content);
|
|
207
|
-
|
|
584
|
+
|
|
585
|
+
// Create zoom container with inline styles (no Tailwind in embed)
|
|
586
|
+
const container = document.createElement('div');
|
|
587
|
+
container.style.cssText =
|
|
588
|
+
'position: relative; width: 100%; min-height: 200px; overflow: hidden; margin: 1em 0; cursor: grab;';
|
|
589
|
+
|
|
590
|
+
// Insert the rendered SVG
|
|
591
|
+
container.innerHTML = result.svg;
|
|
592
|
+
|
|
593
|
+
// Replace the graph content with the container
|
|
594
|
+
graph.innerHTML = '';
|
|
595
|
+
graph.appendChild(container);
|
|
596
|
+
|
|
597
|
+
// Initialize zoom on the SVG
|
|
598
|
+
const svgElement = container.querySelector('svg');
|
|
599
|
+
if (svgElement) {
|
|
600
|
+
await initMermaidZoom(svgElement, container, id, content);
|
|
601
|
+
}
|
|
208
602
|
} catch (e) {
|
|
209
603
|
console.error('Mermaid render error:', e);
|
|
210
604
|
}
|
|
@@ -213,12 +607,13 @@ const diagramId = props.data.id;
|
|
|
213
607
|
|
|
214
608
|
renderMermaid();
|
|
215
609
|
|
|
216
|
-
// PlantUML rendering
|
|
610
|
+
// PlantUML rendering with zoom
|
|
217
611
|
async function renderPlantUML() {
|
|
218
612
|
const blocks = document.getElementsByClassName('plantuml');
|
|
219
613
|
if (blocks.length === 0) return;
|
|
220
614
|
|
|
221
615
|
const { deflate } = await import('https://cdn.jsdelivr.net/npm/pako@2.1.0/+esm');
|
|
616
|
+
const { default: svgPanZoom } = await import('https://cdn.jsdelivr.net/npm/svg-pan-zoom@3.6.2/+esm');
|
|
222
617
|
|
|
223
618
|
function encode64(data) {
|
|
224
619
|
const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_';
|
|
@@ -250,14 +645,42 @@ const diagramId = props.data.id;
|
|
|
250
645
|
if (!content) continue;
|
|
251
646
|
|
|
252
647
|
const encoded = encodePlantUML(content);
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
648
|
+
const svgUrl = `https://www.plantuml.com/plantuml/svg/~1${encoded}`;
|
|
649
|
+
const id = 'plantuml-' + Math.round(Math.random() * 100000);
|
|
650
|
+
|
|
651
|
+
try {
|
|
652
|
+
// Fetch SVG content for zoom support
|
|
653
|
+
const response = await fetch(svgUrl);
|
|
654
|
+
if (!response.ok) throw new Error('Fetch failed');
|
|
655
|
+
|
|
656
|
+
const svgText = await response.text();
|
|
657
|
+
|
|
658
|
+
// Create zoom container
|
|
659
|
+
const container = document.createElement('div');
|
|
660
|
+
container.style.cssText =
|
|
661
|
+
'position: relative; width: 100%; min-height: 200px; overflow: hidden; margin: 1em 0; cursor: grab;';
|
|
662
|
+
container.innerHTML = svgText;
|
|
663
|
+
|
|
664
|
+
block.innerHTML = '';
|
|
665
|
+
block.appendChild(container);
|
|
666
|
+
|
|
667
|
+
// Initialize zoom
|
|
668
|
+
const svgElement = container.querySelector('svg');
|
|
669
|
+
if (svgElement) {
|
|
670
|
+
await initMermaidZoom(svgElement, container, id, content);
|
|
671
|
+
}
|
|
672
|
+
} catch (e) {
|
|
673
|
+
// Fallback to img
|
|
674
|
+
console.warn('PlantUML fetch failed:', e);
|
|
675
|
+
const img = document.createElement('img');
|
|
676
|
+
img.src = svgUrl;
|
|
677
|
+
img.alt = 'PlantUML diagram';
|
|
678
|
+
img.loading = 'lazy';
|
|
679
|
+
img.style.margin = '0 auto';
|
|
680
|
+
img.style.display = 'block';
|
|
681
|
+
block.innerHTML = '';
|
|
682
|
+
block.appendChild(img);
|
|
683
|
+
}
|
|
261
684
|
}
|
|
262
685
|
}
|
|
263
686
|
|
|
@@ -210,6 +210,8 @@ const chatQuery = `Tell me about the "${props.data.name}" diagram (version ${pro
|
|
|
210
210
|
</script>
|
|
211
211
|
|
|
212
212
|
<script>
|
|
213
|
+
import { destroyZoomInstances, renderMermaidWithZoom } from '@utils/mermaid-zoom';
|
|
214
|
+
|
|
213
215
|
function initDiagramPage() {
|
|
214
216
|
// Version selector
|
|
215
217
|
const versionSelect = document.getElementById('version-select') as HTMLSelectElement;
|
|
@@ -231,7 +233,6 @@ const chatQuery = `Tell me about the "${props.data.name}" diagram (version ${pro
|
|
|
231
233
|
const rightFrame = document.getElementById('compare-right-frame') as HTMLIFrameElement;
|
|
232
234
|
|
|
233
235
|
function buildCompareUrl(version: string) {
|
|
234
|
-
// Parse diagramId from current URL: /diagrams/{id}/{version}
|
|
235
236
|
const pathParts = window.location.pathname.split('/');
|
|
236
237
|
const diagramsIndex = pathParts.indexOf('diagrams');
|
|
237
238
|
const diagramId = diagramsIndex !== -1 ? pathParts[diagramsIndex + 1] : '';
|
|
@@ -244,12 +245,8 @@ const chatQuery = `Tell me about the "${props.data.name}" diagram (version ${pro
|
|
|
244
245
|
if (!compareModal) return;
|
|
245
246
|
compareModal.classList.remove('hidden');
|
|
246
247
|
document.body.style.overflow = 'hidden';
|
|
247
|
-
if (leftFrame && leftVersionSelect)
|
|
248
|
-
|
|
249
|
-
}
|
|
250
|
-
if (rightFrame && rightVersionSelect) {
|
|
251
|
-
rightFrame.src = buildCompareUrl(rightVersionSelect.value);
|
|
252
|
-
}
|
|
248
|
+
if (leftFrame && leftVersionSelect) leftFrame.src = buildCompareUrl(leftVersionSelect.value);
|
|
249
|
+
if (rightFrame && rightVersionSelect) rightFrame.src = buildCompareUrl(rightVersionSelect.value);
|
|
253
250
|
}
|
|
254
251
|
|
|
255
252
|
function closeCompareModal() {
|
|
@@ -272,27 +269,19 @@ const chatQuery = `Tell me about the "${props.data.name}" diagram (version ${pro
|
|
|
272
269
|
document.body.style.overflow = '';
|
|
273
270
|
}
|
|
274
271
|
|
|
275
|
-
// Handle compare button click - check if Scale is enabled
|
|
276
272
|
if (compareBtn) {
|
|
277
273
|
compareBtn.onclick = () => {
|
|
278
274
|
const scaleEnabled = compareBtn.dataset.scaleEnabled === 'true';
|
|
279
|
-
|
|
280
|
-
openCompareModal();
|
|
281
|
-
} else {
|
|
282
|
-
openUpgradeModal();
|
|
283
|
-
}
|
|
275
|
+
scaleEnabled ? openCompareModal() : openUpgradeModal();
|
|
284
276
|
};
|
|
285
277
|
}
|
|
286
278
|
|
|
287
279
|
if (compareCloseBtn) compareCloseBtn.onclick = closeCompareModal;
|
|
288
280
|
if (upgradeCloseBtn) upgradeCloseBtn.onclick = closeUpgradeModal;
|
|
289
281
|
|
|
290
|
-
// Close upgrade modal when clicking outside the card
|
|
291
282
|
if (upgradeModal) {
|
|
292
283
|
upgradeModal.onclick = (e) => {
|
|
293
|
-
if (e.target === upgradeModal)
|
|
294
|
-
closeUpgradeModal();
|
|
295
|
-
}
|
|
284
|
+
if (e.target === upgradeModal) closeUpgradeModal();
|
|
296
285
|
};
|
|
297
286
|
}
|
|
298
287
|
|
|
@@ -309,42 +298,16 @@ const chatQuery = `Tell me about the "${props.data.name}" diagram (version ${pro
|
|
|
309
298
|
|
|
310
299
|
document.onkeydown = (e) => {
|
|
311
300
|
if (e.key === 'Escape') {
|
|
312
|
-
if (compareModal && !compareModal.classList.contains('hidden'))
|
|
313
|
-
|
|
314
|
-
}
|
|
315
|
-
if (upgradeModal && !upgradeModal.classList.contains('hidden')) {
|
|
316
|
-
closeUpgradeModal();
|
|
317
|
-
}
|
|
301
|
+
if (compareModal && !compareModal.classList.contains('hidden')) closeCompareModal();
|
|
302
|
+
if (upgradeModal && !upgradeModal.classList.contains('hidden')) closeUpgradeModal();
|
|
318
303
|
}
|
|
319
304
|
};
|
|
320
305
|
|
|
321
|
-
// Mermaid
|
|
322
|
-
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
async function renderMermaidDiagrams() {
|
|
306
|
+
// Render Mermaid diagrams with zoom
|
|
307
|
+
destroyZoomInstances();
|
|
326
308
|
const graphs = document.getElementsByClassName('mermaid');
|
|
327
|
-
if (graphs.length
|
|
328
|
-
|
|
329
|
-
const { default: mermaid } = await import('mermaid');
|
|
330
|
-
const isDarkMode = document.documentElement.getAttribute('data-theme') === 'dark';
|
|
331
|
-
|
|
332
|
-
mermaid.initialize({
|
|
333
|
-
startOnLoad: false,
|
|
334
|
-
theme: isDarkMode ? 'dark' : 'default',
|
|
335
|
-
fontFamily: 'var(--sans-font)',
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
for (const graph of graphs) {
|
|
339
|
-
const content = graph.getAttribute('data-content');
|
|
340
|
-
if (!content) continue;
|
|
341
|
-
const id = 'mermaid-' + Math.round(Math.random() * 100000);
|
|
342
|
-
try {
|
|
343
|
-
const result = await mermaid.render(id, content);
|
|
344
|
-
graph.innerHTML = result.svg;
|
|
345
|
-
} catch (e) {
|
|
346
|
-
console.error('Mermaid error:', e);
|
|
347
|
-
}
|
|
309
|
+
if (graphs.length > 0) {
|
|
310
|
+
renderMermaidWithZoom(graphs, window.eventcatalog?.mermaid);
|
|
348
311
|
}
|
|
349
312
|
}
|
|
350
313
|
|
|
@@ -354,58 +317,16 @@ const chatQuery = `Tell me about the "${props.data.name}" diagram (version ${pro
|
|
|
354
317
|
</script>
|
|
355
318
|
|
|
356
319
|
<script>
|
|
357
|
-
import
|
|
358
|
-
document.addEventListener('astro:page-load', () => {
|
|
359
|
-
const blocks = document.getElementsByClassName('plantuml');
|
|
360
|
-
if (blocks.length > 0) {
|
|
361
|
-
renderPlantUML(blocks, deflate);
|
|
362
|
-
}
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
function renderPlantUML(blocks: any, deflate: any) {
|
|
366
|
-
for (const block of blocks) {
|
|
367
|
-
const content = block.getAttribute('data-content');
|
|
368
|
-
if (!content) continue;
|
|
320
|
+
import { renderPlantUMLWithZoom } from '@utils/mermaid-zoom';
|
|
369
321
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
img.loading = 'lazy';
|
|
375
|
-
block.innerHTML = '';
|
|
376
|
-
img.classList.add('mx-auto');
|
|
377
|
-
block.appendChild(img);
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
function encodePlantUML(text: any, deflate: any) {
|
|
382
|
-
const data = new TextEncoder().encode(text);
|
|
383
|
-
const compressed = deflate(data, { level: 9, to: 'Uint8Array' });
|
|
384
|
-
return encode64(compressed);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
function encode64(data: any) {
|
|
388
|
-
const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_';
|
|
389
|
-
let str = '';
|
|
390
|
-
const len = data.length;
|
|
391
|
-
for (let i = 0; i < len; i += 3) {
|
|
392
|
-
const b1 = data[i];
|
|
393
|
-
const b2 = i + 1 < len ? data[i + 1] : 0;
|
|
394
|
-
const b3 = i + 2 < len ? data[i + 2] : 0;
|
|
395
|
-
|
|
396
|
-
let c1 = b1 >> 2;
|
|
397
|
-
let c2 = ((b1 & 0x3) << 4) | (b2 >> 4);
|
|
398
|
-
let c3 = ((b2 & 0xf) << 2) | (b3 >> 6);
|
|
399
|
-
let c4 = b3 & 0x3f;
|
|
400
|
-
|
|
401
|
-
str += chars[c1] + chars[c2] + chars[c3] + chars[c4];
|
|
402
|
-
}
|
|
403
|
-
return str;
|
|
322
|
+
function initPlantUML() {
|
|
323
|
+
const blocks = document.getElementsByClassName('plantuml');
|
|
324
|
+
if (blocks.length > 0) {
|
|
325
|
+
renderPlantUMLWithZoom(blocks);
|
|
404
326
|
}
|
|
327
|
+
}
|
|
405
328
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
}
|
|
410
|
-
});
|
|
329
|
+
// Run on initial load and page transitions
|
|
330
|
+
initPlantUML();
|
|
331
|
+
document.addEventListener('astro:page-load', initPlantUML);
|
|
411
332
|
</script>
|