@clidey/dory 1.0.5 → 1.0.7
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 +36 -2
- package/bin/dist/dory.js.map +1 -1
- package/index.html +0 -1
- package/package.json +16 -22
- package/src/components/hooks.tsx +2 -1
- package/src/components/store.ts +5 -4
- package/src/config/mdx.js +8 -3
- package/src/config/mdx.ts +8 -3
- package/src/embed/widget-iframe.ts +10 -3
- package/src/entry-server.tsx +11 -7
- package/src/main.tsx +5 -1
- package/src/mdx/accordion.tsx +11 -8
- package/src/mdx/api.tsx +2 -352
- package/src/mdx/async-api.tsx +124 -295
- package/src/mdx/callout.tsx +22 -24
- package/src/mdx/card.tsx +20 -22
- package/src/mdx/chip.tsx +14 -0
- package/src/mdx/code.tsx +133 -110
- package/src/mdx/expandable.tsx +8 -8
- package/src/mdx/fence-meta.test.ts +39 -0
- package/src/mdx/fence-meta.ts +61 -0
- package/src/mdx/fence.tsx +155 -44
- package/src/mdx/icon-grid.tsx +29 -0
- package/src/mdx/icon.tsx +2 -2
- package/src/mdx/latex.tsx +14 -7
- package/src/mdx/list.tsx +3 -3
- package/src/mdx/mdx.tsx +7 -1
- package/src/mdx/mermaid.tsx +81 -11
- package/src/mdx/open-api.tsx +396 -239
- package/src/mdx/source.tsx +148 -40
- package/src/mdx/specimen.tsx +69 -0
- package/src/mdx/step.tsx +22 -30
- package/src/mdx/table.tsx +4 -9
- package/src/mdx/tabs.tsx +39 -0
- package/src/mdx/tag.tsx +13 -21
- package/src/mdx/text.tsx +17 -9
- package/src/mdx/type-scale.tsx +28 -0
- package/src/mdx/websocket-playground.tsx +221 -704
- package/src/plugins/code-meta.ts +23 -0
- package/src/plugins/frontmatter-dev-server.ts +6 -2
- package/src/plugins/html-files-middleware.ts +9 -2
- package/src/plugins/html-metadata-injector.ts +4 -3
- package/src/plugins/llm-txt-dev-server.ts +5 -2
- package/src/plugins/llm-txt-generator.ts +2 -1
- package/src/plugins/prerender.ts +7 -4
- package/src/plugins/relative-assets.test.ts +26 -0
- package/src/plugins/relative-assets.ts +80 -0
- package/src/plugins/robots-generator.ts +6 -4
- package/src/plugins/sanitize.test.ts +5 -0
- package/src/plugins/sanitize.ts +2 -0
- package/src/plugins/shared.ts +17 -0
- package/src/plugins/sitemap-generator.ts +2 -1
- package/src/theme.scss +282 -3
- package/src/types/config.ts +3 -0
- package/src/ui/header.tsx +4 -3
- package/src/ui/layout.tsx +22 -4
- package/src/ui/page-meta.tsx +37 -0
- package/src/ui/prev-next-link.tsx +4 -3
- package/src/ui/table-of-content.tsx +11 -3
- package/src/utils/base-path.test.ts +46 -0
- package/src/utils/base-path.ts +41 -0
- package/vite.config.ssr.ts +3 -0
- package/vite.config.ts +3 -0
package/src/mdx/api.tsx
CHANGED
|
@@ -1,354 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
Children,
|
|
5
|
-
createContext,
|
|
6
|
-
isValidElement,
|
|
7
|
-
useContext,
|
|
8
|
-
useEffect,
|
|
9
|
-
useRef,
|
|
10
|
-
useState,
|
|
11
|
-
} from 'react'
|
|
12
|
-
import { Tag } from './tag'
|
|
13
|
-
import { CheckIcon } from 'lucide-react'
|
|
14
|
-
import { Button, Card } from '@clidey/ux'
|
|
15
|
-
|
|
16
|
-
const languageNames: Record<string, string> = {
|
|
17
|
-
js: 'JavaScript',
|
|
18
|
-
ts: 'TypeScript',
|
|
19
|
-
javascript: 'JavaScript',
|
|
20
|
-
typescript: 'TypeScript',
|
|
21
|
-
php: 'PHP',
|
|
22
|
-
python: 'Python',
|
|
23
|
-
ruby: 'Ruby',
|
|
24
|
-
go: 'Go',
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function getPanelTitle({
|
|
28
|
-
title,
|
|
29
|
-
language,
|
|
30
|
-
}: {
|
|
31
|
-
title?: string
|
|
32
|
-
language?: string
|
|
33
|
-
}) {
|
|
34
|
-
if (title) {
|
|
35
|
-
return title
|
|
36
|
-
}
|
|
37
|
-
if (language && language in languageNames) {
|
|
38
|
-
return languageNames[language]
|
|
39
|
-
}
|
|
40
|
-
return 'API'
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function ClipboardIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
|
|
44
|
-
return (
|
|
45
|
-
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
|
46
|
-
<path
|
|
47
|
-
strokeWidth="0"
|
|
48
|
-
d="M5.5 13.5v-5a2 2 0 0 1 2-2l.447-.894A2 2 0 0 1 9.737 4.5h.527a2 2 0 0 1 1.789 1.106l.447.894a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2Z"
|
|
49
|
-
/>
|
|
50
|
-
<path
|
|
51
|
-
fill="none"
|
|
52
|
-
strokeLinejoin="round"
|
|
53
|
-
d="M12.5 6.5a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2m5 0-.447-.894a2 2 0 0 0-1.79-1.106h-.527a2 2 0 0 0-1.789 1.106L7.5 6.5m5 0-1 1h-3l-1-1"
|
|
54
|
-
/>
|
|
55
|
-
</svg>
|
|
56
|
-
)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function CopyButton({ code }: { code: string }) {
|
|
60
|
-
const [copyCount, setCopyCount] = useState(0)
|
|
61
|
-
const copied = copyCount > 0
|
|
62
|
-
|
|
63
|
-
useEffect(() => {
|
|
64
|
-
if (copyCount > 0) {
|
|
65
|
-
const timeout = setTimeout(() => setCopyCount(0), 1000)
|
|
66
|
-
return () => {
|
|
67
|
-
clearTimeout(timeout)
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}, [copyCount])
|
|
71
|
-
|
|
72
|
-
return (
|
|
73
|
-
<Button
|
|
74
|
-
className={classNames(
|
|
75
|
-
'group/button absolute top-3.5 right-4 overflow-hidden rounded-full py-1 pr-3 pl-2 text-2xs font-medium opacity-0 backdrop-blur-sm transition group-hover:opacity-100 focus:opacity-100',
|
|
76
|
-
copied
|
|
77
|
-
? 'bg-sky-400/10 ring-1 ring-sky-400/20 ring-inset'
|
|
78
|
-
: 'bg-white/5 hover:bg-white/7.5 dark:bg-white/2.5 dark:hover:bg-white/5',
|
|
79
|
-
)}
|
|
80
|
-
onClick={() => {
|
|
81
|
-
window.navigator.clipboard.writeText(code).then(() => {
|
|
82
|
-
setCopyCount((count) => count + 1)
|
|
83
|
-
})
|
|
84
|
-
}}
|
|
85
|
-
>
|
|
86
|
-
<span
|
|
87
|
-
aria-hidden={copied}
|
|
88
|
-
className={classNames(
|
|
89
|
-
'pointer-events-none flex items-center gap-0.5 text-muted-foreground transition duration-300',
|
|
90
|
-
copied && '-translate-y-1.5 opacity-0',
|
|
91
|
-
)}
|
|
92
|
-
>
|
|
93
|
-
<ClipboardIcon className="h-4 w-4 fill-zinc-500/20 stroke-muted-foreground transition-colors group-hover/button:stroke-muted-foreground" />
|
|
94
|
-
</span>
|
|
95
|
-
<span
|
|
96
|
-
aria-hidden={!copied}
|
|
97
|
-
className={classNames(
|
|
98
|
-
'pointer-events-none absolute inset-0 flex items-center justify-center text-sky-400 transition duration-300',
|
|
99
|
-
!copied && 'translate-y-1.5 opacity-0',
|
|
100
|
-
)}
|
|
101
|
-
>
|
|
102
|
-
<CheckIcon className="h-4 w-4" />
|
|
103
|
-
</span>
|
|
104
|
-
</Button>
|
|
105
|
-
)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function APIPanelHeader({ tag, label }: { tag?: string; label?: string }) {
|
|
109
|
-
if (!tag && !label) {
|
|
110
|
-
return null
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return (
|
|
114
|
-
<div className="flex h-9 items-center gap-2 px-4">
|
|
115
|
-
{tag && (
|
|
116
|
-
<div className="dark flex">
|
|
117
|
-
<Tag variant="small">{tag}</Tag>
|
|
118
|
-
</div>
|
|
119
|
-
)}
|
|
120
|
-
{tag && label && (
|
|
121
|
-
<span className="h-0.5 w-0.5 rounded-full bg-zinc-500" />
|
|
122
|
-
)}
|
|
123
|
-
{label && (
|
|
124
|
-
<span className="font-mono text-xs text-muted-foreground">{label}</span>
|
|
125
|
-
)}
|
|
126
|
-
</div>
|
|
127
|
-
)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function APIPanel({
|
|
131
|
-
children,
|
|
132
|
-
tag,
|
|
133
|
-
label,
|
|
134
|
-
code,
|
|
135
|
-
}: {
|
|
136
|
-
children: React.ReactNode
|
|
137
|
-
tag?: string
|
|
138
|
-
label?: string
|
|
139
|
-
code?: string
|
|
140
|
-
}) {
|
|
141
|
-
const child = Children.only(children)
|
|
142
|
-
|
|
143
|
-
if (isValidElement(child)) {
|
|
144
|
-
// @ts-expect-error child.props is not typed
|
|
145
|
-
tag = child.props.tag ?? tag
|
|
146
|
-
// @ts-expect-error child.props is not typed
|
|
147
|
-
label = child.props.label ?? label
|
|
148
|
-
// @ts-expect-error child.props is not typed
|
|
149
|
-
code = child.props.code ?? code
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return (
|
|
153
|
-
<div className="group dark:bg-white/2.5">
|
|
154
|
-
<APIPanelHeader tag={tag} label={label} />
|
|
155
|
-
<div className="relative">
|
|
156
|
-
<pre className="overflow-x-auto p-4 text-xs">{children}</pre>
|
|
157
|
-
{/* @ts-expect-error code is not typed */}
|
|
158
|
-
<CopyButton code={code} />
|
|
159
|
-
</div>
|
|
160
|
-
</div>
|
|
161
|
-
)
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function APIGroupHeader({
|
|
165
|
-
title,
|
|
166
|
-
children,
|
|
167
|
-
selectedIndex,
|
|
168
|
-
}: {
|
|
169
|
-
title: string
|
|
170
|
-
children: React.ReactNode
|
|
171
|
-
selectedIndex: number
|
|
172
|
-
}) {
|
|
173
|
-
const hasTabs = Children.count(children) > 1
|
|
174
|
-
|
|
175
|
-
if (!title && !hasTabs) {
|
|
176
|
-
return null
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
return (
|
|
180
|
-
<div className="flex min-h-[calc(--spacing(12)+1px)] flex-wrap items-start gap-x-4 px-4 pt-2">
|
|
181
|
-
{title && (
|
|
182
|
-
<h3 className="mr-auto pt-3 text-xs font-semibold">
|
|
183
|
-
{title}
|
|
184
|
-
</h3>
|
|
185
|
-
)}
|
|
186
|
-
{hasTabs && (
|
|
187
|
-
<TabList className="-mb-px flex gap-4 text-xs font-medium">
|
|
188
|
-
{Children.map(children, (child, childIndex) => (
|
|
189
|
-
<Tab
|
|
190
|
-
className={classNames(
|
|
191
|
-
'pt-2 transition data-selected:not-data-focus:outline-hidden',
|
|
192
|
-
childIndex === selectedIndex
|
|
193
|
-
? 'border-sky-500 text-sky-400'
|
|
194
|
-
: 'border-transparent text-muted-foreground hover:text-zinc-300',
|
|
195
|
-
)}
|
|
196
|
-
>
|
|
197
|
-
{/* @ts-expect-error child.props is not typed */}
|
|
198
|
-
{getPanelTitle(isValidElement(child) ? child.props : {})}
|
|
199
|
-
</Tab>
|
|
200
|
-
))}
|
|
201
|
-
</TabList>
|
|
202
|
-
)}
|
|
203
|
-
</div>
|
|
204
|
-
)
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function APIGroupPanels({
|
|
208
|
-
children,
|
|
209
|
-
...props
|
|
210
|
-
}: React.ComponentPropsWithoutRef<typeof APIPanel>) {
|
|
211
|
-
const hasTabs = Children.count(children) > 1
|
|
212
|
-
|
|
213
|
-
if (hasTabs) {
|
|
214
|
-
return (
|
|
215
|
-
<TabPanels>
|
|
216
|
-
{Children.map(children, (child) => (
|
|
217
|
-
<TabPanel>
|
|
218
|
-
<APIPanel {...props}>{child}</APIPanel>
|
|
219
|
-
</TabPanel>
|
|
220
|
-
))}
|
|
221
|
-
</TabPanels>
|
|
222
|
-
)
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
return <APIPanel {...props}>{children}</APIPanel>
|
|
226
|
-
}
|
|
227
|
-
function usePreventLayoutShift() {
|
|
228
|
-
const positionRef = useRef<HTMLElement>(null)
|
|
229
|
-
const rafRef = useRef<number>()
|
|
230
|
-
|
|
231
|
-
useEffect(() => {
|
|
232
|
-
return () => {
|
|
233
|
-
if (typeof rafRef.current !== 'undefined') {
|
|
234
|
-
window.cancelAnimationFrame(rafRef.current)
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}, [])
|
|
238
|
-
|
|
239
|
-
return {
|
|
240
|
-
positionRef,
|
|
241
|
-
preventLayoutShift(callback: () => void) {
|
|
242
|
-
if (!positionRef.current) {
|
|
243
|
-
return
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
const initialTop = positionRef.current.getBoundingClientRect().top
|
|
247
|
-
|
|
248
|
-
callback()
|
|
249
|
-
|
|
250
|
-
rafRef.current = window.requestAnimationFrame(() => {
|
|
251
|
-
const newTop =
|
|
252
|
-
positionRef.current?.getBoundingClientRect().top ?? initialTop
|
|
253
|
-
window.scrollBy(0, newTop - initialTop)
|
|
254
|
-
})
|
|
255
|
-
},
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function useTabGroupProps(availableLanguages: Array<string>) {
|
|
260
|
-
const [selectedIndex, setSelectedIndex] = useState(0)
|
|
261
|
-
const [preferredLanguages, setPreferredLanguages] = useState<string[]>([])
|
|
262
|
-
|
|
263
|
-
const activeLanguage = [...availableLanguages].sort(
|
|
264
|
-
(a, z) => preferredLanguages.indexOf(z) - preferredLanguages.indexOf(a),
|
|
265
|
-
)[0]
|
|
266
|
-
const languageIndex = availableLanguages.indexOf(activeLanguage)
|
|
267
|
-
const newSelectedIndex = languageIndex === -1 ? selectedIndex : languageIndex
|
|
268
|
-
|
|
269
|
-
if (newSelectedIndex !== selectedIndex) {
|
|
270
|
-
setSelectedIndex(newSelectedIndex)
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
const { positionRef, preventLayoutShift } = usePreventLayoutShift()
|
|
274
|
-
|
|
275
|
-
return {
|
|
276
|
-
as: 'div' as const,
|
|
277
|
-
ref: positionRef,
|
|
278
|
-
selectedIndex,
|
|
279
|
-
onChange: (newSelectedIndex: number) => {
|
|
280
|
-
preventLayoutShift(() => {
|
|
281
|
-
const newLanguage = availableLanguages[newSelectedIndex]
|
|
282
|
-
setPreferredLanguages(prev => [
|
|
283
|
-
...prev.filter(lang => lang !== newLanguage),
|
|
284
|
-
newLanguage
|
|
285
|
-
])
|
|
286
|
-
})
|
|
287
|
-
},
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
const APIGroupContext = createContext(false)
|
|
292
|
-
|
|
293
|
-
export function APIGroup({
|
|
294
|
-
children,
|
|
295
|
-
title,
|
|
296
|
-
...props
|
|
297
|
-
}: React.ComponentPropsWithoutRef<typeof APIGroupPanels> & { title: string }) {
|
|
298
|
-
const languages =
|
|
299
|
-
Children.map(children, (child) =>
|
|
300
|
-
// @ts-expect-error child.props is not typed
|
|
301
|
-
getPanelTitle(isValidElement(child) ? child.props : {}),
|
|
302
|
-
) ?? []
|
|
303
|
-
const tabGroupProps = useTabGroupProps(languages)
|
|
304
|
-
const hasTabs = Children.count(children) > 1
|
|
305
|
-
|
|
306
|
-
const header = (
|
|
307
|
-
<APIGroupHeader title={title} selectedIndex={tabGroupProps.selectedIndex}>
|
|
308
|
-
{children}
|
|
309
|
-
</APIGroupHeader>
|
|
310
|
-
)
|
|
311
|
-
const panels = <APIGroupPanels {...props}>{children}</APIGroupPanels>
|
|
312
|
-
|
|
313
|
-
return (
|
|
314
|
-
<APIGroupContext.Provider value={true}>
|
|
315
|
-
{hasTabs ? (
|
|
316
|
-
<Card className="py-0 border-black/10 bg-none overflow-hidden mb-4">
|
|
317
|
-
<TabGroup {...tabGroupProps}>
|
|
318
|
-
<div className="not-prose">
|
|
319
|
-
{header}
|
|
320
|
-
{panels}
|
|
321
|
-
</div>
|
|
322
|
-
</TabGroup>
|
|
323
|
-
</Card>
|
|
324
|
-
) : (
|
|
325
|
-
<Card className="py-0 border-black/10 bg-none overflow-hidden mb-4">
|
|
326
|
-
<div className="not-prose">
|
|
327
|
-
{header}
|
|
328
|
-
{panels}
|
|
329
|
-
</div>
|
|
330
|
-
</Card>
|
|
331
|
-
)}
|
|
332
|
-
</APIGroupContext.Provider>
|
|
333
|
-
)
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
export function API({
|
|
337
|
-
children,
|
|
338
|
-
...props
|
|
339
|
-
}: React.ComponentPropsWithoutRef<'code'>) {
|
|
340
|
-
const isGrouped = useContext(APIGroupContext)
|
|
341
|
-
|
|
342
|
-
if (isGrouped) {
|
|
343
|
-
if (typeof children !== 'string') {
|
|
344
|
-
throw new Error(
|
|
345
|
-
'`API` children must be a string when nested inside a `APIGroup`.',
|
|
346
|
-
)
|
|
347
|
-
}
|
|
348
|
-
return <code {...props}>
|
|
349
|
-
{children}
|
|
350
|
-
</code>
|
|
351
|
-
}
|
|
352
|
-
|
|
1
|
+
/** Inline code element kept for MDX content that uses <API>. */
|
|
2
|
+
export function API({ children, ...props }: React.ComponentPropsWithoutRef<'code'>) {
|
|
353
3
|
return <code {...props}>{children}</code>
|
|
354
4
|
}
|