@takazudo/zudo-doc 5.24.2 → 5.26.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.
Files changed (66) hide show
  1. package/CHANGELOG.md +40 -2
  2. package/dist/asset-components/asset-card.js +2 -2
  3. package/dist/asset-components/asset-code.js +1 -1
  4. package/dist/asset-index-page/body.d.ts +80 -0
  5. package/dist/asset-index-page/body.js +116 -0
  6. package/dist/asset-index-page/index.d.ts +3 -9
  7. package/dist/asset-index-page/index.js +18 -149
  8. package/dist/asset-index-page/tree.d.ts +24 -0
  9. package/dist/asset-index-page/tree.js +43 -0
  10. package/dist/asset-page/body.d.ts +98 -0
  11. package/dist/asset-page/body.js +107 -0
  12. package/dist/asset-page/components.d.ts +94 -0
  13. package/dist/asset-page/components.js +175 -0
  14. package/dist/asset-page/index.d.ts +3 -78
  15. package/dist/asset-page/index.js +30 -240
  16. package/dist/asset-page/shared.d.ts +15 -0
  17. package/dist/asset-page/shared.js +28 -0
  18. package/dist/breadcrumb/breadcrumb.js +1 -1
  19. package/dist/compiled.css +18 -71
  20. package/dist/config/component-tokens.js +3 -1
  21. package/dist/config-assertions/index.d.ts +26 -0
  22. package/dist/config-assertions/index.js +12 -0
  23. package/dist/config.d.ts +28 -0
  24. package/dist/config.js +6 -1
  25. package/dist/doc-pager/index.js +2 -2
  26. package/dist/footer/footer.js +3 -3
  27. package/dist/header/header.js +2 -1
  28. package/dist/header-with-defaults/index.d.ts +20 -0
  29. package/dist/header-with-defaults/index.js +9 -2
  30. package/dist/home-page/index.js +49 -22
  31. package/dist/i18n-defaults/index.js +3 -0
  32. package/dist/integrations/changelog/generate.js +8 -2
  33. package/dist/integrations/changelog/links.d.ts +25 -0
  34. package/dist/integrations/changelog/links.js +280 -0
  35. package/dist/mdx-components/index.js +1 -1
  36. package/dist/nav-indexing/category-nav.js +1 -1
  37. package/dist/nav-indexing/category-tree-nav.js +1 -1
  38. package/dist/nav-indexing/doc-card-grid.js +2 -2
  39. package/dist/nav-indexing/docs-sitemap.js +2 -2
  40. package/dist/nav-indexing/nav-card-grid.d.ts +3 -2
  41. package/dist/nav-indexing/nav-card-grid.js +3 -3
  42. package/dist/nav-indexing/note-tray-index-parts/card-list.js +1 -1
  43. package/dist/nav-indexing/note-tray-index-parts/index-list.js +1 -1
  44. package/dist/nav-indexing/note-tray-index-parts/timeline.js +1 -1
  45. package/dist/nav-indexing/site-tree-nav-demo.js +2 -2
  46. package/dist/nav-indexing/versions-page-content.js +2 -2
  47. package/dist/plugins/routes.js +2 -13
  48. package/dist/plugins/zdtp-loader.js +1 -1
  49. package/dist/preset.d.ts +11 -1
  50. package/dist/preset.js +12 -5
  51. package/dist/safelist.css +1 -1
  52. package/dist/settings.d.ts +9 -0
  53. package/dist/sidebar-toggle-island/index.js +1 -1
  54. package/dist/site-tree-nav-island/index.js +3 -3
  55. package/dist/theme-packs/foundry/pack.css +4 -9
  56. package/dist/theme-packs/riso/pack.css +6 -4
  57. package/dist/theme-packs/swissgrid/pack.css +7 -4
  58. package/eject/asset-components/asset-card.tsx +2 -2
  59. package/eject/asset-components/asset-code.tsx +1 -1
  60. package/eject/breadcrumb/breadcrumb.tsx +1 -1
  61. package/eject/doc-pager/index.tsx +2 -2
  62. package/eject/footer/footer.tsx +4 -4
  63. package/eject/header/header.tsx +11 -1
  64. package/eject/sidebar-toggle-island/index.tsx +1 -1
  65. package/eject/site-tree-nav-island/index.tsx +4 -4
  66. package/package.json +37 -9
package/CHANGELOG.md CHANGED
@@ -8,6 +8,42 @@ The format is based on Keep a Changelog, and release notes are generated from th
8
8
 
9
9
  No unreleased changes.
10
10
 
