@allbluecn/web-app 0.10.0 → 0.10.3
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/package.json +8 -3
- package/src/components/layout/sidebar-03/nav-secondary.tsx +57 -47
- package/src/components/mdx.tsx +9 -0
- package/src/lib/changelog/sdk-versions.ts +20 -20
- package/src/lib/fumadocs-i18n.ts +7 -0
- package/src/lib/layout.shared.tsx +40 -0
- package/src/lib/source.ts +15 -0
- package/src/routeTree.gen.ts +65 -0
- package/src/routes/$lang/docs/$/route.lazy.tsx +62 -0
- package/src/routes/$lang/docs/$/route.tsx +52 -0
- package/src/routes/-tests/docs-redirect.test.ts +45 -0
- package/src/routes/__root.tsx +13 -7
- package/src/routes/api/search.ts +13 -0
- package/src/routes/docs.tsx +9 -0
- package/src/styles/globals.css +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@allbluecn/web-app",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.3",
|
|
4
4
|
"license": "AGPL-3.0-or-later",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -66,6 +66,7 @@
|
|
|
66
66
|
"@floating-ui/react": "^0.27.19",
|
|
67
67
|
"@fontsource-variable/geist": "^5.3.0",
|
|
68
68
|
"@fontsource-variable/geist-mono": "^5.3.0",
|
|
69
|
+
"@fumadocs/language": "^0.2.4",
|
|
69
70
|
"@iconify/react": "^6.0.2",
|
|
70
71
|
"@lobehub/icons": "^5.16.0",
|
|
71
72
|
"@open-file-viewer/core": "^0.1.43",
|
|
@@ -146,6 +147,9 @@
|
|
|
146
147
|
"date-fns": "^4.4.0",
|
|
147
148
|
"date-fns-tz": "^3.2.0",
|
|
148
149
|
"emoji-picker-react": "^4.19.1",
|
|
150
|
+
"fumadocs-core": "16.15.8",
|
|
151
|
+
"fumadocs-mdx": "15.4.0",
|
|
152
|
+
"fumadocs-ui": "16.15.8",
|
|
149
153
|
"ioredis": "^5.11.0",
|
|
150
154
|
"is-hotkey": "^0.2.0",
|
|
151
155
|
"isomorphic-dompurify": "^3.22.0",
|
|
@@ -179,8 +183,8 @@
|
|
|
179
183
|
"three": "^0.180.0",
|
|
180
184
|
"tw-animate-css": "^1.4.0",
|
|
181
185
|
"zod": "^4.4.3",
|
|
182
|
-
"@allbluecn/database": "0.
|
|
183
|
-
"@allbluecn/kernel": "0.
|
|
186
|
+
"@allbluecn/database": "0.5.0",
|
|
187
|
+
"@allbluecn/kernel": "0.6.0"
|
|
184
188
|
},
|
|
185
189
|
"devDependencies": {
|
|
186
190
|
"@babel/parser": "^8.0.4",
|
|
@@ -200,6 +204,7 @@
|
|
|
200
204
|
"@types/bcryptjs": "^2.4.6",
|
|
201
205
|
"@types/better-sqlite3": "^7.6.13",
|
|
202
206
|
"@types/is-hotkey": "^0.1.10",
|
|
207
|
+
"@types/mdx": "^2.0.14",
|
|
203
208
|
"@types/node": "^24.13.3",
|
|
204
209
|
"@types/react": "^19",
|
|
205
210
|
"@types/react-dom": "^19",
|
|
@@ -4,24 +4,19 @@ import {
|
|
|
4
4
|
SidebarMenuItem,
|
|
5
5
|
useSidebar,
|
|
6
6
|
} from "@/components/ui/sidebar"
|
|
7
|
-
import { BookOpen, ScrollText } from "lucide-react"
|
|
7
|
+
import { BookOpen, ExternalLink, ScrollText } from "lucide-react"
|
|
8
8
|
import { cn } from "@/lib/utils"
|
|
9
9
|
import { Link } from "@tanstack/react-router"
|
|
10
10
|
import { APP_VERSION_DISPLAY } from "@/lib/app-version"
|
|
11
|
+
import { getLocale } from "@/paraglide/runtime.js"
|
|
12
|
+
import { m } from "@/paraglide/messages.js"
|
|
11
13
|
|
|
12
|
-
const
|
|
14
|
+
const changelogItems = [
|
|
13
15
|
{
|
|
14
|
-
title:
|
|
15
|
-
Icon: BookOpen,
|
|
16
|
-
href: "/docs",
|
|
17
|
-
tooltip: "使用文档",
|
|
18
|
-
external: true,
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
title: "开发者日志",
|
|
16
|
+
title: m.common_changelog_title(),
|
|
22
17
|
Icon: ScrollText,
|
|
23
18
|
href: "/changelog",
|
|
24
|
-
tooltip:
|
|
19
|
+
tooltip: m.common_changelog_title(),
|
|
25
20
|
badge: APP_VERSION_DISPLAY,
|
|
26
21
|
},
|
|
27
22
|
]
|
|
@@ -29,6 +24,7 @@ const items = [
|
|
|
29
24
|
export function NavSecondary() {
|
|
30
25
|
const { state } = useSidebar()
|
|
31
26
|
const isCollapsed = state === "collapsed"
|
|
27
|
+
const lang = getLocale()
|
|
32
28
|
|
|
33
29
|
return (
|
|
34
30
|
<SidebarMenu className={cn("space-y-1", isCollapsed && "px-1")}>
|
|
@@ -37,7 +33,42 @@ export function NavSecondary() {
|
|
|
37
33
|
{/* 资源 */}
|
|
38
34
|
</div>
|
|
39
35
|
)}
|
|
40
|
-
|
|
36
|
+
<SidebarMenuItem className="relative group/docs">
|
|
37
|
+
<SidebarMenuButton
|
|
38
|
+
asChild
|
|
39
|
+
tooltip={m.common_docs_title()}
|
|
40
|
+
className={cn(
|
|
41
|
+
"flex items-center rounded-lg px-2 pr-8",
|
|
42
|
+
isCollapsed && "justify-center"
|
|
43
|
+
)}
|
|
44
|
+
>
|
|
45
|
+
<Link
|
|
46
|
+
to="/$lang/docs/$"
|
|
47
|
+
params={{ lang, _splat: "" }}
|
|
48
|
+
className="flex w-full items-center gap-3 text-muted-foreground hover:text-foreground"
|
|
49
|
+
style={{ width: "100%", height: "100%" }}
|
|
50
|
+
>
|
|
51
|
+
<BookOpen className="text-muted-foreground size-3.5" />
|
|
52
|
+
{!isCollapsed && (
|
|
53
|
+
<span className="text-xs text-muted-foreground">
|
|
54
|
+
{m.common_docs_title()}
|
|
55
|
+
</span>
|
|
56
|
+
)}
|
|
57
|
+
</Link>
|
|
58
|
+
</SidebarMenuButton>
|
|
59
|
+
{!isCollapsed && (
|
|
60
|
+
<a
|
|
61
|
+
href={`/${lang}/docs`}
|
|
62
|
+
target="_blank"
|
|
63
|
+
rel="noopener noreferrer"
|
|
64
|
+
aria-label={m.common_docs_title()}
|
|
65
|
+
className="absolute right-1.5 top-1/2 -translate-y-1/2 hidden items-center rounded-md p-1 text-muted-foreground/60 hover:text-foreground group-hover/docs:flex"
|
|
66
|
+
>
|
|
67
|
+
<ExternalLink className="size-3" />
|
|
68
|
+
</a>
|
|
69
|
+
)}
|
|
70
|
+
</SidebarMenuItem>
|
|
71
|
+
{changelogItems.map((item) => (
|
|
41
72
|
<SidebarMenuItem key={item.title}>
|
|
42
73
|
<SidebarMenuButton
|
|
43
74
|
asChild
|
|
@@ -47,44 +78,23 @@ export function NavSecondary() {
|
|
|
47
78
|
isCollapsed && "justify-center"
|
|
48
79
|
)}
|
|
49
80
|
>
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
target="_blank"
|
|
58
|
-
rel="noopener noreferrer"
|
|
59
|
-
style={{ width: "100%", height: "100%" }}
|
|
60
|
-
>
|
|
61
|
-
<item.Icon className={cn("text-muted-foreground", !isCollapsed && "size-3.5!")} />
|
|
81
|
+
<Link
|
|
82
|
+
to={item.href}
|
|
83
|
+
className="flex w-full items-center justify-between gap-3 text-muted-foreground hover:text-foreground"
|
|
84
|
+
style={{ width: "100%", height: "100%" }}
|
|
85
|
+
>
|
|
86
|
+
<div className="flex items-center gap-3">
|
|
87
|
+
<item.Icon className="text-muted-foreground size-3.5" />
|
|
62
88
|
{!isCollapsed && (
|
|
63
89
|
<span className="text-xs text-muted-foreground">{item.title}</span>
|
|
64
90
|
)}
|
|
65
|
-
</
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
)}
|
|
73
|
-
style={{ width: "100%", height: "100%" }}
|
|
74
|
-
>
|
|
75
|
-
<div className="flex items-center gap-3">
|
|
76
|
-
<item.Icon className={cn("text-muted-foreground", !isCollapsed && "size-3.5!")} />
|
|
77
|
-
{!isCollapsed && (
|
|
78
|
-
<span className="text-xs text-muted-foreground">{item.title}</span>
|
|
79
|
-
)}
|
|
80
|
-
</div>
|
|
81
|
-
{!isCollapsed && item.badge && (
|
|
82
|
-
<code className="text-[10px] font-mono text-muted-foreground/60 bg-muted/50 px-1.5 py-0.5 rounded shrink-0">
|
|
83
|
-
{item.badge}
|
|
84
|
-
</code>
|
|
85
|
-
)}
|
|
86
|
-
</Link>
|
|
87
|
-
)}
|
|
91
|
+
</div>
|
|
92
|
+
{!isCollapsed && item.badge && (
|
|
93
|
+
<code className="text-[10px] font-mono text-muted-foreground/60 bg-muted/50 px-1.5 py-0.5 rounded shrink-0">
|
|
94
|
+
{item.badge}
|
|
95
|
+
</code>
|
|
96
|
+
)}
|
|
97
|
+
</Link>
|
|
88
98
|
</SidebarMenuButton>
|
|
89
99
|
</SidebarMenuItem>
|
|
90
100
|
))}
|
|
@@ -10,7 +10,7 @@ export const SDK_GROUPS: SdkGroup[] = [
|
|
|
10
10
|
"name": "@tanstack/react-start",
|
|
11
11
|
"displayName": "TanStack Start",
|
|
12
12
|
"version": "v1.168.49",
|
|
13
|
-
"lastUpdate": "Sep
|
|
13
|
+
"lastUpdate": "Sep 9, 2026",
|
|
14
14
|
"source": "apps/web/package.json#dependencies",
|
|
15
15
|
"docsUrl": "https://tanstack.com/start/latest"
|
|
16
16
|
},
|
|
@@ -18,7 +18,7 @@ export const SDK_GROUPS: SdkGroup[] = [
|
|
|
18
18
|
"name": "@tanstack/react-router",
|
|
19
19
|
"displayName": "TanStack Router",
|
|
20
20
|
"version": "v1.170.32",
|
|
21
|
-
"lastUpdate": "Sep
|
|
21
|
+
"lastUpdate": "Sep 9, 2026",
|
|
22
22
|
"source": "apps/web/package.json#dependencies",
|
|
23
23
|
"docsUrl": "https://tanstack.com/router/latest"
|
|
24
24
|
},
|
|
@@ -26,7 +26,7 @@ export const SDK_GROUPS: SdkGroup[] = [
|
|
|
26
26
|
"name": "@tanstack/react-query",
|
|
27
27
|
"displayName": "TanStack Query",
|
|
28
28
|
"version": "v5.102.8",
|
|
29
|
-
"lastUpdate": "Sep
|
|
29
|
+
"lastUpdate": "Sep 9, 2026",
|
|
30
30
|
"source": "apps/web/package.json#dependencies",
|
|
31
31
|
"docsUrl": "https://tanstack.com/query/latest"
|
|
32
32
|
},
|
|
@@ -34,7 +34,7 @@ export const SDK_GROUPS: SdkGroup[] = [
|
|
|
34
34
|
"name": "@tanstack/ai",
|
|
35
35
|
"displayName": "TanStack AI",
|
|
36
36
|
"version": "v0.52.0",
|
|
37
|
-
"lastUpdate": "Sep
|
|
37
|
+
"lastUpdate": "Sep 9, 2026",
|
|
38
38
|
"source": "apps/web/package.json#dependencies",
|
|
39
39
|
"docsUrl": "https://tanstack.com/ai/latest"
|
|
40
40
|
},
|
|
@@ -42,7 +42,7 @@ export const SDK_GROUPS: SdkGroup[] = [
|
|
|
42
42
|
"name": "@tanstack/react-table",
|
|
43
43
|
"displayName": "TanStack Table",
|
|
44
44
|
"version": "v9.2.3",
|
|
45
|
-
"lastUpdate": "Sep
|
|
45
|
+
"lastUpdate": "Sep 9, 2026",
|
|
46
46
|
"source": "apps/web/package.json#dependencies",
|
|
47
47
|
"docsUrl": "https://tanstack.com/table/latest"
|
|
48
48
|
},
|
|
@@ -50,7 +50,7 @@ export const SDK_GROUPS: SdkGroup[] = [
|
|
|
50
50
|
"name": "@tanstack/react-form",
|
|
51
51
|
"displayName": "TanStack Form",
|
|
52
52
|
"version": "v2.0.0-alpha.2",
|
|
53
|
-
"lastUpdate": "Sep
|
|
53
|
+
"lastUpdate": "Sep 9, 2026",
|
|
54
54
|
"source": "apps/web/package.json#dependencies",
|
|
55
55
|
"docsUrl": "https://tanstack.com/form/latest"
|
|
56
56
|
},
|
|
@@ -58,7 +58,7 @@ export const SDK_GROUPS: SdkGroup[] = [
|
|
|
58
58
|
"name": "@tanstack/charts",
|
|
59
59
|
"displayName": "TanStack Charts",
|
|
60
60
|
"version": "v0.16.0",
|
|
61
|
-
"lastUpdate": "Sep
|
|
61
|
+
"lastUpdate": "Sep 9, 2026",
|
|
62
62
|
"source": "apps/admin/package.json#dependencies",
|
|
63
63
|
"docsUrl": "https://tanstack.com/charts/latest"
|
|
64
64
|
},
|
|
@@ -66,7 +66,7 @@ export const SDK_GROUPS: SdkGroup[] = [
|
|
|
66
66
|
"name": "@tanstack/react-pacer",
|
|
67
67
|
"displayName": "TanStack Pacer",
|
|
68
68
|
"version": "v0.23.0",
|
|
69
|
-
"lastUpdate": "Sep
|
|
69
|
+
"lastUpdate": "Sep 9, 2026",
|
|
70
70
|
"source": "apps/web/package.json#dependencies",
|
|
71
71
|
"docsUrl": "https://tanstack.com/pacer/latest"
|
|
72
72
|
},
|
|
@@ -74,7 +74,7 @@ export const SDK_GROUPS: SdkGroup[] = [
|
|
|
74
74
|
"name": "@tanstack/react-hotkeys",
|
|
75
75
|
"displayName": "TanStack Hotkeys",
|
|
76
76
|
"version": "v0.10.0",
|
|
77
|
-
"lastUpdate": "Sep
|
|
77
|
+
"lastUpdate": "Sep 9, 2026",
|
|
78
78
|
"source": "apps/web/package.json#dependencies",
|
|
79
79
|
"docsUrl": "https://tanstack.com/hotkeys/latest"
|
|
80
80
|
},
|
|
@@ -82,7 +82,7 @@ export const SDK_GROUPS: SdkGroup[] = [
|
|
|
82
82
|
"name": "@tanstack/react-virtual",
|
|
83
83
|
"displayName": "TanStack Virtual",
|
|
84
84
|
"version": "v3.14.10",
|
|
85
|
-
"lastUpdate": "Sep
|
|
85
|
+
"lastUpdate": "Sep 9, 2026",
|
|
86
86
|
"source": "apps/admin/package.json#dependencies",
|
|
87
87
|
"docsUrl": "https://tanstack.com/virtual/latest"
|
|
88
88
|
}
|
|
@@ -95,15 +95,15 @@ export const SDK_GROUPS: SdkGroup[] = [
|
|
|
95
95
|
"items": [
|
|
96
96
|
{
|
|
97
97
|
"name": "@allbluecn/web-app",
|
|
98
|
-
"version": "v0.10.
|
|
99
|
-
"lastUpdate": "Sep
|
|
98
|
+
"version": "v0.10.3",
|
|
99
|
+
"lastUpdate": "Sep 9, 2026",
|
|
100
100
|
"source": "apps/web/package.json",
|
|
101
101
|
"docsUrl": "https://github.com/allbluecn/allblue"
|
|
102
102
|
},
|
|
103
103
|
{
|
|
104
104
|
"name": "@allbluecn/kernel",
|
|
105
|
-
"version": "v0.
|
|
106
|
-
"lastUpdate": "Sep
|
|
105
|
+
"version": "v0.6.0",
|
|
106
|
+
"lastUpdate": "Sep 9, 2026",
|
|
107
107
|
"source": "packages/kernel/package.json",
|
|
108
108
|
"docsUrl": "https://github.com/allbluecn/allblue"
|
|
109
109
|
}
|
|
@@ -117,42 +117,42 @@ export const SDK_GROUPS: SdkGroup[] = [
|
|
|
117
117
|
{
|
|
118
118
|
"name": "Vite",
|
|
119
119
|
"version": "v8.1.5",
|
|
120
|
-
"lastUpdate": "Sep
|
|
120
|
+
"lastUpdate": "Sep 9, 2026",
|
|
121
121
|
"source": "apps/web/package.json#devDependencies",
|
|
122
122
|
"docsUrl": "https://vite.dev/"
|
|
123
123
|
},
|
|
124
124
|
{
|
|
125
125
|
"name": "Node.js",
|
|
126
126
|
"version": "v24.19.0",
|
|
127
|
-
"lastUpdate": "Sep
|
|
127
|
+
"lastUpdate": "Sep 9, 2026",
|
|
128
128
|
"source": ".nvmrc",
|
|
129
129
|
"docsUrl": "https://nodejs.org/"
|
|
130
130
|
},
|
|
131
131
|
{
|
|
132
132
|
"name": "Prisma",
|
|
133
133
|
"version": "v7.8.0",
|
|
134
|
-
"lastUpdate": "Sep
|
|
134
|
+
"lastUpdate": "Sep 9, 2026",
|
|
135
135
|
"source": "packages/database/package.json#devDependencies",
|
|
136
136
|
"docsUrl": "https://www.prisma.io/docs"
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
139
|
"name": "shadcn UI",
|
|
140
140
|
"version": "v1.2.4",
|
|
141
|
-
"lastUpdate": "Sep
|
|
141
|
+
"lastUpdate": "Sep 9, 2026",
|
|
142
142
|
"source": "apps/web/package.json#dependencies",
|
|
143
143
|
"docsUrl": "https://ui.shadcn.com/"
|
|
144
144
|
},
|
|
145
145
|
{
|
|
146
146
|
"name": "TipTap",
|
|
147
147
|
"version": "v3.27.4",
|
|
148
|
-
"lastUpdate": "Sep
|
|
148
|
+
"lastUpdate": "Sep 9, 2026",
|
|
149
149
|
"source": "apps/web/package.json#dependencies",
|
|
150
150
|
"docsUrl": "https://tiptap.dev/"
|
|
151
151
|
},
|
|
152
152
|
{
|
|
153
153
|
"name": "Lucide React",
|
|
154
154
|
"version": "v1.27.0",
|
|
155
|
-
"lastUpdate": "Sep
|
|
155
|
+
"lastUpdate": "Sep 9, 2026",
|
|
156
156
|
"source": "apps/web/package.json#dependencies",
|
|
157
157
|
"docsUrl": "https://lucide.dev/"
|
|
158
158
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'
|
|
2
|
+
import { uiTranslations } from 'fumadocs-ui/i18n'
|
|
3
|
+
import { zhCN } from '@fumadocs/language/zh-cn'
|
|
4
|
+
import { fumadocsI18n } from './fumadocs-i18n'
|
|
5
|
+
|
|
6
|
+
export type DocsLang = 'zh' | 'en'
|
|
7
|
+
|
|
8
|
+
const DOCS_LANGS: readonly DocsLang[] = ['zh', 'en'] as const
|
|
9
|
+
|
|
10
|
+
export function isDocsLang(v: string): v is DocsLang {
|
|
11
|
+
return DOCS_LANGS.includes(v as DocsLang)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const translations = fumadocsI18n
|
|
15
|
+
.translations()
|
|
16
|
+
.extend(uiTranslations())
|
|
17
|
+
.preset('zh', zhCN())
|
|
18
|
+
.add({
|
|
19
|
+
zh: {
|
|
20
|
+
displayName: '简体中文',
|
|
21
|
+
},
|
|
22
|
+
en: {
|
|
23
|
+
displayName: 'English',
|
|
24
|
+
},
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
export function baseOptions(lang: DocsLang): BaseLayoutProps {
|
|
28
|
+
return {
|
|
29
|
+
nav: {
|
|
30
|
+
title: lang === 'en' ? 'AllBlue Docs' : 'AllBlue 使用文档',
|
|
31
|
+
url: `/${lang}/docs`,
|
|
32
|
+
},
|
|
33
|
+
links: [
|
|
34
|
+
{
|
|
35
|
+
text: lang === 'en' ? 'Back to App' : '返回应用',
|
|
36
|
+
url: '/dashboard',
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineDocs } from 'fumadocs-mdx/macro'
|
|
2
|
+
import { loader } from 'fumadocs-core/source'
|
|
3
|
+
import { fumadocsI18n } from './fumadocs-i18n'
|
|
4
|
+
|
|
5
|
+
const docs = defineDocs({
|
|
6
|
+
dir: 'content/docs',
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
export const source = loader({
|
|
10
|
+
i18n: fumadocsI18n,
|
|
11
|
+
baseUrl: '/docs',
|
|
12
|
+
source: docs.toFumadocsSource(),
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
export { docs }
|
package/src/routeTree.gen.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { Route as rootRouteImport } from './routes/__root'
|
|
|
14
14
|
import { Route as LoginRouteRouteImport } from './routes/_login/route'
|
|
15
15
|
import { Route as NavRouteRouteImport } from './routes/_nav/route'
|
|
16
16
|
import { Route as PublicRouteRouteImport } from './routes/_public/route'
|
|
17
|
+
import { Route as DocsRouteImport } from './routes/docs'
|
|
17
18
|
import { Route as ShareRouteRouteImport } from './routes/share/route'
|
|
18
19
|
import { Route as WorkspaceRouteImport } from './routes/workspace'
|
|
19
20
|
import { Route as _desktopOnboardingRouteRouteImport } from './routes/__desktop/onboarding/route'
|
|
@@ -46,11 +47,13 @@ import { Route as PublicPricingRouteRouteImport } from './routes/_public/pricing
|
|
|
46
47
|
import { Route as ApiBillingWebhookRouteImport } from './routes/api/billing-webhook'
|
|
47
48
|
import { Route as ApiChatRouteImport } from './routes/api/chat'
|
|
48
49
|
import { Route as ApiHealthRouteImport } from './routes/api/health'
|
|
50
|
+
import { Route as ApiSearchRouteImport } from './routes/api/search'
|
|
49
51
|
import { Route as ApiStoryAiRouteImport } from './routes/api/story-ai'
|
|
50
52
|
import { Route as InviteTokenRouteRouteImport } from './routes/invite/$token/route'
|
|
51
53
|
import { Route as ServerAiStreamRouteImport } from './routes/server.ai-stream'
|
|
52
54
|
import { Route as ServerWsRouteImport } from './routes/server.ws'
|
|
53
55
|
import { Route as ShareVersionIdRouteRouteImport } from './routes/share/$versionId/route'
|
|
56
|
+
import { Route as LangDocsSplatRouteRouteImport } from './routes/$lang/docs/$/route'
|
|
54
57
|
import { Route as NavAppsAnyhubRouteRouteImport } from './routes/_nav/apps/anyhub/route'
|
|
55
58
|
import { Route as NavChatThreadIdRouteImport } from './routes/_nav/chat/$threadId'
|
|
56
59
|
import { Route as NavDesignIndexRouteImport } from './routes/_nav/design/index'
|
|
@@ -160,6 +163,11 @@ const PublicRouteRoute = PublicRouteRouteImport.update({
|
|
|
160
163
|
id: '/_public',
|
|
161
164
|
getParentRoute: () => rootRouteImport,
|
|
162
165
|
} as any)
|
|
166
|
+
const DocsRoute = DocsRouteImport.update({
|
|
167
|
+
id: '/docs',
|
|
168
|
+
path: '/docs',
|
|
169
|
+
getParentRoute: () => rootRouteImport,
|
|
170
|
+
} as any)
|
|
163
171
|
const ShareRouteRoute = ShareRouteRouteImport.update({
|
|
164
172
|
id: '/share',
|
|
165
173
|
path: '/share',
|
|
@@ -346,6 +354,11 @@ const ApiHealthRoute = ApiHealthRouteImport.update({
|
|
|
346
354
|
path: '/api/health',
|
|
347
355
|
getParentRoute: () => rootRouteImport,
|
|
348
356
|
} as any)
|
|
357
|
+
const ApiSearchRoute = ApiSearchRouteImport.update({
|
|
358
|
+
id: '/api/search',
|
|
359
|
+
path: '/api/search',
|
|
360
|
+
getParentRoute: () => rootRouteImport,
|
|
361
|
+
} as any)
|
|
349
362
|
const ApiStoryAiRoute = ApiStoryAiRouteImport.update({
|
|
350
363
|
id: '/api/story-ai',
|
|
351
364
|
path: '/api/story-ai',
|
|
@@ -375,6 +388,13 @@ const ShareVersionIdRouteRoute = ShareVersionIdRouteRouteImport.update({
|
|
|
375
388
|
} as any).lazy(() =>
|
|
376
389
|
import('./routes/share/$versionId/route.lazy').then((d) => d.Route),
|
|
377
390
|
)
|
|
391
|
+
const LangDocsSplatRouteRoute = LangDocsSplatRouteRouteImport.update({
|
|
392
|
+
id: '/$lang/docs/$',
|
|
393
|
+
path: '/$lang/docs/$',
|
|
394
|
+
getParentRoute: () => rootRouteImport,
|
|
395
|
+
} as any).lazy(() =>
|
|
396
|
+
import('./routes/$lang/docs/$/route.lazy').then((d) => d.Route),
|
|
397
|
+
)
|
|
378
398
|
const NavAppsAnyhubRouteRoute = NavAppsAnyhubRouteRouteImport.update({
|
|
379
399
|
id: '/anyhub',
|
|
380
400
|
path: '/anyhub',
|
|
@@ -878,6 +898,7 @@ const ApiSharePrdPrdIdEventsRoute = ApiSharePrdPrdIdEventsRouteImport.update({
|
|
|
878
898
|
export interface FileRoutesByFullPath {
|
|
879
899
|
'/': typeof PublicIndexRouteRoute
|
|
880
900
|
'/share': typeof ShareRouteRouteWithChildren
|
|
901
|
+
'/docs': typeof DocsRoute
|
|
881
902
|
'/workspace': typeof WorkspaceRoute
|
|
882
903
|
'/onboarding': typeof _desktopOnboardingRouteRoute
|
|
883
904
|
'/forgot-password': typeof LoginForgotPasswordRouteRoute
|
|
@@ -910,9 +931,11 @@ export interface FileRoutesByFullPath {
|
|
|
910
931
|
'/api/billing-webhook': typeof ApiBillingWebhookRoute
|
|
911
932
|
'/api/chat': typeof ApiChatRoute
|
|
912
933
|
'/api/health': typeof ApiHealthRoute
|
|
934
|
+
'/api/search': typeof ApiSearchRoute
|
|
913
935
|
'/api/story-ai': typeof ApiStoryAiRoute
|
|
914
936
|
'/server/ai-stream': typeof ServerAiStreamRoute
|
|
915
937
|
'/server/ws': typeof ServerWsRoute
|
|
938
|
+
'/$lang/docs/$': typeof LangDocsSplatRouteRoute
|
|
916
939
|
'/apps/anyhub': typeof NavAppsAnyhubRouteRouteWithChildren
|
|
917
940
|
'/design/$projectId': typeof NavDesignProjectIdRouteRouteWithChildren
|
|
918
941
|
'/knowledge/$kbId': typeof NavKnowledgeKbIdRouteRouteWithChildren
|
|
@@ -987,6 +1010,7 @@ export interface FileRoutesByFullPath {
|
|
|
987
1010
|
export interface FileRoutesByTo {
|
|
988
1011
|
'/': typeof PublicIndexRouteRoute
|
|
989
1012
|
'/share': typeof ShareRouteRouteWithChildren
|
|
1013
|
+
'/docs': typeof DocsRoute
|
|
990
1014
|
'/workspace': typeof WorkspaceRoute
|
|
991
1015
|
'/onboarding': typeof _desktopOnboardingRouteRoute
|
|
992
1016
|
'/forgot-password': typeof LoginForgotPasswordRouteRoute
|
|
@@ -1013,9 +1037,11 @@ export interface FileRoutesByTo {
|
|
|
1013
1037
|
'/api/billing-webhook': typeof ApiBillingWebhookRoute
|
|
1014
1038
|
'/api/chat': typeof ApiChatRoute
|
|
1015
1039
|
'/api/health': typeof ApiHealthRoute
|
|
1040
|
+
'/api/search': typeof ApiSearchRoute
|
|
1016
1041
|
'/api/story-ai': typeof ApiStoryAiRoute
|
|
1017
1042
|
'/server/ai-stream': typeof ServerAiStreamRoute
|
|
1018
1043
|
'/server/ws': typeof ServerWsRoute
|
|
1044
|
+
'/$lang/docs/$': typeof LangDocsSplatRouteRoute
|
|
1019
1045
|
'/apps/anyhub': typeof NavAppsAnyhubRouteRouteWithChildren
|
|
1020
1046
|
'/design/$projectId': typeof NavDesignProjectIdRouteRouteWithChildren
|
|
1021
1047
|
'/prd/$id': typeof NavPrdIdRouteRoute
|
|
@@ -1085,6 +1111,7 @@ export interface FileRoutesById {
|
|
|
1085
1111
|
'/_nav': typeof NavRouteRouteWithChildren
|
|
1086
1112
|
'/_public': typeof PublicRouteRouteWithChildren
|
|
1087
1113
|
'/share': typeof ShareRouteRouteWithChildren
|
|
1114
|
+
'/docs': typeof DocsRoute
|
|
1088
1115
|
'/workspace': typeof WorkspaceRoute
|
|
1089
1116
|
'/_public/': typeof PublicIndexRouteRoute
|
|
1090
1117
|
'/__desktop/onboarding': typeof _desktopOnboardingRouteRoute
|
|
@@ -1118,9 +1145,11 @@ export interface FileRoutesById {
|
|
|
1118
1145
|
'/api/billing-webhook': typeof ApiBillingWebhookRoute
|
|
1119
1146
|
'/api/chat': typeof ApiChatRoute
|
|
1120
1147
|
'/api/health': typeof ApiHealthRoute
|
|
1148
|
+
'/api/search': typeof ApiSearchRoute
|
|
1121
1149
|
'/api/story-ai': typeof ApiStoryAiRoute
|
|
1122
1150
|
'/server/ai-stream': typeof ServerAiStreamRoute
|
|
1123
1151
|
'/server/ws': typeof ServerWsRoute
|
|
1152
|
+
'/$lang/docs/$': typeof LangDocsSplatRouteRoute
|
|
1124
1153
|
'/_nav/apps/anyhub': typeof NavAppsAnyhubRouteRouteWithChildren
|
|
1125
1154
|
'/_nav/design/$projectId': typeof NavDesignProjectIdRouteRouteWithChildren
|
|
1126
1155
|
'/_nav/knowledge/$kbId': typeof NavKnowledgeKbIdRouteRouteWithChildren
|
|
@@ -1197,6 +1226,7 @@ export interface FileRouteTypes {
|
|
|
1197
1226
|
fullPaths:
|
|
1198
1227
|
| '/'
|
|
1199
1228
|
| '/share'
|
|
1229
|
+
| '/docs'
|
|
1200
1230
|
| '/workspace'
|
|
1201
1231
|
| '/onboarding'
|
|
1202
1232
|
| '/forgot-password'
|
|
@@ -1229,9 +1259,11 @@ export interface FileRouteTypes {
|
|
|
1229
1259
|
| '/api/billing-webhook'
|
|
1230
1260
|
| '/api/chat'
|
|
1231
1261
|
| '/api/health'
|
|
1262
|
+
| '/api/search'
|
|
1232
1263
|
| '/api/story-ai'
|
|
1233
1264
|
| '/server/ai-stream'
|
|
1234
1265
|
| '/server/ws'
|
|
1266
|
+
| '/$lang/docs/$'
|
|
1235
1267
|
| '/apps/anyhub'
|
|
1236
1268
|
| '/design/$projectId'
|
|
1237
1269
|
| '/knowledge/$kbId'
|
|
@@ -1306,6 +1338,7 @@ export interface FileRouteTypes {
|
|
|
1306
1338
|
to:
|
|
1307
1339
|
| '/'
|
|
1308
1340
|
| '/share'
|
|
1341
|
+
| '/docs'
|
|
1309
1342
|
| '/workspace'
|
|
1310
1343
|
| '/onboarding'
|
|
1311
1344
|
| '/forgot-password'
|
|
@@ -1332,9 +1365,11 @@ export interface FileRouteTypes {
|
|
|
1332
1365
|
| '/api/billing-webhook'
|
|
1333
1366
|
| '/api/chat'
|
|
1334
1367
|
| '/api/health'
|
|
1368
|
+
| '/api/search'
|
|
1335
1369
|
| '/api/story-ai'
|
|
1336
1370
|
| '/server/ai-stream'
|
|
1337
1371
|
| '/server/ws'
|
|
1372
|
+
| '/$lang/docs/$'
|
|
1338
1373
|
| '/apps/anyhub'
|
|
1339
1374
|
| '/design/$projectId'
|
|
1340
1375
|
| '/prd/$id'
|
|
@@ -1403,6 +1438,7 @@ export interface FileRouteTypes {
|
|
|
1403
1438
|
| '/_nav'
|
|
1404
1439
|
| '/_public'
|
|
1405
1440
|
| '/share'
|
|
1441
|
+
| '/docs'
|
|
1406
1442
|
| '/workspace'
|
|
1407
1443
|
| '/_public/'
|
|
1408
1444
|
| '/__desktop/onboarding'
|
|
@@ -1436,9 +1472,11 @@ export interface FileRouteTypes {
|
|
|
1436
1472
|
| '/api/billing-webhook'
|
|
1437
1473
|
| '/api/chat'
|
|
1438
1474
|
| '/api/health'
|
|
1475
|
+
| '/api/search'
|
|
1439
1476
|
| '/api/story-ai'
|
|
1440
1477
|
| '/server/ai-stream'
|
|
1441
1478
|
| '/server/ws'
|
|
1479
|
+
| '/$lang/docs/$'
|
|
1442
1480
|
| '/_nav/apps/anyhub'
|
|
1443
1481
|
| '/_nav/design/$projectId'
|
|
1444
1482
|
| '/_nav/knowledge/$kbId'
|
|
@@ -1516,15 +1554,18 @@ export interface RootRouteChildren {
|
|
|
1516
1554
|
NavRouteRoute: typeof NavRouteRouteWithChildren
|
|
1517
1555
|
PublicRouteRoute: typeof PublicRouteRouteWithChildren
|
|
1518
1556
|
ShareRouteRoute: typeof ShareRouteRouteWithChildren
|
|
1557
|
+
DocsRoute: typeof DocsRoute
|
|
1519
1558
|
WorkspaceRoute: typeof WorkspaceRoute
|
|
1520
1559
|
_desktopOnboardingRouteRoute: typeof _desktopOnboardingRouteRoute
|
|
1521
1560
|
InviteTokenRouteRoute: typeof InviteTokenRouteRoute
|
|
1522
1561
|
ApiBillingWebhookRoute: typeof ApiBillingWebhookRoute
|
|
1523
1562
|
ApiChatRoute: typeof ApiChatRoute
|
|
1524
1563
|
ApiHealthRoute: typeof ApiHealthRoute
|
|
1564
|
+
ApiSearchRoute: typeof ApiSearchRoute
|
|
1525
1565
|
ApiStoryAiRoute: typeof ApiStoryAiRoute
|
|
1526
1566
|
ServerAiStreamRoute: typeof ServerAiStreamRoute
|
|
1527
1567
|
ServerWsRoute: typeof ServerWsRoute
|
|
1568
|
+
LangDocsSplatRouteRoute: typeof LangDocsSplatRouteRoute
|
|
1528
1569
|
ApiLicenseActivateRouteRoute: typeof ApiLicenseActivateRouteRoute
|
|
1529
1570
|
ApiLicenseHeartbeatRouteRoute: typeof ApiLicenseHeartbeatRouteRoute
|
|
1530
1571
|
ApiAttachmentsUploadRoute: typeof ApiAttachmentsUploadRoute
|
|
@@ -1562,6 +1603,13 @@ declare module '@tanstack/react-router' {
|
|
|
1562
1603
|
preLoaderRoute: typeof PublicRouteRouteImport
|
|
1563
1604
|
parentRoute: typeof rootRouteImport
|
|
1564
1605
|
}
|
|
1606
|
+
'/docs': {
|
|
1607
|
+
id: '/docs'
|
|
1608
|
+
path: '/docs'
|
|
1609
|
+
fullPath: '/docs'
|
|
1610
|
+
preLoaderRoute: typeof DocsRouteImport
|
|
1611
|
+
parentRoute: typeof rootRouteImport
|
|
1612
|
+
}
|
|
1565
1613
|
'/share': {
|
|
1566
1614
|
id: '/share'
|
|
1567
1615
|
path: '/share'
|
|
@@ -1786,6 +1834,13 @@ declare module '@tanstack/react-router' {
|
|
|
1786
1834
|
preLoaderRoute: typeof ApiHealthRouteImport
|
|
1787
1835
|
parentRoute: typeof rootRouteImport
|
|
1788
1836
|
}
|
|
1837
|
+
'/api/search': {
|
|
1838
|
+
id: '/api/search'
|
|
1839
|
+
path: '/api/search'
|
|
1840
|
+
fullPath: '/api/search'
|
|
1841
|
+
preLoaderRoute: typeof ApiSearchRouteImport
|
|
1842
|
+
parentRoute: typeof rootRouteImport
|
|
1843
|
+
}
|
|
1789
1844
|
'/api/story-ai': {
|
|
1790
1845
|
id: '/api/story-ai'
|
|
1791
1846
|
path: '/api/story-ai'
|
|
@@ -1821,6 +1876,13 @@ declare module '@tanstack/react-router' {
|
|
|
1821
1876
|
preLoaderRoute: typeof ShareVersionIdRouteRouteImport
|
|
1822
1877
|
parentRoute: typeof ShareRouteRoute
|
|
1823
1878
|
}
|
|
1879
|
+
'/$lang/docs/$': {
|
|
1880
|
+
id: '/$lang/docs/$'
|
|
1881
|
+
path: '/$lang/docs/$'
|
|
1882
|
+
fullPath: '/$lang/docs/$'
|
|
1883
|
+
preLoaderRoute: typeof LangDocsSplatRouteRouteImport
|
|
1884
|
+
parentRoute: typeof rootRouteImport
|
|
1885
|
+
}
|
|
1824
1886
|
'/_nav/apps/anyhub': {
|
|
1825
1887
|
id: '/_nav/apps/anyhub'
|
|
1826
1888
|
path: '/anyhub'
|
|
@@ -2761,15 +2823,18 @@ const rootRouteChildren: RootRouteChildren = {
|
|
|
2761
2823
|
NavRouteRoute: NavRouteRouteWithChildren,
|
|
2762
2824
|
PublicRouteRoute: PublicRouteRouteWithChildren,
|
|
2763
2825
|
ShareRouteRoute: ShareRouteRouteWithChildren,
|
|
2826
|
+
DocsRoute: DocsRoute,
|
|
2764
2827
|
WorkspaceRoute: WorkspaceRoute,
|
|
2765
2828
|
_desktopOnboardingRouteRoute: _desktopOnboardingRouteRoute,
|
|
2766
2829
|
InviteTokenRouteRoute: InviteTokenRouteRoute,
|
|
2767
2830
|
ApiBillingWebhookRoute: ApiBillingWebhookRoute,
|
|
2768
2831
|
ApiChatRoute: ApiChatRoute,
|
|
2769
2832
|
ApiHealthRoute: ApiHealthRoute,
|
|
2833
|
+
ApiSearchRoute: ApiSearchRoute,
|
|
2770
2834
|
ApiStoryAiRoute: ApiStoryAiRoute,
|
|
2771
2835
|
ServerAiStreamRoute: ServerAiStreamRoute,
|
|
2772
2836
|
ServerWsRoute: ServerWsRoute,
|
|
2837
|
+
LangDocsSplatRouteRoute: LangDocsSplatRouteRoute,
|
|
2773
2838
|
ApiLicenseActivateRouteRoute: ApiLicenseActivateRouteRoute,
|
|
2774
2839
|
ApiLicenseHeartbeatRouteRoute: ApiLicenseHeartbeatRouteRoute,
|
|
2775
2840
|
ApiAttachmentsUploadRoute: ApiAttachmentsUploadRoute,
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { createLazyFileRoute, Link } from '@tanstack/react-router'
|
|
2
|
+
import { Suspense, useEffect } from 'react'
|
|
3
|
+
import { DocsLayout } from 'fumadocs-ui/layouts/docs'
|
|
4
|
+
import {
|
|
5
|
+
DocsBody,
|
|
6
|
+
DocsDescription,
|
|
7
|
+
DocsPage,
|
|
8
|
+
DocsTitle,
|
|
9
|
+
} from 'fumadocs-ui/layouts/docs/page'
|
|
10
|
+
import { useFumadocsLoader } from 'fumadocs-core/source/client'
|
|
11
|
+
import { baseOptions, isDocsLang, type DocsLang } from '@/lib/layout.shared'
|
|
12
|
+
import { getMDXComponents } from '@/components/mdx'
|
|
13
|
+
import { docs } from '@/lib/source'
|
|
14
|
+
import { setLocale } from '@/paraglide/runtime.js'
|
|
15
|
+
|
|
16
|
+
export const Route = createLazyFileRoute('/$lang/docs/$')({
|
|
17
|
+
component: DocsRouteComponent,
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
function Content({ path, lang }: { path: string; lang: DocsLang }) {
|
|
21
|
+
const page = docs.getPage(path)
|
|
22
|
+
if (!page) throw new Error(`unknown page: ${path}`)
|
|
23
|
+
|
|
24
|
+
const { toc, body: MdxContent } = page
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<DocsPage toc={toc} className="pt-8">
|
|
28
|
+
<Link
|
|
29
|
+
to="/dashboard"
|
|
30
|
+
className="text-fd-muted-foreground hover:text-fd-foreground mb-2 inline-flex items-center gap-1 text-sm"
|
|
31
|
+
>
|
|
32
|
+
← {lang === 'en' ? 'Back to App' : '返回应用'}
|
|
33
|
+
</Link>
|
|
34
|
+
<DocsTitle>{page.title}</DocsTitle>
|
|
35
|
+
<DocsDescription>{page.description}</DocsDescription>
|
|
36
|
+
<DocsBody>
|
|
37
|
+
<MdxContent components={getMDXComponents()} />
|
|
38
|
+
</DocsBody>
|
|
39
|
+
</DocsPage>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function DocsRouteComponent() {
|
|
44
|
+
const { lang: rawLang } = Route.useParams()
|
|
45
|
+
const data = useFumadocsLoader(Route.useLoaderData())
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (isDocsLang(rawLang)) setLocale(rawLang)
|
|
49
|
+
}, [rawLang])
|
|
50
|
+
|
|
51
|
+
if (!isDocsLang(rawLang)) return null
|
|
52
|
+
|
|
53
|
+
const lang: DocsLang = rawLang
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<DocsLayout {...baseOptions(lang)} tree={data.pageTree}>
|
|
57
|
+
<Suspense>
|
|
58
|
+
<Content path={data.path} lang={lang} />
|
|
59
|
+
</Suspense>
|
|
60
|
+
</DocsLayout>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { createFileRoute, notFound } from '@tanstack/react-router'
|
|
2
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
3
|
+
import { docs, source } from '@/lib/source'
|
|
4
|
+
|
|
5
|
+
// fumadocs-ui 16.15.8 的 getLayoutTabs 遍历 root/folder 的 children 时
|
|
6
|
+
// 会 `for of child` 递归调用 next,但 page 节点(叶子)无 children 字段导致
|
|
7
|
+
// `node.children is not iterable`。给每个无 children 的节点补 `children: []`。
|
|
8
|
+
function fixPageTreeChildren<T extends { data: unknown }>(tree: T): T {
|
|
9
|
+
const patch = (node: { type?: string; children?: unknown[] }): void => {
|
|
10
|
+
if (!node || typeof node !== 'object') return
|
|
11
|
+
if (Array.isArray(node.children)) {
|
|
12
|
+
for (const child of node.children) patch(child as Parameters<typeof patch>[0])
|
|
13
|
+
} else {
|
|
14
|
+
;(node as { children: unknown[] }).children = []
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
patch(tree.data as { children?: unknown[] })
|
|
18
|
+
return tree
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const loadDocsPage = createServerFn({ method: 'GET' })
|
|
22
|
+
.validator((data: { slugs: string[]; lang: string }) => data)
|
|
23
|
+
.handler(async ({ data: { slugs, lang } }) => {
|
|
24
|
+
const page = source.getPage(slugs, lang)
|
|
25
|
+
if (!page) return null
|
|
26
|
+
|
|
27
|
+
const pageTree = fixPageTreeChildren(await source.serializePageTree(source.getPageTree(lang)))
|
|
28
|
+
return {
|
|
29
|
+
path: page.path,
|
|
30
|
+
title: page.data.title,
|
|
31
|
+
description: page.data.description,
|
|
32
|
+
pageTree,
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
export const Route = createFileRoute('/$lang/docs/$')({
|
|
37
|
+
loader: async ({ params }) => {
|
|
38
|
+
const slugs = params._splat?.split('/') ?? []
|
|
39
|
+
const data = await loadDocsPage({ data: { slugs, lang: params.lang } })
|
|
40
|
+
if (!data) throw notFound()
|
|
41
|
+
await docs.getPage(data.path)?.preload()
|
|
42
|
+
return data
|
|
43
|
+
},
|
|
44
|
+
head: ({ loaderData }) => ({
|
|
45
|
+
meta: [
|
|
46
|
+
{ title: loaderData ? `${loaderData.title} - AllBlue` : 'AllBlue' },
|
|
47
|
+
...(loaderData?.description
|
|
48
|
+
? [{ name: 'description', content: loaderData.description }]
|
|
49
|
+
: []),
|
|
50
|
+
],
|
|
51
|
+
}),
|
|
52
|
+
})
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
vi.mock('@/paraglide/runtime.js', () => ({
|
|
4
|
+
getLocale: vi.fn(() => 'zh'),
|
|
5
|
+
}))
|
|
6
|
+
|
|
7
|
+
import { getLocale } from '@/paraglide/runtime.js'
|
|
8
|
+
import { Route } from '../docs'
|
|
9
|
+
|
|
10
|
+
type BeforeLoadFn = (ctx: never) => unknown
|
|
11
|
+
const beforeLoad = Route.options.beforeLoad as unknown as BeforeLoadFn
|
|
12
|
+
|
|
13
|
+
async function catchRedirectTarget(fn: () => unknown): Promise<string> {
|
|
14
|
+
try {
|
|
15
|
+
await fn()
|
|
16
|
+
return ''
|
|
17
|
+
} catch (e) {
|
|
18
|
+
const err = e as {
|
|
19
|
+
href?: string
|
|
20
|
+
options?: { to?: string; params?: Record<string, string> }
|
|
21
|
+
}
|
|
22
|
+
if (err.href) return err.href
|
|
23
|
+
if (err.options?.to) {
|
|
24
|
+
const p = err.options.params ?? {}
|
|
25
|
+
return err.options.to.replace(/\$(\w+)/g, (_, name: string) => p[name] ?? `$${name}`)
|
|
26
|
+
}
|
|
27
|
+
return JSON.stringify(e)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe('/docs redirect', () => {
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
vi.clearAllMocks()
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('zh locale 重定向到 /zh/docs', async () => {
|
|
37
|
+
vi.mocked(getLocale).mockReturnValue('zh')
|
|
38
|
+
await expect(catchRedirectTarget(() => beforeLoad({} as never))).resolves.toContain('/zh/docs')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('en locale 重定向到 /en/docs', async () => {
|
|
42
|
+
vi.mocked(getLocale).mockReturnValue('en')
|
|
43
|
+
await expect(catchRedirectTarget(() => beforeLoad({} as never))).resolves.toContain('/en/docs')
|
|
44
|
+
})
|
|
45
|
+
})
|
package/src/routes/__root.tsx
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// You should have received a copy of the GNU Affero General Public License
|
|
16
16
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
|
-
import { Link, Outlet, createRootRouteWithContext, HeadContent, Scripts } from '@tanstack/react-router'
|
|
18
|
+
import { Link, Outlet, createRootRouteWithContext, HeadContent, Scripts, useParams } from '@tanstack/react-router'
|
|
19
19
|
import { HotkeysProvider } from '@tanstack/react-hotkeys'
|
|
20
20
|
import { ShortcutsPanel } from '@/components/hotkeys/shortcuts-panel'
|
|
21
21
|
import { Analytics } from '@vercel/analytics/react'
|
|
@@ -23,6 +23,9 @@ import { SpeedInsights } from '@vercel/speed-insights/react'
|
|
|
23
23
|
import globalsCss from '@/styles/globals.css?url'
|
|
24
24
|
import type { RouterContext } from '@/lib/router-context'
|
|
25
25
|
import { Providers } from '@/providers'
|
|
26
|
+
import { RootProvider } from 'fumadocs-ui/provider/tanstack'
|
|
27
|
+
import { i18nProvider } from 'fumadocs-ui/i18n'
|
|
28
|
+
import { translations } from '@/lib/layout.shared'
|
|
26
29
|
import { Toaster } from '@/components/ui/sonner'
|
|
27
30
|
import { DevtoolsPanel } from '@/components/devtools'
|
|
28
31
|
import { getSessionFn } from '@/server/serverFns/auth/session'
|
|
@@ -62,6 +65,7 @@ export const Route = createRootRouteWithContext<RouterContext>()({
|
|
|
62
65
|
|
|
63
66
|
function RootComponent() {
|
|
64
67
|
const session = Route.useRouteContext({ select: (c) => c.session }) as Session | null
|
|
68
|
+
const { lang } = useParams({ strict: false }) as { lang?: string | undefined }
|
|
65
69
|
return (
|
|
66
70
|
<html lang={getLocale()} suppressHydrationWarning>
|
|
67
71
|
<head>
|
|
@@ -69,12 +73,14 @@ function RootComponent() {
|
|
|
69
73
|
</head>
|
|
70
74
|
<body className="w-full h-full min-h-screen antialiased">
|
|
71
75
|
<Providers session={session}>
|
|
72
|
-
<
|
|
73
|
-
<
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
<RootProvider i18n={i18nProvider(translations, lang)}>
|
|
77
|
+
<HotkeysProvider>
|
|
78
|
+
<Outlet />
|
|
79
|
+
<ShortcutsPanel />
|
|
80
|
+
<Toaster position="top-center" />
|
|
81
|
+
{import.meta.env.DEV && <DevtoolsPanel />}
|
|
82
|
+
</HotkeysProvider>
|
|
83
|
+
</RootProvider>
|
|
78
84
|
</Providers>
|
|
79
85
|
<Scripts />
|
|
80
86
|
<Analytics />
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
import { createFromSource } from 'fumadocs-core/search/server'
|
|
3
|
+
import { source } from '@/lib/source'
|
|
4
|
+
|
|
5
|
+
const server = createFromSource(source)
|
|
6
|
+
|
|
7
|
+
export const Route = createFileRoute('/api/search')({
|
|
8
|
+
server: {
|
|
9
|
+
handlers: {
|
|
10
|
+
GET: async ({ request }) => server.GET(request),
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
})
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createFileRoute, redirect } from '@tanstack/react-router'
|
|
2
|
+
import { getLocale } from '@/paraglide/runtime.js'
|
|
3
|
+
|
|
4
|
+
export const Route = createFileRoute('/docs')({
|
|
5
|
+
beforeLoad: () => {
|
|
6
|
+
const lang = getLocale()
|
|
7
|
+
throw redirect({ to: '/$lang/docs/$', params: { lang, _splat: '' } })
|
|
8
|
+
},
|
|
9
|
+
})
|
package/src/styles/globals.css
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
@import './_variables.scss';
|
|
2
2
|
@import './_keyframe-animations.scss';
|
|
3
3
|
@import "tailwindcss";
|
|
4
|
+
@import 'fumadocs-ui/css/neutral.css';
|
|
5
|
+
@import 'fumadocs-ui/css/preset.css';
|
|
4
6
|
@import "@fontsource-variable/geist";
|
|
5
7
|
@import "@fontsource-variable/geist-mono";
|
|
6
8
|
@import "./typeset.css";
|