@eventcatalog/core 3.29.1 → 3.30.0

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 (82) 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-H5UC2A5F.js → chunk-6UG4JMUV.js} +1 -1
  6. package/dist/{chunk-4MSAPCV3.js → chunk-ATRBVTJ6.js} +1 -1
  7. package/dist/{chunk-PLNJC7NZ.js → chunk-K3ZVEX2Y.js} +13 -2
  8. package/dist/{chunk-V4OTI3PF.js → chunk-MVZKHUX2.js} +1 -1
  9. package/dist/{chunk-FVKDNLZK.js → chunk-RRBDF4MM.js} +1 -1
  10. package/dist/{chunk-24NGK43A.js → chunk-Z26P4PCB.js} +1 -1
  11. package/dist/constants.cjs +1 -1
  12. package/dist/constants.js +1 -1
  13. package/dist/eventcatalog.cjs +14 -3
  14. package/dist/eventcatalog.js +6 -6
  15. package/dist/generate.cjs +1 -1
  16. package/dist/generate.js +3 -3
  17. package/dist/utils/cli-logger.cjs +1 -1
  18. package/dist/utils/cli-logger.js +2 -2
  19. package/dist/watcher.cjs +13 -2
  20. package/dist/watcher.js +1 -1
  21. package/eventcatalog/astro.config.mjs +1 -1
  22. package/eventcatalog/public/logo.png +0 -0
  23. package/eventcatalog/src/components/CopyAsMarkdown.tsx +2 -2
  24. package/eventcatalog/src/components/EnvironmentDropdown.tsx +33 -21
  25. package/eventcatalog/src/components/FieldsExplorer/FieldFilters.tsx +3 -53
  26. package/eventcatalog/src/components/FieldsExplorer/FieldsExplorer.tsx +144 -91
  27. package/eventcatalog/src/components/FieldsExplorer/FieldsTable.tsx +112 -109
  28. package/eventcatalog/src/components/Header.astro +9 -19
  29. package/eventcatalog/src/components/MDX/Accordion/Accordion.tsx +12 -14
  30. package/eventcatalog/src/components/MDX/Accordion/AccordionGroup.astro +11 -3
  31. package/eventcatalog/src/components/MDX/ResourceRef/ResourceRef.astro +15 -5
  32. package/eventcatalog/src/components/SchemaExplorer/ApiContentViewer.tsx +164 -53
  33. package/eventcatalog/src/components/SchemaExplorer/DiffViewer.tsx +1 -1
  34. package/eventcatalog/src/components/SchemaExplorer/ExamplesViewer.tsx +4 -4
  35. package/eventcatalog/src/components/SchemaExplorer/Pagination.tsx +12 -10
  36. package/eventcatalog/src/components/SchemaExplorer/SchemaContentViewer.tsx +48 -77
  37. package/eventcatalog/src/components/SchemaExplorer/SchemaDetailsPanel.tsx +238 -169
  38. package/eventcatalog/src/components/SchemaExplorer/SchemaExplorer.tsx +189 -230
  39. package/eventcatalog/src/components/SchemaExplorer/SchemaListItem.tsx +39 -36
  40. package/eventcatalog/src/components/Search/Search.astro +1 -1
  41. package/eventcatalog/src/components/Seo.astro +1 -1
  42. package/eventcatalog/src/components/SideNav/NestedSideBar/SearchBar.tsx +3 -3
  43. package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +229 -256
  44. package/eventcatalog/src/components/Tables/Discover/DiscoverTable.tsx +78 -59
  45. package/eventcatalog/src/components/Tables/Discover/columns.tsx +130 -197
  46. package/eventcatalog/src/components/Tables/Table.tsx +21 -18
  47. package/eventcatalog/src/components/Tables/columns/TeamsTableColumns.tsx +79 -131
  48. package/eventcatalog/src/components/Tables/columns/UserTableColumns.tsx +104 -175
  49. package/eventcatalog/src/enterprise/auth/error.astro +1 -1
  50. package/eventcatalog/src/enterprise/auth/login.astro +1 -1
  51. package/eventcatalog/src/enterprise/custom-documentation/components/CustomDocsNav/index.tsx +95 -93
  52. package/eventcatalog/src/enterprise/custom-documentation/pages/docs/custom/index.astro +174 -136
  53. package/eventcatalog/src/enterprise/fields/pages/fields.astro +10 -8
  54. package/eventcatalog/src/enterprise/integrations/eventcatalog-features.ts +0 -8
  55. package/eventcatalog/src/layouts/DirectoryLayout.astro +17 -88
  56. package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +528 -146
  57. package/eventcatalog/src/layouts/VisualiserLayout.astro +7 -2
  58. package/eventcatalog/src/pages/_index.astro +5 -3
  59. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/index.astro +3 -3
  60. package/eventcatalog/src/pages/diagrams/[id]/[version]/index.astro +223 -73
  61. package/eventcatalog/src/pages/discover/[type]/index.astro +22 -141
  62. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/[docVersion]/index.astro +129 -29
  63. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/index.astro +129 -29
  64. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/asyncapi/[filename].astro +6 -2
  65. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/examples/[...filename].astro +2 -2
  66. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/graphql/[filename].astro +21 -18
  67. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +33 -32
  68. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/spec/[filename].astro +5 -1
  69. package/eventcatalog/src/pages/docs/[type]/[id]/language/[dictionaryId]/index.astro +2 -2
  70. package/eventcatalog/src/pages/docs/[type]/[id]/language/index.astro +4 -6
  71. package/eventcatalog/src/pages/docs/teams/[id]/index.astro +11 -4
  72. package/eventcatalog/src/pages/docs/users/[id]/index.astro +11 -4
  73. package/eventcatalog/src/pages/schemas/explorer/index.astro +10 -8
  74. package/eventcatalog/src/pages/studio.astro +1 -1
  75. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/entity-map/index.astro +2 -7
  76. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/index.astro +2 -2
  77. package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/entity-map/index.astro +2 -7
  78. package/eventcatalog/src/styles/theme.css +68 -12
  79. package/eventcatalog/src/types/react-syntax-highlighter.d.ts +13 -0
  80. package/package.json +1 -1
  81. package/eventcatalog/public/logo.svg +0 -14
  82. package/eventcatalog/src/enterprise/plans/index.astro +0 -319
