boltdocs 2.6.1 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +173 -1328
- package/dist/client/index.d.ts +172 -1327
- package/dist/client/index.js +1 -1
- package/dist/{package-c99Cs7mD.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 +66 -13
- package/dist/node/index.d.mts +66 -14
- 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-C2nWXElP.mjs +112 -0
- package/dist/node-CinkUtxV.cjs +112 -0
- package/dist/package-BMYLDBBP.cjs +6 -0
- package/dist/{package-DukYeKmD.mjs → package-HegMOTL_.mjs} +1 -1
- 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 +120 -53
- package/src/client/hooks/use-localized-to.ts +70 -30
- 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 +64 -81
- 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 -77
- package/src/client/hooks/use-tabs.ts +3 -4
- package/src/client/hooks/use-version.ts +46 -18
- package/src/client/index.ts +13 -86
- package/src/client/mdx.ts +2 -0
- package/src/client/primitives.ts +19 -0
- package/src/client/ssg/boltdocs-shell.tsx +78 -57
- package/src/client/ssg/create-routes.tsx +290 -50
- package/src/client/ssg/mdx-page.tsx +2 -1
- package/src/client/store/boltdocs-context.tsx +83 -12
- 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 +97 -21
- package/dist/node-CWN8U_p8.mjs +0 -88
- package/dist/node-D5iosYXv.cjs +0 -88
- package/dist/search-dialog-3lvKsbVG.js +0 -6
- package/dist/search-dialog-DMK5OpgH.cjs +0 -6
- package/dist/use-search-C9bxCqfF.js +0 -6
- package/dist/use-search-DcfZSunO.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 -76
- 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,19 +1,24 @@
|
|
|
1
1
|
import { useEffect, useMemo } from 'react'
|
|
2
|
-
import { Outlet, useLocation
|
|
3
|
-
import { RouterProvider } from 'react-aria-components'
|
|
2
|
+
import { Outlet, useLocation } from 'react-router-dom'
|
|
4
3
|
import { BoltdocsProvider, useBoltdocsContext } from '../store/boltdocs-context'
|
|
5
4
|
import { ThemeProvider } from '../app/theme-context'
|
|
6
5
|
import { MdxComponentsProvider } from '../app/mdx-components-context'
|
|
7
|
-
import { HelmetProvider } from '
|
|
6
|
+
import { HelmetProvider } from '../app/helmet-compat'
|
|
8
7
|
import { ConfigContext } from '../app/config-context'
|
|
9
8
|
import { ScrollHandler } from '../app/scroll-handler'
|
|
10
9
|
import { mdxComponentsDefault } from '../app/mdx-component'
|
|
11
10
|
import { RoutesProvider } from '../app/routes-context'
|
|
12
11
|
import type { BoltdocsConfig } from '../../shared/types'
|
|
13
12
|
import type { ComponentRoute } from '../types'
|
|
13
|
+
import { UIProvider } from '../app/ui-context'
|
|
14
14
|
|
|
15
15
|
import virtualCustomComponents from 'virtual:boltdocs-mdx-components'
|
|
16
16
|
|
|
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
|
|
20
|
+
}
|
|
21
|
+
|
|
17
22
|
/**
|
|
18
23
|
* Updates the HTML lang and dir attributes based on the current locale configuration.
|
|
19
24
|
*/
|
|
@@ -33,53 +38,34 @@ function I18nUpdater({ config }: { config: BoltdocsConfig }) {
|
|
|
33
38
|
|
|
34
39
|
/**
|
|
35
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().
|
|
36
42
|
*/
|
|
37
|
-
function StoreSync({
|
|
43
|
+
function StoreSync({
|
|
44
|
+
config,
|
|
45
|
+
routeMap,
|
|
46
|
+
}: {
|
|
47
|
+
config: BoltdocsConfig
|
|
48
|
+
routeMap: Map<string, ComponentRoute>
|
|
49
|
+
}) {
|
|
38
50
|
const location = useLocation()
|
|
39
|
-
const { setLocale, setVersion
|
|
40
|
-
useBoltdocsContext()
|
|
51
|
+
const { setLocale, setVersion } = useBoltdocsContext()
|
|
41
52
|
|
|
42
53
|
useEffect(() => {
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
let detectedVersion = config.versions?.defaultVersion
|
|
46
|
-
let detectedLocale = config.i18n?.defaultLocale
|
|
47
|
-
|
|
48
|
-
// 0. Skip docs prefix if present
|
|
49
|
-
if (parts[cIdx] === 'docs') cIdx++
|
|
50
|
-
|
|
51
|
-
// 1. Version detection
|
|
52
|
-
if (config.versions && parts.length > cIdx) {
|
|
53
|
-
const versionMatch = config.versions.versions.find(
|
|
54
|
-
(v) => v.path === parts[cIdx],
|
|
55
|
-
)
|
|
56
|
-
if (versionMatch) {
|
|
57
|
-
detectedVersion = versionMatch.path
|
|
58
|
-
cIdx++
|
|
59
|
-
}
|
|
60
|
-
}
|
|
54
|
+
const currentPath = normalizePath(location.pathname)
|
|
55
|
+
const matchedRoute = routeMap.get(currentPath)
|
|
61
56
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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)
|
|
66
|
+
}
|
|
71
67
|
}
|
|
72
|
-
|
|
73
|
-
if (detectedLocale !== currentLocale) setLocale(detectedLocale || '')
|
|
74
|
-
if (detectedVersion !== currentVersion) setVersion(detectedVersion ?? '')
|
|
75
|
-
}, [
|
|
76
|
-
location.pathname,
|
|
77
|
-
config,
|
|
78
|
-
setLocale,
|
|
79
|
-
setVersion,
|
|
80
|
-
currentLocale,
|
|
81
|
-
currentVersion,
|
|
82
|
-
])
|
|
68
|
+
}, [location.pathname, config, routeMap, setLocale, setVersion])
|
|
83
69
|
|
|
84
70
|
return null
|
|
85
71
|
}
|
|
@@ -102,26 +88,61 @@ export function BoltdocsShell({
|
|
|
102
88
|
[components],
|
|
103
89
|
)
|
|
104
90
|
|
|
105
|
-
const
|
|
91
|
+
const { pathname } = useLocation()
|
|
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])
|
|
106
124
|
|
|
107
125
|
return (
|
|
108
126
|
<HelmetProvider>
|
|
109
|
-
<
|
|
127
|
+
<RoutesProvider routes={routes}>
|
|
110
128
|
<ThemeProvider>
|
|
111
|
-
<
|
|
112
|
-
<
|
|
113
|
-
<
|
|
114
|
-
<
|
|
115
|
-
|
|
116
|
-
|
|
129
|
+
<UIProvider>
|
|
130
|
+
<MdxComponentsProvider components={allComponents}>
|
|
131
|
+
<ConfigContext.Provider value={config}>
|
|
132
|
+
<ScrollHandler />
|
|
133
|
+
<BoltdocsProvider
|
|
134
|
+
initialLocale={initialData.initLocale}
|
|
135
|
+
initialVersion={initialData.initVersion}
|
|
136
|
+
>
|
|
137
|
+
<StoreSync config={config} routeMap={routeMap} />
|
|
117
138
|
<I18nUpdater config={config} />
|
|
118
139
|
<Outlet />
|
|
119
|
-
</
|
|
120
|
-
</
|
|
121
|
-
</
|
|
122
|
-
</
|
|
140
|
+
</BoltdocsProvider>
|
|
141
|
+
</ConfigContext.Provider>
|
|
142
|
+
</MdxComponentsProvider>
|
|
143
|
+
</UIProvider>
|
|
123
144
|
</ThemeProvider>
|
|
124
|
-
</
|
|
145
|
+
</RoutesProvider>
|
|
125
146
|
</HelmetProvider>
|
|
126
147
|
)
|
|
127
148
|
}
|
|
@@ -2,47 +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
|
-
|
|
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} />
|
|
31
61
|
}
|
|
32
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
|
+
|
|
33
73
|
export function createRoutes(options: CreateRoutesOptions): RouteRecord[] {
|
|
34
74
|
const {
|
|
35
75
|
routesData,
|
|
36
76
|
config,
|
|
37
77
|
mdxModules,
|
|
38
|
-
Layout,
|
|
39
|
-
homePage: HomePage,
|
|
40
78
|
externalPages,
|
|
41
79
|
externalLayout,
|
|
42
80
|
components,
|
|
43
81
|
} = options
|
|
44
82
|
|
|
45
|
-
const EffectiveExternalLayout =
|
|
83
|
+
const EffectiveExternalLayout =
|
|
84
|
+
externalLayout || (({ children }: any) => <>{children}</>)
|
|
46
85
|
|
|
47
86
|
const withBase = (path: string) => {
|
|
48
87
|
// Future support for base path in config
|
|
@@ -53,23 +92,103 @@ export function createRoutes(options: CreateRoutesOptions): RouteRecord[] {
|
|
|
53
92
|
return `${b}${p}` || '/'
|
|
54
93
|
}
|
|
55
94
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
95
|
+
const defaultVersionMetadata: ComponentRoute[] = []
|
|
96
|
+
|
|
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(/\/$/, '')
|
|
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(/\/$/, '') || '/'
|
|
60
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
|
|
61
173
|
const path = withBase(route.path === '' ? '/' : route.path)
|
|
62
174
|
|
|
63
175
|
return {
|
|
64
176
|
path,
|
|
65
177
|
element: (
|
|
66
|
-
<
|
|
178
|
+
<MdxRouteElement
|
|
179
|
+
key={moduleKey || path}
|
|
180
|
+
moduleKey={moduleKey}
|
|
181
|
+
moduleLoader={moduleLoader}
|
|
182
|
+
route={route}
|
|
183
|
+
components={components}
|
|
184
|
+
/>
|
|
67
185
|
),
|
|
68
186
|
loader: async () => ({
|
|
69
187
|
path,
|
|
70
188
|
frontmatter: {
|
|
71
189
|
title: route.title,
|
|
72
190
|
description: route.description || '',
|
|
191
|
+
...(route.frontmatter || {}),
|
|
73
192
|
},
|
|
74
193
|
headings: route.headings || [],
|
|
75
194
|
filePath: route.filePath,
|
|
@@ -77,43 +196,152 @@ export function createRoutes(options: CreateRoutesOptions): RouteRecord[] {
|
|
|
77
196
|
version: route.version,
|
|
78
197
|
group: route.group,
|
|
79
198
|
groupTitle: route.groupTitle,
|
|
199
|
+
date: route.date,
|
|
200
|
+
lastUpdated: route.lastUpdated,
|
|
80
201
|
}),
|
|
81
202
|
getStaticPaths: () => [path],
|
|
82
203
|
}
|
|
83
204
|
})
|
|
84
205
|
|
|
85
|
-
|
|
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
|
+
}> = []
|
|
86
223
|
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
224
|
+
// Insert base root always
|
|
225
|
+
targetBasePaths.push({
|
|
226
|
+
path: baseDocsPath,
|
|
227
|
+
filter: () => true, // Take first available doc generally
|
|
228
|
+
})
|
|
229
|
+
|
|
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}`)
|
|
93
243
|
})
|
|
94
|
-
}
|
|
244
|
+
})
|
|
245
|
+
}
|
|
95
246
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
+
})
|
|
255
|
+
|
|
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
|
+
)
|
|
107
273
|
})
|
|
108
274
|
}
|
|
109
275
|
})
|
|
110
276
|
}
|
|
111
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
|
+
|
|
112
327
|
// 3. External pages
|
|
328
|
+
const externalMetadata: ComponentRoute[] = []
|
|
113
329
|
if (externalPages) {
|
|
114
330
|
Object.entries(externalPages).forEach(([rawPath, ExtComponent]) => {
|
|
115
|
-
|
|
331
|
+
// Use the raw path directly (do not prefix with base docs path)
|
|
332
|
+
const path = rawPath.startsWith('/') ? rawPath : `/${rawPath}`
|
|
116
333
|
if (!children.find((r) => r.path === path)) {
|
|
334
|
+
externalMetadata.push({
|
|
335
|
+
path,
|
|
336
|
+
locale: config.i18n?.defaultLocale,
|
|
337
|
+
title:
|
|
338
|
+
rawPath === '/'
|
|
339
|
+
? 'Home'
|
|
340
|
+
: rawPath.replace(/^\//, '').split('/').pop() || 'Page',
|
|
341
|
+
filePath: '',
|
|
342
|
+
headings: [],
|
|
343
|
+
} as any)
|
|
344
|
+
|
|
117
345
|
children.push({
|
|
118
346
|
path,
|
|
119
347
|
element: (
|
|
@@ -121,16 +349,26 @@ export function createRoutes(options: CreateRoutesOptions): RouteRecord[] {
|
|
|
121
349
|
<ExtComponent />
|
|
122
350
|
</EffectiveExternalLayout>
|
|
123
351
|
),
|
|
352
|
+
loader: async () => ({
|
|
353
|
+
path,
|
|
354
|
+
locale: config.i18n?.defaultLocale,
|
|
355
|
+
}),
|
|
124
356
|
getStaticPaths: () => [path],
|
|
125
357
|
})
|
|
126
358
|
|
|
127
|
-
// 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)
|
|
128
360
|
if (config.i18n) {
|
|
129
361
|
Object.keys(config.i18n.locales).forEach((locale) => {
|
|
130
|
-
const localePath =
|
|
131
|
-
`/${locale}${rawPath === '/' ? '' : rawPath}`,
|
|
132
|
-
)
|
|
362
|
+
const localePath = `/${locale}${rawPath === '/' ? '' : rawPath}`
|
|
133
363
|
if (!children.find((r) => r.path === localePath)) {
|
|
364
|
+
externalMetadata.push({
|
|
365
|
+
path: localePath,
|
|
366
|
+
locale,
|
|
367
|
+
title: rawPath,
|
|
368
|
+
filePath: '',
|
|
369
|
+
headings: [],
|
|
370
|
+
} as any)
|
|
371
|
+
|
|
134
372
|
children.push({
|
|
135
373
|
path: localePath,
|
|
136
374
|
element: (
|
|
@@ -138,6 +376,10 @@ export function createRoutes(options: CreateRoutesOptions): RouteRecord[] {
|
|
|
138
376
|
<ExtComponent />
|
|
139
377
|
</EffectiveExternalLayout>
|
|
140
378
|
),
|
|
379
|
+
loader: async () => ({
|
|
380
|
+
path: localePath,
|
|
381
|
+
locale,
|
|
382
|
+
}),
|
|
141
383
|
getStaticPaths: () => [localePath],
|
|
142
384
|
})
|
|
143
385
|
}
|
|
@@ -152,14 +394,12 @@ export function createRoutes(options: CreateRoutesOptions): RouteRecord[] {
|
|
|
152
394
|
path: '*',
|
|
153
395
|
element: (
|
|
154
396
|
<EffectiveExternalLayout>
|
|
155
|
-
<
|
|
397
|
+
<NotFoundWrapper />
|
|
156
398
|
</EffectiveExternalLayout>
|
|
157
399
|
),
|
|
158
400
|
})
|
|
159
401
|
|
|
160
|
-
|
|
161
|
-
// We need to pass the full metadata to BoltdocsShell so that Sidebar/Tabs can work.
|
|
162
|
-
const allMetadata: ComponentRoute[] = [...routesData]
|
|
402
|
+
const allMetadata = [...docMetadata, ...externalMetadata]
|
|
163
403
|
|
|
164
404
|
// Wrap everything in the Boltdocs shell (providers)
|
|
165
405
|
return [
|
|
@@ -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}
|