boltdocs 2.7.10 → 2.7.11

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 (137) hide show
  1. package/dist/client/index.cjs +1929 -1
  2. package/dist/client/index.js +1880 -1
  3. package/dist/client/mdx.cjs +7 -1
  4. package/dist/client/mdx.js +7 -1
  5. package/dist/client/primitives.cjs +60 -1
  6. package/dist/client/primitives.js +20 -1
  7. package/dist/docs-layout-BXHV0xw_.cjs +1431 -0
  8. package/dist/docs-layout-DwFndmj5.js +1231 -0
  9. package/dist/icons-dev-3cZMyt8r.cjs +1204 -0
  10. package/dist/icons-dev-Df8OQ481.js +839 -0
  11. package/dist/image-DtrI2cw3.cjs +268 -0
  12. package/dist/image-jxPb-2iV.js +214 -0
  13. package/dist/mdx-BdWkJTeB.cjs +523 -0
  14. package/dist/mdx-UTTLFWJq.js +494 -0
  15. package/dist/node/cli-entry.cjs +1 -1
  16. package/dist/node/cli-entry.mjs +1 -1
  17. package/dist/node/index.cjs +1 -1
  18. package/dist/node/index.mjs +1 -1
  19. package/dist/{node-DtEDyN1u.cjs → node-BSM4qcDK.cjs} +1 -1
  20. package/dist/{node-_1jhMGYx.mjs → node-BspZN3R2.mjs} +1 -1
  21. package/dist/{package-DrwtlXfk.cjs → package-DIIrjuWI.cjs} +1 -1
  22. package/dist/{package--0Yf0t1N.mjs → package-K0zsjGIz.mjs} +1 -1
  23. package/dist/{search-dialog-ByvGScjt.js → search-dialog-BHuIiUC6.js} +3 -1
  24. package/dist/search-dialog-BNF10tDl.js +375 -0
  25. package/dist/search-dialog-BwkDuI9R.cjs +220 -0
  26. package/dist/search-dialog-C7xuvyNk.cjs +386 -0
  27. package/dist/search-dialog-CIQg6k8c.cjs +8 -0
  28. package/dist/search-dialog-D-DDN7zJ.js +208 -0
  29. package/package.json +3 -4
  30. package/dist/docs-layout-KoWNZc8_.js +0 -6
  31. package/dist/docs-layout-x2yKt2cL.cjs +0 -6
  32. package/dist/icons-dev-B_RZIyxu.js +0 -6
  33. package/dist/icons-dev-BlV3wWFT.cjs +0 -6
  34. package/dist/image-BHhTvQzr.cjs +0 -6
  35. package/dist/image-CqKzYD8f.js +0 -6
  36. package/dist/mdx-DudBEac0.js +0 -7
  37. package/dist/mdx-r4cDQxWu.cjs +0 -7
  38. package/dist/search-dialog-B584t9ZF.js +0 -6
  39. package/dist/search-dialog-BvBopRsZ.cjs +0 -6
  40. package/dist/search-dialog-Cyko6TJm.cjs +0 -6
  41. package/dist/search-dialog-D6BNohIJ.js +0 -6
  42. package/dist/search-dialog-DuYTIefy.cjs +0 -6
  43. package/src/client/app/config-context.tsx +0 -51
  44. package/src/client/app/doc-page.tsx +0 -38
  45. package/src/client/app/docs-layout.tsx +0 -28
  46. package/src/client/app/head.tsx +0 -122
  47. package/src/client/app/helmet-compat.tsx +0 -36
  48. package/src/client/app/mdx-component.tsx +0 -8
  49. package/src/client/app/mdx-components-context.tsx +0 -72
  50. package/src/client/app/routes-context.tsx +0 -34
  51. package/src/client/app/scroll-handler.tsx +0 -74
  52. package/src/client/app/theme-context.tsx +0 -103
  53. package/src/client/app/ui-context.tsx +0 -42
  54. package/src/client/components/docs-layout-default.tsx +0 -85
  55. package/src/client/components/icons-dev.tsx +0 -282
  56. package/src/client/components/mdx/callout.tsx +0 -97
  57. package/src/client/components/mdx/card.tsx +0 -99
  58. package/src/client/components/mdx/cards.tsx +0 -27
  59. package/src/client/components/mdx/code-block.tsx +0 -184
  60. package/src/client/components/mdx/field.tsx +0 -33
  61. package/src/client/components/mdx/image.tsx +0 -44
  62. package/src/client/components/mdx/index.ts +0 -19
  63. package/src/client/components/mdx/table.tsx +0 -54
  64. package/src/client/components/mdx/typographics.tsx +0 -120
  65. package/src/client/components/mdx/use-code-block.ts +0 -34
  66. package/src/client/components/primitives/breadcrumbs.tsx +0 -54
  67. package/src/client/components/primitives/button-group.tsx +0 -54
  68. package/src/client/components/primitives/button.tsx +0 -6
  69. package/src/client/components/primitives/code-block.tsx +0 -120
  70. package/src/client/components/primitives/docs-layout.tsx +0 -125
  71. package/src/client/components/primitives/error-boundary.tsx +0 -107
  72. package/src/client/components/primitives/heading.tsx +0 -128
  73. package/src/client/components/primitives/helpers/observer.ts +0 -141
  74. package/src/client/components/primitives/image.tsx +0 -26
  75. package/src/client/components/primitives/link.tsx +0 -102
  76. package/src/client/components/primitives/menu.tsx +0 -137
  77. package/src/client/components/primitives/navbar.tsx +0 -466
  78. package/src/client/components/primitives/on-this-page.tsx +0 -430
  79. package/src/client/components/primitives/page-nav.tsx +0 -51
  80. package/src/client/components/primitives/popover.tsx +0 -28
  81. package/src/client/components/primitives/search-dialog.tsx +0 -193
  82. package/src/client/components/primitives/sidebar.tsx +0 -423
  83. package/src/client/components/primitives/skeleton.tsx +0 -26
  84. package/src/client/components/primitives/tabs.tsx +0 -70
  85. package/src/client/components/primitives/tooltip.tsx +0 -81
  86. package/src/client/components/primitives/types.ts +0 -11
  87. package/src/client/components/ui-base/banner.tsx +0 -66
  88. package/src/client/components/ui-base/breadcrumbs.tsx +0 -44
  89. package/src/client/components/ui-base/copy-markdown.tsx +0 -107
  90. package/src/client/components/ui-base/error-boundary.tsx +0 -15
  91. package/src/client/components/ui-base/github-stars.tsx +0 -29
  92. package/src/client/components/ui-base/icons.tsx +0 -240
  93. package/src/client/components/ui-base/index.ts +0 -16
  94. package/src/client/components/ui-base/last-updated.tsx +0 -27
  95. package/src/client/components/ui-base/navbar.tsx +0 -266
  96. package/src/client/components/ui-base/not-found.tsx +0 -26
  97. package/src/client/components/ui-base/on-this-page.tsx +0 -57
  98. package/src/client/components/ui-base/page-nav.tsx +0 -50
  99. package/src/client/components/ui-base/search-dialog.tsx +0 -163
  100. package/src/client/components/ui-base/search-highlight.tsx +0 -10
  101. package/src/client/components/ui-base/sidebar.tsx +0 -92
  102. package/src/client/components/ui-base/tabs.tsx +0 -83
  103. package/src/client/components/ui-base/theme-toggle.tsx +0 -130
  104. package/src/client/components/ui-base/version-i18n.tsx +0 -80
  105. package/src/client/hooks/index.ts +0 -13
  106. package/src/client/hooks/use-analytics.ts +0 -272
  107. package/src/client/hooks/use-breadcrumbs.ts +0 -22
  108. package/src/client/hooks/use-i18n.ts +0 -182
  109. package/src/client/hooks/use-localized-to.ts +0 -113
  110. package/src/client/hooks/use-location.ts +0 -5
  111. package/src/client/hooks/use-navbar.ts +0 -130
  112. package/src/client/hooks/use-page-nav.ts +0 -46
  113. package/src/client/hooks/use-routes.ts +0 -108
  114. package/src/client/hooks/use-search-highlight.ts +0 -185
  115. package/src/client/hooks/use-search.ts +0 -118
  116. package/src/client/hooks/use-sidebar.ts +0 -205
  117. package/src/client/hooks/use-tabs.ts +0 -46
  118. package/src/client/hooks/use-version.ts +0 -111
  119. package/src/client/index.ts +0 -31
  120. package/src/client/mdx.ts +0 -2
  121. package/src/client/primitives.ts +0 -19
  122. package/src/client/ssg/boltdocs-shell.tsx +0 -148
  123. package/src/client/ssg/create-routes.tsx +0 -473
  124. package/src/client/ssg/index.ts +0 -4
  125. package/src/client/ssg/mdx-page.tsx +0 -38
  126. package/src/client/store/boltdocs-context.tsx +0 -137
  127. package/src/client/theme/neutral.css +0 -141
  128. package/src/client/theme/reset.css +0 -189
  129. package/src/client/types.ts +0 -116
  130. package/src/client/utils/cn.ts +0 -6
  131. package/src/client/utils/copy-clipboard.ts +0 -22
  132. package/src/client/utils/get-base-file-path.ts +0 -21
  133. package/src/client/utils/github.ts +0 -121
  134. package/src/client/utils/i18n.ts +0 -23
  135. package/src/client/utils/path.ts +0 -9
  136. package/src/client/utils/react-to-text.ts +0 -34
  137. package/src/client/virtual.d.ts +0 -24
