@coffic/cosy-ui 0.5.8 → 0.5.14

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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +14 -46
  3. package/dist/app.css +1 -1
  4. package/dist/collections/ArticleCollection.ts +19 -0
  5. package/dist/collections/BlogCollection.ts +28 -0
  6. package/dist/collections/CourseCollection.ts +11 -0
  7. package/dist/collections/ExperimentCollection.ts +18 -0
  8. package/dist/collections/LessonCollection.ts +25 -0
  9. package/dist/collections/MetaCollection.ts +17 -0
  10. package/dist/components/data-display/TeamMembers.astro +1 -1
  11. package/dist/components/display/Card.astro +0 -3
  12. package/dist/components/display/CodeBlock.astro +1 -2
  13. package/dist/components/display/Modal.astro +1 -2
  14. package/dist/components/icons/SearchIcon.astro +30 -34
  15. package/dist/components/icons/SunCloudyIcon.astro +35 -39
  16. package/dist/components/layouts/BaseLayout.astro +3 -2
  17. package/dist/components/navigation/TableOfContents.astro +6 -3
  18. package/dist/components/typography/Text.astro +1 -1
  19. package/dist/database/BlogDB.ts +199 -0
  20. package/dist/database/CourseDB.ts +85 -0
  21. package/dist/database/ExperimentDB.ts +103 -0
  22. package/dist/database/LessonDB.ts +103 -0
  23. package/dist/database/MetaDB.ts +75 -0
  24. package/dist/entities/BaseDoc.ts +170 -0
  25. package/dist/entities/BlogDoc.ts +53 -0
  26. package/dist/entities/CourseDoc.ts +56 -0
  27. package/dist/entities/ExperimentDoc.ts +117 -0
  28. package/dist/entities/Heading.ts +13 -0
  29. package/dist/entities/LessonDoc.ts +114 -0
  30. package/dist/entities/MetaDoc.ts +82 -0
  31. package/dist/entities/Tag.ts +42 -0
  32. package/dist/index.ts +9 -1
  33. package/dist/types/static-path.ts +8 -0
  34. package/dist/utils/image.ts +74 -70
  35. package/dist/utils/lang_package.ts +205 -206
  36. package/dist/utils/language.ts +0 -7
  37. package/dist/utils/link.ts +245 -239
  38. package/dist/utils/logger.ts +101 -126
  39. package/dist/utils/social.ts +1 -1
  40. package/package.json +24 -18
  41. package/dist/models/BaseDoc.ts +0 -164