@@ -1,18 +1,6 @@
1
1
  ---
2
- import {
3
- QueueListIcon,
4
- RectangleGroupIcon,
5
- BoltIcon,
6
- ChatBubbleLeftIcon,
7
- CubeIcon,
8
- GlobeAltIcon,
9
- } from '@heroicons/react/24/outline';
10
- import ServerIcon from '@heroicons/react/24/outline/ServerIcon';
11
- import { MagnifyingGlassIcon } from '@heroicons/react/20/solid';
12
- import { DatabaseIcon } from 'lucide-react';
13
2
  import { getCommands } from '@utils/collections/commands';
14
3
  import { getDomains, getDomainsForService } from '@utils/collections/domains';
15
- import { getFlows } from '@utils/collections/flows';
16
4
  import { getEvents } from '@utils/collections/events';
17
5
  import { getServices } from '@utils/collections/services';
18
6
  import { getQueries } from '@utils/collections/queries';
@@ -21,7 +9,6 @@ import { getDataProducts } from '@utils/collections/data-products';
21
9
  import { getUsers } from '@utils/collections/users';
22
10
  import { getTeams } from '@utils/collections/teams';
23
11
  import { getChannels } from '@utils/collections/channels';
24
- import { buildUrl } from '@utils/url-builder';
25
12
  import VerticalSideBarLayout from '@layouts/VerticalSideBarLayout.astro';
26
13
  import { DiscoverTable, type DiscoverTableData, type CollectionType } from '@components/Tables/Discover';
27
14
  import config from '@config';
@@ -32,15 +19,11 @@ export const getStaticPaths = Page.getStaticPaths;
32
19
 
33
20
  const { type, data } = await Page.getData(Astro);
34
21
 
35
- // Fetch all collections for tabs
36
22
  const events = await getEvents();
37
23
  const queries = await getQueries();
38
24
  const commands = await getCommands();
39
25
  const services = await getServices();
40
- const internalServices = services.filter((s) => !s.data.externalSystem);
41
- const externalServices = services.filter((s) => s.data.externalSystem);
42
26
  const domains = await getDomains({ getAllVersions: false });
