@takazudo/zudo-doc 5.13.0 → 5.14.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 (110) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/bin/run-parallel.mjs +272 -0
  3. package/bin/tags-suggest-runner.ts +7 -3
  4. package/dist/asset-components/asset-card.d.ts +10 -0
  5. package/dist/asset-components/asset-card.js +91 -0
  6. package/dist/asset-components/asset-code.d.ts +10 -0
  7. package/dist/asset-components/asset-code.js +93 -0
  8. package/dist/asset-components/index.d.ts +3 -0
  9. package/dist/asset-components/index.js +12 -0
  10. package/dist/asset-components/shared.d.ts +25 -0
  11. package/dist/asset-components/shared.js +89 -0
  12. package/dist/asset-index-page/index.d.ts +21 -0
  13. package/dist/asset-index-page/index.js +175 -0
  14. package/dist/asset-index-page/script.d.ts +2 -0
  15. package/dist/asset-index-page/script.js +4 -0
  16. package/dist/asset-page/index.d.ts +67 -0
  17. package/dist/asset-page/index.js +244 -0
  18. package/dist/asset-page/script.d.ts +2 -0
  19. package/dist/asset-page/script.js +4 -0
  20. package/dist/asset-path/index.d.ts +53 -0
  21. package/dist/asset-path/index.js +149 -0
  22. package/dist/chrome/derive.js +45 -9
  23. package/dist/chrome/index.d.ts +2 -0
  24. package/dist/chrome/index.js +7 -1
  25. package/dist/chrome-bindings.d.ts +4 -0
  26. package/dist/client-router/raw-link-attrs.d.ts +8 -0
  27. package/dist/client-router/raw-link-attrs.js +6 -0
  28. package/dist/compiled.css +135 -0
  29. package/dist/config.d.ts +13 -2
  30. package/dist/config.js +36 -1
  31. package/dist/content/content-link.d.ts +12 -0
  32. package/dist/content/content-link.js +43 -2
  33. package/dist/content/index.d.ts +1 -1
  34. package/dist/content/index.js +5 -1
  35. package/dist/doc-body-end-islands/index.d.ts +2 -0
  36. package/dist/doc-body-end-islands/index.js +3 -2
  37. package/dist/doclayout/doc-layout-with-defaults.d.ts +1 -1
  38. package/dist/doclayout/doc-layout-with-defaults.js +1 -1
  39. package/dist/eject/index.js +8 -0
  40. package/dist/factory-context/index.d.ts +3 -1
  41. package/dist/features.css +124 -3
  42. package/dist/frontmatter/index.d.ts +56 -0
  43. package/dist/frontmatter/index.js +61 -0
  44. package/dist/header/header.js +11 -1
  45. package/dist/header/nav-class-tokens.d.ts +2 -0
  46. package/dist/header/nav-class-tokens.js +8 -0
  47. package/dist/header/nav-overflow-generated-script.js +47 -2
  48. package/dist/header-with-defaults/index.d.ts +2 -0
  49. package/dist/header-with-defaults/index.js +3 -2
  50. package/dist/i18n-defaults/index.d.ts +3 -2
  51. package/dist/i18n-defaults/index.js +44 -0
  52. package/dist/icons/index.d.ts +9 -0
  53. package/dist/icons/index.js +246 -0
  54. package/dist/md-utils/index.js +1 -1
  55. package/dist/mdx-components/index.d.ts +4 -1
  56. package/dist/mdx-components/index.js +80 -7
  57. package/dist/plugins/internal/asset-viewer/build.d.ts +25 -0
  58. package/dist/plugins/internal/asset-viewer/build.js +118 -0
  59. package/dist/plugins/internal/asset-viewer/git-meta.d.ts +7 -0
  60. package/dist/plugins/internal/asset-viewer/git-meta.js +40 -0
  61. package/dist/plugins/internal/asset-viewer/highlight.d.ts +34 -0
  62. package/dist/plugins/internal/asset-viewer/highlight.js +181 -0
  63. package/dist/plugins/internal/asset-viewer/link-graph.d.ts +34 -0
  64. package/dist/plugins/internal/asset-viewer/link-graph.js +193 -0
  65. package/dist/plugins/internal/asset-viewer/probe.d.ts +13 -0
  66. package/dist/plugins/internal/asset-viewer/probe.js +322 -0
  67. package/dist/plugins/internal/asset-viewer/scan.d.ts +2 -0
  68. package/dist/plugins/internal/asset-viewer/scan.js +77 -0
  69. package/dist/plugins/internal/asset-viewer/sidecar.d.ts +6 -0
  70. package/dist/plugins/internal/asset-viewer/sidecar.js +34 -0
  71. package/dist/plugins/internal/asset-viewer/types.d.ts +22 -0
  72. package/dist/plugins/internal/asset-viewer/types.js +0 -0
  73. package/dist/plugins/internal/codex-resources/skills.js +1 -1
  74. package/dist/plugins/internal/llms-txt/load.d.ts +1 -1
  75. package/dist/plugins/internal/resource-docs-shared/mdx.d.ts +1 -2
  76. package/dist/plugins/internal/resource-docs-shared/mdx.js +1 -1
  77. package/dist/plugins/routes.js +146 -11
  78. package/dist/preset.d.ts +10 -0
  79. package/dist/preset.js +20 -3
  80. package/dist/route-context/index.js +18 -2
  81. package/dist/route-context-payload/index.d.ts +6 -2
  82. package/dist/route-context-payload/index.js +2 -1
  83. package/dist/route-context-payload/types.d.ts +37 -0
  84. package/dist/routes/_chrome.d.ts +1 -1
  85. package/dist/routes/_chrome.js +5 -1
  86. package/dist/routes/_context.d.ts +2 -0
  87. package/dist/routes/_context.js +2 -0
  88. package/dist/routes/files-path.d.ts +22 -0
  89. package/dist/routes/files-path.js +31 -0
  90. package/dist/safelist.css +1 -1
  91. package/dist/settings.d.ts +10 -0
  92. package/dist/sidebar-tree-island/index.js +2 -2
  93. package/dist/site-tree-nav-island/index.js +3 -3
  94. package/dist/tags-audit.js +1 -1
  95. package/eject/asset-components/asset-card.tsx +106 -0
  96. package/eject/asset-components/asset-code.tsx +127 -0
  97. package/eject/asset-components/index.ts +7 -0
  98. package/eject/asset-components/shared.tsx +113 -0
  99. package/eject/header/gen-nav-overflow-script.mjs +53 -4
  100. package/eject/header/header.tsx +11 -1
  101. package/eject/header/nav-class-tokens.ts +12 -0
  102. package/eject/header/nav-overflow-generated-script.ts +1 -1
  103. package/eject/sidebar-tree-island/index.tsx +3 -3
  104. package/eject/site-tree-nav-island/index.tsx +4 -4
  105. package/package.json +34 -14
  106. package/routes-src/_chrome.tsx +2 -0
  107. package/routes-src/_context.ts +2 -0
  108. package/routes-src/_virtual.d.ts +6 -0
  109. package/routes-src/files-path.tsx +43 -0
  110. package/virtual-modules.d.ts +6 -0
