@coffic/cosy-ui 0.6.2 → 0.6.6
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/dist/collection.ts +104 -0
- package/dist/components/errors/404.astro +15 -0
- package/dist/components/layouts/Header.astro +16 -49
- package/dist/components/layouts/NavItems.astro +45 -0
- package/dist/components/layouts/Sidebar.astro +1 -0
- package/dist/components/layouts/SidebarNav.astro +11 -2
- package/dist/database/BaseDB.ts +173 -142
- package/dist/database/BlogDB.ts +4 -4
- package/dist/database/CourseDB.ts +7 -8
- package/dist/database/ExperimentDB.ts +4 -4
- package/dist/database/LessonDB.ts +4 -4
- package/dist/database/MetaDB.ts +4 -4
- package/dist/entities/BaseDoc.ts +78 -80
- package/dist/entities/BlogDoc.ts +13 -2
- package/dist/entities/CourseDoc.ts +47 -7
- package/dist/entities/ExperimentDoc.ts +6 -9
- package/dist/entities/LessonDoc.ts +6 -6
- package/dist/entities/MetaDoc.ts +2 -2
- package/dist/index.ts +5 -9
- package/dist/utils/link.ts +1 -1
- package/dist/utils/logger.ts +1 -1
- package/dist/utils/path.ts +48 -33
- package/package.json +3 -2
- package/dist/collections/ArticleCollection.ts +0 -19
- package/dist/collections/BlogCollection.ts +0 -28
- package/dist/collections/CourseCollection.ts +0 -11
- package/dist/collections/ExperimentCollection.ts +0 -18
- package/dist/collections/LessonCollection.ts +0 -25
- package/dist/collections/MetaCollection.ts +0 -17
- package/dist/entities/Heading.ts +0 -13
@@ -0,0 +1,104 @@
|
|
1
|
+
import { defineCollection, z } from 'astro:content';
|
2
|
+
import { glob } from 'astro/loaders';
|
3
|
+
|
4
|
+
export const makeArticleCollection = (base: string) => {
|
5
|
+
return defineCollection({
|
6
|
+
loader: glob({
|
7
|
+
pattern: '**/*.{md,mdx}',
|
8
|
+
base,
|
9
|
+
}),
|
10
|
+
schema: z.object({
|
11
|
+
title: z.string(),
|
12
|
+
folder: z.boolean(),
|
13
|
+
order: z.number(),
|
14
|
+
description: z.string().optional(),
|
15
|
+
}),
|
16
|
+
});
|
17
|
+
};
|
18
|
+
|
19
|
+
export const makeBlogCollection = (base: string) => {
|
20
|
+
return defineCollection({
|
21
|
+
loader: glob({
|
22
|
+
pattern: '**/*.{md,mdx}',
|
23
|
+
base,
|
24
|
+
}),
|
25
|
+
schema: z.object({
|
26
|
+
title: z.string(),
|
27
|
+
description: z.string().optional(),
|
28
|
+
tags: z.array(z.string()).optional(),
|
29
|
+
date: z.date().optional(),
|
30
|
+
authors: z
|
31
|
+
.array(
|
32
|
+
z.object({
|
33
|
+
name: z.string(),
|
34
|
+
picture: z.string().optional(),
|
35
|
+
url: z.string().optional(),
|
36
|
+
})
|
37
|
+
)
|
38
|
+
.optional(),
|
39
|
+
}),
|
40
|
+
});
|
41
|
+
};
|
42
|
+
|
43
|
+
export const makeCourseCollection = (base: string) => {
|
44
|
+
return defineCollection({
|
45
|
+
loader: glob({
|
46
|
+
pattern: '**/*.{md,mdx}',
|
47
|
+
base,
|
48
|
+
}),
|
49
|
+
schema: z.object({
|
50
|
+
title: z.string(),
|
51
|
+
description: z.string().optional(),
|
52
|
+
folder: z.boolean(),
|
53
|
+
order: z.number(),
|
54
|
+
}),
|
55
|
+
});
|
56
|
+
};
|
57
|
+
|
58
|
+
export const makeExperimentCollection = (base: string) => {
|
59
|
+
return defineCollection({
|
60
|
+
loader: glob({
|
61
|
+
pattern: '**/*.{md,mdx}',
|
62
|
+
base,
|
63
|
+
}),
|
64
|
+
schema: z.object({
|
65
|
+
title: z.string(),
|
66
|
+
description: z.string().optional(),
|
67
|
+
pubDate: z.date().optional(),
|
68
|
+
}),
|
69
|
+
});
|
70
|
+
};
|
71
|
+
|
72
|
+
export const makeLessonCollection = (base: string) => {
|
73
|
+
return defineCollection({
|
74
|
+
loader: glob({
|
75
|
+
pattern: '**/*.{md,mdx}',
|
76
|
+
base,
|
77
|
+
}),
|
78
|
+
schema: z.object({
|
79
|
+
title: z.string(),
|
80
|
+
description: z.string().optional(),
|
81
|
+
authors: z
|
82
|
+
.array(
|
83
|
+
z.object({
|
84
|
+
name: z.string(),
|
85
|
+
picture: z.string().optional(),
|
86
|
+
})
|
87
|
+
)
|
88
|
+
.optional(),
|
89
|
+
}),
|
90
|
+
});
|
91
|
+
};
|
92
|
+
|
93
|
+
export const makeMetaCollection = (base: string) => {
|
94
|
+
return defineCollection({
|
95
|
+
loader: glob({
|
96
|
+
pattern: '**/*.{md,mdx}',
|
97
|
+
base,
|
98
|
+
}),
|
99
|
+
schema: z.object({
|
100
|
+
title: z.string(),
|
101
|
+
description: z.string().optional(),
|
102
|
+
}),
|
103
|
+
});
|
104
|
+
};
|
@@ -1,12 +1,27 @@
|
|
1
1
|
---
|
2
2
|
import { LinkUtil } from '../../utils/link';
|
3
3
|
const baseUrl = LinkUtil.getBaseUrl();
|
4
|
+
const path = Astro.url.pathname;
|
5
|
+
|
6
|
+
interface Error404Props {
|
7
|
+
debugKVs: Record<string, string>;
|
8
|
+
}
|
9
|
+
|
10
|
+
const { debugKVs = {} } = Astro.props as Error404Props;
|
4
11
|
---
|
5
12
|
|
6
13
|
<div class="flex flex-col items-center justify-center h-screen">
|
7
14
|
<div class="flex flex-col justify-center items-center min-h-[50vh] text-center">
|
8
15
|
<h1 class="mb-4 font-bold text-4xl">404 - 页面未找到</h1>
|
9
16
|
<p class="mb-8 text-lg">抱歉,您要找的文档不存在。</p>
|
17
|
+
<p class="mb-8 text-lg">路径:{path}</p>
|
18
|
+
{
|
19
|
+
Object.entries(debugKVs).map(([key, value]) => (
|
20
|
+
<p class="mb-8 text-lg">
|
21
|
+
{key}: {value}
|
22
|
+
</p>
|
23
|
+
))
|
24
|
+
}
|
10
25
|
<a href={baseUrl} class="btn btn-primary"> 返回首页 </a>
|
11
26
|
</div>
|
12
27
|
</div>
|
@@ -60,6 +60,7 @@
|
|
60
60
|
import '../../app.css';
|
61
61
|
import Link from '../base/Link.astro';
|
62
62
|
import Image from '../base/Image.astro';
|
63
|
+
import NavItems from './NavItems.astro';
|
63
64
|
import { LanguageSwitcher, LinkUtil, type IHeaderProps, type INavItem } from '../../index';
|
64
65
|
import Logo from '../../assets/logo-rounded.png';
|
65
66
|
|
@@ -147,7 +148,6 @@ const activeLink = LinkUtil.getActiveLink(
|
|
147
148
|
'cosy:py-16': paddingVertical === '3xl',
|
148
149
|
},
|
149
150
|
]}>
|
150
|
-
<!-- 背景 -->
|
151
151
|
<div
|
152
152
|
class:list={[
|
153
153
|
'cosy:bg-accent/70 cosy:flex cosy:flex-grow cosy:backdrop-blur not-prose cosy:border-base-200',
|
@@ -162,7 +162,6 @@ const activeLink = LinkUtil.getActiveLink(
|
|
162
162
|
headerHeightClass,
|
163
163
|
{ 'cosy:border cosy:border-dashed cosy:border-red-500': debug },
|
164
164
|
]}>
|
165
|
-
<!-- 左侧 -->
|
166
165
|
<div class="cosy:navbar-start cosy:pl-1">
|
167
166
|
{
|
168
167
|
navPosition === 'start' ? (
|
@@ -182,22 +181,13 @@ const activeLink = LinkUtil.getActiveLink(
|
|
182
181
|
class={logoSizeClass}
|
183
182
|
/>
|
184
183
|
</Link>
|
185
|
-
<
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
<Link
|
193
|
-
variant={activeLink == item.href ? 'primary' : 'default'}
|
194
|
-
href={item.href}
|
195
|
-
class:list={[linkHeightClass]}>
|
196
|
-
{item.label}
|
197
|
-
</Link>
|
198
|
-
</li>
|
199
|
-
))}
|
200
|
-
</ul>
|
184
|
+
<div class="cosy:hidden cosy:lg:flex">
|
185
|
+
<NavItems
|
186
|
+
navItems={navItems}
|
187
|
+
activeLink={activeLink}
|
188
|
+
linkHeightClass={linkHeightClass}
|
189
|
+
/>
|
190
|
+
</div>
|
201
191
|
</div>
|
202
192
|
) : (
|
203
193
|
<Link
|
@@ -219,49 +209,26 @@ const activeLink = LinkUtil.getActiveLink(
|
|
219
209
|
}
|
220
210
|
</div>
|
221
211
|
|
222
|
-
<!-- 中间 -->
|
223
212
|
{
|
224
213
|
navPosition === 'center' && (
|
225
214
|
<div class="cosy:hidden cosy:lg:flex cosy:navbar-center">
|
226
|
-
<
|
227
|
-
{navItems.map((item: INavItem) => (
|
228
|
-
<li>
|
229
|
-
<Link
|
230
|
-
variant={activeLink == item.href ? 'primary' : 'default'}
|
231
|
-
href={item.href}
|
232
|
-
class:list={[linkHeightClass]}>
|
233
|
-
{item.label}
|
234
|
-
</Link>
|
235
|
-
</li>
|
236
|
-
))}
|
237
|
-
</ul>
|
215
|
+
<NavItems navItems={navItems} activeLink={activeLink} linkHeightClass={linkHeightClass} />
|
238
216
|
</div>
|
239
217
|
)
|
240
218
|
}
|
241
219
|
|
242
|
-
<!-- 右侧 -->
|
243
220
|
<div class="cosy:navbar-end cosy:pr-1">
|
244
221
|
{
|
245
222
|
navPosition === 'end' && (
|
246
|
-
<
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
<Link
|
254
|
-
variant={activeLink == item.href ? 'primary' : 'default'}
|
255
|
-
href={item.href}
|
256
|
-
class:list={[linkHeightClass]}>
|
257
|
-
{item.label}
|
258
|
-
</Link>
|
259
|
-
</li>
|
260
|
-
))}
|
261
|
-
</ul>
|
223
|
+
<div class="cosy:hidden cosy:lg:flex">
|
224
|
+
<NavItems
|
225
|
+
navItems={navItems}
|
226
|
+
activeLink={activeLink}
|
227
|
+
linkHeightClass={linkHeightClass}
|
228
|
+
/>
|
229
|
+
</div>
|
262
230
|
)
|
263
231
|
}
|
264
|
-
<!-- 语言切换 -->
|
265
232
|
<LanguageSwitcher languages={languages} />
|
266
233
|
</div>
|
267
234
|
</div>
|
@@ -0,0 +1,45 @@
|
|
1
|
+
---
|
2
|
+
/**
|
3
|
+
* @component NavItems
|
4
|
+
*
|
5
|
+
* @description
|
6
|
+
* NavItems 组件用于渲染导航栏中的导航项目列表。
|
7
|
+
*
|
8
|
+
* @usage
|
9
|
+
* ```astro
|
10
|
+
* <NavItems
|
11
|
+
* navItems={navItems}
|
12
|
+
* activeLink={activeLink}
|
13
|
+
* linkHeightClass={linkHeightClass}
|
14
|
+
* />
|
15
|
+
* ```
|
16
|
+
*/
|
17
|
+
import '../../app.css';
|
18
|
+
import Link from '../base/Link.astro';
|
19
|
+
import type { INavItem } from '../../index';
|
20
|
+
|
21
|
+
interface Props {
|
22
|
+
navItems: INavItem[];
|
23
|
+
activeLink: string;
|
24
|
+
linkHeightClass: string;
|
25
|
+
}
|
26
|
+
|
27
|
+
const { navItems, activeLink, linkHeightClass } = Astro.props;
|
28
|
+
---
|
29
|
+
|
30
|
+
<ul
|
31
|
+
data-active-link={activeLink}
|
32
|
+
class:list={['cosy:px-1 cosy:menu cosy:menu-horizontal', linkHeightClass]}>
|
33
|
+
{
|
34
|
+
navItems.map((item) => (
|
35
|
+
<li>
|
36
|
+
<Link
|
37
|
+
variant={activeLink === item.href ? 'primary' : 'default'}
|
38
|
+
href={item.href}
|
39
|
+
class:list={[linkHeightClass]}>
|
40
|
+
{item.label}
|
41
|
+
</Link>
|
42
|
+
</li>
|
43
|
+
))
|
44
|
+
}
|
45
|
+
</ul>
|
@@ -37,7 +37,10 @@ const { sidebarItems, currentPath, debug = false, class: className } = Astro.pro
|
|
37
37
|
const debugClass = debug ? 'cosy:border cosy:border-red-500' : '';
|
38
38
|
---
|
39
39
|
|
40
|
-
<nav
|
40
|
+
<nav
|
41
|
+
data-sidebar-nav
|
42
|
+
data-current-path={currentPath}
|
43
|
+
class:list={['cosy:p-4', debugClass, className]}>
|
41
44
|
{
|
42
45
|
sidebarItems.map((section: ISidebarItem) => (
|
43
46
|
<div class:list={['cosy:mb-6', debugClass]}>
|
@@ -50,6 +53,8 @@ const debugClass = debug ? 'cosy:border cosy:border-red-500' : '';
|
|
50
53
|
return (
|
51
54
|
<li class:list={[debugClass]}>
|
52
55
|
<a
|
56
|
+
data-sidebar-item
|
57
|
+
data-current-path={currentPath}
|
53
58
|
href={item.href}
|
54
59
|
class:list={[
|
55
60
|
'cosy:hover:bg-base-300',
|
@@ -65,10 +70,12 @@ const debugClass = debug ? 'cosy:border cosy:border-red-500' : '';
|
|
65
70
|
return (
|
66
71
|
<li class:list={[debugClass]}>
|
67
72
|
<a
|
73
|
+
data-sidebar-item
|
74
|
+
data-current-path={currentPath}
|
68
75
|
href={subitem.href}
|
69
76
|
class:list={[
|
70
77
|
'cosy:hover:bg-base-300',
|
71
|
-
{ 'cosy:active': isSubActive },
|
78
|
+
{ 'cosy:menu-active': isSubActive },
|
72
79
|
debugClass,
|
73
80
|
]}>
|
74
81
|
{subitem.text}
|
@@ -80,6 +87,8 @@ const debugClass = debug ? 'cosy:border cosy:border-red-500' : '';
|
|
80
87
|
return (
|
81
88
|
<li class:list={[debugClass]}>
|
82
89
|
<a
|
90
|
+
data-sidebar-item
|
91
|
+
data-current-path={currentPath}
|
83
92
|
href={subsubitem.href}
|
84
93
|
class:list={[
|
85
94
|
'cosy:hover:bg-base-300',
|
package/dist/database/BaseDB.ts
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
import { getCollection, getEntry, type CollectionEntry, type DataEntryMap } from
|
2
|
-
import { logger } from
|
1
|
+
import { getCollection, getEntry, type CollectionEntry, type DataEntryMap } from 'astro:content';
|
2
|
+
import { logger } from '../utils/logger';
|
3
3
|
|
4
4
|
/**
|
5
5
|
* BaseDB 是所有数据库类的基类,提供了通用的文档操作功能。
|
6
|
-
*
|
6
|
+
*
|
7
7
|
* 使用方法:
|
8
8
|
* ```typescript
|
9
9
|
* class MyDB extends BaseDB<'collection', MyEntry, MyDoc> {
|
@@ -12,153 +12,184 @@ import { logger } from "../utils/logger";
|
|
12
12
|
* return new MyDoc(entry);
|
13
13
|
* }
|
14
14
|
* }
|
15
|
-
*
|
15
|
+
*
|
16
16
|
* // 使用单例模式获取实例
|
17
17
|
* const db = MyDB.getInstance();
|
18
18
|
* const docs = await db.allTopLevelDocs();
|
19
19
|
* ```
|
20
|
-
*
|
20
|
+
*
|
21
21
|
* 类型参数说明:
|
22
22
|
* @template Collection - Astro content collection 的名称,必须是 DataEntryMap 的键
|
23
23
|
* @template Entry - Collection 对应的条目类型
|
24
24
|
* @template Doc - 文档类型,通常是自定义的文档类
|
25
25
|
*/
|
26
26
|
export abstract class BaseDB<
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
Collection extends keyof DataEntryMap,
|
28
|
+
Entry extends CollectionEntry<Collection>,
|
29
|
+
Doc,
|
30
30
|
> {
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
31
|
+
/** 集合名称,必须在子类中指定 */
|
32
|
+
protected abstract collectionName: Collection;
|
33
|
+
|
34
|
+
/**
|
35
|
+
* 创建文档实例的方法,必须在子类中实现
|
36
|
+
* @param entry - 集合条目
|
37
|
+
* @returns 文档实例
|
38
|
+
*/
|
39
|
+
protected abstract createDoc(entry: Entry): Doc;
|
40
|
+
|
41
|
+
/**
|
42
|
+
* 获取所有文档的ID
|
43
|
+
* @returns 返回所有文档的ID数组
|
44
|
+
*/
|
45
|
+
async getAllIds(): Promise<string[]> {
|
46
|
+
const entries = await getCollection(this.collectionName);
|
47
|
+
return entries.map((entry) => entry.id);
|
48
|
+
}
|
49
|
+
|
50
|
+
/**
|
51
|
+
* 获取集合中的所有条目
|
52
|
+
* @returns 返回所有条目的数组
|
53
|
+
*/
|
54
|
+
async getEntries(): Promise<Entry[]> {
|
55
|
+
const entries = await getCollection(this.collectionName);
|
56
|
+
return entries.map((entry) => entry as Entry);
|
57
|
+
}
|
58
|
+
|
59
|
+
/**
|
60
|
+
* 获取指定深度的文档
|
61
|
+
* 深度是指文档 ID 中斜杠的数量加1
|
62
|
+
* 例如:
|
63
|
+
* - "blog.md" 深度为1
|
64
|
+
* - "zh-cn/blog.md" 深度为2
|
65
|
+
* - "zh-cn/tech/blog.md" 深度为3
|
66
|
+
*
|
67
|
+
* @param depth - 文档深度
|
68
|
+
* @returns 返回指定深度的文档数组
|
69
|
+
*/
|
70
|
+
async getDocsByDepth(depth: number): Promise<Doc[]> {
|
71
|
+
const entries = await getCollection(
|
72
|
+
this.collectionName,
|
73
|
+
({ id }) => id.split('/').length === depth
|
74
|
+
);
|
75
|
+
return entries.map((entry) => this.createDoc(entry as Entry));
|
76
|
+
}
|
77
|
+
|
78
|
+
/**
|
79
|
+
* 根据ID查找单个文档
|
80
|
+
* @param id - 文档ID
|
81
|
+
* @returns 返回找到的文档,如果不存在则返回null
|
82
|
+
*/
|
83
|
+
async find(id: string): Promise<Doc | null> {
|
84
|
+
const debug = false;
|
85
|
+
if (debug) {
|
86
|
+
logger.info(`查找文档,ID: ${id}`);
|
87
|
+
}
|
88
|
+
|
89
|
+
if (typeof id !== 'string') {
|
90
|
+
throw new Error('ID must be a string, but got ' + typeof id);
|
91
|
+
}
|
92
|
+
|
93
|
+
// 获取所有文档的ID并排好顺序
|
94
|
+
if (debug) {
|
95
|
+
const allIds = (await this.getAllIds()).sort();
|
96
|
+
logger.array('所有文档的ID', allIds);
|
97
|
+
}
|
98
|
+
|
99
|
+
// 根据ID查找文档
|
100
|
+
const entry = await getEntry(this.collectionName, id);
|
101
|
+
return entry ? this.createDoc(entry as Entry) : null;
|
102
|
+
}
|
103
|
+
|
104
|
+
/**
|
105
|
+
* 获取指定文档的直接子文档(不包括更深层级的文档)
|
106
|
+
* 例如对于文档 "zh-cn/blog":
|
107
|
+
* - "zh-cn/blog/post1.md" 会被包含
|
108
|
+
* - "zh-cn/blog/2024/post2.md" 不会被包含
|
109
|
+
*
|
110
|
+
* @param parentId - 父文档ID
|
111
|
+
* @returns 返回子文档数组
|
112
|
+
*/
|
113
|
+
async getChildren(parentId: string): Promise<Doc[]> {
|
114
|
+
const parentLevel = parentId.split('/').length;
|
115
|
+
const childrenLevel = parentLevel + 1;
|
116
|
+
|
117
|
+
const entries = await getCollection(
|
118
|
+
this.collectionName,
|
119
|
+
({ id }) => id.startsWith(parentId) && id.split('/').length === childrenLevel
|
120
|
+
);
|
121
|
+
|
122
|
+
return entries.map((entry) => this.createDoc(entry as Entry));
|
123
|
+
}
|
124
|
+
|
125
|
+
/**
|
126
|
+
* 获取指定文档的所有后代文档(包括所有层级)
|
127
|
+
* 例如对于文档 "zh-cn/blog",以下都会被包含:
|
128
|
+
* - "zh-cn/blog/post1.md"
|
129
|
+
* - "zh-cn/blog/2024/post2.md"
|
130
|
+
* - "zh-cn/blog/2024/tech/post3.md"
|
131
|
+
*
|
132
|
+
* @param parentId - 父文档ID
|
133
|
+
* @returns 返回所有后代文档数组
|
134
|
+
*/
|
135
|
+
async getDescendantDocs(parentId: string): Promise<Doc[]> {
|
136
|
+
const entries = await getCollection(this.collectionName, ({ id }) => id.startsWith(parentId));
|
137
|
+
return entries.map((entry) => this.createDoc(entry as Entry));
|
138
|
+
}
|
139
|
+
|
140
|
+
/**
|
141
|
+
* 获取指定语言的顶级文档
|
142
|
+
* 通过检查文档ID是否以指定语言代码开头来筛选
|
143
|
+
*
|
144
|
+
* @param lang - 语言代码(如 'zh-cn', 'en')
|
145
|
+
* @returns 返回指定语言的顶级文档数组
|
146
|
+
*/
|
147
|
+
async allDocsByLang(lang: string): Promise<Doc[]> {
|
148
|
+
const debug = false;
|
149
|
+
const docs = await this.getDocsByDepth(2);
|
150
|
+
|
151
|
+
if (debug) {
|
152
|
+
logger.array('所有顶级文档', docs);
|
153
|
+
}
|
154
|
+
|
155
|
+
return docs.filter((doc) => {
|
156
|
+
const id = (doc as any).getId();
|
157
|
+
return id && typeof id === 'string' && id.startsWith(lang);
|
158
|
+
});
|
159
|
+
}
|
160
|
+
|
161
|
+
/**
|
162
|
+
* 获取用于 Astro 静态路由生成的路径参数
|
163
|
+
* 为每个文档生成包含语言和slug的路径参数
|
164
|
+
*
|
165
|
+
* @returns 返回路径参数数组,每个元素包含 lang 和 slug
|
166
|
+
* @example
|
167
|
+
* ```typescript
|
168
|
+
* const paths = await db.getStaticPaths();
|
169
|
+
* // 返回格式:
|
170
|
+
* // [
|
171
|
+
* // { params: { lang: 'zh-cn', slug: 'post1' } },
|
172
|
+
* // { params: { lang: 'en', slug: 'post1' } }
|
173
|
+
* // ]
|
174
|
+
* ```
|
175
|
+
*/
|
176
|
+
async getStaticPaths() {
|
177
|
+
const debug = false;
|
178
|
+
const docs = await this.getDescendantDocs('');
|
179
|
+
const paths = docs.map((doc) => {
|
180
|
+
const docWithMethods = doc as any;
|
181
|
+
return {
|
182
|
+
params: {
|
183
|
+
lang: docWithMethods.getLang?.() || '',
|
184
|
+
slug: docWithMethods.getSlug?.() || '',
|
185
|
+
},
|
186
|
+
};
|
187
|
+
});
|
188
|
+
|
189
|
+
if (debug) {
|
190
|
+
logger.array('所有文档的路径', paths);
|
191
|
+
}
|
192
|
+
|
193
|
+
return paths;
|
194
|
+
}
|
195
|
+
}
|