@clidey/dory 0.30.0 → 0.33.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 (70) hide show
  1. package/bin/dist/dory.js +44 -20
  2. package/bin/dist/dory.js.map +1 -1
  3. package/index.html +54 -0
  4. package/package.json +9 -4
  5. package/src/app.css +5 -0
  6. package/src/app.tsx +13 -0
  7. package/src/components/dropdown.tsx +53 -0
  8. package/src/components/hooks.tsx +36 -0
  9. package/src/components/loading.tsx +21 -0
  10. package/src/components/mdx-provider.tsx +13 -0
  11. package/src/components/navigation.tsx +58 -0
  12. package/src/components/notification.tsx +13 -0
  13. package/src/components/routes.tsx +92 -0
  14. package/src/components/search-index.ts +43 -0
  15. package/src/components/search.tsx +448 -0
  16. package/src/components/store.ts +216 -0
  17. package/src/config/mdx.js +12 -0
  18. package/src/config/mdx.ts +12 -0
  19. package/src/embed/loader.ts +200 -0
  20. package/src/embed/types.ts +54 -0
  21. package/src/embed/widget-iframe.ts +302 -0
  22. package/src/main.tsx +22 -0
  23. package/src/mdx/accordion.tsx +41 -0
  24. package/src/mdx/api-playground.tsx +659 -0
  25. package/src/mdx/api.tsx +354 -0
  26. package/src/mdx/async-api.tsx +353 -0
  27. package/src/mdx/callout.tsx +64 -0
  28. package/src/mdx/card.tsx +54 -0
  29. package/src/mdx/code.tsx +289 -0
  30. package/src/mdx/expandable.tsx +36 -0
  31. package/src/mdx/fence.tsx +74 -0
  32. package/src/mdx/icon.tsx +73 -0
  33. package/src/mdx/latex.tsx +26 -0
  34. package/src/mdx/list.tsx +7 -0
  35. package/src/mdx/mdx.tsx +47 -0
  36. package/src/mdx/mermaid.tsx +147 -0
  37. package/src/mdx/open-api.tsx +311 -0
  38. package/src/mdx/processor.ts +174 -0
  39. package/src/mdx/source.tsx +58 -0
  40. package/src/mdx/step.tsx +51 -0
  41. package/src/mdx/table.tsx +20 -0
  42. package/src/mdx/tag.tsx +53 -0
  43. package/src/mdx/text.tsx +99 -0
  44. package/src/mdx/websocket-playground.tsx +824 -0
  45. package/src/plugins/docs-assets.ts +79 -0
  46. package/src/plugins/frontmatter-dev-server.ts +158 -0
  47. package/src/plugins/frontmatter-generator.ts +157 -0
  48. package/src/plugins/html-files-middleware.ts +108 -0
  49. package/src/plugins/html-metadata-injector.ts +76 -0
  50. package/src/plugins/llm-txt-dev-server.ts +183 -0
  51. package/src/plugins/llm-txt-generator.ts +186 -0
  52. package/src/plugins/sanitize.ts +163 -0
  53. package/src/plugins/unified-mdx-plugin.ts +35 -0
  54. package/src/sdk/index.ts +89 -0
  55. package/src/sdk/react.tsx +139 -0
  56. package/src/sdk/vue.ts +107 -0
  57. package/src/theme.scss +72 -0
  58. package/src/types/config.ts +89 -0
  59. package/src/ui/header.tsx +96 -0
  60. package/src/ui/layout.tsx +237 -0
  61. package/src/ui/mobile-navigation.tsx +72 -0
  62. package/src/ui/prev-next-link.tsx +74 -0
  63. package/src/ui/table-of-content.tsx +167 -0
  64. package/src/utils/colors.ts +54 -0
  65. package/src/utils/fonts.ts +23 -0
  66. package/src/utils/functions.ts +12 -0
  67. package/src/vite-env.d.ts +1 -0
  68. package/vite.config.embed-loader.ts +28 -0
  69. package/vite.config.embed-widget.ts +31 -0
  70. package/vite.config.ts +45 -0
