jamdesk 1.1.92 → 1.1.94
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/lib/deps.js
CHANGED
|
@@ -58,7 +58,7 @@ export const REQUIRED_DEPS = {
|
|
|
58
58
|
'remark-math': '^6.0.0',
|
|
59
59
|
'remark-smartypants': '^3.0.2',
|
|
60
60
|
// Math/LaTeX rendering
|
|
61
|
-
'katex': '^0.16.
|
|
61
|
+
'katex': '^0.16.46',
|
|
62
62
|
// Diagrams
|
|
63
63
|
'mermaid': '^11.14.0',
|
|
64
64
|
// YAML parsing (for OpenAPI specs)
|
|
@@ -88,7 +88,7 @@ export const REQUIRED_DEPS = {
|
|
|
88
88
|
'@upstash/redis': '^1.37.0',
|
|
89
89
|
// TypeScript (needed for Next.js to avoid auto-install breaking symlink)
|
|
90
90
|
'typescript': '^6.0.3',
|
|
91
|
-
'@types/node': '^25.
|
|
91
|
+
'@types/node': '^25.8.0',
|
|
92
92
|
'@types/react': '^19.2.14',
|
|
93
93
|
'@types/react-dom': '^19.0.0',
|
|
94
94
|
'@next/third-parties': '^16.2.6',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jamdesk",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.94",
|
|
4
4
|
"description": "CLI for Jamdesk — build, preview, and deploy documentation sites from MDX. Dev server with hot reload, 50+ components, OpenAPI support, AI search, and Mintlify migration",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jamdesk",
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
"devDependencies": {
|
|
121
121
|
"@mdx-js/mdx": "^3.1.1",
|
|
122
122
|
"@types/fs-extra": "^11.0.0",
|
|
123
|
-
"@types/node": "^25.
|
|
123
|
+
"@types/node": "^25.8.0",
|
|
124
124
|
"typescript": "^6.0.2",
|
|
125
125
|
"vitest": "^4.1.5"
|
|
126
126
|
},
|
|
@@ -1,8 +1,86 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
4
4
|
import mermaid from 'mermaid';
|
|
5
5
|
|
|
6
|
+
mermaid.initialize({
|
|
7
|
+
startOnLoad: false,
|
|
8
|
+
theme: 'neutral', // Works well with both light and dark modes
|
|
9
|
+
securityLevel: 'strict', // Sanitizes SVG output to prevent XSS
|
|
10
|
+
gitGraph: {
|
|
11
|
+
mainBranchName: 'main',
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export const CACHE_KEY_PREFIX = 'mermaid:v1:';
|
|
16
|
+
|
|
17
|
+
// djb2; collisions are theoretically possible but the input space is tiny for a docs site.
|
|
18
|
+
export function hashDiagram(source: string): string {
|
|
19
|
+
let h = 5381;
|
|
20
|
+
for (let i = 0; i < source.length; i++) {
|
|
21
|
+
h = ((h << 5) + h) ^ source.charCodeAt(i);
|
|
22
|
+
}
|
|
23
|
+
return (h >>> 0).toString(36);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface CachedDiagram {
|
|
27
|
+
svg: string;
|
|
28
|
+
height: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function readCache(source: string): CachedDiagram | null {
|
|
32
|
+
try {
|
|
33
|
+
const raw = sessionStorage.getItem(CACHE_KEY_PREFIX + hashDiagram(source));
|
|
34
|
+
if (!raw) return null;
|
|
35
|
+
const parsed = JSON.parse(raw) as Partial<CachedDiagram>;
|
|
36
|
+
if (typeof parsed?.svg !== 'string') return null;
|
|
37
|
+
// Defense-in-depth: mermaid sanitizes at render time (securityLevel:
|
|
38
|
+
// 'strict'), but the cache-read path injects the stored bytes via React's
|
|
39
|
+
// raw inner-HTML prop without re-sanitizing. A tampered sessionStorage
|
|
40
|
+
// entry must not be trusted — reject anything that isn't a bare <svg> root
|
|
41
|
+
// or that carries a <script> or an inline on*= event handler. On rejection
|
|
42
|
+
// we return null so the caller falls back to a fresh, re-sanitized render.
|
|
43
|
+
const svg = parsed.svg.trimStart();
|
|
44
|
+
if (
|
|
45
|
+
!svg.startsWith('<svg') ||
|
|
46
|
+
/<script\b/i.test(svg) ||
|
|
47
|
+
/<[^>]+\son[a-z]+\s*=/i.test(svg)
|
|
48
|
+
) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
// Normalize the shape so the return honestly matches CachedDiagram:
|
|
52
|
+
// legacy v1 entries predate height tracking, and a tampered/foreign
|
|
53
|
+
// entry could carry a non-numeric height. Coerce both to a number.
|
|
54
|
+
return {
|
|
55
|
+
svg: parsed.svg,
|
|
56
|
+
height: typeof parsed.height === 'number' ? parsed.height : 0,
|
|
57
|
+
};
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function writeCache(source: string, entry: CachedDiagram): void {
|
|
64
|
+
try {
|
|
65
|
+
sessionStorage.setItem(CACHE_KEY_PREFIX + hashDiagram(source), JSON.stringify(entry));
|
|
66
|
+
} catch {}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Real mermaid output does NOT carry a `height` attribute — it sizes the root
|
|
70
|
+
// <svg> via `viewBox` + `style="max-width"`. So read the explicit `height`
|
|
71
|
+
// attribute first (covers other renderers / older mermaid), then fall back to
|
|
72
|
+
// the 4th `viewBox` token (its height). Parsing the markup rather than
|
|
73
|
+
// getBoundingClientRect works in jsdom and avoids a forced reflow on the
|
|
74
|
+
// production hot path.
|
|
75
|
+
export function readSvgHeight(svgMarkup: string): number {
|
|
76
|
+
const attr = svgMarkup.match(/<svg\b[^>]*\bheight=["']([\d.]+)(?:px)?["']/i);
|
|
77
|
+
if (attr) return parseFloat(attr[1]);
|
|
78
|
+
const viewBox = svgMarkup.match(
|
|
79
|
+
/<svg\b[^>]*\bviewBox=["']\s*[\d.+-]+\s+[\d.+-]+\s+[\d.+-]+\s+([\d.]+)/i
|
|
80
|
+
);
|
|
81
|
+
return viewBox ? parseFloat(viewBox[1]) : 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
6
84
|
interface MermaidInnerProps {
|
|
7
85
|
children: string;
|
|
8
86
|
className?: string;
|
|
@@ -10,7 +88,6 @@ interface MermaidInnerProps {
|
|
|
10
88
|
minWidth?: string;
|
|
11
89
|
}
|
|
12
90
|
|
|
13
|
-
// Color palette for theming diagram elements
|
|
14
91
|
interface ColorPalette {
|
|
15
92
|
text: string;
|
|
16
93
|
line: string;
|
|
@@ -232,40 +309,70 @@ function applyLightModeCleanup(svgEl: SVGElement): void {
|
|
|
232
309
|
*/
|
|
233
310
|
export function MermaidInner({ children, className, minWidth }: MermaidInnerProps) {
|
|
234
311
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
235
|
-
const [svg, setSvg] = useState<string>(
|
|
312
|
+
const [svg, setSvg] = useState<string>(() => {
|
|
313
|
+
if (typeof window === 'undefined') return '';
|
|
314
|
+
return readCache(children)?.svg ?? '';
|
|
315
|
+
});
|
|
236
316
|
const [error, setError] = useState<string | null>(null);
|
|
237
317
|
|
|
318
|
+
// Reserves space so the skeleton → SVG swap doesn't shift layout below it.
|
|
319
|
+
// Derived from svg (not separate state) so it can't desync. The regex is
|
|
320
|
+
// cheap (small string, no backtracking).
|
|
321
|
+
const minHeightPx = useMemo(() => (svg ? readSvgHeight(svg) : 0), [svg]);
|
|
322
|
+
|
|
238
323
|
useEffect(() => {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
324
|
+
let cancelled = false;
|
|
325
|
+
|
|
326
|
+
const cached = readCache(children);
|
|
327
|
+
if (cached) {
|
|
328
|
+
// Already hydrated from cache via useState initializer on first mount;
|
|
329
|
+
// on subsequent `children` changes, sync state to the cached value.
|
|
330
|
+
setSvg(cached.svg);
|
|
331
|
+
setError(null);
|
|
332
|
+
// Synchronous path: no async work to cancel, so no cleanup needed.
|
|
333
|
+
// StrictMode re-invokes this effect, but both setters are idempotent
|
|
334
|
+
// (state already equals these values), so the repeat is a no-op.
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Cache miss — clear previous diagram so user sees skeleton/empty
|
|
339
|
+
// while the new render is in flight, not stale styled markup.
|
|
340
|
+
setSvg('');
|
|
341
|
+
setError(null);
|
|
247
342
|
|
|
248
343
|
const renderDiagram = async () => {
|
|
249
344
|
try {
|
|
250
345
|
if (!children || typeof children !== 'string') {
|
|
251
|
-
setError('Invalid diagram content');
|
|
346
|
+
if (!cancelled) setError('Invalid diagram content');
|
|
252
347
|
return;
|
|
253
348
|
}
|
|
254
349
|
// Generate unique ID for each render to avoid conflicts with React StrictMode
|
|
255
350
|
const uniqueId = `mermaid-${Math.random().toString(36).substring(2, 11)}`;
|
|
256
351
|
const { svg: renderedSvg } = await mermaid.render(uniqueId, children.trim());
|
|
352
|
+
// writeCache runs UNGATED — the entry is keyed by source string and is
|
|
353
|
+
// correct regardless of which `children` the component currently displays.
|
|
354
|
+
writeCache(children, { svg: renderedSvg, height: readSvgHeight(renderedSvg) });
|
|
355
|
+
if (cancelled) return;
|
|
257
356
|
setSvg(renderedSvg);
|
|
258
357
|
setError(null);
|
|
259
358
|
} catch (err) {
|
|
260
|
-
|
|
359
|
+
if (!cancelled) {
|
|
360
|
+
setError(err instanceof Error ? err.message : 'Failed to render diagram');
|
|
361
|
+
}
|
|
261
362
|
}
|
|
262
363
|
};
|
|
263
364
|
|
|
264
365
|
renderDiagram();
|
|
366
|
+
|
|
367
|
+
return () => {
|
|
368
|
+
cancelled = true;
|
|
369
|
+
};
|
|
265
370
|
}, [children]);
|
|
266
371
|
|
|
267
|
-
// Apply styles to SVG after
|
|
268
|
-
useEffect
|
|
372
|
+
// Apply styles to SVG after commit, before paint, for dark mode compatibility.
|
|
373
|
+
// useLayoutEffect — not useEffect — so the theme palette is applied in the same
|
|
374
|
+
// visual frame as the SVG markup, eliminating an unstyled-SVG flash.
|
|
375
|
+
useLayoutEffect(() => {
|
|
269
376
|
if (!containerRef.current) return;
|
|
270
377
|
|
|
271
378
|
const svgEl = containerRef.current.querySelector('svg');
|
|
@@ -298,7 +405,7 @@ export function MermaidInner({ children, className, minWidth }: MermaidInnerProp
|
|
|
298
405
|
applyLightModeStyles();
|
|
299
406
|
}
|
|
300
407
|
|
|
301
|
-
//
|
|
408
|
+
// CSS hook for global mermaid styling.
|
|
302
409
|
svgEl.classList.add('mermaid-svg');
|
|
303
410
|
|
|
304
411
|
// Watch for dark mode changes
|
|
@@ -336,6 +443,7 @@ export function MermaidInner({ children, className, minWidth }: MermaidInnerProp
|
|
|
336
443
|
<div
|
|
337
444
|
ref={containerRef}
|
|
338
445
|
className={`mermaid-container my-6 flex justify-center overflow-x-auto ${className || ''}`}
|
|
446
|
+
style={minHeightPx ? { minHeight: `${minHeightPx}px` } : undefined}
|
|
339
447
|
dangerouslySetInnerHTML={{ __html: svg }}
|
|
340
448
|
/>
|
|
341
449
|
);
|
|
@@ -93,14 +93,14 @@ export function generateSitemap(options: SitemapOptions): string {
|
|
|
93
93
|
hreflangLinks = Object.entries(alternates)
|
|
94
94
|
.map(
|
|
95
95
|
([tag, href]) =>
|
|
96
|
-
`\n <xhtml:link rel="alternate" hreflang="${tag}" href="${href}"/>`,
|
|
96
|
+
`\n <xhtml:link rel="alternate" hreflang="${escapeXml(tag)}" href="${escapeXml(href)}"/>`,
|
|
97
97
|
)
|
|
98
98
|
.join('');
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
return ` <url>
|
|
103
|
-
<loc>${url}</loc>
|
|
103
|
+
<loc>${escapeXml(url)}</loc>
|
|
104
104
|
<lastmod>${lastmod}</lastmod>
|
|
105
105
|
<changefreq>weekly</changefreq>
|
|
106
106
|
<priority>${priority}</priority>${hreflangLinks}
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"@shikijs/transformers": "^4.0.1",
|
|
23
23
|
"@tailwindcss/postcss": "^4.2.4",
|
|
24
24
|
"@tailwindcss/typography": "^0.5.10",
|
|
25
|
-
"@types/node": "^25.
|
|
25
|
+
"@types/node": "^25.8.0",
|
|
26
26
|
"@types/react": "^19.2.14",
|
|
27
27
|
"@types/react-dom": "^19.0.0",
|
|
28
28
|
"@upstash/redis": "^1.37.0",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"gray-matter": "^4.0.3",
|
|
38
38
|
"js-yaml": "^4.1.1",
|
|
39
39
|
"json5": "^2.2.3",
|
|
40
|
-
"katex": "^0.16.
|
|
40
|
+
"katex": "^0.16.46",
|
|
41
41
|
"lucide-react": "^0.562.0",
|
|
42
42
|
"mermaid": "^11.14.0",
|
|
43
43
|
"next": "^16.2.6",
|
|
@@ -2134,9 +2134,9 @@
|
|
|
2134
2134
|
}
|
|
2135
2135
|
},
|
|
2136
2136
|
"node_modules/baseline-browser-mapping": {
|
|
2137
|
-
"version": "2.10.
|
|
2138
|
-
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.
|
|
2139
|
-
"integrity": "sha512-
|
|
2137
|
+
"version": "2.10.30",
|
|
2138
|
+
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.30.tgz",
|
|
2139
|
+
"integrity": "sha512-xjOFN16Ha1+Rz4nFYKqHU/LSB+gx/Vi3yQLX7r7sAW+Wa+8hhF2h4pvqTrTMc8+WcDBEunnUurr46Jvv0jk3Vg==",
|
|
2140
2140
|
"license": "Apache-2.0",
|
|
2141
2141
|
"bin": {
|
|
2142
2142
|
"baseline-browser-mapping": "dist/cli.cjs"
|
|
@@ -2197,9 +2197,9 @@
|
|
|
2197
2197
|
"license": "MIT"
|
|
2198
2198
|
},
|
|
2199
2199
|
"node_modules/caniuse-lite": {
|
|
2200
|
-
"version": "1.0.
|
|
2201
|
-
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.
|
|
2202
|
-
"integrity": "sha512-
|
|
2200
|
+
"version": "1.0.30001793",
|
|
2201
|
+
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz",
|
|
2202
|
+
"integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==",
|
|
2203
2203
|
"funding": [
|
|
2204
2204
|
{
|
|
2205
2205
|
"type": "opencollective",
|
|
@@ -2913,9 +2913,9 @@
|
|
|
2913
2913
|
}
|
|
2914
2914
|
},
|
|
2915
2915
|
"node_modules/dompurify": {
|
|
2916
|
-
"version": "3.4.
|
|
2917
|
-
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.
|
|
2918
|
-
"integrity": "sha512-
|
|
2916
|
+
"version": "3.4.4",
|
|
2917
|
+
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.4.tgz",
|
|
2918
|
+
"integrity": "sha512-r8K7KGKEcztXfA/nfabSYB2hg9tDphORJTdf8xprN/luSLGmNhOBN8dm1/SYjqLLet6YUFEXOcrdTuwryp/Bew==",
|
|
2919
2919
|
"license": "(MPL-2.0 OR Apache-2.0)",
|
|
2920
2920
|
"optionalDependencies": {
|
|
2921
2921
|
"@types/trusted-types": "^2.0.7"
|
|
@@ -2928,9 +2928,9 @@
|
|
|
2928
2928
|
"license": "MIT"
|
|
2929
2929
|
},
|
|
2930
2930
|
"node_modules/electron-to-chromium": {
|
|
2931
|
-
"version": "1.5.
|
|
2932
|
-
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.
|
|
2933
|
-
"integrity": "sha512-
|
|
2931
|
+
"version": "1.5.357",
|
|
2932
|
+
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.357.tgz",
|
|
2933
|
+
"integrity": "sha512-NHlTIQDK8fmVwHwuIzmXYEJ1Ewq3D9wDNc0cWXxDGysP6Pb21giwGNkxiTifyKy/4SoPuN5l6GLP1W9Sv7zB2g==",
|
|
2934
2934
|
"license": "ISC"
|
|
2935
2935
|
},
|
|
2936
2936
|
"node_modules/enhanced-resolve": {
|
|
@@ -3739,9 +3739,9 @@
|
|
|
3739
3739
|
}
|
|
3740
3740
|
},
|
|
3741
3741
|
"node_modules/katex": {
|
|
3742
|
-
"version": "0.16.
|
|
3743
|
-
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.
|
|
3744
|
-
"integrity": "sha512-
|
|
3742
|
+
"version": "0.16.47",
|
|
3743
|
+
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz",
|
|
3744
|
+
"integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==",
|
|
3745
3745
|
"funding": [
|
|
3746
3746
|
"https://opencollective.com/katex",
|
|
3747
3747
|
"https://github.com/sponsors/katex"
|