@eventcatalog/core 2.30.8 → 2.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 (49) hide show
  1. package/README.md +1 -1
  2. package/dist/analytics/analytics.cjs +1 -1
  3. package/dist/analytics/analytics.js +2 -2
  4. package/dist/analytics/log-build.cjs +9 -3
  5. package/dist/analytics/log-build.d.cts +4 -1
  6. package/dist/analytics/log-build.d.ts +4 -1
  7. package/dist/analytics/log-build.js +3 -3
  8. package/dist/{chunk-NALVGTIE.js → chunk-GHBJ372G.js} +1 -1
  9. package/dist/{chunk-XMDPVKIJ.js → chunk-NJGR7XUU.js} +44 -1
  10. package/dist/{chunk-D4IJRFPJ.js → chunk-QR5UTYCE.js} +9 -3
  11. package/dist/{chunk-XJBJFIN7.js → chunk-YUINCBLT.js} +1 -1
  12. package/dist/constants.cjs +1 -1
  13. package/dist/constants.js +1 -1
  14. package/dist/eventcatalog.cjs +74 -14
  15. package/dist/eventcatalog.config.d.cts +28 -0
  16. package/dist/eventcatalog.config.d.ts +28 -0
  17. package/dist/eventcatalog.js +29 -16
  18. package/dist/features.cjs +46 -2
  19. package/dist/features.d.cts +2 -1
  20. package/dist/features.d.ts +2 -1
  21. package/dist/features.js +5 -3
  22. package/eventcatalog/public/images/custom-docs-placeholder.png +0 -0
  23. package/eventcatalog/src/components/MDX/NodeGraph/Nodes/Custom.tsx +0 -2
  24. package/eventcatalog/src/components/MDX/Steps/Step.astro +1 -1
  25. package/eventcatalog/src/components/MDX/Steps/Steps.astro +15 -0
  26. package/eventcatalog/src/components/SideNav/CustomDocsNav/CustomDocsNavWrapper.tsx +11 -0
  27. package/eventcatalog/src/components/SideNav/CustomDocsNav/components/NestedItem.tsx +183 -0
  28. package/eventcatalog/src/components/SideNav/CustomDocsNav/components/NoResultsFound.tsx +21 -0
  29. package/eventcatalog/src/components/SideNav/CustomDocsNav/index.tsx +250 -0
  30. package/eventcatalog/src/components/SideNav/CustomDocsNav/types.ts +29 -0
  31. package/eventcatalog/src/components/SideNav/CustomDocsNav.astro +9 -0
  32. package/eventcatalog/src/content.config.ts +15 -24
  33. package/eventcatalog/src/enterprise/collections/custom-pages.ts +19 -0
  34. package/eventcatalog/src/enterprise/custom-documentation/collection.ts +16 -0
  35. package/eventcatalog/src/enterprise/custom-documentation/components/CustomDocsNav/CustomDocsNavWrapper.tsx +11 -0
  36. package/eventcatalog/src/enterprise/custom-documentation/components/CustomDocsNav/components/NestedItem.tsx +183 -0
  37. package/eventcatalog/src/enterprise/custom-documentation/components/CustomDocsNav/components/NoResultsFound.tsx +21 -0
  38. package/eventcatalog/src/enterprise/custom-documentation/components/CustomDocsNav/index.tsx +250 -0
  39. package/eventcatalog/src/enterprise/custom-documentation/components/CustomDocsNav/types.ts +29 -0
  40. package/eventcatalog/src/enterprise/custom-documentation/pages/index.astro +389 -0
  41. package/eventcatalog/src/enterprise/custom-documentation/utils/custom-docs.ts +118 -0
  42. package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +57 -10
  43. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +117 -3
  44. package/eventcatalog/src/pages/docs/custom/[...path]/index.astro +34 -0
  45. package/eventcatalog/src/pages/pro/index.astro +272 -0
  46. package/eventcatalog/src/shared-collections.ts +25 -0
  47. package/eventcatalog/src/utils/eventcatalog-config/catalog.ts +12 -1
  48. package/eventcatalog/src/utils/feature.ts +5 -0
  49. package/package.json +1 -1
