@eventcatalog/core 3.29.2 → 3.31.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.
Files changed (113) hide show
  1. package/dist/analytics/analytics.cjs +1 -1
  2. package/dist/analytics/analytics.js +2 -2
  3. package/dist/analytics/log-build.cjs +1 -1
  4. package/dist/analytics/log-build.js +3 -3
  5. package/dist/{chunk-36IA4UE4.js → chunk-7IGMIOQF.js} +1 -1
  6. package/dist/{chunk-EGQGCB2B.js → chunk-HVOLSUC2.js} +1 -1
  7. package/dist/{chunk-DB4IQ3GB.js → chunk-LWVHWR77.js} +1 -1
  8. package/dist/{chunk-VEUNSJ6Z.js → chunk-QIJOBQZ7.js} +1 -1
  9. package/dist/{chunk-MEJOYC5Z.js → chunk-UY5QDWK7.js} +1 -1
  10. package/dist/constants.cjs +1 -1
  11. package/dist/constants.js +1 -1
  12. package/dist/eventcatalog.cjs +1 -1
  13. package/dist/eventcatalog.js +5 -5
  14. package/dist/generate.cjs +1 -1
  15. package/dist/generate.js +3 -3
  16. package/dist/utils/cli-logger.cjs +1 -1
  17. package/dist/utils/cli-logger.js +2 -2
  18. package/eventcatalog/astro.config.mjs +11 -7
  19. package/eventcatalog/public/logo.png +0 -0
  20. package/eventcatalog/src/components/CopyAsMarkdown.tsx +29 -24
  21. package/eventcatalog/src/components/EnvironmentDropdown.tsx +33 -21
  22. package/eventcatalog/src/components/FieldsExplorer/FieldFilters.tsx +3 -53
  23. package/eventcatalog/src/components/FieldsExplorer/FieldsExplorer.tsx +144 -91
  24. package/eventcatalog/src/components/FieldsExplorer/FieldsTable.tsx +112 -109
  25. package/eventcatalog/src/components/Header.astro +9 -19
  26. package/eventcatalog/src/components/MDX/Accordion/Accordion.tsx +12 -14
  27. package/eventcatalog/src/components/MDX/Accordion/AccordionGroup.astro +11 -3
  28. package/eventcatalog/src/components/MDX/Design/Design.astro +1 -1
  29. package/eventcatalog/src/components/MDX/ResourceRef/ResourceRef.astro +15 -5
  30. package/eventcatalog/src/components/MDX/Tiles/Tile.astro +11 -8
  31. package/eventcatalog/src/components/SchemaExplorer/ApiContentViewer.tsx +164 -53
  32. package/eventcatalog/src/components/SchemaExplorer/DiffViewer.tsx +1 -1
  33. package/eventcatalog/src/components/SchemaExplorer/ExamplesViewer.tsx +4 -4
  34. package/eventcatalog/src/components/SchemaExplorer/Pagination.tsx +12 -10
  35. package/eventcatalog/src/components/SchemaExplorer/SchemaContentViewer.tsx +48 -77
  36. package/eventcatalog/src/components/SchemaExplorer/SchemaDetailsPanel.tsx +238 -169
  37. package/eventcatalog/src/components/SchemaExplorer/SchemaExplorer.tsx +189 -230
  38. package/eventcatalog/src/components/SchemaExplorer/SchemaListItem.tsx +39 -36
  39. package/eventcatalog/src/components/Search/Search.astro +1 -1
  40. package/eventcatalog/src/components/Seo.astro +1 -1
  41. package/eventcatalog/src/components/Settings/AssistantSettingsForm.tsx +218 -0
  42. package/eventcatalog/src/components/Settings/BillingSettingsForm.tsx +265 -0
  43. package/eventcatalog/src/components/Settings/GeneralSettingsForm.tsx +371 -0
  44. package/eventcatalog/src/components/Settings/LlmAccessSettingsForm.tsx +183 -0
  45. package/eventcatalog/src/components/Settings/LogoUpload.tsx +137 -0
  46. package/eventcatalog/src/components/Settings/McpSettingsForm.tsx +91 -0
  47. package/eventcatalog/src/components/Settings/ReadOnlyBanner.tsx +18 -0
  48. package/eventcatalog/src/components/Settings/Row.tsx +59 -0
  49. package/eventcatalog/src/components/Settings/SettingsShared.tsx +176 -0
  50. package/eventcatalog/src/components/SideNav/NestedSideBar/SearchBar.tsx +3 -3
  51. package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +233 -261
  52. package/eventcatalog/src/components/Tables/Discover/DiscoverTable.tsx +116 -68
  53. package/eventcatalog/src/components/Tables/Discover/FilterComponents.tsx +2 -2
  54. package/eventcatalog/src/components/Tables/Discover/columns.tsx +130 -197
  55. package/eventcatalog/src/components/Tables/Table.tsx +21 -18
  56. package/eventcatalog/src/components/Tables/columns/TeamsTableColumns.tsx +79 -131
  57. package/eventcatalog/src/components/Tables/columns/UserTableColumns.tsx +104 -175
  58. package/eventcatalog/src/content.config.ts +1 -1
  59. package/eventcatalog/src/enterprise/auth/error.astro +1 -1
  60. package/eventcatalog/src/enterprise/auth/login.astro +1 -1
  61. package/eventcatalog/src/enterprise/auth/middleware/middleware-auth.ts +11 -7
  62. package/eventcatalog/src/enterprise/custom-documentation/components/CustomDocsNav/index.tsx +97 -95
  63. package/eventcatalog/src/enterprise/custom-documentation/pages/docs/custom/index.astro +232 -181
  64. package/eventcatalog/src/enterprise/feature.ts +2 -1
  65. package/eventcatalog/src/enterprise/fields/pages/fields.astro +10 -8
  66. package/eventcatalog/src/enterprise/integrations/eventcatalog-features.ts +0 -8
  67. package/eventcatalog/src/layouts/DirectoryLayout.astro +17 -88
  68. package/eventcatalog/src/layouts/SettingsLayout.astro +116 -0
  69. package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +562 -141
  70. package/eventcatalog/src/layouts/VisualiserLayout.astro +7 -2
  71. package/eventcatalog/src/pages/_index.astro +253 -256
  72. package/eventcatalog/src/pages/api/settings/ai.ts +57 -0
  73. package/eventcatalog/src/pages/api/settings/general.ts +71 -0
  74. package/eventcatalog/src/pages/api/settings/logo.ts +113 -0
  75. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/index.astro +3 -3
  76. package/eventcatalog/src/pages/diagrams/[id]/[version]/index.astro +223 -73
  77. package/eventcatalog/src/pages/discover/[type]/index.astro +22 -141
  78. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/[docVersion]/index.astro +130 -30
  79. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/index.astro +147 -53
  80. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/asyncapi/[filename].astro +6 -2
  81. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/examples/[...filename].astro +2 -2
  82. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/graphql/[filename].astro +22 -19
  83. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +71 -61
  84. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/spec/[filename].astro +5 -1
  85. package/eventcatalog/src/pages/docs/[type]/[id]/language/[dictionaryId]/index.astro +3 -3
  86. package/eventcatalog/src/pages/docs/[type]/[id]/language/index.astro +6 -32
  87. package/eventcatalog/src/pages/docs/llm/llms.txt.ts +5 -1
  88. package/eventcatalog/src/pages/docs/teams/[id]/index.astro +11 -4
  89. package/eventcatalog/src/pages/docs/users/[id]/index.astro +12 -5
  90. package/eventcatalog/src/pages/schemas/explorer/index.astro +10 -8
  91. package/eventcatalog/src/pages/settings/assistant.astro +37 -0
  92. package/eventcatalog/src/pages/settings/billing.astro +17 -0
  93. package/eventcatalog/src/pages/settings/general.astro +32 -0
  94. package/eventcatalog/src/pages/settings/index.astro +21 -0
  95. package/eventcatalog/src/pages/settings/llm-access.astro +34 -0
  96. package/eventcatalog/src/pages/settings/mcp.astro +14 -0
  97. package/eventcatalog/src/pages/studio.astro +1 -1
  98. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/entity-map/index.astro +2 -7
  99. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/index.astro +2 -2
  100. package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/entity-map/index.astro +2 -7
  101. package/eventcatalog/src/styles/theme.css +95 -30
  102. package/eventcatalog/src/styles/themes/forest.css +17 -9
  103. package/eventcatalog/src/styles/themes/ocean.css +10 -2
  104. package/eventcatalog/src/styles/themes/sapphire.css +10 -2
  105. package/eventcatalog/src/styles/themes/sunset.css +25 -17
  106. package/eventcatalog/src/types/react-syntax-highlighter.d.ts +13 -0
  107. package/eventcatalog/src/utils/eventcatalog-config/config-schema.ts +49 -0
  108. package/eventcatalog/src/utils/eventcatalog-config/config-writer.ts +149 -0
  109. package/eventcatalog/src/utils/url-builder.ts +4 -2
  110. package/package.json +7 -5
  111. package/eventcatalog/public/logo.svg +0 -14
  112. package/eventcatalog/src/enterprise/plans/index.astro +0 -319
  113. package/eventcatalog/src/pages/docs/llm/llms-services.txt.ts +0 -81
