@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.
- package/CHANGELOG.md +40 -2
- package/dist/asset-components/asset-card.js +2 -2
- package/dist/asset-components/asset-code.js +1 -1
- package/dist/asset-index-page/body.d.ts +80 -0
- package/dist/asset-index-page/body.js +116 -0
- package/dist/asset-index-page/index.d.ts +3 -9
- package/dist/asset-index-page/index.js +18 -149
- package/dist/asset-index-page/tree.d.ts +24 -0
- package/dist/asset-index-page/tree.js +43 -0
- package/dist/asset-page/body.d.ts +98 -0
- package/dist/asset-page/body.js +107 -0
- package/dist/asset-page/components.d.ts +94 -0
- package/dist/asset-page/components.js +175 -0
- package/dist/asset-page/index.d.ts +3 -78
- package/dist/asset-page/index.js +30 -240
- package/dist/asset-page/shared.d.ts +15 -0
- package/dist/asset-page/shared.js +28 -0
- package/dist/breadcrumb/breadcrumb.js +1 -1
- package/dist/compiled.css +18 -71
- package/dist/config/component-tokens.js +3 -1
- package/dist/config-assertions/index.d.ts +26 -0
- package/dist/config-assertions/index.js +12 -0
- package/dist/config.d.ts +28 -0
- package/dist/config.js +6 -1
- package/dist/doc-pager/index.js +2 -2
- package/dist/footer/footer.js +3 -3
- package/dist/header/header.js +2 -1
- package/dist/header-with-defaults/index.d.ts +20 -0
- package/dist/header-with-defaults/index.js +9 -2
- package/dist/home-page/index.js +49 -22
- package/dist/i18n-defaults/index.js +3 -0
- package/dist/integrations/changelog/generate.js +8 -2
- package/dist/integrations/changelog/links.d.ts +25 -0
- package/dist/integrations/changelog/links.js +280 -0
- package/dist/mdx-components/index.js +1 -1
- package/dist/nav-indexing/category-nav.js +1 -1
- package/dist/nav-indexing/category-tree-nav.js +1 -1
- package/dist/nav-indexing/doc-card-grid.js +2 -2
- package/dist/nav-indexing/docs-sitemap.js +2 -2
- package/dist/nav-indexing/nav-card-grid.d.ts +3 -2
- package/dist/nav-indexing/nav-card-grid.js +3 -3
- package/dist/nav-indexing/note-tray-index-parts/card-list.js +1 -1
- package/dist/nav-indexing/note-tray-index-parts/index-list.js +1 -1
- package/dist/nav-indexing/note-tray-index-parts/timeline.js +1 -1
- package/dist/nav-indexing/site-tree-nav-demo.js +2 -2
- package/dist/nav-indexing/versions-page-content.js +2 -2
- package/dist/plugins/routes.js +2 -13
- package/dist/plugins/zdtp-loader.js +1 -1
- package/dist/preset.d.ts +11 -1
- package/dist/preset.js +12 -5
- package/dist/safelist.css +1 -1
- package/dist/settings.d.ts +9 -0
- package/dist/sidebar-toggle-island/index.js +1 -1
- package/dist/site-tree-nav-island/index.js +3 -3
- package/dist/theme-packs/foundry/pack.css +4 -9
- package/dist/theme-packs/riso/pack.css +6 -4
- package/dist/theme-packs/swissgrid/pack.css +7 -4
- package/eject/asset-components/asset-card.tsx +2 -2
- package/eject/asset-components/asset-code.tsx +1 -1
- package/eject/breadcrumb/breadcrumb.tsx +1 -1
- package/eject/doc-pager/index.tsx +2 -2
- package/eject/footer/footer.tsx +4 -4
- package/eject/header/header.tsx +11 -1
- package/eject/sidebar-toggle-island/index.tsx +1 -1
- package/eject/site-tree-nav-island/index.tsx +4 -4
- 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
|
-
-
|
|
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
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
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 {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
136
|
-
const
|
|
137
|
-
|
|
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
|
-
|
|
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
|
+
};
|