@takazudo/zudo-doc 5.9.0 → 5.10.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 +17 -0
- package/README.md +1 -1
- package/dist/chrome/derive.d.ts +1 -1
- package/dist/chrome/derive.js +24 -2
- package/dist/config-assertions/index.d.ts +30 -0
- package/dist/config-assertions/index.js +41 -1
- package/dist/doc-content-header/index.js +6 -0
- package/dist/doc-metainfo-area/index.js +1 -14
- package/dist/doc-page-renderer/index.d.ts +3 -3
- package/dist/doc-page-renderer/index.js +1 -1
- package/dist/doc-page-shell/index.d.ts +6 -0
- package/dist/doc-pager/index.d.ts +6 -0
- package/dist/doc-pager/index.js +5 -2
- package/dist/docs-schema/index.d.ts +11 -0
- package/dist/docs-schema/index.js +8 -1
- package/dist/format-date/index.d.ts +12 -0
- package/dist/format-date/index.js +64 -0
- package/dist/frontmatter-preview-defaults/index.js +6 -1
- package/dist/header/header.js +9 -1
- package/dist/home-page/index.js +3 -1
- package/dist/html-preview-wrapper/highlight-runtime.d.ts +7 -7
- package/dist/html-preview-wrapper/highlight-runtime.js +1 -1
- package/dist/integrations/changelog/emit.js +19 -2
- package/dist/mdx-components/index.d.ts +4 -1
- package/dist/mdx-components/index.js +3 -1
- package/dist/nav-indexing/index.d.ts +2 -0
- package/dist/nav-indexing/index.js +2 -0
- package/dist/nav-indexing/note-tray-index.d.ts +29 -0
- package/dist/nav-indexing/note-tray-index.js +120 -0
- package/dist/note-tray-index/index.d.ts +45 -0
- package/dist/note-tray-index/index.js +52 -0
- package/dist/note-tray-model/index.d.ts +31 -0
- package/dist/note-tray-model/index.js +74 -0
- package/dist/preset.d.ts +11 -0
- package/dist/preset.js +5 -1
- package/dist/safelist.css +1 -1
- package/dist/sidebar/types.d.ts +6 -0
- package/dist/sidebar-tree/build-tree.js +8 -0
- package/dist/sidebar-tree/types.d.ts +12 -0
- package/dist/sidebar-tree-island/index.js +175 -1
- package/dist/sidebar-utils/index.d.ts +6 -0
- package/dist/site-schema/doc-route-entries.js +2 -0
- package/dist/site-schema/index.d.ts +1 -0
- package/dist/site-schema/index.js +3 -1
- package/dist/site-schema/nav-tree.js +17 -2
- package/dist/site-schema/note-tray-validate.d.ts +7 -0
- package/dist/site-schema/note-tray-validate.js +94 -0
- package/dist/site-schema/types.d.ts +11 -0
- package/dist/site-tree-nav-island/index.d.ts +5 -1
- package/dist/site-tree-nav-island/index.js +119 -12
- package/dist/theme-packs/drift/pack.css +6 -1
- package/dist/theme-packs/hearth/pack.css +5 -0
- package/dist/theme-packs/sakura/pack.css +6 -0
- package/dist/theme-packs/scandi/pack.css +7 -7
- package/dist/theme-packs/timberline/pack.css +5 -0
- package/eject/doc-pager/index.tsx +9 -0
- package/eject/header/header.tsx +26 -1
- package/eject/sidebar/types.ts +6 -0
- package/eject/sidebar-tree-island/index.tsx +238 -6
- package/eject/site-tree-nav-island/index.tsx +166 -16
- package/package.json +20 -8
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,23 @@ All notable changes to `@takazudo/zudo-doc` are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on Keep a Changelog, and release notes are generated from the changelog MDX pages.
|
|
6
6
|
|
|
7
|
+
## [5.10.0] - 2026-08-22
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- Added first-class multi-package changelogs, including nested package navigation, per-package release pages, bilingual guidance, and the `create-zudo-doc --changelog-packages` option. (`91f179c8`, `302eac4f`)
|
|
12
|
+
- Upgraded the zfb package family to 2.9.0 and moved HtmlPreview to the public highlight-only WASM runtime, reducing the emitted WASM payload by about 55%. (`52291ec5`, `2911a651`)
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
- Hardened nested changelog navigation and added diagnostics when an emitter targets a multi-package landing directory instead of a package release directory. (`302eac4f`, `c4266e4a`)
|
|
17
|
+
- Normalized non-pill sidebar current rows in Drift, Hearth, Sakura, Scandi, and Timberline while preserving each pack's nested active treatment. (`7c4e0444`)
|
|
18
|
+
|
|
19
|
+
### Other Changes
|
|
20
|
+
|
|
21
|
+
- Added bilingual documentation, publish-contract coverage, and positive browser detectors for the highlight-only runtime. (`b97ef30c`, `92269d49`)
|
|
22
|
+
- Added package-test hook headroom for concurrent CI load without changing subprocess deadlines. (`4d1417a9`)
|
|
23
|
+
|
|
7
24
|
## [5.9.0] - 2026-08-20
|
|
8
25
|
|
|
9
26
|
### Features
|
package/README.md
CHANGED
|
@@ -59,7 +59,7 @@ const breadcrumbs = buildBreadcrumbs(tree, "guides/color", homeHref);
|
|
|
59
59
|
|
|
60
60
|
## Optional peer dependency: `@takazudo/zfb-md-wasm`
|
|
61
61
|
|
|
62
|
-
`./html-preview-wrapper`'s `<HighlightedCode>` lazily imports the
|
|
62
|
+
`./html-preview-wrapper`'s `<HighlightedCode>` lazily imports the public `@takazudo/zfb-md-wasm/highlight` subpath and calls `highlightCode()` for client-side semantic syntax highlighting. `@takazudo/zfb-md-wasm` is declared as an **optional peerDependency** — install the same version as the rest of your zfb packages if you use that subpath:
|
|
63
63
|
|
|
64
64
|
```sh
|
|
65
65
|
pnpm add @takazudo/zfb-md-wasm
|
package/dist/chrome/derive.d.ts
CHANGED
|
@@ -152,7 +152,7 @@ export declare function deriveInlineVersionSwitcher(ctx: ChromeContext, getUnava
|
|
|
152
152
|
* happens one layer up in `createMdxComponents()`, after the wrapper choice.
|
|
153
153
|
*/
|
|
154
154
|
export declare function deriveMdxComponents(ctx: ChromeContext): {
|
|
155
|
-
createMdxComponentsBound: (lang?: string, currentVersion?: string) => Record<string, unknown>;
|
|
155
|
+
createMdxComponentsBound: (lang?: string, currentVersion?: string, currentSlug?: string) => Record<string, unknown>;
|
|
156
156
|
SiteTreeNavWrapper: FactoryComponent;
|
|
157
157
|
};
|
|
158
158
|
export type { Settings };
|
package/dist/chrome/derive.js
CHANGED
|
@@ -14,6 +14,7 @@ import { DEFAULT_THEME_PACK_SLUG } from "../theme-pack-switcher/theme-pack-sync.
|
|
|
14
14
|
import { SearchWidget } from "../search-widget/index.js";
|
|
15
15
|
import { createMdxComponents } from "../mdx-components/index.js";
|
|
16
16
|
import { createCategoryNavWrapper } from "../category-nav/index.js";
|
|
17
|
+
import { createNoteTrayIndexWrapper } from "../note-tray-index/index.js";
|
|
17
18
|
import { createCategoryTreeNavWrapper } from "../category-tree-nav/index.js";
|
|
18
19
|
import { createSiteTreeNavWrapper } from "../site-tree-nav/index.js";
|
|
19
20
|
import { Details } from "../details/index.js";
|
|
@@ -271,6 +272,25 @@ function deriveMdxComponents(ctx) {
|
|
|
271
272
|
getCategoryOrder: ctx.getCategoryOrder,
|
|
272
273
|
versionedDocsUrl: ctx.versionedDocsUrl
|
|
273
274
|
});
|
|
275
|
+
const NoteTrayIndexWrapper = createNoteTrayIndexWrapper({
|
|
276
|
+
defaultLocale: ctx.defaultLocale,
|
|
277
|
+
docTags: ctx.settings.docTags,
|
|
278
|
+
resolveNavSource: ctx.resolveNavSource,
|
|
279
|
+
buildNavTree: ((docs, locale, categoryMeta) => ctx.buildNavTree(
|
|
280
|
+
docs,
|
|
281
|
+
locale,
|
|
282
|
+
categoryMeta,
|
|
283
|
+
(slug, loc) => ctx.docsUrl(slug, loc)
|
|
284
|
+
)),
|
|
285
|
+
findNode: ctx.findNode,
|
|
286
|
+
toRouteSlug: ctx.toRouteSlug,
|
|
287
|
+
resolveTag: (raw) => ctx.resolveTagBound(raw).canonical,
|
|
288
|
+
tagHref: (tag, locale) => ctx.withBase(
|
|
289
|
+
locale === ctx.defaultLocale ? `/docs/tags/${encodeURIComponent(tag)}` : `/${locale}/docs/tags/${encodeURIComponent(tag)}`
|
|
290
|
+
),
|
|
291
|
+
t: (key, locale) => ctx.t(key, locale),
|
|
292
|
+
versionedDocsUrl: ctx.versionedDocsUrl
|
|
293
|
+
});
|
|
274
294
|
function HtmlPreviewBound(props) {
|
|
275
295
|
return HtmlPreviewWrapper({
|
|
276
296
|
globalConfig: ctx.settings.htmlPreview ?? null,
|
|
@@ -286,15 +306,17 @@ function deriveMdxComponents(ctx) {
|
|
|
286
306
|
PresetGenerator: (_props) => null
|
|
287
307
|
};
|
|
288
308
|
const mdxExtras = { ...mdxExtrasDefault, ...ctx.hostBindings.mdxExtras ?? {} };
|
|
289
|
-
function createMdxComponentsBound(lang = ctx.defaultLocale, currentVersion) {
|
|
309
|
+
function createMdxComponentsBound(lang = ctx.defaultLocale, currentVersion, currentSlug = "") {
|
|
290
310
|
return createMdxComponents({
|
|
291
311
|
settings: ctx.settings,
|
|
292
312
|
locale: lang,
|
|
293
313
|
currentVersion,
|
|
314
|
+
currentSlug,
|
|
294
315
|
navData: {
|
|
295
316
|
CategoryNav: CategoryNavWrapper,
|
|
296
317
|
CategoryTreeNav: CategoryTreeNavWrapper,
|
|
297
|
-
SiteTreeNav: SiteTreeNavWrapper
|
|
318
|
+
SiteTreeNav: SiteTreeNavWrapper,
|
|
319
|
+
NoteTrayIndex: NoteTrayIndexWrapper
|
|
298
320
|
},
|
|
299
321
|
// Package-owned content components wired here so an INJECTED docs route
|
|
300
322
|
// renders MDX using these tags without the "MDX requires '<X>' to be
|
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
import type { FaviconConfig } from "../settings.js";
|
|
2
|
+
/** Structural subset of a header dropdown read by the category-match guard. */
|
|
3
|
+
export interface AmbiguousDropdownCategoryMatchItem {
|
|
4
|
+
label?: string;
|
|
5
|
+
categoryMatch?: string;
|
|
6
|
+
children?: Array<{
|
|
7
|
+
label?: string;
|
|
8
|
+
categoryMatch?: string;
|
|
9
|
+
}>;
|
|
10
|
+
}
|
|
11
|
+
/** Logger surface accepted by the non-throwing header-nav diagnostic. */
|
|
12
|
+
export interface ConfigAssertionLogger {
|
|
13
|
+
warn(message: string): void;
|
|
14
|
+
}
|
|
2
15
|
/** Structural subset of `Settings` {@link assertNoEmptyStringFaviconOrLogo} reads. */
|
|
3
16
|
export interface EmptyStringFaviconOrLogoSubject {
|
|
4
17
|
logo?: string | false;
|
|
@@ -29,3 +42,20 @@ export interface EmptyStringFaviconOrLogoSubject {
|
|
|
29
42
|
* object is not an empty string).
|
|
30
43
|
*/
|
|
31
44
|
export declare function assertNoEmptyStringFaviconOrLogo(settings: EmptyStringFaviconOrLogoSubject): void;
|
|
45
|
+
/**
|
|
46
|
+
* Warn about `categoryMatch` values that cannot express the intended
|
|
47
|
+
* dropdown grouping.
|
|
48
|
+
*
|
|
49
|
+
* A category matcher is compared with the first slug segment by the nav-scope
|
|
50
|
+
* resolver, so a value containing `/` never matches. Likewise, duplicate
|
|
51
|
+
* child values make every matching child category-active. Children grouped
|
|
52
|
+
* under one top-level directory should omit `categoryMatch` and rely on the
|
|
53
|
+
* deepest matching child path; Learn-style children should use distinct
|
|
54
|
+
* top-level values.
|
|
55
|
+
*
|
|
56
|
+
* This is intentionally a diagnostic rather than a config assertion: malformed
|
|
57
|
+
* navigation should remain buildable, and each offending value is reported at
|
|
58
|
+
* most once per parent dropdown. The `"!"` matcher is the intentional default
|
|
59
|
+
* bucket and is excluded from both checks.
|
|
60
|
+
*/
|
|
61
|
+
export declare function warnAmbiguousDropdownCategoryMatch(headerNav: readonly AmbiguousDropdownCategoryMatchItem[] | undefined, logger?: ConfigAssertionLogger): void;
|
|
@@ -19,6 +19,46 @@ function assertNoEmptyStringFaviconOrLogo(settings) {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
+
function warnAmbiguousDropdownCategoryMatch(headerNav, logger = console) {
|
|
23
|
+
if (!headerNav) return;
|
|
24
|
+
for (const item of headerNav) {
|
|
25
|
+
const children = item.children;
|
|
26
|
+
if (!children || children.length === 0) continue;
|
|
27
|
+
const childCounts = /* @__PURE__ */ new Map();
|
|
28
|
+
for (const child of children) {
|
|
29
|
+
const value = child.categoryMatch;
|
|
30
|
+
if (value == null || value === "!") continue;
|
|
31
|
+
childCounts.set(value, (childCounts.get(value) ?? 0) + 1);
|
|
32
|
+
}
|
|
33
|
+
const offendingValues = [];
|
|
34
|
+
const addOffendingValue = (value) => {
|
|
35
|
+
if (value == null || value === "!" || offendingValues.includes(value)) return;
|
|
36
|
+
offendingValues.push(value);
|
|
37
|
+
};
|
|
38
|
+
if (item.categoryMatch?.includes("/")) {
|
|
39
|
+
addOffendingValue(item.categoryMatch);
|
|
40
|
+
}
|
|
41
|
+
for (const child of children) {
|
|
42
|
+
if (child.categoryMatch?.includes("/")) {
|
|
43
|
+
addOffendingValue(child.categoryMatch);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
for (const [value, count] of childCounts) {
|
|
47
|
+
if (count >= 2) addOffendingValue(value);
|
|
48
|
+
}
|
|
49
|
+
for (const value of offendingValues) {
|
|
50
|
+
const reasons = [
|
|
51
|
+
...value.includes("/") ? ["contains `/` and never matches a top-level slug"] : [],
|
|
52
|
+
...(childCounts.get(value) ?? 0) >= 2 ? ["is shared by multiple children"] : []
|
|
53
|
+
].join("; ");
|
|
54
|
+
const parentLabel = item.label ?? "(unnamed)";
|
|
55
|
+
logger.warn(
|
|
56
|
+
`zudo-doc: header dropdown "${parentLabel}" has categoryMatch "${value}" that ${reasons}. Children grouped under one top-level directory should omit categoryMatch (active state follows the deepest matching child path); Learn-style children should use distinct top-level values.`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
22
61
|
export {
|
|
23
|
-
assertNoEmptyStringFaviconOrLogo
|
|
62
|
+
assertNoEmptyStringFaviconOrLogo,
|
|
63
|
+
warnAmbiguousDropdownCategoryMatch
|
|
24
64
|
};
|
|
@@ -3,6 +3,7 @@ import { FrontmatterPreview } from "../metainfo/index.js";
|
|
|
3
3
|
import { createDocMetainfoArea } from "../doc-metainfo-area/index.js";
|
|
4
4
|
import { createDocTagsArea } from "../doc-tags-area/index.js";
|
|
5
5
|
import { assertChromeContext } from "../chrome/assert-chrome-context.js";
|
|
6
|
+
import { formatDate } from "../format-date/index.js";
|
|
6
7
|
function createDocContentHeader(ctx) {
|
|
7
8
|
assertChromeContext(ctx, "createDocContentHeader");
|
|
8
9
|
const t = ctx.t;
|
|
@@ -27,6 +28,11 @@ function createDocContentHeader(ctx) {
|
|
|
27
28
|
isFallback,
|
|
28
29
|
version
|
|
29
30
|
}),
|
|
31
|
+
(entry.data.date || entry.data.updated) && /* @__PURE__ */ jsxs("p", { class: "text-caption text-muted mb-vsp-md", "data-doc-date": true, children: [
|
|
32
|
+
entry.data.date ? formatDate(entry.data.date, locale) : null,
|
|
33
|
+
entry.data.date && entry.data.updated ? " \xB7 " : null,
|
|
34
|
+
entry.data.updated ? `${t("doc.updated", locale)} ${formatDate(entry.data.updated, locale)}` : null
|
|
35
|
+
] }),
|
|
30
36
|
!version && /* @__PURE__ */ jsx(DocMetainfoArea, { slug, locale, isFallback }),
|
|
31
37
|
!version && /* @__PURE__ */ jsx(DocTagsArea, { slug, locale, tags: entry.data.tags }),
|
|
32
38
|
isFallback && !entry.data.generated && /* @__PURE__ */ jsx(
|
|
@@ -2,20 +2,7 @@ import { jsx } from "preact/jsx-runtime";
|
|
|
2
2
|
import { DocMetainfo } from "../metainfo/index.js";
|
|
3
3
|
import { toHistorySlug } from "../slug/index.js";
|
|
4
4
|
import { assertChromeContext } from "../chrome/assert-chrome-context.js";
|
|
5
|
-
|
|
6
|
-
en: "en-US",
|
|
7
|
-
ja: "ja-JP",
|
|
8
|
-
de: "de-DE"
|
|
9
|
-
};
|
|
10
|
-
function formatDate(isoDate, locale) {
|
|
11
|
-
const d = new Date(isoDate);
|
|
12
|
-
if (isNaN(d.getTime())) return isoDate;
|
|
13
|
-
return d.toLocaleDateString(LOCALE_TO_BCP47[locale] ?? "en-US", {
|
|
14
|
-
year: "numeric",
|
|
15
|
-
month: "short",
|
|
16
|
-
day: "numeric"
|
|
17
|
-
});
|
|
18
|
-
}
|
|
5
|
+
import { formatDate } from "../format-date/index.js";
|
|
19
6
|
function createDocMetainfoArea(ctx) {
|
|
20
7
|
assertChromeContext(ctx, "createDocMetainfoArea");
|
|
21
8
|
const settings = ctx.settings;
|
|
@@ -48,10 +48,10 @@ export interface DocPageRendererDeps {
|
|
|
48
48
|
* Build locale-aware MDX components bag.
|
|
49
49
|
* Host passes `createMdxComponents` (from `pages/_mdx-components.ts`).
|
|
50
50
|
* `currentVersion` (#3218) threads the active `/v/{version}` slug into the
|
|
51
|
-
* nav
|
|
52
|
-
*
|
|
51
|
+
* nav wrappers so their data and hrefs resolve within the version. The
|
|
52
|
+
* current slug lets NoteTrayIndex infer its containing tray.
|
|
53
53
|
*/
|
|
54
|
-
createMdxComponents: (locale: string, currentVersion
|
|
54
|
+
createMdxComponents: (locale: string, currentVersion: string | undefined, currentSlug: string) => Record<string, unknown>;
|
|
55
55
|
/** Translate a UI string key for a locale. */
|
|
56
56
|
t: (key: string, locale: string) => string;
|
|
57
57
|
/**
|
|
@@ -35,7 +35,7 @@ function createRenderDocPage(ctx) {
|
|
|
35
35
|
const slug = props.kind === "autoIndex" ? props.autoIndex.slug : props.entry.data.slug ?? toRouteSlug(props.entry.slug);
|
|
36
36
|
const title = props.kind === "autoIndex" ? props.autoIndex.label : props.entry.data.title;
|
|
37
37
|
const description = props.kind === "autoIndex" ? props.autoIndex.description : props.entry.data.description;
|
|
38
|
-
const components = createMdxComponents(locale, version?.slug);
|
|
38
|
+
const components = createMdxComponents(locale, version?.slug, slug);
|
|
39
39
|
const autoIndexChildren = props.kind === "autoIndex" ? version ? props.autoIndex.children.filter((c) => c.hasPage || c.children.length > 0) : props.autoIndex.children.filter((c) => c.hasPage || c.children.length > 0).map((c) => ({
|
|
40
40
|
...c,
|
|
41
41
|
href: c.href ?? docsUrl(c.slug, locale)
|
|
@@ -22,6 +22,12 @@ export interface DocPageNavNode {
|
|
|
22
22
|
href?: string;
|
|
23
23
|
hasPage: boolean;
|
|
24
24
|
children: DocPageNavNode[];
|
|
25
|
+
shape?: "note-tray";
|
|
26
|
+
noteTrayDated?: boolean;
|
|
27
|
+
noteTraySidebar?: "index" | "year" | "month";
|
|
28
|
+
date?: string;
|
|
29
|
+
updated?: string;
|
|
30
|
+
rank?: number;
|
|
25
31
|
}
|
|
26
32
|
/** Slots and parameters that vary between the 4 doc routes. */
|
|
27
33
|
export interface DocPageShellProps {
|
|
@@ -6,6 +6,12 @@ import type { Settings } from "../settings.js";
|
|
|
6
6
|
interface PagerNode {
|
|
7
7
|
href?: string;
|
|
8
8
|
label: string;
|
|
9
|
+
shape?: "note-tray";
|
|
10
|
+
noteTrayDated?: boolean;
|
|
11
|
+
noteTraySidebar?: "index" | "year" | "month";
|
|
12
|
+
date?: string;
|
|
13
|
+
updated?: string;
|
|
14
|
+
rank?: number;
|
|
9
15
|
}
|
|
10
16
|
export interface DocPagerProps {
|
|
11
17
|
/** Previous page node (null = no previous page → renders placeholder). */
|
package/dist/doc-pager/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx, jsxs } from "preact/jsx-runtime";
|
|
2
2
|
import { ChevronLeft, ChevronRight } from "../icons/index.js";
|
|
3
3
|
import { assertChromeContext } from "../chrome/assert-chrome-context.js";
|
|
4
|
+
import { formatDate } from "../format-date/index.js";
|
|
4
5
|
function createDocPager(ctx) {
|
|
5
6
|
assertChromeContext(ctx, "createDocPager");
|
|
6
7
|
const t = ctx.t;
|
|
@@ -16,7 +17,8 @@ function createDocPager(ctx) {
|
|
|
16
17
|
/* @__PURE__ */ jsx(ChevronLeft, { className: "h-[1.125rem] w-[1.125rem]" }),
|
|
17
18
|
/* @__PURE__ */ jsx("span", { class: "no-underline", children: t("nav.previous", locale) })
|
|
18
19
|
] }),
|
|
19
|
-
/* @__PURE__ */ jsx("p", { class: "text-small font-semibold underline group-hover:text-accent", children: prev.label })
|
|
20
|
+
/* @__PURE__ */ jsx("p", { class: "text-small font-semibold underline group-hover:text-accent", children: prev.label }),
|
|
21
|
+
prev.date && /* @__PURE__ */ jsx("p", { class: "text-caption text-muted", children: formatDate(prev.date, locale) })
|
|
20
22
|
]
|
|
21
23
|
}
|
|
22
24
|
) : /* @__PURE__ */ jsx("div", {}),
|
|
@@ -30,7 +32,8 @@ function createDocPager(ctx) {
|
|
|
30
32
|
/* @__PURE__ */ jsx("span", { class: "no-underline", children: t("nav.next", locale) }),
|
|
31
33
|
/* @__PURE__ */ jsx(ChevronRight, { className: "h-[1.125rem] w-[1.125rem]" })
|
|
32
34
|
] }),
|
|
33
|
-
/* @__PURE__ */ jsx("p", { class: "text-small font-semibold underline group-hover:text-accent", children: next.label })
|
|
35
|
+
/* @__PURE__ */ jsx("p", { class: "text-small font-semibold underline group-hover:text-accent", children: next.label }),
|
|
36
|
+
next.date && /* @__PURE__ */ jsx("p", { class: "text-caption text-muted", children: formatDate(next.date, locale) })
|
|
34
37
|
]
|
|
35
38
|
}
|
|
36
39
|
) : /* @__PURE__ */ jsx("div", {})
|
|
@@ -58,6 +58,17 @@ export declare function buildDocsSchema(opts?: BuildDocsSchemaOptions): z.ZodObj
|
|
|
58
58
|
asc: "asc";
|
|
59
59
|
desc: "desc";
|
|
60
60
|
}>>;
|
|
61
|
+
category_shape: z.ZodOptional<z.ZodEnum<{
|
|
62
|
+
"note-tray": "note-tray";
|
|
63
|
+
}>>;
|
|
64
|
+
note_tray_dated: z.ZodOptional<z.ZodBoolean>;
|
|
65
|
+
note_tray_sidebar: z.ZodOptional<z.ZodEnum<{
|
|
66
|
+
index: "index";
|
|
67
|
+
year: "year";
|
|
68
|
+
month: "month";
|
|
69
|
+
}>>;
|
|
70
|
+
date: z.ZodOptional<z.ZodString>;
|
|
71
|
+
updated: z.ZodOptional<z.ZodString>;
|
|
61
72
|
}, z.core.$loose>;
|
|
62
73
|
/**
|
|
63
74
|
* TypeScript type inferred from the default docs frontmatter zod schema.
|
|
@@ -35,7 +35,14 @@ function buildDocsSchema(opts) {
|
|
|
35
35
|
// `category_sort_order` sets the child sort direction. Frontmatter wins
|
|
36
36
|
// over the sidecar.
|
|
37
37
|
category_no_page: z.boolean().optional(),
|
|
38
|
-
category_sort_order: z.enum(["asc", "desc"]).optional()
|
|
38
|
+
category_sort_order: z.enum(["asc", "desc"]).optional(),
|
|
39
|
+
category_shape: z.enum(["note-tray"]).optional(),
|
|
40
|
+
note_tray_dated: z.boolean().optional(),
|
|
41
|
+
note_tray_sidebar: z.enum(["index", "year", "month"]).optional(),
|
|
42
|
+
// Quote these values in YAML (for example, date: "2026-08-22") so
|
|
43
|
+
// parsers do not coerce them into Date objects before schema validation.
|
|
44
|
+
date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
|
|
45
|
+
updated: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional()
|
|
39
46
|
}).passthrough();
|
|
40
47
|
}
|
|
41
48
|
export {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface IsoDateParts {
|
|
2
|
+
year: number;
|
|
3
|
+
month: number;
|
|
4
|
+
day: number;
|
|
5
|
+
}
|
|
6
|
+
export declare function parseIsoDate(iso: string): IsoDateParts | undefined;
|
|
7
|
+
/** Format an ISO date for display while retaining the established locale map. */
|
|
8
|
+
export declare function formatDate(iso: string, locale: string): string;
|
|
9
|
+
/** Format a year/month label with the year first in every locale. */
|
|
10
|
+
export declare function formatYearMonth(iso: string, locale: string): string;
|
|
11
|
+
/** Return the stable numeric month/day portion of a calendar-valid ISO date. */
|
|
12
|
+
export declare function formatMonthDay(iso: string): string;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const LOCALE_TO_BCP47 = {
|
|
2
|
+
en: "en-US",
|
|
3
|
+
ja: "ja-JP",
|
|
4
|
+
de: "de-DE"
|
|
5
|
+
};
|
|
6
|
+
function parseIsoDate(iso) {
|
|
7
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(iso);
|
|
8
|
+
if (!match) return void 0;
|
|
9
|
+
const year = Number(match[1]);
|
|
10
|
+
const month = Number(match[2]);
|
|
11
|
+
const day = Number(match[3]);
|
|
12
|
+
const date = /* @__PURE__ */ new Date(0);
|
|
13
|
+
date.setUTCHours(0, 0, 0, 0);
|
|
14
|
+
date.setUTCFullYear(year, month - 1, day);
|
|
15
|
+
if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day) {
|
|
16
|
+
return void 0;
|
|
17
|
+
}
|
|
18
|
+
return { year, month, day };
|
|
19
|
+
}
|
|
20
|
+
function toUtcDate(iso) {
|
|
21
|
+
const parts = parseIsoDate(iso);
|
|
22
|
+
if (parts) {
|
|
23
|
+
const date = /* @__PURE__ */ new Date(0);
|
|
24
|
+
date.setUTCHours(0, 0, 0, 0);
|
|
25
|
+
date.setUTCFullYear(parts.year, parts.month - 1, parts.day);
|
|
26
|
+
return date;
|
|
27
|
+
}
|
|
28
|
+
if (!/^\d{4}-\d{2}-\d{2}T/.test(iso)) return void 0;
|
|
29
|
+
const timestamp = new Date(iso);
|
|
30
|
+
return Number.isNaN(timestamp.getTime()) ? void 0 : timestamp;
|
|
31
|
+
}
|
|
32
|
+
function formatDate(iso, locale) {
|
|
33
|
+
const date = toUtcDate(iso);
|
|
34
|
+
if (!date) return iso;
|
|
35
|
+
return new Intl.DateTimeFormat(LOCALE_TO_BCP47[locale] ?? "en-US", {
|
|
36
|
+
year: "numeric",
|
|
37
|
+
month: "short",
|
|
38
|
+
day: "numeric",
|
|
39
|
+
timeZone: "UTC"
|
|
40
|
+
}).format(date);
|
|
41
|
+
}
|
|
42
|
+
function formatYearMonth(iso, locale) {
|
|
43
|
+
const date = toUtcDate(iso.length === 7 ? `${iso}-01` : iso);
|
|
44
|
+
if (!date) return iso;
|
|
45
|
+
const year = new Intl.DateTimeFormat(LOCALE_TO_BCP47[locale] ?? "en-US", {
|
|
46
|
+
year: "numeric",
|
|
47
|
+
timeZone: "UTC"
|
|
48
|
+
}).format(date);
|
|
49
|
+
const month = new Intl.DateTimeFormat(LOCALE_TO_BCP47[locale] ?? "en-US", {
|
|
50
|
+
month: "long",
|
|
51
|
+
timeZone: "UTC"
|
|
52
|
+
}).format(date);
|
|
53
|
+
return locale === "ja" ? `${year}${month}` : `${year} ${month}`;
|
|
54
|
+
}
|
|
55
|
+
function formatMonthDay(iso) {
|
|
56
|
+
const parts = parseIsoDate(iso);
|
|
57
|
+
return parts ? `${String(parts.month).padStart(2, "0")}-${String(parts.day).padStart(2, "0")}` : iso;
|
|
58
|
+
}
|
|
59
|
+
export {
|
|
60
|
+
formatDate,
|
|
61
|
+
formatMonthDay,
|
|
62
|
+
formatYearMonth,
|
|
63
|
+
parseIsoDate
|
|
64
|
+
};
|
|
@@ -18,7 +18,12 @@ const defaultFrontmatterPreviewIgnoreKeys = [
|
|
|
18
18
|
"slug",
|
|
19
19
|
"generated",
|
|
20
20
|
"category_no_page",
|
|
21
|
-
"category_sort_order"
|
|
21
|
+
"category_sort_order",
|
|
22
|
+
"category_shape",
|
|
23
|
+
"note_tray_dated",
|
|
24
|
+
"note_tray_sidebar",
|
|
25
|
+
"date",
|
|
26
|
+
"updated"
|
|
22
27
|
];
|
|
23
28
|
export {
|
|
24
29
|
defaultFrontmatterPreviewIgnoreKeys
|
package/dist/header/header.js
CHANGED
|
@@ -44,7 +44,8 @@ function Header(props) {
|
|
|
44
44
|
} = props;
|
|
45
45
|
const isNonDefaultLocale = lang != null && lang !== i18n.defaultLocale;
|
|
46
46
|
const pathWithoutBase = urlHelpers.stripBase(currentPath);
|
|
47
|
-
const
|
|
47
|
+
const pathWithoutVersion = stripCurrentVersionPrefix(pathWithoutBase, currentVersion);
|
|
48
|
+
const matchPath = pathForMatch(pathWithoutVersion, lang, i18n.defaultLocale);
|
|
48
49
|
const activeNavPath = computeActiveNavPath(headerNav, matchPath);
|
|
49
50
|
const rightItemDispatch = createRightItemDispatch(headerRightComponents);
|
|
50
51
|
return /* @__PURE__ */ jsxs(
|
|
@@ -249,6 +250,13 @@ function renderNavItem(item, activeNavPath, activeCategory, lang, currentVersion
|
|
|
249
250
|
}
|
|
250
251
|
);
|
|
251
252
|
}
|
|
253
|
+
function stripCurrentVersionPrefix(path, currentVersion) {
|
|
254
|
+
if (currentVersion == null || currentVersion === "") return path;
|
|
255
|
+
const prefix = `/v/${currentVersion}`;
|
|
256
|
+
if (path === prefix) return "/";
|
|
257
|
+
if (path.startsWith(`${prefix}/`)) return path.slice(prefix.length);
|
|
258
|
+
return path;
|
|
259
|
+
}
|
|
252
260
|
function TriggerButton({
|
|
253
261
|
index,
|
|
254
262
|
id,
|
package/dist/home-page/index.js
CHANGED
|
@@ -132,7 +132,9 @@ function createHomePageView(ctx) {
|
|
|
132
132
|
tree,
|
|
133
133
|
categoryOrder,
|
|
134
134
|
categoryIgnore: ["inbox", "develop"],
|
|
135
|
-
initiallyCollapsedCategorySlugs
|
|
135
|
+
initiallyCollapsedCategorySlugs,
|
|
136
|
+
locale,
|
|
137
|
+
updatedLabel: t("doc.updated", locale)
|
|
136
138
|
}
|
|
137
139
|
)
|
|
138
140
|
}),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { HighlightCodeOptions, HighlightCodeResult } from "@takazudo/zfb-md-wasm";
|
|
2
|
-
type HighlightModule = Pick<typeof import("@takazudo/zfb-md-wasm"), "highlightCode">;
|
|
1
|
+
import type { HighlightCodeOptions, HighlightCodeResult } from "@takazudo/zfb-md-wasm/highlight";
|
|
2
|
+
type HighlightModule = Pick<typeof import("@takazudo/zfb-md-wasm/highlight"), "highlightCode">;
|
|
3
3
|
export type HighlightModuleImporter = () => Promise<HighlightModule>;
|
|
4
4
|
export interface HtmlPreviewHighlightRuntime {
|
|
5
5
|
highlightCode(code: string, options: HighlightCodeOptions): Promise<HighlightCodeResult>;
|
|
@@ -7,11 +7,11 @@ export interface HtmlPreviewHighlightRuntime {
|
|
|
7
7
|
/**
|
|
8
8
|
* Build the HTML Preview's lazy zfb highlighting adapter.
|
|
9
9
|
*
|
|
10
|
-
* The cached value is only the public
|
|
11
|
-
* import is evicted so a later source-panel mount can retry a transient
|
|
12
|
-
* load. Calls to `highlightCode` are deliberately not cached: the
|
|
13
|
-
* package owns WASM initialization and trap recovery, while the
|
|
14
|
-
* request cancellation and stale-result protection.
|
|
10
|
+
* The cached value is only the public `./highlight` subpath module import. A
|
|
11
|
+
* rejected import is evicted so a later source-panel mount can retry a transient
|
|
12
|
+
* chunk load. Calls to `highlightCode` are deliberately not cached: the
|
|
13
|
+
* upstream package owns WASM initialization and trap recovery, while the
|
|
14
|
+
* component owns request cancellation and stale-result protection.
|
|
15
15
|
*/
|
|
16
16
|
export declare function createHighlightRuntime(importModule: HighlightModuleImporter): HtmlPreviewHighlightRuntime;
|
|
17
17
|
/**
|
|
@@ -20,7 +20,7 @@ function createHighlightRuntime(importModule) {
|
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
22
|
const defaultRuntime = createHighlightRuntime(
|
|
23
|
-
() => import("@takazudo/zfb-md-wasm")
|
|
23
|
+
() => import("@takazudo/zfb-md-wasm/highlight")
|
|
24
24
|
);
|
|
25
25
|
function getUsableHighlightHtml(result) {
|
|
26
26
|
if (result.html == null || result.diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { dirname, resolve } from "node:path";
|
|
1
|
+
import { mkdirSync, readdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
3
|
import { generateChangelogMarkdown } from "./generate.js";
|
|
4
4
|
import { loadChangelogEntries } from "./load.js";
|
|
5
5
|
function emitChangelogs(options) {
|
|
@@ -8,6 +8,16 @@ function emitChangelogs(options) {
|
|
|
8
8
|
const sourceDir = resolve(options.projectRoot, config.sourceDir);
|
|
9
9
|
const outputFile = resolve(options.projectRoot, config.outputFile);
|
|
10
10
|
const entries = loadChangelogEntries({ sourceDir });
|
|
11
|
+
if (entries.length === 0) {
|
|
12
|
+
const nestedMdxDirs = findNestedMdxDirs(sourceDir);
|
|
13
|
+
if (nestedMdxDirs.length > 0) {
|
|
14
|
+
options.logger?.warn?.(
|
|
15
|
+
`Changelog sourceDir "${config.sourceDir}" (${sourceDir}) yielded 0 releases but contains .mdx files in sub-directories: ${nestedMdxDirs.join(
|
|
16
|
+
", "
|
|
17
|
+
)}. In a multi-changelog layout, each changelogs[] entry must point at a per-package directory (for example "${config.sourceDir}/<name>") whose per-release files are non-index .mdx files; the loader intentionally skips index.mdx.`
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
11
21
|
const markdown = generateChangelogMarkdown(entries, {
|
|
12
22
|
title: config.title,
|
|
13
23
|
packageName: config.packageName
|
|
@@ -19,6 +29,13 @@ function emitChangelogs(options) {
|
|
|
19
29
|
}
|
|
20
30
|
return { written };
|
|
21
31
|
}
|
|
32
|
+
function findNestedMdxDirs(sourceDir) {
|
|
33
|
+
return readdirSync(sourceDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).filter(
|
|
34
|
+
(entry) => readdirSync(join(sourceDir, entry.name), { withFileTypes: true }).some(
|
|
35
|
+
(nestedEntry) => nestedEntry.isFile() && nestedEntry.name.endsWith(".mdx")
|
|
36
|
+
)
|
|
37
|
+
).map((entry) => entry.name).sort();
|
|
38
|
+
}
|
|
22
39
|
export {
|
|
23
40
|
emitChangelogs
|
|
24
41
|
};
|
|
@@ -2,7 +2,7 @@ import type { Settings } from "../settings.js";
|
|
|
2
2
|
/** Any Preact-renderable component (function returning a VNode/children). */
|
|
3
3
|
type AnyComponent = (props: Record<string, unknown>) => unknown;
|
|
4
4
|
/**
|
|
5
|
-
* The
|
|
5
|
+
* The host-supplied, locale-aware nav wrappers. Each accepts a `lang` prop;
|
|
6
6
|
* the factory injects the active `locale` so the wrapper queries the correct
|
|
7
7
|
* collection. `SiteTreeNav` is reused for both the `SiteTreeNav` and
|
|
8
8
|
* `SiteTreeNavDemo` MDX tags (matching the showcase mapping).
|
|
@@ -11,6 +11,7 @@ export interface MdxNavData {
|
|
|
11
11
|
CategoryNav: AnyComponent;
|
|
12
12
|
CategoryTreeNav: AnyComponent;
|
|
13
13
|
SiteTreeNav: AnyComponent;
|
|
14
|
+
NoteTrayIndex: AnyComponent;
|
|
14
15
|
}
|
|
15
16
|
export interface CreateMdxComponentsOptions {
|
|
16
17
|
/**
|
|
@@ -35,6 +36,8 @@ export interface CreateMdxComponentsOptions {
|
|
|
35
36
|
* which wrapper `navData` holds.
|
|
36
37
|
*/
|
|
37
38
|
currentVersion?: string;
|
|
39
|
+
/** Canonical slug of the page being rendered, used to infer its containing note tray. */
|
|
40
|
+
currentSlug: string;
|
|
38
41
|
/** Host-supplied locale-aware nav wrappers. */
|
|
39
42
|
navData: MdxNavData;
|
|
40
43
|
/**
|
|
@@ -109,7 +109,7 @@ function makeEnlargeableParagraph(imageEnlarge, ContentImg) {
|
|
|
109
109
|
};
|
|
110
110
|
}
|
|
111
111
|
function createMdxComponents(options) {
|
|
112
|
-
const { settings, locale, currentVersion, navData, extras } = options;
|
|
112
|
+
const { settings, locale, currentVersion, currentSlug, navData, extras } = options;
|
|
113
113
|
const ContentImg = makeContentImg(settings.base);
|
|
114
114
|
const EnlargeableParagraph = makeEnlargeableParagraph(
|
|
115
115
|
settings.imageEnlarge,
|
|
@@ -118,6 +118,7 @@ function createMdxComponents(options) {
|
|
|
118
118
|
const CategoryNavBound = (props) => navData.CategoryNav({ ...props, lang: locale, currentVersion });
|
|
119
119
|
const CategoryTreeNavBound = (props) => navData.CategoryTreeNav({ ...props, lang: locale, currentVersion });
|
|
120
120
|
const SiteTreeNavBound = (props) => navData.SiteTreeNav({ ...props, lang: locale, currentVersion });
|
|
121
|
+
const NoteTrayIndexBound = (props) => navData.NoteTrayIndex({ ...props, lang: locale, currentVersion, currentSlug });
|
|
121
122
|
return {
|
|
122
123
|
...defaultComponents,
|
|
123
124
|
// img override: rewrites root-relative src to include settings.base.
|
|
@@ -151,6 +152,7 @@ function createMdxComponents(options) {
|
|
|
151
152
|
CategoryTreeNav: CategoryTreeNavBound,
|
|
152
153
|
SiteTreeNav: SiteTreeNavBound,
|
|
153
154
|
SiteTreeNavDemo: SiteTreeNavBound,
|
|
155
|
+
NoteTrayIndex: NoteTrayIndexBound,
|
|
154
156
|
// Host extras win over any same-named default above (Details, HtmlPreview,
|
|
155
157
|
// Island, PresetGenerator, showcase stubs, etc.).
|
|
156
158
|
...extras ?? {}
|
|
@@ -34,4 +34,6 @@ export { DocCardGrid } from "./doc-card-grid.js";
|
|
|
34
34
|
export type { DocCardGridProps, DocCardItem } from "./doc-card-grid.js";
|
|
35
35
|
export { VersionsPageContent } from "./versions-page-content.js";
|
|
36
36
|
export type { VersionsPageContentProps } from "./versions-page-content.js";
|
|
37
|
+
export { NoteTrayIndex } from "./note-tray-index.js";
|
|
38
|
+
export type { NoteTrayIndexItem, NoteTrayIndexProps, NoteTrayIndexStyle, } from "./note-tray-index.js";
|
|
37
39
|
export type { NavNode, TagItem, TagLink, TagNavLabels, VersionPageEntry, VersionsPageLabels, } from "./types.js";
|
|
@@ -6,12 +6,14 @@ import { DocsSitemap } from "./docs-sitemap.js";
|
|
|
6
6
|
import { NavCardGrid } from "./nav-card-grid.js";
|
|
7
7
|
import { DocCardGrid } from "./doc-card-grid.js";
|
|
8
8
|
import { VersionsPageContent } from "./versions-page-content.js";
|
|
9
|
+
import { NoteTrayIndex } from "./note-tray-index.js";
|
|
9
10
|
export {
|
|
10
11
|
CategoryNav,
|
|
11
12
|
CategoryTreeNav,
|
|
12
13
|
DocCardGrid,
|
|
13
14
|
DocsSitemap,
|
|
14
15
|
NavCardGrid,
|
|
16
|
+
NoteTrayIndex,
|
|
15
17
|
SiteTreeNavDemo,
|
|
16
18
|
TagNav,
|
|
17
19
|
VersionsPageContent
|