@@ -0,0 +1,139 @@
1
+ /**
2
+ * React SDK for Dory Embedded Documentation
3
+ * Usage: npm install @clidey/dory-embedded
4
+ */
5
+
6
+ import { useEffect, useRef, useCallback } from 'react';
7
+ import type { DoryDocsAPI } from '../embed/types';
8
+
9
+ interface DoryDocsConfig {
10
+ docsUrl: string;
11
+ position?: 'left' | 'right' | 'bottom';
12
+ width?: number | string;
13
+ height?: string;
14
+ theme?: 'light' | 'dark' | 'inherit';
15
+ initialPath?: string;
16
+ showOverlay?: boolean;
17
+ closeOnEscape?: boolean;
18
+ closeOnOverlayClick?: boolean;
19
+ onOpen?: () => void;
20
+ onClose?: () => void;
21
+ onNavigate?: (path: string) => void;
22
+ onReady?: () => void;
23
+ }
24
+
25
+ /**
26
+ * Hook to use Dory Documentation in React components
27
+ */
28
+ export function useDoryDocs(config: DoryDocsConfig) {
29
+ const docsRef = useRef<any>(null);
30
+ const configRef = useRef(config);
31
+
32
+ useEffect(() => {
33
+ configRef.current = config;
34
+ }, [config]);
35
+
36
+ useEffect(() => {
37
+ // Wait for DoryDocs to be available
38
+ const checkDoryDocs = () => {
39
+ if (window.DoryDocs) {
40
+ docsRef.current = window.DoryDocs.init(configRef.current);
41
+ } else {
42
+ console.warn('[useDoryDocs] DoryDocs not loaded. Make sure to include the embed script.');
43
+ }
44
+ };
45
+
46
+ if (document.readyState === 'loading') {
47
+ window.addEventListener('dory:ready', checkDoryDocs);
48
+ } else {
49
+ checkDoryDocs();
50
+ }
51
+
52
+ return () => {
53
+ window.removeEventListener('dory:ready', checkDoryDocs);
54
+ docsRef.current?.destroy();
55
+ };
56
+ }, []);
57
+
58
+ const open = useCallback((path?: string) => {
59
+ return docsRef.current?.open(path);
60
+ }, []);
61
+
62
+ const close = useCallback(() => {
63
+ return docsRef.current?.close();
64
+ }, []);
65
+
66
+ const toggle = useCallback(() => {
67
+ return docsRef.current?.toggle();
68
+ }, []);
69
+
70
+ const navigate = useCallback((path: string) => {
71
+ return docsRef.current?.navigate(path);
72
+ }, []);
73
+
74
+ const setTheme = useCallback((theme: 'light' | 'dark') => {
75
+ return docsRef.current?.setTheme(theme);
76
+ }, []);
77
+
78
+ const isOpen = useCallback(() => {
79
+ return docsRef.current?.isOpen() || false;
80
+ }, []);
81
+
82
+ return {
83
+ open,
84
+ close,
85
+ toggle,
86
+ navigate,
87
+ setTheme,
88
+ isOpen,
89
+ };
90
+ }
91
+
92
+ /**
93
+ * Component that renders a trigger button for Dory Documentation
94
+ */
95
+ export function DoryDocsTrigger({
96
+ children,
97
+ config,
98
+ className,
99
+ path,
100
+ }: {
101
+ children: React.ReactNode;
102
+ config: DoryDocsConfig;
103
+ className?: string;
104
+ path?: string;
105
+ }) {
106
+ const docs = useDoryDocs(config);
107
+
108
+ const handleClick = () => {
109
+ docs.open(path);
110
+ };
111
+
112
+ return (
113
+ <button onClick={handleClick} className={className}>
114
+ {children}
115
+ </button>
116
+ );
117
+ }
118
+
119
+ /**
120
+ * Component that initializes Dory Documentation without rendering anything
121
+ */
122
+ export function DoryDocsProvider({
123
+ config,
124
+ children,
125
+ }: {
126
+ config: DoryDocsConfig;
127
+ children: React.ReactNode;
128
+ }) {
129
+ useDoryDocs(config);
130
+
131
+ return <>{children}</>;
132
+ }
133
+
134
+ // Type declarations
135
+ declare global {
136
+ interface Window {
137
+ DoryDocs: DoryDocsAPI;
138
+ }
139
+ }
package/src/sdk/vue.ts ADDED
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Vue SDK for Dory Embedded Documentation
3
+ * Usage: npm install @clidey/dory-embedded
4
+ */
5
+
6
+ // @ts-ignore - Vue is an optional peer dependency
7
+ import { ref, onMounted, onUnmounted, computed } from 'vue';
8
+ import type { DoryDocsAPI } from '../embed/types';
9
+
10
+ interface DoryDocsConfig {
11
+ docsUrl: string;
12
+ position?: 'left' | 'right' | 'bottom';
13
+ width?: number | string;
14
+ height?: string;
15
+ theme?: 'light' | 'dark' | 'inherit';
16
+ initialPath?: string;
17
+ showOverlay?: boolean;
18
+ closeOnEscape?: boolean;
19
+ closeOnOverlayClick?: boolean;
20
+ onOpen?: () => void;
21
+ onClose?: () => void;
22
+ onNavigate?: (path: string) => void;
23
+ onReady?: () => void;
24
+ }
25
+
26
+ /**
27
+ * Composable to use Dory Documentation in Vue components
28
+ */
29
+ export function useDoryDocs(config: DoryDocsConfig) {
30
+ const docsInstance = ref<any>(null);
31
+ const isReady = ref(false);
32
+
33
+ const initDocs = () => {
34
+ if (window.DoryDocs) {
35
+ docsInstance.value = window.DoryDocs.init(config);
36
+ isReady.value = true;
37
+ } else {
38
+ console.warn('[useDoryDocs] DoryDocs not loaded. Make sure to include the embed script.');
39
+ }
40
+ };
41
+
42
+ onMounted(() => {
43
+ if (document.readyState === 'loading') {
44
+ window.addEventListener('dory:ready', initDocs);
45
+ } else {
46
+ initDocs();
47
+ }
48
+ });
49
+
50
+ onUnmounted(() => {
51
+ window.removeEventListener('dory:ready', initDocs);
52
+ if (docsInstance.value) {
53
+ docsInstance.value.destroy();
54
+ }
55
+ });
56
+
57
+ const open = async (path?: string) => {
58
+ if (docsInstance.value) {
59
+ await docsInstance.value.open(path);
60
+ }
61
+ };
62
+
63
+ const close = async () => {
64
+ if (docsInstance.value) {
65
+ await docsInstance.value.close();
66
+ }
67
+ };
68
+
69
+ const toggle = async () => {
70
+ if (docsInstance.value) {
71
+ await docsInstance.value.toggle();
72
+ }
73
+ };
74
+
75
+ const navigate = async (path: string) => {
76
+ if (docsInstance.value) {
77
+ await docsInstance.value.navigate(path);
78
+ }
79
+ };
80
+
81
+ const setTheme = async (theme: 'light' | 'dark') => {
82
+ if (docsInstance.value) {
83
+ await docsInstance.value.setTheme(theme);
84
+ }
85
+ };
86
+
87
+ const isOpen = computed(() => {
88
+ return docsInstance.value?.isOpen() || false;
89
+ });
90
+
91
+ return {
92
+ open,
93
+ close,
94
+ toggle,
95
+ navigate,
96
+ setTheme,
97
+ isOpen: isOpen.value,
98
+ isReady,
99
+ };
100
+ }
101
+
102
+ // Type declarations
103
+ declare global {
104
+ interface Window {
105
+ DoryDocs: DoryDocsAPI;
106
+ }
107
+ }
package/src/theme.scss ADDED
@@ -0,0 +1,72 @@
1
+ :root {
2
+ font-synthesis: none;
3
+ text-rendering: optimizeLegibility;
4
+ -webkit-font-smoothing: antialiased;
5
+ -moz-osx-font-smoothing: grayscale;
6
+
7
+ // Brand colors (dynamically set from dory.json via loadColors())
8
+ // These variables are set at runtime:
9
+ // --brand-primary: primary color
10
+ // --brand-light: light mode accent
11
+ // --brand-dark: dark mode accent
12
+ // --brand-foreground: legacy/fallback (set to primary)
13
+
14
+ // Default fallback colors (will be overridden by loadColors())
15
+ --brand-foreground: #0ea5e9;
16
+ --brand-primary: #0ea5e9;
17
+ --brand-light: #38bdf8;
18
+ --brand-dark: #0284c7;
19
+
20
+ // ShadCN/clidey/ux compatible primary color
21
+ // Using light color for light mode
22
+ --primary: var(--brand-light);
23
+ --primary-rgb: var(--brand-light-rgb);
24
+
25
+ --ring: rgba(0, 0, 0, 0.1);
26
+ }
27
+
28
+ // Dark mode overrides
29
+ :root.dark {
30
+ // Use dark color for dark mode
31
+ --primary: var(--brand-dark);
32
+ --primary-rgb: var(--brand-dark-rgb);
33
+
34
+ --ring: rgba(255, 255, 255, 0.1);
35
+ }
36
+
37
+ article h2:first-of-type {
38
+ display: none;
39
+ }
40
+
41
+ article hr:first-of-type {
42
+ display: none;
43
+ }
44
+
45
+ article img {
46
+ margin-top: 1rem;
47
+ margin-bottom: 1rem;
48
+ }
49
+
50
+ [data-testid="header-actions"] button,
51
+ [data-slot="dropdown-menu-content"] {
52
+ border-color: var(--ring);
53
+ }
54
+
55
+ [data-slot="search-input-wrapper"] {
56
+ font-size: 12px;
57
+ outline: none;
58
+ }
59
+
60
+ [data-slot="search-input"] {
61
+ font-size: 12px;
62
+ }
63
+
64
+ [data-slot="search-input-wrapper"]:focus-within {
65
+ outline: none !important;
66
+ }
67
+
68
+ [role="dialog"] [data-slot="search-input-wrapper"]:focus-within {
69
+ outline: none !important;
70
+ border: 0px;
71
+ box-shadow: none !important;
72
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * TypeScript interfaces for dory.json configuration
3
+ */
4
+
5
+ export interface DoryColors {
6
+ primary: string;
7
+ light: string;
8
+ dark: string;
9
+ }
10
+
11
+ export interface DoryFonts {
12
+ family: string;
13
+ source: string;
14
+ format: string;
15
+ }
16
+
17
+ export interface DoryLogo {
18
+ light: string;
19
+ dark: string;
20
+ }
21
+
22
+ export interface DoryNavigationPage {
23
+ [key: string]: string;
24
+ }
25
+
26
+ export interface DoryNavigationGroup {
27
+ group: string;
28
+ pages: string[];
29
+ }
30
+
31
+ export interface DoryNavigationTab {
32
+ tab: string;
33
+ groups: DoryNavigationGroup[];
34
+ }
35
+
36
+ export interface DoryNavigation {
37
+ tabs: DoryNavigationTab[];
38
+ }
39
+
40
+ export interface DoryEmbedConfig {
41
+ enabled?: boolean;
42
+ version?: string;
43
+ defaults?: {
44
+ position?: 'left' | 'right' | 'bottom';
45
+ width?: number;
46
+ height?: string;
47
+ theme?: 'light' | 'dark' | 'inherit';
48
+ showOverlay?: boolean;
49
+ closeOnEscape?: boolean;
50
+ closeOnOverlayClick?: boolean;
51
+ };
52
+ branding?: {
53
+ showLogo?: boolean;
54
+ showName?: boolean;
55
+ customCSS?: string;
56
+ };
57
+ features?: {
58
+ search?: boolean;
59
+ toc?: boolean;
60
+ codeBlocks?: boolean;
61
+ mermaid?: boolean;
62
+ katex?: boolean;
63
+ apiPlayground?: boolean;
64
+ darkMode?: boolean;
65
+ };
66
+ analytics?: {
67
+ enabled?: boolean;
68
+ trackEvents?: string[];
69
+ };
70
+ security?: {
71
+ allowedOrigins?: string[];
72
+ corsEnabled?: boolean;
73
+ };
74
+ performance?: {
75
+ lazyLoadImages?: boolean;
76
+ prefetchPages?: boolean;
77
+ cacheStrategy?: 'cache-first' | 'network-first';
78
+ };
79
+ }
80
+
81
+ export interface DoryConfig {
82
+ name: string;
83
+ favicon: string;
84
+ colors?: DoryColors;
85
+ fonts?: DoryFonts;
86
+ navigation: DoryNavigation;
87
+ logo: DoryLogo;
88
+ embed?: DoryEmbedConfig;
89
+ }
@@ -0,0 +1,96 @@
1
+ import classNames from 'classnames';
2
+ import { useEffect, useMemo, useState } from 'preact/hooks';
3
+ import { Link } from 'wouter-preact';
4
+ import { usePathname } from 'wouter-preact/use-browser-location';
5
+ import docsConfig from '../../docs/dory.json' with { type: 'json' };
6
+ import type { DoryConfig } from '../types/config';
7
+ import { MobileSearch, Search } from '../components/search';
8
+ import { ALL_NAVIGATION } from '../components/store';
9
+ import { MobileNavigation } from './mobile-navigation';
10
+ import { ModeToggle } from '@clidey/ux';
11
+ import { useIsEmbedded } from '../components/hooks';
12
+
13
+ const config = docsConfig as DoryConfig;
14
+
15
+ function TopLevelNavItem({ href, children, isActive }: { href: string; children: string; isActive?: boolean }) {
16
+ return (
17
+ <li>
18
+ <Link
19
+ to={href}
20
+ className={classNames(
21
+ "text-xs transition hover:opacity-80",
22
+ {
23
+ "text-brand-foreground": isActive,
24
+ },
25
+ )}
26
+ >
27
+ {children}
28
+ </Link>
29
+ </li>
30
+ );
31
+ }
32
+
33
+ export function Header({ className }: { className?: string }) {
34
+ const [isScrolled, setIsScrolled] = useState(false);
35
+ const pathname = usePathname();
36
+ const isEmbedded = useIsEmbedded();
37
+
38
+ const { light, dark } = useMemo(() => {
39
+ return { light: config.logo.light, dark: config.logo.dark };
40
+ }, []);
41
+
42
+ useEffect(() => {
43
+ function onScroll() {
44
+ setIsScrolled(window.scrollY > 0);
45
+ }
46
+ onScroll();
47
+ window.addEventListener('scroll', onScroll, { passive: true });
48
+ return () => {
49
+ window.removeEventListener('scroll', onScroll);
50
+ }
51
+ }, []);
52
+
53
+ return (
54
+ <header
55
+ className={classNames(
56
+ 'sticky top-0 z-50 flex flex-none flex-wrap items-center justify-between bg-white px-4 py-5 shadow-md shadow-gray-900/5 sm:px-6 lg:px-8 dark:shadow-none',
57
+ className,
58
+ {
59
+ 'dark:bg-gray-900/95 dark:backdrop-blur-sm dark:[@supports(backdrop-filter:blur(0))]:bg-neutral-900/75': isScrolled,
60
+ 'dark:bg-transparent': !isScrolled,
61
+ },
62
+ )}
63
+ >
64
+ <div className="relative flex justify-between gap-6 sm:gap-8 md:grow items-center w-full px-4">
65
+ <div className="flex items-center grow gap-2">
66
+ <img src={`/${light}`} alt="logo" className="w-8 hidden dark:block" />
67
+ <img src={`/${dark}`} alt="logo" className="w-8 block dark:hidden" />
68
+ <h1 className="text-base lg:text-2xl font-bold">{config.name}</h1>
69
+ </div>
70
+ <nav className="hidden md:block">
71
+ <ul role="list" className="flex items-center gap-8">
72
+ {ALL_NAVIGATION.map((tab) => (
73
+ <TopLevelNavItem
74
+ key={tab.title}
75
+ href={tab.groups[0].pages[0].href}
76
+ isActive={pathname.startsWith(`/${tab.groups[0].pages[0].href.split('/')[1]}`)}
77
+ >
78
+ {tab.title}
79
+ </TopLevelNavItem>
80
+ ))}
81
+ </ul>
82
+ </nav>
83
+ <div className="hidden md:block md:h-5 md:w-px md:bg-zinc-900/10 md:dark:bg-white/15" />
84
+ <div className="flex items-center gap-3" data-testid="header-actions">
85
+ <MobileSearch />
86
+ <Search />
87
+ {
88
+ !isEmbedded &&
89
+ <ModeToggle data-testid="mode-toggle" />
90
+ }
91
+ <MobileNavigation />
92
+ </div>
93
+ </div>
94
+ </header>
95
+ );
96
+ }