@@ -0,0 +1,57 @@
1
+ import type { APIRoute } from 'astro';
2
+ import { isDevMode } from '@utils/feature';
3
+ import { aiSettingsSchema } from '@utils/eventcatalog-config/config-schema';
4
+ import {
5
+ applyConfigUpdate,
6
+ readConfigSource,
7
+ writeConfigUpdate,
8
+ type ConfigUpdate,
9
+ } from '@utils/eventcatalog-config/config-writer';
10
+
11
+ export const prerender = !isDevMode();
12
+
13
+ const json = (status: number, body: unknown) =>
14
+ new Response(JSON.stringify(body), {
15
+ status,
16
+ headers: { 'Content-Type': 'application/json' },
17
+ });
18
+
19
+ export const POST: APIRoute = async ({ request }) => {
20
+ if (!isDevMode()) {
21
+ return json(403, { error: 'Settings can only be edited when running in dev mode (EVENTCATALOG_DEV_MODE=true).' });
22
+ }
23
+
24
+ let body: unknown;
25
+ try {
26
+ body = await request.json();
27
+ } catch {
28
+ return json(400, { error: 'Invalid JSON body' });
29
+ }
30
+
31
+ const parsed = aiSettingsSchema.safeParse(body);
32
+ if (!parsed.success) {
33
+ return json(400, { error: 'Validation failed', issues: parsed.error.issues });
34
+ }
35
+
36
+ let source: string;
37
+ try {
38
+ source = readConfigSource();
39
+ } catch (err) {
40
+ return json(500, { error: `Could not read eventcatalog.config.js: ${(err as Error).message}` });
41
+ }
42
+
43
+ const data = parsed.data;
44
+ const update: ConfigUpdate = {
45
+ llmsTxt: { enabled: data.llmsTxtEnabled },
46
+ chat: { enabled: data.chatEnabled },
47
+ };
48
+
49
+ try {
50
+ applyConfigUpdate(source, update);
51
+ writeConfigUpdate(update);
52
+ } catch (err) {
53
+ return json(500, { error: `Could not update eventcatalog.config.js: ${(err as Error).message}` });
54
+ }
55
+
56
+ return json(200, { ok: true, settings: data });
57
+ };
@@ -0,0 +1,71 @@
1
+ import type { APIRoute } from 'astro';
2
+ import { isDevMode } from '@utils/feature';
3
+ import { generalSettingsSchema } from '@utils/eventcatalog-config/config-schema';
4
+ import {
5
+ applyConfigUpdate,
6
+ readConfigSource,
7
+ writeConfigUpdate,
8
+ type ConfigUpdate,
9
+ } from '@utils/eventcatalog-config/config-writer';
10
+
11
+ export const prerender = !isDevMode();
12
+
13
+ const json = (status: number, body: unknown) =>
14
+ new Response(JSON.stringify(body), {
15
+ status,
16
+ headers: { 'Content-Type': 'application/json' },
17
+ });
18
+
19
+ export const POST: APIRoute = async ({ request }) => {
20
+ if (!isDevMode()) {
21
+ return json(403, { error: 'Settings can only be edited when running in dev mode (EVENTCATALOG_DEV_MODE=true).' });
22
+ }
23
+
24
+ let body: unknown;
25
+ try {
26
+ body = await request.json();
27
+ } catch {
28
+ return json(400, { error: 'Invalid JSON body' });
29
+ }
30
+
31
+ const parsed = generalSettingsSchema.safeParse(body);
32
+ if (!parsed.success) {
33
+ return json(400, { error: 'Validation failed', issues: parsed.error.issues });
34
+ }
35
+
36
+ // Verify the config file exists and is parseable before we attempt the write.
37
+ let source: string;
38
+ try {
39
+ source = readConfigSource();
40
+ } catch (err) {
41
+ return json(500, { error: `Could not read eventcatalog.config.js: ${(err as Error).message}` });
42
+ }
43
+
44
+ const data = parsed.data;
45
+ const update: ConfigUpdate = {
46
+ title: data.title,
47
+ tagline: data.tagline ?? null,
48
+ organizationName: data.organizationName ?? null,
49
+ homepageLink: data.homepageLink ?? null,
50
+ editUrl: data.editUrl ?? null,
51
+ repositoryUrl: data.repositoryUrl ?? null,
52
+ theme: data.theme,
53
+ };
54
+
55
+ if (data.logo) {
56
+ update.logo = {
57
+ alt: data.logo.alt ?? null,
58
+ text: data.logo.text ?? null,
59
+ };
60
+ }
61
+
62
+ try {
63
+ // Dry-run the write first to surface parse errors before touching disk.
64
+ applyConfigUpdate(source, update);
65
+ writeConfigUpdate(update);
66
+ } catch (err) {
67
+ return json(500, { error: `Could not update eventcatalog.config.js: ${(err as Error).message}` });
68
+ }
69
+
70
+ return json(200, { ok: true, settings: data });
71
+ };
@@ -0,0 +1,113 @@
1
+ import type { APIRoute } from 'astro';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { isDevMode } from '@utils/feature';
5
+ import { writeConfigUpdate } from '@utils/eventcatalog-config/config-writer';
6
+
7
+ export const prerender = !isDevMode();
8
+
9
+ const MAX_BYTES = 2 * 1024 * 1024;
10
+ const ALLOWED: Record<string, string> = {
11
+ 'image/png': 'png',
12
+ 'image/jpeg': 'jpg',
13
+ 'image/svg+xml': 'svg',
14
+ 'image/webp': 'webp',
15
+ };
16
+
17
+ const json = (status: number, body: unknown) =>
18
+ new Response(JSON.stringify(body), {
19
+ status,
20
+ headers: { 'Content-Type': 'application/json' },
21
+ });
22
+
23
+ const projectRoot = () => process.env.PROJECT_DIR ?? process.cwd();
24
+ const publicDir = () => path.join(projectRoot(), 'public');
25
+ const LOGO_BASENAME = 'eventcatalog-logo';
26
+
27
+ const removeExistingLogos = () => {
28
+ const dir = publicDir();
29
+ if (!fs.existsSync(dir)) return;
30
+ for (const entry of fs.readdirSync(dir)) {
31
+ if (entry.startsWith(`${LOGO_BASENAME}.`)) {
32
+ fs.unlinkSync(path.join(dir, entry));
33
+ }
34
+ }
35
+ };
36
+
37
+ /**
38
+ * Best-effort SVG sanitization: strips <script> blocks and on* event handlers.
39
+ * Not a full sanitizer; we accept this as a v1 limitation for SVG uploads.
40
+ */
41
+ const sanitizeSvg = (svg: string): string =>
42
+ svg
43
+ .replace(/<script[\s\S]*?<\/script>/gi, '')
44
+ .replace(/\son[a-z]+\s*=\s*"[^"]*"/gi, '')
45
+ .replace(/\son[a-z]+\s*=\s*'[^']*'/gi, '')
46
+ .replace(/javascript:/gi, '');
47
+
48
+ export const POST: APIRoute = async ({ request }) => {
49
+ if (!isDevMode()) {
50
+ return json(403, { error: 'Logo can only be changed when running in dev mode (EVENTCATALOG_DEV_MODE=true).' });
51
+ }
52
+
53
+ let form: FormData;
54
+ try {
55
+ form = await request.formData();
56
+ } catch {
57
+ return json(400, { error: 'Expected multipart/form-data body' });
58
+ }
59
+
60
+ const file = form.get('logo');
61
+ if (!(file instanceof File)) {
62
+ return json(400, { error: 'Missing `logo` file field' });
63
+ }
64
+
65
+ const ext = ALLOWED[file.type];
66
+ if (!ext) {
67
+ return json(400, { error: `Unsupported logo type: ${file.type}. Allowed: PNG, JPG, SVG, WebP.` });
68
+ }
69
+
70
+ if (file.size > MAX_BYTES) {
71
+ return json(413, { error: `Logo exceeds 2MB size limit (got ${file.size} bytes).` });
72
+ }
73
+
74
+ const buffer = Buffer.from(await file.arrayBuffer());
75
+ const dir = publicDir();
76
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
77
+
78
+ removeExistingLogos();
79
+
80
+ const targetName = `${LOGO_BASENAME}.${ext}`;
81
+ const targetPath = path.join(dir, targetName);
82
+
83
+ if (ext === 'svg') {
84
+ const sanitized = sanitizeSvg(buffer.toString('utf8'));
85
+ fs.writeFileSync(targetPath, sanitized, 'utf8');
86
+ } else {
87
+ fs.writeFileSync(targetPath, buffer);
88
+ }
89
+
90
+ const src = `/${targetName}`;
91
+ try {
92
+ writeConfigUpdate({ logo: { src } });
93
+ } catch (err) {
94
+ return json(500, { error: `Logo saved but config update failed: ${(err as Error).message}` });
95
+ }
96
+
97
+ return json(200, { ok: true, src });
98
+ };
99
+
100
+ export const DELETE: APIRoute = async () => {
101
+ if (!isDevMode()) {
102
+ return json(403, { error: 'Logo can only be changed when running in dev mode.' });
103
+ }
104
+
105
+ removeExistingLogos();
106
+ try {
107
+ writeConfigUpdate({ logo: { src: null } });
108
+ } catch (err) {
109
+ return json(500, { error: `Logo files removed but config update failed: ${(err as Error).message}` });
110
+ }
111
+
112
+ return json(200, { ok: true });
113
+ };
@@ -22,9 +22,9 @@ const specifications = type === 'services' ? getSpecificationsForService(props)
22
22
  ---
