@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.
- package/README.md +1 -1
- package/dist/analytics/analytics.cjs +1 -1
- package/dist/analytics/analytics.js +2 -2
- package/dist/analytics/log-build.cjs +9 -3
- package/dist/analytics/log-build.d.cts +4 -1
- package/dist/analytics/log-build.d.ts +4 -1
- package/dist/analytics/log-build.js +3 -3
- package/dist/{chunk-NALVGTIE.js → chunk-GHBJ372G.js} +1 -1
- package/dist/{chunk-XMDPVKIJ.js → chunk-NJGR7XUU.js} +44 -1
- package/dist/{chunk-D4IJRFPJ.js → chunk-QR5UTYCE.js} +9 -3
- package/dist/{chunk-XJBJFIN7.js → chunk-YUINCBLT.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +74 -14
- package/dist/eventcatalog.config.d.cts +28 -0
- package/dist/eventcatalog.config.d.ts +28 -0
- package/dist/eventcatalog.js +29 -16
- package/dist/features.cjs +46 -2
- package/dist/features.d.cts +2 -1
- package/dist/features.d.ts +2 -1
- package/dist/features.js +5 -3
- package/eventcatalog/public/images/custom-docs-placeholder.png +0 -0
- package/eventcatalog/src/components/MDX/NodeGraph/Nodes/Custom.tsx +0 -2
- package/eventcatalog/src/components/MDX/Steps/Step.astro +1 -1
- package/eventcatalog/src/components/MDX/Steps/Steps.astro +15 -0
- package/eventcatalog/src/components/SideNav/CustomDocsNav/CustomDocsNavWrapper.tsx +11 -0
- package/eventcatalog/src/components/SideNav/CustomDocsNav/components/NestedItem.tsx +183 -0
- package/eventcatalog/src/components/SideNav/CustomDocsNav/components/NoResultsFound.tsx +21 -0
- package/eventcatalog/src/components/SideNav/CustomDocsNav/index.tsx +250 -0
- package/eventcatalog/src/components/SideNav/CustomDocsNav/types.ts +29 -0
- package/eventcatalog/src/components/SideNav/CustomDocsNav.astro +9 -0
- package/eventcatalog/src/content.config.ts +15 -24
- package/eventcatalog/src/enterprise/collections/custom-pages.ts +19 -0
- package/eventcatalog/src/enterprise/custom-documentation/collection.ts +16 -0
- package/eventcatalog/src/enterprise/custom-documentation/components/CustomDocsNav/CustomDocsNavWrapper.tsx +11 -0
- package/eventcatalog/src/enterprise/custom-documentation/components/CustomDocsNav/components/NestedItem.tsx +183 -0
- package/eventcatalog/src/enterprise/custom-documentation/components/CustomDocsNav/components/NoResultsFound.tsx +21 -0
- package/eventcatalog/src/enterprise/custom-documentation/components/CustomDocsNav/index.tsx +250 -0
- package/eventcatalog/src/enterprise/custom-documentation/components/CustomDocsNav/types.ts +29 -0
- package/eventcatalog/src/enterprise/custom-documentation/pages/index.astro +389 -0
- package/eventcatalog/src/enterprise/custom-documentation/utils/custom-docs.ts +118 -0
- package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +57 -10
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +117 -3
- package/eventcatalog/src/pages/docs/custom/[...path]/index.astro +34 -0
- package/eventcatalog/src/pages/pro/index.astro +272 -0
- package/eventcatalog/src/shared-collections.ts +25 -0
- package/eventcatalog/src/utils/eventcatalog-config/catalog.ts +12 -1
- package/eventcatalog/src/utils/feature.ts +5 -0
- 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 {
|
|
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:
|
|
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-[
|
|
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
|
-
<!--
|
|
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
|
|
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
|
}
|