@@ -0,0 +1,149 @@
1
+ const CONTROL_CHARS = /[\u0000-\u001f\u007f-\u009f]/;
2
+ function normalizeAssetPath(path) {
3
+ if (path.length === 0) throw new Error("Asset path must not be empty");
4
+ if (path.startsWith("/")) throw new Error(`Asset path must be relative: ${path}`);
5
+ if (path.includes("\\")) throw new Error(`Asset path must use POSIX separators: ${path}`);
6
+ if (CONTROL_CHARS.test(path)) throw new Error("Asset path must not contain control characters");
7
+ const normalized = path.normalize("NFC");
8
+ const segments = normalized.split("/");
9
+ if (segments.some((segment) => segment.length === 0)) {
10
+ throw new Error(`Asset path must not contain empty segments: ${path}`);
11
+ }
12
+ if (segments.some((segment) => segment === "." || segment === "..")) {
13
+ throw new Error(`Asset path must not contain dot segments: ${path}`);
14
+ }
15
+ return normalized;
16
+ }
17
+ function encodeAssetPathForUrl(path) {
18
+ return normalizeAssetPath(path).split("/").map(encodeURIComponent).join("/");
19
+ }
20
+ function normalizedBase(base) {
21
+ if (base === "" || base === "/") return "";
22
+ const withLeadingSlash = base.startsWith("/") ? base : `/${base}`;
23
+ return withLeadingSlash.replace(/\/+$/, "");
24
+ }
25
+ function withBase(base, path) {
26
+ const prefix = normalizedBase(base);
27
+ return prefix + (path.startsWith("/") ? path : `/${path}`);
28
+ }
29
+ function decodeAuthoredHref(href, options) {
30
+ if (CONTROL_CHARS.test(href)) return null;
31
+ const hashIndex = href.indexOf("#");
32
+ const pathAndQuery = hashIndex === -1 ? href : href.slice(0, hashIndex);
33
+ const fragment = hashIndex === -1 ? void 0 : href.slice(hashIndex);
34
+ if (pathAndQuery.includes("?")) return null;
35
+ let dir;
36
+ try {
37
+ dir = normalizeSettingPath(options.dir, "dir");
38
+ } catch {
39
+ return null;
40
+ }
41
+ const prefix = `/${dir}/`;
42
+ const base = normalizedBase(options.base);
43
+ const candidates = [pathAndQuery];
44
+ if (base !== "" && pathAndQuery.startsWith(`${base}/`)) {
45
+ candidates.push(pathAndQuery.slice(base.length));
46
+ }
47
+ const pathname = candidates.find((candidate) => candidate.startsWith(prefix));
48
+ if (pathname === void 0) return null;
49
+ const encodedPath = pathname.slice(prefix.length);
50
+ if (encodedPath.length === 0) return null;
51
+ try {
52
+ const decodedSegments = encodedPath.split("/").map((segment) => decodeURIComponent(segment));
53
+ if (decodedSegments.some((segment) => segment.includes("/"))) return null;
54
+ const path = normalizeAssetPath(decodedSegments.join("/"));
55
+ return fragment === void 0 ? { path } : { path, fragment };
56
+ } catch {
57
+ return null;
58
+ }
59
+ }
60
+ function assetViewerHref(options) {
61
+ const routePrefix = normalizeSettingPath(options.routePrefix, "routePrefix");
62
+ const href = withBase(
63
+ options.base,
64
+ `/${routePrefix}/${encodeAssetPathForUrl(options.path)}/`
65
+ );
66
+ if (options.fragment == null || options.fragment === "") return href;
67
+ if (CONTROL_CHARS.test(options.fragment)) {
68
+ throw new Error("Asset URL fragment must not contain control characters");
69
+ }
70
+ return href + (options.fragment.startsWith("#") ? options.fragment : `#${options.fragment}`);
71
+ }
72
+ function assetRawHref(options) {
73
+ const dir = normalizeSettingPath(options.dir, "dir");
74
+ return withBase(options.base, `/${dir}/${encodeAssetPathForUrl(options.path)}`);
75
+ }
76
+ function normalizeSettingPath(value, label) {
77
+ if (value.length === 0) throw new Error(`assetViewer.${label} must not be empty`);
78
+ if (value.startsWith("/") || value.endsWith("/")) {
79
+ throw new Error(`assetViewer.${label} must be relative with no leading or trailing slash`);
80
+ }
81
+ if (value.includes("\\") || CONTROL_CHARS.test(value)) {
82
+ throw new Error(`assetViewer.${label} must be a safe POSIX path`);
83
+ }
84
+ const normalized = value.normalize("NFC");
85
+ const segments = normalized.split("/");
86
+ if (segments.some((segment) => segment === "" || segment === "." || segment === "..")) {
87
+ throw new Error(`assetViewer.${label} must not contain empty or dot segments`);
88
+ }
89
+ return normalized;
90
+ }
91
+ function validateAssetViewerSettings(settings) {
92
+ const dir = normalizeSettingPath(settings.dir, "dir");
93
+ const routePrefix = normalizeSettingPath(settings.routePrefix, "routePrefix");
94
+ if (dir === routePrefix) {
95
+ throw new Error("assetViewer.dir and assetViewer.routePrefix must be different");
96
+ }
97
+ if (dir === "assets/client" || routePrefix === "assets/client") {
98
+ throw new Error('assetViewer.dir and assetViewer.routePrefix must not equal reserved "assets/client"');
99
+ }
100
+ }
101
+ function escapeRegex(char) {
102
+ return /[\\^$.*+?()[\]{}|]/.test(char) ? `\\${char}` : char;
103
+ }
104
+ function globToRegex(glob) {
105
+ let source = "^";
106
+ for (let index = 0; index < glob.length; index += 1) {
107
+ const char = glob[index] ?? "";
108
+ if (char === "*") {
109
+ if (glob[index + 1] === "*") {
110
+ index += 1;
111
+ if (glob[index + 1] === "/") {
112
+ index += 1;
113
+ source += "(?:.*/)?";
114
+ } else {
115
+ source += ".*";
116
+ }
117
+ } else {
118
+ source += "[^/]*";
119
+ }
120
+ } else if (char === "?") {
121
+ source += "[^/]";
122
+ } else if (char === "{") {
123
+ const close = glob.indexOf("}", index + 1);
124
+ if (close === -1) {
125
+ source += "\\{";
126
+ } else {
127
+ const alternatives = glob.slice(index + 1, close).split(",");
128
+ source += `(?:${alternatives.map((value) => [...value].map(escapeRegex).join("")).join("|")})`;
129
+ index = close;
130
+ }
131
+ } else {
132
+ source += escapeRegex(char);
133
+ }
134
+ }
135
+ return new RegExp(`${source}$`);
136
+ }
137
+ function matchExclude(path, globs) {
138
+ const normalized = normalizeAssetPath(path);
139
+ return globs.some((glob) => globToRegex(glob.normalize("NFC")).test(normalized));
140
+ }
141
+ export {
142
+ assetRawHref,
143
+ assetViewerHref,
144
+ decodeAuthoredHref,
145
+ encodeAssetPathForUrl,
146
+ matchExclude,
147
+ normalizeAssetPath,
148
+ validateAssetViewerSettings
149
+ };
@@ -13,6 +13,7 @@ import { createThemePackSwitcherIsland } from "../doc-body-end-islands/theme-pac
13
13
  import { DEFAULT_THEME_PACK_SLUG } from "../theme-pack-switcher/theme-pack-sync.js";
