@takazudo/zudo-doc 2.2.2 → 2.4.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/chrome/index.d.ts +1 -0
- package/dist/chrome/index.js +4 -1
- package/dist/doc-content-header/index.d.ts +4 -0
- package/dist/doc-content-header/index.js +8 -0
- package/dist/factory-context/index.d.ts +26 -0
- package/dist/home-page/index.d.ts +51 -0
- package/dist/home-page/index.js +124 -0
- package/dist/home-page/prepare-home-data.d.ts +46 -0
- package/dist/home-page/prepare-home-data.js +37 -0
- package/dist/integrations/doc-history/pre-build.js +11 -33
- package/dist/plugins/routes.js +28 -1
- package/dist/preset.d.ts +9 -0
- package/dist/routes/_chrome.d.ts +1 -1
- package/dist/routes/_chrome.js +6 -2
- package/dist/routes/index.js +6 -109
- package/dist/routes/locale-index.js +6 -117
- package/dist/safelist.css +1 -1
- package/dist/search-widget-script/index.js +8 -27
- package/dist/search-widget-script/scoring.d.ts +16 -0
- package/dist/search-widget-script/scoring.js +25 -0
- package/dist/settings.d.ts +34 -0
- package/dist/sidebar-active-slug/index.d.ts +4 -0
- package/dist/sidebar-active-slug/index.js +15 -0
- package/dist/sidebar-filter/index.d.ts +10 -0
- package/dist/sidebar-filter/index.js +16 -0
- package/dist/sidebar-tree-island/index.js +2 -24
- package/eject/sidebar-tree-island/index.tsx +2 -33
- package/package.json +19 -5
- package/routes-src/_chrome.tsx +16 -0
- package/routes-src/_virtual.d.ts +15 -0
- package/routes-src/index.tsx +11 -117
- package/routes-src/locale-index.tsx +15 -123
package/dist/chrome/index.d.ts
CHANGED
|
@@ -32,6 +32,7 @@ export declare function createChrome<S extends Settings = Settings>(context: Rou
|
|
|
32
32
|
}) => import("preact").JSX.Element;
|
|
33
33
|
SiteTreeNavWrapper: import("../factory-context/index.js").FactoryComponent;
|
|
34
34
|
BodyEndIslands: (props: import("../doc-body-end-islands/index.js").BodyEndIslandsProps) => import("preact").JSX.Element;
|
|
35
|
+
HomePageView: (props: import("../home-page/index.js").HomePageViewProps) => import("preact").JSX.Element;
|
|
35
36
|
};
|
|
36
37
|
/** The wired page-chrome surface returned by {@link createChrome}. */
|
|
37
38
|
export type Chrome = ReturnType<typeof createChrome>;
|
package/dist/chrome/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { createSidebarWithDefaults } from "../sidebar-with-defaults/index.js";
|
|
|
5
5
|
import { createRenderDocPage } from "../doc-page-renderer/index.js";
|
|
6
6
|
import { createVersionsPageView } from "../versions-page/index.js";
|
|
7
7
|
import { createTagPages } from "../tag-pages/index.js";
|
|
8
|
+
import { createHomePageView } from "../home-page/index.js";
|
|
8
9
|
import {
|
|
9
10
|
deriveComposeMetaTitle,
|
|
10
11
|
deriveBodyEndIslands,
|
|
@@ -23,6 +24,7 @@ function createChrome(context, hostBindings = {}) {
|
|
|
23
24
|
const { collectTagMapForLocale, TagDetailPageView, TagsIndexPageView } = createTagPages(ctx);
|
|
24
25
|
const { SiteTreeNavWrapper } = deriveMdxComponents(ctx);
|
|
25
26
|
const BodyEndIslands = deriveBodyEndIslands(ctx);
|
|
27
|
+
const HomePageView = createHomePageView(ctx);
|
|
26
28
|
return {
|
|
27
29
|
composeMetaTitle,
|
|
28
30
|
HeadWithDefaults,
|
|
@@ -35,7 +37,8 @@ function createChrome(context, hostBindings = {}) {
|
|
|
35
37
|
TagDetailPageView,
|
|
36
38
|
TagsIndexPageView,
|
|
37
39
|
SiteTreeNavWrapper,
|
|
38
|
-
BodyEndIslands
|
|
40
|
+
BodyEndIslands,
|
|
41
|
+
HomePageView
|
|
39
42
|
};
|
|
40
43
|
}
|
|
41
44
|
export {
|
|
@@ -73,5 +73,9 @@ interface DocContentHeaderProps {
|
|
|
73
73
|
* are HOST-bound slots (`ctx.hostBindings.buildFrontmatterPreviewEntries` /
|
|
74
74
|
* `…frontmatterRenderers`, defaulting to `() => []` / `{}`). The nested
|
|
75
75
|
* `DocMetainfoArea` / `DocTagsArea` are rebuilt from the same context.
|
|
76
|
+
*
|
|
77
|
+
* `ctx.hostBindings.docContentHeaderExtras` (default: absent) renders after
|
|
78
|
+
* the `<h1>` and before `DocMetainfoArea` — see the `ChromeHostBindings`
|
|
79
|
+
* interface in `./factory-context`.
|
|
76
80
|
*/
|
|
77
81
|
export declare function createDocContentHeader<S extends Settings = Settings>(ctx: ChromeContext<S>): (props: DocContentHeaderProps) => JSX.Element;
|
|
@@ -8,6 +8,7 @@ function createDocContentHeader(ctx) {
|
|
|
8
8
|
const t = ctx.t;
|
|
9
9
|
const buildFrontmatterPreviewEntries = ctx.hostBindings.buildFrontmatterPreviewEntries ?? (() => []);
|
|
10
10
|
const frontmatterRenderers = ctx.hostBindings.frontmatterRenderers ?? {};
|
|
11
|
+
const docContentHeaderExtras = ctx.hostBindings.docContentHeaderExtras;
|
|
11
12
|
const DocMetainfoArea = createDocMetainfoArea(ctx);
|
|
12
13
|
const DocTagsArea = createDocTagsArea(ctx);
|
|
13
14
|
function DocContentHeader({
|
|
@@ -19,6 +20,13 @@ function createDocContentHeader(ctx) {
|
|
|
19
20
|
}) {
|
|
20
21
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
21
22
|
/* @__PURE__ */ jsx("h1", { class: "text-heading font-bold mb-vsp-xs", children: entry.data.title }),
|
|
23
|
+
docContentHeaderExtras?.({
|
|
24
|
+
entry,
|
|
25
|
+
slug,
|
|
26
|
+
locale,
|
|
27
|
+
isFallback,
|
|
28
|
+
version
|
|
29
|
+
}),
|
|
22
30
|
!version && /* @__PURE__ */ jsx(DocMetainfoArea, { slug, locale, isFallback }),
|
|
23
31
|
!version && /* @__PURE__ */ jsx(DocTagsArea, { slug, locale, tags: entry.data.tags }),
|
|
24
32
|
isFallback && !entry.data.generated && /* @__PURE__ */ jsx(
|
|
@@ -224,6 +224,32 @@ export interface ChromeHostBindings {
|
|
|
224
224
|
/** MDX content-component overrides (Details / HtmlPreview / Island /
|
|
225
225
|
* PresetGenerator). Default: package SSR impls + a `PresetGenerator` stub. */
|
|
226
226
|
mdxExtras?: Record<string, FactoryComponent>;
|
|
227
|
+
/**
|
|
228
|
+
* Extra content rendered inside the doc content header, between the `<h1>`
|
|
229
|
+
* and the metainfo block. A RENDERER (not a component) so it is naturally
|
|
230
|
+
* keyed on the page entry/frontmatter rather than re-deriving them from
|
|
231
|
+
* props. Default: absent → renders nothing (byte-identical output to the
|
|
232
|
+
* pre-seam header). Called for `kind === "entry"` doc pages on all 4 doc
|
|
233
|
+
* routes, INCLUDING versioned pages — it receives `version` and decides for
|
|
234
|
+
* itself whether/how to render on a versioned page.
|
|
235
|
+
*/
|
|
236
|
+
docContentHeaderExtras?: (args: {
|
|
237
|
+
entry: DocPageEntry;
|
|
238
|
+
slug: string;
|
|
239
|
+
locale: string;
|
|
240
|
+
isFallback?: boolean;
|
|
241
|
+
version?: string;
|
|
242
|
+
}) => unknown;
|
|
243
|
+
/**
|
|
244
|
+
* Extra content rendered in the home hero. A RENDERER (not a component).
|
|
245
|
+
* Default: absent → renders nothing. The `/` home route is never injected
|
|
246
|
+
* by the routes plugin (zfb rejects `/`), so this binding fires on injected
|
|
247
|
+
* `/[locale]` homes and on any host that threads it through `createChrome`.
|
|
248
|
+
* A `HomePageView` `extras` prop (added in a later task) takes precedence.
|
|
249
|
+
*/
|
|
250
|
+
homeExtras?: (args: {
|
|
251
|
+
locale: string;
|
|
252
|
+
}) => unknown;
|
|
227
253
|
}
|
|
228
254
|
/**
|
|
229
255
|
* The unified chrome context — the superset that fully wires the page chrome:
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** @jsxRuntime automatic */
|
|
2
|
+
/** @jsxImportSource preact */
|
|
3
|
+
import type { ComponentChildren, JSX } from "preact";
|
|
4
|
+
import type { DocNavNode } from "../doc-page-props/index.js";
|
|
5
|
+
import type { ChromeContext } from "../factory-context/index.js";
|
|
6
|
+
import type { Settings } from "../settings.js";
|
|
7
|
+
export { prepareHomeData } from "./prepare-home-data.js";
|
|
8
|
+
export type { PrepareHomeDataOptions, HomeData } from "./prepare-home-data.js";
|
|
9
|
+
/** Props for the `HomePageView` component built by {@link createHomePageView}. */
|
|
10
|
+
export interface HomePageViewProps {
|
|
11
|
+
/** Active locale — drives hero copy, link locale-prefixing, and the
|
|
12
|
+
* `hostBindings.homeExtras({ locale })` call when `extras` is absent. */
|
|
13
|
+
locale: string;
|
|
14
|
+
/**
|
|
15
|
+
* Extra content rendered after the links row, inside the hero text column.
|
|
16
|
+
* A VALUE (already-rendered children) — the host-PAGE path. Takes
|
|
17
|
+
* precedence over `ctx.hostBindings.homeExtras` when both are present.
|
|
18
|
+
*
|
|
19
|
+
* This value-vs-renderer asymmetry (prop = value, `hostBindings.homeExtras`
|
|
20
|
+
* = renderer) is INTENTIONAL, not an oversight to "unify": a host page
|
|
21
|
+
* already has its JSX in hand (a value), while the injected/bindings path
|
|
22
|
+
* only has a locale string at render time and must derive its own content
|
|
23
|
+
* from it (a renderer). Resolved as `extras ?? hostBindings.homeExtras?.({
|
|
24
|
+
* locale })`.
|
|
25
|
+
*/
|
|
26
|
+
extras?: ComponentChildren;
|
|
27
|
+
/** Prepared nav tree for the `SiteTreeNav` grid (already grouped via
|
|
28
|
+
* `groupSatelliteNodes` by the caller — the two routes group their own
|
|
29
|
+
* locale-specific tree before handing it here). */
|
|
30
|
+
tree: DocNavNode[];
|
|
31
|
+
/** Ordered category prefixes, passed through to `SiteTreeNav`. */
|
|
32
|
+
categoryOrder: string[];
|
|
33
|
+
/** Unique tag count for the current locale — gates the "all tags" section
|
|
34
|
+
* together with `settings.docTags`. */
|
|
35
|
+
tagCount: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Create a `HomePageView` component from the unified {@link ChromeContext}
|
|
39
|
+
* (epic Collapse Wiring Shells #2420-style factory shape, adopted for the
|
|
40
|
+
* home page in #2502).
|
|
41
|
+
*
|
|
42
|
+
* Reads `settings`/`t`/`withBase`/`defaultLocale` off the context, derives
|
|
43
|
+
* `composeMetaTitle` + Head/Header/Footer/BodyEndIslands chrome from the SAME
|
|
44
|
+
* context (so the injected package-routes path and a host-bound context both
|
|
45
|
+
* render byte-identically), and computes the locale-URL prefix (`""` for the
|
|
46
|
+
* default locale, `/{locale}` otherwise) shared by the overview link, the
|
|
47
|
+
* header `currentPath`, and the "all tags" link — reproducing the exact
|
|
48
|
+
* per-route path construction `routes/index.tsx` / `routes/locale-index.tsx`
|
|
49
|
+
* did inline before this extraction.
|
|
50
|
+
*/
|
|
51
|
+
export declare function createHomePageView<S extends Settings = Settings>(ctx: ChromeContext<S>): (props: HomePageViewProps) => JSX.Element;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
|
|
2
|
+
import { Island } from "@takazudo/zfb";
|
|
3
|
+
import { DocLayoutWithDefaults } from "../doclayout/index.js";
|
|
4
|
+
import { SiteTreeNav } from "../site-tree-nav-island/index.js";
|
|
5
|
+
import { createHeadWithDefaults } from "../head-with-defaults/index.js";
|
|
6
|
+
import { createHeaderWithDefaults } from "../header-with-defaults/index.js";
|
|
7
|
+
import { createFooterWithDefaults } from "../footer-with-defaults/index.js";
|
|
8
|
+
import { deriveComposeMetaTitle, deriveBodyEndIslands } from "../chrome/derive.js";
|
|
9
|
+
import { assertChromeContext } from "../chrome/assert-chrome-context.js";
|
|
10
|
+
import { prepareHomeData } from "./prepare-home-data.js";
|
|
11
|
+
function createHomePageView(ctx) {
|
|
12
|
+
assertChromeContext(ctx, "createHomePageView");
|
|
13
|
+
const settings = ctx.settings;
|
|
14
|
+
const t = ctx.t;
|
|
15
|
+
const withBase = ctx.withBase;
|
|
16
|
+
const defaultLocale = ctx.defaultLocale;
|
|
17
|
+
const composeMetaTitle = deriveComposeMetaTitle(ctx);
|
|
18
|
+
const HeadWithDefaults = createHeadWithDefaults(ctx);
|
|
19
|
+
const HeaderWithDefaults = createHeaderWithDefaults(ctx);
|
|
20
|
+
const FooterWithDefaults = createFooterWithDefaults(ctx);
|
|
21
|
+
const BodyEndIslands = deriveBodyEndIslands(ctx);
|
|
22
|
+
const homeExtras = ctx.hostBindings.homeExtras;
|
|
23
|
+
function HomePageView({
|
|
24
|
+
locale,
|
|
25
|
+
extras,
|
|
26
|
+
tree,
|
|
27
|
+
categoryOrder,
|
|
28
|
+
tagCount
|
|
29
|
+
}) {
|
|
30
|
+
const prefix = locale === defaultLocale ? "" : `/${locale}`;
|
|
31
|
+
const ctaNav = settings.headerNav[0] ?? null;
|
|
32
|
+
const overview = ctaNav ? withBase(`${prefix}${ctaNav.path}`) : null;
|
|
33
|
+
const logoUrl = withBase("/img/logo.svg");
|
|
34
|
+
const resolvedExtras = extras ?? homeExtras?.({ locale });
|
|
35
|
+
return /* @__PURE__ */ jsxs(
|
|
36
|
+
DocLayoutWithDefaults,
|
|
37
|
+
{
|
|
38
|
+
title: composeMetaTitle(settings.siteName),
|
|
39
|
+
head: /* @__PURE__ */ jsx(HeadWithDefaults, { title: settings.siteName }),
|
|
40
|
+
lang: locale,
|
|
41
|
+
noindex: settings.noindex,
|
|
42
|
+
hideSidebar: true,
|
|
43
|
+
hideToc: true,
|
|
44
|
+
sidebarOverride: /* @__PURE__ */ jsx(Fragment, {}),
|
|
45
|
+
headerOverride: /* @__PURE__ */ jsx(HeaderWithDefaults, { lang: locale, currentPath: withBase(`${prefix}/`) }),
|
|
46
|
+
footerOverride: /* @__PURE__ */ jsx(FooterWithDefaults, { lang: locale }),
|
|
47
|
+
bodyEndComponents: /* @__PURE__ */ jsx(BodyEndIslands, { basePath: settings.base ?? "/" }),
|
|
48
|
+
enableClientRouter: settings.dynamicPageTransition,
|
|
49
|
+
children: [
|
|
50
|
+
/* @__PURE__ */ jsx("div", { class: "flex justify-center mb-vsp-xl", children: /* @__PURE__ */ jsxs("div", { class: "flex flex-col items-center text-center gap-hsp-md lg:flex-row lg:text-left lg:gap-hsp-xl", children: [
|
|
51
|
+
/* @__PURE__ */ jsx(
|
|
52
|
+
"div",
|
|
53
|
+
{
|
|
54
|
+
class: "w-[320px] max-w-full aspect-[1200/630] bg-fg shrink-0",
|
|
55
|
+
style: {
|
|
56
|
+
WebkitMask: `url(${logoUrl}) center/contain no-repeat`,
|
|
57
|
+
mask: `url(${logoUrl}) center/contain no-repeat`
|
|
58
|
+
},
|
|
59
|
+
"aria-hidden": "true"
|
|
60
|
+
}
|
|
61
|
+
),
|
|
62
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
63
|
+
/* @__PURE__ */ jsx("h1", { class: "text-heading font-bold mb-vsp-2xs", children: settings.siteName }),
|
|
64
|
+
/* @__PURE__ */ jsx("p", { class: "text-muted text-small mb-vsp-sm", children: settings.siteDescription }),
|
|
65
|
+
/* @__PURE__ */ jsxs("div", { class: "flex items-center justify-center lg:justify-start gap-hsp-md text-small", children: [
|
|
66
|
+
overview && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
67
|
+
/* @__PURE__ */ jsx("a", { href: overview, class: "text-fg underline hover:text-accent", children: t("nav.overview", locale) }),
|
|
68
|
+
/* @__PURE__ */ jsx("span", { class: "text-muted", children: "/" })
|
|
69
|
+
] }),
|
|
70
|
+
settings.githubUrl && /* @__PURE__ */ jsxs(
|
|
71
|
+
"a",
|
|
72
|
+
{
|
|
73
|
+
href: settings.githubUrl,
|
|
74
|
+
class: "inline-flex items-center gap-[0.3em] text-fg underline hover:text-accent",
|
|
75
|
+
target: "_blank",
|
|
76
|
+
rel: "noopener noreferrer",
|
|
77
|
+
children: [
|
|
78
|
+
/* @__PURE__ */ jsx("svg", { viewBox: "0 0 16 16", "aria-hidden": "true", class: "w-[1em] h-[1em] shrink-0", children: /* @__PURE__ */ jsx(
|
|
79
|
+
"path",
|
|
80
|
+
{
|
|
81
|
+
fill: "currentColor",
|
|
82
|
+
d: "M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"
|
|
83
|
+
}
|
|
84
|
+
) }),
|
|
85
|
+
"GitHub"
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
)
|
|
89
|
+
] }),
|
|
90
|
+
resolvedExtras
|
|
91
|
+
] })
|
|
92
|
+
] }) }),
|
|
93
|
+
Island({
|
|
94
|
+
when: "idle",
|
|
95
|
+
children: /* @__PURE__ */ jsx(
|
|
96
|
+
SiteTreeNav,
|
|
97
|
+
{
|
|
98
|
+
tree,
|
|
99
|
+
categoryOrder,
|
|
100
|
+
categoryIgnore: ["inbox", "develop"]
|
|
101
|
+
}
|
|
102
|
+
)
|
|
103
|
+
}),
|
|
104
|
+
settings.docTags && tagCount > 0 && /* @__PURE__ */ jsxs("section", { class: "mt-vsp-xl", children: [
|
|
105
|
+
/* @__PURE__ */ jsx("h2", { class: "text-title font-bold mb-vsp-md", children: t("doc.allTags", locale) }),
|
|
106
|
+
/* @__PURE__ */ jsx(
|
|
107
|
+
"a",
|
|
108
|
+
{
|
|
109
|
+
href: withBase(`${prefix}/docs/tags`),
|
|
110
|
+
class: "text-accent underline hover:text-accent-hover",
|
|
111
|
+
children: t("doc.allTags", locale)
|
|
112
|
+
}
|
|
113
|
+
)
|
|
114
|
+
] })
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
return HomePageView;
|
|
120
|
+
}
|
|
121
|
+
export {
|
|
122
|
+
createHomePageView,
|
|
123
|
+
prepareHomeData
|
|
124
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { RouteContext } from "../factory-context/index.js";
|
|
2
|
+
import type { NavSourceOptions } from "../nav-source-docs/index.js";
|
|
3
|
+
import type { DocNavNode } from "../doc-page-props/index.js";
|
|
4
|
+
export interface PrepareHomeDataOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Override nav-source filtering. Default: `{}` for the default locale;
|
|
7
|
+
* `{ applyDefaultLocaleOnlyFilter: true, keepUnlisted: true }` otherwise.
|
|
8
|
+
*/
|
|
9
|
+
navSourceOptions?: NavSourceOptions;
|
|
10
|
+
/**
|
|
11
|
+
* Override the category-meta directory. When set, category meta is loaded via
|
|
12
|
+
* `loadCategoryMeta(categoryMetaDir)` on BOTH branches — replacing the default
|
|
13
|
+
* locale's merged `resolveNavSource` categoryMeta and the non-default locale's
|
|
14
|
+
* `loadCategoryMeta(getLocaleConfig(locale).dir)`. When unset, the default
|
|
15
|
+
* locale uses the categoryMeta `resolveNavSource` returns and non-default
|
|
16
|
+
* locales use `loadCategoryMeta(cfg.dir)` — locale-dir-ONLY.
|
|
17
|
+
*/
|
|
18
|
+
categoryMetaDir?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface HomeData {
|
|
21
|
+
/** GROUPED tree — the post-`groupSatelliteNodes` output (spreads onto
|
|
22
|
+
* `HomePageViewProps.tree`). */
|
|
23
|
+
tree: DocNavNode[];
|
|
24
|
+
categoryOrder: string[];
|
|
25
|
+
tagCount: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Prepare the home-page nav tree / category order / tag count for `locale`.
|
|
29
|
+
*
|
|
30
|
+
* Branches on `locale === ctx.defaultLocale`:
|
|
31
|
+
*
|
|
32
|
+
* - Default locale: `resolveNavSource(locale, undefined, {})` and its
|
|
33
|
+
* returned `categoryMeta` (the merged base+locale meta) — unless
|
|
34
|
+
* `options.categoryMetaDir` is set, in which case that dir is loaded via
|
|
35
|
+
* `loadCategoryMeta` instead.
|
|
36
|
+
* - Non-default locale: requires `ctx.getLocaleConfig(locale)` — throws if
|
|
37
|
+
* the locale isn't configured, absorbing the duplicated route/page guard —
|
|
38
|
+
* then resolves nav source with the locale-home filter and reads category
|
|
39
|
+
* meta from the locale dir ONLY, `loadCategoryMeta(cfg.dir)`. This is
|
|
40
|
+
* deliberately NOT the merged base+locale categoryMeta `resolveNavSource`
|
|
41
|
+
* returns: locale home pages historically never merged in base meta (unlike
|
|
42
|
+
* the locale doc route), and that exact behavior is preserved here — do not
|
|
43
|
+
* "fix" it to use the merged meta, that would silently change sidebar-grid
|
|
44
|
+
* labels/positions.
|
|
45
|
+
*/
|
|
46
|
+
export declare function prepareHomeData(ctx: RouteContext, locale: string, options?: PrepareHomeDataOptions): HomeData;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { loadCategoryMeta } from "../sidebar-tree/index.js";
|
|
2
|
+
const LOCALE_NAV_SOURCE_OPTIONS = {
|
|
3
|
+
applyDefaultLocaleOnlyFilter: true,
|
|
4
|
+
keepUnlisted: true
|
|
5
|
+
};
|
|
6
|
+
function prepareHomeData(ctx, locale, options) {
|
|
7
|
+
let navDocs;
|
|
8
|
+
let categoryMeta;
|
|
9
|
+
if (locale === ctx.defaultLocale) {
|
|
10
|
+
const resolved = ctx.resolveNavSource(locale, void 0, options?.navSourceOptions ?? {});
|
|
11
|
+
navDocs = resolved.navDocs;
|
|
12
|
+
categoryMeta = options?.categoryMetaDir ? loadCategoryMeta(options.categoryMetaDir) : resolved.categoryMeta;
|
|
13
|
+
} else {
|
|
14
|
+
const cfg = ctx.getLocaleConfig(locale);
|
|
15
|
+
if (!cfg) {
|
|
16
|
+
throw new Error(`prepareHomeData: locale "${locale}" is not configured in settings.locales`);
|
|
17
|
+
}
|
|
18
|
+
const resolved = ctx.resolveNavSource(
|
|
19
|
+
locale,
|
|
20
|
+
void 0,
|
|
21
|
+
options?.navSourceOptions ?? LOCALE_NAV_SOURCE_OPTIONS
|
|
22
|
+
);
|
|
23
|
+
navDocs = resolved.navDocs;
|
|
24
|
+
categoryMeta = loadCategoryMeta(options?.categoryMetaDir ?? cfg.dir);
|
|
25
|
+
}
|
|
26
|
+
const tree = ctx.buildNavTree(navDocs, locale, categoryMeta, (slug, loc) => ctx.docsUrl(slug, loc));
|
|
27
|
+
const categoryOrder = ctx.getCategoryOrder();
|
|
28
|
+
const grouped = ctx.groupSatelliteNodes(tree, categoryOrder);
|
|
29
|
+
const tagCount = ctx.collectTags(
|
|
30
|
+
navDocs.filter((d) => !d.data.category_no_page),
|
|
31
|
+
(id, data) => data.slug ?? ctx.toRouteSlug(id)
|
|
32
|
+
).size;
|
|
33
|
+
return { tree: grouped, categoryOrder, tagCount };
|
|
34
|
+
}
|
|
35
|
+
export {
|
|
36
|
+
prepareHomeData
|
|
37
|
+
};
|
|
@@ -2,12 +2,8 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import {
|
|
4
4
|
collectContentFiles,
|
|
5
|
-
|
|
5
|
+
getAllFilesFirstLastMetaAsync
|
|
6
6
|
} from "@takazudo/zudo-doc-history-server/git-history";
|
|
7
|
-
import {
|
|
8
|
-
makeSemaphore,
|
|
9
|
-
defaultGitConcurrency
|
|
10
|
-
} from "@takazudo/zudo-doc-history-server/concurrency";
|
|
11
7
|
const META_OUT_RELATIVE_DIR = ".zfb";
|
|
12
8
|
const META_OUT_FILENAME = "doc-history-meta.json";
|
|
13
9
|
function deriveSourceExt(filePath) {
|
|
@@ -34,8 +30,6 @@ async function runDocHistoryMetaStep(options) {
|
|
|
34
30
|
dirEntries.push([code, path.resolve(projectRoot, locale.dir)]);
|
|
35
31
|
}
|
|
36
32
|
}
|
|
37
|
-
const concurrency = defaultGitConcurrency();
|
|
38
|
-
const acquire = makeSemaphore(concurrency);
|
|
39
33
|
const jobs = [];
|
|
40
34
|
for (const [localeKey, contentDir] of dirEntries) {
|
|
41
35
|
const files = collectContentFiles(contentDir);
|
|
@@ -44,36 +38,20 @@ async function runDocHistoryMetaStep(options) {
|
|
|
44
38
|
jobs.push({ composedSlug, filePath });
|
|
45
39
|
}
|
|
46
40
|
}
|
|
47
|
-
const
|
|
48
|
-
await
|
|
49
|
-
jobs.map(async ({ filePath }, i) => {
|
|
50
|
-
const release = await acquire();
|
|
51
|
-
try {
|
|
52
|
-
const allCommits = await getFileCommitsMetaAsync(filePath);
|
|
53
|
-
if (allCommits.length === 0) return;
|
|
54
|
-
const newestInfo = allCommits[0];
|
|
55
|
-
const oldestInfo = allCommits[allCommits.length - 1] ?? newestInfo;
|
|
56
|
-
results[i] = {
|
|
57
|
-
newest: { author: newestInfo.author, date: newestInfo.date },
|
|
58
|
-
oldest: { author: oldestInfo.author, date: oldestInfo.date }
|
|
59
|
-
};
|
|
60
|
-
} finally {
|
|
61
|
-
release();
|
|
62
|
-
}
|
|
63
|
-
})
|
|
64
|
-
);
|
|
41
|
+
const contentDirs = dirEntries.map(([, dir]) => dir);
|
|
42
|
+
const firstLast = await getAllFilesFirstLastMetaAsync(contentDirs);
|
|
65
43
|
const meta = {};
|
|
66
|
-
for (
|
|
67
|
-
const
|
|
68
|
-
if (
|
|
69
|
-
meta[
|
|
44
|
+
for (const { composedSlug, filePath } of jobs) {
|
|
45
|
+
const entry = firstLast.get(filePath);
|
|
46
|
+
if (entry == null) continue;
|
|
47
|
+
meta[composedSlug] = {
|
|
70
48
|
// Author comes from the FIRST (oldest) commit.
|
|
71
|
-
author:
|
|
49
|
+
author: entry.oldest.author,
|
|
72
50
|
// createdDate = oldest commit; updatedDate = newest commit.
|
|
73
|
-
createdDate:
|
|
74
|
-
updatedDate:
|
|
51
|
+
createdDate: entry.oldest.date,
|
|
52
|
+
updatedDate: entry.newest.date,
|
|
75
53
|
// Source extension for the view-source URL (".md" walkers accepted).
|
|
76
|
-
ext: deriveSourceExt(
|
|
54
|
+
ext: deriveSourceExt(filePath)
|
|
77
55
|
};
|
|
78
56
|
}
|
|
79
57
|
fs.mkdirSync(zfbDir, { recursive: true });
|
package/dist/plugins/routes.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import { existsSync, cpSync, rmSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { existsSync, statSync, cpSync, rmSync, mkdirSync } from "node:fs";
|
|
3
3
|
import { dirname, basename, join } from "node:path";
|
|
4
4
|
import { definePlugin } from "@takazudo/zfb/plugins";
|
|
5
5
|
function deriveRoutes(settings) {
|
|
@@ -57,6 +57,33 @@ const plugin = definePlugin({
|
|
|
57
57
|
tagVocabulary,
|
|
58
58
|
colorSchemes
|
|
59
59
|
})};
|
|
60
|
+
`
|
|
61
|
+
);
|
|
62
|
+
if (typeof settings.chromeBindingsModule === "string" && settings.chromeBindingsModule.trim() === "") {
|
|
63
|
+
throw new Error(
|
|
64
|
+
'zudo-doc: settings.chromeBindingsModule is set to an empty string \u2014 set it to a project-root-relative module path (e.g. "./src/chrome-bindings.tsx") or remove the setting.'
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
const chromeBindingsModule = typeof settings.chromeBindingsModule === "string" ? settings.chromeBindingsModule : void 0;
|
|
68
|
+
let chromeBindingsAbsPath;
|
|
69
|
+
if (chromeBindingsModule) {
|
|
70
|
+
const resolved = join(ctx.projectRoot, chromeBindingsModule).split("\\").join("/");
|
|
71
|
+
if (!existsSync(resolved)) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`zudo-doc: settings.chromeBindingsModule is set to "${chromeBindingsModule}", which resolves to "${resolved}" \u2014 that file does not exist. Create it (must export a named \`chromeBindings: ChromeHostBindings\`) or remove the setting.`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
if (!statSync(resolved).isFile()) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`zudo-doc: settings.chromeBindingsModule is set to "${chromeBindingsModule}", which resolves to "${resolved}" \u2014 that path is a directory, not a module file. Point it at a module file (e.g. "./src/chrome-bindings.tsx") or remove the setting.`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
chromeBindingsAbsPath = resolved;
|
|
82
|
+
}
|
|
83
|
+
ctx.addVirtualModule(
|
|
84
|
+
"virtual:zudo-doc-chrome-bindings",
|
|
85
|
+
() => chromeBindingsAbsPath ? `export { chromeBindings } from ${JSON.stringify(chromeBindingsAbsPath)};
|
|
86
|
+
` : `export const chromeBindings = {};
|
|
60
87
|
`
|
|
61
88
|
);
|
|
62
89
|
const require2 = createRequire(import.meta.url);
|
package/dist/preset.d.ts
CHANGED
|
@@ -83,6 +83,15 @@ export interface PresetSettings {
|
|
|
83
83
|
* succeeds). See `docs/adr/route-injection-seam.md`. (#2404)
|
|
84
84
|
*/
|
|
85
85
|
packageOwnedRoutes?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Project-root-relative path to a host module exporting a named
|
|
88
|
+
* `chromeBindings: ChromeHostBindings` (from `@takazudo/zudo-doc/factory-context`).
|
|
89
|
+
* Only consumed when `packageOwnedRoutes` is on — see `settings.ts` and
|
|
90
|
+
* `docs/adr/route-injection-seam.md` ("Host-callables channel") for the
|
|
91
|
+
* full contract. Omit to keep the injected chrome shim's bindings at their
|
|
92
|
+
* package-default stubs (byte-identical to today).
|
|
93
|
+
*/
|
|
94
|
+
chromeBindingsModule?: string;
|
|
86
95
|
/** Gate for the `/docs/tags` + `/docs/tags/[tag]` injected routes. */
|
|
87
96
|
docTags?: boolean;
|
|
88
97
|
/** Gate for the SSR `/api/ai-chat` injected route (`prerender: false`). */
|
package/dist/routes/_chrome.d.ts
CHANGED
|
@@ -8,6 +8,6 @@ export declare const composeMetaTitle: (title: string) => string, HeadWithDefaul
|
|
|
8
8
|
}) => import("preact").JSX.Element, TagsIndexPageView: (props: {
|
|
9
9
|
locale: string;
|
|
10
10
|
children?: import("preact").ComponentChildren;
|
|
11
|
-
}) => import("preact").JSX.Element, SiteTreeNavWrapper: import("../factory-context/index.js").FactoryComponent, BodyEndIslands: (props: import("../doc-body-end-islands/index.js").BodyEndIslandsProps) => import("preact").JSX.Element;
|
|
11
|
+
}) => import("preact").JSX.Element, SiteTreeNavWrapper: import("../factory-context/index.js").FactoryComponent, BodyEndIslands: (props: import("../doc-body-end-islands/index.js").BodyEndIslandsProps) => import("preact").JSX.Element, HomePageView: (props: import("../home-page/index.js").HomePageViewProps) => import("preact").JSX.Element;
|
|
12
12
|
export { buildDocRouteEntries } from "./_context.js";
|
|
13
13
|
export type { DocNavNode };
|
package/dist/routes/_chrome.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { routeCtx } from "./_context.js";
|
|
2
2
|
import { createChrome } from "../chrome/index.js";
|
|
3
3
|
import { DocHistory } from "../doc-history/index.js";
|
|
4
|
+
import { chromeBindings } from "virtual:zudo-doc-chrome-bindings";
|
|
4
5
|
const chrome = createChrome(routeCtx, {
|
|
5
|
-
DocHistory
|
|
6
|
+
DocHistory,
|
|
7
|
+
...chromeBindings
|
|
6
8
|
});
|
|
7
9
|
const {
|
|
8
10
|
composeMetaTitle,
|
|
@@ -16,7 +18,8 @@ const {
|
|
|
16
18
|
TagDetailPageView,
|
|
17
19
|
TagsIndexPageView,
|
|
18
20
|
SiteTreeNavWrapper,
|
|
19
|
-
BodyEndIslands
|
|
21
|
+
BodyEndIslands,
|
|
22
|
+
HomePageView
|
|
20
23
|
} = chrome;
|
|
21
24
|
import { buildDocRouteEntries } from "./_context.js";
|
|
22
25
|
export {
|
|
@@ -24,6 +27,7 @@ export {
|
|
|
24
27
|
FooterWithDefaults,
|
|
25
28
|
HeadWithDefaults,
|
|
26
29
|
HeaderWithDefaults,
|
|
30
|
+
HomePageView,
|
|
27
31
|
SidebarWithDefaults,
|
|
28
32
|
SiteTreeNavWrapper,
|
|
29
33
|
TagDetailPageView,
|
package/dist/routes/index.js
CHANGED
|
@@ -1,115 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
settings,
|
|
7
|
-
defaultLocale,
|
|
8
|
-
t,
|
|
9
|
-
withBase,
|
|
10
|
-
resolveNavSource,
|
|
11
|
-
buildNavTree,
|
|
12
|
-
groupSatelliteNodes,
|
|
13
|
-
getCategoryOrder,
|
|
14
|
-
collectTags,
|
|
15
|
-
docsUrl,
|
|
16
|
-
toRouteSlug
|
|
17
|
-
} from "./_context.js";
|
|
18
|
-
import {
|
|
19
|
-
HeadWithDefaults,
|
|
20
|
-
HeaderWithDefaults,
|
|
21
|
-
FooterWithDefaults,
|
|
22
|
-
BodyEndIslands,
|
|
23
|
-
composeMetaTitle
|
|
24
|
-
} from "./_chrome.js";
|
|
1
|
+
import { jsx } from "preact/jsx-runtime";
|
|
2
|
+
import { defaultLocale, routeCtx } from "./_context.js";
|
|
3
|
+
import { prepareHomeData } from "../home-page/prepare-home-data.js";
|
|
4
|
+
import { HomePageView } from "./_chrome.js";
|
|
25
5
|
const frontmatter = { title: "Home" };
|
|
26
6
|
function IndexPage() {
|
|
27
7
|
const locale = defaultLocale;
|
|
28
|
-
const {
|
|
29
|
-
|
|
30
|
-
navDocs,
|
|
31
|
-
locale,
|
|
32
|
-
categoryMeta,
|
|
33
|
-
(slug, loc) => docsUrl(slug, loc)
|
|
34
|
-
);
|
|
35
|
-
const categoryOrder = getCategoryOrder();
|
|
36
|
-
const groupedTree = groupSatelliteNodes(tree, categoryOrder);
|
|
37
|
-
const tagCount = collectTags(
|
|
38
|
-
navDocs.filter((d) => !d.data.category_no_page),
|
|
39
|
-
(id, data) => data.slug ?? toRouteSlug(id)
|
|
40
|
-
).size;
|
|
41
|
-
const ctaNav = settings.headerNav[0] ?? null;
|
|
42
|
-
const overview = ctaNav ? withBase(ctaNav.path) : null;
|
|
43
|
-
const logoUrl = withBase("/img/logo.svg");
|
|
44
|
-
return /* @__PURE__ */ jsxs(
|
|
45
|
-
DocLayoutWithDefaults,
|
|
46
|
-
{
|
|
47
|
-
title: composeMetaTitle(settings.siteName),
|
|
48
|
-
head: /* @__PURE__ */ jsx(HeadWithDefaults, { title: settings.siteName }),
|
|
49
|
-
lang: locale,
|
|
50
|
-
noindex: settings.noindex,
|
|
51
|
-
hideSidebar: true,
|
|
52
|
-
hideToc: true,
|
|
53
|
-
sidebarOverride: /* @__PURE__ */ jsx(Fragment, {}),
|
|
54
|
-
headerOverride: /* @__PURE__ */ jsx(HeaderWithDefaults, { lang: locale, currentPath: withBase("/") }),
|
|
55
|
-
footerOverride: /* @__PURE__ */ jsx(FooterWithDefaults, { lang: locale }),
|
|
56
|
-
bodyEndComponents: /* @__PURE__ */ jsx(BodyEndIslands, { basePath: settings.base ?? "/" }),
|
|
57
|
-
enableClientRouter: settings.dynamicPageTransition,
|
|
58
|
-
children: [
|
|
59
|
-
/* @__PURE__ */ jsx("div", { class: "flex justify-center mb-vsp-xl", children: /* @__PURE__ */ jsxs("div", { class: "flex flex-col items-center text-center gap-hsp-md lg:flex-row lg:text-left lg:gap-hsp-xl", children: [
|
|
60
|
-
/* @__PURE__ */ jsx(
|
|
61
|
-
"div",
|
|
62
|
-
{
|
|
63
|
-
class: "w-[320px] max-w-full aspect-[1200/630] bg-fg shrink-0",
|
|
64
|
-
style: {
|
|
65
|
-
WebkitMask: `url(${logoUrl}) center/contain no-repeat`,
|
|
66
|
-
mask: `url(${logoUrl}) center/contain no-repeat`
|
|
67
|
-
},
|
|
68
|
-
"aria-hidden": "true"
|
|
69
|
-
}
|
|
70
|
-
),
|
|
71
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
72
|
-
/* @__PURE__ */ jsx("h1", { class: "text-heading font-bold mb-vsp-2xs", children: settings.siteName }),
|
|
73
|
-
/* @__PURE__ */ jsx("p", { class: "text-muted text-small mb-vsp-sm", children: settings.siteDescription }),
|
|
74
|
-
/* @__PURE__ */ jsxs("div", { class: "flex items-center justify-center lg:justify-start gap-hsp-md text-small", children: [
|
|
75
|
-
overview && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
76
|
-
/* @__PURE__ */ jsx("a", { href: overview, class: "text-fg underline hover:text-accent", children: t("nav.overview", locale) }),
|
|
77
|
-
/* @__PURE__ */ jsx("span", { class: "text-muted", children: "/" })
|
|
78
|
-
] }),
|
|
79
|
-
settings.githubUrl && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
80
|
-
/* @__PURE__ */ jsx(
|
|
81
|
-
"a",
|
|
82
|
-
{
|
|
83
|
-
href: settings.githubUrl,
|
|
84
|
-
class: "inline-flex items-center gap-[0.3em] text-fg underline hover:text-accent",
|
|
85
|
-
target: "_blank",
|
|
86
|
-
rel: "noopener noreferrer",
|
|
87
|
-
children: "GitHub"
|
|
88
|
-
}
|
|
89
|
-
),
|
|
90
|
-
/* @__PURE__ */ jsx("span", { class: "text-muted", children: "/" })
|
|
91
|
-
] })
|
|
92
|
-
] })
|
|
93
|
-
] })
|
|
94
|
-
] }) }),
|
|
95
|
-
Island({
|
|
96
|
-
when: "idle",
|
|
97
|
-
children: /* @__PURE__ */ jsx(
|
|
98
|
-
SiteTreeNav,
|
|
99
|
-
{
|
|
100
|
-
tree: groupedTree,
|
|
101
|
-
categoryOrder,
|
|
102
|
-
categoryIgnore: ["inbox", "develop"]
|
|
103
|
-
}
|
|
104
|
-
)
|
|
105
|
-
}),
|
|
106
|
-
settings.docTags && tagCount > 0 && /* @__PURE__ */ jsxs("section", { class: "mt-vsp-xl", children: [
|
|
107
|
-
/* @__PURE__ */ jsx("h2", { class: "text-title font-bold mb-vsp-md", children: t("doc.allTags", locale) }),
|
|
108
|
-
/* @__PURE__ */ jsx("a", { href: withBase("/docs/tags"), class: "text-accent underline hover:text-accent-hover", children: t("doc.allTags", locale) })
|
|
109
|
-
] })
|
|
110
|
-
]
|
|
111
|
-
}
|
|
112
|
-
);
|
|
8
|
+
const { tree, categoryOrder, tagCount } = prepareHomeData(routeCtx, locale);
|
|
9
|
+
return /* @__PURE__ */ jsx(HomePageView, { locale, tree, categoryOrder, tagCount });
|
|
113
10
|
}
|
|
114
11
|
export {
|
|
115
12
|
IndexPage as default,
|