@takazudo/zudo-doc 4.2.1 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +3 -3
  3. package/bin/zudo-doc.mjs +54 -13
  4. package/dist/auto-logo/index.d.ts +18 -0
  5. package/dist/auto-logo/index.js +33 -0
  6. package/dist/auto-logo/shapes.d.ts +32 -0
  7. package/dist/auto-logo/shapes.js +183 -0
  8. package/dist/auto-logo/standalone.d.ts +7 -0
  9. package/dist/auto-logo/standalone.js +35 -0
  10. package/dist/color-scheme-utils.d.ts +4 -3
  11. package/dist/color-scheme-utils.js +1 -1
  12. package/dist/config/component-tokens.js +2 -2
  13. package/dist/config.d.ts +23 -1
  14. package/dist/config.js +3 -0
  15. package/dist/content.css +4 -3
  16. package/dist/doc-content-header/index.js +1 -1
  17. package/dist/doc-history/index.js +1 -1
  18. package/dist/doc-history-area/index.d.ts +1 -0
  19. package/dist/doc-history-area/index.js +3 -0
  20. package/dist/doc-page-shell/index.js +1 -1
  21. package/dist/doc-pager/index.js +2 -2
  22. package/dist/eject-logo/config-parse.d.ts +25 -0
  23. package/dist/eject-logo/config-parse.js +68 -0
  24. package/dist/eject-logo/config-rewriter.d.ts +22 -0
  25. package/dist/eject-logo/config-rewriter.js +68 -0
  26. package/dist/eject-logo/eject.d.ts +18 -0
  27. package/dist/eject-logo/eject.js +66 -0
  28. package/dist/eject-logo/index.d.ts +4 -0
  29. package/dist/eject-logo/index.js +16 -0
  30. package/dist/eject-logo/site-name.d.ts +21 -0
  31. package/dist/eject-logo/site-name.js +31 -0
  32. package/dist/features.css +8 -29
  33. package/dist/header/header.js +14 -6
  34. package/dist/header/nav-class-tokens.d.ts +12 -0
  35. package/dist/header/nav-class-tokens.js +80 -0
  36. package/dist/header/nav-overflow-script.js +33 -16
  37. package/dist/home-page/index.d.ts +34 -5
  38. package/dist/home-page/index.js +95 -38
  39. package/dist/home-page/prepare-home-data.d.ts +9 -0
  40. package/dist/home-page/prepare-home-data.js +10 -3
  41. package/dist/html-preview-wrapper/highlight-runtime.d.ts +40 -0
  42. package/dist/html-preview-wrapper/highlight-runtime.js +58 -0
  43. package/dist/html-preview-wrapper/highlighted-code.d.ts +3 -2
  44. package/dist/html-preview-wrapper/highlighted-code.js +11 -30
  45. package/dist/i18n-defaults/index.js +3 -0
  46. package/dist/metainfo/doc-metainfo.js +35 -22
  47. package/dist/plugins/doc-history.js +6 -2
  48. package/dist/plugins/internal/doc-history/index.d.ts +2 -0
  49. package/dist/plugins/internal/doc-history/index.js +5 -0
  50. package/dist/plugins/internal/doc-history/pre-build.d.ts +2 -0
  51. package/dist/plugins/internal/doc-history/pre-build.js +3 -0
  52. package/dist/preset.d.ts +1 -0
  53. package/dist/preset.js +2 -1
  54. package/dist/routes/index.js +13 -3
  55. package/dist/routes/locale-index.js +12 -2
  56. package/dist/safelist.css +1 -1
  57. package/dist/settings.d.ts +15 -0
  58. package/dist/sidebar-tree-island/index.js +3 -3
  59. package/dist/theme-cli/config-scanner.js +47 -0
  60. package/dist/theme-packs/academia/fonts/CourierPrime-400-latin.woff2 +0 -0
  61. package/dist/theme-packs/academia/fonts/CourierPrime-700-latin.woff2 +0 -0
  62. package/dist/theme-packs/academia/fonts/OFL.txt +94 -0
  63. package/dist/theme-packs/academia/fonts/SourceSerif4-italic-latin.woff2 +0 -0
  64. package/dist/theme-packs/academia/fonts/SourceSerif4-latin.woff2 +0 -0
  65. package/dist/theme-packs/academia/meta.json +37 -0
  66. package/dist/theme-packs/academia/pack.css +505 -0
  67. package/dist/theme-packs/bauhaus/fonts/ArchivoBlack-latin.woff2 +0 -0
  68. package/dist/theme-packs/bauhaus/fonts/Jost-latin.woff2 +0 -0
  69. package/dist/theme-packs/bauhaus/fonts/OFL.txt +95 -0
  70. package/dist/theme-packs/bauhaus/fonts/SpaceMono-400-latin.woff2 +0 -0
  71. package/dist/theme-packs/bauhaus/fonts/SpaceMono-700-latin.woff2 +0 -0
  72. package/dist/theme-packs/bauhaus/meta.json +38 -0
  73. package/dist/theme-packs/bauhaus/pack.css +614 -0
  74. package/dist/theme-packs/blueprint/fonts/IBMPlexMono-400-latin-ext.woff2 +0 -0
  75. package/dist/theme-packs/blueprint/fonts/IBMPlexMono-400-latin.woff2 +0 -0
  76. package/dist/theme-packs/blueprint/fonts/IBMPlexMono-500-latin-ext.woff2 +0 -0
  77. package/dist/theme-packs/blueprint/fonts/IBMPlexMono-500-latin.woff2 +0 -0
  78. package/dist/theme-packs/blueprint/fonts/Inter-latin-ext.woff2 +0 -0
  79. package/dist/theme-packs/blueprint/fonts/Inter-latin.woff2 +0 -0
  80. package/dist/theme-packs/blueprint/fonts/JosefinSans-latin.woff2 +0 -0
  81. package/dist/theme-packs/blueprint/fonts/OFL.txt +95 -0
  82. package/dist/theme-packs/blueprint/meta.json +38 -0
  83. package/dist/theme-packs/blueprint/pack.css +577 -0
  84. package/dist/theme-packs/botanica/fonts/CormorantGaramond-latin.woff2 +0 -0
  85. package/dist/theme-packs/botanica/fonts/CourierPrime-400-latin-ext.woff2 +0 -0
  86. package/dist/theme-packs/botanica/fonts/CourierPrime-400-latin.woff2 +0 -0
  87. package/dist/theme-packs/botanica/fonts/CourierPrime-700-latin-ext.woff2 +0 -0
  88. package/dist/theme-packs/botanica/fonts/CourierPrime-700-latin.woff2 +0 -0
  89. package/dist/theme-packs/botanica/fonts/CrimsonPro-latin-italic.woff2 +0 -0
  90. package/dist/theme-packs/botanica/fonts/CrimsonPro-latin.woff2 +0 -0
  91. package/dist/theme-packs/botanica/fonts/OFL.txt +95 -0
  92. package/dist/theme-packs/botanica/meta.json +38 -0
  93. package/dist/theme-packs/botanica/pack.css +546 -0
  94. package/dist/theme-packs/brutalist/meta.json +1 -1
  95. package/dist/theme-packs/brutalist/pack.css +10 -9
  96. package/dist/theme-packs/eink/fonts/CourierPrime-400-latin-ext.woff2 +0 -0
  97. package/dist/theme-packs/eink/fonts/CourierPrime-400-latin.woff2 +0 -0
  98. package/dist/theme-packs/eink/fonts/CourierPrime-700-latin-ext.woff2 +0 -0
  99. package/dist/theme-packs/eink/fonts/CourierPrime-700-latin.woff2 +0 -0
  100. package/dist/theme-packs/eink/fonts/EBGaramond-latin-italic.woff2 +0 -0
  101. package/dist/theme-packs/eink/fonts/EBGaramond-latin.woff2 +0 -0
  102. package/dist/theme-packs/eink/fonts/OFL.txt +94 -0
  103. package/dist/theme-packs/eink/meta.json +37 -0
  104. package/dist/theme-packs/eink/pack.css +490 -0
  105. package/dist/theme-packs/foundry/meta.json +1 -1
  106. package/dist/theme-packs/foundry/pack.css +2 -2
  107. package/dist/theme-packs/hearth/meta.json +1 -1
  108. package/dist/theme-packs/hearth/pack.css +15 -4
  109. package/dist/theme-packs/nocturne/pack.css +4 -0
  110. package/dist/theme-packs/observatory/meta.json +1 -1
  111. package/dist/theme-packs/observatory/pack.css +4 -1
  112. package/dist/theme-packs/onyx/pack.css +6 -0
  113. package/dist/theme-packs/phosphor/pack.css +4 -0
  114. package/dist/theme-packs/riso/fonts/ArchivoBlack-latin.woff2 +0 -0
  115. package/dist/theme-packs/riso/fonts/OFL.txt +95 -0
  116. package/dist/theme-packs/riso/fonts/SpaceGrotesk-latin.woff2 +0 -0
  117. package/dist/theme-packs/riso/fonts/SpaceMono-400-italic-latin.woff2 +0 -0
  118. package/dist/theme-packs/riso/fonts/SpaceMono-400-latin.woff2 +0 -0
  119. package/dist/theme-packs/riso/fonts/SpaceMono-700-latin.woff2 +0 -0
  120. package/dist/theme-packs/riso/meta.json +38 -0
  121. package/dist/theme-packs/riso/pack.css +602 -0
  122. package/dist/theme-packs/sakura/fonts/MPlus1Code-400-latin.woff2 +0 -0
  123. package/dist/theme-packs/sakura/fonts/MPlus1Code-500-latin.woff2 +0 -0
  124. package/dist/theme-packs/sakura/fonts/Nunito-latin.woff2 +0 -0
  125. package/dist/theme-packs/sakura/fonts/OFL.txt +95 -0
  126. package/dist/theme-packs/sakura/fonts/ShipporiMincho-600-latin.woff2 +0 -0
  127. package/dist/theme-packs/sakura/fonts/ShipporiMincho-700-latin.woff2 +0 -0
  128. package/dist/theme-packs/sakura/meta.json +38 -0
  129. package/dist/theme-packs/sakura/pack.css +473 -0
  130. package/dist/theme-packs/scandi/fonts/Manrope-latin.woff2 +0 -0
  131. package/dist/theme-packs/scandi/fonts/OFL.txt +94 -0
  132. package/dist/theme-packs/scandi/fonts/SplineSansMono-latin.woff2 +0 -0
  133. package/dist/theme-packs/scandi/meta.json +37 -0
  134. package/dist/theme-packs/scandi/pack.css +370 -0
  135. package/dist/theme-packs/sumi/meta.json +1 -1
  136. package/dist/theme-packs/sumi/pack.css +4 -1
  137. package/dist/theme-packs/swissgrid/pack.css +4 -0
  138. package/dist/theme-packs/tidepool/fonts/JetBrainsMono-latin-ext.woff2 +0 -0
  139. package/dist/theme-packs/tidepool/fonts/JetBrainsMono-latin.woff2 +0 -0
  140. package/dist/theme-packs/tidepool/fonts/OFL.txt +94 -0
  141. package/dist/theme-packs/tidepool/fonts/SpaceGrotesk-latin.woff2 +0 -0
  142. package/dist/theme-packs/tidepool/meta.json +37 -0
  143. package/dist/theme-packs/tidepool/pack.css +425 -0
  144. package/dist/theme-packs/timberline/fonts/Fraunces-italic-latin.woff2 +0 -0
  145. package/dist/theme-packs/timberline/fonts/Fraunces-latin.woff2 +0 -0
  146. package/dist/theme-packs/timberline/fonts/JetBrainsMono-latin.woff2 +0 -0
  147. package/dist/theme-packs/timberline/fonts/OFL.txt +95 -0
  148. package/dist/theme-packs/timberline/fonts/SourceSans3-italic-latin.woff2 +0 -0
  149. package/dist/theme-packs/timberline/fonts/SourceSans3-latin.woff2 +0 -0
  150. package/dist/theme-packs/timberline/meta.json +38 -0
  151. package/dist/theme-packs/timberline/pack.css +509 -0
  152. package/dist/theme-packs/washi/meta.json +1 -1
  153. package/dist/theme-packs/washi/pack.css +6 -26
  154. package/dist/theme-packs-registry/validator.js +24 -1
  155. package/dist/theme.css +1 -1
  156. package/eject/doc-history/index.tsx +1 -1
  157. package/eject/doc-pager/index.tsx +2 -2
  158. package/eject/header/header.tsx +16 -8
  159. package/eject/header/nav-class-tokens.ts +115 -0
  160. package/eject/header/nav-overflow-script.ts +49 -16
  161. package/eject/sidebar-tree-island/index.tsx +4 -4
  162. package/package.json +16 -13
  163. package/routes-src/index.tsx +10 -3
  164. package/routes-src/locale-index.tsx +9 -2