@@ -1,19 +0,0 @@
1
- export * from './components/primitives/docs-layout'
2
- export * from './components/primitives/button-group'
3
- export * from './components/primitives/tabs'
4
- export * from './components/primitives/sidebar'
5
- export * from './components/primitives/on-this-page'
6
- export * from './components/primitives/code-block'
7
- export * from './components/primitives/button'
8
- export * from './components/primitives/popover'
9
- export * from './components/primitives/tooltip'
10
- export * from './components/primitives/link'
11
- export * from './components/primitives/error-boundary'
12
- export * from './components/primitives/heading'
13
- export * from './components/primitives/image'
14
- export * from './components/primitives/menu'
15
- export * from './components/primitives/page-nav'
16
- export * from './components/primitives/search-dialog'
17
- export * from './components/primitives/skeleton'
18
- export * from './components/primitives/breadcrumbs'
19
- export * from './components/primitives/navbar'
@@ -1,148 +0,0 @@
1
- import { useEffect, useMemo } from 'react'
2
- import { Outlet, useLocation } from 'react-router-dom'
3
- import { BoltdocsProvider, useBoltdocsContext } from '../store/boltdocs-context'
4
- import { ThemeProvider } from '../app/theme-context'
5
- import { MdxComponentsProvider } from '../app/mdx-components-context'
6
- import { HelmetProvider } from '../app/helmet-compat'
7
- import { ConfigContext } from '../app/config-context'
8
- import { ScrollHandler } from '../app/scroll-handler'
9
- import { mdxComponentsDefault } from '../app/mdx-component'
10
- import { RoutesProvider } from '../app/routes-context'
11
- import type { BoltdocsConfig } from '../../shared/types'
12
- import type { ComponentRoute } from '../types'
13
- import { UIProvider } from '../app/ui-context'
14
-
15
- import virtualCustomComponents from 'virtual:boltdocs-mdx-components'
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
-
22
- /**
23
- * Updates the HTML lang and dir attributes based on the current locale configuration.
24
- */
25
- function I18nUpdater({ config }: { config: BoltdocsConfig }) {
26
- const { currentLocale } = useBoltdocsContext()
27
-
28
- useEffect(() => {
29
- if (!config.i18n || typeof document === 'undefined') return
30
- const locale = currentLocale || config.i18n.defaultLocale
31
- const localeConfig = config.i18n.localeConfigs?.[locale]
32
- document.documentElement.lang = localeConfig?.htmlLang || locale || 'en'
33
- document.documentElement.dir = localeConfig?.direction || 'ltr'
34
- }, [currentLocale, config.i18n])
35
-
36
- return null
37
- }
38
-
39
- /**
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().
42
- */
43
- function StoreSync({
44
- config,
45
- routeMap,
46
- }: {
47
- config: BoltdocsConfig
48
- routeMap: Map<string, ComponentRoute>
49
- }) {
50
- const location = useLocation()
51
- const { setLocale, setVersion } = useBoltdocsContext()
52
-
53
- useEffect(() => {
54
- const currentPath = normalizePath(location.pathname)
55
- const matchedRoute = routeMap.get(currentPath)
56
-
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
- }
67
- }
68
- }, [location.pathname, config, routeMap, setLocale, setVersion])
69
-
70
- return null
71
- }
72
-
73
- export function BoltdocsShell({
74
- config,
75
- routes,
76
- components = {},
77
- }: {
78
- config: BoltdocsConfig
79
- routes: ComponentRoute[]
80
- components?: Record<string, React.ComponentType>
81
- }) {
82
- const allComponents = useMemo(
83
- () => ({
84
- ...mdxComponentsDefault,
85
- ...virtualCustomComponents,
86
- ...components,
87
- }),
88
- [components],
89
- )
90
-
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])
124
-
125
- return (
126
- <HelmetProvider>
127
- <RoutesProvider routes={routes}>
128
- <ThemeProvider>
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} />
138
- <I18nUpdater config={config} />
139
- <Outlet />
140
- </BoltdocsProvider>
141
- </ConfigContext.Provider>
142
- </MdxComponentsProvider>
143
- </UIProvider>
144
- </ThemeProvider>
145
- </RoutesProvider>
146
- </HelmetProvider>
147
- )
148
- }
@@ -1,473 +0,0 @@
1
- import type { RouteRecord } from '@bdocs/ssg'
2
- import type { ComponentRoute, BoltdocsConfig } from '../types'
3
- import { MdxPage } from './mdx-page'
4
- import { BoltdocsShell } from './boltdocs-shell'
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
-
10
- interface CreateRoutesOptions {
11
- routesData: ComponentRoute[]
12
- config: BoltdocsConfig
13
- mdxModules: Record<string, any>
14
- Layout: React.ComponentType<{ children: React.ReactNode }>
15
-
16
- externalPages?: Record<string, React.ComponentType>
17
- externalLayout?: React.ComponentType<{ children: React.ReactNode }>
18
- components?: Record<string, React.ComponentType>
19
- }
20
-
21
- /**
22
- * Stable component to render MDX pages.
23
- * By being outside createRoutes, it prevents React from unmounting the page on HMR.
24
- */
25
- const MdxRouteElement = ({
26
- moduleLoader,
27
- moduleKey,
28
- route,
29
- components,
30
- }: {
31
- moduleLoader: any
32
- moduleKey: string | undefined
33
- route: ComponentRoute
34
- components: any
35
- }) => {
36
- const MDXComponent = moduleLoader?.default ?? moduleLoader ?? null
37
-
38
- useEffect(() => {
39
- if (!import.meta.hot || !moduleKey) return
40
-
41
- const handler = (data: { relPath: string }) => {
42
- const incoming = data.relPath.replace(/\\/g, '/').replace(/^\//, '')
43
- const routeFile = route.filePath.replace(/\\/g, '/').replace(/^\//, '')
44
-
45
- if (incoming !== routeFile) return
46
-
47
- const cacheBustUrl = moduleKey + '?t=' + Date.now()
48
- import(/* @vite-ignore */ cacheBustUrl).then((m: any) => {
49
- MDXComponent
50
- })
51
- }
52
-
53
- import.meta.hot.on('boltdocs:mdx-update', handler)
54
- return () => import.meta.hot?.off('boltdocs:mdx-update', handler)
55
- }, [moduleKey, route.filePath])
56
-
57
- if (!MDXComponent) return <Loading />
58
-
59
- return <MdxPage MDXComponent={MDXComponent} mdxComponents={components} />
60
- }
61
-
62
- import { useMdxComponents } from '../app/mdx-components-context'
63
-
64
- const NotFoundWrapper = () => {
65
- const components = useMdxComponents()
66
- const ActiveNotFound = components.NotFound || components['404'] || NotFound
67
- return <ActiveNotFound />
68
- }
69
-
70
- import { DocsLayout } from '../app/docs-layout'
71
-
72
- export function createRoutes(options: CreateRoutesOptions): RouteRecord[] {
73
- const {
74
- routesData,
75
- config,
76
- mdxModules,
77
- externalPages,
78
- externalLayout,
79
- components,
80
- } = options
81
-
82
- const EffectiveExternalLayout =
83
- externalLayout || (({ children }: any) => <>{children}</>)
84
-
85
- const withBase = (path: string) => {
86
- // Future support for base path in config
87
- const base = config.base || '/'
88
- if (path.startsWith(base)) return path
89
- const b = base === '/' ? '' : base.replace(/\/$/, '')
90
- const p = path.startsWith('/') ? path : `/${path}`
91
- return `${b}${p}` || '/'
92
- }
93
-
94
- const defaultVersionMetadata: ComponentRoute[] = []
95
-
96
- // Inject virtual explicit routes for default version to ensure paths like /docs/latest/... aren't 404s
97
- const defaultVersion = config.versions?.defaultVersion
98
- const docsBase = (config.base || '/docs').replace(/\/$/, '')
99
-
100
- // Base path under which all doc routes are nested (e.g., "/docs")
101
- // Used to compute relative child paths for correct React Router nesting
102
- let baseDocsPath = (config.base || '/docs').replace(/\/$/, '')
103
- if (!baseDocsPath) baseDocsPath = '/'
104
-
105
- if (defaultVersion) {
106
- routesData.forEach((route) => {
107
- // If this route explicitly already belongs to a version, do not clone.
108
- if (route.version) return
109
-
110
- // Compute path without docs base prefix to properly place version token
111
- const p = route.path || ''
112
- const subPath = p.startsWith(docsBase)
113
- ? p.substring(docsBase.length).replace(/^\//, '')
114
- : p.replace(/^\//, '')
115
-
116
- // Detect if it already includes the target version segment
117
- const hasVersionPrefix =
118
- subPath === defaultVersion || subPath.startsWith(`${defaultVersion}/`)
119
-
120
- if (!hasVersionPrefix) {
121
- // Standardize reconstruction: [docsBase] / [version] / [remaining_path]
122
- const explicitPath =
123
- `${docsBase}/${defaultVersion}/${subPath}`
124
- .replace(/\/+/g, '/')
125
- .replace(/\/$/, '') || '/'
126
-
127
- defaultVersionMetadata.push({
128
- ...route,
129
- path: explicitPath,
130
- version: defaultVersion,
131
- })
132
- }
133
- })
134
- }
135
-
136
- const docMetadata = [...routesData, ...defaultVersionMetadata]
137
-
138
- // 0. Build a single pre-computed lookup map for the MDX modules (O(N) build, O(1) access).
139
- // This replaces the inner findModuleKey loops that executed an O(N) scan for EVERY route.
140
- const moduleMap = new Map<string, string>()
141
- const mdxModuleKeys = Object.keys(mdxModules)
142
-
143
- if (mdxModuleKeys.length > 0) {
144
- // Detect docs directory structure from keys (e.g., "/docs/intro.md")
145
- const firstKeyNormalized = mdxModuleKeys[0].replace(/\\/g, '/')
146
- const parts = firstKeyNormalized.split('/').filter(Boolean)
147
- const docsDirName = parts[0] || 'docs'
148
- const primaryPrefix = `/${docsDirName}/`
149
- const altPrefix = `./${docsDirName}/`
150
-
151
- for (const rawKey of mdxModuleKeys) {
152
- const k = rawKey.replace(/\\/g, '/')
153
- let relativePath = ''
154
- if (k.indexOf(primaryPrefix) !== -1) {
155
- relativePath = k.substring(
156
- k.indexOf(primaryPrefix) + primaryPrefix.length,
157
- )
158
- } else if (k.startsWith(altPrefix)) {
159
- relativePath = k.substring(altPrefix.length)
160
- }
161
-
162
- if (relativePath) {
163
- moduleMap.set(relativePath, rawKey)
164
- } else {
165
- // Fallback: store full normalized key as a catch-all
166
- moduleMap.set(k, rawKey)
167
- }
168
- }
169
- }
170
-
171
- // 1. Documentation routes
172
- const docRoutes: RouteRecord[] = docMetadata.map((route) => {
173
- // Perform constant-time lookup using the pre-computed map
174
- const normalizedFilePath = route.filePath.replace(/\\/g, '/')
175
- const moduleKey = moduleMap.get(normalizedFilePath)
176
- const moduleLoader = moduleKey ? mdxModules[moduleKey] : null
177
- const fullPath = withBase(route.path === '' ? '/' : route.path)
178
- const path =
179
- fullPath === baseDocsPath
180
- ? '.'
181
- : fullPath.startsWith(baseDocsPath + '/')
182
- ? fullPath.slice(baseDocsPath.length + 1)
183
- : fullPath
184
-
185
- return {
186
- path,
187
- element: (
188
- <MdxRouteElement
189
- key={moduleKey || path}
190
- moduleKey={moduleKey}
191
- moduleLoader={moduleLoader}
192
- route={route}
193
- components={components}
194
- />
195
- ),
196
- loader: async () => ({
197
- path,
198
- frontmatter: {
199
- title: route.title,
200
- description: route.description || '',
201
- ...(route.frontmatter || {}),
202
- },
203
- headings: route.headings || [],
204
- filePath: route.filePath,
205
- locale: route.locale,
206
- version: route.version,
207
- group: route.group,
208
- groupTitle: route.groupTitle,
209
- date: route.date,
210
- lastUpdated: route.lastUpdated,
211
- }),
212
- getStaticPaths: () => [path],
213
- }
214
- })
215
-
216
- // 2. Auto-fallback for the base paths (e.g. /docs, /docs/es) to the first documentation page
217
-
218
- const locales = config.i18n?.locales
219
- ? Array.isArray(config.i18n.locales)
220
- ? config.i18n.locales
221
- : Object.keys(config.i18n.locales)
222
- : []
223
-
224
- // 2a. Generate dynamic permutation matrix of version/locale combinations
225
- const allVersions = config.versions?.versions?.map((v) => v.path) || []
226
-
227
- const targetBasePaths: Array<{
228
- path: string
229
- filter: (p: string) => boolean
230
- }> = []
231
-
232
- // Insert base root always
233
- targetBasePaths.push({
234
- path: baseDocsPath,
235
- filter: () => true, // Take first available doc generally
236
- })
237
-
238
- // Permutation builder: version loop nested with locale loop
239
- // Ensures paths like /docs/v2.0, /docs/es, and /docs/v2.0/es ALL receive fallback logic.
240
- const subPaths: string[] = []
241
- if (allVersions.length > 0) {
242
- allVersions.forEach((v) => subPaths.push(`/${v}`))
243
- }
244
- if (locales.length > 0) {
245
- locales.forEach((l) => subPaths.push(`/${l}`))
246
- }
247
- if (allVersions.length > 0 && locales.length > 0) {
248
- allVersions.forEach((v) => {
249
- locales.forEach((l) => {
250
- subPaths.push(`/${v}/${l}`)
251
- })
252
- })
253
- }
254
-
255
- // Map permutations onto the physical base docs route
256
- subPaths.forEach((sp) => {
257
- const fullP = baseDocsPath === '/' ? sp : `${baseDocsPath}${sp}`
258
- targetBasePaths.push({
259
- path: fullP,
260
- filter: (rp) => rp.startsWith(fullP.replace(/\/$/, '') + '/'),
261
- })
262
- })
263
-
264
- // Pre-compute a Set of absolute and normalized path strings from the real routes
265
- // to perform O(1) validation checks within the redirection loops below.
266
- const docPathRegistry = new Set(
267
- docRoutes.map((r) => (r.path || '').replace(/\/$/, '')),
268
- )
269
-
270
- // Pre-compute external pages paths so we do not hijack them with redirects
271
- const externalPaths = new Set<string>()
272
- if (externalPages) {
273
- Object.keys(externalPages).forEach((rawPath) => {
274
- const p = rawPath.startsWith('/') ? rawPath : `/${rawPath}`
275
- externalPaths.add(p.replace(/\/$/, ''))
276
- if (config.i18n) {
277
- Object.keys(config.i18n.locales).forEach((locale) => {
278
- externalPaths.add(
279
- `/${locale}${p === '/' ? '' : p}`.replace(/\/$/, ''),
280
- )
281
- })
282
- }
283
- })
284
- }
285
-
286
- // 2b. Deploy smart redirects
287
- targetBasePaths.forEach(({ path: bPath, filter }) => {
288
- if (bPath === '/') return // Never hijack global app root
289
-
290
- const normalizedPath = bPath.replace(/\/$/, '')
291
- const hasExplicitMatch =
292
- docPathRegistry.has(normalizedPath) || externalPaths.has(normalizedPath)
293
-
294
- if (!hasExplicitMatch) {
295
- const defaultTab = config.theme?.tabs?.[0]?.id
296
- const defaultTabPath = defaultTab
297
- ? `${normalizedPath}/${defaultTab}`.replace(/\/+/g, '/')
298
- : null
299
-
300
- // Prioritize: Find a real route that matches the default tab first, then fall back to the first route beginning with this pattern.
301
- let matchedRouteObj: RouteRecord | undefined =
302
- defaultTabPath && docPathRegistry.has(defaultTabPath.replace(/\/$/, ''))
303
- ? docRoutes.find(
304
- (r) =>
305
- r.path.replace(/\/$/, '') === defaultTabPath.replace(/\/$/, ''),
306
- )
307
- : docRoutes.find((r) => filter(r.path) && r.path !== normalizedPath)
308
-
309
- // Ultimate fallback: the absolute first document
310
- if (!matchedRouteObj && docRoutes.length > 0) {
311
- matchedRouteObj = docRoutes[0]
312
- }
313
-
314
- if (matchedRouteObj) {
315
- const redirectPath =
316
- bPath === baseDocsPath
317
- ? '.'
318
- : bPath.startsWith(baseDocsPath + '/')
319
- ? bPath.slice(baseDocsPath.length + 1)
320
- : bPath
321
-
322
- // Use `index: true` for the docs base path (e.g. /docs) instead of
323
- // `path: '.'`. The dot-path is a client-only React Router feature: it
324
- // means "same URL as parent" in createBrowserRouter, but createStaticHandler
325
- // does not recognise it during SSG. This caused the static handler to skip
326
- // the fallback loader entirely → loaderData was empty for /docs → the SSR
327
- // rendered an empty content slot while the client hydrated with the first
328
- // doc's element → structural mismatch → visual page duplication on refresh.
329
- // Index routes are correctly matched by both browser and static handler.
330
- const isBasePathFallback = redirectPath === '.'
331
- docRoutes.push({
332
- ...(isBasePathFallback
333
- ? { index: true as const }
334
- : { path: redirectPath }),
335
- element: matchedRouteObj.element,
336
- loader: matchedRouteObj.loader,
337
- getStaticPaths: () => [],
338
- })
339
-
340
- const matchedMetaObj = docMetadata.find((m) => {
341
- const fullPath = withBase(m.path === '' ? '/' : m.path)
342
- const p =
343
- fullPath === baseDocsPath
344
- ? '.'
345
- : fullPath.startsWith(baseDocsPath + '/')
346
- ? fullPath.slice(baseDocsPath.length + 1)
347
- : fullPath
348
- return p === matchedRouteObj.path
349
- })
350
-
351
- if (matchedMetaObj) {
352
- const canonicalPath = withBase(matchedMetaObj.path)
353
- const canonicalUrl = config.siteUrl
354
- ? `${config.siteUrl.replace(/\/$/, '')}${canonicalPath}`
355
- : canonicalPath
356
-
357
- docMetadata.push({
358
- ...matchedMetaObj,
359
- path: bPath,
360
- filePath: '',
361
- slugParts: [],
362
- seo: {
363
- ...matchedMetaObj.seo,
364
- canonical: canonicalUrl,
365
- },
366
- })
367
- }
368
- }
369
- }
370
- })
371
-
372
- // Group all documentation routes under the persistent DocsLayout
373
- const docsLayoutRoute: RouteRecord = {
374
- path: baseDocsPath,
375
- element: <DocsLayout />,
376
- children: docRoutes,
377
- }
378
-
379
- const children: RouteRecord[] = [docsLayoutRoute]
380
-
381
- // 3. External pages
382
- const externalMetadata: ComponentRoute[] = []
383
- if (externalPages) {
384
- Object.entries(externalPages).forEach(([rawPath, ExtComponent]) => {
385
- // Use the raw path directly (do not prefix with base docs path)
386
- const path = rawPath.startsWith('/') ? rawPath : `/${rawPath}`
387
- if (!children.find((r) => r.path === path)) {
388
- externalMetadata.push({
389
- path,
390
- locale: config.i18n?.defaultLocale,
391
- title:
392
- rawPath === '/'
393
- ? 'Home'
394
- : rawPath.replace(/^\//, '').split('/').pop() || 'Page',
395
- filePath: '',
396
- headings: [],
397
- } as any)
398
-
399
- children.push({
400
- path,
401
- element: (
402
- <EffectiveExternalLayout>
403
- <ExtComponent />
404
- </EffectiveExternalLayout>
405
- ),
406
- loader: async () => ({
407
- path,
408
- locale: config.i18n?.defaultLocale,
409
- }),
410
- getStaticPaths: () => [path],
411
- })
412
-
413
- // Also add i18n variants for external pages if needed (do not prefix with base docs path)
414
- if (config.i18n) {
415
- Object.keys(config.i18n.locales).forEach((locale) => {
416
- const localePath = `/${locale}${rawPath === '/' ? '' : rawPath}`
417
- if (!children.find((r) => r.path === localePath)) {
418
- externalMetadata.push({
419
- path: localePath,
420
- locale,
421
- title: rawPath,
422
- filePath: '',
423
- headings: [],
424
- } as any)
425
-
426
- children.push({
427
- path: localePath,
428
- element: (
429
- <EffectiveExternalLayout>
430
- <ExtComponent />
431
- </EffectiveExternalLayout>
432
- ),
433
- loader: async () => ({
434
- path: localePath,
435
- locale,
436
- }),
437
- getStaticPaths: () => [localePath],
438
- })
439
- }
440
- })
441
- }
442
- }
443
- })
444
- }
445
-
446
- // --- 4. 404 catch-all ---
447
- children.push({
448
- path: '*',
449
- element: (
450
- <EffectiveExternalLayout>
451
- <NotFoundWrapper />
452
- </EffectiveExternalLayout>
453
- ),
454
- })
455
-
456
- const allMetadata = [...docMetadata, ...externalMetadata]
457
-
458
- // Wrap everything in the Boltdocs shell (providers)
459
- return [
460
- {
461
- // No path = Layout Route
462
- // This allows children to retain their absolute paths while being wrapped in the shell.
463
- element: (
464
- <BoltdocsShell
465
- config={config}
466
- routes={allMetadata}
467
- components={components}
468
- />
469
- ),
470
- children,
471
- },
472
- ]
473
- }
@@ -1,4 +0,0 @@
1
- export { ViteReactSSG } from '@bdocs/ssg'
2
- export { createRoutes } from './create-routes'
3
- export { MdxPage } from './mdx-page'
4
- export { BoltdocsShell } from './boltdocs-shell'
@@ -1,38 +0,0 @@
1
- import { useLoaderData } from 'react-router-dom'
2
- import { DocPage } from '../app/doc-page'
3
-
4
- /**
5
- * Renders an MDX page by consuming pre-loaded module data.
6
- * Uses DocPage to ensure consistent layout and metadata application.
7
- */
8
- export function MdxPage({
9
- MDXComponent: propMDX,
10
- mdxComponents: propComponents,
11
- }: any) {
12
- const data = useLoaderData() as any
13
- const MDXComponent = propMDX || data?.MDXComponent
14
- const components = propComponents || data?.mdxComponents
15
- if (!MDXComponent) return null
16
-
17
- return (
18
- <DocPage
19
- route={
20
- {
21
- path: data.path,
22
- filePath: data.filePath,
23
- title: data.frontmatter.title,
24
- description: data.frontmatter.description,
25
- headings: data.headings,
26
- locale: data.locale,
27
- version: data.version,
28
- group: data.group,
29
- groupTitle: data.groupTitle,
30
- lastUpdated: data.lastUpdated,
31
- frontmatter: data.frontmatter,
32
- } as any
33
- }
34
- content={MDXComponent}
35
- mdxComponents={components}
36
- />
37
- )
38
- }