@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.
- package/dist/app.css +1 -1
- package/dist/components/base/Alert.astro +3 -3
- package/dist/components/base/Module.astro +18 -0
- package/dist/components/base/Speak.astro +22 -0
- package/dist/components/containers/Container.astro +78 -2
- package/dist/components/containers/Main.astro +1 -1
- package/dist/components/data-display/Blog.astro +1 -0
- package/dist/components/data-display/ProductCard.astro +0 -2
- package/dist/components/data-display/Products.astro +0 -2
- package/dist/components/data-display/TeamMember.astro +0 -2
- package/dist/components/data-display/TeamMembers.astro +0 -2
- package/dist/components/display/Banner.astro +0 -1
- package/dist/components/display/Card.astro +0 -1
- package/dist/components/display/CodeBlock.astro +0 -1
- package/dist/components/display/CodeExample.astro +0 -1
- package/dist/components/display/Modal.astro +65 -67
- package/dist/components/layouts/AppLayout.astro +13 -9
- package/dist/components/layouts/BaseLayout.astro +47 -44
- package/dist/components/layouts/DashboardLayout.astro +615 -604
- package/dist/components/layouts/Header.astro +3 -2
- package/dist/components/layouts/Sidebar.astro +2 -3
- package/dist/components/typography/Article.astro +2 -1
- package/dist/database/BaseDB.ts +164 -0
- package/dist/database/MetaDB.ts +75 -0
- package/dist/entities/SidebarItem.ts +96 -0
- package/dist/i18n/Language.ts +25 -0
- package/dist/i18n/ui.ts +18 -0
- package/dist/i18n/utils.ts +54 -0
- package/dist/index.ts +17 -3
- package/dist/models/BaseDoc.ts +164 -0
- package/dist/types/header.ts +1 -1
- package/dist/types/heading.ts +13 -0
- package/dist/utils/link.ts +216 -0
- package/dist/utils/logger.ts +139 -0
- package/package.json +1 -1
- package/dist/components/layouts/DefaultLayout.astro +0 -170
- package/dist/components/layouts/LandingLayout.astro +0 -388
@@ -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,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">© {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>
|