create-zudo-doc 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/LICENSE +21 -0
- package/README.md +146 -0
- package/bin/create-zudo-doc.js +2 -0
- package/dist/api.d.ts +20 -0
- package/dist/api.js +13 -0
- package/dist/claude-md-gen.d.ts +2 -0
- package/dist/claude-md-gen.js +113 -0
- package/dist/cli.d.ts +39 -0
- package/dist/cli.js +157 -0
- package/dist/compose.d.ts +95 -0
- package/dist/compose.js +206 -0
- package/dist/constants.d.ts +20 -0
- package/dist/constants.js +224 -0
- package/dist/features/body-foot-util.d.ts +10 -0
- package/dist/features/body-foot-util.js +12 -0
- package/dist/features/claude-resources.d.ts +2 -0
- package/dist/features/claude-resources.js +6 -0
- package/dist/features/design-token-panel.d.ts +14 -0
- package/dist/features/design-token-panel.js +27 -0
- package/dist/features/doc-history.d.ts +9 -0
- package/dist/features/doc-history.js +11 -0
- package/dist/features/doc-tags.d.ts +19 -0
- package/dist/features/doc-tags.js +33 -0
- package/dist/features/footer-taglist.d.ts +14 -0
- package/dist/features/footer-taglist.js +17 -0
- package/dist/features/footer.d.ts +8 -0
- package/dist/features/footer.js +10 -0
- package/dist/features/i18n.d.ts +22 -0
- package/dist/features/i18n.js +41 -0
- package/dist/features/image-enlarge.d.ts +11 -0
- package/dist/features/image-enlarge.js +13 -0
- package/dist/features/index.d.ts +15 -0
- package/dist/features/index.js +53 -0
- package/dist/features/llms-txt.d.ts +11 -0
- package/dist/features/llms-txt.js +13 -0
- package/dist/features/search.d.ts +9 -0
- package/dist/features/search.js +11 -0
- package/dist/features/sidebar-resizer.d.ts +14 -0
- package/dist/features/sidebar-resizer.js +16 -0
- package/dist/features/sidebar-toggle.d.ts +13 -0
- package/dist/features/sidebar-toggle.js +15 -0
- package/dist/features/tag-governance.d.ts +14 -0
- package/dist/features/tag-governance.js +16 -0
- package/dist/features/tauri-dev.d.ts +2 -0
- package/dist/features/tauri-dev.js +25 -0
- package/dist/features/tauri.d.ts +11 -0
- package/dist/features/tauri.js +52 -0
- package/dist/features/versioning.d.ts +27 -0
- package/dist/features/versioning.js +43 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +150 -0
- package/dist/preset.d.ts +37 -0
- package/dist/preset.js +156 -0
- package/dist/prompts.d.ts +32 -0
- package/dist/prompts.js +248 -0
- package/dist/scaffold.d.ts +4 -0
- package/dist/scaffold.js +344 -0
- package/dist/settings-gen.d.ts +2 -0
- package/dist/settings-gen.js +237 -0
- package/dist/utils.d.ts +8 -0
- package/dist/utils.js +34 -0
- package/dist/zfb-config-gen.d.ts +19 -0
- package/dist/zfb-config-gen.js +222 -0
- package/package.json +65 -0
- package/templates/base/.htmlvalidate.json +5 -0
- package/templates/base/.zfb/doc-history-meta.json +1 -0
- package/templates/base/pages/404.tsx +55 -0
- package/templates/base/pages/_data.ts +179 -0
- package/templates/base/pages/_mdx-components.ts +249 -0
- package/templates/base/pages/docs/[...slug].tsx +448 -0
- package/templates/base/pages/index.tsx +158 -0
- package/templates/base/pages/lib/_body-end-islands.tsx +201 -0
- package/templates/base/pages/lib/_category-nav.tsx +148 -0
- package/templates/base/pages/lib/_category-tree-nav.tsx +104 -0
- package/templates/base/pages/lib/_compose-meta-title.ts +29 -0
- package/templates/base/pages/lib/_details.tsx +30 -0
- package/templates/base/pages/lib/_doc-history-area.tsx +178 -0
- package/templates/base/pages/lib/_doc-metainfo-area.tsx +100 -0
- package/templates/base/pages/lib/_doc-tags-area.tsx +89 -0
- package/templates/base/pages/lib/_extract-headings.ts +81 -0
- package/templates/base/pages/lib/_footer-with-defaults.tsx +234 -0
- package/templates/base/pages/lib/_frontmatter-preview-data.ts +53 -0
- package/templates/base/pages/lib/_head-with-defaults.tsx +113 -0
- package/templates/base/pages/lib/_header-with-defaults.tsx +386 -0
- package/templates/base/pages/lib/_inline-version-switcher.tsx +84 -0
- package/templates/base/pages/lib/_math-block.tsx +63 -0
- package/templates/base/pages/lib/_nav-source-docs.ts +68 -0
- package/templates/base/pages/lib/_preset-generator.tsx +81 -0
- package/templates/base/pages/lib/_search-widget-script.ts +388 -0
- package/templates/base/pages/lib/_search-widget.tsx +196 -0
- package/templates/base/pages/lib/_sidebar-with-defaults.tsx +176 -0
- package/templates/base/pages/lib/_site-tree-nav.tsx +128 -0
- package/templates/base/pages/lib/locale-merge.ts +58 -0
- package/templates/base/pages/lib/route-enumerators.ts +302 -0
- package/templates/base/pages/sitemap.xml.tsx +51 -0
- package/templates/base/plugins/connect-adapter.mjs +144 -0
- package/templates/base/plugins/copy-public-plugin.mjs +50 -0
- package/templates/base/plugins/search-index-plugin.mjs +54 -0
- package/templates/base/scripts/run-b4push.sh +102 -0
- package/templates/base/src/components/ai-chat-modal.tsx +15 -0
- package/templates/base/src/components/client-router-bootstrap.tsx +14 -0
- package/templates/base/src/components/content/component-map.ts +25 -0
- package/templates/base/src/components/content/content-blockquote.tsx +16 -0
- package/templates/base/src/components/content/content-code.tsx +117 -0
- package/templates/base/src/components/content/content-link.tsx +83 -0
- package/templates/base/src/components/content/content-ol.tsx +19 -0
- package/templates/base/src/components/content/content-paragraph.tsx +10 -0
- package/templates/base/src/components/content/content-strong.tsx +16 -0
- package/templates/base/src/components/content/content-table.tsx +18 -0
- package/templates/base/src/components/content/content-ul.tsx +18 -0
- package/templates/base/src/components/content/heading-h2.tsx +26 -0
- package/templates/base/src/components/content/heading-h3.tsx +26 -0
- package/templates/base/src/components/content/heading-h4.tsx +26 -0
- package/templates/base/src/components/design-token-panel-bootstrap.tsx +15 -0
- package/templates/base/src/components/desktop-sidebar-toggle.tsx +15 -0
- package/templates/base/src/components/doc-history.tsx +18 -0
- package/templates/base/src/components/html-preview/highlighted-code.tsx +74 -0
- package/templates/base/src/components/html-preview/html-preview.tsx +108 -0
- package/templates/base/src/components/html-preview/preflight.ts +112 -0
- package/templates/base/src/components/html-preview/preview-base.tsx +159 -0
- package/templates/base/src/components/image-enlarge.tsx +19 -0
- package/templates/base/src/components/mobile-toc.tsx +94 -0
- package/templates/base/src/components/preset-generator.tsx +14 -0
- package/templates/base/src/components/sidebar-toggle.tsx +98 -0
- package/templates/base/src/components/sidebar-tree.tsx +543 -0
- package/templates/base/src/components/site-tree-nav.tsx +233 -0
- package/templates/base/src/components/theme-toggle.tsx +93 -0
- package/templates/base/src/components/toc.tsx +63 -0
- package/templates/base/src/components/tree-nav-shared.tsx +71 -0
- package/templates/base/src/config/color-scheme-utils.ts +182 -0
- package/templates/base/src/config/color-schemes.ts +128 -0
- package/templates/base/src/config/frontmatter-preview-defaults.ts +24 -0
- package/templates/base/src/config/frontmatter-preview-renderers.tsx +46 -0
- package/templates/base/src/config/i18n.ts +225 -0
- package/templates/base/src/config/settings-types.ts +162 -0
- package/templates/base/src/config/sidebars.ts +66 -0
- package/templates/base/src/config/tag-vocabulary-types.ts +39 -0
- package/templates/base/src/config/tag-vocabulary.ts +20 -0
- package/templates/base/src/hooks/use-active-heading.ts +133 -0
- package/templates/base/src/plugins/docs-source-map.ts +103 -0
- package/templates/base/src/plugins/hast-utils.ts +10 -0
- package/templates/base/src/plugins/rehype-code-title.ts +50 -0
- package/templates/base/src/plugins/rehype-heading-links.ts +53 -0
- package/templates/base/src/plugins/rehype-image-enlarge.ts +113 -0
- package/templates/base/src/plugins/rehype-mermaid.ts +41 -0
- package/templates/base/src/plugins/rehype-strip-md-extension.ts +58 -0
- package/templates/base/src/plugins/remark-admonitions.ts +99 -0
- package/templates/base/src/plugins/remark-resolve-markdown-links.ts +127 -0
- package/templates/base/src/plugins/url-utils.ts +4 -0
- package/templates/base/src/styles/global.css +1066 -0
- package/templates/base/src/types/docs-entry.ts +39 -0
- package/templates/base/src/types/heading.ts +5 -0
- package/templates/base/src/types/locale.ts +10 -0
- package/templates/base/src/utils/base.ts +139 -0
- package/templates/base/src/utils/content-files.ts +106 -0
- package/templates/base/src/utils/dedent.ts +24 -0
- package/templates/base/src/utils/docs.ts +335 -0
- package/templates/base/src/utils/git-info.ts +70 -0
- package/templates/base/src/utils/github.ts +19 -0
- package/templates/base/src/utils/header-right-items.ts +38 -0
- package/templates/base/src/utils/nav-scope.ts +63 -0
- package/templates/base/src/utils/sidebar.ts +104 -0
- package/templates/base/src/utils/slug.ts +10 -0
- package/templates/base/src/utils/smart-break.tsx +126 -0
- package/templates/base/src/utils/tags.ts +126 -0
- package/templates/base/tsconfig.json +36 -0
- package/templates/features/bodyFootUtil/files/src/utils/github.ts +19 -0
- package/templates/features/claudeResources/files/plugins/claude-resources-plugin.mjs +137 -0
- package/templates/features/claudeResources/files/src/integrations/claude-resources/__tests__/escape-for-mdx.test.ts +34 -0
- package/templates/features/claudeResources/files/src/integrations/claude-resources/__tests__/generate.test.ts +376 -0
- package/templates/features/claudeResources/files/src/integrations/claude-resources/escape-for-mdx.ts +93 -0
- package/templates/features/claudeResources/files/src/integrations/claude-resources/generate.ts +586 -0
- package/templates/features/designTokenPanel/files/src/components/design-token-panel-bootstrap.tsx +15 -0
- package/templates/features/designTokenPanel/files/src/config/design-token-panel-config.ts +99 -0
- package/templates/features/designTokenPanel/files/src/config/design-tokens-manifest.ts +177 -0
- package/templates/features/designTokenPanel/files/src/lib/design-token-panel-bootstrap.ts +50 -0
- package/templates/features/docHistory/files/plugins/doc-history-plugin.mjs +99 -0
- package/templates/features/docHistory/files/src/components/doc-history.tsx +598 -0
- package/templates/features/docHistory/files/src/types/doc-history.ts +23 -0
- package/templates/features/docHistory/files/src/utils/doc-history.ts +180 -0
- package/templates/features/docTags/files/pages/[locale]/docs/tags/[tag].tsx +116 -0
- package/templates/features/docTags/files/pages/[locale]/docs/tags/index.tsx +99 -0
- package/templates/features/docTags/files/pages/docs/tags/[tag].tsx +101 -0
- package/templates/features/docTags/files/pages/docs/tags/index.tsx +86 -0
- package/templates/features/i18n/files/pages/[locale]/docs/[...slug].tsx +467 -0
- package/templates/features/i18n/files/pages/[locale]/index.tsx +213 -0
- package/templates/features/imageEnlarge/files/src/components/image-enlarge.tsx +248 -0
- package/templates/features/llmsTxt/files/plugins/llms-txt-plugin.mjs +74 -0
- package/templates/features/sidebarResizer/files/src/scripts/sidebar-resizer.ts +185 -0
- package/templates/features/sidebarToggle/files/src/components/desktop-sidebar-toggle.tsx +126 -0
- package/templates/features/tagGovernance/files/scripts/tags-audit.ts +576 -0
- package/templates/features/tagGovernance/files/scripts/tags-suggest.ts +428 -0
- package/templates/features/tauri/files/src/components/find-bar.tsx +122 -0
- package/templates/features/tauri/files/src/components/find-in-page-init.tsx +53 -0
- package/templates/features/tauri/files/src/utils/find-in-page.ts +175 -0
- package/templates/features/tauri/files/src-tauri/Cargo.toml +14 -0
- package/templates/features/tauri/files/src-tauri/build.rs +3 -0
- package/templates/features/tauri/files/src-tauri/capabilities/default.json +11 -0
- package/templates/features/tauri/files/src-tauri/src/main.rs +250 -0
- package/templates/features/tauri/files/src-tauri/tauri.conf.json +25 -0
- package/templates/features/tauriDev/files/src-tauri-dev/Cargo.toml +15 -0
- package/templates/features/tauriDev/files/src-tauri-dev/build.rs +3 -0
- package/templates/features/tauriDev/files/src-tauri-dev/capabilities/default.json +7 -0
- package/templates/features/tauriDev/files/src-tauri-dev/frontend/index.html +187 -0
- package/templates/features/tauriDev/files/src-tauri-dev/icons/icon.png +0 -0
- package/templates/features/tauriDev/files/src-tauri-dev/src/main.rs +995 -0
- package/templates/features/tauriDev/files/src-tauri-dev/tauri.conf.json +22 -0
- package/templates/features/tauriDev/files/src-tauri-dev/test-launch.sh +65 -0
- package/templates/features/versioning/files/pages/[locale]/docs/versions.tsx +100 -0
- package/templates/features/versioning/files/pages/docs/versions.tsx +78 -0
- package/templates/features/versioning/files/pages/v/[version]/docs/[...slug].tsx +451 -0
- package/templates/features/versioning/files/pages/v/[version]/ja/docs/[...slug].tsx +490 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Host-side filter helper that turns a doc entry's `data` (frontmatter)
|
|
2
|
+
// into the `entries` array expected by `<FrontmatterPreview>`.
|
|
3
|
+
//
|
|
4
|
+
// The v2 component (packages/zudo-doc/src/metainfo/frontmatter-preview.tsx)
|
|
5
|
+
// is a pure renderer — the caller is responsible for:
|
|
6
|
+
//
|
|
7
|
+
// 1. Honouring `settings.frontmatterPreview === false` (block hidden everywhere).
|
|
8
|
+
// 2. Removing schema-managed system keys (DEFAULT_FRONTMATTER_IGNORE_KEYS).
|
|
9
|
+
// 3. Applying the host's `ignoreKeys` (replaces the default list) or
|
|
10
|
+
// `extraIgnoreKeys` (adds to the default).
|
|
11
|
+
// 4. Skipping null/undefined values.
|
|
12
|
+
//
|
|
13
|
+
// Returning an empty array suppresses the block — the v2 component
|
|
14
|
+
// short-circuits with `null` when `entries` is empty.
|
|
15
|
+
//
|
|
16
|
+
// Custom per-key renderers (from `src/config/frontmatter-preview-renderers.tsx`)
|
|
17
|
+
// are wired by the page template directly on the `<FrontmatterPreview>` call site
|
|
18
|
+
// via the `renderers` prop. This helper only produces the filtered entries array.
|
|
19
|
+
|
|
20
|
+
import { settings } from "@/config/settings";
|
|
21
|
+
import { DEFAULT_FRONTMATTER_IGNORE_KEYS } from "@/config/frontmatter-preview-defaults";
|
|
22
|
+
|
|
23
|
+
export function buildFrontmatterPreviewEntries(
|
|
24
|
+
data: Record<string, unknown> | null | undefined,
|
|
25
|
+
): Array<[string, unknown]> {
|
|
26
|
+
if (!data) return [];
|
|
27
|
+
|
|
28
|
+
// `frontmatterPreview` may be absent (undefined) in fixtures that
|
|
29
|
+
// don't opt into the feature. `false` means the host explicitly
|
|
30
|
+
// disabled the block. Both should produce an empty entries array.
|
|
31
|
+
const config = (settings as { frontmatterPreview?: unknown })
|
|
32
|
+
.frontmatterPreview;
|
|
33
|
+
if (config === false || config === undefined) return [];
|
|
34
|
+
|
|
35
|
+
const cfg = config as {
|
|
36
|
+
ignoreKeys?: string[];
|
|
37
|
+
extraIgnoreKeys?: string[];
|
|
38
|
+
};
|
|
39
|
+
const ignoreSet = new Set<string>(
|
|
40
|
+
cfg.ignoreKeys ?? [
|
|
41
|
+
...DEFAULT_FRONTMATTER_IGNORE_KEYS,
|
|
42
|
+
...(cfg.extraIgnoreKeys ?? []),
|
|
43
|
+
],
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const entries: Array<[string, unknown]> = [];
|
|
47
|
+
for (const [key, value] of Object.entries(data)) {
|
|
48
|
+
if (ignoreSet.has(key)) continue;
|
|
49
|
+
if (value === null || value === undefined) continue;
|
|
50
|
+
entries.push([key, value]);
|
|
51
|
+
}
|
|
52
|
+
return entries;
|
|
53
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/** @jsxRuntime automatic */
|
|
2
|
+
/** @jsxImportSource preact */
|
|
3
|
+
// og:title / og:description / color-scheme head injection for the zfb doc pages.
|
|
4
|
+
//
|
|
5
|
+
// Why this wrapper exists: Astro's baseline doc-layout.astro synthesized
|
|
6
|
+
// og:* meta from frontmatter title/description AND mounted the
|
|
7
|
+
// `<color-scheme-provider>` Astro component (deleted in commit a4d9956 when
|
|
8
|
+
// `src/**/*.astro` was retired). The v2 `<DocLayout>` shell exposes a
|
|
9
|
+
// `head` slot but intentionally does NOT emit either — that is the host's
|
|
10
|
+
// responsibility.
|
|
11
|
+
//
|
|
12
|
+
// Without OgTags the SSG output is missing og:title / og:description,
|
|
13
|
+
// which crawlers and link-preview tools rely on. Without ColorSchemeProvider
|
|
14
|
+
// the runtime `:root { --zd-* }` palette is missing, so every component that
|
|
15
|
+
// resolves a color via `--zd-*` (search match-keyword highlight, image-overlay,
|
|
16
|
+
// etc.) falls back to UA defaults — and the smoke-search "matched
|
|
17
|
+
// keywords" regression guard at e2e/smoke-search.spec.ts:167 fires
|
|
18
|
+
// because `getComputedStyle(root).getPropertyValue("--zd-matched-keyword-bg")`
|
|
19
|
+
// returns "" instead of the resolved palette token.
|
|
20
|
+
//
|
|
21
|
+
// (#1355 wave 13 — restoring the Astro-era ColorSchemeProvider mount that
|
|
22
|
+
// was orphaned during the .astro retirement.)
|
|
23
|
+
|
|
24
|
+
import type { JSX } from "preact";
|
|
25
|
+
import { OgTags, TwitterCard } from "@takazudo/zudo-doc/head";
|
|
26
|
+
// Don't import ColorSchemeProvider from "@takazudo/zudo-doc/theme" — that
|
|
27
|
+
// barrel also re-exports DesignTokenTweakPanel + ColorTweakExportModal, which
|
|
28
|
+
// transitively pull `src/components/design-token-tweak/*` and the v2 panel
|
|
29
|
+
// modules (and react-dependent code) into the zfb esbuild graph. Same hazard
|
|
30
|
+
// the host's `_header-with-defaults.tsx` documents for ThemeToggle. The v2
|
|
31
|
+
// package exposes a dedicated `./theme/color-scheme-provider` subpath whose
|
|
32
|
+
// only output is the SSR-only ColorSchemeProvider component, keeping this
|
|
33
|
+
// head emission free of the panel-module dependency chain.
|
|
34
|
+
import ColorSchemeProvider from "@takazudo/zudo-doc/theme/color-scheme-provider";
|
|
35
|
+
import { composeMetaTitle } from "./_compose-meta-title";
|
|
36
|
+
import { withBase } from "@/utils/base";
|
|
37
|
+
import { settings } from "@/config/settings";
|
|
38
|
+
// W3B (#1730): cssText + colorMode are precomputed here — the v2
|
|
39
|
+
// ColorSchemeProvider no longer reaches into the host config tree.
|
|
40
|
+
import {
|
|
41
|
+
generateCssCustomProperties,
|
|
42
|
+
generateLightDarkCssProperties,
|
|
43
|
+
} from "@/config/color-scheme-utils";
|
|
44
|
+
|
|
45
|
+
export interface HeadWithDefaultsProps {
|
|
46
|
+
/** Page title forwarded to og:title. Required. */
|
|
47
|
+
title: string;
|
|
48
|
+
/** Optional page description forwarded to og:description. */
|
|
49
|
+
description?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Absolute canonical URL for this page. When supplied, emits
|
|
52
|
+
* <link rel="canonical" href="...">. Compute as:
|
|
53
|
+
* settings.siteUrl.replace(/\/$/, '') + pageUrl
|
|
54
|
+
* in each host page and pass only when settings.siteUrl is non-empty.
|
|
55
|
+
*/
|
|
56
|
+
canonical?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Default-bearing host wrapper that injects og:title / og:description,
|
|
61
|
+
* the ColorSchemeProvider (`:root { --zd-* }` palette + theme bootstrap),
|
|
62
|
+
* the favicon link, and an optional canonical link into the v2 layout's
|
|
63
|
+
* `head` slot.
|
|
64
|
+
*
|
|
65
|
+
* og:title is run through composeMetaTitle so it matches the
|
|
66
|
+
* "<title> | <siteName>" shape emitted by the host's <title> element
|
|
67
|
+
* (the legacy Astro layout produced both shapes; the zfb host has to
|
|
68
|
+
* compose them itself).
|
|
69
|
+
*
|
|
70
|
+
* Pure SSR — no state, no client-only imports. Intended for use as:
|
|
71
|
+
* head={<HeadWithDefaults title={title} description={description} canonical={canonical} />}
|
|
72
|
+
* on every DocLayoutWithDefaults call site in the host pages.
|
|
73
|
+
*/
|
|
74
|
+
export function HeadWithDefaults({
|
|
75
|
+
title,
|
|
76
|
+
description,
|
|
77
|
+
canonical,
|
|
78
|
+
}: HeadWithDefaultsProps): JSX.Element {
|
|
79
|
+
// og:image / twitter:image must be absolute URLs — crawlers silently drop
|
|
80
|
+
// relative og:image values. Computed as siteUrl (no trailing slash) + the
|
|
81
|
+
// base-prefixed asset path.
|
|
82
|
+
const ogImageUrl = `${settings.siteUrl.replace(/\/$/, "")}${withBase("/img/ogp.png")}`;
|
|
83
|
+
|
|
84
|
+
// Resolve the palette CSS body once per page render (the v2 component
|
|
85
|
+
// is pure SSR — no caching needed).
|
|
86
|
+
const colorMode = settings.colorMode ? settings.colorMode : null;
|
|
87
|
+
const cssText = colorMode
|
|
88
|
+
? generateLightDarkCssProperties()
|
|
89
|
+
: generateCssCustomProperties();
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<>
|
|
93
|
+
<OgTags
|
|
94
|
+
title={composeMetaTitle(title)}
|
|
95
|
+
description={description}
|
|
96
|
+
ogImage={ogImageUrl}
|
|
97
|
+
/>
|
|
98
|
+
{/* og:image:width / og:image:height / og:image:alt — not in OgTags API;
|
|
99
|
+
emitted here directly to avoid expanding the shared HeadProps surface.
|
|
100
|
+
Standard 1200×630 social preview dimensions. */}
|
|
101
|
+
<meta property="og:image:width" content="1200" />
|
|
102
|
+
<meta property="og:image:height" content="630" />
|
|
103
|
+
<meta property="og:image:alt" content={composeMetaTitle(title)} />
|
|
104
|
+
<TwitterCard card="summary_large_image" image={ogImageUrl} />
|
|
105
|
+
<ColorSchemeProvider cssText={cssText} colorMode={colorMode} />
|
|
106
|
+
{/* favicon set — withBase() handles the configured base path prefix */}
|
|
107
|
+
<link rel="icon" href={withBase("/favicon.ico")} sizes="any" />
|
|
108
|
+
<link rel="icon" type="image/png" sizes="32x32" href={withBase("/favicon-32x32.png")} />
|
|
109
|
+
<link rel="icon" type="image/png" sizes="16x16" href={withBase("/favicon-16x16.png")} />
|
|
110
|
+
{canonical !== undefined && <link rel="canonical" href={canonical} />}
|
|
111
|
+
</>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
/** @jsxRuntime automatic */
|
|
2
|
+
/** @jsxImportSource preact */
|
|
3
|
+
// Locale-/version-aware Header wrapper for the zfb doc pages.
|
|
4
|
+
//
|
|
5
|
+
// Mirrors the data-prep that lived in src/components/header.astro
|
|
6
|
+
// (deleted in commit a4d9956): build the header nav, compute active-path
|
|
7
|
+
// state, wire the mobile SidebarToggle island (hamburger + slide-in panel)
|
|
8
|
+
// with the full sidebar tree for doc routes, and feed everything into the
|
|
9
|
+
// v2 <Header> shell.
|
|
10
|
+
//
|
|
11
|
+
// Why this wrapper exists: the v2 Header shell is intentionally
|
|
12
|
+
// framework-agnostic — it accepts slot props (sidebarToggle, themeToggle,
|
|
13
|
+
// etc.) but does not import host helpers (@/config/*, @/utils/*) so the
|
|
14
|
+
// package can be published independently. The data prep stays on the host
|
|
15
|
+
// side. Without this wrapper the zfb doc pages fall through to the
|
|
16
|
+
// DocLayoutWithDefaults minimal default (a bare <header> with only
|
|
17
|
+
// <ThemeToggle>) — the full logo + nav + mobile-sidebar markup is absent
|
|
18
|
+
// from the SSG output, breaking crawlers, screen readers, and no-JS users.
|
|
19
|
+
//
|
|
20
|
+
// Mobile sidebar strategy:
|
|
21
|
+
// - The v2 <Header> accepts a `sidebarToggle` slot that holds the
|
|
22
|
+
// complete mobile sidebar widget: hamburger button + backdrop overlay +
|
|
23
|
+
// slide-in <aside> panel (all rendered by <SidebarToggle>).
|
|
24
|
+
// - This wrapper ALWAYS builds the sidebarToggle (refs #1453: the original
|
|
25
|
+
// header.astro rendered SidebarToggle unconditionally on every page,
|
|
26
|
+
// including the home page with hideSidebar=true). When `navSection` is
|
|
27
|
+
// defined the panel gets the full section tree; when undefined (home,
|
|
28
|
+
// 404, tags, versions) nodes=[] so the panel shows only rootMenuItems.
|
|
29
|
+
// - ThemeToggle from the package (self-island-wrapped) is always passed to
|
|
30
|
+
// Header.themeToggle so the ThemeToggle island marker appears in the
|
|
31
|
+
// header on every page — matching the original header.astro behavior.
|
|
32
|
+
//
|
|
33
|
+
// Locale switcher strategy (refs #1453):
|
|
34
|
+
// - The original header.astro always rendered <LanguageSwitcher /> in the
|
|
35
|
+
// right-items row. This wrapper builds locale links from buildLocaleLinks()
|
|
36
|
+
// and passes a <LanguageSwitcher> as the languageSwitcher slot prop.
|
|
37
|
+
// - Header only renders the slot when settings.locales has > 1 entry, so
|
|
38
|
+
// single-locale projects are unaffected.
|
|
39
|
+
|
|
40
|
+
import type { VNode, JSX } from "preact";
|
|
41
|
+
import { Island } from "@takazudo/zfb";
|
|
42
|
+
import { Header } from "@takazudo/zudo-doc/header";
|
|
43
|
+
import {
|
|
44
|
+
LanguageSwitcher,
|
|
45
|
+
VersionSwitcher,
|
|
46
|
+
type VersionSwitcherLabels,
|
|
47
|
+
} from "@takazudo/zudo-doc/i18n-version";
|
|
48
|
+
// Don't import ThemeToggle from "@takazudo/zudo-doc/theme" — that barrel
|
|
49
|
+
// also re-exports DesignTokenTweakPanel and ColorTweakExportModal, which
|
|
50
|
+
// transitively pull `src/components/design-token-tweak/*` and the v2 panel
|
|
51
|
+
// modules into the zfb esbuild graph. Those files import `react`, which
|
|
52
|
+
// zfb does not alias to `preact/compat`, so the build fails. Use the host's
|
|
53
|
+
// local ThemeToggle (already on `preact/hooks`) and wrap it in Island here
|
|
54
|
+
// so the SSG output still emits the `data-zfb-island="ThemeToggle"` marker.
|
|
55
|
+
import ThemeToggle from "@/components/theme-toggle";
|
|
56
|
+
import SidebarToggle from "@/components/sidebar-toggle";
|
|
57
|
+
import { settings } from "@/config/settings";
|
|
58
|
+
import { defaultLocale, locales, t, type Locale } from "@/config/i18n";
|
|
59
|
+
import { buildGitHubRepoUrl } from "@/utils/github";
|
|
60
|
+
import {
|
|
61
|
+
buildLocaleLinks,
|
|
62
|
+
docsUrl,
|
|
63
|
+
navHref,
|
|
64
|
+
stripBase,
|
|
65
|
+
versionedDocsUrl,
|
|
66
|
+
withBase,
|
|
67
|
+
} from "@/utils/base";
|
|
68
|
+
import {
|
|
69
|
+
isNavVisible,
|
|
70
|
+
type NavNode,
|
|
71
|
+
} from "@/utils/docs";
|
|
72
|
+
import { buildSidebarForSection } from "@/utils/sidebar";
|
|
73
|
+
import { filterHeaderRightItems } from "@takazudo/zudo-doc/header";
|
|
74
|
+
import { SearchWidget } from "./_search-widget";
|
|
75
|
+
import { loadNavSourceDocs } from "./_nav-source-docs";
|
|
76
|
+
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// Internal helpers
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Walk the nav tree and rewrite each node's `href` to its versioned form.
|
|
83
|
+
*
|
|
84
|
+
* `buildNavTree` always emits hrefs via `docsUrl()`; when the active route
|
|
85
|
+
* lives under `/v/{version}/...` we need the same nodes pointing at the
|
|
86
|
+
* versioned URL so internal nav clicks stay inside the version. Skips
|
|
87
|
+
* nodes without an href (link-only or category placeholders).
|
|
88
|
+
*
|
|
89
|
+
* Intentionally kept as a local copy in this module (not extracted) —
|
|
90
|
+
* T2 only dedupes loadNavSourceDocs; remapVersionedHrefs is out of scope.
|
|
91
|
+
*/
|
|
92
|
+
function remapVersionedHrefs(
|
|
93
|
+
nodes: NavNode[],
|
|
94
|
+
version: string,
|
|
95
|
+
nodeLang: Locale,
|
|
96
|
+
): NavNode[] {
|
|
97
|
+
return nodes.map((node) => {
|
|
98
|
+
const children =
|
|
99
|
+
node.children.length > 0
|
|
100
|
+
? remapVersionedHrefs(node.children, version, nodeLang)
|
|
101
|
+
: node.children;
|
|
102
|
+
|
|
103
|
+
if (!node.href || node.slug.startsWith("__link__")) {
|
|
104
|
+
return children !== node.children ? { ...node, children } : node;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const newHref = versionedDocsUrl(node.slug, version, nodeLang);
|
|
108
|
+
return { ...node, href: newHref, children };
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// Component
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
export interface HeaderWithDefaultsProps {
|
|
117
|
+
/** Active locale; defaults to the configured defaultLocale. */
|
|
118
|
+
lang?: Locale;
|
|
119
|
+
/**
|
|
120
|
+
* Current page URL path (as the layout passes from Astro.url.pathname or
|
|
121
|
+
* the zfb equivalent). Used by the Header to compute the active nav item
|
|
122
|
+
* and by the mobile sidebar footer locale-switcher links.
|
|
123
|
+
*/
|
|
124
|
+
currentPath?: string;
|
|
125
|
+
/** Active version slug, when rendering inside /v/{version}/... routes. */
|
|
126
|
+
currentVersion?: string;
|
|
127
|
+
/**
|
|
128
|
+
* Slug of the active doc page — forwarded to SidebarTree so it can
|
|
129
|
+
* highlight the current entry. Required when navSection is set.
|
|
130
|
+
*/
|
|
131
|
+
currentSlug?: string;
|
|
132
|
+
/**
|
|
133
|
+
* Header-nav category matcher used to scope the sidebar tree (e.g.
|
|
134
|
+
* "guides"). When provided the mobile sidebar toggle is wired with the
|
|
135
|
+
* full sidebar tree for this section. When omitted (404, index, tags,
|
|
136
|
+
* versions pages) no mobile sidebar toggle is included in the header.
|
|
137
|
+
*/
|
|
138
|
+
navSection?: string;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Default-bearing host wrapper around v2's <Header> shell.
|
|
143
|
+
*
|
|
144
|
+
* Handles:
|
|
145
|
+
* 1. Logo, main nav with active-path highlight — delegated to <Header>.
|
|
146
|
+
* 2. ThemeToggle island in the right items row — passed as themeToggle slot.
|
|
147
|
+
* 3. Mobile SidebarToggle island (hamburger + slide-in aside + tree) —
|
|
148
|
+
* built from the same nav data as SidebarWithDefaults and passed as
|
|
149
|
+
* the sidebarToggle slot when navSection is defined.
|
|
150
|
+
*/
|
|
151
|
+
export function HeaderWithDefaults(
|
|
152
|
+
props: HeaderWithDefaultsProps,
|
|
153
|
+
): JSX.Element {
|
|
154
|
+
const {
|
|
155
|
+
lang = defaultLocale,
|
|
156
|
+
currentPath = "",
|
|
157
|
+
currentVersion,
|
|
158
|
+
currentSlug,
|
|
159
|
+
navSection,
|
|
160
|
+
} = props;
|
|
161
|
+
|
|
162
|
+
// Root-menu items for the mobile sidebar's "back to menu" list.
|
|
163
|
+
// Mirrors the data-prep in _sidebar-with-defaults.tsx.
|
|
164
|
+
const rootMenuItems = settings.headerNav.map((item) => ({
|
|
165
|
+
label: item.labelKey
|
|
166
|
+
? t(item.labelKey as Parameters<typeof t>[0], lang)
|
|
167
|
+
: item.label,
|
|
168
|
+
href: navHref(item.path, lang, currentVersion),
|
|
169
|
+
children: item.children?.map((child) => ({
|
|
170
|
+
label: child.labelKey
|
|
171
|
+
? t(child.labelKey as Parameters<typeof t>[0], lang)
|
|
172
|
+
: child.label,
|
|
173
|
+
href: navHref(child.path, lang, currentVersion),
|
|
174
|
+
})),
|
|
175
|
+
}));
|
|
176
|
+
|
|
177
|
+
// Build the mobile sidebar toggle unconditionally — the original header.astro
|
|
178
|
+
// rendered SidebarToggle on every page (refs #1453). When navSection is
|
|
179
|
+
// defined the panel gets the full section tree; when undefined (home, 404,
|
|
180
|
+
// tags, versions) nodes=[] so the panel shows only rootMenuItems + locale
|
|
181
|
+
// links (the basic nav menu without a doc tree).
|
|
182
|
+
const backToMenuLabel = t("nav.backToMenu", lang);
|
|
183
|
+
|
|
184
|
+
// Locale-switcher links in the mobile sidebar footer — only when
|
|
185
|
+
// multiple locales are configured (mirrors _sidebar-with-defaults.tsx).
|
|
186
|
+
const localeLinks =
|
|
187
|
+
locales.length > 1 ? buildLocaleLinks(currentPath, lang) : undefined;
|
|
188
|
+
|
|
189
|
+
const themeDefaultMode = settings.colorMode
|
|
190
|
+
? settings.colorMode.defaultMode
|
|
191
|
+
: undefined;
|
|
192
|
+
|
|
193
|
+
let sidebarNodes: NavNode[] = [];
|
|
194
|
+
if (navSection !== undefined) {
|
|
195
|
+
const { docs, categoryMeta } = loadNavSourceDocs(lang, currentVersion);
|
|
196
|
+
const navDocs = docs.filter(isNavVisible);
|
|
197
|
+
const rawNodes = buildSidebarForSection(navDocs, lang, navSection, categoryMeta);
|
|
198
|
+
sidebarNodes = currentVersion
|
|
199
|
+
? remapVersionedHrefs(rawNodes, currentVersion, lang)
|
|
200
|
+
: rawNodes;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Wrap SidebarToggle (hamburger button + slide-in aside + SidebarTree) in
|
|
204
|
+
// Island so the SSG output carries the full tree HTML AND the
|
|
205
|
+
// data-zfb-island="SidebarToggle" marker for client-side hydration.
|
|
206
|
+
// Island.captureComponentName reads SidebarToggle.name → "SidebarToggle".
|
|
207
|
+
//
|
|
208
|
+
// SidebarTree's data props (nodes, currentSlug, rootMenuItems, …) are
|
|
209
|
+
// passed to SidebarToggle directly rather than as JSX children so they
|
|
210
|
+
// ride across the SSR → hydrate boundary in the Island marker's
|
|
211
|
+
// `data-props` attribute. Island only serialises a wrapped child
|
|
212
|
+
// component's *own* props (excluding `children`); when SidebarTree was
|
|
213
|
+
// nested as a JSX child its data was dropped during hydration and
|
|
214
|
+
// SidebarToggle re-rendered with `children=undefined`, wiping the SSR
|
|
215
|
+
// tree DOM. zudolab/zudo-doc#1355 wave 13.5.
|
|
216
|
+
const sidebarToggle = Island({
|
|
217
|
+
when: "load",
|
|
218
|
+
children: (
|
|
219
|
+
<SidebarToggle
|
|
220
|
+
nodes={sidebarNodes}
|
|
221
|
+
currentSlug={currentSlug}
|
|
222
|
+
rootMenuItems={rootMenuItems}
|
|
223
|
+
backToMenuLabel={backToMenuLabel}
|
|
224
|
+
localeLinks={localeLinks}
|
|
225
|
+
themeDefaultMode={themeDefaultMode}
|
|
226
|
+
/>
|
|
227
|
+
),
|
|
228
|
+
}) as unknown as VNode;
|
|
229
|
+
|
|
230
|
+
// Wrap the host's local ThemeToggle in Island({when:"load"}) so the SSG
|
|
231
|
+
// output emits a data-zfb-island="ThemeToggle" marker the hydration
|
|
232
|
+
// runtime can find — matching the original header.astro output. The v2
|
|
233
|
+
// package's <ThemeToggle> already does this internally, but importing it
|
|
234
|
+
// forces the v2 theme barrel into the bundle (see import note at the top
|
|
235
|
+
// of this file).
|
|
236
|
+
const themeToggle = Island({
|
|
237
|
+
when: "load",
|
|
238
|
+
children: <ThemeToggle />,
|
|
239
|
+
}) as unknown as VNode;
|
|
240
|
+
|
|
241
|
+
// Locale-aware search widget — mirrors `<Search />` from the deleted
|
|
242
|
+
// `src/components/search.astro`. Renders the full dialog markup in SSR
|
|
243
|
+
// so the placeholder text ("Type to search..." / 「検索したい単語を入力」)
|
|
244
|
+
// and keyboard-shortcut hint appear in the static HTML on every page.
|
|
245
|
+
// Strings are derived from the host's t() helper so locale switching works.
|
|
246
|
+
const searchWidget = (
|
|
247
|
+
<SearchWidget
|
|
248
|
+
placeholderText={t("search.placeholder", lang)}
|
|
249
|
+
shortcutHint={t("search.shortcutHint", lang)}
|
|
250
|
+
resultCountTemplate={t("search.resultCount", lang)}
|
|
251
|
+
searchLabel={t("search.label", lang)}
|
|
252
|
+
/>
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
// Build the version-switcher component when versioning is configured.
|
|
256
|
+
// The VersionSwitcher is a pure SSR component — it emits the full dropdown
|
|
257
|
+
// markup (including the "All versions" footer link) directly in the SSG
|
|
258
|
+
// HTML so crawlers and JS-off users see the version list. The interactive
|
|
259
|
+
// toggle behavior is wired by VERSION_SWITCHER_INIT_SCRIPT included in
|
|
260
|
+
// the layout's body-end scripts.
|
|
261
|
+
//
|
|
262
|
+
// Gate: only render when settings.versions is a non-empty array. When
|
|
263
|
+
// versioning is disabled (settings.versions === false) the slot is
|
|
264
|
+
// undefined and the Header renders nothing for the version-switcher item.
|
|
265
|
+
let versionSwitcher: VNode | undefined;
|
|
266
|
+
|
|
267
|
+
if (settings.versions && settings.versions.length > 0) {
|
|
268
|
+
const isNonDefaultLocale = lang !== defaultLocale;
|
|
269
|
+
// "All versions" page URL — locale-prefixed when not on the default locale.
|
|
270
|
+
const versionsPageUrl = withBase(
|
|
271
|
+
isNonDefaultLocale ? `/${lang}/docs/versions` : "/docs/versions",
|
|
272
|
+
);
|
|
273
|
+
// "Latest" entry links to the current page in the latest (unversioned)
|
|
274
|
+
// docs when a slug is available, or falls back to the versions index page.
|
|
275
|
+
const latestUrl = currentSlug
|
|
276
|
+
? docsUrl(currentSlug, lang)
|
|
277
|
+
: versionsPageUrl;
|
|
278
|
+
|
|
279
|
+
// Per-version URLs for the current page. When there is no slug in scope
|
|
280
|
+
// (e.g. on the versions page itself) all entries point to the versions
|
|
281
|
+
// index. This mirrors the original version-switcher.astro behavior.
|
|
282
|
+
const versionUrls: Record<string, string> = {};
|
|
283
|
+
for (const v of settings.versions) {
|
|
284
|
+
versionUrls[v.slug] = currentSlug
|
|
285
|
+
? versionedDocsUrl(currentSlug, v.slug, lang)
|
|
286
|
+
: versionsPageUrl;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const labels: VersionSwitcherLabels = {
|
|
290
|
+
latest: t("version.latest", lang),
|
|
291
|
+
switcher: t("version.switcher.label", lang),
|
|
292
|
+
unavailable: t("version.switcher.unavailable", lang),
|
|
293
|
+
allVersions: t("version.switcher.allVersions", lang),
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
versionSwitcher = (
|
|
297
|
+
<VersionSwitcher
|
|
298
|
+
versions={settings.versions.map((v) => ({
|
|
299
|
+
slug: v.slug,
|
|
300
|
+
label: v.label ?? v.slug,
|
|
301
|
+
}))}
|
|
302
|
+
currentVersion={currentVersion}
|
|
303
|
+
latestUrl={latestUrl}
|
|
304
|
+
versionsPageUrl={versionsPageUrl}
|
|
305
|
+
versionUrls={versionUrls}
|
|
306
|
+
labels={labels}
|
|
307
|
+
idSuffix="header"
|
|
308
|
+
/>
|
|
309
|
+
) as unknown as VNode;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Build locale-switcher for the header right-items row (refs #1453).
|
|
313
|
+
// The original header.astro always rendered <LanguageSwitcher /> when
|
|
314
|
+
// multiple locales are configured. Reuses the same localeLinks array built
|
|
315
|
+
// above for the mobile sidebar footer (buildLocaleLinks is pure, but one
|
|
316
|
+
// call is cleaner).
|
|
317
|
+
const languageSwitcher =
|
|
318
|
+
localeLinks != null ? (
|
|
319
|
+
<LanguageSwitcher
|
|
320
|
+
links={localeLinks}
|
|
321
|
+
/>
|
|
322
|
+
) as unknown as VNode : undefined;
|
|
323
|
+
|
|
324
|
+
// Locale-keyed persist key: same-locale swaps preserve the header's
|
|
325
|
+
// DOM-node identity; cross-locale swaps use a different key and the
|
|
326
|
+
// router replaces the header entirely (re-rendering locale-specific
|
|
327
|
+
// SSR content such as the LanguageSwitcher anchors). See #1546 + #1549.
|
|
328
|
+
const persistKey = `header-${lang}`;
|
|
329
|
+
|
|
330
|
+
// Compute the right-items flags from the host's settings. The v2
|
|
331
|
+
// `<Header>` no longer consults `@/config/settings` directly — see
|
|
332
|
+
// sub-issue #1729 — so the wrapper is responsible for translating
|
|
333
|
+
// host state into the prop bag the renderer expects. Boolean
|
|
334
|
+
// coercion mirrors the original filter predicates verbatim.
|
|
335
|
+
const headerRightItems = filterHeaderRightItems(
|
|
336
|
+
settings.headerRightItems ?? [],
|
|
337
|
+
{
|
|
338
|
+
designTokenPanel: Boolean(settings.designTokenPanel),
|
|
339
|
+
colorTweakPanel: Boolean(settings.colorTweakPanel),
|
|
340
|
+
aiAssistant: Boolean(settings.aiAssistant),
|
|
341
|
+
colorMode: Boolean(settings.colorMode),
|
|
342
|
+
hasLocales: Object.keys(settings.locales).length > 0,
|
|
343
|
+
hasVersions: Boolean(settings.versions),
|
|
344
|
+
hasGithubUrl: Boolean(settings.githubUrl),
|
|
345
|
+
},
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
const githubRepoUrl = buildGitHubRepoUrl();
|
|
349
|
+
const githubLabel = t("header.github", lang);
|
|
350
|
+
|
|
351
|
+
return (
|
|
352
|
+
<Header
|
|
353
|
+
lang={lang}
|
|
354
|
+
currentPath={currentPath}
|
|
355
|
+
currentVersion={currentVersion}
|
|
356
|
+
sidebarToggle={sidebarToggle}
|
|
357
|
+
themeToggle={themeToggle}
|
|
358
|
+
search={searchWidget}
|
|
359
|
+
versionSwitcher={versionSwitcher}
|
|
360
|
+
languageSwitcher={languageSwitcher}
|
|
361
|
+
persistKey={persistKey}
|
|
362
|
+
siteName={settings.siteName}
|
|
363
|
+
headerNav={settings.headerNav}
|
|
364
|
+
headerRightItems={headerRightItems}
|
|
365
|
+
colorModeEnabled={Boolean(settings.colorMode)}
|
|
366
|
+
hasLocales={locales.length > 1}
|
|
367
|
+
hasVersions={Boolean(settings.versions)}
|
|
368
|
+
githubRepoUrl={githubRepoUrl}
|
|
369
|
+
githubLabel={githubLabel}
|
|
370
|
+
urlHelpers={{
|
|
371
|
+
withBase,
|
|
372
|
+
stripBase,
|
|
373
|
+
// `navHref` from `@/utils/base` types the lang param as the
|
|
374
|
+
// host's literal-locale union; v2's `Locale` is `string`. Wrap
|
|
375
|
+
// so strictFunctionTypes accepts the assignment without losing
|
|
376
|
+
// the runtime call shape (sub-issue #1729 boundary widening).
|
|
377
|
+
navHref: (path, l, v) => navHref(path, l as Locale | undefined, v),
|
|
378
|
+
}}
|
|
379
|
+
i18n={{
|
|
380
|
+
defaultLocale,
|
|
381
|
+
locales,
|
|
382
|
+
t,
|
|
383
|
+
}}
|
|
384
|
+
/>
|
|
385
|
+
);
|
|
386
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/** @jsxRuntime automatic */
|
|
2
|
+
/** @jsxImportSource preact */
|
|
3
|
+
// Inline VersionSwitcher for the breadcrumb rightSlot on doc pages.
|
|
4
|
+
//
|
|
5
|
+
// The reference site (takazudomodular.com) renders the version-switcher
|
|
6
|
+
// pill inline at the right of the breadcrumb row on every doc page:
|
|
7
|
+
//
|
|
8
|
+
// <div class="mb-vsp-sm flex ... sm:justify-between [&_nav]:mb-0">
|
|
9
|
+
// <breadcrumb nav>
|
|
10
|
+
// <VersionSwitcher ... />
|
|
11
|
+
// </div>
|
|
12
|
+
//
|
|
13
|
+
// (Wave 2 parity sweep, zudolab/zudo-doc#1478; placement fix #1534.)
|
|
14
|
+
// The header already shows a VersionSwitcher via _header-with-defaults.tsx;
|
|
15
|
+
// this helper provides the pill for the breadcrumb's `rightSlot` prop so
|
|
16
|
+
// both the DOM position AND the flex-row layout match the reference.
|
|
17
|
+
//
|
|
18
|
+
// Gate: only returns a non-null element when settings.versions is a
|
|
19
|
+
// non-empty array. When versioning is disabled the caller gets undefined
|
|
20
|
+
// and <Breadcrumb> renders without a rightSlot (no wrapper div emitted).
|
|
21
|
+
|
|
22
|
+
import type { JSX } from "preact";
|
|
23
|
+
import {
|
|
24
|
+
VersionSwitcher,
|
|
25
|
+
type VersionSwitcherLabels,
|
|
26
|
+
} from "@takazudo/zudo-doc/i18n-version";
|
|
27
|
+
import { settings } from "@/config/settings";
|
|
28
|
+
import { defaultLocale, t, type Locale } from "@/config/i18n";
|
|
29
|
+
import { docsUrl, versionedDocsUrl, withBase } from "@/utils/base";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Build an inline VersionSwitcher element for the `<Breadcrumb rightSlot>` prop.
|
|
33
|
+
*
|
|
34
|
+
* Returns `undefined` when versioning is not configured (callers can pass
|
|
35
|
+
* the return value directly as `rightSlot` — `<Breadcrumb>` renders the
|
|
36
|
+
* plain nav (no flex wrapper, no version pill) when `rightSlot` is
|
|
37
|
+
* `undefined`).
|
|
38
|
+
*
|
|
39
|
+
* @param slug - Slug of the current doc page (used to build per-version URLs).
|
|
40
|
+
* @param locale - Active locale string.
|
|
41
|
+
* @param currentVersion - Active version slug, or `undefined` on the "latest" route.
|
|
42
|
+
*/
|
|
43
|
+
export function buildInlineVersionSwitcher(
|
|
44
|
+
slug: string,
|
|
45
|
+
locale: Locale,
|
|
46
|
+
currentVersion?: string,
|
|
47
|
+
): JSX.Element | undefined {
|
|
48
|
+
if (!settings.versions || settings.versions.length === 0) return undefined;
|
|
49
|
+
|
|
50
|
+
const isNonDefaultLocale = locale !== defaultLocale;
|
|
51
|
+
const versionsPageUrl = withBase(
|
|
52
|
+
isNonDefaultLocale ? `/${locale}/docs/versions` : "/docs/versions",
|
|
53
|
+
);
|
|
54
|
+
const latestUrl = slug ? docsUrl(slug, locale) : versionsPageUrl;
|
|
55
|
+
|
|
56
|
+
const versionUrls: Record<string, string> = {};
|
|
57
|
+
for (const v of settings.versions) {
|
|
58
|
+
versionUrls[v.slug] = slug
|
|
59
|
+
? versionedDocsUrl(slug, v.slug, locale)
|
|
60
|
+
: versionsPageUrl;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const labels: VersionSwitcherLabels = {
|
|
64
|
+
latest: t("version.latest", locale),
|
|
65
|
+
switcher: t("version.switcher.label", locale),
|
|
66
|
+
unavailable: t("version.switcher.unavailable", locale),
|
|
67
|
+
allVersions: t("version.switcher.allVersions", locale),
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<VersionSwitcher
|
|
72
|
+
versions={settings.versions.map((v) => ({
|
|
73
|
+
slug: v.slug,
|
|
74
|
+
label: v.label ?? v.slug,
|
|
75
|
+
}))}
|
|
76
|
+
currentVersion={currentVersion}
|
|
77
|
+
latestUrl={latestUrl}
|
|
78
|
+
versionsPageUrl={versionsPageUrl}
|
|
79
|
+
versionUrls={versionUrls}
|
|
80
|
+
labels={labels}
|
|
81
|
+
idSuffix="inline"
|
|
82
|
+
/>
|
|
83
|
+
);
|
|
84
|
+
}
|