14
14
  import { SearchWidget } from "../search-widget/index.js";
15
15
  import { createMdxComponents } from "../mdx-components/index.js";
16
+ import { createAssetCard, createAssetCode } from "../asset-components/index.js";
16
17
  import { createCategoryNavWrapper } from "../category-nav/index.js";
17
18
  import { createNoteTrayIndexWrapper } from "../note-tray-index/index.js";
18
19
  import { createCategoryTreeNavWrapper } from "../category-tree-nav/index.js";
@@ -200,8 +201,27 @@ function deriveBodyEndIslands(ctx) {
200
201
  });
201
202
  const HostBodyEnd = HostBodyEndIslands;
202
203
  function BodyEndIslands(props) {
204
+ const addsForcedImageEnlarge = props.forceImageEnlarge === true && !ctx.settings.imageEnlarge;
205
+ const ForceImageEnlarge = addsForcedImageEnlarge ? createBodyEndIslands({
206
+ settings: {
207
+ aiAssistant: false,
208
+ imageEnlarge: true,
209
+ mermaid: false,
210
+ dynamicPageTransition: false,
211
+ designTokenPanel: false,
212
+ findInPage: false,
213
+ themePackSwitcher: false
214
+ }
215
+ }) : null;
203
216
  return /* @__PURE__ */ jsxs(Fragment, { children: [
204
- /* @__PURE__ */ jsx(HostBodyEnd, { ...props }),
217
+ /* @__PURE__ */ jsx(
218
+ HostBodyEnd,
219
+ {
220
+ ...props,
221
+ forceImageEnlarge: addsForcedImageEnlarge ? false : props.forceImageEnlarge
222
+ }
223
+ ),
224
+ ForceImageEnlarge ? /* @__PURE__ */ jsx(ForceImageEnlarge, { basePath: props.basePath }) : null,
205
225
  /* @__PURE__ */ jsx(DesignTokenPanelIsland, {}),
206
226
  /* @__PURE__ */ jsx(ThemePackSwitcherIsland, {})
207
227
  ] });
@@ -300,18 +320,34 @@ function deriveMdxComponents(ctx) {
300
320
  ...props
301
321
  });
