@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
package/index.html ADDED
@@ -0,0 +1,54 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <link rel="icon" href="./docs/favicon.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ <meta name="theme-color" content="#000000" />
8
+
9
+ <!-- Primary Meta Tags -->
10
+ <meta name="description" content="Docucod generates and maintains up-to-date documentation from your codebase so your team can move fast, onboard quickly, and stay focused on building." />
11
+ <meta name="keywords" content="documentation, code documentation, auto documentation, developer tools, technical documentation, codebase documentation, llm, ai, anthropic, openai, chatgpt, claude, clidey" />
12
+ <meta name="author" content="Clidey" />
13
+ <meta name="language" content="English" />
14
+
15
+ <!-- Open Graph / Facebook -->
16
+ <meta property="og:title" content="Docucod: Generate and Maintain Documentation With No Work" />
17
+ <meta property="og:description" content="Docucod auto-generates and maintains up-to-date documentation from your codebase so your team can move fast, onboard quickly, and stay focused on building." />
18
+ <meta property="og:image" content="./docs/favicon.svg" />
19
+ <meta property="og:url" content="https://docucod.com/" />
20
+ <meta property="og:type" content="website" />
21
+ <meta property="og:site_name" content="Docucod" />
22
+ <meta property="og:locale" content="en_US" />
23
+
24
+ <!-- Twitter -->
25
+ <meta name="twitter:card" content="summary_large_image" />
26
+ <meta name="twitter:title" content="Docucod: Generate and Maintain Documentation With No Work" />
27
+ <meta name="twitter:description" content="Docucod auto-generates and maintains up-to-date documentation from your codebase so your team can move fast, onboard quickly, and stay focused on building." />
28
+ <meta name="twitter:image" content="./docs/favicon.svg" />
29
+ <meta name="twitter:site" content="@ClideyOfficial" />
30
+ <meta name="twitter:creator" content="@ClideyOfficial" />
31
+
32
+ <link rel="apple-touch-icon" href="./docs/favicon.svg" />
33
+ <link rel="manifest" href="/manifest.json" />
34
+
35
+ <title>Docucod - Instantly Generate and Maintain Your Documentation</title>
36
+
37
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
38
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
39
+ <link href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Inter&display=swap" rel="stylesheet">
40
+
41
+ <script>
42
+ !function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.crossOrigin="anonymous",p.async=!0,p.src=s.api_host.replace(".i.posthog.com","-assets.i.posthog.com")+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="init be ys Ss me gs ws capture Ne calculateEventProperties xs register register_once register_for_session unregister unregister_for_session Rs getFeatureFlag getFeatureFlagPayload isFeatureEnabled reloadFeatureFlags updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures on onFeatureFlags onSurveysLoaded onSessionId getSurveys getActiveMatchingSurveys renderSurvey canRenderSurvey canRenderSurveyAsync identify setPersonProperties group resetGroups setPersonPropertiesForFlags resetPersonPropertiesForFlags setGroupPropertiesForFlags resetGroupPropertiesForFlags reset get_distinct_id getGroups get_session_id get_session_replay_url alias set_config startSessionRecording stopSessionRecording sessionRecordingStarted captureException loadToolbar get_property getSessionProperty Is ks createPersonProfile Ps bs opt_in_capturing opt_out_capturing has_opted_in_capturing has_opted_out_capturing clear_opt_in_out_capturing $s debug Es getPageViewId captureTraceFeedback captureTraceMetric".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[]);
43
+ posthog.init('phc_hxwfcMiIgSXYKkbWl9zpQE0HaPPmlRaVXCWODricYu1', {
44
+ api_host: 'https://us.i.posthog.com',
45
+ person_profiles: 'identified_only', // or 'always' to create profiles for anonymous users as well
46
+ })
47
+ </script>
48
+ </head>
49
+ <body>
50
+ <noscript>You need to enable JavaScript to run this app.</noscript>
51
+ <div id="app"></div>
52
+ <script type="module" src="/src/main.tsx"></script>
53
+ </body>
54
+ </html>
package/k8s/Dockerfile ADDED
@@ -0,0 +1,15 @@
1
+ FROM node:20-alpine AS builder
2
+
3
+ RUN corepack enable && corepack prepare pnpm@10.10.0 --activate
4
+ WORKDIR /app
5
+ COPY package.json pnpm-lock.yaml ./
6
+ RUN pnpm install --frozen-lockfile
7
+ COPY . .
8
+ RUN pnpm build
9
+
10
+ FROM nginx:alpine
11
+ COPY --from=builder /app/dist /usr/share/nginx/html
12
+ COPY --from=builder /app/k8s/config/nginx-default.conf /etc/nginx/conf.d/default.conf
13
+ EXPOSE 80
14
+
15
+ CMD ["nginx", "-g", "daemon off;"]
@@ -0,0 +1,22 @@
1
+ server {
2
+ listen 80;
3
+ listen [::]:80;
4
+ server_name localhost;
5
+
6
+ location ^~ /docs {
7
+ alias /usr/share/docs;
8
+ index index.html index.htm;
9
+ try_files $uri $uri/ /index.html;
10
+ }
11
+
12
+ location / {
13
+ root /usr/share/nginx/html;
14
+ index index.html index.htm;
15
+ try_files $uri /index.html;
16
+ }
17
+
18
+ error_page 500 502 503 504 /50x.html;
19
+ location = /50x.html {
20
+ root /usr/share/nginx/html;
21
+ }
22
+ }
@@ -0,0 +1,16 @@
1
+ FROM node:22.16-alpine
2
+
3
+ WORKDIR /app
4
+ RUN corepack enable && corepack prepare pnpm@latest --activate
5
+ RUN apk add --no-cache git
6
+
7
+ COPY package.json pnpm-lock.yaml ./
8
+ RUN pnpm i
9
+
10
+ COPY . .
11
+
12
+ RUN rm -rf /app/src/data
13
+
14
+ EXPOSE 3000
15
+
16
+ CMD ["pnpm", "dev", "--host", "0.0.0.0", "--port", "3000"]
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@clidey/dory",
3
+ "private": false,
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "description": "A CLI tool for building and previewing documentation sites",
7
+ "keywords": [
8
+ "cli",
9
+ "documentation",
10
+ "build",
11
+ "preview",
12
+ "static-site"
13
+ ],
14
+ "bin": {
15
+ "dory": "./bin/dory.js"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/clidey/dory.git"
20
+ },
21
+ "author": "Clidey",
22
+ "license": "MIT",
23
+ "homepage": "https://github.com/clidey/dory",
24
+ "bugs": {
25
+ "url": "https://github.com/clidey/dory/issues"
26
+ },
27
+ "scripts": {
28
+ "dev": "vite",
29
+ "build": "tsc -b && vite build",
30
+ "preview": "vite preview"
31
+ },
32
+ "dependencies": {
33
+ "@algolia/autocomplete-core": "^1.19.2",
34
+ "@fortawesome/fontawesome-svg-core": "^6.7.2",
35
+ "@fortawesome/free-brands-svg-icons": "^6.7.2",
36
+ "@fortawesome/free-regular-svg-icons": "^6.7.2",
37
+ "@fortawesome/free-solid-svg-icons": "^6.7.2",
38
+ "@fortawesome/react-fontawesome": "^0.2.2",
39
+ "@headlessui/react": "^2.2.4",
40
+ "@heroicons/react": "^2.2.0",
41
+ "@mdx-js/preact": "^3.1.0",
42
+ "@mdx-js/rollup": "^3.1.0",
43
+ "@tailwindcss/vite": "^4.1.8",
44
+ "classnames": "^2.5.1",
45
+ "flexsearch": "^0.8.205",
46
+ "gray-matter": "^4.0.3",
47
+ "katex": "^0.16.22",
48
+ "lucide-react": "^0.513.0",
49
+ "mermaid": "^11.6.0",
50
+ "motion": "^12.16.0",
51
+ "preact": "^10.26.5",
52
+ "prism-react-renderer": "^2.4.1",
53
+ "react-highlight-words": "^0.21.0",
54
+ "remark-gfm": "^4.0.1",
55
+ "saas": "^1.0.0",
56
+ "sirv": "^3.0.1",
57
+ "tailwindcss": "^4.1.8",
58
+ "unist-util-visit": "^5.0.0",
59
+ "vite-plugin-mdx": "^3.6.1",
60
+ "wouter-preact": "^3.7.1"
61
+ },
62
+ "devDependencies": {
63
+ "@preact/preset-vite": "^2.10.1",
64
+ "@types/react-highlight-words": "^0.20.0",
65
+ "gzip-size-cli": "^5.1.0",
66
+ "sass-embedded": "^1.89.2",
67
+ "typescript": "~5.8.3",
68
+ "vite": "^6.3.5"
69
+ },
70
+ "packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39"
71
+ }
package/src/app.css ADDED
@@ -0,0 +1,3 @@
1
+ @import "tailwindcss";
2
+ @custom-variant dark (&:where(.dark, .dark *));
3
+
package/src/app.tsx ADDED
@@ -0,0 +1,13 @@
1
+ import './app.css'
2
+ import './theme.scss'
3
+ import Routes from './components/routes'
4
+
5
+ export function App() {
6
+ return (
7
+ <div className="h-full antialiased">
8
+ <div className="flex min-h-full">
9
+ <Routes />
10
+ </div>
11
+ </div>
12
+ )
13
+ }
@@ -0,0 +1,25 @@
1
+ import { SunIcon, MoonIcon } from '@heroicons/react/24/outline';
2
+ import { useDarkMode } from '../utils/hooks';
3
+ import classNames from 'classnames';
4
+
5
+ export function DarkModeToggle() {
6
+ const { isDark, toggleDarkMode } = useDarkMode();
7
+
8
+ return (
9
+ <button
10
+ onClick={toggleDarkMode}
11
+ className={classNames(
12
+ 'rounded-lg p-2 transition-colors duration-200',
13
+ 'hover:bg-gray-100 dark:hover:bg-gray-800',
14
+ 'focus:outline-none focus:ring-2 focus:ring-sky-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900'
15
+ )}
16
+ aria-label={isDark ? 'Switch to light mode' : 'Switch to dark mode'}
17
+ title={isDark ? 'Switch to light mode' : 'Switch to dark mode'}>
18
+ {isDark ? (
19
+ <SunIcon className="h-5 w-5 text-yellow-500" />
20
+ ) : (
21
+ <MoonIcon className="h-5 w-5 text-gray-600 dark:text-gray-400" />
22
+ )}
23
+ </button>
24
+ );
25
+ }
@@ -0,0 +1,49 @@
1
+ import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'
2
+ import { ChevronDownIcon } from '@heroicons/react/20/solid'
3
+ import type { ComponentChildren } from 'preact'
4
+
5
+ interface DropdownItem {
6
+ icon?: ComponentChildren
7
+ label: string
8
+ href?: string
9
+ onClick?: () => void
10
+ divider?: boolean
11
+ }
12
+
13
+ interface DropdownProps {
14
+ buttonLabel: string
15
+ items: DropdownItem[]
16
+ className?: string
17
+ }
18
+
19
+ export default function Dropdown({ buttonLabel, items, className = '' }: DropdownProps) {
20
+ return (
21
+ <Menu as="div" className={`relative inline-block text-left ${className}`}>
22
+ <div>
23
+ <MenuButton className="inline-flex w-full justify-center gap-x-1.5 rounded-md bg-[#1e1e1e] px-3 py-2 text-sm font-semibold text-[#ffffff] shadow-xs ring-1 ring-[#444444] ring-inset hover:bg-[#2a2a2a]">
24
+ {buttonLabel}
25
+ <ChevronDownIcon aria-hidden="true" className="-mr-1 size-5 text-[#aaaaaa]" />
26
+ </MenuButton>
27
+ </div>
28
+
29
+ <MenuItems
30
+ transition
31
+ className="absolute left-0 right-0 z-10 mt-2 w-full origin-top-right divide-y divide-[#444444] rounded-md bg-[#1e1e1e] shadow-lg ring-1 ring-[#444444]/50 transition focus:outline-hidden data-closed:scale-95 data-closed:transform data-closed:opacity-0 data-enter:duration-100 data-enter:ease-out data-leave:duration-75 data-leave:ease-in">
32
+ {items.map((item, index) => (
33
+ <div key={index} className={item.divider ? 'py-1' : ''}>
34
+ <MenuItem className="group">
35
+ <a href={item.href} onClick={item.onClick} className="group flex items-center px-4 py-2 text-sm text-[#ffffff] data-focus:bg-[#2a2a2a] data-focus:text-[#ffffff] data-focus:outline-hidden cursor-pointer">
36
+ {item.icon && (
37
+ <span className="mr-3 size-5 text-[#aaaaaa] group-data-focus:text-[#cccccc]">
38
+ {item.icon}
39
+ </span>
40
+ )}
41
+ {item.label}
42
+ </a>
43
+ </MenuItem>
44
+ </div>
45
+ ))}
46
+ </MenuItems>
47
+ </Menu>
48
+ )
49
+ }
@@ -0,0 +1,28 @@
1
+ import { useEffect, useState } from 'preact/hooks';
2
+ import { useLocation } from 'wouter-preact';
3
+
4
+ /**
5
+ * Custom hook to get the current pathname
6
+ */
7
+ export const usePathname = () => {
8
+ const [pathname] = useLocation();
9
+ return pathname;
10
+ };
11
+
12
+ /**
13
+ * Custom hook to get the URL search params as a URLSearchParams instance
14
+ */
15
+ export const useSearchParams = () => {
16
+ const [searchParams, setSearchParams] = useState(new URLSearchParams(window.location.search));
17
+
18
+ useEffect(() => {
19
+ const handlePopState = () => {
20
+ setSearchParams(new URLSearchParams(window.location.search));
21
+ };
22
+
23
+ window.addEventListener('popstate', handlePopState);
24
+ return () => window.removeEventListener('popstate', handlePopState);
25
+ }, []);
26
+
27
+ return searchParams;
28
+ };
@@ -0,0 +1,23 @@
1
+ import classNames from "classnames";
2
+
3
+ type ILoadingProps = {
4
+ className?: string;
5
+ hideText?: boolean;
6
+ loadingText?: string;
7
+ textClassName?: string;
8
+ }
9
+
10
+ export const Loading = ({ className, hideText, loadingText, textClassName }: ILoadingProps) => {
11
+ return (
12
+ <div className="h-full w-full flex flex-col justify-center items-center gap-2" role="status">
13
+ <svg aria-hidden="true" className={classNames("w-8 h-8 text-gray-200 animate-spin fill-black/50", className)} viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
14
+ <path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/>
15
+ <path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/>
16
+ </svg>
17
+ {
18
+ !hideText &&
19
+ <div className={classNames("text-sm text-gray-500", textClassName)}>{ loadingText ?? "Loading..."}</div>
20
+ }
21
+ </div>
22
+ )
23
+ }
@@ -0,0 +1,13 @@
1
+ import { MDXProvider } from '@mdx-js/preact';
2
+ import Layout from '../ui/layout';
3
+ import * as mdx from '../mdx/mdx';
4
+
5
+ export const withMDX = (Content: preact.ComponentType) => () => {
6
+ return (
7
+ <MDXProvider components={mdx}>
8
+ <Layout>
9
+ <Content />
10
+ </Layout>
11
+ </MDXProvider>
12
+ );
13
+ };
@@ -0,0 +1,53 @@
1
+ import classNames from 'classnames';
2
+ import { useMemo } from 'preact/hooks';
3
+ import { usePathname } from './hooks';
4
+ import { ALL_NAVIGATION } from './store';
5
+ import { Link } from 'wouter-preact';
6
+
7
+ export function Navigation() {
8
+ const pathname = usePathname();
9
+
10
+ const currentTab = useMemo(() => {
11
+ // Find the tab containing the current pathname
12
+ return ALL_NAVIGATION.find(tab =>
13
+ tab.groups.some(group =>
14
+ group.pages.some(page => page.href === pathname)
15
+ )
16
+ );
17
+ }, [pathname]);
18
+
19
+ return (
20
+ <nav className="docucod-navigation text-base lg:text-sm">
21
+ <ul role="list" className="space-y-9">
22
+ {currentTab && (
23
+ <li key={currentTab.title}>
24
+ <h2 className="font-display font-medium text-slate-900 dark:text-white">{currentTab.title}</h2>
25
+ {currentTab.groups.map(group => (
26
+ <div key={group.title}>
27
+ <h3 className="mt-4 text-sm font-semibold text-slate-900 dark:text-white">{group.title}</h3>
28
+ <ul role="list" className="mt-2 space-y-2 border-l-2 border-slate-100 lg:mt-4 lg:space-y-4 lg:border-slate-200 dark:border-slate-800">
29
+ {group.pages.map((page) => {
30
+ const isActive = pathname === page.href;
31
+ return (
32
+ <li key={page.href} className="relative">
33
+ <Link to={page.href} className={classNames(
34
+ 'block w-full pl-3.5 before:pointer-events-none before:absolute before:top-1/2 before:-left-1 before:h-1.5 before:w-1.5 before:-translate-y-1/2 before:rounded-full',
35
+ {
36
+ 'font-semibold text-sky-500 before:bg-sky-500': isActive,
37
+ 'text-slate-500 before:hidden before:bg-slate-300 hover:text-slate-600 hover:before:block dark:text-slate-400 dark:before:bg-slate-700 dark:hover:text-slate-300': !isActive
38
+ },
39
+ )}>
40
+ {page.title}
41
+ </Link>
42
+ </li>
43
+ );
44
+ })}
45
+ </ul>
46
+ </div>
47
+ ))}
48
+ </li>
49
+ )}
50
+ </ul>
51
+ </nav>
52
+ );
53
+ }
@@ -0,0 +1,97 @@
1
+ import { Transition } from '@headlessui/react';
2
+ import { CheckCircleIcon } from '@heroicons/react/24/outline';
3
+ import { createContext, type ComponentChildren } from 'preact';
4
+ import { useContext, useState } from 'preact/hooks';
5
+
6
+ interface Notification {
7
+ id: number;
8
+ message: string;
9
+ description?: string;
10
+ }
11
+
12
+ interface NotificationContextType {
13
+ showNotification: (message: string, description?: string) => void;
14
+ }
15
+
16
+ const NotificationContext = createContext<NotificationContextType | null>(null);
17
+
18
+ export function useNotification() {
19
+ const context = useContext(NotificationContext);
20
+ if (!context) {
21
+ throw new Error('useNotification must be used within a NotificationProvider');
22
+ }
23
+ return context;
24
+ }
25
+
26
+ interface NotificationProviderProps {
27
+ children: ComponentChildren;
28
+ }
29
+
30
+ export function NotificationProvider({ children }: NotificationProviderProps) {
31
+ const [notifications, setNotifications] = useState<Notification[]>([]);
32
+
33
+ const showNotification = (message: string, description?: string) => {
34
+ const id = Date.now();
35
+ setNotifications(prev => [...prev, { id, message, description }]);
36
+
37
+ // Auto-dismiss after 5 seconds
38
+ setTimeout(() => {
39
+ setNotifications(prev => prev.filter(notif => notif.id !== id));
40
+ }, 5000);
41
+ };
42
+
43
+ // const dismissNotification = (id: number) => {
44
+ // setNotifications(prev => prev.filter(notif => notif.id !== id));
45
+ // };
46
+
47
+ return (
48
+ <NotificationContext.Provider value={{ showNotification }}>
49
+ <div
50
+ aria-live="assertive"
51
+ className="pointer-events-none fixed inset-0 flex items-start justify-end px-8 py-6 sm:p-6 z-[100]"
52
+ >
53
+ <div className="flex w-full flex-col items-end space-y-4">
54
+ {notifications.map((notif) => (
55
+ <Transition
56
+ key={notif.id}
57
+ show={true}
58
+ enter="transform ease-out duration-300 transition"
59
+ enterFrom="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2"
60
+ enterTo="translate-y-0 opacity-100 sm:translate-x-0"
61
+ leave="transition ease-in duration-100"
62
+ leaveFrom="opacity-100"
63
+ leaveTo="opacity-0"
64
+ >
65
+ <div className="pointer-events-auto w-full max-w-sm overflow-hidden rounded-lg bg-[#222222] shadow-lg ring-1 ring-[#444444]/5">
66
+ <div className="p-4">
67
+ <div className="flex items-start">
68
+ <div className="shrink-0">
69
+ <CheckCircleIcon aria-hidden="true" className="size-6 text-green-400" />
70
+ </div>
71
+ <div className="ml-3 w-0 flex-1 pt-0.5">
72
+ <p className="text-sm font-medium text-[#ffffff]">{notif.message}</p>
73
+ {notif.description && (
74
+ <p className="mt-1 text-sm text-[#aaaaaa]">{notif.description}</p>
75
+ )}
76
+ </div>
77
+ {/* <div className="ml-4 flex shrink-0">
78
+ <button
79
+ type="button"
80
+ onClick={() => dismissNotification(notif.id)}
81
+ className="inline-flex rounded-md bg-transparent text-[#aaaaaa] hover:text-[#ffffff] focus:ring-2 focus:ring-[#444444] focus:ring-offset-2 focus:outline-hidden"
82
+ >
83
+ <span className="sr-only">Close</span>
84
+ <XMarkIcon aria-hidden="true" className="size-5" />
85
+ </button>
86
+ </div> */}
87
+ </div>
88
+ </div>
89
+ </div>
90
+ </Transition>
91
+ ))}
92
+ </div>
93
+ </div>
94
+ {children}
95
+ </NotificationContext.Provider>
96
+ );
97
+ }
@@ -0,0 +1,89 @@
1
+ import { useEffect, useState } from 'preact/hooks';
2
+ import { Route, Router, useLocation } from 'wouter-preact';
3
+ import { usePathname } from './hooks';
4
+ import { ALL_NAVIGATION, ALL_PAGES, pathFromFilename } from './store';
5
+ import { Loading } from './loading';
6
+
7
+
8
+ export default function Routes() {
9
+ const [routes, setRoutes] = useState<any[]>([]);
10
+ const pathname = usePathname();
11
+ const [, navigate] = useLocation();
12
+ const [loading, setLoading] = useState(true);
13
+
14
+ useEffect(() => {
15
+ const links = document.querySelectorAll('.docucod-navigation a');
16
+ const listeners: (() => void)[] = [];
17
+ const preloaded = new Set();
18
+
19
+ links.forEach(link => {
20
+ const href = link.getAttribute('href');
21
+ if (!href) return;
22
+
23
+ const loaderEntry = Object.entries(ALL_PAGES).find(
24
+ ([path]) => path === href
25
+ );
26
+
27
+ if (!loaderEntry) return;
28
+
29
+ const [path, loader] = loaderEntry;
30
+
31
+ const handleMouseEnter = () => {
32
+ if (!preloaded.has(path)) {
33
+ loader();
34
+ preloaded.add(path);
35
+ }
36
+ };
37
+
38
+ link.addEventListener('mouseenter', handleMouseEnter);
39
+ listeners.push(() => link.removeEventListener('mouseenter', handleMouseEnter));
40
+ });
41
+
42
+ return () => {
43
+ listeners.forEach(unsub => unsub());
44
+ };
45
+ }, []);
46
+
47
+ useEffect(() => {
48
+ setLoading(true);
49
+ const loadRoutes = async () => {
50
+ const entries = await Promise.all(
51
+ Object.entries(ALL_PAGES).filter(([path]) => path === pathname).map(async ([path, loader]) => {
52
+ const module = await loader();
53
+ const routePath = pathFromFilename(path);
54
+ return {
55
+ path: routePath,
56
+ component: module.default,
57
+ };
58
+ })
59
+ );
60
+ setRoutes(entries);
61
+ setLoading(false);
62
+ };
63
+
64
+ loadRoutes();
65
+ }, [pathname]);
66
+
67
+ useEffect(() => {
68
+ const page = ALL_NAVIGATION.find((tab) => tab.groups.find((group) => group.pages.find((page) => page.href === pathname)));
69
+ if (!page) {
70
+ navigate(ALL_NAVIGATION[0].groups[0].pages[0].href);
71
+ window.location.reload();
72
+ }
73
+ }, [routes, pathname]);
74
+
75
+ useEffect(() => {
76
+ window.scrollTo({ top: 0, behavior: 'smooth' });
77
+ }, [pathname]);
78
+
79
+
80
+ if (loading) return <div className="h-[25vh] flex grow"><Loading /></div>;
81
+
82
+ return (
83
+ <Router>
84
+ {routes.map(({ path, component: Component }) => (
85
+ <Route key={path} path={path} component={Component} />
86
+ ))}
87
+ </Router>
88
+ );
89
+ }
@@ -0,0 +1,43 @@
1
+ import { Document } from "flexsearch";
2
+
3
+ export const searchIndex = new Document({
4
+ tokenize: 'full',
5
+ document: {
6
+ id: 'url',
7
+ index: 'content',
8
+ store: ['title', 'pageTitle'],
9
+ },
10
+ context: {
11
+ resolution: 9,
12
+ depth: 2,
13
+ bidirectional: true
14
+ }
15
+ });
16
+
17
+ export function addToSearchIndex(filename: string, data: any, content: string) {
18
+ searchIndex.add({
19
+ url: filename,
20
+ title: data.title || '',
21
+ content: content,
22
+ pageTitle: data.title
23
+ });
24
+ }
25
+
26
+ export function search(query: string, options = {}) {
27
+ if (!searchIndex) return [];
28
+
29
+ const result = searchIndex.search(query, {
30
+ ...options,
31
+ enrich: true,
32
+ });
33
+
34
+ if (result.length === 0) {
35
+ return [];
36
+ }
37
+
38
+ return result[0].result.map((item) => ({
39
+ url: item.id,
40
+ title: item.doc?.title || '',
41
+ pageTitle: item.doc?.pageTitle || '',
42
+ }));
43
+ }