@@ -0,0 +1,389 @@
1
+ ---
2
+ import VerticalSideBarLayout from '@layouts/VerticalSideBarLayout.astro';
3
+ import { render } from 'astro:content';
4
+ import { buildUrl } from '@utils/url-builder';
5
+ import config from '@config';
6
+ import { AlignLeftIcon } from 'lucide-react';
7
+ import mdxComponents from '@components/MDX/components';
8
+ import OwnersList from '@components/Lists/OwnersList';
9
+ import { getOwner } from '@utils/collections/owners';
10
+ import CustomDocsNav from '@components/SideNav/CustomDocsNav.astro';
11
+
12
+ const props = Astro.props;
13
+ const doc = props.data;
14
+ const { Content, headings } = await render(props as any);
15
+ const currentSlug = props.id;
16
+
17
+ // Get sidebar data
18
+ const sidebar = config?.customDocs?.sidebar || [];
19
+
20
+ // Flatten the sidebar to find previous and next pages
21
+ type FlatItem = {
22
+ label: string;
23
+ slug: string;
24
+ };
25
+
26
+ // Define sidebar section type
27
+ type SidebarSection = {
28
+ label: string;
29
+ slug?: string;
30
+ items?: Array<{
31
+ label: string;
32
+ slug: string;
33
+ }>;
34
+ };
35
+
36
+ const flattenedItems: FlatItem[] = [];
37
+
38
+ // Process all sidebar sections to create a flattened array
39
+ sidebar.forEach((section: SidebarSection) => {
40
+ if (section.slug && !section.items) {
41
+ flattenedItems.push({
42
+ label: section.label,
43
+ slug: section.slug,
44
+ });
45
+ }
46
+ if (section.items) {
47
+ section.items.forEach((item: { label: string; slug: string }) => {
48
+ flattenedItems.push({
49
+ label: item.label,
50
+ slug: item.slug,
51
+ });
52
+ });
53
+ }
54
+ });
55
+
56
+ // Find current page index in flattened items
57
+ const currentIndex = flattenedItems.findIndex((item) => currentSlug === item.slug);
58
+
59
+ // Get previous and next items
60
+ const prevItem = currentIndex > 0 ? flattenedItems[currentIndex - 1] : null;
61
+ const nextItem = currentIndex < flattenedItems.length - 1 ? flattenedItems[currentIndex + 1] : null;
62
+
63
+ const ownersRaw = doc?.owners || [];
64
+ const owners = await Promise.all<ReturnType<typeof getOwner>>(ownersRaw.map(getOwner));
65
+ const filteredOwners = owners.filter((o) => o !== undefined);
66
+
67
+ const ownersList = filteredOwners.map((o) => ({
68
+ label: o.data.name,
69
+ type: o.collection,
70
+ badge: o.collection === 'users' ? o.data.role : 'Team',
71
+ avatarUrl: o.collection === 'users' ? o.data.avatarUrl : '',
72
+ href: buildUrl(`/docs/${o.collection}/${o.data.id}`),
73
+ }));
74
+
75
+ const badges = doc?.badges || [];
76
+ ---
77
+
78
+ <VerticalSideBarLayout title={doc.title || 'Documentation'}>
79
+ <div class="flex w-full">
80
+ <!-- Left Sidebar Navigation -->
81
+ <aside class="sidebar-transition overflow-y-auto bg-white border-r border-gray-100 w-80 fixed top-16 bottom-0 z-10">
82
+ <CustomDocsNav />
83
+ </aside>
84
+
85
+ <!-- Main Content Area - Independent scrolling -->
86
+ <main class="sidebar-transition w-full max-h-content ml-[22em] 2xl:ml-[24em] mr-80 lg:mr-[26em] max-w-5xl mx-auto">
87
+ <div class="max-w-7xl mx-auto px-6 py-10">
88
+ <div class="border-b border-gray-200 flex justify-between items-start md:pb-6">
89
+ <div>
90
+ <h2 id="doc-page-header" class="text-2xl md:text-4xl font-bold text-black">{doc.title}</h2>
91
+ {
92
+ badges && (
93
+ <div class="flex flex-wrap pt-4">
94
+ {badges.map((badge: any) => {
95
+ return (
96
+ <a href={badge.url || '#'} class="pb-2">
97
+ <span
98
+ id={badge.id || ''}
99
+ class={`text-sm font-light text-gray-500 px-2 py-1 rounded-md mr-2 bg-gradient-to-b from-${badge.backgroundColor}-100 to-${badge.backgroundColor}-200 space-x-1 border border-${badge.backgroundColor}-200 text-${badge.textColor}-800 flex items-center ${badge.class ? badge.class : ''} `}
100
+ >
101
+ {badge.icon && <badge.icon className="w-4 h-4 inline-block mr-1 " />}
102
+ {badge.iconURL && <img src={badge.iconURL} class="w-5 h-5 inline-block " />}
103
+ <span>{badge.content}</span>
104
+ </span>
105
+ </a>
106
+ );
107
+ })}
108
+ </div>
109
+ )
110
+ }
111
+ </div>
112
+ </div>
113
+ <div class="flex-auto prose py-8 max-w-none">
114
+ <Content components={{ ...mdxComponents(props) }} />
115
+ </div>
116
+
117
+ <!-- Previous / Next Navigation -->
118
+ <div class="py-8 border-t border-gray-200 mt-8">
119
+ <div class="flex flex-col sm:flex-row justify-between w-full gap-4">
120
+ {
121
+ prevItem && (
122
+ <a
123
+ href={buildUrl(`/docs/custom/${prevItem.slug}`)}
124
+ class="group flex flex-col border border-gray-200 rounded-lg p-4 hover:border-gray-300 hover:bg-gray-50 transition-colors w-full sm:w-1/2"
125
+ >
126
+ <span class="text-sm text-gray-500 mb-1">Previous</span>
127
+ <span class="font-medium group-hover:text-primary-600 transition-colors">{prevItem.label}</span>
128
+ </a>
129
+ )
130
+ }
131
+
132
+ {!prevItem && <div class="w-full sm:w-1/2" />}
133
+
134
+ {
135
+ nextItem && (
136
+ <a
137
+ href={buildUrl(`/docs/custom/${nextItem.slug}`)}
138
+ class="group flex flex-col items-end text-right border border-gray-200 rounded-lg p-4 hover:border-gray-300 hover:bg-gray-50 transition-colors w-full sm:w-1/2"
139
+ >
140
+ <span class="text-sm text-gray-500 mb-1">Next</span>
141
+ <span class="font-medium group-hover:text-primary-600 transition-colors">{nextItem.label}</span>
142
+ </a>
143
+ )
144
+ }
145
+ </div>
146
+ </div>
147
+ </div>
148
+ </main>
149
+
150
+ <!-- Right Sidebar TOC -->
151
+ <aside
152
+ class="fixed right-0 top-16 bottom-0 w-96 overflow-y-auto border-l border-gray-100 bg-white p-6 flex-shrink-0 hidden lg:block z-10"
153
+ >
154
+ <div>
155
+ <div>
156
+ {
157
+ ownersList.length > 0 && (
158
+ <OwnersList
159
+ title={`Document Owners (${ownersList.length})`}
160
+ emptyMessage={`This page does not have any documented owners.`}
161
+ owners={ownersList}
162
+ client:load
163
+ />
164
+ )
165
+ }
166
+ </div>
167
+ <h3 class="text-sm text-black font-semibold group-data-[hover]:text-black/80 capitalize flex items-center gap-2">
168
+ <AlignLeftIcon className="w-4 h-4" />
169
+ On this page
170
+ </h3>
171
+ <nav class="space-y-1 text-sm py-4">
172
+ {
173
+ headings.map((heading) => {
174
+ if (heading.depth > 3) {
175
+ return null;
176
+ }
177
+ return (
178
+ <a
179
+ href={`#${heading.slug}`}
180
+ class={`block text-[12px] py-0.5 ${heading.depth === 1 ? 'font-light' : ''} text-gray-400 hover:text-primary-600`}
181
+ style={`padding-left: ${(heading.depth - 1) * 8}px`}
182
+ >
183
+ {heading.text}
184
+ </a>
185
+ );
186
+ })
187
+ }
188
+ </nav>
189
+ </div>
190
+ </aside>
191
+ </div>
192
+ </VerticalSideBarLayout>
193
+
194
+ <style is:global>
195
+ .mermaid svg {
196
+ margin: 1em auto 2em;
197
+ }
198
+
199
+ /* Fix for architecture diagrams */
200
+ .mermaid[data-content*='architecture'] svg {
201
+ max-width: 350px !important;
202
+ margin: 0;
203
+ /* width: 100px !important; */
204
+ }
205
+ </style>
206
+
207
+ <script define:vars={{ props, config }}>
208
+ // Fix to pass information to componets that are client side only
209
+ // and require catalog information
210
+ window.eventcatalog = {};
211
+ // @ts-ignore
212
+
213
+ window.eventcatalog.mermaid = config.mermaid;
214
+ </script>
215
+
216
+ <script>
217
+ // @ts-nocheck
218
+ function setupObserver() {
219
+ try {
220
+ const observerOptions = {
221
+ rootMargin: '0px 0px -40% 0px',
222
+ threshold: 0.1,
223
+ };
224
+
225
+ // Flag to temporarily disable observer after click
226
+ let observerPaused = false;
227
+
228
+ // Function to highlight a TOC item
229
+ function highlightTocItem(id) {
230
+ // Remove active class from all links
231
+ document.querySelectorAll('.active-toc-item').forEach((link) => {
232
+ link.classList.remove('active-toc-item', 'text-purple-600', 'font-light');
233
+ link.classList.add('text-gray-400');
234
+ });
235
+
236
+ // Add active class to current link
237
+ const tocLink = document.querySelector(`nav a[href="#${id}"]`);
238
+ if (tocLink) {
239
+ tocLink.classList.add('active-toc-item', 'text-purple-600', 'font-light');
240
+ tocLink.classList.remove('text-gray-400');
241
+
242
+ // Scroll the highlighted item into view with a small delay to ensure DOM updates first
243
+ setTimeout(() => {
244
+ tocLink.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' });
245
+ }, 10);
246
+ }
247
+ }
248
+
249
+ // Set up the intersection observer for scrolling
250
+ const observer = new IntersectionObserver((entries) => {
251
+ // If observer is paused, don't process entries
252
+ if (observerPaused) return;
253
+
254
+ entries.forEach((entry) => {
255
+ try {
256
+ const id = entry.target.getAttribute('id');
257
+ if (entry.isIntersecting && id) {
258
+ highlightTocItem(id);
259
+ }
260
+ } catch (entryError) {
261
+ console.error('Error processing intersection entry:', entryError);
262
+ }
263
+ });
264
+ }, observerOptions);
265
+
266
+ // Find all headings in the content area within the .prose container to observe
267
+ const prose = document.querySelector('.prose');
268
+ if (!prose) {
269
+ console.warn('No .prose container found for TOC highlighting');
270
+ return;
271
+ }
272
+
273
+ // First try to find headings with IDs
274
+ const headings = prose.querySelectorAll('h1[id], h2[id], h3[id]');
275
+ console.log('Headings with IDs found:', headings.length);
276
+
277
+ if (headings.length > 0) {
278
+ headings.forEach((heading) => {
279
+ observer.observe(heading);
280
+ });
281
+ } else {
282
+ // Fallback: If no headings with IDs found, attach IDs to them
283
+ const allHeadings = prose.querySelectorAll('h1, h2, h3');
284
+ console.log('Total headings found:', allHeadings.length);
285
+
286
+ allHeadings.forEach((heading) => {
287
+ // Only add ID if it doesn't exist
288
+ if (!heading.id) {
289
+ const text = heading.textContent || '';
290
+ const slug = text
291
+ .toLowerCase()
292
+ .replace(/[^\w\s-]/g, '')
293
+ .replace(/\s+/g, '-');
294
+ heading.id = slug;
295
+ }
296
+ observer.observe(heading);
297
+ });
298
+ }
299
+
300
+ // Add click event listeners to all TOC links
301
+ const tocLinks = document.querySelectorAll('nav a[href^="#"]');
302
+ tocLinks.forEach((link) => {
303
+ link.addEventListener('click', (e) => {
304
+ // Get the ID from the href attribute
305
+ const hrefAttr = link.getAttribute('href');
306
+ if (!hrefAttr) return;
307
+
308
+ const id = hrefAttr.substring(1);
309
+
310
+ // Highlight the clicked item
311
+ highlightTocItem(id);
312
+
313
+ // Temporarily pause the observer to prevent immediate highlighting changes
314
+ observerPaused = true;
315
+
316
+ // Resume the observer after a delay (1.5 seconds)
317
+ setTimeout(() => {
318
+ observerPaused = false;
319
+ }, 500);
320
+ });
321
+ });
322
+ } catch (error) {
323
+ console.error('Error setting up TOC highlighting:', error);
324
+ }
325
+ }
326
+
327
+ setupObserver();
328
+
329
+ document.addEventListener('astro:page-load', () => {
330
+ const graphs = document.getElementsByClassName('mermaid');
331
+ if (document.getElementsByClassName('mermaid').length > 0) {
332
+ renderDiagrams(graphs);
333
+ }
334
+ });
335
+
336
+ async function renderDiagrams(graphs: any) {
337
+ const { default: mermaid } = await import('mermaid');
338
+
339
+ if (window.eventcatalog.mermaid) {
340
+ const { icons } = await import('@iconify-json/logos');
341
+ const { iconPacks = [] } = window.eventcatalog.mermaid ?? {};
342
+
343
+ if (iconPacks.length > 0) {
344
+ const iconPacksToRegister = iconPacks.map((name: string) => {
345
+ return {
346
+ name,
347
+ icons,
348
+ };
349
+ });
350
+
351
+ mermaid.registerIconPacks(iconPacksToRegister);
352
+ }
353
+ }
354
+
355
+ mermaid.initialize({
356
+ // fontSize: 2,
357
+ flowchart: {
358
+ curve: 'linear',
359
+ rankSpacing: 0,
360
+ nodeSpacing: 0,
361
+ },
362
+ startOnLoad: false,
363
+ fontFamily: 'var(--sans-font)',
364
+ // @ts-ignore This works, but TS expects a enum for some reason
365
+ theme: 'light',
366
+ architecture: {
367
+ useMaxWidth: true,
368
+ },
369
+ });
370
+
371
+ for (const graph of graphs) {
372
+ const content = graph.getAttribute('data-content');
373
+ if (!content) continue;
374
+ let svg = document.createElement('svg');
375
+ const id = (svg.id = 'mermaid-' + Math.round(Math.random() * 100000));
376
+ graph.appendChild(svg);
377
+ mermaid.render(id, content).then((result) => {
378
+ graph.innerHTML = result.svg;
379
+ });
380
+ }
381
+ }
382
+
383
+ const graphs = document.getElementsByClassName('mermaid');
384
+ if (document.getElementsByClassName('mermaid').length > 0) {
385
+ renderDiagrams(graphs);
386
+ }
387
+
388
+ document.addEventListener('astro:page-load', setupObserver);
389
+ </script>
@@ -0,0 +1,118 @@
1
+ // import { getCollection } from "astro:content";
2
+ import config from '@config';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { getEntry } from 'astro:content';
6
+ import matter from 'gray-matter';
7
+
8
+ type Badge = {
9
+ text: string;
10
+ color: string;
11
+ };
12
+
13
+ type SidebarItem = {
14
+ label: string;
15
+ slug?: string;
16
+ items?: SidebarItem[];
17
+ badge?: Badge;
18
+ collapsed?: boolean;
19
+ };
20
+
21
+ type SideBarConfigurationItem = {
22
+ label: string;
23
+ items?: SidebarItem[];
24
+ slug?: string;
25
+ autogenerated?: {
26
+ directory: string;
27
+ };
28
+ badge?: Badge;
29
+ collapsed?: boolean;
30
+ };
31
+
32
+ const DOCS_DIR = 'docs';
33
+
34
+ /**
35
+ * Processes auto-generated directory and returns navigation items
36
+ */
37
+ const processAutoGeneratedDirectory = async (
38
+ directory: string,
39
+ label: string,
40
+ badge?: Badge,
41
+ collapsed?: boolean
42
+ ): Promise<SidebarItem> => {
43
+ // @ts-ignore
44
+ const files = fs.readdirSync(path.join(process.env.PROJECT_DIR || '', DOCS_DIR, directory));
45
+
46
+ // Convert files to frontmatter
47
+ const docsWithFrontmatter = files.map((file) => {
48
+ // @ts-ignore
49
+ const content = fs.readFileSync(path.join(process.env.PROJECT_DIR || '', DOCS_DIR, directory, file), 'utf8');
50
+ const { data } = matter(content);
51
+ return { ...data, file };
52
+ });
53
+
54
+ // If user defined slug use it, otherwise use the file
55
+ const astroIdsForFiles = docsWithFrontmatter.map(
56
+ (doc: any) => doc.slug || path.join(DOCS_DIR, directory, doc.file).replace('.mdx', '')
57
+ );
58
+
59
+ const entries = await Promise.all(
60
+ astroIdsForFiles.map(async (astroId) => {
61
+ const entry = await getEntry('customPages', astroId);
62
+ return entry;
63
+ })
64
+ );
65
+
66
+ // Filter anything we haven't found
67
+ const filteredEntries = entries.filter((entry) => entry !== undefined);
68
+
69
+ return {
70
+ label,
71
+ items: filteredEntries.map((entry) => ({
72
+ label: entry?.data?.title,
73
+ slug: entry?.data?.slug || entry?.id.replace(DOCS_DIR, ''),
74
+ })),
75
+ };
76
+ };
77
+
78
+ /**
79
+ * Recursively process sidebar items to handle auto-generated content at any nesting level
80
+ */
81
+ const processSidebarItems = async (items: SideBarConfigurationItem[]): Promise<SidebarItem[]> => {
82
+ const processedItems: SidebarItem[] = [];
83
+
84
+ for (const item of items) {
85
+ // If item has autogenerated property, process it
86
+ if (item.autogenerated) {
87
+ const processedItem = await processAutoGeneratedDirectory(
88
+ item.autogenerated.directory,
89
+ item.label,
90
+ item.badge,
91
+ item.collapsed
92
+ );
93
+ processedItems.push(processedItem);
94
+ }
95
+ // If item has nested items, process them recursively
96
+ else if (item.items && item.items.length > 0) {
97
+ const processedNestedItems = await processSidebarItems(item.items as SideBarConfigurationItem[]);
98
+ processedItems.push({
99
+ label: item.label,
100
+ slug: item.slug,
101
+ items: processedNestedItems,
102
+ badge: item.badge,
103
+ collapsed: item.collapsed,
104
+ });
105
+ }
106
+ // Otherwise, it's a regular item
107
+ else {
108
+ processedItems.push(item as SidebarItem);
109
+ }
110
+ }
111
+
112
+ return processedItems;
113
+ };
114
+
115
+ export const getNavigationItems = async (): Promise<SidebarItem[]> => {
116
+ const configuredSidebar = config.customDocs.sidebar;
117
+ return processSidebarItems(configuredSidebar as SideBarConfigurationItem[]);
118
+ };
@@ -5,12 +5,25 @@ interface Props {
5
5
  description?: string;
6
6
  }