23
23
 
24
24
  <VerticalSideBarLayout title={pageTitle}>
25
- <main class="flex docs-layout h-full bg-[rgb(var(--ec-page-bg))]">
26
- <div class="flex docs-layout w-full pl-16">
27
- <div class="w-full lg:mr-2 pr-8 overflow-y-auto py-8 bg-[rgb(var(--ec-page-bg))]">
25
+ <main class="flex docs-layout min-h-full bg-[rgb(var(--ec-page-bg))]">
26
+ <div class="flex docs-layout w-full">
27
+ <div class="w-full lg:mr-2 pr-24 py-8 bg-[rgb(var(--ec-page-bg))]">
28
28
  {type === 'domains' && <DomainGrid domain={domain} client:load />}
29
29
  {type === 'services' && <MessageGrid service={props} specifications={specifications} client:load />}
30
30
  </div>
@@ -5,7 +5,7 @@ import VisualiserLayout from '@layouts/VisualiserLayout.astro';
5
5
  import components from '@components/MDX/components';
6
6
  import config from '@config';
7
7
  import { buildUrl } from '@utils/url-builder';
8
- import { ChevronDown, GitCompare, X, Rocket } from 'lucide-react';
8
+ import { GitCompare, X, Rocket, AlignLeft, HistoryIcon } from 'lucide-react';
9
9
  import CopyAsMarkdown from '@components/CopyAsMarkdown';
