@wrikka/create-docs 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +137 -0
- package/bin/cli.js +408 -0
- package/dist/index.cjs +20 -0
- package/dist/index.d.cts +475 -0
- package/dist/index.d.ts +475 -0
- package/dist/index.js +20 -0
- package/docs/_dir.yml +2 -0
- package/docs/config.md +75 -0
- package/docs/data-sources.md +88 -0
- package/docs/deploy.md +38 -0
- package/docs/features.md +51 -0
- package/docs/getting-started.md +42 -0
- package/docs/index.md +34 -0
- package/docs/markdown.md +154 -0
- package/docs/theming.md +53 -0
- package/package.json +142 -0
- package/scripts/generate-docs-index.ts +111 -0
- package/scripts/github-pull.ts +124 -0
- package/src/adapters/config/index.ts +1 -0
- package/src/adapters/config/node-config-loader.ts +36 -0
- package/src/adapters/fs/fs-docs-loader.ts +31 -0
- package/src/adapters/fs/index.ts +12 -0
- package/src/adapters/fs/memory-fs.ts +60 -0
- package/src/adapters/fs/node-fs.ts +116 -0
- package/src/adapters/git/index.ts +164 -0
- package/src/adapters/github/github-pull.ts +340 -0
- package/src/adapters/index.ts +6 -0
- package/src/adapters/velite.ts +112 -0
- package/src/adapters/vite/docs-watcher.ts +36 -0
- package/src/adapters/vite/index.ts +2 -0
- package/src/adapters/vite/polling-watcher.ts +89 -0
- package/src/code-block-enhancements/index.ts +3 -0
- package/src/config/index.ts +15 -0
- package/src/content/index.ts +11 -0
- package/src/content-validation/index.ts +5 -0
- package/src/index.ts +174 -0
- package/src/live-editor/index.ts +7 -0
- package/src/modules/analytics/application/plausible-adapter.ts +103 -0
- package/src/modules/analytics/application/umami-adapter.ts +109 -0
- package/src/modules/analytics/domain/events/index.ts +37 -0
- package/src/modules/analytics/domain/models/index.ts +35 -0
- package/src/modules/analytics/domain/operations/analytics-operations.ts +67 -0
- package/src/modules/analytics/domain/validators/index.ts +50 -0
- package/src/modules/analytics/index.ts +9 -0
- package/src/modules/analytics/ports/analytics-port.ts +17 -0
- package/src/modules/analytics/types/analytics.ts +16 -0
- package/src/modules/api-docs/application/type-docs-adapter.ts +34 -0
- package/src/modules/api-docs/domain/events/index.ts +42 -0
- package/src/modules/api-docs/domain/models/index.ts +40 -0
- package/src/modules/api-docs/domain/operations/api-docs-operations.ts +59 -0
- package/src/modules/api-docs/domain/validators/index.ts +56 -0
- package/src/modules/api-docs/index.ts +8 -0
- package/src/modules/api-docs/ports/api-docs-port.ts +21 -0
- package/src/modules/api-docs/types/api-docs.ts +15 -0
- package/src/modules/api-reference/openapi.ts +166 -0
- package/src/modules/api-reference/orpc.ts +94 -0
- package/src/modules/code-block-enhancements/application/usecases/index.ts +39 -0
- package/src/modules/code-block-enhancements/domain/code-operations.ts +72 -0
- package/src/modules/code-block-enhancements/domain/events/index.ts +58 -0
- package/src/modules/code-block-enhancements/domain/index.ts +11 -0
- package/src/modules/code-block-enhancements/domain/models/index.ts +36 -0
- package/src/modules/code-block-enhancements/domain/validators/index.ts +50 -0
- package/src/modules/code-block-enhancements/index.ts +10 -0
- package/src/modules/code-block-enhancements/ports/index.ts +18 -0
- package/src/modules/code-block-enhancements/types/index.ts +37 -0
- package/src/modules/code-execution/application/sandbox-adapter.ts +44 -0
- package/src/modules/code-execution/domain/events/index.ts +58 -0
- package/src/modules/code-execution/domain/models/index.ts +33 -0
- package/src/modules/code-execution/domain/operations/execution-operations.ts +60 -0
- package/src/modules/code-execution/domain/validators/index.ts +50 -0
- package/src/modules/code-execution/index.ts +8 -0
- package/src/modules/code-execution/ports/execution-port.ts +15 -0
- package/src/modules/code-execution/types/execution.ts +15 -0
- package/src/modules/comments/application/giscus-adapter.ts +55 -0
- package/src/modules/comments/domain/events/index.ts +55 -0
- package/src/modules/comments/domain/models/index.ts +30 -0
- package/src/modules/comments/domain/operations/comment-operations.ts +46 -0
- package/src/modules/comments/domain/validators/index.ts +50 -0
- package/src/modules/comments/index.ts +8 -0
- package/src/modules/comments/ports/comment-port.ts +18 -0
- package/src/modules/comments/types/comments.ts +19 -0
- package/src/modules/config/application/usecases/index.ts +1 -0
- package/src/modules/config/application/usecases/loadConfig.ts +38 -0
- package/src/modules/config/application/workflows/index.ts +1 -0
- package/src/modules/config/application/workflows/validateConfig.ts +14 -0
- package/src/modules/config/domain/events/index.ts +50 -0
- package/src/modules/config/domain/models/index.ts +9 -0
- package/src/modules/config/domain/operations/defaults.ts +74 -0
- package/src/modules/config/domain/operations/index.ts +2 -0
- package/src/modules/config/domain/operations/resolve.ts +40 -0
- package/src/modules/config/domain/validators/index.ts +152 -0
- package/src/modules/config/index.ts +23 -0
- package/src/modules/config/ports/index.ts +15 -0
- package/src/modules/config/types/index.ts +113 -0
- package/src/modules/config/types/public.ts +19 -0
- package/src/modules/content/application/cache/index.ts +6 -0
- package/src/modules/content/application/cache/parse-cache.ts +6 -0
- package/src/modules/content/application/cache/parseCache.ts +56 -0
- package/src/modules/content/application/usecases/index.ts +2 -0
- package/src/modules/content/application/usecases/load-doc-page.ts +1 -0
- package/src/modules/content/application/usecases/loadDocPage.ts +111 -0
- package/src/modules/content/application/usecases/scanDocsDir.ts +105 -0
- package/src/modules/content/application/workflows/index.ts +1 -0
- package/src/modules/content/application/workflows/processDocs.ts +51 -0
- package/src/modules/content/domain/events/index.ts +61 -0
- package/src/modules/content/domain/models/index.ts +13 -0
- package/src/modules/content/domain/operations/build-page.ts +146 -0
- package/src/modules/content/domain/operations/buildPage.ts +93 -0
- package/src/modules/content/domain/operations/index.ts +17 -0
- package/src/modules/content/domain/operations/parse.ts +241 -0
- package/src/modules/content/domain/validators/frontmatter.ts +82 -0
- package/src/modules/content/domain/validators/index.ts +6 -0
- package/src/modules/content/index.ts +27 -0
- package/src/modules/content/ports/index.ts +45 -0
- package/src/modules/content/ports/velite.ts +134 -0
- package/src/modules/content/types/index.ts +76 -0
- package/src/modules/content/types/public.ts +1 -0
- package/src/modules/content-linting/application/builtin-linting-adapter.ts +82 -0
- package/src/modules/content-linting/domain/events/index.ts +58 -0
- package/src/modules/content-linting/domain/models/index.ts +46 -0
- package/src/modules/content-linting/domain/operations/linting-operations.ts +82 -0
- package/src/modules/content-linting/domain/validators/index.ts +50 -0
- package/src/modules/content-linting/index.ts +8 -0
- package/src/modules/content-linting/ports/linting-port.ts +21 -0
- package/src/modules/content-linting/types/linting.ts +18 -0
- package/src/modules/content-validation/application/usecases/index.ts +18 -0
- package/src/modules/content-validation/domain/events/index.ts +7 -0
- package/src/modules/content-validation/domain/index.ts +16 -0
- package/src/modules/content-validation/domain/models/index.ts +33 -0
- package/src/modules/content-validation/domain/operations/index.ts +157 -0
- package/src/modules/content-validation/domain/types/index.ts +17 -0
- package/src/modules/content-validation/domain/validators/index.ts +8 -0
- package/src/modules/content-validation/index.ts +9 -0
- package/src/modules/content-validation/ports/index.ts +15 -0
- package/src/modules/content-validation/types/index.ts +5 -0
- package/src/modules/export/application/puppeteer-export-adapter.ts +63 -0
- package/src/modules/export/domain/events/index.ts +55 -0
- package/src/modules/export/domain/models/index.ts +38 -0
- package/src/modules/export/domain/operations/export-operations.ts +56 -0
- package/src/modules/export/domain/validators/index.ts +52 -0
- package/src/modules/export/index.ts +8 -0
- package/src/modules/export/ports/export-port.ts +15 -0
- package/src/modules/export/types/export.ts +15 -0
- package/src/modules/external-search/application/algolia-adapter.ts +182 -0
- package/src/modules/external-search/application/meilisearch-adapter.ts +161 -0
- package/src/modules/external-search/domain/events/index.ts +42 -0
- package/src/modules/external-search/domain/models/index.ts +42 -0
- package/src/modules/external-search/domain/operations/search-operations.ts +53 -0
- package/src/modules/external-search/domain/validators/index.ts +55 -0
- package/src/modules/external-search/index.ts +9 -0
- package/src/modules/external-search/ports/external-search-port.ts +23 -0
- package/src/modules/external-search/types/search.ts +19 -0
- package/src/modules/git-history/application/simple-git-adapter.ts +46 -0
- package/src/modules/git-history/domain/events/index.ts +34 -0
- package/src/modules/git-history/domain/models/index.ts +25 -0
- package/src/modules/git-history/domain/operations/git-history-operations.ts +56 -0
- package/src/modules/git-history/domain/validators/index.ts +52 -0
- package/src/modules/git-history/index.ts +8 -0
- package/src/modules/git-history/ports/git-history-port.ts +21 -0
- package/src/modules/git-history/types/git-history.ts +15 -0
- package/src/modules/i18n/application/fs-locale-loader.ts +39 -0
- package/src/modules/i18n/domain/events/index.ts +34 -0
- package/src/modules/i18n/domain/models/index.ts +34 -0
- package/src/modules/i18n/domain/operations/locale-operations.ts +74 -0
- package/src/modules/i18n/domain/validators/index.ts +52 -0
- package/src/modules/i18n/index.ts +8 -0
- package/src/modules/i18n/ports/locale-loader-port.ts +18 -0
- package/src/modules/i18n/types/locale.ts +17 -0
- package/src/modules/image-optimization/application/sharp-adapter.ts +58 -0
- package/src/modules/image-optimization/domain/events/index.ts +39 -0
- package/src/modules/image-optimization/domain/models/index.ts +27 -0
- package/src/modules/image-optimization/domain/operations/image-operations.ts +65 -0
- package/src/modules/image-optimization/domain/validators/index.ts +52 -0
- package/src/modules/image-optimization/index.ts +8 -0
- package/src/modules/image-optimization/ports/image-optimizer-port.ts +15 -0
- package/src/modules/image-optimization/types/image.ts +17 -0
- package/src/modules/live-editor/application/usecases/index.ts +60 -0
- package/src/modules/live-editor/domain/events/index.ts +37 -0
- package/src/modules/live-editor/domain/gitOperations.ts +110 -0
- package/src/modules/live-editor/domain/index.ts +15 -0
- package/src/modules/live-editor/domain/models/index.ts +21 -0
- package/src/modules/live-editor/domain/validators/index.ts +55 -0
- package/src/modules/live-editor/index.ts +14 -0
- package/src/modules/live-editor/ports/index.ts +49 -0
- package/src/modules/live-editor/types/index.ts +80 -0
- package/src/modules/live-preview/application/vite-preview-adapter.ts +20 -0
- package/src/modules/live-preview/domain/operations/preview-operations.ts +56 -0
- package/src/modules/live-preview/index.ts +8 -0
- package/src/modules/live-preview/ports/preview-port.ts +20 -0
- package/src/modules/live-preview/types/preview.ts +16 -0
- package/src/modules/mdx/application/mdx-js-adapter.ts +38 -0
- package/src/modules/mdx/domain/operations/parse-mdx.ts +43 -0
- package/src/modules/mdx/index.ts +8 -0
- package/src/modules/mdx/ports/mdx-parser-port.ts +15 -0
- package/src/modules/mdx/types/mdx.ts +14 -0
- package/src/modules/navigation/application/usecases/buildNav.ts +15 -0
- package/src/modules/navigation/application/usecases/buildSidebar.ts +45 -0
- package/src/modules/navigation/application/usecases/index.ts +2 -0
- package/src/modules/navigation/domain/events/index.ts +30 -0
- package/src/modules/navigation/domain/models/index.ts +7 -0
- package/src/modules/navigation/domain/operations/group.ts +79 -0
- package/src/modules/navigation/domain/operations/index.ts +10 -0
- package/src/modules/navigation/domain/operations/sort.ts +27 -0
- package/src/modules/navigation/domain/shared/utils/array.ts +39 -0
- package/src/modules/navigation/domain/validators/index.ts +88 -0
- package/src/modules/navigation/index.ts +15 -0
- package/src/modules/navigation/ports/index.ts +9 -0
- package/src/modules/navigation/types/index.ts +38 -0
- package/src/modules/navigation/types/public.ts +9 -0
- package/src/modules/openapi/application/swagger-parser-adapter.ts +178 -0
- package/src/modules/openapi/domain/operations/parse-openapi.ts +46 -0
- package/src/modules/openapi/index.ts +8 -0
- package/src/modules/openapi/ports/openapi-parser-port.ts +15 -0
- package/src/modules/openapi/types/openapi.ts +37 -0
- package/src/modules/plugin/application/usecases/build-virtual-modules.ts +53 -0
- package/src/modules/plugin/application/usecases/index.ts +1 -0
- package/src/modules/plugin/application/workflows/build-docs.ts +79 -0
- package/src/modules/plugin/application/workflows/index.ts +1 -0
- package/src/modules/plugin/domain/events/index.ts +62 -0
- package/src/modules/plugin/domain/models/index.ts +1 -0
- package/src/modules/plugin/domain/operations/build-virtual.ts +76 -0
- package/src/modules/plugin/domain/operations/index.ts +11 -0
- package/src/modules/plugin/domain/validators/index.ts +30 -0
- package/src/modules/plugin/index.ts +14 -0
- package/src/modules/plugin/ports/index.ts +21 -0
- package/src/modules/plugin/types/index.ts +27 -0
- package/src/modules/plugin/types/public.ts +1 -0
- package/src/modules/search/application/usecases/index.ts +1 -0
- package/src/modules/search/application/usecases/indexPages.ts +15 -0
- package/src/modules/search/domain/events/index.ts +32 -0
- package/src/modules/search/domain/models/index.ts +1 -0
- package/src/modules/search/domain/operations/build-index.ts +6 -0
- package/src/modules/search/domain/operations/buildIndex.ts +70 -0
- package/src/modules/search/domain/operations/index.ts +7 -0
- package/src/modules/search/domain/operations/query.ts +186 -0
- package/src/modules/search/domain/validators/index.ts +32 -0
- package/src/modules/search/index.ts +9 -0
- package/src/modules/search/types/index.ts +22 -0
- package/src/modules/search/types/public.ts +1 -0
- package/src/modules/search-analytics/application/local-storage-adapter.ts +68 -0
- package/src/modules/search-analytics/domain/operations/search-analytics-operations.ts +74 -0
- package/src/modules/search-analytics/index.ts +8 -0
- package/src/modules/search-analytics/ports/search-analytics-port.ts +20 -0
- package/src/modules/search-analytics/types/search-analytics.ts +16 -0
- package/src/modules/search-highlighting/application/dom-highlighting-adapter.ts +36 -0
- package/src/modules/search-highlighting/domain/operations/highlighting-operations.ts +72 -0
- package/src/modules/search-highlighting/index.ts +8 -0
- package/src/modules/search-highlighting/ports/highlighting-port.ts +14 -0
- package/src/modules/search-highlighting/types/highlighting.ts +15 -0
- package/src/modules/seo-optimization/application/usecases/index.ts +18 -0
- package/src/modules/seo-optimization/domain/index.ts +12 -0
- package/src/modules/seo-optimization/domain/seoOperations.ts +92 -0
- package/src/modules/seo-optimization/index.ts +8 -0
- package/src/modules/seo-optimization/ports/index.ts +18 -0
- package/src/modules/seo-optimization/types/index.ts +41 -0
- package/src/modules/theme/application/css-theme-adapter.ts +50 -0
- package/src/modules/theme/domain/operations/theme-operations.ts +68 -0
- package/src/modules/theme/index.ts +8 -0
- package/src/modules/theme/ports/theme-port.ts +8 -0
- package/src/modules/theme/types/theme.ts +12 -0
- package/src/modules/toc/application/markdown-toc-adapter.ts +106 -0
- package/src/modules/toc/domain/operations/toc-operations.ts +92 -0
- package/src/modules/toc/index.ts +8 -0
- package/src/modules/toc/ports/toc-port.ts +21 -0
- package/src/modules/toc/types/toc.ts +17 -0
- package/src/modules/toc-generator/application/usecases/index.ts +30 -0
- package/src/modules/toc-generator/domain/index.ts +12 -0
- package/src/modules/toc-generator/domain/tocOperations.ts +104 -0
- package/src/modules/toc-generator/index.ts +9 -0
- package/src/modules/toc-generator/ports/index.ts +20 -0
- package/src/modules/toc-generator/types/index.ts +50 -0
- package/src/modules/version-control/domain/events/index.ts +39 -0
- package/src/modules/version-control/domain/models/index.ts +25 -0
- package/src/modules/version-control/domain/validators/index.ts +50 -0
- package/src/modules/versioning/application/fs-version-loader.ts +44 -0
- package/src/modules/versioning/domain/operations/versioning-operations.ts +80 -0
- package/src/modules/versioning/index.ts +8 -0
- package/src/modules/versioning/ports/version-loader-port.ts +18 -0
- package/src/modules/versioning/types/versioning.ts +17 -0
- package/src/modules/workflow/domain/events/index.ts +55 -0
- package/src/modules/workflow/domain/models/index.ts +40 -0
- package/src/modules/workflow/domain/validators/index.ts +50 -0
- package/src/modules/yaml-parser/application/js-yaml-adapter.ts +34 -0
- package/src/modules/yaml-parser/domain/operations/parse-yaml.ts +58 -0
- package/src/modules/yaml-parser/index.ts +7 -0
- package/src/modules/yaml-parser/ports/yaml-parser-port.ts +18 -0
- package/src/navigation/index.ts +9 -0
- package/src/plugin/index.ts +4 -0
- package/src/presentation/index.ts +1 -0
- package/src/presentation/live-editor/index.ts +22 -0
- package/src/presentation/live-editor/live-editor-component.ts +219 -0
- package/src/presentation/live-editor/virtual-module.ts +16 -0
- package/src/presentation/solid/CodeBlock.tsx +120 -0
- package/src/presentation/solid/Toc.tsx +58 -0
- package/src/presentation/solid/index.ts +11 -0
- package/src/presentation/vite-plugin/docs-plugin.ts +212 -0
- package/src/presentation/vite-plugin/index.ts +9 -0
- package/src/presentation/vite-plugin/transform-markdown.ts +26 -0
- package/src/runtime/analytics.ts +59 -0
- package/src/runtime/api-diff.ts +115 -0
- package/src/runtime/app.tsx +32 -0
- package/src/runtime/components/AccentPicker.tsx +114 -0
- package/src/runtime/components/ApiPlayground.tsx +332 -0
- package/src/runtime/components/AskAiDialog.tsx +127 -0
- package/src/runtime/components/BackToTop.tsx +38 -0
- package/src/runtime/components/Banner.tsx +75 -0
- package/src/runtime/components/Breadcrumbs.tsx +42 -0
- package/src/runtime/components/CollectionDropdown.tsx +85 -0
- package/src/runtime/components/CustomizeDrawer.tsx +195 -0
- package/src/runtime/components/DocMarkdown.tsx +579 -0
- package/src/runtime/components/DocPrevNext.tsx +54 -0
- package/src/runtime/components/DocToc.tsx +147 -0
- package/src/runtime/components/DocsDropdown.tsx +184 -0
- package/src/runtime/components/Footer.tsx +52 -0
- package/src/runtime/components/GitHubAuth.tsx +82 -0
- package/src/runtime/components/GitHubStats.tsx +73 -0
- package/src/runtime/components/Head.tsx +225 -0
- package/src/runtime/components/LocaleDropdown.tsx +75 -0
- package/src/runtime/components/MobileBottomNav.tsx +111 -0
- package/src/runtime/components/NotFound.tsx +139 -0
- package/src/runtime/components/PageActions.tsx +144 -0
- package/src/runtime/components/RelatedDocs.tsx +50 -0
- package/src/runtime/components/ScrollProgress.tsx +32 -0
- package/src/runtime/components/SearchPalette.tsx +333 -0
- package/src/runtime/components/ShowcaseCard.tsx +87 -0
- package/src/runtime/components/SidebarNav.tsx +237 -0
- package/src/runtime/components/Skeleton.tsx +39 -0
- package/src/runtime/components/ThemeToggle.tsx +26 -0
- package/src/runtime/components/TopNav.tsx +270 -0
- package/src/runtime/components/VersionDropdown.tsx +189 -0
- package/src/runtime/components/markdown/components.ts +280 -0
- package/src/runtime/config.ts +225 -0
- package/src/runtime/content.ts +451 -0
- package/src/runtime/context.tsx +30 -0
- package/src/runtime/data.ts +93 -0
- package/src/runtime/github.ts +226 -0
- package/src/runtime/icons.ts +32 -0
- package/src/runtime/index.ts +153 -0
- package/src/runtime/layouts/DocsLayout.tsx +144 -0
- package/src/runtime/markdown-content.css +996 -0
- package/src/runtime/pages/AbTestPage.tsx +88 -0
- package/src/runtime/pages/AnalyticsPage.tsx +90 -0
- package/src/runtime/pages/ApiDiffPage.tsx +86 -0
- package/src/runtime/pages/ApiEndpointPage.tsx +248 -0
- package/src/runtime/pages/BuilderPage.tsx +150 -0
- package/src/runtime/pages/ChangelogPage.tsx +210 -0
- package/src/runtime/pages/CollectionPage.tsx +140 -0
- package/src/runtime/pages/CommunityPage.tsx +384 -0
- package/src/runtime/pages/CreatePage.tsx +448 -0
- package/src/runtime/pages/DocPage.tsx +193 -0
- package/src/runtime/pages/EditPage.tsx +201 -0
- package/src/runtime/pages/HomePage.tsx +187 -0
- package/src/runtime/pages/IssuesPage.tsx +140 -0
- package/src/runtime/pages/OAuthCallbackPage.tsx +80 -0
- package/src/runtime/pages/PluginsPage.tsx +249 -0
- package/src/runtime/pages/ShowcasePage.tsx +202 -0
- package/src/runtime/pwa.ts +39 -0
- package/src/runtime/router.tsx +192 -0
- package/src/runtime/seo.ts +192 -0
- package/src/runtime/theme.css +175 -0
- package/src/runtime/theme.ts +224 -0
- package/src/runtime/types.ts +217 -0
- package/src/search/index.ts +6 -0
- package/src/seo-optimization/index.ts +4 -0
- package/src/shared/constants/index.ts +26 -0
- package/src/shared/errors/index.ts +195 -0
- package/src/shared/index.ts +27 -0
- package/src/shared/types/brand.ts +14 -0
- package/src/shared/types/client.d.ts +45 -0
- package/src/shared/types/either.ts +38 -0
- package/src/shared/types/index.ts +42 -0
- package/src/shared/types/option.ts +52 -0
- package/src/shared/types/result.ts +81 -0
- package/src/shared/utils/array.ts +97 -0
- package/src/shared/utils/index.ts +46 -0
- package/src/shared/utils/object.ts +47 -0
- package/src/shared/utils/string.ts +74 -0
- package/src/toc-generator/index.ts +1 -0
- package/src/types/js-yaml.d.ts +42 -0
- package/src/worker.ts +462 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import { createSignal, For, Show } from "solid-js";
|
|
2
|
+
import type { ApiEndpoint } from "../types";
|
|
3
|
+
|
|
4
|
+
const METHOD_COLOR: Record<string, string> = {
|
|
5
|
+
GET: "text-accent",
|
|
6
|
+
POST: "text-success",
|
|
7
|
+
PUT: "text-warning",
|
|
8
|
+
PATCH: "text-warning",
|
|
9
|
+
DELETE: "text-destructive",
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function buildCurl(ep: ApiEndpoint, url: string, body: string): string {
|
|
13
|
+
const lines = [`curl -X ${ep.method} '${url}'`];
|
|
14
|
+
if (body.trim()) {
|
|
15
|
+
lines.push(` -H 'Content-Type: application/json'`, ` -d '${body}'`);
|
|
16
|
+
}
|
|
17
|
+
return lines.join(" \\\n");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function buildCommand(
|
|
21
|
+
ep: ApiEndpoint,
|
|
22
|
+
argValues: Record<string, string>,
|
|
23
|
+
): string {
|
|
24
|
+
if (!ep.cli) return `${ep.method} ${ep.path}`;
|
|
25
|
+
const parts = [ep.cli.command];
|
|
26
|
+
if (ep.cli.subcommand) parts.push(ep.cli.subcommand);
|
|
27
|
+
for (const a of ep.cli.args ?? []) {
|
|
28
|
+
const v = argValues[a.name] ?? "";
|
|
29
|
+
if (v) parts.push(`--${a.name}`, v);
|
|
30
|
+
}
|
|
31
|
+
return parts.join(" ");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function ApiPlayground(props: {
|
|
35
|
+
endpoint: ApiEndpoint;
|
|
36
|
+
style?: string;
|
|
37
|
+
}) {
|
|
38
|
+
const ep = () => props.endpoint;
|
|
39
|
+
const style = () => props.style ?? "rest";
|
|
40
|
+
|
|
41
|
+
const [paramValues, setParamValues] = createSignal<Record<string, string>>(
|
|
42
|
+
{},
|
|
43
|
+
);
|
|
44
|
+
const [argValues, setArgValues] = createSignal<Record<string, string>>({});
|
|
45
|
+
const [body, setBody] = createSignal(
|
|
46
|
+
ep().requestBody?.example != null
|
|
47
|
+
? JSON.stringify(ep().requestBody?.example, null, 2)
|
|
48
|
+
: "",
|
|
49
|
+
);
|
|
50
|
+
const [query, setQuery] = createSignal(ep().graphql?.query ?? "");
|
|
51
|
+
const [variables, setVariables] = createSignal(
|
|
52
|
+
ep().graphql?.variables != null
|
|
53
|
+
? JSON.stringify(ep().graphql?.variables, null, 2)
|
|
54
|
+
: "",
|
|
55
|
+
);
|
|
56
|
+
const [sending, setSending] = createSignal(false);
|
|
57
|
+
const [result, setResult] = createSignal<{
|
|
58
|
+
status: number;
|
|
59
|
+
ms: number;
|
|
60
|
+
body: string;
|
|
61
|
+
} | null>(null);
|
|
62
|
+
const [error, setError] = createSignal("");
|
|
63
|
+
const [copied, setCopied] = createSignal(false);
|
|
64
|
+
|
|
65
|
+
const setParam = (name: string, value: string) =>
|
|
66
|
+
setParamValues((p) => ({ ...p, [name]: value }));
|
|
67
|
+
const setArg = (name: string, value: string) =>
|
|
68
|
+
setArgValues((p) => ({ ...p, [name]: value }));
|
|
69
|
+
|
|
70
|
+
const resolvedUrl = () => {
|
|
71
|
+
let path = ep().path;
|
|
72
|
+
const queryParams = new URLSearchParams();
|
|
73
|
+
for (const p of ep().parameters) {
|
|
74
|
+
const v = paramValues()[p.name] ?? "";
|
|
75
|
+
if (p.in === "path")
|
|
76
|
+
path = path.replace(`{${p.name}}`, v || `{${p.name}}`);
|
|
77
|
+
else if (p.in === "query" && v) queryParams.set(p.name, v);
|
|
78
|
+
}
|
|
79
|
+
const base = ep().server ?? "";
|
|
80
|
+
const qs = queryParams.toString();
|
|
81
|
+
return `${base}${path}${qs ? `?${qs}` : ""}`;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const send = async () => {
|
|
85
|
+
setSending(true);
|
|
86
|
+
setError("");
|
|
87
|
+
setResult(null);
|
|
88
|
+
const t0 = performance.now();
|
|
89
|
+
try {
|
|
90
|
+
let requestBody: string | undefined;
|
|
91
|
+
let headers: Record<string, string> | undefined;
|
|
92
|
+
|
|
93
|
+
if (style() === "graphql") {
|
|
94
|
+
const vars = variables().trim()
|
|
95
|
+
? (JSON.parse(variables()) as Record<string, unknown>)
|
|
96
|
+
: {};
|
|
97
|
+
requestBody = JSON.stringify({ query: query(), variables: vars });
|
|
98
|
+
headers = { "Content-Type": "application/json" };
|
|
99
|
+
} else if (body().trim()) {
|
|
100
|
+
requestBody = body();
|
|
101
|
+
headers = { "Content-Type": "application/json" };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const res = await fetch(resolvedUrl(), {
|
|
105
|
+
method: ep().method,
|
|
106
|
+
headers,
|
|
107
|
+
body: ep().method === "GET" || !requestBody ? undefined : requestBody,
|
|
108
|
+
});
|
|
109
|
+
const text = await res.text();
|
|
110
|
+
setResult({
|
|
111
|
+
status: res.status,
|
|
112
|
+
ms: Math.round(performance.now() - t0),
|
|
113
|
+
body: text,
|
|
114
|
+
});
|
|
115
|
+
} catch (e) {
|
|
116
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
117
|
+
} finally {
|
|
118
|
+
setSending(false);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const prettyResult = () => {
|
|
123
|
+
const r = result();
|
|
124
|
+
if (!r) return "";
|
|
125
|
+
try {
|
|
126
|
+
return JSON.stringify(JSON.parse(r.body), null, 2);
|
|
127
|
+
} catch {
|
|
128
|
+
return r.body;
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const copyCommand = () => {
|
|
133
|
+
const text = buildCommand(ep(), argValues());
|
|
134
|
+
navigator.clipboard.writeText(text).catch(() => {});
|
|
135
|
+
setCopied(true);
|
|
136
|
+
setTimeout(() => setCopied(false), 1500);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<div class="rounded-lg border border-border bg-surface overflow-hidden">
|
|
141
|
+
<Show when={style() !== "cli"}>
|
|
142
|
+
<div class="flex items-center gap-2 px-3 py-2 border-b border-border">
|
|
143
|
+
<span
|
|
144
|
+
class={`text-xs font-bold ${METHOD_COLOR[ep().method] ?? "text-foreground"}`}
|
|
145
|
+
>
|
|
146
|
+
{ep().method}
|
|
147
|
+
</span>
|
|
148
|
+
<code class="text-xs text-muted font-mono truncate flex-1">
|
|
149
|
+
{resolvedUrl() || ep().path}
|
|
150
|
+
</code>
|
|
151
|
+
</div>
|
|
152
|
+
</Show>
|
|
153
|
+
|
|
154
|
+
<div class="p-3 flex flex-col gap-3">
|
|
155
|
+
<Show when={style() === "graphql"}>
|
|
156
|
+
<div>
|
|
157
|
+
<p class="text-[11px] font-semibold uppercase tracking-wide text-muted m-0 mb-1.5">
|
|
158
|
+
Query
|
|
159
|
+
</p>
|
|
160
|
+
<textarea
|
|
161
|
+
value={query()}
|
|
162
|
+
onInput={(e) => setQuery(e.currentTarget.value)}
|
|
163
|
+
rows={8}
|
|
164
|
+
spellcheck={false}
|
|
165
|
+
class="w-full px-2 py-1.5 rounded border border-border bg-background text-xs font-mono text-foreground outline-none focus:border-focus resize-y"
|
|
166
|
+
/>
|
|
167
|
+
</div>
|
|
168
|
+
<div>
|
|
169
|
+
<p class="text-[11px] font-semibold uppercase tracking-wide text-muted m-0 mb-1.5">
|
|
170
|
+
Variables
|
|
171
|
+
</p>
|
|
172
|
+
<textarea
|
|
173
|
+
value={variables()}
|
|
174
|
+
onInput={(e) => setVariables(e.currentTarget.value)}
|
|
175
|
+
rows={4}
|
|
176
|
+
spellcheck={false}
|
|
177
|
+
class="w-full px-2 py-1.5 rounded border border-border bg-background text-xs font-mono text-foreground outline-none focus:border-focus resize-y"
|
|
178
|
+
/>
|
|
179
|
+
</div>
|
|
180
|
+
</Show>
|
|
181
|
+
|
|
182
|
+
<Show when={style() === "cli"}>
|
|
183
|
+
<div>
|
|
184
|
+
<p class="text-[11px] font-semibold uppercase tracking-wide text-muted m-0 mb-1.5">
|
|
185
|
+
Command
|
|
186
|
+
</p>
|
|
187
|
+
<pre class="m-0 p-2 rounded border border-border bg-background text-xs font-mono text-foreground overflow-auto whitespace-pre-wrap">
|
|
188
|
+
{buildCommand(ep(), argValues())}
|
|
189
|
+
</pre>
|
|
190
|
+
</div>
|
|
191
|
+
<Show when={ep().cli?.args?.length}>
|
|
192
|
+
<div>
|
|
193
|
+
<p class="text-[11px] font-semibold uppercase tracking-wide text-muted m-0 mb-1.5">
|
|
194
|
+
Arguments
|
|
195
|
+
</p>
|
|
196
|
+
<For each={ep().cli?.args ?? []}>
|
|
197
|
+
{(a) => (
|
|
198
|
+
<label class="flex items-center gap-2 mb-1.5">
|
|
199
|
+
<span class="text-xs font-mono text-foreground w-28 truncate shrink-0">
|
|
200
|
+
{a.name}
|
|
201
|
+
<Show when={a.required}>
|
|
202
|
+
<span class="text-destructive">*</span>
|
|
203
|
+
</Show>
|
|
204
|
+
</span>
|
|
205
|
+
<input
|
|
206
|
+
type="text"
|
|
207
|
+
value={argValues()[a.name] ?? ""}
|
|
208
|
+
onInput={(e) => setArg(a.name, e.currentTarget.value)}
|
|
209
|
+
placeholder={a.example ?? ""}
|
|
210
|
+
class="flex-1 min-w-0 h-7 px-2 rounded border border-border bg-background text-xs font-mono text-foreground outline-none focus:border-focus"
|
|
211
|
+
/>
|
|
212
|
+
</label>
|
|
213
|
+
)}
|
|
214
|
+
</For>
|
|
215
|
+
</div>
|
|
216
|
+
</Show>
|
|
217
|
+
<button
|
|
218
|
+
type="button"
|
|
219
|
+
onClick={copyCommand}
|
|
220
|
+
class="inline-flex items-center justify-center gap-1.5 h-9 rounded-md bg-primary text-primary-foreground text-sm font-medium hover:bg-primary-hover transition-colors cursor-pointer border-none"
|
|
221
|
+
>
|
|
222
|
+
<span
|
|
223
|
+
class={copied() ? "i-mdi:check" : "i-mdi:content-copy"}
|
|
224
|
+
aria-hidden="true"
|
|
225
|
+
/>
|
|
226
|
+
{copied() ? "Copied!" : "Copy command"}
|
|
227
|
+
</button>
|
|
228
|
+
</Show>
|
|
229
|
+
|
|
230
|
+
<Show
|
|
231
|
+
when={
|
|
232
|
+
ep().parameters.length > 0 &&
|
|
233
|
+
style() !== "graphql" &&
|
|
234
|
+
style() !== "cli"
|
|
235
|
+
}
|
|
236
|
+
>
|
|
237
|
+
<div>
|
|
238
|
+
<p class="text-[11px] font-semibold uppercase tracking-wide text-muted m-0 mb-1.5">
|
|
239
|
+
Parameters
|
|
240
|
+
</p>
|
|
241
|
+
<For each={ep().parameters}>
|
|
242
|
+
{(p) => (
|
|
243
|
+
<label class="flex items-center gap-2 mb-1.5">
|
|
244
|
+
<span class="text-xs font-mono text-foreground w-28 truncate shrink-0">
|
|
245
|
+
{p.name}
|
|
246
|
+
<Show when={p.required}>
|
|
247
|
+
<span class="text-destructive">*</span>
|
|
248
|
+
</Show>
|
|
249
|
+
</span>
|
|
250
|
+
<input
|
|
251
|
+
type="text"
|
|
252
|
+
value={paramValues()[p.name] ?? ""}
|
|
253
|
+
onInput={(e) => setParam(p.name, e.currentTarget.value)}
|
|
254
|
+
placeholder={p.in}
|
|
255
|
+
class="flex-1 min-w-0 h-7 px-2 rounded border border-border bg-background text-xs font-mono text-foreground outline-none focus:border-focus"
|
|
256
|
+
/>
|
|
257
|
+
</label>
|
|
258
|
+
)}
|
|
259
|
+
</For>
|
|
260
|
+
</div>
|
|
261
|
+
</Show>
|
|
262
|
+
|
|
263
|
+
<Show
|
|
264
|
+
when={ep().requestBody && style() !== "graphql" && style() !== "cli"}
|
|
265
|
+
>
|
|
266
|
+
<div>
|
|
267
|
+
<p class="text-[11px] font-semibold uppercase tracking-wide text-muted m-0 mb-1.5">
|
|
268
|
+
Body
|
|
269
|
+
</p>
|
|
270
|
+
<textarea
|
|
271
|
+
value={body()}
|
|
272
|
+
onInput={(e) => setBody(e.currentTarget.value)}
|
|
273
|
+
rows={6}
|
|
274
|
+
spellcheck={false}
|
|
275
|
+
class="w-full px-2 py-1.5 rounded border border-border bg-background text-xs font-mono text-foreground outline-none focus:border-focus resize-y"
|
|
276
|
+
/>
|
|
277
|
+
</div>
|
|
278
|
+
</Show>
|
|
279
|
+
|
|
280
|
+
<Show when={style() !== "cli"}>
|
|
281
|
+
<button
|
|
282
|
+
type="button"
|
|
283
|
+
onClick={send}
|
|
284
|
+
disabled={sending()}
|
|
285
|
+
class="inline-flex items-center justify-center gap-1.5 h-9 rounded-md bg-primary text-primary-foreground text-sm font-medium hover:bg-primary-hover transition-colors cursor-pointer border-none disabled:opacity-50"
|
|
286
|
+
>
|
|
287
|
+
<span
|
|
288
|
+
class={sending() ? "i-mdi:loading animate-spin" : "i-mdi:send"}
|
|
289
|
+
aria-hidden="true"
|
|
290
|
+
/>
|
|
291
|
+
{sending()
|
|
292
|
+
? "Sending…"
|
|
293
|
+
: style() === "graphql"
|
|
294
|
+
? "Run query"
|
|
295
|
+
: "Send request"}
|
|
296
|
+
</button>
|
|
297
|
+
</Show>
|
|
298
|
+
|
|
299
|
+
<Show when={error()}>
|
|
300
|
+
<p class="text-xs text-destructive m-0">{error()}</p>
|
|
301
|
+
</Show>
|
|
302
|
+
|
|
303
|
+
<Show when={result()}>
|
|
304
|
+
{(r) => (
|
|
305
|
+
<div>
|
|
306
|
+
<div class="flex items-center gap-2 mb-1.5">
|
|
307
|
+
<span
|
|
308
|
+
class={`text-xs font-bold ${r().status < 400 ? "text-success" : "text-destructive"}`}
|
|
309
|
+
>
|
|
310
|
+
{r().status}
|
|
311
|
+
</span>
|
|
312
|
+
<span class="text-[11px] text-muted">{r().ms}ms</span>
|
|
313
|
+
</div>
|
|
314
|
+
<pre class="m-0 p-2 rounded border border-border bg-background text-xs font-mono text-foreground overflow-auto max-h-64 whitespace-pre-wrap">
|
|
315
|
+
{prettyResult()}
|
|
316
|
+
</pre>
|
|
317
|
+
</div>
|
|
318
|
+
)}
|
|
319
|
+
</Show>
|
|
320
|
+
|
|
321
|
+
<Show when={style() !== "cli" && style() !== "graphql"}>
|
|
322
|
+
<details class="text-xs text-muted">
|
|
323
|
+
<summary class="cursor-pointer select-none">cURL</summary>
|
|
324
|
+
<pre class="m-0 mt-1.5 p-2 rounded border border-border bg-background font-mono text-foreground overflow-auto whitespace-pre-wrap">
|
|
325
|
+
{buildCurl(ep(), resolvedUrl(), body())}
|
|
326
|
+
</pre>
|
|
327
|
+
</details>
|
|
328
|
+
</Show>
|
|
329
|
+
</div>
|
|
330
|
+
</div>
|
|
331
|
+
);
|
|
332
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { createSignal, Show } from "solid-js";
|
|
2
|
+
import { useDocs } from "../context";
|
|
3
|
+
import { DocMarkdown } from "./DocMarkdown";
|
|
4
|
+
|
|
5
|
+
async function suggest(prompt: string): Promise<string> {
|
|
6
|
+
const res = await fetch("/api/ai/suggest", {
|
|
7
|
+
method: "POST",
|
|
8
|
+
headers: { "Content-Type": "application/json" },
|
|
9
|
+
body: JSON.stringify({ prompt }),
|
|
10
|
+
});
|
|
11
|
+
const data = (await res.json()) as { suggestion?: string; error?: string };
|
|
12
|
+
if (!res.ok) throw new Error(data.error ?? "AI request failed");
|
|
13
|
+
return data.suggestion ?? "";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function AskAiDialog(props: {
|
|
17
|
+
open: boolean;
|
|
18
|
+
onClose: () => void;
|
|
19
|
+
collection: string;
|
|
20
|
+
docId: string;
|
|
21
|
+
docTitle: string;
|
|
22
|
+
}) {
|
|
23
|
+
const [question, setQuestion] = createSignal("");
|
|
24
|
+
const [answer, setAnswer] = createSignal("");
|
|
25
|
+
const [loading, setLoading] = createSignal(false);
|
|
26
|
+
const [error, setError] = createSignal("");
|
|
27
|
+
const dataSource = useDocs().dataSource;
|
|
28
|
+
|
|
29
|
+
const ask = async () => {
|
|
30
|
+
const q = question().trim();
|
|
31
|
+
if (!q || loading()) return;
|
|
32
|
+
setLoading(true);
|
|
33
|
+
setError("");
|
|
34
|
+
setAnswer("");
|
|
35
|
+
try {
|
|
36
|
+
let answer = "";
|
|
37
|
+
if (dataSource.ask) {
|
|
38
|
+
const res = await dataSource.ask({
|
|
39
|
+
collection: props.collection,
|
|
40
|
+
id: props.docId,
|
|
41
|
+
question: q,
|
|
42
|
+
});
|
|
43
|
+
answer = res.answer;
|
|
44
|
+
} else {
|
|
45
|
+
answer = await suggest(`Document: ${props.docTitle}\nQuestion: ${q}`);
|
|
46
|
+
}
|
|
47
|
+
setAnswer(answer);
|
|
48
|
+
} catch (e) {
|
|
49
|
+
setError(e instanceof Error ? e.message : "Failed to get answer");
|
|
50
|
+
} finally {
|
|
51
|
+
setLoading(false);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<Show when={props.open}>
|
|
57
|
+
<div
|
|
58
|
+
class="fixed inset-0 z-50 bg-overlay flex items-start justify-center pt-20 px-4"
|
|
59
|
+
onClick={(e) => {
|
|
60
|
+
if (e.target === e.currentTarget) props.onClose();
|
|
61
|
+
}}
|
|
62
|
+
role="presentation"
|
|
63
|
+
>
|
|
64
|
+
<div class="w-full max-w-2xl rounded-lg border border-border bg-surface shadow-2xl overflow-hidden">
|
|
65
|
+
<div class="flex items-center gap-2 px-4 py-3 border-b border-border">
|
|
66
|
+
<span
|
|
67
|
+
class="i-mdi:robot-happy-outline text-primary"
|
|
68
|
+
aria-hidden="true"
|
|
69
|
+
/>
|
|
70
|
+
<span class="text-sm font-semibold text-foreground">
|
|
71
|
+
Ask AI about this page
|
|
72
|
+
</span>
|
|
73
|
+
<span class="text-xs text-muted truncate">— {props.docTitle}</span>
|
|
74
|
+
<button
|
|
75
|
+
type="button"
|
|
76
|
+
onClick={props.onClose}
|
|
77
|
+
aria-label="Close"
|
|
78
|
+
class="ml-auto w-8 h-8 inline-flex items-center justify-center rounded-md text-muted hover:text-foreground hover:bg-background transition-colors cursor-pointer border-none bg-transparent"
|
|
79
|
+
>
|
|
80
|
+
<span class="i-mdi:close" aria-hidden="true" />
|
|
81
|
+
</button>
|
|
82
|
+
</div>
|
|
83
|
+
<div class="p-4">
|
|
84
|
+
<div class="flex gap-2">
|
|
85
|
+
<textarea
|
|
86
|
+
value={question()}
|
|
87
|
+
onInput={(e) => setQuestion(e.currentTarget.value)}
|
|
88
|
+
onKeyDown={(e) => {
|
|
89
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
90
|
+
e.preventDefault();
|
|
91
|
+
ask();
|
|
92
|
+
}
|
|
93
|
+
}}
|
|
94
|
+
placeholder="Ask a question about this document..."
|
|
95
|
+
aria-label="Question"
|
|
96
|
+
rows={2}
|
|
97
|
+
class="flex-1 px-3 py-2 rounded-md border border-border bg-background text-sm text-foreground placeholder:text-muted resize-none outline-none focus:border-focus"
|
|
98
|
+
/>
|
|
99
|
+
<button
|
|
100
|
+
type="button"
|
|
101
|
+
onClick={ask}
|
|
102
|
+
disabled={loading() || !question().trim()}
|
|
103
|
+
class="self-end inline-flex items-center gap-1.5 px-4 h-9 rounded-md bg-primary text-primary-foreground text-sm font-medium hover:bg-primary-hover transition-colors cursor-pointer border-none disabled:opacity-50 disabled:cursor-not-allowed"
|
|
104
|
+
>
|
|
105
|
+
<span
|
|
106
|
+
class={
|
|
107
|
+
loading() ? "i-mdi:loading animate-spin" : "i-mdi:send"
|
|
108
|
+
}
|
|
109
|
+
aria-hidden="true"
|
|
110
|
+
/>
|
|
111
|
+
{loading() ? "Asking…" : "Ask"}
|
|
112
|
+
</button>
|
|
113
|
+
</div>
|
|
114
|
+
<Show when={error()}>
|
|
115
|
+
<p class="mt-3 text-sm text-destructive">{error()}</p>
|
|
116
|
+
</Show>
|
|
117
|
+
<Show when={answer()}>
|
|
118
|
+
<div class="mt-4 pt-3 border-t border-border">
|
|
119
|
+
<DocMarkdown source={answer()} />
|
|
120
|
+
</div>
|
|
121
|
+
</Show>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
</Show>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createEffect, createSignal, onCleanup, Show } from "solid-js";
|
|
2
|
+
|
|
3
|
+
export function BackToTop() {
|
|
4
|
+
const [visible, setVisible] = createSignal(false);
|
|
5
|
+
|
|
6
|
+
createEffect(() => {
|
|
7
|
+
const onScroll = () => {
|
|
8
|
+
setVisible(window.scrollY > 400);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
onScroll();
|
|
12
|
+
window.addEventListener("scroll", onScroll, { passive: true });
|
|
13
|
+
onCleanup(() => window.removeEventListener("scroll", onScroll));
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const scrollToTop = () =>
|
|
17
|
+
window.scrollTo({
|
|
18
|
+
top: 0,
|
|
19
|
+
behavior: prefersReducedMotion() ? "auto" : "smooth",
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<Show when={visible()}>
|
|
24
|
+
<button
|
|
25
|
+
type="button"
|
|
26
|
+
onClick={scrollToTop}
|
|
27
|
+
class="fixed bottom-6 right-6 z-50 flex items-center justify-center w-10 h-10 rounded-full border border-border bg-surface text-foreground shadow-md hover:bg-primary hover:text-primary-foreground transition-colors"
|
|
28
|
+
aria-label="Back to top"
|
|
29
|
+
>
|
|
30
|
+
<span class="i-mdi:chevron-up" aria-hidden="true" />
|
|
31
|
+
</button>
|
|
32
|
+
</Show>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function prefersReducedMotion() {
|
|
37
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
38
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { createSignal, onMount, Show } from "solid-js";
|
|
2
|
+
import { useDocs } from "../context";
|
|
3
|
+
|
|
4
|
+
const BANNER_HEIGHT_VAR = "--docs-banner-height";
|
|
5
|
+
|
|
6
|
+
function setBannerHeight(height: number) {
|
|
7
|
+
document.documentElement.style.setProperty(BANNER_HEIGHT_VAR, `${height}px`);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function Banner() {
|
|
11
|
+
const config = useDocs();
|
|
12
|
+
const banner = () => config.announcement;
|
|
13
|
+
const storageKey = "docs-banner-dismissed";
|
|
14
|
+
const [dismissed, setDismissed] = createSignal(false);
|
|
15
|
+
|
|
16
|
+
const isVisible = () => {
|
|
17
|
+
if (!banner()?.text) return false;
|
|
18
|
+
if (dismissed()) return false;
|
|
19
|
+
if (
|
|
20
|
+
banner()?.closable !== false &&
|
|
21
|
+
localStorage.getItem(storageKey) === "1"
|
|
22
|
+
) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const dismiss = () => {
|
|
29
|
+
setDismissed(true);
|
|
30
|
+
setBannerHeight(0);
|
|
31
|
+
if (banner()?.closable !== false) {
|
|
32
|
+
localStorage.setItem(storageKey, "1");
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
onMount(() => {
|
|
37
|
+
if (!isVisible()) setBannerHeight(0);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<Show when={isVisible()}>
|
|
42
|
+
<div
|
|
43
|
+
class="sticky top-0 z-50 w-full bg-primary text-primary-foreground text-sm"
|
|
44
|
+
ref={(el) => {
|
|
45
|
+
queueMicrotask(() => {
|
|
46
|
+
if (isVisible()) setBannerHeight(el.getBoundingClientRect().height);
|
|
47
|
+
});
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
50
|
+
<div class="max-w-6xl mx-auto px-4 py-2 flex items-center justify-center gap-2">
|
|
51
|
+
<Show when={banner()?.to} fallback={<span>{banner()?.text}</span>}>
|
|
52
|
+
{(to) => (
|
|
53
|
+
<a
|
|
54
|
+
href={to()}
|
|
55
|
+
class="underline hover:no-underline text-primary-foreground"
|
|
56
|
+
>
|
|
57
|
+
{banner()?.text}
|
|
58
|
+
</a>
|
|
59
|
+
)}
|
|
60
|
+
</Show>
|
|
61
|
+
<Show when={banner()?.closable !== false}>
|
|
62
|
+
<button
|
|
63
|
+
type="button"
|
|
64
|
+
onClick={dismiss}
|
|
65
|
+
class="ml-2 inline-flex items-center justify-center w-6 h-6 rounded-full hover:bg-primary-foreground/20"
|
|
66
|
+
aria-label="Dismiss announcement"
|
|
67
|
+
>
|
|
68
|
+
<span class="i-mdi:close" aria-hidden="true" />
|
|
69
|
+
</button>
|
|
70
|
+
</Show>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
</Show>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Link } from "@tanstack/solid-router";
|
|
2
|
+
import { useDocs } from "../context";
|
|
3
|
+
import { useCollections } from "../data";
|
|
4
|
+
|
|
5
|
+
export function Breadcrumbs(props: {
|
|
6
|
+
collection: string;
|
|
7
|
+
category: string;
|
|
8
|
+
label: string;
|
|
9
|
+
}) {
|
|
10
|
+
const collections = useCollections();
|
|
11
|
+
const config = useDocs();
|
|
12
|
+
const collectionMeta = () =>
|
|
13
|
+
collections()?.find((c) => c.id === props.collection);
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<nav
|
|
17
|
+
aria-label="Breadcrumbs"
|
|
18
|
+
class="flex items-center gap-1.5 text-xs text-muted pb-3"
|
|
19
|
+
>
|
|
20
|
+
<Link to="/" class="hover:text-foreground transition-colors">
|
|
21
|
+
{config.site.title}
|
|
22
|
+
</Link>
|
|
23
|
+
<span class="i-mdi:chevron-right" aria-hidden="true" />
|
|
24
|
+
<Link
|
|
25
|
+
to={`/${props.collection}`}
|
|
26
|
+
class="hover:text-foreground transition-colors inline-flex items-center gap-1"
|
|
27
|
+
>
|
|
28
|
+
<span
|
|
29
|
+
class={collectionMeta()?.icon ?? "i-mdi:folder-outline"}
|
|
30
|
+
aria-hidden="true"
|
|
31
|
+
/>
|
|
32
|
+
<span>{collectionMeta()?.label ?? props.collection}</span>
|
|
33
|
+
</Link>
|
|
34
|
+
<span class="i-mdi:chevron-right" aria-hidden="true" />
|
|
35
|
+
<span>{props.category}</span>
|
|
36
|
+
<span class="i-mdi:chevron-right" aria-hidden="true" />
|
|
37
|
+
<span class="text-foreground font-medium truncate max-w-[16rem]">
|
|
38
|
+
{props.label}
|
|
39
|
+
</span>
|
|
40
|
+
</nav>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Link } from "@tanstack/solid-router";
|
|
2
|
+
import { createSignal, For, onCleanup, onMount, Show } from "solid-js";
|
|
3
|
+
import { useCollections } from "../data";
|
|
4
|
+
|
|
5
|
+
export function CollectionDropdown(props: { current: string | undefined }) {
|
|
6
|
+
const [open, setOpen] = createSignal(false);
|
|
7
|
+
const collections = useCollections();
|
|
8
|
+
let rootEl: HTMLDivElement | undefined;
|
|
9
|
+
|
|
10
|
+
const currentMeta = () => collections()?.find((c) => c.id === props.current);
|
|
11
|
+
|
|
12
|
+
const onDocClick = (e: MouseEvent) => {
|
|
13
|
+
if (rootEl && !rootEl.contains(e.target as Node)) setOpen(false);
|
|
14
|
+
};
|
|
15
|
+
const onKey = (e: KeyboardEvent) => {
|
|
16
|
+
if (e.key === "Escape") setOpen(false);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
onMount(() => {
|
|
20
|
+
document.addEventListener("click", onDocClick);
|
|
21
|
+
document.addEventListener("keydown", onKey);
|
|
22
|
+
});
|
|
23
|
+
onCleanup(() => {
|
|
24
|
+
document.removeEventListener("click", onDocClick);
|
|
25
|
+
document.removeEventListener("keydown", onKey);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div ref={rootEl} class="relative">
|
|
30
|
+
<button
|
|
31
|
+
type="button"
|
|
32
|
+
onClick={() => setOpen(!open())}
|
|
33
|
+
aria-expanded={open()}
|
|
34
|
+
aria-haspopup="listbox"
|
|
35
|
+
class="inline-flex items-center gap-2 px-3 h-9 rounded-md border border-border bg-surface text-sm text-foreground hover:border-focus transition-colors cursor-pointer"
|
|
36
|
+
>
|
|
37
|
+
<Show when={currentMeta()}>
|
|
38
|
+
<span class={currentMeta()!.icon} aria-hidden="true" />
|
|
39
|
+
</Show>
|
|
40
|
+
<span class="font-medium">
|
|
41
|
+
{currentMeta()?.label ?? "Select collection"}
|
|
42
|
+
</span>
|
|
43
|
+
<span
|
|
44
|
+
class={`i-mdi:chevron-down text-muted transition-transform ${open() ? "rotate-180" : ""}`}
|
|
45
|
+
aria-hidden="true"
|
|
46
|
+
/>
|
|
47
|
+
</button>
|
|
48
|
+
<Show when={open()}>
|
|
49
|
+
<ul
|
|
50
|
+
role="listbox"
|
|
51
|
+
aria-label="Documentation collections"
|
|
52
|
+
class="absolute left-0 top-full mt-2 min-w-56 rounded-lg border border-border bg-surface shadow-lg py-1 z-50 list-none m-0"
|
|
53
|
+
>
|
|
54
|
+
<For each={collections()}>
|
|
55
|
+
{(c) => (
|
|
56
|
+
<li role="option" aria-selected={c.id === props.current}>
|
|
57
|
+
<Link
|
|
58
|
+
to="/$collection"
|
|
59
|
+
params={{ collection: c.id }}
|
|
60
|
+
onClick={() => setOpen(false)}
|
|
61
|
+
class="flex items-start gap-3 px-3 py-2 text-sm no-underline transition-colors hover:bg-background"
|
|
62
|
+
>
|
|
63
|
+
<span
|
|
64
|
+
class={`${c.icon} mt-0.5 text-primary`}
|
|
65
|
+
aria-hidden="true"
|
|
66
|
+
/>
|
|
67
|
+
<span class="min-w-0">
|
|
68
|
+
<span
|
|
69
|
+
class={`block font-medium ${c.id === props.current ? "text-primary" : "text-foreground"}`}
|
|
70
|
+
>
|
|
71
|
+
{c.label}
|
|
72
|
+
</span>
|
|
73
|
+
<span class="block text-xs text-muted leading-snug">
|
|
74
|
+
{c.description}
|
|
75
|
+
</span>
|
|
76
|
+
</span>
|
|
77
|
+
</Link>
|
|
78
|
+
</li>
|
|
79
|
+
)}
|
|
80
|
+
</For>
|
|
81
|
+
</ul>
|
|
82
|
+
</Show>
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
}
|