@clidey/dory 0.29.0 → 0.30.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/bin/dist/dory.js +115 -29
- package/bin/dist/dory.js.map +1 -1
- package/package.json +12 -4
- package/.dockerignore +0 -4
- package/CLAUDE.md +0 -178
- package/CODE_OF_CONDUCT.md +0 -128
- package/CONTRIBUTING.md +0 -91
- package/SECURITY.md +0 -11
- package/bin/dory.ts +0 -433
- package/dist/favicon.svg +0 -75
- package/dist/frontmatter.json +0 -77
- package/dist/index.html +0 -55
- package/dist/llms.txt +0 -561
- package/dist/logo/logo.svg +0 -18
- package/docs/api-playground-demo.mdx +0 -119
- package/docs/api-reference/asyncapi.json +0 -324
- package/docs/api-reference/openapi.json +0 -366
- package/docs/api-reference/operations/create.mdx +0 -5
- package/docs/api-reference/operations/delete.mdx +0 -5
- package/docs/api-reference/operations/get.mdx +0 -5
- package/docs/api-reference/operations/update.mdx +0 -5
- package/docs/api-reference/websocket-operations/receive-chat-message.mdx +0 -5
- package/docs/api-reference/websocket-operations/receive-notifications.mdx +0 -5
- package/docs/api-reference/websocket-operations/receive-presence-update.mdx +0 -5
- package/docs/api-reference/websocket-operations/send-chat-message.mdx +0 -5
- package/docs/api-reference/websocket-operations/send-presence-update.mdx +0 -5
- package/docs/dory.json +0 -64
- package/docs/favicon.svg +0 -75
- package/docs/features.mdx +0 -440
- package/docs/introduction.mdx +0 -44
- package/docs/logo/logo.svg +0 -18
- package/docs/websocket-playground-demo.mdx +0 -135
- package/index.html +0 -54
- package/scripts/generate-frontmatter-json.js +0 -154
- package/scripts/generate-llm-txt.js +0 -115
- package/src/app.css +0 -5
- package/src/app.tsx +0 -13
- package/src/components/dropdown.tsx +0 -53
- package/src/components/hooks.tsx +0 -28
- package/src/components/loading.tsx +0 -21
- package/src/components/mdx-provider.tsx +0 -13
- package/src/components/navigation.tsx +0 -58
- package/src/components/notification.tsx +0 -13
- package/src/components/routes.tsx +0 -92
- package/src/components/search-index.ts +0 -43
- package/src/components/search.tsx +0 -435
- package/src/components/store.ts +0 -216
- package/src/config/mdx.js +0 -12
- package/src/config/mdx.ts +0 -12
- package/src/main.tsx +0 -22
- package/src/mdx/accordion.tsx +0 -41
- package/src/mdx/api-playground.tsx +0 -659
- package/src/mdx/api.tsx +0 -354
- package/src/mdx/async-api.tsx +0 -353
- package/src/mdx/callout.tsx +0 -64
- package/src/mdx/card.tsx +0 -54
- package/src/mdx/code.tsx +0 -289
- package/src/mdx/expandable.tsx +0 -36
- package/src/mdx/fence.tsx +0 -74
- package/src/mdx/icon.tsx +0 -73
- package/src/mdx/latex.tsx +0 -26
- package/src/mdx/list.tsx +0 -7
- package/src/mdx/mdx.tsx +0 -47
- package/src/mdx/mermaid.tsx +0 -147
- package/src/mdx/open-api.tsx +0 -311
- package/src/mdx/processor.ts +0 -174
- package/src/mdx/source.tsx +0 -58
- package/src/mdx/step.tsx +0 -51
- package/src/mdx/table.tsx +0 -20
- package/src/mdx/tag.tsx +0 -53
- package/src/mdx/text.tsx +0 -99
- package/src/mdx/websocket-playground.tsx +0 -824
- package/src/plugins/docs-assets.ts +0 -79
- package/src/plugins/frontmatter-dev-server.ts +0 -158
- package/src/plugins/frontmatter-generator.ts +0 -157
- package/src/plugins/html-metadata-injector.ts +0 -76
- package/src/plugins/llm-txt-dev-server.ts +0 -183
- package/src/plugins/llm-txt-generator.ts +0 -186
- package/src/plugins/sanitize.ts +0 -163
- package/src/plugins/unified-mdx-plugin.ts +0 -35
- package/src/theme.scss +0 -72
- package/src/types/config.ts +0 -47
- package/src/ui/header.tsx +0 -91
- package/src/ui/layout.tsx +0 -226
- package/src/ui/mobile-navigation.tsx +0 -64
- package/src/ui/prev-next-link.tsx +0 -74
- package/src/ui/table-of-content.tsx +0 -166
- package/src/utils/colors.ts +0 -54
- package/src/utils/fonts.ts +0 -23
- package/src/utils/functions.ts +0 -12
- package/src/vite-env.d.ts +0 -1
- package/tsconfig.app.json +0 -33
- package/tsconfig.bin.json +0 -24
- package/tsconfig.json +0 -7
- package/tsconfig.node.json +0 -26
- package/vite.config.ts +0 -43
package/src/ui/layout.tsx
DELETED
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
import type { ComponentChildren } from 'preact';
|
|
2
|
-
import { useCallback, useEffect, useMemo, useState } from 'preact/hooks';
|
|
3
|
-
import { useLocation } from 'wouter-preact';
|
|
4
|
-
import { Navigation } from '../components/navigation';
|
|
5
|
-
import { ALL_NAVIGATION, ALL_OPENAPI, completeFrontMatter, loadMDXFrontMatterForPath, loadAllMDXFrontMatter, ALL_PAGES, pathFromFilename, preloadFrontmatter, ALL_ASYNCAPI } from '../components/store';
|
|
6
|
-
import { OpenAPI } from '../mdx/open-api';
|
|
7
|
-
import { AsyncAPI } from '../mdx/async-api';
|
|
8
|
-
import { Header } from './header';
|
|
9
|
-
import { PrevNextLinks } from './prev-next-link';
|
|
10
|
-
import { TableOfContents } from './table-of-content';
|
|
11
|
-
import { Loading } from '../components/loading';
|
|
12
|
-
import Dropdown from '../components/dropdown';
|
|
13
|
-
import { useNotification } from '../components/notification';
|
|
14
|
-
import { SparkleIcon } from 'lucide-react';
|
|
15
|
-
|
|
16
|
-
interface LayoutProps {
|
|
17
|
-
children: ComponentChildren;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const prompt = (pathname: string) => `Please read and analyze the content from this documentation page: ${window.location.origin}${pathname}.mdx\n\n` +
|
|
21
|
-
`Then, help me understand and answer any questions I have about it. ` +
|
|
22
|
-
`Please provide clear, detailed explanations and examples where relevant.`;
|
|
23
|
-
|
|
24
|
-
export default function Layout({ children }: LayoutProps) {
|
|
25
|
-
const [loading, setLoading] = useState(true);
|
|
26
|
-
const [pathname] = useLocation();
|
|
27
|
-
const { showNotification } = useNotification();
|
|
28
|
-
|
|
29
|
-
const handleOpenMDX = useCallback(() => {
|
|
30
|
-
window.open(`${window.location.origin}${pathname}.mdx`, '_blank');
|
|
31
|
-
}, [pathname]);
|
|
32
|
-
|
|
33
|
-
const handleCopyMDX = useCallback(() => {
|
|
34
|
-
ALL_PAGES[pathFromFilename(pathname)]().then((mdx) => {
|
|
35
|
-
navigator.clipboard.writeText(mdx.default.toString());
|
|
36
|
-
});
|
|
37
|
-
showNotification('Copied to clipboard');
|
|
38
|
-
}, []);
|
|
39
|
-
const handleOpenChatGPT = useCallback(() => {
|
|
40
|
-
const url = `https://chat.openai.com/?q=${encodeURIComponent(prompt(pathname))}`;
|
|
41
|
-
window.open(url, '_blank');
|
|
42
|
-
}, [pathname]);
|
|
43
|
-
const handleOpenAnthropic = useCallback(() => {
|
|
44
|
-
const url = `https://claude.ai/new?q=${encodeURIComponent(prompt(pathname))}`;
|
|
45
|
-
window.open(url, '_blank');
|
|
46
|
-
}, [pathname]);
|
|
47
|
-
|
|
48
|
-
const dropdownItems = useMemo(() => [
|
|
49
|
-
{
|
|
50
|
-
label: 'Open MDX',
|
|
51
|
-
onClick: handleOpenMDX,
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
label: 'Copy MDX',
|
|
55
|
-
onClick: handleCopyMDX
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
label: 'Open in ChatGPT',
|
|
59
|
-
onClick: handleOpenChatGPT
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
label: 'Open in Anthropic',
|
|
63
|
-
onClick: handleOpenAnthropic
|
|
64
|
-
}
|
|
65
|
-
], [handleOpenMDX, handleCopyMDX, handleOpenChatGPT, handleOpenAnthropic]);
|
|
66
|
-
|
|
67
|
-
useEffect(() => {
|
|
68
|
-
// Try to preload frontmatter first (optimized approach)
|
|
69
|
-
preloadFrontmatter().then(() => {
|
|
70
|
-
// If preloading succeeds, we're done
|
|
71
|
-
setLoading(false);
|
|
72
|
-
}).catch(() => {
|
|
73
|
-
// Fallback to the original approach
|
|
74
|
-
loadMDXFrontMatterForPath(pathname).then(() => {
|
|
75
|
-
loadAllMDXFrontMatter(pathname).then(() => {
|
|
76
|
-
setLoading(false);
|
|
77
|
-
}).catch(() => {
|
|
78
|
-
setLoading(false);
|
|
79
|
-
});
|
|
80
|
-
}).catch(() => {
|
|
81
|
-
setLoading(false);
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
}, [pathname]);
|
|
85
|
-
|
|
86
|
-
const { title: group, group: title, page } = useMemo(() => {
|
|
87
|
-
const group = ALL_NAVIGATION.flatMap((tab) =>
|
|
88
|
-
tab.groups.find((group) =>
|
|
89
|
-
group.pages.some((page) => page.href === pathname)
|
|
90
|
-
)
|
|
91
|
-
);
|
|
92
|
-
const page = completeFrontMatter.find((page) => page.path === pathname);
|
|
93
|
-
|
|
94
|
-
return { group: group[0]?.title, title: page?.title, page };
|
|
95
|
-
}, [pathname, loading]);
|
|
96
|
-
|
|
97
|
-
// Update document title and meta tags when page changes
|
|
98
|
-
useEffect(() => {
|
|
99
|
-
if (page?.title) {
|
|
100
|
-
document.title = page.title;
|
|
101
|
-
|
|
102
|
-
// Update Open Graph title
|
|
103
|
-
const ogTitle = document.querySelector('meta[property="og:title"]');
|
|
104
|
-
if (ogTitle) {
|
|
105
|
-
ogTitle.setAttribute('content', page.title);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Update Twitter title
|
|
109
|
-
const twitterTitle = document.querySelector('meta[name="twitter:title"]');
|
|
110
|
-
if (twitterTitle) {
|
|
111
|
-
twitterTitle.setAttribute('content', page.title);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (page?.description) {
|
|
116
|
-
// Update standard meta description
|
|
117
|
-
const metaDescription = document.querySelector('meta[name="description"]');
|
|
118
|
-
if (metaDescription) {
|
|
119
|
-
metaDescription.setAttribute('content', page.description);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Update Open Graph description
|
|
123
|
-
const ogDescription = document.querySelector('meta[property="og:description"]');
|
|
124
|
-
if (ogDescription) {
|
|
125
|
-
ogDescription.setAttribute('content', page.description);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Update Twitter description
|
|
129
|
-
const twitterDescription = document.querySelector('meta[name="twitter:description"]');
|
|
130
|
-
if (twitterDescription) {
|
|
131
|
-
twitterDescription.setAttribute('content', page.description);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Update canonical URL
|
|
136
|
-
const ogUrl = document.querySelector('meta[property="og:url"]');
|
|
137
|
-
if (ogUrl) {
|
|
138
|
-
ogUrl.setAttribute('content', window.location.href);
|
|
139
|
-
}
|
|
140
|
-
}, [page]);
|
|
141
|
-
|
|
142
|
-
const tab = useMemo(() => {
|
|
143
|
-
return completeFrontMatter.find((page) => page.path === pathname);
|
|
144
|
-
}, [pathname, loading]);
|
|
145
|
-
|
|
146
|
-
const openAPIJSON = useMemo(() => {
|
|
147
|
-
if (tab == null || tab.openapi == null) {
|
|
148
|
-
return null;
|
|
149
|
-
}
|
|
150
|
-
const firstDirectory = `/${tab.path.split('/')[1]}`;
|
|
151
|
-
return ALL_OPENAPI[`${firstDirectory}/openapi.json`].default;
|
|
152
|
-
}, [tab, loading]);
|
|
153
|
-
|
|
154
|
-
const { method, path } = useMemo(() => {
|
|
155
|
-
if (tab == null || tab.openapi == null) {
|
|
156
|
-
return { method: '', path: '' };
|
|
157
|
-
}
|
|
158
|
-
return { method: tab.openapi.split(' ')[0], path: tab.openapi.split(' ')[1] };
|
|
159
|
-
}, [tab, loading]);
|
|
160
|
-
|
|
161
|
-
const asyncAPIJSON = useMemo(() => {
|
|
162
|
-
if (tab == null || tab.asyncapi == null) {
|
|
163
|
-
return null;
|
|
164
|
-
}
|
|
165
|
-
const firstDirectory = `/${tab.path.split('/')[1]}`;
|
|
166
|
-
return ALL_ASYNCAPI[`${firstDirectory}/asyncapi.json`].default;
|
|
167
|
-
}, [tab, loading]);
|
|
168
|
-
|
|
169
|
-
const { operation, channel } = useMemo(() => {
|
|
170
|
-
if (tab == null || tab.asyncapi == null) {
|
|
171
|
-
return { operation: '', channel: '' };
|
|
172
|
-
}
|
|
173
|
-
return { operation: tab.asyncapi.split(' ')[0].toLowerCase(), channel: tab.asyncapi.split(' ')[1] };
|
|
174
|
-
}, [tab, loading]);
|
|
175
|
-
|
|
176
|
-
if (loading) return <div className="h-[25vh] flex grow"><Loading /></div>;
|
|
177
|
-
|
|
178
|
-
return (
|
|
179
|
-
<div className="flex w-full flex-col">
|
|
180
|
-
<Header />
|
|
181
|
-
<div id="container" className="relative flex w-full max-w-8xl sm:px-2 lg:px-8">
|
|
182
|
-
<div className="hidden lg:relative lg:block lg:flex-none">
|
|
183
|
-
<div className="sticky top-[4.75rem] -ml-0.5 h-[calc(100vh-4.75rem)] w-64 overflow-x-hidden overflow-y-auto py-16 pr-8 pl-0.5 xl:w-72 xl:pr-16">
|
|
184
|
-
<Navigation />
|
|
185
|
-
</div>
|
|
186
|
-
</div>
|
|
187
|
-
<main className="flex px-4 py-16 sm:px-6 lg:px-8 grow">
|
|
188
|
-
<div className="min-w-0 w-full">
|
|
189
|
-
<article className="h-full flex flex-1 flex-col">
|
|
190
|
-
<div className="flex justify-between items-start">
|
|
191
|
-
<header className="mb-8">
|
|
192
|
-
{title && (
|
|
193
|
-
<h1 className="font-display text-lg tracking-tight">
|
|
194
|
-
{title}
|
|
195
|
-
</h1>
|
|
196
|
-
)}
|
|
197
|
-
{group && (
|
|
198
|
-
<p className="text-4xl tracking-tight">
|
|
199
|
-
{group}
|
|
200
|
-
</p>
|
|
201
|
-
)}
|
|
202
|
-
</header>
|
|
203
|
-
{
|
|
204
|
-
openAPIJSON == null && asyncAPIJSON == null &&
|
|
205
|
-
<Dropdown className="gap-0" buttonLabel={<SparkleIcon className="w-4 h-4" />} items={dropdownItems} />
|
|
206
|
-
}
|
|
207
|
-
</div>
|
|
208
|
-
<div className="flex-1 min-h-[calc(100vh-2rem)]">
|
|
209
|
-
{openAPIJSON && tab && "openapi" in tab && method && path ?
|
|
210
|
-
<OpenAPI openAPIJson={JSON.parse(openAPIJSON)} method={method} path={path} />
|
|
211
|
-
: asyncAPIJSON && tab && "asyncapi" in tab && operation && channel ?
|
|
212
|
-
<AsyncAPI asyncAPIJson={JSON.parse(asyncAPIJSON)} operation={operation} channel={channel} />
|
|
213
|
-
: children}
|
|
214
|
-
</div>
|
|
215
|
-
<PrevNextLinks />
|
|
216
|
-
</article>
|
|
217
|
-
</div>
|
|
218
|
-
{
|
|
219
|
-
openAPIJSON == null && asyncAPIJSON == null &&
|
|
220
|
-
<TableOfContents />
|
|
221
|
-
}
|
|
222
|
-
</main>
|
|
223
|
-
</div>
|
|
224
|
-
</div>
|
|
225
|
-
);
|
|
226
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { Sheet, SheetContent, Button } from '@clidey/ux'
|
|
4
|
-
import { createContext, Suspense, useCallback, useContext, useRef, useState } from 'react'
|
|
5
|
-
|
|
6
|
-
import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline'
|
|
7
|
-
import { Navigation } from '../components/navigation'
|
|
8
|
-
|
|
9
|
-
const IsInsideMobileNavigationContext = createContext(false)
|
|
10
|
-
|
|
11
|
-
export function useIsInsideMobileNavigation() {
|
|
12
|
-
return useContext(IsInsideMobileNavigationContext)
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function useMobileNavigation() {
|
|
16
|
-
const [isOpen, setIsOpen] = useState(false)
|
|
17
|
-
const isOpenRef = useRef(isOpen)
|
|
18
|
-
isOpenRef.current = isOpen
|
|
19
|
-
|
|
20
|
-
const open = useCallback(() => setIsOpen(true), [])
|
|
21
|
-
const close = useCallback(() => setIsOpen(false), [])
|
|
22
|
-
const toggle = useCallback(() => setIsOpen(prev => !prev), [])
|
|
23
|
-
|
|
24
|
-
return {
|
|
25
|
-
isOpen,
|
|
26
|
-
isOpenRef,
|
|
27
|
-
open,
|
|
28
|
-
close,
|
|
29
|
-
toggle,
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function MobileNavigation() {
|
|
34
|
-
const isInsideMobileNavigation = useIsInsideMobileNavigation()
|
|
35
|
-
const { isOpen, open, close } = useMobileNavigation()
|
|
36
|
-
const ToggleIcon = isOpen ? XMarkIcon : Bars3Icon;
|
|
37
|
-
|
|
38
|
-
return (
|
|
39
|
-
<IsInsideMobileNavigationContext.Provider value={true}>
|
|
40
|
-
<Sheet open={isOpen} onOpenChange={(status) => {
|
|
41
|
-
if (!status) {
|
|
42
|
-
close();
|
|
43
|
-
}
|
|
44
|
-
}}>
|
|
45
|
-
<Button
|
|
46
|
-
variant="outline"
|
|
47
|
-
size="icon"
|
|
48
|
-
className="relative flex size-6 lg:hidden p-4"
|
|
49
|
-
aria-label="Toggle navigation"
|
|
50
|
-
onClick={open}
|
|
51
|
-
>
|
|
52
|
-
<ToggleIcon className="w-8 stroke-zinc-900 dark:stroke-white" />
|
|
53
|
-
</Button>
|
|
54
|
-
{!isInsideMobileNavigation && (
|
|
55
|
-
<Suspense fallback={null}>
|
|
56
|
-
<SheetContent side="left" className="w-full sm:max-w-sm p-8 overflow-y-auto flex flex-col justify-between">
|
|
57
|
-
<Navigation />
|
|
58
|
-
</SheetContent>
|
|
59
|
-
</Suspense>
|
|
60
|
-
)}
|
|
61
|
-
</Sheet>
|
|
62
|
-
</IsInsideMobileNavigationContext.Provider>
|
|
63
|
-
)
|
|
64
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { ArrowRightIcon } from "@heroicons/react/24/outline";
|
|
2
|
-
import classNames from "classnames";
|
|
3
|
-
import { ALL_NAVIGATION } from "../components/store";
|
|
4
|
-
import { usePathname } from "../components/hooks";
|
|
5
|
-
|
|
6
|
-
function PageLink({
|
|
7
|
-
title,
|
|
8
|
-
href,
|
|
9
|
-
dir = 'next',
|
|
10
|
-
...props
|
|
11
|
-
}: Omit<React.ComponentPropsWithoutRef<'div'>, 'dir' | 'title'> & {
|
|
12
|
-
title: string
|
|
13
|
-
href: string
|
|
14
|
-
dir?: 'previous' | 'next'
|
|
15
|
-
}) {
|
|
16
|
-
return (
|
|
17
|
-
<div {...props}>
|
|
18
|
-
<p className="font-display text-sm font-medium">
|
|
19
|
-
{dir === 'next' ? 'Next' : 'Previous'}
|
|
20
|
-
</p>
|
|
21
|
-
<div className="mt-1">
|
|
22
|
-
<a href={href} className={classNames('flex items-center gap-x-1 text-base font-semibold hover:opacity-80', {
|
|
23
|
-
'flex-row-reverse': dir === 'previous',
|
|
24
|
-
},
|
|
25
|
-
)}>
|
|
26
|
-
<p className={classNames("flex items-center gap-x-1 text-base font-semibold hover:opacity-80", {
|
|
27
|
-
"flex-row-reverse": dir === "previous"
|
|
28
|
-
})}>
|
|
29
|
-
{title}
|
|
30
|
-
<ArrowRightIcon
|
|
31
|
-
className={classNames(
|
|
32
|
-
'h-4 w-4 flex-none fill-current',
|
|
33
|
-
{
|
|
34
|
-
'rotate-180': dir === 'previous',
|
|
35
|
-
},
|
|
36
|
-
)}
|
|
37
|
-
/>
|
|
38
|
-
</p>
|
|
39
|
-
</a>
|
|
40
|
-
</div>
|
|
41
|
-
</div>
|
|
42
|
-
)
|
|
43
|
-
}
|
|
44
|
-
export function PrevNextLinks() {
|
|
45
|
-
const pathname = usePathname();
|
|
46
|
-
const allLinks = ALL_NAVIGATION.flatMap(tab =>
|
|
47
|
-
tab.groups.flatMap(group =>
|
|
48
|
-
group.pages.map(page => page.href)
|
|
49
|
-
)
|
|
50
|
-
);
|
|
51
|
-
const linkIndex = allLinks.findIndex(link => link === pathname);
|
|
52
|
-
const previousPage = linkIndex > -1 ? allLinks[linkIndex - 1] : null;
|
|
53
|
-
const nextPage = linkIndex > -1 ? allLinks[linkIndex + 1] : null;
|
|
54
|
-
|
|
55
|
-
const previousPageTitle = previousPage ? ALL_NAVIGATION
|
|
56
|
-
.flatMap(tab => tab.groups)
|
|
57
|
-
.flatMap(group => group.pages)
|
|
58
|
-
.find(page => page.href === previousPage)?.title : null;
|
|
59
|
-
const nextPageTitle = nextPage ? ALL_NAVIGATION
|
|
60
|
-
.flatMap(tab => tab.groups)
|
|
61
|
-
.flatMap(group => group.pages)
|
|
62
|
-
.find(page => page.href === nextPage)?.title : null;
|
|
63
|
-
|
|
64
|
-
if (!nextPage && !previousPage) {
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return (
|
|
69
|
-
<div className="mt-12 pt-6 flex border-t border-black/20 dark:border-white/20">
|
|
70
|
-
{previousPage && <PageLink dir="previous" title={previousPageTitle ?? ''} href={previousPage} />}
|
|
71
|
-
{nextPage && <PageLink className="ml-auto text-right" title={nextPageTitle ?? ''} href={nextPage} />}
|
|
72
|
-
</div>
|
|
73
|
-
)
|
|
74
|
-
}
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useState } from 'react'
|
|
2
|
-
import classNames from 'classnames'
|
|
3
|
-
|
|
4
|
-
export type Section = {
|
|
5
|
-
id: string
|
|
6
|
-
title: string
|
|
7
|
-
children: Array<Subsection>
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export type Subsection = {
|
|
11
|
-
id: string
|
|
12
|
-
title: string
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function TableOfContents() {
|
|
16
|
-
const [currentSection, setCurrentSection] = useState<string>('')
|
|
17
|
-
const [sections, setSections] = useState<Section[]>([])
|
|
18
|
-
const [isContentLoaded, setIsContentLoaded] = useState(false)
|
|
19
|
-
|
|
20
|
-
const collectHeadings = useCallback(() => {
|
|
21
|
-
const article = document.querySelector('article')
|
|
22
|
-
if (!article) return
|
|
23
|
-
|
|
24
|
-
// Wait for content to be loaded
|
|
25
|
-
const observer = new MutationObserver(() => {
|
|
26
|
-
const headings = article.querySelectorAll('h2:not([style*="display: none"]), h3:not([style*="display: none"])')
|
|
27
|
-
const sections: Section[] = []
|
|
28
|
-
let currentSection: Section | null = null
|
|
29
|
-
|
|
30
|
-
headings.forEach((heading) => {
|
|
31
|
-
// Check if heading is visible
|
|
32
|
-
const style = window.getComputedStyle(heading)
|
|
33
|
-
if (style.display === 'none' || style.visibility === 'hidden') return
|
|
34
|
-
|
|
35
|
-
const id = heading.id || heading.textContent?.toLowerCase().replace(/\s+/g, '-') || ''
|
|
36
|
-
heading.id = id
|
|
37
|
-
|
|
38
|
-
if (heading.tagName === 'H2') {
|
|
39
|
-
currentSection = {
|
|
40
|
-
id,
|
|
41
|
-
title: heading.textContent || '',
|
|
42
|
-
children: []
|
|
43
|
-
}
|
|
44
|
-
sections.push(currentSection)
|
|
45
|
-
} else if (heading.tagName === 'H3' && currentSection) {
|
|
46
|
-
currentSection.children.push({
|
|
47
|
-
id,
|
|
48
|
-
title: heading.textContent || ''
|
|
49
|
-
})
|
|
50
|
-
}
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
setSections(sections)
|
|
54
|
-
setIsContentLoaded(true)
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
observer.observe(article, {
|
|
58
|
-
childList: true,
|
|
59
|
-
subtree: true,
|
|
60
|
-
attributes: true,
|
|
61
|
-
attributeFilter: ['style', 'class']
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
return () => observer.disconnect()
|
|
65
|
-
}, [])
|
|
66
|
-
|
|
67
|
-
useEffect(() => {
|
|
68
|
-
const cleanup = collectHeadings()
|
|
69
|
-
return () => cleanup?.()
|
|
70
|
-
}, [collectHeadings])
|
|
71
|
-
|
|
72
|
-
const getHeadings = useCallback(() => {
|
|
73
|
-
return sections
|
|
74
|
-
.flatMap((section) => [section.id, ...section.children.map((child) => child.id)])
|
|
75
|
-
.map((id) => {
|
|
76
|
-
const el = document.getElementById(id)
|
|
77
|
-
if (!el) return null
|
|
78
|
-
const top = window.scrollY + el.getBoundingClientRect().top
|
|
79
|
-
return { id, top }
|
|
80
|
-
})
|
|
81
|
-
.filter((x): x is { id: string; top: number } => x !== null)
|
|
82
|
-
}, [sections])
|
|
83
|
-
|
|
84
|
-
useEffect(() => {
|
|
85
|
-
if (!isContentLoaded || sections.length === 0) return
|
|
86
|
-
const headings = getHeadings()
|
|
87
|
-
|
|
88
|
-
function onScroll() {
|
|
89
|
-
const top = window.scrollY
|
|
90
|
-
let current = headings[0].id
|
|
91
|
-
for (const heading of headings) {
|
|
92
|
-
if (top >= heading.top - 10) {
|
|
93
|
-
current = heading.id
|
|
94
|
-
} else {
|
|
95
|
-
break
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
setCurrentSection(current)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
window.addEventListener('scroll', onScroll, { passive: true })
|
|
102
|
-
onScroll()
|
|
103
|
-
return () => {
|
|
104
|
-
window.removeEventListener('scroll', onScroll)
|
|
105
|
-
}
|
|
106
|
-
}, [getHeadings, sections, isContentLoaded])
|
|
107
|
-
|
|
108
|
-
function isActive(section: Section | Subsection) {
|
|
109
|
-
return section.id === currentSection
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (!isContentLoaded) {
|
|
113
|
-
return null
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return (
|
|
117
|
-
<div className="hidden xl:sticky xl:top-[4.75rem] xl:-mr-6 xl:block xl:h-[calc(100vh-4.75rem)] xl:flex-none xl:overflow-y-auto xl:py-16 xl:pr-6 pl-8">
|
|
118
|
-
<nav aria-labelledby="on-this-page-title" className="w-56">
|
|
119
|
-
{sections.length > 0 && (
|
|
120
|
-
<>
|
|
121
|
-
<h2
|
|
122
|
-
id="on-this-page-title"
|
|
123
|
-
className="font-display text-sm font-medium text-slate-900 dark:text-white"
|
|
124
|
-
>
|
|
125
|
-
On this page
|
|
126
|
-
</h2>
|
|
127
|
-
<ol role="list" className="mt-4 space-y-3 text-sm">
|
|
128
|
-
{sections.map((section) => (
|
|
129
|
-
<li key={section.id}>
|
|
130
|
-
<h3>
|
|
131
|
-
<a
|
|
132
|
-
href={`#${section.id}`}
|
|
133
|
-
className={classNames({
|
|
134
|
-
"text-brand-foreground": isActive(section),
|
|
135
|
-
"font-normal text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-300": !isActive(section),
|
|
136
|
-
})}>
|
|
137
|
-
{section.title}
|
|
138
|
-
</a>
|
|
139
|
-
</h3>
|
|
140
|
-
{section.children.length > 0 && (
|
|
141
|
-
<ol role="list" className="mt-2 space-y-3 pl-5 text-slate-500 dark:text-slate-400">
|
|
142
|
-
{section.children.map((subSection) => (
|
|
143
|
-
<li key={subSection.id}>
|
|
144
|
-
<a
|
|
145
|
-
href={`#${subSection.id}`}
|
|
146
|
-
className={
|
|
147
|
-
isActive(subSection)
|
|
148
|
-
? 'text-brand-foreground'
|
|
149
|
-
: 'hover:text-slate-600 dark:hover:text-slate-300'
|
|
150
|
-
}
|
|
151
|
-
>
|
|
152
|
-
{subSection.title}
|
|
153
|
-
</a>
|
|
154
|
-
</li>
|
|
155
|
-
))}
|
|
156
|
-
</ol>
|
|
157
|
-
)}
|
|
158
|
-
</li>
|
|
159
|
-
))}
|
|
160
|
-
</ol>
|
|
161
|
-
</>
|
|
162
|
-
)}
|
|
163
|
-
</nav>
|
|
164
|
-
</div>
|
|
165
|
-
)
|
|
166
|
-
}
|
package/src/utils/colors.ts
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import docsConfig from '../../docs/dory.json' with { type: 'json' };
|
|
2
|
-
import type { DoryConfig } from '../types/config';
|
|
3
|
-
|
|
4
|
-
const config = docsConfig as DoryConfig;
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Converts hex color to RGB values for CSS variable usage
|
|
8
|
-
*/
|
|
9
|
-
function hexToRgb(hex: string): string {
|
|
10
|
-
// Remove # if present
|
|
11
|
-
const cleanHex = hex.replace('#', '');
|
|
12
|
-
|
|
13
|
-
// Parse hex values
|
|
14
|
-
const r = parseInt(cleanHex.substring(0, 2), 16);
|
|
15
|
-
const g = parseInt(cleanHex.substring(2, 4), 16);
|
|
16
|
-
const b = parseInt(cleanHex.substring(4, 6), 16);
|
|
17
|
-
|
|
18
|
-
return `${r} ${g} ${b}`;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Load and apply brand colors from dory.json configuration
|
|
23
|
-
* Sets CSS variables for use throughout the application
|
|
24
|
-
*/
|
|
25
|
-
export function loadColors() {
|
|
26
|
-
const { colors } = config;
|
|
27
|
-
if (!colors) return;
|
|
28
|
-
|
|
29
|
-
// Set CSS variables on the root element for both light and dark modes
|
|
30
|
-
const root = document.documentElement;
|
|
31
|
-
|
|
32
|
-
// Set primary color (used in both light and dark mode)
|
|
33
|
-
if (colors.primary) {
|
|
34
|
-
root.style.setProperty('--brand-primary', colors.primary);
|
|
35
|
-
root.style.setProperty('--brand-primary-rgb', hexToRgb(colors.primary));
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Set light mode brand color
|
|
39
|
-
if (colors.light) {
|
|
40
|
-
root.style.setProperty('--brand-light', colors.light);
|
|
41
|
-
root.style.setProperty('--brand-light-rgb', hexToRgb(colors.light));
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Set dark mode brand color
|
|
45
|
-
if (colors.dark) {
|
|
46
|
-
root.style.setProperty('--brand-dark', colors.dark);
|
|
47
|
-
root.style.setProperty('--brand-dark-rgb', hexToRgb(colors.dark));
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Set the legacy variable for backward compatibility
|
|
51
|
-
if (colors.primary) {
|
|
52
|
-
root.style.setProperty('--brand-foreground', colors.primary);
|
|
53
|
-
}
|
|
54
|
-
}
|
package/src/utils/fonts.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import docsConfig from '../../docs/dory.json' with { type: 'json' };
|
|
2
|
-
import type { DoryConfig } from '../types/config';
|
|
3
|
-
|
|
4
|
-
const config = docsConfig as DoryConfig;
|
|
5
|
-
|
|
6
|
-
export function loadFonts() {
|
|
7
|
-
const { fonts } = config;
|
|
8
|
-
if (!fonts) return;
|
|
9
|
-
|
|
10
|
-
// Create a style element for the font face
|
|
11
|
-
const style = document.createElement('style');
|
|
12
|
-
style.textContent = `
|
|
13
|
-
@font-face {
|
|
14
|
-
font-family: '${fonts.family}';
|
|
15
|
-
src: url('${fonts.source}') format('${fonts.format}');
|
|
16
|
-
font-display: swap;
|
|
17
|
-
}
|
|
18
|
-
`;
|
|
19
|
-
document.head.appendChild(style);
|
|
20
|
-
|
|
21
|
-
// Apply the font family to the root element
|
|
22
|
-
document.documentElement.style.fontFamily = fonts.family;
|
|
23
|
-
}
|
package/src/utils/functions.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export function slugifyWithCounter() {
|
|
2
|
-
const counter = new Map<string, number>();
|
|
3
|
-
return (text: string) => {
|
|
4
|
-
const count = counter.get(text) ?? 0;
|
|
5
|
-
counter.set(text, count + 1);
|
|
6
|
-
return count === 0 ? text : `${text}-${count}`;
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function toTitleCase(text: string) {
|
|
11
|
-
return text.replace(/_/g, ' ').replace(/\b\w/g, char => char.toUpperCase());
|
|
12
|
-
}
|
package/src/vite-env.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
/// <reference types="vite/client" />
|
package/tsconfig.app.json
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
4
|
-
"target": "ES2020",
|
|
5
|
-
"useDefineForClassFields": true,
|
|
6
|
-
"module": "ESNext",
|
|
7
|
-
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
8
|
-
"skipLibCheck": true,
|
|
9
|
-
"paths": {
|
|
10
|
-
"react": ["./node_modules/preact/compat/"],
|
|
11
|
-
"react-dom": ["./node_modules/preact/compat/"]
|
|
12
|
-
},
|
|
13
|
-
|
|
14
|
-
/* Bundler mode */
|
|
15
|
-
"moduleResolution": "bundler",
|
|
16
|
-
"allowImportingTsExtensions": true,
|
|
17
|
-
"verbatimModuleSyntax": true,
|
|
18
|
-
"moduleDetection": "force",
|
|
19
|
-
"noEmit": true,
|
|
20
|
-
"jsx": "react-jsx",
|
|
21
|
-
"jsxImportSource": "preact",
|
|
22
|
-
|
|
23
|
-
/* Linting */
|
|
24
|
-
"strict": true,
|
|
25
|
-
"noUnusedLocals": true,
|
|
26
|
-
"noUnusedParameters": true,
|
|
27
|
-
"erasableSyntaxOnly": true,
|
|
28
|
-
"noFallthroughCasesInSwitch": true,
|
|
29
|
-
"noUncheckedSideEffectImports": true
|
|
30
|
-
},
|
|
31
|
-
"include": ["src"],
|
|
32
|
-
"exclude": ["src/plugins"]
|
|
33
|
-
}
|
package/tsconfig.bin.json
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"lib": ["ES2023"],
|
|
5
|
-
"module": "ESNext",
|
|
6
|
-
"moduleResolution": "node",
|
|
7
|
-
"skipLibCheck": true,
|
|
8
|
-
"strict": true,
|
|
9
|
-
"esModuleInterop": true,
|
|
10
|
-
"allowSyntheticDefaultImports": true,
|
|
11
|
-
"noEmit": false,
|
|
12
|
-
"outDir": "./bin/dist",
|
|
13
|
-
"rootDir": "./bin",
|
|
14
|
-
"declaration": true,
|
|
15
|
-
"declarationMap": true,
|
|
16
|
-
"sourceMap": true
|
|
17
|
-
},
|
|
18
|
-
"include": [
|
|
19
|
-
"bin/**/*.ts"
|
|
20
|
-
],
|
|
21
|
-
"exclude": [
|
|
22
|
-
"bin/dist"
|
|
23
|
-
]
|
|
24
|
-
}
|