@coffic/cosy-ui 0.6.38 → 0.6.40
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/assets/book.png +0 -0
- package/dist/card/CardCourse.astro +32 -0
- package/dist/card/index.ts +1 -1
- package/dist/cosy.ts +6 -0
- package/dist/database/BaseDB.ts +204 -168
- package/dist/database/BlogDB.ts +8 -8
- package/dist/database/CourseDB.ts +5 -5
- package/dist/database/ExperimentDB.ts +6 -6
- package/dist/database/LessonDB.ts +6 -6
- package/dist/database/MetaDB.ts +4 -4
- package/dist/entities/BaseDoc.ts +31 -4
- package/dist/entities/BlogDoc.ts +40 -0
- package/dist/entities/CourseDoc.ts +5 -5
- package/dist/entities/ExperimentDoc.ts +6 -6
- package/dist/entities/LessonDoc.ts +40 -6
- package/dist/index.ts +1 -1
- package/dist/utils/language.ts +7 -8
- package/dist/utils/link.ts +3 -3
- package/dist/utils/logger.ts +119 -103
- package/dist/utils/path.ts +3 -3
- package/package.json +1 -1
Binary file
|
@@ -0,0 +1,32 @@
|
|
1
|
+
---
|
2
|
+
import { Image } from 'astro:assets';
|
3
|
+
import bookCover from '../assets/book.png';
|
4
|
+
|
5
|
+
interface Props {
|
6
|
+
title: string;
|
7
|
+
link: string;
|
8
|
+
}
|
9
|
+
|
10
|
+
const { title, link } = Astro.props;
|
11
|
+
---
|
12
|
+
|
13
|
+
<div>
|
14
|
+
<a href={link} class="justify-center flex no-underline text-base-content">
|
15
|
+
<div class="w-56 h-80">
|
16
|
+
<div
|
17
|
+
class="bg-gradient-to-br w-full h-full from-accent/50 to-primary/30 rounded-3xl shadow-lg backdrop-blur-sm">
|
18
|
+
<div
|
19
|
+
class="bg-base-100/60 w-full h-full rounded-3xl border border-base-content/30 hover:scale-105 hover:shadow-2xl transform duration-200 backdrop-filter backdrop-blur-sm">
|
20
|
+
<div class="card-body p-1 h-full">
|
21
|
+
<Image src={bookCover} alt={title} class="h-3/5 w-full" />
|
22
|
+
<div>
|
23
|
+
<h2 class="text-lg text-center font-medium">
|
24
|
+
{title}
|
25
|
+
</h2>
|
26
|
+
</div>
|
27
|
+
</div>
|
28
|
+
</div>
|
29
|
+
</div>
|
30
|
+
</div>
|
31
|
+
</a>
|
32
|
+
</div>
|
package/dist/card/index.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
|
2
|
-
|
3
2
|
export { default as Card } from './Card.astro';
|
3
|
+
export { default as CardCourse } from './CardCourse.astro';
|
4
4
|
export { default as CardBasic } from './CardBasic.astro';
|
5
5
|
export { default as CardWithImage } from './CardWithImage.astro';
|
6
6
|
export { default as CardLink } from './CardLink.astro';
|
package/dist/cosy.ts
ADDED
package/dist/database/BaseDB.ts
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import { getCollection, getEntry, type CollectionEntry, type DataEntryMap } from 'astro:content';
|
2
|
-
import {
|
2
|
+
import { cosyLogger, ERROR_PREFIX } from '../cosy';
|
3
|
+
import type { BaseDoc } from '..';
|
3
4
|
|
4
5
|
/**
|
5
6
|
* BaseDB 是所有数据库类的基类,提供了通用的文档操作功能。
|
@@ -24,172 +25,207 @@ import { logger } from '../utils/logger';
|
|
24
25
|
* @template Doc - 文档类型,通常是自定义的文档类
|
25
26
|
*/
|
26
27
|
export abstract class BaseDB<
|
27
|
-
|
28
|
-
|
29
|
-
|
28
|
+
Collection extends keyof DataEntryMap,
|
29
|
+
Entry extends CollectionEntry<Collection>,
|
30
|
+
Doc extends BaseDoc<Collection, Entry>,
|
30
31
|
> {
|
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
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
32
|
+
/** 集合名称,必须在子类中指定 */
|
33
|
+
protected abstract collectionName: Collection;
|
34
|
+
|
35
|
+
/**
|
36
|
+
* 创建文档实例的方法,必须在子类中实现
|
37
|
+
* @param entry - 集合条目
|
38
|
+
* @returns 文档实例
|
39
|
+
*/
|
40
|
+
protected abstract createDoc(entry: Entry): Doc;
|
41
|
+
|
42
|
+
/**
|
43
|
+
* 获取所有文档的ID
|
44
|
+
* @returns 返回所有文档的ID数组
|
45
|
+
*/
|
46
|
+
protected async getAllIds(): Promise<string[]> {
|
47
|
+
const entries = await getCollection(this.collectionName);
|
48
|
+
return entries.map(entry => entry.id);
|
49
|
+
}
|
50
|
+
|
51
|
+
/**
|
52
|
+
* 获取集合中的所有条目
|
53
|
+
* @returns 返回所有条目的数组
|
54
|
+
*/
|
55
|
+
protected async getEntries(): Promise<Entry[]> {
|
56
|
+
return await getCollection(this.collectionName);
|
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
|
+
protected async getDocsByDepth(depth: number): Promise<Doc[]> {
|
71
|
+
const entries = await getCollection(
|
72
|
+
this.collectionName,
|
73
|
+
({ id }: { id: string }) => id.split('/').length === depth
|
74
|
+
);
|
75
|
+
|
76
|
+
if (entries.length === 0) {
|
77
|
+
cosyLogger.warn(`[BaseDB] 没有找到深度为${depth}的文档(collection=${this.collectionName as string})`);
|
78
|
+
const allEntries = await getCollection(this.collectionName);
|
79
|
+
cosyLogger.array('[BaseDB] 所有文档', allEntries.map(entry => entry.id));
|
80
|
+
return [];
|
81
|
+
}
|
82
|
+
|
83
|
+
return entries.map(entry => this.createDoc(entry as Entry));
|
84
|
+
}
|
85
|
+
|
86
|
+
/**
|
87
|
+
* 根据ID查找单个文档
|
88
|
+
* @param id - 文档ID
|
89
|
+
* @param debug - 是否启用调试模式, 默认为false
|
90
|
+
* @throws 如果ID不是字符串类型,则抛出错误
|
91
|
+
* @throws 如果文档不存在,则返回null
|
92
|
+
* @throws 如果发生其他错误,则抛出错误
|
93
|
+
* @returns 返回找到的文档,如果不存在则返回null
|
94
|
+
*/
|
95
|
+
async find(id: string, debug: boolean = false): Promise<Doc | null> {
|
96
|
+
if (debug) {
|
97
|
+
cosyLogger.info(`查找文档,ID: ${id}`);
|
98
|
+
}
|
99
|
+
|
100
|
+
if (id == undefined) {
|
101
|
+
cosyLogger.error('ID is undefined');
|
102
|
+
throw new Error(ERROR_PREFIX + 'ID is undefined');
|
103
|
+
}
|
104
|
+
|
105
|
+
if (typeof id !== 'string') {
|
106
|
+
cosyLogger.error('ID must be a string, but got ' + typeof id);
|
107
|
+
cosyLogger.debug(id);
|
108
|
+
throw new Error(ERROR_PREFIX + 'ID must be a string, but got ' + typeof id);
|
109
|
+
}
|
110
|
+
|
111
|
+
// 获取所有文档的ID并排好顺序
|
112
|
+
if (debug) {
|
113
|
+
const allIds = (await this.getAllIds()).sort();
|
114
|
+
cosyLogger.array('所有文档的ID', allIds);
|
115
|
+
}
|
116
|
+
|
117
|
+
// 根据ID查找文档
|
118
|
+
const entry = await getEntry(this.collectionName, id);
|
119
|
+
return entry ? this.createDoc(entry as Entry) : null;
|
120
|
+
}
|
121
|
+
|
122
|
+
/**
|
123
|
+
* 获取指定文档的直接子文档(不包括更深层级的文档)
|
124
|
+
* 例如对于文档 "zh-cn/blog":
|
125
|
+
* - "zh-cn/blog/post1.md" 会被包含
|
126
|
+
* - "zh-cn/blog/2024/post2.md" 不会被包含
|
127
|
+
*
|
128
|
+
* @param parentId - 父文档ID
|
129
|
+
* @returns 返回子文档数组
|
130
|
+
*/
|
131
|
+
async getChildren(parentId: string): Promise<Doc[]> {
|
132
|
+
const parentLevel = parentId.split('/').length;
|
133
|
+
const childrenLevel = parentLevel + 1;
|
134
|
+
|
135
|
+
const entries = await getCollection(
|
136
|
+
this.collectionName,
|
137
|
+
({ id }: { id: string }) => id.startsWith(parentId) && id.split('/').length === childrenLevel
|
138
|
+
);
|
139
|
+
|
140
|
+
return entries.map(entry => this.createDoc(entry as Entry));
|
141
|
+
}
|
142
|
+
|
143
|
+
/**
|
144
|
+
* 获取指定文档的所有后代文档(包括所有层级)
|
145
|
+
* 例如对于文档 "zh-cn/blog",以下都会被包含:
|
146
|
+
* - "zh-cn/blog/post1.md"
|
147
|
+
* - "zh-cn/blog/2024/post2.md"
|
148
|
+
* - "zh-cn/blog/2024/tech/post3.md"
|
149
|
+
*
|
150
|
+
* @param parentId - 父文档ID
|
151
|
+
* @returns 返回所有后代文档数组
|
152
|
+
*/
|
153
|
+
async getDescendantDocs(parentId: string): Promise<Doc[]> {
|
154
|
+
const entries = await getCollection(this.collectionName, ({ id }: { id: string }) => id.startsWith(parentId));
|
155
|
+
return entries.map(entry => this.createDoc(entry as Entry));
|
156
|
+
}
|
157
|
+
|
158
|
+
/**
|
159
|
+
* 获取指定语言和级别的文档
|
160
|
+
* 通过检查文档ID是否以指定语言代码开头来筛选
|
161
|
+
*
|
162
|
+
* @param lang - 语言代码(如 'zh-cn', 'en')
|
163
|
+
* @param level - 文档级别
|
164
|
+
* @returns 返回指定语言和级别的文档数组
|
165
|
+
*/
|
166
|
+
protected async allDocsByLangAndLevel(lang: string, level: number = 1, debug: boolean = false): Promise<Doc[]> {
|
167
|
+
const collectionName = this.collectionName as string;
|
168
|
+
const docs = await this.getDocsByDepth(level);
|
169
|
+
|
170
|
+
if (debug) {
|
171
|
+
cosyLogger.array(`[BaseDB] 所有${level}级文档(lang=any,collection=${collectionName})`, docs);
|
172
|
+
}
|
173
|
+
|
174
|
+
if (docs.length === 0) {
|
175
|
+
cosyLogger.warn(`[BaseDB] 没有找到${level}级文档(lang=any,collection=${collectionName})`);
|
176
|
+
return [];
|
177
|
+
}
|
178
|
+
|
179
|
+
const filteredDocs = docs.filter((doc) => {
|
180
|
+
const id = (doc as any).getId();
|
181
|
+
return id && typeof id === 'string' && id.startsWith(lang);
|
182
|
+
});
|
183
|
+
|
184
|
+
if (debug) {
|
185
|
+
cosyLogger.array(`[BaseDB] 所有${level}级文档(lang=${lang},collection=${collectionName})`, filteredDocs);
|
186
|
+
}
|
187
|
+
|
188
|
+
if (filteredDocs.length === 0) {
|
189
|
+
cosyLogger.warn(`[BaseDB] 没有找到${level}级文档(lang=${lang},collection=${collectionName})`);
|
190
|
+
cosyLogger.array(`[BaseDB] 所有${level}级文档`, docs.map((doc) => doc.getId()));
|
191
|
+
return [];
|
192
|
+
}
|
193
|
+
|
194
|
+
return filteredDocs;
|
195
|
+
}
|
196
|
+
|
197
|
+
/**
|
198
|
+
* 获取用于 Astro 静态路由生成的路径参数
|
199
|
+
* 为每个文档生成包含语言和slug的路径参数
|
200
|
+
*
|
201
|
+
* @returns 返回路径参数数组,每个元素包含 lang 和 slug
|
202
|
+
* @example
|
203
|
+
* ```typescript
|
204
|
+
* const paths = await db.getStaticPaths();
|
205
|
+
* // 返回格式:
|
206
|
+
* // [
|
207
|
+
* // { params: { lang: 'zh-cn', slug: 'post1' } },
|
208
|
+
* // { params: { lang: 'en', slug: 'post1' } }
|
209
|
+
* // ]
|
210
|
+
* ```
|
211
|
+
*/
|
212
|
+
async getStaticPaths() {
|
213
|
+
const debug = false;
|
214
|
+
const docs = await this.getDescendantDocs('');
|
215
|
+
const paths = docs.map((doc) => {
|
216
|
+
const docWithMethods = doc as any;
|
217
|
+
return {
|
218
|
+
params: {
|
219
|
+
lang: docWithMethods.getLang?.() || '',
|
220
|
+
slug: docWithMethods.getSlug?.() || '',
|
221
|
+
},
|
222
|
+
};
|
223
|
+
});
|
224
|
+
|
225
|
+
if (debug) {
|
226
|
+
cosyLogger.array('所有文档的路径', paths);
|
227
|
+
}
|
228
|
+
|
229
|
+
return paths;
|
230
|
+
}
|
195
231
|
}
|
package/dist/database/BlogDB.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import BlogDoc from '../entities/BlogDoc';
|
2
2
|
import type Tag from '../entities/Tag';
|
3
|
-
import {
|
3
|
+
import { cosyLogger } from '../cosy';
|
4
4
|
import { type CollectionEntry } from 'astro:content';
|
5
5
|
import { BaseDB } from './BaseDB';
|
6
6
|
|
@@ -40,7 +40,7 @@ class BlogDB extends BaseDB<typeof COLLECTION_BLOG, BlogEntry, BlogDoc> {
|
|
40
40
|
const entries = await this.getDocsByDepth(2);
|
41
41
|
|
42
42
|
if (debug) {
|
43
|
-
|
43
|
+
cosyLogger.array('所有博客文档', entries);
|
44
44
|
}
|
45
45
|
|
46
46
|
return entries;
|
@@ -62,10 +62,10 @@ class BlogDB extends BaseDB<typeof COLLECTION_BLOG, BlogEntry, BlogDoc> {
|
|
62
62
|
/**
|
63
63
|
* 获取用于 Astro 静态路由生成的路径参数,专门配合 [lang]/blogs/[slug].astro 使用
|
64
64
|
*
|
65
|
+
* @param debug - 是否开启调试模式
|
65
66
|
* @returns 返回路径参数数组
|
66
67
|
*/
|
67
|
-
async getStaticPaths() {
|
68
|
-
const debug = false;
|
68
|
+
async getStaticPaths(debug: boolean = false) {
|
69
69
|
const docs = await this.allBlogs();
|
70
70
|
|
71
71
|
const paths = docs.map((doc) => {
|
@@ -78,7 +78,7 @@ class BlogDB extends BaseDB<typeof COLLECTION_BLOG, BlogEntry, BlogDoc> {
|
|
78
78
|
});
|
79
79
|
|
80
80
|
if (debug) {
|
81
|
-
|
81
|
+
cosyLogger.array('所有博客文档的路径', paths);
|
82
82
|
}
|
83
83
|
|
84
84
|
return paths;
|
@@ -123,7 +123,7 @@ class BlogDB extends BaseDB<typeof COLLECTION_BLOG, BlogEntry, BlogDoc> {
|
|
123
123
|
const posts = await this.allBlogsByLang(lang);
|
124
124
|
|
125
125
|
if (debug) {
|
126
|
-
|
126
|
+
cosyLogger.array('posts', posts);
|
127
127
|
}
|
128
128
|
|
129
129
|
if (posts.length === 0) {
|
@@ -140,7 +140,7 @@ class BlogDB extends BaseDB<typeof COLLECTION_BLOG, BlogEntry, BlogDoc> {
|
|
140
140
|
});
|
141
141
|
|
142
142
|
if (debug) {
|
143
|
-
|
143
|
+
cosyLogger.array('tags', Array.from(tagsMap.values()));
|
144
144
|
}
|
145
145
|
|
146
146
|
return Array.from(tagsMap.values());
|
@@ -187,7 +187,7 @@ class BlogDB extends BaseDB<typeof COLLECTION_BLOG, BlogEntry, BlogDoc> {
|
|
187
187
|
});
|
188
188
|
|
189
189
|
if (debug) {
|
190
|
-
|
190
|
+
cosyLogger.array('所有的标签路径', paths);
|
191
191
|
}
|
192
192
|
|
193
193
|
return paths;
|
@@ -11,9 +11,9 @@ export type CourseEntry = CollectionEntry<typeof COLLECTION_COURSE>;
|
|
11
11
|
* 目录结构:
|
12
12
|
* ```
|
13
13
|
* courses/
|
14
|
-
* ├── zh-cn/
|
15
|
-
* │ ├── web-development/
|
16
|
-
* │ │ ├── index.md
|
14
|
+
* ├── zh-cn/ # 中文版本
|
15
|
+
* │ ├── web-development/ # 课程1
|
16
|
+
* │ │ ├── index.md # 课程文档
|
17
17
|
* │ │ ├── chapter1
|
18
18
|
* │ │ │ ├── index.md
|
19
19
|
* │ │ │ ├── content.md
|
@@ -22,10 +22,10 @@ export type CourseEntry = CollectionEntry<typeof COLLECTION_COURSE>;
|
|
22
22
|
* │ │ ├── index.md
|
23
23
|
* │ │ ├── content.md
|
24
24
|
* │ │ └── ...
|
25
|
-
* │ └── mobile-dev/
|
25
|
+
* │ └── mobile-dev/ # 课程2
|
26
26
|
* │ ├── index.md
|
27
27
|
* │ └── ...
|
28
|
-
* └── en/
|
28
|
+
* └── en/ # 英文版本
|
29
29
|
* └── ...
|
30
30
|
* ```
|
31
31
|
*/
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import { type CollectionEntry } from 'astro:content';
|
2
2
|
import { BaseDB } from './BaseDB';
|
3
3
|
import ExperimentDoc from '../entities/ExperimentDoc';
|
4
|
-
import {
|
4
|
+
import { cosyLogger } from '../cosy';
|
5
5
|
|
6
6
|
export const COLLECTION_EXPERIMENT = 'experiments' as const;
|
7
7
|
export type ExperimentEntry = CollectionEntry<typeof COLLECTION_EXPERIMENT>;
|
@@ -13,7 +13,7 @@ export type ExperimentEntry = CollectionEntry<typeof COLLECTION_EXPERIMENT>;
|
|
13
13
|
* ```
|
14
14
|
* experiments/
|
15
15
|
* ├── safari_itp/ # 实验目录
|
16
|
-
* │ ├── images/
|
16
|
+
* │ ├── images/ # 实验图片资源
|
17
17
|
* │ ├── components/ # 课程组件
|
18
18
|
* │ ├── en/ # 英文版本
|
19
19
|
* │ │ ├── index.mdx # 课程首页
|
@@ -61,14 +61,14 @@ class ExperimentDB extends BaseDB<typeof COLLECTION_EXPERIMENT, ExperimentEntry,
|
|
61
61
|
/**
|
62
62
|
* 获取用于 Astro 静态路由生成的路径参数
|
63
63
|
*
|
64
|
+
* @param debug - 是否开启调试模式
|
64
65
|
* @returns 返回路径参数数组
|
65
66
|
*/
|
66
|
-
async getStaticPaths() {
|
67
|
-
const debug = false;
|
67
|
+
async getStaticPaths(debug: boolean = false) {
|
68
68
|
const docs = await this.getEntries();
|
69
69
|
|
70
70
|
if (debug) {
|
71
|
-
|
71
|
+
cosyLogger.array('所有文档', docs);
|
72
72
|
}
|
73
73
|
|
74
74
|
const paths = docs.map((doc) => {
|
@@ -91,7 +91,7 @@ class ExperimentDB extends BaseDB<typeof COLLECTION_EXPERIMENT, ExperimentEntry,
|
|
91
91
|
});
|
92
92
|
|
93
93
|
if (debug) {
|
94
|
-
|
94
|
+
cosyLogger.array('所有文档的路径', paths);
|
95
95
|
}
|
96
96
|
|
97
97
|
return paths;
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import { type CollectionEntry } from 'astro:content';
|
2
2
|
import { BaseDB } from './BaseDB';
|
3
3
|
import LessonDoc from '../entities/LessonDoc';
|
4
|
-
import {
|
4
|
+
import { cosyLogger } from '../cosy';
|
5
5
|
|
6
6
|
export const COLLECTION_LESSON = 'lessons' as const;
|
7
7
|
export type LessonEntry = CollectionEntry<typeof COLLECTION_LESSON>;
|
@@ -60,15 +60,15 @@ class LessonDB extends BaseDB<typeof COLLECTION_LESSON, LessonEntry, LessonDoc>
|
|
60
60
|
|
61
61
|
/**
|
62
62
|
* 获取用于 Astro 静态路由生成的路径参数
|
63
|
-
*
|
63
|
+
*
|
64
|
+
* @param debug - 是否开启调试模式
|
64
65
|
* @returns 返回路径参数数组
|
65
66
|
*/
|
66
|
-
async getStaticPaths() {
|
67
|
-
const debug = false;
|
67
|
+
async getStaticPaths(debug: boolean = false) {
|
68
68
|
const docs = await this.getEntries();
|
69
69
|
|
70
70
|
if (debug) {
|
71
|
-
|
71
|
+
cosyLogger.array('所有文档', docs);
|
72
72
|
}
|
73
73
|
|
74
74
|
const paths = docs.map((doc) => {
|
@@ -91,7 +91,7 @@ class LessonDB extends BaseDB<typeof COLLECTION_LESSON, LessonEntry, LessonDoc>
|
|
91
91
|
});
|
92
92
|
|
93
93
|
if (debug) {
|
94
|
-
|
94
|
+
cosyLogger.array('所有文档的路径', paths);
|
95
95
|
}
|
96
96
|
|
97
97
|
return paths;
|
package/dist/database/MetaDB.ts
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import MetaDoc from '../entities/MetaDoc';
|
2
|
-
import {
|
2
|
+
import { cosyLogger } from '../cosy';
|
3
3
|
import { type CollectionEntry } from 'astro:content';
|
4
4
|
import { BaseDB } from './BaseDB';
|
5
5
|
|
@@ -47,10 +47,10 @@ class MetaDB extends BaseDB<typeof COLLECTION_META, MetaEntry, MetaDoc> {
|
|
47
47
|
/**
|
48
48
|
* 获取用于 Astro 静态路由生成的路径参数,专门配合 [lang]/meta/[slug].astro 使用
|
49
49
|
*
|
50
|
+
* @param debug - 是否开启调试模式
|
50
51
|
* @returns 返回路径参数数组
|
51
52
|
*/
|
52
|
-
async getStaticPaths() {
|
53
|
-
const debug = false;
|
53
|
+
async getStaticPaths(debug: boolean = false) {
|
54
54
|
const docs = await this.getDescendantDocs('');
|
55
55
|
|
56
56
|
const paths = docs.map((doc) => {
|
@@ -63,7 +63,7 @@ class MetaDB extends BaseDB<typeof COLLECTION_META, MetaEntry, MetaDoc> {
|
|
63
63
|
});
|
64
64
|
|
65
65
|
if (debug) {
|
66
|
-
|
66
|
+
cosyLogger.array('所有元数据文档的路径', paths);
|
67
67
|
}
|
68
68
|
|
69
69
|
return paths;
|
package/dist/entities/BaseDoc.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import { render, type RenderResult, type CollectionEntry, type DataEntryMap } from 'astro:content';
|
2
2
|
import { SidebarItemEntity, type SidebarProvider } from './SidebarItem';
|
3
|
-
import {
|
3
|
+
import { cosyLogger, ERROR_PREFIX } from '../cosy';
|
4
4
|
|
5
5
|
/**
|
6
6
|
* 文档基类,提供所有文档类型共享的基本功能
|
@@ -8,8 +8,7 @@ import { logger } from '../utils/logger';
|
|
8
8
|
export abstract class BaseDoc<
|
9
9
|
Collection extends keyof DataEntryMap,
|
10
10
|
T extends CollectionEntry<Collection>,
|
11
|
-
> implements SidebarProvider
|
12
|
-
{
|
11
|
+
> implements SidebarProvider {
|
13
12
|
protected entry: T;
|
14
13
|
|
15
14
|
constructor(entry: T) {
|
@@ -54,6 +53,28 @@ export abstract class BaseDoc<
|
|
54
53
|
*/
|
55
54
|
getAncestorId(level: number): string {
|
56
55
|
const ancestorIds = this.getAncestorIds();
|
56
|
+
|
57
|
+
if (level < 1) {
|
58
|
+
cosyLogger.error('Level must be greater than 0');
|
59
|
+
throw new Error(ERROR_PREFIX + 'Level must be greater than 0');
|
60
|
+
}
|
61
|
+
|
62
|
+
if (level >= this.getLevel()) {
|
63
|
+
cosyLogger.debug("当前文档的ID:" + this.entry.id);
|
64
|
+
cosyLogger.debug("当前文档的Level:" + this.getLevel());
|
65
|
+
cosyLogger.debug("正在获取祖先ID,level:" + level);
|
66
|
+
|
67
|
+
throw new Error(ERROR_PREFIX + 'Level must be less than the document level');
|
68
|
+
}
|
69
|
+
|
70
|
+
if (ancestorIds.length < level) {
|
71
|
+
cosyLogger.debug("当前文档的ID\n" + this.entry.id);
|
72
|
+
cosyLogger.array("当前文档的祖先IDs", ancestorIds);
|
73
|
+
cosyLogger.debug("正在获取祖先ID,level:\n" + level);
|
74
|
+
|
75
|
+
throw new Error(ERROR_PREFIX + 'Level must be greater than 0');
|
76
|
+
}
|
77
|
+
|
57
78
|
return ancestorIds[level - 1];
|
58
79
|
}
|
59
80
|
|
@@ -93,6 +114,12 @@ export abstract class BaseDoc<
|
|
93
114
|
* 子类可以根据需要覆盖此方法
|
94
115
|
*/
|
95
116
|
getTopDocId(): string {
|
117
|
+
const level = this.getLevel();
|
118
|
+
|
119
|
+
if (level <= 2) {
|
120
|
+
return this.entry.id;
|
121
|
+
}
|
122
|
+
|
96
123
|
return this.getAncestorId(2);
|
97
124
|
}
|
98
125
|
|
@@ -155,7 +182,7 @@ export abstract class BaseDoc<
|
|
155
182
|
const childItems = await Promise.all(children.map((child) => child.toSidebarItem()));
|
156
183
|
|
157
184
|
if (debug) {
|
158
|
-
|
185
|
+
cosyLogger.info(`${this.entry.id} 的侧边栏项目`);
|
159
186
|
console.log(childItems);
|
160
187
|
}
|
161
188
|
|
package/dist/entities/BlogDoc.ts
CHANGED
@@ -5,6 +5,46 @@ import { BaseDoc } from './BaseDoc';
|
|
5
5
|
import { COLLECTION_BLOG } from '../database/BlogDB';
|
6
6
|
import { blogDB } from '../database/BlogDB';
|
7
7
|
|
8
|
+
/**
|
9
|
+
* 博客文档类,配合 BlogDB 使用
|
10
|
+
*
|
11
|
+
* 目录结构:
|
12
|
+
* ```
|
13
|
+
* blogs/
|
14
|
+
* ├── zh-cn/ # 中文博客目录
|
15
|
+
* │ ├── typescript-intro.md # 文章内容
|
16
|
+
* │ ├── images/ # 文章相关图片
|
17
|
+
* │ └── web-performance.md
|
18
|
+
* └── en/ # 英文博客目录
|
19
|
+
* ├── typescript-intro.md
|
20
|
+
* ├── images/
|
21
|
+
* └── web-performance.md
|
22
|
+
* ```
|
23
|
+
*
|
24
|
+
* 说明:
|
25
|
+
* - 每个语言(如 zh-cn, en)是一个独立的目录,存放该语言下的所有博客文章
|
26
|
+
* - 每篇博客为一个 markdown 文件,文件名即为 slug
|
27
|
+
* - 每个语言目录下可包含 images 文件夹,存放该语言博客相关图片
|
28
|
+
* - 支持多语言博客内容,便于国际化
|
29
|
+
* - 博客目录可作为 git 子模块独立管理
|
30
|
+
*
|
31
|
+
* 相关:
|
32
|
+
* - BlogDB:博客数据库管理类,负责博客文档的增删查改
|
33
|
+
* - BlogDoc:博客文档实体类,封装单篇博客的相关操作
|
34
|
+
*
|
35
|
+
* 用法示例:
|
36
|
+
* ```typescript
|
37
|
+
* import BlogDoc from '../entities/BlogDoc';
|
38
|
+
* import { blogDB } from '../database/BlogDB';
|
39
|
+
*
|
40
|
+
* // 获取所有中文博客
|
41
|
+
* const blogs = await blogDB.allBlogsByLang('zh-cn');
|
42
|
+
*
|
43
|
+
* // 获取博客链接
|
44
|
+
* const link = blogs[0].getLink();
|
45
|
+
* ```
|
46
|
+
*/
|
47
|
+
|
8
48
|
export default class BlogDoc extends BaseDoc<typeof COLLECTION_BLOG, BlogEntry> {
|
9
49
|
private constructor(entry: BlogEntry) {
|
10
50
|
super(entry);
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import {
|
1
|
+
import { cosyLogger } from '../cosy';
|
2
2
|
import { SidebarItemEntity } from './SidebarItem';
|
3
3
|
import type { CourseEntry } from '../database/CourseDB';
|
4
4
|
import { courseDB } from '../database/CourseDB';
|
@@ -36,12 +36,12 @@ export default class CourseDoc extends BaseDoc<typeof COLLECTION_COURSE, CourseE
|
|
36
36
|
async getAncestor(level: number): Promise<CourseDoc | null> {
|
37
37
|
const debug = false;
|
38
38
|
if (debug) {
|
39
|
-
|
39
|
+
cosyLogger.info(`获取 ${this.entry.id} 的祖先文档,level: ${level}`);
|
40
40
|
}
|
41
41
|
|
42
42
|
if (level >= this.getLevel()) {
|
43
43
|
if (debug) {
|
44
|
-
|
44
|
+
cosyLogger.info(`祖先文档为自身`);
|
45
45
|
}
|
46
46
|
return this;
|
47
47
|
}
|
@@ -61,7 +61,7 @@ export default class CourseDoc extends BaseDoc<typeof COLLECTION_COURSE, CourseE
|
|
61
61
|
(a, b) => a.getOrder() - b.getOrder()
|
62
62
|
);
|
63
63
|
if (debug && children.length > 0) {
|
64
|
-
|
64
|
+
cosyLogger.array(
|
65
65
|
`${this.entry.id} 的子文档(${children.length})`,
|
66
66
|
children.map((child) => `#${child.getOrder()} ${child.entry.id}`)
|
67
67
|
);
|
@@ -79,7 +79,7 @@ export default class CourseDoc extends BaseDoc<typeof COLLECTION_COURSE, CourseE
|
|
79
79
|
}
|
80
80
|
|
81
81
|
if (debug) {
|
82
|
-
|
82
|
+
cosyLogger.info(`${this.entry.id} 的侧边栏项目`);
|
83
83
|
console.log(childItems);
|
84
84
|
}
|
85
85
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import type { ExperimentEntry } from '../database/ExperimentDB';
|
2
2
|
import { experimentDB } from '../database/ExperimentDB';
|
3
|
-
import {
|
3
|
+
import { cosyLogger } from '../cosy';
|
4
4
|
import { SidebarItemEntity } from './SidebarItem';
|
5
5
|
import { LinkUtil } from '../utils/link';
|
6
6
|
import { COLLECTION_EXPERIMENT } from '../database/ExperimentDB';
|
@@ -31,7 +31,7 @@ export default class ExperimentDoc extends BaseDoc<typeof COLLECTION_EXPERIMENT,
|
|
31
31
|
const link = LinkUtil.getExperimentLink(lang, this.getId());
|
32
32
|
|
33
33
|
if (debug) {
|
34
|
-
|
34
|
+
cosyLogger.info(`获取 ${this.entry.id} 的链接: ${link}`);
|
35
35
|
}
|
36
36
|
|
37
37
|
return link;
|
@@ -51,7 +51,7 @@ export default class ExperimentDoc extends BaseDoc<typeof COLLECTION_EXPERIMENT,
|
|
51
51
|
const lang = parts[1];
|
52
52
|
|
53
53
|
if (debug) {
|
54
|
-
|
54
|
+
cosyLogger.info(`获取 ${this.entry.id} 的语言: ${lang}`);
|
55
55
|
}
|
56
56
|
|
57
57
|
return lang;
|
@@ -61,7 +61,7 @@ export default class ExperimentDoc extends BaseDoc<typeof COLLECTION_EXPERIMENT,
|
|
61
61
|
const debug = false;
|
62
62
|
|
63
63
|
if (debug) {
|
64
|
-
|
64
|
+
cosyLogger.info(`获取 ${this.entry.id} 的 HTML`);
|
65
65
|
}
|
66
66
|
|
67
67
|
return this.entry.rendered?.html || '';
|
@@ -71,7 +71,7 @@ export default class ExperimentDoc extends BaseDoc<typeof COLLECTION_EXPERIMENT,
|
|
71
71
|
const debug = false;
|
72
72
|
|
73
73
|
if (debug) {
|
74
|
-
|
74
|
+
cosyLogger.info(`获取 ${this.entry.id} 的 headings`);
|
75
75
|
}
|
76
76
|
|
77
77
|
return (this.entry.rendered?.metadata?.headings as IHeadingType[]) || [];
|
@@ -101,7 +101,7 @@ export default class ExperimentDoc extends BaseDoc<typeof COLLECTION_EXPERIMENT,
|
|
101
101
|
}
|
102
102
|
|
103
103
|
if (debug) {
|
104
|
-
|
104
|
+
cosyLogger.info(`${this.entry.id} 的侧边栏项目`);
|
105
105
|
console.log(childItems);
|
106
106
|
}
|
107
107
|
|
@@ -1,12 +1,46 @@
|
|
1
1
|
import type { LessonEntry } from '../database/LessonDB';
|
2
2
|
import { lessonDB } from '../database/LessonDB';
|
3
|
-
import {
|
3
|
+
import { cosyLogger } from '../cosy';
|
4
4
|
import { SidebarItemEntity } from './SidebarItem';
|
5
5
|
import { LinkUtil } from '../utils/link';
|
6
6
|
import { COLLECTION_LESSON } from '../database/LessonDB';
|
7
7
|
import { BaseDoc } from './BaseDoc';
|
8
8
|
import type { IHeadingType } from '../types/heading';
|
9
9
|
|
10
|
+
/**
|
11
|
+
* 课程文档类,配合 LessonDB 使用
|
12
|
+
*
|
13
|
+
* 目录结构:
|
14
|
+
* ```
|
15
|
+
* lessons/
|
16
|
+
* ├── build_your_own_web_toolbox/ # 课程目录
|
17
|
+
* │ ├── images/ # 课程图片资源
|
18
|
+
* │ ├── components/ # 课程组件
|
19
|
+
* │ ├── en/ # 英文版本
|
20
|
+
* │ │ ├── index.mdx # 课程首页
|
21
|
+
* │ │ ├── 1.mdx # 第一章
|
22
|
+
* │ │ └── 2.mdx # 第二章
|
23
|
+
* │ └── zh-cn/ # 中文版本
|
24
|
+
* │ ├── index.mdx # 课程首页
|
25
|
+
* │ ├── 1.mdx # 第一章
|
26
|
+
* │ └── 2.mdx # 第二章
|
27
|
+
* └── learn_astro/ # 另一个课程
|
28
|
+
* ├── en/
|
29
|
+
* │ ├── index.mdx
|
30
|
+
* │ ├── 1.mdx
|
31
|
+
* │ └── 2.mdx
|
32
|
+
* └── zh-cn/
|
33
|
+
* ├── index.mdx
|
34
|
+
* ├── 1.mdx
|
35
|
+
* └── 2.mdx
|
36
|
+
* ```
|
37
|
+
*
|
38
|
+
* 说明:
|
39
|
+
* - 每个课程(如 build_your_own_web_toolbox)是一个独立的目录
|
40
|
+
* - 课程目录可以包含多语言版本(en, zh-cn 等)
|
41
|
+
* - 每个语言版本包含完整的课程内容
|
42
|
+
* - 课程目录可以作为 git 子模块独立管理
|
43
|
+
*/
|
10
44
|
export default class LessonDoc extends BaseDoc<typeof COLLECTION_LESSON, LessonEntry> {
|
11
45
|
constructor(entry: LessonEntry) {
|
12
46
|
super(entry);
|
@@ -31,7 +65,7 @@ export default class LessonDoc extends BaseDoc<typeof COLLECTION_LESSON, LessonE
|
|
31
65
|
const link = LinkUtil.getLessonLink(lang, this.getId());
|
32
66
|
|
33
67
|
if (debug) {
|
34
|
-
|
68
|
+
cosyLogger.info(`获取 ${this.entry.id} 的链接: ${link}`);
|
35
69
|
}
|
36
70
|
|
37
71
|
return link;
|
@@ -51,7 +85,7 @@ export default class LessonDoc extends BaseDoc<typeof COLLECTION_LESSON, LessonE
|
|
51
85
|
const lang = parts[1];
|
52
86
|
|
53
87
|
if (debug) {
|
54
|
-
|
88
|
+
cosyLogger.info(`获取 ${this.entry.id} 的语言: ${lang}`);
|
55
89
|
}
|
56
90
|
|
57
91
|
return lang;
|
@@ -61,7 +95,7 @@ export default class LessonDoc extends BaseDoc<typeof COLLECTION_LESSON, LessonE
|
|
61
95
|
const debug = false;
|
62
96
|
|
63
97
|
if (debug) {
|
64
|
-
|
98
|
+
cosyLogger.info(`获取 ${this.entry.id} 的 HTML`);
|
65
99
|
}
|
66
100
|
|
67
101
|
return this.entry.rendered?.html || '';
|
@@ -71,7 +105,7 @@ export default class LessonDoc extends BaseDoc<typeof COLLECTION_LESSON, LessonE
|
|
71
105
|
const debug = false;
|
72
106
|
|
73
107
|
if (debug) {
|
74
|
-
|
108
|
+
cosyLogger.info(`获取 ${this.entry.id} 的 headings`);
|
75
109
|
}
|
76
110
|
|
77
111
|
return (this.entry.rendered?.metadata?.headings as IHeadingType[]) || [];
|
@@ -101,7 +135,7 @@ export default class LessonDoc extends BaseDoc<typeof COLLECTION_LESSON, LessonE
|
|
101
135
|
}
|
102
136
|
|
103
137
|
if (debug) {
|
104
|
-
|
138
|
+
cosyLogger.info(`${this.entry.id} 的侧边栏项目`);
|
105
139
|
console.log(childItems);
|
106
140
|
}
|
107
141
|
|
package/dist/index.ts
CHANGED
package/dist/utils/language.ts
CHANGED
@@ -1,16 +1,15 @@
|
|
1
|
-
/**
|
2
|
-
* 语言工具模块
|
3
|
-
*
|
4
|
-
* 提供语言相关的工具函数,用于多语言支持
|
5
|
-
*/
|
6
|
-
|
7
1
|
import { getRelativeLocaleUrl } from 'astro:i18n';
|
8
|
-
import { logger } from './logger';
|
9
2
|
import type { AstroGlobal } from 'astro';
|
3
|
+
import { cosyLogger } from '../cosy';
|
10
4
|
|
11
5
|
// 默认语言
|
12
6
|
export const DEFAULT_LANGUAGE = 'en';
|
13
7
|
|
8
|
+
/**
|
9
|
+
* 语言工具模块
|
10
|
+
*
|
11
|
+
* 提供语言相关的工具函数,用于多语言支持
|
12
|
+
*/
|
14
13
|
export class LanguageUtil {
|
15
14
|
static getRelativeLink(locale: string, astro: AstroGlobal): string {
|
16
15
|
const debug = false;
|
@@ -19,7 +18,7 @@ export class LanguageUtil {
|
|
19
18
|
const result = getRelativeLocaleUrl(locale, originalPath.replaceAll('/' + currentLocale, ''));
|
20
19
|
|
21
20
|
if (debug) {
|
22
|
-
|
21
|
+
cosyLogger.debug(
|
23
22
|
`getRelativeLink: locale=${locale}, currentPath=${originalPath}, currentLocale=${currentLocale}, result=${result}`
|
24
23
|
);
|
25
24
|
}
|
package/dist/utils/link.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import {
|
1
|
+
import { cosyLogger } from '../cosy';
|
2
2
|
|
3
3
|
export class LinkUtil {
|
4
4
|
// 从 astro.config.ts 中获取基础路径
|
@@ -78,7 +78,7 @@ export class LinkUtil {
|
|
78
78
|
const blogIdWithoutLang = blogId.replace(`${lang}/`, '');
|
79
79
|
|
80
80
|
if (debug) {
|
81
|
-
|
81
|
+
cosyLogger.info(`获取博客文档链接,博客文档ID: ${blogId}`);
|
82
82
|
}
|
83
83
|
|
84
84
|
return `/${lang}/blogs/${blogIdWithoutLang}`;
|
@@ -90,7 +90,7 @@ export class LinkUtil {
|
|
90
90
|
const courseIdWithoutLang = courseId.replace(`${lang}/`, '');
|
91
91
|
|
92
92
|
if (debug) {
|
93
|
-
|
93
|
+
cosyLogger.info(`获取课程文档链接,课程文档ID: ${courseId}`);
|
94
94
|
}
|
95
95
|
|
96
96
|
return LinkUtil.createUrl(`/${lang}/courses/${courseIdWithoutLang}`);
|
package/dist/utils/logger.ts
CHANGED
@@ -5,110 +5,126 @@ const SHOW_TIMESTAMP = false;
|
|
5
5
|
|
6
6
|
// ANSI 颜色代码
|
7
7
|
const colors = {
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
8
|
+
reset: '\x1b[0m',
|
9
|
+
debug: '\x1b[36m', // 青色
|
10
|
+
info: '\x1b[32m', // 绿色
|
11
|
+
warn: '\x1b[33m', // 黄色
|
12
|
+
error: '\x1b[31m', // 红色
|
13
|
+
gray: '\x1b[90m', // 灰色用于时间戳
|
14
14
|
};
|
15
15
|
|
16
|
-
class Logger {
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
-
|
16
|
+
export class Logger {
|
17
|
+
private topic: string = '';
|
18
|
+
|
19
|
+
private formatArray(arr: any[]): string {
|
20
|
+
const MAX_LINES = 70;
|
21
|
+
const MAX_LENGTH = 100;
|
22
|
+
|
23
|
+
const truncateString = (str: string): string => {
|
24
|
+
return str.length > MAX_LENGTH ? str.slice(0, MAX_LENGTH) + '...' : str;
|
25
|
+
};
|
26
|
+
|
27
|
+
const truncateObject = (obj: any): any => {
|
28
|
+
if (typeof obj !== 'object' || obj === null) {
|
29
|
+
return typeof obj === 'string' ? truncateString(obj) : obj;
|
30
|
+
}
|
31
|
+
|
32
|
+
const result: any = Array.isArray(obj) ? [] : {};
|
33
|
+
for (const [key, value] of Object.entries(obj)) {
|
34
|
+
result[key] =
|
35
|
+
typeof value === 'string'
|
36
|
+
? truncateString(value)
|
37
|
+
: typeof value === 'object'
|
38
|
+
? truncateObject(value)
|
39
|
+
: value;
|
40
|
+
}
|
41
|
+
return result;
|
42
|
+
};
|
43
|
+
|
44
|
+
const items = arr.slice(0, MAX_LINES).map((item) => {
|
45
|
+
const truncatedItem = truncateObject(item);
|
46
|
+
// 使用2个空格缩进,并在每行前添加 " • "
|
47
|
+
const jsonString = JSON.stringify(truncatedItem, null, 2)
|
48
|
+
.split('\n')
|
49
|
+
.map((line, index) => (index === 0 ? ` • ${line}` : ` ${line}`))
|
50
|
+
.join('\n');
|
51
|
+
return jsonString;
|
52
|
+
});
|
53
|
+
|
54
|
+
let output = items.join('\n');
|
55
|
+
if (arr.length > MAX_LINES) {
|
56
|
+
const remainingCount = arr.length - MAX_LINES;
|
57
|
+
output += `\n ⋮ ... and ${remainingCount} more items`;
|
58
|
+
}
|
59
|
+
|
60
|
+
return output;
|
61
|
+
}
|
62
|
+
|
63
|
+
private log(level: LogLevel, message: string | object | any[], newLine: boolean = false) {
|
64
|
+
// 使用本地时间,并格式化为 HH:mm:ss 格式
|
65
|
+
const timestamp = new Date().toLocaleTimeString('zh-CN', {
|
66
|
+
hour12: false,
|
67
|
+
hour: '2-digit',
|
68
|
+
minute: '2-digit',
|
69
|
+
second: '2-digit',
|
70
|
+
});
|
71
|
+
|
72
|
+
const formattedMessage = Array.isArray(message)
|
73
|
+
? this.formatArray(message)
|
74
|
+
: typeof message === 'object'
|
75
|
+
? JSON.stringify(message, null, 2)
|
76
|
+
: message;
|
77
|
+
|
78
|
+
const timestampPart = SHOW_TIMESTAMP ? `${colors.gray}${timestamp}${colors.reset} ` : '';
|
79
|
+
|
80
|
+
const emoji = {
|
81
|
+
debug: '🔍',
|
82
|
+
info: '🐳',
|
83
|
+
warn: '🚨',
|
84
|
+
error: '❌',
|
85
|
+
}[level];
|
86
|
+
|
87
|
+
if (newLine) {
|
88
|
+
console.log("")
|
89
|
+
}
|
90
|
+
|
91
|
+
console.log(
|
92
|
+
timestampPart +
|
93
|
+
`${colors[level]}${emoji} ` +
|
94
|
+
this.getPrefix() +
|
95
|
+
`${level.toUpperCase()}${colors.reset} ` +
|
96
|
+
`${colors.gray}:${colors.reset} ` +
|
97
|
+
`${colors[level]}${formattedMessage}${colors.reset}`
|
98
|
+
);
|
99
|
+
}
|
100
|
+
|
101
|
+
private getPrefix() {
|
102
|
+
return this.topic ? `[${this.topic}] ` : '';
|
103
|
+
}
|
104
|
+
|
105
|
+
constructor(topic: string = '') {
|
106
|
+
this.topic = topic;
|
107
|
+
}
|
108
|
+
|
109
|
+
debug(message: string | object) {
|
110
|
+
this.log('debug', message);
|
111
|
+
}
|
112
|
+
|
113
|
+
info(message: string | object) {
|
114
|
+
this.log('info', message);
|
115
|
+
}
|
116
|
+
|
117
|
+
warn(message: string | object) {
|
118
|
+
this.log('warn', message);
|
119
|
+
}
|
120
|
+
|
121
|
+
error(message: string | object) {
|
122
|
+
this.log('error', message, true);
|
123
|
+
}
|
124
|
+
|
125
|
+
array(title: string, arr: any[]) {
|
126
|
+
this.log('info', title + '\n' + this.formatArray(arr));
|
127
|
+
}
|
112
128
|
}
|
113
129
|
|
114
|
-
export const
|
130
|
+
export const defaultLogger = new Logger();
|
package/dist/utils/path.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import {
|
1
|
+
import { cosyLogger } from '../cosy';
|
2
2
|
|
3
3
|
/**
|
4
4
|
* 判断当前路径是否匹配目标路径
|
@@ -10,13 +10,13 @@ export function isPathMatch(currentPath: string, targetPath: string): boolean {
|
|
10
10
|
const debug = false;
|
11
11
|
|
12
12
|
if (debug) {
|
13
|
-
|
13
|
+
cosyLogger.info(`判断 ${currentPath} 是否匹配 ${targetPath}`);
|
14
14
|
}
|
15
15
|
|
16
16
|
// 如果完全匹配,直接返回
|
17
17
|
if (currentPath === targetPath) {
|
18
18
|
if (debug) {
|
19
|
-
|
19
|
+
cosyLogger.info(`${currentPath} 完全匹配 ${targetPath}`);
|
20
20
|
}
|
21
21
|
return true;
|
22
22
|
}
|