11
+ ## [5.26.0] - 2026-09-20
12
+
13
+ ### Features
14
+
15
+ - Added a `bundleZdtp` setting so hosts can keep the real `@takazudo/zdtp` loader while mounting their own panel with `designTokenPanel: false`. The setting defaults to `designTokenPanel`, preserving existing behavior; the invalid `designTokenPanel: true` plus `bundleZdtp: false` combination is rejected before it can ship a mounted panel backed by the throwing stub. (28bb8e8e4)
16
+
17
+ ### Bug Fixes
18
+
19
+ - `gen:changelog` now rewrites relative `.md`/`.mdx` links to emitted entries as in-file anchors and unlinks other relative document links, keeping published package changelogs free of source-tree-only URLs. It also preserves parenthesized link titles, accounts for Setext headings when suffixing duplicate anchors, and retains case-sensitive filename matching. (267b27d12, 290fb0214)
20
+ - Long site names no longer push the mobile header's right-side controls outside a narrow viewport at increased browser font sizes. The site-name link truncates with an ellipsis while its `title` preserves the full text, and the hamburger button no longer shrinks. Swissgrid and Riso also retain their flat truncation treatment with corrected marker baselines. (cc3a3b053, 104841612, 44fd94899)
21
+ - Home-page hero headings now wrap long unbroken words instead of overflowing their container. (c7e058f68)
22
+
23
+ ### Other Changes
24
+
25
+ - The `@takazudo/zfb`, `@takazudo/zfb-runtime`, and `@takazudo/zfb-md-wasm` peer dependency floors are now `^2.19.0`. zfb 2.19 adds an opt-in build-shadow diagnostic mode and a parser dependency patch verified equivalent across all 18 upstream baseline cases; it requires no zudo-doc config migration. (4c2048528)
26
+ - Investigated a reported package-route resolution failure on zfb 2.18.0 across 11 consumer topologies, including the reporter's pnpm-workspace symlink shape, without reproducing it. No runtime code or workaround changed; the diagnostic findings remain documented for affected users. (0bd27b8e9)
27
+
28
+ ## [5.25.0] - 2026-09-16
29
+
30
+ ### Features
31
+
32
+ - Added a `siteTreeNavSecondary` setting: top-level categories named here move out of the home page's card grid into a compact secondary link row, and the "See all tags" link was restyled to match it. There is no CLI or prompt surface yet — set it by hand in your config after scaffolding. (42bd642c5)
33
+ - Added an optional `sidebarNodes` prop to `HeaderWithDefaultsProps`, letting a host override the mobile-drawer sidebar tree per render (as a fixed array or a callback with a lazy `buildDefault()`) without replacing the whole `Header` chrome component. (01a7d088d)
34
+ - Added zfb-free asset-viewer subpaths — `./asset-page/{body,components,script}`, `./asset-index-page`, and `./asset-index-page/{body,tree,script}` — so `AssetPageBody` / `AssetIndexPageBody` and their `resolveAsset*Labels(t, locale)` helpers can be imported and rendered without a `ChromeContext` or any `@takazudo/zfb*` package. `./asset-page` and `./asset-index-page` remain the zfb-bound factories; neither body provides `DocLayout`, so the caller owns the outer page shell. (558aa58cf)
35
+ - Applied the sitewide link-color rule across package chrome, so link styling is now consistent everywhere instead of varying per component. Prose links stay accent + underline; every other link sits at `text-fg` (or `text-muted` in tertiary chrome) and turns accent on hover and focus, with static underlines removed outside prose and tertiary inline rows. This covers the footer, breadcrumb, and DocPager (ffef68952), nav-indexing lists, note-tray parts, and site-tree-nav focus (4ed4e2f7d), nav-indexing card grids (224232644), and the asset controls and image-caption viewer link (87ee6c1d5). Expect visible link-color and underline changes if you relied on the previous per-component styling.
36
+
37
+ ### Bug Fixes
38
+
39
+ - The asset page no longer reuses a singleton prepaint-script VNode across SSR passes, which could let one render's prepaint state leak into another. (9bc892343)
40
+ - The `foundry` theme pack keeps its footer copyright underline, which the link-color rule work had removed. (dcec0daeb)
41
+
42
+ ### Other Changes
43
+
44
+ - Removed the `.zudo-doc/routes-src` project-local staging step for package-owned route injection. It was a workaround for zfb not resolving `virtual:*` imports from route files under `node_modules`; zfb resolves those imports directly now, so the published `routes-src/` file is injected without a staging copy. If your project has a leftover `<project>/.zudo-doc/` directory, it is now inert and can be deleted. (c56b24342)
45
+ - The `@takazudo/zfb`, `@takazudo/zfb-runtime`, and `@takazudo/zfb-md-wasm` peer dependency floors are now `^2.18.0` (e4d4b64b3). That release reworks package-injected routes and plugin virtual modules — the islands scanner resolves plugin virtual modules and is seeded with package-route entrypoints, dev CSS content globs are seeded from injected routes, and injected-route entrypoints are staged as exact files that SSR virtual modules may import. It changes no API, export, or config default, so existing projects need no migration beyond having zfb 2.18.0 installed.
46
+
11
47
  ## [5.24.2] - 2026-09-15
12
48
 
13
49
  ### Bug Fixes
@@ -236,6 +272,9 @@ No unreleased changes.
236
272
 
237
273
  ### Other Changes
238
274
 
