@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.
Files changed (140) hide show
  1. package/dist/chunk-FXPGR372.js +0 -0
  2. package/dist/chunk-GYLSY3OJ.js +173 -0
  3. package/dist/config/index.d.ts +166 -0
  4. package/dist/config/index.js +38 -0
  5. package/dist/index.d.ts +34 -0
  6. package/dist/index.js +59 -0
  7. package/dist/types/index.d.ts +75 -0
  8. package/dist/types/index.js +1 -0
  9. package/package.json +84 -0
  10. package/src/components/EChartsCard.vue +118 -0
  11. package/src/components/Mermaid.vue +73 -0
  12. package/src/components/about/ContentCard.astro +27 -0
  13. package/src/components/about/IconCard.astro +77 -0
  14. package/src/components/about/SocialLinks.astro +54 -0
  15. package/src/components/about/TagCard.astro +65 -0
  16. package/src/components/about/TagGroup.astro +33 -0
  17. package/src/components/about/TimelineCard.astro +52 -0
  18. package/src/components/blog/FloatingToc.vue +198 -0
  19. package/src/components/blog/Hero.astro +147 -0
  20. package/src/components/blog/NavigationTabs.vue +245 -0
  21. package/src/components/blog/PostCard.astro +161 -0
  22. package/src/components/blog/PostNavigation.astro +106 -0
  23. package/src/components/blog/RelatedPosts.astro +175 -0
  24. package/src/components/blog/TableOfContents.astro +153 -0
  25. package/src/components/blog/TagCloud.astro +91 -0
  26. package/src/components/home/FeaturedPostsSection.astro +54 -0
  27. package/src/components/home/QuickNavSection.astro +81 -0
  28. package/src/components/home/RecentPostsSection.astro +52 -0
  29. package/src/components/home/StatsSection.astro +44 -0
  30. package/src/components/layout/Footer.astro +103 -0
  31. package/src/components/layout/Header.astro +68 -0
  32. package/src/components/layout/Sidebar.astro +594 -0
  33. package/src/components/media/Bilibili.astro +114 -0
  34. package/src/components/media/Slides.astro +313 -0
  35. package/src/components/media/Video.astro +111 -0
  36. package/src/components/media/VideoPlayer.astro +89 -0
  37. package/src/components/media/YouTube.astro +92 -0
  38. package/src/components/pte/StudyCalendar.vue +1348 -0
  39. package/src/components/ui/Icon.astro +187 -0
  40. package/src/components/ui/MobileMenu.vue +201 -0
  41. package/src/components/ui/Pagination.astro +143 -0
  42. package/src/components/ui/SearchBox.vue +179 -0
  43. package/src/components/ui/SearchInterface.vue +409 -0
  44. package/src/components/ui/SidebarToggle.vue +57 -0
  45. package/src/components/ui/ThemeToggle.vue +90 -0
  46. package/src/layouts/AboutLayout.astro +18 -0
  47. package/src/layouts/BaseLayout.astro +362 -0
  48. package/src/layouts/PageLayout.astro +217 -0
  49. package/src/layouts/SlidesLayout.astro +320 -0
  50. package/src/plugins/rehype-clean-containers.mjs +24 -0
  51. package/src/plugins/rehype-relative-links.mjs +43 -0
  52. package/src/plugins/rehype-tabs.mjs +116 -0
  53. package/src/plugins/remark-containers.mjs +407 -0
  54. package/src/plugins/remark-mermaid.mjs +46 -0
  55. package/src/styles/global.css +870 -0
  56. package/src/styles/slides.css +220 -0
  57. package/src/utils/sidebar.ts +492 -0
  58. package/templates/default/astro.config.mjs +51 -0
  59. package/templates/default/content/pages/about.mdx +93 -0
  60. package/templates/default/content/pages/index.mdx +20 -0
  61. package/templates/default/content/posts/blog_docs/01-quick-start.md +162 -0
  62. package/templates/default/content/posts/blog_docs/02-frontmatter.md +277 -0
  63. package/templates/default/content/posts/blog_docs/03-markdown-basic.md +350 -0
  64. package/templates/default/content/posts/blog_docs/04-containers.md +331 -0
  65. package/templates/default/content/posts/blog_docs/05-code-blocks.md +388 -0
  66. package/templates/default/content/posts/blog_docs/06-mermaid.md +431 -0
  67. package/templates/default/content/posts/blog_docs/07-video.md +243 -0
  68. package/templates/default/content/posts/blog_docs/08-latex.md +382 -0
  69. package/templates/default/content/posts/blog_docs/09-icons.md +326 -0
  70. package/templates/default/content/posts/blog_docs/10-sidebar.md +445 -0
  71. package/templates/default/content/posts/blog_docs/11-config.md +334 -0
  72. package/templates/default/content/posts/blog_docs/12-slides.mdx +552 -0
  73. package/templates/default/content/posts/blog_docs/README.md +151 -0
  74. package/templates/default/content/slides/demo.md +146 -0
  75. package/templates/default/content/slides/docs/basic-demo.md +35 -0
  76. package/templates/default/content/slides/docs/code-demo.md +62 -0
  77. package/templates/default/content/slides/docs/echarts-demo.md +139 -0
  78. package/templates/default/content/slides/docs/fragment-demo.md +35 -0
  79. package/templates/default/content/slides/docs/math-demo.md +48 -0
  80. package/templates/default/content/slides/docs/mermaid-demo.md +105 -0
  81. package/templates/default/content/slides/docs/theme-demo.md +38 -0
  82. package/templates/default/content/slides/docs/vertical-demo.md +50 -0
  83. package/templates/default/package.json +31 -0
  84. package/templates/default/public/favicon-bak.svg +4 -0
  85. package/templates/default/public/images/avatar.jpg +0 -0
  86. package/templates/default/public/images/avatar.svg +142 -0
  87. package/templates/default/public/js/mermaid-container.js +402 -0
  88. package/templates/default/public/js/mermaid-init.js +131 -0
  89. package/templates/default/public/js/mermaid-render.js +98 -0
  90. package/templates/default/public/js/mermaid-simple.js +95 -0
  91. package/templates/default/public/js/tabs-init.js +86 -0
  92. package/templates/default/public/media/individual_portfolio/INDIVIDUAL PORTFOLIO.png +0 -0
  93. package/templates/default/public/slides/plugin/highlight/highlight.js +5 -0
  94. package/templates/default/public/slides/plugin/highlight/monokai.css +71 -0
  95. package/templates/default/public/slides/plugin/markdown/markdown.js +7 -0
  96. package/templates/default/public/slides/plugin/math/math.js +1 -0
  97. package/templates/default/public/slides/plugin/notes/notes.js +1 -0
  98. package/templates/default/public/slides/reveal.css +9 -0
  99. package/templates/default/public/slides/reveal.js +9 -0
  100. package/templates/default/public/slides/theme/beige.css +366 -0
  101. package/templates/default/public/slides/theme/black-contrast.css +362 -0
  102. package/templates/default/public/slides/theme/black.css +359 -0
  103. package/templates/default/public/slides/theme/blood.css +392 -0
  104. package/templates/default/public/slides/theme/dracula.css +385 -0
  105. package/templates/default/public/slides/theme/league.css +368 -0
  106. package/templates/default/public/slides/theme/moon.css +362 -0
  107. package/templates/default/public/slides/theme/night.css +360 -0
  108. package/templates/default/public/slides/theme/serif.css +363 -0
  109. package/templates/default/public/slides/theme/simple.css +362 -0
  110. package/templates/default/public/slides/theme/sky.css +370 -0
  111. package/templates/default/public/slides/theme/solarized.css +363 -0
  112. package/templates/default/public/slides/theme/white-contrast.css +362 -0
  113. package/templates/default/public/slides/theme/white.css +359 -0
  114. package/templates/default/public/slides/theme/white_contrast_compact_verbatim_headers.css +360 -0
  115. package/templates/default/public/test-complete.html +43 -0
  116. package/templates/default/public/test-mermaid.html +124 -0
  117. package/templates/default/src/config/index.ts +114 -0
  118. package/templates/default/src/content.config.ts +96 -0
  119. package/templates/default/src/pages/[...slug].astro +27 -0
  120. package/templates/default/src/pages/archives/[year]/[month]/page/[page].astro +176 -0
  121. package/templates/default/src/pages/archives/[year]/[month].astro +158 -0
  122. package/templates/default/src/pages/archives/index.astro +210 -0
  123. package/templates/default/src/pages/categories/[category]/page/[page].astro +218 -0
  124. package/templates/default/src/pages/categories/[category].astro +198 -0
  125. package/templates/default/src/pages/categories/index.astro +190 -0
  126. package/templates/default/src/pages/container-test.astro +79 -0
  127. package/templates/default/src/pages/mermaid-direct.html +78 -0
  128. package/templates/default/src/pages/posts/[...slug].astro +335 -0
  129. package/templates/default/src/pages/posts/index.astro +541 -0
  130. package/templates/default/src/pages/posts/page/[page].astro +146 -0
  131. package/templates/default/src/pages/rss.xml.ts +28 -0
  132. package/templates/default/src/pages/search-index.json.ts +21 -0
  133. package/templates/default/src/pages/search.astro +50 -0
  134. package/templates/default/src/pages/slides/[...slug].astro +54 -0
  135. package/templates/default/src/pages/slides/index.astro +135 -0
  136. package/templates/default/src/pages/tags/[tag]/page/[page].astro +211 -0
  137. package/templates/default/src/pages/tags/[tag].astro +191 -0
  138. package/templates/default/src/pages/tags/index.astro +167 -0
  139. package/templates/default/tailwind.config.mjs +78 -0
  140. package/templates/default/tsconfig.json +9 -0
