create-zudo-doc 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/dist/api.js +4 -1
  2. package/dist/cli.js +4 -6
  3. package/dist/preset.js +11 -0
  4. package/dist/prompts.js +2 -6
  5. package/dist/scaffold.js +15 -9
  6. package/dist/settings-gen.js +7 -7
  7. package/dist/utils.d.ts +8 -0
  8. package/dist/utils.js +25 -0
  9. package/dist/zfb-config-gen.js +11 -50
  10. package/package.json +1 -1
  11. package/templates/base/pages/_data.ts +10 -23
  12. package/templates/base/pages/docs/[[...slug]].tsx +27 -168
  13. package/templates/base/pages/lib/_doc-content-header.tsx +24 -4
  14. package/templates/base/pages/lib/_doc-history-area.tsx +21 -5
  15. package/templates/base/pages/lib/_doc-metainfo-area.tsx +22 -2
  16. package/templates/base/pages/lib/_doc-page-renderer.tsx +192 -0
  17. package/templates/base/pages/lib/_doc-page-shell.tsx +3 -2
  18. package/templates/base/pages/lib/_doc-route-entries.ts +188 -0
  19. package/templates/base/pages/lib/_doc-tags-area.tsx +7 -2
  20. package/templates/base/pages/lib/_footer-with-defaults.tsx +38 -27
  21. package/templates/base/pages/lib/_head-with-defaults.tsx +7 -10
  22. package/templates/base/pages/lib/_header-with-defaults.tsx +51 -89
  23. package/templates/base/pages/lib/_inline-version-switcher.tsx +5 -4
  24. package/templates/base/pages/lib/_nav-data-prep.ts +137 -0
  25. package/templates/base/pages/lib/_nav-source-docs.ts +10 -6
  26. package/templates/base/pages/lib/_search-widget-script.ts +32 -9
  27. package/templates/base/pages/lib/_sidebar-with-defaults.tsx +15 -60
  28. package/templates/base/pages/lib/locale-merge.ts +1 -1
  29. package/templates/base/pages/lib/route-enumerators.ts +11 -7
  30. package/templates/base/plugins/connect-adapter.mjs +30 -1
  31. package/templates/base/plugins/copy-public-plugin.mjs +10 -2
  32. package/templates/base/plugins/search-index-plugin.mjs +20 -8
  33. package/templates/base/src/components/sidebar-toggle.tsx +1 -1
  34. package/templates/base/src/components/sidebar-tree.tsx +10 -4
  35. package/templates/base/src/config/color-schemes.ts +4 -0
  36. package/templates/base/src/config/docs-schema.ts +94 -0
  37. package/templates/base/src/config/i18n.ts +10 -3
  38. package/templates/base/src/styles/global.css +14 -0
  39. package/templates/base/src/types/docs-entry.ts +8 -26
  40. package/templates/base/src/utils/base.ts +5 -3
  41. package/templates/base/src/utils/docs.ts +144 -169
  42. package/templates/features/claudeResources/files/plugins/claude-resources-plugin.mjs +20 -110
  43. package/templates/features/claudeResources/files/src/integrations/claude-resources/generate.ts +62 -38
  44. package/templates/features/designTokenPanel/files/src/config/design-token-panel-config.ts +34 -8
  45. package/templates/features/docHistory/files/plugins/doc-history-plugin.mjs +27 -45
  46. package/templates/features/docHistory/files/src/components/doc-history.tsx +28 -8
  47. package/templates/features/docTags/files/pages/[locale]/docs/tags/[tag].tsx +6 -74
  48. package/templates/features/docTags/files/pages/[locale]/docs/tags/index.tsx +6 -77
  49. package/templates/features/docTags/files/pages/docs/tags/[tag].tsx +7 -69
  50. package/templates/features/docTags/files/pages/docs/tags/index.tsx +6 -76
  51. package/templates/features/docTags/files/pages/lib/_tag-pages.tsx +201 -0
  52. package/templates/features/i18n/files/pages/[locale]/docs/[[...slug]].tsx +41 -179
  53. package/templates/features/i18n/files/pages/[locale]/index.tsx +5 -5
  54. package/templates/features/llmsTxt/files/plugins/llms-txt-plugin.mjs +33 -21
  55. package/templates/features/sidebarToggle/files/src/components/desktop-sidebar-toggle.tsx +1 -1
  56. package/templates/features/versioning/files/pages/[locale]/docs/versions.tsx +5 -59
  57. package/templates/features/versioning/files/pages/docs/versions.tsx +8 -66
  58. package/templates/features/versioning/files/pages/lib/_versions-page.tsx +79 -0
  59. package/templates/features/versioning/files/pages/v/[version]/[locale]/docs/[[...slug]].tsx +46 -191
  60. package/templates/features/versioning/files/pages/v/[version]/docs/[[...slug]].tsx +31 -173
  61. package/templates/base/src/components/content/heading-h3.tsx +0 -20
  62. package/templates/base/src/components/theme-toggle.tsx +0 -107
  63. package/templates/base/src/hooks/use-active-heading.ts +0 -133
  64. package/templates/base/src/plugins/docs-source-map.ts +0 -103
  65. package/templates/base/src/plugins/hast-utils.ts +0 -10
  66. package/templates/base/src/plugins/rehype-code-title.ts +0 -50
  67. package/templates/base/src/plugins/rehype-heading-links.ts +0 -53
  68. package/templates/base/src/plugins/rehype-mermaid.ts +0 -41
  69. package/templates/base/src/plugins/url-utils.ts +0 -4
  70. package/templates/base/src/utils/dedent.ts +0 -24
  71. package/templates/features/docHistory/files/src/utils/doc-history.ts +0 -180
  72. package/templates/features/sidebarResizer/files/src/scripts/sidebar-resizer.ts +0 -198