@@ -0,0 +1,114 @@
1
+ import type { LessonEntry } from '../database/LessonDB';
2
+ import lessonDB from '../database/LessonDB';
3
+ import { logger } from '../utils/logger';
4
+ import { SidebarItemEntity } from './SidebarItem';
5
+ import type { Heading } from './Heading';
6
+ import { LinkUtil } from '../utils/link';
7
+ import { HierarchicalDoc } from './BaseDoc';
8
+ import { COLLECTION_NAME } from '../database/LessonDB';
9
+
10
+ export default class LessonDoc extends HierarchicalDoc<typeof COLLECTION_NAME, LessonEntry> {
11
+ constructor(entry: LessonEntry) {
12
+ super(entry);
13
+ }
14
+
15
+ isBook(): boolean {
16
+ return this.entry.id.split('/').length === 2;
17
+ }
18
+
19
+ async getBookId(): Promise<string> {
20
+ return await this.getTopDocId();
21
+ }
22
+
23
+ async getBook(): Promise<LessonDoc | null> {
24
+ const bookId = await this.getBookId();
25
+ return await lessonDB.find(bookId);
26
+ }
27
+
28
+ getLink(): string {
29
+ const debug = false;
30
+ const lang = this.getLang();
31
+ const link = LinkUtil.getLessonLink(lang, this.getId());
32
+
33
+ if (debug) {
34
+ logger.info(`获取 ${this.entry.id} 的链接: ${link}`);
35
+ }
36
+
37
+ return link;
38
+ }
39
+
40
+ /**
41
+ * 获取文档的语言
42
+ *
43
+ * 文档的 id 格式为 `book-id/zh-cn/chapter-id/lesson-id`
44
+ *
45
+ * @returns 语言
46
+ */
47
+ override getLang(): string {
48
+ const debug = false;
49
+
50
+ const parts = this.entry.id.split('/');
51
+ const lang = parts[1];
52
+
53
+ if (debug) {
54
+ logger.info(`获取 ${this.entry.id} 的语言: ${lang}`);
55
+ }
56
+
57
+ return lang;
58
+ }
59
+
60
+ getHTML(): string {
61
+ const debug = false;
62
+
63
+ if (debug) {
64
+ logger.info(`获取 ${this.entry.id} 的 HTML`);
65
+ }
66
+
67
+ return this.entry.rendered?.html || '';
68
+ }
69
+
70
+ getHeadings(): Heading[] {
71
+ const debug = false;
72
+
73
+ if (debug) {
74
+ logger.info(`获取 ${this.entry.id} 的 headings`);
75
+ }
76
+
77
+ return (this.entry.rendered?.metadata?.headings as Heading[]) || [];
78
+ }
79
+
80
+ async getTopDoc(): Promise<LessonDoc | null> {
81
+ const bookId = await this.getBookId();
82
+ return await lessonDB.find(bookId);
83
+ }
84
+
85
+ async getChildren(): Promise<LessonDoc[]> {
86
+ return await lessonDB.getChildren(this.entry.id);
87
+ }
88
+
89
+ async getDescendants(): Promise<LessonDoc[]> {
90
+ return await lessonDB.getDescendantDocs(this.entry.id);
91
+ }
92
+
93
+ override async toSidebarItem(): Promise<SidebarItemEntity> {
94
+ const debug = false;
95
+
96
+ const children = await this.getChildren();
97
+ let childItems = await Promise.all(children.map((child) => child.toSidebarItem()));
98
+
99
+ if (this.isBook()) {
100
+ childItems = [...childItems];
101
+ }
102
+
103
+ if (debug) {
104
+ logger.info(`${this.entry.id} 的侧边栏项目`);
105
+ console.log(childItems);
106
+ }
107
+
108
+ return new SidebarItemEntity({
109
+ text: this.getTitle(),
110
+ items: childItems,
111
+ link: this.getLink(),
112
+ });
113
+ }
114
+ }
@@ -0,0 +1,82 @@
1
+ import { SidebarItemEntity } from './SidebarItem';
2
+ import type { MetaEntry } from '../database/MetaDB';
3
+ import { LinkUtil } from '../utils/link';
4
+ import { BaseDoc } from './BaseDoc';
5
+ import metaDB from '../database/MetaDB';
6
+ import { COLLECTION_NAME } from '../database/MetaDB';
7
+
8
+ export default class MetaDoc extends BaseDoc<typeof COLLECTION_NAME, MetaEntry> {
9
+ constructor(entry: MetaEntry) {
10
+ super(entry);
11
+ }
12
+
13
+ static fromEntry(entry: MetaEntry) {
14
+ return new MetaDoc(entry);
15
+ }
16
+
17
+ getLink(): string {
18
+ return LinkUtil.getMetaLink(this.getLang(), this.getSlug());
19
+ }
20
+
21
+ getLang(): string {
22
+ return this.entry.id.split('/')[0];
23
+ }
24
+
25
+ /**
26
+ * 获取元数据页面的 slug
27
+ * 例如:
28
+ * ID: zh-cn/about 的 slug 为 about
29
+ * ID: en/privacy 的 slug 为 privacy
30
+ */
31
+ override getSlug(): string {
32
+ // 从 ID 中获取 slug,即删除以/分割后的第一个元素
33
+ return this.getId().split('/').slice(1).join('/');
34
+ }
35
+
36
+ /**
37
+ * 获取兄弟文档
38
+ * 例如:对于 'zh-cn/about',会返回 'zh-cn' 下的其他文档
39
+ */
40
+ async getSiblingDocs(): Promise<MetaDoc[]> {
41
+ return await metaDB.getSiblings(this.entry.id);
42
+ }
43
+
44
+ /**
45
+ * 获取兄弟文档的侧边栏项目
46
+ */
47
+ async getSiblingSidebarItems(): Promise<SidebarItemEntity[]> {
48
+ const siblings = await this.getSiblingDocs();
49
+ const siblingItems = await Promise.all(
50
+ siblings.map((sibling) => {
51
+ return new SidebarItemEntity({
52
+ text: sibling.getTitle(),
53
+ link: sibling.getLink(),
54
+ });
55
+ })
56
+ );
57
+ return siblingItems;
58
+ }
59
+
60
+ /**
61
+ * 重写侧边栏项目方法
62
+ * 对于元数据页面,我们不显示子项目
63
+ */
64
+ override async toSidebarItem(): Promise<SidebarItemEntity> {
65
+ return new SidebarItemEntity({
66
+ text: this.getTitle(),
67
+ link: this.getLink(),
68
+ });
69
+ }
70
+
71
+ /**
72
+ * 重写顶级侧边栏项目方法
73
+ * 对于元数据页面,我们显示所有兄弟页面作为侧边栏项目
74
+ */
75
+ async getTopSidebarItem(): Promise<SidebarItemEntity> {
76
+ return new SidebarItemEntity({
77
+ text: '了解我们',
78
+ items: await this.getSiblingSidebarItems(),
79
+ link: '',
80
+ });
81
+ }
82
+ }
@@ -0,0 +1,42 @@
1
+ import blogDB from '../database/BlogDB';
2
+ import { SidebarItemEntity } from './SidebarItem';
3
+ import { type TagStaticPath } from '../types/static-path';
4
+ import { LinkUtil } from '../utils/link';
5
+
6
+ export class Tag {
7
+ name: string;
8
+ lang: string;
9
+ count: number;
10
+
11
+ constructor(name: string, count: number, lang: string) {
12
+ this.name = name;
13
+ this.count = count;
14
+ this.lang = lang;
15
+ }
16
+
17
+ toSidebarItem(lang: string): SidebarItemEntity {
18
+ return new SidebarItemEntity({
19
+ text: this.name,
20
+ link: LinkUtil.getTagLink(lang, this.name),
21
+ });
22
+ }
23
+
24
+ toTagPath(): TagStaticPath {
25
+ return {
26
+ params: { slug: this.lang + '/blogs/tag/' + this.name },
27
+ props: { tag: this.name },
28
+ };
29
+ }
30
+
31
+ static async makeRootSidebarItem(lang: string): Promise<SidebarItemEntity> {
32
+ const tags = await blogDB.getTagsByLang(lang);
33
+
34
+ return new SidebarItemEntity({
35
+ text: 'Tags',
36
+ link: LinkUtil.getTagLink(lang, ''),
37
+ items: tags.map((tag: Tag) => tag.toSidebarItem(lang)),
38
+ });
39
+ }
40
+ }
41
+
42
+ export default Tag;
package/dist/index.ts CHANGED
@@ -7,6 +7,14 @@ export { default as Alert } from './components/base/Alert.astro';
7
7
  export { default as Speak } from './components/base/Speak.astro';
