@clidey/dory 1.0.7 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -1
- package/bin/chat-server.ts +270 -0
- package/bin/dist/analytics-server.d.ts +11 -0
- package/bin/dist/analytics-server.d.ts.map +1 -0
- package/bin/dist/analytics-server.js +121 -0
- package/bin/dist/analytics-server.js.map +1 -0
- package/bin/dist/automation.d.ts +35 -0
- package/bin/dist/automation.d.ts.map +1 -0
- package/bin/dist/automation.js +259 -0
- package/bin/dist/automation.js.map +1 -0
- package/bin/dist/chat-server.d.ts +8 -0
- package/bin/dist/chat-server.d.ts.map +1 -0
- package/bin/dist/chat-server.js +322 -0
- package/bin/dist/chat-server.js.map +1 -0
- package/bin/dist/check.d.ts +16 -0
- package/bin/dist/check.d.ts.map +1 -0
- package/bin/dist/check.js +247 -0
- package/bin/dist/check.js.map +1 -0
- package/bin/dist/dory.js +252 -402
- package/bin/dist/dory.js.map +1 -1
- package/bin/dist/i18n-init.d.ts +6 -0
- package/bin/dist/i18n-init.d.ts.map +1 -0
- package/bin/dist/i18n-init.js +56 -0
- package/bin/dist/i18n-init.js.map +1 -0
- package/bin/dist/mcp-http.d.ts +8 -0
- package/bin/dist/mcp-http.d.ts.map +1 -0
- package/bin/dist/mcp-http.js +72 -0
- package/bin/dist/mcp-http.js.map +1 -0
- package/bin/dist/mcp.d.ts +2 -0
- package/bin/dist/mcp.d.ts.map +1 -0
- package/bin/dist/mcp.js +81 -0
- package/bin/dist/mcp.js.map +1 -0
- package/bin/dist/preview-entry.d.ts +2 -0
- package/bin/dist/preview-entry.d.ts.map +1 -0
- package/bin/dist/preview-entry.js +6 -0
- package/bin/dist/preview-entry.js.map +1 -0
- package/bin/dist/preview-server.d.ts +5 -0
- package/bin/dist/preview-server.d.ts.map +1 -0
- package/bin/dist/preview-server.js +159 -0
- package/bin/dist/preview-server.js.map +1 -0
- package/bin/dist/project-config.d.ts +22 -0
- package/bin/dist/project-config.d.ts.map +1 -0
- package/bin/dist/project-config.js +44 -0
- package/bin/dist/project-config.js.map +1 -0
- package/dist/index.html +57 -0
- package/dist/runtime/dory-runtime.css +2 -0
- package/dist/runtime/dory-runtime.html +14 -0
- package/dist/runtime/dory-runtime.js +5490 -0
- package/docs/api-playground-demo.mdx +119 -0
- package/docs/api-reference/asyncapi.json +324 -0
- package/docs/api-reference/openapi.json +366 -0
- package/docs/api-reference/operations/create.mdx +5 -0
- package/docs/api-reference/operations/delete.mdx +5 -0
- package/docs/api-reference/operations/get.mdx +5 -0
- package/docs/api-reference/operations/update.mdx +5 -0
- package/docs/api-reference/websocket-operations/receive-chat-message.mdx +5 -0
- package/docs/api-reference/websocket-operations/receive-notifications.mdx +5 -0
- package/docs/api-reference/websocket-operations/receive-presence-update.mdx +5 -0
- package/docs/api-reference/websocket-operations/send-chat-message.mdx +5 -0
- package/docs/api-reference/websocket-operations/send-presence-update.mdx +5 -0
- package/docs/assets/dory.jpeg +0 -0
- package/docs/components/accordion.mdx +145 -0
- package/docs/components/callouts.mdx +134 -0
- package/docs/components/cards.mdx +153 -0
- package/docs/components/code-blocks.mdx +281 -0
- package/docs/components/expandable.mdx +189 -0
- package/docs/components/icons.mdx +71 -0
- package/docs/components/images.mdx +82 -0
- package/docs/components/latex.mdx +148 -0
- package/docs/components/layout.mdx +175 -0
- package/docs/components/lists.mdx +134 -0
- package/docs/components/mermaid.mdx +149 -0
- package/docs/components/overview.mdx +81 -0
- package/docs/components/source.mdx +130 -0
- package/docs/components/steps.mdx +122 -0
- package/docs/components/tables.mdx +114 -0
- package/docs/components/tags.mdx +179 -0
- package/docs/components/typography.mdx +185 -0
- package/docs/configuration/analytics.mdx +45 -0
- package/docs/configuration/chat.mdx +59 -0
- package/docs/configuration/dory-json.mdx +278 -0
- package/docs/configuration/embed.mdx +73 -0
- package/docs/configuration/fonts.mdx +86 -0
- package/docs/configuration/internationalization.mdx +61 -0
- package/docs/configuration/navigation.mdx +217 -0
- package/docs/configuration/theming.mdx +166 -0
- package/docs/dory.json +129 -0
- package/docs/embed-example.html +176 -0
- package/docs/favicon.svg +18 -0
- package/docs/features.mdx +440 -0
- package/docs/getting-started/automation.mdx +76 -0
- package/docs/getting-started/configuration.mdx +95 -0
- package/docs/getting-started/installation.mdx +88 -0
- package/docs/getting-started/quick-start.mdx +53 -0
- package/docs/getting-started/whodb-ee-apps.mdx +122 -0
- package/docs/introduction.mdx +50 -0
- package/docs/logo/logo.svg +18 -0
- package/docs/playgrounds/api-playground.mdx +122 -0
- package/docs/playgrounds/websocket-playground.mdx +98 -0
- package/docs/social-card.png +0 -0
- package/docs/social-card.svg +17 -0
- package/docs/websocket-playground-demo.mdx +135 -0
- package/package.json +23 -17
- package/scripts/verify-runtime-parity.mjs +146 -0
- package/src/app.css +19 -1
- package/src/assets/fonts/HankenGrotesk.ttf +0 -0
- package/src/assets/fonts/OFL.txt +94 -0
- package/src/components/chat-assistant.tsx +229 -0
- package/src/components/dropdown.tsx +3 -3
- package/src/components/error-boundary.tsx +2 -2
- package/src/components/hooks.tsx +20 -1
- package/src/components/navigation.tsx +82 -51
- package/src/components/notification.tsx +2 -3
- package/src/components/routes.tsx +17 -19
- package/src/components/search-index.test.ts +15 -0
- package/src/components/search-index.ts +49 -48
- package/src/components/search.tsx +208 -104
- package/src/components/store.ts +27 -9
- package/src/config/mdx-common.ts +11 -0
- package/src/config/mdx.ts +10 -11
- package/src/embed/loader.ts +0 -3
- package/src/embed/types.ts +0 -2
- package/src/embed/widget-iframe.ts +19 -21
- package/src/embedded-runtime/config.ts +138 -0
- package/src/embedded-runtime/index.tsx +680 -0
- package/src/embedded-runtime/runtime.html +14 -0
- package/src/entry-server.tsx +46 -124
- package/src/main.tsx +16 -4
- package/src/mdx/accordion.tsx +3 -3
- package/src/mdx/api-playground.tsx +18 -16
- package/src/mdx/async-api.tsx +2 -2
- package/src/mdx/card.tsx +3 -2
- package/src/mdx/code.tsx +11 -9
- package/src/mdx/fence.tsx +6 -5
- package/src/mdx/icon-grid.tsx +4 -1
- package/src/mdx/icon-names.test.ts +15 -0
- package/src/mdx/icon-names.ts +60 -0
- package/src/mdx/icon-source-variants.ts +102 -0
- package/src/mdx/icon.tsx +12 -73
- package/src/mdx/latex.tsx +3 -2
- package/src/mdx/mdx.tsx +1 -1
- package/src/mdx/mermaid.tsx +12 -6
- package/src/mdx/open-api.tsx +130 -74
- package/src/mdx/preprocess.test.ts +16 -0
- package/src/mdx/preprocess.ts +61 -0
- package/src/mdx/processor.ts +4 -96
- package/src/mdx/source.tsx +5 -4
- package/src/mdx/specimen.tsx +38 -6
- package/src/mdx/tabs.tsx +29 -21
- package/src/mdx/text.tsx +9 -9
- package/src/mdx/try-it-panel.tsx +284 -0
- package/src/mdx/type-scale.tsx +4 -4
- package/src/mdx/websocket-playground.tsx +21 -14
- package/src/plugins/analytics-injector.ts +8 -0
- package/src/plugins/chat-dev-server.ts +32 -0
- package/src/plugins/docs-assets.test.ts +25 -0
- package/src/plugins/docs-assets.ts +6 -2
- package/src/plugins/frontmatter-dev-server.ts +2 -2
- package/src/plugins/frontmatter-generator.ts +1 -1
- package/src/plugins/html-files-middleware.test.ts +21 -1
- package/src/plugins/html-files-middleware.ts +26 -6
- package/src/plugins/html-metadata-injector.ts +24 -4
- package/src/plugins/llm-txt-dev-server.ts +2 -2
- package/src/plugins/llm-txt-generator.ts +21 -4
- package/src/plugins/prerender.ts +47 -20
- package/src/plugins/robots-generator.ts +1 -1
- package/src/plugins/sanitize.ts +1 -1
- package/src/plugins/shared.ts +6 -18
- package/src/plugins/sitemap-generator.ts +1 -1
- package/src/plugins/social-preview.test.ts +71 -0
- package/src/plugins/social-preview.ts +87 -0
- package/src/plugins/unified-mdx-plugin.ts +1 -1
- package/src/theme.scss +630 -207
- package/src/types/config.ts +53 -1
- package/src/ui/breadcrumb.tsx +17 -0
- package/src/ui/docs-header-view.tsx +32 -0
- package/src/ui/docs-navigation-view.tsx +69 -0
- package/src/ui/docs-page-frame.tsx +50 -0
- package/src/ui/dory-mark.tsx +9 -0
- package/src/ui/header.tsx +33 -90
- package/src/ui/layout.tsx +99 -153
- package/src/ui/mobile-navigation.tsx +10 -8
- package/src/ui/page-actions.tsx +67 -0
- package/src/ui/page-feedback.tsx +50 -0
- package/src/ui/page-footer.tsx +23 -0
- package/src/ui/prev-next-link.tsx +15 -69
- package/src/ui/prev-next-view.tsx +27 -0
- package/src/ui/table-of-content-view.tsx +167 -0
- package/src/ui/table-of-content.tsx +25 -183
- package/src/utils/analytics.test.ts +36 -0
- package/src/utils/analytics.ts +35 -0
- package/src/utils/colors.ts +7 -0
- package/src/utils/content-scope.test.ts +26 -0
- package/src/utils/content-scope.ts +39 -0
- package/src/utils/search-content.ts +12 -0
- package/src/utils/theme.ts +1 -7
- package/vite.config.ssr.ts +3 -3
- package/vite.config.ts +21 -15
package/src/components/hooks.tsx
CHANGED
|
@@ -2,6 +2,9 @@ import { useEffect, useState } from 'preact/hooks';
|
|
|
2
2
|
import { useLocation } from 'wouter-preact';
|
|
3
3
|
import { withBase } from '../utils/base-path';
|
|
4
4
|
|
|
5
|
+
const initialEmbedParams = typeof window !== 'undefined' && new URLSearchParams(window.location.search).get('embedded') === 'true'
|
|
6
|
+
? new URLSearchParams(window.location.search) : null;
|
|
7
|
+
|
|
5
8
|
/**
|
|
6
9
|
* Strips trailing slash from a pathname (except root "/").
|
|
7
10
|
*/
|
|
@@ -53,5 +56,21 @@ export const useSearchParams = () => {
|
|
|
53
56
|
*/
|
|
54
57
|
export const useIsEmbedded = () => {
|
|
55
58
|
const searchParams = useSearchParams();
|
|
56
|
-
|
|
59
|
+
const [pathname] = useLocation();
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (!initialEmbedParams) return;
|
|
63
|
+
const params = new URLSearchParams(window.location.search);
|
|
64
|
+
let changed = false;
|
|
65
|
+
for (const key of ['embedded', 'parentOrigin', 'theme']) {
|
|
66
|
+
const value = initialEmbedParams.get(key);
|
|
67
|
+
if (value !== null && params.get(key) !== value) {
|
|
68
|
+
params.set(key, value);
|
|
69
|
+
changed = true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (changed) window.history.replaceState(window.history.state, '', `${window.location.pathname}?${params}${window.location.hash}`);
|
|
73
|
+
}, [pathname]);
|
|
74
|
+
|
|
75
|
+
return searchParams.get('embedded') === 'true' || initialEmbedParams !== null;
|
|
57
76
|
};
|
|
@@ -1,58 +1,89 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { CatalogIcon } from '@clidey/ux'
|
|
2
|
+
import type { ComponentChildren } from 'preact';
|
|
3
|
+
|
|
3
4
|
import { Link } from 'wouter-preact';
|
|
5
|
+
import docsConfig from '../../docs/dory.json' with { type: 'json' };
|
|
6
|
+
import type { DoryConfig } from '../types/config';
|
|
7
|
+
import { DocsNavigationView, type NavigationPage } from '../ui/docs-navigation-view';
|
|
4
8
|
import { usePathname } from './hooks';
|
|
5
|
-
import {
|
|
9
|
+
import { ALL_PAGES, completeFrontMatter, getNavigationForPath } from './store';
|
|
10
|
+
import { contentScope, scopedPath } from '../utils/content-scope';
|
|
11
|
+
import { withBase } from '../utils/base-path';
|
|
12
|
+
|
|
13
|
+
const config = docsConfig as DoryConfig;
|
|
6
14
|
|
|
7
|
-
|
|
8
|
-
const pathname = usePathname();
|
|
15
|
+
type Frontmatter = Record<string, any>;
|
|
9
16
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
17
|
+
/** Sidebar label for a page. API pages show a method tag and their path or message name. */
|
|
18
|
+
function pageLabel(href: string, title: string, frontmatter: Frontmatter[]): { method?: string; title: string } {
|
|
19
|
+
const fm = frontmatter.find((entry) => entry.path === href);
|
|
20
|
+
if (typeof fm?.openapi === 'string') {
|
|
21
|
+
const [verb, path] = fm.openapi.split(' ');
|
|
22
|
+
const method = verb.toUpperCase() === 'DELETE' ? 'DEL' : verb.toUpperCase();
|
|
23
|
+
return { method, title: path ?? title };
|
|
24
|
+
}
|
|
25
|
+
if (typeof fm?.asyncapi === 'string') {
|
|
26
|
+
const [operation] = fm.asyncapi.split(' ');
|
|
27
|
+
return { method: operation.toLowerCase() === 'publish' ? 'PUB' : 'SUB', title };
|
|
28
|
+
}
|
|
29
|
+
return { title };
|
|
30
|
+
}
|
|
13
31
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
32
|
+
/** Static-site adapter for Dory's shared documentation navigation. */
|
|
33
|
+
export function Navigation({ frontmatter = completeFrontMatter }: { frontmatter?: Frontmatter[] }) {
|
|
34
|
+
const pathname = usePathname();
|
|
35
|
+
const scope = contentScope(config, pathname);
|
|
36
|
+
const navigation = getNavigationForPath(pathname);
|
|
37
|
+
const switchTo = (locale: string, version: string) => {
|
|
38
|
+
const candidate = scopedPath(config, pathname, locale, version);
|
|
39
|
+
if (ALL_PAGES[candidate]) return candidate;
|
|
40
|
+
return getNavigationForPath(candidate)[0]?.groups[0]?.pages[0]?.href ?? '/';
|
|
41
|
+
};
|
|
42
|
+
const effectivePath = pathname === '/'
|
|
43
|
+
? navigation[0]?.groups[0]?.pages[0]?.href ?? '/'
|
|
44
|
+
: pathname;
|
|
45
|
+
const groups = navigation.flatMap(tab => tab.groups).map(group => ({
|
|
46
|
+
title: group.title,
|
|
47
|
+
pages: group.pages.map(page => {
|
|
48
|
+
const translatedTitle = frontmatter.find(entry => entry.path === page.href)?.title ?? page.title;
|
|
49
|
+
return { ...page, ...pageLabel(page.href, translatedTitle, frontmatter) };
|
|
50
|
+
}),
|
|
51
|
+
}));
|
|
52
|
+
const currentVersion = config.versions?.find(version => version.id === scope.version);
|
|
53
|
+
const versionLabel = currentVersion?.label ?? config.version;
|
|
54
|
+
const versionSelect = config.versions && config.versions.length > 0 ? <>
|
|
55
|
+
<CatalogIcon name="up-down" aria-hidden="true" className="h-3.5 w-3.5 text-white/50" />
|
|
56
|
+
{/* Transparent native select over the card: keyboard and screen-reader friendly. */}
|
|
57
|
+
<select
|
|
58
|
+
aria-label="Version"
|
|
59
|
+
value={scope.version}
|
|
60
|
+
onChange={event => {
|
|
61
|
+
const id = (event.target as HTMLSelectElement).value;
|
|
62
|
+
const version = config.versions?.find(entry => entry.id === id || entry.url === id);
|
|
63
|
+
const target = version?.url ?? withBase(switchTo(scope.locale, id));
|
|
64
|
+
window.location.href = target;
|
|
65
|
+
}}
|
|
66
|
+
className="absolute inset-0 cursor-pointer opacity-0"
|
|
67
|
+
>
|
|
68
|
+
<option value="">{config.version}</option>
|
|
69
|
+
{config.versions.map(version => <option key={version.id ?? version.url} value={version.id ?? version.url}>{version.label}</option>)}
|
|
70
|
+
</select>
|
|
71
|
+
</> : undefined;
|
|
72
|
+
const languageSelect = config.i18n && config.i18n.locales.length > 1 ?
|
|
73
|
+
<label className="flex items-center justify-between gap-3 rounded-lg border border-white/10 bg-white/5 px-3 py-2 text-white">
|
|
74
|
+
<span className="dory-mono-label">Language</span>
|
|
75
|
+
<select
|
|
76
|
+
aria-label="Language"
|
|
77
|
+
value={scope.locale}
|
|
78
|
+
onChange={event => { window.location.href = withBase(switchTo((event.target as HTMLSelectElement).value, scope.version)); }}
|
|
79
|
+
className="min-w-0 max-w-32 bg-transparent text-right text-sm text-white"
|
|
80
|
+
>
|
|
81
|
+
{config.i18n.locales.map(locale => <option key={locale.code} value={locale.code} className="text-black">{locale.label}</option>)}
|
|
82
|
+
</select>
|
|
83
|
+
</label> : undefined;
|
|
84
|
+
const renderPageLink = (page: NavigationPage, className: string, active: boolean, children: ComponentChildren) =>
|
|
85
|
+
<Link href={page.href} aria-current={active ? 'page' : undefined} className={className}>{children}</Link>;
|
|
21
86
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
<ul role="list" className="space-y-9">
|
|
25
|
-
{currentTab && (
|
|
26
|
-
<li key={currentTab.title}>
|
|
27
|
-
<h2 className="font-display font-medium text-xs">{currentTab.title}</h2>
|
|
28
|
-
{currentTab.groups.map(group => (
|
|
29
|
-
<div key={group.title}>
|
|
30
|
-
<h3 className="mt-4 text-sm font-semibold">{group.title}</h3>
|
|
31
|
-
<ul role="list" className="mt-2 space-y-2 border-l-2 border-border lg:mt-4 lg:space-y-4 lg:border-border">
|
|
32
|
-
{group.pages.map((page) => {
|
|
33
|
-
const isActive = effectivePath === page.href;
|
|
34
|
-
return (
|
|
35
|
-
<li key={page.href} className="relative">
|
|
36
|
-
<Link
|
|
37
|
-
href={page.href}
|
|
38
|
-
className={classNames(
|
|
39
|
-
'block w-full pl-3.5 before:pointer-events-none before:absolute before:top-1/2 before:-left-1 before:h-1.5 before:w-1.5 before:-translate-y-1/2 before:rounded-full',
|
|
40
|
-
{
|
|
41
|
-
'font-semibold text-brand-foreground': isActive,
|
|
42
|
-
'text-muted-foreground before:hidden before:bg-slate-300 hover:text-muted-foreground hover:before:block dark:text-muted-foreground dark:before:bg-slate-700 dark:hover:text-slate-300': !isActive
|
|
43
|
-
},
|
|
44
|
-
)}>
|
|
45
|
-
{page.title}
|
|
46
|
-
</Link>
|
|
47
|
-
</li>
|
|
48
|
-
);
|
|
49
|
-
})}
|
|
50
|
-
</ul>
|
|
51
|
-
</div>
|
|
52
|
-
))}
|
|
53
|
-
</li>
|
|
54
|
-
)}
|
|
55
|
-
</ul>
|
|
56
|
-
</nav>
|
|
57
|
-
);
|
|
87
|
+
return <DocsNavigationView groups={groups} activeHref={effectivePath} renderPageLink={renderPageLink}
|
|
88
|
+
versionLabel={versionLabel} versionSelect={versionSelect} languageSelect={languageSelect} />;
|
|
58
89
|
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { toast } from '@clidey/ux';
|
|
2
|
-
import { InfoIcon } from 'lucide-react';
|
|
1
|
+
import { CatalogIcon, toast } from '@clidey/ux';
|
|
3
2
|
|
|
4
3
|
export function useNotification() {
|
|
5
4
|
return {
|
|
6
5
|
showNotification: (message: string, description?: string) => {
|
|
7
6
|
toast(message, {
|
|
8
|
-
icon: <
|
|
7
|
+
icon: <CatalogIcon name="info" className="h-4 w-4" />,
|
|
9
8
|
description: description,
|
|
10
9
|
});
|
|
11
10
|
}
|
|
@@ -1,26 +1,32 @@
|
|
|
1
1
|
import { useEffect, useMemo } from 'preact/hooks';
|
|
2
2
|
import { Route, Router, useLocation } from 'wouter-preact';
|
|
3
3
|
import { usePathname } from './hooks';
|
|
4
|
-
import {
|
|
4
|
+
import { ALL_PAGES, getNavigationForPath, pathFromFilename } from './store';
|
|
5
|
+
import docsConfig from '../../docs/dory.json' with { type: 'json' };
|
|
6
|
+
import type { DoryConfig } from '../types/config';
|
|
7
|
+
import { contentScope } from '../utils/content-scope';
|
|
8
|
+
import { NotFoundPage } from '@clidey/ux';
|
|
5
9
|
|
|
6
10
|
|
|
7
11
|
export default function Routes() {
|
|
8
12
|
const pathname = usePathname();
|
|
9
13
|
const [, navigate] = useLocation();
|
|
14
|
+
const scope = contentScope(docsConfig as DoryConfig, pathname);
|
|
15
|
+
const isScopeRoot = pathname === '/' || (Boolean(scope.prefix) && pathname === scope.prefix);
|
|
10
16
|
|
|
11
17
|
// Redirect root to the first page so the URL reflects the actual content
|
|
12
18
|
useEffect(() => {
|
|
13
|
-
if (
|
|
14
|
-
const firstPage =
|
|
19
|
+
if (isScopeRoot) {
|
|
20
|
+
const firstPage = getNavigationForPath(pathname)[0]?.groups[0]?.pages[0]?.href;
|
|
15
21
|
if (firstPage) navigate(firstPage, { replace: true });
|
|
16
22
|
}
|
|
17
23
|
}, [pathname]);
|
|
18
24
|
|
|
19
25
|
const route = useMemo(() => {
|
|
20
26
|
let targetPath = pathname;
|
|
21
|
-
if (
|
|
27
|
+
if (isScopeRoot) {
|
|
22
28
|
const ssrRoute = typeof window !== 'undefined' ? window.__DORY_ROUTE__ : undefined;
|
|
23
|
-
targetPath = ssrRoute ||
|
|
29
|
+
targetPath = (pathname === '/' ? ssrRoute : undefined) || getNavigationForPath(pathname)[0]?.groups[0]?.pages[0]?.href || pathname;
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
const entry = Object.entries(ALL_PAGES).find(([path]) => path === targetPath);
|
|
@@ -28,7 +34,7 @@ export default function Routes() {
|
|
|
28
34
|
|
|
29
35
|
const [path, mod] = entry;
|
|
30
36
|
return {
|
|
31
|
-
path:
|
|
37
|
+
path: isScopeRoot ? pathname : pathFromFilename(path),
|
|
32
38
|
component: (mod as any).default,
|
|
33
39
|
};
|
|
34
40
|
}, [pathname]);
|
|
@@ -36,19 +42,11 @@ export default function Routes() {
|
|
|
36
42
|
// 404 — no MDX module for this path (even if it appears in navigation)
|
|
37
43
|
if (!route && pathname !== '/') {
|
|
38
44
|
return (
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
</p>
|
|
45
|
-
<button
|
|
46
|
-
onClick={() => navigate(ALL_NAVIGATION[0]?.groups[0]?.pages[0]?.href ?? '/')}
|
|
47
|
-
className="px-4 py-2 text-sm font-medium rounded-md bg-[var(--brand-primary)] text-white hover:opacity-90 transition-opacity"
|
|
48
|
-
>
|
|
49
|
-
Go to Home
|
|
50
|
-
</button>
|
|
51
|
-
</div>
|
|
45
|
+
<NotFoundPage
|
|
46
|
+
description={`The page ${pathname} does not exist.`}
|
|
47
|
+
onHome={() => navigate(getNavigationForPath(pathname)[0]?.groups[0]?.pages[0]?.href ?? '/')}
|
|
48
|
+
homeLabel="Go to home"
|
|
49
|
+
/>
|
|
52
50
|
);
|
|
53
51
|
}
|
|
54
52
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { createSearchIndex } from './search-index';
|
|
3
|
+
|
|
4
|
+
describe('createSearchIndex', () => {
|
|
5
|
+
it('keeps runtime locale indexes separate', async () => {
|
|
6
|
+
const english = createSearchIndex();
|
|
7
|
+
const french = createSearchIndex();
|
|
8
|
+
await english.add('docs/index.mdx', { title: 'Inventory' }, 'publish the app');
|
|
9
|
+
await french.add('docs/fr/index.mdx', { title: 'Inventaire' }, 'publier l’application');
|
|
10
|
+
|
|
11
|
+
expect(english.search('publish').map(item => item.url)).toEqual(['docs/index.mdx']);
|
|
12
|
+
expect(french.search('publish')).toEqual([]);
|
|
13
|
+
expect(french.search('publier').map(item => item.url)).toEqual(['docs/fr/index.mdx']);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -1,54 +1,55 @@
|
|
|
1
|
-
|
|
1
|
+
/** A separate index lets the embedded viewer search only its authorized docs. */
|
|
2
|
+
export function createSearchIndex() {
|
|
3
|
+
let searchIndex: any = null;
|
|
2
4
|
|
|
3
|
-
async function getSearchIndex() {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
5
|
+
async function getSearchIndex() {
|
|
6
|
+
if (!searchIndex) {
|
|
7
|
+
const { Document } = await import('flexsearch');
|
|
8
|
+
searchIndex = new Document({
|
|
9
|
+
tokenize: 'full',
|
|
10
|
+
document: {
|
|
11
|
+
id: 'url',
|
|
12
|
+
index: 'content',
|
|
13
|
+
store: ['title', 'pageTitle'],
|
|
14
|
+
},
|
|
15
|
+
context: {
|
|
16
|
+
resolution: 9,
|
|
17
|
+
depth: 2,
|
|
18
|
+
bidirectional: true
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
return searchIndex;
|
|
19
23
|
}
|
|
20
|
-
return searchIndex;
|
|
21
|
-
}
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
25
|
+
return {
|
|
26
|
+
async add(filename: string, data: { title?: string }, content: string) {
|
|
27
|
+
try {
|
|
28
|
+
const index = await getSearchIndex();
|
|
29
|
+
index.add({
|
|
30
|
+
url: filename,
|
|
31
|
+
title: data.title || '',
|
|
32
|
+
content,
|
|
33
|
+
pageTitle: data.title,
|
|
34
|
+
});
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.warn(`Failed to index ${filename}:`, error);
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
search(query: string, options = {}): Array<{ url: string; title: string; pageTitle: string }> {
|
|
40
|
+
if (!searchIndex) return [];
|
|
41
|
+
const result = searchIndex.search(query, { ...options, enrich: true }) as any[];
|
|
42
|
+
if (result.length === 0) return [];
|
|
43
|
+
return result[0].result.map((item: any) => ({
|
|
44
|
+
url: item.id as string,
|
|
45
|
+
title: item.doc?.title || '',
|
|
46
|
+
pageTitle: item.doc?.pageTitle || '',
|
|
47
|
+
}));
|
|
48
|
+
},
|
|
49
|
+
};
|
|
35
50
|
}
|
|
36
51
|
|
|
37
|
-
|
|
38
|
-
if (!searchIndex) return [];
|
|
52
|
+
const defaultIndex = createSearchIndex();
|
|
39
53
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
enrich: true,
|
|
43
|
-
}) as any[];
|
|
44
|
-
|
|
45
|
-
if (result.length === 0) {
|
|
46
|
-
return [];
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return result[0].result.map((item: any) => ({
|
|
50
|
-
url: item.id,
|
|
51
|
-
title: item.doc?.title || '',
|
|
52
|
-
pageTitle: item.doc?.pageTitle || '',
|
|
53
|
-
}));
|
|
54
|
-
}
|
|
54
|
+
export const addToSearchIndex = defaultIndex.add;
|
|
55
|
+
export const search = defaultIndex.search;
|