@eventcatalog/core 5.0.0-beta.2 → 5.0.0-beta.20
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/LICENSE +234 -134
- package/README.md +5 -5
- package/dist/analytics/analytics.cjs +18 -3
- package/dist/analytics/analytics.d.cts +1 -1
- package/dist/analytics/analytics.d.ts +1 -1
- package/dist/analytics/analytics.js +3 -2
- package/dist/analytics/log-build.cjs +71 -35
- package/dist/analytics/log-build.js +5 -4
- package/dist/analytics/telemetry.cjs +42 -0
- package/dist/analytics/telemetry.d.cts +20 -0
- package/dist/analytics/telemetry.d.ts +20 -0
- package/dist/analytics/telemetry.js +8 -0
- package/dist/catalog-runtime.cjs +1 -1
- package/dist/{chunk-HGRFXTFC.js → chunk-3VQSVQTU.js} +1 -1
- package/dist/{chunk-KIDISU6X.js → chunk-5ZUN2TXB.js} +56 -33
- package/dist/{chunk-TQLRG35T.js → chunk-AWOO63ME.js} +71 -45
- package/dist/chunk-CPW6G62V.js +17 -0
- package/dist/{chunk-4J2ZM2HX.js → chunk-F236OJZD.js} +1 -1
- package/dist/{chunk-XARPIXWF.js → chunk-FWH5T6U4.js} +9 -3
- package/dist/{chunk-62EV4IJA.js → chunk-R2X4ZEF6.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +214 -129
- package/dist/eventcatalog.config.d.cts +59 -1
- package/dist/eventcatalog.config.d.ts +59 -1
- package/dist/eventcatalog.js +39 -15
- package/dist/federation/federate.js +2 -2
- package/dist/generate.cjs +1 -1
- package/dist/generate.js +3 -3
- package/dist/runtime-dependencies/@eventcatalog__visualiser__studio-fonts.css.mjs +3 -0
- package/dist/runtime-dependencies/@eventcatalog__visualiser__studio.css.mjs +3 -0
- package/dist/runtime-dependencies/@eventcatalog__visualiser__studio.mjs +3 -0
- package/dist/runtime-dependencies/@hocuspocus__provider.mjs +3 -0
- package/dist/runtime-dependencies/@hocuspocus__server.mjs +3 -0
- package/dist/runtime-dependencies/@mcp-b__webmcp-local-relay.mjs +3 -0
- package/dist/runtime-dependencies/@mcp-b__webmcp-polyfill.mjs +3 -0
- package/dist/runtime-dependencies/htmlparser2.mjs +3 -0
- package/dist/runtime-dependencies/manifest.json +13 -1
- package/dist/runtime-dependencies/react-hotkeys-hook.mjs +3 -0
- package/dist/runtime-dependencies/react-syntax-highlighter__dist__cjs__create-element.js.mjs +3 -0
- package/dist/runtime-dependencies/y-protocols.mjs +3 -0
- package/dist/runtime-dependencies/y-protocols__awareness.mjs +3 -0
- package/dist/runtime-dependencies/yjs.mjs +3 -0
- package/dist/utils/cli-logger.cjs +1 -1
- package/dist/utils/cli-logger.js +2 -2
- package/dist/utils/license-status.cjs +71 -45
- package/dist/utils/license-status.d.cts +14 -5
- package/dist/utils/license-status.d.ts +14 -5
- package/dist/utils/license-status.js +1 -1
- package/eventcatalog/astro.config.mjs +23 -1
- package/eventcatalog/integrations/skip-startup-reload.mjs +31 -0
- package/eventcatalog/integrations/stable-dependency-order.mjs +29 -0
- package/eventcatalog/integrations/storage.mjs +20 -0
- package/eventcatalog/integrations/studio-server.mjs +32 -0
- package/eventcatalog/src/components/CatalogGraph/CatalogForceGraph.tsx +39 -0
- package/eventcatalog/src/components/ChatPanel/ChatPanel.tsx +2 -0
- package/eventcatalog/src/components/Grids/utils.tsx +0 -23
- package/eventcatalog/src/components/Header.astro +3 -3
- package/eventcatalog/src/components/MDX/CodeGroup/CodeGroup.tsx +6 -1
- package/eventcatalog/src/components/MDX/NodeGraph/AstroNodeGraph.tsx +42 -3
- package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.astro +5 -1
- package/eventcatalog/src/components/NavigationProgress.astro +7 -0
- package/eventcatalog/src/components/SchemaExplorer/ApiContentViewer.tsx +2 -0
- package/eventcatalog/src/components/SchemaExplorer/ExamplesViewer.tsx +2 -0
- package/eventcatalog/src/components/SchemaExplorer/SchemaCodeModal.tsx +2 -0
- package/eventcatalog/src/components/SchemaExplorer/SchemaContentViewer.tsx +2 -0
- package/eventcatalog/src/components/SchemaExplorer/SchemaDetailsPanel.tsx +12 -4
- package/eventcatalog/src/components/Search/SearchDataLoader.astro +16 -28
- package/eventcatalog/src/components/Search/SearchModal.tsx +2 -10
- package/eventcatalog/src/components/Search/search-utils.ts +26 -0
- package/eventcatalog/src/components/Seo.astro +5 -1
- package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +53 -83
- package/eventcatalog/src/components/SideNav/NestedSideBar/storage.ts +2 -0
- package/eventcatalog/src/components/SideNav/NestedSideBar/utils.ts +131 -0
- package/eventcatalog/src/components/SideNav/SideNav.astro +9 -1
- package/eventcatalog/src/components/Tables/Discover/DiscoverTable.tsx +4 -62
- package/eventcatalog/src/components/Tables/Discover/columns.tsx +17 -150
- package/eventcatalog/src/components/Tables/Resources/ResourcesTable.tsx +425 -0
- package/eventcatalog/src/components/Tables/RowActionsMenu.tsx +157 -0
- package/eventcatalog/src/components/Tables/TablePagination.tsx +82 -0
- package/eventcatalog/src/features/ai/chat-api.ts +3 -3
- package/eventcatalog/src/features/auth/error.astro +4 -2
- package/eventcatalog/src/features/auth/login.astro +2 -1
- package/eventcatalog/src/features/auth/middleware/middleware-auth.ts +10 -0
- package/eventcatalog/src/features/auth/unauthorized.astro +1 -1
- package/eventcatalog/src/features/fields/pages/fields.astro +1 -1
- package/eventcatalog/src/features/integrations/eventcatalog-features.ts +34 -1
- package/eventcatalog/src/features/mcp/apps/architecture-diagram/node-chat.tsx +6 -45
- package/eventcatalog/src/features/mcp/apps/canvas/shared.ts +47 -0
- package/eventcatalog/src/features/mcp/apps/canvas/styles.css +24 -0
- package/eventcatalog/src/features/mcp/apps/canvas/view.tsx +278 -0
- package/eventcatalog/src/features/mcp/apps/generated/canvas.html +272 -0
- package/eventcatalog/src/features/mcp/apps/generated/viewer.html +77 -76
- package/eventcatalog/src/features/mcp/apps/shared/app-view.tsx +2 -1
- package/eventcatalog/src/features/mcp/mcp-apps.ts +6 -3
- package/eventcatalog/src/features/mcp/mcp-server.ts +36 -5
- package/eventcatalog/src/features/server/start.ts +25 -0
- package/eventcatalog/src/features/storage/database.ts +88 -0
- package/eventcatalog/src/features/storage/migrations.ts +85 -0
- package/eventcatalog/src/features/storage/sql/database.sql +5 -0
- package/eventcatalog/src/features/storage/sql/migrations/0001-studio-canvases.sql +12 -0
- package/eventcatalog/src/features/storage/sql/migrations.sql +15 -0
- package/eventcatalog/src/features/storage/sql.ts +28 -0
- package/eventcatalog/src/features/studio/agent-choreography.ts +384 -0
- package/eventcatalog/src/features/studio/agents.ts +28 -0
- package/eventcatalog/src/features/studio/api/canvases-api.ts +219 -0
- package/eventcatalog/src/features/studio/api/canvases.ts +25 -0
- package/eventcatalog/src/features/studio/api/client.ts +40 -0
- package/eventcatalog/src/features/studio/canvas-actions.ts +540 -0
- package/eventcatalog/src/features/studio/canvas-preview.ts +52 -0
- package/eventcatalog/src/features/studio/catalog-resources.ts +155 -0
- package/eventcatalog/src/features/studio/catalog.ts +173 -0
- package/eventcatalog/src/features/studio/components/CanvasHeader.tsx +518 -0
- package/eventcatalog/src/features/studio/components/CanvasStatusMenu.tsx +118 -0
- package/eventcatalog/src/features/studio/components/ConnectAgentDialog.tsx +434 -0
- package/eventcatalog/src/features/studio/components/ConnectionsChooser.tsx +143 -0
- package/eventcatalog/src/features/studio/components/DeleteCanvasDialog.tsx +65 -0
- package/eventcatalog/src/features/studio/components/Dialog.tsx +60 -0
- package/eventcatalog/src/features/studio/components/JoinForm.tsx +109 -0
- package/eventcatalog/src/features/studio/components/LeftPanel.tsx +203 -0
- package/eventcatalog/src/features/studio/components/NodeDetails.tsx +183 -0
- package/eventcatalog/src/features/studio/components/OpenInStudioDialog.tsx +79 -0
- package/eventcatalog/src/features/studio/components/Presence.tsx +293 -0
- package/eventcatalog/src/features/studio/components/ShareDialog.tsx +396 -0
- package/eventcatalog/src/features/studio/components/ShortcutsDialog.tsx +41 -0
- package/eventcatalog/src/features/studio/components/StudioDesigner.tsx +2091 -0
- package/eventcatalog/src/features/studio/components/StudioNotice.astro +24 -0
- package/eventcatalog/src/features/studio/document-name.ts +7 -0
- package/eventcatalog/src/features/studio/from-visualiser.ts +138 -0
- package/eventcatalog/src/features/studio/hooks/presence-sender.ts +63 -0
- package/eventcatalog/src/features/studio/hooks/presence-store.ts +119 -0
- package/eventcatalog/src/features/studio/hooks/use-canvas-webmcp.ts +353 -0
- package/eventcatalog/src/features/studio/hooks/use-comments.ts +54 -0
- package/eventcatalog/src/features/studio/hooks/use-follow-camera.ts +109 -0
- package/eventcatalog/src/features/studio/hooks/use-studio-flow.ts +782 -0
- package/eventcatalog/src/features/studio/identity.ts +59 -0
- package/eventcatalog/src/features/studio/limits.ts +2 -0
- package/eventcatalog/src/features/studio/pages/[id].astro +69 -0
- package/eventcatalog/src/features/studio/pages/index.astro +784 -0
- package/eventcatalog/src/features/studio/pages/webmcp-relay.ts +27 -0
- package/eventcatalog/src/features/studio/server/canvas-mcp.ts +495 -0
- package/eventcatalog/src/features/studio/server/runtime.ts +498 -0
- package/eventcatalog/src/features/studio/server/sign-in.ts +18 -0
- package/eventcatalog/src/features/studio/server/sql/storage.sql +18 -0
- package/eventcatalog/src/features/studio/server/storage.ts +90 -0
- package/eventcatalog/src/features/studio/server/tool-icons.ts +30 -0
- package/eventcatalog/src/features/studio/tool-sync.ts +75 -0
- package/eventcatalog/src/layouts/BaseLayout.astro +4 -4
- package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +13 -7
- package/eventcatalog/src/pages/_index.astro +1 -1
- package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.spec.ts +52 -63
- package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.ts +34 -46
- package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/index.astro +11 -29
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_ResourcesPage.astro +79 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_index.data.spec.ts +32 -14
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_index.data.ts +63 -58
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/index.astro +2 -62
- package/eventcatalog/src/pages/docs/[type]/[id]/index.astro +1 -1
- package/eventcatalog/src/pages/docs/services/[id]/[version]/resources/index.astro +13 -0
- package/eventcatalog/src/pages/index.astro +1 -1
- package/eventcatalog/src/pages/schemas/explorer/index.astro +1 -1
- package/eventcatalog/src/pages/visualiser/[type]/[id]/index.astro +1 -1
- package/eventcatalog/src/stores/sidebar-store/builders/domain.ts +24 -42
- package/eventcatalog/src/stores/sidebar-store/builders/flow.ts +2 -0
- package/eventcatalog/src/stores/sidebar-store/builders/message.ts +2 -0
- package/eventcatalog/src/stores/sidebar-store/builders/service.ts +23 -23
- package/eventcatalog/src/stores/sidebar-store/builders/shared.ts +13 -0
- package/eventcatalog/src/stores/sidebar-store/builders/system.ts +18 -27
- package/eventcatalog/src/stores/sidebar-store/state.ts +5 -0
- package/eventcatalog/src/styles/tailwind.css +2 -2
- package/eventcatalog/src/types/react-syntax-highlighter.d.ts +10 -0
- package/eventcatalog/src/utils/collections/channels.ts +56 -1
- package/eventcatalog/src/utils/collections/flows.ts +50 -1
- package/eventcatalog/src/utils/collections/message-connections.ts +60 -0
- package/eventcatalog/src/utils/collections/message-triggers.ts +2 -1
- package/eventcatalog/src/utils/collections/resource-owners.ts +193 -0
- package/eventcatalog/src/utils/collections/resources.ts +310 -0
- package/eventcatalog/src/utils/collections/util.ts +31 -0
- package/eventcatalog/src/utils/feature.ts +8 -0
- package/eventcatalog/src/utils/git.ts +11 -5
- package/eventcatalog/src/utils/link-validation.ts +25 -19
- package/eventcatalog/src/utils/node-graphs/domain-entity-map.ts +3 -1
- package/eventcatalog/src/utils/node-graphs/domain-levels-node-graph.ts +3 -3
- package/eventcatalog/src/utils/node-graphs/export-mermaid.ts +0 -2
- package/eventcatalog/src/utils/node-graphs/layout-node-graph.ts +20 -1
- package/eventcatalog/src/utils/node-graphs/node-resource.ts +67 -0
- package/eventcatalog/src/utils/node-graphs/systems-node-graph.ts +1 -1
- package/eventcatalog/src/utils/page-title.ts +15 -0
- package/eventcatalog/src/utils/syntax-highlighter-renderer.ts +64 -0
- package/eventcatalog/src/utils/trial.ts +1 -1
- package/package.json +22 -16
- package/dist/runtime-dependencies/shelljs.mjs +0 -3
- package/eventcatalog/public/studio-bg.png +0 -0
- package/eventcatalog/src/components/Grids/DomainGrid.tsx +0 -838
- package/eventcatalog/src/components/Grids/MessageGrid.tsx +0 -344
- package/eventcatalog/src/components/Grids/SystemGrid.tsx +0 -185
- package/eventcatalog/src/components/Grids/components.tsx +0 -170
- package/eventcatalog/src/components/Grids/message-link.ts +0 -38
- package/eventcatalog/src/components/Studio/StudioPageModal.tsx +0 -150
- package/eventcatalog/src/components/Tables/SystemResources/SystemResourcesTable.tsx +0 -294
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_resources.spec.ts +0 -65
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_resources.ts +0 -46
- package/eventcatalog/src/pages/studio.astro +0 -316
- package/dist/{chunk-AYNZXUB5.js → chunk-KHACCTW2.js} +3 -3
|
@@ -31,11 +31,14 @@ import { pageDataLoader } from '@utils/page-loaders/page-data-loader';
|
|
|
31
31
|
import { getNodesAndEdges as getNodesAndEdgesForContainer } from '@utils/node-graphs/container-node-graph';
|
|
32
32
|
import { getNodesAndEdges as getNodesAndEdgesForChannel } from '@utils/node-graphs/channel-node-graph';
|
|
33
33
|
import config from '@config';
|
|
34
|
-
import { isEventCatalogChatVisible, isDevMode } from '@utils/feature';
|
|
34
|
+
import { isEventCatalogChatVisible, isDevMode, isCanvasEnabled } from '@utils/feature';
|
|
35
|
+
import { CANVASES_API_PATH } from '@features/studio/api/canvases-api';
|
|
35
36
|
import { loadSavedLayout, applyLayoutToNodes, buildResourceKey } from '@utils/node-graphs/layout-persistence';
|
|
36
37
|
import { compactVisualiserGraph } from '@utils/node-graphs/compact-visualiser-graph';
|
|
37
38
|
|
|
38
39
|
const isChatEnabled = isEventCatalogChatVisible();
|
|
40
|
+
// With Studio on, the menu can start a canvas from the diagram
|
|
41
|
+
const studioApiUrl = isCanvasEnabled() ? buildUrl(CANVASES_API_PATH, true) : undefined;
|
|
39
42
|
|
|
40
43
|
interface Props {
|
|
41
44
|
id: string;
|
|
@@ -257,6 +260,7 @@ if (overview && levelsOverview) {
|
|
|
257
260
|
hiddenMessagesGraph={hiddenMessagesGraph}
|
|
258
261
|
preferenceScope={collection}
|
|
259
262
|
swimlanes={lanes}
|
|
263
|
+
studioApiUrl={studioApiUrl}
|
|
260
264
|
/>
|
|
261
265
|
</div>
|
|
262
266
|
|
|
@@ -22,12 +22,19 @@
|
|
|
22
22
|
opacity: 1;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/* On every page, hidden until a navigation takes a while: paused until then (a running animation costs a style
|
|
26
|
+
recalc and paint on every frame, even when it can't be seen) */
|
|
25
27
|
.navigation-progress__bar {
|
|
26
28
|
width: 35%;
|
|
27
29
|
height: 100%;
|
|
28
30
|
background: rgb(var(--ec-accent));
|
|
29
31
|
box-shadow: 0 0 10px rgb(var(--ec-accent) / 0.55);
|
|
30
32
|
animation: navigation-progress 1.1s ease-in-out infinite;
|
|
33
|
+
animation-play-state: paused;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
#navigation-progress[data-visible='true'] .navigation-progress__bar {
|
|
37
|
+
animation-play-state: running;
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
:global([data-navigation-pending='true']) {
|
|
@@ -4,6 +4,7 @@ import { oneDark } from 'react-syntax-highlighter/dist/cjs/styles/prism';
|
|
|
4
4
|
import { oneLight } from 'react-syntax-highlighter/dist/cjs/styles/prism';
|
|
5
5
|
import { getLanguageForHighlight, getSchemaTypeLabel } from './utils';
|
|
6
6
|
import { useDarkMode } from './useDarkMode';
|
|
7
|
+
import { renderHighlightedRows } from '@utils/syntax-highlighter-renderer';
|
|
7
8
|
import type { SchemaItem } from './types';
|
|
8
9
|
|
|
9
10
|
interface ApiContentViewerProps {
|
|
@@ -135,6 +136,7 @@ export default function ApiContentViewer({ message, onCopy, copiedId }: ApiConte
|
|
|
135
136
|
|
|
136
137
|
<div className="border-t" style={{ borderColor: dividerColor }}>
|
|
137
138
|
<SyntaxHighlighter
|
|
139
|
+
renderer={renderHighlightedRows}
|
|
138
140
|
language={getLanguageForHighlight(message.schemaExtension)}
|
|
139
141
|
style={isDarkMode ? oneDark : oneLight}
|
|
140
142
|
customStyle={{
|
|
@@ -13,6 +13,7 @@ import { MarkdownCodeGroup } from '../MDX/CodeGroup/MarkdownCodeGroup';
|
|
|
13
13
|
import { MarkdownColumns, MarkdownColumn } from '../MDX/Columns/MarkdownColumns';
|
|
14
14
|
import { remarkMarkdownColumns } from '../../remark-plugins/markdown-columns';
|
|
15
15
|
import { getLanguageForHighlight } from './utils';
|
|
16
|
+
import { renderHighlightedRows } from '@utils/syntax-highlighter-renderer';
|
|
16
17
|
|
|
17
18
|
interface ExamplesViewerProps {
|
|
18
19
|
examples: MessageExample[];
|
|
@@ -40,6 +41,7 @@ function CodeBlock({ language, code }: { language?: string; code: string }) {
|
|
|
40
41
|
const content = code.replace(/\s+$/, '');
|
|
41
42
|
const highlighter = (
|
|
42
43
|
<SyntaxHighlighter
|
|
44
|
+
renderer={renderHighlightedRows}
|
|
43
45
|
language={language || 'text'}
|
|
44
46
|
style={isDarkMode ? oneDark : oneLight}
|
|
45
47
|
customStyle={{
|
|
@@ -6,6 +6,7 @@ import { oneLight } from 'react-syntax-highlighter/dist/cjs/styles/prism';
|
|
|
6
6
|
import { getLanguageForHighlight } from './utils';
|
|
7
7
|
import type { SchemaItem } from './types';
|
|
8
8
|
import { useDarkMode } from './useDarkMode';
|
|
9
|
+
import { renderHighlightedRows } from '@utils/syntax-highlighter-renderer';
|
|
9
10
|
|
|
10
11
|
interface SchemaCodeModalProps {
|
|
11
12
|
isOpen: boolean;
|
|
@@ -60,6 +61,7 @@ export default function SchemaCodeModal({ isOpen, onOpenChange, message, onCopy,
|
|
|
60
61
|
{/* Content */}
|
|
61
62
|
<div className="flex-1 overflow-auto p-6">
|
|
62
63
|
<SyntaxHighlighter
|
|
64
|
+
renderer={renderHighlightedRows}
|
|
63
65
|
language={getLanguageForHighlight(message.schemaExtension)}
|
|
64
66
|
style={isDarkMode ? oneDark : oneLight}
|
|
65
67
|
customStyle={{
|
|
@@ -9,6 +9,7 @@ import ProtobufSchemaViewer from './ProtobufSchemaViewer';
|
|
|
9
9
|
import { getLanguageForHighlight, SCHEMA_CODE_STYLE } from './utils';
|
|
10
10
|
import type { SchemaItem } from './types';
|
|
11
11
|
import { useDarkMode } from './useDarkMode';
|
|
12
|
+
import { renderHighlightedRows } from '@utils/syntax-highlighter-renderer';
|
|
12
13
|
|
|
13
14
|
interface SchemaContentViewerProps {
|
|
14
15
|
message: SchemaItem;
|
|
@@ -112,6 +113,7 @@ export default function SchemaContentViewer({
|
|
|
112
113
|
|
|
113
114
|
<div className="min-h-0 flex-1 overflow-auto">
|
|
114
115
|
<SyntaxHighlighter
|
|
116
|
+
renderer={renderHighlightedRows}
|
|
115
117
|
language={getLanguageForHighlight(message.schemaExtension)}
|
|
116
118
|
style={isDarkMode ? oneDark : oneLight}
|
|
117
119
|
customStyle={{ ...SCHEMA_CODE_STYLE, height: '100%' }}
|
|
@@ -384,6 +384,9 @@ export default function SchemaDetailsPanel({
|
|
|
384
384
|
[diffToVersion, uniqueAvailableVersions]
|
|
385
385
|
);
|
|
386
386
|
const comparing = (activeTab === 'versions' && hasMultipleVersions) || isDiffModalOpen;
|
|
387
|
+
const contentReady = !content.loading && !content.error;
|
|
388
|
+
// Server-rendered examples stay mounted and are only shown on the Examples tab (see the tab content below).
|
|
389
|
+
const showRenderedExamples = !!renderedExamples && activeTab === 'examples' && contentReady;
|
|
387
390
|
|
|
388
391
|
const compareWithCurrent = (version: string) => {
|
|
389
392
|
setDiffFromVersion(version);
|
|
@@ -705,11 +708,16 @@ export default function SchemaDetailsPanel({
|
|
|
705
708
|
|
|
706
709
|
{/* Tab content */}
|
|
707
710
|
<div className="flex-1 min-h-0 overflow-hidden p-6">
|
|
708
|
-
{
|
|
711
|
+
{/* Always render the examples slot and hide it on other tabs. Astro moves a slot missing from the
|
|
712
|
+
server HTML into a <template>, where its island scripts never run and no island on the page hydrates. */}
|
|
713
|
+
{renderedExamples && (
|
|
714
|
+
<div className="h-full overflow-auto pr-1" hidden={!showRenderedExamples}>
|
|
715
|
+
{renderedExamples}
|
|
716
|
+
</div>
|
|
717
|
+
)}
|
|
718
|
+
{!contentReady ? (
|
|
709
719
|
<SchemaLoadingState loading={content.loading} error={content.error} retry={content.retry} />
|
|
710
|
-
) : activeTab === 'examples' &&
|
|
711
|
-
<div className="h-full overflow-auto pr-1">{renderedExamples}</div>
|
|
712
|
-
) : activeTab === 'examples' && examples.length > 0 ? (
|
|
720
|
+
) : showRenderedExamples ? null : activeTab === 'examples' && examples.length > 0 ? (
|
|
713
721
|
<ExamplesViewer examples={examples} />
|
|
714
722
|
) : activeTab === 'metadata' ? (
|
|
715
723
|
<SchemaMetadata message={message} owners={showOwners ? owners : []} />
|
|
@@ -16,34 +16,22 @@
|
|
|
16
16
|
import { setSidebarData, sidebarStore } from '@stores/sidebar-store';
|
|
17
17
|
import { buildUrl } from '@utils/url-builder';
|
|
18
18
|
|
|
19
|
-
// Only fetch if we haven't already loaded the data
|
|
19
|
+
// Only fetch if we haven't already loaded the data. Fetched straight away rather than when
|
|
20
|
+
// the browser is idle: the sidebar's first paint comes from the server (see SideNav.astro),
|
|
21
|
+
// but drilling down and search wait on this data
|
|
20
22
|
if (!sidebarStore.get()) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
.then((data) => {
|
|
35
|
-
setSidebarData(data);
|
|
36
|
-
})
|
|
37
|
-
.catch((error) => {
|
|
38
|
-
console.error('Error loading sidebar data:', error);
|
|
39
|
-
});
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
// Defer non-critical sidebar payload fetch until browser is idle
|
|
43
|
-
if (typeof window.requestIdleCallback === 'function') {
|
|
44
|
-
window.requestIdleCallback(loadSidebarData, { timeout: 1200 });
|
|
45
|
-
} else {
|
|
46
|
-
setTimeout(loadSidebarData, 300);
|
|
47
|
-
}
|
|
23
|
+
fetch(buildUrl('/api/sidebar-data.json', true))
|
|
24
|
+
.then((response) => {
|
|
25
|
+
if (!response.ok) {
|
|
26
|
+
throw new Error(`Failed to fetch sidebar data: ${response.status}`);
|
|
27
|
+
}
|
|
28
|
+
return response.json();
|
|
29
|
+
})
|
|
30
|
+
.then((data) => {
|
|
31
|
+
setSidebarData(data);
|
|
32
|
+
})
|
|
33
|
+
.catch((error) => {
|
|
34
|
+
console.error('Error loading sidebar data:', error);
|
|
35
|
+
});
|
|
48
36
|
}
|
|
49
37
|
</script>
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
getUrlForSearchItem,
|
|
35
35
|
highlightQuery,
|
|
36
36
|
mapPagefindResultsToSearchItems,
|
|
37
|
+
rankResourceSearchItems,
|
|
37
38
|
type SearchItem,
|
|
38
39
|
type SearchNode,
|
|
39
40
|
} from './search-utils';
|
|
@@ -316,16 +317,7 @@ export default function SearchModal() {
|
|
|
316
317
|
return items;
|
|
317
318
|
}
|
|
318
319
|
|
|
319
|
-
|
|
320
|
-
return items.filter((item) => {
|
|
321
|
-
if (item.name.toLowerCase().includes(lowerQuery)) return true;
|
|
322
|
-
// Key format is "type:id:version" (e.g. "service:OrdersService:0.0.3").
|
|
323
|
-
// Match against the id so users can find resources by their raw id too.
|
|
324
|
-
const keyParts = item.key?.split(':') ?? [];
|
|
325
|
-
const id = keyParts[1];
|
|
326
|
-
if (id?.toLowerCase().includes(lowerQuery)) return true;
|
|
327
|
-
return !!item.rawNode.summary && item.rawNode.summary.toLowerCase().includes(lowerQuery);
|
|
328
|
-
});
|
|
320
|
+
return rankResourceSearchItems(items, query);
|
|
329
321
|
}, [indexedItems, isIndexedSearch, items, query]);
|
|
330
322
|
|
|
331
323
|
const filters = useMemo(() => {
|
|
@@ -168,6 +168,32 @@ export const getIndexedResultRank = ({
|
|
|
168
168
|
return 0;
|
|
169
169
|
};
|
|
170
170
|
|
|
171
|
+
/**
|
|
172
|
+
* How well a resource matches a resource search query: an exact name (5), a name starting with
|
|
173
|
+
* the query (4) or containing it (3), then the id (2), then the summary (1). 0 is no match.
|
|
174
|
+
*/
|
|
175
|
+
export const getResourceResultRank = (item: Pick<SearchItem, 'name' | 'key' | 'rawNode'>, query: string) => {
|
|
176
|
+
const lowerQuery = query.toLowerCase();
|
|
177
|
+
const name = item.name.toLowerCase();
|
|
178
|
+
if (name === lowerQuery) return 5;
|
|
179
|
+
if (name.startsWith(lowerQuery)) return 4;
|
|
180
|
+
if (name.includes(lowerQuery)) return 3;
|
|
181
|
+
// Key format is "type:id:version" (e.g. "service:OrdersService:0.0.3").
|
|
182
|
+
// Match against the id so users can find resources by their raw id too.
|
|
183
|
+
const id = item.key?.split(':')[1];
|
|
184
|
+
if (id?.toLowerCase().includes(lowerQuery)) return 2;
|
|
185
|
+
if (item.rawNode.summary?.toLowerCase().includes(lowerQuery)) return 1;
|
|
186
|
+
return 0;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
/** Resources that match a resource search query, best matches first (index order within a rank) */
|
|
190
|
+
export const rankResourceSearchItems = <T extends Pick<SearchItem, 'name' | 'key' | 'rawNode'>>(items: T[], query: string) =>
|
|
191
|
+
items
|
|
192
|
+
.map((item, index) => ({ item, index, rank: getResourceResultRank(item, query) }))
|
|
193
|
+
.filter(({ rank }) => rank > 0)
|
|
194
|
+
.sort((a, b) => b.rank - a.rank || a.index - b.index)
|
|
195
|
+
.map(({ item }) => item);
|
|
196
|
+
|
|
171
197
|
export const applyActiveFilter = <T extends { type: string }>(items: T[], activeFilter: string) => {
|
|
172
198
|
if (activeFilter === 'all') {
|
|
173
199
|
return items;
|
|
@@ -3,8 +3,12 @@ import { SEO } from 'astro-seo';
|
|
|
3
3
|
import config from '@config';
|
|
4
4
|
import defaultImageFile from '../../public/opengraph.png';
|
|
5
5
|
import { buildUrl } from '@utils/url-builder';
|
|
6
|
+
import { formatPageTitle } from '@utils/page-title';
|
|
6
7
|
|
|
7
|
-
const {
|
|
8
|
+
const { description = config.tagline, ogType = 'website' } = Astro.props;
|
|
9
|
+
// The page's own title (none for the homepage), followed by the catalog's
|
|
10
|
+
const title = formatPageTitle(Astro.props.title);
|
|
11
|
+
const ogTitle = title;
|
|
8
12
|
|
|
9
13
|
const siteBaseURL = new URL(Astro.url);
|
|
10
14
|
const defaultImage = new URL(defaultImageFile.src, siteBaseURL);
|
|
@@ -21,56 +21,56 @@ import {
|
|
|
21
21
|
type FavoriteItem,
|
|
22
22
|
} from '@stores/favorites-store';
|
|
23
23
|
import {
|
|
24
|
+
buildNodeLookup,
|
|
24
25
|
canCollapseGroup,
|
|
25
26
|
findNodeKeyByUrl,
|
|
26
27
|
getBadgeClasses,
|
|
27
28
|
getDefaultCollapsedState,
|
|
29
|
+
hidesDefaultItemIcon,
|
|
28
30
|
isGroupCollapsed,
|
|
31
|
+
resolveNavRef,
|
|
32
|
+
rootLevel,
|
|
33
|
+
stripBasePath,
|
|
29
34
|
toggleGroupCollapsed,
|
|
35
|
+
type NavigationLevel,
|
|
36
|
+
type SidebarSnapshot,
|
|
30
37
|
} from './utils';
|
|
31
38
|
import { resolveIconUrl } from '@utils/icon';
|
|
32
39
|
|
|
33
40
|
const cn = (...classes: (string | false | undefined)[]) => classes.filter(Boolean).join(' ');
|
|
34
41
|
|
|
35
|
-
/**
|
|
36
|
-
* Strip the configured base path from a pathname so URL pattern matching works
|
|
37
|
-
* regardless of the base path setting (e.g. `/eventcatalog/docs/...` → `/docs/...`).
|
|
38
|
-
*/
|
|
39
|
-
const stripBasePath = (pathname: string): string => {
|
|
40
|
-
const base = __EC_BASE__.replace(/\/$/, ''); // e.g. '/eventcatalog'
|
|
41
|
-
if (base && pathname.startsWith(base)) {
|
|
42
|
-
return pathname.slice(base.length) || '/';
|
|
43
|
-
}
|
|
44
|
-
return pathname;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
42
|
// ============================================
|
|
48
43
|
// Component
|
|
49
44
|
// ============================================
|
|
50
45
|
|
|
51
|
-
type
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
46
|
+
type NestedSideBarProps = {
|
|
47
|
+
/**
|
|
48
|
+
* This page's level, built on the server. It renders the sidebar into the HTML and keeps
|
|
49
|
+
* the island showing it until the full navigation data (sidebar-data.json) is loaded.
|
|
50
|
+
*/
|
|
51
|
+
snapshot?: SidebarSnapshot;
|
|
56
52
|
};
|
|
57
53
|
|
|
58
|
-
export default function NestedSideBar() {
|
|
59
|
-
const
|
|
54
|
+
export default function NestedSideBar({ snapshot }: NestedSideBarProps) {
|
|
55
|
+
const storeData = useStore(sidebarStore);
|
|
60
56
|
const favorites = useStore(favoritesStore);
|
|
57
|
+
// Drill-down, restoring state and favorites need the whole catalog; until it loads the
|
|
58
|
+
// snapshot only shows this page's level
|
|
59
|
+
const hasFullData = storeData !== null;
|
|
60
|
+
const data = storeData ?? snapshot?.data ?? null;
|
|
61
61
|
|
|
62
62
|
// Guard against undefined data (e.g., during hydration)
|
|
63
63
|
// Use useMemo to ensure stable references for roots and nodes
|
|
64
64
|
const roots = useMemo(() => data?.roots ?? [], [data?.roots]);
|
|
65
65
|
const nodes = useMemo(() => data?.nodes ?? {}, [data?.nodes]);
|
|
66
66
|
|
|
67
|
-
const [navigationStack, setNavigationStack] = useState<NavigationLevel[]>(
|
|
68
|
-
{ key: null, entries: [], title: 'Documentation' }
|
|
69
|
-
|
|
67
|
+
const [navigationStack, setNavigationStack] = useState<NavigationLevel[]>(
|
|
68
|
+
() => snapshot?.stack ?? [{ key: null, entries: [], title: 'Documentation' }]
|
|
69
|
+
);
|
|
70
70
|
const [animationKey, setAnimationKey] = useState(0);
|
|
71
71
|
const [slideDirection, setSlideDirection] = useState<'forward' | 'backward' | null>(null);
|
|
72
72
|
const [isInitialized, setIsInitialized] = useState(false);
|
|
73
|
-
const [currentPath, setCurrentPath] = useState<string>('');
|
|
73
|
+
const [currentPath, setCurrentPath] = useState<string>(snapshot?.url ?? '');
|
|
74
74
|
const [sectionCollapsePreferences, setSectionCollapsePreferences] = useState(loadCollapsedSections);
|
|
75
75
|
const [showPathPreview, setShowPathPreview] = useState(false);
|
|
76
76
|
const [showFullPath, setShowFullPath] = useState(false);
|
|
@@ -78,33 +78,7 @@ export default function NestedSideBar() {
|
|
|
78
78
|
|
|
79
79
|
// Build a lookup map for faster URL navigation
|
|
80
80
|
// Map format: "type:id" -> "nodeKey"
|
|
81
|
-
const nodeLookup = useMemo(() =>
|
|
82
|
-
const lookup = new Map<string, string>();
|
|
83
|
-
|
|
84
|
-
Object.entries(nodes).forEach(([key, value]) => {
|
|
85
|
-
// Skip keys that are references (string values pointing to other keys)
|
|
86
|
-
// These are unversioned aliases like "domain:E-Commerce" -> "domain:E-Commerce:1.0.0"
|
|
87
|
-
if (typeof value === 'string') return;
|
|
88
|
-
|
|
89
|
-
// Key formats:
|
|
90
|
-
// - "type:id:version" (e.g., "service:OrdersService:0.0.3")
|
|
91
|
-
// - "type:id" (e.g., "user:john", "team:backend") - non-versioned resources
|
|
92
|
-
// - "list:name" (e.g., "list:domains") - skip these
|
|
93
|
-
const parts = key.split(':');
|
|
94
|
-
|
|
95
|
-
// Skip list items
|
|
96
|
-
if (parts[0] === 'list') return;
|
|
97
|
-
|
|
98
|
-
if (parts.length >= 2) {
|
|
99
|
-
// Store as "type:id"
|
|
100
|
-
const type = parts[0];
|
|
101
|
-
const id = parts[1];
|
|
102
|
-
lookup.set(`${type}:${id}`, key);
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
return lookup;
|
|
107
|
-
}, [nodes]);
|
|
81
|
+
const nodeLookup = useMemo(() => buildNodeLookup(nodes), [nodes]);
|
|
108
82
|
|
|
109
83
|
/**
|
|
110
84
|
* Toggle section collapse state
|
|
@@ -125,7 +99,7 @@ export default function NestedSideBar() {
|
|
|
125
99
|
setNavigationStack((prevStack) => {
|
|
126
100
|
// Only update if the current stack has no entries (initial state)
|
|
127
101
|
if (prevStack.length === 1 && prevStack[0].entries.length === 0) {
|
|
128
|
-
return [
|
|
102
|
+
return [rootLevel(roots)];
|
|
129
103
|
}
|
|
130
104
|
return prevStack;
|
|
131
105
|
});
|
|
@@ -145,21 +119,7 @@ export default function NestedSideBar() {
|
|
|
145
119
|
* Resolve a child reference to a NavNode
|
|
146
120
|
* Handles both direct keys and string references (unversioned aliases pointing to versioned keys)
|
|
147
121
|
*/
|
|
148
|
-
const resolveRef = useCallback(
|
|
149
|
-
(ref: ChildRef): NavNode | null => {
|
|
150
|
-
if (typeof ref === 'string') {
|
|
151
|
-
const node = nodes[ref];
|
|
152
|
-
if (!node) return null;
|
|
153
|
-
// If node is a string, it's a reference to another key (e.g., unversioned alias)
|
|
154
|
-
if (typeof node === 'string') {
|
|
155
|
-
return (nodes[node] as NavNode) ?? null;
|
|
156
|
-
}
|
|
157
|
-
return node;
|
|
158
|
-
}
|
|
159
|
-
return ref;
|
|
160
|
-
},
|
|
161
|
-
[nodes]
|
|
162
|
-
);
|
|
122
|
+
const resolveRef = useCallback((ref: ChildRef): NavNode | null => resolveNavRef(nodes, ref), [nodes]);
|
|
163
123
|
|
|
164
124
|
/**
|
|
165
125
|
* Check if a node is visible (default: true)
|
|
@@ -174,7 +134,7 @@ export default function NestedSideBar() {
|
|
|
174
134
|
*/
|
|
175
135
|
const buildStackFromPath = useCallback(
|
|
176
136
|
(path: string[]): NavigationLevel[] => {
|
|
177
|
-
const stack: NavigationLevel[] = [
|
|
137
|
+
const stack: NavigationLevel[] = [rootLevel(roots)];
|
|
178
138
|
|
|
179
139
|
for (const key of path) {
|
|
180
140
|
const node = resolveRef(key);
|
|
@@ -280,7 +240,7 @@ export default function NestedSideBar() {
|
|
|
280
240
|
if (foundNode && foundNode.pages && foundNode.pages.length > 0) {
|
|
281
241
|
// Fallback: Flattened navigation
|
|
282
242
|
return [
|
|
283
|
-
|
|
243
|
+
rootLevel(roots),
|
|
284
244
|
{ key: foundNodeKey, entries: foundNode.pages, title: foundNode.title, badge: foundNode.badge },
|
|
285
245
|
];
|
|
286
246
|
}
|
|
@@ -295,7 +255,7 @@ export default function NestedSideBar() {
|
|
|
295
255
|
setSlideDirection('backward');
|
|
296
256
|
setAnimationKey((prev) => prev + 1);
|
|
297
257
|
}
|
|
298
|
-
return [
|
|
258
|
+
return [rootLevel(roots)];
|
|
299
259
|
});
|
|
300
260
|
return true;
|
|
301
261
|
}
|
|
@@ -308,14 +268,14 @@ export default function NestedSideBar() {
|
|
|
308
268
|
* Restore state from localStorage on mount, or navigate to URL
|
|
309
269
|
*/
|
|
310
270
|
useEffect(() => {
|
|
311
|
-
if (!
|
|
271
|
+
if (!hasFullData || roots.length === 0 || isInitialized) return;
|
|
312
272
|
|
|
313
273
|
const currentUrl = window.location.pathname;
|
|
314
274
|
const normalizedCurrentUrl = stripBasePath(currentUrl);
|
|
315
275
|
|
|
316
276
|
// Force root navigation on homepage
|
|
317
277
|
if (normalizedCurrentUrl === '/' || normalizedCurrentUrl === '') {
|
|
318
|
-
setNavigationStack([
|
|
278
|
+
setNavigationStack([rootLevel(roots)]);
|
|
319
279
|
setIsInitialized(true);
|
|
320
280
|
return;
|
|
321
281
|
}
|
|
@@ -346,7 +306,7 @@ export default function NestedSideBar() {
|
|
|
346
306
|
const targetNode = resolveRef(targetKey);
|
|
347
307
|
if (targetNode && targetNode.pages && targetNode.pages.length > 0) {
|
|
348
308
|
finalStack = [
|
|
349
|
-
|
|
309
|
+
rootLevel(roots),
|
|
350
310
|
{ key: targetKey, entries: targetNode.pages, title: targetNode.title, badge: targetNode.badge },
|
|
351
311
|
];
|
|
352
312
|
}
|
|
@@ -354,13 +314,13 @@ export default function NestedSideBar() {
|
|
|
354
314
|
|
|
355
315
|
// 3. Fallback to root
|
|
356
316
|
if (!finalStack) {
|
|
357
|
-
setNavigationStack([
|
|
317
|
+
setNavigationStack([rootLevel(roots)]);
|
|
358
318
|
} else {
|
|
359
319
|
setNavigationStack(finalStack);
|
|
360
320
|
}
|
|
361
321
|
|
|
362
322
|
setIsInitialized(true);
|
|
363
|
-
}, [
|
|
323
|
+
}, [hasFullData, roots, isInitialized, buildStackFromPath, findNodeKeyForUrl, tryConnectStack, resolveRef]);
|
|
364
324
|
|
|
365
325
|
/**
|
|
366
326
|
* Save state whenever navigation changes
|
|
@@ -574,6 +534,7 @@ export default function NestedSideBar() {
|
|
|
574
534
|
* Handle drilling down into an item with children
|
|
575
535
|
*/
|
|
576
536
|
const handleDrillDown = (node: NavNode, nodeKey: string | null) => {
|
|
537
|
+
if (!hasFullData) return;
|
|
577
538
|
if (node.pages && node.pages.length > 0) {
|
|
578
539
|
setSlideDirection('forward');
|
|
579
540
|
setAnimationKey((prev) => prev + 1);
|
|
@@ -589,7 +550,7 @@ export default function NestedSideBar() {
|
|
|
589
550
|
* Navigate back one level
|
|
590
551
|
*/
|
|
591
552
|
const navigateBack = () => {
|
|
592
|
-
if (navigationStack.length > 1) {
|
|
553
|
+
if (hasFullData && navigationStack.length > 1) {
|
|
593
554
|
setSlideDirection('backward');
|
|
594
555
|
setAnimationKey((prev) => prev + 1);
|
|
595
556
|
setNavigationStack(navigationStack.slice(0, -1));
|
|
@@ -603,7 +564,7 @@ export default function NestedSideBar() {
|
|
|
603
564
|
* Navigate to a specific level in the stack
|
|
604
565
|
*/
|
|
605
566
|
const navigateToLevel = (levelIndex: number) => {
|
|
606
|
-
if (levelIndex < navigationStack.length - 1) {
|
|
567
|
+
if (hasFullData && levelIndex < navigationStack.length - 1) {
|
|
607
568
|
setSlideDirection('backward');
|
|
608
569
|
setAnimationKey((prev) => prev + 1);
|
|
609
570
|
setNavigationStack(navigationStack.slice(0, levelIndex + 1));
|
|
@@ -633,6 +594,7 @@ export default function NestedSideBar() {
|
|
|
633
594
|
* Navigate to a favorited item
|
|
634
595
|
*/
|
|
635
596
|
const navigateToFavorite = (favorite: FavoriteItem) => {
|
|
597
|
+
if (!hasFullData) return;
|
|
636
598
|
// If it has an href and no children, just navigate to the URL
|
|
637
599
|
const node = resolveRef(favorite.nodeKey);
|
|
638
600
|
if (favorite.href && (!node?.pages || node.pages.length === 0)) {
|
|
@@ -663,6 +625,11 @@ export default function NestedSideBar() {
|
|
|
663
625
|
|
|
664
626
|
const isTopLevel = navigationStack.length === 1;
|
|
665
627
|
|
|
628
|
+
// The list drilled into (e.g. Channels): its items, even those in groups under it (e.g. ADRs
|
|
629
|
+
// grouped by status), drop a default icon that repeats the list's own
|
|
630
|
+
const levelNode = currentLevel.key ? resolveRef(currentLevel.key) : null;
|
|
631
|
+
const repeatsLevelIcon = (item: NavNode) => !!levelNode && hidesDefaultItemIcon(levelNode, item, false);
|
|
632
|
+
|
|
666
633
|
/**
|
|
667
634
|
* Render a list of child refs (resolving keys as needed)
|
|
668
635
|
*/
|
|
@@ -674,7 +641,7 @@ export default function NestedSideBar() {
|
|
|
674
641
|
if (currentItemGroup.length > 0) {
|
|
675
642
|
result.push(
|
|
676
643
|
<div key={`items-${result.length}`} className="flex flex-col gap-0.5 mb-1.5">
|
|
677
|
-
{currentItemGroup.map((item, idx) => renderItem(item.node, item.key, idx))}
|
|
644
|
+
{currentItemGroup.map((item, idx) => renderItem(item.node, item.key, idx, repeatsLevelIcon(item.node)))}
|
|
678
645
|
</div>
|
|
679
646
|
);
|
|
680
647
|
currentItemGroup = [];
|
|
@@ -795,9 +762,12 @@ export default function NestedSideBar() {
|
|
|
795
762
|
return renderGroup(child, childKey, childIndex, depth + 1);
|
|
796
763
|
}
|
|
797
764
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
765
|
+
return renderItem(
|
|
766
|
+
child,
|
|
767
|
+
childKey,
|
|
768
|
+
childIndex,
|
|
769
|
+
hidesDefaultItemIcon(group, child, isNested) || repeatsLevelIcon(child)
|
|
770
|
+
);
|
|
801
771
|
})}
|
|
802
772
|
</div>
|
|
803
773
|
)}
|
|
@@ -814,9 +784,9 @@ export default function NestedSideBar() {
|
|
|
814
784
|
const isFav = isFavorited(itemKey);
|
|
815
785
|
const canFavorite = itemKey !== null; // Only items with keys can be favorited
|
|
816
786
|
|
|
817
|
-
// Get icon component from lucide-react. When suppressDefaultIcon is set (
|
|
818
|
-
//
|
|
819
|
-
// a custom icon (item.leftIcon) is always still shown.
|
|
787
|
+
// Get icon component from lucide-react. When suppressDefaultIcon is set (the group heading
|
|
788
|
+
// already shows the item's type, see hidesDefaultItemIcon) the default per-collection glyph
|
|
789
|
+
// is hidden; a custom icon (item.leftIcon) is always still shown.
|
|
820
790
|
const IconComponent =
|
|
821
791
|
item.icon && !suppressDefaultIcon ? (LucideIcons as unknown as Record<string, LucideIcons.LucideIcon>)[item.icon] : null;
|
|
822
792
|
const handleStarClick = (e: React.MouseEvent) => {
|
|
@@ -107,6 +107,8 @@ export const saveCollapsedSections = (preferences: SectionCollapsePreferences):
|
|
|
107
107
|
};
|
|
108
108
|
|
|
109
109
|
export const loadCollapsedSections = (): SectionCollapsePreferences => {
|
|
110
|
+
// Rendered on the server for the first paint, where there is nothing saved yet
|
|
111
|
+
if (typeof localStorage === 'undefined') return createSectionCollapsePreferences();
|
|
110
112
|
try {
|
|
111
113
|
const stored = localStorage.getItem(SECTION_PREFERENCES_KEY);
|
|
112
114
|
if (stored) {
|