@coffic/cosy-ui 0.4.3 → 0.4.7

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 (37) hide show
  1. package/dist/app.css +1 -1
  2. package/dist/components/base/Alert.astro +3 -3
  3. package/dist/components/base/Module.astro +18 -0
  4. package/dist/components/base/Speak.astro +22 -0
  5. package/dist/components/containers/Container.astro +78 -2
  6. package/dist/components/containers/Main.astro +1 -1
  7. package/dist/components/data-display/Blog.astro +1 -0
  8. package/dist/components/data-display/ProductCard.astro +0 -2
  9. package/dist/components/data-display/Products.astro +0 -2
  10. package/dist/components/data-display/TeamMember.astro +0 -2
  11. package/dist/components/data-display/TeamMembers.astro +0 -2
  12. package/dist/components/display/Banner.astro +0 -1
  13. package/dist/components/display/Card.astro +0 -1
  14. package/dist/components/display/CodeBlock.astro +0 -1
  15. package/dist/components/display/CodeExample.astro +0 -1
  16. package/dist/components/display/Modal.astro +65 -67
  17. package/dist/components/layouts/AppLayout.astro +13 -9
  18. package/dist/components/layouts/BaseLayout.astro +47 -44
  19. package/dist/components/layouts/DashboardLayout.astro +615 -604
  20. package/dist/components/layouts/Header.astro +3 -2
  21. package/dist/components/layouts/Sidebar.astro +2 -3
  22. package/dist/components/typography/Article.astro +2 -1
  23. package/dist/database/BaseDB.ts +164 -0
  24. package/dist/database/MetaDB.ts +75 -0
  25. package/dist/entities/SidebarItem.ts +96 -0
  26. package/dist/i18n/Language.ts +25 -0
  27. package/dist/i18n/ui.ts +18 -0
  28. package/dist/i18n/utils.ts +54 -0
  29. package/dist/index.ts +17 -3
  30. package/dist/models/BaseDoc.ts +164 -0
  31. package/dist/types/header.ts +1 -1
  32. package/dist/types/heading.ts +13 -0
  33. package/dist/utils/link.ts +216 -0
  34. package/dist/utils/logger.ts +139 -0
  35. package/package.json +1 -1
  36. package/dist/components/layouts/DefaultLayout.astro +0 -170
  37. package/dist/components/layouts/LandingLayout.astro +0 -388