8
8
  export { default as Module } from './components/base/Module.astro';
9
9
 
10
+ // Collections
11
+ export * from './collections/ArticleCollection';
12
+ export * from './collections/BlogCollection';
13
+ export * from './collections/CourseCollection';
14
+ export * from './collections/ExperimentCollection';
15
+ export * from './collections/LessonCollection';
16
+ export * from './collections/MetaCollection';
17
+
10
18
  // Navigation
11
19
  export { default as ThemeSwitcher } from './components/navigation/ThemeSwitcher.astro';
12
20
  export { default as TableOfContents } from './components/navigation/TableOfContents.astro';
@@ -94,7 +102,7 @@ export * from './types/meta';
94
102
  export type { ImageProvider, ImageOptions } from './utils/image';
95
103
 
96
104
  // Models
97
- export * from './models/BaseDoc';
105
+ export * from './entities/BaseDoc';
98
106
 
99
107
  // Database
100
108
  export * from './database/BaseDB';
@@ -0,0 +1,8 @@
1
+ export interface TagStaticPath {
2
+ params: {
3
+ slug: string;
4
+ };
5
+ props: {
6
+ tag: string;
7
+ };
8
+ }
@@ -6,78 +6,82 @@
6
6
  export type ImageProvider = 'picsum' | 'unsplash' | 'placeholder' | 'robohash';