7
7
 
8
- import { BookOpenText, Workflow, TableProperties, House, BookUser, MessageSquare, BotMessageSquare, Users } from 'lucide-react';
8
+ import {
9
+ BookOpenText,
10
+ BookText,
11
+ Workflow,
12
+ TableProperties,
13
+ House,
14
+ BookUser,
15
+ MessageSquare,
16
+ BotMessageSquare,
17
+ Users,
18
+ Star,
19
+ BadgeCheck,
20
+ Sparkles,
21
+ } from 'lucide-react';
9
22
  import Header from '../components/Header.astro';
10
23
  import SEO from '../components/Seo.astro';
11
24
  import SideNav from '../components/SideNav/SideNav.astro';
12
25
  import config from '@config';
13
-
26
+ import { getCollection } from 'astro:content';
14
27
  import '@fontsource/inter';
15
28
  import '@fontsource/inter/400.css'; // Specify weight
16
29
  import '@fontsource/inter/700.css'; // Specify weight
@@ -24,19 +37,24 @@ import { isCollectionVisibleInCatalog } from '@eventcatalog';
24
37
  import { buildUrl } from '@utils/url-builder';
25
38
  import { getQueries } from '@utils/queries';
26
39
  import { hasLandingPageForDocs } from '@utils/pages';