275
+ - Enabled Asset Viewer routes now localize automatically for existing multi-locale consumers after a package bump, unless `/${assetViewerRoutePrefix}/` is explicitly present in `defaultLocaleOnlyPrefixes`. (#3820)
276
+ - Migration: because the resource indexes are now locale-owned, a resource section only stays default-locale-only when `defaultLocaleOnlyPrefixes` lists its **overview** prefix — `/docs/claude/` or `/docs/codex/` — alongside the sub-section prefixes (`/docs/claude-md/`, `/docs/claude-skills/`, `/docs/claude-agents/`, `/docs/claude-commands/`, and the Codex equivalents). Listing only the sub-section prefixes does not make the section default-locale-only. With the overview prefix missing, the overview index is generated into every configured locale; if that locale directory already holds an authored `claude/index.mdx` without `generated: true` in its frontmatter, the build fails with `resource-docs: refusing to overwrite authored locale index`. Either add the overview prefix, or remove the authored index and move its title, description, and labels to `ZudoDocConfig.translations`. Remove the prefixes entirely for resource sections you want generated in every configured locale. The rule and both ways out are in the Internationalization guide, under Default-Locale-Only Prefixes. (#3820)
277
+ - Migration: before building with 5.15.0, remove or rename any authored locale resource overview/category `index.mdx` at a generator target and move custom titles, descriptions, and labels to `ZudoDocConfig.translations`. The generator refuses to overwrite an existing file unless its frontmatter contains `generated: true`. (#3820)
239
278
  - Updated the zfb peer family to 2.14.2. (`5c5bfd2f`)
240
279
 
241
280
  ## [5.14.0] - 2026-08-31
@@ -257,8 +296,7 @@ No unreleased changes.
257
296
 
258
297
  ### Other Changes
259
298
 
260
- - Enabled Asset Viewer routes now localize automatically for existing multi-locale consumers after a package bump, unless `/${assetViewerRoutePrefix}/` is explicitly present in `defaultLocaleOnlyPrefixes`. Existing apps that already carry the legacy Claude/Codex resource prefixes remain default-locale-only after upgrading; remove the prefixes for resource sections you want generated in every configured locale. (#3820)
261
- - Migration: before building with 5.14.0, remove or rename any authored locale resource overview/category `index.mdx` at a generator target and move custom titles, descriptions, and labels to `ZudoDocConfig.translations`. The generator refuses to overwrite an existing file unless its frontmatter contains `generated: true`. (#3820)
299
+ - Locale-owned Claude/Codex resource indexes and automatically localized Asset Viewer routes ship in [5.15.0](#5150---2026-09-01), not in this release. Before moving on to 5.15.0, add `/docs/claude/` and `/docs/codex/` to `defaultLocaleOnlyPrefixes` if those sections should stay default-locale-only — or remove any authored locale resource `index.mdx` and move its title, description, and labels to `ZudoDocConfig.translations`. The 5.15.0 entry has the full migration. (#3820)
262
300
  - Updated the zfb peer family to 2.14.0, `@takazudo/zdtp` to 0.4.14, and the doc-history-server peer floor to 5.13.1. (`dbe8553c7`, `038870c94`, `95a82f15f`)
263
301
 
264
302
  ## [5.13.1] - 2026-08-28
@@ -55,7 +55,7 @@ function createAssetCard(context) {
55
55
  /* @__PURE__ */ jsxs(
56
56
  "a",
57
57
  {
58
- className: "text-accent hover:underline focus-visible:underline",
58
+ className: "text-fg hover:text-accent focus-visible:text-accent hover:underline focus-visible:underline",
59
59
  href: viewerHref,
60
60
  children: [
61
61
  assetComponentText(
@@ -70,7 +70,7 @@ function createAssetCard(context) {
70
70
  /* @__PURE__ */ jsxs(
71
71
  "a",
72
72
  {
73
- className: "text-accent hover:underline focus-visible:underline",
73
+ className: "text-fg hover:text-accent focus-visible:text-accent hover:underline focus-visible:underline",
74
74
  href: rawHref,
75
75
  download: true,
76
76
  children: [
@@ -74,7 +74,7 @@ function createAssetCode(context) {
74
74
  /* @__PURE__ */ jsxs(
75
75
  "a",
76
76
  {
77
- className: "text-accent hover:underline focus-visible:underline",
77
+ className: "text-fg hover:text-accent focus-visible:text-accent hover:underline focus-visible:underline",
78
78
  href: viewerHref,
79
79
  children: [
80
80
  assetComponentText(
@@ -0,0 +1,80 @@
1
+ /** @jsxRuntime automatic */
2
+ /** @jsxImportSource preact */
3
+ import type { ComponentType, VNode } from "preact";
4
+ import type { AssetIndexEntry } from "../route-context-payload/types.js";
5
+ import { type AssetTreeNode } from "./tree.js";
6
+ /**
7
+ * Localized strings `AssetIndexPageBody` needs to render, resolved ahead of
8
+ * time (typically via `resolveAssetIndexPageLabels`) so the body needs no
9
+ * `t()`/`ctx` of its own.
10
+ *
11
+ * `fileCount`, `fileCountSingle`, `folderCount`, `folderCountSingle`, and
12
+ * `lines` are raw translation templates and still carry the literal
13
+ * `"{count}"` placeholder — they are NOT pre-expanded. The body substitutes
14
+ * the placeholder itself (via `countLabel` for the two counts, and per-file
15
+ * for `lines`) because the substituted count depends on the specific node/
16
+ * asset being rendered, not on locale alone. Every other field below is an
17
+ * already-resolved, ready-to-render string.
18
+ */
19
+ export interface AssetIndexPageLabels {
20
+ /** Page/crumb title (e.g. "Assets") — doubles as the eyebrow badge and `<h1>` text. */
21
+ crumb: string;
22
+ /** Secondary eyebrow badge, e.g. "Index". */
23
+ indexBadge: string;
24
+ /** Description shown under the title. */
25
+ indexDescription: string;
26
+ /** Raw `asset.fileCount` template — see the interface doc comment above. */
27
+ fileCount: string;
28
+ /** Raw `asset.fileCountSingle` template. */
29
+ fileCountSingle: string;
30
+ /** Raw `asset.folderCount` template. */
31
+ folderCount: string;
32
+ /** Raw `asset.folderCountSingle` template. */
33
+ folderCountSingle: string;
34
+ expandAll: string;
35
+ collapseAll: string;
36
+ indexEmpty: string;
37
+ /** Raw `asset.lines` template. */
38
+ lines: string;
39
+ }
40
+ /**
41
+ * Builds `AssetIndexPageLabels` from a `t(key, locale)` translator, bound to
42
+ * the given `locale` — NOT the default locale, so a localized page gets its
43
+ * own strings even when `t` itself falls back internally.
44
+ */
45
+ export declare function resolveAssetIndexPageLabels(t: (key: string, locale?: string) => string, locale: string): AssetIndexPageLabels;
46
+ export declare function iconFor(asset: AssetIndexEntry): ComponentType<{
47
+ className?: string;
48
+ }>;
49
+ export declare function AssetTree({ node, base, routePrefix, locale, fileCountLabel, fileCountSingleLabel, linesLabel, root }: {
50
+ node: AssetTreeNode;
51
+ base: string;
52
+ routePrefix: string;
53
+ locale?: string;
54
+ fileCountLabel: string;
55
+ fileCountSingleLabel: string;
56
+ linesLabel: string;
57
+ root?: boolean;
58
+ }): VNode;
59
+ export interface AssetIndexPageBodyProps {
60
+ /** Every asset entry in the index. */
61
+ entries: AssetIndexEntry[];
62
+ /** Localized strings — see `resolveAssetIndexPageLabels()`. */
63
+ labels: AssetIndexPageLabels;
64
+ /** Resolved `settings.base`, forwarded to file link hrefs. */
65
+ base: string;
66
+ /** Resolved asset-viewer route prefix, forwarded to file link hrefs. */
67
+ routePrefix: string;
68
+ /** Asset-viewer source directory (e.g. `assets`), rendered verbatim as `public/${dir}/`. */
69
+ dir: string;
70
+ /** Locale segment (undefined for the default locale), forwarded to file link hrefs. */
71
+ locale?: string;
72
+ }
73
+ /**
74
+ * Renders the asset index page body — header, file/folder counts, toolbar,
75
+ * the nested tree (or empty state), and the inline bootstrap script —
76
+ * exactly what `createAssetIndexPageView` placed inline as
77
+ * `DocLayoutWithDefaults`'s children before the #4223 split. Plain props
78
+ * only, no `ChromeContext`.
79
+ */
80
+ export declare function AssetIndexPageBody({ entries, labels, base, routePrefix, dir, locale }: AssetIndexPageBodyProps): VNode;
@@ -0,0 +1,116 @@
1
+ import { jsx, jsxs } from "preact/jsx-runtime";
2
+ import { formatAssetBytes } from "../asset-components/index.js";
3
+ import { assetViewerHref } from "../asset-path/index.js";
4
+ import { facetLabel, kindLabel } from "../asset-page/shared.js";
5
+ import {
6
+ ChevronRight,
7
+ FileArchive,
8
+ FileCode,
9
+ FileGeneric,
10
+ FileImage,
11
+ FilePdf,
12
+ FileText,
13
+ FileVideo,
14
+ Folder,
15
+ FolderOpen
16
+ } from "../icons/index.js";
17
+ import { ASSET_INDEX_PAGE_SCRIPT } from "./script.js";
18
+ import { basename, buildAssetTree, countLabel, folderCount } from "./tree.js";
19
+ const ARCHIVE_EXTENSIONS = /* @__PURE__ */ new Set(["zip", "tar", "gz", "tgz", "7z", "rar"]);
20
+ function resolveAssetIndexPageLabels(t, locale) {
21
+ return {
22
+ crumb: t("asset.crumb", locale),
23
+ indexBadge: t("asset.indexBadge", locale),
24
+ indexDescription: t("asset.indexDescription", locale),
25
+ fileCount: t("asset.fileCount", locale),
26
+ fileCountSingle: t("asset.fileCountSingle", locale),
27
+ folderCount: t("asset.folderCount", locale),
28
+ folderCountSingle: t("asset.folderCountSingle", locale),
29
+ expandAll: t("asset.expandAll", locale),
30
+ collapseAll: t("asset.collapseAll", locale),
31
+ indexEmpty: t("asset.indexEmpty", locale),
32
+ lines: t("asset.lines", locale)
33
+ };
34
+ }
35
+ function iconFor(asset) {
36
+ const extension = basename(asset.path).split(".").at(-1)?.toLowerCase();
37
+ if (asset.kind === "other" && extension && ARCHIVE_EXTENSIONS.has(extension)) return FileArchive;
38
+ const icons = {
39
+ code: FileCode,
40
+ text: FileText,
41
+ image: FileImage,
42
+ video: FileVideo,
43
+ pdf: FilePdf,
44
+ other: FileGeneric
45
+ };
46
+ return icons[asset.kind];
47
+ }
48
+ function AssetTree({ node, base, routePrefix, locale, fileCountLabel, fileCountSingleLabel, linesLabel, root = false }) {
49
+ return /* @__PURE__ */ jsxs("ul", { "data-zd-asset-tree": root ? true : void 0, children: [
50
+ node.dirs.map((dir) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("details", { open: true, children: [
51
+ /* @__PURE__ */ jsxs("summary", { class: "flex cursor-pointer items-center gap-hsp-xs rounded px-hsp-sm py-vsp-3xs text-small hover:bg-accent/10 focus-visible:bg-accent/10", children: [
52
+ /* @__PURE__ */ jsx(ChevronRight, { className: "h-icon-xs w-icon-xs shrink-0 text-muted transition-transform" }),
53
+ /* @__PURE__ */ jsx(Folder, { className: "h-icon-sm w-icon-sm shrink-0 text-muted" }),
54
+ /* @__PURE__ */ jsx(FolderOpen, { className: "h-icon-sm w-icon-sm shrink-0 text-muted" }),
55
+ /* @__PURE__ */ jsxs("span", { class: "min-w-0 truncate font-mono text-fg", children: [
56
+ dir.name,
57
+ "/"
58
+ ] }),
59
+ /* @__PURE__ */ jsxs("span", { class: "ml-auto hidden whitespace-nowrap pl-hsp-lg text-caption text-muted sm:block", children: [
60
+ countLabel(dir.fileCount, fileCountLabel, fileCountSingleLabel),
61
+ " \xB7 ",
62
+ formatAssetBytes(dir.bytes)
63
+ ] })
64
+ ] }),
65
+ /* @__PURE__ */ jsx(AssetTree, { node: dir, base, routePrefix, locale, fileCountLabel, fileCountSingleLabel, linesLabel })
66
+ ] }) })),
67
+ node.files.map((asset) => {
68
+ const Icon = iconFor(asset);
69
+ const facet = facetLabel(asset, asset.lines !== void 0 ? linesLabel.replace("{count}", String(asset.lines)) : void 0);
70
+ const meta = [kindLabel(asset), facet, formatAssetBytes(asset.bytes)].filter(Boolean).join(" \xB7 ");
71
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("a", { href: assetViewerHref({ base, routePrefix, path: asset.path, locale }), title: asset.name, class: "flex min-w-0 items-center gap-hsp-xs rounded px-hsp-sm py-vsp-3xs text-small text-fg hover:bg-accent/10 hover:text-accent hover:underline focus-visible:bg-accent/10 focus-visible:text-accent focus-visible:underline", children: [
72
+ /* @__PURE__ */ jsx(Icon, { className: "h-icon-sm w-icon-sm shrink-0 text-muted" }),
73
+ /* @__PURE__ */ jsx("span", { class: "min-w-0 truncate font-mono", children: basename(asset.path) }),
74
+ /* @__PURE__ */ jsx("span", { class: "ml-auto hidden whitespace-nowrap pl-hsp-lg text-caption text-muted sm:block", children: meta })
75
+ ] }) });
76
+ })
77
+ ] });
78
+ }
79
+ function AssetIndexPageBody({ entries, labels, base, routePrefix, dir, locale }) {
80
+ const tree = buildAssetTree(entries);
81
+ const folders = folderCount(tree);
82
+ return /* @__PURE__ */ jsxs("div", { "data-zd-asset-index-page": true, children: [
83
+ /* @__PURE__ */ jsxs("header", { children: [
84
+ /* @__PURE__ */ jsxs("div", { class: "mb-vsp-xs flex flex-wrap items-center gap-hsp-xs text-micro tracking-wide uppercase", children: [
85
+ /* @__PURE__ */ jsx("span", { class: "rounded-full border border-muted px-hsp-sm py-vsp-3xs text-fg", children: labels.crumb }),
86
+ /* @__PURE__ */ jsx("span", { class: "rounded-full border border-muted px-hsp-sm py-vsp-3xs text-muted", children: labels.indexBadge })
87
+ ] }),
88
+ /* @__PURE__ */ jsx("h1", { class: "mb-vsp-xs border-b border-fg pb-vsp-xs font-mono text-heading font-bold leading-tight break-words", children: labels.crumb }),
89
+ /* @__PURE__ */ jsxs("div", { "data-doc-metainfo": true, class: "mb-vsp-md flex flex-wrap items-center gap-x-hsp-md gap-y-vsp-2xs text-caption text-fg", children: [
90
+ /* @__PURE__ */ jsx("span", { children: countLabel(tree.fileCount, labels.fileCount, labels.fileCountSingle) }),
91
+ /* @__PURE__ */ jsx("span", { children: countLabel(folders, labels.folderCount, labels.folderCountSingle) }),
92
+ /* @__PURE__ */ jsx("span", { children: formatAssetBytes(tree.bytes) })
93
+ ] }),
94
+ /* @__PURE__ */ jsx("p", { class: "mb-vsp-lg text-title text-muted", children: labels.indexDescription })
95
+ ] }),
96
+ /* @__PURE__ */ jsxs("div", { class: "mb-vsp-sm flex flex-wrap items-center justify-between gap-hsp-sm text-caption", children: [
97
+ /* @__PURE__ */ jsxs("span", { class: "font-mono text-muted", children: [
98
+ "public/",
99
+ dir,
100
+ "/"
101
+ ] }),
102
+ /* @__PURE__ */ jsxs("span", { class: "flex gap-hsp-sm", children: [
103
+ /* @__PURE__ */ jsx("button", { type: "button", disabled: true, "data-zd-asset-index-action": "expand", class: "text-fg hover:text-accent focus-visible:text-accent", children: labels.expandAll }),
104
+ /* @__PURE__ */ jsx("button", { type: "button", disabled: true, "data-zd-asset-index-action": "collapse", class: "text-fg hover:text-accent focus-visible:text-accent", children: labels.collapseAll })
105
+ ] })
106
+ ] }),
107
+ entries.length > 0 ? /* @__PURE__ */ jsx(AssetTree, { root: true, node: tree, base, routePrefix, locale, fileCountLabel: labels.fileCount, fileCountSingleLabel: labels.fileCountSingle, linesLabel: labels.lines }) : /* @__PURE__ */ jsx("p", { class: "text-small text-muted", "data-zd-asset-index-empty": true, children: labels.indexEmpty }),
108
+ /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: ASSET_INDEX_PAGE_SCRIPT } })
109
+ ] });
110
+ }
111
+ export {
112
+ AssetIndexPageBody,
113
+ AssetTree,
114
+ iconFor,
115
+ resolveAssetIndexPageLabels
116
+ };
@@ -5,18 +5,12 @@ import type { ChromeContext } from "../factory-context/index.js";
5
5
  import type { AssetIndexEntry } from "../route-context-payload/types.js";
6
6
  import type { Settings } from "../settings.js";
7
7
  export { ASSET_INDEX_PAGE_SCRIPT } from "./script.js";
8
- export interface AssetTreeNode {
9
- name: string;
10
- dirs: AssetTreeNode[];
11
- files: AssetIndexEntry[];
12
- fileCount: number;
13
- bytes: number;
14
- }
8
+ export { buildAssetTree } from "./tree.js";
9
+ export type { AssetTreeNode } from "./tree.js";
10
+ export { AssetIndexPageBody, AssetTree, iconFor, resolveAssetIndexPageLabels, type AssetIndexPageBodyProps, type AssetIndexPageLabels, } from "./body.js";
15
11
  export interface AssetIndexPageViewProps {
16
12
  entries: AssetIndexEntry[];
17
13
  locale?: string;
18
14
  }
19
- /** Build a sorted directory tree and aggregate every directory's subtree totals. */
20
- export declare function buildAssetTree(entries: AssetIndexEntry[]): AssetTreeNode;
21
15
  /** Build the package-owned wide asset index from a chrome context. */
22
16
  export declare function createAssetIndexPageView<S extends Settings = Settings>(ctx: ChromeContext<S>): (props: AssetIndexPageViewProps) => JSX.Element;
@@ -1,122 +1,19 @@
1
- import { jsx, jsxs } from "preact/jsx-runtime";
2
- import { assetViewerHref } from "../asset-path/index.js";
3
- import { formatAssetBytes } from "../asset-components/index.js";
1
+ import { jsx } from "preact/jsx-runtime";
4
2
  import { assertChromeContext } from "../chrome/assert-chrome-context.js";
5
3
  import { deriveBodyEndIslands, deriveComposeMetaTitle } from "../chrome/derive.js";
6
4
  import { derivePrimaryChromeSlots } from "../chrome/primary-slots.js";
7
5
  import { DocLayoutWithDefaults } from "../doclayout/index.js";
8
6
  import { createHeadWithDefaults } from "../head-with-defaults/index.js";
9
- import {
10
- ChevronRight,
11
- FileArchive,
12
- FileCode,
13
- FileGeneric,
14
- FileImage,
15
- FilePdf,
16
- FileText,
17
- FileVideo,
18
- Folder,
19
- FolderOpen
20
- } from "../icons/index.js";
21
7
  import { resolveThemePackSsrSlug } from "../theme/theme-pack-provider.js";
8
+ import { AssetIndexPageBody, resolveAssetIndexPageLabels } from "./body.js";
22
9
  import { ASSET_INDEX_PAGE_SCRIPT } from "./script.js";
23
- import { ASSET_INDEX_PAGE_SCRIPT as ASSET_INDEX_PAGE_SCRIPT2 } from "./script.js";
24
- const ARCHIVE_EXTENSIONS = /* @__PURE__ */ new Set(["zip", "tar", "gz", "tgz", "7z", "rar"]);
25
- function basename(path) {
26
- return path.split("/").at(-1) ?? path;
27
- }
28
- function freezeTree(node) {
29
- const dirs = [...node.dirs.values()].sort((a, b) => a.name.localeCompare(b.name)).map(freezeTree);
30
- const files = [...node.files].sort((a, b) => basename(a.path).localeCompare(basename(b.path)));
31
- return {
32
- name: node.name,
33
- dirs,
34
- files,
35
- fileCount: files.length + dirs.reduce((total, dir) => total + dir.fileCount, 0),
36
- bytes: files.reduce((total, file) => total + file.bytes, 0) + dirs.reduce((total, dir) => total + dir.bytes, 0)
37
- };
38
- }
39
- function buildAssetTree(entries) {
40
- const root = { name: "", dirs: /* @__PURE__ */ new Map(), files: [] };
41
- for (const entry of entries) {
42
- let node = root;
43
- for (const segment of entry.dir.split("/").filter(Boolean)) {
44
- let child = node.dirs.get(segment);
45
- if (!child) {
46
- child = { name: segment, dirs: /* @__PURE__ */ new Map(), files: [] };
47
- node.dirs.set(segment, child);
48
- }
49
- node = child;
50
- }
51
- node.files.push(entry);
52
- }
53
- return freezeTree(root);
54
- }
55
- function folderCount(node) {
56
- return node.dirs.reduce((total, dir) => total + 1 + folderCount(dir), 0);
57
- }
58
- function formatDuration(seconds) {
59
- const rounded = Math.round(seconds);
60
- return `${Math.floor(rounded / 60)}:${String(rounded % 60).padStart(2, "0")}`;
61
- }
62
- function kindLabel(asset) {
63
- if (asset.language) return asset.language;
64
- return asset.mime.split("/").at(-1)?.toUpperCase() ?? asset.kind.toUpperCase();
65
- }
66
- function facetLabel(asset, linesLabel) {
67
- if (asset.lines !== void 0) return linesLabel.replace("{count}", String(asset.lines));
68
- if (asset.kind === "video" && asset.durationSec !== void 0) return formatDuration(asset.durationSec);
69
- if (asset.width !== void 0 && asset.height !== void 0) return `${asset.width} \xD7 ${asset.height}`;
70
- if (asset.durationSec !== void 0) return formatDuration(asset.durationSec);
71
- return null;
72
- }
73
- function iconFor(asset) {
74
- const extension = basename(asset.path).split(".").at(-1)?.toLowerCase();
75
- if (asset.kind === "other" && extension && ARCHIVE_EXTENSIONS.has(extension)) return FileArchive;
76
- const icons = {
77
- code: FileCode,
78
- text: FileText,
79
- image: FileImage,
80
- video: FileVideo,
81
- pdf: FilePdf,
82
- other: FileGeneric
83
- };
84
- return icons[asset.kind];
85
- }
86
- function countLabel(count, plural, single) {
87
- return (count === 1 ? single : plural).replace("{count}", String(count));
88
- }
89
- function AssetTree({ node, base, routePrefix, locale, fileCountLabel, fileCountSingleLabel, linesLabel, root = false }) {
90
- return /* @__PURE__ */ jsxs("ul", { "data-zd-asset-tree": root ? true : void 0, children: [
91
- node.dirs.map((dir) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("details", { open: true, children: [
92
- /* @__PURE__ */ jsxs("summary", { class: "flex cursor-pointer items-center gap-hsp-xs rounded px-hsp-sm py-vsp-3xs text-small hover:bg-accent/10 focus-visible:bg-accent/10", children: [
93
- /* @__PURE__ */ jsx(ChevronRight, { className: "h-icon-xs w-icon-xs shrink-0 text-muted transition-transform" }),
94
- /* @__PURE__ */ jsx(Folder, { className: "h-icon-sm w-icon-sm shrink-0 text-muted" }),
95
- /* @__PURE__ */ jsx(FolderOpen, { className: "h-icon-sm w-icon-sm shrink-0 text-muted" }),
96
- /* @__PURE__ */ jsxs("span", { class: "min-w-0 truncate font-mono text-fg", children: [
97
- dir.name,
98
- "/"
99
- ] }),
100
- /* @__PURE__ */ jsxs("span", { class: "ml-auto hidden whitespace-nowrap pl-hsp-lg text-caption text-muted sm:block", children: [
101
- countLabel(dir.fileCount, fileCountLabel, fileCountSingleLabel),
102
- " \xB7 ",
103
- formatAssetBytes(dir.bytes)
104
- ] })
105
- ] }),
106
- /* @__PURE__ */ jsx(AssetTree, { node: dir, base, routePrefix, locale, fileCountLabel, fileCountSingleLabel, linesLabel })
107
- ] }) })),
108
- node.files.map((asset) => {
109
- const Icon = iconFor(asset);
110
- const facet = facetLabel(asset, linesLabel);
111
- const meta = [kindLabel(asset), facet, formatAssetBytes(asset.bytes)].filter(Boolean).join(" \xB7 ");
112
- return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("a", { href: assetViewerHref({ base, routePrefix, path: asset.path, locale }), title: asset.name, class: "flex min-w-0 items-center gap-hsp-xs rounded px-hsp-sm py-vsp-3xs text-small text-fg hover:bg-accent/10 hover:text-accent hover:underline focus-visible:bg-accent/10 focus-visible:text-accent focus-visible:underline", children: [
113
- /* @__PURE__ */ jsx(Icon, { className: "h-icon-sm w-icon-sm shrink-0 text-muted" }),
114
- /* @__PURE__ */ jsx("span", { class: "min-w-0 truncate font-mono", children: basename(asset.path) }),
115
- /* @__PURE__ */ jsx("span", { class: "ml-auto hidden whitespace-nowrap pl-hsp-lg text-caption text-muted sm:block", children: meta })
116
- ] }) });
117
- })
118
- ] });
119
- }
10
+ import { buildAssetTree } from "./tree.js";
11
+ import {
12
+ AssetIndexPageBody as AssetIndexPageBody2,
13
+ AssetTree,
14
+ iconFor,
15
+ resolveAssetIndexPageLabels as resolveAssetIndexPageLabels2
16
+ } from "./body.js";
120
17
  function createAssetIndexPageView(ctx) {
121
18
  assertChromeContext(ctx, "createAssetIndexPageView");
122
19
  const settings = ctx.settings;
@@ -132,45 +29,17 @@ function createAssetIndexPageView(ctx) {
132
29
  const dir = ctx.assetManifest?.dir ?? settings.assetViewerDir;
133
30
  const indexUrl = ctx.withBase(`/${localeSegment ? `${localeSegment}/` : ""}${routePrefix}/`);
134
31
  const homeUrl = ctx.withBase(`/${localeSegment ? `${localeSegment}/` : ""}`);
135
- const tree = buildAssetTree(entries);
136
- const folders = folderCount(tree);
137
- const fileCountLabel = t("asset.fileCount", locale);
138
- const fileCountSingleLabel = t("asset.fileCountSingle", locale);
139
- const folderCountLabel = t("asset.folderCount", locale);
140
- const folderCountSingleLabel = t("asset.folderCountSingle", locale);
141
- const title = t("asset.crumb", locale);
142
- return /* @__PURE__ */ jsx(DocLayoutWithDefaults, { title: composeMetaTitle(title), head: /* @__PURE__ */ jsx(HeadWithDefaults, { title, description: t("asset.indexDescription", locale), canonical: ctx.absoluteUrl(indexUrl) }), lang: locale, dataThemePack, noindex: settings.noindex, hideSidebar: true, hideToc: true, sidebarOverride: false, contentWide: true, breadcrumbOverride: /* @__PURE__ */ jsx(BreadcrumbWithDefaults, { items: [{ label: "", href: homeUrl }, { label: title }] }), headerOverride: /* @__PURE__ */ jsx(HeaderWithDefaults, { lang: locale, currentPath: indexUrl, hideSidebarToggle: true }), footerOverride: /* @__PURE__ */ jsx(FooterWithDefaults, { lang: locale }), bodyEndComponents: /* @__PURE__ */ jsx(BodyEndIslands, { basePath: settings.base ?? "/" }), enableClientRouter: settings.dynamicPageTransition, children: /* @__PURE__ */ jsxs("div", { "data-zd-asset-index-page": true, children: [
143
- /* @__PURE__ */ jsxs("header", { children: [
144
- /* @__PURE__ */ jsxs("div", { class: "mb-vsp-xs flex flex-wrap items-center gap-hsp-xs text-micro tracking-wide uppercase", children: [
145
- /* @__PURE__ */ jsx("span", { class: "rounded-full border border-muted px-hsp-sm py-vsp-3xs text-fg", children: title }),
146
- /* @__PURE__ */ jsx("span", { class: "rounded-full border border-muted px-hsp-sm py-vsp-3xs text-muted", children: t("asset.indexBadge", locale) })
147
- ] }),
148
- /* @__PURE__ */ jsx("h1", { class: "mb-vsp-xs border-b border-fg pb-vsp-xs font-mono text-heading font-bold leading-tight break-words", children: title }),
149
- /* @__PURE__ */ jsxs("div", { "data-doc-metainfo": true, class: "mb-vsp-md flex flex-wrap items-center gap-x-hsp-md gap-y-vsp-2xs text-caption text-fg", children: [
150
- /* @__PURE__ */ jsx("span", { children: countLabel(tree.fileCount, fileCountLabel, fileCountSingleLabel) }),
151
- /* @__PURE__ */ jsx("span", { children: countLabel(folders, folderCountLabel, folderCountSingleLabel) }),
152
- /* @__PURE__ */ jsx("span", { children: formatAssetBytes(tree.bytes) })
153
- ] }),
154
- /* @__PURE__ */ jsx("p", { class: "mb-vsp-lg text-title text-muted", children: t("asset.indexDescription", locale) })
155
- ] }),
156
- /* @__PURE__ */ jsxs("div", { class: "mb-vsp-sm flex flex-wrap items-center justify-between gap-hsp-sm text-caption", children: [
157
- /* @__PURE__ */ jsxs("span", { class: "font-mono text-muted", children: [
158
- "public/",
159
- dir,
160
- "/"
161
- ] }),
162
- /* @__PURE__ */ jsxs("span", { class: "flex gap-hsp-sm", children: [
163
- /* @__PURE__ */ jsx("button", { type: "button", disabled: true, "data-zd-asset-index-action": "expand", class: "text-fg hover:text-accent focus-visible:text-accent", children: t("asset.expandAll", locale) }),
164
- /* @__PURE__ */ jsx("button", { type: "button", disabled: true, "data-zd-asset-index-action": "collapse", class: "text-fg hover:text-accent focus-visible:text-accent", children: t("asset.collapseAll", locale) })
165
- ] })
166
- ] }),
167
- entries.length > 0 ? /* @__PURE__ */ jsx(AssetTree, { root: true, node: tree, base: settings.base, routePrefix, locale: localeSegment, fileCountLabel, fileCountSingleLabel, linesLabel: t("asset.lines", locale) }) : /* @__PURE__ */ jsx("p", { class: "text-small text-muted", "data-zd-asset-index-empty": true, children: t("asset.indexEmpty", locale) }),
168
- /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: ASSET_INDEX_PAGE_SCRIPT } })
169
- ] }) });
32
+ const labels = resolveAssetIndexPageLabels(t, locale);
33
+ const title = labels.crumb;
34
+ return /* @__PURE__ */ jsx(DocLayoutWithDefaults, { title: composeMetaTitle(title), head: /* @__PURE__ */ jsx(HeadWithDefaults, { title, description: labels.indexDescription, canonical: ctx.absoluteUrl(indexUrl) }), lang: locale, dataThemePack, noindex: settings.noindex, hideSidebar: true, hideToc: true, sidebarOverride: false, contentWide: true, breadcrumbOverride: /* @__PURE__ */ jsx(BreadcrumbWithDefaults, { items: [{ label: "", href: homeUrl }, { label: title }] }), headerOverride: /* @__PURE__ */ jsx(HeaderWithDefaults, { lang: locale, currentPath: indexUrl, hideSidebarToggle: true }), footerOverride: /* @__PURE__ */ jsx(FooterWithDefaults, { lang: locale }), bodyEndComponents: /* @__PURE__ */ jsx(BodyEndIslands, { basePath: settings.base ?? "/" }), enableClientRouter: settings.dynamicPageTransition, children: /* @__PURE__ */ jsx(AssetIndexPageBody, { entries, labels, base: settings.base, routePrefix, dir, locale: localeSegment }) });
170
35
  };
171
36
  }
172
37
  export {
173
- ASSET_INDEX_PAGE_SCRIPT2 as ASSET_INDEX_PAGE_SCRIPT,
38
+ ASSET_INDEX_PAGE_SCRIPT,
39
+ AssetIndexPageBody2 as AssetIndexPageBody,
40
+ AssetTree,
174
41
  buildAssetTree,
175
- createAssetIndexPageView
42
+ createAssetIndexPageView,
43
+ iconFor,
44
+ resolveAssetIndexPageLabels2 as resolveAssetIndexPageLabels
176
45
  };
@@ -0,0 +1,24 @@
1
+ import type { AssetIndexEntry } from "../route-context-payload/types.js";
2
+ export interface AssetTreeNode {
3
+ name: string;
4
+ dirs: AssetTreeNode[];
5
+ files: AssetIndexEntry[];
6
+ fileCount: number;
7
+ bytes: number;
8
+ }
9
+ interface MutableAssetTreeNode {
10
+ name: string;
11
+ dirs: Map<string, MutableAssetTreeNode>;
12
+ files: AssetIndexEntry[];
13
+ }
14
+ /** Last path segment (basename) of an asset's path, used for sorting and display. */
15
+ export declare function basename(path: string): string;
16
+ /** Freeze a mutable build-time node into the sorted, aggregated `AssetTreeNode` shape. */
17
+ export declare function freezeTree(node: MutableAssetTreeNode): AssetTreeNode;
18
+ /** Build a sorted directory tree and aggregate every directory's subtree totals. */
19
+ export declare function buildAssetTree(entries: AssetIndexEntry[]): AssetTreeNode;
20
+ /** Total number of directories in the subtree rooted at `node`, excluding the root itself. */
21
+ export declare function folderCount(node: AssetTreeNode): number;
22
+ /** Pick the singular/plural label variant for `count` and substitute its `{count}` placeholder. */
23
+ export declare function countLabel(count: number, plural: string, single: string): string;
24
+ export {};
@@ -0,0 +1,43 @@
1
+ function basename(path) {
2
+ return path.split("/").at(-1) ?? path;
3
+ }
4
+ function freezeTree(node) {
5
+ const dirs = [...node.dirs.values()].sort((a, b) => a.name.localeCompare(b.name)).map(freezeTree);
6
+ const files = [...node.files].sort((a, b) => basename(a.path).localeCompare(basename(b.path)));
7
+ return {
8
+ name: node.name,
9
+ dirs,
10
+ files,
11
+ fileCount: files.length + dirs.reduce((total, dir) => total + dir.fileCount, 0),
12
+ bytes: files.reduce((total, file) => total + file.bytes, 0) + dirs.reduce((total, dir) => total + dir.bytes, 0)
13
+ };
14
+ }
15
+ function buildAssetTree(entries) {
16
+ const root = { name: "", dirs: /* @__PURE__ */ new Map(), files: [] };
17
+ for (const entry of entries) {
18
+ let node = root;
19
+ for (const segment of entry.dir.split("/").filter(Boolean)) {
20
+ let child = node.dirs.get(segment);
21
+ if (!child) {
22
+ child = { name: segment, dirs: /* @__PURE__ */ new Map(), files: [] };
23
+ node.dirs.set(segment, child);
24
+ }
25
+ node = child;
26
+ }
27
+ node.files.push(entry);
28
+ }
29
+ return freezeTree(root);
30
+ }
31
+ function folderCount(node) {
32
+ return node.dirs.reduce((total, dir) => total + 1 + folderCount(dir), 0);
33
+ }
34
+ function countLabel(count, plural, single) {
35
+ return (count === 1 ? single : plural).replace("{count}", String(count));
36
+ }
37
+ export {
38
+ basename,
39
+ buildAssetTree,
40
+ countLabel,
41
+ folderCount,
42
+ freezeTree
43
+ };