@@ -0,0 +1,13 @@
1
+ /**
2
+ * 表示文档中的标题结构
3
+ */
4
+ export interface Heading {
5
+ /** 标题深度,如 h1=1, h2=2, h3=3 等 */
6
+ depth: number;
7
+
8
+ /** 标题的唯一标识符,用于锚点链接 */
9
+ slug: string;
10
+
11
+ /** 标题文本内容 */
12
+ text: string;
13
+ }
@@ -0,0 +1,216 @@
1
+ import { logger } from "./logger";
2
+
3
+ export default class LinkUtil {
4
+ /**
5
+ * 规范化语言代码
6
+ * @param lang - 语言代码
7
+ * @returns 规范化后的语言代码
8
+ */
9
+ static normalizeLanguage(lang: string): string {
10
+ const normalizedLang = lang.toLowerCase().replace('zh-CN', 'zh-cn');
11
+ if (normalizedLang.length == 0) {
12
+ console.error('lang is empty');
13
+ return 'en';
14
+ }
15
+ return normalizedLang;
16
+ }
17
+
18
+ static getHomeLink(lang: string): string {
19
+ return `/${lang}`;
20
+ }
21
+
22
+ static getLessonsLink(lang: string): string {
23
+ return `/${lang}/lessons`;
24
+ }
25
+
26
+ static getExperimentsLink(lang: string): string {
27
+ return `/${lang}/experiments`;
28
+ }
29
+
30
+ static getExperimentLink(lang: string, experimentId: string): string {
31
+ if (experimentId.endsWith(lang)) {
32
+ return `/${lang}/experiments/${experimentId.replace(`${lang}`, '')}`;
33
+ } else {
34
+ const idWithoutLang = experimentId.replace(`${lang}/`, '');
35
+ return `/${lang}/experiments/${idWithoutLang}`;
36
+ }
37
+ }
38
+
39
+ static getCoursesLink(lang: string): string {
40
+ return `/${lang}/courses`;
41
+ }
42
+
43
+ static getBlogsLink(lang: string): string {
44
+ return `/${lang}/blogs`;
45
+ }
46
+
47
+ static getLessonLink(lang: string, lessonId: string): string {
48
+ if (lessonId.endsWith(lang)) {
49
+ return `/${lang}/lessons/${lessonId.replace(`${lang}`, '')}`;
50
+ } else {
51
+ const idWithoutLang = lessonId.replace(`${lang}/`, '');
52
+ return `/${lang}/lessons/${idWithoutLang}`;
53
+ }
54
+ }
55
+
56
+ static getTagLink(lang: string, tagName: string): string {
57
+ return `/${lang}/blogs/tag/${tagName}`;
58
+ }
59
+
60
+ static getBlogLink(blogId: string, lang: string): string {
61
+ const debug = false
62
+ const blogIdWithoutLang = blogId.replace(`${lang}/`, '')
63
+
64
+ if (debug) {
65
+ logger.info(`获取博客文档链接,博客文档ID: ${blogId}`);
66
+ }
67
+
68
+ return `/${lang}/blogs/${blogIdWithoutLang}`;
69
+ }
70
+
71
+ static getCourseLink(courseId: string): string {
72
+ const debug = false
73
+ const lang = courseId.split('/')[0]
74
+ const courseIdWithoutLang = courseId.replace(`${lang}/`, '')
75
+
76
+ if (debug) {
77
+ logger.info(`获取课程文档链接,课程文档ID: ${courseId}`);
78
+ }
79
+
80
+ return `/${lang}/courses/${courseIdWithoutLang}`;
81
+ }
82
+
83
+ static getMetaLink(lang: string, slug: string): string {
84
+ return `/${this.normalizeLanguage(lang)}/meta/${slug}`;
85
+ }
86
+
87
+ static getSigninLink(lang: string): string {
88
+ return `/${this.normalizeLanguage(lang)}/signin`;
89
+ }
90
+
91
+ static getAuthCallbackCookieLink(lang: string): string {
92
+ return `/${this.normalizeLanguage(lang)}/auth/callback_cookie`;
93
+ }
94
+
95
+ static getAuthCallbackTokenLink(lang: string): string {
96
+ return `/${this.normalizeLanguage(lang)}/auth/callback_token`;
97
+ }
98
+
99
+ static getAuthAccountLink(lang: string): string {
100
+ return `/${this.normalizeLanguage(lang)}/auth/account`;
101
+ }
102
+
103
+ static getDashboardUrl(lang: string): string {
104
+ return `/${this.normalizeLanguage(lang)}/auth/dashboard`;
105
+ }
106
+
107
+ static getAuthErrorLink(lang: string): string {
108
+ return `/${this.normalizeLanguage(lang)}/auth/error`;
109
+ }
110
+
111
+ static getPrivacyLink(lang: string): string {
112
+ return this.getMetaLink(lang, 'privacy');
113
+ }
114
+
115
+ static getTermsLink(lang: string): string {
116
+ return this.getMetaLink(lang, 'terms');
117
+ }
118
+
119
+ static getAboutLink(lang: string): string {
120
+ return this.getMetaLink(lang, 'about');
121
+ }
122
+
123
+ /**
124
+ * 根据ID生成链接
125
+ *
126
+ * 该函数根据文档ID生成对应的链接路径。
127
+ *
128
+ * @param {string} id - 文档ID, 例如 'courses/zh-cn/supervisor/index.md'
129
+ * @returns {string} 返回生成的链接路径
130
+ * @example
131
+ * // 例如:
132
+ * // id=courses/zh-cn/supervisor/index.md,则返回/zh-cn/courses/supervisor
133
+ * // id=courses/en/supervisor/index.md,则返回/en/courses/supervisor
134
+ */
135
+ static getLink(id: string): string {
136
+ let category = id.split('/')[0];
137
+ let lang = id.split('/')[1];
138
+ let path = id.split('/').slice(2).join('/');
139
+
140
+ let link = `/${lang}/${category}/${path}`;
141
+ return link.replace(/\/+/g, '/');
142
+ }
143
+
144
+ /**
145
+ * 根据分类生成顶级链接
146
+ *
147
+ * @param {string} category - 分类名称
148
+ * @param {string} lang - 语言代码,例如 'zh-cn', 'en'
149
+ * @returns {string} 返回生成的顶级链接路径
150
+ * @example
151
+ * // 例如:
152
+ * // category=courses, lang=zh-cn,则返回/zh-cn/courses
153
+ * // category=courses, lang=en,则返回/en/courses
154
+ */
155
+ static getTopLevelLink(category: string, lang: string): string {
156
+ return `/${lang}/${category}`;
157
+ }
158
+
159
+ /**
160
+ * 处理首页重定向
161
+ * @param locale - 语言代码
162
+ * @returns 规范化的语言代码
163
+ */
164
+ static homeRedirect(locale: string): string {
165
+ return locale || "en";
166
+ }
167
+
168
+ /**
169
+ * 检查是否为首页路径
170
+ * @param pathname - 路径
171
+ * @returns 是否为首页
172
+ */
173
+ static isHomePath(pathname: string): boolean {
174
+ return pathname === "/" || pathname === "";
175
+ }
176
+
177
+ static isHomeLink(path: string, lang: string): boolean {
178
+ return path === `/${lang}` || path === `/${lang}/`;
179
+ }
180
+
181
+ static isLessonsLink(path: string, lang: string): boolean {
182
+ return path === `/${lang}/lessons` ||
183
+ path === `/${lang}/lessons/` ||
184
+ path.startsWith(`/${lang}/lessons/`);
185
+ }
186
+
187
+ static isExperimentsLink(path: string, lang: string): boolean {
188
+ return path === `/${lang}/experiments` ||
189
+ path === `/${lang}/experiments/` ||
190
+ path.startsWith(`/${lang}/experiments/`);
191
+ }
192
+
193
+ static isCoursesLink(path: string, lang: string): boolean {
194
+ return path === `/${lang}/courses`
195
+ || path === `/${lang}/courses/`
196
+ || path.startsWith(`/${lang}/courses/`);
197
+ }
198
+
199
+ static isBlogsLink(path: string, lang: string): boolean {
200
+ return path === `/${lang}/blogs`
201
+ || path === `/${lang}/blogs/`
202
+ || path.startsWith(`/${lang}/blogs/`);
203
+ }
204
+
205
+ static getOAuthSuccessLink(currentOrigin: string): string {
206
+ return `${currentOrigin}/api/callback_success`;
207
+ }
208
+
209
+ static getOAuthErrorLink(currentOrigin: string): string {
210
+ return `${currentOrigin}/api/callback_error`;
211
+ }
212
+
213
+ static getLoginLink(currentOrigin: string): string {
214
+ return `${currentOrigin}/api/login`;
215
+ }
216
+ }
@@ -0,0 +1,139 @@
1
+ type LogLevel = 'debug' | 'info' | 'warn' | 'error';
2
+
3
+ // 控制是否显示时间戳
4
+ const SHOW_TIMESTAMP = false;
5
+
6
+ // ANSI 颜色代码
7
+ const colors = {
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
+ };
15
+
16
+ class Logger {
17
+ private getCallerInfo(): string {
18
+ const error = new Error();
19
+ const stackLines = error.stack?.split('\n') || [];
20
+
21
+ // 从第3行开始查找,跳过不是来自logger.ts的第一个调用
22
+ let targetLine = '';
23
+ for (let i = 3; i < stackLines.length; i++) {
24
+ const line = stackLines[i];
25
+ if (!line.includes('logger.ts')) {
26
+ targetLine = line;
27
+ break;
28
+ }
29
+ }
30
+
31
+ if (!targetLine) return '';
32
+
33
+ // 匹配文件路径和行号
34
+ const match = targetLine.match(/\((.+):(\d+):\d+\)/) || targetLine.match(/at (.+):(\d+):\d+/);
35
+ if (!match) return '';
36
+
37
+ const [_, filePath, line] = match;
38
+ return `${filePath}:${line}`;
39
+ }
40
+
41
+ private formatArray(arr: any[]): string {
42
+ const MAX_LINES = 30;
43
+ const MAX_LENGTH = 100;
44
+
45
+ const truncateString = (str: string): string => {
46
+ return str.length > MAX_LENGTH ? str.slice(0, MAX_LENGTH) + '...' : str;
47
+ };
48
+
49
+ const truncateObject = (obj: any): any => {
50
+ if (typeof obj !== 'object' || obj === null) {
51
+ return typeof obj === 'string' ? truncateString(obj) : obj;
52
+ }
53
+
54
+ const result: any = Array.isArray(obj) ? [] : {};
55
+ for (const [key, value] of Object.entries(obj)) {
56
+ result[key] = typeof value === 'string' ? truncateString(value)
57
+ : typeof value === 'object' ? truncateObject(value)
58
+ : value;
59
+ }
60
+ return result;
61
+ };
62
+
63
+ const items = arr.slice(0, MAX_LINES).map(item => {
64
+ const truncatedItem = truncateObject(item);
65
+ // 使用2个空格缩进,并在每行前添加 " • "
66
+ const jsonString = JSON.stringify(truncatedItem, null, 2)
67
+ .split('\n')
68
+ .map((line, index) => index === 0 ? ` • ${line}` : ` ${line}`)
69
+ .join('\n');
70
+ return jsonString;
71
+ });
72
+
73
+ let output = items.join('\n');
74
+ if (arr.length > MAX_LINES) {
75
+ const remainingCount = arr.length - MAX_LINES;
76
+ output += `\n ⋮ ... and ${remainingCount} more items`;
77
+ }
78
+
79
+ return output;
80
+ }
81
+
82
+ private log(level: LogLevel, message: string | object | any[]) {
83
+ const caller = this.getCallerInfo();
84
+ // 使用本地时间,并格式化为 HH:mm:ss 格式
85
+ const timestamp = new Date().toLocaleTimeString('zh-CN', {
86
+ hour12: false,
87
+ hour: '2-digit',
88
+ minute: '2-digit',
89
+ second: '2-digit'
90
+ });
91
+
92
+ const formattedMessage = Array.isArray(message)
93
+ ? this.formatArray(message)
94
+ : typeof message === 'object'
95
+ ? JSON.stringify(message, null, 2)
96
+ : message;
97
+
98
+ const timestampPart = SHOW_TIMESTAMP
99
+ ? `${colors.gray}${timestamp}${colors.reset} `
100
+ : '';
101
+
102
+ const emoji = {
103
+ debug: '🔍',
104
+ info: '🐳',
105
+ warn: '🚨',
106
+ error: '❌'
107
+ }[level];
108
+
109
+ // eslint-disable-next-line no-console
110
+ console.log(
111
+ timestampPart +
112
+ `${colors[level]}${emoji} ${level.toUpperCase()}${colors.reset} ` +
113
+ `${colors.gray}:${colors.reset} ` +
114
+ `${colors[level]}${formattedMessage}${colors.reset}`
115
+ );
116
+ }
117
+
118
+ debug(message: string | object) {
119
+ this.log('debug', message);
120
+ }
121
+
122
+ info(message: string | object) {
123
+ this.log('info', message);
124
+ }
125
+
126
+ warn(message: string | object) {
127
+ this.log('warn', message);
128
+ }
129
+
130
+ error(message: string | object) {
131
+ this.log('error', message);
132
+ }
133
+
134
+ array(title: string, arr: any[]) {
135
+ this.log('info', title + '\n' + this.formatArray(arr));
136
+ }
137
+ }
138
+
139
+ export const logger = new Logger();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coffic/cosy-ui",
3
- "version": "0.4.3",
3
+ "version": "0.4.7",
4
4
  "description": "An astro component library",
