boltdocs 2.1.1 → 2.2.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 +9 -0
- 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-CRAZ55X7.mjs} +1 -1
- package/dist/chunk-5D6XPYQ3.mjs +74 -0
- package/dist/chunk-6QXCKZAT.mjs +1 -0
- package/dist/chunk-H4M6P3DM.mjs +1 -0
- package/dist/chunk-JD3RSDE4.mjs +1 -0
- package/dist/chunk-JXHNX2WN.mjs +1 -0
- package/dist/chunk-JZXLCA2E.mjs +1 -0
- package/dist/chunk-MZBG4N4W.mjs +1 -0
- package/dist/chunk-NBCYHLAA.mjs +1 -0
- package/dist/chunk-Q3MLYTIQ.mjs +1 -0
- package/dist/chunk-RSII2UPE.mjs +1 -0
- package/dist/chunk-T3W44KWY.mjs +1 -0
- package/dist/chunk-ZK2266IZ.mjs +1 -0
- package/dist/chunk-ZRJ55GGF.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 +25 -12
- package/dist/{client/hooks → hooks}/index.d.ts +25 -12
- 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-BqGrFWO5.d.mts +68 -0
- package/dist/loading-chS3pm9W.d.ts +68 -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 +27 -27
- package/dist/node/cli-entry.mjs +1 -1
- package/dist/node/index.d.mts +44 -14
- package/dist/node/index.d.ts +44 -14
- package/dist/node/index.js +23 -23
- 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-MA5AISC7.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 +34 -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 +40 -23
- package/src/client/components/default-layout.tsx +12 -6
- package/src/client/components/primitives/breadcrumbs.tsx +1 -1
- package/src/client/components/primitives/navbar.tsx +5 -2
- package/src/client/components/primitives/search-dialog.tsx +12 -3
- 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/navbar.tsx +66 -33
- package/src/client/components/ui-base/powered-by.tsx +8 -5
- package/src/client/components/ui-base/sidebar.tsx +31 -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 +37 -7
- package/src/client/hooks/use-localized-to.ts +45 -68
- package/src/client/hooks/use-navbar.ts +10 -3
- package/src/client/hooks/use-routes.ts +61 -17
- package/src/client/hooks/use-search.ts +21 -5
- package/src/client/hooks/use-sidebar.ts +5 -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 +43 -0
- package/src/client/types.ts +4 -2
- package/src/client/utils/i18n.ts +23 -0
- 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/html.ts +8 -4
- package/src/node/plugin/index.ts +117 -68
- package/src/node/routes/index.ts +34 -13
- package/src/node/routes/parser.ts +12 -4
- package/src/node/ssg/index.ts +3 -3
- 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
|
@@ -1,95 +1,72 @@
|
|
|
1
|
-
import { useLocation } from 'react-router-dom'
|
|
2
1
|
import { useConfig } from '@client/app/config-context'
|
|
3
2
|
import type { LinkProps as RouterLinkProps } from 'react-router-dom'
|
|
3
|
+
import { useRoutes } from './use-routes'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Hook to automatically localize a path based on the current version and locale context.
|
|
7
|
-
* It ensures that navigation
|
|
7
|
+
* It ensures that navigation preserves the active version and language across the entire site.
|
|
8
8
|
*/
|
|
9
9
|
export function useLocalizedTo(to: RouterLinkProps['to']) {
|
|
10
|
-
const location = useLocation()
|
|
11
10
|
const config = useConfig()
|
|
11
|
+
const { currentLocale: activeLocale, currentVersion: activeVersion } = useRoutes()
|
|
12
12
|
|
|
13
13
|
if (!config || typeof to !== 'string') return to
|
|
14
|
-
|
|
14
|
+
|
|
15
|
+
// External or absolute links don't need localization
|
|
16
|
+
if (to.startsWith('http') || to.startsWith('//')) return to
|
|
15
17
|
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
+
const i18n = config.i18n
|
|
19
|
+
const versions = config.versions
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
const curSub = location.pathname.substring(basePath.length)
|
|
21
|
-
const curParts = curSub.split('/').filter(Boolean)
|
|
21
|
+
if (!i18n && !versions) return to
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
// 1. Identify the input intent
|
|
24
|
+
const isDocLink = to.startsWith('/docs')
|
|
25
|
+
|
|
26
|
+
// 3. Clean the 'to' path of ANY existing prefixes to avoid stacking
|
|
27
|
+
const parts = to.split('/').filter(Boolean)
|
|
28
|
+
let pIdx = 0
|
|
25
29
|
|
|
26
|
-
|
|
27
|
-
if (
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
cIdx++
|
|
34
|
-
}
|
|
35
|
-
if (
|
|
36
|
-
config.i18n &&
|
|
37
|
-
curParts.length > cIdx &&
|
|
38
|
-
config.i18n.locales[curParts[cIdx]]
|
|
39
|
-
) {
|
|
40
|
-
currentLocale = curParts[cIdx]
|
|
30
|
+
// Strip '/docs' if present at start
|
|
31
|
+
if (parts[pIdx] === 'docs') pIdx++
|
|
32
|
+
|
|
33
|
+
// Strip versions if present
|
|
34
|
+
if (versions && parts.length > pIdx) {
|
|
35
|
+
const vMatch = versions.versions.find(v => v.path === parts[pIdx])
|
|
36
|
+
if (vMatch) pIdx++
|
|
41
37
|
}
|
|
42
38
|
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
const toParts = toSub.split('/').filter(Boolean)
|
|
39
|
+
// Strip locales if present
|
|
40
|
+
if (i18n && parts.length > pIdx && i18n.locales[parts[pIdx]]) pIdx++
|
|
46
41
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
let hasLocale = false
|
|
42
|
+
// The actual relative route remaining
|
|
43
|
+
const routeContent = parts.slice(pIdx)
|
|
50
44
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
toParts.length > tIdx &&
|
|
54
|
-
config.versions.versions[toParts[tIdx]]
|
|
55
|
-
) {
|
|
56
|
-
hasVersion = true
|
|
57
|
-
tIdx++
|
|
58
|
-
}
|
|
59
|
-
if (
|
|
60
|
-
config.i18n &&
|
|
61
|
-
toParts.length > tIdx &&
|
|
62
|
-
config.i18n.locales[toParts[tIdx]]
|
|
63
|
-
) {
|
|
64
|
-
hasLocale = true
|
|
65
|
-
tIdx++
|
|
66
|
-
}
|
|
45
|
+
// 4. Reconstruct strictly from base
|
|
46
|
+
const resultParts: string[] = []
|
|
67
47
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const finalParts = []
|
|
73
|
-
if (config.versions) {
|
|
74
|
-
if (hasVersion) {
|
|
75
|
-
finalParts.push(toParts[0])
|
|
76
|
-
} else if (currentVersion) {
|
|
77
|
-
finalParts.push(currentVersion)
|
|
48
|
+
if (isDocLink) {
|
|
49
|
+
resultParts.push('docs')
|
|
50
|
+
if (versions && activeVersion) {
|
|
51
|
+
resultParts.push(activeVersion)
|
|
78
52
|
}
|
|
79
53
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
54
|
+
|
|
55
|
+
if (i18n && activeLocale) {
|
|
56
|
+
// Only prefix if it's NOT the default locale (cleaner URLs)
|
|
57
|
+
if (activeLocale !== i18n.defaultLocale) {
|
|
58
|
+
resultParts.push(activeLocale)
|
|
85
59
|
}
|
|
86
60
|
}
|
|
87
61
|
|
|
88
|
-
|
|
62
|
+
resultParts.push(...routeContent)
|
|
89
63
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
64
|
+
const finalPath = `/${resultParts.join('/')}`
|
|
65
|
+
|
|
66
|
+
// Cleanup trailing slashes unless it's just root
|
|
67
|
+
if (finalPath.length > 1 && finalPath.endsWith('/')) {
|
|
68
|
+
return finalPath.slice(0, -1)
|
|
93
69
|
}
|
|
94
|
-
|
|
70
|
+
|
|
71
|
+
return finalPath || '/'
|
|
95
72
|
}
|
|
@@ -2,14 +2,17 @@ import { useLocation } from 'react-router-dom'
|
|
|
2
2
|
import { useConfig } from '@client/app/config-context'
|
|
3
3
|
import { useTheme } from '@client/app/theme-context'
|
|
4
4
|
import type { NavbarLink } from '@client/types'
|
|
5
|
+
import { getTranslated } from '@client/utils/i18n'
|
|
6
|
+
import { useRoutes } from './use-routes'
|
|
5
7
|
|
|
6
8
|
export function useNavbar() {
|
|
7
9
|
const config = useConfig()
|
|
8
10
|
const { theme } = useTheme()
|
|
9
11
|
const location = useLocation()
|
|
12
|
+
const { currentLocale } = useRoutes()
|
|
10
13
|
|
|
11
|
-
const themeConfig = config.theme ||
|
|
12
|
-
const title = themeConfig.title || 'Boltdocs'
|
|
14
|
+
const themeConfig = config.theme || {}
|
|
15
|
+
const title = getTranslated(themeConfig.title, currentLocale) || 'Boltdocs'
|
|
13
16
|
const rawLinks = themeConfig.navbar || []
|
|
14
17
|
const socialLinks = themeConfig.socialLinks || []
|
|
15
18
|
const githubRepo = themeConfig.githubRepo
|
|
@@ -18,13 +21,17 @@ export function useNavbar() {
|
|
|
18
21
|
const links: NavbarLink[] = rawLinks.map((item: any) => {
|
|
19
22
|
const href = item.href || item.to || item.link || ''
|
|
20
23
|
return {
|
|
21
|
-
label: item.label || item.text
|
|
24
|
+
label: getTranslated(item.label || item.text, currentLocale),
|
|
22
25
|
href,
|
|
23
26
|
active: location.pathname === href,
|
|
24
27
|
to:
|
|
25
28
|
href.startsWith('http') || href.startsWith('//')
|
|
26
29
|
? 'external'
|
|
27
30
|
: undefined,
|
|
31
|
+
items: item.items?.map((sub: any) => ({
|
|
32
|
+
label: getTranslated(sub.label || sub.text, currentLocale),
|
|
33
|
+
href: sub.href || sub.link || sub.to || '',
|
|
34
|
+
})),
|
|
28
35
|
}
|
|
29
36
|
})
|
|
30
37
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useLocation } from 'react-router-dom'
|
|
2
2
|
import { useConfig } from '@client/app/config-context'
|
|
3
3
|
import { usePreload } from '@client/app/preload'
|
|
4
|
+
import { useBoltdocsStore } from '../store/use-boltdocs-store'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Hook to access the framework's routing state.
|
|
@@ -12,16 +13,26 @@ export function useRoutes() {
|
|
|
12
13
|
const config = useConfig()
|
|
13
14
|
const location = useLocation()
|
|
14
15
|
|
|
15
|
-
//
|
|
16
|
+
// Use Zustand store for active state
|
|
17
|
+
const currentLocaleStore = useBoltdocsStore((s) => s.currentLocale)
|
|
18
|
+
const currentVersionStore = useBoltdocsStore((s) => s.currentVersion)
|
|
19
|
+
const hasHydrated = useBoltdocsStore((s) => s.hasHydrated)
|
|
20
|
+
|
|
21
|
+
// Find the current route matching the pathname
|
|
16
22
|
const currentRoute = allRoutes.find((r) => r.path === location.pathname)
|
|
17
23
|
|
|
18
|
-
// Derive current locale and version
|
|
24
|
+
// Derive current locale and version
|
|
25
|
+
// Priority: URL (currentRoute) > Zustand Store (Persistence) > Config Default
|
|
19
26
|
const currentLocale = config.i18n
|
|
20
|
-
? currentRoute?.locale ||
|
|
27
|
+
? currentRoute?.locale ||
|
|
28
|
+
(hasHydrated ? currentLocaleStore : undefined) ||
|
|
29
|
+
config.i18n.defaultLocale
|
|
21
30
|
: undefined
|
|
22
31
|
|
|
23
32
|
const currentVersion = config.versions
|
|
24
|
-
? currentRoute?.version ||
|
|
33
|
+
? currentRoute?.version ||
|
|
34
|
+
(hasHydrated ? currentVersionStore : undefined) ||
|
|
35
|
+
config.versions.defaultVersion
|
|
25
36
|
: undefined
|
|
26
37
|
|
|
27
38
|
// Filter routes to those matching the current version and locale
|
|
@@ -32,28 +43,61 @@ export function useRoutes() {
|
|
|
32
43
|
const versionMatch = config.versions
|
|
33
44
|
? (r.version || config.versions.defaultVersion) === currentVersion
|
|
34
45
|
: true
|
|
35
|
-
|
|
46
|
+
|
|
47
|
+
if (!(localeMatch && versionMatch)) return false
|
|
48
|
+
|
|
49
|
+
// Resolve duplicate paths (aliases) like /docs vs /docs/en
|
|
50
|
+
// We prefer the version that matches the current route's prefix style
|
|
51
|
+
const i18n = config.i18n
|
|
52
|
+
if (i18n) {
|
|
53
|
+
const isCurrentRoutePrefixed = !!currentRoute?.locale
|
|
54
|
+
const isRoutePrefixed = !!r.locale
|
|
55
|
+
|
|
56
|
+
const hasAlternate = allRoutes.some(
|
|
57
|
+
(alt) =>
|
|
58
|
+
alt !== r &&
|
|
59
|
+
alt.filePath === r.filePath &&
|
|
60
|
+
alt.version === r.version &&
|
|
61
|
+
(alt.locale || i18n.defaultLocale) ===
|
|
62
|
+
(r.locale || i18n.defaultLocale),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if (hasAlternate && isCurrentRoutePrefixed !== isRoutePrefixed) {
|
|
66
|
+
return false
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return true
|
|
36
71
|
})
|
|
37
72
|
|
|
38
73
|
// Labels and lists for UI convenience
|
|
74
|
+
const currentLocaleConfig = config.i18n?.localeConfigs?.[currentLocale as string]
|
|
39
75
|
const currentLocaleLabel =
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
76
|
+
currentLocaleConfig?.label ||
|
|
77
|
+
config.i18n?.locales[currentLocale as string] ||
|
|
78
|
+
currentLocale
|
|
79
|
+
|
|
80
|
+
const currentVersionConfig = config.versions?.versions.find(
|
|
81
|
+
(v) => v.path === currentVersion,
|
|
82
|
+
)
|
|
83
|
+
const currentVersionLabel = currentVersionConfig?.label || currentVersion
|
|
43
84
|
|
|
44
85
|
const availableLocales = config.i18n
|
|
45
|
-
? Object.entries(config.i18n.locales).map(([key,
|
|
46
|
-
key
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
86
|
+
? Object.entries(config.i18n.locales).map(([key, defaultLabel]) => {
|
|
87
|
+
const localeConfig = config.i18n?.localeConfigs?.[key]
|
|
88
|
+
return {
|
|
89
|
+
key,
|
|
90
|
+
label: localeConfig?.label || defaultLabel,
|
|
91
|
+
isCurrent: key === currentLocale,
|
|
92
|
+
}
|
|
93
|
+
})
|
|
50
94
|
: []
|
|
51
95
|
|
|
52
96
|
const availableVersions = config.versions
|
|
53
|
-
?
|
|
54
|
-
key,
|
|
55
|
-
label,
|
|
56
|
-
isCurrent:
|
|
97
|
+
? config.versions.versions.map((v) => ({
|
|
98
|
+
key: v.path,
|
|
99
|
+
label: v.label,
|
|
100
|
+
isCurrent: v.path === currentVersion,
|
|
57
101
|
}))
|
|
58
102
|
: []
|
|
59
103
|
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import { useState, useMemo } from 'react'
|
|
2
|
+
import { useRoutes } from './use-routes'
|
|
2
3
|
import type { ComponentRoute } from '@client/types'
|
|
3
4
|
|
|
4
5
|
export function useSearch(routes: ComponentRoute[]) {
|
|
6
|
+
const { currentLocale, currentVersion } = useRoutes()
|
|
5
7
|
const [isOpen, setIsOpen] = useState(false)
|
|
6
8
|
const [query, setQuery] = useState('')
|
|
7
9
|
|
|
8
10
|
const list = useMemo(() => {
|
|
11
|
+
// 0. Filter routes by active context
|
|
12
|
+
const activeRoutes = routes.filter((r) => {
|
|
13
|
+
const localeMatch = !currentLocale || r.locale === currentLocale
|
|
14
|
+
const versionMatch = !currentVersion || r.version === currentVersion
|
|
15
|
+
return localeMatch && versionMatch
|
|
16
|
+
})
|
|
17
|
+
|
|
9
18
|
if (!query) {
|
|
10
|
-
return
|
|
19
|
+
return activeRoutes.slice(0, 10).map((r) => ({
|
|
11
20
|
id: r.path,
|
|
12
21
|
title: r.title,
|
|
13
22
|
path: r.path,
|
|
@@ -16,10 +25,17 @@ export function useSearch(routes: ComponentRoute[]) {
|
|
|
16
25
|
}))
|
|
17
26
|
}
|
|
18
27
|
|
|
19
|
-
const results:
|
|
28
|
+
const results: {
|
|
29
|
+
id: string
|
|
30
|
+
title: string | undefined
|
|
31
|
+
path: string
|
|
32
|
+
bio: string
|
|
33
|
+
groupTitle: string | undefined
|
|
34
|
+
isHeading?: boolean
|
|
35
|
+
}[] = []
|
|
20
36
|
const lowerQuery = query.toLowerCase()
|
|
21
37
|
|
|
22
|
-
for (const route of
|
|
38
|
+
for (const route of activeRoutes) {
|
|
23
39
|
if (route.title?.toLowerCase().includes(lowerQuery)) {
|
|
24
40
|
results.push({
|
|
25
41
|
id: route.path,
|
|
@@ -55,7 +71,7 @@ export function useSearch(routes: ComponentRoute[]) {
|
|
|
55
71
|
return true
|
|
56
72
|
})
|
|
57
73
|
.slice(0, 10)
|
|
58
|
-
}, [routes, query])
|
|
74
|
+
}, [routes, query, currentLocale, currentVersion])
|
|
59
75
|
|
|
60
76
|
return {
|
|
61
77
|
isOpen,
|
|
@@ -65,7 +81,7 @@ export function useSearch(routes: ComponentRoute[]) {
|
|
|
65
81
|
list,
|
|
66
82
|
input: {
|
|
67
83
|
value: query,
|
|
68
|
-
onChange: (e:
|
|
84
|
+
onChange: (e: React.ChangeEvent<HTMLInputElement>) => setQuery(e.target.value),
|
|
69
85
|
},
|
|
70
86
|
}
|
|
71
87
|
}
|
|
@@ -7,7 +7,10 @@ export function useSidebar(routes: ComponentRoute[]) {
|
|
|
7
7
|
const location = useLocation()
|
|
8
8
|
|
|
9
9
|
// Find active route and tab
|
|
10
|
-
const
|
|
10
|
+
const normalize = (p: string) => (p.endsWith('/') && p.length > 1 ? p.slice(0, -1) : p)
|
|
11
|
+
const currentPath = normalize(location.pathname)
|
|
12
|
+
|
|
13
|
+
const activeRoute = routes.find((r) => normalize(r.path) === currentPath)
|
|
11
14
|
const activeTabId = activeRoute?.tab?.toLowerCase()
|
|
12
15
|
|
|
13
16
|
// Filter routes by active tab if any
|
|
@@ -43,7 +46,7 @@ export function useSidebar(routes: ComponentRoute[]) {
|
|
|
43
46
|
groups,
|
|
44
47
|
ungrouped,
|
|
45
48
|
activeRoute,
|
|
46
|
-
activePath:
|
|
49
|
+
activePath: currentPath,
|
|
47
50
|
config,
|
|
48
51
|
}
|
|
49
52
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useNavigate } from 'react-router-dom'
|
|
2
2
|
import { getBaseFilePath } from '@client/utils/get-base-file-path'
|
|
3
3
|
import { useRoutes } from './use-routes'
|
|
4
|
+
import { useBoltdocsStore } from '../store/use-boltdocs-store'
|
|
4
5
|
|
|
5
6
|
export interface VersionOption {
|
|
6
7
|
key: string
|
|
@@ -25,10 +26,14 @@ export function useVersion(): UseVersionReturn {
|
|
|
25
26
|
const { allRoutes, currentRoute, currentVersion, currentLocale, config } =
|
|
26
27
|
routeContext
|
|
27
28
|
const versions = config.versions
|
|
29
|
+
const setVersion = useBoltdocsStore((s) => s.setVersion)
|
|
28
30
|
|
|
29
31
|
const handleVersionChange = (version: string) => {
|
|
30
32
|
if (!versions || version === currentVersion) return
|
|
31
33
|
|
|
34
|
+
// Update store
|
|
35
|
+
setVersion(version)
|
|
36
|
+
|
|
32
37
|
let targetPath = `/docs/${version}`
|
|
33
38
|
|
|
34
39
|
if (currentRoute) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './codesandbox'
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { create } from 'zustand'
|
|
2
|
+
import { persist, createJSONStorage } from 'zustand/middleware'
|
|
3
|
+
|
|
4
|
+
interface BoltdocsState {
|
|
5
|
+
currentLocale: string | undefined
|
|
6
|
+
currentVersion: string | undefined
|
|
7
|
+
hasHydrated: boolean
|
|
8
|
+
|
|
9
|
+
// Actions
|
|
10
|
+
setLocale: (locale: string | undefined) => void
|
|
11
|
+
setVersion: (version: string | undefined) => void
|
|
12
|
+
setHasHydrated: (val: boolean) => void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Global store for Boltdocs documentation state.
|
|
17
|
+
* Uses localStorage persistence to remember user preferences across sessions.
|
|
18
|
+
*/
|
|
19
|
+
export const useBoltdocsStore = create<BoltdocsState>()(
|
|
20
|
+
persist(
|
|
21
|
+
(set) => ({
|
|
22
|
+
currentLocale: undefined,
|
|
23
|
+
currentVersion: undefined,
|
|
24
|
+
hasHydrated: false,
|
|
25
|
+
|
|
26
|
+
setLocale: (locale: string | undefined) => set({ currentLocale: locale }),
|
|
27
|
+
setVersion: (version: string | undefined) => set({ currentVersion: version }),
|
|
28
|
+
setHasHydrated: (val: boolean) => set({ hasHydrated: val }),
|
|
29
|
+
}),
|
|
30
|
+
{
|
|
31
|
+
name: 'boltdocs-storage',
|
|
32
|
+
storage: createJSONStorage(() => localStorage),
|
|
33
|
+
// Only persist identifying state
|
|
34
|
+
partialize: (state: BoltdocsState) => ({
|
|
35
|
+
currentLocale: state.currentLocale,
|
|
36
|
+
currentVersion: state.currentVersion,
|
|
37
|
+
}),
|
|
38
|
+
onRehydrateStorage: () => (state?: BoltdocsState) => {
|
|
39
|
+
state?.setHasHydrated(true)
|
|
40
|
+
},
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
)
|
package/src/client/types.ts
CHANGED
|
@@ -119,7 +119,8 @@ export interface SandboxEmbedOptions {
|
|
|
119
119
|
*/
|
|
120
120
|
export interface BoltdocsTab {
|
|
121
121
|
id: string
|
|
122
|
-
|
|
122
|
+
/** Text to display (can be a string or a map of translations) */
|
|
123
|
+
text: string | Record<string, string>
|
|
123
124
|
icon?: string
|
|
124
125
|
}
|
|
125
126
|
|
|
@@ -160,7 +161,8 @@ export interface LayoutProps {
|
|
|
160
161
|
* Unified type for navbar links.
|
|
161
162
|
*/
|
|
162
163
|
export interface NavbarLink {
|
|
163
|
-
|
|
164
|
+
/** Label to display (can be a string or a map of translations) */
|
|
165
|
+
label: string | Record<string, string>
|
|
164
166
|
href: string
|
|
165
167
|
active: boolean
|
|
166
168
|
/** Optional icon or string for external link indication */
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retrieves the correct translation from a value that can be either
|
|
3
|
+
* a simple string or a map of locale-specific strings.
|
|
4
|
+
*
|
|
5
|
+
* @param value - The text to translate
|
|
6
|
+
* @param locale - The current active locale (e.g., 'en', 'es')
|
|
7
|
+
* @returns The translated string
|
|
8
|
+
*/
|
|
9
|
+
export function getTranslated(
|
|
10
|
+
value: string | Record<string, string> | undefined,
|
|
11
|
+
locale?: string,
|
|
12
|
+
): string {
|
|
13
|
+
if (!value) return ''
|
|
14
|
+
if (typeof value === 'string') return value
|
|
15
|
+
|
|
16
|
+
if (locale && value[locale]) {
|
|
17
|
+
return value[locale]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Fallback: Use the first available translation or an empty string
|
|
21
|
+
const firstValue = Object.values(value)[0]
|
|
22
|
+
return firstValue || ''
|
|
23
|
+
}
|
package/src/node/config.ts
CHANGED
|
@@ -24,10 +24,10 @@ export interface BoltdocsFooterConfig {
|
|
|
24
24
|
* Theme-specific configuration options governing the appearance and navigation of the site.
|
|
25
25
|
*/
|
|
26
26
|
export interface BoltdocsThemeConfig {
|
|
27
|
-
/** The global title of the documentation site */
|
|
28
|
-
title?: string
|
|
29
|
-
/** The global description of the site (
|
|
30
|
-
description?: string
|
|
27
|
+
/** The global title of the documentation site (can be translated) */
|
|
28
|
+
title?: string | Record<string, string>
|
|
29
|
+
/** The global description of the site (can be translated) */
|
|
30
|
+
description?: string | Record<string, string>
|
|
31
31
|
/** URL path to the site logo or an object for light/dark versions */
|
|
32
32
|
logo?:
|
|
33
33
|
| string
|
|
@@ -40,12 +40,17 @@ export interface BoltdocsThemeConfig {
|
|
|
40
40
|
}
|
|
41
41
|
/** Items to display in the top navigation bar */
|
|
42
42
|
navbar?: Array<{
|
|
43
|
-
/** Text to display */
|
|
44
|
-
label: string
|
|
43
|
+
/** Text to display (can be a string or a map of translations) */
|
|
44
|
+
label: string | Record<string, string>
|
|
45
45
|
/** URL path or external link */
|
|
46
46
|
href: string
|
|
47
47
|
/** Nested items for NavigationMenu */
|
|
48
|
-
items?: Array<{
|
|
48
|
+
items?: Array<{
|
|
49
|
+
/** Text to display (can be a string or a map of translations) */
|
|
50
|
+
label: string | Record<string, string>
|
|
51
|
+
/** URL path or external link */
|
|
52
|
+
href: string
|
|
53
|
+
}>
|
|
49
54
|
}>
|
|
50
55
|
/** Items to display in the sidebar, organized optionally by group URLs */
|
|
51
56
|
sidebar?: Record<string, Array<{ text: string; link: string }>>
|
|
@@ -78,7 +83,12 @@ export interface BoltdocsThemeConfig {
|
|
|
78
83
|
* Top-level tabs for organizing documentation groups.
|
|
79
84
|
* Tab discovery uses the (tab-id) directory syntax.
|
|
80
85
|
*/
|
|
81
|
-
tabs?: Array<{
|
|
86
|
+
tabs?: Array<{
|
|
87
|
+
id: string
|
|
88
|
+
/** Text to display (can be a string or a map of translations) */
|
|
89
|
+
text: string | Record<string, string>
|
|
90
|
+
icon?: string
|
|
91
|
+
}>
|
|
82
92
|
/**
|
|
83
93
|
* The syntax highlighting theme for code blocks.
|
|
84
94
|
* Supports any Shiki theme name (e.g., 'github-dark', 'one-dark-pro', 'aurora-x').
|
|
@@ -112,24 +122,55 @@ export type BoltdocsRobotsConfig =
|
|
|
112
122
|
sitemaps?: string[]
|
|
113
123
|
}
|
|
114
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Configuration for a specific locale.
|
|
127
|
+
*/
|
|
128
|
+
export interface BoltdocsLocaleConfig {
|
|
129
|
+
/** The display name of the locale */
|
|
130
|
+
label?: string
|
|
131
|
+
/** The text direction (ltr or rtl) */
|
|
132
|
+
direction?: 'ltr' | 'rtl'
|
|
133
|
+
/** The HTML lang attribute value (e.g., 'en-US') */
|
|
134
|
+
htmlLang?: string
|
|
135
|
+
/** The calendar system to use (e.g., 'gregory') */
|
|
136
|
+
calendar?: string
|
|
137
|
+
}
|
|
138
|
+
|
|
115
139
|
/**
|
|
116
140
|
* Configuration for internationalization (i18n).
|
|
117
141
|
*/
|
|
118
142
|
export interface BoltdocsI18nConfig {
|
|
119
143
|
/** The default locale (e.g., 'en') */
|
|
120
144
|
defaultLocale: string
|
|
121
|
-
/** Available locales and their display names (e.g., { en: 'English', es: 'Español' }) */
|
|
145
|
+
/** Available locales and their basic display names (e.g., { en: 'English', es: 'Español' }) */
|
|
122
146
|
locales: Record<string, string>
|
|
147
|
+
/** Detailed configuration for each locale */
|
|
148
|
+
localeConfigs?: Record<string, BoltdocsLocaleConfig>
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Configuration for a specific documentation version.
|
|
153
|
+
*/
|
|
154
|
+
export interface BoltdocsVersionConfig {
|
|
155
|
+
/** The display name of the version (e.g., 'v2.0') */
|
|
156
|
+
label: string
|
|
157
|
+
/** The URL path prefix for the version (e.g., '2.0') */
|
|
158
|
+
path: string
|
|
123
159
|
}
|
|
124
160
|
|
|
125
161
|
/**
|
|
126
162
|
* Configuration for documentation versioning.
|
|
127
163
|
*/
|
|
128
164
|
export interface BoltdocsVersionsConfig {
|
|
129
|
-
/** The default version (e.g., 'v2') */
|
|
165
|
+
/** The default version path (e.g., 'v2') */
|
|
130
166
|
defaultVersion: string
|
|
131
|
-
/**
|
|
132
|
-
|
|
167
|
+
/**
|
|
168
|
+
* Optional prefix for all version paths (e.g., 'v').
|
|
169
|
+
* If set to 'v', version '1.1' will be available at '/docs/v1.1'.
|
|
170
|
+
*/
|
|
171
|
+
prefix?: string
|
|
172
|
+
/** Available versions configurations */
|
|
173
|
+
versions: BoltdocsVersionConfig[]
|
|
133
174
|
}
|
|
134
175
|
|
|
135
176
|
/**
|
|
@@ -189,8 +230,6 @@ export interface BoltdocsConfig {
|
|
|
189
230
|
robots?: BoltdocsRobotsConfig
|
|
190
231
|
/** Low-level Vite configuration overrides */
|
|
191
232
|
vite?: import('vite').InlineConfig
|
|
192
|
-
/** @deprecated Use theme instead */
|
|
193
|
-
themeConfig?: BoltdocsThemeConfig
|
|
194
233
|
}
|
|
195
234
|
|
|
196
235
|
export function defineConfig(config: BoltdocsConfig): BoltdocsConfig {
|
|
@@ -286,13 +325,12 @@ export async function resolveConfig(
|
|
|
286
325
|
poweredBy: userConfig.poweredBy,
|
|
287
326
|
communityHelp: userConfig.communityHelp,
|
|
288
327
|
version: userConfig.version,
|
|
289
|
-
editLink: userConfig.editLink
|
|
328
|
+
editLink: userConfig.editLink,
|
|
290
329
|
}
|
|
291
330
|
|
|
292
331
|
// User can define properties at top level or inside themeConfig/theme
|
|
293
332
|
const userThemeConfig: BoltdocsThemeConfig = {
|
|
294
333
|
...themeConfigFromTop,
|
|
295
|
-
...(userConfig.themeConfig || {}),
|
|
296
334
|
...(userConfig.theme || {}),
|
|
297
335
|
}
|
|
298
336
|
|
|
@@ -330,4 +368,3 @@ export async function resolveConfig(
|
|
|
330
368
|
vite: userConfig.vite,
|
|
331
369
|
}
|
|
332
370
|
}
|
|
333
|
-
|
package/src/node/index.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { Plugin, InlineConfig } from 'vite'
|
|
|
2
2
|
import react from '@vitejs/plugin-react'
|
|
3
3
|
import tailwindcss from '@tailwindcss/vite'
|
|
4
4
|
import { boltdocsPlugin } from './plugin/index'
|
|
5
|
-
import { boltdocsMdxPlugin } from './mdx'
|
|
5
|
+
import { boltdocsMdxPlugin } from './mdx/index'
|
|
6
6
|
import type { BoltdocsPluginOptions } from './plugin/index'
|
|
7
7
|
|
|
8
8
|
import { resolveConfig, type BoltdocsConfig } from './config'
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { TransformCache } from '../cache'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Version identifier for the MDX plugin to invalidate cache if logic changes.
|
|
5
|
+
*/
|
|
6
|
+
export const MDX_PLUGIN_VERSION = 'v3'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Persistent cache for MDX transformations.
|
|
10
|
+
* Saves results to `.boltdocs/transform-mdx.json.gz`.
|
|
11
|
+
*/
|
|
12
|
+
export const mdxCache = new TransformCache('mdx')
|