boltdocs 2.6.2 → 2.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/boltdocs.js +0 -1
- package/dist/cache-CQKlT4fI.mjs +6 -0
- package/dist/cache-DorPMFgW.cjs +6 -0
- package/dist/cards-BLoSiRuL.d.ts +30 -0
- package/dist/cards-CQn9mXZS.d.cts +30 -0
- package/dist/chunk-Ds5LZdWN.cjs +6 -0
- package/dist/client/index.cjs +1 -1
- package/dist/client/index.d.cts +168 -1339
- package/dist/client/index.d.ts +167 -1338
- package/dist/client/index.js +1 -1
- package/dist/{package-CFP44vfn.cjs → client/mdx.cjs} +1 -1
- package/dist/client/mdx.d.cts +128 -0
- package/dist/client/mdx.d.ts +129 -0
- package/dist/client/mdx.js +6 -0
- package/dist/client/primitives.cjs +6 -0
- package/dist/client/primitives.d.cts +818 -0
- package/dist/client/primitives.d.ts +818 -0
- package/dist/client/primitives.js +6 -0
- package/dist/client/theme/neutral.css +74 -361
- package/dist/client/theme/reset.css +189 -0
- package/dist/docs-layout-BlDhcQRv.cjs +6 -0
- package/dist/docs-layout-BvAOWEJw.js +6 -0
- package/dist/doctor-BQiQhCTl.cjs +6 -0
- package/dist/doctor-COpf35L2.cjs +20 -0
- package/dist/doctor-Dh1XP7Pz.mjs +20 -0
- package/dist/generator-DGW6pkCC.cjs +22 -0
- package/dist/generator-Dv3wEmhZ.mjs +22 -0
- package/dist/icons-dev-CrQLjoQp.js +6 -0
- package/dist/icons-dev-rzdz6Lf3.cjs +6 -0
- package/dist/image-BkIfa9oo.js +6 -0
- package/dist/image-DIGjCPe6.cjs +6 -0
- package/dist/mdx-K0WYBAJ3.js +7 -0
- package/dist/mdx-hpErbRUe.cjs +7 -0
- package/dist/meta-loader-0gJ4PtBC.cjs +6 -0
- package/dist/meta-loader-9IpAHWDS.mjs +6 -0
- package/dist/node/cli-entry.cjs +1 -2
- package/dist/node/cli-entry.mjs +1 -2
- package/dist/node/index.cjs +1 -1
- package/dist/node/index.d.cts +55 -11
- package/dist/node/index.d.mts +55 -12
- package/dist/node/index.mjs +1 -1
- package/dist/node/routes/worker.cjs +6 -0
- package/dist/node/routes/worker.d.cts +2 -0
- package/dist/node/routes/worker.d.mts +2 -0
- package/dist/node/routes/worker.mjs +6 -0
- package/dist/node-BzKYJJuY.cjs +111 -0
- package/dist/node-m6fKXXVs.mjs +111 -0
- package/dist/{package-Bqbn1AYK.mjs → package-2TVh81ZC.mjs} +1 -1
- package/dist/package-D1O_gJub.cjs +6 -0
- package/dist/parser-Bh11BsdA.cjs +6 -0
- package/dist/parser-D8eQvE7N.mjs +6 -0
- package/dist/parser-DYRzXWmA.cjs +6 -0
- package/dist/routes-CHf76Ye4.cjs +6 -0
- package/dist/routes-CMUZGI6T.mjs +6 -0
- package/dist/routes-Co1mRM58.cjs +6 -0
- package/dist/search-dialog-BACuzoVX.cjs +6 -0
- package/dist/search-dialog-BKagVT17.js +6 -0
- package/dist/search-dialog-C8w12eUx.js +6 -0
- package/dist/search-dialog-CGyrozZE.cjs +6 -0
- package/dist/search-dialog-D26rUnJ_.cjs +6 -0
- package/dist/sidebar-DKvg6KOc.d.cts +491 -0
- package/dist/sidebar-Dr1TiRIy.d.ts +491 -0
- package/dist/utils-BxNAXhZZ.mjs +7 -0
- package/dist/utils-Clzu7jvb.cjs +7 -0
- package/dist/worker-pool-Bd8Y9KDv.mjs +6 -0
- package/dist/worker-pool-BwU8ckrg.cjs +6 -0
- package/package.json +27 -8
- package/src/client/app/doc-page.tsx +9 -5
- package/src/client/app/docs-layout.tsx +17 -3
- package/src/client/app/head.tsx +122 -0
- package/src/client/app/helmet-compat.tsx +36 -0
- package/src/client/app/mdx-component.tsx +5 -52
- package/src/client/app/mdx-components-context.tsx +32 -8
- package/src/client/app/routes-context.tsx +2 -2
- package/src/client/app/scroll-handler.tsx +1 -1
- package/src/client/app/theme-context.tsx +5 -5
- package/src/client/app/ui-context.tsx +42 -0
- package/src/client/components/docs-layout-default.tsx +85 -0
- package/src/client/components/icons-dev.tsx +38 -15
- package/src/client/components/mdx/callout.tsx +97 -0
- package/src/client/components/mdx/card.tsx +73 -98
- package/src/client/components/mdx/cards.tsx +27 -0
- package/src/client/components/mdx/code-block.tsx +37 -17
- package/src/client/components/mdx/field.tsx +24 -56
- package/src/client/components/mdx/image.tsx +36 -15
- package/src/client/components/mdx/index.ts +19 -53
- package/src/client/components/mdx/table.tsx +46 -148
- package/src/client/components/mdx/typographics.tsx +120 -0
- package/src/client/components/mdx/{hooks/use-code-block.ts → use-code-block.ts} +5 -7
- package/src/client/components/primitives/breadcrumbs.tsx +5 -24
- package/src/client/components/primitives/button.tsx +3 -142
- package/src/client/components/primitives/code-block.tsx +104 -97
- package/src/client/components/{docs-layout.tsx → primitives/docs-layout.tsx} +15 -24
- package/src/client/components/primitives/error-boundary.tsx +107 -0
- package/src/client/components/primitives/heading.tsx +128 -0
- package/src/client/components/primitives/helpers/observer.ts +62 -32
- package/src/client/components/primitives/image.tsx +26 -0
- package/src/client/components/primitives/link.tsx +50 -52
- package/src/client/components/primitives/menu.tsx +25 -49
- package/src/client/components/primitives/navbar.tsx +234 -59
- package/src/client/components/primitives/on-this-page.tsx +169 -40
- package/src/client/components/primitives/page-nav.tsx +11 -39
- package/src/client/components/primitives/popover.tsx +12 -30
- package/src/client/components/primitives/search-dialog.tsx +77 -71
- package/src/client/components/primitives/sidebar.tsx +312 -119
- package/src/client/components/primitives/skeleton.tsx +1 -1
- package/src/client/components/primitives/tabs.tsx +5 -16
- package/src/client/components/primitives/tooltip.tsx +1 -1
- package/src/client/components/ui-base/banner.tsx +66 -0
- package/src/client/components/ui-base/breadcrumbs.tsx +26 -20
- package/src/client/components/ui-base/copy-markdown.tsx +43 -35
- package/src/client/components/ui-base/error-boundary.tsx +9 -46
- package/src/client/components/ui-base/github-stars.tsx +5 -3
- package/src/client/components/ui-base/index.ts +3 -3
- package/src/client/components/ui-base/last-updated.tsx +27 -0
- package/src/client/components/ui-base/navbar.tsx +183 -89
- package/src/client/components/ui-base/not-found.tsx +11 -9
- package/src/client/components/ui-base/on-this-page.tsx +8 -104
- package/src/client/components/ui-base/page-nav.tsx +23 -9
- package/src/client/components/ui-base/search-dialog.tsx +111 -36
- package/src/client/components/ui-base/search-highlight.tsx +10 -0
- package/src/client/components/ui-base/sidebar.tsx +77 -154
- package/src/client/components/ui-base/tabs.tsx +20 -7
- package/src/client/components/ui-base/theme-toggle.tsx +88 -10
- package/src/client/components/ui-base/version-i18n.tsx +80 -0
- package/src/client/hooks/index.ts +2 -1
- package/src/client/hooks/use-analytics.ts +272 -0
- package/src/client/hooks/use-i18n.ts +116 -50
- package/src/client/hooks/use-localized-to.ts +70 -27
- package/src/client/hooks/use-navbar.ts +69 -39
- package/src/client/hooks/use-page-nav.ts +28 -25
- package/src/client/hooks/use-routes.ts +63 -80
- package/src/client/hooks/use-search-highlight.ts +185 -0
- package/src/client/hooks/use-search.ts +12 -3
- package/src/client/hooks/use-sidebar.ts +183 -80
- package/src/client/hooks/use-tabs.ts +3 -4
- package/src/client/hooks/use-version.ts +44 -29
- package/src/client/index.ts +13 -87
- package/src/client/mdx.ts +2 -0
- package/src/client/primitives.ts +19 -0
- package/src/client/ssg/boltdocs-shell.tsx +68 -79
- package/src/client/ssg/create-routes.tsx +268 -72
- package/src/client/ssg/index.ts +1 -0
- package/src/client/ssg/mdx-page.tsx +2 -1
- package/src/client/store/boltdocs-context.tsx +72 -20
- package/src/client/theme/neutral.css +74 -361
- package/src/client/theme/reset.css +189 -0
- package/src/client/types.ts +10 -2
- package/src/client/utils/path.ts +9 -0
- package/src/client/utils/react-to-text.ts +24 -24
- package/src/client/virtual.d.ts +1 -1
- package/src/shared/types.ts +82 -22
- package/dist/node-Bogvkxao.mjs +0 -101
- package/dist/node-CXaog6St.cjs +0 -101
- package/dist/search-dialog-CV3eJzMm.cjs +0 -6
- package/dist/search-dialog-DNTomKgu.js +0 -6
- package/dist/use-search-CS3gH19M.js +0 -6
- package/dist/use-search-DBpJZQuw.cjs +0 -6
- package/src/client/components/mdx/admonition.tsx +0 -91
- package/src/client/components/mdx/badge.tsx +0 -41
- package/src/client/components/mdx/button.tsx +0 -35
- package/src/client/components/mdx/component-preview.tsx +0 -37
- package/src/client/components/mdx/component-props.tsx +0 -83
- package/src/client/components/mdx/file-tree.tsx +0 -325
- package/src/client/components/mdx/hooks/use-component-preview.ts +0 -16
- package/src/client/components/mdx/hooks/useTable.ts +0 -74
- package/src/client/components/mdx/hooks/useTabs.ts +0 -68
- package/src/client/components/mdx/link.tsx +0 -38
- package/src/client/components/mdx/list.tsx +0 -192
- package/src/client/components/mdx/tabs.tsx +0 -135
- package/src/client/components/mdx/video.tsx +0 -68
- package/src/client/components/primitives/index.ts +0 -19
- package/src/client/components/primitives/navigation-menu.tsx +0 -114
- package/src/client/components/ui-base/head.tsx +0 -83
- package/src/client/components/ui-base/loading.tsx +0 -57
- package/src/client/components/ui-base/powered-by.tsx +0 -25
- package/src/client/hooks/use-onthispage.ts +0 -23
- package/src/client/utils/use-on-change.ts +0 -15
|
@@ -1,29 +1,23 @@
|
|
|
1
1
|
import { useEffect, useMemo } from 'react'
|
|
2
|
-
import
|
|
3
|
-
import { Outlet, useLocation, useNavigate } from 'react-router-dom'
|
|
4
|
-
import { RouterProvider } from 'react-aria-components'
|
|
2
|
+
import { Outlet, useLocation } from 'react-router-dom'
|
|
5
3
|
import { BoltdocsProvider, useBoltdocsContext } from '../store/boltdocs-context'
|
|
6
4
|
import { ThemeProvider } from '../app/theme-context'
|
|
7
5
|
import { MdxComponentsProvider } from '../app/mdx-components-context'
|
|
8
|
-
import
|
|
6
|
+
import { HelmetProvider } from '../app/helmet-compat'
|
|
9
7
|
import { ConfigContext } from '../app/config-context'
|
|
10
8
|
import { ScrollHandler } from '../app/scroll-handler'
|
|
11
9
|
import { mdxComponentsDefault } from '../app/mdx-component'
|
|
12
10
|
import { RoutesProvider } from '../app/routes-context'
|
|
13
11
|
import type { BoltdocsConfig } from '../../shared/types'
|
|
14
12
|
import type { ComponentRoute } from '../types'
|
|
13
|
+
import { UIProvider } from '../app/ui-context'
|
|
15
14
|
|
|
16
15
|
import virtualCustomComponents from 'virtual:boltdocs-mdx-components'
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
/** Normalize a path: strip trailing slash unless it is exactly '/'. */
|
|
18
|
+
function normalizePath(p: string): string {
|
|
19
|
+
return p.endsWith('/') && p.length > 1 ? p.slice(0, -1) : p
|
|
21
20
|
}
|
|
22
|
-
const helmetProviderModule = ReactHelmetAsync as unknown as HelmetProviderModule
|
|
23
|
-
const HelmetProvider =
|
|
24
|
-
helmetProviderModule.HelmetProvider
|
|
25
|
-
|| helmetProviderModule.default?.HelmetProvider
|
|
26
|
-
|| (({ children }) => <>{children}</>)
|
|
27
21
|
|
|
28
22
|
/**
|
|
29
23
|
* Updates the HTML lang and dir attributes based on the current locale configuration.
|
|
@@ -44,56 +38,34 @@ function I18nUpdater({ config }: { config: BoltdocsConfig }) {
|
|
|
44
38
|
|
|
45
39
|
/**
|
|
46
40
|
* Synchronizes the Zustand store with the current URL pathname.
|
|
41
|
+
* Receives a pre-built Map for O(1) route lookups instead of O(n) .find().
|
|
47
42
|
*/
|
|
48
|
-
function StoreSync({
|
|
43
|
+
function StoreSync({
|
|
44
|
+
config,
|
|
45
|
+
routeMap,
|
|
46
|
+
}: {
|
|
47
|
+
config: BoltdocsConfig
|
|
48
|
+
routeMap: Map<string, ComponentRoute>
|
|
49
|
+
}) {
|
|
49
50
|
const location = useLocation()
|
|
50
|
-
const { setLocale, setVersion
|
|
51
|
-
useBoltdocsContext()
|
|
51
|
+
const { setLocale, setVersion } = useBoltdocsContext()
|
|
52
52
|
|
|
53
53
|
useEffect(() => {
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
let detectedVersion = config.versions?.defaultVersion
|
|
57
|
-
let detectedLocale = config.i18n?.defaultLocale
|
|
58
|
-
|
|
59
|
-
// 0. Skip docs prefix if present
|
|
60
|
-
if (parts[cIdx] === 'docs') cIdx++
|
|
61
|
-
|
|
62
|
-
// 1. Version detection
|
|
63
|
-
if (config.versions && parts.length > cIdx) {
|
|
64
|
-
const versionMatch = config.versions.versions.find(
|
|
65
|
-
(v) => v.path === parts[cIdx],
|
|
66
|
-
)
|
|
67
|
-
if (versionMatch) {
|
|
68
|
-
detectedVersion = versionMatch.path
|
|
69
|
-
cIdx++
|
|
70
|
-
}
|
|
71
|
-
}
|
|
54
|
+
const currentPath = normalizePath(location.pathname)
|
|
55
|
+
const matchedRoute = routeMap.get(currentPath)
|
|
72
56
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
57
|
+
if (matchedRoute) {
|
|
58
|
+
if (config.i18n) {
|
|
59
|
+
const targetLocale = matchedRoute.locale || config.i18n.defaultLocale
|
|
60
|
+
setLocale(targetLocale)
|
|
61
|
+
}
|
|
62
|
+
if (config.versions) {
|
|
63
|
+
const targetVersion =
|
|
64
|
+
matchedRoute.version || config.versions.defaultVersion
|
|
65
|
+
setVersion(targetVersion)
|
|
82
66
|
}
|
|
83
|
-
} else if (config.i18n && parts.length === 0) {
|
|
84
|
-
detectedLocale = currentLocale || config.i18n.defaultLocale
|
|
85
67
|
}
|
|
86
|
-
|
|
87
|
-
if (detectedLocale !== currentLocale) setLocale(detectedLocale || '')
|
|
88
|
-
if (detectedVersion !== currentVersion) setVersion(detectedVersion ?? '')
|
|
89
|
-
}, [
|
|
90
|
-
location.pathname,
|
|
91
|
-
config,
|
|
92
|
-
setLocale,
|
|
93
|
-
setVersion,
|
|
94
|
-
currentLocale,
|
|
95
|
-
currentVersion,
|
|
96
|
-
])
|
|
68
|
+
}, [location.pathname, config, routeMap, setLocale, setVersion])
|
|
97
69
|
|
|
98
70
|
return null
|
|
99
71
|
}
|
|
@@ -116,42 +88,59 @@ export function BoltdocsShell({
|
|
|
116
88
|
[components],
|
|
117
89
|
)
|
|
118
90
|
|
|
119
|
-
const navigate = useNavigate()
|
|
120
91
|
const { pathname } = useLocation()
|
|
121
|
-
|
|
122
|
-
const currentPath = useMemo(() =>
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
92
|
+
|
|
93
|
+
const currentPath = useMemo(() => normalizePath(pathname || '/'), [pathname])
|
|
94
|
+
|
|
95
|
+
// Build a single O(1) lookup Map from the routes array.
|
|
96
|
+
// This replaces the 3 separate O(n) .find() calls that previously ran on every render.
|
|
97
|
+
const routeMap = useMemo(() => {
|
|
98
|
+
const map = new Map<string, ComponentRoute>()
|
|
99
|
+
for (const r of routes) {
|
|
100
|
+
const key = normalizePath(r.path === '' ? '/' : r.path)
|
|
101
|
+
map.set(key, r)
|
|
102
|
+
}
|
|
103
|
+
return map
|
|
104
|
+
}, [routes])
|
|
105
|
+
|
|
106
|
+
// Calculate frame-perfect initial values derived AUTHORITATIVELY from the static route match
|
|
107
|
+
const initialData = useMemo(() => {
|
|
108
|
+
const matched = routeMap.get(currentPath)
|
|
109
|
+
|
|
110
|
+
let initLocale = undefined
|
|
111
|
+
let initVersion = undefined
|
|
112
|
+
|
|
113
|
+
if (matched) {
|
|
114
|
+
if (config.i18n) {
|
|
115
|
+
initLocale = matched.locale || config.i18n.defaultLocale
|
|
116
|
+
}
|
|
117
|
+
if (config.versions) {
|
|
118
|
+
initVersion = matched.version || config.versions.defaultVersion
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return { initLocale, initVersion }
|
|
123
|
+
}, [currentPath, config, routeMap])
|
|
135
124
|
|
|
136
125
|
return (
|
|
137
126
|
<HelmetProvider>
|
|
138
127
|
<RoutesProvider routes={routes}>
|
|
139
128
|
<ThemeProvider>
|
|
140
|
-
<
|
|
141
|
-
<
|
|
142
|
-
<
|
|
129
|
+
<UIProvider>
|
|
130
|
+
<MdxComponentsProvider components={allComponents}>
|
|
131
|
+
<ConfigContext.Provider value={config}>
|
|
143
132
|
<ScrollHandler />
|
|
144
133
|
<BoltdocsProvider
|
|
145
|
-
initialLocale={
|
|
146
|
-
initialVersion={
|
|
134
|
+
initialLocale={initialData.initLocale}
|
|
135
|
+
initialVersion={initialData.initVersion}
|
|
147
136
|
>
|
|
148
|
-
<StoreSync config={config} />
|
|
137
|
+
<StoreSync config={config} routeMap={routeMap} />
|
|
149
138
|
<I18nUpdater config={config} />
|
|
150
139
|
<Outlet />
|
|
151
140
|
</BoltdocsProvider>
|
|
152
|
-
</
|
|
153
|
-
</
|
|
154
|
-
</
|
|
141
|
+
</ConfigContext.Provider>
|
|
142
|
+
</MdxComponentsProvider>
|
|
143
|
+
</UIProvider>
|
|
155
144
|
</ThemeProvider>
|
|
156
145
|
</RoutesProvider>
|
|
157
146
|
</HelmetProvider>
|
|
@@ -2,57 +2,86 @@ import type { RouteRecord } from '@bdocs/ssg'
|
|
|
2
2
|
import type { ComponentRoute, BoltdocsConfig } from '../types'
|
|
3
3
|
import { MdxPage } from './mdx-page'
|
|
4
4
|
import { BoltdocsShell } from './boltdocs-shell'
|
|
5
|
-
import { NotFound } from '../components/ui-base
|
|
5
|
+
import { NotFound } from '../components/ui-base'
|
|
6
|
+
const Loading = () => <div className="text-muted text-sm py-4">Loading...</div>
|
|
7
|
+
import type React from 'react'
|
|
8
|
+
import { useEffect } from 'react'
|
|
9
|
+
import { Navigate } from 'react-router-dom'
|
|
6
10
|
|
|
7
11
|
interface CreateRoutesOptions {
|
|
8
12
|
routesData: ComponentRoute[]
|
|
9
13
|
config: BoltdocsConfig
|
|
10
|
-
mdxModules: Record<string,
|
|
14
|
+
mdxModules: Record<string, any>
|
|
11
15
|
Layout: React.ComponentType<{ children: React.ReactNode }>
|
|
12
|
-
|
|
16
|
+
|
|
13
17
|
externalPages?: Record<string, React.ComponentType>
|
|
14
18
|
externalLayout?: React.ComponentType<{ children: React.ReactNode }>
|
|
15
19
|
components?: Record<string, React.ComponentType>
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
/**
|
|
19
|
-
*
|
|
23
|
+
* Stable component to render MDX pages.
|
|
24
|
+
* By being outside createRoutes, it prevents React from unmounting the page on HMR.
|
|
20
25
|
*/
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
26
|
+
const MdxRouteElement = ({
|
|
27
|
+
moduleLoader,
|
|
28
|
+
moduleKey,
|
|
29
|
+
route,
|
|
30
|
+
components,
|
|
31
|
+
}: {
|
|
32
|
+
moduleLoader: any
|
|
33
|
+
moduleKey: string | undefined
|
|
34
|
+
route: ComponentRoute
|
|
35
|
+
components: any
|
|
36
|
+
}) => {
|
|
37
|
+
const MDXComponent = moduleLoader?.default ?? moduleLoader ?? null
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (!import.meta.hot || !moduleKey) return
|
|
41
|
+
|
|
42
|
+
const handler = (data: { relPath: string }) => {
|
|
43
|
+
const incoming = data.relPath.replace(/\\/g, '/').replace(/^\//, '')
|
|
44
|
+
const routeFile = route.filePath.replace(/\\/g, '/').replace(/^\//, '')
|
|
45
|
+
|
|
46
|
+
if (incoming !== routeFile) return
|
|
47
|
+
|
|
48
|
+
const cacheBustUrl = moduleKey + '?t=' + Date.now()
|
|
49
|
+
import(/* @vite-ignore */ cacheBustUrl).then((m: any) => {
|
|
50
|
+
MDXComponent
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
import.meta.hot.on('boltdocs:mdx-update', handler)
|
|
55
|
+
return () => import.meta.hot?.off('boltdocs:mdx-update', handler)
|
|
56
|
+
}, [moduleKey, route.filePath])
|
|
57
|
+
|
|
58
|
+
if (!MDXComponent) return <Loading />
|
|
59
|
+
|
|
60
|
+
return <MdxPage MDXComponent={MDXComponent} mdxComponents={components} />
|
|
41
61
|
}
|
|
42
62
|
|
|
63
|
+
import { useMdxComponents } from '../app/mdx-components-context'
|
|
64
|
+
|
|
65
|
+
const NotFoundWrapper = () => {
|
|
66
|
+
const components = useMdxComponents()
|
|
67
|
+
const ActiveNotFound = components.NotFound || components['404'] || NotFound
|
|
68
|
+
return <ActiveNotFound />
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
import { DocsLayout } from '../app/docs-layout'
|
|
72
|
+
|
|
43
73
|
export function createRoutes(options: CreateRoutesOptions): RouteRecord[] {
|
|
44
74
|
const {
|
|
45
75
|
routesData,
|
|
46
76
|
config,
|
|
47
77
|
mdxModules,
|
|
48
|
-
Layout,
|
|
49
|
-
homePage: HomePage,
|
|
50
78
|
externalPages,
|
|
51
79
|
externalLayout,
|
|
52
80
|
components,
|
|
53
81
|
} = options
|
|
54
82
|
|
|
55
|
-
const EffectiveExternalLayout =
|
|
83
|
+
const EffectiveExternalLayout =
|
|
84
|
+
externalLayout || (({ children }: any) => <>{children}</>)
|
|
56
85
|
|
|
57
86
|
const withBase = (path: string) => {
|
|
58
87
|
// Future support for base path in config
|
|
@@ -63,25 +92,103 @@ export function createRoutes(options: CreateRoutesOptions): RouteRecord[] {
|
|
|
63
92
|
return `${b}${p}` || '/'
|
|
64
93
|
}
|
|
65
94
|
|
|
66
|
-
const
|
|
95
|
+
const defaultVersionMetadata: ComponentRoute[] = []
|
|
67
96
|
|
|
68
|
-
//
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
const MDXComponent = moduleKey ? mdxModules[moduleKey]?.default : null
|
|
97
|
+
// Inject virtual explicit routes for default version to ensure paths like /docs/latest/... aren't 404s
|
|
98
|
+
const defaultVersion = config.versions?.defaultVersion
|
|
99
|
+
const docsBase = (config.base || '/docs').replace(/\/$/, '')
|
|
72
100
|
|
|
101
|
+
if (defaultVersion) {
|
|
102
|
+
routesData.forEach((route) => {
|
|
103
|
+
// If this route explicitly already belongs to a version, do not clone.
|
|
104
|
+
if (route.version) return
|
|
105
|
+
|
|
106
|
+
// Compute path without docs base prefix to properly place version token
|
|
107
|
+
const p = route.path || ''
|
|
108
|
+
const subPath = p.startsWith(docsBase)
|
|
109
|
+
? p.substring(docsBase.length).replace(/^\//, '')
|
|
110
|
+
: p.replace(/^\//, '')
|
|
111
|
+
|
|
112
|
+
// Detect if it already includes the target version segment
|
|
113
|
+
const hasVersionPrefix =
|
|
114
|
+
subPath === defaultVersion || subPath.startsWith(`${defaultVersion}/`)
|
|
115
|
+
|
|
116
|
+
if (!hasVersionPrefix) {
|
|
117
|
+
// Standardize reconstruction: [docsBase] / [version] / [remaining_path]
|
|
118
|
+
const explicitPath =
|
|
119
|
+
`${docsBase}/${defaultVersion}/${subPath}`
|
|
120
|
+
.replace(/\/+/g, '/')
|
|
121
|
+
.replace(/\/$/, '') || '/'
|
|
122
|
+
|
|
123
|
+
defaultVersionMetadata.push({
|
|
124
|
+
...route,
|
|
125
|
+
path: explicitPath,
|
|
126
|
+
version: defaultVersion,
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const docMetadata = [...routesData, ...defaultVersionMetadata]
|
|
133
|
+
|
|
134
|
+
// 0. Build a single pre-computed lookup map for the MDX modules (O(N) build, O(1) access).
|
|
135
|
+
// This replaces the inner findModuleKey loops that executed an O(N) scan for EVERY route.
|
|
136
|
+
const moduleMap = new Map<string, string>()
|
|
137
|
+
const mdxModuleKeys = Object.keys(mdxModules)
|
|
138
|
+
|
|
139
|
+
if (mdxModuleKeys.length > 0) {
|
|
140
|
+
// Detect docs directory structure from keys (e.g., "/docs/intro.md")
|
|
141
|
+
const firstKeyNormalized = mdxModuleKeys[0].replace(/\\/g, '/')
|
|
142
|
+
const parts = firstKeyNormalized.split('/').filter(Boolean)
|
|
143
|
+
const docsDirName = parts[0] || 'docs'
|
|
144
|
+
const primaryPrefix = `/${docsDirName}/`
|
|
145
|
+
const altPrefix = `./${docsDirName}/`
|
|
146
|
+
|
|
147
|
+
for (const rawKey of mdxModuleKeys) {
|
|
148
|
+
const k = rawKey.replace(/\\/g, '/')
|
|
149
|
+
let relativePath = ''
|
|
150
|
+
if (k.indexOf(primaryPrefix) !== -1) {
|
|
151
|
+
relativePath = k.substring(
|
|
152
|
+
k.indexOf(primaryPrefix) + primaryPrefix.length,
|
|
153
|
+
)
|
|
154
|
+
} else if (k.startsWith(altPrefix)) {
|
|
155
|
+
relativePath = k.substring(altPrefix.length)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (relativePath) {
|
|
159
|
+
moduleMap.set(relativePath, rawKey)
|
|
160
|
+
} else {
|
|
161
|
+
// Fallback: store full normalized key as a catch-all
|
|
162
|
+
moduleMap.set(k, rawKey)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 1. Documentation routes
|
|
168
|
+
const docRoutes: RouteRecord[] = docMetadata.map((route) => {
|
|
169
|
+
// Perform constant-time lookup using the pre-computed map
|
|
170
|
+
const normalizedFilePath = route.filePath.replace(/\\/g, '/')
|
|
171
|
+
const moduleKey = moduleMap.get(normalizedFilePath)
|
|
172
|
+
const moduleLoader = moduleKey ? mdxModules[moduleKey] : null
|
|
73
173
|
const path = withBase(route.path === '' ? '/' : route.path)
|
|
74
174
|
|
|
75
175
|
return {
|
|
76
176
|
path,
|
|
77
177
|
element: (
|
|
78
|
-
<
|
|
178
|
+
<MdxRouteElement
|
|
179
|
+
key={moduleKey || path}
|
|
180
|
+
moduleKey={moduleKey}
|
|
181
|
+
moduleLoader={moduleLoader}
|
|
182
|
+
route={route}
|
|
183
|
+
components={components}
|
|
184
|
+
/>
|
|
79
185
|
),
|
|
80
186
|
loader: async () => ({
|
|
81
187
|
path,
|
|
82
188
|
frontmatter: {
|
|
83
189
|
title: route.title,
|
|
84
190
|
description: route.description || '',
|
|
191
|
+
...(route.frontmatter || {}),
|
|
85
192
|
},
|
|
86
193
|
headings: route.headings || [],
|
|
87
194
|
filePath: route.filePath,
|
|
@@ -89,59 +196,148 @@ export function createRoutes(options: CreateRoutesOptions): RouteRecord[] {
|
|
|
89
196
|
version: route.version,
|
|
90
197
|
group: route.group,
|
|
91
198
|
groupTitle: route.groupTitle,
|
|
199
|
+
date: route.date,
|
|
200
|
+
lastUpdated: route.lastUpdated,
|
|
92
201
|
}),
|
|
93
202
|
getStaticPaths: () => [path],
|
|
94
203
|
}
|
|
95
204
|
})
|
|
96
205
|
|
|
97
|
-
|
|
206
|
+
// 2. Auto-fallback for the base paths (e.g. /docs, /docs/es) to the first documentation page
|
|
207
|
+
let baseDocsPath = (config.base || '/docs').replace(/\/$/, '')
|
|
208
|
+
if (!baseDocsPath) baseDocsPath = '/'
|
|
209
|
+
|
|
210
|
+
const locales = config.i18n?.locales
|
|
211
|
+
? Array.isArray(config.i18n.locales)
|
|
212
|
+
? config.i18n.locales
|
|
213
|
+
: Object.keys(config.i18n.locales)
|
|
214
|
+
: []
|
|
215
|
+
|
|
216
|
+
// 2a. Generate dynamic permutation matrix of version/locale combinations
|
|
217
|
+
const allVersions = config.versions?.versions?.map((v) => v.path) || []
|
|
218
|
+
|
|
219
|
+
const targetBasePaths: Array<{
|
|
220
|
+
path: string
|
|
221
|
+
filter: (p: string) => boolean
|
|
222
|
+
}> = []
|
|
223
|
+
|
|
224
|
+
// Insert base root always
|
|
225
|
+
targetBasePaths.push({
|
|
226
|
+
path: baseDocsPath,
|
|
227
|
+
filter: () => true, // Take first available doc generally
|
|
228
|
+
})
|
|
98
229
|
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
230
|
+
// Permutation builder: version loop nested with locale loop
|
|
231
|
+
// Ensures paths like /docs/v2.0, /docs/es, and /docs/v2.0/es ALL receive fallback logic.
|
|
232
|
+
const subPaths: string[] = []
|
|
233
|
+
if (allVersions.length > 0) {
|
|
234
|
+
allVersions.forEach((v) => subPaths.push(`/${v}`))
|
|
235
|
+
}
|
|
236
|
+
if (locales.length > 0) {
|
|
237
|
+
locales.forEach((l) => subPaths.push(`/${l}`))
|
|
238
|
+
}
|
|
239
|
+
if (allVersions.length > 0 && locales.length > 0) {
|
|
240
|
+
allVersions.forEach((v) => {
|
|
241
|
+
locales.forEach((l) => {
|
|
242
|
+
subPaths.push(`/${v}/${l}`)
|
|
105
243
|
})
|
|
106
|
-
}
|
|
244
|
+
})
|
|
245
|
+
}
|
|
107
246
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
headings: [],
|
|
117
|
-
} as any)
|
|
247
|
+
// Map permutations onto the physical base docs route
|
|
248
|
+
subPaths.forEach((sp) => {
|
|
249
|
+
const fullP = baseDocsPath === '/' ? sp : `${baseDocsPath}${sp}`
|
|
250
|
+
targetBasePaths.push({
|
|
251
|
+
path: fullP,
|
|
252
|
+
filter: (rp) => rp.startsWith(fullP.replace(/\/$/, '') + '/'),
|
|
253
|
+
})
|
|
254
|
+
})
|
|
118
255
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
256
|
+
// Pre-compute a Set of absolute and normalized path strings from the real routes
|
|
257
|
+
// to perform O(1) validation checks within the redirection loops below.
|
|
258
|
+
const docPathRegistry = new Set(
|
|
259
|
+
docRoutes.map((r) => (r.path || '').replace(/\/$/, '')),
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
// Pre-compute external pages paths so we do not hijack them with redirects
|
|
263
|
+
const externalPaths = new Set<string>()
|
|
264
|
+
if (externalPages) {
|
|
265
|
+
Object.keys(externalPages).forEach((rawPath) => {
|
|
266
|
+
const p = rawPath.startsWith('/') ? rawPath : `/${rawPath}`
|
|
267
|
+
externalPaths.add(p.replace(/\/$/, ''))
|
|
268
|
+
if (config.i18n) {
|
|
269
|
+
Object.keys(config.i18n.locales).forEach((locale) => {
|
|
270
|
+
externalPaths.add(
|
|
271
|
+
`/${locale}${p === '/' ? '' : p}`.replace(/\/$/, ''),
|
|
272
|
+
)
|
|
131
273
|
})
|
|
132
274
|
}
|
|
133
275
|
})
|
|
134
276
|
}
|
|
135
277
|
|
|
278
|
+
// 2b. Deploy smart redirects
|
|
279
|
+
targetBasePaths.forEach(({ path: bPath, filter }) => {
|
|
280
|
+
if (bPath === '/') return // Never hijack global app root
|
|
281
|
+
|
|
282
|
+
const normalizedPath = bPath.replace(/\/$/, '')
|
|
283
|
+
const hasExplicitMatch =
|
|
284
|
+
docPathRegistry.has(normalizedPath) || externalPaths.has(normalizedPath)
|
|
285
|
+
|
|
286
|
+
if (!hasExplicitMatch) {
|
|
287
|
+
const defaultTab = config.theme?.tabs?.[0]?.id
|
|
288
|
+
const defaultTabPath = defaultTab
|
|
289
|
+
? `${normalizedPath}/${defaultTab}`.replace(/\/+/g, '/')
|
|
290
|
+
: null
|
|
291
|
+
|
|
292
|
+
// Prioritize: Find a real route that matches the default tab first, then fall back to the first route beginning with this pattern.
|
|
293
|
+
const matchedRoute =
|
|
294
|
+
defaultTabPath && docPathRegistry.has(defaultTabPath.replace(/\/$/, ''))
|
|
295
|
+
? docRoutes.find(
|
|
296
|
+
(r) =>
|
|
297
|
+
r.path.replace(/\/$/, '') === defaultTabPath.replace(/\/$/, ''),
|
|
298
|
+
)
|
|
299
|
+
: docRoutes.find((r) => filter(r.path) && r.path !== normalizedPath)
|
|
300
|
+
|
|
301
|
+
// Ultimate fallback: the absolute first document
|
|
302
|
+
const finalTarget = matchedRoute
|
|
303
|
+
? matchedRoute.path
|
|
304
|
+
: docRoutes.length > 0
|
|
305
|
+
? docRoutes[0].path
|
|
306
|
+
: null
|
|
307
|
+
|
|
308
|
+
if (finalTarget) {
|
|
309
|
+
docRoutes.push({
|
|
310
|
+
path: bPath,
|
|
311
|
+
element: <Navigate to={finalTarget} replace />,
|
|
312
|
+
loader: async () => ({ path: bPath }),
|
|
313
|
+
getStaticPaths: () => [bPath],
|
|
314
|
+
})
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
// Group all documentation routes under the persistent DocsLayout
|
|
320
|
+
const docsLayoutRoute: RouteRecord = {
|
|
321
|
+
element: <DocsLayout />,
|
|
322
|
+
children: docRoutes,
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const children: RouteRecord[] = [docsLayoutRoute]
|
|
326
|
+
|
|
136
327
|
// 3. External pages
|
|
328
|
+
const externalMetadata: ComponentRoute[] = []
|
|
137
329
|
if (externalPages) {
|
|
138
330
|
Object.entries(externalPages).forEach(([rawPath, ExtComponent]) => {
|
|
139
|
-
|
|
331
|
+
// Use the raw path directly (do not prefix with base docs path)
|
|
332
|
+
const path = rawPath.startsWith('/') ? rawPath : `/${rawPath}`
|
|
140
333
|
if (!children.find((r) => r.path === path)) {
|
|
141
|
-
|
|
334
|
+
externalMetadata.push({
|
|
142
335
|
path,
|
|
143
336
|
locale: config.i18n?.defaultLocale,
|
|
144
|
-
title:
|
|
337
|
+
title:
|
|
338
|
+
rawPath === '/'
|
|
339
|
+
? 'Home'
|
|
340
|
+
: rawPath.replace(/^\//, '').split('/').pop() || 'Page',
|
|
145
341
|
filePath: '',
|
|
146
342
|
headings: [],
|
|
147
343
|
} as any)
|
|
@@ -160,14 +356,12 @@ export function createRoutes(options: CreateRoutesOptions): RouteRecord[] {
|
|
|
160
356
|
getStaticPaths: () => [path],
|
|
161
357
|
})
|
|
162
358
|
|
|
163
|
-
// Also add i18n variants for external pages if needed
|
|
359
|
+
// Also add i18n variants for external pages if needed (do not prefix with base docs path)
|
|
164
360
|
if (config.i18n) {
|
|
165
361
|
Object.keys(config.i18n.locales).forEach((locale) => {
|
|
166
|
-
const localePath =
|
|
167
|
-
`/${locale}${rawPath === '/' ? '' : rawPath}`,
|
|
168
|
-
)
|
|
362
|
+
const localePath = `/${locale}${rawPath === '/' ? '' : rawPath}`
|
|
169
363
|
if (!children.find((r) => r.path === localePath)) {
|
|
170
|
-
|
|
364
|
+
externalMetadata.push({
|
|
171
365
|
path: localePath,
|
|
172
366
|
locale,
|
|
173
367
|
title: rawPath,
|
|
@@ -200,11 +394,13 @@ export function createRoutes(options: CreateRoutesOptions): RouteRecord[] {
|
|
|
200
394
|
path: '*',
|
|
201
395
|
element: (
|
|
202
396
|
<EffectiveExternalLayout>
|
|
203
|
-
<
|
|
397
|
+
<NotFoundWrapper />
|
|
204
398
|
</EffectiveExternalLayout>
|
|
205
399
|
),
|
|
206
400
|
})
|
|
207
401
|
|
|
402
|
+
const allMetadata = [...docMetadata, ...externalMetadata]
|
|
403
|
+
|
|
208
404
|
// Wrap everything in the Boltdocs shell (providers)
|
|
209
405
|
return [
|
|
210
406
|
{
|
package/src/client/ssg/index.ts
CHANGED
|
@@ -12,7 +12,6 @@ export function MdxPage({
|
|
|
12
12
|
const data = useLoaderData() as any
|
|
13
13
|
const MDXComponent = propMDX || data?.MDXComponent
|
|
14
14
|
const components = propComponents || data?.mdxComponents
|
|
15
|
-
|
|
16
15
|
if (!MDXComponent) return null
|
|
17
16
|
|
|
18
17
|
return (
|
|
@@ -28,6 +27,8 @@ export function MdxPage({
|
|
|
28
27
|
version: data.version,
|
|
29
28
|
group: data.group,
|
|
30
29
|
groupTitle: data.groupTitle,
|
|
30
|
+
lastUpdated: data.lastUpdated,
|
|
31
|
+
frontmatter: data.frontmatter,
|
|
31
32
|
} as any
|
|
32
33
|
}
|
|
33
34
|
content={MDXComponent}
|