boltdocs 2.1.1 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/bin/boltdocs.js +2 -2
- package/dist/base-ui/index.d.mts +25 -0
- package/dist/base-ui/index.d.ts +25 -0
- package/dist/base-ui/index.js +1 -0
- package/dist/base-ui/index.mjs +1 -0
- package/dist/{cache-Q4T6VAUL.mjs → cache-P6WK424C.mjs} +1 -1
- package/dist/chunk-22NXDNP4.mjs +74 -0
- package/dist/chunk-2HUVMMJU.mjs +1 -0
- package/dist/chunk-2Z5T6EAU.mjs +1 -0
- package/dist/chunk-CRZGOE32.mjs +1 -0
- package/dist/chunk-HA6543SL.mjs +1 -0
- package/dist/chunk-JD3RSDE4.mjs +1 -0
- package/dist/chunk-JZXLCA2E.mjs +1 -0
- package/dist/chunk-NBCYHLAA.mjs +1 -0
- package/dist/chunk-RPUERTVC.mjs +1 -0
- package/dist/chunk-T3W44KWY.mjs +1 -0
- package/dist/chunk-URTD6E6S.mjs +1 -0
- package/dist/chunk-W2NB4T6V.mjs +1 -0
- package/dist/chunk-Y4RRHPXC.mjs +1 -0
- package/dist/client/index.d.mts +13 -115
- package/dist/client/index.d.ts +13 -115
- package/dist/client/index.js +1 -1
- package/dist/client/index.mjs +1 -1
- package/dist/client/ssr.js +1 -1
- package/dist/client/ssr.mjs +1 -1
- package/dist/client/types.d.mts +3 -0
- package/dist/client/types.d.ts +3 -0
- package/dist/client/types.js +1 -0
- package/dist/client/types.mjs +0 -0
- package/dist/copy-markdown-C-90ixSe.d.ts +15 -0
- package/dist/copy-markdown-CbS8X-qe.d.mts +15 -0
- package/dist/{client/hooks → hooks}/index.d.mts +16 -11
- package/dist/{client/hooks → hooks}/index.d.ts +16 -11
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/index.mjs +1 -0
- package/dist/integrations/index.d.mts +48 -0
- package/dist/integrations/index.d.ts +48 -0
- package/dist/integrations/index.js +1 -0
- package/dist/integrations/index.mjs +1 -0
- package/dist/link-DfBwCeZc.d.mts +68 -0
- package/dist/link-DfBwCeZc.d.ts +68 -0
- package/dist/loading-B7X5Wchs.d.ts +66 -0
- package/dist/loading-WuaQbsKb.d.mts +66 -0
- package/dist/{client/components/mdx → mdx}/index.d.mts +6 -38
- package/dist/{client/components/mdx → mdx}/index.d.ts +6 -38
- package/dist/mdx/index.js +1 -0
- package/dist/mdx/index.mjs +1 -0
- package/dist/node/cli-entry.js +31 -27
- package/dist/node/cli-entry.mjs +5 -1
- package/dist/node/index.d.mts +44 -14
- package/dist/node/index.d.ts +44 -14
- package/dist/node/index.js +24 -24
- package/dist/node/index.mjs +1 -1
- package/dist/primitives/index.d.mts +301 -0
- package/dist/primitives/index.d.ts +301 -0
- package/dist/primitives/index.js +1 -0
- package/dist/primitives/index.mjs +1 -0
- package/dist/search-dialog-ZRXBAQJ5.mjs +1 -0
- package/dist/{types-Cp21DHI6.d.mts → types-j7jvWsJj.d.mts} +63 -17
- package/dist/{types-Cp21DHI6.d.ts → types-j7jvWsJj.d.ts} +63 -17
- package/dist/{use-routes-xLhumjbV.d.ts → use-routes-Cd806kGw.d.ts} +1 -1
- package/dist/{use-routes-8Iei6jTp.d.mts → use-routes-DDL0_jkQ.d.mts} +1 -1
- package/package.json +35 -8
- package/src/client/app/index.tsx +155 -35
- package/src/client/app/mdx-component.tsx +7 -3
- package/src/client/app/theme-context.tsx +47 -23
- package/src/client/components/default-layout.tsx +16 -6
- package/src/client/components/primitives/breadcrumbs.tsx +1 -1
- package/src/client/components/primitives/navbar.tsx +8 -5
- package/src/client/components/primitives/search-dialog.tsx +15 -6
- package/src/client/components/primitives/sidebar.tsx +3 -2
- package/src/client/components/primitives/skeleton.tsx +26 -0
- package/src/client/components/ui-base/breadcrumbs.tsx +1 -1
- package/src/client/components/ui-base/index.ts +17 -0
- package/src/client/components/ui-base/loading.tsx +43 -73
- package/src/client/components/ui-base/navbar.tsx +74 -39
- package/src/client/components/ui-base/page-nav.tsx +2 -1
- package/src/client/components/ui-base/powered-by.tsx +11 -5
- package/src/client/components/ui-base/search-dialog.tsx +16 -5
- package/src/client/components/ui-base/sidebar.tsx +33 -22
- package/src/client/components/ui-base/tabs.tsx +4 -1
- package/src/client/components/ui-base/theme-toggle.tsx +35 -15
- package/src/client/hooks/use-i18n.ts +38 -7
- package/src/client/hooks/use-localized-to.ts +51 -73
- package/src/client/hooks/use-navbar.ts +10 -3
- package/src/client/hooks/use-page-nav.ts +27 -6
- package/src/client/hooks/use-routes.ts +62 -17
- package/src/client/hooks/use-search.ts +84 -46
- package/src/client/hooks/use-sidebar.ts +6 -2
- package/src/client/hooks/use-version.ts +5 -0
- package/src/client/integrations/index.ts +1 -0
- package/src/client/store/use-boltdocs-store.ts +44 -0
- package/src/client/theme/neutral.css +29 -0
- package/src/client/types.ts +4 -2
- package/src/client/utils/i18n.ts +23 -0
- package/src/node/{cli.ts → cli/build.ts} +17 -23
- package/src/node/cli/dev.ts +22 -0
- package/src/node/cli/doctor.ts +243 -0
- package/src/node/cli/index.ts +9 -0
- package/src/node/cli/ui.ts +54 -0
- package/src/node/cli-entry.ts +16 -16
- package/src/node/config.ts +54 -17
- package/src/node/index.ts +1 -1
- package/src/node/mdx/cache.ts +12 -0
- package/src/node/mdx/highlighter.ts +47 -0
- package/src/node/mdx/index.ts +114 -0
- package/src/node/mdx/rehype-shiki.ts +53 -0
- package/src/node/mdx/remark-shiki.ts +61 -0
- package/src/node/plugin/entry.ts +1 -1
- package/src/node/plugin/html.ts +8 -4
- package/src/node/plugin/index.ts +135 -72
- package/src/node/routes/index.ts +34 -13
- package/src/node/routes/parser.ts +13 -5
- package/src/node/search/index.ts +55 -0
- package/src/node/ssg/index.ts +15 -7
- package/src/node/ssg/robots.ts +7 -4
- package/src/node/utils.ts +32 -2
- package/tsup.config.ts +7 -2
- package/dist/chunk-52MVMZWS.mjs +0 -1
- package/dist/chunk-BVWWKXJH.mjs +0 -1
- package/dist/chunk-DVY3RDXD.mjs +0 -1
- package/dist/chunk-FUVYCYWC.mjs +0 -1
- package/dist/chunk-GBLMDJ2B.mjs +0 -1
- package/dist/chunk-ISPX45DF.mjs +0 -1
- package/dist/chunk-PNXZMUCO.mjs +0 -1
- package/dist/chunk-V2ZHKQSP.mjs +0 -74
- package/dist/client/components/mdx/index.js +0 -1
- package/dist/client/components/mdx/index.mjs +0 -1
- package/dist/client/hooks/index.js +0 -1
- package/dist/client/hooks/index.mjs +0 -1
- package/dist/search-dialog-TWGYKF2D.mjs +0 -1
- package/src/node/mdx.ts +0 -279
package/src/node/plugin/index.ts
CHANGED
|
@@ -9,9 +9,9 @@ import type { BoltdocsPluginOptions } from './types'
|
|
|
9
9
|
import { generateEntryCode } from './entry'
|
|
10
10
|
import { injectHtmlMeta, getHtmlTemplate } from './html'
|
|
11
11
|
import { generateRobotsTxt } from '../ssg/robots'
|
|
12
|
+
import { generateSearchData } from '../search'
|
|
12
13
|
import fs from 'fs'
|
|
13
14
|
|
|
14
|
-
|
|
15
15
|
export * from './types'
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -55,7 +55,20 @@ export function boltdocsPlugin(
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
return {
|
|
58
|
-
optimizeDeps: {
|
|
58
|
+
optimizeDeps: {
|
|
59
|
+
include: ['react', 'react-dom'],
|
|
60
|
+
exclude: [
|
|
61
|
+
'boltdocs',
|
|
62
|
+
'boltdocs/client',
|
|
63
|
+
'boltdocs/hooks',
|
|
64
|
+
'boltdocs/primitives',
|
|
65
|
+
'boltdocs/base-ui',
|
|
66
|
+
'boltdocs/mdx',
|
|
67
|
+
'boltdocs/integrations',
|
|
68
|
+
'boltdocs/client/hooks',
|
|
69
|
+
'boltdocs/client/primitives',
|
|
70
|
+
],
|
|
71
|
+
},
|
|
59
72
|
}
|
|
60
73
|
},
|
|
61
74
|
|
|
@@ -76,25 +89,39 @@ export function boltdocsPlugin(
|
|
|
76
89
|
next()
|
|
77
90
|
})
|
|
78
91
|
|
|
79
|
-
// Serve default HTML if index.html is missing
|
|
92
|
+
// Serve default HTML for documentation routes or if index.html is missing
|
|
80
93
|
server.middlewares.use(async (req, res, next) => {
|
|
81
94
|
const url = req.url?.split('?')[0] || '/'
|
|
82
95
|
const accept = req.headers.accept || ''
|
|
83
96
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
const isDocRoute =
|
|
98
|
+
url === '/' ||
|
|
99
|
+
url.startsWith('/docs') ||
|
|
100
|
+
(config.i18n &&
|
|
101
|
+
Object.keys(config.i18n.locales).some(
|
|
102
|
+
(locale) =>
|
|
103
|
+
url.startsWith(`/${locale}/docs`) || url === `/${locale}`,
|
|
104
|
+
)) ||
|
|
105
|
+
(config.external &&
|
|
106
|
+
Object.keys(config.external).some((extPath) =>
|
|
107
|
+
url.startsWith(extPath),
|
|
108
|
+
))
|
|
109
|
+
|
|
110
|
+
// Improved check: If it's a doc route, serve HTML even if it has a dot (e.g. version 1.1)
|
|
111
|
+
// We only skip if it has a known asset extension to prevent serving HTML for images/js/etc.
|
|
112
|
+
const isAsset =
|
|
113
|
+
/\.(js|css|png|jpe?g|gif|svg|ico|webp|woff2?|ttf|otf|mp4|webm|ogg|mp3|wav|flac|aac|pdf|zip|gz|map|json)$/i.test(
|
|
114
|
+
url,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
if (accept.includes('text/html') && !isAsset && isDocRoute) {
|
|
118
|
+
let html = getHtmlTemplate(config)
|
|
119
|
+
html = injectHtmlMeta(html, config)
|
|
120
|
+
html = await server.transformIndexHtml(req.url || '/', html)
|
|
121
|
+
res.statusCode = 200
|
|
122
|
+
res.setHeader('Content-Type', 'text/html')
|
|
123
|
+
res.end(html)
|
|
124
|
+
return
|
|
98
125
|
}
|
|
99
126
|
|
|
100
127
|
next()
|
|
@@ -124,66 +151,96 @@ export function boltdocsPlugin(
|
|
|
124
151
|
file: string,
|
|
125
152
|
type: 'add' | 'unlink' | 'change',
|
|
126
153
|
) => {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
// Restart the Vite server if the Boltdocs config changes
|
|
130
|
-
if (CONFIG_FILES.some((c) => normalized.endsWith(c))) {
|
|
131
|
-
server.restart()
|
|
132
|
-
return
|
|
133
|
-
}
|
|
154
|
+
try {
|
|
155
|
+
const normalized = normalizePath(file)
|
|
134
156
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
) {
|
|
141
|
-
const mod = server.moduleGraph.getModuleById(
|
|
142
|
-
'\0virtual:boltdocs-mdx-components',
|
|
143
|
-
)
|
|
144
|
-
if (mod) server.moduleGraph.invalidateModule(mod)
|
|
145
|
-
server.ws.send({ type: 'full-reload' })
|
|
146
|
-
return
|
|
147
|
-
}
|
|
157
|
+
// Restart the Vite server if the Boltdocs config changes
|
|
158
|
+
if (CONFIG_FILES.some((c) => normalized.endsWith(c))) {
|
|
159
|
+
server.restart()
|
|
160
|
+
return
|
|
161
|
+
}
|
|
148
162
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
163
|
+
// If mdx-components file changes, invalidate the virtual module
|
|
164
|
+
if (
|
|
165
|
+
mdxCompExtensions.some((ext) =>
|
|
166
|
+
normalized.endsWith(`mdx-components.${ext}`),
|
|
167
|
+
)
|
|
168
|
+
) {
|
|
169
|
+
const mod = server.moduleGraph.getModuleById(
|
|
170
|
+
'\0virtual:boltdocs-mdx-components',
|
|
171
|
+
)
|
|
172
|
+
if (mod) server.moduleGraph.invalidateModule(mod)
|
|
173
|
+
server.ws.send({ type: 'full-reload' })
|
|
174
|
+
return
|
|
175
|
+
}
|
|
160
176
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
177
|
+
// If layout.tsx/jsx file changes, invalidate the virtual module
|
|
178
|
+
if (
|
|
179
|
+
compExtensions.some((ext) => normalized.endsWith(`layout.${ext}`))
|
|
180
|
+
) {
|
|
181
|
+
const mod = server.moduleGraph.getModuleById(
|
|
182
|
+
'\0virtual:boltdocs-layout',
|
|
183
|
+
)
|
|
184
|
+
if (mod) server.moduleGraph.invalidateModule(mod)
|
|
185
|
+
server.ws.send({ type: 'full-reload' })
|
|
186
|
+
return
|
|
187
|
+
}
|
|
166
188
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
189
|
+
if (
|
|
190
|
+
!normalized.startsWith(normalizedDocsDir) ||
|
|
191
|
+
!isDocFile(normalized)
|
|
192
|
+
)
|
|
193
|
+
return
|
|
173
194
|
|
|
174
|
-
|
|
175
|
-
|
|
195
|
+
// Invalidate appropriately
|
|
196
|
+
if (type === 'add' || type === 'unlink') {
|
|
197
|
+
invalidateRouteCache()
|
|
198
|
+
// Re-resolve config as it might affect versions/routes
|
|
199
|
+
config = await resolveConfig(docsDir)
|
|
200
|
+
|
|
201
|
+
const configMod = server.moduleGraph.getModuleById(
|
|
202
|
+
'\0virtual:boltdocs-config',
|
|
203
|
+
)
|
|
204
|
+
if (configMod) server.moduleGraph.invalidateModule(configMod)
|
|
205
|
+
|
|
206
|
+
server.ws.send({
|
|
207
|
+
type: 'custom',
|
|
208
|
+
event: 'boltdocs:config-update',
|
|
209
|
+
data: {
|
|
210
|
+
theme: config?.theme,
|
|
211
|
+
integrations: config?.integrations,
|
|
212
|
+
i18n: config?.i18n,
|
|
213
|
+
versions: config?.versions,
|
|
214
|
+
siteUrl: config?.siteUrl,
|
|
215
|
+
},
|
|
216
|
+
})
|
|
217
|
+
} else {
|
|
218
|
+
invalidateFile(file)
|
|
219
|
+
}
|
|
176
220
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
221
|
+
// Regenerate and push to client
|
|
222
|
+
// Optimization: generateRoutes is mostly incremental thanks to docCache
|
|
223
|
+
// We only force a full disk scan on add/unlink events
|
|
224
|
+
const newRoutes = await generateRoutes(
|
|
225
|
+
docsDir,
|
|
226
|
+
config,
|
|
227
|
+
'/docs',
|
|
228
|
+
type !== 'change',
|
|
229
|
+
)
|
|
181
230
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
231
|
+
const routesMod = server.moduleGraph.getModuleById(
|
|
232
|
+
'\0virtual:boltdocs-routes',
|
|
233
|
+
)
|
|
234
|
+
if (routesMod) server.moduleGraph.invalidateModule(routesMod)
|
|
235
|
+
|
|
236
|
+
server.ws.send({
|
|
237
|
+
type: 'custom',
|
|
238
|
+
event: 'boltdocs:routes-update',
|
|
239
|
+
data: newRoutes,
|
|
240
|
+
})
|
|
241
|
+
} catch (e) {
|
|
242
|
+
console.error(`[boltdocs] HMR error during ${type} event:`, e)
|
|
243
|
+
}
|
|
187
244
|
}
|
|
188
245
|
|
|
189
246
|
server.watcher.on('add', (f) => handleFileEvent(f, 'add'))
|
|
@@ -197,7 +254,8 @@ export function boltdocsPlugin(
|
|
|
197
254
|
id === 'virtual:boltdocs-config' ||
|
|
198
255
|
id === 'virtual:boltdocs-entry' ||
|
|
199
256
|
id === 'virtual:boltdocs-mdx-components' ||
|
|
200
|
-
id === 'virtual:boltdocs-layout'
|
|
257
|
+
id === 'virtual:boltdocs-layout' ||
|
|
258
|
+
id === 'virtual:boltdocs-search'
|
|
201
259
|
) {
|
|
202
260
|
return '\0' + id
|
|
203
261
|
}
|
|
@@ -211,7 +269,6 @@ export function boltdocsPlugin(
|
|
|
211
269
|
if (id === '\0virtual:boltdocs-config') {
|
|
212
270
|
const clientConfig = {
|
|
213
271
|
theme: config?.theme,
|
|
214
|
-
themeConfig: config?.themeConfig,
|
|
215
272
|
integrations: config?.integrations,
|
|
216
273
|
i18n: config?.i18n,
|
|
217
274
|
versions: config?.versions,
|
|
@@ -267,6 +324,12 @@ export default UserLayout;`
|
|
|
267
324
|
return `import { DefaultLayout } from 'boltdocs/client';
|
|
268
325
|
export default DefaultLayout;`
|
|
269
326
|
}
|
|
327
|
+
|
|
328
|
+
if (id === '\0virtual:boltdocs-search') {
|
|
329
|
+
const routes = await generateRoutes(docsDir, config)
|
|
330
|
+
const searchData = generateSearchData(routes)
|
|
331
|
+
return `export default ${JSON.stringify(searchData, null, 2)};`
|
|
332
|
+
}
|
|
270
333
|
},
|
|
271
334
|
|
|
272
335
|
transformIndexHtml: {
|
package/src/node/routes/index.ts
CHANGED
|
@@ -11,7 +11,8 @@ import { sortRoutes } from './sorter'
|
|
|
11
11
|
export type { RouteMeta }
|
|
12
12
|
export { invalidateRouteCache, invalidateFile }
|
|
13
13
|
|
|
14
|
-
// Cache for localized path computations
|
|
14
|
+
// Cache for file list and localized path computations
|
|
15
|
+
let cachedFileList: string[] | null = null
|
|
15
16
|
const localizedPathCache = new Map<string, string>()
|
|
16
17
|
|
|
17
18
|
/**
|
|
@@ -30,6 +31,7 @@ export async function generateRoutes(
|
|
|
30
31
|
docsDir: string,
|
|
31
32
|
config?: BoltdocsConfig,
|
|
32
33
|
basePath: string = '/docs',
|
|
34
|
+
forceScan: boolean = true,
|
|
33
35
|
): Promise<RouteMeta[]> {
|
|
34
36
|
const start = performance.now()
|
|
35
37
|
|
|
@@ -44,13 +46,19 @@ export async function generateRoutes(
|
|
|
44
46
|
docCache.invalidateAll()
|
|
45
47
|
}
|
|
46
48
|
|
|
47
|
-
// 1. FAST SCAN
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
// 1. FAST SCAN (Skip if incremental and we have a cache)
|
|
50
|
+
let files: string[]
|
|
51
|
+
if (!forceScan && cachedFileList) {
|
|
52
|
+
files = cachedFileList
|
|
53
|
+
} else {
|
|
54
|
+
files = await fastGlob(['**/*.md', '**/*.mdx'], {
|
|
55
|
+
cwd: docsDir,
|
|
56
|
+
absolute: true,
|
|
57
|
+
suppressErrors: true,
|
|
58
|
+
followSymbolicLinks: false,
|
|
59
|
+
})
|
|
60
|
+
cachedFileList = files
|
|
61
|
+
}
|
|
54
62
|
|
|
55
63
|
// Prune cache entries for deleted files
|
|
56
64
|
docCache.pruneStale(new Set(files))
|
|
@@ -197,8 +205,6 @@ function generateI18nFallbacks(
|
|
|
197
205
|
}
|
|
198
206
|
|
|
199
207
|
for (const locale of allLocales) {
|
|
200
|
-
if (locale === defaultLocale) continue
|
|
201
|
-
|
|
202
208
|
const localePaths = routesByLocale.get(locale) || new Set<string>()
|
|
203
209
|
|
|
204
210
|
for (const defRoute of defaultRoutes) {
|
|
@@ -207,8 +213,12 @@ function generateI18nFallbacks(
|
|
|
207
213
|
defaultLocale,
|
|
208
214
|
locale,
|
|
209
215
|
basePath,
|
|
216
|
+
config,
|
|
210
217
|
)
|
|
211
218
|
|
|
219
|
+
// Skip if the path is already the same (e.g. for default locale unprefixed)
|
|
220
|
+
if (targetPath === defRoute.path) continue
|
|
221
|
+
|
|
212
222
|
if (!localePaths.has(targetPath)) {
|
|
213
223
|
fallbackRoutes.push({
|
|
214
224
|
...defRoute,
|
|
@@ -231,15 +241,26 @@ function computeLocalizedPath(
|
|
|
231
241
|
defaultLocale: string,
|
|
232
242
|
targetLocale: string,
|
|
233
243
|
basePath: string,
|
|
244
|
+
config?: BoltdocsConfig,
|
|
234
245
|
): string {
|
|
235
246
|
const cacheKey = `${path}:${targetLocale}`
|
|
236
247
|
const cached = localizedPathCache.get(cacheKey)
|
|
237
248
|
if (cached) return cached
|
|
238
249
|
|
|
239
250
|
let prefix = basePath
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
251
|
+
if (config?.versions) {
|
|
252
|
+
const vPrefix = config.versions.prefix || ''
|
|
253
|
+
for (const vConfig of config.versions.versions) {
|
|
254
|
+
const fullVPath = vPrefix + vConfig.path
|
|
255
|
+
if (path.startsWith(`${basePath}/${fullVPath}`)) {
|
|
256
|
+
prefix += '/' + fullVPath
|
|
257
|
+
break
|
|
258
|
+
}
|
|
259
|
+
if (path.startsWith(`${basePath}/${vConfig.path}`)) {
|
|
260
|
+
prefix += '/' + vConfig.path
|
|
261
|
+
break
|
|
262
|
+
}
|
|
263
|
+
}
|
|
243
264
|
}
|
|
244
265
|
|
|
245
266
|
let pathAfterVersion = path.substring(prefix.length)
|
|
@@ -59,8 +59,15 @@ export function parseDocFile(
|
|
|
59
59
|
// Level 1: Check for version
|
|
60
60
|
if (config?.versions && parts.length > 0) {
|
|
61
61
|
const potentialVersion = parts[0]
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
const prefix = config.versions.prefix || ''
|
|
63
|
+
|
|
64
|
+
const versionMatch = config.versions.versions.find((v) => {
|
|
65
|
+
const fullPath = prefix + v.path
|
|
66
|
+
return potentialVersion === fullPath || potentialVersion === v.path
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
if (versionMatch) {
|
|
70
|
+
version = versionMatch.path
|
|
64
71
|
parts = parts.slice(1)
|
|
65
72
|
}
|
|
66
73
|
}
|
|
@@ -124,8 +131,7 @@ export function parseDocFile(
|
|
|
124
131
|
const headings: { level: number; text: string; id: string }[] = []
|
|
125
132
|
const headingsRegex = /^(#{2,4})\s+(.+)$/gm
|
|
126
133
|
|
|
127
|
-
|
|
128
|
-
while ((match = headingsRegex.exec(content)) !== null) {
|
|
134
|
+
for (const match of content.matchAll(headingsRegex)) {
|
|
129
135
|
const level = match[1].length
|
|
130
136
|
const rawText = match[2]
|
|
131
137
|
.replace(/\[([^\]]+)\]\([^\)]+\)/g, '$1') // Strip markdown links
|
|
@@ -160,7 +166,9 @@ export function parseDocFile(
|
|
|
160
166
|
sanitizedDescription = plainExcerpt
|
|
161
167
|
}
|
|
162
168
|
|
|
163
|
-
const sanitizedBadge = data.badge
|
|
169
|
+
const sanitizedBadge = data.badge
|
|
170
|
+
? sanitizeHtml(String(data.badge))
|
|
171
|
+
: undefined
|
|
164
172
|
const icon = data.icon ? String(data.icon) : undefined
|
|
165
173
|
|
|
166
174
|
// Extract full content as plain text for search indexing
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { RouteMeta } from '../routes/types'
|
|
2
|
+
|
|
3
|
+
export interface SearchDocument {
|
|
4
|
+
id: string
|
|
5
|
+
title: string
|
|
6
|
+
content: string
|
|
7
|
+
url: string
|
|
8
|
+
display: string
|
|
9
|
+
locale?: string
|
|
10
|
+
version?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Generates a flat list of searchable documents from the route metadata.
|
|
15
|
+
* Each page is indexed as a primary document, and its sections (headings)
|
|
16
|
+
* are indexed as secondary documents to provide granular search results.
|
|
17
|
+
*/
|
|
18
|
+
export function generateSearchData(routes: RouteMeta[]): SearchDocument[] {
|
|
19
|
+
const documents: SearchDocument[] = []
|
|
20
|
+
|
|
21
|
+
for (const route of routes) {
|
|
22
|
+
// 1. Index the main page
|
|
23
|
+
documents.push({
|
|
24
|
+
id: route.path,
|
|
25
|
+
title: route.title,
|
|
26
|
+
content: route._content || '',
|
|
27
|
+
url: route.path,
|
|
28
|
+
display: route.groupTitle
|
|
29
|
+
? `${route.groupTitle} > ${route.title}`
|
|
30
|
+
: route.title,
|
|
31
|
+
locale: route.locale,
|
|
32
|
+
version: route.version,
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
// 2. Index headings as sub-documents for deep linking
|
|
36
|
+
if (route.headings) {
|
|
37
|
+
for (const heading of route.headings) {
|
|
38
|
+
// We find the content belonging to this heading?
|
|
39
|
+
// For now, indexing just the heading text and a bit of context is standard.
|
|
40
|
+
// Deep full-text mapping to specific headings is more complex.
|
|
41
|
+
documents.push({
|
|
42
|
+
id: `${route.path}#${heading.id}`,
|
|
43
|
+
title: heading.text,
|
|
44
|
+
content: `${heading.text} in ${route.title}`,
|
|
45
|
+
url: `${route.path}#${heading.id}`,
|
|
46
|
+
display: `${route.title} > ${heading.text}`,
|
|
47
|
+
locale: route.locale,
|
|
48
|
+
version: route.version,
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return documents
|
|
55
|
+
}
|
package/src/node/ssg/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'fs'
|
|
2
2
|
import path from 'path'
|
|
3
3
|
import { generateRoutes } from '../routes'
|
|
4
|
-
import { escapeHtml } from '../utils'
|
|
4
|
+
import { escapeHtml, getTranslated } from '../utils'
|
|
5
5
|
import { fileURLToPath } from 'url'
|
|
6
6
|
import { createRequire } from 'module'
|
|
7
7
|
|
|
@@ -27,8 +27,6 @@ const _require = createRequire(import.meta.url)
|
|
|
27
27
|
export async function generateStaticPages(options: SSGOptions): Promise<void> {
|
|
28
28
|
const { docsDir, docsDirName, outDir, config } = options
|
|
29
29
|
const routes = await generateRoutes(docsDir, config)
|
|
30
|
-
const siteTitle = config?.themeConfig?.title || 'Boltdocs'
|
|
31
|
-
const siteDescription = config?.themeConfig?.description || ''
|
|
32
30
|
|
|
33
31
|
// Resolve the SSR module (compiled by tsup)
|
|
34
32
|
const ssrModulePath = path.resolve(_dirname, '../client/ssr.js')
|
|
@@ -50,14 +48,17 @@ export async function generateStaticPages(options: SSGOptions): Promise<void> {
|
|
|
50
48
|
__esModule: true,
|
|
51
49
|
default: function SSG_Virtual_Layout(props: any) {
|
|
52
50
|
try {
|
|
53
|
-
const client = originalRequire.apply(this, [
|
|
54
|
-
|
|
51
|
+
const client = originalRequire.apply(this, [
|
|
52
|
+
path.resolve(_dirname, '../client/index.js'),
|
|
53
|
+
])
|
|
54
|
+
const Comp =
|
|
55
|
+
client.DefaultLayout || (({ children }: any) => children)
|
|
55
56
|
const React = originalRequire.apply(this, ['react'])
|
|
56
57
|
return React.createElement(Comp, props)
|
|
57
58
|
} catch (e) {
|
|
58
59
|
return props.children
|
|
59
60
|
}
|
|
60
|
-
}
|
|
61
|
+
},
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
64
|
return originalRequire.apply(this, [id, ...args])
|
|
@@ -79,6 +80,10 @@ export async function generateStaticPages(options: SSGOptions): Promise<void> {
|
|
|
79
80
|
// Generate an HTML file for each route concurrently
|
|
80
81
|
await Promise.all(
|
|
81
82
|
routes.map(async (route) => {
|
|
83
|
+
const siteTitle =
|
|
84
|
+
getTranslated(config?.theme?.title, route.locale) || 'Boltdocs'
|
|
85
|
+
const siteDescription =
|
|
86
|
+
getTranslated(config?.theme?.description, route.locale) || ''
|
|
82
87
|
const pageTitle = `${route.title} | ${siteTitle}`
|
|
83
88
|
const pageDescription = route.description || siteDescription
|
|
84
89
|
|
|
@@ -111,7 +116,10 @@ export async function generateStaticPages(options: SSGOptions): Promise<void> {
|
|
|
111
116
|
'utf-8',
|
|
112
117
|
)
|
|
113
118
|
} catch (e: any) {
|
|
114
|
-
console.error(
|
|
119
|
+
console.error(
|
|
120
|
+
`[boltdocs] Error SSR rendering route ${route.path}:`,
|
|
121
|
+
e ? e.stack || e : e,
|
|
122
|
+
)
|
|
115
123
|
}
|
|
116
124
|
}),
|
|
117
125
|
)
|
package/src/node/ssg/robots.ts
CHANGED
|
@@ -19,20 +19,23 @@ export function generateRobotsTxt(config: BoltdocsConfig): string {
|
|
|
19
19
|
allow: '/',
|
|
20
20
|
},
|
|
21
21
|
]
|
|
22
|
-
const sitemaps =
|
|
22
|
+
const sitemaps =
|
|
23
|
+
(robots as any).sitemaps || (siteUrl ? [`${siteUrl}/sitemap.xml`] : [])
|
|
23
24
|
|
|
24
25
|
let content = ''
|
|
25
26
|
|
|
26
27
|
for (const rule of rules) {
|
|
27
28
|
content += `User-agent: ${rule.userAgent}\n`
|
|
28
|
-
|
|
29
|
+
|
|
29
30
|
if (rule.disallow) {
|
|
30
|
-
const disallows = Array.isArray(rule.disallow)
|
|
31
|
+
const disallows = Array.isArray(rule.disallow)
|
|
32
|
+
? rule.disallow
|
|
33
|
+
: [rule.disallow]
|
|
31
34
|
for (const d of disallows) {
|
|
32
35
|
content += `Disallow: ${d}\n`
|
|
33
36
|
}
|
|
34
37
|
}
|
|
35
|
-
|
|
38
|
+
|
|
36
39
|
if (rule.allow) {
|
|
37
40
|
const allows = Array.isArray(rule.allow) ? rule.allow : [rule.allow]
|
|
38
41
|
for (const a of allows) {
|
package/src/node/utils.ts
CHANGED
|
@@ -72,8 +72,13 @@ export function parseFrontmatter(filePath: string): {
|
|
|
72
72
|
content: string
|
|
73
73
|
} {
|
|
74
74
|
const raw = fs.readFileSync(filePath, 'utf-8')
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
try {
|
|
76
|
+
const { data, content } = matter(raw)
|
|
77
|
+
return { data, content }
|
|
78
|
+
} catch (e) {
|
|
79
|
+
// If frontmatter is malformed (e.g. while editing), return empty data and raw content
|
|
80
|
+
return { data: {}, content: raw }
|
|
81
|
+
}
|
|
77
82
|
}
|
|
78
83
|
|
|
79
84
|
/**
|
|
@@ -169,3 +174,28 @@ export function stripHtmlTags(html: string): string {
|
|
|
169
174
|
export function capitalize(str: string): string {
|
|
170
175
|
return str.charAt(0).toUpperCase() + str.slice(1)
|
|
171
176
|
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Retrieves the correct translation from a value that can be either
|
|
180
|
+
* a simple string or a map of locale-specific strings.
|
|
181
|
+
* Node-side version for SSG and config resolution.
|
|
182
|
+
*
|
|
183
|
+
* @param value - The text to translate
|
|
184
|
+
* @param locale - The current active locale (e.g., 'en', 'es')
|
|
185
|
+
* @returns The translated string
|
|
186
|
+
*/
|
|
187
|
+
export function getTranslated(
|
|
188
|
+
value: string | Record<string, string> | undefined,
|
|
189
|
+
locale?: string,
|
|
190
|
+
): string {
|
|
191
|
+
if (!value) return ''
|
|
192
|
+
if (typeof value === 'string') return value
|
|
193
|
+
|
|
194
|
+
if (locale && value[locale]) {
|
|
195
|
+
return value[locale]
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Fallback: Use the first available translation or an empty string
|
|
199
|
+
const firstValue = Object.values(value)[0]
|
|
200
|
+
return firstValue || ''
|
|
201
|
+
}
|
package/tsup.config.ts
CHANGED
|
@@ -20,6 +20,7 @@ const commonConfig: Options = {
|
|
|
20
20
|
'virtual:boltdocs-layout',
|
|
21
21
|
'virtual:boltdocs-mdx-components',
|
|
22
22
|
'virtual:boltdocs-entry',
|
|
23
|
+
/^virtual:boltdocs-/, // Broad catch-all for any virtual modules
|
|
23
24
|
],
|
|
24
25
|
}
|
|
25
26
|
|
|
@@ -41,8 +42,12 @@ export default defineConfig([
|
|
|
41
42
|
platform: 'browser',
|
|
42
43
|
entry: {
|
|
43
44
|
'client/index': 'src/client/index.ts',
|
|
44
|
-
'client/
|
|
45
|
-
'
|
|
45
|
+
'client/types': 'src/client/types.ts',
|
|
46
|
+
'hooks/index': 'src/client/hooks/index.ts',
|
|
47
|
+
'primitives/index': 'src/client/components/primitives/index.ts',
|
|
48
|
+
'base-ui/index': 'src/client/components/ui-base/index.ts',
|
|
49
|
+
'mdx/index': 'src/client/components/mdx/index.ts',
|
|
50
|
+
'integrations/index': 'src/client/integrations/index.ts',
|
|
46
51
|
},
|
|
47
52
|
},
|
|
48
53
|
// SSR Build (Needs Node platform for Server-Side Rendering)
|
package/dist/chunk-52MVMZWS.mjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
var v=Object.defineProperty;var S=(t,i)=>{for(var e in i)v(t,e,{get:i[e],enumerable:!0})};import{createContext as x,use as b}from"react";var y=x(null);function P(){let t=b(y);if(!t)throw new Error("useConfig must be used within a ConfigProvider");return t}import{useLocation as R}from"react-router-dom";function z(t){let i=R(),e=P();if(!e||typeof t!="string"||!e.i18n&&!e.versions)return t;let n="/docs";if(!t.startsWith(n))return t;let r=i.pathname.substring(n.length).split("/").filter(Boolean),u=e.versions?.defaultVersion,c=e.i18n?.defaultLocale,o=0;e.versions&&r.length>o&&e.versions.versions[r[o]]&&(u=r[o],o++),e.i18n&&r.length>o&&e.i18n.locales[r[o]]&&(c=r[o]);let s=t.substring(n.length).split("/").filter(Boolean),l=0,d=!1,m=!1;e.versions&&s.length>l&&e.versions.versions[s[l]]&&(d=!0,l++),e.i18n&&s.length>l&&e.i18n.locales[s[l]]&&(m=!0,l++);let C=s.slice(l),a=[];e.versions&&(d?a.push(s[0]):u&&a.push(u)),e.i18n&&(m?a.push(s[d?1:0]):c&&a.push(c)),a.push(...C);let f=`${n}/${a.join("/")}`;return f.endsWith("/")&&(f=f.slice(0,-1)),f===n?n:f}import{createContext as L,use as T,useCallback as k,useRef as w}from"react";import{jsx as B}from"react/jsx-runtime";var g=L({preload:()=>{},routes:[]});function K(){return T(g)}function N({routes:t,modules:i,children:e}){let n=w(null),h=k(r=>{n.current&&clearTimeout(n.current),n.current=setTimeout(()=>{let u=r.split("#")[0].split("?")[0],c=t.find(o=>o.path===u||u==="/"&&o.path==="");if(c?.filePath){let o=Object.keys(i).find(p=>p.endsWith("/"+c.filePath));o&&i[o]().catch(p=>{console.error(`[boltdocs] Failed to preload route ${r}:`,p)})}},100)},[t,i]);return B(g.Provider,{value:{preload:h,routes:t},children:e})}export{S as a,y as b,P as c,z as d,K as e,N as f};
|