7
7
 
8
8
  export interface ImageOptions {
9
- width: number;
10
- height: number;
11
- tag?: string;
12
- randomSeed?: number | string;
13
- provider?: ImageProvider;
14
- grayscale?: boolean;
15
- blur?: number;
9
+ width: number;
10
+ height: number;
11
+ tag?: string;
12
+ randomSeed?: number | string;
13
+ provider?: ImageProvider;
14
+ grayscale?: boolean;
15
+ blur?: number;
16
16
  }
17
17
 
18
18
  /**
19
19
  * 获取示例图片URL
20
20
  * @param options 图片选项配置
21
21
  * @returns 生成的图片URL
22
- *
22
+ *
23
23
  * @example
24
24
  * // 基本用法
25
25
  * getExampleImage({ width: 400, height: 300 })
26
- *
26
+ *
27
27
  * @example
28
28
  * // 使用Unsplash并添加标签
29
- * getExampleImage({
30
- * width: 400,
31
- * height: 300,
32
- * provider: 'unsplash',
33
- * tag: 'nature'
29
+ * getExampleImage({
30
+ * width: 400,
31
+ * height: 300,
32
+ * provider: 'unsplash',
33
+ * tag: 'nature'
34
34
  * })
35
35
  */
36
36
  export function getExampleImage(options: ImageOptions): string {
37
- const {
38
- width,
39
- height,
40
- provider = 'picsum',
41
- tag = '',
42
- randomSeed = Math.floor(Math.random() * 1000),
43
- grayscale = false,
44
- blur = 0
45
- } = options;
37
+ const {
38
+ width,
39
+ height,
40
+ provider = 'picsum',
41
+ tag = '',
42
+ randomSeed = Math.floor(Math.random() * 1000),
43
+ grayscale = false,
44
+ blur = 0,
45
+ } = options;
46
46
 
47
- switch (provider) {
48
- case 'picsum':
49
- const picsumParams = [];
50
- if (grayscale) picsumParams.push('grayscale');
51
- if (blur > 0 && blur <= 10) picsumParams.push(`blur=${blur}`);
47
+ switch (provider) {
48
+ case 'picsum': {
49
+ const picsumParams = [];
50
+ if (grayscale) picsumParams.push('grayscale');
51
+ if (blur > 0 && blur <= 10) picsumParams.push(`blur=${blur}`);
52
52
 
53
- const picsumQuery = picsumParams.length ?
54
- `?${picsumParams.join('&')}` :
55
- `?random=${randomSeed}`;
53
+ const picsumQuery = picsumParams.length
54
+ ? `?${picsumParams.join('&')}`
55
+ : `?random=${randomSeed}`;
56
56
 
57
- return `https://picsum.photos/${width}/${height}${picsumQuery}`;
57
+ return `https://picsum.photos/${width}/${height}${picsumQuery}`;
58
+ }
58
59
 
59
- case 'unsplash':
60
- const tagQuery = tag ? `?${tag}` : '';
61
- return `https://source.unsplash.com/random/${width}x${height}${tagQuery}`;
60
+ case 'unsplash': {
61
+ const tagQuery = tag ? `?${tag}` : '';
62
+ return `https://source.unsplash.com/random/${width}x${height}${tagQuery}`;
63
+ }
62
64
 
63
- case 'placeholder':
64
- const color = grayscale ? 'gray' : '';
65
- const placeholderParams = [];
66
- if (tag) placeholderParams.push(`text=${encodeURIComponent(tag)}`);
67
- if (color) placeholderParams.push(`bg=${color}`);
65
+ case 'placeholder': {
66
+ const color = grayscale ? 'gray' : '';
67
+ const placeholderParams = [];
68
+ if (tag) placeholderParams.push(`text=${encodeURIComponent(tag)}`);
69
+ if (color) placeholderParams.push(`bg=${color}`);
68
70
 
69
- const placeholderQuery = placeholderParams.length ?
70
- `?${placeholderParams.join('&')}` : '';
71
+ const placeholderQuery = placeholderParams.length ? `?${placeholderParams.join('&')}` : '';
71
72
 
72
- return `https://via.placeholder.com/${width}x${height}${placeholderQuery}`;
73
+ return `https://via.placeholder.com/${width}x${height}${placeholderQuery}`;
74
+ }
73
75
 
74
- case 'robohash':
75
- const seed = String(tag || randomSeed);
76
- return `https://robohash.org/${encodeURIComponent(seed)}?size=${width}x${height}`;
76
+ case 'robohash': {
77
+ const seed = String(tag || randomSeed);
78
+ return `https://robohash.org/${encodeURIComponent(seed)}?size=${width}x${height}`;
79
+ }
77
80
 
78
- default:
79
- return `https://picsum.photos/${width}/${height}?random=${randomSeed}`;
80
- }
81
+ default: {
82
+ return `https://picsum.photos/${width}/${height}?random=${randomSeed}`;
83
+ }
84
+ }
81
85
  }
