boltdocs 3.2.1 → 3.3.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 (155) hide show
  1. package/bin/boltdocs.js +20 -1
  2. package/bin/preloader.mjs +28 -0
  3. package/dist/{adapters-ByYY44Zy.mjs → adapters-BmB4TKCc.mjs} +1 -1
  4. package/dist/{adapters-DvmtpdSF.cjs → adapters-Cp1Maurn.cjs} +1 -1
  5. package/dist/audit-CLXjMzwQ.mjs +11 -0
  6. package/dist/audit-COgjnQ5n.cjs +11 -0
  7. package/dist/build-BS2hefiL.cjs +6 -0
  8. package/dist/build-C5mNh9D-.mjs +6 -0
  9. package/dist/cache-CJE9nWLE.cjs +6 -0
  10. package/dist/cache-OY4HzIXl.mjs +6 -0
  11. package/dist/callout-oI_LCbxS.d.ts +71 -0
  12. package/dist/callout-plqx9Dw-.d.cts +71 -0
  13. package/dist/client/index.cjs +1265 -569
  14. package/dist/client/index.d.cts +3 -542
  15. package/dist/client/index.d.ts +3 -542
  16. package/dist/client/index.js +1167 -521
  17. package/dist/client/mdx.cjs +1 -1
  18. package/dist/client/mdx.d.cts +24 -2
  19. package/dist/client/mdx.d.ts +24 -2
  20. package/dist/client/mdx.js +1 -1
  21. package/dist/client/primitives.cjs +14 -4
  22. package/dist/client/primitives.d.cts +281 -38
  23. package/dist/client/primitives.d.ts +281 -38
  24. package/dist/client/primitives.js +5 -5
  25. package/dist/client/router.cjs +49 -0
  26. package/dist/client/router.d.cts +3 -0
  27. package/dist/client/router.d.ts +3 -0
  28. package/dist/client/router.js +8 -0
  29. package/dist/client/theme/reset.css +40 -4
  30. package/dist/{config-BpzaPsJj.d.cts → config-BoRlz0Ca.d.mts} +157 -14
  31. package/dist/{config-xtS0ZkMm.d.mts → config-CTsX_tGM.d.cts} +157 -14
  32. package/dist/config-Ceib-AOH.cjs +7 -0
  33. package/dist/config-DGLQsy7y.mjs +7 -0
  34. package/dist/{package-BcISI5-P.cjs → config-files-BYHDOrKn.mjs} +1 -1
  35. package/dist/config-files-D9cdBMl2.cjs +6 -0
  36. package/dist/config-jJh8WYJI.cjs +6 -0
  37. package/dist/csp-C4LmN1ko.cjs +6 -0
  38. package/dist/csp-D6JEOqKh.mjs +6 -0
  39. package/dist/dev-Ch3hEe5Y.cjs +6 -0
  40. package/dist/dev-QPNxysrd.mjs +6 -0
  41. package/dist/{docs-layout-BdtZ9z6s.js → docs-layout-CbZ7LDRe.js} +504 -249
  42. package/dist/{docs-layout-CFWI15Id.cjs → docs-layout-vzfXed1Q.cjs} +516 -254
  43. package/dist/{doctor-BBtmNJE0.cjs → doctor-B1_YL0Wt.cjs} +1 -1
  44. package/dist/doctor-C7whl4ED.cjs +6 -0
  45. package/dist/doctor-DmQnml2k.mjs +6 -0
  46. package/dist/{generator-COepVWGi.cjs → generator-BlW45ST7.cjs} +5 -3
  47. package/dist/{generator-CcOBN5vI.mjs → generator-BwknNejd.mjs} +5 -3
  48. package/dist/{handler-XX7cLTqe.cjs → handler-CDEoKARD.cjs} +2 -2
  49. package/dist/{handler-DzQae4eA.mjs → handler-D7UyZlPm.mjs} +2 -2
  50. package/dist/{image-Bz87fCla.js → image-B-NDSDuY.js} +129 -11
  51. package/dist/{image-C4TlpMHn.cjs → image-Hnaa2bJd.cjs} +175 -9
  52. package/dist/index-C56bL-A-.d.cts +1344 -0
  53. package/dist/{sidebar-COTaMHR-.d.ts → index-CPUUKzxD.d.cts} +344 -327
  54. package/dist/{sidebar-CrEdJuHX.d.cts → index-Ci4kfOHF.d.ts} +344 -327
  55. package/dist/index-Cu9H6CRa.d.ts +1344 -0
  56. package/dist/link-CgdI6O2o.js +872 -0
  57. package/dist/{link-D1Gjkb-2.cjs → link-vZjmD-es.cjs} +406 -223
  58. package/dist/{mdx-BuO6a1f7.js → mdx-BRT71V5Q.js} +211 -168
  59. package/dist/{mdx-CD-tSjRG.cjs → mdx-D0Absdq5.cjs} +225 -169
  60. package/dist/native-BNCY7PRF.cjs +6 -0
  61. package/dist/native-Di9Fnfrv.mjs +6 -0
  62. package/dist/native-Kre2T5eS.cjs +6 -0
  63. package/dist/node/cache.cjs +1 -1
  64. package/dist/node/cache.d.cts +19 -3
  65. package/dist/node/cache.d.mts +19 -3
  66. package/dist/node/cache.mjs +1 -1
  67. package/dist/node/cli-entry.cjs +1 -9
  68. package/dist/node/cli-entry.mjs +1 -9
  69. package/dist/node/index.cjs +1 -1
  70. package/dist/node/index.d.cts +245 -17
  71. package/dist/node/index.d.mts +246 -18
  72. package/dist/node/index.mjs +1 -1
  73. package/dist/node/mdx/shiki-adapter.cjs +1 -1
  74. package/dist/node/mdx/shiki-adapter.d.cts +24 -3
  75. package/dist/node/mdx/shiki-adapter.d.mts +24 -2
  76. package/dist/node/mdx/shiki-adapter.mjs +1 -1
  77. package/dist/node-Du5UL5wo.mjs +23 -0
  78. package/dist/node-DunuGqyp.cjs +23 -0
  79. package/dist/{package-CCYEAYeA.mjs → package-DA848keM.mjs} +1 -1
  80. package/dist/package-wplzySjz.cjs +6 -0
  81. package/dist/pages-external-BBw8ObVK.mjs +6 -0
  82. package/dist/pages-external-DPiPFxxP.cjs +6 -0
  83. package/dist/parser-BqTOsEUa.cjs +6 -0
  84. package/dist/parser-CGMY2lJ7.mjs +6 -0
  85. package/dist/plugin-BcTzvHAJ.mjs +69 -0
  86. package/dist/plugin-TulVMt1z.cjs +69 -0
  87. package/dist/plugin-context-C-l90hFt.cjs +15 -0
  88. package/dist/plugin-context-wqHNSZum.mjs +15 -0
  89. package/dist/plugin-lifecycle-B1BzSHdn.cjs +6 -0
  90. package/dist/resolve-B7vWOd8R.cjs +6 -0
  91. package/dist/resolve-C2kKnk_5.mjs +6 -0
  92. package/dist/resolve-It1Iug83.cjs +6 -0
  93. package/dist/resolve-YdTrt_FG.mjs +6 -0
  94. package/dist/router-C3kygdRK.cjs +1285 -0
  95. package/dist/router-CZBxMRAH.js +987 -0
  96. package/dist/routes-BL4y79tb.mjs +6 -0
  97. package/dist/routes-C2nPecwu.cjs +6 -0
  98. package/dist/routes-U9L8psZ9.cjs +6 -0
  99. package/dist/{search-dialog-DHqwjJuG.cjs → search-dialog-BCOHQGSt.cjs} +1 -1
  100. package/dist/search-dialog-Bp_P1MPP.js +481 -0
  101. package/dist/{search-dialog-DFnlx8LO.js → search-dialog-BqkzDTq2.js} +107 -57
  102. package/dist/{search-dialog-BRFno7HD.cjs → search-dialog-CIeIUhAn.cjs} +218 -43
  103. package/dist/{search-dialog-D6TvjcuV.js → search-dialog-DtmxmfEu.js} +1 -1
  104. package/dist/{search-dialog-B1NERUNx.cjs → search-dialog-x_qHU8ty.cjs} +107 -58
  105. package/dist/server/index.cjs +1 -1
  106. package/dist/server/index.d.mts +1 -1
  107. package/dist/server/index.mjs +1 -1
  108. package/dist/update-check-BKqmYfwT.cjs +11 -0
  109. package/dist/update-check-QnrVR3q1.mjs +11 -0
  110. package/dist/utils-8lzafE49.mjs +7 -0
  111. package/dist/utils-ej6_SjIZ.cjs +7 -0
  112. package/dist/virtual-modules-Ca5tHk1X.cjs +30 -0
  113. package/dist/virtual-modules-Dlgt1fP0.mjs +30 -0
  114. package/package.json +18 -16
  115. package/src/shared/mdx-path.ts +12 -0
  116. package/src/shared/structured-data.ts +145 -0
  117. package/src/shared/types.ts +220 -16
  118. package/dist/banner-BMop1KLF.d.ts +0 -100
  119. package/dist/banner-CVD0GugR.d.cts +0 -100
  120. package/dist/cache-CO4Dy2uQ.cjs +0 -6
  121. package/dist/cache-Dk53iOo6.mjs +0 -6
  122. package/dist/config-CCXp3aXO.mjs +0 -7
  123. package/dist/config-CMd4XoFq.cjs +0 -6
  124. package/dist/config-DlGoEM9D.cjs +0 -7
  125. package/dist/csp-CRp_3U7a.cjs +0 -6
  126. package/dist/csp-oTC2ygvF.mjs +0 -6
  127. package/dist/doctor-Bv16QSrI.cjs +0 -7
  128. package/dist/doctor-DOAbSaiL.mjs +0 -7
  129. package/dist/headers-Ddwek8K7.mjs +0 -6
  130. package/dist/headers-dg4SDrjY.cjs +0 -6
  131. package/dist/link-2PzgTnaP.js +0 -673
  132. package/dist/meta-loader-C4Hu9RN9.mjs +0 -6
  133. package/dist/meta-loader-Dq1zcFnh.cjs +0 -6
  134. package/dist/node-BXtqM1uH.mjs +0 -6
  135. package/dist/node-BlVk4EVg.cjs +0 -104
  136. package/dist/node-DOf4zhWc.mjs +0 -104
  137. package/dist/node-aD1eisrR.cjs +0 -6
  138. package/dist/pages-external-BYa4JSda.mjs +0 -6
  139. package/dist/pages-external-XkYIs-d5.cjs +0 -6
  140. package/dist/parser-DCiEWkSk.cjs +0 -8
  141. package/dist/parser-qk9FIHJ7.mjs +0 -8
  142. package/dist/plugin-C95kvu6h.mjs +0 -73
  143. package/dist/plugin-DM3u2bVj.cjs +0 -73
  144. package/dist/routes-BW6zsk7Z.mjs +0 -6
  145. package/dist/routes-BgoIB5wb.cjs +0 -6
  146. package/dist/routes-CORVxc02.cjs +0 -6
  147. package/dist/search-dialog-BmxHiKwt.js +0 -312
  148. package/dist/shiki-adapter-CoQ6jyJ4.cjs +0 -6
  149. package/dist/shiki-adapter-D-aKw7PP.mjs +0 -6
  150. package/dist/utils-Crm9oPcV.mjs +0 -7
  151. package/dist/utils-D6Xtj0FZ.cjs +0 -7
  152. /package/dist/{errors-0zGHRLue.mjs → errors-N-b6Sn2G.mjs} +0 -0
  153. /package/dist/{index-1KsTDq4o.d.mts → index-lFoyzCXy.d.mts} +0 -0
  154. /package/dist/{types-generator-BXhv7Ill.cjs → types-generator-DDSA93P3.cjs} +0 -0
  155. /package/dist/{types-generator-bDPvUztF.mjs → types-generator-DG1GX1EZ.mjs} +0 -0
@@ -3,21 +3,19 @@
3
3
  * Copyright (c) 2026 Jesus Alcala
4
4
  * Licensed under the MIT License.
5
5
  */
6
- import { A as Monitor, B as useConfig, E as Languages, F as Sun, H as useBoltdocsContext, I as TextAlignStart, L as X, N as Pencil, R as icons_exports, V as BoltdocsProvider, a as useRoutes, b as FaceGood, d as Check, f as ChevronDown, g as Copy, h as CircleHelp, i as useLocalizedTo, j as Moon, k as Menu, o as normalizePath, r as cn, s as RoutesProvider, t as Link$1, u as ArrowLeft, v as ExternalLink, w as Home, x as FaceRegular, y as FaceBad, z as ConfigContext } from "../link-2PzgTnaP.js";
7
- import { c as ThemeProvider, l as useTheme } from "../image-Bz87fCla.js";
8
- import { i as copyToClipboard, n as Timeline, r as useCodeBlock, t as mdx_components_default } from "../mdx-BuO6a1f7.js";
9
- import { A as useUI, C as useSidebar, D as ButtonGroup, O as Breadcrumbs$2, S as useLocation, T as Menu$1, f as PageNav$1, k as UIProvider, m as OnThisPage$1, n as Sidebar$1, t as DocsLayout$1, w as Tabs$1, x as useSearchHighlight } from "../docs-layout-BdtZ9z6s.js";
10
- import { i as ErrorBoundary$1, n as Navbar$1, o as Button$1, r as Github } from "../search-dialog-DFnlx8LO.js";
11
- import { n as useSearch, r as InternalErrorBoundary, t as SearchDialog } from "../search-dialog-BmxHiKwt.js";
6
+ import { A as Languages, B as TextAlignStart, C as FaceBad, F as Moon, K as BoltdocsProvider, L as Pencil, N as Menu, O as Home, P as Monitor, S as ExternalLink, T as FaceRegular, U as ConfigContext, V as X, W as useConfig, _ as ChevronLeft, a as useViewTransition, b as Copy, c as RoutesProvider, d as resolvePublicAssetUrl, g as ChevronDown, h as Check, i as startViewTransition, m as ArrowLeft, o as cn, q as useBoltdocsContext, s as useLocalizedTo, t as Link, u as normalizePath, v as ChevronRight, w as FaceGood, y as CircleHelp, z as Sun } from "../link-CgdI6O2o.js";
7
+ import { A as useRouteData, B as normalizeBasename, C as Outlet, D as useMatches, E as useLocation, F as PrefetchContext, I as RouteDataContext, L as defaultNavigate, M as LocationProvider, N as MatchesContext, O as useNavigate, P as NavigateContext, R as addBasename, S as resolveRouteBranch, T as useLoaderData, V as stripBasename, _ as stripSiteProtocol, a as getConfiguredVersions, b as matchRouteBranch, c as hasUriScheme, d as isConfiguredVersion, f as normalizeUrlBase, g as splitUrlReference, h as resolveUrlReference, i as getConfiguredLocales, j as LocationContext, k as usePrefetch, l as hasUrlBase, m as parseUrlReference, n as buildUrl, o as getVersionPrefixSegments, p as normalizeUrlPath, r as classifyUrlPath, s as getVersionSegments, t as addUrlBase, u as isConfiguredLocale, v as stripUrlBase, w as OutletContext, x as matchRouteBranchWithParams, y as RouteRenderer, z as hasBasename } from "../router-CZBxMRAH.js";
8
+ import { _ as useTheme, g as ThemeProvider } from "../image-B-NDSDuY.js";
9
+ import { a as useCopyButton, i as useExpandable, n as Timeline, o as copyToClipboard, r as useCodeBlockFeedback, t as mdx_components_default } from "../mdx-BRT71V5Q.js";
10
+ import { A as UIProvider, C as IconRenderer, E as Menu$1, O as ButtonGroup, S as useSidebar, T as Tabs$1, f as PageNav$1, j as useUI, k as Breadcrumbs$2, m as OnThisPage$1, n as Sidebar$1, t as DocsLayout$1, w as resolveIcon, x as useSearchHighlight } from "../docs-layout-CbZ7LDRe.js";
11
+ import { i as useRoutes, n as useSearch, r as InternalErrorBoundary, t as SearchDialog } from "../search-dialog-Bp_P1MPP.js";
12
+ import { i as ErrorBoundary$1, n as Navbar$1, o as Button$1, r as Github } from "../search-dialog-BqkzDTq2.js";
12
13
  import { ViteReactSSG } from "@bdocs/ssg";
