create-zudo-doc 0.2.21 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/compose.d.ts +6 -3
- package/dist/compose.js +6 -4
- package/dist/features/image-enlarge.d.ts +22 -12
- package/dist/features/image-enlarge.js +23 -191
- package/dist/scaffold.d.ts +11 -0
- package/dist/scaffold.js +51 -24
- package/dist/settings-gen.js +4 -0
- package/dist/zfb-config-gen.d.ts +16 -10
- package/dist/zfb-config-gen.js +34 -239
- package/package.json +1 -1
- package/templates/base/pages/_mdx-components.ts +64 -185
- package/templates/base/pages/index.tsx +1 -1
- package/templates/base/pages/lib/_body-end-islands.tsx +6 -13
- package/templates/base/pages/lib/_category-nav.tsx +30 -115
- package/templates/base/pages/lib/_category-tree-nav.tsx +38 -65
- package/templates/base/pages/lib/_compose-meta-title.ts +2 -6
- package/templates/base/pages/lib/_doc-body-end.tsx +3 -35
- package/templates/base/pages/lib/_doc-content-header.tsx +10 -111
- package/templates/base/pages/lib/_doc-history-area.tsx +19 -211
- package/templates/base/pages/lib/_doc-metainfo-area.tsx +10 -113
- package/templates/base/pages/lib/_doc-page-renderer.tsx +22 -183
- package/templates/base/pages/lib/_doc-page-shell.tsx +17 -251
- package/templates/base/pages/lib/_doc-pager.tsx +4 -74
- package/templates/base/pages/lib/_doc-route-entries.ts +43 -177
- package/templates/base/pages/lib/_doc-route-paths.ts +16 -101
- package/templates/base/pages/lib/_doc-tags-area.tsx +14 -77
- package/templates/base/pages/lib/_extract-headings.ts +21 -295
- package/templates/base/pages/lib/_footer-with-defaults.tsx +37 -225
- package/templates/base/pages/lib/_frontmatter-preview-data.ts +5 -31
- package/templates/base/pages/lib/_head-with-defaults.tsx +13 -138
- package/templates/base/pages/lib/_header-with-defaults.tsx +24 -324
- package/templates/base/pages/lib/_inline-version-switcher.tsx +16 -78
- package/templates/base/pages/lib/_math-block.tsx +4 -63
- package/templates/base/pages/lib/_nav-data-prep.ts +32 -51
- package/templates/base/pages/lib/_nav-source-cache.ts +12 -57
- package/templates/base/pages/lib/_nav-source-docs.ts +33 -233
- package/templates/base/pages/lib/_search-widget-script.ts +2 -470
- package/templates/base/pages/lib/_search-widget.tsx +9 -195
- package/templates/base/pages/lib/_sidebar-prepaint.tsx +6 -59
- package/templates/base/pages/lib/_sidebar-with-defaults.tsx +16 -118
- package/templates/base/pages/lib/_site-tree-nav.tsx +29 -80
- package/templates/base/pages/lib/doc-page-props.ts +26 -44
- package/templates/base/pages/lib/locale-merge.ts +32 -145
- package/templates/base/pages/lib/route-enumerators.ts +52 -286
- package/templates/base/src/components/ai-chat-modal.tsx +9 -8
- package/templates/base/src/components/content/code-group.tsx +3 -76
- package/templates/base/src/components/content/content-admonition.tsx +4 -56
- package/templates/base/src/components/doc-history.tsx +9 -8
- package/templates/base/src/components/image-enlarge.tsx +11 -8
- package/templates/base/src/components/sidebar-toggle.tsx +6 -170
- package/templates/base/src/components/sidebar-tree.tsx +6 -548
- package/templates/base/src/config/color-scheme-utils.ts +34 -158
- package/templates/base/src/config/i18n.ts +9 -0
- package/templates/base/src/config/settings-types.ts +34 -172
- package/templates/base/src/config/z-index-tokens.ts +5 -4
- package/templates/base/src/styles/global.css +40 -587
- package/templates/base/src/utils/base.ts +1 -1
- package/templates/base/src/utils/docs.ts +47 -16
- package/templates/base/src/utils/github.ts +12 -9
- package/templates/base/src/utils/nav-scope.ts +13 -42
- package/templates/base/src/utils/sidebar.ts +18 -86
- package/templates/base/src/utils/slug.ts +10 -53
- package/templates/base/src/utils/smart-break.tsx +12 -120
- package/templates/base/src/utils/tags.ts +25 -68
- package/templates/features/bodyFootUtil/files/src/utils/github.ts +12 -9
- package/templates/features/designTokenPanel/files/src/lib/design-token-panel-bootstrap.ts +13 -39
- package/templates/features/docHistory/files/src/components/doc-history.tsx +8 -636
- package/templates/features/docHistory/files/src/types/doc-history.ts +7 -23
- package/templates/features/docTags/files/pages/lib/_tag-pages.tsx +35 -201
- package/templates/features/i18n/files/pages/[locale]/index.tsx +1 -1
- package/templates/features/imageEnlarge/files/src/components/image-enlarge.tsx +8 -269
- package/templates/features/sidebarToggle/files/src/components/desktop-sidebar-toggle.tsx +6 -99
- package/templates/features/tagGovernance/files/scripts/tags-audit.ts +67 -515
- package/templates/features/versioning/files/pages/lib/_versions-page.tsx +21 -73
- package/templates/base/pages/404.tsx +0 -61
- package/templates/base/pages/robots.txt.tsx +0 -29
- package/templates/base/pages/sitemap.xml.tsx +0 -59
- package/templates/base/plugins/connect-adapter.mjs +0 -169
- package/templates/base/plugins/copy-public-plugin.mjs +0 -58
- package/templates/base/plugins/search-index-plugin.mjs +0 -66
- package/templates/base/scripts/gen-z-index.mjs +0 -157
- package/templates/base/src/components/mermaid-enlarge.tsx +0 -490
- package/templates/base/src/components/site-tree-nav.tsx +0 -220
- package/templates/features/claudeResources/files/plugins/claude-resources-plugin.mjs +0 -47
- package/templates/features/docHistory/files/plugins/doc-history-plugin.mjs +0 -111
- package/templates/features/docTags/files/pages/[locale]/docs/tags/[tag].tsx +0 -59
- package/templates/features/docTags/files/pages/[locale]/docs/tags/index.tsx +0 -39
- package/templates/features/docTags/files/pages/docs/tags/[tag].tsx +0 -43
- package/templates/features/docTags/files/pages/docs/tags/index.tsx +0 -20
- package/templates/features/llmsTxt/files/plugins/llms-txt-plugin.mjs +0 -93
- package/templates/features/versioning/files/pages/[locale]/docs/versions.tsx +0 -48
- package/templates/features/versioning/files/pages/docs/versions.tsx +0 -20
|
@@ -1,30 +1,9 @@
|
|
|
1
1
|
/** @jsxRuntime automatic */
|
|
2
2
|
/** @jsxImportSource preact */
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// hrefs and titles, and optionally collects tag columns when taglist is
|
|
7
|
-
// enabled — then feeds the result into the presentational <Footer> shell
|
|
8
|
-
// from @takazudo/zudo-doc/footer.
|
|
9
|
-
//
|
|
10
|
-
// Callers pass a `lang` prop (the active locale string, e.g. "en", "ja").
|
|
11
|
-
// The component returns a fully populated <Footer> when settings.footer is
|
|
12
|
-
// configured, or a bare <Footer /> shell when it is not (the shell still
|
|
13
|
-
// emits the contentinfo ARIA landmark).
|
|
14
|
-
//
|
|
15
|
-
// Data-prep helpers used:
|
|
16
|
-
// settings.footer — link columns, copyright, taglist config
|
|
17
|
-
// isExternal / resolveHref / withBase — href normalization
|
|
18
|
-
// defaultLocale — determines when locale prefix is needed
|
|
19
|
-
// tagVocabulary — group-by ordering for grouped taglist mode
|
|
20
|
-
// collectTags — builds tag → { count, docs } map
|
|
21
|
-
// toRouteSlug — derives route slug from collection id
|
|
22
|
-
// loadDocs / filterDrafts — synchronous zfb collection helpers (ADR-004)
|
|
23
|
-
|
|
24
|
-
import type { VNode } from "preact";
|
|
3
|
+
// Host thin-stub — see @takazudo/zudo-doc/footer-with-defaults (epic #2344, S5).
|
|
4
|
+
// Wires host singletons into the package factory and passes the tag loading
|
|
5
|
+
// function as a dependency so the factory never imports @/ aliases.
|
|
25
6
|
import { settings } from "@/config/settings";
|
|
26
|
-
import { Footer } from "@takazudo/zudo-doc/footer";
|
|
27
|
-
import type { FooterLinkColumn, FooterTagColumn } from "@takazudo/zudo-doc/footer";
|
|
28
7
|
import { isExternal, resolveHref, withBase } from "@/utils/base";
|
|
29
8
|
import { defaultLocale } from "@/config/i18n";
|
|
30
9
|
import { tagVocabulary } from "@/config/tag-vocabulary";
|
|
@@ -33,214 +12,47 @@ import { toRouteSlug } from "@/utils/slug";
|
|
|
33
12
|
import { mergeLocaleDocs } from "./locale-merge";
|
|
34
13
|
import { stableDocs, memoizeDerived } from "./_nav-source-cache";
|
|
35
14
|
import type { DocsEntry } from "@/types/docs-entry";
|
|
15
|
+
import { createFooterWithDefaults } from "@takazudo/zudo-doc/footer-with-defaults";
|
|
36
16
|
|
|
37
17
|
// ---------------------------------------------------------------------------
|
|
38
|
-
//
|
|
18
|
+
// Tag loading (host-side, reads collections via stableDocs / memoizeDerived)
|
|
39
19
|
// ---------------------------------------------------------------------------
|
|
40
20
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
21
|
+
function loadTagsForLocale(lang: string) {
|
|
22
|
+
if (lang === defaultLocale) {
|
|
23
|
+
const baseDocs = stableDocs("docs");
|
|
24
|
+
return memoizeDerived([baseDocs], "footerTaglist;default", () => {
|
|
25
|
+
const docs: DocsEntry[] = baseDocs.filter(
|
|
26
|
+
(d) => !d.data.draft && !d.data.unlisted && !d.data.category_no_page,
|
|
27
|
+
);
|
|
28
|
+
const tagMap = collectTags(docs, (id, data) => data.slug ?? toRouteSlug(id));
|
|
29
|
+
return [...tagMap.values()].sort((a, b) => a.tag.localeCompare(b.tag, lang));
|
|
30
|
+
});
|
|
50
31
|
}
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
lang === defaultLocale
|
|
64
|
-
? `/docs/tags/${encoded}`
|
|
65
|
-
: `/${lang}/docs/tags/${encoded}`;
|
|
66
|
-
return withBase(path);
|
|
32
|
+
const baseDocs = stableDocs("docs");
|
|
33
|
+
const localeDocs = stableDocs(`docs-${lang}`);
|
|
34
|
+
return memoizeDerived([baseDocs, localeDocs], `footerTaglist;${lang}`, () => {
|
|
35
|
+
const result = mergeLocaleDocs({
|
|
36
|
+
baseDocs: baseDocs.filter((d) => !d.data.draft),
|
|
37
|
+
localeDocs: localeDocs.filter((d) => !d.data.draft),
|
|
38
|
+
applyDefaultLocaleOnlyFilter: true,
|
|
39
|
+
});
|
|
40
|
+
const docs: DocsEntry[] = result.docs.filter((d) => !d.data.category_no_page);
|
|
41
|
+
const tagMap = collectTags(docs, (id, data) => data.slug ?? toRouteSlug(id));
|
|
42
|
+
return [...tagMap.values()].sort((a, b) => a.tag.localeCompare(b.tag, lang));
|
|
43
|
+
});
|
|
67
44
|
}
|
|
68
45
|
|
|
69
46
|
// ---------------------------------------------------------------------------
|
|
70
|
-
//
|
|
47
|
+
// Thin-stub export
|
|
71
48
|
// ---------------------------------------------------------------------------
|
|
72
49
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
* copyright props expected by the presentational <Footer> shell. When
|
|
83
|
-
* settings.footer is false, a bare <Footer /> shell is returned (the
|
|
84
|
-
* contentinfo ARIA landmark is still present).
|
|
85
|
-
*/
|
|
86
|
-
export function FooterWithDefaults({
|
|
87
|
-
lang = defaultLocale,
|
|
88
|
-
}: FooterWithDefaultsProps): VNode {
|
|
89
|
-
const footer = settings.footer;
|
|
90
|
-
|
|
91
|
-
// Locale-keyed persist key: same-locale swaps preserve DOM identity;
|
|
92
|
-
// cross-locale swaps discard the stale footer and re-render. (#1546)
|
|
93
|
-
const persistKey = `footer-${lang}`;
|
|
94
|
-
|
|
95
|
-
// When footer is not configured, return the bare shell so the
|
|
96
|
-
// contentinfo ARIA landmark is present.
|
|
97
|
-
if (!footer) {
|
|
98
|
-
return <Footer persistKey={persistKey} />;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const { links, copyright, taglist } = footer;
|
|
102
|
-
|
|
103
|
-
// ── Link columns ────────────────────────────────────────────────────────
|
|
104
|
-
|
|
105
|
-
const linkColumns: FooterLinkColumn[] = links.map((column) => ({
|
|
106
|
-
title: (column.locales as Record<string, { title: string }> | undefined)?.[lang]?.title ?? column.title,
|
|
107
|
-
items: column.items.map((item) => ({
|
|
108
|
-
label: (item.locales as Record<string, { label: string }> | undefined)?.[lang]?.label ?? item.label,
|
|
109
|
-
href: localizeHref(item.href, lang),
|
|
110
|
-
isExternal: isExternal(item.href),
|
|
111
|
-
})),
|
|
112
|
-
}));
|
|
113
|
-
|
|
114
|
-
// ── Tag columns (optional) ───────────────────────────────────────────────
|
|
115
|
-
|
|
116
|
-
let tagColumns: FooterTagColumn[] = [];
|
|
117
|
-
|
|
118
|
-
if (taglist?.enabled) {
|
|
119
|
-
// Memoize docs loading and tag aggregation on the snapshot-anchored stable
|
|
120
|
-
// docs identity — same pattern as _nav-source-cache — so this block runs
|
|
121
|
-
// once per locale per build, not once per page render.
|
|
122
|
-
const allTags = (() => {
|
|
123
|
-
if (lang === defaultLocale) {
|
|
124
|
-
const baseDocs = stableDocs("docs");
|
|
125
|
-
return memoizeDerived([baseDocs], "footerTaglist;default", () => {
|
|
126
|
-
// category_no_page index files build no route — drop them so the
|
|
127
|
-
// footer taglist matches the tag-route pages (which all now filter too).
|
|
128
|
-
const docs: DocsEntry[] = baseDocs.filter(
|
|
129
|
-
(d) => !d.data.draft && !d.data.unlisted && !d.data.category_no_page,
|
|
130
|
-
);
|
|
131
|
-
const tagMap = collectTags(docs, (id, data) => data.slug ?? toRouteSlug(id));
|
|
132
|
-
return [...tagMap.values()].sort((a, b) => a.tag.localeCompare(b.tag, lang));
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
// Apply the default-locale-only filter so the footer taglist only counts
|
|
136
|
-
// tags that have a locale-routable tag page — matching the tag-route
|
|
137
|
-
// pages ([tag].tsx / tags/index.tsx) and enumerateTagsRoutes, which all
|
|
138
|
-
// now filter. Without this, the footer would link to /{locale}/docs/tags/
|
|
139
|
-
// pages that are never built for tags living only on default-locale-only
|
|
140
|
-
// prefix pages. category_no_page is dropped for the same reason — AFTER
|
|
141
|
-
// the merge, so a locale override carrying the flag first wins the merge
|
|
142
|
-
// (pre-merge filtering would let the unflagged base doc resurface).
|
|
143
|
-
const baseDocs = stableDocs("docs");
|
|
144
|
-
const localeDocs = stableDocs(`docs-${lang}`);
|
|
145
|
-
return memoizeDerived([baseDocs, localeDocs], `footerTaglist;${lang}`, () => {
|
|
146
|
-
const result = mergeLocaleDocs({
|
|
147
|
-
baseDocs: baseDocs.filter((d) => !d.data.draft),
|
|
148
|
-
localeDocs: localeDocs.filter((d) => !d.data.draft),
|
|
149
|
-
applyDefaultLocaleOnlyFilter: true,
|
|
150
|
-
});
|
|
151
|
-
const docs: DocsEntry[] = result.docs.filter((d) => !d.data.category_no_page);
|
|
152
|
-
const tagMap = collectTags(docs, (id, data) => data.slug ?? toRouteSlug(id));
|
|
153
|
-
return [...tagMap.values()].sort((a, b) => a.tag.localeCompare(b.tag, lang));
|
|
154
|
-
});
|
|
155
|
-
})();
|
|
156
|
-
|
|
157
|
-
const vocabularyActive =
|
|
158
|
-
Boolean(settings.tagVocabulary) && settings.tagGovernance !== "off";
|
|
159
|
-
const requestedGroupBy = taglist.groupBy ?? "group";
|
|
160
|
-
const effectiveGroupBy = vocabularyActive ? requestedGroupBy : "flat";
|
|
161
|
-
|
|
162
|
-
const localeOverrides = (taglist.locales as Record<string, { title?: string; groupTitles?: Record<string, string> }> | undefined)?.[lang];
|
|
163
|
-
const groupTitles: Record<string, string> = {
|
|
164
|
-
...taglist.groupTitles,
|
|
165
|
-
...localeOverrides?.groupTitles,
|
|
166
|
-
};
|
|
167
|
-
const flatTitle = localeOverrides?.title ?? taglist.title ?? "Tags";
|
|
168
|
-
|
|
169
|
-
if (effectiveGroupBy === "flat" || !vocabularyActive) {
|
|
170
|
-
if (allTags.length > 0) {
|
|
171
|
-
tagColumns = [
|
|
172
|
-
{
|
|
173
|
-
group: "__flat__",
|
|
174
|
-
title: flatTitle,
|
|
175
|
-
tags: allTags.map(({ tag, count }) => ({
|
|
176
|
-
tag,
|
|
177
|
-
count,
|
|
178
|
-
href: tagHref(tag, lang),
|
|
179
|
-
})),
|
|
180
|
-
},
|
|
181
|
-
];
|
|
182
|
-
}
|
|
183
|
-
} else {
|
|
184
|
-
// Grouped mode: one column per vocabulary group, in declaration order.
|
|
185
|
-
const groupByCanonical = new Map<string, string>();
|
|
186
|
-
const groupOrder: string[] = [];
|
|
187
|
-
const seenGroups = new Set<string>();
|
|
188
|
-
for (const entry of tagVocabulary) {
|
|
189
|
-
if (!entry.group) continue;
|
|
190
|
-
groupByCanonical.set(entry.id, entry.group);
|
|
191
|
-
if (!seenGroups.has(entry.group)) {
|
|
192
|
-
seenGroups.add(entry.group);
|
|
193
|
-
groupOrder.push(entry.group);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const buckets = new Map<string, typeof allTags>();
|
|
198
|
-
for (const group of groupOrder) buckets.set(group, []);
|
|
199
|
-
const ungrouped: typeof allTags = [];
|
|
200
|
-
|
|
201
|
-
for (const info of allTags) {
|
|
202
|
-
const group = groupByCanonical.get(info.tag);
|
|
203
|
-
if (group && buckets.has(group)) {
|
|
204
|
-
buckets.get(group)!.push(info);
|
|
205
|
-
} else {
|
|
206
|
-
ungrouped.push(info);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
tagColumns = groupOrder
|
|
211
|
-
.filter((g) => (buckets.get(g)?.length ?? 0) > 0)
|
|
212
|
-
.map((g) => ({
|
|
213
|
-
group: g,
|
|
214
|
-
title:
|
|
215
|
-
groupTitles[g] ??
|
|
216
|
-
g.charAt(0).toUpperCase() + g.slice(1),
|
|
217
|
-
tags: buckets.get(g)!.map(({ tag, count }) => ({
|
|
218
|
-
tag,
|
|
219
|
-
count,
|
|
220
|
-
href: tagHref(tag, lang),
|
|
221
|
-
})),
|
|
222
|
-
}));
|
|
223
|
-
|
|
224
|
-
if (ungrouped.length > 0) {
|
|
225
|
-
tagColumns.push({
|
|
226
|
-
group: "__flat__",
|
|
227
|
-
title: flatTitle,
|
|
228
|
-
tags: ungrouped.map(({ tag, count }) => ({
|
|
229
|
-
tag,
|
|
230
|
-
count,
|
|
231
|
-
href: tagHref(tag, lang),
|
|
232
|
-
})),
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
return (
|
|
239
|
-
<Footer
|
|
240
|
-
linkColumns={linkColumns}
|
|
241
|
-
tagColumns={tagColumns}
|
|
242
|
-
copyright={copyright}
|
|
243
|
-
persistKey={persistKey}
|
|
244
|
-
/>
|
|
245
|
-
);
|
|
246
|
-
}
|
|
50
|
+
export const FooterWithDefaults = createFooterWithDefaults({
|
|
51
|
+
settings,
|
|
52
|
+
defaultLocale,
|
|
53
|
+
tagVocabulary,
|
|
54
|
+
isExternal,
|
|
55
|
+
resolveHref,
|
|
56
|
+
withBase,
|
|
57
|
+
loadTagsForLocale,
|
|
58
|
+
});
|
|
@@ -17,37 +17,11 @@
|
|
|
17
17
|
// are wired by the page template directly on the `<FrontmatterPreview>` call site
|
|
18
18
|
// via the `renderers` prop. This helper only produces the filtered entries array.
|
|
19
19
|
|
|
20
|
+
import { createBuildFrontmatterPreviewEntries } from "@takazudo/zudo-doc/frontmatter-preview-data";
|
|
20
21
|
import { settings } from "@/config/settings";
|
|
21
22
|
import { DEFAULT_FRONTMATTER_IGNORE_KEYS } from "@/config/frontmatter-preview-defaults";
|
|
22
23
|
|
|
23
|
-
export
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
// `frontmatterPreview` may be absent (undefined) in fixtures that
|
|
29
|
-
// don't opt into the feature. `false` means the host explicitly
|
|
30
|
-
// disabled the block. Both should produce an empty entries array.
|
|
31
|
-
const config = (settings as { frontmatterPreview?: unknown })
|
|
32
|
-
.frontmatterPreview;
|
|
33
|
-
if (config === false || config === undefined) return [];
|
|
34
|
-
|
|
35
|
-
const cfg = config as {
|
|
36
|
-
ignoreKeys?: string[];
|
|
37
|
-
extraIgnoreKeys?: string[];
|
|
38
|
-
};
|
|
39
|
-
const ignoreSet = new Set<string>(
|
|
40
|
-
cfg.ignoreKeys ?? [
|
|
41
|
-
...DEFAULT_FRONTMATTER_IGNORE_KEYS,
|
|
42
|
-
...(cfg.extraIgnoreKeys ?? []),
|
|
43
|
-
],
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
const entries: Array<[string, unknown]> = [];
|
|
47
|
-
for (const [key, value] of Object.entries(data)) {
|
|
48
|
-
if (ignoreSet.has(key)) continue;
|
|
49
|
-
if (value === null || value === undefined) continue;
|
|
50
|
-
entries.push([key, value]);
|
|
51
|
-
}
|
|
52
|
-
return entries;
|
|
53
|
-
}
|
|
24
|
+
export const buildFrontmatterPreviewEntries = createBuildFrontmatterPreviewEntries({
|
|
25
|
+
frontmatterPreview: settings.frontmatterPreview,
|
|
26
|
+
defaultIgnoreKeys: DEFAULT_FRONTMATTER_IGNORE_KEYS,
|
|
27
|
+
});
|
|
@@ -1,147 +1,22 @@
|
|
|
1
1
|
/** @jsxRuntime automatic */
|
|
2
2
|
/** @jsxImportSource preact */
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// Why this wrapper exists: the v2 `<DocLayout>` shell exposes a `head` slot
|
|
6
|
-
// but intentionally does NOT emit og:* meta or mount `<ColorSchemeProvider>` —
|
|
7
|
-
// that is the host's responsibility.
|
|
8
|
-
//
|
|
9
|
-
// Without OgTags the SSG output is missing og:title / og:description,
|
|
10
|
-
// which crawlers and link-preview tools rely on. Without ColorSchemeProvider
|
|
11
|
-
// the runtime `:root { --zd-* }` palette is missing, so every component that
|
|
12
|
-
// resolves a color via `--zd-*` (search match-keyword highlight, image-overlay,
|
|
13
|
-
// etc.) falls back to UA defaults — and the smoke-search "matched
|
|
14
|
-
// keywords" regression guard at e2e/smoke-search.spec.ts:167 fires
|
|
15
|
-
// because `getComputedStyle(root).getPropertyValue("--zd-matched-keyword-bg")`
|
|
16
|
-
// returns "" instead of the resolved palette token.
|
|
17
|
-
//
|
|
18
|
-
// (#1355 wave 13 — ColorSchemeProvider mount restored after the retirement of
|
|
19
|
-
// the zfb cutover's initial setup.)
|
|
20
|
-
|
|
21
|
-
import type { JSX } from "preact";
|
|
22
|
-
import { OgTags, TwitterCard } from "@takazudo/zudo-doc/head";
|
|
23
|
-
import { SIDEBAR_RESIZER_RESTORE_SCRIPT } from "@takazudo/zudo-doc/sidebar-resizer";
|
|
24
|
-
// Import ColorSchemeProvider from the dedicated
|
|
25
|
-
// `./theme/color-scheme-provider` subpath rather than the
|
|
26
|
-
// "@takazudo/zudo-doc/theme" barrel — the barrel also re-exports the
|
|
27
|
-
// ColorTweakExportModal island and the design-token SerDe/iframe-bridge
|
|
28
|
-
// modules, which this SSR-only head emission does not need in its zfb
|
|
29
|
-
// esbuild graph.
|
|
30
|
-
import ColorSchemeProvider from "@takazudo/zudo-doc/theme/color-scheme-provider";
|
|
31
|
-
import { composeMetaTitle } from "./_compose-meta-title";
|
|
32
|
-
import { withBase, absoluteUrl } from "@/utils/base";
|
|
3
|
+
// Host thin-stub — see @takazudo/zudo-doc/head-with-defaults (epic #2344, S5).
|
|
33
4
|
import { settings } from "@/config/settings";
|
|
34
|
-
|
|
35
|
-
// ColorSchemeProvider no longer reaches into the host config tree.
|
|
5
|
+
import { withBase, absoluteUrl } from "@/utils/base";
|
|
36
6
|
import {
|
|
37
7
|
generateCssCustomProperties,
|
|
38
8
|
generateLightDarkCssProperties,
|
|
39
9
|
} from "@/config/color-scheme-utils";
|
|
10
|
+
import { composeMetaTitle } from "./_compose-meta-title";
|
|
11
|
+
import { createHeadWithDefaults } from "@takazudo/zudo-doc/head-with-defaults";
|
|
40
12
|
|
|
41
|
-
export
|
|
42
|
-
/** Page title forwarded to og:title. Required. */
|
|
43
|
-
title: string;
|
|
44
|
-
/** Optional page description forwarded to og:description. */
|
|
45
|
-
description?: string;
|
|
46
|
-
/**
|
|
47
|
-
* Absolute canonical URL for this page. When supplied, emits
|
|
48
|
-
* <link rel="canonical" href="...">. Compute via `absoluteUrl(pageUrl)`
|
|
49
|
-
* (@/utils/base) in each host page; it returns undefined when
|
|
50
|
-
* settings.siteUrl is empty so the link is simply omitted.
|
|
51
|
-
*/
|
|
52
|
-
canonical?: string;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Default-bearing host wrapper that injects og:title / og:description,
|
|
57
|
-
* the ColorSchemeProvider (`:root { --zd-* }` palette + theme bootstrap),
|
|
58
|
-
* the favicon link, and an optional canonical link into the v2 layout's
|
|
59
|
-
* `head` slot.
|
|
60
|
-
*
|
|
61
|
-
* og:title is run through composeMetaTitle so it matches the
|
|
62
|
-
* "<title> | <siteName>" shape emitted by the host's <title> element
|
|
63
|
-
* (the legacy Astro layout produced both shapes; the zfb host has to
|
|
64
|
-
* compose them itself).
|
|
65
|
-
*
|
|
66
|
-
* og:title is always emitted — it is the unconditional DocHead contract
|
|
67
|
-
* (OgTags always emits og:title regardless of settings). All other tags
|
|
68
|
-
* are gated by settings.metaTags.
|
|
69
|
-
*
|
|
70
|
-
* Pure SSR — no state, no client-only imports. Intended for use as:
|
|
71
|
-
* head={<HeadWithDefaults title={title} description={description} canonical={canonical} />}
|
|
72
|
-
* on every DocLayoutWithDefaults call site in the host pages.
|
|
73
|
-
*/
|
|
74
|
-
export function HeadWithDefaults({
|
|
75
|
-
title,
|
|
76
|
-
description,
|
|
77
|
-
canonical,
|
|
78
|
-
}: HeadWithDefaultsProps): JSX.Element {
|
|
79
|
-
const { metaTags } = settings;
|
|
80
|
-
|
|
81
|
-
// og:image / twitter:image must be absolute URLs — crawlers silently drop
|
|
82
|
-
// relative og:image values. absoluteUrl joins siteUrl (no trailing slash) +
|
|
83
|
-
// the base-prefixed asset path, and returns undefined when siteUrl is empty
|
|
84
|
-
// (e.g. a freshly scaffolded create-zudo-doc project that hasn't configured
|
|
85
|
-
// siteUrl yet) so we never ship a useless relative og:image. OgTags /
|
|
86
|
-
// TwitterCard already gate their image emission on the prop being defined;
|
|
87
|
-
// the og:image:* companion tags below are gated explicitly because they
|
|
88
|
-
// would dangle without the parent og:image.
|
|
89
|
-
const ogImageUrl =
|
|
90
|
-
metaTags.ogImage !== false
|
|
91
|
-
? absoluteUrl(withBase(metaTags.ogImage))
|
|
92
|
-
: undefined;
|
|
93
|
-
|
|
94
|
-
// Resolve the palette CSS body once per page render (the v2 component
|
|
95
|
-
// is pure SSR — no caching needed).
|
|
96
|
-
const colorMode = settings.colorMode ? settings.colorMode : null;
|
|
97
|
-
const cssText = colorMode
|
|
98
|
-
? generateLightDarkCssProperties()
|
|
99
|
-
: generateCssCustomProperties();
|
|
13
|
+
export type { HeadWithDefaultsProps } from "@takazudo/zudo-doc/head-with-defaults";
|
|
100
14
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
ogSiteName={metaTags.ogSiteName ? settings.siteName : undefined}
|
|
110
|
-
/>
|
|
111
|
-
{metaTags.keywords !== false && metaTags.keywords.length > 0 && (
|
|
112
|
-
<meta name="keywords" content={metaTags.keywords} />
|
|
113
|
-
)}
|
|
114
|
-
{/* og:image:width / og:image:height / og:image:alt — not in OgTags API;
|
|
115
|
-
emitted here directly to avoid expanding the shared HeadProps surface.
|
|
116
|
-
Standard 1200×630 social preview dimensions. Gated on ogImageUrl so
|
|
117
|
-
the companion tags don't dangle when og:image itself was suppressed. */}
|
|
118
|
-
{ogImageUrl !== undefined && (
|
|
119
|
-
<>
|
|
120
|
-
<meta property="og:image:width" content="1200" />
|
|
121
|
-
<meta property="og:image:height" content="630" />
|
|
122
|
-
<meta property="og:image:alt" content={composeMetaTitle(title)} />
|
|
123
|
-
</>
|
|
124
|
-
)}
|
|
125
|
-
{metaTags.twitterCard !== false && (
|
|
126
|
-
<TwitterCard
|
|
127
|
-
card={metaTags.twitterCard}
|
|
128
|
-
image={ogImageUrl}
|
|
129
|
-
site={metaTags.twitterSite}
|
|
130
|
-
creator={metaTags.twitterCreator}
|
|
131
|
-
/>
|
|
132
|
-
)}
|
|
133
|
-
<ColorSchemeProvider cssText={cssText} colorMode={colorMode} />
|
|
134
|
-
{/* Pre-paint inline script: restore persisted sidebar width to
|
|
135
|
-
--zd-sidebar-w on :root before first paint, so a reload after
|
|
136
|
-
drag-resizing the sidebar doesn't snap back to the CSS default
|
|
137
|
-
clamp() width. Mirrors the sibling sidebar-toggle restore
|
|
138
|
-
script emitted from the page's afterSidebar slot. */}
|
|
139
|
-
{settings.sidebarResizer && <script dangerouslySetInnerHTML={{ __html: SIDEBAR_RESIZER_RESTORE_SCRIPT }} />}
|
|
140
|
-
{/* favicon set — withBase() handles the configured base path prefix */}
|
|
141
|
-
<link rel="icon" href={withBase("/favicon.ico")} sizes="any" />
|
|
142
|
-
<link rel="icon" type="image/png" sizes="32x32" href={withBase("/favicon-32x32.png")} />
|
|
143
|
-
<link rel="icon" type="image/png" sizes="16x16" href={withBase("/favicon-16x16.png")} />
|
|
144
|
-
{canonical !== undefined && <link rel="canonical" href={canonical} />}
|
|
145
|
-
</>
|
|
146
|
-
);
|
|
147
|
-
}
|
|
15
|
+
export const HeadWithDefaults = createHeadWithDefaults({
|
|
16
|
+
settings,
|
|
17
|
+
composeMetaTitle,
|
|
18
|
+
withBase,
|
|
19
|
+
absoluteUrl,
|
|
20
|
+
generateCssCustomProperties,
|
|
21
|
+
generateLightDarkCssProperties,
|
|
22
|
+
});
|