@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.
Files changed (104) hide show
  1. package/.dockerignore +4 -0
  2. package/.github/ISSUE_TEMPLATE/anything-else.md +10 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.md +39 -0
  4. package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  5. package/.github/dependabot.yml +11 -0
  6. package/.github/workflows/claude.yml +37 -0
  7. package/CODE_OF_CONDUCT.md +128 -0
  8. package/CONTRIBUTING.md +91 -0
  9. package/LICENSE +201 -0
  10. package/README.md +185 -0
  11. package/SECURITY.md +11 -0
  12. package/assets/dory_logo.png +0 -0
  13. package/bin/dory.js +131 -0
  14. package/docs/README.md +2 -0
  15. package/docs/configuration/app-settings/feature-flags.mdx +157 -0
  16. package/docs/configuration/app-settings/user-settings.mdx +201 -0
  17. package/docs/configuration/installation-setup/env-vars.mdx +198 -0
  18. package/docs/configuration/installation-setup/installation.mdx +127 -0
  19. package/docs/dory.json +162 -0
  20. package/docs/integrations/analytics-monitoring/error-tracking.mdx +199 -0
  21. package/docs/integrations/analytics-monitoring/notifications.mdx +189 -0
  22. package/docs/integrations/analytics-monitoring/user-analytics.mdx +182 -0
  23. package/docs/integrations/shopify-integration/shopify-setup.mdx +226 -0
  24. package/docs/integrations/shopify-integration/shopify-webhooks.mdx +207 -0
  25. package/docs/overview/developer-overview/dev-architecture.mdx +292 -0
  26. package/docs/overview/product-intro/app-overview.mdx +167 -0
  27. package/docs/overview/product-intro/architecture.mdx +203 -0
  28. package/docs/overview/product-intro/core-concepts.mdx +267 -0
  29. package/docs/platform-admin/billing-subscriptions/billing.mdx +176 -0
  30. package/docs/platform-admin/system-maintenance/db-migrations.mdx +184 -0
  31. package/docs/platform-admin/system-maintenance/health-monitoring.mdx +222 -0
  32. package/docs/platform-admin/system-maintenance/queue-management.mdx +234 -0
  33. package/docs/platform-admin/user-team-management/teams.mdx +170 -0
  34. package/docs/platform-admin/user-team-management/users.mdx +185 -0
  35. package/docs/support/legal/dependency-analysis.mdx +132 -0
  36. package/docs/support/legal/privacy-terms.mdx +73 -0
  37. package/docs/support/troubleshooting/common-issues.mdx +159 -0
  38. package/docs/usage/api-usage/api-inventory.mdx +213 -0
  39. package/docs/usage/api-usage/api-products.mdx +249 -0
  40. package/docs/usage/api-usage/api-purchase-orders.mdx +289 -0
  41. package/docs/usage/api-usage/api-reports.mdx +227 -0
  42. package/docs/usage/api-usage/api-suppliers.mdx +171 -0
  43. package/docs/usage/api-usage/api-user-management.mdx +171 -0
  44. package/docs/usage/ui-workflows/bundles.mdx +157 -0
  45. package/docs/usage/ui-workflows/demand-planning.mdx +210 -0
  46. package/docs/usage/ui-workflows/favorites.mdx +167 -0
  47. package/docs/usage/ui-workflows/inventory-management.mdx +179 -0
  48. package/docs/usage/ui-workflows/onboarding.mdx +215 -0
  49. package/docs/usage/ui-workflows/purchase-orders.mdx +200 -0
  50. package/docs/usage/ui-workflows/reports.mdx +257 -0
  51. package/index.html +54 -0
  52. package/k8s/Dockerfile +15 -0
  53. package/k8s/config/nginx-default.conf +22 -0
  54. package/k8s/dev.Dockerfile +16 -0
  55. package/package.json +71 -0
  56. package/src/app.css +3 -0
  57. package/src/app.tsx +13 -0
  58. package/src/components/dark-mode-toggle.tsx +25 -0
  59. package/src/components/dropdown.tsx +49 -0
  60. package/src/components/hooks.tsx +28 -0
  61. package/src/components/loading.tsx +23 -0
  62. package/src/components/mdx-provider.tsx +13 -0
  63. package/src/components/navigation.tsx +53 -0
  64. package/src/components/notification.tsx +97 -0
  65. package/src/components/routes.tsx +89 -0
  66. package/src/components/search-index.ts +43 -0
  67. package/src/components/search.tsx +508 -0
  68. package/src/components/store.ts +144 -0
  69. package/src/index.css +24 -0
  70. package/src/main.tsx +18 -0
  71. package/src/mdx/accordion.tsx +48 -0
  72. package/src/mdx/api-playground.tsx +674 -0
  73. package/src/mdx/api.tsx +354 -0
  74. package/src/mdx/callout.tsx +80 -0
  75. package/src/mdx/card.tsx +48 -0
  76. package/src/mdx/code.tsx +289 -0
  77. package/src/mdx/expandable.tsx +40 -0
  78. package/src/mdx/fence.tsx +73 -0
  79. package/src/mdx/icon.tsx +73 -0
  80. package/src/mdx/latex.tsx +26 -0
  81. package/src/mdx/list.tsx +7 -0
  82. package/src/mdx/mdx.tsx +43 -0
  83. package/src/mdx/mermaid.tsx +102 -0
  84. package/src/mdx/open-api.tsx +325 -0
  85. package/src/mdx/source.tsx +47 -0
  86. package/src/mdx/step.tsx +49 -0
  87. package/src/mdx/table.tsx +15 -0
  88. package/src/mdx/tag.tsx +66 -0
  89. package/src/mdx/text.tsx +98 -0
  90. package/src/plugins/sanitize.ts +15 -0
  91. package/src/theme.scss +124 -0
  92. package/src/ui/header.tsx +88 -0
  93. package/src/ui/layout.tsx +167 -0
  94. package/src/ui/mobile-navigation.tsx +91 -0
  95. package/src/ui/prev-next-link.tsx +74 -0
  96. package/src/ui/table-of-content.tsx +166 -0
  97. package/src/utils/fonts.ts +20 -0
  98. package/src/utils/functions.ts +12 -0
  99. package/src/utils/hooks.ts +46 -0
  100. package/src/vite-env.d.ts +1 -0
  101. package/tsconfig.app.json +32 -0
  102. package/tsconfig.json +7 -0
  103. package/tsconfig.node.json +25 -0
  104. package/vite.config.ts +27 -0
@@ -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
+ }
@@ -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
+ }
@@ -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} />;
@@ -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
+ }