@takazudo/zudo-doc 5.5.2 → 5.6.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/CHANGELOG.md +36 -0
- package/README.md +39 -0
- package/dist/chrome/derive.d.ts +46 -0
- package/dist/chrome/derive.js +6 -2
- package/dist/config-assertions/index.d.ts +31 -0
- package/dist/config-assertions/index.js +24 -0
- package/dist/config.d.ts +23 -2
- package/dist/config.js +3 -0
- package/dist/current-path/index.d.ts +27 -0
- package/dist/current-path/index.js +11 -0
- package/dist/design-token-panel-bootstrap.d.ts +89 -23
- package/dist/design-token-panel-bootstrap.js +19 -6
- package/dist/doc-page-props/index.d.ts +5 -67
- package/dist/doc-route-entries/index.d.ts +10 -95
- package/dist/doc-route-entries/index.js +1 -78
- package/dist/doc-route-paths/index.d.ts +1 -1
- package/dist/head-with-defaults/index.d.ts +3 -1
- package/dist/head-with-defaults/index.js +76 -4
- package/dist/header/nav-active.d.ts +28 -0
- package/dist/header/nav-active.js +2 -1
- package/dist/header/nav-overflow-script.js +30 -17
- package/dist/header-with-defaults/index.js +11 -2
- package/dist/i18n-version/language-switcher.d.ts +6 -0
- package/dist/i18n-version/language-switcher.js +3 -1
- package/dist/i18n-version/version-switcher.d.ts +6 -0
- package/dist/i18n-version/version-switcher.js +3 -1
- package/dist/nav-source-docs/index.d.ts +7 -11
- package/dist/plugins/route-pages-candidates.d.ts +19 -0
- package/dist/plugins/route-pages-candidates.js +17 -0
- package/dist/plugins/routes.d.ts +46 -0
- package/dist/plugins/routes.js +72 -18
- package/dist/preset.d.ts +12 -1
- package/dist/preset.js +2 -0
- package/dist/route-context/index.js +2 -2
- package/dist/routes/_chrome.d.ts +1 -1
- package/dist/routes/_chrome.js +4 -0
- package/dist/routes/_context.d.ts +3 -3
- package/dist/routes/_design-token-panel-bootstrap.d.ts +18 -0
- package/dist/routes/_design-token-panel-bootstrap.js +11 -0
- package/dist/routes/_docs-helpers.d.ts +1 -36
- package/dist/routes/_docs-helpers.js +0 -138
- package/dist/safelist.css +1 -1
- package/dist/search-widget-script/generated-script.d.ts +8 -0
- package/dist/search-widget-script/generated-script.js +465 -0
- package/dist/search-widget-script/index.d.ts +1 -18
- package/dist/search-widget-script/index.js +1 -443
- package/dist/settings.d.ts +82 -1
- package/dist/sidebar-tree/category-meta.d.ts +9 -0
- package/dist/sidebar-tree/category-meta.js +21 -12
- package/dist/sidebar-tree-island/index.d.ts +8 -1
- package/dist/sidebar-tree-island/index.js +16 -14
- package/dist/site-schema/doc-route-entries.d.ts +89 -0
- package/dist/site-schema/doc-route-entries.js +83 -0
- package/dist/site-schema/index.d.ts +17 -0
- package/dist/site-schema/index.js +46 -0
- package/dist/site-schema/nav-tree.d.ts +28 -0
- package/dist/site-schema/nav-tree.js +138 -0
- package/dist/site-schema/types.d.ts +97 -0
- package/dist/site-schema/types.js +0 -0
- package/dist/theme/theme-pack-provider.d.ts +34 -3
- package/dist/theme/theme-pack-provider.js +30 -2
- package/eject/header/nav-active.ts +13 -1
- package/eject/header/nav-overflow-script.ts +30 -17
- package/eject/sidebar-tree-island/index.tsx +44 -20
- package/package.json +22 -12
- package/routes-src/_chrome.tsx +21 -9
- package/routes-src/_design-token-panel-bootstrap.tsx +63 -0
- package/routes-src/_docs-helpers.ts +18 -225
- package/routes-src/_virtual.d.ts +5 -2
- package/virtual-modules.d.ts +5 -2
|
@@ -2,100 +2,15 @@ import type { DocPageEntry, DocNavNode, AutoIndexNode, DocPageBaseProps } from "
|
|
|
2
2
|
import type { NavSourceDocs } from "../nav-source-docs/index.js";
|
|
3
3
|
import type { BreadcrumbItem } from "../breadcrumb/types.js";
|
|
4
4
|
import type { HeadingItem } from "../extract-headings/index.js";
|
|
5
|
-
import type {
|
|
5
|
+
import type { BuildDocRouteEntriesArgs as BuildDocRouteEntriesArgsGeneric, DocRouteEntriesAPI as DocRouteEntriesAPIGeneric, DocRouteEntriesContext as DocRouteEntriesContextGeneric, DocRouteEntry as DocRouteEntryGeneric } from "../site-schema/doc-route-entries.js";
|
|
6
|
+
export { createDocRouteEntries } from "../site-schema/doc-route-entries.js";
|
|
6
7
|
export type { DocPageEntry, DocNavNode, AutoIndexNode, DocPageBaseProps, NavSourceDocs };
|
|
7
|
-
/** One enumerated doc route: a content entry or an auto-generated category
|
|
8
|
-
* index, with all per-page derived data pre-computed. */
|
|
9
|
-
export interface DocRouteEntry {
|
|
10
|
-
/** Canonical route slug ("" for the docs root index — #1891). */
|
|
11
|
-
slug: string;
|
|
12
|
-
/** Optional-catchall params array — `toSlugParams(slug)` ([] for the root). */
|
|
13
|
-
slugParams: string[];
|
|
14
|
-
/**
|
|
15
|
-
* True when the entry came from the base collection rather than the locale
|
|
16
|
-
* collection (`!localeSlugSet.has(slug)`). Only meaningful on routes whose
|
|
17
|
-
* nav source performs a locale merge — routes without one (default-locale /
|
|
18
|
-
* versioned-EN, where `localeSlugSet` is empty) must ignore this field.
|
|
19
|
-
* Always false for autoIndex items.
|
|
20
|
-
*/
|
|
21
|
-
isFallback: boolean;
|
|
22
|
-
/** Shared page props (kind/entry/autoIndex/breadcrumbs/prev/next/headings). */
|
|
23
|
-
props: DocPageBaseProps;
|
|
24
|
-
}
|
|
25
|
-
export interface BuildDocRouteEntriesArgs {
|
|
26
|
-
/** Identity-stable nav source for this route's (locale, version) context. */
|
|
27
|
-
source: NavSourceDocs;
|
|
28
|
-
/** Active locale for nav-tree labels and breadcrumbs. */
|
|
29
|
-
locale: string;
|
|
30
|
-
/**
|
|
31
|
-
* Unique memo signature for this route context. Each route file passes its
|
|
32
|
-
* own prefix plus the loop variables (version slug / locale), e.g.
|
|
33
|
-
* "docs;en", "locale-docs;ja", "v-docs;1.0", "v-locale-docs;1.0;ja" —
|
|
34
|
-
* call sites that share a docs array identity must never collide on a key.
|
|
35
|
-
*/
|
|
36
|
-
routeSig: string;
|
|
37
|
-
/** Versioned URL closure bound to the route's version (+ locale). Presence
|
|
38
|
-
* switches the versioned behaviors (breadcrumbs, prev/next, child hrefs). */
|
|
39
|
-
urlFor?: (slug: string) => string;
|
|
40
|
-
}
|
|
41
8
|
export type { BreadcrumbItem, HeadingItem };
|
|
42
|
-
/**
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
export interface DocRouteEntriesContext {
|
|
51
|
-
/** Default locale code (drives the breadcrumb base-prefix selection). */
|
|
52
|
-
defaultLocale: string;
|
|
53
|
-
/** Build the nav tree for a locale (4-arg form with an explicit href builder). */
|
|
54
|
-
buildNavTree: (docs: DocPageEntry[], locale: string, categoryMeta: Map<string, CategoryMeta> | undefined, buildHref: (slug: string, locale: string) => string) => DocNavNode[];
|
|
55
|
-
/** Build a docs URL for the given slug and locale. */
|
|
56
|
-
docsUrl: (slug: string, locale?: string) => string;
|
|
57
|
-
/** Prefix a path with the configured base directory. */
|
|
58
|
-
withBase: (path: string) => string;
|
|
59
|
-
/** Collect all auto-generated index nodes (categories without index.mdx). */
|
|
60
|
-
collectAutoIndexNodes: (tree: DocNavNode[]) => AutoIndexNode[];
|
|
61
|
-
/** Determine the nav section (categoryMatch) for a slug. */
|
|
62
|
-
getNavSectionForSlug: (slug: string) => string | undefined;
|
|
63
|
-
/** Filter top-level nav nodes by a categoryMatch value. */
|
|
64
|
-
getNavSubtree: (tree: DocNavNode[], categoryMatch?: string) => DocNavNode[];
|
|
65
|
-
/** Convert a content entry slug to a canonical route slug. */
|
|
66
|
-
toRouteSlug: (entrySlug: string) => string;
|
|
67
|
-
/** Convert a canonical route slug to an optional-catchall params array. */
|
|
68
|
-
toSlugParams: (slug: string) => string[];
|
|
69
|
-
/** Extract headings from a raw MDX body (bound to settings depth/strategy). */
|
|
70
|
-
extractHeadings: (body: string) => HeadingItem[];
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Result of `createDocRouteEntries`. Contains the `buildDocRouteEntries`
|
|
74
|
-
* function bound to the injected context.
|
|
75
|
-
*/
|
|
76
|
-
export interface DocRouteEntriesAPI {
|
|
77
|
-
/**
|
|
78
|
-
* Enumerate all doc routes for one (locale, version) context, with per-entry
|
|
79
|
-
* derived data pre-computed. Memoized per build on the identity-stable
|
|
80
|
-
* `source.docs` array.
|
|
81
|
-
*/
|
|
82
|
-
buildDocRouteEntries: (args: BuildDocRouteEntriesArgs) => DocRouteEntry[];
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Create the `buildDocRouteEntries` function bound to the host's injected
|
|
86
|
-
* context.
|
|
87
|
-
*
|
|
88
|
-
* @example
|
|
89
|
-
* ```ts
|
|
90
|
-
* // pages/lib/_doc-route-entries.ts (thin stub)
|
|
91
|
-
* import { createDocRouteEntries } from "@takazudo/zudo-doc/doc-route-entries";
|
|
92
|
-
* import { buildNavTree, buildBreadcrumbs, collectAutoIndexNodes } from "@/utils/docs";
|
|
93
|
-
* import { getNavSectionForSlug, getNavSubtree } from "@/utils/nav-scope";
|
|
94
|
-
* import { toRouteSlug, toSlugParams } from "@takazudo/zudo-doc/slug";
|
|
95
|
-
* import { extractHeadings } from "./_extract-headings";
|
|
96
|
-
*
|
|
97
|
-
* export const { buildDocRouteEntries } = createDocRouteEntries(routeContext);
|
|
98
|
-
* export type { DocRouteEntry, BuildDocRouteEntriesArgs };
|
|
99
|
-
* ```
|
|
100
|
-
*/
|
|
101
|
-
export declare function createDocRouteEntries(ctx: DocRouteEntriesContext): DocRouteEntriesAPI;
|
|
9
|
+
/** One enumerated doc route, carrying a zfb collection entry. */
|
|
10
|
+
export type DocRouteEntry = DocRouteEntryGeneric<DocPageEntry>;
|
|
11
|
+
/** Arguments to `buildDocRouteEntries` for one (locale, version) context. */
|
|
12
|
+
export type BuildDocRouteEntriesArgs = BuildDocRouteEntriesArgsGeneric<DocPageEntry>;
|
|
13
|
+
/** The context slice `createDocRouteEntries` derives its bag from. */
|
|
14
|
+
export type DocRouteEntriesContext = DocRouteEntriesContextGeneric<DocPageEntry>;
|
|
15
|
+
/** The functions `createDocRouteEntries` returns. */
|
|
16
|
+
export type DocRouteEntriesAPI = DocRouteEntriesAPIGeneric<DocPageEntry>;
|
|
@@ -1,81 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { buildBreadcrumbs as buildBreadcrumbsImpl } from "../routes/_docs-helpers.js";
|
|
3
|
-
import {
|
|
4
|
-
resolveDocPrevNext,
|
|
5
|
-
flattenSubtree,
|
|
6
|
-
rewriteNavHref,
|
|
7
|
-
remapNavChildHrefs
|
|
8
|
-
} from "../doc-route-paths/index.js";
|
|
9
|
-
function createDocRouteEntries(ctx) {
|
|
10
|
-
const defaultLocale = ctx.defaultLocale;
|
|
11
|
-
const buildNavTree = (docs, locale, categoryMeta) => ctx.buildNavTree(docs, locale, categoryMeta, (slug, loc) => ctx.docsUrl(slug, loc));
|
|
12
|
-
const buildBreadcrumbs = (tree, slug, locale, urlFor) => buildBreadcrumbsImpl(
|
|
13
|
-
tree,
|
|
14
|
-
slug,
|
|
15
|
-
locale === defaultLocale ? ctx.withBase("/") : ctx.withBase(`/${locale}/`),
|
|
16
|
-
urlFor
|
|
17
|
-
);
|
|
18
|
-
const collectAutoIndexNodes = ctx.collectAutoIndexNodes;
|
|
19
|
-
const getNavSectionForSlug = ctx.getNavSectionForSlug;
|
|
20
|
-
const getNavSubtree = ctx.getNavSubtree;
|
|
21
|
-
const toRouteSlug = ctx.toRouteSlug;
|
|
22
|
-
const toSlugParams = ctx.toSlugParams;
|
|
23
|
-
const extractHeadings = ctx.extractHeadings;
|
|
24
|
-
function buildDocRouteEntries(args) {
|
|
25
|
-
const { source, locale, routeSig, urlFor } = args;
|
|
26
|
-
return memoizeDerived([source.docs], `docRouteEntries;${routeSig}`, () => {
|
|
27
|
-
const { docs, navDocs, categoryMeta, localeSlugSet } = source;
|
|
28
|
-
const tree = buildNavTree(navDocs, locale, categoryMeta);
|
|
29
|
-
const breadcrumbTree = urlFor ? tree : buildNavTree(docs, locale, categoryMeta);
|
|
30
|
-
const result = [];
|
|
31
|
-
for (const entry of docs) {
|
|
32
|
-
if (entry.data.category_no_page === true) continue;
|
|
33
|
-
const slug = entry.data.slug ?? toRouteSlug(entry.slug);
|
|
34
|
-
const navSection = getNavSectionForSlug(slug);
|
|
35
|
-
const subtree = getNavSubtree(tree, navSection);
|
|
36
|
-
const { prev: prevNode, next: nextNode } = resolveDocPrevNext(
|
|
37
|
-
tree,
|
|
38
|
-
flattenSubtree(subtree),
|
|
39
|
-
slug,
|
|
40
|
-
entry.data
|
|
41
|
-
);
|
|
42
|
-
result.push({
|
|
43
|
-
slug,
|
|
44
|
-
slugParams: toSlugParams(slug),
|
|
45
|
-
isFallback: !localeSlugSet.has(slug),
|
|
46
|
-
props: {
|
|
47
|
-
kind: "entry",
|
|
48
|
-
entry,
|
|
49
|
-
breadcrumbs: buildBreadcrumbs(breadcrumbTree, slug, locale, urlFor),
|
|
50
|
-
prev: rewriteNavHref(prevNode, urlFor),
|
|
51
|
-
next: rewriteNavHref(nextNode, urlFor),
|
|
52
|
-
headings: extractHeadings(entry.body ?? "")
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
for (const node of collectAutoIndexNodes(tree)) {
|
|
57
|
-
result.push({
|
|
58
|
-
slug: node.slug,
|
|
59
|
-
slugParams: toSlugParams(node.slug),
|
|
60
|
-
isFallback: false,
|
|
61
|
-
props: {
|
|
62
|
-
kind: "autoIndex",
|
|
63
|
-
autoIndex: urlFor ? {
|
|
64
|
-
...node,
|
|
65
|
-
children: remapNavChildHrefs(node.children, urlFor)
|
|
66
|
-
} : node,
|
|
67
|
-
breadcrumbs: buildBreadcrumbs(breadcrumbTree, node.slug, locale, urlFor),
|
|
68
|
-
prev: null,
|
|
69
|
-
next: null,
|
|
70
|
-
headings: []
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
return result;
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
return { buildDocRouteEntries };
|
|
78
|
-
}
|
|
1
|
+
import { createDocRouteEntries } from "../site-schema/doc-route-entries.js";
|
|
79
2
|
export {
|
|
80
3
|
createDocRouteEntries
|
|
81
4
|
};
|
|
@@ -4,7 +4,7 @@ import type { JSX } from "preact";
|
|
|
4
4
|
import type { HeadProps } from "../head/types.js";
|
|
5
5
|
import type { ColorSchemeProviderColorMode } from "../theme/color-scheme-provider.js";
|
|
6
6
|
import type { ChromeContext } from "../factory-context/index.js";
|
|
7
|
-
import type { Settings } from "../settings.js";
|
|
7
|
+
import type { Settings, FaviconConfig } from "../settings.js";
|
|
8
8
|
export interface HeadWithDefaultsProps {
|
|
9
9
|
/** Page title forwarded to og:title. Required. */
|
|
10
10
|
title: string;
|
|
@@ -33,6 +33,8 @@ export interface HeadWithDefaultsSettings {
|
|
|
33
33
|
sidebarResizer?: boolean;
|
|
34
34
|
/** Configured theme-pack slug (ADR `docs/adr/theme-packs.md`, #2822). */
|
|
35
35
|
themePack?: string;
|
|
36
|
+
/** Favicon link set — see {@link resolveFaviconLinks} for the emission table. */
|
|
37
|
+
favicon?: string | FaviconConfig | false;
|
|
36
38
|
}
|
|
37
39
|
/**
|
|
38
40
|
* Create a `HeadWithDefaults` component from the unified {@link ChromeContext}
|
|
@@ -6,8 +6,82 @@ import ThemePackProvider, {
|
|
|
6
6
|
themePackVersionMap
|
|
7
7
|
} from "../theme/theme-pack-provider.js";
|
|
8
8
|
import { DEFAULT_THEME_PACK_SLUG } from "../theme-pack-switcher/theme-pack-sync.js";
|
|
9
|
+
import { renderAutoLogoIconSvg } from "../auto-logo/icon.js";
|
|
9
10
|
import { deriveComposeMetaTitle, deriveColorSchemeGenerators } from "../chrome/derive.js";
|
|
10
11
|
import { assertChromeContext } from "../chrome/assert-chrome-context.js";
|
|
12
|
+
const FAVICON_AUTO = "auto";
|
|
13
|
+
const DEFAULT_FAVICON = {
|
|
14
|
+
svg: "/favicon.svg",
|
|
15
|
+
ico: "/favicon.ico",
|
|
16
|
+
png32: "/favicon-32x32.png",
|
|
17
|
+
png16: "/favicon-16x16.png"
|
|
18
|
+
};
|
|
19
|
+
const FAVICON_TYPE_BY_EXT = {
|
|
20
|
+
svg: "image/svg+xml",
|
|
21
|
+
png: "image/png",
|
|
22
|
+
ico: "image/x-icon",
|
|
23
|
+
jpg: "image/jpeg",
|
|
24
|
+
jpeg: "image/jpeg",
|
|
25
|
+
gif: "image/gif",
|
|
26
|
+
webp: "image/webp",
|
|
27
|
+
avif: "image/avif"
|
|
28
|
+
};
|
|
29
|
+
function faviconType(value) {
|
|
30
|
+
const path = value.split(/[?#]/)[0] ?? "";
|
|
31
|
+
const file = path.slice(path.lastIndexOf("/") + 1);
|
|
32
|
+
const dot = file.lastIndexOf(".");
|
|
33
|
+
if (dot < 0) return void 0;
|
|
34
|
+
return FAVICON_TYPE_BY_EXT[file.slice(dot + 1).toLowerCase()];
|
|
35
|
+
}
|
|
36
|
+
function faviconHref(value, withBase) {
|
|
37
|
+
return value.startsWith("/") ? withBase(value) : value;
|
|
38
|
+
}
|
|
39
|
+
function resolveFaviconLinks(favicon, siteName, withBase) {
|
|
40
|
+
if (favicon === false) return [];
|
|
41
|
+
if (typeof favicon === "string") {
|
|
42
|
+
if (favicon === FAVICON_AUTO) {
|
|
43
|
+
const svg = encodeURIComponent(renderAutoLogoIconSvg(siteName));
|
|
44
|
+
return [{ rel: "icon", type: "image/svg+xml", href: `data:image/svg+xml,${svg}` }];
|
|
45
|
+
}
|
|
46
|
+
const type = faviconType(favicon);
|
|
47
|
+
return [
|
|
48
|
+
{
|
|
49
|
+
rel: "icon",
|
|
50
|
+
...type !== void 0 ? { type } : {},
|
|
51
|
+
href: faviconHref(favicon, withBase)
|
|
52
|
+
}
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
const slots = favicon ?? DEFAULT_FAVICON;
|
|
56
|
+
const links = [];
|
|
57
|
+
if (slots.svg !== void 0) {
|
|
58
|
+
links.push({
|
|
59
|
+
rel: "icon",
|
|
60
|
+
type: faviconType(slots.svg) ?? "image/svg+xml",
|
|
61
|
+
href: faviconHref(slots.svg, withBase)
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
if (slots.ico !== void 0) {
|
|
65
|
+
links.push({ rel: "icon", href: faviconHref(slots.ico, withBase), sizes: "any" });
|
|
66
|
+
}
|
|
67
|
+
if (slots.png32 !== void 0) {
|
|
68
|
+
links.push({
|
|
69
|
+
rel: "icon",
|
|
70
|
+
type: faviconType(slots.png32) ?? "image/png",
|
|
71
|
+
sizes: "32x32",
|
|
72
|
+
href: faviconHref(slots.png32, withBase)
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
if (slots.png16 !== void 0) {
|
|
76
|
+
links.push({
|
|
77
|
+
rel: "icon",
|
|
78
|
+
type: faviconType(slots.png16) ?? "image/png",
|
|
79
|
+
sizes: "16x16",
|
|
80
|
+
href: faviconHref(slots.png16, withBase)
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return links;
|
|
84
|
+
}
|
|
11
85
|
function createHeadWithDefaults(ctx) {
|
|
12
86
|
assertChromeContext(ctx, "createHeadWithDefaults");
|
|
13
87
|
const settings = ctx.settings;
|
|
@@ -19,6 +93,7 @@ function createHeadWithDefaults(ctx) {
|
|
|
19
93
|
const themePackEnabled = themePackRegistry !== null ? themePackVersionMap(themePackRegistry) : null;
|
|
20
94
|
const themePackConfigured = settings.themePack ?? DEFAULT_THEME_PACK_SLUG;
|
|
21
95
|
const themePackBase = withBase("/");
|
|
96
|
+
const faviconLinks = resolveFaviconLinks(settings.favicon, settings.siteName, withBase);
|
|
22
97
|
function HeadWithDefaults({
|
|
23
98
|
title,
|
|
24
99
|
description,
|
|
@@ -65,10 +140,7 @@ function createHeadWithDefaults(ctx) {
|
|
|
65
140
|
}
|
|
66
141
|
),
|
|
67
142
|
settings.sidebarResizer && /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: SIDEBAR_RESIZER_RESTORE_SCRIPT } }),
|
|
68
|
-
/* @__PURE__ */ jsx("link", {
|
|
69
|
-
/* @__PURE__ */ jsx("link", { rel: "icon", href: withBase("/favicon.ico"), sizes: "any" }),
|
|
70
|
-
/* @__PURE__ */ jsx("link", { rel: "icon", type: "image/png", sizes: "32x32", href: withBase("/favicon-32x32.png") }),
|
|
71
|
-
/* @__PURE__ */ jsx("link", { rel: "icon", type: "image/png", sizes: "16x16", href: withBase("/favicon-16x16.png") }),
|
|
143
|
+
faviconLinks.map((attrs, i) => /* @__PURE__ */ jsx("link", { ...attrs }, i)),
|
|
72
144
|
canonical !== void 0 && /* @__PURE__ */ jsx("link", { rel: "canonical", href: canonical }),
|
|
73
145
|
ctx.settings.head && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
74
146
|
ctx.settings.head.preconnect?.map((p, i) => /* @__PURE__ */ jsx(
|
|
@@ -25,6 +25,34 @@ export interface NavItemLike {
|
|
|
25
25
|
* pathForMatch(null, "ja", "en") === ""
|
|
26
26
|
*/
|
|
27
27
|
export declare function pathForMatch(pathWithoutBase: string, lang: string | undefined, defaultLocale: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Pick the deepest header-nav path that the current page lives under.
|
|
30
|
+
*
|
|
31
|
+
* The legacy template flattens parent + child paths, filters by
|
|
32
|
+
* `pathForMatch.startsWith(p)`, and picks the longest match — this keeps
|
|
33
|
+
* a child link active even when its parent's path is also a prefix of
|
|
34
|
+
* the current page.
|
|
35
|
+
*
|
|
36
|
+
* Returns `undefined` when no nav entry matches; callers should treat
|
|
37
|
+
* that as "no active link."
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* Segment-aware prefix test: nav path `/docs/guides` matches `/docs/guides`
|
|
41
|
+
* and `/docs/guides/...` but NOT `/docs/guideship`.
|
|
42
|
+
*
|
|
43
|
+
* Exported (not just module-private) so `nav-overflow-script.ts` can embed it
|
|
44
|
+
* verbatim via `.toString()` alongside `computeActiveNavPath` — that function
|
|
45
|
+
* closes over this one, so a bare `computeActiveNavPath.toString()` embed
|
|
46
|
+
* would reference an undefined `pathMatchesNavPath` in the browser (see the
|
|
47
|
+
* caution note on `computeActiveNavPath` below). Kept self-contained (no
|
|
48
|
+
* outer references) for the same reason.
|
|
49
|
+
*/
|
|
50
|
+
export declare function pathMatchesNavPath(currentPath: string, navPath: string): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* CAUTION (zudolab/zudo-doc#3398): this closes over `pathMatchesNavPath`
|
|
53
|
+
* above. `nav-overflow-script.ts` embeds both via `.toString()` (never this
|
|
54
|
+
* one alone) so the generated browser script is self-contained.
|
|
55
|
+
*/
|
|
28
56
|
export declare function computeActiveNavPath(navItems: readonly NavItemLike[], pathForMatchValue: string): string | undefined;
|
|
29
57
|
/**
|
|
30
58
|
* Active-state predicate for a top-level nav item: the item itself, or
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { AFTER_NAVIGATE_EVENT } from "../transitions/page-events.js";
|
|
2
|
+
import { CURRENT_PATH_SCRIPT_PRELUDE } from "../current-path/index.js";
|
|
3
|
+
import { computeActiveNavPath, pathMatchesNavPath } from "./nav-active.js";
|
|
2
4
|
import {
|
|
3
5
|
NAV_CHEVRON_ACTIVE,
|
|
4
6
|
NAV_CHEVRON_INACTIVE,
|
|
@@ -29,20 +31,26 @@ const NAV_OVERFLOW_SCRIPT = `(function () {
|
|
|
29
31
|
catch (e) { return ""; }
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
// Explicit current-route override, embedded from current-path/index.ts so
|
|
35
|
+
// this script cannot drift from the three other read sites
|
|
36
|
+
// (zudolab/zudo-doc#3398, #3408).
|
|
37
|
+
${CURRENT_PATH_SCRIPT_PRELUDE}
|
|
38
|
+
|
|
39
|
+
// Shared matching core (zudolab/zudo-doc#3398): embedded verbatim from
|
|
40
|
+
// nav-active.ts so this script's longest-match walk cannot drift from the
|
|
41
|
+
// SSR header's own computeActiveNavPath call (header.tsx). computeActiveNavPath
|
|
42
|
+
// closes over pathMatchesNavPath, so both are embedded together.
|
|
43
|
+
var pathMatchesNavPath = ${pathMatchesNavPath.toString()};
|
|
44
|
+
var computeActiveNavPath = ${computeActiveNavPath.toString()};
|
|
37
45
|
|
|
38
46
|
// Recompute which header nav item is "active" from the CURRENT URL and
|
|
39
47
|
// repaint the highlight. SSR sets the active item on first paint, but the
|
|
40
48
|
// header is persisted across same-locale client-router swaps
|
|
41
49
|
// (data-zfb-transition-persist), so without this the highlight would stay
|
|
42
50
|
// frozen on the page where the header was first rendered. Mirrors the
|
|
43
|
-
// sidebar island's client-side approach (match
|
|
51
|
+
// sidebar island's client-side approach (match the current path against
|
|
44
52
|
// each entry's href) and the SSR longest-match + dropdown-parent rules.
|
|
45
|
-
// URL-based: hrefs and
|
|
53
|
+
// URL-based: hrefs and the current path both carry the base + locale
|
|
46
54
|
// prefix, so they compare directly without stripping.
|
|
47
55
|
function applyActiveNav() {
|
|
48
56
|
var nav = document.querySelector("[data-header-nav]");
|
|
@@ -50,26 +58,31 @@ const NAV_OVERFLOW_SCRIPT = `(function () {
|
|
|
50
58
|
var topItems = Array.from(nav.querySelectorAll(":scope > [data-nav-item]"));
|
|
51
59
|
if (topItems.length === 0) return;
|
|
52
60
|
|
|
53
|
-
var cur = trimSlashes(
|
|
61
|
+
var cur = trimSlashes(readCurrentPath(CURRENT_PATH_DATASET_KEY));
|
|
54
62
|
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
|
|
63
|
+
// Build NavItemLike-shaped entries from the live DOM so the shared
|
|
64
|
+
// computeActiveNavPath can do the deepest-match walk \u2014 the same call
|
|
65
|
+
// shape the SSR header uses (matches computeActiveNavPath). A dropdown
|
|
66
|
+
// missing its own top-level anchor is skipped entirely (path "" would
|
|
67
|
+
// otherwise match every current path \u2014 pathMatchesNavPath treats "" as
|
|
68
|
+
// the root "/"), mirroring the parentLink guard used below for the same
|
|
69
|
+
// malformed-markup case.
|
|
70
|
+
var navItems = [];
|
|
58
71
|
topItems.forEach(function (it) {
|
|
59
72
|
var isDropdown = it.hasAttribute("data-nav-item-dropdown");
|
|
60
73
|
var topA = isDropdown ? it.querySelector(":scope > a") : it;
|
|
61
|
-
if (topA)
|
|
62
|
-
|
|
63
|
-
if (isUnderPath(cur, tp) && tp.length > activePath.length) activePath = tp;
|
|
64
|
-
}
|
|
74
|
+
if (!topA) return;
|
|
75
|
+
var children = [];
|
|
65
76
|
if (isDropdown) {
|
|
66
77
|
it.querySelectorAll(":scope > div a").forEach(function (c) {
|
|
67
|
-
|
|
68
|
-
if (isUnderPath(cur, cp) && cp.length > activePath.length) activePath = cp;
|
|
78
|
+
children.push({ path: navPathname(c) });
|
|
69
79
|
});
|
|
70
80
|
}
|
|
81
|
+
navItems.push({ path: navPathname(topA), children: children });
|
|
71
82
|
});
|
|
72
83
|
|
|
84
|
+
var activePath = computeActiveNavPath(navItems, cur) || "";
|
|
85
|
+
|
|
73
86
|
function setTopActive(a, active) {
|
|
74
87
|
if (!a) return;
|
|
75
88
|
if (active) {
|
|
@@ -12,7 +12,8 @@ import { assertChromeContext } from "../chrome/assert-chrome-context.js";
|
|
|
12
12
|
import {
|
|
13
13
|
deriveGetUnavailableVersions,
|
|
14
14
|
deriveNavDataPrep,
|
|
15
|
-
deriveSearchWidgetSlot
|
|
15
|
+
deriveSearchWidgetSlot,
|
|
16
|
+
skipsPackageDefaultDesignTokenPanel
|
|
16
17
|
} from "../chrome/derive.js";
|
|
17
18
|
function createHeaderWithDefaults(ctx) {
|
|
18
19
|
assertChromeContext(ctx, "createHeaderWithDefaults");
|
|
@@ -130,7 +131,15 @@ function createHeaderWithDefaults(ctx) {
|
|
|
130
131
|
const headerRightItems = filterHeaderRightItems(
|
|
131
132
|
settings.headerRightItems ?? [],
|
|
132
133
|
{
|
|
133
|
-
|
|
134
|
+
// Mirrors `deriveBodyEndIslands`' slot resolution (#3414): when the
|
|
135
|
+
// derive-level skip leaves a page with no panel island and no
|
|
136
|
+
// pre-hydration toggle shim, the header toggle button must go with
|
|
137
|
+
// them — otherwise it dispatches `toggle-design-token-panel` into a
|
|
138
|
+
// document with no listener and no click queue (a dead button).
|
|
139
|
+
// `hostBindings.DesignTokenPanelBootstrap` covers the injected-route
|
|
140
|
+
// path too (`routes/_chrome.tsx` threads the configured island through
|
|
141
|
+
// that slot), so injected pages keep the button.
|
|
142
|
+
designTokenPanel: Boolean(settings.designTokenPanel) && (ctx.hostBindings.DesignTokenPanelBootstrap != null || !skipsPackageDefaultDesignTokenPanel(ctx.settings)),
|
|
134
143
|
aiAssistant: Boolean(settings.aiAssistant),
|
|
135
144
|
colorMode: Boolean(settings.colorMode),
|
|
136
145
|
hasLocales: Object.keys(settings.locales).length > 0,
|
|
@@ -40,6 +40,12 @@ export declare function switchLocaleHref(pathname: string, config: LanguageSwitc
|
|
|
40
40
|
* `window[FLAG]` makes it idempotent: the tag may re-execute on a hard reload
|
|
41
41
|
* or a cross-locale header repaint, but the listener is registered exactly
|
|
42
42
|
* once per page lifetime.
|
|
43
|
+
*
|
|
44
|
+
* The pathname fed to `switchLocaleHref` comes from the embedded
|
|
45
|
+
* `readCurrentPath`, which prefers the `data-zd-current-path` override over
|
|
46
|
+
* `location.pathname` — the same explicit current-route reader
|
|
47
|
+
* `nav-overflow-script.ts` / `sidebar-tree-island` / `version-switcher.tsx`
|
|
48
|
+
* use (zudolab/zudo-doc#3398, consolidated by #3408).
|
|
43
49
|
*/
|
|
44
50
|
export declare const LANGUAGE_SWITCHER_INIT_SCRIPT: string;
|
|
45
51
|
export interface LanguageSwitcherProps {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx, jsxs } from "preact/jsx-runtime";
|
|
2
2
|
import { Fragment } from "preact";
|
|
3
3
|
import { AFTER_NAVIGATE_EVENT } from "../transitions/index.js";
|
|
4
|
+
import { CURRENT_PATH_SCRIPT_PRELUDE } from "../current-path/index.js";
|
|
4
5
|
function switchLocaleHref(pathname, config, currentLang, targetLang) {
|
|
5
6
|
const normalizedBase = config.base;
|
|
6
7
|
const defaultLocale = config.defaultLocale;
|
|
@@ -45,6 +46,7 @@ const LANGUAGE_SWITCHER_INIT_SCRIPT = `(function(){
|
|
|
45
46
|
var FLAG="__zdLanguageSwitcherInit";
|
|
46
47
|
if(window[FLAG])return;
|
|
47
48
|
window[FLAG]=true;
|
|
49
|
+
${CURRENT_PATH_SCRIPT_PRELUDE}
|
|
48
50
|
var switchLocaleHref=${switchLocaleHref.toString()};
|
|
49
51
|
function rewire(){
|
|
50
52
|
var containers=document.querySelectorAll("[data-language-switcher]");
|
|
@@ -57,7 +59,7 @@ for(var j=0;j<anchors.length;j++){
|
|
|
57
59
|
var a=anchors[j];
|
|
58
60
|
var target=a.getAttribute("lang");
|
|
59
61
|
if(!target)continue;
|
|
60
|
-
a.setAttribute("href",switchLocaleHref(
|
|
62
|
+
a.setAttribute("href",switchLocaleHref(readCurrentPath(CURRENT_PATH_DATASET_KEY),config,currentLang,target));
|
|
61
63
|
}
|
|
62
64
|
}
|
|
63
65
|
}
|
|
@@ -208,6 +208,12 @@ export declare const VERSION_SWITCHER_INIT_SCRIPT: string;
|
|
|
208
208
|
* `window[FLAG]` makes it idempotent: the tag may re-execute on a hard reload or
|
|
209
209
|
* a cross-locale header repaint, but the listener registers exactly once per
|
|
210
210
|
* page lifetime.
|
|
211
|
+
*
|
|
212
|
+
* The pathname fed to `computeVersionSwitcherState` comes from the embedded
|
|
213
|
+
* `readCurrentPath`, which prefers the `data-zd-current-path` override over
|
|
214
|
+
* `location.pathname` — the same explicit current-route reader
|
|
215
|
+
* `nav-overflow-script.ts` / `sidebar-tree-island` / `language-switcher.tsx`
|
|
216
|
+
* use (zudolab/zudo-doc#3398, consolidated by #3408).
|
|
211
217
|
*/
|
|
212
218
|
export declare const VERSION_SWITCHER_REWIRE_SCRIPT: string;
|
|
213
219
|
export type { VersionEntry, VersionSwitcherLabels };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx, jsxs } from "preact/jsx-runtime";
|
|
2
2
|
import { AFTER_NAVIGATE_EVENT } from "../transitions/page-events.js";
|
|
3
|
+
import { CURRENT_PATH_SCRIPT_PRELUDE } from "../current-path/index.js";
|
|
3
4
|
import { UNAVAILABLE_VERSIONS_ATTR } from "../version-availability/index.js";
|
|
4
5
|
function computeVersionSwitcherState(pathname, config, versionSlugs) {
|
|
5
6
|
const normalizedBase = config.base;
|
|
@@ -235,6 +236,7 @@ var FLAG="__zdVersionSwitcherRewire";
|
|
|
235
236
|
if(window[FLAG])return;
|
|
236
237
|
window[FLAG]=true;
|
|
237
238
|
var ATTR=${JSON.stringify(UNAVAILABLE_VERSIONS_ATTR)};
|
|
239
|
+
${CURRENT_PATH_SCRIPT_PRELUDE}
|
|
238
240
|
var computeVersionSwitcherState=${computeVersionSwitcherState.toString()};
|
|
239
241
|
function setActive(a,active){
|
|
240
242
|
a.classList.toggle("font-bold",active);
|
|
@@ -276,7 +278,7 @@ for(var j=0;j<versionAnchors.length;j++){
|
|
|
276
278
|
var s=versionAnchors[j].getAttribute("data-version-slug");
|
|
277
279
|
if(s)slugs.push(s);
|
|
278
280
|
}
|
|
279
|
-
var state=computeVersionSwitcherState(
|
|
281
|
+
var state=computeVersionSwitcherState(readCurrentPath(CURRENT_PATH_DATASET_KEY),config,slugs);
|
|
280
282
|
var latest=c.querySelector("[data-version-latest]");
|
|
281
283
|
if(latest){
|
|
282
284
|
latest.setAttribute("href",state.latestHref);
|
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
import type { CategoryMeta } from "../sidebar-tree/index.js";
|
|
2
2
|
import type { DocPageEntry } from "../doc-page-props/index.js";
|
|
3
|
+
import type { NavSourceDocs as NavSourceDocsShape } from "../site-schema/types.js";
|
|
3
4
|
import type { Settings } from "../settings.js";
|
|
4
5
|
export type { CategoryMeta, DocPageEntry };
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
categoryMeta: Map<string, CategoryMeta>;
|
|
12
|
-
/** Slugs that came from the locale collection (for isFallback). Empty for
|
|
13
|
-
* default-locale / single-collection cases. */
|
|
14
|
-
localeSlugSet: ReadonlySet<string>;
|
|
15
|
-
}
|
|
6
|
+
/**
|
|
7
|
+
* The resolved nav source for one (locale, version) context, carrying zfb
|
|
8
|
+
* collection entries. The shape itself is browser-safe and declared in
|
|
9
|
+
* `../site-schema/types.js` (#3395); this alias pins it to `DocPageEntry`.
|
|
10
|
+
*/
|
|
11
|
+
export type NavSourceDocs = NavSourceDocsShape<DocPageEntry>;
|
|
16
12
|
/**
|
|
17
13
|
* How to filter the merged doc list.
|
|
18
14
|
*/
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File extensions zfb's router accepts as `pages/` route sources. Mirrors
|
|
3
|
+
* the Rust-side single source of truth `zfb_types::ROUTABLE_PAGE_EXTENSIONS`
|
|
4
|
+
* (zfb crate `crates/zfb-types/src/page_extensions.rs`) — that constant is
|
|
5
|
+
* not importable from JS, so it is copied here deliberately; keep in sync if
|
|
6
|
+
* zfb ever changes its accepted extension list.
|
|
7
|
+
*/
|
|
8
|
+
export declare const ROUTABLE_PAGE_EXTENSIONS: readonly ["tsx", "ts", "jsx", "js", "mdx", "md", "html"];
|
|
9
|
+
/**
|
|
10
|
+
* Derive every `pages/`-relative path that would shadow the given injected
|
|
11
|
+
* route pattern (a zfb `injectRoute` pattern string, e.g.
|
|
12
|
+
* `"/docs/tags/[tag]"` or `"/[locale]"`). Pure — no filesystem I/O; the
|
|
13
|
+
* caller probes each candidate against the host's real `pages/` dir.
|
|
14
|
+
*
|
|
15
|
+
* Returns `[]` for the empty/root pattern (`"/"`) — never produced by this
|
|
16
|
+
* plugin's route catalog (zfb rejects `injectRoute("/")` outright), so there
|
|
17
|
+
* is nothing meaningful to derive.
|
|
18
|
+
*/
|
|
19
|
+
export declare function derivePagesCandidates(pattern: string): string[];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const ROUTABLE_PAGE_EXTENSIONS = ["tsx", "ts", "jsx", "js", "mdx", "md", "html"];
|
|
2
|
+
function derivePagesCandidates(pattern) {
|
|
3
|
+
const segments = pattern.split("/").filter(Boolean);
|
|
4
|
+
if (segments.length === 0) return [];
|
|
5
|
+
const lastSegment = segments[segments.length - 1];
|
|
6
|
+
const dirPrefix = segments.length > 1 ? `${segments.slice(0, -1).join("/")}/` : "";
|
|
7
|
+
const candidates = [];
|
|
8
|
+
for (const ext of ROUTABLE_PAGE_EXTENSIONS) {
|
|
9
|
+
candidates.push(`${dirPrefix}${lastSegment}.${ext}`);
|
|
10
|
+
candidates.push(`${dirPrefix}${lastSegment}/index.${ext}`);
|
|
11
|
+
}
|
|
12
|
+
return candidates;
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
ROUTABLE_PAGE_EXTENSIONS,
|
|
16
|
+
derivePagesCandidates
|
|
17
|
+
};
|