82
86
 
83
87
  /**
@@ -85,13 +89,13 @@ export function getExampleImage(options: ImageOptions): string {
85
89
  * @param options 可选的图片选项配置
86
90
  */
87
91
  export function getProductImage(options: Partial<ImageOptions> = {}): string {
88
- return getExampleImage({
89
- width: options.width || 400,
90
- height: options.height || 300,
91
- provider: options.provider || 'picsum',
92
- tag: options.tag || 'product',
93
- ...options
94
- });
92
+ return getExampleImage({
93
+ width: options.width || 400,
94
+ height: options.height || 300,
95
+ provider: options.provider || 'picsum',
96
+ tag: options.tag || 'product',
97
+ ...options,
98
+ });
95
99
  }
96
100
 
97
101
  /**
@@ -99,12 +103,12 @@ export function getProductImage(options: Partial<ImageOptions> = {}): string {
99
103
  * @param options 可选的图片选项配置
100
104
  */
101
105
  export function getAvatarImage(options: Partial<ImageOptions> = {}): string {
102
- return getExampleImage({
103
- width: options.width || 200,
104
- height: options.height || 200,
105
- provider: options.provider || 'robohash',
106
- ...options
107
- });
106
+ return getExampleImage({
107
+ width: options.width || 200,
108
+ height: options.height || 200,
109
+ provider: options.provider || 'robohash',
110
+ ...options,
111
+ });
108
112
  }
109
113
 
110
114
  /**
@@ -112,11 +116,11 @@ export function getAvatarImage(options: Partial<ImageOptions> = {}): string {
112
116
  * @param options 可选的图片选项配置
113
117
  */
114
118
  export function getLandscapeImage(options: Partial<ImageOptions> = {}): string {
115
- return getExampleImage({
116
- width: options.width || 800,
117
- height: options.height || 400,
118
- provider: options.provider || 'unsplash',
119
- tag: options.tag || 'landscape',
120
- ...options
121
- });
122
- }
119
+ return getExampleImage({
120
+ width: options.width || 800,
121
+ height: options.height || 400,
122
+ provider: options.provider || 'unsplash',
123
+ tag: options.tag || 'landscape',
124
+ ...options,
125
+ });
126
+ }