@clidey/dory 0.27.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/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 -193
- 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 -41
|
@@ -1,435 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createAutocomplete,
|
|
3
|
-
type AutocompleteApi,
|
|
4
|
-
type AutocompleteCollection,
|
|
5
|
-
type AutocompleteState,
|
|
6
|
-
} from '@algolia/autocomplete-core'
|
|
7
|
-
import { Dialog, DialogBackdrop, DialogPanel } from '@headlessui/react'
|
|
8
|
-
import {
|
|
9
|
-
forwardRef,
|
|
10
|
-
Fragment,
|
|
11
|
-
Suspense,
|
|
12
|
-
useCallback,
|
|
13
|
-
useEffect,
|
|
14
|
-
useId,
|
|
15
|
-
useRef,
|
|
16
|
-
useState,
|
|
17
|
-
type JSX,
|
|
18
|
-
} from 'react'
|
|
19
|
-
import Highlighter from 'react-highlight-words'
|
|
20
|
-
import { cn, Spinner, SearchInput as UxSearchInput } from '@clidey/ux'
|
|
21
|
-
import classNames from 'classnames'
|
|
22
|
-
import { useLocation } from 'wouter-preact'
|
|
23
|
-
import { useMobileNavigation } from '../ui/mobile-navigation'
|
|
24
|
-
import { usePathname, useSearchParams } from './hooks'
|
|
25
|
-
import { search } from './search-index'
|
|
26
|
-
import { ALL_NAVIGATION } from './store'
|
|
27
|
-
|
|
28
|
-
type EmptyObject = Record<string, never>
|
|
29
|
-
type Result = any;
|
|
30
|
-
|
|
31
|
-
type Autocomplete = AutocompleteApi<
|
|
32
|
-
Result,
|
|
33
|
-
JSX.TargetedEvent,
|
|
34
|
-
JSX.TargetedMouseEvent<HTMLElement>,
|
|
35
|
-
JSX.TargetedKeyboardEvent<HTMLElement>
|
|
36
|
-
>
|
|
37
|
-
|
|
38
|
-
function useAutocomplete({ onNavigate }: { onNavigate: () => void }) {
|
|
39
|
-
const id = useId()
|
|
40
|
-
const [, nav] = useLocation()
|
|
41
|
-
const [autocompleteState, setAutocompleteState] = useState<
|
|
42
|
-
AutocompleteState<Result> | EmptyObject
|
|
43
|
-
>({})
|
|
44
|
-
|
|
45
|
-
function navigate({ itemUrl }: { itemUrl?: string }) {
|
|
46
|
-
if (itemUrl) {
|
|
47
|
-
nav(itemUrl);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
onNavigate()
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const [autocomplete] = useState<Autocomplete>(() =>
|
|
54
|
-
createAutocomplete<
|
|
55
|
-
Result,
|
|
56
|
-
JSX.TargetedEvent,
|
|
57
|
-
JSX.TargetedMouseEvent<HTMLElement>,
|
|
58
|
-
JSX.TargetedKeyboardEvent<HTMLElement>
|
|
59
|
-
>({
|
|
60
|
-
id,
|
|
61
|
-
placeholder: 'Find something...',
|
|
62
|
-
defaultActiveItemId: 0,
|
|
63
|
-
onStateChange({ state }) {
|
|
64
|
-
setAutocompleteState(state)
|
|
65
|
-
},
|
|
66
|
-
shouldPanelOpen({ state }) {
|
|
67
|
-
return state.query !== ''
|
|
68
|
-
},
|
|
69
|
-
navigator: {
|
|
70
|
-
navigate,
|
|
71
|
-
},
|
|
72
|
-
getSources({ query }) {
|
|
73
|
-
return [
|
|
74
|
-
{
|
|
75
|
-
sourceId: 'documentation',
|
|
76
|
-
getItems() {
|
|
77
|
-
return search(query, { limit: 5 })
|
|
78
|
-
},
|
|
79
|
-
getItemUrl({ item }) {
|
|
80
|
-
return item.url
|
|
81
|
-
},
|
|
82
|
-
onSelect: navigate,
|
|
83
|
-
},
|
|
84
|
-
]
|
|
85
|
-
},
|
|
86
|
-
}),
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
return { autocomplete, autocompleteState }
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
function HighlightQuery({ text, query }: { text: string; query: string }) {
|
|
94
|
-
return (
|
|
95
|
-
<Highlighter
|
|
96
|
-
highlightClassName="underline bg-transparent text-brand-foreground"
|
|
97
|
-
searchWords={[query]}
|
|
98
|
-
autoEscape={true}
|
|
99
|
-
textToHighlight={text}
|
|
100
|
-
/>
|
|
101
|
-
)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function SearchResult({
|
|
105
|
-
result,
|
|
106
|
-
autocomplete,
|
|
107
|
-
collection,
|
|
108
|
-
query,
|
|
109
|
-
}: {
|
|
110
|
-
result: Result
|
|
111
|
-
autocomplete: Autocomplete
|
|
112
|
-
collection: AutocompleteCollection<Result>
|
|
113
|
-
query: string
|
|
114
|
-
}) {
|
|
115
|
-
const id = useId()
|
|
116
|
-
|
|
117
|
-
const currentPage = result.url.split('#')[0];
|
|
118
|
-
|
|
119
|
-
// Get the group containing the current page
|
|
120
|
-
const group = ALL_NAVIGATION.find(group =>
|
|
121
|
-
group.groups.some(group =>
|
|
122
|
-
group.pages.some(page => page.href === currentPage)
|
|
123
|
-
)
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
const page = group?.groups.find(group => group.pages.find(page => page.href === currentPage));
|
|
127
|
-
|
|
128
|
-
const sectionTitle = group?.title;
|
|
129
|
-
const pageTitle = page?.title;
|
|
130
|
-
|
|
131
|
-
const hierarchy: any[] = [sectionTitle, pageTitle].filter(
|
|
132
|
-
(x): x is string => typeof x === 'string',
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
return (
|
|
136
|
-
<li
|
|
137
|
-
className={classNames(
|
|
138
|
-
'group block cursor-default px-4 py-3 aria-selected:bg-zinc-50 dark:aria-selected:bg-zinc-800/50',
|
|
139
|
-
)}
|
|
140
|
-
aria-labelledby={`${id}-hierarchy ${id}-title`}
|
|
141
|
-
{...autocomplete.getItemProps({
|
|
142
|
-
item: result,
|
|
143
|
-
source: collection.source,
|
|
144
|
-
})}
|
|
145
|
-
>
|
|
146
|
-
<div
|
|
147
|
-
id={`${id}-title`}
|
|
148
|
-
aria-hidden="true"
|
|
149
|
-
className="text-sm font-medium text-brand-foreground group-aria-selected:text-brand-foreground"
|
|
150
|
-
>
|
|
151
|
-
<HighlightQuery text={result.title} query={query} />
|
|
152
|
-
</div>
|
|
153
|
-
{hierarchy.length > 0 && (
|
|
154
|
-
<div
|
|
155
|
-
id={`${id}-hierarchy`}
|
|
156
|
-
aria-hidden="true"
|
|
157
|
-
className="mt-1 truncate text-2xs whitespace-nowrap text-zinc-500"
|
|
158
|
-
>
|
|
159
|
-
{hierarchy.map((item, itemIndex, items) => (
|
|
160
|
-
<Fragment key={itemIndex}>
|
|
161
|
-
<HighlightQuery text={item} query={query} />
|
|
162
|
-
<span
|
|
163
|
-
className={
|
|
164
|
-
itemIndex === items.length - 1
|
|
165
|
-
? 'sr-only'
|
|
166
|
-
: 'mx-2 text-zinc-300 dark:text-zinc-700'
|
|
167
|
-
}
|
|
168
|
-
>
|
|
169
|
-
/
|
|
170
|
-
</span>
|
|
171
|
-
</Fragment>
|
|
172
|
-
))}
|
|
173
|
-
</div>
|
|
174
|
-
)}
|
|
175
|
-
</li>
|
|
176
|
-
)
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function SearchResults({
|
|
180
|
-
autocomplete,
|
|
181
|
-
query,
|
|
182
|
-
collection,
|
|
183
|
-
}: {
|
|
184
|
-
autocomplete: Autocomplete
|
|
185
|
-
query: string
|
|
186
|
-
collection: AutocompleteCollection<Result>
|
|
187
|
-
}) {
|
|
188
|
-
if (collection.items.length === 0) {
|
|
189
|
-
return (
|
|
190
|
-
<div className="p-6 text-center">
|
|
191
|
-
<svg className="mx-auto h-5 w-5 stroke-zinc-900 dark:stroke-zinc-600" viewBox="0 0 20 20" fill="none" aria-hidden="true">
|
|
192
|
-
<path
|
|
193
|
-
strokeLinecap="round"
|
|
194
|
-
strokeLinejoin="round"
|
|
195
|
-
d="M12.01 12a4.237 4.237 0 0 0 1.24-3c0-.62-.132-1.207-.37-1.738M12.01 12A4.237 4.237 0 0 1 9 13.25c-.635 0-1.237-.14-1.777-.388M12.01 12l3.24 3.25m-3.715-9.661a4.25 4.25 0 0 0-5.975 5.908M4.5 15.5l11-11"
|
|
196
|
-
/>
|
|
197
|
-
</svg>
|
|
198
|
-
<p className="mt-2 text-xs">
|
|
199
|
-
Nothing found for{' '}
|
|
200
|
-
<strong className="font-semibold break-words">
|
|
201
|
-
‘{query}’
|
|
202
|
-
</strong>
|
|
203
|
-
. Please try again.
|
|
204
|
-
</p>
|
|
205
|
-
</div>
|
|
206
|
-
)
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
return (
|
|
210
|
-
<ul {...autocomplete.getListProps()}>
|
|
211
|
-
{collection.items.map((result) => (
|
|
212
|
-
<SearchResult
|
|
213
|
-
key={result.url}
|
|
214
|
-
result={result}
|
|
215
|
-
autocomplete={autocomplete}
|
|
216
|
-
collection={collection}
|
|
217
|
-
query={query}
|
|
218
|
-
/>
|
|
219
|
-
))}
|
|
220
|
-
</ul>
|
|
221
|
-
)
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const SearchInput = forwardRef<
|
|
225
|
-
React.ElementRef<'input'>,
|
|
226
|
-
{
|
|
227
|
-
autocomplete: Autocomplete
|
|
228
|
-
autocompleteState: AutocompleteState<Result> | EmptyObject
|
|
229
|
-
onClose: () => void
|
|
230
|
-
}
|
|
231
|
-
>(function SearchInput({ autocomplete, autocompleteState, onClose }, inputRef) {
|
|
232
|
-
const inputProps = autocomplete.getInputProps({ inputElement: null })
|
|
233
|
-
|
|
234
|
-
return (
|
|
235
|
-
<div className="group relative flex">
|
|
236
|
-
<UxSearchInput
|
|
237
|
-
ref={inputRef}
|
|
238
|
-
data-autofocus
|
|
239
|
-
className={cn({
|
|
240
|
-
'pr-11': autocompleteState.status === 'stalled',
|
|
241
|
-
'pr-4': autocompleteState.status !== 'stalled',
|
|
242
|
-
})}
|
|
243
|
-
{...inputProps}
|
|
244
|
-
onKeyDown={(event) => {
|
|
245
|
-
if (
|
|
246
|
-
event.key === 'Escape' &&
|
|
247
|
-
!autocompleteState.isOpen &&
|
|
248
|
-
autocompleteState.query === ''
|
|
249
|
-
) {
|
|
250
|
-
// In Safari, closing the dialog with the escape key can sometimes cause the scroll position to jump to the
|
|
251
|
-
// bottom of the page. This is a workaround for that until we can figure out a proper fix in Headless UI.
|
|
252
|
-
if (document.activeElement instanceof HTMLElement) {
|
|
253
|
-
document.activeElement.blur()
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
onClose()
|
|
257
|
-
} else {
|
|
258
|
-
inputProps.onKeyDown(event)
|
|
259
|
-
}
|
|
260
|
-
}}
|
|
261
|
-
/>
|
|
262
|
-
{autocompleteState.status === 'stalled' && (
|
|
263
|
-
<div className="absolute inset-y-0 right-3 flex items-center">
|
|
264
|
-
<Spinner className="h-5 w-5 animate-spin text-brand-foreground" />
|
|
265
|
-
</div>
|
|
266
|
-
)}
|
|
267
|
-
</div>
|
|
268
|
-
)
|
|
269
|
-
})
|
|
270
|
-
|
|
271
|
-
function SearchDialog({
|
|
272
|
-
open,
|
|
273
|
-
setOpen,
|
|
274
|
-
className,
|
|
275
|
-
onNavigate = () => {},
|
|
276
|
-
}: {
|
|
277
|
-
open: boolean
|
|
278
|
-
setOpen: (open: boolean) => void
|
|
279
|
-
className?: string
|
|
280
|
-
onNavigate?: () => void
|
|
281
|
-
}) {
|
|
282
|
-
const formRef = useRef<React.ElementRef<'form'>>(null)
|
|
283
|
-
const panelRef = useRef<React.ElementRef<'div'>>(null)
|
|
284
|
-
const inputRef = useRef<React.ElementRef<typeof SearchInput>>(null)
|
|
285
|
-
const { autocomplete, autocompleteState } = useAutocomplete({
|
|
286
|
-
onNavigate() {
|
|
287
|
-
onNavigate()
|
|
288
|
-
setOpen(false)
|
|
289
|
-
},
|
|
290
|
-
})
|
|
291
|
-
const pathname = usePathname()
|
|
292
|
-
const searchParams = useSearchParams()
|
|
293
|
-
|
|
294
|
-
useEffect(() => {
|
|
295
|
-
setOpen(false)
|
|
296
|
-
}, [pathname, searchParams, setOpen])
|
|
297
|
-
|
|
298
|
-
useEffect(() => {
|
|
299
|
-
if (open) {
|
|
300
|
-
return
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
function onKeyDown(event: KeyboardEvent) {
|
|
304
|
-
if (event.key === 'k' && (event.metaKey || event.ctrlKey)) {
|
|
305
|
-
event.preventDefault()
|
|
306
|
-
setOpen(true)
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
window.addEventListener('keydown', onKeyDown)
|
|
311
|
-
|
|
312
|
-
return () => {
|
|
313
|
-
window.removeEventListener('keydown', onKeyDown)
|
|
314
|
-
}
|
|
315
|
-
}, [open, setOpen])
|
|
316
|
-
|
|
317
|
-
return (
|
|
318
|
-
<Dialog
|
|
319
|
-
open={open}
|
|
320
|
-
onClose={() => {
|
|
321
|
-
setOpen(false)
|
|
322
|
-
autocomplete.setQuery('')
|
|
323
|
-
}}
|
|
324
|
-
className={classNames('fixed inset-0 z-50', className)}
|
|
325
|
-
>
|
|
326
|
-
<DialogBackdrop
|
|
327
|
-
transition
|
|
328
|
-
className="fixed inset-0 bg-zinc-400/25 backdrop-blur-xs data-closed:opacity-0 data-enter:duration-300 data-enter:ease-out data-leave:duration-200 data-leave:ease-in dark:bg-black/40"
|
|
329
|
-
/>
|
|
330
|
-
|
|
331
|
-
<div className="fixed inset-0 overflow-y-auto px-4 py-4 sm:px-6 sm:py-20 md:py-32 lg:px-8 lg:py-[15vh] max-w-[calc(100vw-16px)]">
|
|
332
|
-
<DialogPanel
|
|
333
|
-
transition
|
|
334
|
-
className="max-sm:mt-[25vh] mx-auto transform-gpu overflow-hidden rounded-lg bg-zinc-50 shadow-xl ring-1 ring-zinc-900/7.5 data-closed:scale-95 data-closed:opacity-0 data-enter:duration-300 data-enter:ease-out data-leave:duration-200 data-leave:ease-in sm:max-w-xl dark:bg-zinc-900 dark:ring-zinc-800"
|
|
335
|
-
>
|
|
336
|
-
<div {...autocomplete.getRootProps({})}>
|
|
337
|
-
<form
|
|
338
|
-
ref={formRef}
|
|
339
|
-
{...autocomplete.getFormProps({
|
|
340
|
-
inputElement: inputRef.current,
|
|
341
|
-
})}
|
|
342
|
-
>
|
|
343
|
-
<SearchInput
|
|
344
|
-
ref={inputRef}
|
|
345
|
-
autocomplete={autocomplete}
|
|
346
|
-
autocompleteState={autocompleteState}
|
|
347
|
-
onClose={() => setOpen(false)}
|
|
348
|
-
/>
|
|
349
|
-
<div
|
|
350
|
-
ref={panelRef}
|
|
351
|
-
className="border-t border-zinc-200 bg-white empty:hidden dark:border-zinc-100/5 dark:bg-white/2.5"
|
|
352
|
-
{...autocomplete.getPanelProps({})}
|
|
353
|
-
>
|
|
354
|
-
{autocompleteState.isOpen && (
|
|
355
|
-
<SearchResults
|
|
356
|
-
autocomplete={autocomplete}
|
|
357
|
-
query={autocompleteState.query}
|
|
358
|
-
collection={autocompleteState.collections[0]}
|
|
359
|
-
/>
|
|
360
|
-
)}
|
|
361
|
-
</div>
|
|
362
|
-
</form>
|
|
363
|
-
</div>
|
|
364
|
-
</DialogPanel>
|
|
365
|
-
</div>
|
|
366
|
-
</Dialog>
|
|
367
|
-
)
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
function useSearchProps() {
|
|
371
|
-
const inputRef = useRef<React.ElementRef<typeof SearchInput>>(null)
|
|
372
|
-
const [open, setOpen] = useState(false)
|
|
373
|
-
|
|
374
|
-
return {
|
|
375
|
-
inputProps: {
|
|
376
|
-
ref: inputRef,
|
|
377
|
-
onClick() {
|
|
378
|
-
setOpen(true)
|
|
379
|
-
},
|
|
380
|
-
},
|
|
381
|
-
dialogProps: {
|
|
382
|
-
open,
|
|
383
|
-
setOpen: useCallback(
|
|
384
|
-
(open: boolean) => {
|
|
385
|
-
const { width = 0, height = 0 } =
|
|
386
|
-
inputRef.current?.getBoundingClientRect?.() ?? {}
|
|
387
|
-
if (!open || (width !== 0 && height !== 0)) {
|
|
388
|
-
setOpen(open)
|
|
389
|
-
}
|
|
390
|
-
},
|
|
391
|
-
[setOpen],
|
|
392
|
-
),
|
|
393
|
-
},
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
export function Search() {
|
|
398
|
-
const { inputProps: buttonProps, dialogProps } = useSearchProps()
|
|
399
|
-
|
|
400
|
-
return (
|
|
401
|
-
<div className="hidden lg:block lg:max-w-md lg:flex-auto">
|
|
402
|
-
<UxSearchInput
|
|
403
|
-
aria-label="Find something..."
|
|
404
|
-
placeholder="Find something..."
|
|
405
|
-
{...buttonProps}
|
|
406
|
-
/>
|
|
407
|
-
<Suspense fallback={null}>
|
|
408
|
-
<SearchDialog className="hidden lg:block" {...dialogProps} />
|
|
409
|
-
</Suspense>
|
|
410
|
-
</div>
|
|
411
|
-
)
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
export function MobileSearch() {
|
|
415
|
-
const { close } = useMobileNavigation()
|
|
416
|
-
const { inputProps: buttonProps, dialogProps } = useSearchProps()
|
|
417
|
-
|
|
418
|
-
return (
|
|
419
|
-
<div className="contents lg:hidden">
|
|
420
|
-
<UxSearchInput
|
|
421
|
-
className="text-sm"
|
|
422
|
-
aria-label="Find something..."
|
|
423
|
-
{...buttonProps}
|
|
424
|
-
placeholder="Find something..."
|
|
425
|
-
/>
|
|
426
|
-
<Suspense fallback={null}>
|
|
427
|
-
<SearchDialog
|
|
428
|
-
className="lg:hidden"
|
|
429
|
-
onNavigate={close}
|
|
430
|
-
{...dialogProps}
|
|
431
|
-
/>
|
|
432
|
-
</Suspense>
|
|
433
|
-
</div>
|
|
434
|
-
)
|
|
435
|
-
}
|
package/src/components/store.ts
DELETED
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
import type { ComponentType } from 'preact';
|
|
2
|
-
import { type RouteComponentProps } from 'wouter-preact';
|
|
3
|
-
import docsConfig from '../../docs/dory.json' with { type: 'json' };
|
|
4
|
-
import type { DoryConfig } from '../types/config';
|
|
5
|
-
import { searchIndex } from './search-index';
|
|
6
|
-
|
|
7
|
-
const config = docsConfig as DoryConfig;
|
|
8
|
-
|
|
9
|
-
export const ALL_PAGES = Object.fromEntries(
|
|
10
|
-
Object.entries(import.meta.glob<{ default: ComponentType; }>('../../docs/**/*.mdx'))
|
|
11
|
-
.map(([path, loader]) => [pathFromFilename(path), loader])
|
|
12
|
-
);
|
|
13
|
-
export const ALL_OPENAPI = Object.fromEntries(
|
|
14
|
-
Object.entries(import.meta.glob<{ default: string }>('../../docs/**/openapi.json', { query: 'raw', eager: true }))
|
|
15
|
-
.map(([path, loader]) => [pathFromFilename(path), loader])
|
|
16
|
-
);
|
|
17
|
-
export const ALL_ASYNCAPI = Object.fromEntries(
|
|
18
|
-
Object.entries(import.meta.glob<{ default: string }>('../../docs/**/asyncapi.json', { query: 'raw', eager: true }))
|
|
19
|
-
.map(([path, loader]) => [pathFromFilename(path), loader])
|
|
20
|
-
);
|
|
21
|
-
|
|
22
|
-
export interface Route {
|
|
23
|
-
path: string;
|
|
24
|
-
component: ComponentType<RouteComponentProps<{ [param: number]: string | undefined; }>>;
|
|
25
|
-
title?: string;
|
|
26
|
-
description?: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface Navigation {
|
|
30
|
-
title: string;
|
|
31
|
-
groups: Array<{
|
|
32
|
-
title: string;
|
|
33
|
-
pages: Array<{
|
|
34
|
-
title: string;
|
|
35
|
-
href: string;
|
|
36
|
-
}>;
|
|
37
|
-
}>;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export const ALL_NAVIGATION: Navigation[] = config.navigation.tabs.map(tab => ({
|
|
41
|
-
title: tab.tab,
|
|
42
|
-
groups: tab.groups.map(group => ({
|
|
43
|
-
title: group.group,
|
|
44
|
-
pages: group.pages.map(page => {
|
|
45
|
-
const path = `/${page}`;
|
|
46
|
-
return {
|
|
47
|
-
title: page.charAt(0).toUpperCase() + page.slice(1).replace(/-/g, ' '),
|
|
48
|
-
href: path
|
|
49
|
-
};
|
|
50
|
-
})
|
|
51
|
-
}))
|
|
52
|
-
}));
|
|
53
|
-
|
|
54
|
-
export function pathFromFilename(filename: string): string {
|
|
55
|
-
return filename
|
|
56
|
-
.replace('../../docs', '')
|
|
57
|
-
.replace(/\/?index\.mdx$/, '/')
|
|
58
|
-
.replace(/\.mdx$/, '')
|
|
59
|
-
.toLowerCase();
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function parseFrontMatter(content: string) {
|
|
63
|
-
const frontMatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
64
|
-
if (!frontMatterMatch) return { data: {}, content };
|
|
65
|
-
|
|
66
|
-
const frontMatter = frontMatterMatch[1];
|
|
67
|
-
const data: Record<string, any> = {};
|
|
68
|
-
|
|
69
|
-
frontMatter.split('\n').forEach(line => {
|
|
70
|
-
const [key, ...valueParts] = line.split(':');
|
|
71
|
-
if (key && valueParts.length) {
|
|
72
|
-
const value = valueParts.join(':').trim();
|
|
73
|
-
data[key.trim()] = value.replace(/^["']|["']$/g, ''); // Remove quotes if present
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
return { data, content: content.slice(frontMatterMatch[0].length) };
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export let completeFrontMatter: Record<string, any>[] = [];
|
|
81
|
-
let preloadedFrontMatter: Record<string, any>[] | null = null;
|
|
82
|
-
|
|
83
|
-
// Preload frontmatter from JSON file (optimized approach)
|
|
84
|
-
export async function preloadFrontmatter() {
|
|
85
|
-
if (preloadedFrontMatter) return; // Already loaded
|
|
86
|
-
|
|
87
|
-
try {
|
|
88
|
-
const response = await fetch('/frontmatter.json');
|
|
89
|
-
if (response.ok) {
|
|
90
|
-
preloadedFrontMatter = await response.json();
|
|
91
|
-
if (preloadedFrontMatter) {
|
|
92
|
-
completeFrontMatter = [...preloadedFrontMatter];
|
|
93
|
-
|
|
94
|
-
// Update navigation titles from preloaded data
|
|
95
|
-
for (const fm of preloadedFrontMatter) {
|
|
96
|
-
updateNavigationTitle(fm.path, fm.title);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Add to search index
|
|
100
|
-
await addPreloadedContentToSearch();
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
} catch (error) {
|
|
104
|
-
console.warn('Failed to load prebuilt frontmatter JSON, falling back to dynamic loading:', error);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Add preloaded content to search index
|
|
109
|
-
async function addPreloadedContentToSearch() {
|
|
110
|
-
if (!preloadedFrontMatter) return;
|
|
111
|
-
|
|
112
|
-
const mdxFiles = import.meta.glob<{ default: string }>('../../docs/**/*.mdx', { query: 'raw' });
|
|
113
|
-
const fileEntries = Object.entries(mdxFiles);
|
|
114
|
-
|
|
115
|
-
await Promise.all(
|
|
116
|
-
preloadedFrontMatter.map(async (fm) => {
|
|
117
|
-
const fileEntry = fileEntries.find(([filePath]) => pathFromFilename(filePath) === fm.path);
|
|
118
|
-
if (fileEntry) {
|
|
119
|
-
const [, loader] = fileEntry;
|
|
120
|
-
const rawContent = (await loader()).default;
|
|
121
|
-
const { content } = parseFrontMatter(rawContent);
|
|
122
|
-
|
|
123
|
-
searchIndex.add({
|
|
124
|
-
url: fm.path,
|
|
125
|
-
title: fm.title || '',
|
|
126
|
-
content,
|
|
127
|
-
pageTitle: fm.title || ''
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
})
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Load frontmatter for a specific pathname (optimized with fallback)
|
|
135
|
-
export async function loadMDXFrontMatterForPath(pathname: string) {
|
|
136
|
-
// If we have preloaded data, check if this path is already loaded
|
|
137
|
-
if (preloadedFrontMatter && preloadedFrontMatter.find(fm => fm.path === pathname)) {
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Check if already loaded in completeFrontMatter
|
|
142
|
-
if (completeFrontMatter.find(fm => fm.path === pathname)) return;
|
|
143
|
-
|
|
144
|
-
// Fallback to dynamic loading
|
|
145
|
-
const mdxFiles = import.meta.glob<{ default: string }>('../../docs/**/*.mdx', { query: 'raw' });
|
|
146
|
-
const fileEntries = Object.entries(mdxFiles);
|
|
147
|
-
|
|
148
|
-
const fileEntry = fileEntries.find(([path]) => pathFromFilename(path) === pathname);
|
|
149
|
-
if (!fileEntry) return;
|
|
150
|
-
|
|
151
|
-
const [path, loader] = fileEntry;
|
|
152
|
-
const rawContent = (await loader()).default;
|
|
153
|
-
const { data, content } = parseFrontMatter(rawContent);
|
|
154
|
-
const filename = pathFromFilename(path);
|
|
155
|
-
|
|
156
|
-
updateNavigationTitle(filename, data.title);
|
|
157
|
-
searchIndex.add({
|
|
158
|
-
url: filename,
|
|
159
|
-
title: data.title || '',
|
|
160
|
-
content,
|
|
161
|
-
pageTitle: data.title || ''
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
const frontmatterItem = { ...data, path: filename };
|
|
165
|
-
completeFrontMatter = [...completeFrontMatter, frontmatterItem];
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Load all remaining frontmatter (after initial load) - optimized with fallback
|
|
169
|
-
export async function loadAllMDXFrontMatter(pathname: string) {
|
|
170
|
-
// If we have preloaded data, we're already done
|
|
171
|
-
if (preloadedFrontMatter) {
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Fallback to dynamic loading
|
|
176
|
-
const mdxFiles = import.meta.glob<{ default: string }>('../../docs/**/*.mdx', { query: 'raw' });
|
|
177
|
-
const fileEntries = Object.entries(mdxFiles);
|
|
178
|
-
|
|
179
|
-
const remainingEntries = fileEntries.filter(([path]) => pathFromFilename(path) !== pathname);
|
|
180
|
-
const newFrontmatter: Record<string, any>[] = [];
|
|
181
|
-
|
|
182
|
-
await Promise.all(
|
|
183
|
-
remainingEntries.map(async ([path, loader]) => {
|
|
184
|
-
const rawContent = (await loader()).default;
|
|
185
|
-
const { data, content } = parseFrontMatter(rawContent);
|
|
186
|
-
const filename = pathFromFilename(path);
|
|
187
|
-
|
|
188
|
-
updateNavigationTitle(filename, data.title);
|
|
189
|
-
searchIndex.add({
|
|
190
|
-
url: filename,
|
|
191
|
-
title: data.title || '',
|
|
192
|
-
content,
|
|
193
|
-
pageTitle: data.title || ''
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
newFrontmatter.push({ ...data, path: filename });
|
|
197
|
-
})
|
|
198
|
-
);
|
|
199
|
-
|
|
200
|
-
completeFrontMatter = [...completeFrontMatter, ...newFrontmatter];
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Shared helper
|
|
204
|
-
function updateNavigationTitle(filename: string, title?: string) {
|
|
205
|
-
if (!title) return;
|
|
206
|
-
|
|
207
|
-
for (const tab of ALL_NAVIGATION) {
|
|
208
|
-
for (const group of tab.groups) {
|
|
209
|
-
for (const page of group.pages) {
|
|
210
|
-
if (page.href === filename) {
|
|
211
|
-
page.title = title;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
package/src/config/mdx.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import remarkGfm from 'remark-gfm';
|
|
2
|
-
import { remarkSafeVars } from '../plugins/sanitize.ts';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Shared MDX configuration used by both main build (vite.config.ts)
|
|
6
|
-
* and CLI build:file command (bin/dory.js)
|
|
7
|
-
*/
|
|
8
|
-
export const getMdxConfig = (isDevelopment = false) => ({
|
|
9
|
-
providerImportSource: '@mdx-js/preact',
|
|
10
|
-
remarkPlugins: [remarkGfm, remarkSafeVars],
|
|
11
|
-
development: isDevelopment,
|
|
12
|
-
});
|
package/src/config/mdx.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import remarkGfm from 'remark-gfm';
|
|
2
|
-
import { remarkSafeVars } from '../plugins/sanitize';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Shared MDX configuration used by both main build (vite.config.ts)
|
|
6
|
-
* and CLI build:file command (bin/dory.js)
|
|
7
|
-
*/
|
|
8
|
-
export const getMdxConfig = (isDevelopment: boolean = false) => ({
|
|
9
|
-
providerImportSource: '@mdx-js/preact',
|
|
10
|
-
remarkPlugins: [remarkGfm, remarkSafeVars],
|
|
11
|
-
development: isDevelopment,
|
|
12
|
-
});
|
package/src/main.tsx
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { render } from 'preact'
|
|
2
|
-
import '@clidey/ux/styles.css';
|
|
3
|
-
import { App } from './app.tsx'
|
|
4
|
-
import { withMDX } from './components/mdx-provider.tsx'
|
|
5
|
-
import { loadFonts } from './utils/fonts'
|
|
6
|
-
import { loadColors } from './utils/colors'
|
|
7
|
-
import { Toaster } from '@clidey/ux'
|
|
8
|
-
import { ThemeProvider } from '@clidey/ux'
|
|
9
|
-
|
|
10
|
-
// Load fonts and colors before rendering the app
|
|
11
|
-
loadFonts()
|
|
12
|
-
loadColors()
|
|
13
|
-
|
|
14
|
-
const AppWithMDX = withMDX(App)
|
|
15
|
-
|
|
16
|
-
render(
|
|
17
|
-
<ThemeProvider storageKey="@clidey/dory/theme">
|
|
18
|
-
<Toaster />
|
|
19
|
-
<AppWithMDX />
|
|
20
|
-
</ThemeProvider>,
|
|
21
|
-
document.getElementById('app')!
|
|
22
|
-
)
|