5
5
  "author": {
6
6
  "name": "nookery",
@@ -1,170 +0,0 @@
1
- ---
2
- /**
3
- * DefaultLayout组件
4
- *
5
- * 包含常用页面结构的默认布局,包括简单的页眉和页脚
6
- *
7
- * @example
8
- * ```astro
9
- * ---
10
- * import DefaultLayout from '../layouts/DefaultLayout.astro';
11
- * ---
12
- *
13
- * <DefaultLayout title="页面标题" description="页面描述">
14
- * <h1>页面内容</h1>
15
- * <p>这是页面的主要内容</p>
16
- * </DefaultLayout>
17
- * ```
18
- */
19
-
20
- import BaseLayout from './BaseLayout.astro';
21
- import Link from '../base/Link.astro';
22
-
23
- // 导入样式
24
- import '../../app.css';
25
-
26
- export interface NavLink {
27
- href: string;
28
- label: string;
29
- }
30
-
31
- export interface Props {
32
- /**
33
- * 页面标题
34
- */
35
- title: string;
36
-
37
- /**
38
- * 页面描述
39
- */
40
- description?: string;
41
-
42
- /**
43
- * 页面关键词
44
- */
45
- keywords?: string;
46
-
47
- /**
48
- * 是否显示页眉
49
- * @default true
50
- */
51
- showHeader?: boolean;
52
-
53
- /**
54
- * 是否显示页脚
55
- * @default true
56
- */
57
- showFooter?: boolean;
58
-
59
- /**
60
- * 网站名称
61
- * @default "网站名称"
62
- */
63
- siteName?: string;
64
-
65
- /**
66
- * 导航链接
67
- */
68
- navLinks?: NavLink[];
69
-
70
- /**
71
- * 页面容器类名
72
- */
73
- containerClass?: string;
74
-
75
- /**
76
- * 自定义头部内容
77
- */
78
- head?: astroHTML.JSX.Element;
79
-
80
- /**
81
- * 页面类名
82
- */
83
- class?: string;
84
-
85
- /**
86
- * 类名列表
87
- */
88
- 'class:list'?: any;
89
- }
90
-
91
- const {
92
- title,
93
- description,
94
- keywords,
95
- showHeader = true,
96
- showFooter = true,
97
- siteName = "网站名称",
98
- navLinks = [
99
- { href: "/", label: "首页" },
100
- { href: "/about", label: "关于" },
101
- { href: "/contact", label: "联系我们" }
102
- ],
103
- containerClass = "container mx-auto px-4 py-8",
104
- head,
105
- class: className,
106
- 'class:list': classList,
107
- ...rest
108
- } = Astro.props;
109
-
110
- const year = new Date().getFullYear();
111
- ---
112
-
113
- <BaseLayout
114
- title={title}
115
- description={description}
116
- keywords={keywords}
117
- head={head}
118
- class={className}
119
- {...rest}
120
- >
121
- {showHeader && (
122
- <header class="site-header">
123
- <div class="container px-4 py-4">
124
- <div class="header-content">
125
- <div class="logo">
126
- <a href="/" class="site-name">{siteName}</a>
127
- </div>
128
-
129
- <nav class="main-nav">
130
- <ul class="nav-list">
131
- {navLinks.map((link: NavLink) => (
132
- <li class="nav-item">
133
- <Link href={link.href} variant="text">{link.label}</Link>
134
- </li>
135
- ))}
136
- </ul>
137
- </nav>
138
- </div>
139
- </div>
140
- </header>
141
- )}
142
-
143
- <main class={containerClass}>
144
- <slot />
145
- </main>
146
-
147
- {showFooter && (
148
- <footer class="site-footer">
149
- <div class="container px-4 py-8">
150
- <div class="footer-content">
151
- <div class="footer-info">
152
- <p class="copyright">&copy; {year} {siteName}. 保留所有权利。</p>
153
- </div>
154
-
155
- <nav class="footer-nav">
156
- <ul class="nav-list">
157
- {navLinks.map((link: NavLink) => (
158
- <li class="nav-item">
159
- <Link href={link.href} variant="text" size="sm">{link.label}</Link>
160
- </li>
161
- ))}
162
- </ul>
163
- </nav>
164
- </div>
165
- </div>
166
- </footer>
167
- )}
168
-
169
- <slot name="after-footer" />
170
- </BaseLayout>