10
10
  import { isLLMSTxtEnabled, isEventCatalogChatEnabled, isDiagramComparisonEnabled } from '@utils/feature';
11
11
 
@@ -15,7 +15,7 @@ export const prerender = Page.prerender;
15
15
  export const getStaticPaths = Page.getStaticPaths;
16
16
 
17
17
  const props = await Page.getData(Astro);
18
- const { Content } = await render(props);
18
+ const { Content, headings } = await render(props);
19
19
 
20
20
  const pageTitle = `Diagram | ${props.data.name}`;
21
21
  const currentVersion = props.data.version;
@@ -29,73 +29,123 @@ const chatQuery = `Tell me about the "${props.data.name}" diagram (version ${pro
29
29
 
30
30
  <VisualiserLayout title={pageTitle} description={props.data.summary}>
31
31
  <div class="diagram-page min-h-[calc(100vh-64px)] bg-[rgb(var(--ec-page-bg))]">
32
- <header class="diagram-header border-b border-[rgb(var(--ec-page-border))]">
33
- <div class="px-6 py-6 flex items-center justify-between gap-4">
34
- <div class="min-w-0">
35
- <div class="flex items-center gap-3">
36
- <h1 class="text-2xl md:text-2xl font-bold text-[rgb(var(--ec-page-text))] truncate">
37
- {props.data.name}
38
- </h1>
39
- {
40
- hasMultipleVersions ? (
41
- <div class="relative flex-shrink-0">
42
- <select
43
- id="version-select"
44
- class="appearance-none text-xs font-medium text-[rgb(var(--ec-page-text-muted))] bg-transparent border border-[rgb(var(--ec-page-border))] rounded-md pl-2 pr-6 py-1 cursor-pointer hover:border-[rgb(var(--ec-accent))] transition-colors"
45
- >
46
- {allVersions.map((version: string) => (
47
- <option value={buildUrl(`/diagrams/${props.data.id}/${version}`)} selected={version === currentVersion}>
48
- v{version}
49
- {version === props.data.latestVersion ? ' (latest)' : ''}
50
- </option>
51
- ))}
52
- </select>
53
- <ChevronDown className="absolute right-1.5 top-1/2 -translate-y-1/2 w-3 h-3 text-[rgb(var(--ec-icon-color))] pointer-events-none" />
54
- </div>
55
- ) : (
56
- <span class="text-xs font-medium text-[rgb(var(--ec-page-text-muted))]">v{currentVersion}</span>
57
- )
58
- }
32
+ <div
33
+ class="flex docs-layout w-full"
34
+ style="padding-left: var(--ec-app-content-padding-left, 5rem); padding-right: var(--ec-app-content-padding-right, 5rem);"
35
+ >
36
+ <main class="diagram-content w-full lg:mr-2 pr-24 py-8 bg-[rgb(var(--ec-page-bg))]">
37
+ <div class="border-b border-[rgb(var(--ec-page-border))] md:pb-2">
38
+ <div class="flex items-start justify-between gap-4">
39
+ <div class="min-w-0">
40
+ <h1 class="text-2xl md:text-4xl font-bold text-[rgb(var(--ec-page-text))] truncate">
41
+ {props.data.name}
42
+ </h1>
43
+ {
44
+ props.data.summary && (
45
+ <p class="pt-2 text-base font-light text-[rgb(var(--ec-page-text-muted))]">{props.data.summary}</p>
46
+ )
47
+ }
48
+ </div>
49
+
50
+ <div class="flex items-center gap-2">
51
+ <CopyAsMarkdown
52
+ client:only="react"
53
+ schemas={[]}
54
+ chatQuery={chatQuery}
55
+ chatEnabled={chatEnabled}
56
+ editUrl=""
57
+ markdownDownloadEnabled={markdownDownloadEnabled}
58
+ rssFeedEnabled={false}
59
+ preferChatAsDefault={chatEnabled}
60
+ chatButtonText="Ask about this diagram"
61
+ />
62
+ </div>
59
63
  </div>
60
- {props.data.summary && <p class="mt-1 text-sm text-[rgb(var(--ec-page-text-muted))]">{props.data.summary}</p>}
61
64
  </div>
62
65
 
63
- <div class="flex items-center gap-2">
64
- {
65
- hasMultipleVersions && (
66
- <button
67
- id="compare-btn"
68
- type="button"
69
- data-scale-enabled={scaleEnabled}
70
- class="inline-flex items-center justify-center gap-1.5 px-3 py-1.5 text-sm font-medium text-[rgb(var(--ec-dropdown-text))] bg-[rgb(var(--ec-dropdown-bg))] border border-[rgb(var(--ec-dropdown-border))] rounded-md shadow-xs hover:bg-[rgb(var(--ec-dropdown-hover))] focus:outline-hidden focus:ring-1 focus:ring-[rgb(var(--ec-accent))] transition-colors"
71
- >
72
- <GitCompare className="w-4 h-4" />
73
- Compare diagram versions
74
- </button>
75
- )
76
- }
77
- <CopyAsMarkdown
78
- client:only="react"
79
- schemas={[]}
80
- chatQuery={chatQuery}
81
- chatEnabled={chatEnabled}
82
- editUrl=""
83
- markdownDownloadEnabled={markdownDownloadEnabled}
84
- rssFeedEnabled={false}
85
- preferChatAsDefault={chatEnabled}
86
- chatButtonText="Ask about this diagram"
87
- />
88
- </div>
89
- </div>
90
- </header>
66
+ <article
67
+ class="diagram-article prose prose-md w-full max-w-none py-4 text-[15px] dark:prose-invert prose-headings:text-[rgb(var(--ec-page-text))] prose-p:text-[rgb(var(--ec-page-text))] prose-strong:text-[rgb(var(--ec-page-text))] prose-code:text-[rgb(var(--ec-page-text))]"
68
+ >
69
+ <Content components={components(props)} />
70
+ </article>
71
+ </main>
91
72
 
92
- <main class="diagram-content p-6">
93
- <article
94
- class="diagram-article prose prose-lg max-w-none dark:prose-invert prose-headings:text-[rgb(var(--ec-page-text))] prose-p:text-[rgb(var(--ec-page-text))] prose-strong:text-[rgb(var(--ec-page-text))] prose-code:text-[rgb(var(--ec-page-text))]"
95
- >
96
- <Content components={components(props)} />
97
- </article>
98
- </main>
73
+ {
74
+ headings.length > 0 && (
75
+ <aside
76
+ id="eventcatalog-docs-sidebar"
77
+ class="hidden xl:block sticky top-[4rem] self-start w-[280px] max-h-[calc(100vh-4rem)] overflow-y-auto py-2 flex-shrink-0 pr-10 bg-[rgb(var(--ec-page-bg))]"
78
+ >
79
+ <div class="mt-8 space-y-8">
80
+ <div>
81
+ <h3 class="text-xs text-[rgb(var(--ec-page-text))] font-semibold capitalize flex items-center gap-2 mb-4">
82
+ <AlignLeft className="w-4 h-4" />
83
+ On this page
84
+ </h3>
85
+ <nav class="text-xs border-l border-[rgb(var(--ec-page-border))]">
86
+ {headings.map((heading) => {
87
+ const level = heading.depth > 2 ? heading.depth : 1;
88
+ if (heading.depth > 3) {
89
+ return null;
90
+ }
91
+ return (
92
+ <a
93
+ href={`#${heading.slug}`}
94
+ class="block py-1.5 pr-2.5 leading-5 text-[rgb(var(--ec-page-text-muted))] hover:border-[rgb(var(--ec-page-border))] hover:text-[rgb(var(--ec-page-text))] border-l-2 border-transparent -ml-px transition-all duration-200"
95
+ style={`padding-left: ${level * 0.75}rem`}
96
+ >
97
+ {heading.text}
98
+ </a>
99
+ );
100
+ })}
101
+ </nav>
102
+ </div>
103
+ {hasMultipleVersions && (
104
+ <div>
105
+ <h3 class="text-xs text-[rgb(var(--ec-page-text))] font-semibold capitalize flex items-center gap-2 mb-4">
106
+ <HistoryIcon className="w-4 h-4" />
107
+ {`Versions (${allVersions.length})`}
108
+ </h3>
109
+ <ul role="list" class="space-y-2">
110
+ {allVersions.map((version: string) => {
111
+ const isCurrent = version === currentVersion;
112
+ return (
113
+ <li>
114
+ <a
115
+ href={buildUrl(`/diagrams/${props.data.id}/${version}`)}
116
+ class={`flex items-center gap-2 text-xs transition-colors ${
117
+ isCurrent
118
+ ? 'text-[rgb(var(--ec-accent))] font-medium'
119
+ : 'text-[rgb(var(--ec-page-text-muted))] hover:text-[rgb(var(--ec-page-text))]'
120
+ }`}
121
+ >
122
+ <HistoryIcon className="h-4 w-4 flex-shrink-0" />
123
+ <span>{version === props.data.latestVersion ? `v${version} (latest)` : `v${version}`}</span>
124
+ </a>
125
+ </li>
126
+ );
127
+ })}
128
+ </ul>
129
+ </div>
130
+ )}
131
+ {hasMultipleVersions && (
132
+ <div>
133
+ <button
134
+ id="compare-btn"
135
+ type="button"
136
+ data-scale-enabled={scaleEnabled}
137
+ class="inline-flex w-full items-center justify-center gap-1.5 rounded-md border border-[rgb(var(--ec-dropdown-border))] bg-[rgb(var(--ec-dropdown-bg))] px-3 py-2 text-sm font-medium text-[rgb(var(--ec-dropdown-text))] shadow-xs transition-colors hover:bg-[rgb(var(--ec-dropdown-hover))] focus:outline-hidden focus:ring-1 focus:ring-[rgb(var(--ec-accent))]"
138
+ >
139
+ <GitCompare className="w-4 h-4" />
140
+ Compare diagram versions
141
+ </button>
142
+ </div>
143
+ )}
144
+ </div>
145
+ </aside>
146
+ )
147
+ }
148
+ </div>
99
149
  </div>
