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,180 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import type { DocHistoryEntry, DocHistoryData } from "@/types/doc-history";
|
|
4
|
+
import { collectMdFiles } from "./content-files";
|
|
5
|
+
|
|
6
|
+
/** Shared options to suppress git stderr noise */
|
|
7
|
+
const QUIET: { encoding: "utf-8"; stdio: ["pipe", "pipe", "pipe"] } = {
|
|
8
|
+
encoding: "utf-8",
|
|
9
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/** Cache the repo root to avoid repeated git calls */
|
|
13
|
+
let repoRootCache: string | null = null;
|
|
14
|
+
|
|
15
|
+
function getRepoRoot(): string {
|
|
16
|
+
if (repoRootCache) return repoRootCache;
|
|
17
|
+
repoRootCache = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
18
|
+
encoding: "utf-8",
|
|
19
|
+
}).trim();
|
|
20
|
+
return repoRootCache;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Convert an absolute path to a repo-relative path for git commands */
|
|
24
|
+
function toRepoRelative(absolutePath: string): string {
|
|
25
|
+
return path.relative(getRepoRoot(), absolutePath);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get the list of commit hashes that touched a file, newest first.
|
|
30
|
+
* Uses --follow to track renames.
|
|
31
|
+
* Limits to maxEntries commits (default 50).
|
|
32
|
+
*/
|
|
33
|
+
export function getFileCommits(
|
|
34
|
+
filePath: string,
|
|
35
|
+
maxEntries = 50,
|
|
36
|
+
): string[] {
|
|
37
|
+
try {
|
|
38
|
+
const output = execFileSync(
|
|
39
|
+
"git",
|
|
40
|
+
[
|
|
41
|
+
"log",
|
|
42
|
+
"--follow",
|
|
43
|
+
"--format=%H",
|
|
44
|
+
"-n",
|
|
45
|
+
String(maxEntries),
|
|
46
|
+
"--",
|
|
47
|
+
filePath,
|
|
48
|
+
],
|
|
49
|
+
QUIET,
|
|
50
|
+
).trim();
|
|
51
|
+
return output ? [...new Set(output.split("\n"))] : [];
|
|
52
|
+
} catch {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get metadata for a specific commit on a file.
|
|
59
|
+
* Returns { hash, date, author, message } with full hash for unique identification.
|
|
60
|
+
*/
|
|
61
|
+
export function getCommitInfo(
|
|
62
|
+
hash: string,
|
|
63
|
+
filePath: string,
|
|
64
|
+
): Omit<DocHistoryEntry, "content"> {
|
|
65
|
+
try {
|
|
66
|
+
const output = execFileSync(
|
|
67
|
+
"git",
|
|
68
|
+
["log", "-1", "--format=%H%n%aI%n%aN%n%s", hash, "--", filePath],
|
|
69
|
+
QUIET,
|
|
70
|
+
).trim();
|
|
71
|
+
const lines = output.split("\n");
|
|
72
|
+
return {
|
|
73
|
+
hash: lines[0] ?? hash,
|
|
74
|
+
date: lines[1] ?? "",
|
|
75
|
+
author: lines[2] ?? "",
|
|
76
|
+
message: lines[3] ?? "",
|
|
77
|
+
};
|
|
78
|
+
} catch {
|
|
79
|
+
return { hash, date: "", author: "", message: "" };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get the file content at a specific commit.
|
|
85
|
+
* Accepts absolute paths and converts to repo-relative for git show.
|
|
86
|
+
* Handles renamed files by falling back to the old path via git log --follow.
|
|
87
|
+
*/
|
|
88
|
+
export function getFileAtCommit(hash: string, filePath: string): string {
|
|
89
|
+
const relPath = path.isAbsolute(filePath)
|
|
90
|
+
? toRepoRelative(filePath)
|
|
91
|
+
: filePath;
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
return execFileSync("git", ["show", `${hash}:${relPath}`], QUIET);
|
|
95
|
+
} catch {
|
|
96
|
+
// File may have been renamed — find the old path at this commit
|
|
97
|
+
try {
|
|
98
|
+
const oldPath = execFileSync(
|
|
99
|
+
"git",
|
|
100
|
+
[
|
|
101
|
+
"log",
|
|
102
|
+
"-1",
|
|
103
|
+
"--follow",
|
|
104
|
+
"--diff-filter=R",
|
|
105
|
+
"--format=",
|
|
106
|
+
"--name-only",
|
|
107
|
+
hash,
|
|
108
|
+
"--",
|
|
109
|
+
relPath,
|
|
110
|
+
],
|
|
111
|
+
QUIET,
|
|
112
|
+
).trim();
|
|
113
|
+
if (oldPath) {
|
|
114
|
+
return execFileSync("git", ["show", `${hash}:${oldPath}`], QUIET);
|
|
115
|
+
}
|
|
116
|
+
} catch {
|
|
117
|
+
// ignore
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Last resort: use git log --follow to find the path at this revision
|
|
121
|
+
try {
|
|
122
|
+
const followOutput = execFileSync(
|
|
123
|
+
"git",
|
|
124
|
+
[
|
|
125
|
+
"log",
|
|
126
|
+
"--follow",
|
|
127
|
+
"--format=%H",
|
|
128
|
+
"--name-only",
|
|
129
|
+
"--diff-filter=AMRC",
|
|
130
|
+
"--",
|
|
131
|
+
relPath,
|
|
132
|
+
],
|
|
133
|
+
QUIET,
|
|
134
|
+
).trim();
|
|
135
|
+
const lines = followOutput.split("\n").filter(Boolean);
|
|
136
|
+
// Lines alternate: hash, filename, hash, filename...
|
|
137
|
+
for (let i = 0; i < lines.length - 1; i += 2) {
|
|
138
|
+
if (lines[i] === hash && lines[i + 1]) {
|
|
139
|
+
return execFileSync(
|
|
140
|
+
"git",
|
|
141
|
+
["show", `${hash}:${lines[i + 1]}`],
|
|
142
|
+
QUIET,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
} catch {
|
|
147
|
+
// ignore
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return "";
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Get the complete history for a document file.
|
|
156
|
+
* Returns DocHistoryData with all entries populated.
|
|
157
|
+
*/
|
|
158
|
+
export function getDocHistory(
|
|
159
|
+
filePath: string,
|
|
160
|
+
slug: string,
|
|
161
|
+
maxEntries = 50,
|
|
162
|
+
): DocHistoryData {
|
|
163
|
+
const commits = getFileCommits(filePath, maxEntries);
|
|
164
|
+
const entries: DocHistoryEntry[] = commits.map((hash) => {
|
|
165
|
+
const info = getCommitInfo(hash, filePath);
|
|
166
|
+
const content = getFileAtCommit(hash, filePath);
|
|
167
|
+
return { ...info, content };
|
|
168
|
+
});
|
|
169
|
+
return { slug, filePath, entries };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Collect all MDX/md files in a content directory.
|
|
174
|
+
* Delegates to the shared collectMdFiles utility from content-files.ts.
|
|
175
|
+
*/
|
|
176
|
+
export function collectContentFiles(
|
|
177
|
+
contentDir: string,
|
|
178
|
+
): Array<{ filePath: string; slug: string }> {
|
|
179
|
+
return collectMdFiles(contentDir);
|
|
180
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/** @jsxRuntime automatic */
|
|
2
|
+
/** @jsxImportSource preact */
|
|
3
|
+
// Port of src/pages/[locale]/docs/tags/[tag].astro → zfb page module.
|
|
4
|
+
//
|
|
5
|
+
// Non-default-locale per-tag detail page. paths() enumerates every
|
|
6
|
+
// (locale, tag) combination across all non-default locales defined in
|
|
7
|
+
// settings.locales, using the same locale-first + base-fallback merge
|
|
8
|
+
// strategy as the Astro original.
|
|
9
|
+
//
|
|
10
|
+
// paths() contract (zfb ADR-004 — synchronous):
|
|
11
|
+
// params: { locale: string; tag: string }
|
|
12
|
+
// props: { tagInfo: TagInfo }
|
|
13
|
+
//
|
|
14
|
+
// 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
|
+
|
|
18
|
+
import { mergeLocaleDocs } from "../../../lib/locale-merge";
|
|
19
|
+
import { collectTags } from "@/utils/tags";
|
|
20
|
+
import type { TagInfo } from "@/utils/tags";
|
|
21
|
+
import { toRouteSlug } from "@/utils/slug";
|
|
22
|
+
import { t } from "@/config/i18n";
|
|
23
|
+
import { withBase, docsUrl } from "@/utils/base";
|
|
24
|
+
import { settings } from "@/config/settings";
|
|
25
|
+
import { DocLayoutWithDefaults } from "@takazudo/zudo-doc/doclayout";
|
|
26
|
+
import { Breadcrumb } from "@takazudo/zudo-doc/breadcrumb";
|
|
27
|
+
import type { BreadcrumbItem } from "@takazudo/zudo-doc/breadcrumb";
|
|
28
|
+
import { DocCardGrid } from "@takazudo/zudo-doc/nav-indexing";
|
|
29
|
+
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";
|
|
36
|
+
|
|
37
|
+
export const frontmatter = { title: "Tag" };
|
|
38
|
+
|
|
39
|
+
/** One route per (non-default locale, tag) pair. */
|
|
40
|
+
export function paths(): Array<{
|
|
41
|
+
params: { locale: string; tag: string };
|
|
42
|
+
props: { tagInfo: TagInfo };
|
|
43
|
+
}> {
|
|
44
|
+
const result: Array<{
|
|
45
|
+
params: { locale: string; tag: string };
|
|
46
|
+
props: { tagInfo: TagInfo };
|
|
47
|
+
}> = [];
|
|
48
|
+
|
|
49
|
+
for (const locale of Object.keys(settings.locales)) {
|
|
50
|
+
const docs = mergeLocaleDocs(locale);
|
|
51
|
+
const tagMap = collectTags(docs, (id, data) => data.slug ?? toRouteSlug(id));
|
|
52
|
+
|
|
53
|
+
for (const [tag, tagInfo] of tagMap.entries()) {
|
|
54
|
+
result.push({
|
|
55
|
+
params: { locale, tag },
|
|
56
|
+
props: { tagInfo },
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface PageProps {
|
|
65
|
+
params: { locale: string; tag: string };
|
|
66
|
+
tagInfo: TagInfo;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default function LocaleDocTagPage({
|
|
70
|
+
params,
|
|
71
|
+
tagInfo,
|
|
72
|
+
}: PageProps): JSX.Element {
|
|
73
|
+
const { locale, tag } = params;
|
|
74
|
+
|
|
75
|
+
const countText =
|
|
76
|
+
tagInfo.count === 1
|
|
77
|
+
? t("doc.pageCountSingle", locale).replace("{count}", String(tagInfo.count))
|
|
78
|
+
: t("doc.pageCount", locale).replace("{count}", String(tagInfo.count));
|
|
79
|
+
|
|
80
|
+
const pageTitle = `${t("doc.taggedWith", locale)}: ${tag}`;
|
|
81
|
+
|
|
82
|
+
const breadcrumbItems: BreadcrumbItem[] = [
|
|
83
|
+
{ label: "Docs" },
|
|
84
|
+
{
|
|
85
|
+
label: t("doc.allTags", locale),
|
|
86
|
+
href: withBase(`/${locale}/docs/tags`),
|
|
87
|
+
},
|
|
88
|
+
{ label: tag },
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
const cardItems = tagInfo.docs.map((doc) => ({
|
|
92
|
+
href: docsUrl(doc.slug, locale),
|
|
93
|
+
title: doc.title,
|
|
94
|
+
description: doc.description,
|
|
95
|
+
}));
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<DocLayoutWithDefaults
|
|
99
|
+
title={composeMetaTitle(pageTitle)}
|
|
100
|
+
head={<HeadWithDefaults title={pageTitle} />}
|
|
101
|
+
lang={locale}
|
|
102
|
+
noindex={settings.noindex}
|
|
103
|
+
hideSidebar={true}
|
|
104
|
+
hideToc={true}
|
|
105
|
+
headerOverride={<HeaderWithDefaults lang={locale} currentPath={withBase(`/${locale}/docs/tags/${tag}`)} />}
|
|
106
|
+
breadcrumbOverride={<Breadcrumb items={breadcrumbItems} />}
|
|
107
|
+
footerOverride={<FooterWithDefaults lang={locale} />}
|
|
108
|
+
bodyEndComponents={<BodyEndIslands basePath={settings.base ?? "/"} />}
|
|
109
|
+
>
|
|
110
|
+
<h1 class="text-heading font-bold mb-vsp-xs">{pageTitle}</h1>
|
|
111
|
+
<p class="text-muted mb-vsp-lg">{countText}</p>
|
|
112
|
+
<DocCardGrid ariaLabel={pageTitle} items={cardItems} />
|
|
113
|
+
<DocHistoryArea slug={`tags/${tag}`} locale={locale} />
|
|
114
|
+
</DocLayoutWithDefaults>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/** @jsxRuntime automatic */
|
|
2
|
+
/** @jsxImportSource preact */
|
|
3
|
+
// Port of src/pages/[locale]/docs/tags/index.astro → zfb page module.
|
|
4
|
+
//
|
|
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
|
|
8
|
+
// map at render time using a locale-doc + base-doc fallback strategy that
|
|
9
|
+
// mirrors src/components/tag-nav.astro's non-default-locale branch.
|
|
10
|
+
//
|
|
11
|
+
// Fallback strategy (locale first, base as fill): see pages/lib/locale-merge.ts
|
|
12
|
+
//
|
|
13
|
+
// paths() contract (zfb ADR-004 — synchronous):
|
|
14
|
+
// params: { locale: string }
|
|
15
|
+
// props: (none — tag map computed at render time)
|
|
16
|
+
|
|
17
|
+
import { mergeLocaleDocs } from "../../../lib/locale-merge";
|
|
18
|
+
import { collectTags } from "@/utils/tags";
|
|
19
|
+
import { toRouteSlug } from "@/utils/slug";
|
|
20
|
+
import { t } from "@/config/i18n";
|
|
21
|
+
import { withBase } from "@/utils/base";
|
|
22
|
+
import { settings } from "@/config/settings";
|
|
23
|
+
import { DocLayoutWithDefaults } from "@takazudo/zudo-doc/doclayout";
|
|
24
|
+
import { Breadcrumb } from "@takazudo/zudo-doc/breadcrumb";
|
|
25
|
+
import type { BreadcrumbItem } from "@takazudo/zudo-doc/breadcrumb";
|
|
26
|
+
import { TagNav } from "@takazudo/zudo-doc/nav-indexing";
|
|
27
|
+
import type { TagItem, TagNavLabels } from "@takazudo/zudo-doc/nav-indexing";
|
|
28
|
+
import type { JSX } from "preact";
|
|
29
|
+
import { FooterWithDefaults } from "../../../lib/_footer-with-defaults";
|
|
30
|
+
import { HeaderWithDefaults } from "../../../lib/_header-with-defaults";
|
|
31
|
+
import { HeadWithDefaults } from "../../../lib/_head-with-defaults";
|
|
32
|
+
import { composeMetaTitle } from "../../../lib/_compose-meta-title";
|
|
33
|
+
import { DocHistoryArea } from "../../../lib/_doc-history-area";
|
|
34
|
+
import { BodyEndIslands } from "../../../lib/_body-end-islands";
|
|
35
|
+
|
|
36
|
+
export const frontmatter = { title: "All Tags" };
|
|
37
|
+
|
|
38
|
+
/** One route per non-default locale (prefixDefaultLocale: false). */
|
|
39
|
+
export function paths(): Array<{ params: { locale: string } }> {
|
|
40
|
+
return Object.keys(settings.locales).map((locale) => ({
|
|
41
|
+
params: { locale },
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface PageProps {
|
|
46
|
+
params: { locale: string };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default function LocaleTagsIndexPage({
|
|
50
|
+
params,
|
|
51
|
+
}: PageProps): JSX.Element {
|
|
52
|
+
const { locale } = params;
|
|
53
|
+
const pageTitle = t("doc.allTags", locale);
|
|
54
|
+
|
|
55
|
+
const docs = mergeLocaleDocs(locale);
|
|
56
|
+
const tagMap = collectTags(docs, (id, data) => data.slug ?? toRouteSlug(id));
|
|
57
|
+
|
|
58
|
+
const labels: TagNavLabels = {
|
|
59
|
+
tags: t("doc.tags", locale),
|
|
60
|
+
taggedWith: t("doc.taggedWith", locale),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Sort alphabetically using the page locale — mirrors tag-nav.astro sort.
|
|
64
|
+
const tags: TagItem[] = [...tagMap.values()]
|
|
65
|
+
.sort((a, b) => a.tag.localeCompare(b.tag, locale))
|
|
66
|
+
.map((info) => ({
|
|
67
|
+
tag: info.tag,
|
|
68
|
+
count: info.count,
|
|
69
|
+
href: withBase(`/${locale}/docs/tags/${info.tag}`),
|
|
70
|
+
}));
|
|
71
|
+
|
|
72
|
+
const breadcrumbItems: BreadcrumbItem[] = [
|
|
73
|
+
{ label: "Docs" },
|
|
74
|
+
{ label: pageTitle },
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<DocLayoutWithDefaults
|
|
79
|
+
title={composeMetaTitle(pageTitle)}
|
|
80
|
+
head={<HeadWithDefaults title={pageTitle} />}
|
|
81
|
+
lang={locale}
|
|
82
|
+
noindex={settings.noindex}
|
|
83
|
+
hideSidebar={true}
|
|
84
|
+
hideToc={true}
|
|
85
|
+
headerOverride={<HeaderWithDefaults lang={locale} currentPath={withBase(`/${locale}/docs/tags`)} />}
|
|
86
|
+
breadcrumbOverride={<Breadcrumb items={breadcrumbItems} />}
|
|
87
|
+
footerOverride={<FooterWithDefaults lang={locale} />}
|
|
88
|
+
bodyEndComponents={<BodyEndIslands basePath={settings.base ?? "/"} />}
|
|
89
|
+
>
|
|
90
|
+
<h1 class="text-heading font-bold mb-vsp-lg">{pageTitle}</h1>
|
|
91
|
+
{!settings.docTags || tags.length === 0 ? (
|
|
92
|
+
<p class="text-muted">{t("doc.noTags", locale)}</p>
|
|
93
|
+
) : (
|
|
94
|
+
<TagNav variant="all" tags={tags} labels={labels} />
|
|
95
|
+
)}
|
|
96
|
+
<DocHistoryArea slug="tags" locale={locale} />
|
|
97
|
+
</DocLayoutWithDefaults>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/** @jsxRuntime automatic */
|
|
2
|
+
/** @jsxImportSource preact */
|
|
3
|
+
// Port of src/pages/docs/tags/[tag].astro → zfb page module.
|
|
4
|
+
//
|
|
5
|
+
// Default-locale (en) per-tag detail page. paths() enumerates one route per
|
|
6
|
+
// unique tag in the "docs" collection and passes the resolved TagInfo as a
|
|
7
|
+
// prop so the component has zero extra collection reads at render time.
|
|
8
|
+
//
|
|
9
|
+
// paths() contract (zfb ADR-004 — synchronous):
|
|
10
|
+
// params: { tag: string }
|
|
11
|
+
// props: { tagInfo: TagInfo }
|
|
12
|
+
//
|
|
13
|
+
// Data flow:
|
|
14
|
+
// getCollection("docs") [sync] → collectTags() → one route per tag
|
|
15
|
+
// render: DocCardGrid with pre-resolved TagInfo.docs items
|
|
16
|
+
|
|
17
|
+
import { getCollection } from "zfb/content";
|
|
18
|
+
import { collectTags } from "@/utils/tags";
|
|
19
|
+
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 type { DocsEntry } from "@/types/docs-entry";
|
|
25
|
+
import { DocLayoutWithDefaults } from "@takazudo/zudo-doc/doclayout";
|
|
26
|
+
import { Breadcrumb } from "@takazudo/zudo-doc/breadcrumb";
|
|
27
|
+
import type { BreadcrumbItem } from "@takazudo/zudo-doc/breadcrumb";
|
|
28
|
+
import { DocCardGrid } from "@takazudo/zudo-doc/nav-indexing";
|
|
29
|
+
import type { JSX } from "preact";
|
|
30
|
+
import { bridgeEntries } from "../../_data";
|
|
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";
|
|
37
|
+
|
|
38
|
+
export const frontmatter = { title: "Tag" };
|
|
39
|
+
|
|
40
|
+
/** One entry per unique tag in the default-locale collection. */
|
|
41
|
+
export function paths(): Array<{
|
|
42
|
+
params: { tag: string };
|
|
43
|
+
props: { tagInfo: TagInfo };
|
|
44
|
+
}> {
|
|
45
|
+
const allDocs = (bridgeEntries(getCollection("docs"), "docs") as unknown as DocsEntry[]);
|
|
46
|
+
const docs = allDocs.filter((doc) => !doc.data.unlisted && !doc.data.draft);
|
|
47
|
+
const tagMap = collectTags(docs, (id, data) => data.slug ?? toRouteSlug(id));
|
|
48
|
+
|
|
49
|
+
return [...tagMap.entries()].map(([tag, tagInfo]) => ({
|
|
50
|
+
params: { tag },
|
|
51
|
+
props: { tagInfo },
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface PageProps {
|
|
56
|
+
params: { tag: string };
|
|
57
|
+
tagInfo: TagInfo;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default function DocTagPage({ params, tagInfo }: PageProps): JSX.Element {
|
|
61
|
+
const { tag } = params;
|
|
62
|
+
const locale = defaultLocale;
|
|
63
|
+
|
|
64
|
+
const countText =
|
|
65
|
+
tagInfo.count === 1
|
|
66
|
+
? t("doc.pageCountSingle", locale).replace("{count}", String(tagInfo.count))
|
|
67
|
+
: t("doc.pageCount", locale).replace("{count}", String(tagInfo.count));
|
|
68
|
+
|
|
69
|
+
const pageTitle = `${t("doc.taggedWith", locale)}: ${tag}`;
|
|
70
|
+
|
|
71
|
+
const breadcrumbItems: BreadcrumbItem[] = [
|
|
72
|
+
{ label: "Docs" },
|
|
73
|
+
{ label: t("doc.allTags", locale), href: withBase("/docs/tags") },
|
|
74
|
+
{ label: tag },
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
const cardItems = tagInfo.docs.map((doc) => ({
|
|
78
|
+
href: docsUrl(doc.slug, locale),
|
|
79
|
+
title: doc.title,
|
|
80
|
+
description: doc.description,
|
|
81
|
+
}));
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<DocLayoutWithDefaults
|
|
85
|
+
title={composeMetaTitle(pageTitle)}
|
|
86
|
+
head={<HeadWithDefaults title={pageTitle} />}
|
|
87
|
+
noindex={settings.noindex}
|
|
88
|
+
hideSidebar={true}
|
|
89
|
+
hideToc={true}
|
|
90
|
+
headerOverride={<HeaderWithDefaults lang={locale} currentPath={withBase(`/docs/tags/${tag}`)} />}
|
|
91
|
+
breadcrumbOverride={<Breadcrumb items={breadcrumbItems} />}
|
|
92
|
+
footerOverride={<FooterWithDefaults lang={locale} />}
|
|
93
|
+
bodyEndComponents={<BodyEndIslands basePath={settings.base ?? "/"} />}
|
|
94
|
+
>
|
|
95
|
+
<h1 class="text-heading font-bold mb-vsp-xs">{pageTitle}</h1>
|
|
96
|
+
<p class="text-muted mb-vsp-lg">{countText}</p>
|
|
97
|
+
<DocCardGrid ariaLabel={pageTitle} items={cardItems} />
|
|
98
|
+
<DocHistoryArea slug={`tags/${tag}`} locale={locale} />
|
|
99
|
+
</DocLayoutWithDefaults>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/** @jsxRuntime automatic */
|
|
2
|
+
/** @jsxImportSource preact */
|
|
3
|
+
// Port of src/pages/docs/tags/index.astro → zfb page module.
|
|
4
|
+
//
|
|
5
|
+
// Default-locale (en) "All Tags" index page. Collects every tag across the
|
|
6
|
+
// "docs" collection, sorts them alphabetically, and renders a full tag cloud
|
|
7
|
+
// via the v2 TagNav component. No dynamic params — single static route.
|
|
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
|
|
14
|
+
|
|
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 type { DocsEntry } from "@/types/docs-entry";
|
|
22
|
+
import { DocLayoutWithDefaults } from "@takazudo/zudo-doc/doclayout";
|
|
23
|
+
import { Breadcrumb } from "@takazudo/zudo-doc/breadcrumb";
|
|
24
|
+
import type { BreadcrumbItem } from "@takazudo/zudo-doc/breadcrumb";
|
|
25
|
+
import { TagNav } from "@takazudo/zudo-doc/nav-indexing";
|
|
26
|
+
import type { TagItem, TagNavLabels } from "@takazudo/zudo-doc/nav-indexing";
|
|
27
|
+
import type { JSX } from "preact";
|
|
28
|
+
import { bridgeEntries } from "../../_data";
|
|
29
|
+
import { FooterWithDefaults } from "../../lib/_footer-with-defaults";
|
|
30
|
+
import { HeaderWithDefaults } from "../../lib/_header-with-defaults";
|
|
31
|
+
import { HeadWithDefaults } from "../../lib/_head-with-defaults";
|
|
32
|
+
import { composeMetaTitle } from "../../lib/_compose-meta-title";
|
|
33
|
+
import { DocHistoryArea } from "../../lib/_doc-history-area";
|
|
34
|
+
import { BodyEndIslands } from "../../lib/_body-end-islands";
|
|
35
|
+
|
|
36
|
+
export const frontmatter = { title: "All Tags" };
|
|
37
|
+
|
|
38
|
+
export default function DocsTagsIndexPage(): JSX.Element {
|
|
39
|
+
const locale = defaultLocale;
|
|
40
|
+
const pageTitle = t("doc.allTags", locale);
|
|
41
|
+
|
|
42
|
+
const allDocs = (bridgeEntries(getCollection("docs"), "docs") as unknown as DocsEntry[]);
|
|
43
|
+
const docs = allDocs.filter((doc) => !doc.data.unlisted && !doc.data.draft);
|
|
44
|
+
const tagMap = collectTags(docs, (id, data) => data.slug ?? toRouteSlug(id));
|
|
45
|
+
|
|
46
|
+
const labels: TagNavLabels = {
|
|
47
|
+
tags: t("doc.tags", locale),
|
|
48
|
+
taggedWith: t("doc.taggedWith", locale),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Sort alphabetically — mirrors the Astro tag-nav.astro sort order.
|
|
52
|
+
const tags: TagItem[] = [...tagMap.values()]
|
|
53
|
+
.sort((a, b) => a.tag.localeCompare(b.tag, locale))
|
|
54
|
+
.map((info) => ({
|
|
55
|
+
tag: info.tag,
|
|
56
|
+
count: info.count,
|
|
57
|
+
href: withBase(`/docs/tags/${info.tag}`),
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
const breadcrumbItems: BreadcrumbItem[] = [
|
|
61
|
+
{ label: "Docs" },
|
|
62
|
+
{ label: pageTitle },
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<DocLayoutWithDefaults
|
|
67
|
+
title={composeMetaTitle(pageTitle)}
|
|
68
|
+
head={<HeadWithDefaults title={pageTitle} />}
|
|
69
|
+
noindex={settings.noindex}
|
|
70
|
+
hideSidebar={true}
|
|
71
|
+
hideToc={true}
|
|
72
|
+
headerOverride={<HeaderWithDefaults lang={locale} currentPath={withBase("/docs/tags")} />}
|
|
73
|
+
breadcrumbOverride={<Breadcrumb items={breadcrumbItems} />}
|
|
74
|
+
footerOverride={<FooterWithDefaults lang={locale} />}
|
|
75
|
+
bodyEndComponents={<BodyEndIslands basePath={settings.base ?? "/"} />}
|
|
76
|
+
>
|
|
77
|
+
<h1 class="text-heading font-bold mb-vsp-lg">{pageTitle}</h1>
|
|
78
|
+
{!settings.docTags || tags.length === 0 ? (
|
|
79
|
+
<p class="text-muted">{t("doc.noTags", locale)}</p>
|
|
80
|
+
) : (
|
|
81
|
+
<TagNav variant="all" tags={tags} labels={labels} />
|
|
82
|
+
)}
|
|
83
|
+
<DocHistoryArea slug="tags" locale={locale} />
|
|
84
|
+
</DocLayoutWithDefaults>
|
|
85
|
+
);
|
|
86
|
+
}
|