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
|
@@ -22,10 +22,10 @@ interface BoltdocsFooterConfig {
|
|
|
22
22
|
* Theme-specific configuration options governing the appearance and navigation of the site.
|
|
23
23
|
*/
|
|
24
24
|
interface BoltdocsThemeConfig {
|
|
25
|
-
/** The global title of the documentation site */
|
|
26
|
-
title?: string
|
|
27
|
-
/** The global description of the site (
|
|
28
|
-
description?: string
|
|
25
|
+
/** The global title of the documentation site (can be translated) */
|
|
26
|
+
title?: string | Record<string, string>;
|
|
27
|
+
/** The global description of the site (can be translated) */
|
|
28
|
+
description?: string | Record<string, string>;
|
|
29
29
|
/** URL path to the site logo or an object for light/dark versions */
|
|
30
30
|
logo?: string | {
|
|
31
31
|
dark: string;
|
|
@@ -36,13 +36,15 @@ interface BoltdocsThemeConfig {
|
|
|
36
36
|
};
|
|
37
37
|
/** Items to display in the top navigation bar */
|
|
38
38
|
navbar?: Array<{
|
|
39
|
-
/** Text to display */
|
|
40
|
-
label: string
|
|
39
|
+
/** Text to display (can be a string or a map of translations) */
|
|
40
|
+
label: string | Record<string, string>;
|
|
41
41
|
/** URL path or external link */
|
|
42
42
|
href: string;
|
|
43
43
|
/** Nested items for NavigationMenu */
|
|
44
44
|
items?: Array<{
|
|
45
|
-
|
|
45
|
+
/** Text to display (can be a string or a map of translations) */
|
|
46
|
+
label: string | Record<string, string>;
|
|
47
|
+
/** URL path or external link */
|
|
46
48
|
href: string;
|
|
47
49
|
}>;
|
|
48
50
|
}>;
|
|
@@ -82,7 +84,8 @@ interface BoltdocsThemeConfig {
|
|
|
82
84
|
*/
|
|
83
85
|
tabs?: Array<{
|
|
84
86
|
id: string;
|
|
85
|
-
|
|
87
|
+
/** Text to display (can be a string or a map of translations) */
|
|
88
|
+
text: string | Record<string, string>;
|
|
86
89
|
icon?: string;
|
|
87
90
|
}>;
|
|
88
91
|
/**
|
|
@@ -120,23 +123,52 @@ type BoltdocsRobotsConfig = string | {
|
|
|
120
123
|
/** Sitemaps to include in the robots.txt */
|
|
121
124
|
sitemaps?: string[];
|
|
122
125
|
};
|
|
126
|
+
/**
|
|
127
|
+
* Configuration for a specific locale.
|
|
128
|
+
*/
|
|
129
|
+
interface BoltdocsLocaleConfig {
|
|
130
|
+
/** The display name of the locale */
|
|
131
|
+
label?: string;
|
|
132
|
+
/** The text direction (ltr or rtl) */
|
|
133
|
+
direction?: 'ltr' | 'rtl';
|
|
134
|
+
/** The HTML lang attribute value (e.g., 'en-US') */
|
|
135
|
+
htmlLang?: string;
|
|
136
|
+
/** The calendar system to use (e.g., 'gregory') */
|
|
137
|
+
calendar?: string;
|
|
138
|
+
}
|
|
123
139
|
/**
|
|
124
140
|
* Configuration for internationalization (i18n).
|
|
125
141
|
*/
|
|
126
142
|
interface BoltdocsI18nConfig {
|
|
127
143
|
/** The default locale (e.g., 'en') */
|
|
128
144
|
defaultLocale: string;
|
|
129
|
-
/** 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' }) */
|
|
130
146
|
locales: Record<string, string>;
|
|
147
|
+
/** Detailed configuration for each locale */
|
|
148
|
+
localeConfigs?: Record<string, BoltdocsLocaleConfig>;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Configuration for a specific documentation version.
|
|
152
|
+
*/
|
|
153
|
+
interface BoltdocsVersionConfig {
|
|
154
|
+
/** The display name of the version (e.g., 'v2.0') */
|
|
155
|
+
label: string;
|
|
156
|
+
/** The URL path prefix for the version (e.g., '2.0') */
|
|
157
|
+
path: string;
|
|
131
158
|
}
|
|
132
159
|
/**
|
|
133
160
|
* Configuration for documentation versioning.
|
|
134
161
|
*/
|
|
135
162
|
interface BoltdocsVersionsConfig {
|
|
136
|
-
/** The default version (e.g., 'v2') */
|
|
163
|
+
/** The default version path (e.g., 'v2') */
|
|
137
164
|
defaultVersion: string;
|
|
138
|
-
/**
|
|
139
|
-
|
|
165
|
+
/**
|
|
166
|
+
* Optional prefix for all version paths (e.g., 'v').
|
|
167
|
+
* If set to 'v', version '1.1' will be available at '/docs/v1.1'.
|
|
168
|
+
*/
|
|
169
|
+
prefix?: string;
|
|
170
|
+
/** Available versions configurations */
|
|
171
|
+
versions: BoltdocsVersionConfig[];
|
|
140
172
|
}
|
|
141
173
|
/**
|
|
142
174
|
* Defines a Boltdocs plugin that can extend the build process and client-side functionality.
|
|
@@ -193,8 +225,6 @@ interface BoltdocsConfig {
|
|
|
193
225
|
robots?: BoltdocsRobotsConfig;
|
|
194
226
|
/** Low-level Vite configuration overrides */
|
|
195
227
|
vite?: vite.InlineConfig;
|
|
196
|
-
/** @deprecated Use theme instead */
|
|
197
|
-
themeConfig?: BoltdocsThemeConfig;
|
|
198
228
|
}
|
|
199
229
|
|
|
200
230
|
/**
|
|
@@ -317,9 +347,17 @@ interface SandboxEmbedOptions {
|
|
|
317
347
|
*/
|
|
318
348
|
interface BoltdocsTab {
|
|
319
349
|
id: string;
|
|
320
|
-
|
|
350
|
+
/** Text to display (can be a string or a map of translations) */
|
|
351
|
+
text: string | Record<string, string>;
|
|
321
352
|
icon?: string;
|
|
322
353
|
}
|
|
354
|
+
/**
|
|
355
|
+
* Props for the Sidebar component.
|
|
356
|
+
*/
|
|
357
|
+
interface SidebarProps {
|
|
358
|
+
routes: ComponentRoute[];
|
|
359
|
+
config: BoltdocsConfig;
|
|
360
|
+
}
|
|
323
361
|
/**
|
|
324
362
|
* Props for the OnThisPage (TOC) component.
|
|
325
363
|
*/
|
|
@@ -333,6 +371,13 @@ interface OnThisPageProps {
|
|
|
333
371
|
communityHelp?: string;
|
|
334
372
|
filePath?: string;
|
|
335
373
|
}
|
|
374
|
+
/**
|
|
375
|
+
* Props for the Tabs component.
|
|
376
|
+
*/
|
|
377
|
+
interface TabsProps {
|
|
378
|
+
tabs: BoltdocsTab[];
|
|
379
|
+
routes: ComponentRoute[];
|
|
380
|
+
}
|
|
336
381
|
/**
|
|
337
382
|
* Props for user-defined layout components (layout.tsx).
|
|
338
383
|
*/
|
|
@@ -343,7 +388,8 @@ interface LayoutProps {
|
|
|
343
388
|
* Unified type for navbar links.
|
|
344
389
|
*/
|
|
345
390
|
interface NavbarLink {
|
|
346
|
-
|
|
391
|
+
/** Label to display (can be a string or a map of translations) */
|
|
392
|
+
label: string | Record<string, string>;
|
|
347
393
|
href: string;
|
|
348
394
|
active: boolean;
|
|
349
395
|
/** Optional icon or string for external link indication */
|
|
@@ -352,4 +398,4 @@ interface NavbarLink {
|
|
|
352
398
|
items?: NavbarLink[];
|
|
353
399
|
}
|
|
354
400
|
|
|
355
|
-
export type { BoltdocsConfig as B, ComponentRoute as C, LayoutProps as L, NavbarLink as N, OnThisPageProps as O,
|
|
401
|
+
export type { BoltdocsConfig as B, ComponentRoute as C, LayoutProps as L, NavbarLink as N, OnThisPageProps as O, SandboxEmbedOptions as S, TabsProps as T, BoltdocsTab as a, CreateBoltdocsAppOptions as b, BoltdocsThemeConfig as c, SandboxFile as d, SandboxFiles as e, SandboxOptions as f, BoltdocsSocialLink as g, SidebarProps as h, SiteConfig as i };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "boltdocs",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "A lightweight documentation generator for React projects.",
|
|
5
5
|
"main": "dist/node/index.js",
|
|
6
6
|
"module": "dist/node/index.mjs",
|
|
@@ -22,15 +22,40 @@
|
|
|
22
22
|
"import": "./dist/client/index.mjs",
|
|
23
23
|
"require": "./dist/client/index.js"
|
|
24
24
|
},
|
|
25
|
+
"./hooks": {
|
|
26
|
+
"types": "./dist/hooks/index.d.ts",
|
|
27
|
+
"import": "./dist/hooks/index.mjs",
|
|
28
|
+
"require": "./dist/hooks/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./primitives": {
|
|
31
|
+
"types": "./dist/primitives/index.d.ts",
|
|
32
|
+
"import": "./dist/primitives/index.mjs",
|
|
33
|
+
"require": "./dist/primitives/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./base-ui": {
|
|
36
|
+
"types": "./dist/base-ui/index.d.ts",
|
|
37
|
+
"import": "./dist/base-ui/index.mjs",
|
|
38
|
+
"require": "./dist/base-ui/index.js"
|
|
39
|
+
},
|
|
40
|
+
"./mdx": {
|
|
41
|
+
"types": "./dist/mdx/index.d.ts",
|
|
42
|
+
"import": "./dist/mdx/index.mjs",
|
|
43
|
+
"require": "./dist/mdx/index.js"
|
|
44
|
+
},
|
|
45
|
+
"./integrations": {
|
|
46
|
+
"types": "./dist/integrations/index.d.ts",
|
|
47
|
+
"import": "./dist/integrations/index.mjs",
|
|
48
|
+
"require": "./dist/integrations/index.js"
|
|
49
|
+
},
|
|
25
50
|
"./client/hooks": {
|
|
26
|
-
"types": "./dist/
|
|
27
|
-
"import": "./dist/
|
|
28
|
-
"require": "./dist/
|
|
51
|
+
"types": "./dist/hooks/index.d.ts",
|
|
52
|
+
"import": "./dist/hooks/index.mjs",
|
|
53
|
+
"require": "./dist/hooks/index.js"
|
|
29
54
|
},
|
|
30
55
|
"./client/primitives": {
|
|
31
|
-
"types": "./dist/
|
|
32
|
-
"import": "./dist/
|
|
33
|
-
"require": "./dist/
|
|
56
|
+
"types": "./dist/primitives/index.d.ts",
|
|
57
|
+
"import": "./dist/primitives/index.mjs",
|
|
58
|
+
"require": "./dist/primitives/index.js"
|
|
34
59
|
},
|
|
35
60
|
"./client/types": {
|
|
36
61
|
"types": "./dist/client/types.d.ts",
|
|
@@ -63,6 +88,7 @@
|
|
|
63
88
|
"clsx": "^2.1.1",
|
|
64
89
|
"codesandbox": "^2.2.3",
|
|
65
90
|
"fast-glob": "^3.3.3",
|
|
91
|
+
"flexsearch": "^0.8.212",
|
|
66
92
|
"github-slugger": "^2.0.0",
|
|
67
93
|
"gray-matter": "^4.0.3",
|
|
68
94
|
"isomorphic-dompurify": "^3.7.1",
|
|
@@ -79,7 +105,8 @@
|
|
|
79
105
|
"tailwind-merge": "^3.5.0",
|
|
80
106
|
"unist-util-visit": "^5.1.0",
|
|
81
107
|
"vite": "^7.3.1",
|
|
82
|
-
"vite-plugin-image-optimizer": "^2.0.3"
|
|
108
|
+
"vite-plugin-image-optimizer": "^2.0.3",
|
|
109
|
+
"zustand": "^5.0.12"
|
|
83
110
|
},
|
|
84
111
|
"peerDependencies": {
|
|
85
112
|
"react": "^19.1.0",
|
package/src/client/app/index.tsx
CHANGED
|
@@ -2,7 +2,6 @@ import React, { useEffect, useState, useMemo } from 'react'
|
|
|
2
2
|
import ReactDOM from 'react-dom/client'
|
|
3
3
|
import { BrowserRouter, Routes, Route } from 'react-router-dom'
|
|
4
4
|
import { NotFound } from '@components/ui-base/not-found'
|
|
5
|
-
import { Loading } from '@components/ui-base/loading'
|
|
6
5
|
import { ThemeProvider } from './theme-context'
|
|
7
6
|
import type { ComponentRoute, CreateBoltdocsAppOptions } from '../types'
|
|
8
7
|
import type { BoltdocsConfig } from '@node/config'
|
|
@@ -17,6 +16,84 @@ import { DocsLayout } from './docs-layout'
|
|
|
17
16
|
import { MdxPage } from './mdx-page'
|
|
18
17
|
import { MdxComponentsProvider } from './mdx-components-context'
|
|
19
18
|
import { mdxComponentsDefault } from './mdx-component'
|
|
19
|
+
import { useRoutes } from '../hooks/use-routes'
|
|
20
|
+
import { useLocation } from 'react-router-dom'
|
|
21
|
+
import { useBoltdocsStore } from '../store/use-boltdocs-store'
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Updates the HTML lang and dir attributes based on the current locale configuration.
|
|
25
|
+
*/
|
|
26
|
+
function I18nUpdater() {
|
|
27
|
+
const { currentLocale, config } = useRoutes()
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (!config.i18n) return
|
|
31
|
+
const localeConfig = config.i18n.localeConfigs?.[currentLocale as string]
|
|
32
|
+
document.documentElement.lang =
|
|
33
|
+
localeConfig?.htmlLang || currentLocale || 'en'
|
|
34
|
+
document.documentElement.dir = localeConfig?.direction || 'ltr'
|
|
35
|
+
}, [currentLocale, config.i18n])
|
|
36
|
+
|
|
37
|
+
return null
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Synchronizes the Zustand store with the current URL pathname.
|
|
42
|
+
*/
|
|
43
|
+
function StoreSync() {
|
|
44
|
+
const location = useLocation()
|
|
45
|
+
const { config } = useRoutes()
|
|
46
|
+
const setLocale = useBoltdocsStore((s) => s.setLocale)
|
|
47
|
+
const setVersion = useBoltdocsStore((s) => s.setVersion)
|
|
48
|
+
const currentLocaleStore = useBoltdocsStore((s) => s.currentLocale)
|
|
49
|
+
const currentVersionStore = useBoltdocsStore((s) => s.currentVersion)
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
const parts = location.pathname.split('/').filter(Boolean)
|
|
53
|
+
let cIdx = 0
|
|
54
|
+
let detectedVersion = config.versions?.defaultVersion
|
|
55
|
+
let detectedLocale = config.i18n?.defaultLocale
|
|
56
|
+
|
|
57
|
+
// 0. Skip docs prefix if present
|
|
58
|
+
if (parts[cIdx] === 'docs') cIdx++
|
|
59
|
+
|
|
60
|
+
// 1. Version detection
|
|
61
|
+
if (config.versions && parts.length > cIdx) {
|
|
62
|
+
const versionMatch = config.versions.versions.find(
|
|
63
|
+
(v) => v.path === parts[cIdx],
|
|
64
|
+
)
|
|
65
|
+
if (versionMatch) {
|
|
66
|
+
detectedVersion = versionMatch.path
|
|
67
|
+
cIdx++
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 2. Locale detection
|
|
72
|
+
if (
|
|
73
|
+
config.i18n &&
|
|
74
|
+
parts.length > cIdx &&
|
|
75
|
+
config.i18n.locales[parts[cIdx]]
|
|
76
|
+
) {
|
|
77
|
+
detectedLocale = parts[cIdx]
|
|
78
|
+
} else if (config.i18n && parts.length === 0) {
|
|
79
|
+
// On root, use the stored preference if it exists, otherwise default
|
|
80
|
+
detectedLocale = currentLocaleStore || config.i18n.defaultLocale
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Only update if changed to avoid loops
|
|
84
|
+
if (detectedLocale !== currentLocaleStore) setLocale(detectedLocale)
|
|
85
|
+
if (detectedVersion !== currentVersionStore) setVersion(detectedVersion)
|
|
86
|
+
}, [
|
|
87
|
+
location.pathname,
|
|
88
|
+
config,
|
|
89
|
+
setLocale,
|
|
90
|
+
setVersion,
|
|
91
|
+
currentLocaleStore,
|
|
92
|
+
currentVersionStore,
|
|
93
|
+
])
|
|
94
|
+
|
|
95
|
+
return null
|
|
96
|
+
}
|
|
20
97
|
|
|
21
98
|
export function AppShell({
|
|
22
99
|
initialRoutes,
|
|
@@ -31,14 +108,17 @@ export function AppShell({
|
|
|
31
108
|
initialRoutes: ComponentRoute[]
|
|
32
109
|
initialConfig: BoltdocsConfig
|
|
33
110
|
docsDirName: string
|
|
34
|
-
modules: Record<
|
|
111
|
+
modules: Record<
|
|
112
|
+
string,
|
|
113
|
+
() => Promise<{ default: React.ComponentType<unknown> }>
|
|
114
|
+
>
|
|
35
115
|
hot?: CreateBoltdocsAppOptions['hot']
|
|
36
116
|
homePage?: React.ComponentType
|
|
37
117
|
externalPages?: Record<string, React.ComponentType>
|
|
38
118
|
components?: Record<string, React.ComponentType>
|
|
39
119
|
}) {
|
|
40
120
|
const [routesInfo, setRoutesInfo] = useState<ComponentRoute[]>(initialRoutes)
|
|
41
|
-
const [config] = useState(initialConfig)
|
|
121
|
+
const [config, setConfig] = useState(initialConfig)
|
|
42
122
|
const computedExternalPages = externalPages || {}
|
|
43
123
|
|
|
44
124
|
const resolvedRoutes = useMemo(() => {
|
|
@@ -53,16 +133,19 @@ export function AppShell({
|
|
|
53
133
|
(k) =>
|
|
54
134
|
k === `/${docsDirName}/${route.filePath}` || // Vite dev/build relative path
|
|
55
135
|
k.endsWith(`/${docsDirName}/${route.filePath}`) || // SSG absolute path fallback
|
|
56
|
-
k.endsWith(
|
|
136
|
+
k.endsWith(
|
|
137
|
+
`/${docsDirName}\\${route.filePath.replace(/\\/g, '/')}`,
|
|
138
|
+
), // Windows fallback
|
|
57
139
|
)
|
|
58
140
|
const loader = loaderKey ? modules[loaderKey] : null
|
|
59
141
|
|
|
60
142
|
return {
|
|
61
143
|
...route,
|
|
62
|
-
Component: React.lazy<React.ComponentType<
|
|
63
|
-
if (!loader)
|
|
144
|
+
Component: React.lazy<React.ComponentType<unknown>>(async () => {
|
|
145
|
+
if (!loader)
|
|
146
|
+
return { default: NotFound as React.ComponentType<unknown> }
|
|
64
147
|
const mod = await loader()
|
|
65
|
-
return mod
|
|
148
|
+
return mod as { default: React.ComponentType<unknown> }
|
|
66
149
|
}),
|
|
67
150
|
}
|
|
68
151
|
})
|
|
@@ -74,6 +157,9 @@ export function AppShell({
|
|
|
74
157
|
hot.on('boltdocs:routes-update', (newRoutes: ComponentRoute[]) => {
|
|
75
158
|
setRoutesInfo(newRoutes)
|
|
76
159
|
})
|
|
160
|
+
hot.on('boltdocs:config-update', (newConfig: BoltdocsConfig) => {
|
|
161
|
+
setConfig(newConfig)
|
|
162
|
+
})
|
|
77
163
|
}
|
|
78
164
|
}, [hot])
|
|
79
165
|
|
|
@@ -82,6 +168,8 @@ export function AppShell({
|
|
|
82
168
|
[customComponents],
|
|
83
169
|
)
|
|
84
170
|
|
|
171
|
+
const LoadingFallback = allComponents.Loading as React.ComponentType
|
|
172
|
+
|
|
85
173
|
return (
|
|
86
174
|
<ThemeProvider>
|
|
87
175
|
<MdxComponentsProvider components={allComponents}>
|
|
@@ -89,41 +177,16 @@ export function AppShell({
|
|
|
89
177
|
<BoltdocsRouterProvider>
|
|
90
178
|
<PreloadProvider routes={routesInfo} modules={modules}>
|
|
91
179
|
<ScrollHandler />
|
|
180
|
+
<StoreSync />
|
|
181
|
+
<I18nUpdater />
|
|
92
182
|
<Routes>
|
|
93
|
-
{/* Custom home page with user layout */}
|
|
94
|
-
{HomePage && (
|
|
95
|
-
<Route
|
|
96
|
-
path="/"
|
|
97
|
-
element={
|
|
98
|
-
<UserLayout>
|
|
99
|
-
<HomePage />
|
|
100
|
-
</UserLayout>
|
|
101
|
-
}
|
|
102
|
-
/>
|
|
103
|
-
)}
|
|
104
|
-
|
|
105
|
-
{/* Custom External Pages with user layout */}
|
|
106
|
-
{Object.entries(computedExternalPages).map(
|
|
107
|
-
([extPath, ExtComponent]) => (
|
|
108
|
-
<Route
|
|
109
|
-
key={extPath}
|
|
110
|
-
path={extPath}
|
|
111
|
-
element={
|
|
112
|
-
<UserLayout>
|
|
113
|
-
<ExtComponent />
|
|
114
|
-
</UserLayout>
|
|
115
|
-
}
|
|
116
|
-
/>
|
|
117
|
-
),
|
|
118
|
-
)}
|
|
119
|
-
|
|
120
183
|
<Route key="docs-layout" element={<DocsLayout />}>
|
|
121
184
|
{resolvedRoutes.map((route) => (
|
|
122
185
|
<Route
|
|
123
186
|
key={route.path}
|
|
124
187
|
path={route.path === '' ? '/' : route.path}
|
|
125
188
|
element={
|
|
126
|
-
<React.Suspense fallback={<
|
|
189
|
+
<React.Suspense fallback={<LoadingFallback />}>
|
|
127
190
|
<MdxPage Component={route.Component} />
|
|
128
191
|
</React.Suspense>
|
|
129
192
|
}
|
|
@@ -131,6 +194,63 @@ export function AppShell({
|
|
|
131
194
|
))}
|
|
132
195
|
</Route>
|
|
133
196
|
|
|
197
|
+
{/* Custom home page with user layout */}
|
|
198
|
+
{HomePage && (
|
|
199
|
+
<>
|
|
200
|
+
<Route
|
|
201
|
+
path="/"
|
|
202
|
+
element={
|
|
203
|
+
<UserLayout>
|
|
204
|
+
<HomePage />
|
|
205
|
+
</UserLayout>
|
|
206
|
+
}
|
|
207
|
+
/>
|
|
208
|
+
{config.i18n &&
|
|
209
|
+
Object.keys(config.i18n.locales).map((locale) => (
|
|
210
|
+
<Route
|
|
211
|
+
key={`home-${locale}`}
|
|
212
|
+
path={`/${locale}`}
|
|
213
|
+
element={
|
|
214
|
+
<UserLayout>
|
|
215
|
+
<HomePage />
|
|
216
|
+
</UserLayout>
|
|
217
|
+
}
|
|
218
|
+
/>
|
|
219
|
+
))}
|
|
220
|
+
</>
|
|
221
|
+
)}
|
|
222
|
+
|
|
223
|
+
{/* Custom External Pages with user layout */}
|
|
224
|
+
{Object.entries(computedExternalPages).map(
|
|
225
|
+
([extPath, ExtComponent]) => {
|
|
226
|
+
const cleanPath = extPath === '/' ? '' : extPath
|
|
227
|
+
return (
|
|
228
|
+
<React.Fragment key={extPath}>
|
|
229
|
+
<Route
|
|
230
|
+
path={extPath}
|
|
231
|
+
element={
|
|
232
|
+
<UserLayout>
|
|
233
|
+
<ExtComponent />
|
|
234
|
+
</UserLayout>
|
|
235
|
+
}
|
|
236
|
+
/>
|
|
237
|
+
{config.i18n &&
|
|
238
|
+
Object.keys(config.i18n.locales).map((locale) => (
|
|
239
|
+
<Route
|
|
240
|
+
key={`${extPath}-${locale}`}
|
|
241
|
+
path={`/${locale}${cleanPath}`}
|
|
242
|
+
element={
|
|
243
|
+
<UserLayout>
|
|
244
|
+
<ExtComponent />
|
|
245
|
+
</UserLayout>
|
|
246
|
+
}
|
|
247
|
+
/>
|
|
248
|
+
))}
|
|
249
|
+
</React.Fragment>
|
|
250
|
+
)
|
|
251
|
+
},
|
|
252
|
+
)}
|
|
253
|
+
|
|
134
254
|
<Route
|
|
135
255
|
path="*"
|
|
136
256
|
element={
|
|
@@ -6,14 +6,15 @@ const Heading = ({
|
|
|
6
6
|
level,
|
|
7
7
|
id,
|
|
8
8
|
children,
|
|
9
|
+
...props
|
|
9
10
|
}: {
|
|
10
11
|
level: number
|
|
11
12
|
id?: string
|
|
12
13
|
children?: React.ReactNode
|
|
13
|
-
}) => {
|
|
14
|
-
const Tag = `h${level}` as
|
|
14
|
+
} & React.HTMLAttributes<HTMLHeadingElement>) => {
|
|
15
|
+
const Tag = `h${level}` as any
|
|
15
16
|
return (
|
|
16
|
-
<Tag id={id} className="boltdocs-heading">
|
|
17
|
+
<Tag id={id} {...props} className="boltdocs-heading">
|
|
17
18
|
{children}
|
|
18
19
|
{id && (
|
|
19
20
|
<a href={`#${id}`} className="header-anchor" aria-label="Anchor">
|
|
@@ -24,8 +25,11 @@ const Heading = ({
|
|
|
24
25
|
)
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
import { Loading } from '@components/ui-base/loading'
|
|
29
|
+
|
|
27
30
|
export const mdxComponentsDefault = {
|
|
28
31
|
...MdxComponents,
|
|
32
|
+
Loading,
|
|
29
33
|
h1: (props: React.HTMLAttributes<HTMLHeadingElement>) => (
|
|
30
34
|
<Heading level={1} {...props} />
|
|
31
35
|
),
|
|
@@ -1,45 +1,75 @@
|
|
|
1
1
|
import { createContext, use, useEffect, useState } from 'react'
|
|
2
2
|
|
|
3
|
-
type Theme = 'light' | 'dark'
|
|
3
|
+
type Theme = 'light' | 'dark' | 'system'
|
|
4
4
|
|
|
5
5
|
interface ThemeContextType {
|
|
6
6
|
theme: Theme
|
|
7
|
-
|
|
7
|
+
resolvedTheme: 'light' | 'dark'
|
|
8
8
|
setTheme: (theme: Theme) => void
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
const ThemeContext = createContext<ThemeContextType | undefined>(undefined)
|
|
12
12
|
|
|
13
13
|
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
|
14
|
-
const [theme, setThemeState] = useState<Theme>('
|
|
14
|
+
const [theme, setThemeState] = useState<Theme>('system')
|
|
15
|
+
const [resolvedTheme, setResolvedTheme] = useState<'light' | 'dark'>('dark')
|
|
15
16
|
const [mounted, setMounted] = useState(false)
|
|
16
17
|
|
|
18
|
+
// Initialize theme from localStorage and set internal resolved theme
|
|
17
19
|
useEffect(() => {
|
|
18
20
|
setMounted(true)
|
|
19
|
-
const stored = localStorage.getItem('boltdocs-theme')
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
setThemeState(prefersDark ? 'dark' : 'light')
|
|
27
|
-
}
|
|
21
|
+
const stored = localStorage.getItem('boltdocs-theme') as Theme | null
|
|
22
|
+
const initialTheme =
|
|
23
|
+
stored === 'light' || stored === 'dark' || stored === 'system'
|
|
24
|
+
? stored
|
|
25
|
+
: 'system'
|
|
26
|
+
|
|
27
|
+
setThemeState(initialTheme)
|
|
28
28
|
|
|
29
29
|
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
|
|
31
|
+
const updateResolved = (currentTheme: Theme, isDark: boolean) => {
|
|
32
|
+
if (currentTheme === 'system') {
|
|
33
|
+
setResolvedTheme(isDark ? 'dark' : 'light')
|
|
34
|
+
} else {
|
|
35
|
+
setResolvedTheme(currentTheme as 'light' | 'dark')
|
|
33
36
|
}
|
|
34
37
|
}
|
|
38
|
+
|
|
39
|
+
updateResolved(initialTheme, mediaQuery.matches)
|
|
40
|
+
|
|
41
|
+
const handleChange = (e: MediaQueryListEvent) => {
|
|
42
|
+
// Re-read current theme state from some stable ref would be better, but we can capture it
|
|
43
|
+
// actually, the second useEffect will handle the source of truth,
|
|
44
|
+
// but this listener ensures 'system' updates instantly.
|
|
45
|
+
setResolvedTheme((prevResolved) => {
|
|
46
|
+
const currentTheme =
|
|
47
|
+
(localStorage.getItem('boltdocs-theme') as Theme) || 'system'
|
|
48
|
+
if (currentTheme === 'system') {
|
|
49
|
+
return e.matches ? 'dark' : 'light'
|
|
50
|
+
}
|
|
51
|
+
return prevResolved
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
35
55
|
mediaQuery.addEventListener('change', handleChange)
|
|
36
56
|
return () => mediaQuery.removeEventListener('change', handleChange)
|
|
37
57
|
}, [])
|
|
38
58
|
|
|
59
|
+
// Sync with DOM and resolved theme when theme preference changes
|
|
39
60
|
useEffect(() => {
|
|
40
61
|
if (!mounted) return
|
|
62
|
+
|
|
63
|
+
const isSystemDark = window.matchMedia(
|
|
64
|
+
'(prefers-color-scheme: dark)',
|
|
65
|
+
).matches
|
|
66
|
+
const nextResolved =
|
|
67
|
+
theme === 'system' ? (isSystemDark ? 'dark' : 'light') : theme
|
|
68
|
+
|
|
69
|
+
setResolvedTheme(nextResolved as 'light' | 'dark')
|
|
70
|
+
|
|
41
71
|
const root = document.documentElement
|
|
42
|
-
if (
|
|
72
|
+
if (nextResolved === 'light') {
|
|
43
73
|
root.classList.add('theme-light')
|
|
44
74
|
root.dataset.theme = 'light'
|
|
45
75
|
} else {
|
|
@@ -48,19 +78,13 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
|
|
48
78
|
}
|
|
49
79
|
}, [theme, mounted])
|
|
50
80
|
|
|
51
|
-
const toggleTheme = () => {
|
|
52
|
-
const newTheme = theme === 'dark' ? 'light' : 'dark'
|
|
53
|
-
setThemeState(newTheme)
|
|
54
|
-
localStorage.setItem('boltdocs-theme', newTheme)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
81
|
const setTheme = (newTheme: Theme) => {
|
|
58
82
|
setThemeState(newTheme)
|
|
59
83
|
localStorage.setItem('boltdocs-theme', newTheme)
|
|
60
84
|
}
|
|
61
85
|
|
|
62
86
|
return (
|
|
63
|
-
<ThemeContext.Provider value={{ theme,
|
|
87
|
+
<ThemeContext.Provider value={{ theme, resolvedTheme, setTheme }}>
|
|
64
88
|
{children}
|
|
65
89
|
</ThemeContext.Provider>
|
|
66
90
|
)
|