27
-
40
+ import { isEventCatalogProEnabled } from '@utils/feature';
28
41
  const events = await getEvents({ getAllVersions: false });
29
42
  const commands = await getCommands({ getAllVersions: false });
30
43
  const queries = await getQueries({ getAllVersions: false });
31
44
  const services = await getServices({ getAllVersions: false });
32
45
  const domains = await getDomains({ getAllVersions: false });
33
46
  const flows = await getFlows({ getAllVersions: false });
47
+ const customDocs = await getCollection('customPages');
34
48
 
35
49
  const currentPath = Astro.url.pathname;
36
50
 
37
51
  const catalogHasDefaultLandingPageForDocs = await hasLandingPageForDocs();
38
52
 
39
53
  const getDefaultUrl = (route: string, defaultValue: string) => {
54
+ if (route === 'docs/custom') {
55
+ return customDocs.length > 0 ? buildUrl(`/${route}/${customDocs[0].id.replace('docs', '')}`) : buildUrl(defaultValue);
56
+ }
57
+
40
58
  const collections = [
41
59
  { data: domains, key: 'domains' },
42
60
  { data: services, key: 'services' },
@@ -68,12 +86,22 @@ const navigationItems = [
68
86
  },
69
87
  {
70
88
  id: '/docs',
71
- label: 'Documentation',
89
+ label: 'Architecture Documentation',
72
90
  icon: BookOpenText,
73
91
  href: catalogHasDefaultLandingPageForDocs ? buildUrl('/docs') : getDefaultUrl('docs', '/docs'),
74
- current: currentPath.includes('/docs') || currentPath.includes('/architecture/docs/'),
92
+ current:
93
+ (currentPath.includes('/docs') && !currentPath.includes('/docs/custom')) || currentPath.includes('/architecture/docs/'),
75
94
  sidebar: true,
76
95
  },
96
+ {
97
+ id: '/docs/custom',
98
+ label: 'Documentation',
99
+ icon: BookText,
100
+ href: getDefaultUrl('docs/custom', '/docs/custom'),
101
+ current: currentPath.includes('/docs/custom'),
102
+ sidebar: false,
103
+ hidden: !isEventCatalogProEnabled(),
104
+ },
77
105
  {
78
106
  id: '/visualiser',
79
107
  label: 'Visualiser',
@@ -131,7 +159,7 @@ const canPageBeEmbedded = process.env.ENABLE_EMBED === 'true';
131
159
  <html lang="en">
132
160
  <head>
133
161
  <SEO title={`EventCatalog | ${title}`} description={description} ogTitle={title} />
134
- <style>
162
+ <style is:global>
135
163
  .sidebar-transition {
136
164
  transition-property: width, transform;
137
165
  transition-duration: 300ms;
@@ -154,7 +182,7 @@ const canPageBeEmbedded = process.env.ENABLE_EMBED === 'true';
154
182
  <div
155
183
  class="fixed flex flex-col items-center w-16 h-screen py-4 bg-white bg-gradient-to-b from-white to-gray-100 border-r border-gray-200 z-20 shadow-md justify-between"
156
184
  >
157
- <nav class="flex flex-col h-[84vh] justify-between">
185
+ <nav class="flex flex-col h-[calc(100vh-70px)] justify-between">
158
186
  <div class="flex flex-col items-center flex-1 space-y-8">
159
187
  {
160
188
  navigationItems
@@ -178,6 +206,25 @@ const canPageBeEmbedded = process.env.ENABLE_EMBED === 'true';
178
206
  })
179
207
  }
180
208
  </div>
209
+
210
+ {/* Pro link at the bottom */}
211
+ <div class="mb-4">
212
+ <a
213
+ id="/pro"
214
+ data-role="nav-item"
215
+ href={buildUrl('/pro')}
216
+ class={`p-1.5 inline-block transition-colors duration-200 rounded-lg ${currentPath.includes('/pro') ? 'text-white bg-gradient-to-b from-purple-500 to-purple-700' : 'bg-gradient-to-r from-purple-100 to-indigo-100 hover:from-purple-500 hover:to-purple-700 hover:text-white text-purple-700'}`}
217
+ >
218
+ <div class="has-tooltip">
219
+ <span
220
+ class="tooltip rounded shadow-lg p-1 text-xs bg-gradient-to-l from-purple-500 to-purple-700 text-white ml-10"
221
+ >
222
+ EventCatalog Pro
223
+ </span>
224
+ <Sparkles className="h-6 w-6" />
225
+ </div>
226
+ </a>
227
+ </div>
181
228
  </nav>
182
229
  </div>
183
230
 
@@ -193,7 +240,7 @@ const canPageBeEmbedded = process.env.ENABLE_EMBED === 'true';
193
240
  <slot />
194
241
  </main>
195
242
 
196
- <!-- Creae a overlay that tells people to purchase backstage plugin if they want to embed the page -->
243
+ <!-- Create a overlay that tells people to purchase backstage plugin if they want to embed the page -->
197
244
  <div class="absolute inset-0 bg-black items-center justify-center z-50 hidden" id="embed-overlay">
198
245
  <div class="text-white text-center space-y-4">
199
246
  <div>
@@ -259,7 +306,7 @@ const canPageBeEmbedded = process.env.ENABLE_EMBED === 'true';
259
306
  const currentPath = window.location.href;
260
307
 
261
308
  // Hide the sidebar if the current navigation item is not a sidebar item
262
- if (!currentNavigationItem.sidebar) {
309
+ if (!currentNavigationItem?.sidebar) {
263
310
  sidebar.style.display = 'none';
264
311
  content.style.width = '100%';
265
312
  content.classList.add('ml-16');
@@ -277,7 +324,7 @@ const canPageBeEmbedded = process.env.ENABLE_EMBED === 'true';
277
324
 
278
325
  const navItem = navigationItems.find((navItem) => navItem.id === id);
279
326
 
280
- if (!navItem.sidebar || !currentPath.includes(navItem.id)) {
327
+ if (!navItem.sidebar || !currentPath.includes(navItem.id) || currentPath.includes('/docs/custom')) {
281
328
  window.location.href = navItem.href;
282
329
  return;
283
330
  }