@coffic/cosy-ui 0.1.29 → 0.2.0
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 +67 -23
- package/dist/app.css +1 -0
- package/dist/assets/logo-rounded.png +0 -0
- package/dist/assets/logo.png +0 -0
- package/dist/components/base/Alert.astro +186 -0
- package/dist/components/base/Button.astro +103 -0
- package/dist/components/base/Image.astro +291 -0
- package/dist/components/base/Link.astro +131 -0
- package/dist/components/base/README.md +53 -0
- package/dist/components/base/index.ts +6 -0
- package/dist/components/containers/Container.astro +103 -0
- package/dist/components/containers/Main.astro +167 -0
- package/dist/components/containers/Section.astro +145 -0
- package/dist/components/containers/index.ts +3 -0
- package/dist/components/data-display/Blog.astro +195 -0
- package/dist/components/data-display/README.md +37 -0
- package/dist/components/data-display/TeamMember.astro +135 -0
- package/dist/components/data-display/TeamMembers.astro +101 -0
- package/dist/components/data-display/index.ts +3 -0
- package/dist/components/display/Banner.astro +57 -0
- package/dist/components/display/Card.astro +135 -0
- package/dist/components/display/CodeBlock.astro +147 -0
- package/dist/components/display/CodeExample.astro +330 -0
- package/dist/components/display/Hero.astro +119 -0
- package/dist/components/display/Modal.astro +115 -0
- package/dist/components/display/README.md +32 -0
- package/dist/components/display/index.ts +6 -0
- package/dist/components/icons/AlertTriangle.astro +35 -0
- package/dist/components/icons/CalendarIcon.astro +38 -0
- package/dist/components/icons/CheckCircle.astro +36 -0
- package/dist/components/icons/CheckIcon.astro +38 -0
- package/dist/components/icons/ClipboardIcon.astro +39 -0
- package/dist/components/icons/CloseIcon.astro +38 -0
- package/dist/components/icons/ErrorIcon.astro +35 -0
- package/dist/components/icons/GithubIcon.astro +31 -0
- package/dist/components/icons/InfoCircle.astro +37 -0
- package/dist/components/icons/InfoIcon.astro +38 -0
- package/dist/components/icons/LinkIcon.astro +39 -0
- package/dist/components/icons/LinkedinIcon.astro +31 -0
- package/dist/components/icons/MenuIcon.astro +41 -0
- package/dist/components/icons/SearchIcon.astro +40 -0
- package/dist/components/icons/SocialIcon.astro +100 -0
- package/dist/components/icons/SuccessIcon.astro +35 -0
- package/dist/components/icons/SunCloudyIcon.astro +45 -0
- package/dist/components/icons/TwitterIcon.astro +31 -0
- package/dist/components/icons/UserIcon.astro +35 -0
- package/dist/components/icons/WarningIcon.astro +38 -0
- package/dist/components/icons/XCircle.astro +37 -0
- package/dist/components/icons/index.ts +23 -0
- package/dist/components/layouts/BaseLayout.astro +144 -0
- package/dist/components/layouts/DashboardLayout.astro +660 -0
- package/dist/components/layouts/DefaultLayout.astro +170 -0
- package/dist/components/layouts/DocumentationLayout.astro +469 -0
- package/dist/components/layouts/Flex.astro +138 -0
- package/dist/components/layouts/Footer.astro +284 -0
- package/dist/components/layouts/Grid.astro +182 -0
- package/dist/components/layouts/Header.astro +114 -0
- package/dist/components/layouts/LandingLayout.astro +388 -0
- package/dist/components/layouts/README.md +37 -0
- package/dist/components/layouts/Stack.astro +149 -0
- package/dist/components/layouts/index.ts +6 -0
- package/dist/components/navigation/LanguageSwitcher.astro +81 -0
- package/dist/components/navigation/README.md +24 -0
- package/dist/components/navigation/TableOfContents.astro +352 -0
- package/dist/components/navigation/ThemeSwitcher.astro +89 -0
- package/dist/components/navigation/index.ts +3 -0
- package/dist/components/typography/Article.astro +144 -0
- package/dist/components/typography/Heading.astro +205 -0
- package/dist/components/typography/README.md +29 -0
- package/dist/components/typography/Text.astro +187 -0
- package/dist/components/typography/index.ts +3 -0
- package/dist/index.ts +9 -0
- package/dist/integration.ts +14 -0
- package/dist/style.ts +1 -0
- package/{src → dist}/types/footer.ts +1 -0
- package/dist/utils/theme.ts +55 -0
- package/package.json +65 -59
- package/index.ts +0 -18
- package/src/components/Alert.astro +0 -78
- package/src/components/Article.astro +0 -11
- package/src/components/Banner.astro +0 -49
- package/src/components/Blog.astro +0 -115
- package/src/components/Button.astro +0 -49
- package/src/components/Card.astro +0 -113
- package/src/components/CodeBlock.astro +0 -186
- package/src/components/Footer.astro +0 -148
- package/src/components/Header.astro +0 -305
- package/src/components/Hero.astro +0 -69
- package/src/components/Image.astro +0 -251
- package/src/components/Link.astro +0 -82
- package/src/components/Modal.astro +0 -67
- package/src/components/SocialIcon.astro +0 -36
- package/src/components/TeamMember.astro +0 -68
- package/src/components/TeamMembers.astro +0 -43
- package/src/env.d.ts +0 -0
- /package/{src/components → dist/components/base}/ThemeItem.astro +0 -0
- /package/{src → dist}/utils/social.ts +0 -0
@@ -1,78 +0,0 @@
|
|
1
|
-
---
|
2
|
-
interface Props {
|
3
|
-
type?: 'info' | 'success' | 'warning' | 'error';
|
4
|
-
title?: string;
|
5
|
-
closeable?: boolean;
|
6
|
-
class?: string;
|
7
|
-
}
|
8
|
-
|
9
|
-
const {
|
10
|
-
type = 'info',
|
11
|
-
title,
|
12
|
-
closeable = false,
|
13
|
-
class: className = '',
|
14
|
-
} = Astro.props;
|
15
|
-
|
16
|
-
// 根据类型设置样式
|
17
|
-
const alertClass = {
|
18
|
-
info: 'alert-info',
|
19
|
-
success: 'alert-success',
|
20
|
-
warning: 'alert-warning',
|
21
|
-
error: 'alert-error',
|
22
|
-
}[type];
|
23
|
-
|
24
|
-
// 根据类型设置图标
|
25
|
-
const icons = {
|
26
|
-
info: `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="h-6 w-6 stroke-current">
|
27
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
28
|
-
</svg>`,
|
29
|
-
success: `<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 stroke-current" fill="none" viewBox="0 0 24 24">
|
30
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
31
|
-
</svg>`,
|
32
|
-
warning: `<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 stroke-current" fill="none" viewBox="0 0 24 24">
|
33
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
34
|
-
</svg>`,
|
35
|
-
error: `<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 stroke-current" fill="none" viewBox="0 0 24 24">
|
36
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
37
|
-
</svg>`,
|
38
|
-
};
|
39
|
-
---
|
40
|
-
|
41
|
-
<div class={`alert ${alertClass} ${className}`} role="alert">
|
42
|
-
<div class="flex items-center gap-2">
|
43
|
-
<div set:html={icons[type]} />
|
44
|
-
<div class="flex-1">
|
45
|
-
{title && <h3 class="font-bold">{title}</h3>}
|
46
|
-
<div><slot /></div>
|
47
|
-
</div>
|
48
|
-
{closeable && (
|
49
|
-
<button class="btn btn-ghost btn-sm btn-circle close-alert">
|
50
|
-
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
51
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
52
|
-
</svg>
|
53
|
-
</button>
|
54
|
-
)}
|
55
|
-
</div>
|
56
|
-
</div>
|
57
|
-
|
58
|
-
{closeable && (
|
59
|
-
<script>
|
60
|
-
function initializeCloseButtons() {
|
61
|
-
const closeButtons = document.querySelectorAll('.close-alert');
|
62
|
-
closeButtons.forEach(button => {
|
63
|
-
button.addEventListener('click', () => {
|
64
|
-
const alert = button.closest('.alert');
|
65
|
-
if (alert) {
|
66
|
-
alert.remove();
|
67
|
-
}
|
68
|
-
});
|
69
|
-
});
|
70
|
-
}
|
71
|
-
|
72
|
-
// 初始化关闭按钮
|
73
|
-
initializeCloseButtons();
|
74
|
-
|
75
|
-
// Astro 页面切换时重新初始化
|
76
|
-
document.addEventListener('astro:page-load', initializeCloseButtons);
|
77
|
-
</script>
|
78
|
-
)}
|
@@ -1,49 +0,0 @@
|
|
1
|
-
<div class="w-full max-w-7xl px-4 sm: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
|
4
|
-
hover:shadow-2xl transform duration-300 ease-in-out
|
5
|
-
hover:scale-[1.02] hover:bg-base-300/70 dark:hover:bg-base-200/70
|
6
|
-
motion-safe:animate-fadeIn"
|
7
|
-
>
|
8
|
-
<div class="text-center hero-content">
|
9
|
-
<div class="w-full max-w-prose flex flex-row justify-center items-center mx-auto">
|
10
|
-
<p
|
11
|
-
class="text-xl sm:text-2xl md:text-3xl font-bold text-base-content
|
12
|
-
motion-safe:animate-slideUp"
|
13
|
-
>
|
14
|
-
<slot />
|
15
|
-
</p>
|
16
|
-
</div>
|
17
|
-
</div>
|
18
|
-
</div>
|
19
|
-
</div>
|
20
|
-
|
21
|
-
<style>
|
22
|
-
@keyframes fadeIn {
|
23
|
-
from {
|
24
|
-
opacity: 0;
|
25
|
-
}
|
26
|
-
to {
|
27
|
-
opacity: 1;
|
28
|
-
}
|
29
|
-
}
|
30
|
-
|
31
|
-
@keyframes slideUp {
|
32
|
-
from {
|
33
|
-
opacity: 0;
|
34
|
-
transform: translateY(1rem);
|
35
|
-
}
|
36
|
-
to {
|
37
|
-
opacity: 1;
|
38
|
-
transform: translateY(0);
|
39
|
-
}
|
40
|
-
}
|
41
|
-
|
42
|
-
.animate-fadeIn {
|
43
|
-
animation: fadeIn 0.5s ease-out;
|
44
|
-
}
|
45
|
-
|
46
|
-
.animate-slideUp {
|
47
|
-
animation: slideUp 0.5s ease-out;
|
48
|
-
}
|
49
|
-
</style>
|
@@ -1,115 +0,0 @@
|
|
1
|
-
---
|
2
|
-
interface Props {
|
3
|
-
title: string;
|
4
|
-
subtitle?: string;
|
5
|
-
author?: string;
|
6
|
-
date?: Date;
|
7
|
-
cover?: {
|
8
|
-
src: string;
|
9
|
-
alt?: string;
|
10
|
-
};
|
11
|
-
tags?: string[];
|
12
|
-
}
|
13
|
-
|
14
|
-
const {
|
15
|
-
title,
|
16
|
-
subtitle,
|
17
|
-
author,
|
18
|
-
date,
|
19
|
-
cover,
|
20
|
-
tags = [],
|
21
|
-
} = Astro.props;
|
22
|
-
|
23
|
-
// 格式化日期
|
24
|
-
const formatDate = (date?: Date) => {
|
25
|
-
if (!date) return '';
|
26
|
-
return new Intl.DateTimeFormat('zh-CN', {
|
27
|
-
year: 'numeric',
|
28
|
-
month: 'long',
|
29
|
-
day: 'numeric'
|
30
|
-
}).format(date);
|
31
|
-
};
|
32
|
-
---
|
33
|
-
|
34
|
-
<article class="article">
|
35
|
-
{/* 文章头部 */}
|
36
|
-
<header class="mb-8">
|
37
|
-
{cover && (
|
38
|
-
<div class="relative w-full h-[300px] md:h-[400px] mb-6 rounded-lg overflow-hidden">
|
39
|
-
<img
|
40
|
-
src={cover.src}
|
41
|
-
alt={cover.alt || title}
|
42
|
-
class="w-full h-full object-cover"
|
43
|
-
/>
|
44
|
-
</div>
|
45
|
-
)}
|
46
|
-
|
47
|
-
<div class="space-y-4">
|
48
|
-
<h1 class="text-4xl md:text-5xl font-bold tracking-tight">{title}</h1>
|
49
|
-
{subtitle && (
|
50
|
-
<p class="text-xl text-base-content/70">{subtitle}</p>
|
51
|
-
)}
|
52
|
-
|
53
|
-
<div class="flex flex-wrap items-center gap-4 text-sm text-base-content/60">
|
54
|
-
{author && (
|
55
|
-
<div class="flex items-center gap-2">
|
56
|
-
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
57
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
58
|
-
</svg>
|
59
|
-
<span>{author}</span>
|
60
|
-
</div>
|
61
|
-
)}
|
62
|
-
|
63
|
-
{date && (
|
64
|
-
<div class="flex items-center gap-2">
|
65
|
-
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
66
|
-
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
|
67
|
-
<line x1="16" y1="2" x2="16" y2="6"></line>
|
68
|
-
<line x1="8" y1="2" x2="8" y2="6"></line>
|
69
|
-
<line x1="3" y1="10" x2="21" y2="10"></line>
|
70
|
-
</svg>
|
71
|
-
<time datetime={date.toISOString()}>{formatDate(date)}</time>
|
72
|
-
</div>
|
73
|
-
)}
|
74
|
-
</div>
|
75
|
-
|
76
|
-
{tags.length > 0 && (
|
77
|
-
<div class="flex flex-wrap gap-2">
|
78
|
-
{tags.map(tag => (
|
79
|
-
<span class="badge badge-outline">{tag}</span>
|
80
|
-
))}
|
81
|
-
</div>
|
82
|
-
)}
|
83
|
-
</div>
|
84
|
-
</header>
|
85
|
-
|
86
|
-
{/* 文章内容 */}
|
87
|
-
<div class="prose prose-lg max-w-none">
|
88
|
-
<slot />
|
89
|
-
</div>
|
90
|
-
</article>
|
91
|
-
|
92
|
-
<style>
|
93
|
-
/* 自定义文章样式 */
|
94
|
-
.article {
|
95
|
-
container-type: inline-size;
|
96
|
-
}
|
97
|
-
|
98
|
-
/* 优化大屏幕阅读体验 */
|
99
|
-
@container (min-width: 768px) {
|
100
|
-
.article {
|
101
|
-
font-size: 1.125rem;
|
102
|
-
line-height: 1.75;
|
103
|
-
}
|
104
|
-
}
|
105
|
-
|
106
|
-
/* 暗色主题适配 */
|
107
|
-
:global([data-theme="dark"]) .article {
|
108
|
-
--tw-prose-body: hsl(var(--bc));
|
109
|
-
--tw-prose-headings: hsl(var(--bc));
|
110
|
-
--tw-prose-links: hsl(var(--p));
|
111
|
-
--tw-prose-bold: hsl(var(--bc));
|
112
|
-
--tw-prose-quotes: hsl(var(--bc));
|
113
|
-
--tw-prose-code: hsl(var(--bc));
|
114
|
-
}
|
115
|
-
</style>
|
@@ -1,49 +0,0 @@
|
|
1
|
-
---
|
2
|
-
interface Props {
|
3
|
-
variant?: 'primary' | 'secondary' | 'accent' | 'info' | 'success' | 'warning' | 'error' | 'ghost' | 'link' | 'outline' | 'neutral';
|
4
|
-
size?: 'lg' | 'md' | 'sm' | 'xs';
|
5
|
-
shape?: 'circle' | 'square';
|
6
|
-
wide?: boolean;
|
7
|
-
block?: boolean;
|
8
|
-
loading?: boolean;
|
9
|
-
disabled?: boolean;
|
10
|
-
type?: 'button' | 'submit' | 'reset';
|
11
|
-
class?: string;
|
12
|
-
onClick?: string;
|
13
|
-
}
|
14
|
-
|
15
|
-
const {
|
16
|
-
variant,
|
17
|
-
size,
|
18
|
-
shape,
|
19
|
-
wide = false,
|
20
|
-
block = false,
|
21
|
-
loading = false,
|
22
|
-
disabled = false,
|
23
|
-
type = 'button',
|
24
|
-
class: className,
|
25
|
-
onClick,
|
26
|
-
} = Astro.props;
|
27
|
-
|
28
|
-
const classes = [
|
29
|
-
'btn',
|
30
|
-
variant && `btn-${variant}`,
|
31
|
-
size && `btn-${size}`,
|
32
|
-
shape && `btn-${shape}`,
|
33
|
-
wide && 'btn-wide',
|
34
|
-
block && 'btn-block',
|
35
|
-
loading && 'loading',
|
36
|
-
className,
|
37
|
-
].filter(Boolean).join(' ');
|
38
|
-
---
|
39
|
-
|
40
|
-
<button
|
41
|
-
type={type}
|
42
|
-
class={classes}
|
43
|
-
disabled={disabled}
|
44
|
-
onclick={onClick}
|
45
|
-
>
|
46
|
-
<slot name="icon-left" />
|
47
|
-
<slot />
|
48
|
-
<slot name="icon-right" />
|
49
|
-
</button>
|
@@ -1,113 +0,0 @@
|
|
1
|
-
---
|
2
|
-
interface Props {
|
3
|
-
title?: string;
|
4
|
-
subtitle?: string;
|
5
|
-
imageUrl?: string;
|
6
|
-
href?: string;
|
7
|
-
}
|
8
|
-
|
9
|
-
const { title, subtitle, imageUrl, href } = Astro.props;
|
10
|
-
---
|
11
|
-
|
12
|
-
<div class="group perspective-1000">
|
13
|
-
<div class:list={[
|
14
|
-
"card w-full bg-base-100/60 dark:bg-base-300/60 backdrop-blur-lg",
|
15
|
-
"transform transition-all duration-300 ease-out",
|
16
|
-
"hover:shadow-2xl hover:scale-[1.02]",
|
17
|
-
"active:scale-[0.98]",
|
18
|
-
"motion-safe:animate-fadeIn",
|
19
|
-
href && "cursor-pointer"
|
20
|
-
]}>
|
21
|
-
{href ? (
|
22
|
-
<a href={href} class="card-body group-hover:translate-y-[-2px] transition-transform duration-300">
|
23
|
-
{imageUrl && (
|
24
|
-
<figure class="relative overflow-hidden rounded-t-2xl">
|
25
|
-
<img
|
26
|
-
src={imageUrl}
|
27
|
-
alt={title}
|
28
|
-
class="w-full h-48 object-cover transform transition-transform duration-300 group-hover:scale-105"
|
29
|
-
/>
|
30
|
-
</figure>
|
31
|
-
)}
|
32
|
-
{title && (
|
33
|
-
<h2 class="card-title text-xl font-bold tracking-tight text-base-content">
|
34
|
-
{title}
|
35
|
-
</h2>
|
36
|
-
)}
|
37
|
-
{subtitle && (
|
38
|
-
<p class="text-base-content/80">
|
39
|
-
{subtitle}
|
40
|
-
</p>
|
41
|
-
)}
|
42
|
-
<slot />
|
43
|
-
</a>
|
44
|
-
) : (
|
45
|
-
<div class="card-body">
|
46
|
-
{imageUrl && (
|
47
|
-
<figure class="relative overflow-hidden rounded-t-2xl">
|
48
|
-
<img
|
49
|
-
src={imageUrl}
|
50
|
-
alt={title}
|
51
|
-
class="w-full h-48 object-cover transform transition-transform duration-300 group-hover:scale-105"
|
52
|
-
/>
|
53
|
-
</figure>
|
54
|
-
)}
|
55
|
-
{title && (
|
56
|
-
<h2 class="card-title text-xl font-bold tracking-tight text-base-content">
|
57
|
-
{title}
|
58
|
-
</h2>
|
59
|
-
)}
|
60
|
-
{subtitle && (
|
61
|
-
<p class="text-base-content/80">
|
62
|
-
{subtitle}
|
63
|
-
</p>
|
64
|
-
)}
|
65
|
-
<slot />
|
66
|
-
</div>
|
67
|
-
)}
|
68
|
-
</div>
|
69
|
-
</div>
|
70
|
-
|
71
|
-
<style>
|
72
|
-
@keyframes fadeIn {
|
73
|
-
from {
|
74
|
-
opacity: 0;
|
75
|
-
transform: translateY(10px);
|
76
|
-
}
|
77
|
-
to {
|
78
|
-
opacity: 1;
|
79
|
-
transform: translateY(0);
|
80
|
-
}
|
81
|
-
}
|
82
|
-
|
83
|
-
.animate-fadeIn {
|
84
|
-
animation: fadeIn 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
85
|
-
}
|
86
|
-
|
87
|
-
.perspective-1000 {
|
88
|
-
perspective: 1000px;
|
89
|
-
}
|
90
|
-
|
91
|
-
.card {
|
92
|
-
position: relative;
|
93
|
-
border: 1px solid rgba(255, 255, 255, 0.1);
|
94
|
-
}
|
95
|
-
|
96
|
-
.card::before {
|
97
|
-
content: '';
|
98
|
-
position: absolute;
|
99
|
-
inset: 0;
|
100
|
-
z-index: -1;
|
101
|
-
background: radial-gradient(
|
102
|
-
circle at 50% 0%,
|
103
|
-
rgba(255, 255, 255, 0.1),
|
104
|
-
transparent 70%
|
105
|
-
);
|
106
|
-
opacity: 0;
|
107
|
-
transition: opacity 0.3s ease;
|
108
|
-
}
|
109
|
-
|
110
|
-
.card:hover::before {
|
111
|
-
opacity: 1;
|
112
|
-
}
|
113
|
-
</style>
|
@@ -1,186 +0,0 @@
|
|
1
|
-
---
|
2
|
-
interface Props {
|
3
|
-
code: string;
|
4
|
-
lang?: string;
|
5
|
-
title?: string;
|
6
|
-
showLineNumbers?: boolean;
|
7
|
-
}
|
8
|
-
|
9
|
-
const {
|
10
|
-
code,
|
11
|
-
lang = 'plaintext',
|
12
|
-
title,
|
13
|
-
showLineNumbers = true,
|
14
|
-
} = Astro.props;
|
15
|
-
|
16
|
-
// 移除代码字符串开头和结尾的空行
|
17
|
-
const trimmedCode = code.trim();
|
18
|
-
|
19
|
-
// 生成行号
|
20
|
-
const lines = trimmedCode.split('\n');
|
21
|
-
const lineNumbers = Array.from({ length: lines.length }, (_, i) => i + 1);
|
22
|
-
---
|
23
|
-
|
24
|
-
<div class="code-block not-prose rounded-lg border border-base-300 bg-base-200 my-4">
|
25
|
-
{/* 标题栏 */}
|
26
|
-
{title && (
|
27
|
-
<div class="flex items-center justify-between px-4 py-2 border-b border-base-300">
|
28
|
-
<span class="text-sm font-medium">{title}</span>
|
29
|
-
<button
|
30
|
-
class="copy-button btn btn-ghost btn-xs gap-1"
|
31
|
-
data-code={trimmedCode}
|
32
|
-
title="复制代码"
|
33
|
-
>
|
34
|
-
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
35
|
-
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
36
|
-
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
37
|
-
</svg>
|
38
|
-
<span class="copy-text">复制</span>
|
39
|
-
</button>
|
40
|
-
</div>
|
41
|
-
)}
|
42
|
-
|
43
|
-
{/* 代码区域 */}
|
44
|
-
<div class="relative">
|
45
|
-
{/* 行号 */}
|
46
|
-
{showLineNumbers && (
|
47
|
-
<div class="hidden md:block absolute left-0 top-0 bottom-0 px-4 py-4 select-none text-base-content/50 bg-base-300 border-r border-base-300">
|
48
|
-
{lineNumbers.map(num => (
|
49
|
-
<div class="text-right leading-6 text-sm">{num}</div>
|
50
|
-
))}
|
51
|
-
</div>
|
52
|
-
)}
|
53
|
-
|
54
|
-
{/* 代码内容 */}
|
55
|
-
<pre class={`language-${lang} ${showLineNumbers ? 'pl-[3.5rem]' : 'px-4'} py-4 m-0 overflow-x-auto`}><code class={`language-${lang}`} set:html={trimmedCode} /></pre>
|
56
|
-
</div>
|
57
|
-
</div>
|
58
|
-
|
59
|
-
<script>
|
60
|
-
function initializeCopyButtons() {
|
61
|
-
const copyButtons = document.querySelectorAll('.copy-button');
|
62
|
-
|
63
|
-
copyButtons.forEach(button => {
|
64
|
-
button.addEventListener('click', async () => {
|
65
|
-
const code = button.getAttribute('data-code') || '';
|
66
|
-
const copyText = button.querySelector('.copy-text');
|
67
|
-
|
68
|
-
try {
|
69
|
-
await navigator.clipboard.writeText(code);
|
70
|
-
if (copyText) {
|
71
|
-
copyText.textContent = '已复制!';
|
72
|
-
setTimeout(() => {
|
73
|
-
copyText.textContent = '复制';
|
74
|
-
}, 2000);
|
75
|
-
}
|
76
|
-
} catch (err) {
|
77
|
-
console.error('复制失败:', err);
|
78
|
-
if (copyText) {
|
79
|
-
copyText.textContent = '复制失败';
|
80
|
-
setTimeout(() => {
|
81
|
-
copyText.textContent = '复制';
|
82
|
-
}, 2000);
|
83
|
-
}
|
84
|
-
}
|
85
|
-
});
|
86
|
-
});
|
87
|
-
}
|
88
|
-
|
89
|
-
// 在页面加载时初始化
|
90
|
-
initializeCopyButtons();
|
91
|
-
|
92
|
-
// 在 Astro 页面切换时重新初始化
|
93
|
-
document.addEventListener('astro:page-load', initializeCopyButtons);
|
94
|
-
</script>
|
95
|
-
|
96
|
-
<style is:global>
|
97
|
-
/* Prism.js 暗色主题自定义 */
|
98
|
-
.code-block pre {
|
99
|
-
background: transparent !important;
|
100
|
-
}
|
101
|
-
|
102
|
-
.code-block code {
|
103
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
104
|
-
font-size: 0.875rem;
|
105
|
-
line-height: 1.5rem;
|
106
|
-
}
|
107
|
-
|
108
|
-
/* 代码高亮颜色 */
|
109
|
-
.token.comment,
|
110
|
-
.token.prolog,
|
111
|
-
.token.doctype,
|
112
|
-
.token.cdata {
|
113
|
-
color: #636f88;
|
114
|
-
}
|
115
|
-
|
116
|
-
.token.punctuation {
|
117
|
-
color: #81a1c1;
|
118
|
-
}
|
119
|
-
|
120
|
-
.namespace {
|
121
|
-
opacity: 0.7;
|
122
|
-
}
|
123
|
-
|
124
|
-
.token.property,
|
125
|
-
.token.tag,
|
126
|
-
.token.constant,
|
127
|
-
.token.symbol,
|
128
|
-
.token.deleted {
|
129
|
-
color: #81a1c1;
|
130
|
-
}
|
131
|
-
|
132
|
-
.token.number {
|
133
|
-
color: #b48ead;
|
134
|
-
}
|
135
|
-
|
136
|
-
.token.boolean {
|
137
|
-
color: #81a1c1;
|
138
|
-
}
|
139
|
-
|
140
|
-
.token.selector,
|
141
|
-
.token.attr-name,
|
142
|
-
.token.string,
|
143
|
-
.token.char,
|
144
|
-
.token.builtin,
|
145
|
-
.token.inserted {
|
146
|
-
color: #a3be8c;
|
147
|
-
}
|
148
|
-
|
149
|
-
.token.operator,
|
150
|
-
.token.entity,
|
151
|
-
.token.url,
|
152
|
-
.language-css .token.string,
|
153
|
-
.style .token.string,
|
154
|
-
.token.variable {
|
155
|
-
color: #81a1c1;
|
156
|
-
}
|
157
|
-
|
158
|
-
.token.atrule,
|
159
|
-
.token.attr-value,
|
160
|
-
.token.function,
|
161
|
-
.token.class-name {
|
162
|
-
color: #88c0d0;
|
163
|
-
}
|
164
|
-
|
165
|
-
.token.keyword {
|
166
|
-
color: #81a1c1;
|
167
|
-
}
|
168
|
-
|
169
|
-
.token.regex,
|
170
|
-
.token.important {
|
171
|
-
color: #ebcb8b;
|
172
|
-
}
|
173
|
-
|
174
|
-
.token.important,
|
175
|
-
.token.bold {
|
176
|
-
font-weight: bold;
|
177
|
-
}
|
178
|
-
|
179
|
-
.token.italic {
|
180
|
-
font-style: italic;
|
181
|
-
}
|
182
|
-
|
183
|
-
.token.entity {
|
184
|
-
cursor: help;
|
185
|
-
}
|
186
|
-
</style>
|