302
322
  }
303
- const mdxExtrasDefault = {
304
- Details,
305
- HtmlPreview: HtmlPreviewBound,
306
- Island: IslandPassthrough,
307
- // PresetGenerator stays a package stub (render nothing): it is the
308
- // showcase's project-bound interactive island; downstream projects stub it.
309
- PresetGenerator: (_props) => null
323
+ const assetComponentContext = {
324
+ base: ctx.settings.base,
325
+ assetManifest: ctx.assetManifest,
326
+ routePrefix: ctx.assetManifest?.routePrefix ?? ctx.settings.assetViewerRoutePrefix,
327
+ dir: ctx.assetManifest?.dir ?? ctx.settings.assetViewerDir
310
328
  };
311
- const mdxExtras = { ...mdxExtrasDefault, ...ctx.hostBindings.mdxExtras ?? {} };
312
329
  function createMdxComponentsBound(lang = ctx.defaultLocale, currentVersion, currentSlug = "") {
330
+ const localizedAssetContext = {
331
+ ...assetComponentContext,
332
+ t: (key) => ctx.t(key, lang)
333
+ };
334
+ const mdxExtrasDefault = {
335
+ Asset: createAssetCard(localizedAssetContext),
336
+ AssetCode: createAssetCode(localizedAssetContext),
337
+ Details,
338
+ HtmlPreview: HtmlPreviewBound,
339
+ Island: IslandPassthrough,
340
+ // PresetGenerator stays a package stub (render nothing): it is the
341
+ // showcase's project-bound interactive island; downstream projects stub it.
342
+ PresetGenerator: (_props) => null
343
+ };
344
+ const mdxExtras = {
345
+ ...mdxExtrasDefault,
346
+ ...ctx.hostBindings.mdxExtras ?? {}
347
+ };
313
348
  return createMdxComponents({
314
349
  settings: ctx.settings,
350
+ assetManifest: ctx.assetManifest,
315
351
  locale: lang,
316
352
  currentVersion,
317
353
  currentSlug,
@@ -36,6 +36,8 @@ export declare function createChrome<S extends Settings = Settings>(context: Rou
36
36
  SiteTreeNavWrapper: import("../factory-context/index.js").FactoryComponent;
37
37
  BodyEndIslands: (props: import("../doc-body-end-islands/index.js").BodyEndIslandsProps) => JSX.Element;
38
38
  HomePageView: (props: import("../home-page/index.js").HomePageViewProps) => JSX.Element;
39
+ AssetPageView: (props: import("../asset-page/index.js").AssetPageViewProps) => JSX.Element;
40
+ AssetIndexPageView: (props: import("../asset-index-page/index.js").AssetIndexPageViewProps) => JSX.Element;
39
41
  };
40
42
  /** The wired page-chrome surface returned by {@link createChrome}. */
41
43
  export type Chrome = ReturnType<typeof createChrome>;
@@ -4,6 +4,8 @@ import { createRenderDocPage } from "../doc-page-renderer/index.js";
4
4
  import { createVersionsPageView } from "../versions-page/index.js";
5
5
  import { createTagPages } from "../tag-pages/index.js";
6
6
  import { createHomePageView } from "../home-page/index.js";
7
+ import { createAssetPageView } from "../asset-page/index.js";
8
+ import { createAssetIndexPageView } from "../asset-index-page/index.js";
7
9
  import {
8
10
  deriveComposeMetaTitle,
9
11
  deriveBodyEndIslands,
@@ -31,6 +33,8 @@ function createChrome(context, hostBindings = {}) {
31
33
  const { SiteTreeNavWrapper } = deriveMdxComponents(ctx);
32
34
  const BodyEndIslands = deriveBodyEndIslands(ctx);
33
35
  const HomePageView = createHomePageView(ctx);
36
+ const AssetPageView = createAssetPageView(ctx);
37
+ const AssetIndexPageView = createAssetIndexPageView(ctx);
34
38
  return {
35
39
  composeMetaTitle,
36
40
  HeadWithDefaults,
@@ -44,7 +48,9 @@ function createChrome(context, hostBindings = {}) {
44
48
  TagsIndexPageView,
45
49
  SiteTreeNavWrapper,
46
50
  BodyEndIslands,
47
- HomePageView
51
+ HomePageView,
52
+ AssetPageView,
53
+ AssetIndexPageView
48
54
  };
49
55
  }
50
56
  export {
@@ -29,6 +29,8 @@ export interface SearchWidgetSlotProps {
29
29
  export interface BodyEndIslandsSlotProps {
30
30
  basePath: string;
31
31
  aiChatBodyLabel?: string;
32
+ /** Asset image pages force one ImageEnlarge mount independently of the site setting. */
33
+ forceImageEnlarge?: boolean;
32
34
  }
33
35
  /**
34
36
  * Props the chrome passes to the `DocHistory` island slot — matches
@@ -83,6 +85,8 @@ export interface HeaderSlotProps {
83
85
  currentVersion?: string;
84
86
  currentSlug?: string;
85
87
  navSection?: string;
88
+ /** Omit the mobile sidebar toggle and its drawer landmark. */
89
+ hideSidebarToggle?: boolean;
86
90
  }
87
91
  /** Props supplied at every package `Footer` replacement call site. */
88
92
  export interface FooterSlotProps {
@@ -0,0 +1,8 @@
1
+ /** Attributes that make a raw asset link bypass zfb's client router. */
2
+ export declare const RAW_LINK_ATTRS: {
3
+ readonly download: "";
4
+ };
5
+ /** Attributes that make a link perform a full browser reload. */
6
+ export declare const RELOAD_LINK_ATTRS: {
7
+ readonly "data-zfb-reload": "";
8
+ };
@@ -0,0 +1,6 @@
1
+ const RAW_LINK_ATTRS = { download: "" };
2
+ const RELOAD_LINK_ATTRS = { "data-zfb-reload": "" };
3
+ export {
4
+ RAW_LINK_ATTRS,
5
+ RELOAD_LINK_ATTRS
6
+ };
package/dist/compiled.css CHANGED
@@ -610,6 +610,9 @@
610
610
  .h-\[14px\] {
611
611
  height: 14px;
612
612
  }
613
+ .h-\[70vh\] {
614
+ height: 70vh;
615
+ }
613
616
  .h-\[90vh\] {
614
617
  height: 90vh;
615
618
  }
@@ -652,6 +655,9 @@
652
655
  .min-h-0 {
653
656
  min-height: var(--spacing-0);
654
657
  }
658
+ .min-h-\[20rem\] {
659
+ min-height: 20rem;
660
+ }
655
661
  .min-h-\[60vh\] {
656
662
  min-height: 60vh;
657
663
  }
@@ -935,6 +941,13 @@
935
941
  margin-block-end: calc(var(--spacing-vsp-lg) * calc(1 - var(--tw-space-y-reverse)));
936
942
  }
937
943
  }
944
+ .space-y-vsp-sm {
945
+ :where(& > :not(:last-child)) {
946
+ --tw-space-y-reverse: 0;
947
+ margin-block-start: calc(var(--spacing-vsp-sm) * var(--tw-space-y-reverse));
948
+ margin-block-end: calc(var(--spacing-vsp-sm) * calc(1 - var(--tw-space-y-reverse)));
949
+ }
950
+ }
938
951
  .gap-x-hsp-2xs {
939
952
  column-gap: var(--spacing-hsp-2xs);
940
953
  }
@@ -1208,6 +1221,9 @@
1208
1221
  .p-hsp-sm {
1209
1222
  padding: var(--spacing-hsp-sm);
1210
1223
  }
1224
+ .p-hsp-xl {
1225
+ padding: var(--spacing-hsp-xl);
1226
+ }
1211
1227
  .px-hsp-2xl {
1212
1228
  padding-inline: var(--spacing-hsp-2xl);
1213
1229
  }
@@ -1307,6 +1323,9 @@
1307
1323
  .pr-hsp-xl {
1308
1324
  padding-right: var(--spacing-hsp-xl);
1309
1325
  }
1326
+ .pr-hsp-xs {
1327
+ padding-right: var(--spacing-hsp-xs);
1328
+ }
1310
1329
  .pb-\[50vh\] {
1311
1330
  padding-bottom: 50vh;
1312
1331
  }
@@ -1904,6 +1923,14 @@
1904
1923
  border-color: var(--color-accent);
1905
1924
  }
1906
1925
  }
1926
+ .focus-visible\:bg-accent\/10 {
1927
+ &:focus-visible {
1928
+ background-color: var(--color-accent);
1929
+ @supports (color: color-mix(in lab, red, red)) {
1930
+ background-color: color-mix(in oklab, var(--color-accent) 10%, transparent);
1931
+ }
1932
+ }
1933
+ }
1907
1934
  .focus-visible\:text-accent {
1908
1935
  &:focus-visible {
1909
1936
  color: var(--color-accent);
@@ -1975,6 +2002,11 @@
1975
2002
  margin-right: var(--spacing-0);
1976
2003
  }
1977
2004
  }
2005
+ .sm\:block {
2006
+ @media (width >= 640px) {
2007
+ display: block;
2008
+ }
2009
+ }
1978
2010
  .sm\:flex {
1979
2011
  @media (width >= 640px) {
1980
2012
  display: flex;
@@ -2168,6 +2200,11 @@
2168
2200
  padding-top: var(--spacing-vsp-2xl);
2169
2201
  }
2170
2202
  }
2203
+ .lg\:pr-hsp-sm {
2204
+ @media (width >= 1024px) {
2205
+ padding-right: var(--spacing-hsp-sm);
2206
+ }
2207
+ }
2171
2208
  .lg\:text-left {
2172
2209
  @media (width >= 1024px) {
2173
2210
  text-align: left;
@@ -3362,6 +3399,104 @@ button[data-zd-pending] {
3362
3399
  opacity: 0.7;
3363
3400
  pointer-events: none;
3364
3401
  }
3402
+ .zd-asset-page .zd-asset-code {
3403
+ counter-reset: line;
3404
+ }
3405
+ .zd-asset-page .zd-asset-code .line {
3406
+ display: block;
3407
+ width: 100%;
3408
+ }
3409
+ .zd-asset-page .zd-asset-code .line::before {
3410
+ counter-increment: line;
3411
+ content: counter(line);
3412
+ display: inline-block;
3413
+ box-sizing: border-box;
3414
+ width: var(--zd-asset-gutter, 4ch);
3415
+ padding-right: var(--spacing-hsp-lg);
3416
+ color: var(--color-muted);
3417
+ text-align: right;
3418
+ user-select: none;
3419
+ }
3420
+ .zd-asset-page .zd-asset-code .line:target {
3421
+ background: var(--color-accent);
3422
+ @supports (color: color-mix(in lab, red, red)) {
3423
+ background: color-mix(in srgb, var(--color-accent) 15%, transparent);
3424
+ }
3425
+ box-shadow: inset 3px 0 0 var(--color-accent);
3426
+ }
3427
+ .zd-asset-page .zd-asset-filebar {
3428
+ position: sticky;
3429
+ top: 3.5rem;
3430
+ z-index: var(--z-index-local-2);
3431
+ }
3432
+ .zd-asset-page .zd-asset-code + .code-buttons {
3433
+ display: none;
3434
+ }
3435
+ .zd-asset-page .zd-asset-stage.is-checker {
3436
+ background-color: var(--color-bg);
3437
+ background-image: linear-gradient(45deg, var(--color-muted) 25%, transparent 25%), linear-gradient(-45deg, var(--color-muted) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, var(--color-muted) 75%), linear-gradient(-45deg, transparent 75%, var(--color-muted) 75%);
3438
+ @supports (color: color-mix(in lab, red, red)) {
3439
+ background-image: linear-gradient(45deg, color-mix(in srgb, var(--color-muted) 10%, var(--color-bg)) 25%, transparent 25%), linear-gradient(-45deg, color-mix(in srgb, var(--color-muted) 10%, var(--color-bg)) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, color-mix(in srgb, var(--color-muted) 10%, var(--color-bg)) 75%), linear-gradient(-45deg, transparent 75%, color-mix(in srgb, var(--color-muted) 10%, var(--color-bg)) 75%);
3440
+ }
3441
+ background-position: 0 0, 0 var(--spacing-hsp-sm), var(--spacing-hsp-sm) calc(-1 * var(--spacing-hsp-sm)), calc(-1 * var(--spacing-hsp-sm)) 0;
3442
+ background-size: calc(2 * var(--spacing-hsp-sm)) calc(2 * var(--spacing-hsp-sm));
3443
+ }
3444
+ .zd-asset-page .zd-asset-stage.is-dark {
3445
+ background: var(--color-code-bg);
3446
+ }
3447
+ .zd-asset-page .zd-asset-stage.is-1to1 img {
3448
+ max-width: none;
3449
+ }
3450
+ .zd-asset-page .zd-asset-media-grid {
3451
+ display: grid;
3452
+ gap: var(--spacing-vsp-lg);
3453
+ }
3454
+ .zd-asset-page .zd-asset-media-rail {
3455
+ display: flex;
3456
+ flex-direction: column;
3457
+ align-items: start;
3458
+ gap: var(--spacing-vsp-md);
3459
+ }
3460
+ .zd-asset-page .zd-asset-media-rail > * {
3461
+ width: 100%;
3462
+ }
3463
+ @media (min-width: 1024px) {
3464
+ .zd-asset-page .zd-asset-media-grid {
3465
+ grid-template-columns: minmax(0, 1fr) 20rem;
3466
+ align-items: start;
3467
+ }
3468
+ }
3469
+ [data-zd-asset-tree]:where(ul), [data-zd-asset-tree] :where(ul) {
3470
+ margin: 0;
3471
+ padding: 0;
3472
+ list-style: none;
3473
+ }
3474
+ [data-zd-asset-tree] :where(li) {
3475
+ margin: 0;
3476
+ }
3477
+ [data-zd-asset-tree] :where(ul) {
3478
+ margin-left: var(--spacing-hsp-md);
3479
+ padding-left: var(--spacing-hsp-md);
3480
+ border-left: 1px solid currentColor;
3481
+ @supports (color: color-mix(in lab, red, red)) {
3482
+ border-left: 1px solid color-mix(in srgb, currentColor 18%, transparent);
3483
+ }
3484
+ }
3485
+ [data-zd-asset-tree] :where(summary) {
3486
+ list-style: none;
3487
+ }
3488
+ [data-zd-asset-tree] :where(summary)::-webkit-details-marker {
3489
+ display: none;
3490
+ }
3491
+ [data-zd-asset-tree] :where(summary)::marker {
3492
+ content: "";
3493
+ }
3494
+ [data-zd-asset-tree] details[open] > summary > svg:first-child {
3495
+ transform: rotate(90deg);
3496
+ }
3497
+ [data-zd-asset-tree] details:not([open]) > summary > svg:nth-child(3), [data-zd-asset-tree] details[open] > summary > svg:nth-child(2) {
3498
+ display: none;
3499
+ }
3365
3500
  @property --tw-translate-x {
3366
3501
  syntax: "*";
3367
3502
  inherits: false;
package/dist/config.d.ts CHANGED
@@ -367,6 +367,16 @@ export interface ZudoDocConfig {
367
367
  * @default []
368
368
  */
369
369
  docHistoryExclude?: string[];
370
+ /** Generate a viewer page for every file under `public/<assetViewerDir>/**`. @default false */
371
+ assetViewer?: boolean;
372
+ /** Directory under `public/` holding viewable assets (also the raw URL prefix `/<dir>/…`). `client/` is reserved by zfb. @default "assets" */
373
+ assetViewerDir?: string;
374
+ /** URL prefix of the generated viewer pages (`/<prefix>/<path>/`). Must differ from `assetViewerDir`. @default "files" */
375
+ assetViewerRoutePrefix?: string;
376
+ /** Glob patterns (relative to the asset dir) excluded from viewer generation. @default [] */
377
+ assetViewerExclude?: string[];
378
+ /** When `true` (and `assetViewer` is `true`), generate a listing page at `/<assetViewerRoutePrefix>/` showing every managed asset as a folder tree. No effect when `assetViewer` is `false`. @default false */
379
+ assetViewerIndex?: boolean;
370
380
  /**
371
381
  * Body-foot utility area (doc-history / view-source), or `false` to disable.
372
382
  * @default false
@@ -485,8 +495,9 @@ export interface ZudoDocConfig {
485
495
  */
486
496
  colorSchemes?: Record<string, ColorScheme>;
487
497
  /**
488
- * Override the UI-string translation table. When omitted, the shipped
489
- * en/ja/de defaults (`@takazudo/zudo-doc/i18n-defaults`) are used.
498
+ * Override individual UI strings by locale and key. Shipped en/ja/de
499
+ * defaults (`@takazudo/zudo-doc/i18n-defaults`) are retained for keys the
500
+ * project does not supply.
490
501
  * @default undefined (`defaultTranslations` is used)
491
502
  */
492
503
  translations?: PresetTranslations;
package/dist/config.js CHANGED
@@ -5,6 +5,32 @@ import { defaultTranslations } from "./i18n-defaults/index.js";
5
5
  import { defaultColorSchemes } from "./color-schemes-defaults/index.js";
6
6
  import { assertNoCommaInVersionSlugs } from "./version-availability/index.js";
7
7
  import { assertNoEmptyStringFaviconOrLogo } from "./config-assertions/index.js";
8
+ import { validateAssetViewerSettings } from "./asset-path/index.js";
9
+ function mergeDefaultTranslations(overrides) {
10
+ if (overrides === void 0) return defaultTranslations;
11
+ const merged = {};
12
+ const locales = /* @__PURE__ */ new Set([
13
+ ...Object.keys(defaultTranslations),
14
+ ...Object.keys(overrides)
15
+ ]);
16
+ for (const locale of locales) {
17
+ merged[locale] = {
18
+ ...defaultTranslations[locale] ?? {},
19
+ ...overrides[locale] ?? {}
20
+ };
21
+ }
22
+ return merged;
23
+ }
24
+ function assertValidAssetViewerSettings(dir, routePrefix) {
25
+ try {
26
+ validateAssetViewerSettings({ dir, routePrefix });
27
+ } catch (error) {
28
+ const message = error instanceof Error ? error.message : String(error);
29
+ throw new TypeError(
30
+ `zudo-doc: ${message.replaceAll("assetViewer.dir", "assetViewerDir").replaceAll("assetViewer.routePrefix", "assetViewerRoutePrefix").replace("must be different", "must differ")}`
31
+ );
32
+ }
33
+ }
8
34
  const DEFAULT_SETTINGS = {
9
35
  colorScheme: "Default Dark",
10
36
  colorMode: {
@@ -67,6 +93,11 @@ const DEFAULT_SETTINGS = {
67
93
  frontmatterPreview: false,
68
94
  docHistory: false,
69
95
  docHistoryExclude: [],
96
+ assetViewer: false,
97
+ assetViewerDir: "assets",
98
+ assetViewerRoutePrefix: "files",
99
+ assetViewerExclude: [],
100
+ assetViewerIndex: false,
70
101
  bodyFootUtilArea: false,
71
102
  htmlPreview: void 0,
72
103
  versions: false,
@@ -105,6 +136,10 @@ function zudoDoc(user = {}) {
105
136
  ...settingsOverrides
106
137
  } = user;
107
138
  const settings = { ...DEFAULT_SETTINGS, ...settingsOverrides };
139
+ assertValidAssetViewerSettings(
140
+ settings.assetViewerDir,
141
+ settings.assetViewerRoutePrefix
142
+ );
108
143
  assertNoCommaInVersionSlugs(settings.versions);
109
144
  assertNoEmptyStringFaviconOrLogo(settings);
110
145
  const fragment = zudoDocPreset({
@@ -114,7 +149,7 @@ function zudoDoc(user = {}) {
114
149
  tagVocabulary: userTagVocabularyEntries
115
150
  })),
116
151
  directiveVocabulary: userDirectives ?? defaultDirectiveVocabulary,
117
- translations: userTranslations ?? defaultTranslations,
152
+ translations: mergeDefaultTranslations(userTranslations),
118
153
  colorSchemes: userColorSchemes ?? defaultColorSchemes,
119
154
  tagVocabulary: userTagVocabularyEntries ?? []
120
155
  });
@@ -1,6 +1,18 @@
1
1
  /** @jsxRuntime automatic */
2
2
  /** @jsxImportSource preact */
3
3
  import type { JSX } from "preact";
4
+ import type { AssetManifest } from "../route-context-payload/types.js";
4
5
  type Props = JSX.IntrinsicElements["a"];
5
6
  export declare function ContentLink({ href, className, children, ...rest }: Props): JSX.Element;
7
+ export interface CreateContentLinkOptions {
8
+ base: string;
9
+ assetManifest: AssetManifest | null;
10
+ routePrefix: string;
11
+ dir: string;
12
+ }
13
+ /**
14
+ * Bind ContentLink to an asset manifest. Only exact manifest entries are
15
+ * decorated and redirected; every other anchor follows ContentLink verbatim.
16
+ */
17
+ export declare function createContentLink({ base, assetManifest, routePrefix, dir, }: CreateContentLinkOptions): (props: Props) => JSX.Element;
6
18
  export {};
@@ -1,5 +1,7 @@
1
- import { jsx } from "preact/jsx-runtime";
1
+ import { jsx, jsxs } from "preact/jsx-runtime";
2
2
  import { SmartBreak as SmartBreakBase } from "../smart-break/index.js";
3
+ import { decodeAuthoredHref, assetViewerHref } from "../asset-path/index.js";
4
+ import { AssetFileIcon, formatAssetBytes } from "../asset-components/index.js";
3
5
  const SmartBreak = SmartBreakBase;
4
6
  function ContentLink({ href, className, children, ...rest }) {
5
7
  const classes = typeof className === "string" ? className.split(" ") : [];
@@ -18,6 +20,44 @@ function ContentLink({ href, className, children, ...rest }) {
18
20
  }
19
21
  );
20
22
  }
23
+ function createContentLink({
24
+ base,
25
+ assetManifest,
26
+ routePrefix,
27
+ dir
28
+ }) {
29
+ return function ManifestAwareContentLink(props) {
30
+ const { href, className, children } = props;
31
+ const classes = typeof className === "string" ? className.split(" ") : [];
32
+ if (assetManifest === null || typeof href !== "string" || href.startsWith("#") || classes.includes("block") || classes.includes("hash-link")) {
33
+ return /* @__PURE__ */ jsx(ContentLink, { ...props });
34
+ }
35
+ const decoded = decodeAuthoredHref(href, { base, dir });
36
+ const entry = decoded ? assetManifest.entries.find((candidate) => candidate.path === decoded.path) : void 0;
37
+ if (!decoded || !entry) return /* @__PURE__ */ jsx(ContentLink, { ...props });
38
+ return /* @__PURE__ */ jsx(
39
+ ContentLink,
40
+ {
41
+ ...props,
42
+ href: assetViewerHref({
43
+ base,
44
+ routePrefix,
45
+ path: entry.path,
46
+ fragment: decoded.fragment
47
+ }),
48
+ children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-baseline gap-x-hsp-xs font-mono", children: [
49
+ /* @__PURE__ */ jsx(AssetFileIcon, { className: "h-icon-sm w-icon-sm shrink-0" }),
50
+ /* @__PURE__ */ jsx("span", { children }),
51
+ /* @__PURE__ */ jsxs("span", { className: "text-caption text-muted", children: [
52
+ "(",
53
+ formatAssetBytes(entry.bytes),
54
+ ")"
55
+ ] })
56
+ ] })
57
+ }
58
+ );
59
+ };
60
+ }
21
61
  function extractText(children) {
22
62
  if (typeof children === "string") return children;
23
63
  if (typeof children === "number") return String(children);
@@ -39,5 +79,6 @@ function decodeEntities(s) {
39
79
  ).replace(/&amp;/g, "&");
40
80
  }
41
81
  export {
42
- ContentLink
82
+ ContentLink,
83
+ createContentLink
43
84
  };
@@ -19,7 +19,7 @@ export { HeadingH2 } from "./heading-h2.js";
19
19
  export { HeadingH3 } from "./heading-h3.js";
20
20
  export { HeadingH4 } from "./heading-h4.js";
21
21
  export { ContentParagraph } from "./content-paragraph.js";
22
- export { ContentLink } from "./content-link.js";
22
+ export { ContentLink, createContentLink, type CreateContentLinkOptions, } from "./content-link.js";
23
23
  export { ContentStrong } from "./content-strong.js";
24
24
  export { ContentBlockquote } from "./content-blockquote.js";
25
25
  export { ContentUl } from "./content-ul.js";