jamdesk 1.0.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/LICENSE +21 -0
- package/README.md +323 -0
- package/bin/jamdesk.js +76 -0
- package/dist/__tests__/integration/deprecated-components.integration.test.d.ts +8 -0
- package/dist/__tests__/integration/deprecated-components.integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration/deprecated-components.integration.test.js +165 -0
- package/dist/__tests__/integration/deprecated-components.integration.test.js.map +1 -0
- package/dist/__tests__/integration/migrate.integration.test.d.ts +2 -0
- package/dist/__tests__/integration/migrate.integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration/migrate.integration.test.js +64 -0
- package/dist/__tests__/integration/migrate.integration.test.js.map +1 -0
- package/dist/__tests__/integration/prepublish.integration.test.d.ts +2 -0
- package/dist/__tests__/integration/prepublish.integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration/prepublish.integration.test.js +27 -0
- package/dist/__tests__/integration/prepublish.integration.test.js.map +1 -0
- package/dist/__tests__/integration/validate.integration.test.d.ts +2 -0
- package/dist/__tests__/integration/validate.integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration/validate.integration.test.js +56 -0
- package/dist/__tests__/integration/validate.integration.test.js.map +1 -0
- package/dist/__tests__/unit/deploy-templates.test.d.ts +2 -0
- package/dist/__tests__/unit/deploy-templates.test.d.ts.map +1 -0
- package/dist/__tests__/unit/deploy-templates.test.js +124 -0
- package/dist/__tests__/unit/deploy-templates.test.js.map +1 -0
- package/dist/__tests__/unit/deprecated-components-sync.test.d.ts +2 -0
- package/dist/__tests__/unit/deprecated-components-sync.test.d.ts.map +1 -0
- package/dist/__tests__/unit/deprecated-components-sync.test.js +69 -0
- package/dist/__tests__/unit/deprecated-components-sync.test.js.map +1 -0
- package/dist/__tests__/unit/deps-sync.test.d.ts +14 -0
- package/dist/__tests__/unit/deps-sync.test.d.ts.map +1 -0
- package/dist/__tests__/unit/deps-sync.test.js +166 -0
- package/dist/__tests__/unit/deps-sync.test.js.map +1 -0
- package/dist/__tests__/unit/docs-config.test.d.ts +2 -0
- package/dist/__tests__/unit/docs-config.test.d.ts.map +1 -0
- package/dist/__tests__/unit/docs-config.test.js +288 -0
- package/dist/__tests__/unit/docs-config.test.js.map +1 -0
- package/dist/__tests__/unit/errors.test.d.ts +2 -0
- package/dist/__tests__/unit/errors.test.d.ts.map +1 -0
- package/dist/__tests__/unit/errors.test.js +27 -0
- package/dist/__tests__/unit/errors.test.js.map +1 -0
- package/dist/__tests__/unit/extract-hooks.test.d.ts +5 -0
- package/dist/__tests__/unit/extract-hooks.test.d.ts.map +1 -0
- package/dist/__tests__/unit/extract-hooks.test.js +205 -0
- package/dist/__tests__/unit/extract-hooks.test.js.map +1 -0
- package/dist/__tests__/unit/frontmatter-sync.test.d.ts +8 -0
- package/dist/__tests__/unit/frontmatter-sync.test.d.ts.map +1 -0
- package/dist/__tests__/unit/frontmatter-sync.test.js +26 -0
- package/dist/__tests__/unit/frontmatter-sync.test.js.map +1 -0
- package/dist/__tests__/unit/mdx-validator.test.d.ts +2 -0
- package/dist/__tests__/unit/mdx-validator.test.d.ts.map +1 -0
- package/dist/__tests__/unit/mdx-validator.test.js +264 -0
- package/dist/__tests__/unit/mdx-validator.test.js.map +1 -0
- package/dist/__tests__/unit/migrate-convert.test.d.ts +2 -0
- package/dist/__tests__/unit/migrate-convert.test.d.ts.map +1 -0
- package/dist/__tests__/unit/migrate-convert.test.js +297 -0
- package/dist/__tests__/unit/migrate-convert.test.js.map +1 -0
- package/dist/__tests__/unit/migrate-detect.test.d.ts +2 -0
- package/dist/__tests__/unit/migrate-detect.test.d.ts.map +1 -0
- package/dist/__tests__/unit/migrate-detect.test.js +35 -0
- package/dist/__tests__/unit/migrate-detect.test.js.map +1 -0
- package/dist/__tests__/unit/migrate-mdx.test.d.ts +2 -0
- package/dist/__tests__/unit/migrate-mdx.test.d.ts.map +1 -0
- package/dist/__tests__/unit/migrate-mdx.test.js +158 -0
- package/dist/__tests__/unit/migrate-mdx.test.js.map +1 -0
- package/dist/__tests__/unit/openapi.test.d.ts +2 -0
- package/dist/__tests__/unit/openapi.test.d.ts.map +1 -0
- package/dist/__tests__/unit/openapi.test.js +52 -0
- package/dist/__tests__/unit/openapi.test.js.map +1 -0
- package/dist/__tests__/unit/package-config.test.d.ts +2 -0
- package/dist/__tests__/unit/package-config.test.d.ts.map +1 -0
- package/dist/__tests__/unit/package-config.test.js +63 -0
- package/dist/__tests__/unit/package-config.test.js.map +1 -0
- package/dist/__tests__/unit/port.test.d.ts +2 -0
- package/dist/__tests__/unit/port.test.d.ts.map +1 -0
- package/dist/__tests__/unit/port.test.js +20 -0
- package/dist/__tests__/unit/port.test.js.map +1 -0
- package/dist/__tests__/unit/vendored-sync.test.d.ts +14 -0
- package/dist/__tests__/unit/vendored-sync.test.d.ts.map +1 -0
- package/dist/__tests__/unit/vendored-sync.test.js +90 -0
- package/dist/__tests__/unit/vendored-sync.test.js.map +1 -0
- package/dist/commands/broken-links.d.ts +11 -0
- package/dist/commands/broken-links.d.ts.map +1 -0
- package/dist/commands/broken-links.js +95 -0
- package/dist/commands/broken-links.js.map +1 -0
- package/dist/commands/clean.d.ts +7 -0
- package/dist/commands/clean.d.ts.map +1 -0
- package/dist/commands/clean.js +59 -0
- package/dist/commands/clean.js.map +1 -0
- package/dist/commands/deploy/cloudflare.d.ts +12 -0
- package/dist/commands/deploy/cloudflare.d.ts.map +1 -0
- package/dist/commands/deploy/cloudflare.js +409 -0
- package/dist/commands/deploy/cloudflare.js.map +1 -0
- package/dist/commands/deploy/templates.d.ts +23 -0
- package/dist/commands/deploy/templates.d.ts.map +1 -0
- package/dist/commands/deploy/templates.js +179 -0
- package/dist/commands/deploy/templates.js.map +1 -0
- package/dist/commands/deploy/types.d.ts +19 -0
- package/dist/commands/deploy/types.d.ts.map +1 -0
- package/dist/commands/deploy/types.js +5 -0
- package/dist/commands/deploy/types.js.map +1 -0
- package/dist/commands/dev.d.ts +14 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +817 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/doctor.d.ts +7 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +159 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/init.d.ts +7 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +96 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/migrate/convert-mdx.d.ts +50 -0
- package/dist/commands/migrate/convert-mdx.d.ts.map +1 -0
- package/dist/commands/migrate/convert-mdx.js +108 -0
- package/dist/commands/migrate/convert-mdx.js.map +1 -0
- package/dist/commands/migrate/convert.d.ts +80 -0
- package/dist/commands/migrate/convert.d.ts.map +1 -0
- package/dist/commands/migrate/convert.js +158 -0
- package/dist/commands/migrate/convert.js.map +1 -0
- package/dist/commands/migrate/detect.d.ts +31 -0
- package/dist/commands/migrate/detect.d.ts.map +1 -0
- package/dist/commands/migrate/detect.js +62 -0
- package/dist/commands/migrate/detect.js.map +1 -0
- package/dist/commands/migrate/extract-hooks.d.ts +71 -0
- package/dist/commands/migrate/extract-hooks.d.ts.map +1 -0
- package/dist/commands/migrate/extract-hooks.js +473 -0
- package/dist/commands/migrate/extract-hooks.js.map +1 -0
- package/dist/commands/migrate/index.d.ts +17 -0
- package/dist/commands/migrate/index.d.ts.map +1 -0
- package/dist/commands/migrate/index.js +282 -0
- package/dist/commands/migrate/index.js.map +1 -0
- package/dist/commands/migrate/prompts.d.ts +22 -0
- package/dist/commands/migrate/prompts.d.ts.map +1 -0
- package/dist/commands/migrate/prompts.js +67 -0
- package/dist/commands/migrate/prompts.js.map +1 -0
- package/dist/commands/migrate/types.d.ts +22 -0
- package/dist/commands/migrate/types.d.ts.map +1 -0
- package/dist/commands/migrate/types.js +26 -0
- package/dist/commands/migrate/types.js.map +1 -0
- package/dist/commands/openapi-check.d.ts +11 -0
- package/dist/commands/openapi-check.d.ts.map +1 -0
- package/dist/commands/openapi-check.js +88 -0
- package/dist/commands/openapi-check.js.map +1 -0
- package/dist/commands/rename.d.ts +10 -0
- package/dist/commands/rename.d.ts.map +1 -0
- package/dist/commands/rename.js +125 -0
- package/dist/commands/rename.js.map +1 -0
- package/dist/commands/update.d.ts +10 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +57 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/validate.d.ts +12 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +163 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +334 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/config.d.ts +7 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +18 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/deprecated-components.d.ts +72 -0
- package/dist/lib/deprecated-components.d.ts.map +1 -0
- package/dist/lib/deprecated-components.js +138 -0
- package/dist/lib/deprecated-components.js.map +1 -0
- package/dist/lib/deps.d.ts +17 -0
- package/dist/lib/deps.d.ts.map +1 -0
- package/dist/lib/deps.js +186 -0
- package/dist/lib/deps.js.map +1 -0
- package/dist/lib/docs-config.d.ts +67 -0
- package/dist/lib/docs-config.d.ts.map +1 -0
- package/dist/lib/docs-config.js +294 -0
- package/dist/lib/docs-config.js.map +1 -0
- package/dist/lib/errors.d.ts +23 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +32 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/frontmatter-utils.d.ts +25 -0
- package/dist/lib/frontmatter-utils.d.ts.map +1 -0
- package/dist/lib/frontmatter-utils.js +64 -0
- package/dist/lib/frontmatter-utils.js.map +1 -0
- package/dist/lib/mdx-validator.d.ts +27 -0
- package/dist/lib/mdx-validator.d.ts.map +1 -0
- package/dist/lib/mdx-validator.js +148 -0
- package/dist/lib/mdx-validator.js.map +1 -0
- package/dist/lib/navigation-validator.d.ts +31 -0
- package/dist/lib/navigation-validator.d.ts.map +1 -0
- package/dist/lib/navigation-validator.js +75 -0
- package/dist/lib/navigation-validator.js.map +1 -0
- package/dist/lib/normalize-config.d.ts +57 -0
- package/dist/lib/normalize-config.d.ts.map +1 -0
- package/dist/lib/normalize-config.js +63 -0
- package/dist/lib/normalize-config.js.map +1 -0
- package/dist/lib/openapi/cache.d.ts +40 -0
- package/dist/lib/openapi/cache.d.ts.map +1 -0
- package/dist/lib/openapi/cache.js +76 -0
- package/dist/lib/openapi/cache.js.map +1 -0
- package/dist/lib/openapi/errors.d.ts +36 -0
- package/dist/lib/openapi/errors.d.ts.map +1 -0
- package/dist/lib/openapi/errors.js +162 -0
- package/dist/lib/openapi/errors.js.map +1 -0
- package/dist/lib/openapi/index.d.ts +10 -0
- package/dist/lib/openapi/index.d.ts.map +1 -0
- package/dist/lib/openapi/index.js +12 -0
- package/dist/lib/openapi/index.js.map +1 -0
- package/dist/lib/openapi/types.d.ts +198 -0
- package/dist/lib/openapi/types.d.ts.map +1 -0
- package/dist/lib/openapi/types.js +8 -0
- package/dist/lib/openapi/types.js.map +1 -0
- package/dist/lib/openapi/validator.d.ts +45 -0
- package/dist/lib/openapi/validator.d.ts.map +1 -0
- package/dist/lib/openapi/validator.js +128 -0
- package/dist/lib/openapi/validator.js.map +1 -0
- package/dist/lib/openapi.d.ts +7 -0
- package/dist/lib/openapi.d.ts.map +1 -0
- package/dist/lib/openapi.js +7 -0
- package/dist/lib/openapi.js.map +1 -0
- package/dist/lib/output.d.ts +14 -0
- package/dist/lib/output.d.ts.map +1 -0
- package/dist/lib/output.js +19 -0
- package/dist/lib/output.js.map +1 -0
- package/dist/lib/path-security.d.ts +23 -0
- package/dist/lib/path-security.d.ts.map +1 -0
- package/dist/lib/path-security.js +35 -0
- package/dist/lib/path-security.js.map +1 -0
- package/dist/lib/port.d.ts +18 -0
- package/dist/lib/port.d.ts.map +1 -0
- package/dist/lib/port.js +65 -0
- package/dist/lib/port.js.map +1 -0
- package/dist/lib/spinner.d.ts +4 -0
- package/dist/lib/spinner.d.ts.map +1 -0
- package/dist/lib/spinner.js +16 -0
- package/dist/lib/spinner.js.map +1 -0
- package/dist/lib/version.d.ts +2 -0
- package/dist/lib/version.d.ts.map +1 -0
- package/dist/lib/version.js +49 -0
- package/dist/lib/version.js.map +1 -0
- package/dist/utils/update-checker.d.ts +34 -0
- package/dist/utils/update-checker.d.ts.map +1 -0
- package/dist/utils/update-checker.js +142 -0
- package/dist/utils/update-checker.js.map +1 -0
- package/package.json +125 -0
- package/templates/docs.json +11 -0
- package/templates/introduction.mdx +19 -0
- package/templates/quickstart.mdx +20 -0
- package/vendored/app/[[...slug]]/error.tsx +103 -0
- package/vendored/app/[[...slug]]/page.tsx +690 -0
- package/vendored/app/api/assets/[...path]/route.ts +78 -0
- package/vendored/app/api/ev/route.ts +61 -0
- package/vendored/app/api/isr-health/route.ts +66 -0
- package/vendored/app/api/mcp/[project]/route.ts +435 -0
- package/vendored/app/api/og/route.tsx +167 -0
- package/vendored/app/api/r2/[project]/[...path]/route.ts +214 -0
- package/vendored/app/api/revalidate/route.ts +76 -0
- package/vendored/app/globals.css +37 -0
- package/vendored/app/layout.tsx +571 -0
- package/vendored/app/not-found.tsx +47 -0
- package/vendored/components/CodeBlockCopyButton.tsx +146 -0
- package/vendored/components/HeaderLinkCopy.tsx +135 -0
- package/vendored/components/errors/NotFoundContent.tsx +147 -0
- package/vendored/components/layout/LayoutWrapper.tsx +128 -0
- package/vendored/components/mdx/Accordion.tsx +91 -0
- package/vendored/components/mdx/ApiCodePanel.tsx +51 -0
- package/vendored/components/mdx/ApiEndpoint.tsx +104 -0
- package/vendored/components/mdx/ApiPage.tsx +379 -0
- package/vendored/components/mdx/Badge.tsx +169 -0
- package/vendored/components/mdx/Callouts.tsx +140 -0
- package/vendored/components/mdx/Card.tsx +214 -0
- package/vendored/components/mdx/CodeGroup.tsx +136 -0
- package/vendored/components/mdx/Color.tsx +244 -0
- package/vendored/components/mdx/Columns.tsx +37 -0
- package/vendored/components/mdx/Expandable.tsx +37 -0
- package/vendored/components/mdx/Frame.tsx +51 -0
- package/vendored/components/mdx/Icon.tsx +132 -0
- package/vendored/components/mdx/Latex.tsx +75 -0
- package/vendored/components/mdx/MDXComponents.tsx +414 -0
- package/vendored/components/mdx/Mermaid.tsx +35 -0
- package/vendored/components/mdx/MermaidInner.tsx +342 -0
- package/vendored/components/mdx/OpenApiEndpoint.tsx +971 -0
- package/vendored/components/mdx/Panel.tsx +26 -0
- package/vendored/components/mdx/PanelWrapper.tsx +100 -0
- package/vendored/components/mdx/ParamField.tsx +75 -0
- package/vendored/components/mdx/RequestExample.tsx +91 -0
- package/vendored/components/mdx/ResponseExample.tsx +145 -0
- package/vendored/components/mdx/ResponseField.tsx +109 -0
- package/vendored/components/mdx/Steps.tsx +173 -0
- package/vendored/components/mdx/Table.tsx +352 -0
- package/vendored/components/mdx/Tabs.tsx +147 -0
- package/vendored/components/mdx/Tile.tsx +127 -0
- package/vendored/components/mdx/Tooltip.tsx +111 -0
- package/vendored/components/mdx/Tree.tsx +484 -0
- package/vendored/components/mdx/Update.tsx +90 -0
- package/vendored/components/mdx/View.tsx +354 -0
- package/vendored/components/mdx/YouTube.tsx +35 -0
- package/vendored/components/mdx/ZoomableImage.tsx +83 -0
- package/vendored/components/navigation/Breadcrumb.tsx +241 -0
- package/vendored/components/navigation/DefaultLogo.tsx +81 -0
- package/vendored/components/navigation/Header.tsx +512 -0
- package/vendored/components/navigation/LanguageSelector.tsx +249 -0
- package/vendored/components/navigation/PageNavigation.tsx +174 -0
- package/vendored/components/navigation/Sidebar.tsx +713 -0
- package/vendored/components/navigation/SocialFooter.tsx +186 -0
- package/vendored/components/navigation/TableOfContents.tsx +435 -0
- package/vendored/components/navigation/TabsNav.tsx +182 -0
- package/vendored/components/search/LazySearchModal.tsx +19 -0
- package/vendored/components/search/SearchModal.tsx +573 -0
- package/vendored/components/snippets/ProjectSnippets.tsx +4 -0
- package/vendored/components/theme/ThemeProvider.tsx +31 -0
- package/vendored/components/theme/ThemeToggle.tsx +134 -0
- package/vendored/components/ui/CodePanel.tsx +517 -0
- package/vendored/components/ui/CodePanelModal.tsx +342 -0
- package/vendored/contexts/TabSyncContext.tsx +30 -0
- package/vendored/hooks/useFocusTrap.ts +42 -0
- package/vendored/hooks/useHashNavigation.ts +39 -0
- package/vendored/hooks/useShikiHighlight.ts +101 -0
- package/vendored/lib/analytics-client.ts +77 -0
- package/vendored/lib/build/cache.ts +138 -0
- package/vendored/lib/build/error-parser.ts +690 -0
- package/vendored/lib/build/estimation.ts +113 -0
- package/vendored/lib/build/index.ts +17 -0
- package/vendored/lib/build/page-file-map.ts +48 -0
- package/vendored/lib/build/r2-upload.ts +179 -0
- package/vendored/lib/cache-keys.ts +117 -0
- package/vendored/lib/code-utils.ts +42 -0
- package/vendored/lib/content-loader.ts +176 -0
- package/vendored/lib/deprecated-components.ts +185 -0
- package/vendored/lib/docs-isr.ts +180 -0
- package/vendored/lib/docs-types.ts +874 -0
- package/vendored/lib/docs.ts +203 -0
- package/vendored/lib/domain-helpers.ts +107 -0
- package/vendored/lib/email-notifier.ts +102 -0
- package/vendored/lib/email-templates/build-failure.tsx +193 -0
- package/vendored/lib/email-templates/components/base-layout.tsx +150 -0
- package/vendored/lib/email-templates/components/error-box.tsx +88 -0
- package/vendored/lib/email-templates/components/info-row.tsx +63 -0
- package/vendored/lib/email-templates/index.ts +13 -0
- package/vendored/lib/empty-polyfill.js +3 -0
- package/vendored/lib/extract-highlights.ts +124 -0
- package/vendored/lib/fonts.ts +227 -0
- package/vendored/lib/frontmatter-utils.ts +77 -0
- package/vendored/lib/fs-utils.ts +20 -0
- package/vendored/lib/git-utils.ts +87 -0
- package/vendored/lib/health-checks.ts +224 -0
- package/vendored/lib/icon-utils.ts +492 -0
- package/vendored/lib/infer-page-type.ts +14 -0
- package/vendored/lib/isr-build-executor.ts +185 -0
- package/vendored/lib/language-icons.ts +152 -0
- package/vendored/lib/language-utils.ts +338 -0
- package/vendored/lib/latex-config.ts +64 -0
- package/vendored/lib/link-prefix-context.tsx +32 -0
- package/vendored/lib/logger.ts +63 -0
- package/vendored/lib/mcp-search.ts +255 -0
- package/vendored/lib/mdx-inline-components.ts +155 -0
- package/vendored/lib/mdx.ts +100 -0
- package/vendored/lib/middleware-helpers.ts +519 -0
- package/vendored/lib/navigation-resolver.ts +621 -0
- package/vendored/lib/navigation-utils.ts +103 -0
- package/vendored/lib/normalize-config.ts +94 -0
- package/vendored/lib/openapi/cache.ts +92 -0
- package/vendored/lib/openapi/code-examples.ts +389 -0
- package/vendored/lib/openapi/errors.ts +253 -0
- package/vendored/lib/openapi/generator.ts +230 -0
- package/vendored/lib/openapi/index.ts +84 -0
- package/vendored/lib/openapi/parser.ts +474 -0
- package/vendored/lib/openapi/types.ts +232 -0
- package/vendored/lib/openapi/validator.ts +156 -0
- package/vendored/lib/openapi-isr.ts +121 -0
- package/vendored/lib/page-isr-helpers.ts +137 -0
- package/vendored/lib/path-safety.ts +130 -0
- package/vendored/lib/paths.ts +35 -0
- package/vendored/lib/preprocess-mdx.ts +951 -0
- package/vendored/lib/process-mdx-with-exports.ts +75 -0
- package/vendored/lib/project-resolver.ts +165 -0
- package/vendored/lib/r2-content.ts +60 -0
- package/vendored/lib/r2-manifest.ts +84 -0
- package/vendored/lib/recent-searches.ts +41 -0
- package/vendored/lib/recma-compound-components.ts +84 -0
- package/vendored/lib/redirect-compiler.ts +160 -0
- package/vendored/lib/redirect-matcher.ts +296 -0
- package/vendored/lib/redis.ts +23 -0
- package/vendored/lib/rehype-class-to-classname.ts +31 -0
- package/vendored/lib/rehype-code-meta.ts +275 -0
- package/vendored/lib/rehype-nozoom-to-data.ts +45 -0
- package/vendored/lib/remark-extract-exports.ts +104 -0
- package/vendored/lib/resilience.ts +260 -0
- package/vendored/lib/revalidation-helpers.ts +200 -0
- package/vendored/lib/revalidation-trigger.ts +150 -0
- package/vendored/lib/screenshot-capture.ts +229 -0
- package/vendored/lib/search-client.ts +91 -0
- package/vendored/lib/search-suggestions.ts +38 -0
- package/vendored/lib/search.ts +158 -0
- package/vendored/lib/seo.ts +264 -0
- package/vendored/lib/shiki-client.ts +131 -0
- package/vendored/lib/shiki-config.ts +289 -0
- package/vendored/lib/shiki-css-theme.ts +46 -0
- package/vendored/lib/shiki-highlighter.ts +62 -0
- package/vendored/lib/shiki-transformers.ts +337 -0
- package/vendored/lib/slack-notifier.ts +248 -0
- package/vendored/lib/snippet-compiler-isr.ts +114 -0
- package/vendored/lib/snippet-loader-isr.ts +276 -0
- package/vendored/lib/static-artifacts.ts +375 -0
- package/vendored/lib/static-file-route.ts +72 -0
- package/vendored/lib/tracking-script.ts +19 -0
- package/vendored/lib/typography-config.ts +42 -0
- package/vendored/lib/validate-config.ts +268 -0
- package/vendored/next.config.js +45 -0
- package/vendored/postcss.config.js +6 -0
- package/vendored/schema/README.md +28 -0
- package/vendored/schema/docs-schema.json +4631 -0
- package/vendored/scripts/build-project.cjs +174 -0
- package/vendored/scripts/build-search-index.cjs +347 -0
- package/vendored/scripts/compile-snippets.cjs +488 -0
- package/vendored/scripts/copy-files.cjs +295 -0
- package/vendored/scripts/dev-project.cjs +534 -0
- package/vendored/scripts/enhance-navigation.cjs +354 -0
- package/vendored/scripts/validate-links.cjs +423 -0
- package/vendored/shared/constants.ts +6 -0
- package/vendored/shared/index.ts +19 -0
- package/vendored/shared/logger.ts +62 -0
- package/vendored/shared/memory-monitor.ts +190 -0
- package/vendored/shared/navigation-validator.ts +101 -0
- package/vendored/shared/path-security.ts +39 -0
- package/vendored/shared/status-reporter.ts +199 -0
- package/vendored/shared/timer.ts +51 -0
- package/vendored/shared/types.ts +102 -0
- package/vendored/tailwind.config.ts +39 -0
- package/vendored/themes/base.css +1311 -0
- package/vendored/themes/index.ts +119 -0
- package/vendored/themes/jam/variables.css +835 -0
- package/vendored/themes/nebula/variables.css +282 -0
- package/vendored/themes/pulsar/variables.css +1009 -0
- package/vendored/themes/types.ts +89 -0
- package/vendored/tsconfig.json +48 -0
|
@@ -0,0 +1,713 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useState, useEffect, useMemo, useRef, useCallback } from 'react';
|
|
4
|
+
import Link from 'next/link';
|
|
5
|
+
import Image from 'next/image';
|
|
6
|
+
import { usePathname, useRouter } from 'next/navigation';
|
|
7
|
+
// Icons use Font Awesome CSS classes for lightweight rendering
|
|
8
|
+
import type {
|
|
9
|
+
DocsConfig,
|
|
10
|
+
NavigationPage,
|
|
11
|
+
NavigationConfig,
|
|
12
|
+
AnchorConfig,
|
|
13
|
+
TabConfig,
|
|
14
|
+
GroupConfig,
|
|
15
|
+
} from '@/lib/docs-types';
|
|
16
|
+
import {
|
|
17
|
+
normalizeNavPage,
|
|
18
|
+
normalizeLogo,
|
|
19
|
+
getIconName,
|
|
20
|
+
} from '@/lib/docs-types';
|
|
21
|
+
import type { TabsPosition } from '@/lib/docs-types';
|
|
22
|
+
import { resolveNavigation, type ResolvedGroup, type ResolvedPage, type ResolvedAnchor, type ResolvedTab, type ResolvedExternalAnchor, type ResolvedNavItem } from '@/lib/navigation-resolver';
|
|
23
|
+
import type { LayoutVariant } from '@/themes';
|
|
24
|
+
import { getTheme } from '@/themes';
|
|
25
|
+
import { DefaultLogo, DefaultLogoCompact } from './DefaultLogo';
|
|
26
|
+
import { LanguageSelector } from './LanguageSelector';
|
|
27
|
+
import { LazySearchModal as SearchModal } from '@/components/search/LazySearchModal';
|
|
28
|
+
import { ThemeToggle, ThemeToggleCycle } from '@/components/theme/ThemeToggle';
|
|
29
|
+
import { getIconClass } from '@/lib/icon-utils';
|
|
30
|
+
import { getTabsFromConfig } from '@/lib/navigation-utils';
|
|
31
|
+
import { useLinkPrefix } from '@/lib/link-prefix-context';
|
|
32
|
+
|
|
33
|
+
interface SidebarProps {
|
|
34
|
+
config: DocsConfig;
|
|
35
|
+
layout?: LayoutVariant;
|
|
36
|
+
tabsPosition?: TabsPosition;
|
|
37
|
+
isOpen?: boolean;
|
|
38
|
+
onClose?: () => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// HTTP method badge colors
|
|
42
|
+
const methodColors: Record<string, { bg: string; text: string }> = {
|
|
43
|
+
GET: { bg: 'bg-emerald-500/15', text: 'text-emerald-600 dark:text-emerald-400' },
|
|
44
|
+
POST: { bg: 'bg-blue-500/15', text: 'text-blue-600 dark:text-blue-400' },
|
|
45
|
+
PUT: { bg: 'bg-amber-500/15', text: 'text-amber-600 dark:text-amber-400' },
|
|
46
|
+
PATCH: { bg: 'bg-orange-500/15', text: 'text-orange-600 dark:text-orange-400' },
|
|
47
|
+
DELETE: { bg: 'bg-red-500/15', text: 'text-red-600 dark:text-red-400' },
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Memoized navigation page component to prevent re-renders when only other pages change
|
|
51
|
+
interface NavPageProps {
|
|
52
|
+
page: ResolvedPage;
|
|
53
|
+
pathname: string | null;
|
|
54
|
+
layout: LayoutVariant;
|
|
55
|
+
onPrefetch: (path: string) => void;
|
|
56
|
+
linkPrefix?: string; // e.g., '/docs' when hostAtDocs is true
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const NavPage = React.memo(function NavPage({ page, pathname, layout, onPrefetch, linkPrefix = '' }: NavPageProps) {
|
|
60
|
+
const href = `${linkPrefix}/${page.path}`;
|
|
61
|
+
const isActive = pathname === href;
|
|
62
|
+
const colors = page.method ? methodColors[page.method] : null;
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<li>
|
|
66
|
+
<Link
|
|
67
|
+
href={href}
|
|
68
|
+
prefetch={false}
|
|
69
|
+
onMouseEnter={() => onPrefetch(page.path)}
|
|
70
|
+
className={`flex items-center gap-2 ${layout === 'header-logo' ? 'pr-3' : 'px-3'} py-1.5 rounded-lg text-sm transition-colors ${
|
|
71
|
+
isActive
|
|
72
|
+
? 'text-[var(--color-primary)] nav-active'
|
|
73
|
+
: 'text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)]'
|
|
74
|
+
}`}
|
|
75
|
+
>
|
|
76
|
+
{page.method && colors && (
|
|
77
|
+
<span className={`w-12 text-center px-1.5 py-0.5 text-[10px] font-bold rounded flex-shrink-0 ${colors.bg} ${colors.text}`}>
|
|
78
|
+
{page.method === 'DELETE' ? 'DEL' : page.method}
|
|
79
|
+
</span>
|
|
80
|
+
)}
|
|
81
|
+
{page.icon && !page.method && (
|
|
82
|
+
<i className={`${getIconClass(page.icon)} text-[14px] flex-shrink-0 opacity-60`} aria-hidden="true" />
|
|
83
|
+
)}
|
|
84
|
+
<span className="flex-1 min-w-0">{page.title}</span>
|
|
85
|
+
{page.tag && (
|
|
86
|
+
<span className="px-1.5 py-0.5 text-[10px] font-semibold rounded bg-[var(--color-accent)]/15 text-[var(--color-accent)]">
|
|
87
|
+
{page.tag}
|
|
88
|
+
</span>
|
|
89
|
+
)}
|
|
90
|
+
</Link>
|
|
91
|
+
</li>
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
/** Find all groups in the path from root to the page containing currentPath */
|
|
96
|
+
function findGroupsContainingPath(groups: ResolvedGroup[], currentPath: string, parentNames: string[] = []): Set<string> {
|
|
97
|
+
const result = new Set<string>();
|
|
98
|
+
for (const group of groups) {
|
|
99
|
+
const groupPath = group.name ? [...parentNames, group.name] : parentNames;
|
|
100
|
+
|
|
101
|
+
if (group.pages.some(page => page.path === currentPath)) {
|
|
102
|
+
groupPath.forEach(name => result.add(name));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (group.nested) {
|
|
106
|
+
const nestedResults = findGroupsContainingPath(group.nested, currentPath, groupPath);
|
|
107
|
+
nestedResults.forEach(name => result.add(name));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function Sidebar({ config, layout = 'header-logo', tabsPosition: tabsPositionProp, isOpen = false, onClose }: SidebarProps) {
|
|
114
|
+
const pathname = usePathname();
|
|
115
|
+
const router = useRouter();
|
|
116
|
+
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(new Set());
|
|
117
|
+
const [logoError, setLogoError] = useState(false);
|
|
118
|
+
const [isDark, setIsDark] = useState(false);
|
|
119
|
+
const [isSearchOpen, setIsSearchOpen] = useState(false);
|
|
120
|
+
const sidebarRef = useRef<HTMLElement>(null);
|
|
121
|
+
const linkPrefix = useLinkPrefix();
|
|
122
|
+
|
|
123
|
+
// Stable callback for prefetching pages on hover
|
|
124
|
+
const handlePrefetch = useCallback((path: string) => {
|
|
125
|
+
router.prefetch(`${linkPrefix}/${path}`);
|
|
126
|
+
}, [router, linkPrefix]);
|
|
127
|
+
|
|
128
|
+
// Determine effective tabsPosition:
|
|
129
|
+
// 1. Use config.tabsPosition if specified
|
|
130
|
+
// 2. Otherwise use prop if specified
|
|
131
|
+
// 3. Otherwise use theme default
|
|
132
|
+
const themeConfig = getTheme(config.theme);
|
|
133
|
+
const effectiveTabsPosition: TabsPosition = config.tabsPosition || tabsPositionProp || themeConfig.defaultTabsPosition;
|
|
134
|
+
const showTabsInSidebar = effectiveTabsPosition === 'left';
|
|
135
|
+
|
|
136
|
+
// In sidebar-logo layout, logo and search are in sidebar
|
|
137
|
+
const showLogoInSidebar = layout === 'sidebar-logo';
|
|
138
|
+
const showSearchInSidebar = layout === 'sidebar-logo';
|
|
139
|
+
|
|
140
|
+
// Logo configuration
|
|
141
|
+
const logoConfig = normalizeLogo(config.logo);
|
|
142
|
+
const logoHref = logoConfig?.href || '/';
|
|
143
|
+
const logoSrc = logoConfig ? (isDark && logoConfig.dark ? logoConfig.dark : logoConfig.light) : null;
|
|
144
|
+
const showLogoImage = logoSrc && !logoError;
|
|
145
|
+
|
|
146
|
+
// Resolve navigation using the new resolver
|
|
147
|
+
const resolvedNav = useMemo(() => {
|
|
148
|
+
return resolveNavigation(config, pathname || '/docs');
|
|
149
|
+
}, [config, pathname]);
|
|
150
|
+
|
|
151
|
+
// Track if this is the initial mount (for resetting expanded state on refresh)
|
|
152
|
+
const isInitialMount = useRef(true);
|
|
153
|
+
|
|
154
|
+
// Memoize the groups that should be expanded for the current page
|
|
155
|
+
const groupsForCurrentPage = useMemo(() => {
|
|
156
|
+
const currentPath = pathname?.replace(/^\/(?:docs\/)?/, '') || '';
|
|
157
|
+
return findGroupsContainingPath(resolvedNav.groups, currentPath);
|
|
158
|
+
}, [resolvedNav.groups, pathname]);
|
|
159
|
+
|
|
160
|
+
// Expand groups containing current page
|
|
161
|
+
// On initial mount: reset to only current page's groups
|
|
162
|
+
// On navigation: add current page's groups to existing expanded set (preserve manually opened)
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
if (isInitialMount.current) {
|
|
165
|
+
// Initial mount (page load/refresh): only expand current page's groups
|
|
166
|
+
isInitialMount.current = false;
|
|
167
|
+
setExpandedGroups(groupsForCurrentPage);
|
|
168
|
+
} else {
|
|
169
|
+
// Navigation: add current page's groups to existing expanded set
|
|
170
|
+
setExpandedGroups(prev => {
|
|
171
|
+
const newSet = new Set(prev);
|
|
172
|
+
groupsForCurrentPage.forEach(g => newSet.add(g));
|
|
173
|
+
return newSet;
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}, [groupsForCurrentPage]);
|
|
177
|
+
|
|
178
|
+
// Detect dark mode for logo switching
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
const checkDarkMode = () => {
|
|
181
|
+
setIsDark(document.documentElement.classList.contains('dark'));
|
|
182
|
+
};
|
|
183
|
+
checkDarkMode();
|
|
184
|
+
const observer = new MutationObserver(checkDarkMode);
|
|
185
|
+
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
|
|
186
|
+
return () => observer.disconnect();
|
|
187
|
+
}, []);
|
|
188
|
+
|
|
189
|
+
// Keyboard shortcut for search (Cmd+K / Ctrl+K) - only in sidebar-logo layout
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
if (!showSearchInSidebar) return;
|
|
192
|
+
|
|
193
|
+
const down = (e: KeyboardEvent) => {
|
|
194
|
+
if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
|
|
195
|
+
e.preventDefault();
|
|
196
|
+
setIsSearchOpen((open) => !open);
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
document.addEventListener('keydown', down);
|
|
201
|
+
return () => document.removeEventListener('keydown', down);
|
|
202
|
+
}, [showSearchInSidebar]);
|
|
203
|
+
|
|
204
|
+
// Close sidebar on route change (mobile)
|
|
205
|
+
useEffect(() => {
|
|
206
|
+
onClose?.();
|
|
207
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
208
|
+
}, [pathname]); // Intentionally excluding onClose - we only want to trigger on pathname changes
|
|
209
|
+
|
|
210
|
+
// Scroll the active navigation link into view when pathname changes
|
|
211
|
+
useEffect(() => {
|
|
212
|
+
// Double RAF ensures DOM is painted before measuring
|
|
213
|
+
let rafId = requestAnimationFrame(() => {
|
|
214
|
+
rafId = requestAnimationFrame(() => {
|
|
215
|
+
const scrollContainer = sidebarRef.current?.querySelector('.sidebar-scroll');
|
|
216
|
+
const activeLink = scrollContainer?.querySelector('.nav-active');
|
|
217
|
+
if (!scrollContainer || !activeLink) return;
|
|
218
|
+
|
|
219
|
+
const containerRect = scrollContainer.getBoundingClientRect();
|
|
220
|
+
const linkRect = activeLink.getBoundingClientRect();
|
|
221
|
+
const isVisible = linkRect.top >= containerRect.top && linkRect.bottom <= containerRect.bottom;
|
|
222
|
+
|
|
223
|
+
if (!isVisible) {
|
|
224
|
+
activeLink.scrollIntoView({ block: 'center', behavior: 'instant' });
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
return () => cancelAnimationFrame(rafId);
|
|
230
|
+
}, [pathname]);
|
|
231
|
+
|
|
232
|
+
// Prevent body scroll when sidebar is open on mobile
|
|
233
|
+
useEffect(() => {
|
|
234
|
+
if (isOpen) {
|
|
235
|
+
document.body.style.overflow = 'hidden';
|
|
236
|
+
} else {
|
|
237
|
+
document.body.style.overflow = '';
|
|
238
|
+
}
|
|
239
|
+
return () => {
|
|
240
|
+
document.body.style.overflow = '';
|
|
241
|
+
};
|
|
242
|
+
}, [isOpen]);
|
|
243
|
+
|
|
244
|
+
// Find the first page in a group (including nested groups)
|
|
245
|
+
function findFirstPageInGroup(g: ResolvedGroup): string | null {
|
|
246
|
+
if (g.pages.length > 0) return g.pages[0].path;
|
|
247
|
+
for (const nested of g.nested || []) {
|
|
248
|
+
const nestedFirst = findFirstPageInGroup(nested);
|
|
249
|
+
if (nestedFirst) return nestedFirst;
|
|
250
|
+
}
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Toggle group expansion; if expanding, navigate to first page
|
|
255
|
+
const handleGroupClick = useCallback((group: ResolvedGroup) => {
|
|
256
|
+
setExpandedGroups(prev => {
|
|
257
|
+
const newSet = new Set(prev);
|
|
258
|
+
if (prev.has(group.name)) {
|
|
259
|
+
newSet.delete(group.name);
|
|
260
|
+
} else {
|
|
261
|
+
const firstPagePath = findFirstPageInGroup(group);
|
|
262
|
+
if (firstPagePath) router.push(`${linkPrefix}/${firstPagePath}`);
|
|
263
|
+
newSet.add(group.name);
|
|
264
|
+
}
|
|
265
|
+
return newSet;
|
|
266
|
+
});
|
|
267
|
+
}, [router, linkPrefix]);
|
|
268
|
+
|
|
269
|
+
// Render a navigation group (supports nesting)
|
|
270
|
+
function renderGroup(group: ResolvedGroup, level: number = 0) {
|
|
271
|
+
const isExpanded = expandedGroups.has(group.name);
|
|
272
|
+
|
|
273
|
+
// Skip rendering if group has no name and no content
|
|
274
|
+
if (!group.name && group.pages.length === 0 && !group.nested?.length && !group.items?.length) {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return (
|
|
279
|
+
<div key={group.name || `group-${level}`} className={level === 0 ? 'ml-6' : level === 1 ? '' : 'ml-3'}>
|
|
280
|
+
{group.name && (
|
|
281
|
+
level === 0 ? (
|
|
282
|
+
<button
|
|
283
|
+
onClick={() => handleGroupClick(group)}
|
|
284
|
+
className="flex items-center justify-between w-full text-left mb-2 cursor-pointer"
|
|
285
|
+
>
|
|
286
|
+
<span className="text-sm font-semibold text-[var(--color-text-tertiary)] flex items-center gap-2">
|
|
287
|
+
{group.icon && (
|
|
288
|
+
<i className={`${getIconClass(group.icon)} text-[14px]`} aria-hidden="true" />
|
|
289
|
+
)}
|
|
290
|
+
{group.name}
|
|
291
|
+
{group.tag && (
|
|
292
|
+
<span className="px-1.5 py-0.5 text-[9px] font-semibold rounded bg-[var(--color-accent)]/15 text-[var(--color-accent)] normal-case tracking-normal">
|
|
293
|
+
{group.tag}
|
|
294
|
+
</span>
|
|
295
|
+
)}
|
|
296
|
+
</span>
|
|
297
|
+
</button>
|
|
298
|
+
) : (
|
|
299
|
+
<button
|
|
300
|
+
onClick={() => handleGroupClick(group)}
|
|
301
|
+
className={`nav-group-l1 flex items-center gap-1.5 ${layout === 'header-logo' ? 'pr-3' : 'px-3'} py-1.5 rounded-lg text-sm transition-colors w-full text-left text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)]`}
|
|
302
|
+
>
|
|
303
|
+
<span className="truncate">{group.name}</span>
|
|
304
|
+
<i
|
|
305
|
+
className={`fa-solid fa-chevron-right text-[8px] text-[var(--color-text-muted)] transition-transform ${isExpanded ? 'rotate-90' : ''}`}
|
|
306
|
+
aria-hidden="true"
|
|
307
|
+
/>
|
|
308
|
+
{group.tag && (
|
|
309
|
+
<span className="px-1.5 py-0.5 text-[10px] font-semibold rounded bg-[var(--color-accent)]/15 text-[var(--color-accent)]">
|
|
310
|
+
{group.tag}
|
|
311
|
+
</span>
|
|
312
|
+
)}
|
|
313
|
+
</button>
|
|
314
|
+
)
|
|
315
|
+
)}
|
|
316
|
+
|
|
317
|
+
{(level === 0 || isExpanded || !group.name) && (
|
|
318
|
+
<>
|
|
319
|
+
{group.items ? (
|
|
320
|
+
// Use ordered items array when available (preserves page/group ordering)
|
|
321
|
+
<div className={level === 1 ? 'ml-3' : ''}>
|
|
322
|
+
{(() => {
|
|
323
|
+
// Group consecutive pages into single ul elements for continuous border
|
|
324
|
+
const elements: React.ReactNode[] = [];
|
|
325
|
+
let currentPages: typeof group.items = [];
|
|
326
|
+
|
|
327
|
+
const flushPages = () => {
|
|
328
|
+
if (currentPages.length > 0) {
|
|
329
|
+
elements.push(
|
|
330
|
+
<ul key={`pages-${elements.length}`}>
|
|
331
|
+
{currentPages.map((p) => p.type === 'page' && (
|
|
332
|
+
<NavPage
|
|
333
|
+
key={p.page.path}
|
|
334
|
+
page={p.page}
|
|
335
|
+
pathname={pathname}
|
|
336
|
+
layout={layout}
|
|
337
|
+
onPrefetch={handlePrefetch}
|
|
338
|
+
linkPrefix={linkPrefix}
|
|
339
|
+
/>
|
|
340
|
+
))}
|
|
341
|
+
</ul>
|
|
342
|
+
);
|
|
343
|
+
currentPages = [];
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
group.items.forEach((item, idx) => {
|
|
348
|
+
if (item.type === 'page') {
|
|
349
|
+
currentPages.push(item);
|
|
350
|
+
} else {
|
|
351
|
+
flushPages();
|
|
352
|
+
elements.push(
|
|
353
|
+
<div key={item.group.name || `nested-${idx}`} className={level === 0 ? '' : level === 1 ? '' : 'mt-4'}>
|
|
354
|
+
{renderGroup(item.group, level + 1)}
|
|
355
|
+
</div>
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
flushPages();
|
|
360
|
+
|
|
361
|
+
return elements;
|
|
362
|
+
})()}
|
|
363
|
+
</div>
|
|
364
|
+
) : (
|
|
365
|
+
// Fallback to legacy pages + nested (for backwards compatibility)
|
|
366
|
+
<>
|
|
367
|
+
{group.pages.length > 0 && (
|
|
368
|
+
<ul className={level === 1 ? 'ml-3' : ''}>
|
|
369
|
+
{group.pages.map((page) => (
|
|
370
|
+
<NavPage
|
|
371
|
+
key={page.path}
|
|
372
|
+
page={page}
|
|
373
|
+
pathname={pathname}
|
|
374
|
+
layout={layout}
|
|
375
|
+
onPrefetch={handlePrefetch}
|
|
376
|
+
linkPrefix={linkPrefix}
|
|
377
|
+
/>
|
|
378
|
+
))}
|
|
379
|
+
</ul>
|
|
380
|
+
)}
|
|
381
|
+
{group.nested && group.nested.length > 0 && (
|
|
382
|
+
<div className={level === 0 ? '' : level === 1 ? 'ml-3' : 'mt-4'}>
|
|
383
|
+
{group.nested.map((nested) => renderGroup(nested, level + 1))}
|
|
384
|
+
</div>
|
|
385
|
+
)}
|
|
386
|
+
</>
|
|
387
|
+
)}
|
|
388
|
+
</>
|
|
389
|
+
)}
|
|
390
|
+
</div>
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Render anchors (deprecated - for backwards compatibility)
|
|
395
|
+
function renderAnchors() {
|
|
396
|
+
if (resolvedNav.anchors.length === 0) return null;
|
|
397
|
+
|
|
398
|
+
return (
|
|
399
|
+
<div className="flex flex-col gap-1 mb-6">
|
|
400
|
+
{resolvedNav.anchors.map((anchor) => {
|
|
401
|
+
const isCurrentAnchor = anchor.name === resolvedNav.activeAnchor;
|
|
402
|
+
|
|
403
|
+
if (anchor.isExternal && anchor.href) {
|
|
404
|
+
return (
|
|
405
|
+
<a
|
|
406
|
+
key={anchor.name}
|
|
407
|
+
href={anchor.href}
|
|
408
|
+
target="_blank"
|
|
409
|
+
rel="noopener noreferrer"
|
|
410
|
+
data-nav-anchor
|
|
411
|
+
className="flex items-center gap-2.5 ml-6 pr-3 py-1.5 text-sm font-medium transition-colors text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)]"
|
|
412
|
+
>
|
|
413
|
+
<i className={`${getIconClass(anchor.icon)} text-[14px]`} aria-hidden="true" />
|
|
414
|
+
<span className="flex-1">{anchor.name}</span>
|
|
415
|
+
<i className="fa-solid fa-arrow-up-right-from-square text-[14px] opacity-50" aria-hidden="true" />
|
|
416
|
+
</a>
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Find the first page in this anchor's groups
|
|
421
|
+
const findFirstPage = (groups: ResolvedGroup[]): string | null => {
|
|
422
|
+
for (const group of groups) {
|
|
423
|
+
if (group.pages.length > 0) {
|
|
424
|
+
return group.pages[0].path;
|
|
425
|
+
}
|
|
426
|
+
if (group.nested) {
|
|
427
|
+
const nestedFirst = findFirstPage(group.nested);
|
|
428
|
+
if (nestedFirst) return nestedFirst;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return null;
|
|
432
|
+
};
|
|
433
|
+
const firstPagePath = findFirstPage(anchor.groups);
|
|
434
|
+
|
|
435
|
+
return (
|
|
436
|
+
<button
|
|
437
|
+
key={anchor.name}
|
|
438
|
+
data-nav-anchor
|
|
439
|
+
data-active={isCurrentAnchor ? 'true' : 'false'}
|
|
440
|
+
onClick={() => {
|
|
441
|
+
if (firstPagePath) {
|
|
442
|
+
router.push(`${linkPrefix}/${firstPagePath}`);
|
|
443
|
+
}
|
|
444
|
+
}}
|
|
445
|
+
className={`flex items-center gap-2.5 ml-6 pr-3 py-1.5 text-sm font-medium transition-colors cursor-pointer ${
|
|
446
|
+
isCurrentAnchor
|
|
447
|
+
? 'text-[var(--color-primary)]'
|
|
448
|
+
: 'text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)]'
|
|
449
|
+
}`}
|
|
450
|
+
>
|
|
451
|
+
<i className={`${getIconClass(anchor.icon)} text-[14px] ${isCurrentAnchor ? 'text-[var(--color-primary)]' : ''}`} aria-hidden="true" />
|
|
452
|
+
{anchor.name}
|
|
453
|
+
</button>
|
|
454
|
+
);
|
|
455
|
+
})}
|
|
456
|
+
</div>
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Render external anchors (top-level config.anchors)
|
|
461
|
+
function renderExternalAnchors() {
|
|
462
|
+
if (resolvedNav.externalAnchors.length === 0) return null;
|
|
463
|
+
|
|
464
|
+
return (
|
|
465
|
+
<div className="flex flex-col gap-1 px-4 pt-4">
|
|
466
|
+
{resolvedNav.externalAnchors.map((anchor) => (
|
|
467
|
+
<a
|
|
468
|
+
key={anchor.name}
|
|
469
|
+
href={anchor.href}
|
|
470
|
+
target="_blank"
|
|
471
|
+
rel="noopener noreferrer"
|
|
472
|
+
className="flex items-center gap-2.5 px-2 py-1.5 text-sm font-medium transition-colors text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-bg-secondary)] rounded-lg"
|
|
473
|
+
>
|
|
474
|
+
{anchor.icon && (
|
|
475
|
+
<i className={`${getIconClass(anchor.icon)} text-[14px]`} aria-hidden="true" />
|
|
476
|
+
)}
|
|
477
|
+
<span className="flex-1">{anchor.name}</span>
|
|
478
|
+
<i className="fa-solid fa-arrow-up-right-from-square text-[10px] opacity-50" aria-hidden="true" />
|
|
479
|
+
</a>
|
|
480
|
+
))}
|
|
481
|
+
</div>
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Find first page in a tab's groups
|
|
486
|
+
function findFirstPageInTab(tab: ResolvedTab): string | null {
|
|
487
|
+
// Find the corresponding tab config to get its groups (language-aware)
|
|
488
|
+
const tabs = getTabsFromConfig(config, pathname || undefined);
|
|
489
|
+
const tabConfig = tabs.find(t => t.tab === tab.name);
|
|
490
|
+
if (!tabConfig) return null;
|
|
491
|
+
|
|
492
|
+
// Check groups in tab
|
|
493
|
+
if (tabConfig.groups) {
|
|
494
|
+
for (const group of tabConfig.groups) {
|
|
495
|
+
if (group.pages && group.pages.length > 0) {
|
|
496
|
+
const firstPage = group.pages[0];
|
|
497
|
+
if (typeof firstPage === 'string') return firstPage;
|
|
498
|
+
if ('page' in firstPage) return firstPage.page;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Check direct pages in tab
|
|
504
|
+
if (tabConfig.pages && tabConfig.pages.length > 0) {
|
|
505
|
+
const firstPage = tabConfig.pages[0];
|
|
506
|
+
if (typeof firstPage === 'string') return firstPage;
|
|
507
|
+
if ('page' in firstPage) return (firstPage as { page: string }).page;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return null;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Render sidebar tabs (always on mobile, desktop only when tabsPosition === 'left')
|
|
514
|
+
function renderSidebarTabs() {
|
|
515
|
+
if (resolvedNav.tabs.length <= 1) return null;
|
|
516
|
+
|
|
517
|
+
// On mobile: always show tabs in sidebar
|
|
518
|
+
// On desktop: only show if tabsPosition is 'left' (showTabsInSidebar)
|
|
519
|
+
const visibilityClass = showTabsInSidebar ? '' : 'lg:hidden';
|
|
520
|
+
|
|
521
|
+
return (
|
|
522
|
+
<div className={`sidebar-tabs flex flex-col gap-2 px-4 pt-2 pb-6 ${visibilityClass}`}>
|
|
523
|
+
{resolvedNav.tabs.map((tab) => {
|
|
524
|
+
const isActive = tab.name === resolvedNav.activeTab;
|
|
525
|
+
|
|
526
|
+
// External tab link
|
|
527
|
+
if (tab.isExternal && tab.href) {
|
|
528
|
+
return (
|
|
529
|
+
<a
|
|
530
|
+
key={tab.name}
|
|
531
|
+
href={tab.href}
|
|
532
|
+
target="_blank"
|
|
533
|
+
rel="noopener noreferrer"
|
|
534
|
+
className="nav-tab-link flex items-center gap-2 px-3 py-1.5 text-sm font-medium transition-colors text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-bg-tertiary)]"
|
|
535
|
+
>
|
|
536
|
+
{tab.icon && (
|
|
537
|
+
<i className={`${getIconClass(tab.icon)} text-[14px]`} aria-hidden="true" />
|
|
538
|
+
)}
|
|
539
|
+
<span className="flex-1">{tab.name}</span>
|
|
540
|
+
<i className="fa-solid fa-arrow-up-right-from-square text-[10px] opacity-50" aria-hidden="true" />
|
|
541
|
+
</a>
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Internal tab - navigate to first page
|
|
546
|
+
const firstPagePath = findFirstPageInTab(tab);
|
|
547
|
+
|
|
548
|
+
return (
|
|
549
|
+
<button
|
|
550
|
+
key={tab.name}
|
|
551
|
+
onClick={() => {
|
|
552
|
+
if (firstPagePath) {
|
|
553
|
+
router.push(`${linkPrefix}/${firstPagePath}`);
|
|
554
|
+
}
|
|
555
|
+
}}
|
|
556
|
+
className={`nav-tab-link flex items-center gap-2 px-3 py-1.5 text-sm transition-colors cursor-pointer ${
|
|
557
|
+
isActive
|
|
558
|
+
? 'text-[var(--color-primary)] font-semibold bg-[var(--color-bg-tertiary)]'
|
|
559
|
+
: 'text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-bg-tertiary)]'
|
|
560
|
+
}`}
|
|
561
|
+
>
|
|
562
|
+
{tab.icon && (
|
|
563
|
+
<i className={`${getIconClass(tab.icon)} text-[14px]`} aria-hidden="true" />
|
|
564
|
+
)}
|
|
565
|
+
<span>{tab.name}</span>
|
|
566
|
+
</button>
|
|
567
|
+
);
|
|
568
|
+
})}
|
|
569
|
+
</div>
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const showTopDivider = resolvedNav.externalAnchors.length > 0;
|
|
574
|
+
const hasTopTabsBar = getTabsFromConfig(config, pathname || undefined).length > 1 && !showTabsInSidebar;
|
|
575
|
+
const desktopSidebarClasses = layout === 'sidebar-logo'
|
|
576
|
+
? 'lg:fixed lg:top-0 lg:left-0 lg:bottom-0 lg:z-40'
|
|
577
|
+
: 'lg:sticky lg:self-start lg:z-30 lg:flex-shrink-0';
|
|
578
|
+
|
|
579
|
+
return (
|
|
580
|
+
<>
|
|
581
|
+
{/* Mobile overlay - z-50 to cover header (z-40) */}
|
|
582
|
+
{isOpen && (
|
|
583
|
+
<div
|
|
584
|
+
className="fixed inset-0 z-50 bg-black/50 lg:hidden"
|
|
585
|
+
onClick={onClose}
|
|
586
|
+
/>
|
|
587
|
+
)}
|
|
588
|
+
|
|
589
|
+
{/* Sidebar */}
|
|
590
|
+
<aside
|
|
591
|
+
ref={sidebarRef}
|
|
592
|
+
className={`
|
|
593
|
+
w-[283px] lg:w-[295px] flex flex-col
|
|
594
|
+
${layout === 'sidebar-logo' ? 'bg-[var(--color-bg-sidebar,var(--color-bg-primary))]' : 'bg-[var(--color-bg-primary)]'}
|
|
595
|
+
border-r border-[var(--color-border)]
|
|
596
|
+
transition-transform duration-300 ease-in-out
|
|
597
|
+
fixed top-0 left-0 bottom-0 z-[60]
|
|
598
|
+
${desktopSidebarClasses}
|
|
599
|
+
${isOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'}
|
|
600
|
+
`}
|
|
601
|
+
>
|
|
602
|
+
{/* Mobile header with logo, theme toggle, and close button - fixed at top */}
|
|
603
|
+
<div className="flex-shrink-0 flex items-center justify-between p-4 border-b border-[var(--color-border)] lg:hidden bg-[var(--color-bg-primary)]">
|
|
604
|
+
<div className="flex items-center gap-1">
|
|
605
|
+
<Link href={logoHref} prefetch={false} className="flex items-center">
|
|
606
|
+
{showLogoImage ? (
|
|
607
|
+
<div className="flex items-center gap-2">
|
|
608
|
+
<Image
|
|
609
|
+
src={logoSrc}
|
|
610
|
+
alt={`${config.name} logo`}
|
|
611
|
+
width={100}
|
|
612
|
+
height={28}
|
|
613
|
+
className="h-7 w-auto max-h-7 max-w-[170px] object-contain"
|
|
614
|
+
onError={() => setLogoError(true)}
|
|
615
|
+
/>
|
|
616
|
+
</div>
|
|
617
|
+
) : (
|
|
618
|
+
<DefaultLogoCompact name={config.name} />
|
|
619
|
+
)}
|
|
620
|
+
</Link>
|
|
621
|
+
<ThemeToggleCycle />
|
|
622
|
+
</div>
|
|
623
|
+
<button
|
|
624
|
+
onClick={onClose}
|
|
625
|
+
className="p-1.5 text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-bg-secondary)] rounded-lg transition-colors"
|
|
626
|
+
aria-label="Close sidebar"
|
|
627
|
+
>
|
|
628
|
+
<i className="fa-solid fa-xmark text-[18px]" aria-hidden="true" />
|
|
629
|
+
</button>
|
|
630
|
+
</div>
|
|
631
|
+
|
|
632
|
+
{/* Scrollable content area - everything below mobile header scrolls */}
|
|
633
|
+
<div className="flex-1 overflow-y-auto sidebar-scroll" data-has-tabs={hasTopTabsBar ? 'true' : 'false'}>
|
|
634
|
+
|
|
635
|
+
{/* Logo in sidebar - for sidebar-logo layout on desktop */}
|
|
636
|
+
{showLogoInSidebar && (
|
|
637
|
+
<div className="hidden lg:flex items-center justify-between p-4">
|
|
638
|
+
<Link href={logoHref} prefetch={false} className="flex items-center">
|
|
639
|
+
{showLogoImage ? (
|
|
640
|
+
<div className="flex items-center gap-2">
|
|
641
|
+
<Image
|
|
642
|
+
src={logoSrc}
|
|
643
|
+
alt={`${config.name} logo`}
|
|
644
|
+
width={120}
|
|
645
|
+
height={32}
|
|
646
|
+
className="h-8 w-auto max-h-8 max-w-[170px] object-contain"
|
|
647
|
+
onError={() => setLogoError(true)}
|
|
648
|
+
/>
|
|
649
|
+
</div>
|
|
650
|
+
) : (
|
|
651
|
+
<DefaultLogo name={config.name} showBadge={false} />
|
|
652
|
+
)}
|
|
653
|
+
</Link>
|
|
654
|
+
<ThemeToggle />
|
|
655
|
+
</div>
|
|
656
|
+
)}
|
|
657
|
+
|
|
658
|
+
{/* Search button in sidebar - for sidebar-logo layout (desktop only, mobile uses header search) */}
|
|
659
|
+
{showSearchInSidebar && (
|
|
660
|
+
<div className="hidden lg:block px-4 pt-4 pb-4">
|
|
661
|
+
<button
|
|
662
|
+
onClick={() => setIsSearchOpen(true)}
|
|
663
|
+
className="w-full flex items-center gap-2.5 px-3 py-2 bg-[var(--color-bg-secondary)] border border-[var(--color-border)] rounded-lg text-[var(--color-text-muted)] text-sm hover:border-[var(--color-border-hover)] hover:bg-[var(--color-bg-tertiary)] transition-colors"
|
|
664
|
+
>
|
|
665
|
+
<i className="fa-solid fa-magnifying-glass text-[14px] flex-shrink-0" aria-hidden="true" />
|
|
666
|
+
<span className="flex-1 text-left">Search...</span>
|
|
667
|
+
<kbd className="hidden sm:inline-flex px-1.5 py-0.5 text-[10px] text-[var(--color-text-muted)] bg-[var(--color-bg-primary)] border border-[var(--color-border)] rounded items-center gap-[2px]">
|
|
668
|
+
<span className="text-xs leading-none">⌘</span>
|
|
669
|
+
<span>K</span>
|
|
670
|
+
</kbd>
|
|
671
|
+
</button>
|
|
672
|
+
</div>
|
|
673
|
+
)}
|
|
674
|
+
|
|
675
|
+
<nav className="flex-1">
|
|
676
|
+
{/* External anchors - appear at top of sidebar on all pages */}
|
|
677
|
+
{renderExternalAnchors()}
|
|
678
|
+
|
|
679
|
+
{/* Sidebar tabs - when tabsPosition === 'left' */}
|
|
680
|
+
{renderSidebarTabs()}
|
|
681
|
+
|
|
682
|
+
{/* Divider between top section and navigation groups */}
|
|
683
|
+
{showTopDivider && (
|
|
684
|
+
<div className="mx-4 my-4 border-t border-[var(--color-border)]" />
|
|
685
|
+
)}
|
|
686
|
+
|
|
687
|
+
{/* Legacy anchors (deprecated - for backwards compatibility) */}
|
|
688
|
+
{renderAnchors()}
|
|
689
|
+
|
|
690
|
+
{/* Navigation Groups */}
|
|
691
|
+
<div className="sidebar-nav-groups">
|
|
692
|
+
{resolvedNav.groups.map((group) => renderGroup(group))}
|
|
693
|
+
</div>
|
|
694
|
+
</nav>
|
|
695
|
+
|
|
696
|
+
{/* Language Selector - for sidebar-logo layout */}
|
|
697
|
+
{layout === 'sidebar-logo' && resolvedNav.languages.length > 1 && (
|
|
698
|
+
<div className="px-4 py-3 border-t border-[var(--color-border)]">
|
|
699
|
+
<LanguageSelector
|
|
700
|
+
languages={resolvedNav.languages}
|
|
701
|
+
position="up"
|
|
702
|
+
defaultLanguage={resolvedNav.languages.find((l) => l.isDefault)?.code}
|
|
703
|
+
/>
|
|
704
|
+
</div>
|
|
705
|
+
)}
|
|
706
|
+
</div>{/* End scrollable content area */}
|
|
707
|
+
</aside>
|
|
708
|
+
|
|
709
|
+
{/* Search Modal - shown when search is opened from sidebar */}
|
|
710
|
+
<SearchModal isOpen={isSearchOpen} onClose={() => setIsSearchOpen(false)} />
|
|
711
|
+
</>
|
|
712
|
+
);
|
|
713
|
+
}
|