@clidey/dory 1.0.8 → 1.10.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/README.md +59 -164
- package/bin/chat-server.ts +270 -0
- package/bin/dist/analytics-server.d.ts +11 -0
- package/bin/dist/analytics-server.d.ts.map +1 -0
- package/bin/dist/analytics-server.js +121 -0
- package/bin/dist/analytics-server.js.map +1 -0
- package/bin/dist/automation.d.ts +35 -0
- package/bin/dist/automation.d.ts.map +1 -0
- package/bin/dist/automation.js +259 -0
- package/bin/dist/automation.js.map +1 -0
- package/bin/dist/chat-server.d.ts +8 -0
- package/bin/dist/chat-server.d.ts.map +1 -0
- package/bin/dist/chat-server.js +322 -0
- package/bin/dist/chat-server.js.map +1 -0
- package/bin/dist/check.d.ts +16 -0
- package/bin/dist/check.d.ts.map +1 -0
- package/bin/dist/check.js +247 -0
- package/bin/dist/check.js.map +1 -0
- package/bin/dist/dependency-overlay.d.ts +7 -0
- package/bin/dist/dependency-overlay.d.ts.map +1 -0
- package/bin/dist/dependency-overlay.js +137 -0
- package/bin/dist/dependency-overlay.js.map +1 -0
- package/bin/dist/dory.js +254 -402
- package/bin/dist/dory.js.map +1 -1
- package/bin/dist/i18n-init.d.ts +6 -0
- package/bin/dist/i18n-init.d.ts.map +1 -0
- package/bin/dist/i18n-init.js +56 -0
- package/bin/dist/i18n-init.js.map +1 -0
- package/bin/dist/mcp-http.d.ts +8 -0
- package/bin/dist/mcp-http.d.ts.map +1 -0
- package/bin/dist/mcp-http.js +72 -0
- package/bin/dist/mcp-http.js.map +1 -0
- package/bin/dist/mcp.d.ts +2 -0
- package/bin/dist/mcp.d.ts.map +1 -0
- package/bin/dist/mcp.js +81 -0
- package/bin/dist/mcp.js.map +1 -0
- package/bin/dist/preview-entry.d.ts +2 -0
- package/bin/dist/preview-entry.d.ts.map +1 -0
- package/bin/dist/preview-entry.js +6 -0
- package/bin/dist/preview-entry.js.map +1 -0
- package/bin/dist/preview-server.d.ts +5 -0
- package/bin/dist/preview-server.d.ts.map +1 -0
- package/bin/dist/preview-server.js +146 -0
- package/bin/dist/preview-server.js.map +1 -0
- package/bin/dist/preview-static.d.ts +7 -0
- package/bin/dist/preview-static.d.ts.map +1 -0
- package/bin/dist/preview-static.js +70 -0
- package/bin/dist/preview-static.js.map +1 -0
- package/bin/dist/project-config.d.ts +22 -0
- package/bin/dist/project-config.d.ts.map +1 -0
- package/bin/dist/project-config.js +44 -0
- package/bin/dist/project-config.js.map +1 -0
- package/dist/index.html +57 -0
- package/dist/runtime/dory-runtime.css +2 -0
- package/dist/runtime/dory-runtime.html +14 -0
- package/dist/runtime/dory-runtime.js +5490 -0
- package/docs/api-playground-demo.mdx +119 -0
- package/docs/api-reference/asyncapi.json +324 -0
- package/docs/api-reference/openapi.json +366 -0
- package/docs/api-reference/operations/create.mdx +5 -0
- package/docs/api-reference/operations/delete.mdx +5 -0
- package/docs/api-reference/operations/get.mdx +5 -0
- package/docs/api-reference/operations/update.mdx +5 -0
- package/docs/api-reference/websocket-operations/receive-chat-message.mdx +5 -0
- package/docs/api-reference/websocket-operations/receive-notifications.mdx +5 -0
- package/docs/api-reference/websocket-operations/receive-presence-update.mdx +5 -0
- package/docs/api-reference/websocket-operations/send-chat-message.mdx +5 -0
- package/docs/api-reference/websocket-operations/send-presence-update.mdx +5 -0
- package/docs/assets/dory.jpeg +0 -0
- package/docs/components/accordion.mdx +145 -0
- package/docs/components/callouts.mdx +134 -0
- package/docs/components/cards.mdx +153 -0
- package/docs/components/code-blocks.mdx +281 -0
- package/docs/components/expandable.mdx +189 -0
- package/docs/components/icons.mdx +71 -0
- package/docs/components/images.mdx +82 -0
- package/docs/components/latex.mdx +148 -0
- package/docs/components/layout.mdx +178 -0
- package/docs/components/lists.mdx +134 -0
- package/docs/components/mermaid.mdx +149 -0
- package/docs/components/overview.mdx +81 -0
- package/docs/components/source.mdx +130 -0
- package/docs/components/steps.mdx +122 -0
- package/docs/components/tables.mdx +114 -0
- package/docs/components/tags.mdx +179 -0
- package/docs/components/typography.mdx +185 -0
- package/docs/configuration/analytics.mdx +45 -0
- package/docs/configuration/chat.mdx +59 -0
- package/docs/configuration/dory-json.mdx +278 -0
- package/docs/configuration/embed.mdx +73 -0
- package/docs/configuration/fonts.mdx +86 -0
- package/docs/configuration/internationalization.mdx +61 -0
- package/docs/configuration/navigation.mdx +217 -0
- package/docs/configuration/theming.mdx +166 -0
- package/docs/contributing.mdx +47 -0
- package/docs/dory.json +136 -0
- package/docs/embed-example.html +176 -0
- package/docs/favicon.svg +18 -0
- package/docs/features.mdx +440 -0
- package/docs/getting-started/automation.mdx +76 -0
- package/docs/getting-started/configuration.mdx +95 -0
- package/docs/getting-started/custom-components.mdx +60 -0
- package/docs/getting-started/installation.mdx +88 -0
- package/docs/getting-started/quick-start.mdx +53 -0
- package/docs/introduction.mdx +50 -0
- package/docs/logo/logo.svg +18 -0
- package/docs/playgrounds/api-playground.mdx +122 -0
- package/docs/playgrounds/websocket-playground.mdx +98 -0
- package/docs/social-card.png +0 -0
- package/docs/social-card.svg +17 -0
- package/docs/websocket-playground-demo.mdx +135 -0
- package/package.json +23 -17
- package/scripts/verify-component-examples.mjs +126 -0
- package/scripts/verify-runtime-parity.mjs +147 -0
- package/src/app.css +22 -1
- package/src/assets/fonts/HankenGrotesk.ttf +0 -0
- package/src/assets/fonts/OFL.txt +94 -0
- package/src/components/chat-assistant.tsx +229 -0
- package/src/components/dropdown.tsx +3 -3
- package/src/components/error-boundary.tsx +2 -2
- package/src/components/hooks.tsx +20 -1
- package/src/components/navigation.tsx +82 -51
- package/src/components/notification.tsx +2 -3
- package/src/components/routes.tsx +17 -19
- package/src/components/search-index.test.ts +15 -0
- package/src/components/search-index.ts +49 -48
- package/src/components/search.tsx +208 -104
- package/src/components/store.ts +27 -9
- package/src/config/mdx-common.ts +11 -0
- package/src/config/mdx.ts +10 -11
- package/src/embed/loader.ts +0 -3
- package/src/embed/types.ts +0 -2
- package/src/embed/widget-iframe.ts +19 -21
- package/src/embedded-runtime/config.ts +138 -0
- package/src/embedded-runtime/index.tsx +680 -0
- package/src/embedded-runtime/runtime.html +14 -0
- package/src/entry-server.tsx +46 -124
- package/src/main.tsx +17 -5
- package/src/mdx/accordion.tsx +3 -3
- package/src/mdx/api-playground.tsx +18 -16
- package/src/mdx/async-api.tsx +2 -2
- package/src/mdx/card.tsx +3 -2
- package/src/mdx/code.tsx +11 -9
- package/src/mdx/fence.tsx +6 -5
- package/src/mdx/icon-grid.tsx +4 -1
- package/src/mdx/icon-names.test.ts +15 -0
- package/src/mdx/icon-names.ts +60 -0
- package/src/mdx/icon-source-variants.ts +102 -0
- package/src/mdx/icon.tsx +12 -73
- package/src/mdx/latex.test.ts +38 -0
- package/src/mdx/latex.tsx +42 -64
- package/src/mdx/mdx.tsx +1 -1
- package/src/mdx/mermaid.tsx +12 -6
- package/src/mdx/open-api.tsx +130 -74
- package/src/mdx/preprocess.test.ts +16 -0
- package/src/mdx/preprocess.ts +61 -0
- package/src/mdx/processor.test.ts +135 -37
- package/src/mdx/processor.ts +54 -92
- package/src/mdx/source.tsx +5 -4
- package/src/mdx/specimen.tsx +57 -43
- package/src/mdx/tabs.tsx +32 -21
- package/src/mdx/text.tsx +11 -11
- package/src/mdx/try-it-panel.tsx +284 -0
- package/src/mdx/type-scale.tsx +7 -7
- package/src/mdx/websocket-playground.tsx +21 -14
- package/src/plugins/analytics-injector.ts +8 -0
- package/src/plugins/chat-dev-server.ts +32 -0
- package/src/plugins/docs-assets.test.ts +25 -0
- package/src/plugins/docs-assets.ts +6 -2
- package/src/plugins/frontmatter-dev-server.ts +2 -2
- package/src/plugins/frontmatter-generator.ts +1 -1
- package/src/plugins/html-files-middleware.test.ts +21 -1
- package/src/plugins/html-files-middleware.ts +26 -6
- package/src/plugins/html-metadata-injector.ts +24 -4
- package/src/plugins/llm-txt-dev-server.ts +2 -2
- package/src/plugins/llm-txt-generator.ts +21 -4
- package/src/plugins/prerender.ts +47 -20
- package/src/plugins/robots-generator.ts +1 -1
- package/src/plugins/sanitize.ts +6 -5
- package/src/plugins/shared.ts +6 -18
- package/src/plugins/sitemap-generator.ts +1 -1
- package/src/plugins/social-preview.test.ts +71 -0
- package/src/plugins/social-preview.ts +87 -0
- package/src/plugins/unified-mdx-plugin.ts +1 -1
- package/src/theme.scss +630 -207
- package/src/types/config.ts +53 -1
- package/src/ui/breadcrumb.tsx +17 -0
- package/src/ui/docs-header-view.tsx +32 -0
- package/src/ui/docs-navigation-view.tsx +69 -0
- package/src/ui/docs-page-frame.tsx +50 -0
- package/src/ui/dory-mark.tsx +9 -0
- package/src/ui/header.tsx +33 -90
- package/src/ui/layout.tsx +99 -153
- package/src/ui/mobile-navigation.tsx +10 -8
- package/src/ui/page-actions.tsx +67 -0
- package/src/ui/page-feedback.tsx +50 -0
- package/src/ui/page-footer.tsx +23 -0
- package/src/ui/prev-next-link.tsx +15 -69
- package/src/ui/prev-next-view.tsx +27 -0
- package/src/ui/table-of-content-view.tsx +167 -0
- package/src/ui/table-of-content.tsx +25 -183
- package/src/utils/analytics.test.ts +36 -0
- package/src/utils/analytics.ts +35 -0
- package/src/utils/colors.ts +7 -0
- package/src/utils/content-scope.test.ts +26 -0
- package/src/utils/content-scope.ts +39 -0
- package/src/utils/search-content.ts +12 -0
- package/src/utils/theme.ts +1 -7
- package/vite.config.ssr.ts +4 -4
- package/vite.config.ts +22 -16
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { iconSourceVariants } from './icon-source-variants'
|
|
2
|
+
|
|
3
|
+
// Keep common names used by existing Dory documents working after the icon migration.
|
|
4
|
+
const aliases: Record<string, string> = {
|
|
5
|
+
activity: 'progress',
|
|
6
|
+
'alert-triangle': 'warning',
|
|
7
|
+
'bar-chart': 'chart',
|
|
8
|
+
box: 'packages',
|
|
9
|
+
'chart-column': 'chart',
|
|
10
|
+
'chevrons-down-up': 'up-down',
|
|
11
|
+
'circle-alert': 'alert-circle',
|
|
12
|
+
'circle-check': 'check-circle',
|
|
13
|
+
'code-2': 'code',
|
|
14
|
+
'columns-2': 'columns',
|
|
15
|
+
'columns-3': 'columns',
|
|
16
|
+
'file-json': 'file-code',
|
|
17
|
+
'external-link': 'external',
|
|
18
|
+
github: 'code',
|
|
19
|
+
'git-branch': 'branch',
|
|
20
|
+
'git-fork': 'branch',
|
|
21
|
+
'git-pull-request': 'branch',
|
|
22
|
+
hammer: 'settings',
|
|
23
|
+
image: 'file-image',
|
|
24
|
+
'layout-dashboard': 'dashboard',
|
|
25
|
+
layers: 'layout',
|
|
26
|
+
'list-ordered': 'list',
|
|
27
|
+
'message-square': 'chat',
|
|
28
|
+
'maximize-2': 'fullscreen',
|
|
29
|
+
map: 'globe',
|
|
30
|
+
package: 'packages',
|
|
31
|
+
palette: 'layout',
|
|
32
|
+
'play-circle': 'play',
|
|
33
|
+
'share-2': 'share',
|
|
34
|
+
'settings-2': 'settings',
|
|
35
|
+
'shield-check': 'shield',
|
|
36
|
+
'sliders-horizontal': 'sliders',
|
|
37
|
+
sparkles: 'ai',
|
|
38
|
+
'square-terminal': 'terminal',
|
|
39
|
+
plug: 'integration',
|
|
40
|
+
tag: 'price-tag',
|
|
41
|
+
heart: 'star',
|
|
42
|
+
rocket: 'promote',
|
|
43
|
+
zap: 'bolt',
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function normalize(name: string) {
|
|
47
|
+
const kebab = name.replace(/([a-z0-9])([A-Z])/g, '$1-$2').replace(/_/g, '-').toLowerCase()
|
|
48
|
+
return aliases[kebab] ?? kebab
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Compatibility names accepted by Dory's MDX Icon component. */
|
|
52
|
+
export function resolveIconName(name: string): string {
|
|
53
|
+
const [source, sourceName] = name.split('/', 2)
|
|
54
|
+
if (sourceName) {
|
|
55
|
+
if (source !== 'community' && source !== 'enterprise-core' && source !== 'enterprise') return name
|
|
56
|
+
const normalized = normalize(sourceName)
|
|
57
|
+
return iconSourceVariants[`${source}/${normalized}`] ?? normalized
|
|
58
|
+
}
|
|
59
|
+
return normalize(name)
|
|
60
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/** Published UX names for source-specific variants in the older Dory icon API. */
|
|
2
|
+
export const iconSourceVariants: Record<string, string> = {
|
|
3
|
+
'enterprise-core/banknotes': 'banknotes-alt',
|
|
4
|
+
'enterprise-core/calendar': 'calendar-alt',
|
|
5
|
+
'enterprise-core/check-circle': 'check-circle-alt',
|
|
6
|
+
'enterprise-core/chevron-down': 'chevron-down-alt',
|
|
7
|
+
'enterprise-core/chevron-left': 'chevron-left-alt',
|
|
8
|
+
'enterprise-core/chevron-right': 'chevron-right-alt',
|
|
9
|
+
'enterprise-core/chevron-up': 'chevron-up-alt',
|
|
10
|
+
'enterprise-core/clock': 'clock-alt',
|
|
11
|
+
'enterprise-core/code': 'code-alt',
|
|
12
|
+
'enterprise-core/copy': 'copy-alt',
|
|
13
|
+
'enterprise-core/download': 'download-alt',
|
|
14
|
+
'enterprise-core/file-text': 'file-text-alt',
|
|
15
|
+
'enterprise-core/globe': 'globe-alt',
|
|
16
|
+
'enterprise-core/grid': 'grid-alt',
|
|
17
|
+
'enterprise-core/hash': 'hash-alt',
|
|
18
|
+
'enterprise-core/help': 'help-alt',
|
|
19
|
+
'enterprise-core/home': 'home-alt',
|
|
20
|
+
'enterprise-core/list': 'list-alt',
|
|
21
|
+
'enterprise-core/mail': 'mail-alt',
|
|
22
|
+
'enterprise-core/moon': 'moon-alt',
|
|
23
|
+
'enterprise-core/play': 'play-alt',
|
|
24
|
+
'enterprise-core/plus': 'plus-alt',
|
|
25
|
+
'enterprise-core/plus-circle': 'plus-circle-alt',
|
|
26
|
+
'enterprise-core/search': 'search-alt',
|
|
27
|
+
'enterprise-core/secret': 'secret-alt',
|
|
28
|
+
'enterprise-core/settings': 'settings-alt',
|
|
29
|
+
'enterprise-core/sidebar': 'sidebar-alt',
|
|
30
|
+
'enterprise-core/signout': 'signout-alt',
|
|
31
|
+
'enterprise-core/source': 'source-alt',
|
|
32
|
+
'enterprise-core/sun': 'sun-alt',
|
|
33
|
+
'enterprise-core/trash': 'trash-alt',
|
|
34
|
+
'enterprise-core/upload': 'upload-alt',
|
|
35
|
+
'enterprise/arrow-right': 'arrow-right-alt',
|
|
36
|
+
'enterprise/bag': 'bag-alt',
|
|
37
|
+
'enterprise/bell': 'bell-alt',
|
|
38
|
+
'enterprise/calculator': 'calculator-alt',
|
|
39
|
+
'enterprise/calendar': 'calendar-alt-2',
|
|
40
|
+
'enterprise/card': 'card-alt',
|
|
41
|
+
'enterprise/chat': 'chat-alt',
|
|
42
|
+
'enterprise/check': 'check-alt',
|
|
43
|
+
'enterprise/checkbox': 'select-all',
|
|
44
|
+
'enterprise/chevron-down': 'chevron-down-alt-2',
|
|
45
|
+
'enterprise/chevron-left': 'chevron-left-alt-2',
|
|
46
|
+
'enterprise/chevron-right': 'chevron-right-alt-2',
|
|
47
|
+
'enterprise/chevron-up': 'chevron-up-alt-2',
|
|
48
|
+
'enterprise/clipboard': 'clipboard-alt',
|
|
49
|
+
'enterprise/clock': 'clock-alt-2',
|
|
50
|
+
'enterprise/close': 'close-alt',
|
|
51
|
+
'enterprise/cloud': 'cloud-alt',
|
|
52
|
+
'enterprise/columns': 'columns-alt',
|
|
53
|
+
'enterprise/copy': 'copy-alt-2',
|
|
54
|
+
'enterprise/customer': 'user',
|
|
55
|
+
'enterprise/dashboard': 'dashboard-alt',
|
|
56
|
+
'enterprise/download': 'download-alt-2',
|
|
57
|
+
'enterprise/edit': 'edit-alt',
|
|
58
|
+
'enterprise/external': 'external-alt',
|
|
59
|
+
'enterprise/eye': 'eye-alt',
|
|
60
|
+
'enterprise/eye-off': 'eye-off-alt',
|
|
61
|
+
'enterprise/file': 'file-alt',
|
|
62
|
+
'enterprise/file-text': 'file-text-alt-2',
|
|
63
|
+
'enterprise/filter': 'filter-alt',
|
|
64
|
+
'enterprise/folder': 'folder-alt',
|
|
65
|
+
'enterprise/formula': 'formula-alt',
|
|
66
|
+
'enterprise/globe': 'globe-alt-2',
|
|
67
|
+
'enterprise/group': 'folder-alt',
|
|
68
|
+
'enterprise/hash': 'hash-alt-2',
|
|
69
|
+
'enterprise/help': 'help-alt-2',
|
|
70
|
+
'enterprise/home': 'home-alt-2',
|
|
71
|
+
'enterprise/info': 'info-alt',
|
|
72
|
+
'enterprise/link': 'link-alt',
|
|
73
|
+
'enterprise/lock': 'lock-alt',
|
|
74
|
+
'enterprise/mail': 'mail-alt-2',
|
|
75
|
+
'enterprise/menu': 'menu-alt',
|
|
76
|
+
'enterprise/minus': 'minus-alt',
|
|
77
|
+
'enterprise/monitor': 'monitor-alt',
|
|
78
|
+
'enterprise/moon': 'moon-alt-2',
|
|
79
|
+
'enterprise/more-v': 'more-v-alt',
|
|
80
|
+
'enterprise/order': 'order-alt',
|
|
81
|
+
'enterprise/plus': 'plus-alt-2',
|
|
82
|
+
'enterprise/redo': 'redo-alt',
|
|
83
|
+
'enterprise/refresh': 'refresh-alt',
|
|
84
|
+
'enterprise/save': 'save-alt',
|
|
85
|
+
'enterprise/scan': 'scan-alt',
|
|
86
|
+
'enterprise/search': 'search-alt-2',
|
|
87
|
+
'enterprise/send': 'send-alt',
|
|
88
|
+
'enterprise/settings': 'settings-alt-2',
|
|
89
|
+
'enterprise/share': 'share-alt',
|
|
90
|
+
'enterprise/sidebar': 'sidebar-alt-2',
|
|
91
|
+
'enterprise/sliders': 'sliders-alt',
|
|
92
|
+
'enterprise/star': 'star-alt',
|
|
93
|
+
'enterprise/store': 'store-alt',
|
|
94
|
+
'enterprise/sun': 'sun-alt-2',
|
|
95
|
+
'enterprise/table': 'table-alt',
|
|
96
|
+
'enterprise/toggle': 'toggle-alt',
|
|
97
|
+
'enterprise/trash': 'trash-alt-2',
|
|
98
|
+
'enterprise/undo': 'undo-alt',
|
|
99
|
+
'enterprise/upload': 'upload-alt-2',
|
|
100
|
+
'enterprise/users': 'users-alt',
|
|
101
|
+
'enterprise/warning': 'warning-alt',
|
|
102
|
+
}
|
package/src/mdx/icon.tsx
CHANGED
|
@@ -1,80 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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'
|
|
1
|
+
import { CatalogIcon } from '@clidey/ux'
|
|
2
|
+
import { resolveIconName } from './icon-names'
|
|
6
3
|
import classNames from 'classnames'
|
|
7
|
-
import * as LucideIcons from 'lucide-react'
|
|
8
4
|
|
|
9
|
-
|
|
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
|
-
}: {
|
|
5
|
+
export function Icon({ icon, color, size = 24, className, style, ...rest }: {
|
|
30
6
|
icon: string
|
|
31
|
-
iconType?: keyof typeof iconSets
|
|
32
7
|
color?: string
|
|
33
8
|
size?: number
|
|
34
9
|
} & Omit<React.ComponentPropsWithoutRef<'svg'>, 'color'>) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
<LucideComponent
|
|
45
|
-
className={classNames("inline-block align-[-0.2em] stroke-black dark:stroke-white", className)}
|
|
46
|
-
color={color}
|
|
47
|
-
size={size}
|
|
48
|
-
{...rest}
|
|
49
|
-
/>
|
|
50
|
-
)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// --- Try Font Awesome ---
|
|
54
|
-
// Fall back to the brands set: Lucide 1.x removed all brand icons
|
|
55
|
-
// (github, twitter, npm, ...), which live only in FA brands.
|
|
56
|
-
const iconKey = `fa${pascalCase}`
|
|
57
|
-
const setsToTry: Array<keyof typeof iconSets> =
|
|
58
|
-
iconType === 'brands' ? ['brands'] : [iconType, 'brands']
|
|
59
|
-
|
|
60
|
-
for (const setName of setsToTry) {
|
|
61
|
-
const faSet = iconSets[setName] ?? solidIcons
|
|
62
|
-
const faIcon = faSet[iconKey as keyof typeof faSet]
|
|
63
|
-
|
|
64
|
-
if (faIcon && typeof faIcon === 'object' && 'iconName' in faIcon) {
|
|
65
|
-
library.add(faIcon)
|
|
66
|
-
return (
|
|
67
|
-
<FontAwesomeIcon
|
|
68
|
-
icon={[prefixes[setName] || 'fas', faIcon.iconName] as any}
|
|
69
|
-
className={classNames("inline-block align-[-0.2em] stroke-black dark:stroke-white", className)}
|
|
70
|
-
color={color as any}
|
|
71
|
-
size={size as any}
|
|
72
|
-
{...rest as any}
|
|
73
|
-
/>
|
|
74
|
-
)
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
console.warn(`⚠️ Icon not found: "${icon}" (converted to ${iconKey}) in ${iconType}`)
|
|
79
|
-
return null // or return a default/fallback icon if preferred
|
|
10
|
+
return (
|
|
11
|
+
<CatalogIcon
|
|
12
|
+
name={resolveIconName(icon)}
|
|
13
|
+
style={{ color, ...(typeof style === 'object' && style !== null ? style : {}) }}
|
|
14
|
+
size={size}
|
|
15
|
+
className={classNames('inline-block align-[-0.2em]', className)}
|
|
16
|
+
{...rest}
|
|
17
|
+
/>
|
|
18
|
+
)
|
|
80
19
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { createElement } from 'preact';
|
|
3
|
+
import { renderToString } from 'preact-render-to-string';
|
|
4
|
+
import katex from 'katex';
|
|
5
|
+
import { Latex, renderLatexExpression } from './latex';
|
|
6
|
+
|
|
7
|
+
describe('LaTeX rendering', () => {
|
|
8
|
+
it('server-renders a readable display fallback with the same client shell', () => {
|
|
9
|
+
const html = renderToString(createElement(Latex, { children: 'E = mc^2' }));
|
|
10
|
+
expect(html).toContain('dory-latex not-prose');
|
|
11
|
+
expect(html).toContain('aria-busy="true"');
|
|
12
|
+
expect(html).toContain('data-latex-fallback');
|
|
13
|
+
expect(html).toContain('E = mc^2');
|
|
14
|
+
expect(html).not.toContain('animate-spin');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('keeps the inline fallback inline and escapes source text', () => {
|
|
18
|
+
const html = renderToString(createElement(Latex, { inline: true, children: '<img src=x onerror=alert(1)>' }));
|
|
19
|
+
expect(html).toMatch(/^<span /);
|
|
20
|
+
expect(html).not.toContain('<div');
|
|
21
|
+
expect(html).not.toContain('<img');
|
|
22
|
+
expect(html).toContain('<img');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('renders complex math with KaTeX and the requested mode', () => {
|
|
26
|
+
const expression = String.raw`\frac{a}{b}`;
|
|
27
|
+
expect(renderLatexExpression(katex, expression, false)).toContain('katex-display');
|
|
28
|
+
const inline = renderLatexExpression(katex, expression, true);
|
|
29
|
+
expect(inline).toContain('mfrac');
|
|
30
|
+
expect(inline).not.toContain('katex-display');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('does not allow formulas to create trusted links, HTML classes, or image requests', () => {
|
|
34
|
+
expect(renderLatexExpression(katex, String.raw`\href{javascript:alert(1)}{click}`, true)).not.toMatch(/href=["']javascript:/);
|
|
35
|
+
expect(renderLatexExpression(katex, String.raw`\htmlClass{injected}{x}`, false)).not.toMatch(/class=["']injected/);
|
|
36
|
+
expect(renderLatexExpression(katex, String.raw`\includegraphics{https://example.test/tracker.png}`, false)).not.toContain('<img');
|
|
37
|
+
});
|
|
38
|
+
});
|
package/src/mdx/latex.tsx
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useEffect, useRef, useState } from 'react'
|
|
1
|
+
import { useEffect, useState } from 'preact/hooks'
|
|
3
2
|
|
|
4
3
|
function extractText(node: React.ReactNode): string {
|
|
5
4
|
if (typeof node === 'string') return node;
|
|
6
5
|
if (typeof node === 'number') return String(node);
|
|
7
6
|
if (!node) return '';
|
|
8
7
|
if (Array.isArray(node)) return node.map(extractText).join('');
|
|
9
|
-
if (typeof node === 'object' && 'props' in node)
|
|
8
|
+
if (typeof node === 'object' && 'props' in node) {
|
|
9
|
+
return extractText((node as { props: { children?: React.ReactNode } }).props.children);
|
|
10
|
+
}
|
|
10
11
|
return '';
|
|
11
12
|
}
|
|
12
13
|
|
|
@@ -16,90 +17,67 @@ interface LatexProps {
|
|
|
16
17
|
inline?: boolean
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
type RenderResult = {
|
|
21
|
+
expression: string
|
|
22
|
+
inline: boolean
|
|
23
|
+
html?: string
|
|
24
|
+
error?: string
|
|
25
|
+
}
|
|
21
26
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
/** Only KaTeX produces markup; TeX commands cannot opt into trusted HTML or URLs. */
|
|
28
|
+
export function renderLatexExpression(katex: Pick<typeof import('katex').default, 'renderToString'>, expression: string, inline: boolean): string {
|
|
29
|
+
return katex.renderToString(expression, {
|
|
30
|
+
displayMode: !inline,
|
|
31
|
+
throwOnError: false,
|
|
32
|
+
trust: false,
|
|
33
|
+
maxExpand: 1000,
|
|
34
|
+
})
|
|
35
|
+
}
|
|
25
36
|
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
const [
|
|
37
|
+
export function Latex({ children, inline = false }: LatexProps) {
|
|
38
|
+
const expression = extractText(children).trim()
|
|
39
|
+
const [result, setResult] = useState<RenderResult | null>(null)
|
|
40
|
+
// A changed expression immediately shows its own fallback while it loads.
|
|
41
|
+
const current = result?.expression === expression && result.inline === inline ? result : null
|
|
42
|
+
const isLoading = current === null
|
|
29
43
|
|
|
30
44
|
useEffect(() => {
|
|
31
45
|
let mounted = true
|
|
32
|
-
|
|
33
46
|
const renderLatex = async () => {
|
|
34
47
|
try {
|
|
35
|
-
setError(null)
|
|
36
|
-
setIsLoading(true)
|
|
37
|
-
|
|
38
48
|
const [katexModule] = await Promise.all([
|
|
39
49
|
import('katex'),
|
|
40
50
|
import('katex/dist/katex.min.css'),
|
|
41
51
|
])
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
katex.render(expression, containerRef.current, {
|
|
47
|
-
displayMode: !inline,
|
|
48
|
-
throwOnError: false,
|
|
49
|
-
})
|
|
50
|
-
setIsLoading(false)
|
|
51
|
-
} catch (error) {
|
|
52
|
-
if (mounted) {
|
|
53
|
-
console.error('Error rendering LaTeX:', error)
|
|
54
|
-
setError(error instanceof Error ? error.message : 'Failed to render LaTeX')
|
|
55
|
-
setIsLoading(false)
|
|
56
|
-
}
|
|
52
|
+
const html = renderLatexExpression(katexModule.default, expression, inline)
|
|
53
|
+
if (mounted) setResult({ expression, inline, html })
|
|
54
|
+
} catch {
|
|
55
|
+
if (mounted) setResult({ expression, inline, error: 'The equation could not be rendered.' })
|
|
57
56
|
}
|
|
58
57
|
}
|
|
59
|
-
|
|
60
58
|
renderLatex()
|
|
61
|
-
|
|
62
|
-
return () => {
|
|
63
|
-
mounted = false
|
|
64
|
-
}
|
|
59
|
+
return () => { mounted = false }
|
|
65
60
|
}, [expression, inline])
|
|
66
61
|
|
|
62
|
+
// Server rendering and the first browser render use the same readable fallback.
|
|
63
|
+
// Keep KaTeX lazy, and let Preact own the markup instead of mutating a ref's DOM.
|
|
67
64
|
if (inline) {
|
|
68
|
-
return <span ref={containerRef as unknown as preact.RefObject<HTMLSpanElement>} className="dory-latex-inline">{isLoading && expression}</span>
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (error) {
|
|
72
65
|
return (
|
|
73
|
-
<
|
|
74
|
-
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
<h3 className="text-lg font-semibold text-red-700 dark:text-red-300 mb-2">
|
|
79
|
-
LaTeX Rendering Error
|
|
80
|
-
</h3>
|
|
81
|
-
<p className="text-red-600 dark:text-red-400 mb-4 max-w-md">
|
|
82
|
-
The LaTeX expression could not be rendered.
|
|
83
|
-
</p>
|
|
84
|
-
<div className="bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-800 rounded-md p-3 max-w-md">
|
|
85
|
-
<p className="text-sm text-red-700 dark:text-red-300 font-mono break-all">
|
|
86
|
-
{error}
|
|
87
|
-
</p>
|
|
88
|
-
</div>
|
|
89
|
-
</div>
|
|
90
|
-
</div>
|
|
66
|
+
<span className="dory-latex-inline" aria-busy={isLoading} title={current?.error}>
|
|
67
|
+
{current?.html !== undefined
|
|
68
|
+
? <span dangerouslySetInnerHTML={{ __html: current.html }} />
|
|
69
|
+
: <span data-latex-fallback>{expression}</span>}
|
|
70
|
+
</span>
|
|
91
71
|
)
|
|
92
72
|
}
|
|
93
73
|
|
|
94
74
|
return (
|
|
95
|
-
<div className="dory-latex not-prose my-5 rounded-lg border border-[var(--border)] bg-muted/50 px-5 pb-5 pt-3">
|
|
75
|
+
<div className="dory-latex not-prose my-5 rounded-lg border border-[var(--border)] bg-muted/50 px-5 pb-5 pt-3" aria-busy={isLoading}>
|
|
96
76
|
<span className="block font-mono text-[11px] uppercase tracking-[0.16em] text-muted-foreground">Display</span>
|
|
97
|
-
{
|
|
98
|
-
<div className="
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
)}
|
|
102
|
-
<div ref={containerRef} className={isLoading ? 'hidden' : 'mt-3 text-center'} />
|
|
77
|
+
{current?.html !== undefined
|
|
78
|
+
? <div className="mt-3 overflow-x-auto text-center" dangerouslySetInnerHTML={{ __html: current.html }} />
|
|
79
|
+
: <span data-latex-fallback className="block overflow-x-auto px-2 py-3 text-center font-mono text-sm text-muted-foreground">{expression}</span>}
|
|
80
|
+
{current?.error && <p role="alert" className="mt-2 text-center text-sm text-destructive">{current.error}</p>}
|
|
103
81
|
</div>
|
|
104
82
|
)
|
|
105
83
|
}
|
package/src/mdx/mdx.tsx
CHANGED
|
@@ -22,7 +22,7 @@ export { UnorderedList as ul, OrderedList as ol, ListItem as li } from './list';
|
|
|
22
22
|
export { UnorderedList, OrderedList, ListItem } from './list';
|
|
23
23
|
export { AsyncAPI } from './async-api';
|
|
24
24
|
export { Source } from './source';
|
|
25
|
-
export { Specimen, SpecimenProps } from './specimen';
|
|
25
|
+
export { Specimen, SpecimenProps, Prop } from './specimen';
|
|
26
26
|
export { Chip, ChipGroup } from './chip';
|
|
27
27
|
export { Tabs, Tab } from './tabs';
|
|
28
28
|
export { IconGrid } from './icon-grid';
|
package/src/mdx/mermaid.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CatalogIcon } from '@clidey/ux'
|
|
2
|
+
|
|
2
3
|
import classNames from 'classnames'
|
|
3
4
|
import { useEffect, useMemo, useRef, useState } from 'preact/compat'
|
|
4
5
|
|
|
@@ -31,7 +32,7 @@ function paletteThemeVariables() {
|
|
|
31
32
|
const v = (name: string) => css.getPropertyValue(name).trim()
|
|
32
33
|
return {
|
|
33
34
|
fontFamily: v('--font-body') || 'inherit',
|
|
34
|
-
fontSize: '
|
|
35
|
+
fontSize: '14px',
|
|
35
36
|
background: v('--card'),
|
|
36
37
|
mainBkg: v('--accent'),
|
|
37
38
|
primaryColor: v('--accent'),
|
|
@@ -114,6 +115,9 @@ export function MermaidRenderer({ content }: { content: string }) {
|
|
|
114
115
|
let mounted = true
|
|
115
116
|
|
|
116
117
|
const renderMermaid = async () => {
|
|
118
|
+
setError(null)
|
|
119
|
+
setIsValid(null)
|
|
120
|
+
setIsLoaded(false)
|
|
117
121
|
try {
|
|
118
122
|
const mermaid = (await import('mermaid')).default
|
|
119
123
|
|
|
@@ -122,6 +126,7 @@ export function MermaidRenderer({ content }: { content: string }) {
|
|
|
122
126
|
mermaid.initialize({
|
|
123
127
|
startOnLoad: false,
|
|
124
128
|
theme: 'base',
|
|
129
|
+
look: 'classic',
|
|
125
130
|
themeVariables: paletteThemeVariables(),
|
|
126
131
|
flowchart: {
|
|
127
132
|
nodeSpacing: 50,
|
|
@@ -164,7 +169,6 @@ export function MermaidRenderer({ content }: { content: string }) {
|
|
|
164
169
|
} catch (err) {
|
|
165
170
|
console.error('Mermaid rendering error:', err)
|
|
166
171
|
if (mounted) {
|
|
167
|
-
setIsValid(false)
|
|
168
172
|
setError(err instanceof Error ? err.message : 'Failed to render diagram')
|
|
169
173
|
}
|
|
170
174
|
}
|
|
@@ -186,13 +190,15 @@ export function MermaidRenderer({ content }: { content: string }) {
|
|
|
186
190
|
<div className="mermaid-wrapper my-8">
|
|
187
191
|
<div className="flex flex-col items-center justify-center py-12 px-6 text-center bg-gradient-to-br from-red-50 to-red-100 dark:from-red-900/20 dark:to-red-800/20 border-2 border-dashed border-red-300 dark:border-red-700 rounded-lg">
|
|
188
192
|
<div className="w-16 h-16 mb-4 flex items-center justify-center bg-red-100 dark:bg-red-900/30 rounded-full">
|
|
189
|
-
<
|
|
193
|
+
<CatalogIcon name="warning" className="w-8 h-8 text-red-500" />
|
|
190
194
|
</div>
|
|
191
195
|
<h3 className="text-lg font-semibold text-red-700 dark:text-red-300 mb-2">
|
|
192
|
-
Diagram Syntax Error
|
|
196
|
+
{isValid === false ? 'Diagram Syntax Error' : 'Diagram Load Error'}
|
|
193
197
|
</h3>
|
|
194
198
|
<p className="text-red-600 dark:text-red-400 mb-4 max-w-md">
|
|
195
|
-
|
|
199
|
+
{isValid === false
|
|
200
|
+
? 'The Mermaid diagram contains invalid syntax and cannot be rendered.'
|
|
201
|
+
: 'The diagram could not be loaded. Please reload the page.'}
|
|
196
202
|
</p>
|
|
197
203
|
<div className="bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-800 rounded-md p-3 max-w-md">
|
|
198
204
|
<p className="text-sm text-red-700 dark:text-red-300 font-mono break-all">
|