100
150
 
101
151
  {/* Upgrade modal - shown when Scale is not enabled */}
@@ -203,6 +253,21 @@ const chatQuery = `Tell me about the "${props.data.name}" diagram (version ${pro
203
253
  <ClientRouter />
204
254
  </VisualiserLayout>
205
255
 
256
+ <style is:global>
257
+ .diagram-page .prose {
258
+ max-width: none;
259
+ overflow: auto;
260
+ }
261
+
262
+ .toc-active-text {
263
+ color: rgb(var(--ec-accent));
264
+ }
265
+
266
+ .toc-active-border {
267
+ border-color: rgb(var(--ec-accent));
268
+ }
269
+ </style>
270
+
206
271
  <script is:inline define:vars={{ config, baseUrl: import.meta.env.BASE_URL }}>
207
272
  window.eventcatalog = window.eventcatalog || {};
208
273
  window.eventcatalog.mermaid = config?.mermaid;
@@ -213,14 +278,6 @@ const chatQuery = `Tell me about the "${props.data.name}" diagram (version ${pro
213
278
  import { destroyZoomInstances, renderMermaidWithZoom } from '@utils/mermaid-zoom';
214
279
 
215
280
  function initDiagramPage() {
216
- // Version selector
217
- const versionSelect = document.getElementById('version-select') as HTMLSelectElement;
218
- if (versionSelect) {
219
- versionSelect.onchange = () => {
220
- window.location.href = versionSelect.value;
221
- };
222
- }
223
-
224
281
  // Compare modal (Scale) and Upgrade modal (non-Scale)
225
282
  const compareBtn = document.getElementById('compare-btn') as HTMLButtonElement;
226
283
  const compareModal = document.getElementById('compare-modal');
@@ -316,6 +373,99 @@ const chatQuery = `Tell me about the "${props.data.name}" diagram (version ${pro
316
373
  document.addEventListener('astro:page-load', initDiagramPage);
317
374
  </script>
318
375
 
376
+ <script>
377
+ // @ts-nocheck
378
+ function setupDiagramTocObserver() {
379
+ try {
380
+ const tocRoot = document.getElementById('eventcatalog-docs-sidebar');
381
+ if (!tocRoot) return;
382
+
383
+ const observerOptions = {
384
+ rootMargin: '0px 0px -40% 0px',
385
+ threshold: 0.1,
386
+ };
387
+
388
+ let observerPaused = false;
389
+
390
+ function highlightTocItem(id) {
391
+ tocRoot.querySelectorAll('.active-toc-item').forEach((link) => {
392
+ link.classList.remove('active-toc-item', 'toc-active-text', 'font-medium', 'toc-active-border');
393
+ link.classList.add('border-transparent');
394
+ });
395
+
396
+ const tocLink = tocRoot.querySelector(`nav a[href="#${id}"]`);
397
+ if (tocLink) {
398
+ tocLink.classList.add('active-toc-item', 'toc-active-text', 'font-medium', 'toc-active-border');
399
+ tocLink.classList.remove('border-transparent');
400
+
401
+ setTimeout(() => {
402
+ tocLink.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' });
403
+ }, 10);
404
+ }
405
+ }
406
+
407
+ const observer = new IntersectionObserver((entries) => {
408
+ if (observerPaused) return;
409
+
410
+ entries.forEach((entry) => {
411
+ try {
412
+ const id = entry.target.getAttribute('id');
413
+ if (entry.isIntersecting && id) {
414
+ highlightTocItem(id);
415
+ }
416
+ } catch (entryError) {
417
+ console.error('Error processing intersection entry:', entryError);
418
+ }
419
+ });
420
+ }, observerOptions);
421
+
422
+ const prose = document.querySelector('.diagram-article.prose');
423
+ if (!prose) return;
424
+
425
+ const proseHeadings = prose.querySelectorAll('h1[id], h2[id], h3[id]');
426
+
427
+ if (proseHeadings.length > 0) {
428
+ proseHeadings.forEach((heading) => observer.observe(heading));
429
+ } else {
430
+ const allHeadings = prose.querySelectorAll('h1, h2, h3');
431
+
432
+ allHeadings.forEach((heading) => {
433
+ if (!heading.id) {
434
+ const text = heading.textContent || '';
435
+ const slug = text
436
+ .toLowerCase()
437
+ .replace(/[^\w\s-]/g, '')
438
+ .replace(/\s+/g, '-');
439
+ heading.id = slug;
440
+ }
441
+ observer.observe(heading);
442
+ });
443
+ }
444
+
445
+ const tocLinks = tocRoot.querySelectorAll('nav a[href^="#"]');
446
+ tocLinks.forEach((link) => {
447
+ link.addEventListener('click', () => {
448
+ const hrefAttr = link.getAttribute('href');
449
+ if (!hrefAttr) return;
450
+
451
+ const id = hrefAttr.substring(1);
452
+ highlightTocItem(id);
453
+ observerPaused = true;
454
+
455
+ setTimeout(() => {
456
+ observerPaused = false;
457
+ }, 500);
458
+ });
459
+ });
460
+ } catch (error) {
461
+ console.error('Error setting up diagram TOC highlighting:', error);
462
+ }
463
+ }
464
+
465
+ setupDiagramTocObserver();
466
+ document.addEventListener('astro:page-load', setupDiagramTocObserver);
467
+ </script>
468
+
319
469
  <script>
320
470
  import { renderPlantUMLWithZoom } from '@utils/mermaid-zoom';
321
471