13
- import { Suspense, createContext, isValidElement, lazy, use, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
14
- import { Link, Outlet, useLoaderData, useLocation as useLocation$1, useMatches, useNavigate } from "react-router-dom";
15
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
14
+ import { Suspense, createContext, isValidElement, lazy, use, useCallback, useEffect, useMemo, useRef, useState } from "react";
15
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
16
16
  import { Button } from "react-aria-components";
17
17
  import * as ReactHelmetAsync from "react-helmet-async";
18
18
  import virtualCustomComponents from "virtual:boltdocs-mdx-components";
19
- import virtualIcons from "virtual:boltdocs-icons";
20
- import DOMPurify from "isomorphic-dompurify";
21
19
  import UserLayout from "virtual:boltdocs-layout";
22
20
 
23
21
  //#region src/client/app/mdx-components-context.tsx
@@ -52,12 +50,13 @@ function MdxComponentsProvider({ components, children }) {
52
50
  //#endregion
53
51
  //#region src/client/app/scroll-handler.tsx
54
52
  /**
55
- * Handles scroll restoration and hash scrolling on navigation.
56
- * It ensures the page scrolls to top on pathname changes,
57
- * or specifically to an anchor element if a hash is present.
53
+ * Handles scroll restoration after navigation.
54
+ *
55
+ * Normal page navigation returns the active content container to the top
56
+ * instantly. Explicit hash navigation is positioned at its anchor instead.
58
57
  */
59
58
  function ScrollHandler() {
60
- const { pathname, hash } = useLocation$1();
59
+ const { pathname, hash } = useLocation();
61
60
  const handleScroll = (behavior = "auto") => {
62
61
  const container = document.querySelector(".boltdocs-content") || document.querySelector(".boltdocs-external-content") || document.querySelector(".boltdocs-shell-content") || window;
63
62
  const getScrollTop = () => {
@@ -75,7 +74,10 @@ function ScrollHandler() {
75
74
  });
76
75
  };
77
76
  if (hash) {
78
- const id = hash.replace("#", "");
77
+ let id = hash.slice(1);
78
+ try {
79
+ id = decodeURIComponent(id);
80
+ } catch {}
79
81
  const element = document.getElementById(id);
80
82
  if (element) {
81
83
  const offset = 80;
@@ -84,23 +86,43 @@ function ScrollHandler() {
84
86
  return true;
85
87
  }
86
88
  }
87
- scrollTo(0, behavior);
89
+ if (container === window) {
90
+ document.documentElement.scrollTop = 0;
91
+ document.body.scrollTop = 0;
92
+ } else container.scrollTop = 0;
88
93
  return false;
89
94
  };
90
- useLayoutEffect(() => {
91
- handleScroll("auto");
92
- }, [pathname, hash]);
93
95
  useEffect(() => {
94
- handleScroll("auto");
96
+ const handleRouteCommit = (event) => {
97
+ const detail = event.detail;
98
+ if (detail?.pathname && detail.pathname !== pathname) return;
99
+ if (detail?.hash !== void 0 && detail.hash !== hash) return;
100
+ handleScroll("auto");
101
+ };
102
+ window.addEventListener("boltdocs:route-commit", handleRouteCommit);
103
+ let frame = 0;
104
+ if (hash) {
105
+ let attempts = 0;
106
+ const retry = () => {
107
+ if (handleScroll("auto") || attempts >= 8) return;
108
+ attempts++;
109
+ frame = requestAnimationFrame(retry);
110
+ };
111
+ frame = requestAnimationFrame(retry);
112
+ }
113
+ return () => {
114
+ window.removeEventListener("boltdocs:route-commit", handleRouteCommit);
115
+ if (frame) cancelAnimationFrame(frame);
116
+ };
95
117
  }, [pathname, hash]);
96
118
  return null;
97
119
  }
98
120
 
99
121
  //#endregion
100
122
  //#region src/client/components/ui-base/not-found.tsx
101
- function NotFound() {
123
+ function NotFound({ className }) {
102
124
  return /* @__PURE__ */ jsx("div", {
103
- className: "flex items-center justify-center min-h-[65vh] text-center px-4",
125
+ className: cn("flex items-center justify-center min-h-[65vh] text-center px-4", className),
104
126
  children: /* @__PURE__ */ jsxs("div", {
105
127
  className: "space-y-6 max-w-md mx-auto p-8 border border-subtle bg-surface rounded-2xl shadow-xs",
106
128
  children: [
@@ -118,7 +140,7 @@ function NotFound() {
118
140
  children: "The page you're looking for doesn't exist or has been moved."
119
141
  })]
120
142
  }),
121
- /* @__PURE__ */ jsxs(Link$1, {
143
+ /* @__PURE__ */ jsxs(Link, {
122
144
  href: "/",
123
145
  className: "inline-flex items-center gap-2 rounded-xl border border-subtle bg-main px-6 py-2.5 text-xs font-semibold text-body hover:bg-primary-50/50 hover:border-primary-500/50 transition-all duration-300 outline-none select-none",
124
146
  children: [/* @__PURE__ */ jsx(ArrowLeft, { size: 14 }), " Go to Home"]
@@ -143,15 +165,14 @@ const mod = ReactHelmetAsync;
143
165
  * The `<Helmet>` component, resolved across CJS/ESM module shapes.
144
166
  * Falls back to a transparent fragment wrapper if the module cannot be resolved.
145
167
  */
146
- const Helmet = mod.Helmet || mod.default?.Helmet || (({ children }) => /* @__PURE__ */ jsx(Fragment, { children }));
168
+ const Helmet = mod.Helmet || mod.default?.Helmet || (({ children }) => /* @__PURE__ */ jsx(Fragment$1, { children }));
147
169
  /**
148
170
  * The `<HelmetProvider>` component, resolved across CJS/ESM module shapes.
149
171
  * Falls back to a transparent fragment wrapper if the module cannot be resolved.
150
172
  */
151
- const HelmetProvider = mod.HelmetProvider || mod.default?.HelmetProvider || (({ children }) => /* @__PURE__ */ jsx(Fragment, { children }));
173
+ const HelmetProvider = mod.HelmetProvider || mod.default?.HelmetProvider || (({ children }) => /* @__PURE__ */ jsx(Fragment$1, { children }));
152
174
  if (typeof globalThis !== "undefined" && globalThis.__BOLTDOCS_SSG_RENDERING__) {
153
- const hp = HelmetProvider;
154
- if (hp && typeof hp === "function" && hp.canUseDOM) hp.canUseDOM = false;
175
+ if (typeof HelmetProvider === "function" && HelmetProvider.canUseDOM) HelmetProvider.canUseDOM = false;
155
176
  }
156
177
  if (typeof globalThis !== "undefined") {
157
178
  globalThis.__BOLTDOCS_HELMET_PROVIDER__ = HelmetProvider;
@@ -175,16 +196,120 @@ function getTranslated(value, locale) {
175
196
  return Object.values(value)[0] || "";
176
197
  }
177
198
 
199
+ //#endregion
200
+ //#region src/shared/structured-data.ts
201
+ /** Preserve literal JSON-LD types in config files and frontmatter helpers. */
202
+ function defineStructuredData(data) {
203
+ return data;
204
+ }
205
+ /**
206
+ * Creates one JSON-LD value from the common website/article/breadcrumb presets.
207
+ * Single-node input returns an object; multiple nodes return a graph array.
208
+ */
209
+ function createStructuredData(options) {
210
+ const nodes = [];
211
+ if (options.website) nodes.push(createWebSiteStructuredData(options.website));
212
+ if (options.article) nodes.push(createArticleStructuredData(options.article));
213
+ if (options.breadcrumbs?.length) nodes.push(createBreadcrumbStructuredData(options.breadcrumbs));
214
+ if (options.additional) nodes.push(...Array.isArray(options.additional) ? options.additional : [options.additional]);
215
+ if (nodes.length === 0) throw new Error("createStructuredData() requires at least one of website, article, breadcrumbs, or additional");
216
+ return nodes.length === 1 ? nodes[0] : nodes;
217
+ }
218
+ function createWebSiteStructuredData(options) {
219
+ const data = {
220
+ "@context": "https://schema.org",
221
+ "@type": "WebSite",
222
+ name: options.name,
223
+ url: options.url
224
+ };
225
+ if (options.description) data.description = options.description;
226
+ if (options.searchUrl) data.potentialAction = {
227
+ "@type": "SearchAction",
228
+ target: `${options.searchUrl}{search_term_string}`,
229
+ "query-input": "required name=search_term_string"
230
+ };
231
+ return data;
232
+ }
233
+ function toIsoDate(value, field) {
234
+ const date = new Date(value);
235
+ if (Number.isNaN(date.getTime())) throw new TypeError(`Invalid ${field} value: ${String(value)}`);
236
+ return date.toISOString();
237
+ }
238
+ function createArticleStructuredData(options) {
239
+ const data = {
240
+ "@context": "https://schema.org",
241
+ "@type": "Article",
242
+ headline: options.headline,
243
+ url: options.url
244
+ };
245
+ if (options.description) data.description = options.description;
246
+ if (options.datePublished) data.datePublished = toIsoDate(options.datePublished, "datePublished");
247
+ if (options.dateModified) data.dateModified = toIsoDate(options.dateModified, "dateModified");
248
+ if (options.image) data.image = options.image;
249
+ if (options.author) data.author = typeof options.author === "string" ? {
250
+ "@type": "Person",
251
+ name: options.author
252
+ } : {
253
+ "@type": "Person",
254
+ ...options.author
255
+ };
256
+ return data;
257
+ }
258
+ function createBreadcrumbStructuredData(items) {
259
+ return {
260
+ "@context": "https://schema.org",
261
+ "@type": "BreadcrumbList",
262
+ itemListElement: items.map((item, index) => ({
263
+ "@type": "ListItem",
264
+ position: index + 1,
265
+ name: item.name,
266
+ item: item.url
267
+ }))
268
+ };
269
+ }
270
+
271
+ //#endregion
272
+ //#region src/client/components/structured-data.tsx
273
+ /**
274
+ * Renders safe, valid JSON-LD. Escaping `<` prevents a JSON value from closing
275
+ * the surrounding script tag when the document is rendered server-side.
276
+ */
277
+ function StructuredData({ data, id }) {
278
+ return /* @__PURE__ */ jsx("script", {
279
+ id,
280
+ type: "application/ld+json",
281
+ dangerouslySetInnerHTML: { __html: JSON.stringify(data).replace(/</g, "\\u003c") }
282
+ });
283
+ }
284
+
178
285
  //#endregion
179
286
  //#region src/client/app/head.tsx
287
+ function stringifyMetaValue(value) {
288
+ if (value == null) return "";
289
+ if (typeof value === "string") return value;
290
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
291
+ if (Array.isArray(value)) return value.map(stringifyMetaValue).join(", ");
292
+ if (typeof value === "object") {
293
+ const record = value;
294
+ const namedValue = record.name ?? record.value ?? record.url;
295
+ if (namedValue !== void 0) return stringifyMetaValue(namedValue);
296
+ try {
297
+ return JSON.stringify(value);
298
+ } catch {
299
+ return "";
300
+ }
301
+ }
302
+ return String(value);
303
+ }
180
304
  function Head({ siteTitle, siteDescription, routes }) {
181
- const location = useLocation$1();
305
+ const location = useLocation();
182
306
  const config = useConfig();
183
307
  const { currentLocale, currentRoute } = useRoutes();
184
308
  const route = useMemo(() => {
185
309
  if (currentRoute) return currentRoute;
186
310
  const normalizedPath = location.pathname.endsWith("/") && location.pathname.length > 1 ? location.pathname.slice(0, -1) : location.pathname;
187
311
  return routes?.find?.((r) => {
312
+ if (!r?.path) return false;
188
313
  return (r.path.endsWith("/") && r.path.length > 1 ? r.path.slice(0, -1) : r.path) === normalizedPath;
189
314
  });
190
315
  }, [
@@ -198,18 +323,23 @@ function Head({ siteTitle, siteDescription, routes }) {
198
323
  const translatedSiteTitle = getTranslated(siteTitle, currentLocale);
199
324
  const finalTitle = pageTitle ? `${pageTitle} | ${translatedSiteTitle}` : translatedSiteTitle;
200
325
  const seo = route?.seo || {};
201
- const canonicalUrl = seo.canonical || (config?.siteUrl && route?.path ? `${config.siteUrl.replace(/\/$/, "")}${route.path}` : void 0);
202
- const ogUrl = seo["og:url"] || canonicalUrl || (typeof window !== "undefined" ? window.location.href : void 0);
203
- const globalMetatags = config?.seo?.metatags || {};
326
+ const structuredData = [...Array.isArray(config?.seo?.structuredData) ? config.seo.structuredData : config?.seo?.structuredData ? [config.seo.structuredData] : [], ...Array.isArray(seo.structuredData) ? seo.structuredData : seo.structuredData ? [seo.structuredData] : []];
327
+ const canonicalUrl = stringifyMetaValue(seo.canonical) || (config?.siteUrl && route?.path ? `${config.siteUrl.replace(/\/$/, "")}${route.path}` : void 0);
328
+ const ogUrl = stringifyMetaValue(seo["og:url"]) || canonicalUrl || (typeof window !== "undefined" ? window.location.href : void 0);
204
329
  const defaultOgImage = config?.seo?.thumbnails?.background;
205
- let ogImage = seo["og:image"] || route?.coverImage || defaultOgImage;
206
- if (ogImage && config?.siteUrl && !/^https?:\/\/|^\/\//.test(ogImage)) ogImage = `${config.siteUrl.endsWith("/") ? config.siteUrl.slice(0, -1) : config.siteUrl}${ogImage.startsWith("/") ? ogImage : `/${ogImage}`}`;
330
+ let ogImage = stringifyMetaValue(seo["og:image"]) || route?.coverImage || defaultOgImage;
331
+ if (ogImage && !/^https?:\/\/|^\/\//.test(ogImage)) {
332
+ const assetPath = resolvePublicAssetUrl(ogImage.startsWith("/") ? ogImage : `/${ogImage}`, config.base);
333
+ if (config?.siteUrl) ogImage = `${config.siteUrl.replace(/\/$/, "")}${assetPath}`;
334
+ else ogImage = assetPath;
335
+ }
207
336
  return /* @__PURE__ */ jsxs(Helmet, { children: [
208
337
  /* @__PURE__ */ jsx("title", { children: finalTitle }),
209
338
  /* @__PURE__ */ jsx("meta", {
210
339
  name: "description",
211
340
  content: pageDescription
212
341
  }),
342
+ structuredData.map((data) => /* @__PURE__ */ jsx(StructuredData, { data }, JSON.stringify(data))),
213
343
  /* @__PURE__ */ jsx("meta", {
214
344
  property: "og:title",
215
345
  content: finalTitle
@@ -250,41 +380,6 @@ function Head({ siteTitle, siteDescription, routes }) {
250
380
  property: "og:image",
251
381
  content: ogImage
252
382
  }),
253
- /* @__PURE__ */ jsx("meta", {
254
- name: "generator",
255
- content: "Boltdocs"
256
- }),
257
- config?.seo?.verification?.google && /* @__PURE__ */ jsx("meta", {
258
- name: "google-site-verification",
259
- content: config.seo.verification.google
260
- }),
261
- config?.seo?.verification?.bing && /* @__PURE__ */ jsx("meta", {
262
- name: "msvalidate.01",
263
- content: config.seo.verification.bing
264
- }),
265
- config?.seo?.verification?.yandex && /* @__PURE__ */ jsx("meta", {
266
- name: "yandex-verification",
267
- content: config.seo.verification.yandex
268
- }),
269
- config?.seo?.verification?.pinterest && /* @__PURE__ */ jsx("meta", {
270
- name: "p:domain_verify",
271
- content: config.seo.verification.pinterest
272
- }),
273
- config?.seo?.verification?.facebook && /* @__PURE__ */ jsx("meta", {
274
- name: "facebook-domain-verification",
275
- content: config.seo.verification.facebook
276
- }),
277
- Object.entries(globalMetatags).map(([key, value]) => {
278
- const isProperty = key.startsWith("og:") || key.startsWith("music:") || key.startsWith("video:") || key.startsWith("article:") || key.startsWith("book:") || key.startsWith("profile:");
279
- const strVal = String(value ?? "");
280
- return isProperty ? /* @__PURE__ */ jsx("meta", {
281
- property: key,
282
- content: strVal
283
- }, key) : /* @__PURE__ */ jsx("meta", {
284
- name: key,
285
- content: strVal
286
- }, key);
287
- }),
288
383
  Object.entries(seo).map(([key, value]) => {
289
384
  if (key === "noindex" && value === true) return /* @__PURE__ */ jsx("meta", {
290
385
  name: "robots",
@@ -292,11 +387,11 @@ function Head({ siteTitle, siteDescription, routes }) {
292
387
  }, "noindex");
293
388
  if (key === "robots") return /* @__PURE__ */ jsx("meta", {
294
389
  name: "robots",
295
- content: String(value)
390
+ content: stringifyMetaValue(value)
296
391
  }, "robots");
297
- if (key === "canonical" || key === "og:url" || key === "og:image" || key === "twitter:image") return null;
392
+ if (key === "canonical" || key === "og:url" || key === "og:image" || key === "twitter:image" || key === "structuredData") return null;
298
393
  const isProperty = key.startsWith("og:") || key.startsWith("music:") || key.startsWith("video:") || key.startsWith("article:") || key.startsWith("book:") || key.startsWith("profile:");
299
- const strVal = String(value ?? "");
394
+ const strVal = stringifyMetaValue(value);
300
395
  return isProperty ? /* @__PURE__ */ jsx("meta", {
301
396
  property: key,
302
397
  content: strVal
@@ -314,6 +409,35 @@ const CollectionsContext = createContext({});
314
409
  function useCollectionsData() {
315
410
  return use(CollectionsContext);
316
411
  }
412
+ function CollectionsProvider({ collectionsData, children }) {
413
+ const [data, setData] = useState(collectionsData);
414
+ useEffect(() => {
415
+ if (!import.meta.hot) return;
416
+ const handler = (payload) => {
417
+ setData((prev) => {
418
+ const next = { ...prev };
419
+ for (const post of payload.collections.updated) {
420
+ const collection = post.collection;
421
+ if (!collection) continue;
422
+ const arr = next[collection] ? [...next[collection]] : [];
423
+ const idx = arr.findIndex((p) => p.filePath === post.filePath);
424
+ if (idx >= 0) arr[idx] = post;
425
+ else arr.push(post);
426
+ next[collection] = arr;
427
+ }
428
+ for (const filePath of payload.collections.deleted) for (const key of Object.keys(next)) next[key] = next[key].filter((p) => p.filePath !== filePath);
429
+ return next;
430
+ });
431
+ };
432
+ const hot = import.meta.hot;
433
+ hot.on("boltdocs:frontmatter-update", handler);
434
+ return () => hot.off?.("boltdocs:frontmatter-update", handler);
435
+ }, []);
436
+ return /* @__PURE__ */ jsx(CollectionsContext.Provider, {
437
+ value: data,
438
+ children
439
+ });
440
+ }
317
441
  const CurrentPostContext = createContext(null);
318
442
  function CurrentPostProvider({ value, children }) {
319
443
  return /* @__PURE__ */ jsx(CurrentPostContext.Provider, {
@@ -332,41 +456,58 @@ function useCurrentPostData() {
332
456
  */
333
457
  function I18nUpdater({ config }) {
334
458
  const { currentLocale } = useBoltdocsContext();
459
+ const locale = currentLocale || config.i18n?.defaultLocale || "en";
460
+ const localeConfig = config.i18n?.localeConfigs?.[locale];
461
+ const htmlLang = localeConfig?.htmlLang || locale;
462
+ const direction = localeConfig?.direction || "ltr";
335
463
  useEffect(() => {
336
464
  if (!config.i18n || typeof document === "undefined") return;
337
- const locale = currentLocale || config.i18n.defaultLocale;
338
- const localeConfig = config.i18n.localeConfigs?.[locale];
339
- document.documentElement.lang = localeConfig?.htmlLang || locale || "en";
340
- document.documentElement.dir = localeConfig?.direction || "ltr";
341
- }, [currentLocale, config.i18n]);
342
- return null;
465
+ document.documentElement.lang = htmlLang;
466
+ document.documentElement.dir = direction;
467
+ }, [
468
+ config.i18n,
469
+ direction,
470
+ htmlLang
471
+ ]);
472
+ return /* @__PURE__ */ jsx(Helmet, { htmlAttributes: {
473
+ lang: htmlLang,
474
+ dir: direction
475
+ } });
343
476
  }
344
477
  function StoreSync({ config, routeMap }) {
345
- const location = useLocation$1();
346
- const { setLocale, setVersion } = useBoltdocsContext();
478
+ const location = useLocation();
479
+ const { currentLocale, currentVersion, setLocale, setVersion } = useBoltdocsContext();
347
480
  useEffect(() => {
348
- const currentPath = normalizePath(location.pathname);
481
+ const currentPath = normalizePath(typeof window !== "undefined" ? window.location.pathname : location.pathname);
349
482
  const matchedRoute = routeMap.get(currentPath);
350
483
  if (matchedRoute) {
351
- if (config.i18n) setLocale(matchedRoute.locale || config.i18n.defaultLocale);
352
- if (config.versions) setVersion(matchedRoute.version || config.versions.defaultVersion);
353
- }
484
+ if (config.i18n) {
485
+ const targetLocale = matchedRoute.locale || config.i18n.defaultLocale;
486
+ if (targetLocale !== currentLocale) setLocale(targetLocale);
487
+ }
488
+ if (config.versions) {
489
+ const targetVersion = matchedRoute.version || config.versions.defaultVersion;
490
+ if (targetVersion !== currentVersion) setVersion(targetVersion);
491
+ }
492
+ } else if (config.versions && currentVersion !== config.versions.defaultVersion) setVersion(config.versions.defaultVersion);
354
493
  }, [
355
494
  location.pathname,
356
495
  config,
357
496
  routeMap,
497
+ currentLocale,
498
+ currentVersion,
358
499
  setLocale,
359
500
  setVersion
360
501
  ]);
361
502
  return null;
362
503
  }
363
- function BoltdocsShell({ config, routes, components = {}, collectionsData }) {
504
+ function BoltdocsShell({ config, routes, components = {}, collectionsData, contentClassName }) {
364
505
  const allComponents = useMemo(() => ({
365
506
  ...mdxComponentsDefault,
366
507
  ...virtualCustomComponents,
367
508
  ...components
368
509
  }), [components]);
369
- const { pathname } = useLocation$1();
510
+ const { pathname } = useLocation();
370
511
  const currentPath = useMemo(() => normalizePath(pathname || "/"), [pathname]);
371
512
  const routeMap = useMemo(() => {
372
513
  const map = /* @__PURE__ */ new Map();
@@ -399,8 +540,8 @@ function BoltdocsShell({ config, routes, components = {}, collectionsData }) {
399
540
  components: allComponents,
400
541
  children: /* @__PURE__ */ jsx(ConfigContext.Provider, {
401
542
  value: config,
402
- children: /* @__PURE__ */ jsxs(CollectionsContext.Provider, {
403
- value: collectionsData || {},
543
+ children: /* @__PURE__ */ jsxs(CollectionsProvider, {
544
+ collectionsData: collectionsData || {},
404
545
  children: [/* @__PURE__ */ jsx(ScrollHandler, {}), /* @__PURE__ */ jsxs(BoltdocsProvider, {
405
546
  initialLocale: initialData.initLocale,
406
547
  initialVersion: initialData.initVersion,
@@ -416,7 +557,7 @@ function BoltdocsShell({ config, routes, components = {}, collectionsData }) {
416
557
  routes
417
558
  }),
418
559
  /* @__PURE__ */ jsx(InternalErrorBoundary, { children: /* @__PURE__ */ jsx("div", {
419
- className: "boltdocs-shell-content h-screen overflow-hidden",
560
+ className: cn("boltdocs-shell-content h-screen overflow-hidden", contentClassName),
420
561
  children: /* @__PURE__ */ jsx(Outlet, {})
421
562
  }) })
422
563
  ]
@@ -461,7 +602,7 @@ function DocPage({ route, content: Content, mdxComponents: propComponents }) {
461
602
  const allComponents = useMergedComponents(propComponents);
462
603
  const { LastUpdated } = allComponents;
463
604
  if (!Content) return null;
464
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Content, { components: allComponents }), route?.lastUpdated && /* @__PURE__ */ jsx(LastUpdated, { date: route.lastUpdated })] });
605
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Content, { components: allComponents }), route?.lastUpdated && /* @__PURE__ */ jsx(LastUpdated, { date: route.lastUpdated })] });
465
606
  }
466
607
 
467
608
  //#endregion
@@ -476,26 +617,30 @@ function DocPage({ route, content: Content, mdxComponents: propComponents }) {
476
617
  */
477
618
  function MdxPage({ MDXComponent, mdxComponents: propComponents, collectionPostComponent: CollectionPost }) {
478
619
  const data = useLoaderData();
620
+ const mdxComponents = {
621
+ ...useMdxComponents(),
622
+ ...propComponents
623
+ };
479
624
  if (!MDXComponent) return null;
480
- const collectionData = data;
481
- if (!!collectionData?.collection) {
625
+ const collectionData = data || {};
626
+ if (!!collectionData?.collection && !!collectionData?.route) {
482
627
  const postElement = CollectionPost ? /* @__PURE__ */ jsx(CollectionPost, {
483
628
  MDXComponent,
484
- mdxComponents: propComponents
629
+ mdxComponents
485
630
  }) : /* @__PURE__ */ jsx(DocPage, {
486
631
  route: {
487
- path: collectionData.route.path,
488
- filePath: collectionData.route.filePath,
489
- title: collectionData.route.title,
490
- description: collectionData.route.description,
491
- headings: collectionData.headings,
492
- locale: collectionData.route.locale,
493
- version: collectionData.route.version,
494
- lastUpdated: collectionData.route.lastUpdated,
495
- frontmatter: collectionData.route.frontmatter
632
+ path: collectionData.route?.path || "",
633
+ filePath: collectionData.route?.filePath || "",
634
+ title: collectionData.route?.title,
635
+ description: collectionData.route?.description,
636
+ headings: collectionData.headings || [],
637
+ locale: collectionData.route?.locale,
638
+ version: collectionData.route?.version,
639
+ lastUpdated: collectionData.route?.lastUpdated,
640
+ frontmatter: collectionData.route?.frontmatter
496
641
  },
497
642
  content: MDXComponent,
498
- mdxComponents: propComponents
643
+ mdxComponents
499
644
  });
500
645
  return /* @__PURE__ */ jsx(CurrentPostProvider, {
501
646
  value: {
@@ -506,23 +651,23 @@ function MdxPage({ MDXComponent, mdxComponents: propComponents, collectionPostCo
506
651
  children: postElement
507
652
  });
508
653
  }
509
- const docData = data;
654
+ const docData = data || {};
510
655
  return /* @__PURE__ */ jsx(DocPage, {
511
656
  route: {
512
- path: docData.path,
513
- filePath: docData.filePath,
514
- title: docData.frontmatter?.title,
515
- description: docData.frontmatter?.description,
516
- headings: docData.headings,
517
- locale: docData.locale,
518
- version: docData.version,
519
- group: docData.group,
520
- groupTitle: docData.groupTitle,
521
- lastUpdated: docData.lastUpdated,
522
- frontmatter: docData.frontmatter
657
+ path: docData?.path || "",
658
+ filePath: docData?.filePath || "",
659
+ title: docData?.frontmatter?.title,
660
+ description: docData?.frontmatter?.description,
661
+ headings: docData?.headings || [],
662
+ locale: docData?.locale,
663
+ version: docData?.version,
664
+ group: docData?.group,
665
+ groupTitle: docData?.groupTitle,
666
+ lastUpdated: docData?.lastUpdated,
667
+ frontmatter: docData?.frontmatter
523
668
  },
524
669
  content: MDXComponent,
525
- mdxComponents: propComponents
670
+ mdxComponents
526
671
  });
527
672
  }
528
673
 
@@ -549,11 +694,11 @@ function useBreadcrumbs() {
549
694
 
550
695
  //#endregion
551
696
  //#region src/client/components/ui-base/breadcrumbs.tsx
552
- function Breadcrumbs() {
697
+ function Breadcrumbs({ className }) {
553
698
  const { crumbs, activeRoute } = useBreadcrumbs();
554
699
  if (crumbs.length === 0) return null;
555
700
  return /* @__PURE__ */ jsxs(Breadcrumbs$2.Root, {
556
- className: "gap-2 text-xs sm:text-sm font-medium",
701
+ className: cn("gap-2 text-xs sm:text-sm font-medium", className),
557
702
  children: [/* @__PURE__ */ jsx(Breadcrumbs$2.Item, { children: /* @__PURE__ */ jsx(Breadcrumbs$2.Link, {
558
703
  href: "/",
559
704
  className: "text-muted hover:text-body transition-colors flex items-center",
@@ -602,12 +747,12 @@ const useCopyMarkdown = (content) => {
602
747
  handleOpenRaw
603
748
  };
604
749
  };
605
- function CopyMarkdown({ content, mdxRaw }) {
750
+ function CopyMarkdown({ content, mdxRaw, className }) {
606
751
  const displayContent = mdxRaw || content || "";
607
752
  const { copied, handleCopy, handleOpenRaw } = useCopyMarkdown(displayContent);
608
753
  if (!displayContent) return null;
609
754
  return /* @__PURE__ */ jsx("div", {
610
- className: "relative inline-flex z-100 shrink-0 w-max",
755
+ className: cn("relative inline-flex z-100 shrink-0 w-max", className),
611
756
  children: /* @__PURE__ */ jsxs(ButtonGroup, {
612
757
  className: "rounded-xl border border-subtle bg-surface transition-all duration-300 hover:border-primary-500/50 group overflow-hidden",
613
758
  children: [
@@ -699,7 +844,7 @@ const formatStars = (count) => {
699
844
 
700
845
  //#endregion
701
846
  //#region src/client/components/ui-base/github-stars.tsx
702
- function GithubStars({ repo }) {
847
+ function GithubStars({ repo, className }) {
703
848
  const [stars, setStars] = useState(null);
704
849
  useEffect(() => {
705
850
  if (repo) getStarsRepo(repo).then((stars) => setStars(stars)).catch(() => setStars("0"));
@@ -708,7 +853,7 @@ function GithubStars({ repo }) {
708
853
  href: `https://github.com/${repo}`,
709
854
  target: "_blank",
710
855
  rel: "noopener noreferrer",
711
- className: "inline-flex items-center gap-2 rounded-xl border border-subtle bg-surface px-3 py-1.5 text-xs font-semibold text-muted dark:hover:bg-primary-300/50 hover:bg-primary-200/50 transition-colors duration-100 hover:border-primary-500/50 hover:text-body select-none outline-none",
856
+ className: cn("inline-flex items-center gap-2 rounded-xl border border-subtle bg-surface px-3 py-1.5 text-xs font-semibold text-muted dark:hover:bg-primary-300/50 hover:bg-primary-200/50 transition-colors duration-100 hover:border-primary-500/50 hover:text-body select-none outline-none", className),
712
857
  children: [/* @__PURE__ */ jsx(Github, { className: "h-4 w-4 text-body" }), stars !== null && /* @__PURE__ */ jsxs("span", {
713
858
  className: "tabular-nums font-medium",
714
859
  children: [stars, " stars"]
@@ -721,7 +866,7 @@ function GithubStars({ repo }) {
721
866
  function useNavbar() {
722
867
  const config = useConfig();
723
868
  const { theme, resolvedTheme } = useTheme();
724
- const location = useLocation$1();
869
+ const location = useLocation();
725
870
  const { currentLocale } = useRoutes();
726
871
  const themeConfig = config.theme || {};
727
872
  const title = getTranslated(themeConfig.title, currentLocale) || "Boltdocs";
@@ -796,18 +941,18 @@ function useNavbar() {
796
941
 
797
942
  //#endregion
798
943
  //#region src/client/components/ui-base/theme-toggle.tsx
799
- function ThemeToggle() {
944
+ function ThemeToggle({ className }) {
800
945
  const { theme, setTheme } = useTheme();
801
946
  const [mounted, setMounted] = useState(false);
802
947
  useEffect(() => {
803
948
  setMounted(true);
804
949
  }, []);
805
- if (!mounted) return /* @__PURE__ */ jsx("div", { className: "h-9 w-9" });
950
+ if (!mounted) return /* @__PURE__ */ jsx("div", { className: cn("h-9 w-9", className) });
806
951
  const Icon = theme === "system" ? Monitor : theme === "dark" ? Moon : Sun;
807
952
  return /* @__PURE__ */ jsxs(Menu$1.Trigger, {
808
953
  placement: "bottom right",
809
954
  children: [/* @__PURE__ */ jsx(Button, {
810
- className: "flex h-9 w-9 items-center justify-center rounded-xl text-muted transition-colors hover:bg-surface hover:text-body outline-none border-none bg-transparent cursor-pointer",
955
+ className: cn("flex h-9 w-9 items-center justify-center rounded-xl text-muted transition-colors hover:bg-surface hover:text-body outline-none border-none bg-transparent cursor-pointer", className),
811
956
  "aria-label": "Selection theme",
812
957
  children: /* @__PURE__ */ jsx(Icon, {
813
958
  size: 20,
@@ -890,7 +1035,7 @@ function ThemeSwitcher({ className }) {
890
1035
  //#endregion
891
1036
  //#region src/client/hooks/use-tabs.ts
892
1037
  function useTabs(tabs = [], routes = []) {
893
- const location = useLocation$1();
1038
+ const location = useLocation();
894
1039
  const tabRefs = useRef([]);
895
1040
  const [indicatorStyle, setIndicatorStyle] = useState({
896
1041
  opacity: 0,
@@ -924,9 +1069,11 @@ function useTabs(tabs = [], routes = []) {
924
1069
 
925
1070
  //#endregion
926
1071
  //#region src/client/components/ui-base/tabs.tsx
927
- function Tabs({ tabs, routes }) {
1072
+ function Tabs({ tabs, routes, allRoutes, className }) {
928
1073
  const { currentLocale } = useRoutes();
929
1074
  const { indicatorStyle, tabRefs, activeIndex } = useTabs(tabs, routes);
1075
+ const routeCandidates = routes;
1076
+ const fallbackRouteCandidates = allRoutes || [];
930
1077
  useEffect(() => {
931
1078
  const activeTab = tabRefs.current[activeIndex];
932
1079
  if (activeTab) activeTab.scrollIntoView({
@@ -934,33 +1081,19 @@ function Tabs({ tabs, routes }) {
934
1081
  block: "nearest",
935
1082
  inline: "center"
936
1083
  });
937
- }, [activeIndex]);
938
- const renderTabIcon = (iconName) => {
939
- if (!iconName) return null;
940
- if (iconName.trim().startsWith("<svg")) return /* @__PURE__ */ jsx("span", {
941
- className: "h-4 w-4",
942
- dangerouslySetInnerHTML: { __html: DOMPurify.sanitize(iconName, { USE_PROFILES: { svg: true } }) }
943
- });
944
- const icons = {
945
- ...icons_exports,
946
- ...virtualIcons
947
- };
948
- const TabIcon = icons[iconName] || icons[iconName + "Icon"];
949
- if (TabIcon) return /* @__PURE__ */ jsx(TabIcon, { size: 16 });
950
- return /* @__PURE__ */ jsx("img", {
951
- src: iconName,
952
- alt: "",
953
- className: "h-4 w-4 object-contain"
954
- });
955
- };
1084
+ }, [activeIndex, tabRefs]);
1085
+ const renderTabIcon = (iconName) => /* @__PURE__ */ jsx(IconRenderer, {
1086
+ icon: resolveIcon(iconName),
1087
+ size: 16
1088
+ });
956
1089
  return /* @__PURE__ */ jsx("div", {
957
- className: "mx-auto max-w-(--breakpoint-3xl) px-4 md:px-6 select-none",
1090
+ className: cn("mx-auto max-w-(--breakpoint-3xl) px-4 md:px-6 select-none", className),
958
1091
  children: /* @__PURE__ */ jsxs(Tabs$1.List, {
959
1092
  className: "border-none py-0 scrollbar-hide relative flex flex-row items-center overflow-x-auto",
960
1093
  children: [tabs.map((tab, index) => {
961
1094
  const isActive = index === activeIndex;
962
- const firstRoute = routes.find((r) => r.tab && r.tab.toLowerCase() === tab.id.toLowerCase());
963
- return /* @__PURE__ */ jsxs(Link$1, {
1095
+ const firstRoute = routeCandidates.find((r) => r.tab && r.tab.toLowerCase() === tab.id.toLowerCase()) || fallbackRouteCandidates.find((r) => r.tab && r.tab.toLowerCase() === tab.id.toLowerCase());
1096
+ return /* @__PURE__ */ jsxs(Link, {
964
1097
  href: firstRoute ? firstRoute.path : "#",
965
1098
  ref: (el) => {
966
1099
  tabRefs.current[index] = el;
@@ -1067,6 +1200,7 @@ function VersionSelector({ className }) {
1067
1200
  */
1068
1201
  function useI18n() {
1069
1202
  const navigate = useNavigate();
1203
+ const location = useLocation();
1070
1204
  const config = useConfig();
1071
1205
  const { allRoutes, currentRoute, currentLocale, isCollectionPage } = useRoutes();
1072
1206
  const i18n = config.i18n;
@@ -1079,16 +1213,16 @@ function useI18n() {
1079
1213
  const isDocRoute = !!currentRoute?.filePath;
1080
1214
  let targetPath = "";
1081
1215
  if (currentRoute) if (isDocRoute && !currentRoute.collection && !isCollectionPage) {
1082
- const baseFile = getBaseFilePath(currentRoute.filePath, currentRoute.version, currentRoute.locale);
1083
- const targetRoute = allRoutes.find((r) => getBaseFilePath(r.filePath, r.version, r.locale) === baseFile && (r.locale || i18n.defaultLocale) === locale && r.version === currentRoute.version);
1216
+ const baseFile = currentRoute.filePath ? getBaseFilePath(currentRoute.filePath, currentRoute.version, currentRoute.locale) : "";
1217
+ const targetRoute = allRoutes.find((r) => r.filePath && getBaseFilePath(r.filePath, r.version, r.locale) === baseFile && (r.locale || i18n.defaultLocale) === locale && r.version === currentRoute.version);
1084
1218
  if (targetRoute) targetPath = targetRoute.path;
1085
1219
  else {
1086
- const defaultIndexRoute = allRoutes.find((r) => getBaseFilePath(r.filePath, r.version, r.locale) === "index.md" && (r.locale || i18n.defaultLocale) === locale && r.version === currentRoute.version);
1220
+ const defaultIndexRoute = allRoutes.find((r) => r.filePath && ["index.md", "_index.md"].includes(getBaseFilePath(r.filePath, r.version, r.locale)) && (r.locale || i18n.defaultLocale) === locale && r.version === currentRoute.version);
1087
1221
  if (defaultIndexRoute) targetPath = defaultIndexRoute.path;
1088
1222
  else targetPath = `${safeBase}${currentRoute.version ? `/${currentRoute.version}` : ""}${locale === i18n.defaultLocale ? "" : `/${locale}`}` || "/";
1089
1223
  }
1090
1224
  } else {
1091
- let rawExternal = currentRoute.path;
1225
+ let rawExternal = currentRoute.path || "";
1092
1226
  const parts = rawExternal.split("/").filter(Boolean);
1093
1227
  const localePosition = currentRoute.collection ? 1 : 0;
1094
1228
  if (parts.length > localePosition && (Array.isArray(i18n.locales) ? i18n.locales.includes(parts[localePosition]) : !!i18n.locales[parts[localePosition]])) {
@@ -1112,7 +1246,7 @@ function useI18n() {
1112
1246
  targetPath = "/" + parts.join("/");
1113
1247
  }
1114
1248
  }
1115
- if (!targetPath || targetPath === "") targetPath = "/s";
1249
+ if (!targetPath || targetPath === "") targetPath = "/";
1116
1250
  targetPath = targetPath.replace(/\/+/g, "/");
1117
1251
  navigate(targetPath);
1118
1252
  };
@@ -1146,7 +1280,7 @@ function useI18n() {
1146
1280
  //#region src/client/hooks/use-page-nav.ts
1147
1281
  function usePageNav() {
1148
1282
  const { routes, currentRoute } = useRoutes();
1149
- const location = useLocation$1();
1283
+ const location = useLocation();
1150
1284
  const { groups, ungrouped } = useSidebar(routes);
1151
1285
  return useMemo(() => {
1152
1286
  if (!currentRoute) return {
@@ -1171,14 +1305,20 @@ function usePageNav() {
1171
1305
  });
1172
1306
  const orderedRoutes = [];
1173
1307
  const flattenNode = (node) => {
1308
+ if (!node) return;
1174
1309
  if (node.path && node.path !== "#" && !node.sidebarHidden) orderedRoutes.push(node);
1175
1310
  const children = node.routes || node.subRoutes;
1176
- if (children && children.length > 0) for (const child of children) flattenNode(child);
1311
+ if (children && children.length > 0) {
1312
+ for (const child of children) if (child) flattenNode(child);
1313
+ }
1177
1314
  };
1178
- for (const item of mergedItems) if (item.type === "link") flattenNode(item.route);
1179
- else if (item.group.routes) for (const route of item.group.routes) flattenNode(route);
1315
+ for (const item of mergedItems) if (item.type === "link") {
1316
+ if (item.route) flattenNode(item.route);
1317
+ } else if (item.group.routes) {
1318
+ for (const route of item.group.routes) if (route) flattenNode(route);
1319
+ }
1180
1320
  const currentNormalizedPath = normalizePath(location.pathname);
1181
- const currentIndex = orderedRoutes.findIndex((r) => normalizePath(r.path) === currentNormalizedPath);
1321
+ const currentIndex = orderedRoutes.findIndex((r) => r && r.path && normalizePath(r.path) === currentNormalizedPath);
1182
1322
  return {
1183
1323
  prevPage: currentIndex > 0 ? orderedRoutes[currentIndex - 1] : null,
1184
1324
  nextPage: currentIndex !== -1 && currentIndex < orderedRoutes.length - 1 ? orderedRoutes[currentIndex + 1] : null,
@@ -1336,16 +1476,17 @@ function useAnalytics(options = {}) {
1336
1476
  const config = optionsConfig ?? globalConfig?.integrations;
1337
1477
  const analytics = useMemo(() => createAnalyticsInstance(config), [config]);
1338
1478
  const previousPath = useRef("");
1339
- const location = useLocation();
1479
+ const { pathname, search } = location;
1340
1480
  useEffect(() => {
1341
1481
  if (!autoTrackPageViews || !analytics.isEnabled) return;
1342
- const path = location.pathname + location.search;
1482
+ const path = pathname + search;
1343
1483
  if (path !== previousPath.current) {
1344
1484
  previousPath.current = path;
1345
1485
  analytics.trackPageView(path, document.title);
1346
1486
  }
1347
1487
  }, [
1348
- location.pathname,
1488
+ pathname,
1489
+ search,
1349
1490
  autoTrackPageViews,
1350
1491
  analytics
1351
1492
  ]);
@@ -1366,7 +1507,6 @@ function useAnalytics(options = {}) {
1366
1507
  return () => document.removeEventListener("click", handleClick);
1367
1508
  }, [
1368
1509
  autoTrackDownloads,
1369
- autoTrackExternalLinks,
1370
1510
  analytics,
1371
1511
  excludePatterns
1372
1512
  ]);
@@ -1431,12 +1571,13 @@ function useFeedback(options = {}) {
1431
1571
  const [loading, setLoading] = useState(false);
1432
1572
  const [submitted, setSubmitted] = useState(false);
1433
1573
  const [error, setError] = useState(null);
1574
+ const routePath = currentRoute?.path;
1434
1575
  useEffect(() => {
1435
1576
  setRating(null);
1436
1577
  setComment("");
1437
1578
  setSubmitted(false);
1438
1579
  setError(null);
1439
- }, [currentRoute?.path]);
1580
+ }, [routePath]);
1440
1581
  const submit = async (commentOverride, blockId) => {
1441
1582
  if (!rating) return;
1442
1583
  setLoading(true);
@@ -1515,17 +1656,17 @@ function I18nSelector({ className }) {
1515
1656
 
1516
1657
  //#endregion
1517
1658
  //#region src/client/components/ui-base/navbar.tsx
1518
- const SearchDialog$1 = lazy(() => import("../search-dialog-D6TvjcuV.js").then((m) => ({ default: m.SearchDialog })));
1519
- function Navbar() {
1659
+ const SearchDialog$1 = lazy(() => import("../search-dialog-DtmxmfEu.js").then((m) => ({ default: m.SearchDialog })));
1660
+ function Navbar({ className }) {
1520
1661
  const { links, title, logo, logoProps, github, social, config } = useNavbar();
1521
- const { routes, currentRoute, isCollectionPage, currentVersion, currentLocale } = useRoutes();
1662
+ const { routes, allRoutes, currentRoute, isCollectionPage, currentVersion, currentLocale } = useRoutes();
1522
1663
  const { isSidebarOpen, toggleSidebar } = useUI();
1523
1664
  const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
1524
1665
  const themeConfig = config.theme;
1525
1666
  const isDocs = !!currentRoute?.filePath && !isCollectionPage;
1526
1667
  const hasTabs = themeConfig?.tabs && themeConfig.tabs.length > 0;
1527
1668
  return /* @__PURE__ */ jsxs(Navbar$1.Root, {
1528
- className: cn("border-b border-subtle bg-main/80 backdrop-blur-md", hasTabs && "border-b-0"),
1669
+ className: cn("border-b border-subtle bg-main/80 backdrop-blur-md", hasTabs && "border-b-0", className),
1529
1670
  children: [
1530
1671
  /* @__PURE__ */ jsxs(Navbar$1.Content, { children: [
1531
1672
  /* @__PURE__ */ jsxs(Navbar$1.Left, { children: [
@@ -1630,7 +1771,8 @@ function Navbar() {
1630
1771
  className: "w-full border-b border-subtle bg-main",
1631
1772
  children: /* @__PURE__ */ jsx(Tabs, {
1632
1773
  tabs: themeConfig.tabs,
1633
- routes: routes || []
1774
+ routes: routes || [],
1775
+ allRoutes: allRoutes || []
1634
1776
  })
1635
1777
  })
1636
1778
  ]
@@ -1638,7 +1780,7 @@ function Navbar() {
1638
1780
  }
1639
1781
  function NavbarLinkItem({ link }) {
1640
1782
  const localizedHref = useLocalizedTo(link.href || "");
1641
- const { pathname } = useLocation$1();
1783
+ const { pathname } = useLocation();
1642
1784
  const active = pathname === localizedHref || pathname.startsWith(`${localizedHref}/`);
1643
1785
  if (link.items && link.items.length > 0) return /* @__PURE__ */ jsx(Navbar$1.Dropdown, {
1644
1786
  label: /* @__PURE__ */ jsx("span", {
@@ -1658,70 +1800,58 @@ function NavbarLinkItem({ link }) {
1658
1800
  });
1659
1801
  }
1660
1802
  function NavbarMobileLinkItem({ link, onClose }) {
1661
- const localizedHref = useLocalizedTo(link.href || "");
1662
- const { pathname } = useLocation$1();
1663
- const active = pathname === localizedHref;
1664
- if (link.items && link.items.length > 0) return /* @__PURE__ */ jsxs("div", {
1665
- className: "flex flex-col gap-1",
1666
- children: [/* @__PURE__ */ jsx("div", {
1667
- className: cn("px-3 py-2 text-sm transition-all", active ? "text-body" : "text-muted/80 hover:text-body"),
1668
- children: link.label
1669
- }), /* @__PURE__ */ jsx("div", {
1670
- className: "flex flex-col gap-1 pl-4",
1671
- children: link.items?.map((item) => /* @__PURE__ */ jsx(NavbarMobileLinkItem, {
1672
- link: item,
1673
- onClose
1674
- }, item.href))
1675
- })]
1676
- });
1677
- return /* @__PURE__ */ jsx(Navbar$1.MobileLink, {
1678
- ...link,
1679
- href: localizedHref,
1680
- active,
1681
- onPress: onClose,
1682
- className: cn("transition-all", active ? "text-body" : "text-muted/80 hover:text-body")
1803
+ return /* @__PURE__ */ jsx(Navbar$1.MobileLinkItem, {
1804
+ link,
1805
+ onClose
1683
1806
  });
1684
1807
  }
1685
1808
 
1686
1809
  //#endregion
1687
1810
  //#region src/client/components/ui-base/on-this-page.tsx
1688
- function OnThisPage({ headings = [], editLink, communityHelp, filePath }) {
1811
+ function OnThisPage({ headings = [], editLink, communityHelp, filePath, className }) {
1689
1812
  if (headings.length === 0) return /* @__PURE__ */ jsx("nav", {
1690
- className: "w-toc shrink-0 hidden xl:block",
1813
+ className: cn("w-toc shrink-0 hidden xl:block", className),
1691
1814
  "aria-hidden": "true"
1692
1815
  });
1693
- return /* @__PURE__ */ jsxs(OnThisPage$1.Root, { children: [
1694
- /* @__PURE__ */ jsxs(OnThisPage$1.Header, {
1695
- className: "flex flex-row gap-x-2",
1696
- children: [/* @__PURE__ */ jsx(TextAlignStart, { size: 16 }), "On this page"]
1697
- }),
1698
- /* @__PURE__ */ jsx(OnThisPage$1.Tree, {
1699
- className: "max-h-[300px]",
1700
- headings
1701
- }),
1702
- (editLink || communityHelp) && /* @__PURE__ */ jsxs("div", {
1703
- className: "mt-8 pt-8 border-t border-subtle space-y-4",
1704
- children: [/* @__PURE__ */ jsx("p", {
1705
- className: "text-xs font-bold text-body",
1706
- children: "Need help?"
1707
- }), /* @__PURE__ */ jsxs("ul", {
1708
- className: "space-y-3",
1709
- children: [editLink && filePath && /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("a", {
1710
- href: editLink.replace(":path", filePath),
1711
- target: "_blank",
1712
- rel: "noopener noreferrer",
1713
- className: "flex items-center gap-2 text-sm text-muted hover:text-body transition-colors",
1714
- children: [/* @__PURE__ */ jsx(Pencil, { size: 16 }), "Edit this page"]
1715
- }) }), communityHelp && /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("a", {
1716
- href: communityHelp,
1717
- target: "_blank",
1718
- rel: "noopener noreferrer",
1719
- className: "flex items-center gap-2 text-sm text-muted hover:text-body transition-colors",
1720
- children: [/* @__PURE__ */ jsx(CircleHelp, { size: 16 }), "Community help"]
1721
- }) })]
1722
- })]
1723
- })
1724
- ] });
1816
+ return /* @__PURE__ */ jsxs(OnThisPage$1.Root, {
1817
+ className: cn("sticky top-navbar hidden xl:flex flex-col shrink-0 w-toc py-4 pl-6 pr-4", className),
1818
+ children: [
1819
+ /* @__PURE__ */ jsxs(OnThisPage$1.Header, {
1820
+ className: "mb-4 text-xs font-bold text-body flex flex-row gap-x-2",
1821
+ children: [/* @__PURE__ */ jsx(TextAlignStart, { size: 16 }), "On this page"]
1822
+ }),
1823
+ /* @__PURE__ */ jsx(OnThisPage$1.Tree, {
1824
+ className: "space-y-0.5 text-sm border-l border-subtle",
1825
+ itemClassName: "data-[level=3]:pl-3",
1826
+ linkClassName: "block py-0.5 pl-4 text-[13px] outline-none transition-colors text-muted hover:text-body data-active:text-primary-500",
1827
+ indicatorClassName: "-left-px w-0.5 rounded-full bg-primary-500",
1828
+ fadeClassName: "bg-linear-to-b from-transparent to-main",
1829
+ headings
1830
+ }),
1831
+ (editLink || communityHelp) && /* @__PURE__ */ jsxs("div", {
1832
+ className: "mt-8 pt-8 border-t border-subtle space-y-4",
1833
+ children: [/* @__PURE__ */ jsx("p", {
1834
+ className: "text-xs font-bold text-body",
1835
+ children: "Need help?"
1836
+ }), /* @__PURE__ */ jsxs("ul", {
1837
+ className: "space-y-3",
1838
+ children: [editLink && filePath && /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("a", {
1839
+ href: editLink.replace(":path", filePath),
1840
+ target: "_blank",
1841
+ rel: "noopener noreferrer",
1842
+ className: "flex items-center gap-2 text-sm text-muted hover:text-body transition-colors",
1843
+ children: [/* @__PURE__ */ jsx(Pencil, { size: 16 }), "Edit this page"]
1844
+ }) }), communityHelp && /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("a", {
1845
+ href: communityHelp,
1846
+ target: "_blank",
1847
+ rel: "noopener noreferrer",
1848
+ className: "flex items-center gap-2 text-sm text-muted hover:text-body transition-colors",
1849
+ children: [/* @__PURE__ */ jsx(CircleHelp, { size: 16 }), "Community help"]
1850
+ }) })]
1851
+ })]
1852
+ })
1853
+ ]
1854
+ });
1725
1855
  }
1726
1856
 
1727
1857
  //#endregion
@@ -1730,32 +1860,44 @@ function OnThisPage({ headings = [], editLink, communityHelp, filePath }) {
1730
1860
  * Component to display the previous and next page navigation buttons.
1731
1861
  * Enhanced with subtle entrance animations, modern card layout, and hover highlights.
1732
1862
  */
1733
- function PageNav() {
1863
+ function PageNav({ className }) {
1734
1864
  const { prevPage, nextPage } = usePageNav();
1735
1865
  if (!prevPage && !nextPage) return null;
1736
1866
  return /* @__PURE__ */ jsxs(PageNav$1.Root, {
1737
- className: "pt-8 border-t border-subtle grid sm:grid-cols-2 gap-4 animate-in fade-in slide-in-from-bottom-4 duration-700 select-none",
1867
+ className: cn("pt-8 border-t border-subtle grid sm:grid-cols-2 gap-4 animate-in fade-in slide-in-from-bottom-4 duration-700 select-none", className),
1738
1868
  children: [prevPage ? /* @__PURE__ */ jsxs(PageNav$1.Link, {
1739
1869
  to: prevPage.path,
1740
1870
  direction: "prev",
1741
1871
  className: "group border border-subtle bg-surface p-5 rounded-2xl transition-all duration-300 hover:border-primary-500/50 hover:bg-primary-50/20",
1742
- children: [/* @__PURE__ */ jsx(PageNav$1.Title, {
1743
- className: "text-xs font-bold uppercase tracking-wider text-muted/60 mb-1",
1744
- children: "Previous"
1745
- }), /* @__PURE__ */ jsx(PageNav$1.Description, {
1746
- className: "text-sm sm:text-base font-bold text-body group-hover:text-primary-500 transition-colors",
1747
- children: prevPage.title
1872
+ children: [/* @__PURE__ */ jsx(PageNav$1.Icon, {
1873
+ className: "text-muted/60 shrink-0",
1874
+ children: /* @__PURE__ */ jsx(ChevronLeft, {})
1875
+ }), /* @__PURE__ */ jsxs("div", {
1876
+ className: "flex flex-col",
1877
+ children: [/* @__PURE__ */ jsx(PageNav$1.Title, {
1878
+ className: "text-xs font-bold uppercase tracking-wider text-muted/60 mb-1",
1879
+ children: "Previous"
1880
+ }), /* @__PURE__ */ jsx(PageNav$1.Description, {
1881
+ className: "text-sm sm:text-base font-bold text-body group-hover:text-primary-500 transition-colors",
1882
+ children: prevPage.title
1883
+ })]
1748
1884
  })]
1749
1885
  }) : /* @__PURE__ */ jsx("div", {}), nextPage ? /* @__PURE__ */ jsxs(PageNav$1.Link, {
1750
1886
  to: nextPage.path,
1751
1887
  direction: "next",
1752
1888
  className: "group border border-subtle bg-surface p-5 rounded-2xl transition-all duration-300 hover:border-primary-500/50 hover:bg-primary-50/20",
1753
- children: [/* @__PURE__ */ jsx(PageNav$1.Title, {
1754
- className: "text-xs font-bold uppercase tracking-wider text-muted/60 mb-1",
1755
- children: "Next"
1756
- }), /* @__PURE__ */ jsx(PageNav$1.Description, {
1757
- className: "text-sm sm:text-base font-bold text-body group-hover:text-primary-500 transition-colors",
1758
- children: nextPage.title
1889
+ children: [/* @__PURE__ */ jsxs("div", {
1890
+ className: "flex flex-col",
1891
+ children: [/* @__PURE__ */ jsx(PageNav$1.Title, {
1892
+ className: "text-xs font-bold uppercase tracking-wider text-muted/60 mb-1",
1893
+ children: "Next"
1894
+ }), /* @__PURE__ */ jsx(PageNav$1.Description, {
1895
+ className: "text-sm sm:text-base font-bold text-body group-hover:text-primary-500 transition-colors",
1896
+ children: nextPage.title
1897
+ })]
1898
+ }), /* @__PURE__ */ jsx(PageNav$1.Icon, {
1899
+ className: "text-muted/60 shrink-0",
1900
+ children: /* @__PURE__ */ jsx(ChevronRight, {})
1759
1901
  })]
1760
1902
  }) : /* @__PURE__ */ jsx("div", {})]
1761
1903
  });
@@ -1763,38 +1905,90 @@ function PageNav() {
1763
1905
 
1764
1906
  //#endregion
1765
1907
  //#region src/client/components/ui-base/sidebar.tsx
1766
- function SidebarMain({ routes, config }) {
1908
+ /**
1909
+ * Default look for the sidebar tree. States are driven by the primitive's
1910
+ * `data-*` attributes (`data-active`, `data-open`, `data-badge`).
1911
+ */
1912
+ const defaultItemsClassNames = {
1913
+ item: "rounded-lg px-2.5 py-1.5 text-sm transition-all text-muted hover:bg-surface hover:text-body data-active:bg-primary-500/10 data-active:text-primary-500 data-active:font-medium data-active:shadow-sm",
1914
+ groupHeader: "px-2 mb-2 text-[11px] font-bold uppercase tracking-widest text-muted/50",
1915
+ groupContent: "gap-0.5",
1916
+ subgroup: "gap-0.5",
1917
+ subgroupLink: "rounded-lg px-2.5 py-1.5 text-sm transition-all text-muted hover:bg-surface hover:text-body data-active:bg-primary-500/10 data-active:text-primary-500 data-active:font-medium data-active:shadow-sm",
1918
+ subgroupContent: "ml-4 pl-3 border-l border-subtle/50 mt-0.5 gap-0.5",
1919
+ toggle: "p-1.5 text-muted hover:text-body transition-colors"
1920
+ };
1921
+ const defaultContentClassName = "flex-1 overflow-y-auto p-4 pb-16 custom-scrollbar flex flex-col gap-6";
1922
+ const mergeSlots = (defaults, overrides) => {
1923
+ if (!overrides) return defaults;
1924
+ const merged = { ...defaults };
1925
+ for (const key of Object.keys(defaults)) if (overrides[key]) merged[key] = cn(defaults[key], overrides[key]);
1926
+ return merged;
1927
+ };
1928
+ function SidebarMain({ routes, config, className }) {
1767
1929
  const { logo, title, logoProps } = useNavbar();
1768
1930
  const { closeSidebar } = useUI();
1769
1931
  const SidebarLogo = logo ? /* @__PURE__ */ jsx("img", {
1770
- src: logo,
1932
+ src: resolvePublicAssetUrl(logo, config.base),
1771
1933
  alt: logoProps?.alt || title,
1772
1934
  width: 24,
1773
1935
  height: 24,
1774
1936
  className: "rounded-xl"
1775
1937
  }) : null;
1776
1938
  const hasUtilities = config.versions || config.i18n;
1777
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Sidebar$1.Root, { children: /* @__PURE__ */ jsx(Sidebar$1.Content, { children: /* @__PURE__ */ jsx(Sidebar$1.Items, { routes }) }) }), /* @__PURE__ */ jsxs(Sidebar$1.Mobile, { children: [/* @__PURE__ */ jsxs(Sidebar$1.Header, { children: [/* @__PURE__ */ jsxs("div", {
1778
- className: "flex items-center gap-3",
1779
- children: [SidebarLogo, /* @__PURE__ */ jsx("span", {
1780
- className: "font-bold text-lg tracking-tight text-body truncate max-w-[120px]",
1781
- children: title
1782
- })]
1783
- }), /* @__PURE__ */ jsxs("div", {
1784
- className: "flex items-center gap-2",
1785
- children: [/* @__PURE__ */ jsx(ThemeSwitcher, { className: "w-24 h-9 rounded-xl" }), /* @__PURE__ */ jsx(Button$1, {
1786
- onPress: closeSidebar,
1787
- className: "h-9 w-9 flex items-center justify-center bg-transparent border-none outline-none select-none cursor-pointer rounded-xl hover:bg-primary-50/50 text-muted hover:text-body transition-colors",
1788
- "aria-label": "Close sidebar",
1789
- children: /* @__PURE__ */ jsx(X, { size: 20 })
1939
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Sidebar$1.Root, {
1940
+ className: cn("hidden lg:flex flex-col w-sidebar sticky top-navbar h-[calc(100vh-var(--spacing-navbar))] border-r border-subtle bg-main", className),
1941
+ children: /* @__PURE__ */ jsx(Sidebar$1.Content, {
1942
+ className: defaultContentClassName,
1943
+ children: /* @__PURE__ */ jsx(Sidebar$1.Items, {
1944
+ routes,
1945
+ className: "flex flex-col gap-6",
1946
+ classNames: defaultItemsClassNames
1947
+ })
1948
+ })
1949
+ }), /* @__PURE__ */ jsxs(Sidebar$1.Mobile, {
1950
+ overlayClassName: "fixed inset-0 z-50 bg-black/20 backdrop-blur-sm lg:hidden entering:animate-in entering:fade-in exiting:animate-out exiting:fade-out duration-300",
1951
+ className: cn("fixed top-0 left-0 bottom-0 w-80 bg-main border-r border-subtle shadow-2xl outline-none entering:animate-in entering:slide-in-from-left exiting:animate-out exiting:slide-out-to-left duration-300", className),
1952
+ children: [/* @__PURE__ */ jsxs(Sidebar$1.Header, {
1953
+ className: "flex items-center justify-between p-4 border-b border-subtle",
1954
+ children: [/* @__PURE__ */ jsxs("div", {
1955
+ className: "flex items-center gap-3",
1956
+ children: [SidebarLogo, /* @__PURE__ */ jsx("span", {
1957
+ className: "font-bold text-lg tracking-tight text-body truncate max-w-[120px]",
1958
+ children: title
1959
+ })]
1960
+ }), /* @__PURE__ */ jsxs("div", {
1961
+ className: "flex items-center gap-2",
1962
+ children: [/* @__PURE__ */ jsx(ThemeSwitcher, { className: "w-24 h-9 rounded-xl" }), /* @__PURE__ */ jsx(Button$1, {
1963
+ onPress: closeSidebar,
1964
+ className: "h-9 w-9 flex items-center justify-center bg-transparent border-none outline-none select-none cursor-pointer rounded-xl hover:bg-primary-50/50 text-muted hover:text-body transition-colors",
1965
+ "aria-label": "Close sidebar",
1966
+ children: /* @__PURE__ */ jsx(X, { size: 20 })
1967
+ })]
1968
+ })]
1969
+ }), /* @__PURE__ */ jsxs(Sidebar$1.Content, {
1970
+ className: defaultContentClassName,
1971
+ children: [hasUtilities && /* @__PURE__ */ jsxs("div", {
1972
+ className: "flex flex-col gap-4 mb-10",
1973
+ children: [/* @__PURE__ */ jsxs("div", {
1974
+ className: "flex gap-3",
1975
+ children: [config.versions && /* @__PURE__ */ jsx(VersionSelector, { className: "flex-1 justify-between h-10 bg-surface border-subtle rounded-xl" }), config.i18n && /* @__PURE__ */ jsx(I18nSelector, { className: "flex-1 justify-between h-10 bg-surface border-subtle rounded-xl" })]
1976
+ }), /* @__PURE__ */ jsx("div", { className: "mt-2 border-b border-subtle" })]
1977
+ }), /* @__PURE__ */ jsx(Sidebar$1.Items, {
1978
+ routes,
1979
+ className: "flex flex-col gap-6",
1980
+ classNames: defaultItemsClassNames
1981
+ })]
1790
1982
  })]
1791
- })] }), /* @__PURE__ */ jsxs(Sidebar$1.Content, { children: [hasUtilities && /* @__PURE__ */ jsxs("div", {
1792
- className: "flex flex-col gap-4 mb-10",
1793
- children: [/* @__PURE__ */ jsxs("div", {
1794
- className: "flex gap-3",
1795
- children: [config.versions && /* @__PURE__ */ jsx(VersionSelector, { className: "flex-1 justify-between h-10 bg-surface border-subtle rounded-xl" }), config.i18n && /* @__PURE__ */ jsx(I18nSelector, { className: "flex-1 justify-between h-10 bg-surface border-subtle rounded-xl" })]
1796
- }), /* @__PURE__ */ jsx("div", { className: "mt-2 border-b border-subtle" })]
1797
- }), /* @__PURE__ */ jsx(Sidebar$1.Items, { routes })] })] })] });
1983
+ })] });
1984
+ }
1985
+ function SidebarItems(props) {
1986
+ const { classNames, ...rest } = props;
1987
+ return /* @__PURE__ */ jsx(Sidebar$1.Items, {
1988
+ ...rest,
1989
+ className: cn("flex flex-col gap-6", rest.className),
1990
+ classNames: mergeSlots(defaultItemsClassNames, classNames)
1991
+ });
1798
1992
  }
1799
1993
  const Sidebar = Object.assign(SidebarMain, {
1800
1994
  Root: Sidebar$1.Root,
@@ -1805,7 +1999,7 @@ const Sidebar = Object.assign(SidebarMain, {
1805
1999
  Link: Sidebar$1.Link,
1806
2000
  SubGroup: Sidebar$1.SubGroup,
1807
2001
  Item: Sidebar$1.Item,
1808
- Items: Sidebar$1.Items
2002
+ Items: SidebarItems
1809
2003
  });
1810
2004
 
1811
2005
  //#endregion
@@ -1969,10 +2163,12 @@ function Giscus({ className }) {
1969
2163
  return () => {
1970
2164
  initializedRef.current = false;
1971
2165
  if (script.parentNode) script.parentNode.removeChild(script);
1972
- (ref.current?.querySelectorAll("iframe"))?.forEach((iframe) => iframe.remove());
2166
+ (ref.current?.querySelectorAll("iframe"))?.forEach((iframe) => {
2167
+ iframe.remove();
2168
+ });
1973
2169
  };
1974
2170
  }
1975
- }, [giscusConfig]);
2171
+ }, [giscusConfig, resolvedTheme]);
1976
2172
  useEffect(() => {
1977
2173
  if (!giscusConfig || !initializedRef.current) return;
1978
2174
  const theme = giscusConfig.theme || (resolvedTheme === "dark" ? "dark" : "light");
@@ -1986,14 +2182,44 @@ function Giscus({ className }) {
1986
2182
  });
1987
2183
  }
1988
2184
 
2185
+ //#endregion
2186
+ //#region src/shared/mdx-path.ts
2187
+ function normalizeMdxPath(value) {
2188
+ return value.replace(/\\/g, "/").replace(/^\/+/, "");
2189
+ }
2190
+ function matchesMdxUpdatePath(routeFilePath, updateRelativePath) {
2191
+ return normalizeMdxPath(routeFilePath) === normalizeMdxPath(updateRelativePath);
2192
+ }
2193
+
1989
2194
  //#endregion
1990
2195
  //#region src/client/ssg/mdx-elements.tsx
1991
- const Loading = () => /* @__PURE__ */ jsx("div", {
1992
- className: "text-muted text-sm py-4",
1993
- children: "Loading..."
1994
- });
1995
- function resolveModuleLoader(loader) {
1996
- return typeof loader === "function" ? loader() : Promise.resolve(loader);
2196
+ async function resolveModuleLoader(loader) {
2197
+ if (typeof loader !== "function") return Promise.resolve(loader);
2198
+ try {
2199
+ const res = loader();
2200
+ if (res && typeof res.then === "function") return await res;
2201
+ return { default: loader };
2202
+ } catch {
2203
+ return { default: loader };
2204
+ }
2205
+ }
2206
+ function extractMdxComponent(mod, moduleKey, pathKey) {
2207
+ if (!mod) return null;
2208
+ let target = mod;
2209
+ if (mod.default !== void 0) target = mod.default;
2210
+ if (typeof target === "function" || typeof target === "string") return target;
2211
+ if (target && typeof target === "object") {
2212
+ const possibleKeys = [moduleKey, pathKey].filter(Boolean);
2213
+ for (const key of possibleKeys) {
2214
+ const value = target[key];
2215
+ if (value) {
2216
+ if (typeof value === "function" || typeof value === "string") return value;
2217
+ if (typeof value === "object" && value.default) return value.default;
2218
+ }
2219
+ }
2220
+ if (target.default !== void 0) return extractMdxComponent({ default: target.default }, moduleKey, pathKey);
2221
+ }
2222
+ return null;
1997
2223
  }
1998
2224
  const EagerMdxElement = ({ moduleLoader, moduleKey, route, components, collectionPostComponent }) => {
1999
2225
  const [mod, setMod] = useState(moduleLoader);
@@ -2003,16 +2229,20 @@ const EagerMdxElement = ({ moduleLoader, moduleKey, route, components, collectio
2003
2229
  useEffect(() => {
2004
2230
  if (!import.meta.hot || !moduleKey) return;
2005
2231
  const handler = (data) => {
2006
- if (data.relPath.replace(/\\/g, "/").replace(/^\//, "") !== route.filePath.replace(/\\/g, "/").replace(/^\//, "")) return;
2007
- import(moduleKey + "?t=" + Date.now()).then((m) => {
2232
+ if (!matchesMdxUpdatePath(route.filePath, data.relPath)) return;
2233
+ import(`${import.meta.env.BASE_URL.replace(/\/$/, "")}${moduleKey}?t=${Date.now()}`).then((m) => {
2008
2234
  setMod(m);
2009
2235
  });
2010
2236
  };
2011
- import.meta.hot.on("boltdocs:mdx-update", handler);
2012
- return () => import.meta.hot?.off("boltdocs:mdx-update", handler);
2237
+ const hot = import.meta.hot;
2238
+ hot.on("boltdocs:mdx-update", handler);
2239
+ return () => hot.off?.("boltdocs:mdx-update", handler);
2013
2240
  }, [moduleKey, route.filePath]);
2014
- const MDXComponent = mod?.default ?? mod ?? null;
2015
- if (!MDXComponent) return /* @__PURE__ */ jsx(Loading, {});
2241
+ const MDXComponent = extractMdxComponent(mod, moduleKey, route.path || "/");
2242
+ if (!MDXComponent) return /* @__PURE__ */ jsxs("div", {
2243
+ className: "prose dark:prose-invert py-8",
2244
+ children: [/* @__PURE__ */ jsx("h1", { children: route.title }), route.description && /* @__PURE__ */ jsx("p", { children: route.description })]
2245
+ });
2016
2246
  return /* @__PURE__ */ jsx(MdxPage, {
2017
2247
  MDXComponent,
2018
2248
  mdxComponents: components ?? {},
@@ -2086,21 +2316,59 @@ function buildModuleMap(mdxModules) {
2086
2316
  }
2087
2317
  return moduleMap;
2088
2318
  }
2319
+ function cleanModulePath(value) {
2320
+ return value.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/\([^)]+\)/g, "").replace(/^\([^)]+\)\//, "");
2321
+ }
2322
+ /**
2323
+ * Resolves a route source path against both Vite glob keys and relative keys.
2324
+ * Exact cleaned matches are preferred so `es/guide.md` cannot select the
2325
+ * default-locale `guide.md` when both modules are present.
2326
+ */
2327
+ function resolveModuleKey(filePath, moduleMap) {
2328
+ const normalized = filePath.replace(/\\/g, "/");
2329
+ const cleanPath = cleanModulePath(normalized);
2330
+ for (const candidate of [
2331
+ normalized,
2332
+ normalized.replace(/^\//, ""),
2333
+ cleanPath
2334
+ ]) {
2335
+ const direct = moduleMap.get(candidate);
2336
+ if (direct) return direct;
2337
+ }
2338
+ for (const [relativePath, moduleKey] of moduleMap.entries()) {
2339
+ const cleanRelative = cleanModulePath(relativePath);
2340
+ if (normalized.endsWith(relativePath) || relativePath.endsWith(normalized) || cleanPath.endsWith(cleanRelative) || cleanRelative.endsWith(cleanPath)) return moduleKey;
2341
+ }
2342
+ }
2089
2343
 
2090
2344
  //#endregion
2091
2345
  //#region src/client/ssg/create-routes.doc.tsx
2346
+ function FallbackWrapper({ children }) {
2347
+ return /* @__PURE__ */ jsx(Fragment$1, { children });
2348
+ }
2092
2349
  function buildDocRoutes(options) {
2093
2350
  const { routesData, config, mdxModules, components, externalPages } = options;
2094
2351
  const baseDocsPath = (config.base || "/docs").replace(/\/$/, "") || "/";
2095
2352
  const defaultVersionMetadata = [];
2096
2353
  const defaultVersion = config.versions?.defaultVersion;
2097
2354
  const docsBase = (config.base || "/docs").replace(/\/$/, "");
2355
+ const urlConfig = {
2356
+ base: config.base,
2357
+ i18n: config.i18n,
2358
+ versions: config.versions,
2359
+ collections: []
2360
+ };
2098
2361
  if (defaultVersion) routesData.filter((r) => !r.collection).forEach((route) => {
2099
2362
  if (route.version) return;
2100
2363
  const p = route.path || "";
2101
2364
  const subPath = p.startsWith(docsBase) ? p.substring(docsBase.length).replace(/^\//, "") : p.replace(/^\//, "");
2102
- if (!(subPath === defaultVersion || subPath.startsWith(`${defaultVersion}/`))) {
2103
- const explicitPath = `${docsBase}/${defaultVersion}/${subPath}`.replace(/\/+/g, "/").replace(/\/$/, "") || "/";
2365
+ if (!(route.version === defaultVersion)) {
2366
+ const explicitPath = buildUrl({
2367
+ kind: "doc",
2368
+ path: `/${subPath}`,
2369
+ locale: route.locale,
2370
+ version: defaultVersion
2371
+ }, urlConfig);
2104
2372
  defaultVersionMetadata.push({
2105
2373
  ...route,
2106
2374
  path: explicitPath,
@@ -2110,16 +2378,22 @@ function buildDocRoutes(options) {
2110
2378
  });
2111
2379
  const docMetadata = [...routesData.filter((r) => !r.collection), ...defaultVersionMetadata];
2112
2380
  const moduleMap = buildModuleMap(mdxModules);
2113
- const mdxModuleKeys = Object.keys(mdxModules);
2114
- const isLazy = mdxModuleKeys.length > 0 && typeof mdxModules[mdxModuleKeys[0]] === "function";
2381
+ const routeMetadata = /* @__PURE__ */ new Map();
2382
+ const getDocumentIdentity = (route) => {
2383
+ const parsed = parseUrlReference(route.path, urlConfig, { kind: "doc" });
2384
+ return {
2385
+ path: parsed.routePath,
2386
+ version: route.version || parsed.version || config.versions?.defaultVersion
2387
+ };
2388
+ };
2115
2389
  const docRoutes = docMetadata.map((route) => {
2116
- const normalizedFilePath = route.filePath.replace(/\\/g, "/");
2117
- const moduleKey = moduleMap.get(normalizedFilePath);
2390
+ const moduleKey = resolveModuleKey(route.filePath, moduleMap);
2118
2391
  const moduleLoader = moduleKey ? mdxModules[moduleKey] : null;
2119
2392
  const fullPath = withBase(route.path === "" ? "/" : route.path, config);
2120
2393
  const path = fullPath === baseDocsPath ? "." : fullPath.startsWith(baseDocsPath + "/") ? fullPath.slice(baseDocsPath.length + 1) : fullPath;
2121
2394
  const routeRecord = {
2122
2395
  path,
2396
+ locale: route.locale,
2123
2397
  loader: async () => ({
2124
2398
  path,
2125
2399
  frontmatter: {
@@ -2139,7 +2413,8 @@ function buildDocRoutes(options) {
2139
2413
  }),
2140
2414
  getStaticPaths: () => [path]
2141
2415
  };
2142
- if (isLazy && moduleLoader) routeRecord.lazy = async () => {
2416
+ routeMetadata.set(routeRecord, route);
2417
+ if (moduleLoader) routeRecord.lazy = async () => {
2143
2418
  const mod = await resolveModuleLoader(moduleLoader);
2144
2419
  return { Component: function LoadedMdxRoute() {
2145
2420
  return /* @__PURE__ */ jsx(EagerMdxElement, {
@@ -2147,7 +2422,7 @@ function buildDocRoutes(options) {
2147
2422
  moduleLoader: mod,
2148
2423
  route,
2149
2424
  components: components ?? {}
2150
- }, moduleKey || path);
2425
+ }, `${moduleKey || path}-${route.locale || config.i18n?.defaultLocale || "en"}`);
2151
2426
  } };
2152
2427
  };
2153
2428
  else routeRecord.element = /* @__PURE__ */ jsx(EagerMdxElement, {
@@ -2155,95 +2430,147 @@ function buildDocRoutes(options) {
2155
2430
  moduleLoader: moduleLoader ?? {},
2156
2431
  route,
2157
2432
  components: components ?? {}
2158
- }, moduleKey || path);
2433
+ }, `${moduleKey || path}-${route.locale || config.i18n?.defaultLocale || "en"}`);
2159
2434
  return routeRecord;
2160
2435
  });
2161
2436
  const locales = config.i18n?.locales ? Array.isArray(config.i18n.locales) ? config.i18n.locales : Object.keys(config.i18n.locales) : [];
2162
2437
  const allVersions = config.versions?.versions?.map((v) => v.path) || [];
2163
2438
  const targetBasePaths = [];
2164
- targetBasePaths.push({
2165
- path: baseDocsPath,
2166
- filter: () => true,
2167
- locale: config.i18n?.defaultLocale,
2168
- version: config.versions?.defaultVersion
2169
- });
2170
- if (allVersions.length > 0) allVersions.forEach((v) => {
2171
- const fullP = baseDocsPath === "/" ? `/${v}` : `${baseDocsPath}/${v}`;
2439
+ const addTargetBase = (locale, version) => {
2440
+ const fullPath = buildUrl({
2441
+ kind: "doc",
2442
+ path: "/",
2443
+ locale,
2444
+ version
2445
+ }, urlConfig);
2172
2446
  targetBasePaths.push({
2173
- path: fullP,
2174
- filter: (rp) => !!rp && rp.startsWith(fullP.replace(/\/$/, "") + "/"),
2175
- locale: config.i18n?.defaultLocale,
2176
- version: v
2447
+ path: fullPath,
2448
+ locale,
2449
+ version
2177
2450
  });
2178
- });
2179
- if (locales.length > 0) locales.forEach((l) => {
2180
- const fullP = baseDocsPath === "/" ? `/${l}` : `${baseDocsPath}/${l}`;
2181
- targetBasePaths.push({
2182
- path: fullP,
2183
- filter: (rp) => !!rp && rp.startsWith(fullP.replace(/\/$/, "") + "/"),
2184
- locale: l,
2185
- version: config.versions?.defaultVersion
2186
- });
2187
- });
2188
- if (allVersions.length > 0 && locales.length > 0) allVersions.forEach((v) => {
2189
- locales.forEach((l) => {
2190
- const fullP = baseDocsPath === "/" ? `/${v}/${l}` : `${baseDocsPath}/${v}/${l}`;
2191
- targetBasePaths.push({
2192
- path: fullP,
2193
- filter: (rp) => !!rp && rp.startsWith(fullP.replace(/\/$/, "") + "/"),
2194
- locale: l,
2195
- version: v
2196
- });
2197
- });
2198
- });
2451
+ };
2452
+ addTargetBase(config.i18n?.defaultLocale);
2453
+ for (const version of allVersions) addTargetBase(config.i18n?.defaultLocale, version);
2454
+ for (const locale of locales) addTargetBase(locale);
2455
+ for (const version of allVersions) for (const locale of locales) addTargetBase(locale, version);
2199
2456
  const docPathRegistry = new Set(docRoutes.map((r) => (r.path || "").replace(/\/$/, "")));
2200
2457
  const externalPaths = /* @__PURE__ */ new Set();
2201
2458
  if (externalPages) Object.keys(externalPages).forEach((rawPath) => {
2202
2459
  const p = rawPath.startsWith("/") ? rawPath : `/${rawPath}`;
2203
2460
  externalPaths.add(p.replace(/\/$/, ""));
2204
- if (config.i18n) Object.keys(config.i18n.locales).forEach((locale) => {
2461
+ if (config.i18n) (Array.isArray(config.i18n.locales) ? config.i18n.locales : Object.keys(config.i18n.locales)).forEach((locale) => {
2205
2462
  externalPaths.add(`/${locale}${p === "/" ? "" : p}`.replace(/\/$/, ""));
2206
2463
  });
2207
2464
  });
2208
- targetBasePaths.forEach(({ path: bPath, filter, locale: bLocale, version: bVersion }) => {
2465
+ targetBasePaths.forEach(({ path: bPath, locale: bLocale, version: bVersion }) => {
2209
2466
  if (bPath === "/") return;
2210
2467
  const normalizedPath = bPath.replace(/\/$/, "");
2211
2468
  if (!(docPathRegistry.has(normalizedPath) || externalPaths.has(normalizedPath))) {
2212
2469
  const defaultTab = config.theme?.tabs?.[0]?.id;
2213
2470
  const defaultTabPath = defaultTab ? `${normalizedPath}/${defaultTab}`.replace(/\/+/g, "/") : null;
2214
- let matchedRouteObj = defaultTabPath && docPathRegistry.has(defaultTabPath.replace(/\/$/, "")) ? docRoutes.find((r) => r.path && r.path.replace(/\/$/, "") === defaultTabPath.replace(/\/$/, "")) : docRoutes.find((r) => r.path && filter(r.path) && r.path !== normalizedPath);
2471
+ let matchedRouteObj = defaultTabPath ? docRoutes.find((r) => {
2472
+ return routeMetadata.get(r)?.path?.replace(/\/$/, "") === defaultTabPath.replace(/\/$/, "");
2473
+ }) : void 0;
2474
+ if (!matchedRouteObj) matchedRouteObj = docRoutes.find((r) => {
2475
+ const routePath = routeMetadata.get(r)?.path?.replace(/\/$/, "");
2476
+ return !!routePath && (routePath === normalizedPath || routePath.startsWith(`${normalizedPath}/`));
2477
+ });
2215
2478
  if (!matchedRouteObj && docRoutes.length > 0) matchedRouteObj = docRoutes[0];
2479
+ if (matchedRouteObj && bLocale) {
2480
+ const localeBaseRoute = matchedRouteObj;
2481
+ const defaultLocale = config.i18n?.defaultLocale;
2482
+ const matchedMeta = routeMetadata.get(localeBaseRoute);
2483
+ const matchedIdentity = matchedMeta ? getDocumentIdentity(matchedMeta) : void 0;
2484
+ const localeMatch = docRoutes.find((r) => {
2485
+ const meta = routeMetadata.get(r);
2486
+ if (!meta || !matchedIdentity) return false;
2487
+ if (!(meta.locale === bLocale || bLocale === defaultLocale && !meta.locale)) return false;
2488
+ const identity = getDocumentIdentity(meta);
2489
+ return identity.path === matchedIdentity.path && identity.version === (bVersion || config.versions?.defaultVersion);
2490
+ });
2491
+ if (localeMatch) matchedRouteObj = localeMatch;
2492
+ }
2216
2493
  if (matchedRouteObj) {
2494
+ const matchedRoute = matchedRouteObj;
2217
2495
  const redirectPath = bPath === baseDocsPath ? "." : bPath.startsWith(baseDocsPath + "/") ? bPath.slice(baseDocsPath.length + 1) : bPath;
2218
2496
  const isBasePathFallback = redirectPath === ".";
2219
- docRoutes.push({
2497
+ const matchedMetaObj = (() => {
2498
+ const matchedMeta = routeMetadata.get(matchedRoute);
2499
+ if (!matchedMeta) return void 0;
2500
+ const matchedIdentity = getDocumentIdentity(matchedMeta);
2501
+ const targetVersion = bVersion || config.versions?.defaultVersion;
2502
+ const targetLocale = bLocale || config.i18n?.defaultLocale;
2503
+ return docMetadata.find((m) => {
2504
+ const identity = getDocumentIdentity(m);
2505
+ const locale = m.locale || config.i18n?.defaultLocale;
2506
+ return identity.path === matchedIdentity.path && locale === targetLocale && identity.version === targetVersion;
2507
+ }) || matchedMeta;
2508
+ })();
2509
+ const fallbackMetaObj = matchedMetaObj ? {
2510
+ ...matchedMetaObj,
2511
+ path: bPath,
2512
+ filePath: matchedMetaObj.filePath,
2513
+ locale: bLocale,
2514
+ version: bVersion,
2515
+ slugParts: [],
2516
+ fallback: true,
2517
+ seo: {
2518
+ ...matchedMetaObj.seo,
2519
+ canonical: config.siteUrl ? `${config.siteUrl.replace(/\/$/, "")}${withBase(bPath, config)}` : withBase(bPath, config)
2520
+ }
2521
+ } : {
2522
+ path: bPath,
2523
+ filePath: "",
2524
+ title: redirectPath,
2525
+ componentPath: "",
2526
+ headings: [],
2527
+ locale: bLocale,
2528
+ version: bVersion,
2529
+ fallback: true
2530
+ };
2531
+ const fallbackRoute = {
2220
2532
  ...isBasePathFallback ? { index: true } : { path: redirectPath },
2221
- element: matchedRouteObj.element,
2222
- lazy: matchedRouteObj.lazy,
2223
- loader: matchedRouteObj.loader,
2533
+ locale: bLocale,
2534
+ loader: matchedRoute.loader,
2224
2535
  getStaticPaths: () => []
2536
+ };
2537
+ const resolveFallbackComponent = async () => {
2538
+ const moduleKey = resolveModuleKey(fallbackMetaObj.filePath, moduleMap);
2539
+ const moduleLoader = moduleKey ? mdxModules[moduleKey] : null;
2540
+ const localeKey = bLocale || config.i18n?.defaultLocale || "en";
2541
+ const key = `${moduleKey || bPath}-${localeKey}`;
2542
+ if (!moduleLoader) return null;
2543
+ return /* @__PURE__ */ jsx(EagerMdxElement, {
2544
+ moduleKey,
2545
+ moduleLoader: await resolveModuleLoader(moduleLoader),
2546
+ route: fallbackMetaObj,
2547
+ components: components ?? {}
2548
+ }, key);
2549
+ };
2550
+ if (matchedRoute.lazy) fallbackRoute.lazy = async () => {
2551
+ const fallbackComponent = await resolveFallbackComponent();
2552
+ return { Component: () => /* @__PURE__ */ jsx(FallbackWrapper, { children: fallbackComponent }) };
2553
+ };
2554
+ else if (matchedRoute.element) fallbackRoute.element = matchedRoute.element;
2555
+ fallbackRoute.loader = async () => ({
2556
+ path: redirectPath,
2557
+ frontmatter: {
2558
+ title: fallbackMetaObj.title,
2559
+ description: fallbackMetaObj.description || "",
2560
+ ...fallbackMetaObj.frontmatter || {}
2561
+ },
2562
+ seo: fallbackMetaObj.seo,
2563
+ headings: fallbackMetaObj.headings || [],
2564
+ filePath: fallbackMetaObj.filePath,
2565
+ locale: fallbackMetaObj.locale,
2566
+ version: fallbackMetaObj.version,
2567
+ group: fallbackMetaObj.group,
2568
+ groupTitle: fallbackMetaObj.groupTitle,
2569
+ date: fallbackMetaObj.date,
2570
+ lastUpdated: fallbackMetaObj.lastUpdated
2225
2571
  });
2226
- const matchedMetaObj = docMetadata.find((m) => {
2227
- const fullPath = withBase(m.path === "" ? "/" : m.path, config);
2228
- return (fullPath === baseDocsPath ? "." : fullPath.startsWith(baseDocsPath + "/") ? fullPath.slice(baseDocsPath.length + 1) : fullPath) === matchedRouteObj.path;
2229
- });
2230
- if (matchedMetaObj) {
2231
- const canonicalPath = withBase(matchedMetaObj.path, config);
2232
- const canonicalUrl = config.siteUrl ? `${config.siteUrl.replace(/\/$/, "")}${canonicalPath}` : canonicalPath;
2233
- docMetadata.push({
2234
- ...matchedMetaObj,
2235
- path: bPath,
2236
- filePath: matchedMetaObj.filePath,
2237
- locale: bLocale,
2238
- version: bVersion,
2239
- slugParts: [],
2240
- fallback: true,
2241
- seo: {
2242
- ...matchedMetaObj.seo,
2243
- canonical: canonicalUrl
2244
- }
2245
- });
2246
- }
2572
+ docRoutes.push(fallbackRoute);
2573
+ docMetadata.push(fallbackMetaObj);
2247
2574
  }
2248
2575
  }
2249
2576
  });
@@ -2264,6 +2591,97 @@ function ExternalPageWrapper({ children }) {
2264
2591
 
2265
2592
  //#endregion
2266
2593
  //#region src/client/ssg/create-routes.external.tsx
2594
+ function getLocales(config) {
2595
+ if (!config.i18n) return [];
2596
+ return Array.isArray(config.i18n.locales) ? config.i18n.locales : Object.keys(config.i18n.locales);
2597
+ }
2598
+ function getLocalizedPaths(pathname, config) {
2599
+ const paths = [{
2600
+ path: pathname,
2601
+ locale: config.i18n?.defaultLocale
2602
+ }];
2603
+ for (const locale of getLocales(config)) {
2604
+ const localizedPath = pathname === "/" ? `/${locale}` : `/${locale}${pathname}`;
2605
+ if (!paths.some((entry) => entry.path === localizedPath)) paths.push({
2606
+ path: localizedPath,
2607
+ locale
2608
+ });
2609
+ }
2610
+ return paths;
2611
+ }
2612
+ /**
2613
+ * Content wrapper for file-routed MDX pages. The external layout renders the
2614
+ * site chrome (navbar/footer), but unlike docs pages these routes are not
2615
+ * nested inside DocsLayout, so the MDX content needs its own page container
2616
+ * with the page title and prose typography — otherwise it renders raw and
2617
+ * edge-to-edge directly below the navbar.
2618
+ */
2619
+ function ExternalMdxContent({ title, children, className, contentClassName }) {
2620
+ return /* @__PURE__ */ jsx("div", {
2621
+ className: cn("boltdocs-page w-full pt-4 pb-20 px-4 sm:px-8", className),
2622
+ children: /* @__PURE__ */ jsxs("div", {
2623
+ className: cn("mx-auto w-full max-w-3xl sm:max-w-4xl lg:max-w-5xl", contentClassName),
2624
+ children: [title && /* @__PURE__ */ jsx("h1", {
2625
+ className: "text-4xl font-bold tracking-tight text-body mb-3",
2626
+ children: title
2627
+ }), /* @__PURE__ */ jsx("div", {
2628
+ className: "prose prose-neutral dark:prose-invert max-w-none",
2629
+ children
2630
+ })]
2631
+ })
2632
+ });
2633
+ }
2634
+ function routeTitle(pathname) {
2635
+ const segment = pathname === "/" ? "Home" : pathname.split("/").filter(Boolean).at(-1);
2636
+ return segment ? segment.replace(/[-_]/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase()) : "Page";
2637
+ }
2638
+ function buildExternalRouteRecord(options) {
2639
+ const { path, locale, component, mdxLoader, externalLayout, components, title } = options;
2640
+ const record = {
2641
+ path,
2642
+ locale,
2643
+ loader: async () => ({
2644
+ path,
2645
+ locale
2646
+ }),
2647
+ getStaticPaths: () => [path]
2648
+ };
2649
+ const ExternalLayout = externalLayout;
2650
+ const ExternalComponent = component;
2651
+ if (ExternalComponent) record.element = /* @__PURE__ */ jsx(ExternalPageWrapper, { children: /* @__PURE__ */ jsx(ExternalLayout, { children: /* @__PURE__ */ jsx(ExternalComponent, {}) }) });
2652
+ else if (mdxLoader) record.lazy = async () => {
2653
+ const module = await resolveModuleLoader(mdxLoader);
2654
+ return { Component: function ExternalMdxRoute() {
2655
+ return /* @__PURE__ */ jsx(ExternalPageWrapper, { children: /* @__PURE__ */ jsx(ExternalLayout, { children: /* @__PURE__ */ jsx(ExternalMdxContent, {
2656
+ title,
2657
+ children: /* @__PURE__ */ jsx(EagerMdxElement, {
2658
+ moduleLoader: module,
2659
+ moduleKey: path,
2660
+ route: {
2661
+ path,
2662
+ title,
2663
+ filePath: path,
2664
+ componentPath: "",
2665
+ headings: [],
2666
+ locale
2667
+ },
2668
+ components: components || {}
2669
+ })
2670
+ }) }) });
2671
+ } };
2672
+ };
2673
+ return record;
2674
+ }
2675
+ function buildExternalRouteMetadata(path, locale, title) {
2676
+ return {
2677
+ path,
2678
+ locale,
2679
+ title,
2680
+ filePath: "",
2681
+ componentPath: "",
2682
+ headings: []
2683
+ };
2684
+ }
2267
2685
  function buildExternalRoutes(options) {
2268
2686
  const { externalPages, externalLayout, config } = options;
2269
2687
  const children = [];
@@ -2272,56 +2690,78 @@ function buildExternalRoutes(options) {
2272
2690
  children,
2273
2691
  metadata
2274
2692
  };
2275
- const EffectiveExternalLayout = externalLayout || (({ children }) => /* @__PURE__ */ jsx(Fragment, { children }));
2693
+ const EffectiveExternalLayout = externalLayout || (({ children }) => /* @__PURE__ */ jsx(Fragment$1, { children }));
2276
2694
  Object.entries(externalPages).forEach(([rawPath, ExtComponent]) => {
2277
- const path = rawPath.startsWith("/") ? rawPath : `/${rawPath}`;
2278
- metadata.push({
2279
- path,
2280
- locale: config.i18n?.defaultLocale,
2281
- title: rawPath === "/" ? "Home" : rawPath.replace(/^\//, "").split("/").pop() || "Page",
2282
- filePath: "",
2283
- componentPath: "",
2284
- headings: []
2285
- });
2286
- children.push({
2287
- path,
2288
- element: /* @__PURE__ */ jsx(ExternalPageWrapper, { children: /* @__PURE__ */ jsx(EffectiveExternalLayout, { children: /* @__PURE__ */ jsx(ExtComponent, {}) }) }),
2289
- loader: async () => ({
2290
- path,
2291
- locale: config.i18n?.defaultLocale
2292
- }),
2293
- getStaticPaths: () => [path]
2294
- });
2295
- if (config.i18n) Object.keys(config.i18n.locales).forEach((locale) => {
2296
- const localePath = `/${locale}${rawPath === "/" ? "" : rawPath}`;
2297
- metadata.push({
2298
- path: localePath,
2299
- locale,
2300
- title: rawPath,
2301
- filePath: "",
2302
- componentPath: "",
2303
- headings: []
2304
- });
2305
- children.push({
2306
- path: localePath,
2307
- element: /* @__PURE__ */ jsx(ExternalPageWrapper, { children: /* @__PURE__ */ jsx(EffectiveExternalLayout, { children: /* @__PURE__ */ jsx(ExtComponent, {}) }) }),
2308
- loader: async () => ({
2309
- path: localePath,
2310
- locale
2311
- }),
2312
- getStaticPaths: () => [localePath]
2313
- });
2314
- });
2695
+ const pathname = rawPath.startsWith("/") ? rawPath : `/${rawPath}`;
2696
+ for (const localized of getLocalizedPaths(pathname, config)) {
2697
+ metadata.push(buildExternalRouteMetadata(localized.path, localized.locale, routeTitle(pathname)));
2698
+ children.push({ ...buildExternalRouteRecord({
2699
+ path: localized.path,
2700
+ locale: localized.locale,
2701
+ component: ExtComponent,
2702
+ externalLayout: EffectiveExternalLayout,
2703
+ title: routeTitle(pathname)
2704
+ }) });
2705
+ }
2315
2706
  });
2316
2707
  return {
2317
2708
  children,
2318
2709
  metadata
2319
2710
  };
2320
2711
  }
2712
+ function buildExternalFileRoutes(options) {
2713
+ const { externalFilePages, externalFileMdx, externalLayout, components, config } = options;
2714
+ const children = [];
2715
+ const metadata = [];
2716
+ if (!externalFilePages && !externalFileMdx) return {
2717
+ children,
2718
+ metadata
2719
+ };
2720
+ const EffectiveExternalLayout = externalLayout || (({ children }) => /* @__PURE__ */ jsx(Fragment$1, { children }));
2721
+ const locales = getLocales(config);
2722
+ const isLocalizedPath = (pathname) => locales.some((locale) => pathname === `/${locale}` || pathname.startsWith(`/${locale}/`));
2723
+ const localeOfPath = (pathname) => {
2724
+ const first = pathname.split("/").filter(Boolean)[0];
2725
+ return first && locales.includes(first) ? first : void 0;
2726
+ };
2727
+ const fileRoutes = new Set([...Object.keys(externalFilePages || {}), ...Object.keys(externalFileMdx || {})]);
2728
+ const registeredPaths = /* @__PURE__ */ new Set();
2729
+ const pushRoute = (pathname, locale, mdxLoader, component) => {
2730
+ if (registeredPaths.has(pathname)) return;
2731
+ registeredPaths.add(pathname);
2732
+ metadata.push(buildExternalRouteMetadata(pathname, locale, routeTitle(pathname)));
2733
+ children.push(buildExternalRouteRecord({
2734
+ path: pathname,
2735
+ locale,
2736
+ component,
2737
+ mdxLoader,
2738
+ externalLayout: EffectiveExternalLayout,
2739
+ components,
2740
+ title: routeTitle(pathname)
2741
+ }));
2742
+ };
2743
+ for (const pathname of fileRoutes) pushRoute(pathname, localeOfPath(pathname) || config.i18n?.defaultLocale, externalFileMdx?.[pathname], externalFilePages?.[pathname]);
2744
+ for (const pathname of fileRoutes) {
2745
+ if (isLocalizedPath(pathname)) continue;
2746
+ const mdxLoader = externalFileMdx?.[pathname];
2747
+ const component = externalFilePages?.[pathname];
2748
+ for (const localized of getLocalizedPaths(pathname, config)) pushRoute(localized.path, localized.locale, mdxLoader, component);
2749
+ }
2750
+ return {
2751
+ children,
2752
+ metadata
2753
+ };
2754
+ }
2321
2755
 
2322
2756
  //#endregion
2323
2757
  //#region src/client/collections/hooks.ts
2324
2758
  const DEFAULT_COLLECTION = "blog";
2759
+ const NO_DATE = Number.NEGATIVE_INFINITY;
2760
+ function getPostTimestamp(date) {
2761
+ if (!date) return NO_DATE;
2762
+ const timestamp = new Date(date).getTime();
2763
+ return Number.isFinite(timestamp) ? timestamp : NO_DATE;
2764
+ }
2325
2765
  /**
2326
2766
  * Returns the posts of a collection, filtered by the current locale and version.
2327
2767
  * Defaults to "blog" if no collection is specified.
@@ -2345,6 +2785,10 @@ function usePosts(collection = DEFAULT_COLLECTION, options) {
2345
2785
  const versionMatch = !currentVersion || postVersion === currentVersion;
2346
2786
  const draftMatch = options?.includeDrafts || !post.draft;
2347
2787
  return localeMatch && versionMatch && draftMatch;
2788
+ }).sort((a, b) => {
2789
+ const dateA = getPostTimestamp(a.date);
2790
+ const dateOrder = getPostTimestamp(b.date) - dateA;
2791
+ return dateOrder !== 0 ? dateOrder : a.path.localeCompare(b.path);
2348
2792
  });
2349
2793
  }, [
2350
2794
  posts,
@@ -2396,20 +2840,57 @@ function useRecentPosts(collection = DEFAULT_COLLECTION, count = 5) {
2396
2840
 
2397
2841
  //#endregion
2398
2842
  //#region src/client/ssg/create-routes.collection.tsx
2843
+ function CollectionIndex({ indexContent, ListElement }) {
2844
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [indexContent, /* @__PURE__ */ jsx(ListElement, {})] });
2845
+ }
2846
+ function splitCollectionPath(routePath) {
2847
+ return routePath.split("/").filter(Boolean);
2848
+ }
2849
+ /**
2850
+ * Resolve the child path of a collection post relative to its layout route.
2851
+ *
2852
+ * Route generation emits collection paths without the configured docs base
2853
+ * (e.g. `/blog/post` or `/es/blog/post`), while the collection layout lives
2854
+ * under the base (`/docs/blog`). Keeping everything after the collection
2855
+ * segment as a *relative* child path is what makes the router match
2856
+ * `/docs/blog/post` and the SSG emit `dist/docs/blog/post.html` — never a
2857
+ * root-level `/blog/post` that neither the matcher nor the cache can reach.
2858
+ */
2859
+ function getCollectionSubPath(routePath, colBase, collection) {
2860
+ if (routePath.startsWith(`${colBase}/`)) return routePath.slice(colBase.length + 1);
2861
+ const parts = splitCollectionPath(routePath);
2862
+ const collectionIndex = parts.indexOf(collection);
2863
+ if (collectionIndex >= 0) return parts.slice(collectionIndex + 1).join("/");
2864
+ return parts.join("/");
2865
+ }
2866
+ function isCollectionIndexRoute(route, collection) {
2867
+ const normalizedPath = route.path.replace(/\/$/, "");
2868
+ return normalizedPath === `/${collection}` || normalizedPath.endsWith(`/${collection}`);
2869
+ }
2870
+ function getCollectionModule(route, moduleMap, mdxModules) {
2871
+ const moduleKey = resolveModuleKey(route.filePath, moduleMap);
2872
+ return {
2873
+ moduleKey,
2874
+ moduleLoader: moduleKey ? mdxModules[moduleKey] : null
2875
+ };
2876
+ }
2399
2877
  function DefaultCollectionList() {
2400
- const location = useLocation$1();
2878
+ const location = useLocation();
2401
2879
  const config = useConfig();
2880
+ const loaderData = useLoaderData();
2402
2881
  const postsPerPage = config.collections?.postsPerPage ?? 10;
2403
- const segments = location.pathname.split("/").filter(Boolean);
2404
- const isLocalePrefixed = config.i18n && segments[0] in (config.i18n.locales || {});
2405
- const collection = isLocalePrefixed ? segments[1] : segments[0];
2406
- const currentPage = (isLocalePrefixed ? segments[3] : segments[2]) === "page" ? Number(isLocalePrefixed ? segments[4] : segments[3]) || 1 : 1;
2882
+ const basePath = location.pathname.replace(/\/page\/\d+\/?$/, "").replace(/\/$/, "");
2883
+ const segments = basePath.split("/").filter(Boolean);
2884
+ const collection = loaderData && loaderData.collection || segments[segments.length - 1];
2407
2885
  const allPosts = usePosts(collection);
2408
- const totalPages = Math.ceil(allPosts.length / postsPerPage);
2886
+ const totalPages = loaderData && loaderData.totalPages || Math.ceil(allPosts.length / postsPerPage);
2887
+ const currentPage = loaderData && loaderData.currentPage || (() => {
2888
+ const match = location.pathname.match(/\/page\/(\d+)\/?$/);
2889
+ return match ? Number(match[1]) : 1;
2890
+ })();
2409
2891
  const start = (currentPage - 1) * postsPerPage;
2410
- const posts = allPosts.slice(start, start + postsPerPage);
2892
+ const posts = loaderData && loaderData.posts || allPosts.slice(start, start + postsPerPage);
2411
2893
  if (!posts.length) return null;
2412
- const basePath = isLocalePrefixed ? `/${segments[0]}/${segments[1]}` : `/${segments[0]}`;
2413
2894
  return /* @__PURE__ */ jsxs("div", {
2414
2895
  className: "py-8 max-w-2xl mx-auto px-4",
2415
2896
  children: [
@@ -2476,111 +2957,197 @@ function buildCollectionRoutes(options) {
2476
2957
  collectionsMap.get(r.collection).push(r);
2477
2958
  }
2478
2959
  const moduleMap = buildModuleMap(mdxModules);
2479
- const mdxModuleKeys = Object.keys(mdxModules);
2480
- const isLazy = mdxModuleKeys.length > 0 && typeof mdxModules[mdxModuleKeys[0]] === "function";
2960
+ const defaultLocale = config.i18n?.defaultLocale || "en";
2481
2961
  for (const [colName, colRoutes] of collectionsMap) {
2482
- const colBase = `/${colName}`;
2483
- colRoutes.sort((a, b) => {
2484
- const da = a.date ? new Date(a.date).getTime() : 0;
2485
- return (b.date ? new Date(b.date).getTime() : 0) - da;
2486
- });
2487
- const colChildren = [];
2488
- for (const route of colRoutes) {
2489
- const normalizedFilePath = route.filePath.replace(/\\/g, "/");
2490
- const moduleKey = moduleMap.get(normalizedFilePath);
2491
- const moduleLoader = moduleKey ? mdxModules[moduleKey] : null;
2492
- const subPath = route.path.startsWith(colBase + "/") ? route.path.slice(colBase.length + 1) : route.path.replace(colBase, "") || "";
2493
- const routeWithCollection = {
2494
- ...route,
2495
- collection: colName
2496
- };
2497
- const postComponent = collectionPosts?.[colName];
2498
- const routeRecord = {
2499
- path: subPath,
2962
+ const postsByVariant = /* @__PURE__ */ new Map();
2963
+ const indexByVariant = /* @__PURE__ */ new Map();
2964
+ for (const r of colRoutes) {
2965
+ const effectiveLocale = r.locale || defaultLocale;
2966
+ const key = `${r.version || ""}::${effectiveLocale}`;
2967
+ if (!postsByVariant.has(key)) postsByVariant.set(key, []);
2968
+ if (isCollectionIndexRoute(r, colName)) indexByVariant.set(key, r);
2969
+ else postsByVariant.get(key).push(r);
2970
+ }
2971
+ for (const [variant, variantRoutes] of postsByVariant) {
2972
+ const [version, locale] = variant.split("::");
2973
+ const localeRoutes = variantRoutes;
2974
+ const colBase = buildUrl({
2975
+ kind: "collection",
2976
+ collection: colName,
2977
+ path: "/",
2978
+ locale: locale === defaultLocale ? void 0 : locale,
2979
+ version: version || void 0
2980
+ }, {
2981
+ base: config.base,
2982
+ i18n: config.i18n,
2983
+ versions: config.versions,
2984
+ collections: [colName]
2985
+ });
2986
+ localeRoutes.sort((a, b) => {
2987
+ const getTimestamp = (date) => {
2988
+ if (!date) return Number.NEGATIVE_INFINITY;
2989
+ const value = new Date(date).getTime();
2990
+ return Number.isFinite(value) ? value : Number.NEGATIVE_INFINITY;
2991
+ };
2992
+ const da = getTimestamp(a.date);
2993
+ const dateOrder = getTimestamp(b.date) - da;
2994
+ return dateOrder !== 0 ? dateOrder : a.path.localeCompare(b.path);
2995
+ });
2996
+ const colChildren = [];
2997
+ for (const route of localeRoutes) {
2998
+ const { moduleKey, moduleLoader } = getCollectionModule(route, moduleMap, mdxModules);
2999
+ const subPath = getCollectionSubPath(route.path, colBase, colName);
3000
+ const routeWithCollection = {
3001
+ ...route,
3002
+ collection: colName
3003
+ };
3004
+ const postComponent = collectionPosts?.[colName];
3005
+ const routeRecord = {
3006
+ path: subPath,
3007
+ loader: async () => ({
3008
+ route: routeWithCollection,
3009
+ headings: route.headings || [],
3010
+ collection: colName,
3011
+ path: subPath,
3012
+ frontmatter: {
3013
+ title: route.title,
3014
+ description: route.description || "",
3015
+ ...route.frontmatter || {}
3016
+ },
3017
+ filePath: route.filePath,
3018
+ locale: route.locale,
3019
+ version: route.version,
3020
+ date: route.date,
3021
+ lastUpdated: route.lastUpdated
3022
+ }),
3023
+ getStaticPaths: () => [subPath || "."]
3024
+ };
3025
+ if (moduleLoader) routeRecord.lazy = async () => {
3026
+ const mod = await resolveModuleLoader(moduleLoader);
3027
+ return { Component: function LoadedCollectionMdxRoute() {
3028
+ return /* @__PURE__ */ jsx(EagerMdxElement, {
3029
+ moduleKey,
3030
+ moduleLoader: mod,
3031
+ route,
3032
+ components,
3033
+ collectionPostComponent: postComponent
3034
+ }, `${moduleKey || subPath}-${locale}`);
3035
+ } };
3036
+ };
3037
+ else routeRecord.element = /* @__PURE__ */ jsx(EagerMdxElement, {
3038
+ moduleKey,
3039
+ moduleLoader: {},
3040
+ route,
3041
+ components,
3042
+ collectionPostComponent: postComponent
3043
+ }, `${moduleKey || subPath}-${locale}`);
3044
+ colChildren.push(routeRecord);
3045
+ metadata.push(route);
3046
+ }
3047
+ const totalPages = Math.ceil(localeRoutes.length / postsPerPage);
3048
+ const paginatedPosts = localeRoutes.map((r) => ({
3049
+ path: r.path,
3050
+ title: r.title,
3051
+ date: r.date,
3052
+ excerpt: r.excerpt,
3053
+ tags: r.tags,
3054
+ author: r.author,
3055
+ coverImage: r.coverImage,
3056
+ filePath: r.filePath,
3057
+ locale: r.locale,
3058
+ version: r.version,
3059
+ lastUpdated: r.lastUpdated,
3060
+ frontmatter: r.frontmatter
3061
+ }));
3062
+ const ListElement = collectionLists?.[colName] || DefaultCollectionList;
3063
+ const indexRoute = indexByVariant.get(variant);
3064
+ const indexRecord = {
3065
+ index: true,
3066
+ path: "",
2500
3067
  loader: async () => ({
2501
- route: routeWithCollection,
2502
- headings: route.headings || [],
3068
+ posts: paginatedPosts.slice(0, postsPerPage),
3069
+ totalPages,
3070
+ currentPage: 1,
2503
3071
  collection: colName,
2504
- path: subPath,
2505
- frontmatter: {
2506
- title: route.title,
2507
- description: route.description || "",
2508
- ...route.frontmatter || {}
2509
- },
2510
- filePath: route.filePath,
2511
- locale: route.locale,
2512
- version: route.version,
2513
- date: route.date,
2514
- lastUpdated: route.lastUpdated
3072
+ ...indexRoute ? {
3073
+ route: indexRoute,
3074
+ headings: indexRoute.headings || [],
3075
+ frontmatter: {
3076
+ title: indexRoute.title,
3077
+ description: indexRoute.description || "",
3078
+ ...indexRoute.frontmatter || {}
3079
+ },
3080
+ filePath: indexRoute.filePath,
3081
+ locale: indexRoute.locale,
3082
+ version: indexRoute.version
3083
+ } : {}
2515
3084
  }),
2516
- getStaticPaths: () => [subPath || "."]
3085
+ getStaticPaths: () => [""]
2517
3086
  };
2518
- if (isLazy && moduleLoader) routeRecord.lazy = async () => {
2519
- const mod = await resolveModuleLoader(moduleLoader);
2520
- return { Component: function LoadedCollectionMdxRoute() {
2521
- return /* @__PURE__ */ jsx(EagerMdxElement, {
2522
- moduleKey,
2523
- moduleLoader: mod,
2524
- route,
2525
- components,
2526
- collectionPostComponent: postComponent
2527
- }, moduleKey || subPath);
2528
- } };
3087
+ if (indexRoute) {
3088
+ const { moduleKey, moduleLoader } = getCollectionModule(indexRoute, moduleMap, mdxModules);
3089
+ const postComponent = collectionPosts?.[colName];
3090
+ const createIndexContent = (resolvedModule) => /* @__PURE__ */ jsx(EagerMdxElement, {
3091
+ moduleKey,
3092
+ moduleLoader: resolvedModule,
3093
+ route: indexRoute,
3094
+ components,
3095
+ collectionPostComponent: postComponent
3096
+ }, `${moduleKey || colBase}-${locale}`);
3097
+ if (moduleLoader) indexRecord.lazy = async () => {
3098
+ const mod = await resolveModuleLoader(moduleLoader);
3099
+ return { Component: function LoadedCollectionIndex() {
3100
+ return /* @__PURE__ */ jsx(CollectionIndex, {
3101
+ indexContent: createIndexContent(mod),
3102
+ ListElement
3103
+ });
3104
+ } };
3105
+ };
3106
+ else indexRecord.element = /* @__PURE__ */ jsx(CollectionIndex, {
3107
+ indexContent: createIndexContent({}),
3108
+ ListElement
3109
+ });
3110
+ } else indexRecord.element = /* @__PURE__ */ jsx(ListElement, {});
3111
+ colChildren.unshift(indexRecord);
3112
+ metadata.push({
3113
+ path: colBase,
3114
+ locale,
3115
+ collection: colName,
3116
+ title: indexRoute?.title || colName,
3117
+ filePath: indexRoute?.filePath || "",
3118
+ componentPath: indexRoute?.componentPath || "",
3119
+ headings: indexRoute?.headings || []
3120
+ });
3121
+ for (let p = 2; p <= totalPages; p++) {
3122
+ const pagePath = `page/${p}`;
3123
+ colChildren.push({
3124
+ path: pagePath,
3125
+ element: /* @__PURE__ */ jsx(ListElement, {}),
3126
+ loader: async () => ({
3127
+ posts: paginatedPosts.slice((p - 1) * postsPerPage, p * postsPerPage),
3128
+ totalPages,
3129
+ currentPage: p,
3130
+ collection: colName
3131
+ }),
3132
+ getStaticPaths: () => [pagePath]
3133
+ });
3134
+ metadata.push({
3135
+ path: `${colBase}/${pagePath}`.replace(/\/+/g, "/"),
3136
+ locale,
3137
+ collection: colName,
3138
+ title: colName,
3139
+ filePath: "",
3140
+ componentPath: "",
3141
+ headings: []
3142
+ });
3143
+ }
3144
+ const blogLayoutRoute = {
3145
+ path: colBase,
3146
+ element: /* @__PURE__ */ jsx(collectionLayouts?.[colName] || DocsLayout, { collectionsData: collectionsData || {} }),
3147
+ children: colChildren
2529
3148
  };
2530
- else routeRecord.element = /* @__PURE__ */ jsx(EagerMdxElement, {
2531
- moduleKey,
2532
- moduleLoader,
2533
- route,
2534
- components,
2535
- collectionPostComponent: postComponent
2536
- }, moduleKey || subPath);
2537
- colChildren.push(routeRecord);
2538
- metadata.push(route);
3149
+ children.push(blogLayoutRoute);
2539
3150
  }
2540
- const totalPages = Math.ceil(colRoutes.length / postsPerPage);
2541
- const paginatedPosts = colRoutes.map((r) => ({
2542
- path: r.path,
2543
- title: r.title,
2544
- date: r.date,
2545
- excerpt: r.excerpt,
2546
- tags: r.tags,
2547
- author: r.author,
2548
- coverImage: r.coverImage,
2549
- filePath: r.filePath,
2550
- locale: r.locale,
2551
- version: r.version,
2552
- lastUpdated: r.lastUpdated,
2553
- frontmatter: r.frontmatter
2554
- }));
2555
- const ListElement = collectionLists?.[colName] || DefaultCollectionList;
2556
- colChildren.unshift({
2557
- index: true,
2558
- element: /* @__PURE__ */ jsx(ListElement, {}),
2559
- loader: async () => ({
2560
- posts: paginatedPosts.slice(0, postsPerPage),
2561
- totalPages,
2562
- currentPage: 1,
2563
- collection: colName
2564
- }),
2565
- getStaticPaths: () => []
2566
- });
2567
- for (let p = 2; p <= totalPages; p++) colChildren.push({
2568
- path: `page/${p}`,
2569
- element: /* @__PURE__ */ jsx(ListElement, {}),
2570
- loader: async () => ({
2571
- posts: paginatedPosts.slice((p - 1) * postsPerPage, p * postsPerPage),
2572
- totalPages,
2573
- currentPage: p,
2574
- collection: colName
2575
- }),
2576
- getStaticPaths: () => [`page/${p}`]
2577
- });
2578
- const blogLayoutRoute = {
2579
- path: colBase,
2580
- element: /* @__PURE__ */ jsx(collectionLayouts?.[colName] || DocsLayout, { collectionsData: collectionsData || {} }),
2581
- children: colChildren
2582
- };
2583
- children.push(blogLayoutRoute);
2584
3151
  }
2585
3152
  return {
2586
3153
  children,
@@ -2592,10 +3159,26 @@ function buildCollectionRoutes(options) {
2592
3159
  //#region src/client/ssg/create-routes.tsx
2593
3160
  function createRoutes(options) {
2594
3161
  const { config, components, externalLayout } = options;
2595
- const EffectiveExternalLayout = externalLayout || (({ children }) => /* @__PURE__ */ jsx(Fragment, { children }));
3162
+ const EffectiveExternalLayout = externalLayout || (({ children }) => /* @__PURE__ */ jsx(Fragment$1, { children }));
2596
3163
  const baseDocsPath = (config.base || "/docs").replace(/\/$/, "") || "/";
2597
3164
  const { routes: docRoutes, metadata: docMetadata } = buildDocRoutes(options);
2598
3165
  const externalRoutes = buildExternalRoutes(options);
3166
+ const externalFileRoutes = config.experimental?.fileRouting ? buildExternalFileRoutes(options) : {
3167
+ children: [],
3168
+ metadata: []
3169
+ };
3170
+ const occupiedPaths = new Set([...docMetadata.map((route) => route.path), ...externalRoutes.metadata.map((route) => route.path)]);
3171
+ const acceptedExternalFilePaths = /* @__PURE__ */ new Set();
3172
+ const filteredExternalFileRoutes = {
3173
+ children: externalFileRoutes.children,
3174
+ metadata: externalFileRoutes.metadata.filter((route) => {
3175
+ if (occupiedPaths.has(route.path)) return false;
3176
+ occupiedPaths.add(route.path);
3177
+ acceptedExternalFilePaths.add(route.path);
3178
+ return true;
3179
+ })
3180
+ };
3181
+ filteredExternalFileRoutes.children = externalFileRoutes.children.filter((route) => !route.path || acceptedExternalFilePaths.has(route.path));
2599
3182
  const collectionRoutes = buildCollectionRoutes(options);
2600
3183
  const children = [
2601
3184
  {
@@ -2604,18 +3187,20 @@ function createRoutes(options) {
2604
3187
  children: docRoutes
2605
3188
  },
2606
3189
  ...externalRoutes.children,
3190
+ ...filteredExternalFileRoutes.children,
2607
3191
  ...collectionRoutes.children,
2608
3192
  {
2609
3193
  path: "*",
2610
3194
  element: /* @__PURE__ */ jsx(ExternalPageWrapper, { children: /* @__PURE__ */ jsx(EffectiveExternalLayout, { children: /* @__PURE__ */ jsx(NotFoundWrapper, {}) }) })
2611
3195
  }
2612
3196
  ];
2613
- return [{
3197
+ const routes = [{
2614
3198
  element: /* @__PURE__ */ jsx(BoltdocsShell, {
2615
3199
  config,
2616
3200
  routes: [
2617
3201
  ...docMetadata,
2618
3202
  ...externalRoutes.metadata,
3203
+ ...filteredExternalFileRoutes.metadata,
2619
3204
  ...collectionRoutes.metadata
2620
3205
  ],
2621
3206
  components,
@@ -2623,6 +3208,19 @@ function createRoutes(options) {
2623
3208
  }),
2624
3209
  children
2625
3210
  }];
3211
+ const BaseAwareRouteRenderer = (props) => /* @__PURE__ */ jsx(RouteRenderer, {
3212
+ ...props,
3213
+ basename: config.base,
3214
+ defaultLocale: config.i18n?.defaultLocale,
3215
+ viewTransitions: config.experimental?.viewTransitions === true ? true : typeof config.experimental?.viewTransitions === "object" ? config.experimental.viewTransitions : void 0
3216
+ });
3217
+ return {
3218
+ routes,
3219
+ RouteRenderer: BaseAwareRouteRenderer,
3220
+ matchRouteBranch,
3221
+ matchRouteBranchWithParams,
3222
+ resolveRouteBranch
3223
+ };
2626
3224
  }
2627
3225
 
2628
3226
  //#endregion
@@ -2647,32 +3245,38 @@ function DocsLayoutComponent({ children }) {
2647
3245
  routes: filteredRoutes || [],
2648
3246
  config
2649
3247
  }),
2650
- /* @__PURE__ */ jsx(DocsLayout$1.Content, { children: /* @__PURE__ */ jsxs(DocsLayout$1.ContentMdx, { children: [
2651
- !isCollectionPage && /* @__PURE__ */ jsxs(DocsLayout$1.Header, { children: [
2652
- /* @__PURE__ */ jsxs("div", {
2653
- className: "mb-4 border-b border-subtle pb-4 flex flex-wrap items-center justify-between gap-3",
2654
- children: [/* @__PURE__ */ jsx(Breadcrumbs, {}), /* @__PURE__ */ jsx(CopyMarkdown, {
2655
- mdxRaw: currentRoute?._rawContent,
2656
- route: currentRoute
2657
- })]
2658
- }),
2659
- currentRoute?.title && /* @__PURE__ */ jsx("h1", {
2660
- className: "text-4xl font-bold tracking-tight text-default mb-3",
2661
- children: currentRoute.title
2662
- }),
2663
- currentRoute?.description && /* @__PURE__ */ jsx("p", {
2664
- className: "text-lg text-muted-foreground mb-6 leading-relaxed",
2665
- children: currentRoute.description
3248
+ /* @__PURE__ */ jsx(DocsLayout$1.Content, { children: /* @__PURE__ */ jsxs(DocsLayout$1.ContentMdx, {
3249
+ className: "pt-4 pb-20 px-4 sm:px-8",
3250
+ children: [
3251
+ !isCollectionPage && /* @__PURE__ */ jsxs(DocsLayout$1.Header, { children: [
3252
+ /* @__PURE__ */ jsxs("div", {
3253
+ className: "mb-4 border-b border-subtle pb-4 flex flex-wrap items-center justify-between gap-3",
3254
+ children: [/* @__PURE__ */ jsx(Breadcrumbs, {}), /* @__PURE__ */ jsx(CopyMarkdown, {
3255
+ mdxRaw: currentRoute?._rawContent,
3256
+ route: currentRoute
3257
+ })]
3258
+ }),
3259
+ currentRoute?.title && /* @__PURE__ */ jsx("h1", {
3260
+ className: "text-4xl font-bold tracking-tight text-body mb-3",
3261
+ children: currentRoute.title
3262
+ }),
3263
+ currentRoute?.description && /* @__PURE__ */ jsx("p", {
3264
+ className: "text-lg text-muted mb-6 leading-relaxed",
3265
+ children: currentRoute.description
3266
+ })
3267
+ ] }),
3268
+ /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx("div", {
3269
+ className: "prose prose-neutral dark:prose-invert max-w-none",
3270
+ children: children ?? /* @__PURE__ */ jsx(Outlet, {})
3271
+ }) }),
3272
+ !isCollectionPage && /* @__PURE__ */ jsx(Feedback, {}),
3273
+ !isCollectionPage && /* @__PURE__ */ jsx(Giscus, {}),
3274
+ !isCollectionPage && /* @__PURE__ */ jsx("div", {
3275
+ className: "mt-20",
3276
+ children: /* @__PURE__ */ jsx(PageNav, {})
2666
3277
  })
2667
- ] }),
2668
- /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx("div", {
2669
- className: "prose prose-neutral dark:prose-invert max-w-none",
2670
- children
2671
- }) }),
2672
- !isCollectionPage && /* @__PURE__ */ jsx(Feedback, {}),
2673
- !isCollectionPage && /* @__PURE__ */ jsx(Giscus, {}),
2674
- /* @__PURE__ */ jsx(DocsLayout$1.Footer, { children: !isCollectionPage && /* @__PURE__ */ jsx(PageNav, {}) })
2675
- ] }) }),
3278
+ ]
3279
+ }) }),
2676
3280
  /* @__PURE__ */ jsx("div", {
2677
3281
  className: "overflow-y-auto sticky",
2678
3282
  children: /* @__PURE__ */ jsx(OnThisPage, {
@@ -2702,4 +3306,46 @@ const reactToText = (node, resolvers) => {
2702
3306
  };
2703
3307
 
2704
3308
  //#endregion
2705
- export { Banner, BoltdocsShell, Breadcrumbs, CollectionsContext, CopyMarkdown, CurrentPostProvider, DocsLayoutComponent as DocsLayout, ErrorBoundary, MdxPage, Navbar, NotFound, OnThisPage, PageNav, SearchDialog, Sidebar, Timeline, ViteReactSSG, cn, copyToClipboard, createRoutes, getStarsRepo, getTranslated, reactToText, useAnalytics, useBreadcrumbs, useCodeBlock, useConfig, useFeedback, useHeadings, useI18n, useLocalizedTo, useLocation, useMdxComponents, useMergedComponents, useNavbar, usePageNav, usePost, usePosts, useRecentPosts, useRoutes, useSearch, useSearchHighlight, useSidebar, useTabs, useTheme, useTrackEvent, useTrackPageView, useUI, useVersion };
3309
+ //#region src/client/components/mdx/use-code-block.ts
3310
+ /**
3311
+ * @deprecated Use the feature-scoped hooks instead: `useCopyButton`,
3312
+ * `useExpandable` and `useCodeBlockFeedback`. This composition keeps the
3313
+ * historical single-hook API and will be removed in a future major version.
3314
+ */
3315
+ function useCodeBlock(props) {
3316
+ const lang = props.lang || props["data-lang"] || "";
3317
+ const isHighlighted = props["data-highlighted"] === "true" || typeof props.className === "string" && props.className.includes("shiki");
3318
+ const rawHighlightedHtml = props.highlightedHtml || props["data-highlighted-html"];
3319
+ const effectiveHighlightedHtml = typeof rawHighlightedHtml === "string" ? rawHighlightedHtml.replace(/<span class="line">\s*(?:<span[^>]*>\s*<\/span>)?\s*<\/span>\s*(<\/code>\s*<\/pre>)/g, "$1") : rawHighlightedHtml;
3320
+ const effectiveTitle = props.title || props["data-title"];
3321
+ const { copied, handleCopy } = useCopyButton();
3322
+ const { isExpanded, setIsExpanded, isExpandable, shouldTruncate, preRef } = useExpandable({
3323
+ children: props.children,
3324
+ highlightedHtml: effectiveHighlightedHtml
3325
+ });
3326
+ const { rated, handleRate, enabled: showCodeBlockFeedback } = useCodeBlockFeedback({
3327
+ plain: props.plain,
3328
+ lang
3329
+ });
3330
+ const copyFromRef = () => handleCopy(preRef.current?.textContent ?? "");
3331
+ const rateFromRef = (type) => handleRate(type, preRef.current?.textContent ?? "");
3332
+ return {
3333
+ copied,
3334
+ isExpanded,
3335
+ setIsExpanded,
3336
+ isExpandable,
3337
+ preRef,
3338
+ handleCopy: copyFromRef,
3339
+ shouldTruncate,
3340
+ isHighlighted,
3341
+ effectiveHighlightedHtml,
3342
+ effectiveTitle,
3343
+ lang,
3344
+ showCodeBlockFeedback,
3345
+ rated,
3346
+ handleRate: rateFromRef
3347
+ };
3348
+ }
3349
+
3350
+ //#endregion
3351
+ export { Banner, BoltdocsShell, Breadcrumbs, CollectionsContext, CopyMarkdown, CurrentPostProvider, DocsLayoutComponent as DocsLayout, ErrorBoundary, LocationContext, LocationProvider, MatchesContext, MdxPage, Navbar, NavigateContext, NotFound, OnThisPage, Outlet, OutletContext, PageNav, PrefetchContext, RouteDataContext, RouteRenderer, SearchDialog, Sidebar, StructuredData, Timeline, ViteReactSSG, addBasename, addUrlBase, buildUrl, classifyUrlPath, cn, copyToClipboard, createArticleStructuredData, createBreadcrumbStructuredData, createRoutes, createStructuredData, createWebSiteStructuredData, defaultNavigate, defineStructuredData, getConfiguredLocales, getConfiguredVersions, getStarsRepo, getTranslated, getVersionPrefixSegments, getVersionSegments, hasBasename, hasUriScheme, hasUrlBase, isConfiguredLocale, isConfiguredVersion, matchRouteBranch, matchRouteBranchWithParams, normalizeBasename, normalizeUrlBase, normalizeUrlPath, parseUrlReference, reactToText, resolvePublicAssetUrl, resolveRouteBranch, resolveUrlReference, splitUrlReference, startViewTransition, stripBasename, stripSiteProtocol, stripUrlBase, useAnalytics, useBreadcrumbs, useCodeBlock, useCodeBlockFeedback, useConfig, useCopyButton, useExpandable, useFeedback, useHeadings, useI18n, useLoaderData, useLocalizedTo, useLocation, useMatches, useMdxComponents, useMergedComponents, useNavbar, useNavigate, usePageNav, usePost, usePosts, usePrefetch, useRecentPosts, useRouteData, useRoutes, useSearch, useSearchHighlight, useSidebar, useTabs, useTheme, useTrackEvent, useTrackPageView, useUI, useVersion, useViewTransition };