@@ -16,7 +16,7 @@
16
16
  // → collectTags() → tag section
17
17
 
18
18
  import { settings } from "@/config/settings";
19
- import { t } from "@/config/i18n";
19
+ import { t, getLocaleConfig, type Locale } from "@/config/i18n";
20
20
  import { withBase } from "@/utils/base";
21
21
  import {
22
22
  buildNavTree,
@@ -71,16 +71,16 @@ export default function LocaleIndexPage({ params }: PageArgs): JSX.Element {
71
71
  // instance). categoryMeta is intentionally locale-dir-only here — this page
72
72
  // historically did NOT merge in base meta (unlike the locale doc route), so
73
73
  // we keep that exact behavior to preserve output.
74
- const { navDocs } = resolveNavSource(locale, undefined, {
74
+ const { navDocs } = resolveNavSource(locale as Locale, undefined, {
75
75
  applyDefaultLocaleOnlyFilter: true,
76
76
  keepUnlisted: true,
77
77
  });
78
- const localeConfig = settings.locales[locale];
78
+ const localeConfig = getLocaleConfig(locale);
79
79
  const categoryMeta = localeConfig
80
80
  ? loadCategoryMeta(localeConfig.dir)
81
81
  : loadCategoryMeta(settings.docsDir);
82
82
 
83
- const tree = buildNavTree(navDocs, locale, categoryMeta);
83
+ const tree = buildNavTree(navDocs, locale as Locale, categoryMeta);
84
84
  const categoryOrder = getCategoryOrder();
85
85
  const groupedTree = groupSatelliteNodes(tree, categoryOrder);
86
86
 
@@ -103,7 +103,7 @@ export default function LocaleIndexPage({ params }: PageArgs): JSX.Element {
103
103
  noindex={settings.noindex}
104
104
  hideSidebar={true}
105
105
  hideToc={true}
106
- headerOverride={<HeaderWithDefaults lang={locale} currentPath={withBase(`/${locale}/`)} />}
106
+ headerOverride={<HeaderWithDefaults lang={locale as Locale} currentPath={withBase(`/${locale}/`)} />}
107
107
  footerOverride={<FooterWithDefaults lang={locale} />}
108
108
  bodyEndComponents={<BodyEndIslands basePath={settings.base ?? "/"} />}
109
109
  >
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  // zfb plugin module: llms-txt.
2
3
  //
3
4
  // Wires two lifecycle hooks for the llms-txt integration:
@@ -20,35 +21,35 @@
20
21
  // Inline functions are not supported by zfb's plugin runtime; see the
21
22
  // sibling `doc-history-plugin.mjs` for the rationale.
22
23
 
24
+ /** @import { ZfbBuildHookContext, ZfbDevMiddlewareContext, ZfbPlugin } from "@takazudo/zfb/plugins" */
25
+ /** @import { LlmsTxtEmitOptions, LlmsTxtDevMiddlewareOptions } from "@takazudo/zudo-doc/integrations/llms-txt" */
26
+
23
27
  import { emitLlmsTxt, createLlmsTxtDevMiddleware } from "@takazudo/zudo-doc/integrations/llms-txt";
24
28
  import { connectToZfbHandler } from "./connect-adapter.mjs";
25
29
 
30
+ /** @type {ZfbPlugin} */
26
31
  export default {
27
32
  name: "llms-txt",
28
33
 
34
+ /** @param {ZfbBuildHookContext} ctx */
29
35
  postBuild(ctx) {
30
- const {
31
- siteName,
32
- siteDescription,
33
- base,
34
- siteUrl,
35
- defaultLocaleDir,
36
- locales,
37
- } = ctx.options;
38
- emitLlmsTxt({
36
+ emitLlmsTxt(/** @type {LlmsTxtEmitOptions} */ (/** @type {unknown} */ ({
37
+ ...ctx.options,
39
38
  outDir: ctx.outDir,
40
- siteName,
41
- siteDescription,
42
- base,
43
- siteUrl: siteUrl || undefined,
44
- defaultLocaleDir,
45
- locales,
39
+ // siteUrl is normalised to undefined when falsy because the runner
40
+ // switches between absolute and root-relative URLs based on its
41
+ // presence (matches legacy Astro behaviour).
42
+ siteUrl: ctx.options["siteUrl"] || undefined,
46
43
  logger: ctx.logger,
47
- });
44
+ })));
48
45
  },
49
46
 
47
+ /** @param {ZfbDevMiddlewareContext} ctx */
50
48
  devMiddleware(ctx) {
51
- const middleware = createLlmsTxtDevMiddleware(ctx.options, ctx.logger);
49
+ const middleware = createLlmsTxtDevMiddleware(
50
+ /** @type {LlmsTxtDevMiddlewareOptions} */ (/** @type {unknown} */ (ctx.options)),
51
+ ctx.logger,
52
+ );
52
53
  const handler = connectToZfbHandler(middleware);
53
54
 
54
55
  // zfb's `register(path, handler)` matches against the FULL request
@@ -58,16 +59,27 @@ export default {
58
59
  // empty and routes are `/llms.txt` etc. as expected. The middleware
59
60
  // accepts base-prefixed URLs via the matcher (see `matchLlmsRoute`
60
61
  // in `dev-middleware.ts`).
61
- const basePrefix = stripTrailingSlash(ctx.options.base ?? "");
62
+ const basePrefix = stripTrailingSlash(
63
+ typeof ctx.options["base"] === "string" ? ctx.options["base"] : "",
64
+ );
62
65
  ctx.register(`${basePrefix}/llms.txt`, handler);
63
66
  ctx.register(`${basePrefix}/llms-full.txt`, handler);
64
- for (const locale of ctx.options.locales ?? []) {
65
- ctx.register(`${basePrefix}/${locale.code}/llms.txt`, handler);
66
- ctx.register(`${basePrefix}/${locale.code}/llms-full.txt`, handler);
67
+ const locales = ctx.options["locales"];
68
+ if (Array.isArray(locales)) {
69
+ for (const locale of locales) {
70
+ if (locale && typeof locale === "object" && typeof locale.code === "string") {
71
+ ctx.register(`${basePrefix}/${locale.code}/llms.txt`, handler);
72
+ ctx.register(`${basePrefix}/${locale.code}/llms-full.txt`, handler);
73
+ }
74
+ }
67
75
  }
68
76
  },
69
77
  };
70
78
 
79
+ /**
80
+ * @param {string} s
81
+ * @returns {string}
82
+ */
71
83
  function stripTrailingSlash(s) {
72
84
  if (typeof s !== "string" || s.length === 0) return "";
73
85
  return s.endsWith("/") ? s.slice(0, -1) : s;
@@ -29,7 +29,7 @@ export default function DesktopSidebarToggle() {
29
29
  // to <html> from localStorage *before* this island mounts, so the
30
30
  // visual state stays correct; we only need to sync this island's
31
31
  // React state to the persisted preference after hydration. Same
32
- // pattern as src/components/theme-toggle.tsx (commit 9aebd8e).
32
+ // pattern as packages/zudo-doc/src/theme-toggle/index.tsx (commit 9aebd8e).
33
33
  const [visible, setVisible] = useState<boolean>(true);
34
34
  // Tracks whether the hydration sync (below) has run. The persistence
35
35
  // effect below skips the very first mount so we don't overwrite the
@@ -6,22 +6,16 @@
6
6
  // settings.locales. Locale string is passed as a prop to drive label
7
7
  // translation in the component.
8
8
  //
9
+ // Rendering is shared with the default-locale route via
10
+ // pages/lib/_versions-page.tsx (#2010).
11
+ //
9
12
  // paths() contract (zfb ADR-004 — synchronous):
10
13
  // params: { locale: string }
11
14
  // props: { locale }
12
15
 
13
16
  import { settings } from "@/config/settings";
14
- import { t } from "@/config/i18n";
15
- import { withBase } from "@/utils/base";
16
- import { DocLayoutWithDefaults } from "@takazudo/zudo-doc/doclayout";
17
- import { VersionsPageContent } from "@takazudo/zudo-doc/nav-indexing";
18
- import type { VersionPageEntry, VersionsPageLabels } from "@takazudo/zudo-doc/nav-indexing";
19
17
  import type { JSX } from "preact";
20
- import { FooterWithDefaults } from "../../lib/_footer-with-defaults";
21
- import { HeaderWithDefaults } from "../../lib/_header-with-defaults";
22
- import { HeadWithDefaults } from "../../lib/_head-with-defaults";
23
- import { composeMetaTitle } from "../../lib/_compose-meta-title";
24
- import { BodyEndIslands } from "../../lib/_body-end-islands";
18
+ import { VersionsPageView } from "../../lib/_versions-page";
25
19
 
26
20
  export const frontmatter = { title: "Versions" };
27
21
 
@@ -50,53 +44,5 @@ interface PageArgs {
50
44
  }
51
45
 
52
46
  export default function LocaleVersionsPage({ params }: PageArgs): JSX.Element {
53
- const locale = params.locale;
54
- const pageTitle = t("version.page.title", locale);
55
-
56
- const labels: VersionsPageLabels = {
57
- pageTitle,
58
- latestTitle: t("version.page.latest.title", locale),
59
- latestDescription: t("version.page.latest.description", locale),
60
- latestLink: t("version.page.latest.link", locale),
61
- pastTitle: t("version.page.past.title", locale),
62
- pastDescription: t("version.page.past.description", locale),
63
- unmaintained: t("version.page.unmaintained", locale),
64
- unreleased: t("version.page.unreleased", locale),
65
- versionCol: t("version.switcher.label", locale),
66
- statusCol: t("version.page.status", locale),
67
- docsCol: t("version.page.docs", locale),
68
- };
69
-
70
- const latestHref = withBase(`/${locale}/docs/getting-started`);
71
-
72
- const versions: VersionPageEntry[] = settings.versions
73
- ? settings.versions.map((v) => ({
74
- slug: v.slug,
75
- label: v.label ?? v.slug,
76
- // Version prefix comes BEFORE the locale — the only routed shape is
77
- // pages/v/[version]/{locale}/docs/...; /{locale}/v/... has no route.
78
- docsHref: withBase(`/v/${v.slug}/${locale}/docs/getting-started/`),
79
- banner: v.banner as "unmaintained" | "unreleased" | undefined,
80
- }))
81
- : [];
82
-
83
- return (
84
- <DocLayoutWithDefaults
85
- title={composeMetaTitle(pageTitle)}
86
- head={<HeadWithDefaults title={pageTitle} />}
87
- lang={locale}
88
- noindex={settings.noindex}
89
- hideSidebar={true}
90
- hideToc={true}
91
- headerOverride={<HeaderWithDefaults lang={locale} currentPath={withBase(`/${locale}/docs/versions`)} />}
92
- footerOverride={<FooterWithDefaults lang={locale} />}
93
- bodyEndComponents={<BodyEndIslands basePath={settings.base ?? "/"} />}
94
- >
95
- <VersionsPageContent
96
- latestHref={latestHref}
97
- versions={versions}
98
- labels={labels}
99
- />
100
- </DocLayoutWithDefaults>
101
- );
47
+ return <VersionsPageView locale={params.locale} />;
102
48
  }
@@ -2,77 +2,19 @@
2
2
  /** @jsxImportSource preact */
3
3
  // Page module for the default-locale versions index route.
4
4
  //
5
- // Default-locale (EN) documentation versions page. Static route — no
6
- // paths() export needed. Lists the latest version and any past versions
7
- // configured in settings.versions.
5
+ // Default-locale documentation versions page. Static route — no paths()
6
+ // export needed. Lists the latest version and any past versions configured
7
+ // in settings.versions.
8
8
  //
9
- // Data flow:
10
- // settings.versions → build version entry list
11
- // t() for locale strings → labels bag passed to VersionsPageContent
9
+ // Rendering is shared with the locale-prefixed route via
10
+ // pages/lib/_versions-page.tsx (#2010).
12
11
 
13
- import { settings } from "@/config/settings";
14
- import { defaultLocale, t } from "@/config/i18n";
15
- import { withBase } from "@/utils/base";
16
- import { DocLayoutWithDefaults } from "@takazudo/zudo-doc/doclayout";
17
- import { VersionsPageContent } from "@takazudo/zudo-doc/nav-indexing";
18
- import type { VersionPageEntry, VersionsPageLabels } from "@takazudo/zudo-doc/nav-indexing";
12
+ import { defaultLocale } from "@/config/i18n";
19
13
  import type { JSX } from "preact";
20
- import { FooterWithDefaults } from "../lib/_footer-with-defaults";
21
- import { HeaderWithDefaults } from "../lib/_header-with-defaults";
22
- import { HeadWithDefaults } from "../lib/_head-with-defaults";
23
- import { composeMetaTitle } from "../lib/_compose-meta-title";
24
- import { BodyEndIslands } from "../lib/_body-end-islands";
14
+ import { VersionsPageView } from "../lib/_versions-page";
25
15
 
26
16
  export const frontmatter = { title: "Versions" };
27
17
 
28
18
  export default function VersionsPage(): JSX.Element {
29
- const locale = defaultLocale;
30
- const pageTitle = t("version.page.title", locale);
31
-
32
- const labels: VersionsPageLabels = {
33
- pageTitle,
34
- latestTitle: t("version.page.latest.title", locale),
35
- latestDescription: t("version.page.latest.description", locale),
36
- latestLink: t("version.page.latest.link", locale),
37
- pastTitle: t("version.page.past.title", locale),
38
- pastDescription: t("version.page.past.description", locale),
39
- unmaintained: t("version.page.unmaintained", locale),
40
- unreleased: t("version.page.unreleased", locale),
41
- versionCol: t("version.switcher.label", locale),
42
- statusCol: t("version.page.status", locale),
43
- docsCol: t("version.page.docs", locale),
44
- };
45
-
46
- // Latest docs href — points to the default docs entry point
47
- const latestHref = withBase("/docs/getting-started");
48
-
49
- // Past version entries from settings
50
- const versions: VersionPageEntry[] = settings.versions
51
- ? settings.versions.map((v) => ({
52
- slug: v.slug,
53
- label: v.label ?? v.slug,
54
- docsHref: withBase(`/v/${v.slug}/docs/getting-started/`),
55
- banner: v.banner as "unmaintained" | "unreleased" | undefined,
56
- }))
57
- : [];
58
-
59
- return (
60
- <DocLayoutWithDefaults
61
- title={composeMetaTitle(pageTitle)}
62
- head={<HeadWithDefaults title={pageTitle} />}
63
- lang={locale}
64
- noindex={settings.noindex}
65
- hideSidebar={true}
66
- hideToc={true}
67
- headerOverride={<HeaderWithDefaults lang={locale} currentPath={withBase("/docs/versions")} />}
68
- footerOverride={<FooterWithDefaults lang={locale} />}
69
- bodyEndComponents={<BodyEndIslands basePath={settings.base ?? "/"} />}
70
- >
71
- <VersionsPageContent
72
- latestHref={latestHref}
73
- versions={versions}
74
- labels={labels}
75
- />
76
- </DocLayoutWithDefaults>
77
- );
19
+ return <VersionsPageView locale={defaultLocale} />;
78
20
  }
@@ -0,0 +1,79 @@
1
+ /** @jsxRuntime automatic */
2
+ /** @jsxImportSource preact */
3
+ // Shared renderer for the documentation-versions pages (#2010).
4
+ //
5
+ // Collapses the per-locale page pair:
6
+ // pages/docs/versions.tsx + pages/[locale]/docs/versions.tsx
7
+ // into one locale-parameterized renderer. The page files stay thin shells
8
+ // that own only their paths() param shapes; URL prefixes and the
9
+ // default-vs-locale href shapes live here.
10
+
11
+ import { settings } from "@/config/settings";
12
+ import { defaultLocale, t, type Locale } from "@/config/i18n";
13
+ import { withBase } from "@/utils/base";
14
+ import { DocLayoutWithDefaults } from "@takazudo/zudo-doc/doclayout";
15
+ import { VersionsPageContent } from "@takazudo/zudo-doc/nav-indexing";
16
+ import type { VersionPageEntry, VersionsPageLabels } from "@takazudo/zudo-doc/nav-indexing";
17
+ import type { JSX } from "preact";
18
+ import { FooterWithDefaults } from "./_footer-with-defaults";
19
+ import { HeaderWithDefaults } from "./_header-with-defaults";
20
+ import { HeadWithDefaults } from "./_head-with-defaults";
21
+ import { composeMetaTitle } from "./_compose-meta-title";
22
+ import { BodyEndIslands } from "./_body-end-islands";
23
+
24
+ /** Versions index page for one locale. Lists the latest version and any past
25
+ * versions configured in settings.versions. */
26
+ export function VersionsPageView({ locale }: { locale: string }): JSX.Element {
27
+ const isDefault = locale === defaultLocale;
28
+ const prefix = isDefault ? "" : `/${locale}`;
29
+ const pageTitle = t("version.page.title", locale);
30
+
31
+ const labels: VersionsPageLabels = {
32
+ pageTitle,
33
+ latestTitle: t("version.page.latest.title", locale),
34
+ latestDescription: t("version.page.latest.description", locale),
35
+ latestLink: t("version.page.latest.link", locale),
36
+ pastTitle: t("version.page.past.title", locale),
37
+ pastDescription: t("version.page.past.description", locale),
38
+ unmaintained: t("version.page.unmaintained", locale),
39
+ unreleased: t("version.page.unreleased", locale),
40
+ versionCol: t("version.switcher.label", locale),
41
+ statusCol: t("version.page.status", locale),
42
+ docsCol: t("version.page.docs", locale),
43
+ };
44
+
45
+ // Latest docs href — points to the default docs entry point
46
+ const latestHref = withBase(`${prefix}/docs/getting-started`);
47
+
48
+ // Past version entries from settings
49
+ const versions: VersionPageEntry[] = settings.versions
50
+ ? settings.versions.map((v) => ({
51
+ slug: v.slug,
52
+ label: v.label ?? v.slug,
53
+ // Version prefix comes BEFORE the locale — the only routed shape is
54
+ // pages/v/[version]/{locale}/docs/...; /{locale}/v/... has no route.
55
+ docsHref: withBase(`/v/${v.slug}${prefix}/docs/getting-started/`),
56
+ banner: v.banner as "unmaintained" | "unreleased" | undefined,
57
+ }))
58
+ : [];
59
+
60
+ return (
61
+ <DocLayoutWithDefaults
62
+ title={composeMetaTitle(pageTitle)}
63
+ head={<HeadWithDefaults title={pageTitle} />}
64
+ lang={locale}
65
+ noindex={settings.noindex}
66
+ hideSidebar={true}
67
+ hideToc={true}
68
+ headerOverride={<HeaderWithDefaults lang={locale as Locale} currentPath={withBase(`${prefix}/docs/versions`)} />}
69
+ footerOverride={<FooterWithDefaults lang={locale} />}
70
+ bodyEndComponents={<BodyEndIslands basePath={settings.base ?? "/"} />}
71
+ >
72
+ <VersionsPageContent
73
+ latestHref={latestHref}
74
+ versions={versions}
75
+ labels={labels}
76
+ />
77
+ </DocLayoutWithDefaults>
78
+ );
79
+ }
@@ -19,30 +19,21 @@
19
19
  //
20
20
  // Prev/next hrefs are pre-resolved to the versioned locale URL form
21
21
  // (e.g. /v/1.0/ja/docs/…) so the component needs no URL computation.
22
+ //
23
+ // Enumeration + per-entry derived data are built by the shared, memoized
24
+ // buildDocRouteEntries (#2010); rendering by the shared renderDocPage. This
25
+ // file owns only the route's nav source, its versioned-locale URL closure,
26
+ // and the param/prop shapes.
22
27
 
23
28
  import { settings } from "@/config/settings";
24
29
  import type { VersionConfig } from "@/config/settings";
25
- import { t } from "@/config/i18n";
26
- import { docsUrl, versionedDocsUrl, absoluteUrl } from "@/utils/base";
27
- import {
28
- buildNavTree,
29
- buildBreadcrumbs,
30
- collectAutoIndexNodes,
31
- type NavNode,
32
- } from "@/utils/docs";
33
- import { getNavSectionForSlug, getNavSubtree } from "@/utils/nav-scope";
34
- import { toRouteSlug, toSlugParams } from "@/utils/slug";
35
- // Locale-aware MDX components factory — see `pages/_mdx-components.ts`.
36
- import { createMdxComponents } from "../../../../_mdx-components";
30
+ import { type Locale } from "@/config/i18n";
31
+ import { versionedDocsUrl } from "@/utils/base";
37
32
  import type { JSX } from "preact";
38
33
  import { resolveVersionedLocaleSource } from "../../../../lib/_nav-source-docs";
39
- import { extractHeadings } from "../../../../lib/_extract-headings";
40
- import type { DocPageEntry, AutoIndexNode, DocPageEntryProps, DocPageAutoIndexProps } from "../../../../lib/doc-page-props";
41
- import { DocMetainfoArea } from "../../../../lib/_doc-metainfo-area";
42
- import { buildInlineVersionSwitcher } from "../../../../lib/_inline-version-switcher";
43
- import { DocContentHeader } from "../../../../lib/_doc-content-header";
44
- import { DocPageShell } from "../../../../lib/_doc-page-shell";
45
- import { resolveDocPrevNext, flattenSubtree, rewriteNavHref, remapNavChildHrefs } from "../../../../lib/_doc-route-paths";
34
+ import type { DocPageEntryProps, DocPageAutoIndexProps } from "../../../../lib/doc-page-props";
35
+ import { buildDocRouteEntries } from "../../../../lib/_doc-route-entries";
36
+ import { renderDocPage } from "../../../../lib/_doc-page-renderer";
46
37
 
47
38
  export const frontmatter = { title: "Docs" };
48
39
 
@@ -50,8 +41,6 @@ export const frontmatter = { title: "Docs" };
50
41
  // Types
51
42
  // ---------------------------------------------------------------------------
52
43
 
53
- // DocPageEntry, AutoIndexNode imported from pages/lib/doc-page-props.ts
54
-
55
44
  /** Route-specific extra fields — present on both branches of the union. */
56
45
  interface VersionedLocaleDocPageExtra {
57
46
  /** The version config for the active version. */
@@ -102,87 +91,43 @@ export function paths(): Array<{
102
91
 
103
92
  // Identity-stable, locale-first merge over the version's EN base. Reused
104
93
  // across the route's per-page paths() invocations so buildNavTree's
105
- // identity fast-path applies — see pages/lib/_nav-source-docs.ts (#1902).
106
- const { docs: allDocs, navDocs, categoryMeta, localeSlugSet } =
107
- resolveVersionedLocaleSource(version.slug, version.docsDir, locale, localeDir, {
108
- applyDefaultLocaleOnlyFilter: true,
109
- keepUnlisted: true,
110
- });
111
- // isFallback: page came from base docs, not the locale collection.
112
- // toRouteSlug keeps Set keys and lookup keys in lockstep — a versioned
113
- // root index has entry.slug="index" (storage form) but route slug="" so
114
- // d.id would diverge from the lookup key after the #1891 toRouteSlug flip.
115
- const fallbackSlugs = new Set(
116
- allDocs
117
- .filter((d) => !localeSlugSet.has(d.data.slug ?? toRouteSlug(d.slug)))
118
- .map((d) => d.data.slug ?? toRouteSlug(d.slug)),
94
+ // identity fast-path and the buildDocRouteEntries memo apply — see
95
+ // pages/lib/_nav-source-docs.ts (#1902).
96
+ const source = resolveVersionedLocaleSource(
97
+ version.slug,
98
+ version.docsDir,
99
+ locale as Locale,
100
+ localeDir,
101
+ { applyDefaultLocaleOnlyFilter: true, keepUnlisted: true },
119
102
  );
120
103
 
121
- const tree = buildNavTree(navDocs, locale, categoryMeta);
122
-
123
104
  // URL closure for THIS (version, locale) — every versioned-locale href
124
105
  // (prev/next, breadcrumb crumbs, auto-index cards) is produced by this
125
106
  // single function bound to the version slug + locale, resolved against
126
- // this route's own `tree` (#1916).
127
- const urlFor = (s: string): string => versionedDocsUrl(s, version.slug, locale);
128
-
129
- // Regular doc pages
130
- for (const entry of allDocs) {
131
- // A `category_no_page` index.mdx is metadata-only — kept in the nav
132
- // tree for breadcrumbs but emits no route (zfb retains every .mdx as a
133
- // collection entry, so the skip must be explicit).
134
- if (entry.data.category_no_page === true) continue;
135
- const slug = entry.data.slug ?? toRouteSlug(entry.slug);
136
- const isFallback = fallbackSlugs.has(slug);
137
- const entryContentDir = isFallback ? version.docsDir : (localeDir ?? version.docsDir);
138
-
139
- const navSection = getNavSectionForSlug(slug);
140
- const subtree = getNavSubtree(tree, navSection);
141
-
142
- const { prev: prevNode, next: nextNode } = resolveDocPrevNext(
143
- tree,
144
- flattenSubtree(subtree),
145
- slug,
146
- entry.data,
147
- );
148
-
149
- result.push({
150
- params: { version: version.slug, locale, slug: toSlugParams(slug) },
151
- props: {
152
- kind: "entry",
153
- entry,
154
- version,
155
- contentDir: entryContentDir,
156
- isFallback,
157
- // #1916 #1: breadcrumb crumbs remapped to the versioned locale URL.
158
- breadcrumbs: buildBreadcrumbs(tree, slug, locale, urlFor),
159
- prev: rewriteNavHref(prevNode, urlFor),
160
- next: rewriteNavHref(nextNode, urlFor),
161
- headings: extractHeadings(entry.body ?? ""),
162
- },
163
- });
164
- }
165
-
166
- // Auto-generated index pages for categories without index.mdx
167
- for (const node of collectAutoIndexNodes(tree)) {
107
+ // this route's own tree (#1916).
108
+ const urlFor = (s: string): string => versionedDocsUrl(s, version.slug, locale as Locale);
109
+
110
+ for (const item of buildDocRouteEntries({
111
+ source,
112
+ locale: locale as Locale,
113
+ routeSig: `v-locale-docs;${version.slug};${locale}`,
114
+ urlFor,
115
+ })) {
116
+ // isFallback: page came from the version's base EN docs, not the
117
+ // locale collection. Always false for autoIndex items.
118
+ const extra: VersionedLocaleDocPageExtra = {
119
+ version,
120
+ contentDir: item.isFallback
121
+ ? version.docsDir
122
+ : (localeDir ?? version.docsDir),
123
+ isFallback: item.isFallback,
124
+ };
168
125
  result.push({
169
- params: { version: version.slug, locale, slug: toSlugParams(node.slug) },
170
- props: {
171
- kind: "autoIndex",
172
- autoIndex: {
173
- ...node,
174
- // #1916 #2: child-card hrefs ALWAYS resolve to the versioned URL.
175
- children: remapNavChildHrefs(node.children, urlFor) as NavNode[],
176
- } as AutoIndexNode,
177
- version,
178
- contentDir: localeDir ?? version.docsDir,
179
- isFallback: false,
180
- // #1916 #1: breadcrumb crumbs remapped to the versioned locale URL.
181
- breadcrumbs: buildBreadcrumbs(tree, node.slug, locale, urlFor),
182
- prev: null,
183
- next: null,
184
- headings: [],
185
- },
126
+ params: { version: version.slug, locale, slug: item.slugParams },
127
+ props:
128
+ item.props.kind === "entry"
129
+ ? { ...item.props, ...extra }
130
+ : { ...item.props, ...extra },
186
131
  });
187
132
  }
188
133
  }
@@ -198,99 +143,9 @@ export function paths(): Array<{
198
143
  type PageArgs = DocPageProps & { params: { version: string; locale: string; slug: string[] } };
199
144
 
200
145
  export default function VersionedLocaleDocsPage(props: PageArgs): JSX.Element {
201
- const { breadcrumbs, prev, next, headings, version, isFallback } = props;
202
- const locale = props.params.locale;
203
-
204
- const slug = props.kind === "autoIndex"
205
- ? props.autoIndex.slug
206
- : (props.entry.data.slug ?? toRouteSlug(props.entry.slug));
207
-
208
- const title = props.kind === "autoIndex" ? props.autoIndex.label : props.entry.data.title;
209
- const description = props.kind === "autoIndex" ? props.autoIndex.description : props.entry.data.description;
210
-
211
- // Locale-aware components bag — creates nav wrappers bound to the active
212
- // locale so CategoryNav/CategoryTreeNav/SiteTreeNav query the right collection.
213
- const components = createMdxComponents(locale);
214
-
215
- // #1916 #2: child cards already carry versioned hrefs from paths(); just
216
- // filter to renderable nodes here.
217
- const autoIndexChildren = props.kind === "autoIndex"
218
- ? props.autoIndex.children.filter((c: NavNode) => c.hasPage || c.children.length > 0)
219
- : [];
220
-
221
- // Version banner: drives the `<VersionBanner>` element inside
222
- // DocLayoutWithDefaults when `version.banner` is "unmaintained" or
223
- // "unreleased". The banner links out to the latest version of the
224
- // current page (slug-preserving — strips the /v/{version}/ prefix,
225
- // keeps the /{locale}/ locale prefix).
226
- const versionBannerType = version.banner ? version.banner : undefined;
227
- const versionBannerLatestUrl = versionBannerType
228
- ? docsUrl(slug, locale)
229
- : undefined;
230
- const versionBannerLabels = versionBannerType
231
- ? {
232
- message:
233
- versionBannerType === "unmaintained"
234
- ? t("version.banner.unmaintained", locale)
235
- : t("version.banner.unreleased", locale),
236
- latestLink: t("version.banner.latestLink", locale),
237
- }
238
- : undefined;
239
-
240
- // Canonical URL — versioned locale pages use the versioned locale URL as canonical.
241
- const currentPath = versionedDocsUrl(slug, version.slug, locale);
242
- const canonical = absoluteUrl(currentPath);
243
-
244
- // Persist key: locale + nav-section so the sidebar DOM node is reused
245
- // across same-locale + same-section navigations only. No sanitizer needed —
246
- // both lang (BCP-47 locale string) and navSection (filesystem-derived
247
- // kebab-case slug) come from controlled, trusted sources.
248
- const navSection = getNavSectionForSlug(slug);
249
- const hideSidebar = props.kind === "entry" ? props.entry.data.hide_sidebar : undefined;
250
- const sidebarPersistKey = hideSidebar
251
- ? undefined
252
- : `sidebar-${locale}-${navSection ?? "default"}`;
253
-
254
- return (
255
- <DocPageShell
256
- kind={props.kind}
257
- locale={locale}
258
- slug={slug}
259
- title={title}
260
- description={description}
261
- canonical={canonical}
262
- breadcrumbs={breadcrumbs}
263
- prev={prev}
264
- next={next}
265
- headings={headings}
266
- navSection={navSection}
267
- sidebarPersistKey={sidebarPersistKey}
268
- hideSidebar={hideSidebar}
269
- hideToc={props.kind === "entry" ? props.entry.data.hide_toc : undefined}
270
- currentPath={currentPath}
271
- currentVersion={version.slug}
272
- versionSwitcher={buildInlineVersionSwitcher(slug, locale, version.slug)}
273
- versionBanner={versionBannerType}
274
- versionBannerLatestUrl={versionBannerLatestUrl}
275
- versionBannerLabels={versionBannerLabels}
276
- autoIndexLabel={props.kind === "autoIndex" ? props.autoIndex.label : undefined}
277
- autoIndexChildren={autoIndexChildren}
278
- // #1916 #6: add DocMetainfoArea for chrome parity with the other 3
279
- // doc routes (its absence here was accidental drift, not intentional).
280
- metainfoSlot={
281
- props.kind === "autoIndex" ? <DocMetainfoArea slug={slug} locale={locale} /> : null
282
- }
283
- contentHeaderSlot={
284
- props.kind === "entry" ? (
285
- <DocContentHeader entry={props.entry} slug={slug} locale={locale} isFallback={isFallback} />
286
- ) : undefined
287
- }
288
- contentSlot={
289
- props.kind === "entry" ? <props.entry.Content components={components} /> : undefined
290
- }
291
- // #1916 #5: doc-history hidden on versioned pages until versioned
292
- // history is supported.
293
- docHistorySlot={null}
294
- />
295
- );
146
+ return renderDocPage(props, {
147
+ locale: props.params.locale as Locale,
148
+ version: props.version,
149
+ isFallback: props.isFallback,
150
+ });
296
151
  }