@@ -0,0 +1,43 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Complete Feature Test</title>
7
+ <script>
8
+ function checkFeatures() {
9
+ const results = [];
10
+
11
+ // 检查内容是否显示
12
+ const content = document.querySelector('.prose');
13
+ results.push(`内容显示: ${content ? '✅' : '❌'}`);
14
+
15
+ // 检查容器是否渲染
16
+ const containers = document.querySelectorAll('.custom-container');
17
+ results.push(`容器语法: ${containers.length > 0 ? '✅' : '❌'} (${containers.length}个)`);
18
+
19
+ // 检查mermaid代码块
20
+ const mermaidCode = document.querySelectorAll('pre code[data-language="mermaid"]');
21
+ results.push(`Mermaid代码块: ${mermaidCode.length > 0 ? '✅' : '❌'} (${mermaidCode.length}个)`);
22
+
23
+ // 检查mermaid渲染容器(JavaScript渲染后出现)
24
+ setTimeout(() => {
25
+ const mermaidContainers = document.querySelectorAll('.mermaid-container');
26
+ results.push(`Mermaid渲染: ${mermaidContainers.length > 0 ? '✅' : '❌'} (${mermaidContainers.length}个)`);
27
+
28
+ document.getElementById('results').innerHTML = results.join('<br>');
29
+ }, 3000); // 给JavaScript时间渲染
30
+
31
+ document.getElementById('results').innerHTML = results.join('<br>') + '<br>等待Mermaid渲染...';
32
+ }
33
+
34
+ window.onload = checkFeatures;
35
+ </script>
36
+ </head>
37
+ <body>
38
+ <h1>功能测试页面</h1>
39
+ <div id="results">检查中...</div>
40
+ <br>
41
+ <iframe src="/posts/astro-001" width="100%" height="600px" style="border: 1px solid #ccc;"></iframe>
42
+ </body>
43
+ </html>
@@ -0,0 +1,124 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Mermaid Test</title>
7
+ <style>
8
+ body { padding: 20px; font-family: Arial, sans-serif; }
9
+ .test-section { margin: 20px 0; padding: 20px; border: 1px solid #ccc; }
10
+ pre { background: #f5f5f5; padding: 10px; }
11
+ .mermaid-container {
12
+ margin: 1.5rem 0;
13
+ padding: 1rem;
14
+ border: 1px solid #e2e8f0;
15
+ border-radius: 0.5rem;
16
+ background: white;
17
+ overflow-x: auto;
18
+ text-align: center;
19
+ }
20
+ </style>
21
+ </head>
22
+ <body>
23
+ <h1>Mermaid 渲染测试</h1>
24
+
25
+ <div class="test-section">
26
+ <h2>测试1: 直接 mermaid 代码块</h2>
27
+ <pre><code data-language="mermaid">graph TD
28
+ A[Start] --> B[Process]
29
+ B --> C[End]</code></pre>
30
+ </div>
31
+
32
+ <div class="test-section">
33
+ <h2>测试2: 带class的代码块</h2>
34
+ <pre><code class="language-mermaid">graph LR
35
+ X[Input] --> Y[Output]</code></pre>
36
+ </div>
37
+
38
+ <div class="test-section">
39
+ <h2>日志输出</h2>
40
+ <div id="log"></div>
41
+ </div>
42
+
43
+ <script>
44
+ // 复制我们的mermaid渲染逻辑
45
+ function log(message) {
46
+ const logDiv = document.getElementById('log');
47
+ logDiv.innerHTML += '<div>' + new Date().toLocaleTimeString() + ': ' + message + '</div>';
48
+ console.log(message);
49
+ }
50
+
51
+ function loadMermaidAndRender() {
52
+ // 检查是否有mermaid代码块
53
+ const mermaidBlocks = document.querySelectorAll('pre code[data-language="mermaid"], pre code.language-mermaid');
54
+
55
+ log('检查mermaid代码块: 找到 ' + mermaidBlocks.length + ' 个');
56
+
57
+ if (mermaidBlocks.length === 0) {
58
+ log('没有找到mermaid代码块');
59
+ return;
60
+ }
61
+
62
+ // 动态加载mermaid脚本
63
+ const script = document.createElement('script');
64
+ script.src = 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js';
65
+ script.onload = function() {
66
+ log('Mermaid库加载完成');
67
+
68
+ // 初始化mermaid
69
+ window.mermaid.initialize({
70
+ startOnLoad: false,
71
+ theme: 'default',
72
+ securityLevel: 'loose'
73
+ });
74
+
75
+ log('Mermaid初始化完成');
76
+
77
+ // 处理每个mermaid代码块
78
+ mermaidBlocks.forEach(async function(block, index) {
79
+ try {
80
+ const code = block.textContent.trim();
81
+ const id = 'mermaid-diagram-' + index;
82
+
83
+ log('处理第' + (index+1) + '个图表: ' + code.substring(0, 30) + '...');
84
+
85
+ // 使用mermaid渲染
86
+ const result = await window.mermaid.render(id, code);
87
+
88
+ log('第' + (index+1) + '个图表SVG生成成功');
89
+
90
+ // 创建容器
91
+ const container = document.createElement('div');
92
+ container.className = 'mermaid-container';
93
+ container.innerHTML = result.svg;
94
+
95
+ // 替换原始代码块
96
+ const preElement = block.closest('pre');
97
+ if (preElement && preElement.parentNode) {
98
+ preElement.parentNode.insertBefore(container, preElement);
99
+ preElement.remove();
100
+ log('第' + (index+1) + '个图表渲染成功并替换完成');
101
+ }
102
+ } catch (error) {
103
+ log('渲染第' + (index+1) + '个图表失败: ' + error.message);
104
+ console.error(error);
105
+ }
106
+ });
107
+ };
108
+
109
+ script.onerror = function() {
110
+ log('Mermaid库加载失败');
111
+ };
112
+
113
+ document.head.appendChild(script);
114
+ }
115
+
116
+ // 启动
117
+ if (document.readyState === 'loading') {
118
+ document.addEventListener('DOMContentLoaded', loadMermaidAndRender);
119
+ } else {
120
+ loadMermaidAndRender();
121
+ }
122
+ </script>
123
+ </body>
124
+ </html>
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Blog configuration
3
+ *
4
+ * Customize your blog settings here
5
+ */
6
+
7
+ import type { SiteConfig, NavigationItem } from '@jet-w/astro-blog';
8
+ import type { SidebarConfig, FooterConfig, SocialLink } from '@jet-w/astro-blog';
9
+
10
+ /**
11
+ * Site configuration
12
+ */
13
+ export const siteConfig: SiteConfig = {
14
+ title: 'My Astro Blog',
15
+ description: '基于 Astro + Vue + Tailwind 构建的个人技术博客',
16
+ author: 'Author',
17
+ email: 'email@example.com',
18
+ avatar: '/images/avatar.svg',
19
+ social: {
20
+ github: 'https://github.com/username',
21
+ twitter: '',
22
+ linkedin: '',
23
+ email: 'mailto:email@example.com'
24
+ },
25
+ menu: [
26
+ {
27
+ name: '首页',
28
+ href: '/',
29
+ icon: 'home'
30
+ },
31
+ {
32
+ name: '博客教学',
33
+ href: '/posts/blog_docs',
34
+ icon: 'posts'
35
+ },
36
+ {
37
+ name: '演示',
38
+ href: '/slides',
39
+ icon: 'slides'
40
+ },
41
+ {
42
+ name: '关于',
43
+ href: '/about',
44
+ icon: 'about'
45
+ }
46
+ ]
47
+ };
48
+
49
+ /**
50
+ * Default SEO settings
51
+ */
52
+ export const defaultSEO = {
53
+ title: siteConfig.title,
54
+ description: siteConfig.description,
55
+ image: '/images/og-image.jpg',
56
+ type: 'website' as const
57
+ };
58
+
59
+ /**
60
+ * Sidebar configuration
61
+ */
62
+ export const sidebarConfig: SidebarConfig = {
63
+ enabled: true,
64
+ showSearch: true,
65
+ showRecentPosts: true,
66
+ recentPostsCount: 5,
67
+ showPopularTags: true,
68
+ popularTagsCount: 8,
69
+ showArchives: true,
70
+ archivesCount: 6,
71
+ showFriendLinks: false,
72
+ friendLinks: [],
73
+ groups: [
74
+ {
75
+ type: 'scan',
76
+ title: '博客指南',
77
+ icon: 'ri:book-open-line',
78
+ scanPath: 'blog_docs',
79
+ collapsed: true,
80
+ showForPaths: ['/posts/blog_docs/**']
81
+ }
82
+ ]
83
+ };
84
+
85
+ /**
86
+ * Social links
87
+ */
88
+ export const socialLinks: SocialLink[] = [
89
+ { type: 'github', url: 'https://github.com/username', label: 'GitHub' },
90
+ { type: 'email', url: 'mailto:email@example.com', label: 'Email' }
91
+ ];
92
+
93
+ /**
94
+ * Footer configuration
95
+ */
96
+ export const footerConfig: FooterConfig = {
97
+ quickLinksTitle: '快速链接',
98
+ quickLinks: [
99
+ { name: '首页', href: '/' },
100
+ { name: '文章', href: '/posts' },
101
+ { name: '标签', href: '/tags' },
102
+ { name: '归档', href: '/archives' },
103
+ { name: '关于', href: '/about' }
104
+ ],
105
+ contactTitle: '联系方式',
106
+ socialLinks: socialLinks,
107
+ showRss: true,
108
+ rssUrl: '/rss.xml',
109
+ copyright: '© {year} {author}. All rights reserved.',
110
+ poweredBy: {
111
+ text: 'Astro',
112
+ url: 'https://astro.build'
113
+ }
114
+ };
@@ -0,0 +1,96 @@
1
+ import { defineCollection, z } from 'astro:content';
2
+ import { glob } from 'astro/loaders';
3
+
4
+ const posts = defineCollection({
5
+ loader: glob({ pattern: '**/*.{md,mdx}', base: './content/posts' }),
6
+ schema: z.object({
7
+ title: z.string(),
8
+ description: z.string().optional(),
9
+ date: z.coerce.date().optional(),
10
+ pubDate: z.coerce.date().optional(),
11
+ updatedDate: z.coerce.date().optional(),
12
+ tag: z.array(z.string()).default([]),
13
+ tags: z.array(z.string()).default([]),
14
+ categories: z.array(z.string()).default([]),
15
+ category: z.union([z.string(), z.array(z.string())]).optional(),
16
+ author: z.string().optional(),
17
+ image: z.string().optional(),
18
+ icon: z.string().optional(),
19
+ draft: z.boolean().default(false),
20
+ index: z.boolean().default(true),
21
+ star: z.boolean().default(false),
22
+ layout: z.enum(['default', 'slides']).default('default'),
23
+ theme: z.string().default('black'),
24
+ transition: z.string().default('slide'),
25
+ controls: z.boolean().default(true),
26
+ progress: z.boolean().default(true),
27
+ center: z.boolean().default(true),
28
+ slideNumber: z.boolean().default(false),
29
+ }).transform((data) => {
30
+ const pubDate = data.pubDate ?? data.date;
31
+ let categories = data.categories;
32
+ if (data.category) {
33
+ const categoryArray = Array.isArray(data.category) ? data.category : [data.category];
34
+ categories = categories.length > 0 ? categories : categoryArray;
35
+ }
36
+ const tags = data.tags.length > 0 ? data.tags : data.tag;
37
+ return {
38
+ title: data.title,
39
+ description: data.description,
40
+ pubDate,
41
+ date: data.date,
42
+ updatedDate: data.updatedDate,
43
+ tags,
44
+ categories,
45
+ author: data.author,
46
+ image: data.image,
47
+ icon: data.icon,
48
+ draft: data.draft,
49
+ index: data.index,
50
+ star: data.star,
51
+ layout: data.layout,
52
+ theme: data.theme,
53
+ transition: data.transition,
54
+ controls: data.controls,
55
+ progress: data.progress,
56
+ center: data.center,
57
+ slideNumber: data.slideNumber,
58
+ };
59
+ }),
60
+ });
61
+
62
+ const pages = defineCollection({
63
+ loader: glob({ pattern: '**/*.{md,mdx}', base: './content/pages' }),
64
+ schema: z.object({
65
+ title: z.string(),
66
+ description: z.string().optional(),
67
+ pubDate: z.coerce.date().optional(),
68
+ updatedDate: z.coerce.date().optional(),
69
+ }),
70
+ });
71
+
72
+ const slides = defineCollection({
73
+ loader: glob({ pattern: '**/*.{md,mdx}', base: './content/slides' }),
74
+ schema: z.object({
75
+ title: z.string(),
76
+ description: z.string().optional(),
77
+ pubDate: z.coerce.date().optional(),
78
+ updatedDate: z.coerce.date().optional(),
79
+ author: z.string().optional(),
80
+ tags: z.array(z.string()).default([]),
81
+ theme: z.string().default('black'),
82
+ transition: z.string().default('slide'),
83
+ controls: z.boolean().default(true),
84
+ progress: z.boolean().default(true),
85
+ center: z.boolean().default(true),
86
+ hash: z.boolean().default(true),
87
+ slideNumber: z.boolean().default(false),
88
+ draft: z.boolean().default(false),
89
+ }),
90
+ });
91
+
92
+ export const collections = {
93
+ posts,
94
+ pages,
95
+ slides,
96
+ };
@@ -0,0 +1,27 @@
1
+ ---
2
+ import { getCollection, render } from 'astro:content';
3
+ import AboutLayout from '@jet-w/astro-blog/layouts/AboutLayout.astro';
4
+ import PageLayout from '@jet-w/astro-blog/layouts/PageLayout.astro';
5
+
6
+ export async function getStaticPaths() {
7
+ const pages = await getCollection('pages');
8
+ return pages.map((page) => ({
9
+ params: { slug: page.id === 'index' ? undefined : page.id },
10
+ props: { page },
11
+ }));
12
+ }
13
+
14
+ const { page } = Astro.props;
15
+ const { Content } = await render(page);
16
+ const isHomePage = page.id === 'index';
17
+ ---
18
+
19
+ {isHomePage ? (
20
+ <PageLayout title={page.data.title} description={page.data.description} showSidebar={false}>
21
+ <Content />
22
+ </PageLayout>
23
+ ) : (
24
+ <AboutLayout title={page.data.title} description={page.data.description}>
25
+ <Content />
26
+ </AboutLayout>
27
+ )}
@@ -0,0 +1,176 @@
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 archiveMap = new Map<string, { year: number; month: number; posts: typeof allPosts }>();
13
+
14
+ allPosts.forEach(post => {
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)!.posts.push(post);
23
+ } else {
24
+ archiveMap.set(key, { year, month, posts: [post] });
25
+ }
26
+ }
27
+ });
28
+
29
+ // 为每个年月的每一页生成路径
30
+ const paths: Array<{
31
+ params: { year: string; month: string; page: string };
32
+ props: { year: number; month: number; page: number; totalPages: number };
33
+ }> = [];
34
+
35
+ archiveMap.forEach(({ year, month, posts }) => {
36
+ // 按日期排序
37
+ const sortedPosts = posts.sort((a, b) => {
38
+ const dateA = a.data.pubDate ? new Date(a.data.pubDate).getTime() : 0;
39
+ const dateB = b.data.pubDate ? new Date(b.data.pubDate).getTime() : 0;
40
+ return dateB - dateA;
41
+ });
42
+
43
+ const totalPages = Math.ceil(sortedPosts.length / postsPerPage);
44
+
45
+ // 为每一页生成路径(从第2页开始,第1页由 [month].astro 处理)
46
+ for (let page = 2; page <= totalPages; page++) {
47
+ paths.push({
48
+ params: { year: String(year), month: String(month).padStart(2, '0'), page: page.toString() },
49
+ props: { year, month, page, totalPages }
50
+ });
51
+ }
52
+ });
53
+
54
+ return paths;
55
+ }
56
+
57
+ const { year, month, page: currentPage, totalPages } = Astro.props;
58
+ const postsPerPage = 10;
59
+
60
+ // 获取该月的所有文章
61
+ const allPosts = await getCollection('posts', ({ data }) => !data.draft);
62
+
63
+ const filteredPosts = allPosts
64
+ .filter(post => {
65
+ if (!post.data.pubDate) return false;
66
+ const date = new Date(post.data.pubDate);
67
+ return date.getFullYear() === year && date.getMonth() + 1 === month;
68
+ })
69
+ .sort((a, b) => {
70
+ const dateA = a.data.pubDate ? new Date(a.data.pubDate).getTime() : 0;
71
+ const dateB = b.data.pubDate ? new Date(b.data.pubDate).getTime() : 0;
72
+ return dateB - dateA;
73
+ });
74
+
75
+ // 分页逻辑
76
+ const totalPosts = filteredPosts.length;
77
+ const startIndex = (currentPage - 1) * postsPerPage;
78
+ const endIndex = startIndex + postsPerPage;
79
+ const posts = filteredPosts.slice(startIndex, endIndex);
80
+
81
+ // 月份名称
82
+ const monthNames = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'];
83
+ const monthName = monthNames[month - 1];
84
+ ---
85
+
86
+ <PageLayout
87
+ title={`归档: ${year}年${month}月 - 第 ${currentPage} 页`}
88
+ description={`${year}年${month}月的所有文章归档 - 第 ${currentPage} 页`}
89
+ showSidebar={true}
90
+ >
91
+ <!-- 面包屑导航 -->
92
+ <nav class="flex items-center space-x-2 text-sm text-slate-600 dark:text-slate-400 mb-8">
93
+ <a href="/" class="hover:text-primary-500 transition-colors">首页</a>
94
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
95
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
96
+ </svg>
97
+ <a href="/archives" class="hover:text-primary-500 transition-colors">归档</a>
98
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
99
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
100
+ </svg>
101
+ <a href={`/archives/${year}/${String(month).padStart(2, '0')}`} class="hover:text-primary-500 transition-colors">{year}年{month}月</a>
102
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
103
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
104
+ </svg>
105
+ <span class="text-slate-900 dark:text-slate-100">第 {currentPage} 页</span>
106
+ </nav>
107
+
108
+ <!-- 页面头部 -->
109
+ <div class="text-center mb-12">
110
+ <div class="inline-flex items-center justify-center w-16 h-16 bg-primary-100 dark:bg-primary-900/30 rounded-full mb-4">
111
+ <svg class="w-8 h-8 text-primary-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
112
+ <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" />
113
+ </svg>
114
+ </div>
115
+
116
+ <h1 class="text-4xl font-bold text-slate-900 dark:text-slate-100 mb-4">
117
+ {year}年{monthName}
118
+ </h1>
119
+
120
+ <p class="text-xl text-slate-600 dark:text-slate-400 mb-8">
121
+ 共 {totalPosts} 篇文章
122
+ </p>
123
+
124
+ <!-- 归档统计 -->
125
+ <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">
126
+ <span>{year}年{month}月</span>
127
+ <span>•</span>
128
+ <span>{totalPosts} 篇文章</span>
129
+ <span>•</span>
130
+ <span>第 {currentPage} / {totalPages} 页</span>
131
+ </div>
132
+ </div>
133
+
134
+ <!-- 文章列表 -->
135
+ {posts.length > 0 ? (
136
+ <div class="space-y-8 mb-12">
137
+ {posts.map((post) => (
138
+ <PostCard
139
+ post={{
140
+ slug: post.id.toLowerCase(),
141
+ title: post.data.title,
142
+ description: post.data.description,
143
+ pubDate: post.data.pubDate,
144
+ tags: post.data.tags,
145
+ categories: post.data.categories,
146
+ author: post.data.author,
147
+ image: post.data.image
148
+ }}
149
+ layout="horizontal"
150
+ />
151
+ ))}
152
+ </div>
153
+ ) : (
154
+ <div class="text-center py-16">
155
+ <div class="text-6xl mb-4">📅</div>
156
+ <h3 class="text-xl font-semibold text-slate-900 dark:text-slate-100 mb-2">
157
+ 暂无文章
158
+ </h3>
159
+ <p class="text-slate-600 dark:text-slate-400 mb-6">
160
+ {year}年{month}月暂无发布的文章
161
+ </p>
162
+ <a href="/archives" class="btn-secondary">
163
+ 浏览所有归档
164
+ </a>
165
+ </div>
166
+ )}
167
+
168
+ <!-- 分页导航 -->
169
+ {totalPages > 1 && (
170
+ <Pagination
171
+ currentPage={currentPage}
172
+ totalPages={totalPages}
173
+ baseUrl={`/archives/${year}/${String(month).padStart(2, '0')}`}
174
+ />
175
+ )}
176
+ </PageLayout>