@@ -1,4 +1,21 @@
1
1
  import { AFTER_NAVIGATE_EVENT } from "../transitions/page-events.js";
2
+ import {
3
+ NAV_CHEVRON_ACTIVE,
4
+ NAV_CHEVRON_INACTIVE,
5
+ NAV_CHILD_ACTIVE,
6
+ NAV_CHILD_INACTIVE,
7
+ NAV_MENU_CHILD_ACTIVE,
8
+ NAV_MENU_CHILD_INACTIVE,
9
+ NAV_MENU_PARENT,
10
+ NAV_MENU_PARENT_ACTIVE_SUFFIX,
11
+ NAV_MENU_PLAIN,
12
+ NAV_MENU_PLAIN_ACTIVE_SUFFIX,
13
+ NAV_TOP_ACTIVE,
14
+ NAV_TOP_INACTIVE
15
+ } from "./nav-class-tokens.js";
16
+ const clsArgs = (tokens) => tokens.map((token) => JSON.stringify(token)).join(", ");
17
+ const clsLiteral = (tokens) => JSON.stringify(tokens.join(" "));
18
+ const clsAppend = (tokens) => JSON.stringify(" " + tokens.join(" "));
2
19
  const NAV_OVERFLOW_SCRIPT = `(function () {
3
20
  var cleanupNavOverflow = null;
4
21
 
@@ -56,12 +73,12 @@ const NAV_OVERFLOW_SCRIPT = `(function () {
56
73
  function setTopActive(a, active) {
57
74
  if (!a) return;
58
75
  if (active) {
59
- a.classList.add("bg-fg", "text-bg");
60
- a.classList.remove("text-muted", "hover:underline", "focus:underline");
76
+ a.classList.add(${clsArgs(NAV_TOP_ACTIVE)});
77
+ a.classList.remove(${clsArgs(NAV_TOP_INACTIVE)});
61
78
  a.setAttribute("aria-current", "page");
62
79
  } else {
63
- a.classList.remove("bg-fg", "text-bg");
64
- a.classList.add("text-muted", "hover:underline", "focus:underline");
80
+ a.classList.remove(${clsArgs(NAV_TOP_ACTIVE)});
81
+ a.classList.add(${clsArgs(NAV_TOP_INACTIVE)});
65
82
  a.removeAttribute("aria-current");
66
83
  }
67
84
  }
@@ -79,19 +96,19 @@ const NAV_OVERFLOW_SCRIPT = `(function () {
79
96
  if (childActive) {
80
97
  anyChild = true;
81
98
  c.setAttribute("data-active", "");
82
- c.classList.add("font-bold", "text-accent");
83
- c.classList.remove("text-fg");
99
+ c.classList.add(${clsArgs(NAV_CHILD_ACTIVE)});
100
+ c.classList.remove(${clsArgs(NAV_CHILD_INACTIVE)});
84
101
  } else {
85
102
  c.removeAttribute("data-active");
86
- c.classList.remove("font-bold", "text-accent");
87
- c.classList.add("text-fg");
103
+ c.classList.remove(${clsArgs(NAV_CHILD_ACTIVE)});
104
+ c.classList.add(${clsArgs(NAV_CHILD_INACTIVE)});
88
105
  }
89
106
  });
90
107
  topActive = parentMatch || anyChild;
91
108
  var svg = topA ? topA.querySelector("svg") : null;
92
109
  if (svg) {
93
- if (topActive) { svg.classList.add("text-bg"); svg.classList.remove("text-muted"); }
94
- else { svg.classList.add("text-muted"); svg.classList.remove("text-bg"); }
110
+ if (topActive) { svg.classList.add(${clsArgs(NAV_CHEVRON_ACTIVE)}); svg.classList.remove(${clsArgs(NAV_CHEVRON_INACTIVE)}); }
111
+ else { svg.classList.add(${clsArgs(NAV_CHEVRON_INACTIVE)}); svg.classList.remove(${clsArgs(NAV_CHEVRON_ACTIVE)}); }
95
112
  }
96
113
  } else {
97
114
  topActive = activePath !== "" && navPathname(topA) === activePath;
@@ -173,9 +190,9 @@ const NAV_OVERFLOW_SCRIPT = `(function () {
173
190
  a.href = parentLink.href;
174
191
  var parentText = parentLink.textContent ? parentLink.textContent.trim().replace(/\\s+/g, " ") : "";
175
192
  a.textContent = parentText;
176
- a.className = "block px-hsp-md py-vsp-2xs text-small font-bold hover:bg-accent/10 hover:underline focus-visible:underline text-fg";
193
+ a.className = ${clsLiteral(NAV_MENU_PARENT)};
177
194
  if (parentLink.getAttribute("aria-current") === "page") {
178
- a.className += " text-accent";
195
+ a.className += ${clsAppend(NAV_MENU_PARENT_ACTIVE_SUFFIX)};
179
196
  }
180
197
  li.appendChild(a);
181
198
  moreMenu.appendChild(li);
@@ -187,8 +204,8 @@ const NAV_OVERFLOW_SCRIPT = `(function () {
187
204
  a.textContent = child.textContent ? child.textContent.trim() : "";
188
205
  var isChildActive = child.hasAttribute("data-active");
189
206
  a.className = isChildActive
190
- ? "block pl-hsp-xl pr-hsp-md py-vsp-2xs text-small font-bold text-accent hover:bg-accent/10 hover:underline focus-visible:underline"
191
- : "block pl-hsp-xl pr-hsp-md py-vsp-2xs text-small text-muted hover:bg-accent/10 hover:text-fg hover:underline focus-visible:underline";
207
+ ? ${clsLiteral(NAV_MENU_CHILD_ACTIVE)}
208
+ : ${clsLiteral(NAV_MENU_CHILD_INACTIVE)};
192
209
  li.appendChild(a);
193
210
  moreMenu.appendChild(li);
194
211
  });
@@ -198,9 +215,9 @@ const NAV_OVERFLOW_SCRIPT = `(function () {
198
215
  var a2 = document.createElement("a");
199
216
  a2.href = anchor.href;
200
217
  a2.textContent = anchor.textContent ? anchor.textContent.trim() : "";
201
- a2.className = "block px-hsp-md py-vsp-2xs text-small hover:bg-accent/10 hover:underline focus-visible:underline text-fg";
218
+ a2.className = ${clsLiteral(NAV_MENU_PLAIN)};
202
219
  if (anchor.getAttribute("aria-current") === "page") {
203
- a2.className += " font-bold text-accent";
220
+ a2.className += ${clsAppend(NAV_MENU_PLAIN_ACTIVE_SUFFIX)};
204
221
  }
205
222
  li2.appendChild(a2);
206
223
  moreMenu.appendChild(li2);
@@ -1,6 +1,7 @@
1
1
  /** @jsxRuntime automatic */
2
2
  /** @jsxImportSource preact */
3
3
  import type { ComponentChildren, JSX } from "preact";
4
+ import type { TagItem } from "../nav-indexing/types.js";
4
5
  import type { DocNavNode } from "../doc-page-props/index.js";
5
6
  import type { ChromeContext } from "../factory-context/index.js";
6
7
  import type { Settings } from "../settings.js";
@@ -12,9 +13,10 @@ export interface HomePageViewProps {
12
13
  * `hostBindings.homeExtras({ locale })` call when `extras` is absent. */
13
14
  locale: string;
14
15
  /**
15
- * Extra content rendered after the links row, inside the hero text column.
16
- * A VALUE (already-rendered children) the host-PAGE path. Takes
17
- * precedence over `ctx.hostBindings.homeExtras` when both are present.
16
+ * Extra content rendered INLINE at the end of the links row, `/`-separated
17
+ * from whatever precedes it (the primary link and/or the GitHub link). A
18
+ * VALUE (already-rendered children) the host-PAGE path. Takes precedence
19
+ * over `ctx.hostBindings.homeExtras` when both are present.
18
20
  *
19
21
  * This value-vs-renderer asymmetry (prop = value, `hostBindings.homeExtras`
20
22
  * = renderer) is INTENTIONAL, not an oversight to "unify": a host page
@@ -24,6 +26,17 @@ export interface HomePageViewProps {
24
26
  * locale })`.
25
27
  */
26
28
  extras?: ComponentChildren;
29
+ /**
30
+ * Overrides the default first hero link, which is otherwise
31
+ * `settings.headerNav[0]` + `t("nav.overview", locale)`. `path` is
32
+ * locale-relative (the view prefixes it with the locale-URL prefix and
33
+ * resolves it through `withBase`, same as the default); `labelKey`
34
+ * resolves through `t(labelKey, locale)`.
35
+ */
36
+ heroLink?: {
37
+ path: string;
38
+ labelKey: string;
39
+ };
27
40
  /** Prepared nav tree for the `SiteTreeNav` grid (already grouped via
28
41
  * `groupSatelliteNodes` by the caller — the two routes group their own
29
42
  * locale-specific tree before handing it here). */
@@ -32,9 +45,25 @@ export interface HomePageViewProps {
32
45
  categoryOrder: string[];
33
46
  /** Root-category slugs that should start collapsed in `SiteTreeNav`. */
34
47
  initiallyCollapsedCategorySlugs?: string[];
35
- /** Unique tag count for the current locale — gates the "all tags" section
36
- * together with `settings.docTags`. */
48
+ /** Unique tag count for the current locale — gates the "Tags" section
49
+ * together with `settings.docTags`. Part of the documented public
50
+ * `@takazudo/zudo-doc/home-page` props shape (kept for back-compat). */
37
51
  tagCount: number;
52
+ /**
53
+ * Alphabetically-sorted tag list (tag + doc count + pre-resolved href) for
54
+ * the current locale. When provided (and non-empty) the section renders the
55
+ * tag chips (first {@link HomePageViewProps.tagLimit}) plus a "see all tags"
56
+ * nav. When omitted — legacy callers that pass only `tagCount` — the section
57
+ * falls back to the pre-#3027 single "All Tags" link, so existing
58
+ * `@takazudo/zudo-doc/home-page` consumers are not broken.
59
+ */
60
+ tags?: TagItem[];
61
+ /**
62
+ * Max number of tag chips rendered in the home "Tags" section before a "…"
63
+ * overflow indicator is shown. The full set stays reachable via the
64
+ * "see all tags" link. @default 30
65
+ */
66
+ tagLimit?: number;
38
67
  /**
39
68
  * Opt the home page into the **wide** content layout (full-viewport
40
69
  * category grid instead of the standard capped reading column). Threaded to
@@ -1,13 +1,20 @@
1
1
  import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
2
+ import { Fragment as Fragment2 } from "preact";
2
3
  import { Island } from "@takazudo/zfb";
4
+ import { AutoLogo } from "../auto-logo/index.js";
3
5
  import { DocLayoutWithDefaults } from "../doclayout/index.js";
4
6
  import { SiteTreeNav } from "../site-tree-nav-island/index.js";
7
+ import { TagNav } from "../nav-indexing/tag-nav.js";
8
+ import { CategoryLinkIcon } from "../tree-nav-shared/index.js";
5
9
  import { createHeadWithDefaults } from "../head-with-defaults/index.js";
6
10
  import { resolveThemePackSsrSlug } from "../theme/theme-pack-provider.js";
7
11
  import { deriveComposeMetaTitle, deriveBodyEndIslands } from "../chrome/derive.js";
8
12
  import { derivePrimaryChromeSlots } from "../chrome/primary-slots.js";
9
13
  import { assertChromeContext } from "../chrome/assert-chrome-context.js";
10
14
  import { prepareHomeData } from "./prepare-home-data.js";
15
+ function cssUrl(url) {
16
+ return `url("${url.replace(/[\\"]/g, "\\$&")}")`;
17
+ }
11
18
  function createHomePageView(ctx) {
12
19
  assertChromeContext(ctx, "createHomePageView");
13
20
  const settings = ctx.settings;
@@ -26,17 +33,53 @@ function createHomePageView(ctx) {
26
33
  function HomePageView({
27
34
  locale,
28
35
  extras,
36
+ heroLink,
29
37
  tree,
30
38
  categoryOrder,
31
39
  initiallyCollapsedCategorySlugs,
32
40
  tagCount,
41
+ tags,
42
+ tagLimit = 30,
33
43
  wide
34
44
  }) {
35
45
  const prefix = locale === defaultLocale ? "" : `/${locale}`;
36
46
  const ctaNav = settings.headerNav[0] ?? null;
37
- const overview = ctaNav ? withBase(`${prefix}${ctaNav.path}`) : null;
38
- const logoUrl = withBase("/img/logo.svg");
47
+ const primary = heroLink ? { href: withBase(`${prefix}${heroLink.path}`), label: t(heroLink.labelKey, locale) } : ctaNav ? { href: withBase(`${prefix}${ctaNav.path}`), label: t("nav.overview", locale) } : null;
48
+ const logoSetting = settings.logo ?? "auto";
39
49
  const resolvedExtras = extras ?? homeExtras?.({ locale });
50
+ const hasExtras = resolvedExtras != null && typeof resolvedExtras !== "boolean";
51
+ const rowItems = [];
52
+ if (primary) {
53
+ rowItems.push(
54
+ /* @__PURE__ */ jsx("a", { href: primary.href, class: "text-fg underline hover:text-accent", children: primary.label })
55
+ );
56
+ }
57
+ if (settings.githubUrl) {
58
+ rowItems.push(
59
+ /* @__PURE__ */ jsxs(
60
+ "a",
61
+ {
62
+ href: settings.githubUrl,
63
+ class: "inline-flex items-center gap-[0.3em] text-fg underline hover:text-accent",
64
+ target: "_blank",
65
+ rel: "noopener noreferrer",
66
+ children: [
67
+ /* @__PURE__ */ jsx("svg", { viewBox: "0 0 16 16", "aria-hidden": "true", class: "w-[1em] h-[1em] shrink-0", children: /* @__PURE__ */ jsx(
68
+ "path",
69
+ {
70
+ fill: "currentColor",
71
+ d: "M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"
72
+ }
73
+ ) }),
74
+ "GitHub"
75
+ ]
76
+ }
77
+ )
78
+ );
79
+ }
80
+ if (hasExtras) {
81
+ rowItems.push(resolvedExtras);
82
+ }
40
83
  return /* @__PURE__ */ jsxs(
41
84
  DocLayoutWithDefaults,
42
85
  {
@@ -55,46 +98,30 @@ function createHomePageView(ctx) {
55
98
  enableClientRouter: settings.dynamicPageTransition,
56
99
  children: [
57
100
  /* @__PURE__ */ jsx("div", { class: "flex justify-center mb-vsp-xl", children: /* @__PURE__ */ jsxs("div", { class: "flex flex-col items-center text-center gap-hsp-md lg:flex-row lg:text-left lg:gap-hsp-xl", children: [
58
- /* @__PURE__ */ jsx(
101
+ logoSetting === "auto" ? /* @__PURE__ */ jsx(
102
+ AutoLogo,
103
+ {
104
+ seed: settings.siteName,
105
+ class: "w-[320px] max-w-full aspect-[1200/630] text-fg shrink-0"
106
+ }
107
+ ) : logoSetting !== false ? /* @__PURE__ */ jsx(
59
108
  "div",
60
109
  {
61
110
  class: "w-[320px] max-w-full aspect-[1200/630] bg-fg shrink-0",
62
111
  style: {
63
- WebkitMask: `url(${logoUrl}) center/contain no-repeat`,
64
- mask: `url(${logoUrl}) center/contain no-repeat`
112
+ WebkitMask: `${cssUrl(withBase(logoSetting))} center/contain no-repeat`,
113
+ mask: `${cssUrl(withBase(logoSetting))} center/contain no-repeat`
65
114
  },
66
115
  "aria-hidden": "true"
67
116
  }
68
- ),
117
+ ) : null,
69
118
  /* @__PURE__ */ jsxs("div", { children: [
70
119
  /* @__PURE__ */ jsx("h1", { class: "text-heading font-bold mb-vsp-2xs", children: settings.siteName }),
71
120
  /* @__PURE__ */ jsx("p", { class: "text-muted text-small mb-vsp-sm", children: settings.siteDescription }),
72
- /* @__PURE__ */ jsxs("div", { class: "flex items-center justify-center lg:justify-start gap-hsp-md text-small", children: [
73
- overview && /* @__PURE__ */ jsxs(Fragment, { children: [
74
- /* @__PURE__ */ jsx("a", { href: overview, class: "text-fg underline hover:text-accent", children: t("nav.overview", locale) }),
75
- /* @__PURE__ */ jsx("span", { class: "text-muted", children: "/" })
76
- ] }),
77
- settings.githubUrl && /* @__PURE__ */ jsxs(
78
- "a",
79
- {
80
- href: settings.githubUrl,
81
- class: "inline-flex items-center gap-[0.3em] text-fg underline hover:text-accent",
82
- target: "_blank",
83
- rel: "noopener noreferrer",
84
- children: [
85
- /* @__PURE__ */ jsx("svg", { viewBox: "0 0 16 16", "aria-hidden": "true", class: "w-[1em] h-[1em] shrink-0", children: /* @__PURE__ */ jsx(
86
- "path",
87
- {
88
- fill: "currentColor",
89
- d: "M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"
90
- }
91
- ) }),
92
- "GitHub"
93
- ]
94
- }
95
- )
96
- ] }),
97
- resolvedExtras
121
+ /* @__PURE__ */ jsx("div", { class: "flex items-center justify-center lg:justify-start gap-hsp-md text-small", children: rowItems.map((item, index) => /* @__PURE__ */ jsxs(Fragment2, { children: [
122
+ index > 0 && /* @__PURE__ */ jsx("span", { class: "text-muted", children: "/" }),
123
+ item
124
+ ] }, index)) })
98
125
  ] })
99
126
  ] }) }),
100
127
  Island({
@@ -109,17 +136,47 @@ function createHomePageView(ctx) {
109
136
  }
110
137
  )
111
138
  }),
112
- settings.docTags && tagCount > 0 && /* @__PURE__ */ jsxs("section", { class: "mt-vsp-xl", children: [
113
- /* @__PURE__ */ jsx("h2", { class: "text-title font-bold mb-vsp-md", children: t("doc.allTags", locale) }),
139
+ settings.docTags && tagCount > 0 && /* @__PURE__ */ jsx("section", { class: "mt-vsp-xl", children: tags && tags.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
140
+ /* @__PURE__ */ jsx("h2", { class: "text-title font-bold mb-vsp-md", children: t("doc.tags", locale) }),
114
141
  /* @__PURE__ */ jsx(
142
+ TagNav,
143
+ {
144
+ variant: "all",
145
+ tags: tags.slice(0, tagLimit),
146
+ labels: {
147
+ tags: t("doc.tags", locale),
148
+ taggedWith: t("doc.taggedWith", locale)
149
+ }
150
+ }
151
+ ),
152
+ tags.length > tagLimit && /* @__PURE__ */ jsx("div", { class: "mt-vsp-sm text-title text-muted", "aria-hidden": "true", children: "\u2026" }),
153
+ /* @__PURE__ */ jsx("div", { class: "mt-vsp-md", children: /* @__PURE__ */ jsxs(
115
154
  "a",
116
155
  {
117
156
  href: withBase(`${prefix}/docs/tags`),
118
- class: "text-accent underline hover:text-accent-hover",
119
- children: t("doc.allTags", locale)
157
+ class: "group inline-flex items-center gap-hsp-xs text-accent hover:underline",
158
+ children: [
159
+ /* @__PURE__ */ jsx(CategoryLinkIcon, { className: "w-icon-sm text-accent" }),
160
+ /* @__PURE__ */ jsx("span", { children: t("doc.seeAllTags", locale) })
161
+ ]
120
162
  }
121
- )
122
- ] })
163
+ ) })
164
+ ] }) : (
165
+ // Legacy fallback: caller passed only `tagCount` (no tag list) —
166
+ // reproduce the pre-#3027 single "All Tags" link so existing
167
+ // `@takazudo/zudo-doc/home-page` consumers are unaffected.
168
+ /* @__PURE__ */ jsxs(Fragment, { children: [
169
+ /* @__PURE__ */ jsx("h2", { class: "text-title font-bold mb-vsp-md", children: t("doc.allTags", locale) }),
170
+ /* @__PURE__ */ jsx(
171
+ "a",
172
+ {
173
+ href: withBase(`${prefix}/docs/tags`),
174
+ class: "text-accent underline hover:text-accent-hover",
175
+ children: t("doc.allTags", locale)
176
+ }
177
+ )
178
+ ] })
179
+ ) })
123
180
  ]
124
181
  }
125
182
  );
@@ -1,6 +1,7 @@
1
1
  import type { RouteContext } from "../factory-context/index.js";
2
2
  import type { NavSourceOptions } from "../nav-source-docs/index.js";
3
3
  import type { DocNavNode } from "../doc-page-props/index.js";
4
+ import type { TagItem } from "../nav-indexing/types.js";
4
5
  export interface PrepareHomeDataOptions {
5
6
  /**
6
7
  * Override nav-source filtering. Default: `{}` for the default locale;
@@ -22,7 +23,15 @@ export interface HomeData {
22
23
  * `HomePageViewProps.tree`). */
23
24
  tree: DocNavNode[];
24
25
  categoryOrder: string[];
26
+ /** Unique tag count for the current locale — gates the home "Tags" section
27
+ * together with `settings.docTags`. Retained (equals `tags.length`) as the
28
+ * documented `HomePageViewProps.tagCount` gate stays part of the public
29
+ * `@takazudo/zudo-doc/home-page` API. */
25
30
  tagCount: number;
31
+ /** Alphabetically-sorted tag list (tag + doc count + pre-resolved,
32
+ * locale-prefixed href) for the home "Tags" section — spreads onto
33
+ * `HomePageViewProps.tags`. Empty when no tags exist for the locale. */
34
+ tags: TagItem[];
26
35
  }
27
36
  /**
28
37
  * Prepare the home-page nav tree / category order / tag count for `locale`.
@@ -26,11 +26,18 @@ function prepareHomeData(ctx, locale, options) {
26
26
  const tree = ctx.buildNavTree(navDocs, locale, categoryMeta, (slug, loc) => ctx.docsUrl(slug, loc));
27
27
  const categoryOrder = ctx.getCategoryOrder();
28
28
  const grouped = ctx.groupSatelliteNodes(tree, categoryOrder);
29
- const tagCount = ctx.collectTags(
29
+ const tagMap = ctx.collectTags(
30
30
  navDocs.filter((d) => !d.data.category_no_page),
31
31
  (entrySlug, data) => data.slug ?? ctx.toRouteSlug(entrySlug)
32
- ).size;
33
- return { tree: grouped, categoryOrder, tagCount };
32
+ );
33
+ const tagPrefix = locale === ctx.defaultLocale ? "" : `/${locale}`;
34
+ const tags = [...tagMap.values()].sort((a, b) => a.tag.localeCompare(b.tag, locale)).map((info) => ({
35
+ tag: info.tag,
36
+ count: info.count,
37
+ // Tag segment URL-encoded — href sites only; route params stay raw.
38
+ href: ctx.withBase(`${tagPrefix}/docs/tags/${encodeURIComponent(info.tag)}`)
39
+ }));
40
+ return { tree: grouped, categoryOrder, tagCount: tagMap.size, tags };
34
41
  }
35
42
  export {
36
43
  prepareHomeData
@@ -0,0 +1,40 @@
1
+ import type { HighlightCodeOptions, HighlightCodeResult } from "@takazudo/zfb-md-wasm";
2
+ type HighlightModule = Pick<typeof import("@takazudo/zfb-md-wasm"), "highlightCode">;
3
+ export type HighlightModuleImporter = () => Promise<HighlightModule>;
4
+ export interface HtmlPreviewHighlightRuntime {
5
+ highlightCode(code: string, options: HighlightCodeOptions): Promise<HighlightCodeResult>;
6
+ }
7
+ /**
8
+ * Build the HTML Preview's lazy zfb highlighting adapter.
9
+ *
10
+ * The cached value is only the public package-root module import. A rejected
11
+ * import is evicted so a later source-panel mount can retry a transient chunk
12
+ * load. Calls to `highlightCode` are deliberately not cached: the upstream
13
+ * package owns WASM initialization and trap recovery, while the component owns
14
+ * request cancellation and stale-result protection.
15
+ */
16
+ export declare function createHighlightRuntime(importModule: HighlightModuleImporter): HtmlPreviewHighlightRuntime;
17
+ /**
18
+ * Return markup only when zfb produced a usable result.
19
+ *
20
+ * An unknown non-empty language is a supported outcome: zfb returns safely
21
+ * escaped `hi-root` markup plus a warning. Error diagnostics and `html: null`
22
+ * represent invalid options/internal failure and must retain the JSX-escaped
23
+ * fallback instead.
24
+ */
25
+ export declare function getUsableHighlightHtml(result: HighlightCodeResult): string | null;
26
+ export interface HighlightRequestOptions {
27
+ code: string;
28
+ language: string;
29
+ onSettled: (html: string | null) => void;
30
+ runtime?: HtmlPreviewHighlightRuntime;
31
+ }
32
+ /**
33
+ * Start one highlight request and return its effect cleanup.
34
+ *
35
+ * Rejections are expected at this boundary (missing optional peer, resource
36
+ * load failure, or a current-call WASM trap). They resolve the UI to its plain
37
+ * fallback. Cleanup suppresses both successful and failed late completions.
38
+ */
39
+ export declare function startHighlightRequest({ code, language, onSettled, runtime, }: HighlightRequestOptions): () => void;
40
+ export {};
@@ -0,0 +1,58 @@
1
+ function createHighlightRuntime(importModule) {
2
+ let modulePromise = null;
3
+ function loadModule() {
4
+ if (!modulePromise) {
5
+ const pending = importModule().catch((error) => {
6
+ if (modulePromise === pending) {
7
+ modulePromise = null;
8
+ }
9
+ throw error;
10
+ });
11
+ modulePromise = pending;
12
+ }
13
+ return modulePromise;
14
+ }
15
+ return {
16
+ async highlightCode(code, options) {
17
+ const { highlightCode } = await loadModule();
18
+ return highlightCode(code, options);
19
+ }
20
+ };
21
+ }
22
+ const defaultRuntime = createHighlightRuntime(
23
+ () => import("@takazudo/zfb-md-wasm")
24
+ );
25
+ function getUsableHighlightHtml(result) {
26
+ if (result.html == null || result.diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
27
+ return null;
28
+ }
29
+ return result.html;
30
+ }
31
+ function startHighlightRequest({
32
+ code,
33
+ language,
34
+ onSettled,
35
+ runtime = defaultRuntime
36
+ }) {
37
+ let cancelled = false;
38
+ void Promise.resolve().then(() => runtime.highlightCode(code, { language })).then(
39
+ (result) => {
40
+ if (!cancelled) {
41
+ onSettled(getUsableHighlightHtml(result));
42
+ }
43
+ },
44
+ () => {
45
+ if (!cancelled) {
46
+ onSettled(null);
47
+ }
48
+ }
49
+ );
50
+ return () => {
51
+ cancelled = true;
52
+ };
53
+ }
54
+ export {
55
+ createHighlightRuntime,
56
+ getUsableHighlightHtml,
57
+ startHighlightRequest
58
+ };
@@ -6,8 +6,9 @@ export interface HighlightedCodeProps {
6
6
  language: string;
7
7
  }
8
8
  /**
9
- * Syntax-highlighted code block backed by Shiki. Falls back to a
10
- * plain `<pre><code>` block while Shiki is loading or if it fails.
9
+ * Syntax-highlighted code block backed by zfb's semantic-class WASM API.
10
+ * Falls back to a plain `<pre><code>` block while the runtime is loading or
11
+ * when the current request cannot produce safe markup.
11
12
  *
12
13
  * JSX port of src/components/html-preview/highlighted-code.tsx.
13
14
  */
@@ -1,41 +1,22 @@
1
1
  import { jsx } from "preact/jsx-runtime";
2
2
  import { useEffect, useState } from "preact/hooks";
3
- let highlighterPromise = null;
4
- function getHighlighter() {
5
- if (!highlighterPromise) {
6
- highlighterPromise = import("shiki").then(
7
- ({ createHighlighter }) => createHighlighter({
8
- themes: ["catppuccin-latte", "vitesse-dark"],
9
- langs: ["html", "css", "javascript"]
10
- })
11
- ).catch((err) => {
12
- highlighterPromise = null;
13
- throw err;
14
- });
15
- }
16
- return highlighterPromise;
17
- }
3
+ import { startHighlightRequest } from "./highlight-runtime.js";
18
4
  function HighlightedCode({
19
5
  code,
20
6
  language
21
7
  }) {
22
- const [html, setHtml] = useState(null);
8
+ const [highlighted, setHighlighted] = useState(null);
9
+ const html = highlighted?.code === code && highlighted.language === language ? highlighted.html : null;
23
10
  useEffect(() => {
24
- let cancelled = false;
25
- getHighlighter().then((highlighter) => {
26
- if (cancelled) return;
27
- const lang = highlighter.getLoadedLanguages().includes(language) ? language : "text";
28
- const result = highlighter.codeToHtml(code, {
29
- lang,
30
- themes: { light: "catppuccin-latte", dark: "vitesse-dark" },
31
- defaultColor: false
32
- });
33
- setHtml(result);
34
- }).catch(() => {
11
+ return startHighlightRequest({
12
+ code,
13
+ language,
14
+ onSettled(nextHtml) {
15
+ setHighlighted(
16
+ nextHtml == null ? null : { code, language, html: nextHtml }
17
+ );
18
+ }
35
19
  });
36
- return () => {
37
- cancelled = true;
38
- };
39
20
  }, [code, language]);
40
21
  if (!html) {
41
22
  return /* @__PURE__ */ jsx("pre", { class: "m-0 p-hsp-md bg-code-bg text-caption leading-relaxed overflow-x-auto", children: /* @__PURE__ */ jsx("code", { class: "font-mono whitespace-pre", children: code }) });
@@ -29,6 +29,7 @@ const defaultTranslations = {
29
29
  "doc.tags": "Tags",
30
30
  "doc.taggedWith": "Pages tagged with",
31
31
  "doc.allTags": "All Tags",
32
+ "doc.seeAllTags": "See all tags",
32
33
  "doc.created": "Created",
33
34
  "doc.updated": "Updated",
34
35
  "doc.noTags": "No tags found.",
@@ -87,6 +88,7 @@ const defaultTranslations = {
87
88
  "doc.tags": "\u30BF\u30B0",
88
89
  "doc.taggedWith": "\u30BF\u30B0\u4ED8\u304D\u30DA\u30FC\u30B8",
89
90
  "doc.allTags": "\u3059\u3079\u3066\u306E\u30BF\u30B0",
91
+ "doc.seeAllTags": "\u3059\u3079\u3066\u306E\u30BF\u30B0\u3092\u898B\u308B",
90
92
  "doc.created": "\u4F5C\u6210",
91
93
  "doc.updated": "\u66F4\u65B0",
92
94
  "doc.noTags": "\u30BF\u30B0\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
@@ -146,6 +148,7 @@ const defaultTranslations = {
146
148
  "doc.tags": "Tags",
147
149
  "doc.taggedWith": "Seiten mit Tag",
148
150
  "doc.allTags": "Alle Tags",
151
+ "doc.seeAllTags": "Alle Tags anzeigen",
149
152
  "doc.created": "Erstellt",
150
153
  "doc.updated": "Aktualisiert",
151
154
  "doc.noTags": "Keine Tags gefunden.",