@clidey/dory 0.34.1 → 0.36.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/index.html +0 -7
- package/package.json +41 -42
- package/src/components/error-boundary.tsx +59 -0
- package/src/components/routes.tsx +20 -3
- package/src/components/search-index.ts +34 -23
- package/src/components/store.ts +24 -24
- package/src/embed/loader.ts +2 -1
- package/src/embed/widget-iframe.ts +0 -3
- package/src/main.tsx +4 -1
- package/src/mdx/api-playground.tsx +2 -1
- package/src/mdx/fence.tsx +2 -1
- package/src/mdx/latex.tsx +76 -9
- package/src/mdx/mdx.tsx +17 -2
- package/src/plugins/analytics-injector.ts +43 -0
- package/src/plugins/csp-generator.ts +68 -0
- package/src/plugins/docs-assets.ts +1 -6
- package/src/plugins/frontmatter-dev-server.ts +8 -1
- package/src/plugins/frontmatter-generator.ts +12 -7
- package/src/plugins/llm-txt-dev-server.ts +8 -1
- package/src/plugins/llm-txt-generator.ts +8 -1
- package/src/plugins/prerender.ts +119 -0
- package/src/plugins/robots-generator.ts +37 -0
- package/src/plugins/sitemap-generator.ts +73 -0
- package/src/types/config.ts +9 -0
- package/src/ui/header.tsx +3 -8
- package/src/ui/layout.tsx +63 -5
- package/vite.config.embed-widget.ts +11 -8
- package/vite.config.ts +23 -4
- package/bin/dist/dory.d.ts +0 -3
- package/bin/dist/dory.d.ts.map +0 -1
- package/bin/dist/dory.js +0 -486
- package/bin/dist/dory.js.map +0 -1
- package/src/plugins/manifest-generator.ts +0 -57
package/index.html
CHANGED
|
@@ -38,13 +38,6 @@
|
|
|
38
38
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
39
39
|
<link href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Inter&display=swap" rel="stylesheet">
|
|
40
40
|
|
|
41
|
-
<script>
|
|
42
|
-
!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.crossOrigin="anonymous",p.async=!0,p.src=s.api_host.replace(".i.posthog.com","-assets.i.posthog.com")+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="init be ys Ss me gs ws capture Ne calculateEventProperties xs register register_once register_for_session unregister unregister_for_session Rs getFeatureFlag getFeatureFlagPayload isFeatureEnabled reloadFeatureFlags updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures on onFeatureFlags onSurveysLoaded onSessionId getSurveys getActiveMatchingSurveys renderSurvey canRenderSurvey canRenderSurveyAsync identify setPersonProperties group resetGroups setPersonPropertiesForFlags resetPersonPropertiesForFlags setGroupPropertiesForFlags resetGroupPropertiesForFlags reset get_distinct_id getGroups get_session_id get_session_replay_url alias set_config startSessionRecording stopSessionRecording sessionRecordingStarted captureException loadToolbar get_property getSessionProperty Is ks createPersonProfile Ps bs opt_in_capturing opt_out_capturing has_opted_in_capturing has_opted_out_capturing clear_opt_in_out_capturing $s debug Es getPageViewId captureTraceFeedback captureTraceMetric".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[]);
|
|
43
|
-
posthog.init('phc_hxwfcMiIgSXYKkbWl9zpQE0HaPPmlRaVXCWODricYu1', {
|
|
44
|
-
api_host: 'https://us.i.posthog.com',
|
|
45
|
-
person_profiles: 'identified_only', // or 'always' to create profiles for anonymous users as well
|
|
46
|
-
})
|
|
47
|
-
</script>
|
|
48
41
|
</head>
|
|
49
42
|
<body>
|
|
50
43
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clidey/dory",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.36.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "A CLI tool for building and previewing documentation sites",
|
|
7
7
|
"keywords": [
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
},
|
|
35
35
|
"scripts": {
|
|
36
36
|
"dev": "vite",
|
|
37
|
-
"build": "npm run build:cli && tsc -b && vite build
|
|
37
|
+
"build": "npm run build:cli && tsc -b && vite build",
|
|
38
38
|
"build:cli": "tsc -p tsconfig.bin.json",
|
|
39
39
|
"build:docs": "tsc -b && vite build",
|
|
40
40
|
"build:embed": "npm run build:embed:loader && npm run build:embed:widget",
|
|
@@ -45,47 +45,46 @@
|
|
|
45
45
|
"prepublishOnly": "echo 'Skipping prepublishOnly - build handled by publish script'"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@algolia/autocomplete-core": "
|
|
49
|
-
"@clidey/ux": "
|
|
50
|
-
"@fortawesome/fontawesome-svg-core": "
|
|
51
|
-
"@fortawesome/free-brands-svg-icons": "
|
|
52
|
-
"@fortawesome/free-regular-svg-icons": "
|
|
53
|
-
"@fortawesome/free-solid-svg-icons": "
|
|
54
|
-
"@fortawesome/react-fontawesome": "
|
|
55
|
-
"@headlessui/react": "
|
|
56
|
-
"@heroicons/react": "
|
|
57
|
-
"@mdx-js/mdx": "
|
|
58
|
-
"@mdx-js/preact": "
|
|
59
|
-
"@mdx-js/rollup": "
|
|
60
|
-
"@preact/preset-vite": "
|
|
61
|
-
"@tailwindcss/vite": "
|
|
62
|
-
"@types/react-highlight-words": "
|
|
63
|
-
"classnames": "
|
|
64
|
-
"flexsearch": "
|
|
65
|
-
"gray-matter": "
|
|
66
|
-
"gzip-size-cli": "
|
|
67
|
-
"katex": "
|
|
68
|
-
"lucide-react": "
|
|
69
|
-
"mermaid": "
|
|
70
|
-
"motion": "
|
|
71
|
-
"preact": "
|
|
72
|
-
"prism-react-renderer": "
|
|
73
|
-
"react-highlight-words": "
|
|
74
|
-
"remark-gfm": "
|
|
75
|
-
"saas": "
|
|
76
|
-
"sass-embedded": "
|
|
77
|
-
"sirv": "
|
|
78
|
-
"tailwindcss": "
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
"vite-plugin-mdx": "^3.6.1",
|
|
85
|
-
"wouter-preact": "^3.7.1"
|
|
48
|
+
"@algolia/autocomplete-core": "1.19.5",
|
|
49
|
+
"@clidey/ux": "0.39.0",
|
|
50
|
+
"@fortawesome/fontawesome-svg-core": "7.2.0",
|
|
51
|
+
"@fortawesome/free-brands-svg-icons": "7.2.0",
|
|
52
|
+
"@fortawesome/free-regular-svg-icons": "7.2.0",
|
|
53
|
+
"@fortawesome/free-solid-svg-icons": "7.2.0",
|
|
54
|
+
"@fortawesome/react-fontawesome": "3.2.0",
|
|
55
|
+
"@headlessui/react": "2.2.9",
|
|
56
|
+
"@heroicons/react": "2.2.0",
|
|
57
|
+
"@mdx-js/mdx": "3.1.1",
|
|
58
|
+
"@mdx-js/preact": "3.1.1",
|
|
59
|
+
"@mdx-js/rollup": "3.1.1",
|
|
60
|
+
"@preact/preset-vite": "2.10.3",
|
|
61
|
+
"@tailwindcss/vite": "4.1.18",
|
|
62
|
+
"@types/react-highlight-words": "0.20.1",
|
|
63
|
+
"classnames": "2.5.1",
|
|
64
|
+
"flexsearch": "0.8.212",
|
|
65
|
+
"gray-matter": "4.0.3",
|
|
66
|
+
"gzip-size-cli": "5.1.0",
|
|
67
|
+
"katex": "0.16.28",
|
|
68
|
+
"lucide-react": "0.563.0",
|
|
69
|
+
"mermaid": "11.12.2",
|
|
70
|
+
"motion": "12.34.0",
|
|
71
|
+
"preact": "10.28.3",
|
|
72
|
+
"prism-react-renderer": "2.4.1",
|
|
73
|
+
"react-highlight-words": "0.21.0",
|
|
74
|
+
"remark-gfm": "4.0.1",
|
|
75
|
+
"saas": "1.0.0",
|
|
76
|
+
"sass-embedded": "1.97.3",
|
|
77
|
+
"sirv": "3.0.2",
|
|
78
|
+
"tailwindcss": "4.1.18",
|
|
79
|
+
"tsx": "4.21.0",
|
|
80
|
+
"typescript": "5.9.3",
|
|
81
|
+
"unist-util-visit": "5.1.0",
|
|
82
|
+
"vite": "7.3.1",
|
|
83
|
+
"wouter-preact": "3.9.0"
|
|
86
84
|
},
|
|
87
|
-
"packageManager": "pnpm@10.
|
|
85
|
+
"packageManager": "pnpm@10.24.0+sha512.01ff8ae71b4419903b65c60fb2dc9d34cf8bb6e06d03bde112ef38f7a34d6904c424ba66bea5cdcf12890230bf39f9580473140ed9c946fef328b6e5238a345a",
|
|
88
86
|
"devDependencies": {
|
|
89
|
-
"@types/node": "
|
|
87
|
+
"@types/node": "25.2.3",
|
|
88
|
+
"terser": "5.46.0"
|
|
90
89
|
}
|
|
91
90
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Component, type ComponentChildren } from 'preact';
|
|
2
|
+
import { ExclamationTriangleIcon } from '@heroicons/react/24/outline';
|
|
3
|
+
|
|
4
|
+
interface ErrorBoundaryProps {
|
|
5
|
+
children: ComponentChildren;
|
|
6
|
+
fallback?: (error: Error) => ComponentChildren;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface ErrorBoundaryState {
|
|
10
|
+
hasError: boolean;
|
|
11
|
+
error: Error | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|
15
|
+
constructor(props: ErrorBoundaryProps) {
|
|
16
|
+
super(props);
|
|
17
|
+
this.state = { hasError: false, error: null };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
|
21
|
+
return { hasError: true, error };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
componentDidCatch(error: Error, errorInfo: any) {
|
|
25
|
+
console.error('Error caught by boundary:', error, errorInfo);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
render() {
|
|
29
|
+
if (this.state.hasError && this.state.error) {
|
|
30
|
+
if (this.props.fallback) {
|
|
31
|
+
return this.props.fallback(this.state.error);
|
|
32
|
+
}
|
|
33
|
+
return (
|
|
34
|
+
<div className="flex flex-col items-center justify-center min-h-[50vh] p-8">
|
|
35
|
+
<div className="w-16 h-16 mb-4 flex items-center justify-center bg-red-100 dark:bg-red-900/30 rounded-full">
|
|
36
|
+
<ExclamationTriangleIcon className="w-8 h-8 text-red-500" />
|
|
37
|
+
</div>
|
|
38
|
+
<h2 className="text-2xl font-semibold text-red-700 dark:text-red-300 mb-2">
|
|
39
|
+
Something went wrong
|
|
40
|
+
</h2>
|
|
41
|
+
<p className="text-red-600 dark:text-red-400 mb-4 text-center max-w-md">
|
|
42
|
+
An error occurred while rendering this page.
|
|
43
|
+
</p>
|
|
44
|
+
<pre className="text-sm text-red-700 dark:text-red-300 bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-800 rounded-md p-3 max-w-md mb-4 overflow-auto">
|
|
45
|
+
{this.state.error.message}
|
|
46
|
+
</pre>
|
|
47
|
+
<button
|
|
48
|
+
onClick={() => window.location.reload()}
|
|
49
|
+
className="px-4 py-2 text-sm font-medium rounded-md bg-[var(--brand-primary)] text-white hover:opacity-90 transition-opacity"
|
|
50
|
+
>
|
|
51
|
+
Reload Page
|
|
52
|
+
</button>
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return this.props.children;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -70,10 +70,27 @@ export default function Routes() {
|
|
|
70
70
|
useEffect(() => {
|
|
71
71
|
const page = ALL_NAVIGATION.find((tab) => tab.groups.find((group) => group.pages.find((page) => page.href === pathname)));
|
|
72
72
|
if (!page && pathname !== '/') {
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
setRoutes([{
|
|
74
|
+
path: pathname,
|
|
75
|
+
component: () => (
|
|
76
|
+
<div className="flex flex-col items-center justify-center min-h-[50vh] p-8">
|
|
77
|
+
<h1 className="text-6xl font-bold text-gray-300 dark:text-gray-700 mb-4">404</h1>
|
|
78
|
+
<h2 className="text-2xl font-semibold mb-4">Page Not Found</h2>
|
|
79
|
+
<p className="text-gray-600 dark:text-gray-400 mb-6 text-center max-w-md">
|
|
80
|
+
The page <code className="px-1.5 py-0.5 bg-gray-100 dark:bg-gray-800 rounded text-sm">{pathname}</code> doesn't exist.
|
|
81
|
+
</p>
|
|
82
|
+
<button
|
|
83
|
+
onClick={() => navigate(ALL_NAVIGATION[0].groups[0].pages[0].href)}
|
|
84
|
+
className="px-4 py-2 text-sm font-medium rounded-md bg-[var(--brand-primary)] text-white hover:opacity-90 transition-opacity"
|
|
85
|
+
>
|
|
86
|
+
Go to Home
|
|
87
|
+
</button>
|
|
88
|
+
</div>
|
|
89
|
+
)
|
|
90
|
+
}]);
|
|
91
|
+
setLoading(false);
|
|
75
92
|
}
|
|
76
|
-
}, [
|
|
93
|
+
}, [pathname]);
|
|
77
94
|
|
|
78
95
|
useEffect(() => {
|
|
79
96
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
@@ -1,41 +1,52 @@
|
|
|
1
|
-
|
|
1
|
+
let searchIndex: any = null;
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
3
|
+
async function getSearchIndex() {
|
|
4
|
+
if (!searchIndex) {
|
|
5
|
+
const { Document } = await import('flexsearch');
|
|
6
|
+
searchIndex = new Document({
|
|
7
|
+
tokenize: 'full',
|
|
8
|
+
document: {
|
|
9
|
+
id: 'url',
|
|
10
|
+
index: 'content',
|
|
11
|
+
store: ['title', 'pageTitle'],
|
|
12
|
+
},
|
|
13
|
+
context: {
|
|
14
|
+
resolution: 9,
|
|
15
|
+
depth: 2,
|
|
16
|
+
bidirectional: true
|
|
17
|
+
}
|
|
18
|
+
});
|
|
14
19
|
}
|
|
15
|
-
|
|
20
|
+
return searchIndex;
|
|
21
|
+
}
|
|
16
22
|
|
|
17
|
-
export function addToSearchIndex(filename: string, data: any, content: string) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
export async function addToSearchIndex(filename: string, data: any, content: string) {
|
|
24
|
+
try {
|
|
25
|
+
const index = await getSearchIndex();
|
|
26
|
+
index.add({
|
|
27
|
+
url: filename,
|
|
28
|
+
title: data.title || '',
|
|
29
|
+
content: content,
|
|
30
|
+
pageTitle: data.title
|
|
31
|
+
});
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.warn(`Failed to index ${filename}:`, error);
|
|
34
|
+
}
|
|
24
35
|
}
|
|
25
36
|
|
|
26
37
|
export function search(query: string, options = {}) {
|
|
27
38
|
if (!searchIndex) return [];
|
|
28
|
-
|
|
39
|
+
|
|
29
40
|
const result = searchIndex.search(query, {
|
|
30
41
|
...options,
|
|
31
42
|
enrich: true,
|
|
32
|
-
});
|
|
43
|
+
}) as any[];
|
|
33
44
|
|
|
34
45
|
if (result.length === 0) {
|
|
35
46
|
return [];
|
|
36
47
|
}
|
|
37
48
|
|
|
38
|
-
return result[0].result.map((item) => ({
|
|
49
|
+
return result[0].result.map((item: any) => ({
|
|
39
50
|
url: item.id,
|
|
40
51
|
title: item.doc?.title || '',
|
|
41
52
|
pageTitle: item.doc?.pageTitle || '',
|
package/src/components/store.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { ComponentType } from 'preact';
|
|
|
2
2
|
import { type RouteComponentProps } from 'wouter-preact';
|
|
3
3
|
import docsConfig from '../../docs/dory.json' with { type: 'json' };
|
|
4
4
|
import type { DoryConfig } from '../types/config';
|
|
5
|
-
import {
|
|
5
|
+
import { addToSearchIndex } from './search-index';
|
|
6
6
|
|
|
7
7
|
const config = docsConfig as DoryConfig;
|
|
8
8
|
|
|
@@ -108,16 +108,26 @@ export async function preloadFrontmatter() {
|
|
|
108
108
|
// Add preloaded content to search index
|
|
109
109
|
async function addPreloadedContentToSearch() {
|
|
110
110
|
if (!preloadedFrontMatter) return;
|
|
111
|
+
|
|
112
|
+
const mdxFiles = import.meta.glob<{ default: string }>('../../docs/**/*.mdx', { query: '?raw' });
|
|
113
|
+
const fileEntries = Object.entries(mdxFiles);
|
|
111
114
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
115
|
+
await Promise.all(
|
|
116
|
+
preloadedFrontMatter.map(async (fm) => {
|
|
117
|
+
const fileEntry = fileEntries.find(([filePath]) => pathFromFilename(filePath) === fm.path);
|
|
118
|
+
if (fileEntry) {
|
|
119
|
+
try {
|
|
120
|
+
const [, loader] = fileEntry;
|
|
121
|
+
const rawContent = (await loader()).default;
|
|
122
|
+
const { content } = parseFrontMatter(rawContent);
|
|
123
|
+
|
|
124
|
+
await addToSearchIndex(fm.path, { title: fm.title }, content);
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.warn(`Failed to index ${fm.path}:`, error);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
);
|
|
121
131
|
}
|
|
122
132
|
|
|
123
133
|
// Load frontmatter for a specific pathname (optimized with fallback)
|
|
@@ -131,7 +141,7 @@ export async function loadMDXFrontMatterForPath(pathname: string) {
|
|
|
131
141
|
if (completeFrontMatter.find(fm => fm.path === pathname)) return;
|
|
132
142
|
|
|
133
143
|
// Fallback to dynamic loading
|
|
134
|
-
const mdxFiles = import.meta.glob<{ default: string }>('../../docs/**/*.mdx', { query: 'raw' });
|
|
144
|
+
const mdxFiles = import.meta.glob<{ default: string }>('../../docs/**/*.mdx', { query: '?raw' });
|
|
135
145
|
const fileEntries = Object.entries(mdxFiles);
|
|
136
146
|
|
|
137
147
|
const fileEntry = fileEntries.find(([path]) => pathFromFilename(path) === pathname);
|
|
@@ -143,12 +153,7 @@ export async function loadMDXFrontMatterForPath(pathname: string) {
|
|
|
143
153
|
const filename = pathFromFilename(path);
|
|
144
154
|
|
|
145
155
|
updateNavigationTitle(filename, data.title);
|
|
146
|
-
|
|
147
|
-
url: filename,
|
|
148
|
-
title: data.title || '',
|
|
149
|
-
content,
|
|
150
|
-
pageTitle: data.title || ''
|
|
151
|
-
});
|
|
156
|
+
await addToSearchIndex(filename, data, content);
|
|
152
157
|
|
|
153
158
|
const frontmatterItem = { ...data, path: filename };
|
|
154
159
|
completeFrontMatter = [...completeFrontMatter, frontmatterItem];
|
|
@@ -162,7 +167,7 @@ export async function loadAllMDXFrontMatter(pathname: string) {
|
|
|
162
167
|
}
|
|
163
168
|
|
|
164
169
|
// Fallback to dynamic loading
|
|
165
|
-
const mdxFiles = import.meta.glob<{ default: string }>('../../docs/**/*.mdx', { query: 'raw' });
|
|
170
|
+
const mdxFiles = import.meta.glob<{ default: string }>('../../docs/**/*.mdx', { query: '?raw' });
|
|
166
171
|
const fileEntries = Object.entries(mdxFiles);
|
|
167
172
|
|
|
168
173
|
const remainingEntries = fileEntries.filter(([path]) => pathFromFilename(path) !== pathname);
|
|
@@ -175,12 +180,7 @@ export async function loadAllMDXFrontMatter(pathname: string) {
|
|
|
175
180
|
const filename = pathFromFilename(path);
|
|
176
181
|
|
|
177
182
|
updateNavigationTitle(filename, data.title);
|
|
178
|
-
|
|
179
|
-
url: filename,
|
|
180
|
-
title: data.title || '',
|
|
181
|
-
content,
|
|
182
|
-
pageTitle: data.title || ''
|
|
183
|
-
});
|
|
183
|
+
await addToSearchIndex(filename, data, content);
|
|
184
184
|
|
|
185
185
|
newFrontmatter.push({ ...data, path: filename });
|
|
186
186
|
})
|
package/src/embed/loader.ts
CHANGED
|
@@ -47,8 +47,9 @@ if (typeof window !== 'undefined' && window.DoryDocs) {
|
|
|
47
47
|
widgetLoading = true;
|
|
48
48
|
widgetLoadPromise = new Promise((resolve, reject) => {
|
|
49
49
|
const script = document.createElement('script');
|
|
50
|
-
script.src = `${
|
|
50
|
+
script.src = `${baseUrl}/embed-widget.js`;
|
|
51
51
|
script.async = true;
|
|
52
|
+
script.type = 'module';
|
|
52
53
|
|
|
53
54
|
script.onload = () => {
|
|
54
55
|
// Widget script exposes window.__DoryWidget__
|
package/src/main.tsx
CHANGED
|
@@ -2,6 +2,7 @@ import { render } from 'preact'
|
|
|
2
2
|
import '@clidey/ux/styles.css';
|
|
3
3
|
import { App } from './app.tsx'
|
|
4
4
|
import { withMDX } from './components/mdx-provider.tsx'
|
|
5
|
+
import { ErrorBoundary } from './components/error-boundary.tsx'
|
|
5
6
|
import { loadFonts } from './utils/fonts'
|
|
6
7
|
import { loadColors } from './utils/colors'
|
|
7
8
|
import { Toaster } from '@clidey/ux'
|
|
@@ -16,7 +17,9 @@ const AppWithMDX = withMDX(App)
|
|
|
16
17
|
render(
|
|
17
18
|
<ThemeProvider storageKey="@clidey/dory/theme">
|
|
18
19
|
<Toaster />
|
|
19
|
-
<
|
|
20
|
+
<ErrorBoundary>
|
|
21
|
+
<AppWithMDX />
|
|
22
|
+
</ErrorBoundary>
|
|
20
23
|
</ThemeProvider>,
|
|
21
24
|
document.getElementById('app')!
|
|
22
25
|
)
|
|
@@ -327,7 +327,8 @@ export function APIPlayground({
|
|
|
327
327
|
const [isLoading, setIsLoading] = useState(false)
|
|
328
328
|
const [response, setResponse] = useState<ResponseData | null>(null)
|
|
329
329
|
const [error, setError] = useState<string | null>(null)
|
|
330
|
-
const
|
|
330
|
+
const { theme } = useTheme();
|
|
331
|
+
const isDark = theme === 'dark' || (theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
|
331
332
|
const [selectedServer, setSelectedServer] = useState<Server | null>(servers?.[0] || null)
|
|
332
333
|
|
|
333
334
|
// Request configuration state
|
package/src/mdx/fence.tsx
CHANGED
|
@@ -14,7 +14,8 @@ export function Fence({
|
|
|
14
14
|
children: string
|
|
15
15
|
language: string
|
|
16
16
|
}) {
|
|
17
|
-
const
|
|
17
|
+
const { theme } = useTheme();
|
|
18
|
+
const isDark = theme === 'dark' || (theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
|
18
19
|
|
|
19
20
|
if (language === 'mermaid') {
|
|
20
21
|
return (
|
package/src/mdx/latex.tsx
CHANGED
|
@@ -1,26 +1,93 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
|
|
1
|
+
import { ExclamationTriangleIcon } from '@heroicons/react/24/outline'
|
|
2
|
+
import { useEffect, useRef, useState } from 'react'
|
|
3
|
+
|
|
4
|
+
function extractText(node: React.ReactNode): string {
|
|
5
|
+
if (typeof node === 'string') return node;
|
|
6
|
+
if (typeof node === 'number') return String(node);
|
|
7
|
+
if (!node) return '';
|
|
8
|
+
if (Array.isArray(node)) return node.map(extractText).join('');
|
|
9
|
+
if (typeof node === 'object' && 'props' in node) return extractText((node as any).props.children);
|
|
10
|
+
return '';
|
|
11
|
+
}
|
|
4
12
|
|
|
5
13
|
interface LatexProps {
|
|
6
|
-
children:
|
|
14
|
+
children: React.ReactNode
|
|
7
15
|
}
|
|
8
16
|
|
|
9
17
|
export function Latex({ children }: LatexProps) {
|
|
18
|
+
const expression = extractText(children).trim();
|
|
10
19
|
const containerRef = useRef<HTMLDivElement>(null)
|
|
20
|
+
const [error, setError] = useState<string | null>(null)
|
|
21
|
+
const [isLoading, setIsLoading] = useState(true)
|
|
11
22
|
|
|
12
23
|
useEffect(() => {
|
|
13
|
-
|
|
24
|
+
let mounted = true
|
|
25
|
+
|
|
26
|
+
const renderLatex = async () => {
|
|
14
27
|
try {
|
|
15
|
-
|
|
28
|
+
setError(null)
|
|
29
|
+
setIsLoading(true)
|
|
30
|
+
|
|
31
|
+
const [katexModule] = await Promise.all([
|
|
32
|
+
import('katex'),
|
|
33
|
+
import('katex/dist/katex.min.css'),
|
|
34
|
+
])
|
|
35
|
+
const katex = katexModule.default
|
|
36
|
+
|
|
37
|
+
if (!mounted || !containerRef.current) return
|
|
38
|
+
|
|
39
|
+
katex.render(expression, containerRef.current, {
|
|
16
40
|
displayMode: true,
|
|
17
41
|
throwOnError: false,
|
|
18
42
|
})
|
|
43
|
+
setIsLoading(false)
|
|
19
44
|
} catch (error) {
|
|
20
|
-
|
|
45
|
+
if (mounted) {
|
|
46
|
+
console.error('Error rendering LaTeX:', error)
|
|
47
|
+
setError(error instanceof Error ? error.message : 'Failed to render LaTeX')
|
|
48
|
+
setIsLoading(false)
|
|
49
|
+
}
|
|
21
50
|
}
|
|
22
51
|
}
|
|
23
|
-
}, [children])
|
|
24
52
|
|
|
25
|
-
|
|
53
|
+
renderLatex()
|
|
54
|
+
|
|
55
|
+
return () => {
|
|
56
|
+
mounted = false
|
|
57
|
+
}
|
|
58
|
+
}, [expression])
|
|
59
|
+
|
|
60
|
+
if (error) {
|
|
61
|
+
return (
|
|
62
|
+
<div className="my-4">
|
|
63
|
+
<div className="flex flex-col items-center justify-center py-12 px-6 text-center bg-gradient-to-br from-red-50 to-red-100 dark:from-red-900/20 dark:to-red-800/20 border-2 border-dashed border-red-300 dark:border-red-700 rounded-lg">
|
|
64
|
+
<div className="w-16 h-16 mb-4 flex items-center justify-center bg-red-100 dark:bg-red-900/30 rounded-full">
|
|
65
|
+
<ExclamationTriangleIcon className="w-8 h-8 text-red-500" />
|
|
66
|
+
</div>
|
|
67
|
+
<h3 className="text-lg font-semibold text-red-700 dark:text-red-300 mb-2">
|
|
68
|
+
LaTeX Rendering Error
|
|
69
|
+
</h3>
|
|
70
|
+
<p className="text-red-600 dark:text-red-400 mb-4 max-w-md">
|
|
71
|
+
The LaTeX expression could not be rendered.
|
|
72
|
+
</p>
|
|
73
|
+
<div className="bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-800 rounded-md p-3 max-w-md">
|
|
74
|
+
<p className="text-sm text-red-700 dark:text-red-300 font-mono break-all">
|
|
75
|
+
{error}
|
|
76
|
+
</p>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<div className="my-4">
|
|
85
|
+
{isLoading && (
|
|
86
|
+
<div className="flex items-center justify-center py-4">
|
|
87
|
+
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-500" />
|
|
88
|
+
</div>
|
|
89
|
+
)}
|
|
90
|
+
<div ref={containerRef} className={isLoading ? 'hidden' : ''} />
|
|
91
|
+
</div>
|
|
92
|
+
)
|
|
26
93
|
}
|
package/src/mdx/mdx.tsx
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { useEffect, useRef } from 'preact/hooks';
|
|
2
|
+
import { lazy, Suspense } from 'preact/compat';
|
|
2
3
|
import { usePathname } from 'wouter-preact/use-browser-location';
|
|
3
4
|
import { completeFrontMatter } from '../components/store';
|
|
5
|
+
import { Loading } from '../components/loading';
|
|
4
6
|
|
|
5
7
|
export { h1, h2, h3, h4, h5, h6, p, Row, Col, Anchor as a, Image as img } from './text';
|
|
6
8
|
export { Code as code, CodeGroup } from './code';
|
|
@@ -17,11 +19,24 @@ export { ResponseField, Properties, Property } from './open-api';
|
|
|
17
19
|
export { Steps, Step } from './step';
|
|
18
20
|
export { Table as table, Th as th, Td as td } from './table';
|
|
19
21
|
export { UnorderedList as ul, OrderedList as ol, ListItem as li } from './list';
|
|
20
|
-
export { APIPlayground } from './api-playground';
|
|
21
|
-
export { WebSocketPlayground } from './websocket-playground';
|
|
22
22
|
export { AsyncAPI } from './async-api';
|
|
23
23
|
export { Source } from './source';
|
|
24
24
|
|
|
25
|
+
// Lazy-loaded heavy components — only downloaded when a page uses them
|
|
26
|
+
const LazyAPIPlayground = lazy(() => import('./api-playground').then(m => ({ default: m.APIPlayground })));
|
|
27
|
+
export const APIPlayground = (props: any) => (
|
|
28
|
+
<Suspense fallback={<Loading />}>
|
|
29
|
+
<LazyAPIPlayground {...props} />
|
|
30
|
+
</Suspense>
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const LazyWebSocketPlayground = lazy(() => import('./websocket-playground').then(m => ({ default: m.WebSocketPlayground })));
|
|
34
|
+
export const WebSocketPlayground = (props: any) => (
|
|
35
|
+
<Suspense fallback={<Loading />}>
|
|
36
|
+
<LazyWebSocketPlayground {...props} />
|
|
37
|
+
</Suspense>
|
|
38
|
+
);
|
|
39
|
+
|
|
25
40
|
export const wrapper = ({ children }: { children: preact.ComponentChildren }) => {
|
|
26
41
|
const ref = useRef<HTMLDivElement>(null);
|
|
27
42
|
const pathname = usePathname();
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
import { readFileSync, existsSync } from 'fs';
|
|
3
|
+
import { resolve } from 'path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Conditionally injects analytics scripts based on dory.json config.
|
|
7
|
+
* PostHog is only injected if `analytics.posthog.token` is configured.
|
|
8
|
+
*/
|
|
9
|
+
export function analyticsInjector(): Plugin {
|
|
10
|
+
return {
|
|
11
|
+
name: 'analytics-injector',
|
|
12
|
+
transformIndexHtml(html) {
|
|
13
|
+
const doryJsonPath = resolve(process.cwd(), 'docs', 'dory.json');
|
|
14
|
+
if (!existsSync(doryJsonPath)) return html;
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const config = JSON.parse(readFileSync(doryJsonPath, 'utf-8'));
|
|
18
|
+
|
|
19
|
+
if (config.analytics?.posthog?.token) {
|
|
20
|
+
const token = config.analytics.posthog.token;
|
|
21
|
+
const apiHost = config.analytics.posthog.apiHost || 'https://us.i.posthog.com';
|
|
22
|
+
|
|
23
|
+
const script = `
|
|
24
|
+
<script>
|
|
25
|
+
!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.crossOrigin="anonymous",p.async=!0,p.src=s.api_host.replace(".i.posthog.com","-assets.i.posthog.com")+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="init capture identify setPersonProperties group resetGroups reset get_distinct_id getGroups get_session_id opt_in_capturing opt_out_capturing has_opted_in_capturing has_opted_out_capturing".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[]);
|
|
26
|
+
posthog.init('${token}', {
|
|
27
|
+
api_host: '${apiHost}',
|
|
28
|
+
person_profiles: 'identified_only',
|
|
29
|
+
})
|
|
30
|
+
</script>`;
|
|
31
|
+
|
|
32
|
+
return html.replace('</head>', `${script}\n </head>`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return html;
|
|
36
|
+
} catch (error) {
|
|
37
|
+
const detail = error instanceof SyntaxError ? error.message : String(error);
|
|
38
|
+
console.error(`Failed to parse dory.json for analytics injection: ${detail}`);
|
|
39
|
+
return html;
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|