create-zudo-doc 0.2.21 → 0.2.22
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/features/image-enlarge.d.ts +10 -2
- package/dist/features/image-enlarge.js +10 -2
- package/dist/scaffold.js +20 -22
- 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/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/_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/_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/pages/robots.txt.tsx +2 -26
- package/templates/base/src/components/ai-chat-modal.tsx +9 -8
- 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/components/site-tree-nav.tsx +6 -220
- 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/z-index-tokens.ts +5 -4
- package/templates/base/src/styles/global.css +5 -579
- 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/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/plugins/connect-adapter.mjs +0 -169
- 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/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/llmsTxt/files/plugins/llms-txt-plugin.mjs +0 -93
|
@@ -1,101 +1,16 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
/** `undefined` = no override; `null` = suppress; string = target slug. */
|
|
18
|
-
pagination_prev?: string | null;
|
|
19
|
-
pagination_next?: string | null;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Resolve prev/next nav nodes for an entry against the route's OWN nav tree.
|
|
24
|
-
*
|
|
25
|
-
* - Sequential prev/next come from the flattened sub-tree (`subtreeFlat`).
|
|
26
|
-
* - Frontmatter `pagination_prev` / `pagination_next` overrides resolve via
|
|
27
|
-
* `findNode(tree, …)` against the SAME `tree` the route built — never a
|
|
28
|
-
* foreign tree. The caller passes its own version/locale-scoped tree, so a
|
|
29
|
-
* `/v/` override resolves to a `/v/` node and a latest override to a latest
|
|
30
|
-
* node. (#1916 — pagination-override must bind to the correct tree.)
|
|
31
|
-
*
|
|
32
|
-
* Returns the raw NavNodes (hrefs untouched). Callers that need versioned
|
|
33
|
-
* hrefs run the result through `rewriteNavHref` with their `urlFor` closure.
|
|
34
|
-
*/
|
|
35
|
-
export function resolveDocPrevNext(
|
|
36
|
-
tree: NavNode[],
|
|
37
|
-
subtreeFlat: NavNode[],
|
|
38
|
-
slug: string,
|
|
39
|
-
overrides: PaginationOverrides,
|
|
40
|
-
): { prev: NavNode | null; next: NavNode | null } {
|
|
41
|
-
const idx = subtreeFlat.findIndex((n) => n.slug === slug);
|
|
42
|
-
|
|
43
|
-
let prev = idx > 0 ? subtreeFlat[idx - 1] ?? null : null;
|
|
44
|
-
let next = idx >= 0 && idx < subtreeFlat.length - 1 ? subtreeFlat[idx + 1] ?? null : null;
|
|
45
|
-
|
|
46
|
-
if (overrides.pagination_prev !== undefined) {
|
|
47
|
-
if (overrides.pagination_prev === null) {
|
|
48
|
-
prev = null;
|
|
49
|
-
} else {
|
|
50
|
-
const found = findNode(tree, overrides.pagination_prev);
|
|
51
|
-
prev = found ?? prev;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
if (overrides.pagination_next !== undefined) {
|
|
55
|
-
if (overrides.pagination_next === null) {
|
|
56
|
-
next = null;
|
|
57
|
-
} else {
|
|
58
|
-
const found = findNode(tree, overrides.pagination_next);
|
|
59
|
-
next = found ?? next;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return { prev, next };
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/** Flatten the relevant sub-tree for an entry — convenience over flattenTree. */
|
|
67
|
-
export function flattenSubtree(subtree: NavNode[]): NavNode[] {
|
|
68
|
-
return flattenTree(subtree);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Rewrite a single nav node's href via the route's `urlFor` closure.
|
|
73
|
-
*
|
|
74
|
-
* Returns `null` for a `null` node. Latest routes pass `undefined` (leave the
|
|
75
|
-
* href as the latest `docsUrl` already baked into the node); versioned routes
|
|
76
|
-
* pass `urlFor` so prev/next links point at the versioned URL.
|
|
77
|
-
*/
|
|
78
|
-
export function rewriteNavHref(
|
|
79
|
-
node: NavNode | null,
|
|
80
|
-
urlFor: ((slug: string) => string) | undefined,
|
|
81
|
-
): NavNode | null {
|
|
82
|
-
if (!node) return null;
|
|
83
|
-
if (!urlFor) return node;
|
|
84
|
-
return { ...node, href: urlFor(node.slug) };
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Remap an auto-index node's child-card hrefs via `urlFor`.
|
|
89
|
-
*
|
|
90
|
-
* #1916 #2: on versioned routes the children carry latest `docsUrl` hrefs
|
|
91
|
-
* (every nav node does — see toNavNodes). They MUST be overridden to the
|
|
92
|
-
* versioned URL ALWAYS, not only when `c.href` is missing. Passing `urlFor`
|
|
93
|
-
* here does exactly that. Latest routes pass `undefined` to keep the original.
|
|
94
|
-
*/
|
|
95
|
-
export function remapNavChildHrefs(
|
|
96
|
-
children: NavNode[],
|
|
97
|
-
urlFor: ((slug: string) => string) | undefined,
|
|
98
|
-
): NavNode[] {
|
|
99
|
-
if (!urlFor) return children;
|
|
100
|
-
return children.map((c) => ({ ...c, href: urlFor(c.slug) }));
|
|
101
|
-
}
|
|
1
|
+
// Thin stub — doc-route-paths moved to the package (epic #2344, S6).
|
|
2
|
+
// Re-exports the pure prop-builder helpers from @takazudo/zudo-doc/doc-route-paths.
|
|
3
|
+
|
|
4
|
+
export type {
|
|
5
|
+
DocNavNode,
|
|
6
|
+
PaginationOverrides,
|
|
7
|
+
} from "@takazudo/zudo-doc/doc-route-paths";
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
flattenTree,
|
|
11
|
+
findNode,
|
|
12
|
+
flattenSubtree,
|
|
13
|
+
resolveDocPrevNext,
|
|
14
|
+
rewriteNavHref,
|
|
15
|
+
remapNavChildHrefs,
|
|
16
|
+
} from "@takazudo/zudo-doc/doc-route-paths";
|
|
@@ -1,44 +1,15 @@
|
|
|
1
1
|
/** @jsxRuntime automatic */
|
|
2
2
|
/** @jsxImportSource preact */
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// Renders the page-level tag chips (e.g. "Tags: #customization") between
|
|
6
|
-
// the DocMetainfo block and the description paragraph (doc-tags placement
|
|
7
|
-
// — after-title, between the date block and description paragraph).
|
|
8
|
-
//
|
|
9
|
-
// Restoration of a Astro→zfb migration regression: the DocTags component
|
|
10
|
-
// was correctly ported into @takazudo/zudo-doc/metainfo/doc-tags.tsx
|
|
11
|
-
// but no page template wired it up (#1658, closes #1508).
|
|
12
|
-
//
|
|
13
|
-
// tagHref logic: inlined from _footer-with-defaults.tsx (the `tagHref`
|
|
14
|
-
// helper there). Extraction was considered but would cause ripple in the
|
|
15
|
-
// footer file and its callers — per the spec's "no opportunistic refactor"
|
|
16
|
-
// rule, a local copy is used here instead.
|
|
17
|
-
//
|
|
18
|
-
// i18n: both `doc.tags` and `doc.taggedWith` are confirmed present for all
|
|
19
|
-
// project locales (en, ja, de) in src/config/i18n.ts — no fallback needed.
|
|
20
|
-
|
|
21
|
-
import type { VNode } from "preact";
|
|
3
|
+
// Host thin-stub — see @takazudo/zudo-doc/doc-tags-area (epic #2344, S7).
|
|
22
4
|
import { settings } from "@/config/settings";
|
|
23
5
|
import { defaultLocale, t } from "@/config/i18n";
|
|
24
6
|
import { withBase } from "@/utils/base";
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
// ---------------------------------------------------------------------------
|
|
29
|
-
// Internal helpers
|
|
30
|
-
// ---------------------------------------------------------------------------
|
|
7
|
+
import { tagVocabulary } from "@/config/tag-vocabulary";
|
|
8
|
+
import { createDocTagsArea } from "@takazudo/zudo-doc/doc-tags-area";
|
|
31
9
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
* Inlined from _footer-with-defaults.tsx `tagHref` — not extracted to avoid
|
|
36
|
-
* ripple (spec rule: no opportunistic refactor on tagHref extraction).
|
|
37
|
-
*
|
|
38
|
-
* The tag segment is URL-encoded at the href site only — route params stay
|
|
39
|
-
* raw, so the built output dir keeps the raw tag name and the server decodes
|
|
40
|
-
* the percent-encoded href back to it (e.g. "type:guide" → "type%3Aguide").
|
|
41
|
-
*/
|
|
10
|
+
// Inlined from the original _doc-tags-area.tsx `tagHref` helper.
|
|
11
|
+
// Builds the base-prefixed tag detail page href for the given locale.
|
|
12
|
+
// The tag segment is URL-encoded at the href site only.
|
|
42
13
|
function tagHref(tag: string, locale: string): string {
|
|
43
14
|
const encoded = encodeURIComponent(tag);
|
|
44
15
|
const path =
|
|
@@ -48,46 +19,12 @@ function tagHref(tag: string, locale: string): string {
|
|
|
48
19
|
return withBase(path);
|
|
49
20
|
}
|
|
50
21
|
|
|
51
|
-
|
|
52
|
-
// Component
|
|
53
|
-
// ---------------------------------------------------------------------------
|
|
54
|
-
|
|
55
|
-
interface DocTagsAreaProps {
|
|
56
|
-
/** Page slug, e.g. "guides/sidebar". */
|
|
57
|
-
slug: string;
|
|
58
|
-
/** Active locale string, e.g. "en", "ja". */
|
|
59
|
-
locale: string;
|
|
60
|
-
/** Raw tag strings from the page frontmatter (entry.data.tags). */
|
|
61
|
-
tags: readonly string[] | undefined;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Renders the page-level tag chip block when `settings.docTags` is enabled
|
|
66
|
-
* and the page has at least one resolved (non-deprecated) tag.
|
|
67
|
-
*
|
|
68
|
-
* Returns null when `docTags` is disabled, the page has no tags, or all
|
|
69
|
-
* raw tags resolve to deprecated entries.
|
|
70
|
-
*
|
|
71
|
-
* Placement is "after-title" (between the date block and description paragraph).
|
|
72
|
-
*/
|
|
73
|
-
export function DocTagsArea({ locale, tags }: DocTagsAreaProps): VNode | null {
|
|
74
|
-
if (!settings.docTags) return null;
|
|
75
|
-
|
|
76
|
-
const rawTags = tags ?? [];
|
|
77
|
-
const canonicalTags = resolvePageTags(rawTags);
|
|
78
|
-
if (canonicalTags.length === 0) return null;
|
|
22
|
+
export type { DocTagsAreaProps } from "@takazudo/zudo-doc/doc-tags-area";
|
|
79
23
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
placement="after-title"
|
|
88
|
-
tags={resolvedTags}
|
|
89
|
-
tagsLabel={t("doc.tags", locale)}
|
|
90
|
-
taggedWithLabel={t("doc.taggedWith", locale)}
|
|
91
|
-
/>
|
|
92
|
-
);
|
|
93
|
-
}
|
|
24
|
+
export const DocTagsArea = createDocTagsArea({
|
|
25
|
+
settings,
|
|
26
|
+
defaultLocale,
|
|
27
|
+
tagVocabularyEntries: tagVocabulary,
|
|
28
|
+
tagHref,
|
|
29
|
+
t,
|
|
30
|
+
});
|
|
@@ -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
|
+
});
|