@jet-w/astro-blog 0.1.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/dist/chunk-FXPGR372.js +0 -0
- package/dist/chunk-GYLSY3OJ.js +173 -0
- package/dist/config/index.d.ts +166 -0
- package/dist/config/index.js +38 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +59 -0
- package/dist/types/index.d.ts +75 -0
- package/dist/types/index.js +1 -0
- package/package.json +84 -0
- package/src/components/EChartsCard.vue +118 -0
- package/src/components/Mermaid.vue +73 -0
- package/src/components/about/ContentCard.astro +27 -0
- package/src/components/about/IconCard.astro +77 -0
- package/src/components/about/SocialLinks.astro +54 -0
- package/src/components/about/TagCard.astro +65 -0
- package/src/components/about/TagGroup.astro +33 -0
- package/src/components/about/TimelineCard.astro +52 -0
- package/src/components/blog/FloatingToc.vue +198 -0
- package/src/components/blog/Hero.astro +147 -0
- package/src/components/blog/NavigationTabs.vue +245 -0
- package/src/components/blog/PostCard.astro +161 -0
- package/src/components/blog/PostNavigation.astro +106 -0
- package/src/components/blog/RelatedPosts.astro +175 -0
- package/src/components/blog/TableOfContents.astro +153 -0
- package/src/components/blog/TagCloud.astro +91 -0
- package/src/components/home/FeaturedPostsSection.astro +54 -0
- package/src/components/home/QuickNavSection.astro +81 -0
- package/src/components/home/RecentPostsSection.astro +52 -0
- package/src/components/home/StatsSection.astro +44 -0
- package/src/components/layout/Footer.astro +103 -0
- package/src/components/layout/Header.astro +68 -0
- package/src/components/layout/Sidebar.astro +594 -0
- package/src/components/media/Bilibili.astro +114 -0
- package/src/components/media/Slides.astro +313 -0
- package/src/components/media/Video.astro +111 -0
- package/src/components/media/VideoPlayer.astro +89 -0
- package/src/components/media/YouTube.astro +92 -0
- package/src/components/pte/StudyCalendar.vue +1348 -0
- package/src/components/ui/Icon.astro +187 -0
- package/src/components/ui/MobileMenu.vue +201 -0
- package/src/components/ui/Pagination.astro +143 -0
- package/src/components/ui/SearchBox.vue +179 -0
- package/src/components/ui/SearchInterface.vue +409 -0
- package/src/components/ui/SidebarToggle.vue +57 -0
- package/src/components/ui/ThemeToggle.vue +90 -0
- package/src/layouts/AboutLayout.astro +18 -0
- package/src/layouts/BaseLayout.astro +362 -0
- package/src/layouts/PageLayout.astro +217 -0
- package/src/layouts/SlidesLayout.astro +320 -0
- package/src/plugins/rehype-clean-containers.mjs +24 -0
- package/src/plugins/rehype-relative-links.mjs +43 -0
- package/src/plugins/rehype-tabs.mjs +116 -0
- package/src/plugins/remark-containers.mjs +407 -0
- package/src/plugins/remark-mermaid.mjs +46 -0
- package/src/styles/global.css +870 -0
- package/src/styles/slides.css +220 -0
- package/src/utils/sidebar.ts +492 -0
- package/templates/default/astro.config.mjs +51 -0
- package/templates/default/content/pages/about.mdx +93 -0
- package/templates/default/content/pages/index.mdx +20 -0
- package/templates/default/content/posts/blog_docs/01-quick-start.md +162 -0
- package/templates/default/content/posts/blog_docs/02-frontmatter.md +277 -0
- package/templates/default/content/posts/blog_docs/03-markdown-basic.md +350 -0
- package/templates/default/content/posts/blog_docs/04-containers.md +331 -0
- package/templates/default/content/posts/blog_docs/05-code-blocks.md +388 -0
- package/templates/default/content/posts/blog_docs/06-mermaid.md +431 -0
- package/templates/default/content/posts/blog_docs/07-video.md +243 -0
- package/templates/default/content/posts/blog_docs/08-latex.md +382 -0
- package/templates/default/content/posts/blog_docs/09-icons.md +326 -0
- package/templates/default/content/posts/blog_docs/10-sidebar.md +445 -0
- package/templates/default/content/posts/blog_docs/11-config.md +334 -0
- package/templates/default/content/posts/blog_docs/12-slides.mdx +552 -0
- package/templates/default/content/posts/blog_docs/README.md +151 -0
- package/templates/default/content/slides/demo.md +146 -0
- package/templates/default/content/slides/docs/basic-demo.md +35 -0
- package/templates/default/content/slides/docs/code-demo.md +62 -0
- package/templates/default/content/slides/docs/echarts-demo.md +139 -0
- package/templates/default/content/slides/docs/fragment-demo.md +35 -0
- package/templates/default/content/slides/docs/math-demo.md +48 -0
- package/templates/default/content/slides/docs/mermaid-demo.md +105 -0
- package/templates/default/content/slides/docs/theme-demo.md +38 -0
- package/templates/default/content/slides/docs/vertical-demo.md +50 -0
- package/templates/default/package.json +31 -0
- package/templates/default/public/favicon-bak.svg +4 -0
- package/templates/default/public/images/avatar.jpg +0 -0
- package/templates/default/public/images/avatar.svg +142 -0
- package/templates/default/public/js/mermaid-container.js +402 -0
- package/templates/default/public/js/mermaid-init.js +131 -0
- package/templates/default/public/js/mermaid-render.js +98 -0
- package/templates/default/public/js/mermaid-simple.js +95 -0
- package/templates/default/public/js/tabs-init.js +86 -0
- package/templates/default/public/media/individual_portfolio/INDIVIDUAL PORTFOLIO.png +0 -0
- package/templates/default/public/slides/plugin/highlight/highlight.js +5 -0
- package/templates/default/public/slides/plugin/highlight/monokai.css +71 -0
- package/templates/default/public/slides/plugin/markdown/markdown.js +7 -0
- package/templates/default/public/slides/plugin/math/math.js +1 -0
- package/templates/default/public/slides/plugin/notes/notes.js +1 -0
- package/templates/default/public/slides/reveal.css +9 -0
- package/templates/default/public/slides/reveal.js +9 -0
- package/templates/default/public/slides/theme/beige.css +366 -0
- package/templates/default/public/slides/theme/black-contrast.css +362 -0
- package/templates/default/public/slides/theme/black.css +359 -0
- package/templates/default/public/slides/theme/blood.css +392 -0
- package/templates/default/public/slides/theme/dracula.css +385 -0
- package/templates/default/public/slides/theme/league.css +368 -0
- package/templates/default/public/slides/theme/moon.css +362 -0
- package/templates/default/public/slides/theme/night.css +360 -0
- package/templates/default/public/slides/theme/serif.css +363 -0
- package/templates/default/public/slides/theme/simple.css +362 -0
- package/templates/default/public/slides/theme/sky.css +370 -0
- package/templates/default/public/slides/theme/solarized.css +363 -0
- package/templates/default/public/slides/theme/white-contrast.css +362 -0
- package/templates/default/public/slides/theme/white.css +359 -0
- package/templates/default/public/slides/theme/white_contrast_compact_verbatim_headers.css +360 -0
- package/templates/default/public/test-complete.html +43 -0
- package/templates/default/public/test-mermaid.html +124 -0
- package/templates/default/src/config/index.ts +114 -0
- package/templates/default/src/content.config.ts +96 -0
- package/templates/default/src/pages/[...slug].astro +27 -0
- package/templates/default/src/pages/archives/[year]/[month]/page/[page].astro +176 -0
- package/templates/default/src/pages/archives/[year]/[month].astro +158 -0
- package/templates/default/src/pages/archives/index.astro +210 -0
- package/templates/default/src/pages/categories/[category]/page/[page].astro +218 -0
- package/templates/default/src/pages/categories/[category].astro +198 -0
- package/templates/default/src/pages/categories/index.astro +190 -0
- package/templates/default/src/pages/container-test.astro +79 -0
- package/templates/default/src/pages/mermaid-direct.html +78 -0
- package/templates/default/src/pages/posts/[...slug].astro +335 -0
- package/templates/default/src/pages/posts/index.astro +541 -0
- package/templates/default/src/pages/posts/page/[page].astro +146 -0
- package/templates/default/src/pages/rss.xml.ts +28 -0
- package/templates/default/src/pages/search-index.json.ts +21 -0
- package/templates/default/src/pages/search.astro +50 -0
- package/templates/default/src/pages/slides/[...slug].astro +54 -0
- package/templates/default/src/pages/slides/index.astro +135 -0
- package/templates/default/src/pages/tags/[tag]/page/[page].astro +211 -0
- package/templates/default/src/pages/tags/[tag].astro +191 -0
- package/templates/default/src/pages/tags/index.astro +167 -0
- package/templates/default/tailwind.config.mjs +78 -0
- package/templates/default/tsconfig.json +9 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { getCollection } from 'astro:content';
|
|
3
|
+
import PageLayout from '@jet-w/astro-blog/layouts/PageLayout.astro';
|
|
4
|
+
import PostCard from '@jet-w/astro-blog/components/blog/PostCard.astro';
|
|
5
|
+
import Pagination from '@jet-w/astro-blog/components/ui/Pagination.astro';
|
|
6
|
+
|
|
7
|
+
export async function getStaticPaths() {
|
|
8
|
+
const allPosts = await getCollection('posts', ({ data }) => !data.draft);
|
|
9
|
+
|
|
10
|
+
// 收集所有年月组合
|
|
11
|
+
const archiveMap = new Map<string, { year: number; month: number; count: number }>();
|
|
12
|
+
|
|
13
|
+
allPosts.forEach(post => {
|
|
14
|
+
// pubDate 现在通过 schema transform 统一包含 date 或 pubDate 的值
|
|
15
|
+
if (post.data.pubDate) {
|
|
16
|
+
const date = new Date(post.data.pubDate);
|
|
17
|
+
const year = date.getFullYear();
|
|
18
|
+
const month = date.getMonth() + 1;
|
|
19
|
+
const key = `${year}-${month}`;
|
|
20
|
+
|
|
21
|
+
if (archiveMap.has(key)) {
|
|
22
|
+
archiveMap.get(key)!.count++;
|
|
23
|
+
} else {
|
|
24
|
+
archiveMap.set(key, { year, month, count: 1 });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// 生成路径
|
|
30
|
+
return Array.from(archiveMap.entries()).map(([key, { year, month, count }]) => ({
|
|
31
|
+
params: { year: String(year), month: String(month).padStart(2, '0') },
|
|
32
|
+
props: { year, month, count }
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const { year, month, count } = Astro.props;
|
|
37
|
+
|
|
38
|
+
// 获取该月的所有文章
|
|
39
|
+
const allPosts = await getCollection('posts', ({ data }) => !data.draft);
|
|
40
|
+
|
|
41
|
+
const filteredPosts = allPosts
|
|
42
|
+
.filter(post => {
|
|
43
|
+
// pubDate 现在通过 schema transform 统一包含 date 或 pubDate 的值
|
|
44
|
+
if (!post.data.pubDate) return false;
|
|
45
|
+
const date = new Date(post.data.pubDate);
|
|
46
|
+
return date.getFullYear() === year && date.getMonth() + 1 === month;
|
|
47
|
+
})
|
|
48
|
+
.sort((a, b) => {
|
|
49
|
+
const dateA = a.data.pubDate ? new Date(a.data.pubDate).getTime() : 0;
|
|
50
|
+
const dateB = b.data.pubDate ? new Date(b.data.pubDate).getTime() : 0;
|
|
51
|
+
return dateB - dateA;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// 分页逻辑
|
|
55
|
+
const currentPage = 1;
|
|
56
|
+
const postsPerPage = 10;
|
|
57
|
+
const totalPosts = filteredPosts.length;
|
|
58
|
+
const totalPages = Math.ceil(totalPosts / postsPerPage);
|
|
59
|
+
const startIndex = (currentPage - 1) * postsPerPage;
|
|
60
|
+
const endIndex = startIndex + postsPerPage;
|
|
61
|
+
const posts = filteredPosts.slice(startIndex, endIndex);
|
|
62
|
+
|
|
63
|
+
// 月份名称
|
|
64
|
+
const monthNames = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'];
|
|
65
|
+
const monthName = monthNames[month - 1];
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
<PageLayout
|
|
69
|
+
title={`归档: ${year}年${month}月`}
|
|
70
|
+
description={`${year}年${month}月的所有文章归档`}
|
|
71
|
+
showSidebar={true}
|
|
72
|
+
>
|
|
73
|
+
<!-- 面包屑导航 -->
|
|
74
|
+
<nav class="flex items-center space-x-2 text-sm text-slate-600 dark:text-slate-400 mb-8">
|
|
75
|
+
<a href="/" class="hover:text-primary-500 transition-colors">首页</a>
|
|
76
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
77
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
|
78
|
+
</svg>
|
|
79
|
+
<a href="/archives" class="hover:text-primary-500 transition-colors">归档</a>
|
|
80
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
81
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
|
82
|
+
</svg>
|
|
83
|
+
<span class="text-slate-900 dark:text-slate-100">{year}年{month}月</span>
|
|
84
|
+
</nav>
|
|
85
|
+
|
|
86
|
+
<!-- 页面头部 -->
|
|
87
|
+
<div class="text-center mb-12">
|
|
88
|
+
<div class="inline-flex items-center justify-center w-16 h-16 bg-primary-100 dark:bg-primary-900/30 rounded-full mb-4">
|
|
89
|
+
<svg class="w-8 h-8 text-primary-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
90
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
|
91
|
+
</svg>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<h1 class="text-4xl font-bold text-slate-900 dark:text-slate-100 mb-4">
|
|
95
|
+
{year}年{monthName}
|
|
96
|
+
</h1>
|
|
97
|
+
|
|
98
|
+
<p class="text-xl text-slate-600 dark:text-slate-400 mb-8">
|
|
99
|
+
共 {totalPosts} 篇文章
|
|
100
|
+
</p>
|
|
101
|
+
|
|
102
|
+
<!-- 归档统计 -->
|
|
103
|
+
<div class="inline-flex items-center space-x-6 text-sm text-slate-500 dark:text-slate-400 bg-slate-50 dark:bg-slate-800 px-6 py-3 rounded-lg">
|
|
104
|
+
<span>{year}年{month}月</span>
|
|
105
|
+
<span>•</span>
|
|
106
|
+
<span>{totalPosts} 篇文章</span>
|
|
107
|
+
{totalPages > 1 && (
|
|
108
|
+
<>
|
|
109
|
+
<span>•</span>
|
|
110
|
+
<span>共 {totalPages} 页</span>
|
|
111
|
+
</>
|
|
112
|
+
)}
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<!-- 文章列表 -->
|
|
117
|
+
{posts.length > 0 ? (
|
|
118
|
+
<div class="space-y-8 mb-12">
|
|
119
|
+
{posts.map((post) => (
|
|
120
|
+
<PostCard
|
|
121
|
+
post={{
|
|
122
|
+
slug: post.id.toLowerCase(),
|
|
123
|
+
title: post.data.title,
|
|
124
|
+
description: post.data.description,
|
|
125
|
+
pubDate: post.data.pubDate,
|
|
126
|
+
tags: post.data.tags,
|
|
127
|
+
categories: post.data.categories,
|
|
128
|
+
author: post.data.author,
|
|
129
|
+
image: post.data.image
|
|
130
|
+
}}
|
|
131
|
+
layout="horizontal"
|
|
132
|
+
/>
|
|
133
|
+
))}
|
|
134
|
+
</div>
|
|
135
|
+
) : (
|
|
136
|
+
<div class="text-center py-16">
|
|
137
|
+
<div class="text-6xl mb-4">📅</div>
|
|
138
|
+
<h3 class="text-xl font-semibold text-slate-900 dark:text-slate-100 mb-2">
|
|
139
|
+
暂无文章
|
|
140
|
+
</h3>
|
|
141
|
+
<p class="text-slate-600 dark:text-slate-400 mb-6">
|
|
142
|
+
{year}年{month}月暂无发布的文章
|
|
143
|
+
</p>
|
|
144
|
+
<a href="/archives" class="btn-secondary">
|
|
145
|
+
浏览所有归档
|
|
146
|
+
</a>
|
|
147
|
+
</div>
|
|
148
|
+
)}
|
|
149
|
+
|
|
150
|
+
<!-- 分页导航 -->
|
|
151
|
+
{totalPages > 1 && (
|
|
152
|
+
<Pagination
|
|
153
|
+
currentPage={currentPage}
|
|
154
|
+
totalPages={totalPages}
|
|
155
|
+
baseUrl={`/archives/${year}/${String(month).padStart(2, '0')}`}
|
|
156
|
+
/>
|
|
157
|
+
)}
|
|
158
|
+
</PageLayout>
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { getCollection } from 'astro:content';
|
|
3
|
+
import PageLayout from '@jet-w/astro-blog/layouts/PageLayout.astro';
|
|
4
|
+
|
|
5
|
+
// 获取所有非草稿文章
|
|
6
|
+
const allPosts = await getCollection('posts', ({ data }) => !data.draft);
|
|
7
|
+
|
|
8
|
+
// 按年月分组文章
|
|
9
|
+
interface ArchiveMonth {
|
|
10
|
+
month: number;
|
|
11
|
+
monthName: string;
|
|
12
|
+
count: number;
|
|
13
|
+
posts: Array<{
|
|
14
|
+
id: string;
|
|
15
|
+
title: string;
|
|
16
|
+
pubDate: Date;
|
|
17
|
+
}>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface ArchiveYear {
|
|
21
|
+
year: number;
|
|
22
|
+
months: ArchiveMonth[];
|
|
23
|
+
totalCount: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const archiveData = new Map<number, Map<number, ArchiveMonth>>();
|
|
27
|
+
|
|
28
|
+
// 月份名称
|
|
29
|
+
const monthNames = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'];
|
|
30
|
+
|
|
31
|
+
allPosts.forEach(post => {
|
|
32
|
+
// pubDate 现在通过 schema transform 统一包含 date 或 pubDate 的值
|
|
33
|
+
if (post.data.pubDate) {
|
|
34
|
+
const date = new Date(post.data.pubDate);
|
|
35
|
+
const year = date.getFullYear();
|
|
36
|
+
const month = date.getMonth() + 1;
|
|
37
|
+
|
|
38
|
+
if (!archiveData.has(year)) {
|
|
39
|
+
archiveData.set(year, new Map());
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const yearData = archiveData.get(year)!;
|
|
43
|
+
|
|
44
|
+
if (!yearData.has(month)) {
|
|
45
|
+
yearData.set(month, {
|
|
46
|
+
month,
|
|
47
|
+
monthName: monthNames[month - 1],
|
|
48
|
+
count: 0,
|
|
49
|
+
posts: []
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const monthData = yearData.get(month)!;
|
|
54
|
+
monthData.count++;
|
|
55
|
+
monthData.posts.push({
|
|
56
|
+
id: post.id,
|
|
57
|
+
title: post.data.title,
|
|
58
|
+
pubDate: date
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// 转换为数组并排序
|
|
64
|
+
const archives: ArchiveYear[] = Array.from(archiveData.entries())
|
|
65
|
+
.sort((a, b) => b[0] - a[0])
|
|
66
|
+
.map(([year, monthsMap]) => {
|
|
67
|
+
const months = Array.from(monthsMap.values())
|
|
68
|
+
.sort((a, b) => b.month - a.month)
|
|
69
|
+
.map(m => ({
|
|
70
|
+
...m,
|
|
71
|
+
posts: m.posts.sort((a, b) => b.pubDate.getTime() - a.pubDate.getTime())
|
|
72
|
+
}));
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
year,
|
|
76
|
+
months,
|
|
77
|
+
totalCount: months.reduce((sum, m) => sum + m.count, 0)
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const totalPosts = archives.reduce((sum, y) => sum + y.totalCount, 0);
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
<PageLayout
|
|
85
|
+
title="文章归档"
|
|
86
|
+
description="按时间归档的所有文章"
|
|
87
|
+
showSidebar={true}
|
|
88
|
+
>
|
|
89
|
+
<!-- 面包屑导航 -->
|
|
90
|
+
<nav class="flex items-center space-x-2 text-sm text-slate-600 dark:text-slate-400 mb-8">
|
|
91
|
+
<a href="/" class="hover:text-primary-500 transition-colors">首页</a>
|
|
92
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
93
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
|
94
|
+
</svg>
|
|
95
|
+
<span class="text-slate-900 dark:text-slate-100">归档</span>
|
|
96
|
+
</nav>
|
|
97
|
+
|
|
98
|
+
<!-- 页面头部 -->
|
|
99
|
+
<div class="text-center mb-12">
|
|
100
|
+
<div class="inline-flex items-center justify-center w-16 h-16 bg-primary-100 dark:bg-primary-900/30 rounded-full mb-4">
|
|
101
|
+
<svg class="w-8 h-8 text-primary-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
102
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
|
103
|
+
</svg>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<h1 class="text-4xl font-bold text-slate-900 dark:text-slate-100 mb-4">
|
|
107
|
+
文章归档
|
|
108
|
+
</h1>
|
|
109
|
+
|
|
110
|
+
<p class="text-xl text-slate-600 dark:text-slate-400">
|
|
111
|
+
共 {totalPosts} 篇文章,跨越 {archives.length} 年
|
|
112
|
+
</p>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<!-- 归档时间线 -->
|
|
116
|
+
<div class="relative">
|
|
117
|
+
<!-- 时间线 -->
|
|
118
|
+
<div class="absolute left-4 md:left-8 top-0 bottom-0 w-0.5 bg-slate-200 dark:bg-slate-700"></div>
|
|
119
|
+
|
|
120
|
+
{archives.map((yearData) => (
|
|
121
|
+
<div class="mb-12">
|
|
122
|
+
<!-- 年份标题 -->
|
|
123
|
+
<div class="relative flex items-center mb-6">
|
|
124
|
+
<div class="absolute left-4 md:left-8 w-4 h-4 -ml-2 bg-primary-500 rounded-full border-4 border-white dark:border-slate-900 z-10"></div>
|
|
125
|
+
<h2 class="ml-12 md:ml-16 text-2xl font-bold text-slate-900 dark:text-slate-100 flex items-center gap-3">
|
|
126
|
+
{yearData.year}年
|
|
127
|
+
<span class="text-sm font-normal text-slate-500 dark:text-slate-400 bg-slate-100 dark:bg-slate-800 px-3 py-1 rounded-full">
|
|
128
|
+
{yearData.totalCount} 篇
|
|
129
|
+
</span>
|
|
130
|
+
</h2>
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
<!-- 月份列表 -->
|
|
134
|
+
<div class="ml-12 md:ml-16 space-y-6">
|
|
135
|
+
{yearData.months.map((monthData) => (
|
|
136
|
+
<div class="relative">
|
|
137
|
+
<!-- 月份节点 -->
|
|
138
|
+
<div class="absolute -left-8 md:-left-8 w-2 h-2 mt-2 bg-slate-400 dark:bg-slate-500 rounded-full"></div>
|
|
139
|
+
|
|
140
|
+
<div class="bg-white dark:bg-slate-800 rounded-lg border border-slate-200 dark:border-slate-700 overflow-hidden">
|
|
141
|
+
<!-- 月份标题 -->
|
|
142
|
+
<a
|
|
143
|
+
href={`/archives/${yearData.year}/${String(monthData.month).padStart(2, '0')}`}
|
|
144
|
+
class="flex items-center justify-between p-4 bg-slate-50 dark:bg-slate-800/50 hover:bg-slate-100 dark:hover:bg-slate-700/50 transition-colors"
|
|
145
|
+
>
|
|
146
|
+
<span class="font-semibold text-slate-900 dark:text-slate-100">
|
|
147
|
+
{monthData.monthName}
|
|
148
|
+
</span>
|
|
149
|
+
<span class="text-sm text-slate-500 dark:text-slate-400 bg-white dark:bg-slate-700 px-2 py-1 rounded">
|
|
150
|
+
{monthData.count} 篇
|
|
151
|
+
</span>
|
|
152
|
+
</a>
|
|
153
|
+
|
|
154
|
+
<!-- 文章列表预览 -->
|
|
155
|
+
<div class="divide-y divide-slate-100 dark:divide-slate-700">
|
|
156
|
+
{monthData.posts.slice(0, 5).map((post) => (
|
|
157
|
+
<a
|
|
158
|
+
href={`/posts/${post.id.toLowerCase()}`}
|
|
159
|
+
class="flex items-center justify-between p-4 hover:bg-slate-50 dark:hover:bg-slate-700/30 transition-colors"
|
|
160
|
+
>
|
|
161
|
+
<span class="text-slate-700 dark:text-slate-300 hover:text-primary-500 transition-colors line-clamp-1">
|
|
162
|
+
{post.title}
|
|
163
|
+
</span>
|
|
164
|
+
<time class="text-xs text-slate-500 dark:text-slate-400 ml-4 flex-shrink-0">
|
|
165
|
+
{post.pubDate.getDate()}日
|
|
166
|
+
</time>
|
|
167
|
+
</a>
|
|
168
|
+
))}
|
|
169
|
+
{monthData.posts.length > 5 && (
|
|
170
|
+
<a
|
|
171
|
+
href={`/archives/${yearData.year}/${String(monthData.month).padStart(2, '0')}`}
|
|
172
|
+
class="block p-4 text-center text-sm text-primary-500 hover:bg-slate-50 dark:hover:bg-slate-700/30 transition-colors"
|
|
173
|
+
>
|
|
174
|
+
查看全部 {monthData.count} 篇文章 →
|
|
175
|
+
</a>
|
|
176
|
+
)}
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
))}
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
))}
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
<!-- 空状态 -->
|
|
187
|
+
{archives.length === 0 && (
|
|
188
|
+
<div class="text-center py-16">
|
|
189
|
+
<div class="text-6xl mb-4">📅</div>
|
|
190
|
+
<h3 class="text-xl font-semibold text-slate-900 dark:text-slate-100 mb-2">
|
|
191
|
+
暂无文章
|
|
192
|
+
</h3>
|
|
193
|
+
<p class="text-slate-600 dark:text-slate-400 mb-6">
|
|
194
|
+
还没有发布任何文章
|
|
195
|
+
</p>
|
|
196
|
+
<a href="/" class="btn-primary">
|
|
197
|
+
返回首页
|
|
198
|
+
</a>
|
|
199
|
+
</div>
|
|
200
|
+
)}
|
|
201
|
+
</PageLayout>
|
|
202
|
+
|
|
203
|
+
<style>
|
|
204
|
+
.line-clamp-1 {
|
|
205
|
+
display: -webkit-box;
|
|
206
|
+
-webkit-line-clamp: 1;
|
|
207
|
+
-webkit-box-orient: vertical;
|
|
208
|
+
overflow: hidden;
|
|
209
|
+
}
|
|
210
|
+
</style>
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { getCollection } from 'astro:content';
|
|
3
|
+
import PageLayout from '@jet-w/astro-blog/layouts/PageLayout.astro';
|
|
4
|
+
import PostCard from '@jet-w/astro-blog/components/blog/PostCard.astro';
|
|
5
|
+
import Pagination from '@jet-w/astro-blog/components/ui/Pagination.astro';
|
|
6
|
+
|
|
7
|
+
export async function getStaticPaths() {
|
|
8
|
+
const postsPerPage = 10;
|
|
9
|
+
const allPosts = await getCollection('posts', ({ data }) => !data.draft);
|
|
10
|
+
|
|
11
|
+
// 从所有文章中收集分类
|
|
12
|
+
const categoryMap = new Map<string, { name: string; posts: typeof allPosts }>();
|
|
13
|
+
|
|
14
|
+
allPosts.forEach(post => {
|
|
15
|
+
(post.data.categories || []).forEach(category => {
|
|
16
|
+
const slug = category.toLowerCase().replace(/\s+/g, '-');
|
|
17
|
+
if (categoryMap.has(slug)) {
|
|
18
|
+
categoryMap.get(slug)!.posts.push(post);
|
|
19
|
+
} else {
|
|
20
|
+
categoryMap.set(slug, { name: category, posts: [post] });
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// 为每个分类的每一页生成路径
|
|
26
|
+
const paths: Array<{
|
|
27
|
+
params: { category: string; page: string };
|
|
28
|
+
props: { categorySlug: string; categoryName: string; page: number; totalPages: number };
|
|
29
|
+
}> = [];
|
|
30
|
+
|
|
31
|
+
categoryMap.forEach(({ name, posts }, slug) => {
|
|
32
|
+
// 按日期排序
|
|
33
|
+
const sortedPosts = posts.sort((a, b) => {
|
|
34
|
+
const dateA = a.data.pubDate ? new Date(a.data.pubDate).getTime() : 0;
|
|
35
|
+
const dateB = b.data.pubDate ? new Date(b.data.pubDate).getTime() : 0;
|
|
36
|
+
return dateB - dateA;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const totalPages = Math.ceil(sortedPosts.length / postsPerPage);
|
|
40
|
+
|
|
41
|
+
// 为每一页生成路径(从第2页开始,第1页由 [category].astro 处理)
|
|
42
|
+
for (let page = 2; page <= totalPages; page++) {
|
|
43
|
+
paths.push({
|
|
44
|
+
params: { category: slug, page: page.toString() },
|
|
45
|
+
props: { categorySlug: slug, categoryName: name, page, totalPages }
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return paths;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const { categorySlug, categoryName, page: currentPage, totalPages } = Astro.props;
|
|
54
|
+
const postsPerPage = 10;
|
|
55
|
+
|
|
56
|
+
// 获取所有文章并筛选包含该分类的文章
|
|
57
|
+
const allPosts = await getCollection('posts', ({ data }) => !data.draft);
|
|
58
|
+
|
|
59
|
+
// 筛选包含该分类的文章
|
|
60
|
+
const filteredPosts = allPosts
|
|
61
|
+
.filter(post =>
|
|
62
|
+
(post.data.categories || []).some(c => c.toLowerCase().replace(/\s+/g, '-') === categorySlug)
|
|
63
|
+
)
|
|
64
|
+
.sort((a, b) => {
|
|
65
|
+
const dateA = a.data.pubDate ? new Date(a.data.pubDate).getTime() : 0;
|
|
66
|
+
const dateB = b.data.pubDate ? new Date(b.data.pubDate).getTime() : 0;
|
|
67
|
+
return dateB - dateA;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// 分页逻辑
|
|
71
|
+
const totalPosts = filteredPosts.length;
|
|
72
|
+
const startIndex = (currentPage - 1) * postsPerPage;
|
|
73
|
+
const endIndex = startIndex + postsPerPage;
|
|
74
|
+
const posts = filteredPosts.slice(startIndex, endIndex);
|
|
75
|
+
|
|
76
|
+
// 获取相关分类(同一篇文章中出现的其他分类)
|
|
77
|
+
const relatedCategoryMap = new Map<string, { name: string; count: number }>();
|
|
78
|
+
filteredPosts.forEach(post => {
|
|
79
|
+
(post.data.categories || []).forEach(cat => {
|
|
80
|
+
const slug = cat.toLowerCase().replace(/\s+/g, '-');
|
|
81
|
+
if (slug !== categorySlug) {
|
|
82
|
+
if (relatedCategoryMap.has(slug)) {
|
|
83
|
+
relatedCategoryMap.get(slug)!.count++;
|
|
84
|
+
} else {
|
|
85
|
+
relatedCategoryMap.set(slug, { name: cat, count: 1 });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const relatedCategories = Array.from(relatedCategoryMap.entries())
|
|
92
|
+
.sort((a, b) => b[1].count - a[1].count)
|
|
93
|
+
.slice(0, 3)
|
|
94
|
+
.map(([slug, { name, count }]) => ({ slug, name, count }));
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
<PageLayout
|
|
98
|
+
title={`分类: ${categoryName} - 第 ${currentPage} 页`}
|
|
99
|
+
description={`浏览所有 ${categoryName} 相关的文章 - 第 ${currentPage} 页`}
|
|
100
|
+
showSidebar={true}
|
|
101
|
+
>
|
|
102
|
+
<!-- 面包屑导航 -->
|
|
103
|
+
<nav class="flex items-center space-x-2 text-sm text-slate-600 dark:text-slate-400 mb-8">
|
|
104
|
+
<a href="/" class="hover:text-primary-500 transition-colors">首页</a>
|
|
105
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
106
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
|
107
|
+
</svg>
|
|
108
|
+
<a href="/categories" class="hover:text-primary-500 transition-colors">分类</a>
|
|
109
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
110
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
|
111
|
+
</svg>
|
|
112
|
+
<a href={`/categories/${categorySlug}`} class="hover:text-primary-500 transition-colors">{categoryName}</a>
|
|
113
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
114
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
|
115
|
+
</svg>
|
|
116
|
+
<span class="text-slate-900 dark:text-slate-100">第 {currentPage} 页</span>
|
|
117
|
+
</nav>
|
|
118
|
+
|
|
119
|
+
<!-- 页面头部 -->
|
|
120
|
+
<div class="text-center mb-12">
|
|
121
|
+
<div class="inline-flex items-center justify-center w-20 h-20 bg-amber-100 dark:bg-amber-900/30 rounded-full mb-6">
|
|
122
|
+
<svg class="w-10 h-10 text-amber-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
123
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
|
|
124
|
+
</svg>
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<h1 class="text-4xl font-bold text-slate-900 dark:text-slate-100 mb-4">
|
|
128
|
+
{categoryName}
|
|
129
|
+
</h1>
|
|
130
|
+
|
|
131
|
+
<p class="text-xl text-slate-600 dark:text-slate-400 mb-8 max-w-2xl mx-auto">
|
|
132
|
+
浏览 {categoryName} 分类下的所有文章
|
|
133
|
+
</p>
|
|
134
|
+
|
|
135
|
+
<!-- 分类统计 -->
|
|
136
|
+
<div class="inline-flex items-center space-x-6 text-sm text-slate-500 dark:text-slate-400 bg-slate-50 dark:bg-slate-800 px-6 py-3 rounded-lg">
|
|
137
|
+
<span>{categoryName}</span>
|
|
138
|
+
<span>•</span>
|
|
139
|
+
<span>{totalPosts} 篇文章</span>
|
|
140
|
+
<span>•</span>
|
|
141
|
+
<span>第 {currentPage} / {totalPages} 页</span>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<!-- 文章列表 -->
|
|
146
|
+
{posts.length > 0 ? (
|
|
147
|
+
<div class="space-y-8 mb-12">
|
|
148
|
+
{posts.map((post) => (
|
|
149
|
+
<PostCard
|
|
150
|
+
post={{
|
|
151
|
+
slug: post.id.toLowerCase(),
|
|
152
|
+
title: post.data.title,
|
|
153
|
+
description: post.data.description,
|
|
154
|
+
pubDate: post.data.pubDate,
|
|
155
|
+
tags: post.data.tags,
|
|
156
|
+
categories: post.data.categories,
|
|
157
|
+
author: post.data.author,
|
|
158
|
+
image: post.data.image
|
|
159
|
+
}}
|
|
160
|
+
layout="horizontal"
|
|
161
|
+
/>
|
|
162
|
+
))}
|
|
163
|
+
</div>
|
|
164
|
+
) : (
|
|
165
|
+
<div class="text-center py-16">
|
|
166
|
+
<div class="text-6xl mb-4">📂</div>
|
|
167
|
+
<h3 class="text-xl font-semibold text-slate-900 dark:text-slate-100 mb-2">
|
|
168
|
+
暂无相关文章
|
|
169
|
+
</h3>
|
|
170
|
+
<p class="text-slate-600 dark:text-slate-400 mb-6">
|
|
171
|
+
目前还没有 {categoryName} 分类的文章,请查看其他分类。
|
|
172
|
+
</p>
|
|
173
|
+
<a href="/categories" class="btn-secondary">
|
|
174
|
+
浏览所有分类
|
|
175
|
+
</a>
|
|
176
|
+
</div>
|
|
177
|
+
)}
|
|
178
|
+
|
|
179
|
+
<!-- 分页导航 -->
|
|
180
|
+
{totalPages > 1 && (
|
|
181
|
+
<Pagination
|
|
182
|
+
currentPage={currentPage}
|
|
183
|
+
totalPages={totalPages}
|
|
184
|
+
baseUrl={`/categories/${categorySlug}`}
|
|
185
|
+
/>
|
|
186
|
+
)}
|
|
187
|
+
|
|
188
|
+
<!-- 相关分类 -->
|
|
189
|
+
{relatedCategories.length > 0 && (
|
|
190
|
+
<section class="mt-16 pt-8 border-t border-slate-200 dark:border-slate-700">
|
|
191
|
+
<h2 class="text-2xl font-bold text-slate-900 dark:text-slate-100 mb-6">
|
|
192
|
+
相关分类
|
|
193
|
+
</h2>
|
|
194
|
+
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
195
|
+
{relatedCategories.map((relatedCategory) => (
|
|
196
|
+
<a
|
|
197
|
+
href={`/categories/${relatedCategory.slug}`}
|
|
198
|
+
class="group card hover:shadow-lg transform hover:-translate-y-1 transition-all duration-300"
|
|
199
|
+
>
|
|
200
|
+
<div class="flex items-center justify-between">
|
|
201
|
+
<div>
|
|
202
|
+
<h3 class="font-semibold text-slate-900 dark:text-slate-100 group-hover:text-amber-500 transition-colors">
|
|
203
|
+
{relatedCategory.name}
|
|
204
|
+
</h3>
|
|
205
|
+
<p class="text-sm text-slate-600 dark:text-slate-400">
|
|
206
|
+
{relatedCategory.count} 篇文章
|
|
207
|
+
</p>
|
|
208
|
+
</div>
|
|
209
|
+
<svg class="w-5 h-5 text-slate-400 group-hover:text-amber-500 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
210
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
|
211
|
+
</svg>
|
|
212
|
+
</div>
|
|
213
|
+
</a>
|
|
214
|
+
))}
|
|
215
|
+
</div>
|
|
216
|
+
</section>
|
|
217
|
+
)}
|
|
218
|
+
</PageLayout>
|