jamdesk 1.1.91 → 1.1.93

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.45',
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.6.2',
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.91",
3
+ "version": "1.1.93",
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.6.2",
123
+ "@types/node": "^25.8.0",
124
124
  "typescript": "^6.0.2",
125
125
  "vitest": "^4.1.5"
126
126
  },
@@ -1,8 +1,65 @@
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
+ // Normalize the shape so the return honestly matches CachedDiagram:
38
+ // legacy v1 entries predate height tracking, and a tampered/foreign
39
+ // entry could carry a non-numeric height. Coerce both to a number.
40
+ return {
41
+ svg: parsed.svg,
42
+ height: typeof parsed.height === 'number' ? parsed.height : 0,
43
+ };
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+
49
+ export function writeCache(source: string, entry: CachedDiagram): void {
50
+ try {
51
+ sessionStorage.setItem(CACHE_KEY_PREFIX + hashDiagram(source), JSON.stringify(entry));
52
+ } catch {}
53
+ }
54
+
55
+ // Mermaid always emits a `height` attribute on the root <svg>. Reading it
56
+ // from the markup (rather than getBoundingClientRect) works in jsdom and
57
+ // avoids a forced reflow on the production hot path.
58
+ export function readSvgHeight(svgMarkup: string): number {
59
+ const match = svgMarkup.match(/<svg\b[^>]*\bheight=["']([\d.]+)(?:px)?["']/i);
60
+ return match ? parseFloat(match[1]) : 0;
61
+ }
62
+
6
63
  interface MermaidInnerProps {
7
64
  children: string;
8
65
  className?: string;
@@ -10,7 +67,6 @@ interface MermaidInnerProps {
10
67
  minWidth?: string;
11
68
  }
12
69
 
13
- // Color palette for theming diagram elements
14
70
  interface ColorPalette {
15
71
  text: string;
16
72
  line: string;
@@ -232,40 +288,70 @@ function applyLightModeCleanup(svgEl: SVGElement): void {
232
288
  */
233
289
  export function MermaidInner({ children, className, minWidth }: MermaidInnerProps) {
234
290
  const containerRef = useRef<HTMLDivElement>(null);
235
- const [svg, setSvg] = useState<string>('');
291
+ const [svg, setSvg] = useState<string>(() => {
292
+ if (typeof window === 'undefined') return '';
293
+ return readCache(children)?.svg ?? '';
294
+ });
236
295
  const [error, setError] = useState<string | null>(null);
237
296
 
297
+ // Reserves space so the skeleton → SVG swap doesn't shift layout below it.
298
+ // Derived from svg (not separate state) so it can't desync. The regex is
299
+ // cheap (small string, no backtracking).
300
+ const minHeightPx = useMemo(() => (svg ? readSvgHeight(svg) : 0), [svg]);
301
+
238
302
  useEffect(() => {
239
- mermaid.initialize({
240
- startOnLoad: false,
241
- theme: 'neutral', // Works well with both light and dark modes
242
- securityLevel: 'strict', // Sanitizes SVG output to prevent XSS
243
- gitGraph: {
244
- mainBranchName: 'main',
245
- },
246
- });
303
+ let cancelled = false;
304
+
305
+ const cached = readCache(children);
306
+ if (cached) {
307
+ // Already hydrated from cache via useState initializer on first mount;
308
+ // on subsequent `children` changes, sync state to the cached value.
309
+ setSvg(cached.svg);
310
+ setError(null);
311
+ // Synchronous path: no async work to cancel, so no cleanup needed.
312
+ // StrictMode re-invokes this effect, but both setters are idempotent
313
+ // (state already equals these values), so the repeat is a no-op.
314
+ return;
315
+ }
316
+
317
+ // Cache miss — clear previous diagram so user sees skeleton/empty
318
+ // while the new render is in flight, not stale styled markup.
319
+ setSvg('');
320
+ setError(null);
247
321
 
248
322
  const renderDiagram = async () => {
249
323
  try {
250
324
  if (!children || typeof children !== 'string') {
251
- setError('Invalid diagram content');
325
+ if (!cancelled) setError('Invalid diagram content');
252
326
  return;
253
327
  }
254
328
  // Generate unique ID for each render to avoid conflicts with React StrictMode
255
329
  const uniqueId = `mermaid-${Math.random().toString(36).substring(2, 11)}`;
256
330
  const { svg: renderedSvg } = await mermaid.render(uniqueId, children.trim());
331
+ // writeCache runs UNGATED — the entry is keyed by source string and is
332
+ // correct regardless of which `children` the component currently displays.
333
+ writeCache(children, { svg: renderedSvg, height: readSvgHeight(renderedSvg) });
334
+ if (cancelled) return;
257
335
  setSvg(renderedSvg);
258
336
  setError(null);
259
337
  } catch (err) {
260
- setError(err instanceof Error ? err.message : 'Failed to render diagram');
338
+ if (!cancelled) {
339
+ setError(err instanceof Error ? err.message : 'Failed to render diagram');
340
+ }
261
341
  }
262
342
  };
263
343
 
264
344
  renderDiagram();
345
+
346
+ return () => {
347
+ cancelled = true;
348
+ };
265
349
  }, [children]);
266
350
 
267
- // Apply styles to SVG after render for dark mode compatibility
268
- useEffect(() => {
351
+ // Apply styles to SVG after commit, before paint, for dark mode compatibility.
352
+ // useLayoutEffect — not useEffect so the theme palette is applied in the same
353
+ // visual frame as the SVG markup, eliminating an unstyled-SVG flash.
354
+ useLayoutEffect(() => {
269
355
  if (!containerRef.current) return;
270
356
 
271
357
  const svgEl = containerRef.current.querySelector('svg');
@@ -298,7 +384,7 @@ export function MermaidInner({ children, className, minWidth }: MermaidInnerProp
298
384
  applyLightModeStyles();
299
385
  }
300
386
 
301
- // Add class to SVG for potential CSS targeting
387
+ // CSS hook for global mermaid styling.
302
388
  svgEl.classList.add('mermaid-svg');
303
389
 
304
390
  // Watch for dark mode changes
@@ -336,6 +422,7 @@ export function MermaidInner({ children, className, minWidth }: MermaidInnerProp
336
422
  <div
337
423
  ref={containerRef}
338
424
  className={`mermaid-container my-6 flex justify-center overflow-x-auto ${className || ''}`}
425
+ style={minHeightPx ? { minHeight: `${minHeightPx}px` } : undefined}
339
426
  dangerouslySetInnerHTML={{ __html: svg }}
340
427
  />
341
428
  );
@@ -14,6 +14,7 @@ import {
14
14
  toHreflang,
15
15
  } from '@/lib/language-utils';
16
16
  import { useLinkPrefix } from '@/lib/link-prefix-context';
17
+ import { useProjectSlug } from '@/lib/project-slug-context';
17
18
  import { getUiStrings } from '@/lib/ui-strings';
18
19
 
19
20
  interface LanguageSelectorProps {
@@ -35,6 +36,7 @@ export function LanguageSelector({
35
36
  }: LanguageSelectorProps) {
36
37
  const router = useRouter();
37
38
  const linkPrefix = useLinkPrefix();
39
+ const projectSlug = useProjectSlug();
38
40
  const pathname = usePathname() ?? '';
39
41
  const ui = getUiStrings(extractLanguageFromPath(pathname || '/'));
40
42
  const [isOpen, setIsOpen] = useState(false);
@@ -83,7 +85,7 @@ export function LanguageSelector({
83
85
  // fight the user's in-session navigation choices (they may have explicitly
84
86
  // switched languages this session, which we don't want to revert).
85
87
  useEffect(() => {
86
- const savedPref = getLanguagePreference();
88
+ const savedPref = getLanguagePreference(projectSlug);
87
89
  if (savedPref && currentLanguage && savedPref !== currentLanguage.code) {
88
90
  // Only redirect if on default language URL (no explicit language in path)
89
91
  const hasLangInPath = languages.some(
@@ -117,7 +119,7 @@ export function LanguageSelector({
117
119
  return;
118
120
  }
119
121
 
120
- saveLanguagePreference(lang.code);
122
+ saveLanguagePreference(lang.code, projectSlug);
121
123
 
122
124
  const basePath = transformLanguagePath(
123
125
  pathname || '/docs',
@@ -130,7 +132,7 @@ export function LanguageSelector({
130
132
  onNavigate(url);
131
133
  router.push(url);
132
134
  },
133
- [isNavigating, currentLanguage?.code, actualDefault, pathname, linkPrefix, router, onNavigate]
135
+ [isNavigating, currentLanguage?.code, actualDefault, pathname, linkPrefix, router, onNavigate, projectSlug]
134
136
  );
135
137
 
136
138
  // Handle keyboard navigation
@@ -419,17 +419,28 @@ export function resolveLocaleWithLoweredSet(
419
419
  }
420
420
 
421
421
  /**
422
- * localStorage key for language preference
422
+ * localStorage key prefix for project-scoped language preferences. The actual
423
+ * key is `${LANGUAGE_STORAGE_KEY_PREFIX}${slug}`. Scoping by project prevents
424
+ * a `cn` preference saved on Project A from forcing a `/cn/...` redirect on
425
+ * Project B that only declares `zh-Hans` (would 404).
423
426
  */
424
- export const LANGUAGE_STORAGE_KEY = 'jamdesk-language-preference';
427
+ export const LANGUAGE_STORAGE_KEY_PREFIX = 'jamdesk-language-preference:';
428
+
429
+ /** Legacy unscoped key — read-once for one-time migration to per-project storage. */
430
+ const LEGACY_LANGUAGE_STORAGE_KEY = 'jamdesk-language-preference';
431
+
432
+ function storageKey(projectSlug: string): string {
433
+ return `${LANGUAGE_STORAGE_KEY_PREFIX}${projectSlug}`;
434
+ }
425
435
 
426
436
  /**
427
- * Save language preference to localStorage
428
- * Handles private browsing mode gracefully
437
+ * Save language preference to localStorage, scoped to project slug.
438
+ * Handles private browsing mode gracefully.
429
439
  */
430
- export function saveLanguagePreference(code: LanguageCode): void {
440
+ export function saveLanguagePreference(code: LanguageCode, projectSlug: string): void {
441
+ if (!projectSlug) return;
431
442
  try {
432
- localStorage.setItem(LANGUAGE_STORAGE_KEY, code);
443
+ localStorage.setItem(storageKey(projectSlug), code);
433
444
  } catch {
434
445
  // localStorage not available (private browsing, etc.)
435
446
  // Fail silently
@@ -437,15 +448,24 @@ export function saveLanguagePreference(code: LanguageCode): void {
437
448
  }
438
449
 
439
450
  /**
440
- * Get saved language preference from localStorage
441
- * Returns undefined if not set or localStorage unavailable
451
+ * Get saved language preference for a project from localStorage.
452
+ * Returns undefined if not set or localStorage unavailable.
442
453
  */
443
- export function getLanguagePreference(): LanguageCode | undefined {
454
+ export function getLanguagePreference(projectSlug: string): LanguageCode | undefined {
455
+ if (!projectSlug) return undefined;
444
456
  try {
445
- const saved = localStorage.getItem(LANGUAGE_STORAGE_KEY);
457
+ const saved = localStorage.getItem(storageKey(projectSlug));
446
458
  if (saved && isValidLanguageCode(saved)) {
447
459
  return saved as LanguageCode;
448
460
  }
461
+ // One-time read of legacy unscoped key — promotes it to the per-project
462
+ // slot the first time a returning user visits and clears the legacy entry.
463
+ const legacy = localStorage.getItem(LEGACY_LANGUAGE_STORAGE_KEY);
464
+ if (legacy && isValidLanguageCode(legacy)) {
465
+ localStorage.setItem(storageKey(projectSlug), legacy);
466
+ localStorage.removeItem(LEGACY_LANGUAGE_STORAGE_KEY);
467
+ return legacy as LanguageCode;
468
+ }
449
469
  } catch {
450
470
  // localStorage not available
451
471
  }
@@ -453,11 +473,12 @@ export function getLanguagePreference(): LanguageCode | undefined {
453
473
  }
454
474
 
455
475
  /**
456
- * Clear saved language preference
476
+ * Clear saved language preference for a project.
457
477
  */
458
- export function clearLanguagePreference(): void {
478
+ export function clearLanguagePreference(projectSlug: string): void {
479
+ if (!projectSlug) return;
459
480
  try {
460
- localStorage.removeItem(LANGUAGE_STORAGE_KEY);
481
+ localStorage.removeItem(storageKey(projectSlug));
461
482
  } catch {
462
483
  // localStorage not available
463
484
  }
@@ -188,7 +188,7 @@ function getLanguagePagePaths(langConfig: LanguageConfig): Set<string> {
188
188
  * @param languages - Array of language configurations from docs.json
189
189
  * @returns Record of language code to URL for alternates.languages
190
190
  */
191
- function buildHreflangAlternates(
191
+ export function buildHreflangAlternates(
192
192
  baseUrl: string,
193
193
  pagePath: string,
194
194
  languages: LanguageConfig[]
@@ -5,7 +5,7 @@
5
5
  * for ISR projects. These are uploaded to R2 alongside the MDX content.
6
6
  */
7
7
 
8
- import type { NavigationConfig } from './docs-types.js';
8
+ import type { NavigationConfig, LanguageConfig } from './docs-types.js';
9
9
  import { RECURSE_KEYS } from './enhance-navigation.js';
10
10
  import { filterVisibility } from './visibility-filter.js';
11
11
  import {
@@ -13,6 +13,7 @@ import {
13
13
  resolveLocaleFromPath,
14
14
  resolveLocaleWithLoweredSet,
15
15
  } from './language-utils.js';
16
+ import { buildHreflangAlternates } from './seo.js';
16
17
 
17
18
  /**
18
19
  * Page metadata for artifact generation.
@@ -44,6 +45,13 @@ export interface SitemapOptions {
44
45
  hostAtDocs?: boolean;
45
46
  /** Block all crawlers - generates empty sitemap */
46
47
  noindex?: boolean;
48
+ /**
49
+ * Language configurations from docs.json navigation. When provided, the
50
+ * sitemap emits `<xhtml:link rel="alternate" hreflang>` siblings per URL
51
+ * for every language that declares the page — Google's recommended sitemap
52
+ * localization signal (in addition to in-page hreflang link tags).
53
+ */
54
+ languages?: LanguageConfig[];
47
55
  }
48
56
 
49
57
  /**
@@ -53,7 +61,7 @@ export interface SitemapOptions {
53
61
  * @returns XML string
54
62
  */
55
63
  export function generateSitemap(options: SitemapOptions): string {
56
- const { baseUrl, pages, hostAtDocs = false, noindex = false } = options;
64
+ const { baseUrl, pages, hostAtDocs = false, noindex = false, languages } = options;
57
65
 
58
66
  if (noindex) {
59
67
  return `<?xml version="1.0" encoding="UTF-8"?>
@@ -62,6 +70,11 @@ export function generateSitemap(options: SitemapOptions): string {
62
70
  }
63
71
 
64
72
  const urlPrefix = hostAtDocs ? '/docs' : '';
73
+ const hreflangBaseUrl = hostAtDocs ? `${baseUrl}/docs` : baseUrl;
74
+ const multiLang = languages && languages.length > 1;
75
+ const urlsetAttrs = multiLang
76
+ ? 'xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml"'
77
+ : 'xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"';
65
78
 
66
79
  const entries = pages
67
80
  .filter(p => !p.noindex && !p.hidden)
@@ -70,16 +83,32 @@ export function generateSitemap(options: SitemapOptions): string {
70
83
  const lastmod = p.lastModified || new Date().toISOString().split('T')[0];
71
84
  const priority = p.path === 'introduction' ? '1.0' : '0.8';
72
85
 
86
+ // Per Google's spec, emit hreflang siblings only when the page has
87
+ // translations. buildHreflangAlternates already filters to languages
88
+ // whose navigation declares this page, so we won't point at 404s.
89
+ let hreflangLinks = '';
90
+ if (multiLang) {
91
+ const alternates = buildHreflangAlternates(hreflangBaseUrl, p.path, languages);
92
+ if (alternates) {
93
+ hreflangLinks = Object.entries(alternates)
94
+ .map(
95
+ ([tag, href]) =>
96
+ `\n <xhtml:link rel="alternate" hreflang="${escapeXml(tag)}" href="${escapeXml(href)}"/>`,
97
+ )
98
+ .join('');
99
+ }
100
+ }
101
+
73
102
  return ` <url>
74
- <loc>${url}</loc>
103
+ <loc>${escapeXml(url)}</loc>
75
104
  <lastmod>${lastmod}</lastmod>
76
105
  <changefreq>weekly</changefreq>
77
- <priority>${priority}</priority>
106
+ <priority>${priority}</priority>${hreflangLinks}
78
107
  </url>`;
79
108
  });
80
109
 
81
110
  return `<?xml version="1.0" encoding="UTF-8"?>
82
- <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
111
+ <urlset ${urlsetAttrs}>
83
112
  ${entries.join('\n')}
84
113
  </urlset>`;
85
114
  }
@@ -353,6 +382,8 @@ export interface GenerateAllOptions {
353
382
  rssPages?: RssPageInfo[];
354
383
  /** Pages with full content (for llms-full.txt generation) */
355
384
  llmsFullPages?: LlmsFullPageInfo[];
385
+ /** Language configurations (forwarded to sitemap for hreflang siblings) */
386
+ languages?: LanguageConfig[];
356
387
  }
357
388
 
358
389
  /**
@@ -374,10 +405,10 @@ export interface GeneratedArtifacts {
374
405
  */
375
406
  export function generateAllArtifacts(options: GenerateAllOptions): GeneratedArtifacts {
376
407
  const {
377
- baseUrl, name, description, pages, hostAtDocs, noindex, rssPages, llmsFullPages,
408
+ baseUrl, name, description, pages, hostAtDocs, noindex, rssPages, llmsFullPages, languages,
378
409
  } = options;
379
410
 
380
- const sitemap = generateSitemap({ baseUrl, pages, hostAtDocs, noindex });
411
+ const sitemap = generateSitemap({ baseUrl, pages, hostAtDocs, noindex, languages });
381
412
  const llmsTxt = generateLlmsTxt({
382
413
  name, description, baseUrl, pages, hostAtDocs, noindex,
383
414
  });
@@ -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.6.2",
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.45",
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.29",
2138
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.29.tgz",
2139
- "integrity": "sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==",
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.30001792",
2201
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz",
2202
- "integrity": "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==",
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.3",
2917
- "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.3.tgz",
2918
- "integrity": "sha512-VVwJidIJcp1hpg2OMXML3ZVRPYSZiq4aX7qBh83BSIpOaRDqI+qxhXjjIWnpzkOXhmp0L81lnoME1mnCc9H48A==",
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.355",
2932
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.355.tgz",
2933
- "integrity": "sha512-LUPZhKzZPYSPme1jEYohpkA+ybYCJztr1quAdBd7E7h3+VOBVcKkwwtBJu41nrjawrRzfb8mtMfzWozoaK0ZIQ==",
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.46",
3743
- "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.46.tgz",
3744
- "integrity": "sha512-WHy4Coo+bGZyH7NwJKHkS04YFsFcarWbAEOAC3EMndzdN6VSZqklLLIgfxzyaW9jDoeGYJX9SWbJPKpecox0Uw==",
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"