create-zudo-doc 0.2.0 → 0.2.2

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 (83) hide show
  1. package/dist/api.js +4 -1
  2. package/dist/cli.js +4 -6
  3. package/dist/compose.d.ts +2 -3
  4. package/dist/compose.js +7 -4
  5. package/dist/features/tauri.d.ts +10 -5
  6. package/dist/features/tauri.js +49 -6
  7. package/dist/preset.js +11 -0
  8. package/dist/prompts.js +2 -6
  9. package/dist/scaffold.js +15 -9
  10. package/dist/settings-gen.js +9 -6
  11. package/dist/utils.d.ts +8 -0
  12. package/dist/utils.js +25 -0
  13. package/dist/zfb-config-gen.js +11 -50
  14. package/package.json +1 -1
  15. package/templates/base/pages/_data.ts +10 -23
  16. package/templates/base/pages/docs/[[...slug]].tsx +27 -168
  17. package/templates/base/pages/lib/_body-end-islands.tsx +3 -0
  18. package/templates/base/pages/lib/_doc-content-header.tsx +24 -4
  19. package/templates/base/pages/lib/_doc-history-area.tsx +21 -5
  20. package/templates/base/pages/lib/_doc-metainfo-area.tsx +22 -2
  21. package/templates/base/pages/lib/_doc-page-renderer.tsx +192 -0
  22. package/templates/base/pages/lib/_doc-page-shell.tsx +3 -2
  23. package/templates/base/pages/lib/_doc-route-entries.ts +188 -0
  24. package/templates/base/pages/lib/_doc-tags-area.tsx +7 -2
  25. package/templates/base/pages/lib/_footer-with-defaults.tsx +38 -27
  26. package/templates/base/pages/lib/_head-with-defaults.tsx +7 -10
  27. package/templates/base/pages/lib/_header-with-defaults.tsx +54 -89
  28. package/templates/base/pages/lib/_inline-version-switcher.tsx +5 -4
  29. package/templates/base/pages/lib/_nav-data-prep.ts +137 -0
  30. package/templates/base/pages/lib/_nav-source-docs.ts +10 -6
  31. package/templates/base/pages/lib/_search-widget-script.ts +32 -9
  32. package/templates/base/pages/lib/_sidebar-with-defaults.tsx +15 -60
  33. package/templates/base/pages/lib/locale-merge.ts +1 -1
  34. package/templates/base/pages/lib/route-enumerators.ts +11 -7
  35. package/templates/base/plugins/connect-adapter.mjs +30 -1
  36. package/templates/base/plugins/copy-public-plugin.mjs +10 -2
  37. package/templates/base/plugins/search-index-plugin.mjs +20 -8
  38. package/templates/base/src/components/ai-chat-modal.tsx +2 -0
  39. package/templates/base/src/components/doc-history.tsx +2 -0
  40. package/templates/base/src/components/image-enlarge.tsx +2 -0
  41. package/templates/base/src/components/sidebar-toggle.tsx +1 -1
  42. package/templates/base/src/components/sidebar-tree.tsx +11 -5
  43. package/templates/base/src/components/theme-toggle.tsx +18 -102
  44. package/templates/base/src/config/color-schemes.ts +4 -0
  45. package/templates/base/src/config/docs-schema.ts +94 -0
  46. package/templates/base/src/config/i18n.ts +10 -3
  47. package/templates/base/src/styles/global.css +14 -0
  48. package/templates/base/src/types/docs-entry.ts +8 -26
  49. package/templates/base/src/utils/base.ts +5 -3
  50. package/templates/base/src/utils/docs.ts +144 -169
  51. package/templates/base/zfb-shim.d.ts +167 -0
  52. package/templates/features/claudeResources/files/plugins/claude-resources-plugin.mjs +20 -110
  53. package/templates/features/claudeResources/files/src/integrations/claude-resources/generate.ts +62 -38
  54. package/templates/features/designTokenPanel/files/src/config/design-token-panel-config.ts +34 -8
  55. package/templates/features/docHistory/files/plugins/doc-history-plugin.mjs +27 -45
  56. package/templates/features/docHistory/files/src/components/doc-history.tsx +30 -8
  57. package/templates/features/docTags/files/pages/[locale]/docs/tags/[tag].tsx +6 -74
  58. package/templates/features/docTags/files/pages/[locale]/docs/tags/index.tsx +6 -77
  59. package/templates/features/docTags/files/pages/docs/tags/[tag].tsx +7 -69
  60. package/templates/features/docTags/files/pages/docs/tags/index.tsx +6 -76
  61. package/templates/features/docTags/files/pages/lib/_tag-pages.tsx +201 -0
  62. package/templates/features/i18n/files/pages/[locale]/docs/[[...slug]].tsx +41 -179
  63. package/templates/features/i18n/files/pages/[locale]/index.tsx +5 -5
  64. package/templates/features/imageEnlarge/files/src/components/image-enlarge.tsx +2 -0
  65. package/templates/features/llmsTxt/files/plugins/llms-txt-plugin.mjs +33 -21
  66. package/templates/features/sidebarToggle/files/src/components/desktop-sidebar-toggle.tsx +1 -1
  67. package/templates/features/tauri/files/src/components/find-in-page-init.tsx +9 -3
  68. package/templates/features/versioning/files/pages/[locale]/docs/versions.tsx +5 -59
  69. package/templates/features/versioning/files/pages/docs/versions.tsx +8 -66
  70. package/templates/features/versioning/files/pages/lib/_versions-page.tsx +79 -0
  71. package/templates/features/versioning/files/pages/v/[version]/[locale]/docs/[[...slug]].tsx +46 -191
  72. package/templates/features/versioning/files/pages/v/[version]/docs/[[...slug]].tsx +31 -173
  73. package/templates/base/src/components/content/heading-h3.tsx +0 -20
  74. package/templates/base/src/hooks/use-active-heading.ts +0 -133
  75. package/templates/base/src/plugins/docs-source-map.ts +0 -103
  76. package/templates/base/src/plugins/hast-utils.ts +0 -10
  77. package/templates/base/src/plugins/rehype-code-title.ts +0 -50
  78. package/templates/base/src/plugins/rehype-heading-links.ts +0 -53
  79. package/templates/base/src/plugins/rehype-mermaid.ts +0 -41
  80. package/templates/base/src/plugins/url-utils.ts +0 -4
  81. package/templates/base/src/utils/dedent.ts +0 -24
  82. package/templates/features/docHistory/files/src/utils/doc-history.ts +0 -180
  83. package/templates/features/sidebarResizer/files/src/scripts/sidebar-resizer.ts +0 -198
