boltdocs 2.3.0 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/dist/base-ui/index.d.mts +3 -3
- package/dist/base-ui/index.d.ts +3 -3
- package/dist/base-ui/index.js +1 -1
- package/dist/base-ui/index.mjs +1 -1
- package/dist/chunk-2DI3OGHV.mjs +1 -0
- package/dist/chunk-64AJ5QLT.mjs +1 -0
- package/dist/chunk-DDX52BX4.mjs +1 -0
- package/dist/chunk-HRZDSFR5.mjs +1 -0
- package/dist/chunk-PPVDMDEL.mjs +1 -0
- package/dist/{chunk-HA6543SL.mjs → chunk-UBE4CKOA.mjs} +1 -1
- package/dist/chunk-UWT4AJTH.mjs +73 -0
- package/dist/chunk-WWJ7WKDI.mjs +1 -0
- package/dist/client/index.d.mts +15 -21
- package/dist/client/index.d.ts +15 -21
- 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 +1 -1
- package/dist/client/types.d.ts +1 -1
- package/dist/client/types.js +1 -1
- package/dist/{copy-markdown-CbS8X-qe.d.mts → copy-markdown--9yjpbyy.d.mts} +1 -1
- package/dist/{copy-markdown-C-90ixSe.d.ts → copy-markdown-l2MYkcG7.d.ts} +1 -1
- package/dist/hooks/index.d.mts +3 -3
- package/dist/hooks/index.d.ts +3 -3
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/index.mjs +1 -1
- package/dist/integrations/index.d.mts +1 -1
- package/dist/integrations/index.d.ts +1 -1
- package/dist/{loading-B7X5Wchs.d.ts → loading-BwUos0wZ.d.mts} +2 -11
- package/dist/{loading-WuaQbsKb.d.mts → loading-nlnUD01v.d.ts} +2 -11
- package/dist/mdx/index.d.mts +4 -2
- package/dist/mdx/index.d.ts +4 -2
- package/dist/mdx/index.js +1 -1
- package/dist/mdx/index.mjs +1 -1
- package/dist/node/cli-entry.js +16 -17
- package/dist/node/cli-entry.mjs +1 -1
- package/dist/node/index.d.mts +0 -9
- package/dist/node/index.d.ts +0 -9
- package/dist/node/index.js +13 -14
- package/dist/node/index.mjs +1 -1
- package/dist/primitives/index.d.mts +11 -20
- package/dist/primitives/index.d.ts +11 -20
- package/dist/primitives/index.js +1 -1
- package/dist/primitives/index.mjs +1 -1
- package/dist/search-dialog-OONKKC5H.mjs +1 -0
- package/dist/{types-j7jvWsJj.d.ts → types-opDA2E9-.d.mts} +4 -11
- package/dist/{types-j7jvWsJj.d.mts → types-opDA2E9-.d.ts} +4 -11
- package/dist/{use-routes-Cd806kGw.d.ts → use-routes-DNwgTRpU.d.ts} +1 -1
- package/dist/{use-routes-DDL0_jkQ.d.mts → use-routes-DrT80Eom.d.mts} +1 -1
- package/package.json +1 -1
- package/src/client/app/index.tsx +20 -9
- package/src/client/app/mdx-components-context.tsx +2 -2
- package/src/client/app/mdx-page.tsx +0 -1
- package/src/client/app/scroll-handler.tsx +21 -10
- package/src/client/components/default-layout.tsx +0 -2
- package/src/client/components/docs-layout.tsx +34 -4
- package/src/client/components/icons-dev.tsx +154 -0
- package/src/client/components/mdx/code-block.tsx +57 -5
- package/src/client/components/mdx/component-preview.tsx +1 -0
- package/src/client/components/mdx/file-tree.tsx +35 -0
- package/src/client/components/primitives/helpers/observer.ts +30 -39
- package/src/client/components/primitives/index.ts +1 -0
- package/src/client/components/primitives/menu.tsx +18 -12
- package/src/client/components/primitives/navbar.tsx +31 -90
- package/src/client/components/primitives/on-this-page.tsx +7 -161
- package/src/client/components/primitives/popover.tsx +1 -2
- package/src/client/components/ui-base/copy-markdown.tsx +4 -10
- package/src/client/components/ui-base/index.ts +0 -1
- package/src/client/components/ui-base/navbar.tsx +10 -9
- package/src/client/hooks/use-navbar.ts +37 -6
- package/src/client/index.ts +0 -1
- package/src/client/theme/neutral.css +2 -3
- package/src/client/types.ts +2 -2
- package/src/node/config.ts +0 -14
- package/src/node/mdx/cache.ts +1 -1
- package/src/node/mdx/index.ts +2 -0
- package/src/node/mdx/rehype-shiki.ts +9 -0
- package/src/node/mdx/remark-code-meta.ts +35 -0
- package/src/node/mdx/remark-shiki.ts +1 -1
- package/src/node/plugin/entry.ts +21 -14
- package/src/node/plugin/index.ts +22 -4
- package/src/node/routes/parser.ts +3 -0
- package/src/node/ssg/index.ts +76 -16
- package/dist/chunk-22NXDNP4.mjs +0 -74
- package/dist/chunk-2HUVMMJU.mjs +0 -1
- package/dist/chunk-CRZGOE32.mjs +0 -1
- package/dist/chunk-RPUERTVC.mjs +0 -1
- package/dist/chunk-URTD6E6S.mjs +0 -1
- package/dist/chunk-W2NB4T6V.mjs +0 -1
- package/dist/search-dialog-ZRXBAQJ5.mjs +0 -1
- package/src/client/components/ui-base/progress-bar.tsx +0 -67
|
@@ -12,8 +12,8 @@ import React, {
|
|
|
12
12
|
import scrollIntoView from 'scroll-into-view-if-needed'
|
|
13
13
|
import { cn } from '../../utils/cn'
|
|
14
14
|
import { useOnChange } from '../../utils/use-on-change'
|
|
15
|
-
import type { ComponentBase
|
|
16
|
-
import { getItemId } from './helpers/observer'
|
|
15
|
+
import type { ComponentBase } from './types'
|
|
16
|
+
import { getItemId, Observer } from './helpers/observer'
|
|
17
17
|
|
|
18
18
|
export interface TOCItemType {
|
|
19
19
|
title: ReactNode
|
|
@@ -74,120 +74,6 @@ export interface OnThisPageIndicatorProps extends ComponentBase {
|
|
|
74
74
|
const ItemsContext = createContext<TOCItemInfo[] | null>(null)
|
|
75
75
|
const ScrollContext = createContext<RefObject<HTMLElement | null> | null>(null)
|
|
76
76
|
|
|
77
|
-
class Observer {
|
|
78
|
-
items: TOCItemInfo[] = []
|
|
79
|
-
single = false
|
|
80
|
-
private observer: IntersectionObserver | null = null
|
|
81
|
-
onChange?: () => void
|
|
82
|
-
|
|
83
|
-
private callback(entries: IntersectionObserverEntry[]) {
|
|
84
|
-
if (entries.length === 0) return
|
|
85
|
-
|
|
86
|
-
let hasActive = false
|
|
87
|
-
this.items = this.items.map((item) => {
|
|
88
|
-
const entry = entries.find((entry) => entry.target.id === item.id)
|
|
89
|
-
let active = entry ? entry.isIntersecting : item.active && !item.fallback
|
|
90
|
-
if (this.single && hasActive) active = false
|
|
91
|
-
|
|
92
|
-
if (item.active !== active) {
|
|
93
|
-
item = {
|
|
94
|
-
...item,
|
|
95
|
-
t: Date.now(),
|
|
96
|
-
active,
|
|
97
|
-
fallback: false,
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (active) hasActive = true
|
|
102
|
-
return item
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
if (!hasActive && entries[0].rootBounds) {
|
|
106
|
-
const viewTop = entries[0].rootBounds.top
|
|
107
|
-
let min = Number.MAX_VALUE
|
|
108
|
-
let fallbackIdx = -1
|
|
109
|
-
|
|
110
|
-
for (let i = 0; i < this.items.length; i++) {
|
|
111
|
-
const element = document.getElementById(this.items[i].id)
|
|
112
|
-
if (!element) continue
|
|
113
|
-
|
|
114
|
-
const d = Math.abs(viewTop - element.getBoundingClientRect().top)
|
|
115
|
-
if (d < min) {
|
|
116
|
-
fallbackIdx = i
|
|
117
|
-
min = d
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (fallbackIdx !== -1) {
|
|
122
|
-
this.items[fallbackIdx] = {
|
|
123
|
-
...this.items[fallbackIdx],
|
|
124
|
-
active: true,
|
|
125
|
-
fallback: true,
|
|
126
|
-
t: Date.now(),
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
this.onChange?.()
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
setItems(newItems: TOCItemType[]) {
|
|
135
|
-
const observer = this.observer
|
|
136
|
-
if (observer) {
|
|
137
|
-
for (const item of this.items) {
|
|
138
|
-
const element = document.getElementById(item.id)
|
|
139
|
-
if (!element) continue
|
|
140
|
-
observer.unobserve(element)
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
this.items = []
|
|
145
|
-
for (const item of newItems) {
|
|
146
|
-
const id = getItemId(item.url)
|
|
147
|
-
if (!id) continue
|
|
148
|
-
|
|
149
|
-
this.items.push({
|
|
150
|
-
id,
|
|
151
|
-
active: false,
|
|
152
|
-
fallback: false,
|
|
153
|
-
t: 0,
|
|
154
|
-
original: item,
|
|
155
|
-
})
|
|
156
|
-
}
|
|
157
|
-
this.watchItems()
|
|
158
|
-
|
|
159
|
-
// In an SPA, the TOC might update before the MDX content is in the DOM.
|
|
160
|
-
// We perform a few delayed scans to ensure we catch those elements.
|
|
161
|
-
if (typeof window !== 'undefined') {
|
|
162
|
-
setTimeout(() => this.watchItems(), 100)
|
|
163
|
-
setTimeout(() => this.watchItems(), 500)
|
|
164
|
-
setTimeout(() => this.watchItems(), 1000)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
this.onChange?.()
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
watch(options?: IntersectionObserverInit) {
|
|
171
|
-
if (this.observer) return
|
|
172
|
-
this.observer = new IntersectionObserver(this.callback.bind(this), options)
|
|
173
|
-
this.watchItems()
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
private watchItems() {
|
|
177
|
-
if (!this.observer) return
|
|
178
|
-
for (const item of this.items) {
|
|
179
|
-
const element = document.getElementById(item.id)
|
|
180
|
-
if (!element) continue
|
|
181
|
-
this.observer.observe(element)
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
unwatch() {
|
|
186
|
-
this.observer?.disconnect()
|
|
187
|
-
this.observer = null
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
77
|
export function useItems() {
|
|
192
78
|
const ctx = use(ItemsContext)
|
|
193
79
|
if (!ctx)
|
|
@@ -197,40 +83,6 @@ export function useItems() {
|
|
|
197
83
|
return ctx
|
|
198
84
|
}
|
|
199
85
|
|
|
200
|
-
export function useScrollStatus(ref: RefObject<HTMLElement | null>) {
|
|
201
|
-
const [status, setStatus] = useState({
|
|
202
|
-
isOverflowing: false,
|
|
203
|
-
isAtBottom: false,
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
useEffect(() => {
|
|
207
|
-
const el = ref.current
|
|
208
|
-
if (!el) return
|
|
209
|
-
|
|
210
|
-
const checkStatus = () => {
|
|
211
|
-
const isOverflowing = el.scrollHeight > el.clientHeight
|
|
212
|
-
// We use a 2px threshold for floating point math issues
|
|
213
|
-
const isAtBottom = el.scrollHeight - el.scrollTop <= el.clientHeight + 2
|
|
214
|
-
setStatus({ isOverflowing, isAtBottom })
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
checkStatus()
|
|
218
|
-
el.addEventListener('scroll', checkStatus, { passive: true })
|
|
219
|
-
window.addEventListener('resize', checkStatus)
|
|
220
|
-
|
|
221
|
-
const mutationObserver = new MutationObserver(checkStatus)
|
|
222
|
-
mutationObserver.observe(el, { childList: true, subtree: true })
|
|
223
|
-
|
|
224
|
-
return () => {
|
|
225
|
-
el.removeEventListener('scroll', checkStatus)
|
|
226
|
-
window.removeEventListener('resize', checkStatus)
|
|
227
|
-
mutationObserver.disconnect()
|
|
228
|
-
}
|
|
229
|
-
}, [ref])
|
|
230
|
-
|
|
231
|
-
return status
|
|
232
|
-
}
|
|
233
|
-
|
|
234
86
|
export function useActiveAnchor(): string | undefined {
|
|
235
87
|
const items = useItems()
|
|
236
88
|
return useMemo(() => {
|
|
@@ -282,9 +134,11 @@ export function AnchorProvider({
|
|
|
282
134
|
}, [observer, toc])
|
|
283
135
|
|
|
284
136
|
useEffect(() => {
|
|
137
|
+
// We use a rootMargin that acts as an activation "line" near the top.
|
|
138
|
+
// headings are "intersecting" (active=true) when they are BELOW this line.
|
|
285
139
|
observer.watch({
|
|
286
|
-
rootMargin: '
|
|
287
|
-
threshold: 0
|
|
140
|
+
rootMargin: '-100px 0% 0% 0%',
|
|
141
|
+
threshold: 0,
|
|
288
142
|
})
|
|
289
143
|
observer.onChange = () => setItems([...observer.items])
|
|
290
144
|
|
|
@@ -339,18 +193,10 @@ export const OnThisPageContent = ({
|
|
|
339
193
|
|
|
340
194
|
useImperativeHandle(ref, () => internalRef.current!)
|
|
341
195
|
|
|
342
|
-
const { isOverflowing, isAtBottom } = useScrollStatus(internalRef)
|
|
343
|
-
|
|
344
196
|
return (
|
|
345
197
|
<div
|
|
346
198
|
ref={internalRef}
|
|
347
|
-
className={cn(
|
|
348
|
-
'relative overflow-y-auto boltdocs-otp-content',
|
|
349
|
-
isOverflowing &&
|
|
350
|
-
!isAtBottom &&
|
|
351
|
-
'mask-[linear-gradient(to_bottom,black_85%,transparent_100%)]',
|
|
352
|
-
className,
|
|
353
|
-
)}
|
|
199
|
+
className={cn('relative overflow-y-auto boltdocs-otp-content', className)}
|
|
354
200
|
{...props}
|
|
355
201
|
>
|
|
356
202
|
{children}
|
|
@@ -24,8 +24,7 @@ export const Popover = ({
|
|
|
24
24
|
{...props}
|
|
25
25
|
className={RAC.composeRenderProps(className, (className) =>
|
|
26
26
|
cn(
|
|
27
|
-
'z-50 overflow-auto rounded-xl border border-border-subtle bg-bg-surface/80 shadow-xl backdrop-blur-md outline-none',
|
|
28
|
-
'entering:animate-in entering:fade-in entering:zoom-in-95 exiting:animate-out exiting:fade-out exiting:zoom-out-95 fill-mode-forwards',
|
|
27
|
+
'z-50 overflow-auto rounded-xl border border-border-subtle bg-bg-surface/80 shadow-xl backdrop-blur-md outline-none transition-none',
|
|
29
28
|
className,
|
|
30
29
|
),
|
|
31
30
|
)}
|
|
@@ -80,25 +80,19 @@ export function CopyMarkdown({ content, mdxRaw, config }: CopyMarkdownProps) {
|
|
|
80
80
|
)}
|
|
81
81
|
/>
|
|
82
82
|
<Menu className="w-52">
|
|
83
|
-
<MenuItem
|
|
84
|
-
onAction={handleCopy}
|
|
85
|
-
className="flex flex-row items-start gap-2.5 group"
|
|
86
|
-
>
|
|
83
|
+
<MenuItem onAction={handleCopy}>
|
|
87
84
|
<Copy
|
|
88
85
|
size={16}
|
|
89
|
-
className="
|
|
86
|
+
className="size-4 mt-0.5 text-text-muted group-hover:text-primary-500"
|
|
90
87
|
/>
|
|
91
88
|
<span className="font-medium text-[0.8125rem]">
|
|
92
89
|
Copy Markdown
|
|
93
90
|
</span>
|
|
94
91
|
</MenuItem>
|
|
95
|
-
<MenuItem
|
|
96
|
-
onAction={handleOpenRaw}
|
|
97
|
-
className="flex flex-row items-start gap-2.5 group"
|
|
98
|
-
>
|
|
92
|
+
<MenuItem onAction={handleOpenRaw}>
|
|
99
93
|
<ExternalLink
|
|
100
94
|
size={16}
|
|
101
|
-
className="
|
|
95
|
+
className="size-4 mt-0.5 text-text-muted group-hover:text-primary-500"
|
|
102
96
|
/>
|
|
103
97
|
<span className="font-medium text-[0.8125rem]">
|
|
104
98
|
View as Markdown
|
|
@@ -10,7 +10,6 @@ export { NotFound } from './not-found'
|
|
|
10
10
|
export { OnThisPage } from './on-this-page'
|
|
11
11
|
export { PageNav } from './page-nav'
|
|
12
12
|
export { PoweredBy } from './powered-by'
|
|
13
|
-
export { ProgressBar } from './progress-bar'
|
|
14
13
|
export { SearchDialog } from './search-dialog'
|
|
15
14
|
export { Sidebar } from './sidebar'
|
|
16
15
|
export { Tabs } from './tabs'
|
|
@@ -26,7 +26,7 @@ export function Navbar() {
|
|
|
26
26
|
const { routes, allRoutes, currentVersion, currentLocale } = useRoutes()
|
|
27
27
|
const { pathname } = useLocation()
|
|
28
28
|
const themeConfig = config.theme || {}
|
|
29
|
-
|
|
29
|
+
const isDocs = pathname.startsWith('/docs')
|
|
30
30
|
const hasTabs = themeConfig?.tabs && themeConfig.tabs.length > 0
|
|
31
31
|
|
|
32
32
|
return (
|
|
@@ -44,12 +44,6 @@ export function Navbar() {
|
|
|
44
44
|
<NavbarPrimitive.Title>{title}</NavbarPrimitive.Title>
|
|
45
45
|
|
|
46
46
|
{config.versions && currentVersion && <NavbarVersion />}
|
|
47
|
-
|
|
48
|
-
<NavbarPrimitive.Links>
|
|
49
|
-
{links.map((link) => (
|
|
50
|
-
<NavbarLinkItem key={link.href} link={link} />
|
|
51
|
-
))}
|
|
52
|
-
</NavbarPrimitive.Links>
|
|
53
47
|
</NavbarPrimitive.NavbarLeft>
|
|
54
48
|
<NavbarPrimitive.NavbarCenter>
|
|
55
49
|
<Suspense
|
|
@@ -61,6 +55,13 @@ export function Navbar() {
|
|
|
61
55
|
</Suspense>
|
|
62
56
|
</NavbarPrimitive.NavbarCenter>
|
|
63
57
|
<NavbarPrimitive.NavbarRight>
|
|
58
|
+
<NavbarPrimitive.Links>
|
|
59
|
+
{links.map((link) => (
|
|
60
|
+
<>
|
|
61
|
+
<NavbarLinkItem key={link.href} link={link} />
|
|
62
|
+
</>
|
|
63
|
+
))}
|
|
64
|
+
</NavbarPrimitive.Links>
|
|
64
65
|
{config.i18n && currentLocale && <NavbarI18n />}
|
|
65
66
|
<NavbarPrimitive.Split />
|
|
66
67
|
<ThemeToggle />
|
|
@@ -79,7 +80,7 @@ export function Navbar() {
|
|
|
79
80
|
</NavbarPrimitive.NavbarRight>
|
|
80
81
|
</NavbarPrimitive.Content>
|
|
81
82
|
|
|
82
|
-
{
|
|
83
|
+
{isDocs && hasTabs && themeConfig?.tabs && (
|
|
83
84
|
<div className="w-full border-b border-border-subtle bg-bg-main">
|
|
84
85
|
<Tabs tabs={themeConfig.tabs} routes={allRoutes || routes || []} />
|
|
85
86
|
</div>
|
|
@@ -89,7 +90,7 @@ export function Navbar() {
|
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
function NavbarLinkItem({ link }: { link: NavbarLinkType }) {
|
|
92
|
-
const localizedHref = useLocalizedTo(link.href)
|
|
93
|
+
const localizedHref = useLocalizedTo(link.href || '')
|
|
93
94
|
return <NavbarPrimitive.Link {...(link as any)} href={localizedHref} />
|
|
94
95
|
}
|
|
95
96
|
|
|
@@ -19,19 +19,50 @@ export function useNavbar() {
|
|
|
19
19
|
|
|
20
20
|
// Transform links to the new NavbarLink structure
|
|
21
21
|
const links: NavbarLink[] = rawLinks.map((item: any) => {
|
|
22
|
-
const href = item.href || item.to || item.link || ''
|
|
22
|
+
const href = (item.href || item.to || item.link || '') as string
|
|
23
|
+
|
|
24
|
+
// Robust active state calculation
|
|
25
|
+
const getIsActive = (h: string) => {
|
|
26
|
+
const activePath = location.pathname
|
|
27
|
+
if (activePath === h) return true
|
|
28
|
+
if (!h || h === '/') return activePath === '/'
|
|
29
|
+
|
|
30
|
+
const cleanPathParts = (p: string) => {
|
|
31
|
+
const parts = p.split('/').filter(Boolean)
|
|
32
|
+
let i = 0
|
|
33
|
+
// Skip locale
|
|
34
|
+
if (config.i18n?.locales && parts[i] && config.i18n.locales[parts[i]]) {
|
|
35
|
+
i++
|
|
36
|
+
}
|
|
37
|
+
// Skip version
|
|
38
|
+
if (config.versions?.versions && parts[i]) {
|
|
39
|
+
if (config.versions.versions.some((v) => v.path === parts[i])) {
|
|
40
|
+
i++
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return parts.slice(i)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const hParts = cleanPathParts(h)
|
|
47
|
+
const pParts = cleanPathParts(activePath)
|
|
48
|
+
|
|
49
|
+
if (hParts.length === 0) return pParts.length === 0
|
|
50
|
+
|
|
51
|
+
// Must match at least as many parts as the candidate link
|
|
52
|
+
if (pParts.length < hParts.length) return false
|
|
53
|
+
|
|
54
|
+
// Every part of hParts must match pParts at the same position
|
|
55
|
+
return hParts.every((part, i) => pParts[i] === part)
|
|
56
|
+
}
|
|
57
|
+
|
|
23
58
|
return {
|
|
24
59
|
label: getTranslated(item.label || item.text, currentLocale),
|
|
25
60
|
href,
|
|
26
|
-
active:
|
|
61
|
+
active: getIsActive(href),
|
|
27
62
|
to:
|
|
28
63
|
href.startsWith('http') || href.startsWith('//')
|
|
29
64
|
? 'external'
|
|
30
65
|
: 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
|
-
})),
|
|
35
66
|
}
|
|
36
67
|
})
|
|
37
68
|
|
package/src/client/index.ts
CHANGED
|
@@ -21,7 +21,6 @@ export { OnThisPage } from '@components/ui-base/on-this-page'
|
|
|
21
21
|
export { Head } from '@components/ui-base/head'
|
|
22
22
|
export { Breadcrumbs } from '@components/ui-base/breadcrumbs'
|
|
23
23
|
export { PageNav } from '@components/ui-base/page-nav'
|
|
24
|
-
export { ProgressBar } from '@components/ui-base/progress-bar'
|
|
25
24
|
export { ErrorBoundary } from '@components/ui-base/error-boundary'
|
|
26
25
|
export { CopyMarkdown } from '@components/ui-base/copy-markdown'
|
|
27
26
|
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
--spacing-navbar: 3.5rem;
|
|
67
67
|
--spacing-sidebar: 16rem;
|
|
68
68
|
--spacing-toc: 14rem;
|
|
69
|
-
--spacing-content-max:
|
|
69
|
+
--spacing-content-max: 54rem;
|
|
70
70
|
|
|
71
71
|
@keyframes pulse {
|
|
72
72
|
0%,
|
|
@@ -123,9 +123,8 @@
|
|
|
123
123
|
body {
|
|
124
124
|
margin: 0;
|
|
125
125
|
padding: 0;
|
|
126
|
-
height: 100%;
|
|
126
|
+
min-height: 100%;
|
|
127
127
|
overflow-x: hidden;
|
|
128
|
-
overflow-y: hidden;
|
|
129
128
|
}
|
|
130
129
|
|
|
131
130
|
body {
|
package/src/client/types.ts
CHANGED
|
@@ -74,6 +74,8 @@ export interface CreateBoltdocsAppOptions {
|
|
|
74
74
|
homePage?: React.ComponentType
|
|
75
75
|
/** Custom external pages mapped by their route path */
|
|
76
76
|
externalPages?: Record<string, React.ComponentType>
|
|
77
|
+
/** Optional custom layout for external pages */
|
|
78
|
+
externalLayout?: React.ComponentType<{ children: React.ReactNode }>
|
|
77
79
|
/** Optional custom MDX components provided by plugins */
|
|
78
80
|
components?: Record<string, React.ComponentType>
|
|
79
81
|
}
|
|
@@ -167,6 +169,4 @@ export interface NavbarLink {
|
|
|
167
169
|
active: boolean
|
|
168
170
|
/** Optional icon or string for external link indication */
|
|
169
171
|
to?: string
|
|
170
|
-
/** Nested items for NavigationMenu */
|
|
171
|
-
items?: NavbarLink[]
|
|
172
172
|
}
|
package/src/node/config.ts
CHANGED
|
@@ -44,13 +44,6 @@ export interface BoltdocsThemeConfig {
|
|
|
44
44
|
label: string | Record<string, string>
|
|
45
45
|
/** URL path or external link */
|
|
46
46
|
href: string
|
|
47
|
-
/** Nested items for NavigationMenu */
|
|
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
|
-
}>
|
|
54
47
|
}>
|
|
55
48
|
/** Items to display in the sidebar, organized optionally by group URLs */
|
|
56
49
|
sidebar?: Record<string, Array<{ text: string; link: string }>>
|
|
@@ -222,8 +215,6 @@ export interface BoltdocsConfig {
|
|
|
222
215
|
versions?: BoltdocsVersionsConfig
|
|
223
216
|
/** Custom plugins for extending functionality */
|
|
224
217
|
plugins?: BoltdocsPlugin[]
|
|
225
|
-
/** Map of custom external route paths to component file paths */
|
|
226
|
-
external?: Record<string, string>
|
|
227
218
|
/** External integrations configuration */
|
|
228
219
|
integrations?: BoltdocsIntegrationsConfig
|
|
229
220
|
/** Configuration for the robots.txt file */
|
|
@@ -344,10 +335,6 @@ export async function resolveConfig(
|
|
|
344
335
|
cleanThemeConfig.navbar = cleanThemeConfig.navbar.map((item: any) => ({
|
|
345
336
|
label: item.label || item.text || '',
|
|
346
337
|
href: item.href || item.link || item.to || '',
|
|
347
|
-
items: item.items?.map((sub: any) => ({
|
|
348
|
-
label: sub.label || sub.text || '',
|
|
349
|
-
href: sub.href || sub.link || sub.to || '',
|
|
350
|
-
})),
|
|
351
338
|
}))
|
|
352
339
|
}
|
|
353
340
|
|
|
@@ -362,7 +349,6 @@ export async function resolveConfig(
|
|
|
362
349
|
versions: userConfig.versions,
|
|
363
350
|
siteUrl: userConfig.siteUrl,
|
|
364
351
|
plugins: userConfig.plugins || [],
|
|
365
|
-
external: userConfig.external,
|
|
366
352
|
integrations: userConfig.integrations,
|
|
367
353
|
robots: userConfig.robots,
|
|
368
354
|
vite: userConfig.vite,
|
package/src/node/mdx/cache.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { TransformCache } from '../cache'
|
|
|
3
3
|
/**
|
|
4
4
|
* Version identifier for the MDX plugin to invalidate cache if logic changes.
|
|
5
5
|
*/
|
|
6
|
-
export const MDX_PLUGIN_VERSION = '
|
|
6
|
+
export const MDX_PLUGIN_VERSION = 'v4'
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Persistent cache for MDX transformations.
|
package/src/node/mdx/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type { BoltdocsConfig } from '../config'
|
|
|
9
9
|
import { mdxCache, MDX_PLUGIN_VERSION } from './cache'
|
|
10
10
|
import { remarkShiki } from './remark-shiki'
|
|
11
11
|
import { rehypeShiki } from './rehype-shiki'
|
|
12
|
+
import { remarkCodeMeta } from './remark-code-meta'
|
|
12
13
|
|
|
13
14
|
let mdxCacheLoaded = false
|
|
14
15
|
let hits = 0
|
|
@@ -38,6 +39,7 @@ export function boltdocsMdxPlugin(
|
|
|
38
39
|
remarkPlugins: [
|
|
39
40
|
remarkGfm,
|
|
40
41
|
remarkFrontmatter,
|
|
42
|
+
remarkCodeMeta,
|
|
41
43
|
[remarkShiki, config],
|
|
42
44
|
...(extraRemarkPlugins as any[]),
|
|
43
45
|
],
|
|
@@ -31,6 +31,11 @@ export function rehypeShiki(config?: BoltdocsConfig) {
|
|
|
31
31
|
const lang = langMatch ? langMatch.slice(9) : 'text'
|
|
32
32
|
const code = codeNode.children[0]?.value || ''
|
|
33
33
|
|
|
34
|
+
// Extract title from meta string (e.g., ```ts title="app.ts")
|
|
35
|
+
const meta: string = codeNode.data?.meta || codeNode.properties?.metastring || ''
|
|
36
|
+
const titleMatch = meta.match(/title\s*=\s*"([^"]*)"/)
|
|
37
|
+
const title = titleMatch ? titleMatch[1] : undefined
|
|
38
|
+
|
|
34
39
|
const options: any = { lang }
|
|
35
40
|
if (typeof codeTheme === 'object') {
|
|
36
41
|
options.themes = {
|
|
@@ -46,6 +51,10 @@ export function rehypeShiki(config?: BoltdocsConfig) {
|
|
|
46
51
|
// Inject highlighted HTML and mark as highlighted for CodeBlock component
|
|
47
52
|
node.properties.dataHighlighted = 'true'
|
|
48
53
|
node.properties.highlightedHtml = html
|
|
54
|
+
node.properties['data-lang'] = lang
|
|
55
|
+
if (title) {
|
|
56
|
+
node.properties.title = title
|
|
57
|
+
}
|
|
49
58
|
node.children = []
|
|
50
59
|
}
|
|
51
60
|
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { visit } from 'unist-util-visit'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Remark plugin that preserves code fence meta strings (e.g., title="file.ts")
|
|
5
|
+
* and the language identifier by copying them to hProperties so they survive
|
|
6
|
+
* the remark → rehype conversion and are accessible as props on the `<pre>` element.
|
|
7
|
+
*
|
|
8
|
+
* Usage in MDX: ```ts title="app.ts"
|
|
9
|
+
*/
|
|
10
|
+
export function remarkCodeMeta() {
|
|
11
|
+
return (tree: any) => {
|
|
12
|
+
visit(tree, 'code', (node: any) => {
|
|
13
|
+
node.data = node.data || {}
|
|
14
|
+
node.data.hProperties = node.data.hProperties || {}
|
|
15
|
+
|
|
16
|
+
// Always pass the lang through
|
|
17
|
+
if (node.lang) {
|
|
18
|
+
node.data.hProperties['data-lang'] = node.lang
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!node.meta) return
|
|
22
|
+
|
|
23
|
+
const meta: string = node.meta
|
|
24
|
+
|
|
25
|
+
// Extract title="..." from the meta string
|
|
26
|
+
const titleMatch = meta.match(/title\s*=\s*"([^"]*)"/)
|
|
27
|
+
if (titleMatch) {
|
|
28
|
+
node.data.hProperties.title = titleMatch[1]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Preserve the full meta string for other plugins
|
|
32
|
+
node.data.hProperties.metastring = meta
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -32,7 +32,7 @@ export function remarkShiki(config?: BoltdocsConfig) {
|
|
|
32
32
|
code = codeAttr.value
|
|
33
33
|
} else if (codeAttr.value?.type === 'mdxJsxAttributeValueExpression') {
|
|
34
34
|
const expr = codeAttr.value.value ?? ''
|
|
35
|
-
code = expr.match(/^[`'"](
|
|
35
|
+
code = expr.match(/^[`'"]([\s\S]+)[`'"]$/)?.[1] ?? expr
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
package/src/node/plugin/entry.ts
CHANGED
|
@@ -24,7 +24,7 @@ export function generateEntryCode(
|
|
|
24
24
|
const cssPath = path.resolve(process.cwd(), 'index.css')
|
|
25
25
|
const cssImport = fs.existsSync(cssPath) ? "import './index.css';" : ''
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
let homeOption = options.homePage ? 'homePage: HomePage,' : ''
|
|
28
28
|
const pluginComponents =
|
|
29
29
|
config?.plugins?.flatMap((p) => Object.entries(p.components || {})) || []
|
|
30
30
|
|
|
@@ -40,21 +40,28 @@ const ${name} = _comp_${name}.default || _comp_${name}['${name}'] || _comp_${nam
|
|
|
40
40
|
const componentMap = pluginComponents.map(([name]) => name).join(', ')
|
|
41
41
|
|
|
42
42
|
const docsDirName = path.basename(options.docsDir || 'docs')
|
|
43
|
+
const docsDir = path.resolve(process.cwd(), options.docsDir || 'docs')
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
.map(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
45
|
+
// Detect external pages module
|
|
46
|
+
const externalModulePath = ['tsx', 'ts', 'jsx', 'js']
|
|
47
|
+
.map((ext) => path.resolve(docsDir, `pages-external/index.${ext}`))
|
|
48
|
+
.find((p) => fs.existsSync(p))
|
|
49
|
+
|
|
50
|
+
const externalModuleImport = externalModulePath
|
|
51
|
+
? `import * as _external_module from '${normalizePath(externalModulePath)}';`
|
|
52
|
+
: ''
|
|
53
|
+
|
|
54
|
+
// Prioritize homePage from external module if it exists
|
|
55
|
+
homeOption = externalModulePath
|
|
56
|
+
? 'homePage: _external_module.homePage || HomePage,'
|
|
57
|
+
: options.homePage
|
|
58
|
+
? 'homePage: HomePage,'
|
|
56
59
|
: ''
|
|
57
60
|
|
|
61
|
+
const externalOption = externalModulePath
|
|
62
|
+
? 'externalPages: _external_module.pages, externalLayout: _external_module.layout,'
|
|
63
|
+
: ''
|
|
64
|
+
|
|
58
65
|
return `
|
|
59
66
|
import { createBoltdocsApp as _createApp } from 'boltdocs/client';
|
|
60
67
|
import _routes from 'virtual:boltdocs-routes';
|
|
@@ -63,7 +70,7 @@ import _user_mdx_components from 'virtual:boltdocs-mdx-components';
|
|
|
63
70
|
${cssImport}
|
|
64
71
|
${homeImport}
|
|
65
72
|
${componentImports}
|
|
66
|
-
${
|
|
73
|
+
${externalModuleImport}
|
|
67
74
|
|
|
68
75
|
_createApp({
|
|
69
76
|
target: '#root',
|
package/src/node/plugin/index.ts
CHANGED
|
@@ -102,10 +102,10 @@ export function boltdocsPlugin(
|
|
|
102
102
|
(locale) =>
|
|
103
103
|
url.startsWith(`/${locale}/docs`) || url === `/${locale}`,
|
|
104
104
|
)) ||
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
105
|
+
// Handle any HTML request that isn't a known static file or docs,
|
|
106
|
+
// as it potentially could be an external page.
|
|
107
|
+
// (The client-side router will handle 404s if it doesn't match anything)
|
|
108
|
+
true
|
|
109
109
|
|
|
110
110
|
// Improved check: If it's a doc route, serve HTML even if it has a dot (e.g. version 1.1)
|
|
111
111
|
// We only skip if it has a known asset extension to prevent serving HTML for images/js/etc.
|
|
@@ -140,11 +140,15 @@ export function boltdocsPlugin(
|
|
|
140
140
|
const mdxCompPaths = mdxCompExtensions.map((ext) =>
|
|
141
141
|
path.resolve(docsDir, `mdx-components.${ext}`),
|
|
142
142
|
)
|
|
143
|
+
const extPagesPaths = mdxCompExtensions.map((ext) =>
|
|
144
|
+
path.resolve(docsDir, `pages-external/index.${ext}`),
|
|
145
|
+
)
|
|
143
146
|
|
|
144
147
|
server.watcher.add([
|
|
145
148
|
...configPaths,
|
|
146
149
|
...mdxCompPaths,
|
|
147
150
|
...layoutCompPaths,
|
|
151
|
+
...extPagesPaths,
|
|
148
152
|
])
|
|
149
153
|
|
|
150
154
|
const handleFileEvent = async (
|
|
@@ -186,6 +190,19 @@ export function boltdocsPlugin(
|
|
|
186
190
|
return
|
|
187
191
|
}
|
|
188
192
|
|
|
193
|
+
// If any pages-external file changes, invalidate the entry module
|
|
194
|
+
if (
|
|
195
|
+
normalized.includes('/pages-external/') ||
|
|
196
|
+
normalized.includes('\\pages-external\\')
|
|
197
|
+
) {
|
|
198
|
+
const mod = server.moduleGraph.getModuleById(
|
|
199
|
+
'\0virtual:boltdocs-entry',
|
|
200
|
+
)
|
|
201
|
+
if (mod) server.moduleGraph.invalidateModule(mod)
|
|
202
|
+
server.ws.send({ type: 'full-reload' })
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
|
|
189
206
|
if (
|
|
190
207
|
!normalized.startsWith(normalizedDocsDir) ||
|
|
191
208
|
!isDocFile(normalized)
|
|
@@ -273,6 +290,7 @@ export function boltdocsPlugin(
|
|
|
273
290
|
i18n: config?.i18n,
|
|
274
291
|
versions: config?.versions,
|
|
275
292
|
siteUrl: config?.siteUrl,
|
|
293
|
+
plugins: config?.plugins?.map((p) => ({ name: p.name })),
|
|
276
294
|
}
|
|
277
295
|
return `export default ${JSON.stringify(clientConfig, null, 2)};`
|
|
278
296
|
}
|
|
@@ -110,6 +110,9 @@ export function parseDocFile(
|
|
|
110
110
|
if (locale) {
|
|
111
111
|
finalPath += '/' + locale
|
|
112
112
|
}
|
|
113
|
+
if (inferredTab) {
|
|
114
|
+
finalPath += '/' + inferredTab
|
|
115
|
+
}
|
|
113
116
|
finalPath += cleanRoutePath === '/' ? '' : cleanRoutePath
|
|
114
117
|
|
|
115
118
|
if (!finalPath || finalPath === '') finalPath = '/'
|