@coffic/cosy-ui 0.1.25
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/README.md +83 -0
- package/index.ts +7 -0
- package/package.json +57 -0
- package/src/components/Banner.astro +12 -0
- package/src/components/Component.astro +10 -0
- package/src/components/Footer.astro +128 -0
- package/src/components/Header.astro +322 -0
- package/src/components/Link.astro +16 -0
- package/src/components/SocialIcon.astro +36 -0
- package/src/components/ThemeItem.astro +14 -0
- package/src/env.d.ts +1 -0
- package/src/types/footer.ts +11 -0
- package/src/utils/social.ts +90 -0
package/README.md
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
# Coffic Shared UI
|
2
|
+
|
3
|
+
这是一个 Astro 组件库,为同一个组织下的多个项目提供统一的 UI 风格。
|
4
|
+
|
5
|
+
## 安装
|
6
|
+
|
7
|
+
```bash
|
8
|
+
npm install @coffic/shared-ui
|
9
|
+
```
|
10
|
+
|
11
|
+
## 必要依赖
|
12
|
+
|
13
|
+
本组件库依赖以下包,请确保您的项目中已正确安装和配置:
|
14
|
+
|
15
|
+
### 1. Astro
|
16
|
+
|
17
|
+
确保您的项目是一个 Astro 项目。如果不是,可以按照以下步骤创建:
|
18
|
+
|
19
|
+
```bash
|
20
|
+
npm create astro@latest
|
21
|
+
```
|
22
|
+
|
23
|
+
### 2. TailwindCSS
|
24
|
+
|
25
|
+
如果您的项目还没有配置 TailwindCSS,请按照以下步骤安装:
|
26
|
+
|
27
|
+
```bash
|
28
|
+
npm install -D tailwindcss @astrojs/tailwind
|
29
|
+
```
|
30
|
+
|
31
|
+
然后在您的 `astro.config.mjs` 中添加:
|
32
|
+
|
33
|
+
```javascript
|
34
|
+
import tailwind from '@astrojs/tailwind';
|
35
|
+
|
36
|
+
export default defineConfig({
|
37
|
+
integrations: [tailwind()],
|
38
|
+
});
|
39
|
+
```
|
40
|
+
|
41
|
+
### 3. DaisyUI
|
42
|
+
|
43
|
+
安装 DaisyUI:
|
44
|
+
|
45
|
+
```bash
|
46
|
+
npm install -D daisyui
|
47
|
+
```
|
48
|
+
|
49
|
+
在您的 `tailwind.config.js` 中添加 DaisyUI:
|
50
|
+
|
51
|
+
```javascript
|
52
|
+
export default {
|
53
|
+
plugins: [require('daisyui')],
|
54
|
+
};
|
55
|
+
```
|
56
|
+
|
57
|
+
### 4. CSS
|
58
|
+
|
59
|
+
```css
|
60
|
+
@source '../node_modules/@coffic/shared-ui';
|
61
|
+
```
|
62
|
+
|
63
|
+
## 内置组件
|
64
|
+
|
65
|
+
- Footer
|
66
|
+
- 更多组件可通过 IDE 的智能提示看到
|
67
|
+
|
68
|
+
## 注意事项
|
69
|
+
|
70
|
+
1. 本组件库使用 TailwindCSS 和 DaisyUI 的样式类,这意味着:
|
71
|
+
|
72
|
+
- 组件的样式会受到您项目中 Tailwind 配置的影响
|
73
|
+
- 如果您修改了 Tailwind 的默认主题或 DaisyUI 的主题,可能会影响组件的外观
|
74
|
+
|
75
|
+
2. 建议在您的项目中保持默认的 Tailwind 和 DaisyUI 配置,以确保组件显示正常
|
76
|
+
|
77
|
+
## 贡献
|
78
|
+
|
79
|
+
欢迎提交 Issue 和 Pull Request!
|
80
|
+
|
81
|
+
## 许可证
|
82
|
+
|
83
|
+
MIT
|
package/index.ts
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
export { default as Header } from './src/components/Header.astro';
|
2
|
+
export { default as Footer } from './src/components/Footer.astro';
|
3
|
+
export { default as Banner } from './src/components/Banner.astro';
|
4
|
+
export { default as Link } from './src/components/Link.astro';
|
5
|
+
export { default as SocialIcon } from './src/components/SocialIcon.astro';
|
6
|
+
export { default as Component } from './src/components/Component.astro';
|
7
|
+
export { default as ThemeItem } from './src/components/ThemeItem.astro';
|
package/package.json
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
{
|
2
|
+
"name": "@coffic/cosy-ui",
|
3
|
+
"version": "0.1.25",
|
4
|
+
"description": "A astro component library",
|
5
|
+
"repository": {
|
6
|
+
"url": "https://github.com/CofficLab/cosy-ui"
|
7
|
+
},
|
8
|
+
"type": "module",
|
9
|
+
"publishConfig": {
|
10
|
+
"access": "public"
|
11
|
+
},
|
12
|
+
"main": "./index.ts",
|
13
|
+
"exports": {
|
14
|
+
".": "./index.ts"
|
15
|
+
},
|
16
|
+
"files": [
|
17
|
+
"src",
|
18
|
+
"index.ts"
|
19
|
+
],
|
20
|
+
"keywords": [
|
21
|
+
"astro-component"
|
22
|
+
],
|
23
|
+
"scripts": {
|
24
|
+
"format": "prettier -w .",
|
25
|
+
"lint": "eslint . --ext .ts,.js,.astro src",
|
26
|
+
"dev": "cd example && astro dev --host 0.0.0.0"
|
27
|
+
},
|
28
|
+
"devDependencies": {
|
29
|
+
"@tailwindcss/vite": "^4.0.14",
|
30
|
+
"@types/chai": "^5.2.0",
|
31
|
+
"@types/eslint": "^9.6.1",
|
32
|
+
"@types/mocha": "^10.0.10",
|
33
|
+
"@types/node": "^22.13.10",
|
34
|
+
"@typescript-eslint/parser": "^8.26.1",
|
35
|
+
"astro": "^5.5.1",
|
36
|
+
"chai": "^4.5.0",
|
37
|
+
"daisyui": "^5.0.3",
|
38
|
+
"eslint": "^8.57.1",
|
39
|
+
"eslint-plugin-astro": "^1.3.1",
|
40
|
+
"mocha": "^10.8.2",
|
41
|
+
"prettier": "^3.5.3",
|
42
|
+
"prettier-plugin-astro": "^0.14.1",
|
43
|
+
"tailwindcss": "^4.0.14",
|
44
|
+
"typescript": "^5.8.2"
|
45
|
+
},
|
46
|
+
"peerDependencies": {
|
47
|
+
"@remixicon/vue": "^4.6.0",
|
48
|
+
"@astrojs/vue": "^5.0.7",
|
49
|
+
"astro": "^5.0.0",
|
50
|
+
"daisyui": "^5.0.3",
|
51
|
+
"tailwindcss": "^4.0.13"
|
52
|
+
},
|
53
|
+
"dependencies": {
|
54
|
+
"@astrojs/vue": "^5.0.7",
|
55
|
+
"vue": "^3.5.13"
|
56
|
+
}
|
57
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<div class="w-full px-8 z-50 mx-auto">
|
2
|
+
<div
|
3
|
+
class="mt-0 hero p-4 rounded-2xl bg-base-300/50 dark:bg-base-200/50 backdrop-blur-xl hover:shadow-2xl transform duration-100">
|
4
|
+
<div class="text-center hero-content">
|
5
|
+
<div class="max-w-md flex flex-row justify-center items-center mx-auto">
|
6
|
+
<p class="text-2xl md:text-3xl font-bold text-base-content">
|
7
|
+
<slot />
|
8
|
+
</p>
|
9
|
+
</div>
|
10
|
+
</div>
|
11
|
+
</div>
|
12
|
+
</div>
|
@@ -0,0 +1,128 @@
|
|
1
|
+
---
|
2
|
+
import { processSocialLink } from '../utils/social';
|
3
|
+
import Link from './Link.astro';
|
4
|
+
import Banner from './Banner.astro';
|
5
|
+
import SocialIcon from './SocialIcon.astro';
|
6
|
+
|
7
|
+
const {
|
8
|
+
siteName,
|
9
|
+
homeLink,
|
10
|
+
slogan,
|
11
|
+
company,
|
12
|
+
copyright,
|
13
|
+
inspirationalSlogan,
|
14
|
+
icp,
|
15
|
+
logo,
|
16
|
+
products = [],
|
17
|
+
aboutLink,
|
18
|
+
contactLink,
|
19
|
+
termsLink,
|
20
|
+
privacyLink,
|
21
|
+
socialLinks = [],
|
22
|
+
} = Astro.props;
|
23
|
+
|
24
|
+
const currentYear = new Date().getFullYear();
|
25
|
+
|
26
|
+
// 处理社交链接
|
27
|
+
const processedSocialLinks = socialLinks.map((link) => processSocialLink(link));
|
28
|
+
---
|
29
|
+
|
30
|
+
<footer class="bg-green-200/50 dark:bg-base-300/50 z-50 backdrop-blur-md">
|
31
|
+
<div class="footer sm:footer-horizontal p-10 text-base-content">
|
32
|
+
<aside class="flex flex-col items-center sm:items-start gap-2 place-self-center">
|
33
|
+
<Link href={homeLink}>
|
34
|
+
<div class="flex items-center gap-2 rounded-2xl">
|
35
|
+
{
|
36
|
+
logo && (
|
37
|
+
<img
|
38
|
+
src={logo.src}
|
39
|
+
alt={logo.alt}
|
40
|
+
class="h-6 w-6 fill-current rounded-2xl"
|
41
|
+
width="32"
|
42
|
+
height="32"
|
43
|
+
/>
|
44
|
+
)
|
45
|
+
}
|
46
|
+
<span class="text-xl font-bold">{siteName}</span>
|
47
|
+
</div>
|
48
|
+
</Link>
|
49
|
+
<p class="text-sm opacity-70 self-center">{slogan}</p>
|
50
|
+
|
51
|
+
<div class="flex gap-4 mt-4 sm:mt-0">
|
52
|
+
{
|
53
|
+
processedSocialLinks.map((link) => (
|
54
|
+
<a
|
55
|
+
href={link.url}
|
56
|
+
target="_blank"
|
57
|
+
rel="noopener noreferrer"
|
58
|
+
class="btn btn-ghost btn-sm p-1 hover:text-primary"
|
59
|
+
title={link.name}>
|
60
|
+
<SocialIcon platform={link.platform} />
|
61
|
+
<span class="sr-only">{link.name}</span>
|
62
|
+
</a>
|
63
|
+
))
|
64
|
+
}
|
65
|
+
</div>
|
66
|
+
</aside>
|
67
|
+
|
68
|
+
{
|
69
|
+
products.length > 0 && (
|
70
|
+
<nav class="place-self-center text-center flex flex-col items-center min-h-[200px]">
|
71
|
+
<h6 class="footer-title">产品</h6>
|
72
|
+
<div class="flex flex-col gap-2">
|
73
|
+
{products.map((product) => (
|
74
|
+
<Link href={product.href} external={product.external}>
|
75
|
+
{product.name}
|
76
|
+
</Link>
|
77
|
+
))}
|
78
|
+
</div>
|
79
|
+
</nav>
|
80
|
+
)
|
81
|
+
}
|
82
|
+
|
83
|
+
{
|
84
|
+
(aboutLink || contactLink) && (
|
85
|
+
<nav class="place-self-center text-center flex flex-col items-center min-h-[200px]">
|
86
|
+
<h6 class="footer-title">关于</h6>
|
87
|
+
<div class="flex flex-col gap-2">
|
88
|
+
{aboutLink && <Link href={aboutLink}>关于我们</Link>}
|
89
|
+
{contactLink && <Link href={contactLink}>联系我们</Link>}
|
90
|
+
</div>
|
91
|
+
</nav>
|
92
|
+
)
|
93
|
+
}
|
94
|
+
|
95
|
+
{
|
96
|
+
(termsLink || privacyLink) && (
|
97
|
+
<nav class="place-self-center text-center flex flex-col items-center min-h-[200px]">
|
98
|
+
<h6 class="footer-title">法律</h6>
|
99
|
+
<div class="flex flex-col gap-2">
|
100
|
+
{termsLink && <Link href={termsLink}>服务条款</Link>}
|
101
|
+
{privacyLink && <Link href={privacyLink}>隐私政策</Link>}
|
102
|
+
</div>
|
103
|
+
</nav>
|
104
|
+
)
|
105
|
+
}
|
106
|
+
</div>
|
107
|
+
|
108
|
+
<Banner>
|
109
|
+
{inspirationalSlogan}
|
110
|
+
</Banner>
|
111
|
+
|
112
|
+
<div class="flex flex-col sm:flex-col justify-center items-center p-4 text-sm opacity-70">
|
113
|
+
<div class="flex items-center gap-2">
|
114
|
+
<p>
|
115
|
+
© {currentYear}
|
116
|
+
{company} - {copyright}
|
117
|
+
</p>
|
118
|
+
</div>
|
119
|
+
|
120
|
+
{
|
121
|
+
icp && (
|
122
|
+
<div class="text-center py-4">
|
123
|
+
<p>{icp}</p>
|
124
|
+
</div>
|
125
|
+
)
|
126
|
+
}
|
127
|
+
</div>
|
128
|
+
</footer>
|
@@ -0,0 +1,322 @@
|
|
1
|
+
---
|
2
|
+
import { Image } from 'astro:assets';
|
3
|
+
import { RiGithubFill, RiSearch2Line, RiMenuLine, RiSunCloudyLine } from '@remixicon/vue';
|
4
|
+
import ThemeItem from './ThemeItem.astro';
|
5
|
+
import Link from './Link.astro';
|
6
|
+
|
7
|
+
interface Props {
|
8
|
+
logo: ImageMetadata;
|
9
|
+
languages?: Array<{ code: string; name: string }>;
|
10
|
+
currentLocale?: string;
|
11
|
+
navItems?: Array<{
|
12
|
+
href: string;
|
13
|
+
label: string;
|
14
|
+
match: (path: string) => boolean;
|
15
|
+
}>;
|
16
|
+
socialLinks?: Array<{
|
17
|
+
name: string;
|
18
|
+
url: string;
|
19
|
+
icon: any;
|
20
|
+
}>;
|
21
|
+
}
|
22
|
+
|
23
|
+
const {
|
24
|
+
logo,
|
25
|
+
languages = [
|
26
|
+
{ code: 'zh-cn', name: '简体中文' },
|
27
|
+
{ code: 'en', name: 'English' },
|
28
|
+
],
|
29
|
+
currentLocale = 'zh-cn',
|
30
|
+
navItems = [],
|
31
|
+
} = Astro.props;
|
32
|
+
|
33
|
+
const currentLanguageName =
|
34
|
+
languages.find((lang) => lang.code === currentLocale)?.name || '简体中文';
|
35
|
+
|
36
|
+
const themes = [
|
37
|
+
{ id: 'default', name: 'Default' },
|
38
|
+
{ id: 'light', name: 'Light' },
|
39
|
+
{ id: 'dark', name: 'Dark' },
|
40
|
+
{ id: 'pastel', name: 'Pastel' },
|
41
|
+
{ id: 'lemonade', name: 'Lemonade' },
|
42
|
+
{ id: 'cupcake', name: 'Cupcake' },
|
43
|
+
{ id: 'nord', name: 'Nord' },
|
44
|
+
{ id: 'business', name: 'Business' },
|
45
|
+
{ id: 'luxury', name: 'Luxury' },
|
46
|
+
];
|
47
|
+
|
48
|
+
const currentPath = Astro.url.pathname;
|
49
|
+
|
50
|
+
// 为每个语言生成对应的URL
|
51
|
+
function generateLanguageUrl(langCode: string): string {
|
52
|
+
const pathParts = currentPath.split('/').filter(Boolean);
|
53
|
+
const firstPathPart = pathParts[0];
|
54
|
+
const supportedLanguages = languages.map((lang) => lang.code);
|
55
|
+
const isFirstPartLang = supportedLanguages.includes(firstPathPart);
|
56
|
+
|
57
|
+
if (isFirstPartLang) {
|
58
|
+
pathParts[0] = langCode;
|
59
|
+
return '/' + pathParts.join('/');
|
60
|
+
} else {
|
61
|
+
return '/' + langCode + (currentPath === '/' ? '' : currentPath);
|
62
|
+
}
|
63
|
+
}
|
64
|
+
---
|
65
|
+
|
66
|
+
<header
|
67
|
+
class="bg-gradient-to-r from-neutral-900/80 via-neutral-800/80 to-neutral-900/80
|
68
|
+
dark:from-neutral-800/80 dark:via-neutral-700/80 dark:to-neutral-800/80 backdrop-blur
|
69
|
+
flex flex-row justify-between
|
70
|
+
text-neutral-200 dark:text-neutral-300
|
71
|
+
px-4
|
72
|
+
rounded-lg h-12 mt-1 mx-4 fixed top-0 left-0 right-0 z-50
|
73
|
+
border border-white/5 dark:border-black/5">
|
74
|
+
<div class="flex flex-row justify-start items-center">
|
75
|
+
<Link href="/" transition:persist>
|
76
|
+
<div class="h-8 w-8 flex flex-col items-center justify-center">
|
77
|
+
<Image src={logo} alt="logo" class="fill-current" />
|
78
|
+
</div>
|
79
|
+
</Link>
|
80
|
+
|
81
|
+
<!-- 移动端菜单按钮 -->
|
82
|
+
<button class="lg:hidden btn btn-ghost btn-sm p-1 ml-2" onclick="mobile_menu.showModal()">
|
83
|
+
<RiMenuLine class="w-5 h-5" />
|
84
|
+
</button>
|
85
|
+
|
86
|
+
<!-- 桌面端导航 -->
|
87
|
+
<div class="hidden lg:flex flex-row gap-4 items-center ml-4" transition:animate="fade">
|
88
|
+
{
|
89
|
+
navItems.map((item) => (
|
90
|
+
<Link
|
91
|
+
class:list={[
|
92
|
+
'btn btn-sm',
|
93
|
+
{
|
94
|
+
'btn-ghost': !item.match(currentPath),
|
95
|
+
'btn-primary': item.match(currentPath),
|
96
|
+
},
|
97
|
+
]}
|
98
|
+
href={item.href}>
|
99
|
+
{item.label}
|
100
|
+
</Link>
|
101
|
+
))
|
102
|
+
}
|
103
|
+
</div>
|
104
|
+
</div>
|
105
|
+
|
106
|
+
<div class="h-12 flex flex-row justify-end gap-2 items-center">
|
107
|
+
<div class="dropdown dropdown-end">
|
108
|
+
<div tabindex={0} role="button" class="btn btn-ghost btn-sm p-1">
|
109
|
+
{currentLanguageName}
|
110
|
+
</div>
|
111
|
+
<ul
|
112
|
+
tabindex={0}
|
113
|
+
class="dropdown-content menu bg-slate-900 dark:bg-slate-800 rounded-box z-[1] w-40 p-2 shadow-lg">
|
114
|
+
{
|
115
|
+
languages.map((lang) => (
|
116
|
+
<li>
|
117
|
+
<Link
|
118
|
+
href={generateLanguageUrl(lang.code)}
|
119
|
+
class:list={[
|
120
|
+
{
|
121
|
+
'bg-primary text-white': currentLocale === lang.code,
|
122
|
+
},
|
123
|
+
]}>
|
124
|
+
{lang.name}
|
125
|
+
</Link>
|
126
|
+
</li>
|
127
|
+
))
|
128
|
+
}
|
129
|
+
</ul>
|
130
|
+
</div>
|
131
|
+
<div class="dropdown dropdown-end">
|
132
|
+
<div tabindex={0} role="button" class="btn btn-ghost btn-sm p-1">
|
133
|
+
<RiSunCloudyLine class="w-5 h-5" />
|
134
|
+
</div>
|
135
|
+
<ul
|
136
|
+
tabindex={0}
|
137
|
+
class="dropdown-content menu bg-neutral-900 dark:bg-neutral-800 rounded-box z-[1] w-56 p-2 shadow-lg">
|
138
|
+
{themes.map((theme) => <ThemeItem theme={theme.id} label={theme.name} />)}
|
139
|
+
</ul>
|
140
|
+
</div>
|
141
|
+
<button class="btn btn-ghost btn-sm p-1" onclick="my_modal_1.showModal()">
|
142
|
+
<RiSearch2Line class="w-5 h-5" />
|
143
|
+
</button>
|
144
|
+
</div>
|
145
|
+
</header>
|
146
|
+
|
147
|
+
<!-- 移动端菜单模态框 -->
|
148
|
+
<dialog id="mobile_menu" class="modal">
|
149
|
+
<div class="modal-box bg-neutral-900 dark:bg-neutral-800 text-neutral-200 dark:text-neutral-300">
|
150
|
+
<h3 class="font-bold text-lg mb-4">
|
151
|
+
{currentLocale === 'zh-cn' ? '菜单' : 'Menu'}
|
152
|
+
</h3>
|
153
|
+
<div class="flex flex-col gap-2">
|
154
|
+
{
|
155
|
+
navItems.map((item) => (
|
156
|
+
<a
|
157
|
+
class:list={[
|
158
|
+
'btn btn-sm w-full text-left justify-start',
|
159
|
+
{
|
160
|
+
'btn-ghost': !item.match(currentPath),
|
161
|
+
'btn-primary': item.match(currentPath),
|
162
|
+
},
|
163
|
+
]}
|
164
|
+
href={item.href}>
|
165
|
+
{item.label}
|
166
|
+
</a>
|
167
|
+
))
|
168
|
+
}
|
169
|
+
</div>
|
170
|
+
<div class="modal-action">
|
171
|
+
<form method="dialog">
|
172
|
+
<button class="btn">
|
173
|
+
{currentLocale === 'zh-cn' ? '关闭' : 'Close'}
|
174
|
+
</button>
|
175
|
+
</form>
|
176
|
+
</div>
|
177
|
+
</div>
|
178
|
+
<form method="dialog" class="modal-backdrop bg-black/20 backdrop-blur-sm">
|
179
|
+
<button>关闭</button>
|
180
|
+
</form>
|
181
|
+
</dialog>
|
182
|
+
|
183
|
+
<!-- 搜索模态框 -->
|
184
|
+
<!-- <dialog id="my_modal_1" class="modal">
|
185
|
+
<div
|
186
|
+
class="modal-box bg-neutral-900 dark:bg-neutral-800 text-neutral-200 dark:text-neutral-300">
|
187
|
+
<Search />
|
188
|
+
<div class="modal-action">
|
189
|
+
<form method="dialog">
|
190
|
+
<button class="btn">
|
191
|
+
{currentLocale === 'zh-cn' ? '关闭' : 'Close'}
|
192
|
+
</button>
|
193
|
+
</form>
|
194
|
+
</div>
|
195
|
+
</div>
|
196
|
+
<form method="dialog" class="modal-backdrop bg-black/20 backdrop-blur-sm">
|
197
|
+
<button>关闭</button>
|
198
|
+
</form>
|
199
|
+
</dialog> -->
|
200
|
+
|
201
|
+
<style is:global>
|
202
|
+
.pagefind-ui {
|
203
|
+
--pagefind-ui-scale: 0.75;
|
204
|
+
--pagefind-ui-primary: navy;
|
205
|
+
--pagefind-ui-text: black;
|
206
|
+
--pagefind-ui-border: slategrey;
|
207
|
+
--pagefind-ui-border-width: 1px;
|
208
|
+
--pagefind-ui-border-radius: 0.25rem;
|
209
|
+
--pagefind-ui-font: sans-serif;
|
210
|
+
width: 100%;
|
211
|
+
}
|
212
|
+
|
213
|
+
.pagefind-ui.yellow {
|
214
|
+
--pagefind-ui-background: lightyellow;
|
215
|
+
}
|
216
|
+
|
217
|
+
.pagefind-ui.red {
|
218
|
+
--pagefind-ui-background: peachpuff;
|
219
|
+
width: 100%;
|
220
|
+
}
|
221
|
+
|
222
|
+
.pagefind-ui .pagefind-ui__drawer:not(.pagefind-ui__hidden) {
|
223
|
+
position: relative;
|
224
|
+
padding: 0;
|
225
|
+
box-shadow: none;
|
226
|
+
background-color: transparent;
|
227
|
+
}
|
228
|
+
|
229
|
+
.pagefind-ui .pagefind-ui__result-link {
|
230
|
+
color: var(--pagefind-ui-primary);
|
231
|
+
}
|
232
|
+
|
233
|
+
.pagefind-ui .pagefind-ui__result-excerpt {
|
234
|
+
color: var(--pagefind-ui-text);
|
235
|
+
}
|
236
|
+
|
237
|
+
/* 响应式调整 */
|
238
|
+
@media (max-width: 1023px) {
|
239
|
+
.modal-box {
|
240
|
+
max-height: 80vh;
|
241
|
+
overflow-y: auto;
|
242
|
+
}
|
243
|
+
}
|
244
|
+
|
245
|
+
/* 模态框动画 */
|
246
|
+
.modal {
|
247
|
+
transition-duration: 200ms;
|
248
|
+
}
|
249
|
+
|
250
|
+
.modal-box {
|
251
|
+
transition-duration: 300ms;
|
252
|
+
transform-origin: center;
|
253
|
+
}
|
254
|
+
|
255
|
+
@keyframes slideUp {
|
256
|
+
from {
|
257
|
+
transform: translateY(1rem);
|
258
|
+
opacity: 0;
|
259
|
+
}
|
260
|
+
to {
|
261
|
+
transform: translateY(0);
|
262
|
+
opacity: 1;
|
263
|
+
}
|
264
|
+
}
|
265
|
+
|
266
|
+
@keyframes fadeIn {
|
267
|
+
from {
|
268
|
+
opacity: 0;
|
269
|
+
}
|
270
|
+
to {
|
271
|
+
opacity: 1;
|
272
|
+
}
|
273
|
+
}
|
274
|
+
|
275
|
+
.modal[open] .modal-box {
|
276
|
+
animation: slideUp 0.3s ease-out forwards;
|
277
|
+
}
|
278
|
+
|
279
|
+
.modal[open] .modal-backdrop {
|
280
|
+
animation: fadeIn 0.2s ease-out forwards;
|
281
|
+
}
|
282
|
+
</style>
|
283
|
+
|
284
|
+
<script>
|
285
|
+
function initializeThemeSwitch() {
|
286
|
+
const themeItems = document.querySelectorAll('[data-set-theme]');
|
287
|
+
const updateActiveTheme = (currentTheme: string) => {
|
288
|
+
themeItems.forEach((item) => {
|
289
|
+
const themeId = item.getAttribute('data-set-theme');
|
290
|
+
const isActive = themeId === currentTheme;
|
291
|
+
item.classList.toggle('bg-primary', isActive);
|
292
|
+
item.classList.toggle('text-primary-content', isActive);
|
293
|
+
});
|
294
|
+
};
|
295
|
+
|
296
|
+
themeItems.forEach((item) => {
|
297
|
+
// 移除可能存在的旧事件监听器
|
298
|
+
item.removeEventListener('click', handleThemeClick);
|
299
|
+
// 添加新的事件监听器
|
300
|
+
item.addEventListener('click', handleThemeClick);
|
301
|
+
});
|
302
|
+
|
303
|
+
function handleThemeClick(event: Event) {
|
304
|
+
const item = event.currentTarget as HTMLElement;
|
305
|
+
const theme = item.getAttribute('data-set-theme');
|
306
|
+
document.documentElement.setAttribute('data-theme', theme ?? 'default');
|
307
|
+
localStorage.setItem('theme', theme ?? 'default');
|
308
|
+
updateActiveTheme(theme ?? 'default');
|
309
|
+
}
|
310
|
+
|
311
|
+
// 从本地存储中获取主题并更新激活状态
|
312
|
+
const savedTheme = localStorage.getItem('theme') || 'default';
|
313
|
+
document.documentElement.setAttribute('data-theme', savedTheme);
|
314
|
+
updateActiveTheme(savedTheme);
|
315
|
+
}
|
316
|
+
|
317
|
+
// 初始加载时初始化
|
318
|
+
document.addEventListener('DOMContentLoaded', initializeThemeSwitch);
|
319
|
+
|
320
|
+
// Astro view transitions 后重新初始化
|
321
|
+
document.addEventListener('astro:after-swap', initializeThemeSwitch);
|
322
|
+
</script>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
---
|
2
|
+
interface Props {
|
3
|
+
href: string;
|
4
|
+
external?: boolean;
|
5
|
+
class?: string;
|
6
|
+
}
|
7
|
+
|
8
|
+
const { href, external = false, class: className = '' } = Astro.props;
|
9
|
+
---
|
10
|
+
|
11
|
+
<a
|
12
|
+
href={href}
|
13
|
+
class={`link link-hover ${className} no-underline hover:no-underline`}
|
14
|
+
{...external ? { target: '_blank', rel: 'noopener noreferrer' } : {}}>
|
15
|
+
<slot />
|
16
|
+
</a>
|
@@ -0,0 +1,36 @@
|
|
1
|
+
---
|
2
|
+
interface Props {
|
3
|
+
platform: string;
|
4
|
+
class?: string;
|
5
|
+
}
|
6
|
+
|
7
|
+
const { platform = 'default', class: className = 'w-4 h-4' } = Astro.props;
|
8
|
+
|
9
|
+
// 所有平台的图标路径
|
10
|
+
const icons = {
|
11
|
+
github:
|
12
|
+
'M12 0C5.374 0 0 5.373 0 12c0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.509 11.509 0 0112 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576C20.566 21.797 24 17.3 24 12c0-6.627-5.373-12-12-12z',
|
13
|
+
twitter:
|
14
|
+
'M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z',
|
15
|
+
linkedin:
|
16
|
+
'M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z',
|
17
|
+
weibo:
|
18
|
+
'M10.098 20.323c-3.977.391-7.414-1.406-7.672-4.016-.259-2.609 2.759-5.047 6.74-5.441 3.979-.394 7.413 1.404 7.671 4.017.259 2.608-2.759 5.047-6.737 5.44m6.853-7.421c-.34-.714-.996-1.002-1.563-.807-.566.193-.752.849-.414 1.555.336.714.992 1.002 1.559.807.572-.195.756-.85.418-1.555m2.84-2.89c-1.154-3.553-5.027-5.606-8.82-4.779-3.791.836-6.119 4.608-4.965 8.162 1.152 3.553 5.027 5.607 8.82 4.779 3.791-.834 6.119-4.607 4.965-8.162M22.371.748c-2.637-2.637-6.532-2.637-9.168 0L.748 13.203c-.998.998-.998 2.617 0 3.615l5.354 5.355c.998.998 2.617.998 3.615 0L22.371 9.916c2.637-2.637 2.637-6.532 0-9.168',
|
19
|
+
wechat:
|
20
|
+
'M8.691 2.188C3.891 2.188 0 5.476 0 9.53c0 2.212 1.17 4.203 3.002 5.55a.59.59 0 0 1 .213.665l-.39 1.48c-.019.07-.048.141-.048.213 0 .163.13.295.29.295a.326.326 0 0 0 .167-.054l1.903-1.114a.864.864 0 0 1 .717-.098 10.16 10.16 0 0 0 2.837.403c.276 0 .543-.027.811-.05-.857-2.578.157-4.972 1.932-6.446 1.703-1.415 3.882-1.98 5.853-1.838-.576-3.583-4.196-6.348-8.596-6.348zM5.785 5.991c.642 0 1.162.529 1.162 1.182 0 .653-.52 1.182-1.162 1.182-.642 0-1.162-.529-1.162-1.182 0-.653.52-1.182 1.162-1.182zm5.813 0c.642 0 1.162.529 1.162 1.182 0 .653-.52 1.182-1.162 1.182-.642 0-1.162-.529-1.162-1.182 0-.653.52-1.182 1.162-1.182zm6.636 2.456c-3.963 0-7.174 2.803-7.174 6.26 0 3.458 3.211 6.26 7.174 6.26.826 0 1.621-.117 2.35-.332.092-.028.185-.042.277-.042.182 0 .359.056.51.16l1.573.923a.327.327 0 0 0 .168.054c.16 0 .29-.132.29-.295 0-.072-.03-.143-.048-.213l-.322-1.226a.589.589 0 0 1 .213-.665c1.508-1.192 2.363-2.923 2.363-4.624 0-3.457-3.21-6.26-7.174-6.26zm-2.896 3.028c.531 0 .962.438.962.978 0 .54-.43.978-.962.978s-.962-.438-.962-.978c0-.54.43-.978.962-.978zm5.813 0c.531 0 .962.438.962.978 0 .54-.43.978-.962.978s-.962-.438-.962-.978c0-.54.43-.978.962-.978z',
|
21
|
+
zhihu:
|
22
|
+
'M12.2 2.4c0-.4-.3-.7-.7-.7H5.9c-.4 0-.7.3-.7.7v1.4h7v-1.4zM6.9 5.7h2.8v12.8H6.9V5.7zm10.2 0H14v12.8h3.1V5.7zm.9-3.3H4c-.6 0-1 .4-1 1v16c0 .6.4 1 1 1h14c.6 0 1-.4 1-1v-16c0-.6-.4-1-1-1z',
|
23
|
+
bilibili:
|
24
|
+
'M17.813 4.653h.854c1.51.054 2.769.578 3.773 1.574 1.004.995 1.524 2.249 1.56 3.76v7.36c-.036 1.51-.556 2.769-1.56 3.773s-2.262 1.524-3.773 1.56H5.333c-1.51-.036-2.769-.556-3.773-1.56S.036 18.858 0 17.347v-7.36c.036-1.511.556-2.765 1.56-3.76 1.004-.996 2.262-1.52 3.773-1.574h.774l-1.174-1.12a1.234 1.234 0 0 1-.373-.906c0-.356.124-.658.373-.907l.027-.027c.267-.249.573-.373.92-.373.347 0 .653.124.92.373L9.653 4.44c.071.071.134.142.187.213h4.267a.836.836 0 0 1 .16-.213l2.853-2.747c.267-.249.573-.373.92-.373.347 0 .662.151.929.4.267.249.391.551.391.907 0 .355-.124.657-.373.906zM5.333 7.24c-.746.018-1.373.276-1.88.773-.506.498-.769 1.13-.786 1.894v7.52c.017.764.28 1.395.786 1.893.507.498 1.134.756 1.88.773h13.334c.746-.017 1.373-.275 1.88-.773.506-.498.769-1.129.786-1.893v-7.52c-.017-.765-.28-1.396-.786-1.894-.507-.497-1.134-.755-1.88-.773zM8 11.107c.373 0 .684.124.933.373.25.249.383.569.4.96v1.173c-.017.391-.15.711-.4.96-.249.25-.56.374-.933.374s-.684-.125-.933-.374c-.25-.249-.383-.569-.4-.96V12.44c0-.373.129-.689.386-.947.258-.257.574-.386.947-.386zm8 0c.373 0 .684.124.933.373.25.249.383.569.4.96v1.173c-.017.391-.15.711-.4.96-.249.25-.56.374-.933.374s-.684-.125-.933-.374c-.25-.249-.383-.569-.4-.96V12.44c.017-.391.15-.711.4-.96.249-.249.56-.373.933-.373z',
|
25
|
+
youtube:
|
26
|
+
'M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z',
|
27
|
+
discord:
|
28
|
+
'M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z',
|
29
|
+
default:
|
30
|
+
'M12 0C5.373 0 0 5.373 0 12s5.373 12 12 12 12-5.373 12-12S18.627 0 12 0zm0 22c-5.523 0-10-4.477-10-10S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm0-15c-2.761 0-5 2.239-5 5s2.239 5 5 5 5-2.239 5-5-2.239-5-5-5zm0 8c-1.657 0-3-1.343-3-3s1.343-3 3-3 3 1.343 3 3-1.343 3-3 3z',
|
31
|
+
};
|
32
|
+
---
|
33
|
+
|
34
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class={className} fill="currentColor">
|
35
|
+
<path d={icons[platform] || icons.default}></path>
|
36
|
+
</svg>
|
package/src/env.d.ts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
/// <reference types="astro/client" />
|
@@ -0,0 +1,90 @@
|
|
1
|
+
// 定义支持的社交平台及其配置
|
2
|
+
interface PlatformConfig {
|
3
|
+
name: string;
|
4
|
+
domains: string[];
|
5
|
+
}
|
6
|
+
|
7
|
+
// 处理后的社交链接类型
|
8
|
+
export interface ProcessedSocialLink {
|
9
|
+
url: string;
|
10
|
+
name: string;
|
11
|
+
platform: string;
|
12
|
+
}
|
13
|
+
|
14
|
+
// 社交平台配置
|
15
|
+
const platformConfigs: Record<string, PlatformConfig> = {
|
16
|
+
github: {
|
17
|
+
name: 'GitHub',
|
18
|
+
domains: ['github.com'],
|
19
|
+
},
|
20
|
+
twitter: {
|
21
|
+
name: 'Twitter',
|
22
|
+
domains: ['twitter.com', 'x.com'],
|
23
|
+
},
|
24
|
+
linkedin: {
|
25
|
+
name: 'LinkedIn',
|
26
|
+
domains: ['linkedin.com'],
|
27
|
+
},
|
28
|
+
weibo: {
|
29
|
+
name: '微博',
|
30
|
+
domains: ['weibo.com'],
|
31
|
+
},
|
32
|
+
wechat: {
|
33
|
+
name: '微信',
|
34
|
+
domains: ['wechat.com', 'weixin.qq.com'],
|
35
|
+
},
|
36
|
+
zhihu: {
|
37
|
+
name: '知乎',
|
38
|
+
domains: ['zhihu.com'],
|
39
|
+
},
|
40
|
+
bilibili: {
|
41
|
+
name: 'BiliBili',
|
42
|
+
domains: ['bilibili.com'],
|
43
|
+
},
|
44
|
+
youtube: {
|
45
|
+
name: 'YouTube',
|
46
|
+
domains: ['youtube.com', 'youtu.be'],
|
47
|
+
},
|
48
|
+
discord: {
|
49
|
+
name: 'Discord',
|
50
|
+
domains: ['discord.com', 'discord.gg'],
|
51
|
+
},
|
52
|
+
};
|
53
|
+
|
54
|
+
// 从 URL 判断社交平台
|
55
|
+
function detectPlatform(url: string): [string, PlatformConfig] | null {
|
56
|
+
try {
|
57
|
+
const urlObj = new URL(url);
|
58
|
+
const hostname = urlObj.hostname.toLowerCase();
|
59
|
+
|
60
|
+
for (const [platform, config] of Object.entries(platformConfigs)) {
|
61
|
+
if (config.domains.some((domain) => hostname.includes(domain))) {
|
62
|
+
return [platform, config];
|
63
|
+
}
|
64
|
+
}
|
65
|
+
} catch (e) {
|
66
|
+
console.error('Invalid URL:', url);
|
67
|
+
}
|
68
|
+
return null;
|
69
|
+
}
|
70
|
+
|
71
|
+
// 处理社交链接
|
72
|
+
export function processSocialLink(url: string): ProcessedSocialLink {
|
73
|
+
const platformInfo = detectPlatform(url);
|
74
|
+
|
75
|
+
if (!platformInfo) {
|
76
|
+
// 如果无法识别平台,返回默认值
|
77
|
+
return {
|
78
|
+
url,
|
79
|
+
name: new URL(url).hostname,
|
80
|
+
platform: 'default',
|
81
|
+
};
|
82
|
+
}
|
83
|
+
|
84
|
+
const [platform, config] = platformInfo;
|
85
|
+
return {
|
86
|
+
url,
|
87
|
+
name: config.name,
|
88
|
+
platform,
|
89
|
+
};
|
90
|
+
}
|