@@ -11,29 +11,14 @@
11
11
  // params: { locale: string; tag: string }
12
12
  // props: { tagInfo: TagInfo }
13
13
  //
14
+ // Tag collection + rendering are shared with the default-locale route via
15
+ // pages/lib/_tag-pages.tsx (#2010) — this file owns only the param shape.
14
16
  // Fallback strategy: see pages/lib/locale-merge.ts for full details.
15
- // Locale docs take priority; base docs fill in missing slugs.
16
- // Build tag map; emit one route per (locale, tag) pair.
17
17
 
18
- import { mergeLocaleDocs } from "../../../lib/locale-merge";
19
- import { loadDocs } from "../../../_data";
20
- import { collectTags } from "@/utils/tags";
21
- import type { TagInfo } from "@/utils/tags";
22
- import { toRouteSlug } from "@/utils/slug";
23
- import { t } from "@/config/i18n";
24
- import { withBase, docsUrl } from "@/utils/base";
25
18
  import { settings } from "@/config/settings";
26
- import { DocLayoutWithDefaults } from "@takazudo/zudo-doc/doclayout";
27
- import { Breadcrumb } from "@takazudo/zudo-doc/breadcrumb";
28
- import type { BreadcrumbItem } from "@takazudo/zudo-doc/breadcrumb";
29
- import { DocCardGrid } from "@takazudo/zudo-doc/nav-indexing";
19
+ import type { TagInfo } from "@/utils/tags";
30
20
  import type { JSX } from "preact";
31
- import { FooterWithDefaults } from "../../../lib/_footer-with-defaults";
32
- import { HeaderWithDefaults } from "../../../lib/_header-with-defaults";
33
- import { HeadWithDefaults } from "../../../lib/_head-with-defaults";
34
- import { composeMetaTitle } from "../../../lib/_compose-meta-title";
35
- import { DocHistoryArea } from "../../../lib/_doc-history-area";
36
- import { BodyEndIslands } from "../../../lib/_body-end-islands";
21
+ import { collectTagMapForLocale, TagDetailPageView } from "../../../lib/_tag-pages";
37
22
 
38
23
  export const frontmatter = { title: "Tag" };
39
24
 
@@ -48,18 +33,7 @@ export function paths(): Array<{
48
33
  }> = [];
49
34
 
