@clidey/dory 0.1.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/.dockerignore +4 -0
- package/.github/ISSUE_TEMPLATE/anything-else.md +10 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +39 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- package/.github/dependabot.yml +11 -0
- package/.github/workflows/claude.yml +37 -0
- package/CODE_OF_CONDUCT.md +128 -0
- package/CONTRIBUTING.md +91 -0
- package/LICENSE +201 -0
- package/README.md +185 -0
- package/SECURITY.md +11 -0
- package/assets/dory_logo.png +0 -0
- package/bin/dory.js +131 -0
- package/docs/README.md +2 -0
- package/docs/configuration/app-settings/feature-flags.mdx +157 -0
- package/docs/configuration/app-settings/user-settings.mdx +201 -0
- package/docs/configuration/installation-setup/env-vars.mdx +198 -0
- package/docs/configuration/installation-setup/installation.mdx +127 -0
- package/docs/dory.json +162 -0
- package/docs/integrations/analytics-monitoring/error-tracking.mdx +199 -0
- package/docs/integrations/analytics-monitoring/notifications.mdx +189 -0
- package/docs/integrations/analytics-monitoring/user-analytics.mdx +182 -0
- package/docs/integrations/shopify-integration/shopify-setup.mdx +226 -0
- package/docs/integrations/shopify-integration/shopify-webhooks.mdx +207 -0
- package/docs/overview/developer-overview/dev-architecture.mdx +292 -0
- package/docs/overview/product-intro/app-overview.mdx +167 -0
- package/docs/overview/product-intro/architecture.mdx +203 -0
- package/docs/overview/product-intro/core-concepts.mdx +267 -0
- package/docs/platform-admin/billing-subscriptions/billing.mdx +176 -0
- package/docs/platform-admin/system-maintenance/db-migrations.mdx +184 -0
- package/docs/platform-admin/system-maintenance/health-monitoring.mdx +222 -0
- package/docs/platform-admin/system-maintenance/queue-management.mdx +234 -0
- package/docs/platform-admin/user-team-management/teams.mdx +170 -0
- package/docs/platform-admin/user-team-management/users.mdx +185 -0
- package/docs/support/legal/dependency-analysis.mdx +132 -0
- package/docs/support/legal/privacy-terms.mdx +73 -0
- package/docs/support/troubleshooting/common-issues.mdx +159 -0
- package/docs/usage/api-usage/api-inventory.mdx +213 -0
- package/docs/usage/api-usage/api-products.mdx +249 -0
- package/docs/usage/api-usage/api-purchase-orders.mdx +289 -0
- package/docs/usage/api-usage/api-reports.mdx +227 -0
- package/docs/usage/api-usage/api-suppliers.mdx +171 -0
- package/docs/usage/api-usage/api-user-management.mdx +171 -0
- package/docs/usage/ui-workflows/bundles.mdx +157 -0
- package/docs/usage/ui-workflows/demand-planning.mdx +210 -0
- package/docs/usage/ui-workflows/favorites.mdx +167 -0
- package/docs/usage/ui-workflows/inventory-management.mdx +179 -0
- package/docs/usage/ui-workflows/onboarding.mdx +215 -0
- package/docs/usage/ui-workflows/purchase-orders.mdx +200 -0
- package/docs/usage/ui-workflows/reports.mdx +257 -0
- package/index.html +54 -0
- package/k8s/Dockerfile +15 -0
- package/k8s/config/nginx-default.conf +22 -0
- package/k8s/dev.Dockerfile +16 -0
- package/package.json +71 -0
- package/src/app.css +3 -0
- package/src/app.tsx +13 -0
- package/src/components/dark-mode-toggle.tsx +25 -0
- package/src/components/dropdown.tsx +49 -0
- package/src/components/hooks.tsx +28 -0
- package/src/components/loading.tsx +23 -0
- package/src/components/mdx-provider.tsx +13 -0
- package/src/components/navigation.tsx +53 -0
- package/src/components/notification.tsx +97 -0
- package/src/components/routes.tsx +89 -0
- package/src/components/search-index.ts +43 -0
- package/src/components/search.tsx +508 -0
- package/src/components/store.ts +144 -0
- package/src/index.css +24 -0
- package/src/main.tsx +18 -0
- package/src/mdx/accordion.tsx +48 -0
- package/src/mdx/api-playground.tsx +674 -0
- package/src/mdx/api.tsx +354 -0
- package/src/mdx/callout.tsx +80 -0
- package/src/mdx/card.tsx +48 -0
- package/src/mdx/code.tsx +289 -0
- package/src/mdx/expandable.tsx +40 -0
- package/src/mdx/fence.tsx +73 -0
- package/src/mdx/icon.tsx +73 -0
- package/src/mdx/latex.tsx +26 -0
- package/src/mdx/list.tsx +7 -0
- package/src/mdx/mdx.tsx +43 -0
- package/src/mdx/mermaid.tsx +102 -0
- package/src/mdx/open-api.tsx +325 -0
- package/src/mdx/source.tsx +47 -0
- package/src/mdx/step.tsx +49 -0
- package/src/mdx/table.tsx +15 -0
- package/src/mdx/tag.tsx +66 -0
- package/src/mdx/text.tsx +98 -0
- package/src/plugins/sanitize.ts +15 -0
- package/src/theme.scss +124 -0
- package/src/ui/header.tsx +88 -0
- package/src/ui/layout.tsx +167 -0
- package/src/ui/mobile-navigation.tsx +91 -0
- package/src/ui/prev-next-link.tsx +74 -0
- package/src/ui/table-of-content.tsx +166 -0
- package/src/utils/fonts.ts +20 -0
- package/src/utils/functions.ts +12 -0
- package/src/utils/hooks.ts +46 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.app.json +32 -0
- package/tsconfig.json +7 -0
- package/tsconfig.node.json +25 -0
- package/vite.config.ts +27 -0
package/src/mdx/code.tsx
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from '@headlessui/react'
|
|
2
|
+
import { ClipboardIcon } from '@heroicons/react/24/outline'
|
|
3
|
+
import classNames from 'classnames'
|
|
4
|
+
import {
|
|
5
|
+
Children,
|
|
6
|
+
createContext,
|
|
7
|
+
isValidElement,
|
|
8
|
+
useEffect,
|
|
9
|
+
useState
|
|
10
|
+
} from 'react'
|
|
11
|
+
import { Tag } from './tag'
|
|
12
|
+
import { toTitleCase } from '../utils/functions'
|
|
13
|
+
|
|
14
|
+
export const supportedLanguages = {
|
|
15
|
+
'python': 'Python',
|
|
16
|
+
'py': 'Python',
|
|
17
|
+
'javascript': 'JavaScript',
|
|
18
|
+
'js': 'JavaScript',
|
|
19
|
+
'typescript': 'TypeScript',
|
|
20
|
+
'ts': 'TypeScript',
|
|
21
|
+
'java': 'Java',
|
|
22
|
+
'c': 'C',
|
|
23
|
+
'cpp': 'C++',
|
|
24
|
+
'csharp': 'C#',
|
|
25
|
+
'cs': 'C#',
|
|
26
|
+
'rust': 'Rust',
|
|
27
|
+
'go': 'Go',
|
|
28
|
+
'ruby': 'Ruby',
|
|
29
|
+
'php': 'PHP',
|
|
30
|
+
'swift': 'Swift',
|
|
31
|
+
'kotlin': 'Kotlin',
|
|
32
|
+
'scala': 'Scala',
|
|
33
|
+
'haskell': 'Haskell',
|
|
34
|
+
'erlang': 'Erlang',
|
|
35
|
+
'elixir': 'Elixir',
|
|
36
|
+
'markdown': 'Markdown',
|
|
37
|
+
'md': 'Markdown',
|
|
38
|
+
'html': 'HTML',
|
|
39
|
+
'css': 'CSS',
|
|
40
|
+
'json': 'JSON',
|
|
41
|
+
'yaml': 'YAML',
|
|
42
|
+
'yml': 'YAML',
|
|
43
|
+
'toml': 'TOML',
|
|
44
|
+
'bash': 'Bash',
|
|
45
|
+
'markup': 'Markup',
|
|
46
|
+
'xml': 'XML',
|
|
47
|
+
'jsx': 'JSX',
|
|
48
|
+
'tsx': 'TSX',
|
|
49
|
+
'objectivec': 'Objective-C',
|
|
50
|
+
'js-extras': 'JavaScript Extras',
|
|
51
|
+
'reason': 'Reason',
|
|
52
|
+
'graphql': 'GraphQL',
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getPanelTitle(props: { className?: string }) {
|
|
56
|
+
const { className } = props
|
|
57
|
+
if (className) {
|
|
58
|
+
const language = className.replace('language-', '') as keyof typeof supportedLanguages;
|
|
59
|
+
return supportedLanguages[language] || toTitleCase(language)
|
|
60
|
+
}
|
|
61
|
+
return 'Code'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function CopyButton({ code }: { code: string }) {
|
|
65
|
+
const [copyCount, setCopyCount] = useState(0)
|
|
66
|
+
const copied = copyCount > 0
|
|
67
|
+
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
if (copied) {
|
|
70
|
+
const timeout = setTimeout(() => setCopyCount(0), 1000)
|
|
71
|
+
return () => clearTimeout(timeout)
|
|
72
|
+
}
|
|
73
|
+
}, [copied])
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<button
|
|
77
|
+
type="button"
|
|
78
|
+
className={classNames(
|
|
79
|
+
'group/button absolute top-2 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',
|
|
80
|
+
{
|
|
81
|
+
'bg-emerald-400/10 ring-1 ring-emerald-400/20 ring-inset': copied,
|
|
82
|
+
'bg-white/5 hover:bg-white/7.5 dark:bg-white/2.5 dark:hover:bg-white/5': !copied,
|
|
83
|
+
}
|
|
84
|
+
)}
|
|
85
|
+
onClick={() => {
|
|
86
|
+
window.navigator.clipboard.writeText(code).then(() => {
|
|
87
|
+
setCopyCount((count) => count + 1)
|
|
88
|
+
})
|
|
89
|
+
}}
|
|
90
|
+
>
|
|
91
|
+
<span
|
|
92
|
+
aria-hidden={copied}
|
|
93
|
+
className={classNames(
|
|
94
|
+
'pointer-events-none flex items-center gap-0.5 text-zinc-400 transition duration-300 text-sm',
|
|
95
|
+
copied && '-translate-y-1.5 opacity-0',
|
|
96
|
+
)}
|
|
97
|
+
>
|
|
98
|
+
<ClipboardIcon className="h-5 w-5 fill-zinc-500/20 stroke-zinc-500 transition-colors group-hover/button:stroke-zinc-400" />
|
|
99
|
+
Copy
|
|
100
|
+
</span>
|
|
101
|
+
<span
|
|
102
|
+
aria-hidden={!copied}
|
|
103
|
+
className={classNames(
|
|
104
|
+
'pointer-events-none absolute inset-0 flex items-center justify-center text-emerald-400 transition duration-300 text-sm',
|
|
105
|
+
!copied && 'translate-y-1.5 opacity-0',
|
|
106
|
+
)}
|
|
107
|
+
>
|
|
108
|
+
Copied!
|
|
109
|
+
</span>
|
|
110
|
+
</button>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function CodePanelHeader({ tag, label }: { tag?: string; label?: string }) {
|
|
115
|
+
if (!tag && !label) return null
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<div className="flex h-9 items-center gap-2 border-y border-t-transparent border-b-white/7.5 bg-white/2.5 px-4 dark:border-b-white/5 dark:bg-white/1">
|
|
119
|
+
{tag && (
|
|
120
|
+
<div className="dark flex">
|
|
121
|
+
<Tag variant="small">{tag}</Tag>
|
|
122
|
+
</div>
|
|
123
|
+
)}
|
|
124
|
+
{tag && label && <span className="h-0.5 w-0.5 rounded-full bg-zinc-500" />}
|
|
125
|
+
{label && <span className="font-mono text-xs text-zinc-400">{label}</span>}
|
|
126
|
+
</div>
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function CodePanel({
|
|
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
|
|
145
|
+
tag = child.props.tag ?? tag
|
|
146
|
+
// @ts-expect-error
|
|
147
|
+
label = child.props.label ?? label
|
|
148
|
+
// @ts-expect-error
|
|
149
|
+
code = child.props.code ?? code
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (!code) {
|
|
153
|
+
// @ts-expect-error
|
|
154
|
+
code = child.props.children
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<div className="group dark:bg-white/2.5">
|
|
159
|
+
<CodePanelHeader tag={tag} label={label} />
|
|
160
|
+
<div className="relative">
|
|
161
|
+
<pre className="overflow-x-auto p-4 text-xs text-white">{children}</pre>
|
|
162
|
+
<CopyButton code={code ?? ''} />
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function CodeGroupHeader({
|
|
169
|
+
title,
|
|
170
|
+
children,
|
|
171
|
+
selectedIndex,
|
|
172
|
+
}: {
|
|
173
|
+
title: string
|
|
174
|
+
children: React.ReactNode
|
|
175
|
+
selectedIndex: number
|
|
176
|
+
}) {
|
|
177
|
+
const hasTabs = Children.count(children) > 1
|
|
178
|
+
|
|
179
|
+
if (!title && !hasTabs) return null
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<div className="flex min-h-[calc(--spacing(12)+1px)] flex-wrap items-start gap-x-4 bg-black/5 px-4 dark:border-zinc-800 dark:bg-transparent">
|
|
183
|
+
{title && (
|
|
184
|
+
<p className="mr-auto pt-3 text-xs font-semibold text-white">
|
|
185
|
+
{title}
|
|
186
|
+
</p>
|
|
187
|
+
)}
|
|
188
|
+
{hasTabs && (
|
|
189
|
+
<TabList className="-mb-px flex gap-4 text-xs font-medium">
|
|
190
|
+
{Children.map(children, (child, index) => {
|
|
191
|
+
if (!isValidElement(child)) return null
|
|
192
|
+
|
|
193
|
+
const childProps = (child as preact.JSX.Element).props?.children?.props ?? {}
|
|
194
|
+
const panelTitle = getPanelTitle(childProps)
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<Tab
|
|
198
|
+
key={index}
|
|
199
|
+
className={classNames(
|
|
200
|
+
'border-b py-3 transition data-selected:not-data-focus:outline-hidden',
|
|
201
|
+
index === selectedIndex
|
|
202
|
+
? 'border-emerald-500 text-emerald-400'
|
|
203
|
+
: 'border-transparent text-zinc-400 hover:text-zinc-300',
|
|
204
|
+
)}
|
|
205
|
+
>
|
|
206
|
+
{panelTitle}
|
|
207
|
+
</Tab>
|
|
208
|
+
)
|
|
209
|
+
})}
|
|
210
|
+
</TabList>
|
|
211
|
+
)}
|
|
212
|
+
</div>
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function CodeGroupPanels({
|
|
217
|
+
children,
|
|
218
|
+
...props
|
|
219
|
+
}: React.ComponentPropsWithoutRef<typeof CodePanel>) {
|
|
220
|
+
const hasTabs = Children.count(children) > 1
|
|
221
|
+
|
|
222
|
+
if (hasTabs) {
|
|
223
|
+
return (
|
|
224
|
+
<TabPanels>
|
|
225
|
+
{Children.map(children, (child, index) => (
|
|
226
|
+
<TabPanel key={index}>
|
|
227
|
+
<CodePanel {...props}>{child}</CodePanel>
|
|
228
|
+
</TabPanel>
|
|
229
|
+
))}
|
|
230
|
+
</TabPanels>
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return <CodePanel {...props}>{children}</CodePanel>
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export const CodeGroupContext = createContext(false)
|
|
238
|
+
|
|
239
|
+
export function CodeGroup({
|
|
240
|
+
children,
|
|
241
|
+
title,
|
|
242
|
+
...props
|
|
243
|
+
}: React.ComponentPropsWithoutRef<typeof CodeGroupPanels> & { title: string }) {
|
|
244
|
+
const [selectedIndex, setSelectedIndex] = useState(0)
|
|
245
|
+
|
|
246
|
+
const childrenArray = Children.toArray(children)
|
|
247
|
+
|
|
248
|
+
const hasTabs = childrenArray.length > 1
|
|
249
|
+
|
|
250
|
+
const containerClassName = 'docucod-mdx-code-group my-6 overflow-hidden rounded-2xl shadow-md dark:ring-1 dark:ring-white/10'
|
|
251
|
+
const header = (
|
|
252
|
+
<CodeGroupHeader title={title} selectedIndex={selectedIndex}>
|
|
253
|
+
{children}
|
|
254
|
+
</CodeGroupHeader>
|
|
255
|
+
)
|
|
256
|
+
const panels = <CodeGroupPanels {...props}>{children}</CodeGroupPanels>
|
|
257
|
+
|
|
258
|
+
return (
|
|
259
|
+
<CodeGroupContext.Provider value={true}>
|
|
260
|
+
{hasTabs ? (
|
|
261
|
+
<TabGroup
|
|
262
|
+
as="div"
|
|
263
|
+
selectedIndex={selectedIndex}
|
|
264
|
+
onChange={setSelectedIndex}
|
|
265
|
+
className={containerClassName}
|
|
266
|
+
>
|
|
267
|
+
<div className="not-prose">
|
|
268
|
+
{header}
|
|
269
|
+
{panels}
|
|
270
|
+
</div>
|
|
271
|
+
</TabGroup>
|
|
272
|
+
) : (
|
|
273
|
+
<div className={containerClassName}>
|
|
274
|
+
<div className="not-prose">
|
|
275
|
+
{header}
|
|
276
|
+
{panels}
|
|
277
|
+
</div>
|
|
278
|
+
</div>
|
|
279
|
+
)}
|
|
280
|
+
</CodeGroupContext.Provider>
|
|
281
|
+
)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function Code({
|
|
285
|
+
children,
|
|
286
|
+
...props
|
|
287
|
+
}: React.ComponentPropsWithoutRef<'code'>) {
|
|
288
|
+
return <code className={classNames('docucod-mdx-code rounded-md py-1 text-sm px-2', props.className)} {...props}>{children}</code>
|
|
289
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/react'
|
|
2
|
+
import { MinusIcon, PlusIcon } from '@heroicons/react/24/outline'
|
|
3
|
+
import { Icon } from './icon'
|
|
4
|
+
|
|
5
|
+
export function Expandable({
|
|
6
|
+
title,
|
|
7
|
+
children,
|
|
8
|
+
icon,
|
|
9
|
+
}: {
|
|
10
|
+
title: string
|
|
11
|
+
children: React.ReactNode
|
|
12
|
+
icon?: React.ComponentProps<typeof Icon>['icon']
|
|
13
|
+
}) {
|
|
14
|
+
return (
|
|
15
|
+
<Disclosure as="div" className="my-6 rounded-xl border border-slate-200 dark:border-slate-700">
|
|
16
|
+
{({ open }) => (
|
|
17
|
+
<>
|
|
18
|
+
<DisclosureButton className="group flex w-full items-start justify-between text-left text-slate-900 dark:text-white p-6">
|
|
19
|
+
<div className="flex items-center gap-2">
|
|
20
|
+
{icon && <Icon icon={icon} className="h-8 w-8" />}
|
|
21
|
+
<span className="text-base font-display text-slate-900 dark:text-white">{title}</span>
|
|
22
|
+
</div>
|
|
23
|
+
<span className="ml-6 flex h-7 items-center">
|
|
24
|
+
{open ? (
|
|
25
|
+
<MinusIcon className="h-6 w-6 text-slate-700 dark:text-slate-400" />
|
|
26
|
+
) : (
|
|
27
|
+
<PlusIcon className="h-6 w-6 text-slate-700 dark:text-slate-400" />
|
|
28
|
+
)}
|
|
29
|
+
</span>
|
|
30
|
+
</DisclosureButton>
|
|
31
|
+
<DisclosurePanel as="dd" className="px-6 pb-6">
|
|
32
|
+
<div className="text-sm text-slate-700 dark:text-slate-400 leading-tight [&>p]:mb-0">
|
|
33
|
+
{children}
|
|
34
|
+
</div>
|
|
35
|
+
</DisclosurePanel>
|
|
36
|
+
</>
|
|
37
|
+
)}
|
|
38
|
+
</Disclosure>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Fragment, Suspense, useContext } from 'preact/compat'
|
|
2
|
+
import { Highlight, themes } from 'prism-react-renderer'
|
|
3
|
+
import { Code, CodeGroup, CodeGroupContext, supportedLanguages } from './code'
|
|
4
|
+
import { MermaidRenderer } from './mermaid'
|
|
5
|
+
import { Loading } from '../components/loading'
|
|
6
|
+
import { useDarkMode } from '../utils/hooks'
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
export function Fence({
|
|
10
|
+
children,
|
|
11
|
+
language,
|
|
12
|
+
}: {
|
|
13
|
+
children: string
|
|
14
|
+
language: string
|
|
15
|
+
}) {
|
|
16
|
+
const { isDark } = useDarkMode();
|
|
17
|
+
|
|
18
|
+
if (language === 'mermaid') {
|
|
19
|
+
return (
|
|
20
|
+
<div className="mermaid-container">
|
|
21
|
+
<Suspense fallback={<Loading />}>
|
|
22
|
+
<MermaidRenderer content={children} />
|
|
23
|
+
</Suspense>
|
|
24
|
+
</div>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const supportedLanguage = supportedLanguages[language as keyof typeof supportedLanguages] ?? language
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Highlight
|
|
32
|
+
code={children.trimEnd()}
|
|
33
|
+
language={supportedLanguage}
|
|
34
|
+
theme={isDark ? themes.vsDark : themes.vsLight}>
|
|
35
|
+
{({ className, style, tokens, getTokenProps }) => (
|
|
36
|
+
<pre>
|
|
37
|
+
<Code className={className} style={style} title={supportedLanguage}>
|
|
38
|
+
{tokens.map((line, lineIndex) => (
|
|
39
|
+
<Fragment key={lineIndex}>
|
|
40
|
+
{line
|
|
41
|
+
.filter((token) => !token.empty)
|
|
42
|
+
.map((token, tokenIndex) => (
|
|
43
|
+
<span key={tokenIndex} {...getTokenProps({ token })} />
|
|
44
|
+
))}
|
|
45
|
+
{'\n'}
|
|
46
|
+
</Fragment>
|
|
47
|
+
))}
|
|
48
|
+
</Code>
|
|
49
|
+
</pre>
|
|
50
|
+
)}
|
|
51
|
+
</Highlight>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
export function Pre(props: { children: { props: { className?: string; children?: string } } }) {
|
|
55
|
+
const child = props.children?.props
|
|
56
|
+
|
|
57
|
+
// Safely handle className that might not be a string
|
|
58
|
+
const className = child?.className || ''
|
|
59
|
+
const language = supportedLanguages[className.replace('language-', '') as keyof typeof supportedLanguages] ?? className.replace('language-', '')
|
|
60
|
+
const code = child?.children || ''
|
|
61
|
+
|
|
62
|
+
// Check if we're inside a CodeGroup context
|
|
63
|
+
const isInCodeGroup = useContext(CodeGroupContext);
|
|
64
|
+
|
|
65
|
+
const fence = <Fence language={language}>{code}</Fence>
|
|
66
|
+
|
|
67
|
+
// If we're not in a CodeGroup, wrap the Fence in one
|
|
68
|
+
if (!isInCodeGroup && language !== 'mermaid') {
|
|
69
|
+
return <CodeGroup title={language}>{fence}</CodeGroup>
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return fence
|
|
73
|
+
}
|
package/src/mdx/icon.tsx
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { library } from '@fortawesome/fontawesome-svg-core'
|
|
2
|
+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
|
3
|
+
import * as solidIcons from '@fortawesome/free-solid-svg-icons'
|
|
4
|
+
import * as regularIcons from '@fortawesome/free-regular-svg-icons'
|
|
5
|
+
import * as brandIcons from '@fortawesome/free-brands-svg-icons'
|
|
6
|
+
import classNames from 'classnames'
|
|
7
|
+
import * as LucideIcons from 'lucide-react'
|
|
8
|
+
|
|
9
|
+
// Map types to icon sets and FontAwesome prefixes
|
|
10
|
+
const iconSets = {
|
|
11
|
+
solid: solidIcons,
|
|
12
|
+
regular: regularIcons,
|
|
13
|
+
brands: brandIcons,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const prefixes = {
|
|
17
|
+
solid: 'fas',
|
|
18
|
+
regular: 'far',
|
|
19
|
+
brands: 'fab',
|
|
20
|
+
} as const
|
|
21
|
+
|
|
22
|
+
export function Icon({
|
|
23
|
+
icon,
|
|
24
|
+
iconType = 'solid',
|
|
25
|
+
color = '',
|
|
26
|
+
size = 24,
|
|
27
|
+
className,
|
|
28
|
+
...rest
|
|
29
|
+
}: {
|
|
30
|
+
icon: string
|
|
31
|
+
iconType?: keyof typeof iconSets
|
|
32
|
+
color?: string
|
|
33
|
+
size?: number
|
|
34
|
+
} & Omit<React.ComponentPropsWithoutRef<'svg'>, 'color'>) {
|
|
35
|
+
// Convert kebab-case or snake_case to PascalCase (for Lucide and FA variable names)
|
|
36
|
+
const pascalCase = icon
|
|
37
|
+
.replace(/[-_](.)/g, (_, group1) => group1.toUpperCase())
|
|
38
|
+
.replace(/^(.)/, (_, group1) => group1.toUpperCase())
|
|
39
|
+
|
|
40
|
+
// --- Try Lucide First ---
|
|
41
|
+
const LucideComponent = LucideIcons[pascalCase as keyof typeof LucideIcons] as any
|
|
42
|
+
if (LucideComponent) {
|
|
43
|
+
return (
|
|
44
|
+
<LucideComponent
|
|
45
|
+
className={classNames("stroke-black dark:stroke-white", className)}
|
|
46
|
+
color={color}
|
|
47
|
+
size={size}
|
|
48
|
+
{...rest}
|
|
49
|
+
/>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// --- Try Font Awesome ---
|
|
54
|
+
const faSet = iconSets[iconType] ?? solidIcons
|
|
55
|
+
const iconKey = `fa${pascalCase}` as keyof typeof faSet
|
|
56
|
+
const faIcon = faSet[iconKey]
|
|
57
|
+
|
|
58
|
+
if (faIcon && typeof faIcon === 'object' && 'iconName' in faIcon) {
|
|
59
|
+
library.add(faIcon)
|
|
60
|
+
return (
|
|
61
|
+
<FontAwesomeIcon
|
|
62
|
+
icon={[prefixes[iconType] || 'fas', faIcon.iconName] as any}
|
|
63
|
+
className={classNames("stroke-black dark:stroke-white", className)}
|
|
64
|
+
color={color as any}
|
|
65
|
+
size={size as any}
|
|
66
|
+
{...rest as any}
|
|
67
|
+
/>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
console.warn(`⚠️ Icon not found: "${icon}" (converted to ${iconKey}) in ${iconType}`)
|
|
72
|
+
return null // or return a default/fallback icon if preferred
|
|
73
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react'
|
|
2
|
+
import katex from 'katex'
|
|
3
|
+
import 'katex/dist/katex.min.css'
|
|
4
|
+
|
|
5
|
+
interface LatexProps {
|
|
6
|
+
children: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function Latex({ children }: LatexProps) {
|
|
10
|
+
const containerRef = useRef<HTMLDivElement>(null)
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (containerRef.current) {
|
|
14
|
+
try {
|
|
15
|
+
katex.render(children, containerRef.current, {
|
|
16
|
+
displayMode: true,
|
|
17
|
+
throwOnError: false,
|
|
18
|
+
})
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.error('Error rendering LaTeX:', error)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}, [children])
|
|
24
|
+
|
|
25
|
+
return <div ref={containerRef} className="my-4" />
|
|
26
|
+
}
|
package/src/mdx/list.tsx
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { JSX } from 'preact/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
export const UnorderedList = (props: JSX.IntrinsicElements['ul']) => <ul className="list-disc list-inside" {...props} />;
|
|
4
|
+
|
|
5
|
+
export const OrderedList = (props: JSX.IntrinsicElements['ol']) => <ol className="list-decimal list-inside" {...props} />;
|
|
6
|
+
|
|
7
|
+
export const ListItem = (props: JSX.IntrinsicElements['li']) => <li className="ml-4 mt-2" {...props} />;
|
package/src/mdx/mdx.tsx
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'preact/hooks';
|
|
2
|
+
import { usePathname } from 'wouter-preact/use-browser-location';
|
|
3
|
+
import { completeFrontMatter } from '../components/store';
|
|
4
|
+
|
|
5
|
+
export { h1, h2, h3, h4, h5, h6, p, Row, Col, Anchor as a, Image as img } from './text';
|
|
6
|
+
export { Code as code, CodeGroup } from './code';
|
|
7
|
+
export * from './tag';
|
|
8
|
+
export { Pre as pre } from './fence';
|
|
9
|
+
export { Accordion, AccordionGroup } from './accordion';
|
|
10
|
+
export { Icon } from './icon';
|
|
11
|
+
export { Card, CardGroup } from './card';
|
|
12
|
+
export { Callout, Note, Warning, Info, Tip, Check } from './callout';
|
|
13
|
+
export { Latex } from './latex';
|
|
14
|
+
export { API } from './api';
|
|
15
|
+
export { Expandable } from './expandable';
|
|
16
|
+
export { ResponseField, Properties, Property } from './open-api';
|
|
17
|
+
export { Steps, Step } from './step';
|
|
18
|
+
export { Table as table, Th as th, Td as td } from './table';
|
|
19
|
+
export { UnorderedList as ul, OrderedList as ol, ListItem as li } from './list';
|
|
20
|
+
export { APIPlayground } from './api-playground';
|
|
21
|
+
export { Source } from './source';
|
|
22
|
+
|
|
23
|
+
export const wrapper = ({ children }: { children: preact.ComponentChildren }) => {
|
|
24
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
25
|
+
const pathname = usePathname();
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (ref.current) {
|
|
29
|
+
// Hide the first h1 if it matches the title
|
|
30
|
+
// This is to avoid having the same title twice in the navigation
|
|
31
|
+
const title = completeFrontMatter.find(fm => fm.path === pathname)?.title;
|
|
32
|
+
const h1 = ref.current.querySelector('h1') as HTMLHeadingElement;
|
|
33
|
+
if (h1) {
|
|
34
|
+
h1.hidden = title === h1.innerText;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}, [pathname]);
|
|
38
|
+
|
|
39
|
+
return <div className="flex flex-col w-full" ref={ref}>{children}</div>;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
export const hr = () => <hr className="my-4 text-gray-500/25 dark:text-gray-200/25" />;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import classNames from 'classnames'
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from 'preact/compat'
|
|
3
|
+
|
|
4
|
+
export function MermaidRenderer({ content }: { content: string }) {
|
|
5
|
+
const mermaidRef = useRef<HTMLDivElement>(null)
|
|
6
|
+
const [isFullscreen, setIsFullscreen] = useState(false)
|
|
7
|
+
const [isLoaded, setIsLoaded] = useState(false)
|
|
8
|
+
const [error, setError] = useState<string | null>(null)
|
|
9
|
+
|
|
10
|
+
const diagramId = useMemo(() => `mermaid-${btoa(content).replace(/[^a-zA-Z0-9]/g, '').substring(0, 10)}`, [content])
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
let mounted = true
|
|
14
|
+
|
|
15
|
+
const renderMermaid = async () => {
|
|
16
|
+
try {
|
|
17
|
+
const mermaid = (await import('mermaid')).default
|
|
18
|
+
mermaid.initialize({ startOnLoad: true });
|
|
19
|
+
|
|
20
|
+
if (!mounted || !mermaidRef.current) return
|
|
21
|
+
|
|
22
|
+
mermaid.initialize({
|
|
23
|
+
startOnLoad: true,
|
|
24
|
+
theme: 'base',
|
|
25
|
+
themeVariables: {
|
|
26
|
+
fontSize: '14px',
|
|
27
|
+
nodePadding: 20, // More padding inside nodes
|
|
28
|
+
notePadding: 15,
|
|
29
|
+
padding: 10, // Diagram-wide padding
|
|
30
|
+
nodeBorderRadius: 8, // Rounded corners for nodes
|
|
31
|
+
},
|
|
32
|
+
flowchart: {
|
|
33
|
+
nodeSpacing: 50, // horizontal spacing between nodes
|
|
34
|
+
rankSpacing: 80, // vertical spacing between layers
|
|
35
|
+
padding: 20,
|
|
36
|
+
useMaxWidth: true,
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
if (mermaidRef.current) {
|
|
41
|
+
mermaidRef.current.innerHTML = ''
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const contentWithoutStyle = content
|
|
45
|
+
.replace(/^\s*style .*$/gm, '')
|
|
46
|
+
.replace(/^\s*classDef .*$/gm, '')
|
|
47
|
+
.replace(/^\s*class .*$/gm, '');
|
|
48
|
+
|
|
49
|
+
const { svg } = await mermaid.render(diagramId, contentWithoutStyle);
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
if (mounted && mermaidRef.current) {
|
|
53
|
+
mermaidRef.current.innerHTML = svg
|
|
54
|
+
setIsLoaded(true)
|
|
55
|
+
}
|
|
56
|
+
} catch (err) {
|
|
57
|
+
console.error('Mermaid rendering error:', err)
|
|
58
|
+
if (mounted) {
|
|
59
|
+
setError(err instanceof Error ? err.message : 'Failed to render diagram')
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
renderMermaid()
|
|
65
|
+
|
|
66
|
+
return () => {
|
|
67
|
+
mounted = false
|
|
68
|
+
}
|
|
69
|
+
}, [content, diagramId])
|
|
70
|
+
|
|
71
|
+
const toggleFullscreen = () => {
|
|
72
|
+
setIsFullscreen(!isFullscreen)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (error) {
|
|
76
|
+
return (
|
|
77
|
+
<div className="flex items-center justify-center py-8 text-red-400 bg-red-900/20 border border-red-800 rounded">
|
|
78
|
+
<span>Error rendering diagram: {error}</span>
|
|
79
|
+
</div>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<div className="mermaid-wrapper my-8">
|
|
85
|
+
{!isLoaded && (
|
|
86
|
+
<div className="flex items-center justify-center py-8 text-gray-400">
|
|
87
|
+
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
|
|
88
|
+
<span className="ml-2">Loading diagram...</span>
|
|
89
|
+
</div>
|
|
90
|
+
)}
|
|
91
|
+
<div
|
|
92
|
+
ref={mermaidRef}
|
|
93
|
+
className={classNames('mermaid cursor-pointer transition-all duration-300 rounded-lg p-4 bg-white', {
|
|
94
|
+
'fixed inset-0 z-50 bg-black/90 flex items-center justify-center backdrop-blur-sm': isFullscreen,
|
|
95
|
+
'opacity-0': !isLoaded,
|
|
96
|
+
'opacity-100': isLoaded,
|
|
97
|
+
})}
|
|
98
|
+
onClick={toggleFullscreen}
|
|
99
|
+
/>
|
|
100
|
+
</div>
|
|
101
|
+
)
|
|
102
|
+
}
|