43
- const flows = await getFlows();
44
27
  const containers = await getContainers();
45
28
  const dataProducts = await getDataProducts();
46
29
  const channels = await getChannels();
@@ -56,11 +39,13 @@ const typeConfig: Record<
56
39
  string,
57
40
  {
58
41
  label: string;
42
+ description: string;
59
43
  propertyOptions: Array<{ id: string; label: string }>;
60
44
  }
61
45
  > = {
62
46
  events: {
63
47
  label: 'Events',
48
+ description: 'Explore the events flowing through your catalog and understand who produces and consumes them.',
64
49
  propertyOptions: [
65
50
  { id: 'hasProducers', label: 'Has Producers' },
66
51
  { id: 'hasConsumers', label: 'Has Consumers' },
@@ -70,6 +55,7 @@ const typeConfig: Record<
70
55
  },
71
56
  commands: {
72
57
  label: 'Commands',
58
+ description: 'Browse the commands in your catalog and see which services issue and respond to them.',
73
59
  propertyOptions: [
74
60
  { id: 'hasProducers', label: 'Has Producers' },
75
61
  { id: 'hasConsumers', label: 'Has Consumers' },
@@ -79,6 +65,7 @@ const typeConfig: Record<
79
65
  },
80
66
  queries: {
81
67
  label: 'Queries',
68
+ description: 'Explore the queries available across your catalog and trace the services connected to them.',
82
69
  propertyOptions: [
83
70
  { id: 'hasProducers', label: 'Has Producers' },
84
71
  { id: 'hasConsumers', label: 'Has Consumers' },
@@ -88,6 +75,7 @@ const typeConfig: Record<
88
75
  },
89
76
  domains: {
90
77
  label: 'Domains',
78
+ description: 'Organize and explore the business domains that shape your architecture.',
91
79
  propertyOptions: [
92
80
  { id: 'hasServices', label: 'Has Services' },
93
81
  { id: 'hasOwners', label: 'Has Owners' },
@@ -97,6 +85,7 @@ const typeConfig: Record<
97
85
  },
98
86
  services: {
99
87
  label: 'Services',
88
+ description: 'Browse the internal services in your catalog and inspect the capabilities they expose.',
100
89
  propertyOptions: [
101
90
  { id: 'hasSpecifications', label: 'Has Specifications' },
102
91
  { id: 'hasOwners', label: 'Has Owners' },
@@ -107,6 +96,7 @@ const typeConfig: Record<
107
96
  },
108
97
  'external-systems': {
109
98
  label: 'External Systems',
99
+ description: 'Explore the external systems connected to your architecture and the contracts around them.',
110
100
  propertyOptions: [
111
101
  { id: 'hasSpecifications', label: 'Has Specifications' },
112
102
  { id: 'hasOwners', label: 'Has Owners' },
@@ -117,6 +107,7 @@ const typeConfig: Record<
117
107
  },
118
108
  flows: {
119
109
  label: 'Flows',
110
+ description: 'Inspect the business and technical flows that connect resources across your catalog.',
120
111
  propertyOptions: [
121
112
  { id: 'hasOwners', label: 'Has Owners' },
122
113
  { id: 'isDeprecated', label: 'Is Deprecated' },
@@ -124,6 +115,7 @@ const typeConfig: Record<
124
115
  },
125
116
  containers: {
126
117
  label: 'Data',
118
+ description: 'Explore the data stores and containers that services read from and write to.',
127
119
  propertyOptions: [
128
120
  { id: 'hasOwners', label: 'Has Owners' },
129
121
  { id: 'hasWriters', label: 'Has Writers' },
@@ -133,6 +125,7 @@ const typeConfig: Record<
133
125
  },
134
126
  'data-products': {
135
127
  label: 'Data Products',
128
+ description: 'Browse your data products and understand the inputs, outputs, and ownership behind them.',
136
129
  propertyOptions: [
137
130
  { id: 'hasOwners', label: 'Has Owners' },
138
131
  { id: 'hasInputs', label: 'Has Inputs' },
@@ -149,90 +142,6 @@ const tableConfigurationKey = type === 'external-systems' ? 'services' : type;
149
142
  // @ts-ignore
150
143
  const tableConfiguration = config[tableConfigurationKey as keyof typeof config]?.tableConfiguration ?? { columns: {} };
151
144
 
152
- const tabs = [
153
- {
154
- label: `Domains (${domains.length})`,
155
- href: buildUrl('/discover/domains'),
156
- isActive: type === 'domains',
157
- icon: RectangleGroupIcon,
158
- activeColor: 'yellow',
159
- enabled: domains.length > 0,
160
- visible: domains.length > 0,
161
- },
162
- {
163
- label: `Services (${internalServices.length})`,
164
- href: buildUrl('/discover/services'),
165
- isActive: type === 'services',
166
- icon: ServerIcon,
167
- activeColor: 'pink',
168
- enabled: internalServices.length > 0,
169
- visible: internalServices.length > 0,
170
- },
171
- {
172
- label: `External Systems (${externalServices.length})`,
173
- href: buildUrl('/discover/external-systems'),
174
- isActive: type === 'external-systems',
175
- icon: GlobeAltIcon,
176
- activeColor: 'purple',
177
- enabled: externalServices.length > 0,
178
- visible: externalServices.length > 0,
179
- },
180
- {
181
- label: `Data (${containers.length})`,
182
- href: buildUrl('/discover/containers'),
183
- isActive: type === 'containers',
184
- icon: DatabaseIcon,
185
- activeColor: 'blue',
186
- enabled: containers.length > 0,
187
- visible: containers.length > 0,
188
- },
189
- {
190
- label: `Data Products (${dataProducts.length})`,
191
- href: buildUrl('/discover/data-products'),
192
- isActive: type === 'data-products',
193
- icon: CubeIcon,
194
- activeColor: 'cyan',
195
- enabled: dataProducts.length > 0,
196
- visible: dataProducts.length > 0,
197
- },
198
- {
199
- label: `Events (${events.length})`,
200
- href: buildUrl('/discover/events'),
201
- isActive: type === 'events',
202
- icon: BoltIcon,
203
- activeColor: 'orange',
204
- enabled: events.length > 0,
205
- visible: events.length > 0,
206
- },
207
- {
208
- label: `Queries (${queries.length})`,
209
- href: buildUrl('/discover/queries'),
210
- isActive: type === 'queries',
211
- icon: MagnifyingGlassIcon,
212
- activeColor: 'green',
213
- enabled: queries.length > 0,
214
- visible: queries.length > 0,
215
- },
216
- {
217
- label: `Commands (${commands.length})`,
218
- href: buildUrl('/discover/commands'),
219
- isActive: type === 'commands',
220
- icon: ChatBubbleLeftIcon,
221
- activeColor: 'blue',
222
- enabled: commands.length > 0,
223
- visible: commands.length > 0,
224
- },
225
- {
226
- label: `Flows (${flows.length})`,
227
- href: buildUrl('/discover/flows'),
228
- isActive: type === 'flows',
229
- icon: QueueListIcon,
230
- activeColor: 'teal',
231
- enabled: flows.length > 0,
232
- visible: flows.length > 0,
233
- },
234
- ];
235
-
236
145
  // Map data to match the expected structure for the table
237
146
  function mapToItem(i: any) {
238
147
  return {
@@ -346,6 +255,7 @@ const tableData = enrichedData.map((d: any) => ({
346
255
  id: d.data.id,
347
256
  name: d.data.name,
348
257
  summary: d.data?.summary,
258
+ icon: d.data?.styles?.icon,
349
259
  version: d.data.version,
350
260
  latestVersion: d.data?.latestVersion,
351
261
  draft: d.data?.draft,
@@ -409,42 +319,21 @@ const title = `${currentTypeConfig.label} (${data.length})`;
409
319
  ---
410
320
 
411
321
  <VerticalSideBarLayout title={`Explore | ${title}`} showNestedSideBar={false}>
412
- <main class="ml-0 bg-[rgb(var(--ec-page-bg))] h-content flex flex-col overflow-hidden">
413
- <div id="discover-collection-tabs">
414
- <div class="hidden sm:block">
415
- <div class="border-b border-[rgb(var(--ec-page-border))]">
416
- <nav class="flex space-x-8 -mb-0.5 pl-6" aria-label="Tabs">
417
- {
418
- tabs
419
- .filter((tab) => tab.visible)
420
- .map((tab) => (
421
- <a
422
- href={tab.href}
423
- class:list={[
424
- 'group inline-flex items-center py-4 px-1 text-sm font-light text-[rgb(var(--ec-page-text))]',
425
- tab.isActive && 'border-b-[2px] border-[rgb(var(--ec-accent))] text-[rgb(var(--ec-accent))]',
426
- !tab.isActive && 'opacity-70 hover:opacity-100',
427
- !tab.enabled && 'disabled',
428
- ]}
429
- aria-current="page"
430
- >
431
- <tab.icon
432
- className={`w-6 h-6 -ml-0.5 mr-2 ${tab.isActive ? 'text-[rgb(var(--ec-accent))]' : 'text-[rgb(var(--ec-icon-color))]'}`}
433
- />
434
- <span>{tab.label}</span>
435
- </a>
436
- ))
437
- }
438
- </nav>
439
- </div>
440
- </div>
441
- </div>
442
-
443
- <div class="flex-1 overflow-hidden">
322
+ <style is:global>
323
+ #eventcatalog-header {
324
+ left: calc(var(--ec-vertical-nav-width, 14rem) + var(--ec-discover-sidebar-width, 320px)) !important;
325
+ }
326
+ </style>
327
+ <main
328
+ class="ml-0 flex h-full min-h-0 flex-col overflow-hidden bg-[rgb(var(--ec-page-bg))]"
329
+ style={`--ec-discover-sidebar-width: 320px; margin-left: calc(var(--ec-app-content-padding-left, 5rem) * -1); margin-right: calc(var(--ec-app-content-padding-right, 5rem) * -1); height: calc(100dvh - 60px);`}
330
+ >
331
+ <div class="flex-1 min-h-0 overflow-hidden">
444
332
  <DiscoverTable
445
333
  data={tableData as DiscoverTableData[]}
446
334
  collectionType={type as CollectionType}
447
335
  collectionLabel={currentTypeConfig.label}
336
+ collectionDescription={currentTypeConfig.description}
448
337
  domains={uniqueDomains}
449
338
  owners={uniqueOwners as Array<{ id: string; name: string; type?: 'user' | 'team' }>}
450
339
  producers={uniqueProducers}
@@ -459,11 +348,3 @@ const title = `${currentTypeConfig.label} (${data.length})`;
459
348
  </div>
460
349
  </main>
461
350
  </VerticalSideBarLayout>
462
-
463
- <style>
464
- a.disabled {
465
- pointer-events: none;
466
- cursor: default;
467
- opacity: 0.25;
468
- }
469
- </style>
@@ -3,9 +3,10 @@ import { render } from 'astro:content';
3
3
 
4
4
  import VerticalSideBarLayout from '@layouts/VerticalSideBarLayout.astro';
5
5
  import components from '@components/MDX/components';
6
+ import CopyAsMarkdown from '@components/CopyAsMarkdown';
6
7
  import { buildUrl } from '@utils/url-builder';
7
8
  import { AlignLeftIcon, HistoryIcon } from 'lucide-react';
8
- import { isResourceDocsEnabled } from '@utils/feature';
9
+ import { isEventCatalogChatEnabled, isResourceDocsEnabled } from '@utils/feature';
9
10
  import { getIcon } from '@utils/badges';
10
11
  import { collectionToResourceMap } from '@utils/collections/util';
11
12
 
@@ -33,6 +34,8 @@ const docsBasePath = `/docs/${props.data.resourceCollection}/${props.data.resour
33
34
  const singularResourceName =
34
35
  collectionToResourceMap[props.data.resourceCollection as keyof typeof collectionToResourceMap] ??
35
36
  props.data.resourceCollection.slice(0, props.data.resourceCollection.length - 1);
37
+ const chatEnabled = isEventCatalogChatEnabled();
38
+ const chatQuery = `Tell me about the ${props.data.type} doc "${title}" for ${props.data.resourceId} (version ${props.data.version})`;
36
39
 
37
40
  const pagefindAttributes =
38
41
  props.data.version === props.data.latestVersion
@@ -44,35 +47,37 @@ const pagefindAttributes =
44
47
  ---
45
48
 
46
49
  <VerticalSideBarLayout title={pageTitle} description={props.data.summary}>
47
- <main class="flex docs-layout h-full bg-[rgb(var(--ec-page-bg))]" {...pagefindAttributes}>
48
- <div class="flex docs-layout w-full pl-16">
49
- <div class="w-full lg:mr-2 pr-8 overflow-y-auto py-8 bg-[rgb(var(--ec-page-bg))]">
50
- <div class="border-b border-[rgb(var(--ec-page-border))] pb-4">
51
- <p class="text-xs uppercase tracking-wide text-[rgb(var(--ec-page-text-muted))]">
52
- <a
53
- href={buildUrl(`/docs/${props.data.resourceCollection}/${props.data.resourceId}/${props.data.resourceVersion}`)}
54
- class="hover:underline"
55
- >
56
- {singularResourceName}: {props.data.resourceId}
57
- </a>
58
- </p>
59
- <div class="flex items-center gap-2 pt-1">
60
- <h2 id="doc-page-header" class="text-2xl md:text-4xl font-bold text-[rgb(var(--ec-page-text))]">{title}</h2>
61
- <span
62
- class="text-xs rounded-md px-2 py-1 bg-[rgb(var(--ec-content-hover))] text-[rgb(var(--ec-page-text-muted))] border border-[rgb(var(--ec-page-border))]"
63
- >
64
- v{props.data.version}
65
- {props.data.version === props.data.latestVersion && ' (latest)'}
66
- </span>
50
+ <main class="flex docs-layout min-h-full bg-[rgb(var(--ec-page-bg))]" {...pagefindAttributes}>
51
+ <div class="flex docs-layout w-full">
52
+ <div class="w-full lg:mr-2 pr-24 py-8 bg-[rgb(var(--ec-page-bg))]">
53
+ <div class="border-b border-[rgb(var(--ec-page-border))] md:pb-2">
54
+ <div>
55
+ <div class="flex justify-between items-center">
56
+ <div class="flex items-center gap-2">
57
+ <h2 id="doc-page-header" class="text-2xl md:text-4xl font-bold text-[rgb(var(--ec-page-text))]">{title}</h2>
58
+ </div>
59
+ <div class="hidden lg:block">
60
+ <CopyAsMarkdown
61
+ client:only="react"
62
+ schemas={[]}
63
+ chatQuery={chatQuery}
64
+ chatEnabled={chatEnabled}
65
+ editUrl=""
66
+ markdownDownloadEnabled={true}
67
+ rssFeedEnabled={false}
68
+ preferChatAsDefault={false}
69
+ />
70
+ </div>
71
+ </div>
67
72
  </div>
68
73
  {
69
74
  props.data.summary && (
70
- <p class="text-lg pt-2 text-[rgb(var(--ec-page-text-muted))] font-light">{props.data.summary}</p>
75
+ <p class="text-base pt-2 text-[rgb(var(--ec-page-text-muted))] font-light">{props.data.summary}</p>
71
76
  )
72
77
  }
73
78
  {
74
79
  badges.length > 0 && (
75
- <div class="flex flex-wrap gap-3 pt-4">
80
+ <div class="flex flex-wrap gap-3 py-4">
76
81
  {badges.map((badge: any) => (
77
82
  <span
78
83
  class={`
@@ -128,23 +133,26 @@ const pagefindAttributes =
128
133
  <Content components={components(props)} />
129
134
  </div>
130
135
  </div>
131
- <aside class="hidden lg:block sticky top-0 pb-10 w-80 overflow-y-auto border-l border-[rgb(var(--ec-page-border))] py-2">
132
- <div class="sticky top-28 left-0 h-full overflow-y-auto px-6 py-4">
133
- <h3 class="text-sm text-[rgb(var(--ec-page-text))] font-semibold capitalize flex items-center gap-2">
136
+ <aside
137
+ 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))]"
138
+ >
139
+ <div class="mt-8 space-y-8">
140
+ <h3 class="text-xs text-[rgb(var(--ec-page-text))] font-semibold capitalize flex items-center gap-2 mb-4">
134
141
  <AlignLeftIcon className="w-4 h-4" />
135
142
  On this page
136
143
  </h3>
137
- <nav class="space-y-1 text-sm py-4">
144
+ <nav class="text-xs border-l border-[rgb(var(--ec-page-border))]">
138
145
  {
139
146
  headings.map((heading) => {
147
+ const level = heading.depth > 2 ? heading.depth : 1;
140
148
  if (heading.depth > 3) {
141
149
  return null;
142
150
  }
143
151
  return (
144
152
  <a
145
153
  href={`#${heading.slug}`}
146
- class={`block text-[12px] py-0.5 ${heading.depth === 1 ? 'font-light' : ''} text-[rgb(var(--ec-page-text-muted))] hover:text-[rgb(var(--ec-page-text))]`}
147
- style={`padding-left: ${(heading.depth - 1) * 8}px`}
154
+ 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`}
155
+ style={`padding-left: ${level * 0.75}rem`}
148
156
  >
149
157
  {heading.text}
150
158
  </a>
@@ -189,7 +197,99 @@ const pagefindAttributes =
189
197
  overflow: auto;
190
198
  }
191
199
 
200
+ .toc-active-text {
201
+ color: rgb(var(--ec-accent));
202
+ }
203
+
204
+ .toc-active-border {
205
+ border-color: rgb(var(--ec-accent));
206
+ }
207
+
192
208
  .version-item:hover {
193
209
  background: linear-gradient(to left, rgb(var(--ec-accent-gradient-from)), rgb(var(--ec-accent-gradient-to)));
194
210
  }
195
211
  </style>
212
+
213
+ <script>
214
+ // @ts-nocheck
215
+ function setupObserver() {
216
+ try {
217
+ const observerOptions = {
218
+ rootMargin: '0px 0px -40% 0px',
219
+ threshold: 0.1,
220
+ };
221
+
222
+ let observerPaused = false;
223
+
224
+ function highlightTocItem(id) {
225
+ document.querySelectorAll('.active-toc-item').forEach((link) => {
226
+ link.classList.remove('active-toc-item', 'toc-active-text', 'font-medium', 'toc-active-border');
227
+ link.classList.add('border-transparent');
228
+ });
229
+
230
+ const tocLink = document.querySelector(`nav a[href="#${id}"]`);
231
+ if (tocLink) {
232
+ tocLink.classList.add('active-toc-item', 'toc-active-text', 'font-medium', 'toc-active-border');
233
+ tocLink.classList.remove('border-transparent');
234
+
235
+ setTimeout(() => {
236
+ tocLink.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' });
237
+ }, 10);
238
+ }
239
+ }
240
+
241
+ const observer = new IntersectionObserver((entries) => {
242
+ if (observerPaused) return;
243
+
244
+ entries.forEach((entry) => {
245
+ const id = entry.target.getAttribute('id');
246
+ if (entry.isIntersecting && id) {
247
+ highlightTocItem(id);
248
+ }
249
+ });
250
+ }, observerOptions);
251
+
252
+ const prose = document.querySelector('.prose');
253
+ if (!prose) return;
254
+
255
+ const headings = prose.querySelectorAll('h1[id], h2[id], h3[id]');
256
+
257
+ if (headings.length > 0) {
258
+ headings.forEach((heading) => observer.observe(heading));
259
+ } else {
260
+ const allHeadings = prose.querySelectorAll('h1, h2, h3');
261
+ allHeadings.forEach((heading) => {
262
+ if (!heading.id) {
263
+ const text = heading.textContent || '';
264
+ heading.id = text
265
+ .toLowerCase()
266
+ .replace(/[^\w\s-]/g, '')
267
+ .replace(/\s+/g, '-');
268
+ }
269
+ observer.observe(heading);
270
+ });
271
+ }
272
+
273
+ const tocLinks = document.querySelectorAll('nav a[href^="#"]');
274
+ tocLinks.forEach((link) => {
275
+ link.addEventListener('click', () => {
276
+ const hrefAttr = link.getAttribute('href');
277
+ if (!hrefAttr) return;
278
+
279
+ const id = hrefAttr.substring(1);
280
+ highlightTocItem(id);
281
+
282
+ observerPaused = true;
283
+ setTimeout(() => {
284
+ observerPaused = false;
285
+ }, 1000);
286
+ });
287
+ });
288
+ } catch (error) {
289
+ console.error('Error setting up TOC observer:', error);
290
+ }
291
+ }
292
+
293
+ setupObserver();
294
+ document.addEventListener('astro:page-load', setupObserver);
295
+ </script>