50
35
  for (const locale of Object.keys(settings.locales)) {
51
- const { docs: mergedDocs } = mergeLocaleDocs({
52
- baseDocs: loadDocs("docs").filter((d) => !d.data.draft),
53
- localeDocs: loadDocs(`docs-${locale}`).filter((d) => !d.data.draft),
54
- applyDefaultLocaleOnlyFilter: true,
55
- });
56
- // category_no_page index files build no route — drop them AFTER the merge
57
- // so a locale override carrying the flag first wins the merge (suppressing
58
- // the base doc); pre-merge filtering would drop it from localeSlugSet and
59
- // the unflagged base doc would resurface as a card linking to a locale
60
- // route the docs route never builds.
61
- const docs = mergedDocs.filter((d) => !d.data.category_no_page);
62
- const tagMap = collectTags(docs, (id, data) => data.slug ?? toRouteSlug(id));
36
+ const tagMap = collectTagMapForLocale(locale);
63
37
 
64
38
  for (const [tag, tagInfo] of tagMap.entries()) {
65
39
  result.push({
@@ -81,47 +55,5 @@ export default function LocaleDocTagPage({
81
55
  params,
82
56
  tagInfo,
83
57
  }: PageProps): JSX.Element {
84
- const { locale, tag } = params;
85
-
86
- const countText =
87
- tagInfo.count === 1
88
- ? t("doc.pageCountSingle", locale).replace("{count}", String(tagInfo.count))
89
- : t("doc.pageCount", locale).replace("{count}", String(tagInfo.count));
90
-
91
- const pageTitle = `${t("doc.taggedWith", locale)}: ${tag}`;
92
-
93
- const breadcrumbItems: BreadcrumbItem[] = [
94
- { label: "Docs" },
95
- {
96
- label: t("doc.allTags", locale),
97
- href: withBase(`/${locale}/docs/tags`),
98
- },
99
- { label: tag },
100
- ];
101
-
102
- const cardItems = tagInfo.docs.map((doc) => ({
103
- href: docsUrl(doc.slug, locale),
104
- title: doc.title,
105
- description: doc.description,
106
- }));
107
-
108
- return (
109
- <DocLayoutWithDefaults
110
- title={composeMetaTitle(pageTitle)}
111
- head={<HeadWithDefaults title={pageTitle} />}
112
- lang={locale}
113
- noindex={settings.noindex}
114
- hideSidebar={true}
115
- hideToc={true}
116
- headerOverride={<HeaderWithDefaults lang={locale} currentPath={withBase(`/${locale}/docs/tags/${tag}`)} />}
117
- breadcrumbOverride={<Breadcrumb items={breadcrumbItems} />}
118
- footerOverride={<FooterWithDefaults lang={locale} />}
119
- bodyEndComponents={<BodyEndIslands basePath={settings.base ?? "/"} />}
120
- >
121
- <h1 class="text-heading font-bold mb-vsp-xs">{pageTitle}</h1>
122
- <p class="text-muted mb-vsp-lg">{countText}</p>
123
- <DocCardGrid ariaLabel={pageTitle} items={cardItems} />
124
- <DocHistoryArea slug={`tags/${tag}`} locale={locale} />
125
- </DocLayoutWithDefaults>
126
- );
58
+ return <TagDetailPageView locale={params.locale} tag={params.tag} tagInfo={tagInfo} />;
127
59
  }
@@ -3,36 +3,21 @@
3
3
  // Page module for the locale-prefixed "All Tags" index route.
4
4
  //
5
5
  // Non-default-locale "All Tags" index page. paths() emits one route per
6
- // locale defined in settings.locales (English has no /en prefix — it is
7
- // handled by pages/docs/tags/index.tsx). The component recomputes the tag
6
+ // locale defined in settings.locales (the default locale has no prefix — it
7
+ // is handled by pages/docs/tags/index.tsx). The component recomputes the tag
8
8
  // map at render time using a locale-doc + base-doc fallback strategy
9
9
  // (see pages/lib/locale-merge.ts for the merge logic).
10
10
  //
11
- // Fallback strategy (locale first, base as fill): see pages/lib/locale-merge.ts
11
+ // Tag collection + rendering are shared with the default-locale route via
12
+ // pages/lib/_tag-pages.tsx (#2010).
12
13
  //
13
14
  // paths() contract (zfb ADR-004 — synchronous):
14
15
  // params: { locale: string }
15
16
  // props: (none — tag map computed at render time)
16
17
 
17
- import { mergeLocaleDocs } from "../../../lib/locale-merge";
18
- import { loadDocs } from "../../../_data";
19
- import { collectTags } from "@/utils/tags";
20
- import { toRouteSlug } from "@/utils/slug";
21
- import { t } from "@/config/i18n";
22
- import { withBase } from "@/utils/base";
23
18
  import { settings } from "@/config/settings";
24
- import { DocLayoutWithDefaults } from "@takazudo/zudo-doc/doclayout";
25
- import { Breadcrumb } from "@takazudo/zudo-doc/breadcrumb";
26
- import type { BreadcrumbItem } from "@takazudo/zudo-doc/breadcrumb";
27
- import { TagNav } from "@takazudo/zudo-doc/nav-indexing";
28
- import type { TagItem, TagNavLabels } from "@takazudo/zudo-doc/nav-indexing";
29
19
  import type { JSX } from "preact";
30
- import { FooterWithDefaults } from "../../../lib/_footer-with-defaults";
31
- import { HeaderWithDefaults } from "../../../lib/_header-with-defaults";
32
- import { HeadWithDefaults } from "../../../lib/_head-with-defaults";
33
- import { composeMetaTitle } from "../../../lib/_compose-meta-title";
34
- import { DocHistoryArea } from "../../../lib/_doc-history-area";
35
- import { BodyEndIslands } from "../../../lib/_body-end-islands";
20
+ import { TagsIndexPageView } from "../../../lib/_tag-pages";
36
21
 
37
22
  export const frontmatter = { title: "All Tags" };
38
23
 
@@ -50,61 +35,5 @@ interface PageProps {
50
35
  export default function LocaleTagsIndexPage({
51
36
  params,
52
37
  }: PageProps): JSX.Element {
53
- const { locale } = params;
54
- const pageTitle = t("doc.allTags", locale);
55
-
56
- const { docs: mergedDocs } = mergeLocaleDocs({
57
- baseDocs: loadDocs("docs").filter((d) => !d.data.draft),
58
- localeDocs: loadDocs(`docs-${locale}`).filter((d) => !d.data.draft),
59
- applyDefaultLocaleOnlyFilter: true,
60
- });
61
- // category_no_page index files build no route — drop them AFTER the merge
62
- // so a locale override carrying the flag first wins the merge (suppressing
63
- // the base doc); pre-merge filtering would drop it from localeSlugSet and
64
- // the unflagged base doc would resurface as a card linking to a locale
65
- // route the docs route never builds.
66
- const docs = mergedDocs.filter((d) => !d.data.category_no_page);
67
- const tagMap = collectTags(docs, (id, data) => data.slug ?? toRouteSlug(id));
68
-
69
- const labels: TagNavLabels = {
70
- tags: t("doc.tags", locale),
71
- taggedWith: t("doc.taggedWith", locale),
72
- };
73
-
74
- // Sort alphabetically using the page locale — matches documented tag-nav sort order.
75
- const tags: TagItem[] = [...tagMap.values()]
76
- .sort((a, b) => a.tag.localeCompare(b.tag, locale))
77
- .map((info) => ({
78
- tag: info.tag,
79
- count: info.count,
80
- href: withBase(`/${locale}/docs/tags/${info.tag}`),
81
- }));
82
-
83
- const breadcrumbItems: BreadcrumbItem[] = [
84
- { label: "Docs" },
85
- { label: pageTitle },
86
- ];
87
-
88
- return (
89
- <DocLayoutWithDefaults
90
- title={composeMetaTitle(pageTitle)}
91
- head={<HeadWithDefaults title={pageTitle} />}
92
- lang={locale}
93
- noindex={settings.noindex}
94
- hideSidebar={true}
95
- hideToc={true}
96
- headerOverride={<HeaderWithDefaults lang={locale} currentPath={withBase(`/${locale}/docs/tags`)} />}
97
- breadcrumbOverride={<Breadcrumb items={breadcrumbItems} />}
98
- footerOverride={<FooterWithDefaults lang={locale} />}
99
- bodyEndComponents={<BodyEndIslands basePath={settings.base ?? "/"} />}
100
- >
101
- <h1 class="text-heading font-bold mb-vsp-lg">{pageTitle}</h1>
102
- {!settings.docTags || tags.length === 0 ? (
103
- <p class="text-muted">{t("doc.noTags", locale)}</p>
104
- ) : (
105
- <TagNav variant="all" tags={tags} labels={labels} />
106
- )}
107
- <DocHistoryArea slug="tags" locale={locale} />
108
- </DocLayoutWithDefaults>
109
- );
38
+ return <TagsIndexPageView locale={params.locale} />;
110
39
  }
@@ -2,7 +2,7 @@
2
2
  /** @jsxImportSource preact */
3
3
  // Page module for the default-locale per-tag detail route.
4
4
  //
5
- // Default-locale (en) per-tag detail page. paths() enumerates one route per
5
+ // Default-locale per-tag detail page. paths() enumerates one route per
6
6
  // unique tag in the "docs" collection and passes the resolved TagInfo as a
7
7
  // prop so the component has zero extra collection reads at render time.
8
8
  //
@@ -10,29 +10,13 @@
10
10
  // params: { tag: string }
11
11
  // props: { tagInfo: TagInfo }
12
12
  //
13
- // Data flow:
14
- // getCollection("docs") [sync] collectTags() one route per tag
15
- // render: DocCardGrid with pre-resolved TagInfo.docs items
13
+ // Tag collection + rendering are shared with the locale-prefixed route via
14
+ // pages/lib/_tag-pages.tsx (#2010) this file owns only the param shape.
16
15
 
17
- import { getCollection } from "zfb/content";
18
- import { collectTags } from "@/utils/tags";
16
+ import { defaultLocale } from "@/config/i18n";
19
17
  import type { TagInfo } from "@/utils/tags";
20
- import { toRouteSlug } from "@/utils/slug";
21
- import { t, defaultLocale } from "@/config/i18n";
22
- import { settings } from "@/config/settings";
23
- import { withBase, docsUrl } from "@/utils/base";
24
- import { DocLayoutWithDefaults } from "@takazudo/zudo-doc/doclayout";
25
- import { Breadcrumb } from "@takazudo/zudo-doc/breadcrumb";
26
- import type { BreadcrumbItem } from "@takazudo/zudo-doc/breadcrumb";
27
- import { DocCardGrid } from "@takazudo/zudo-doc/nav-indexing";
28
18
  import type { JSX } from "preact";
29
- import { bridgeDocsEntries, type ZfbDocsData } from "../../_data";
30
- import { FooterWithDefaults } from "../../lib/_footer-with-defaults";
31
- import { HeaderWithDefaults } from "../../lib/_header-with-defaults";
32
- import { HeadWithDefaults } from "../../lib/_head-with-defaults";
33
- import { composeMetaTitle } from "../../lib/_compose-meta-title";
34
- import { DocHistoryArea } from "../../lib/_doc-history-area";
35
- import { BodyEndIslands } from "../../lib/_body-end-islands";
19
+ import { collectTagMapForLocale, TagDetailPageView } from "../../lib/_tag-pages";
36
20
 
37
21
  export const frontmatter = { title: "Tag" };
38
22
 
@@ -41,14 +25,7 @@ export function paths(): Array<{
41
25
  params: { tag: string };
42
26
  props: { tagInfo: TagInfo };
43
27
  }> {
44
- const allDocs = bridgeDocsEntries(getCollection<ZfbDocsData>("docs"), "docs");
45
- // category_no_page index.mdx builds no route — drop it so a tag it carries
46
- // doesn't render a DocCard linking to a non-existent /docs/<cat>/ page.
47
- const docs = allDocs.filter(
48
- (doc) =>
49
- !doc.data.unlisted && !doc.data.draft && !doc.data.category_no_page,
50
- );
51
- const tagMap = collectTags(docs, (id, data) => data.slug ?? toRouteSlug(id));
28
+ const tagMap = collectTagMapForLocale(defaultLocale);
52
29
 
53
30
  return [...tagMap.entries()].map(([tag, tagInfo]) => ({
54
31
  params: { tag },
@@ -62,44 +39,5 @@ interface PageProps {
62
39
  }
63
40
 
64
41
  export default function DocTagPage({ params, tagInfo }: PageProps): JSX.Element {
65
- const { tag } = params;
66
- const locale = defaultLocale;
67
-
68
- const countText =
69
- tagInfo.count === 1
70
- ? t("doc.pageCountSingle", locale).replace("{count}", String(tagInfo.count))
71
- : t("doc.pageCount", locale).replace("{count}", String(tagInfo.count));
72
-
73
- const pageTitle = `${t("doc.taggedWith", locale)}: ${tag}`;
74
-
75
- const breadcrumbItems: BreadcrumbItem[] = [
76
- { label: "Docs" },
77
- { label: t("doc.allTags", locale), href: withBase("/docs/tags") },
78
- { label: tag },
79
- ];
80
-
81
- const cardItems = tagInfo.docs.map((doc) => ({
82
- href: docsUrl(doc.slug, locale),
83
- title: doc.title,
84
- description: doc.description,
85
- }));
86
-
87
- return (
88
- <DocLayoutWithDefaults
89
- title={composeMetaTitle(pageTitle)}
90
- head={<HeadWithDefaults title={pageTitle} />}
91
- noindex={settings.noindex}
92
- hideSidebar={true}
93
- hideToc={true}
94
- headerOverride={<HeaderWithDefaults lang={locale} currentPath={withBase(`/docs/tags/${tag}`)} />}
95
- breadcrumbOverride={<Breadcrumb items={breadcrumbItems} />}
96
- footerOverride={<FooterWithDefaults lang={locale} />}
97
- bodyEndComponents={<BodyEndIslands basePath={settings.base ?? "/"} />}
98
- >
99
- <h1 class="text-heading font-bold mb-vsp-xs">{pageTitle}</h1>
100
- <p class="text-muted mb-vsp-lg">{countText}</p>
101
- <DocCardGrid ariaLabel={pageTitle} items={cardItems} />
102
- <DocHistoryArea slug={`tags/${tag}`} locale={locale} />
103
- </DocLayoutWithDefaults>
104
- );
42
+ return <TagDetailPageView locale={defaultLocale} tag={params.tag} tagInfo={tagInfo} />;
105
43
  }
@@ -2,89 +2,19 @@
2
2
  /** @jsxImportSource preact */
3
3
  // Page module for the default-locale "All Tags" index route.
4
4
  //
5
- // Default-locale (en) "All Tags" index page. Collects every tag across the
5
+ // Default-locale "All Tags" index page. Collects every tag across the
6
6
  // "docs" collection, sorts them alphabetically, and renders a full tag cloud
7
7
  // via the v2 TagNav component. No dynamic params — single static route.
8
8
  //
9
- // Data flow:
10
- // getCollection("docs") [sync, zfb/content]
11
- // → collectTags() builds { tag → { count, docs[] } }
12
- // → sort by tag preserves sort parity with Astro original
13
- // → TagNav variant="all" renders the chip cloud
9
+ // Tag collection + rendering are shared with the locale-prefixed route via
10
+ // pages/lib/_tag-pages.tsx (#2010).
14
11
 
15
- import { getCollection } from "zfb/content";
16
- import { collectTags } from "@/utils/tags";
17
- import { toRouteSlug } from "@/utils/slug";
18
- import { t, defaultLocale } from "@/config/i18n";
19
- import { withBase } from "@/utils/base";
20
- import { settings } from "@/config/settings";
21
- import { DocLayoutWithDefaults } from "@takazudo/zudo-doc/doclayout";
22
- import { Breadcrumb } from "@takazudo/zudo-doc/breadcrumb";
23
- import type { BreadcrumbItem } from "@takazudo/zudo-doc/breadcrumb";
24
- import { TagNav } from "@takazudo/zudo-doc/nav-indexing";
25
- import type { TagItem, TagNavLabels } from "@takazudo/zudo-doc/nav-indexing";
12
+ import { defaultLocale } from "@/config/i18n";
26
13
  import type { JSX } from "preact";
27
- import { bridgeDocsEntries, type ZfbDocsData } from "../../_data";
28
- import { FooterWithDefaults } from "../../lib/_footer-with-defaults";
29
- import { HeaderWithDefaults } from "../../lib/_header-with-defaults";
30
- import { HeadWithDefaults } from "../../lib/_head-with-defaults";
31
- import { composeMetaTitle } from "../../lib/_compose-meta-title";
32
- import { DocHistoryArea } from "../../lib/_doc-history-area";
33
- import { BodyEndIslands } from "../../lib/_body-end-islands";
14
+ import { TagsIndexPageView } from "../../lib/_tag-pages";
34
15
 
35
16
  export const frontmatter = { title: "All Tags" };
36
17
 
37
18
  export default function DocsTagsIndexPage(): JSX.Element {
38
- const locale = defaultLocale;
39
- const pageTitle = t("doc.allTags", locale);
40
-
41
- const allDocs = bridgeDocsEntries(getCollection<ZfbDocsData>("docs"), "docs");
42
- // category_no_page index.mdx builds no route — drop it so a tag it carries
43
- // doesn't inflate the tag list with a card linking to a non-existent page.
44
- const docs = allDocs.filter(
45
- (doc) =>
46
- !doc.data.unlisted && !doc.data.draft && !doc.data.category_no_page,
47
- );
48
- const tagMap = collectTags(docs, (id, data) => data.slug ?? toRouteSlug(id));
49
-
50
- const labels: TagNavLabels = {
51
- tags: t("doc.tags", locale),
52
- taggedWith: t("doc.taggedWith", locale),
53
- };
54
-
55
- // Sort alphabetically — matches documented tag-nav sort order.
56
- const tags: TagItem[] = [...tagMap.values()]
57
- .sort((a, b) => a.tag.localeCompare(b.tag, locale))
58
- .map((info) => ({
59
- tag: info.tag,
60
- count: info.count,
61
- href: withBase(`/docs/tags/${info.tag}`),
62
- }));
63
-
64
- const breadcrumbItems: BreadcrumbItem[] = [
65
- { label: "Docs" },
66
- { label: pageTitle },
67
- ];
68
-
69
- return (
70
- <DocLayoutWithDefaults
71
- title={composeMetaTitle(pageTitle)}
72
- head={<HeadWithDefaults title={pageTitle} />}
73
- noindex={settings.noindex}
74
- hideSidebar={true}
75
- hideToc={true}
76
- headerOverride={<HeaderWithDefaults lang={locale} currentPath={withBase("/docs/tags")} />}
77
- breadcrumbOverride={<Breadcrumb items={breadcrumbItems} />}
78
- footerOverride={<FooterWithDefaults lang={locale} />}
79
- bodyEndComponents={<BodyEndIslands basePath={settings.base ?? "/"} />}
80
- >
81
- <h1 class="text-heading font-bold mb-vsp-lg">{pageTitle}</h1>
82
- {!settings.docTags || tags.length === 0 ? (
83
- <p class="text-muted">{t("doc.noTags", locale)}</p>
84
- ) : (
85
- <TagNav variant="all" tags={tags} labels={labels} />
86
- )}
87
- <DocHistoryArea slug="tags" locale={locale} />
88
- </DocLayoutWithDefaults>
89
- );
19
+ return <TagsIndexPageView locale={defaultLocale} />;
90
20
  }
@@ -0,0 +1,201 @@
1
+ /** @jsxRuntime automatic */
2
+ /** @jsxImportSource preact */
3
+ // Shared data + renderers for the doc-tags pages (#2010).
4
+ //
5
+ // Collapses the per-locale page pairs:
6
+ // pages/docs/tags/[tag].tsx + pages/[locale]/docs/tags/[tag].tsx
7
+ // pages/docs/tags/index.tsx + pages/[locale]/docs/tags/index.tsx
8
+ // into locale-parameterized helpers. The page files stay thin shells that own
9
+ // only their paths() param shapes; URL prefixes and the default-vs-locale
10
+ // data path branch live here.
11
+ //
12
+ // Data-path branch (kept exactly as the original pages had it):
13
+ // - Default locale: the "docs" collection directly, filtered
14
+ // unlisted/draft/category_no_page before tag collection.
15
+ // - Non-default locale: locale-first merge with base fallback
16
+ // (see locale-merge.ts) — draft-filtered inputs, unlisted dropped by the
17
+ // merge default, category_no_page filtered AFTER the merge so a locale
18
+ // override carrying the flag first wins the merge (suppressing the base
19
+ // doc); pre-merge filtering would drop it from localeSlugSet and the
20
+ // unflagged base doc would resurface as a card linking to a locale route
21
+ // the docs route never builds.
22
+
23
+ import { collectTags } from "@/utils/tags";
24
+ import type { TagInfo } from "@/utils/tags";
25
+ import { toRouteSlug } from "@/utils/slug";
26
+ import { t, defaultLocale } from "@/config/i18n";
27
+ import { settings } from "@/config/settings";
28
+ import { withBase, docsUrl } from "@/utils/base";
29
+ import { DocLayoutWithDefaults } from "@takazudo/zudo-doc/doclayout";
30
+ import { Breadcrumb } from "@takazudo/zudo-doc/breadcrumb";
31
+ import type { BreadcrumbItem } from "@takazudo/zudo-doc/breadcrumb";
32
+ import { DocCardGrid, TagNav } from "@takazudo/zudo-doc/nav-indexing";
33
+ import type { TagItem, TagNavLabels } from "@takazudo/zudo-doc/nav-indexing";
34
+ import type { JSX } from "preact";
35
+ import { FooterWithDefaults } from "./_footer-with-defaults";
36
+ import { HeaderWithDefaults } from "./_header-with-defaults";
37
+ import { HeadWithDefaults } from "./_head-with-defaults";
38
+ import { composeMetaTitle } from "./_compose-meta-title";
39
+ import { DocHistoryArea } from "./_doc-history-area";
40
+ import { BodyEndIslands } from "./_body-end-islands";
41
+ import { stableDocs, memoizeDerived } from "./_nav-source-cache";
42
+ import { mergeLocaleDocs } from "./locale-merge";
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // Tag collection
46
+ // ---------------------------------------------------------------------------
47
+
48
+ /**
49
+ * Build the { tag → TagInfo } map for one locale, preserving each original
50
+ * page's exact data path (see the module-header branch notes).
51
+ *
52
+ * Memoized on the snapshot-anchored stableDocs identity (same pattern as
53
+ * _nav-source-cache) so the collection bridging and tag aggregation run once
54
+ * per locale per build rather than once per built tag page.
55
+ */
56
+ export function collectTagMapForLocale(locale: string): Map<string, TagInfo> {
57
+ if (locale === defaultLocale) {
58
+ const baseDocs = stableDocs("docs");
59
+ return memoizeDerived([baseDocs], "tagMap;default", () => {
60
+ // category_no_page index.mdx builds no route — drop it so a tag it carries
61
+ // doesn't render a DocCard linking to a non-existent /docs/<cat>/ page.
62
+ const docs = baseDocs.filter(
63
+ (doc) =>
64
+ !doc.data.unlisted && !doc.data.draft && !doc.data.category_no_page,
65
+ );
66
+ return collectTags(docs, (id, data) => data.slug ?? toRouteSlug(id));
67
+ });
68
+ }
69
+
70
+ const baseDocs = stableDocs("docs");
71
+ const localeDocs = stableDocs(`docs-${locale}`);
72
+ return memoizeDerived([baseDocs, localeDocs], `tagMap;${locale}`, () => {
73
+ const { docs: mergedDocs } = mergeLocaleDocs({
74
+ baseDocs: baseDocs.filter((d) => !d.data.draft),
75
+ localeDocs: localeDocs.filter((d) => !d.data.draft),
76
+ applyDefaultLocaleOnlyFilter: true,
77
+ });
78
+ const docs = mergedDocs.filter((d) => !d.data.category_no_page);
79
+ return collectTags(docs, (id, data) => data.slug ?? toRouteSlug(id));
80
+ });
81
+ }
82
+
83
+ // ---------------------------------------------------------------------------
84
+ // Shared renderers
85
+ // ---------------------------------------------------------------------------
86
+
87
+ /** URL prefix for a locale: "" for the default locale, "/{locale}" otherwise. */
88
+ function localePrefix(locale: string): string {
89
+ return locale === defaultLocale ? "" : `/${locale}`;
90
+ }
91
+
92
+ /** Per-tag detail page (chip target). */
93
+ export function TagDetailPageView({
94
+ locale,
95
+ tag,
96
+ tagInfo,
97
+ }: {
98
+ locale: string;
99
+ tag: string;
100
+ tagInfo: TagInfo;
101
+ }): JSX.Element {
102
+ const isDefault = locale === defaultLocale;
103
+ const prefix = localePrefix(locale);
104
+
105
+ const countText =
106
+ tagInfo.count === 1
107
+ ? t("doc.pageCountSingle", locale).replace("{count}", String(tagInfo.count))
108
+ : t("doc.pageCount", locale).replace("{count}", String(tagInfo.count));
109
+
110
+ const pageTitle = `${t("doc.taggedWith", locale)}: ${tag}`;
111
+
112
+ const breadcrumbItems: BreadcrumbItem[] = [
113
+ { label: "Docs" },
114
+ { label: t("doc.allTags", locale), href: withBase(`${prefix}/docs/tags`) },
115
+ { label: tag },
116
+ ];
117
+
118
+ const cardItems = tagInfo.docs.map((doc) => ({
119
+ href: docsUrl(doc.slug, locale),
120
+ title: doc.title,
121
+ description: doc.description,
122
+ }));
123
+
124
+ return (
125
+ <DocLayoutWithDefaults
126
+ title={composeMetaTitle(pageTitle)}
127
+ head={<HeadWithDefaults title={pageTitle} />}
128
+ // The original default-locale page omitted `lang` entirely; passing
129
+ // undefined relies on Preact treating an undefined prop as absent.
130
+ lang={isDefault ? undefined : locale}
131
+ noindex={settings.noindex}
132
+ hideSidebar={true}
133
+ hideToc={true}
134
+ // Tag segment URL-encoded — emitted href/path sites only; route params
135
+ // stay raw (e.g. "type:guide" → "type%3Aguide").
136
+ headerOverride={<HeaderWithDefaults lang={locale} currentPath={withBase(`${prefix}/docs/tags/${encodeURIComponent(tag)}`)} />}
137
+ breadcrumbOverride={<Breadcrumb items={breadcrumbItems} />}
138
+ footerOverride={<FooterWithDefaults lang={locale} />}
139
+ bodyEndComponents={<BodyEndIslands basePath={settings.base ?? "/"} />}
140
+ >
141
+ <h1 class="text-heading font-bold mb-vsp-xs">{pageTitle}</h1>
142
+ <p class="text-muted mb-vsp-lg">{countText}</p>
143
+ <DocCardGrid ariaLabel={pageTitle} items={cardItems} />
144
+ <DocHistoryArea slug={`tags/${tag}`} locale={locale} />
145
+ </DocLayoutWithDefaults>
146
+ );
147
+ }
148
+
149
+ /** "All Tags" index page — computes the tag map at render time (matching the
150
+ * original pages, which had no props from paths()). */
151
+ export function TagsIndexPageView({ locale }: { locale: string }): JSX.Element {
152
+ const isDefault = locale === defaultLocale;
153
+ const prefix = localePrefix(locale);
154
+ const pageTitle = t("doc.allTags", locale);
155
+
156
+ const tagMap = collectTagMapForLocale(locale);
157
+
158
+ const labels: TagNavLabels = {
159
+ tags: t("doc.tags", locale),
160
+ taggedWith: t("doc.taggedWith", locale),
161
+ };
162
+
163
+ // Sort alphabetically using the page locale — matches documented tag-nav sort order.
164
+ const tags: TagItem[] = [...tagMap.values()]
165
+ .sort((a, b) => a.tag.localeCompare(b.tag, locale))
166
+ .map((info) => ({
167
+ tag: info.tag,
168
+ count: info.count,
169
+ // Tag segment URL-encoded — href sites only; route params stay raw.
170
+ href: withBase(`${prefix}/docs/tags/${encodeURIComponent(info.tag)}`),
171
+ }));
172
+
173
+ const breadcrumbItems: BreadcrumbItem[] = [
174
+ { label: "Docs" },
175
+ { label: pageTitle },
176
+ ];
177
+
178
+ return (
179
+ <DocLayoutWithDefaults
180
+ title={composeMetaTitle(pageTitle)}
181
+ head={<HeadWithDefaults title={pageTitle} />}
182
+ // Same undefined-≡-absent reliance as TagDetailPageView above.
183
+ lang={isDefault ? undefined : locale}
184
+ noindex={settings.noindex}
185
+ hideSidebar={true}
186
+ hideToc={true}
187
+ headerOverride={<HeaderWithDefaults lang={locale} currentPath={withBase(`${prefix}/docs/tags`)} />}
188
+ breadcrumbOverride={<Breadcrumb items={breadcrumbItems} />}
189
+ footerOverride={<FooterWithDefaults lang={locale} />}
190
+ bodyEndComponents={<BodyEndIslands basePath={settings.base ?? "/"} />}
191
+ >
192
+ <h1 class="text-heading font-bold mb-vsp-lg">{pageTitle}</h1>
193
+ {!settings.docTags || tags.length === 0 ? (
194
+ <p class="text-muted">{t("doc.noTags", locale)}</p>
195
+ ) : (
196
+ <TagNav variant="all" tags={tags} labels={labels} />
197
+ )}
198
+ <DocHistoryArea slug="tags" locale={locale} />
199
+ </DocLayoutWithDefaults>
200
+ );
201
+ }