@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,191 @@
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 tagMap = new Map<string, { name: string; count: number }>();
12
+
13
+ allPosts.forEach(post => {
14
+ (post.data.tags || []).forEach(tag => {
15
+ const slug = tag.toLowerCase().replace(/\s+/g, '-');
16
+ if (tagMap.has(slug)) {
17
+ tagMap.get(slug)!.count++;
18
+ } else {
19
+ tagMap.set(slug, { name: tag, count: 1 });
20
+ }
21
+ });
22
+ });
23
+
24
+ // 为每个标签生成路径
25
+ return Array.from(tagMap.entries()).map(([slug, { name, count }]) => ({
26
+ params: { tag: slug },
27
+ props: { tagSlug: slug, tagName: name, tagCount: count }
28
+ }));
29
+ }
30
+
31
+ const { tagSlug, tagName, tagCount } = Astro.props;
32
+
33
+ // 获取所有文章并筛选包含该标签的文章
34
+ const allPosts = await getCollection('posts', ({ data }) => !data.draft);
35
+
36
+ // 筛选包含该标签的文章
37
+ const filteredPosts = allPosts
38
+ .filter(post =>
39
+ (post.data.tags || []).some(t => t.toLowerCase().replace(/\s+/g, '-') === tagSlug)
40
+ )
41
+ .sort((a, b) => {
42
+ const dateA = a.data.pubDate ? new Date(a.data.pubDate).getTime() : 0;
43
+ const dateB = b.data.pubDate ? new Date(b.data.pubDate).getTime() : 0;
44
+ return dateB - dateA;
45
+ });
46
+
47
+ // 分页逻辑
48
+ const currentPage = 1;
49
+ const postsPerPage = 10;
50
+ const totalPosts = filteredPosts.length;
51
+ const totalPages = Math.ceil(totalPosts / postsPerPage);
52
+ const startIndex = (currentPage - 1) * postsPerPage;
53
+ const endIndex = startIndex + postsPerPage;
54
+ const posts = filteredPosts.slice(startIndex, endIndex);
55
+
56
+ // 获取相关标签(从当前标签文章中提取其他标签)
57
+ const relatedTagMap = new Map<string, { name: string; count: number }>();
58
+ filteredPosts.forEach(post => {
59
+ (post.data.tags || []).forEach(t => {
60
+ const slug = t.toLowerCase().replace(/\s+/g, '-');
61
+ if (slug !== tagSlug) {
62
+ if (relatedTagMap.has(slug)) {
63
+ relatedTagMap.get(slug)!.count++;
64
+ } else {
65
+ relatedTagMap.set(slug, { name: t, count: 1 });
66
+ }
67
+ }
68
+ });
69
+ });
70
+
71
+ const relatedTags = Array.from(relatedTagMap.entries())
72
+ .map(([slug, { name, count }]) => ({ slug, name, count }))
73
+ .sort((a, b) => b.count - a.count)
74
+ .slice(0, 8);
75
+ ---
76
+
77
+ <PageLayout
78
+ title={`标签: ${tagName}`}
79
+ description={`浏览所有关于 ${tagName} 的文章`}
80
+ showSidebar={true}
81
+ >
82
+ <!-- 面包屑导航 -->
83
+ <nav class="flex items-center space-x-2 text-sm text-slate-600 dark:text-slate-400 mb-8">
84
+ <a href="/" class="hover:text-primary-500 transition-colors">首页</a>
85
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
86
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
87
+ </svg>
88
+ <a href="/tags" class="hover:text-primary-500 transition-colors">标签</a>
89
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
90
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
91
+ </svg>
92
+ <span class="text-slate-900 dark:text-slate-100">{tagName}</span>
93
+ </nav>
94
+
95
+ <!-- 页面头部 -->
96
+ <div class="text-center mb-12">
97
+ <div class="inline-flex items-center justify-center w-16 h-16 bg-primary-100 dark:bg-primary-900/30 rounded-full mb-4">
98
+ <svg class="w-8 h-8 text-primary-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
99
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
100
+ </svg>
101
+ </div>
102
+
103
+ <h1 class="text-4xl font-bold text-slate-900 dark:text-slate-100 mb-4">
104
+ {tagName}
105
+ </h1>
106
+
107
+ <p class="text-xl text-slate-600 dark:text-slate-400 mb-8">
108
+ 找到 {totalPosts} 篇关于 {tagName} 的文章
109
+ </p>
110
+
111
+ <!-- 标签统计 -->
112
+ <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">
113
+ <span>#{tagName}</span>
114
+ <span>•</span>
115
+ <span>{totalPosts} 篇文章</span>
116
+ {totalPages > 1 && (
117
+ <>
118
+ <span>•</span>
119
+ <span>共 {totalPages} 页</span>
120
+ </>
121
+ )}
122
+ </div>
123
+ </div>
124
+
125
+ <!-- 文章列表 -->
126
+ {posts.length > 0 ? (
127
+ <div class="space-y-8 mb-12">
128
+ {posts.map((post) => (
129
+ <PostCard
130
+ post={{
131
+ slug: post.id.toLowerCase(),
132
+ title: post.data.title,
133
+ description: post.data.description,
134
+ pubDate: post.data.pubDate,
135
+ tags: post.data.tags,
136
+ categories: post.data.categories,
137
+ author: post.data.author,
138
+ image: post.data.image
139
+ }}
140
+ layout="horizontal"
141
+ />
142
+ ))}
143
+ </div>
144
+ ) : (
145
+ <div class="text-center py-16">
146
+ <div class="text-6xl mb-4">📝</div>
147
+ <h3 class="text-xl font-semibold text-slate-900 dark:text-slate-100 mb-2">
148
+ 暂无相关文章
149
+ </h3>
150
+ <p class="text-slate-600 dark:text-slate-400 mb-6">
151
+ 目前还没有关于 {tagName} 的文章,请查看其他标签。
152
+ </p>
153
+ <a href="/tags" class="btn-secondary">
154
+ 浏览所有标签
155
+ </a>
156
+ </div>
157
+ )}
158
+
159
+ <!-- 分页导航 -->
160
+ {totalPages > 1 && (
161
+ <Pagination
162
+ currentPage={currentPage}
163
+ totalPages={totalPages}
164
+ baseUrl={`/tags/${tagSlug}`}
165
+ />
166
+ )}
167
+
168
+ <!-- 相关标签 -->
169
+ {relatedTags.length > 0 && (
170
+ <section class="mt-16 pt-8 border-t border-slate-200 dark:border-slate-700">
171
+ <h2 class="text-2xl font-bold text-slate-900 dark:text-slate-100 mb-6">
172
+ 相关标签
173
+ </h2>
174
+ <div class="flex flex-wrap gap-3">
175
+ {relatedTags.map((relatedTag) => (
176
+ <a
177
+ href={`/tags/${relatedTag.slug}`}
178
+ class="inline-flex items-center px-4 py-2 bg-white dark:bg-slate-800 border border-slate-300 dark:border-slate-600 rounded-lg hover:border-primary-300 dark:hover:border-primary-600 hover:shadow-md transition-all duration-200 group"
179
+ >
180
+ <span class="font-medium text-slate-900 dark:text-slate-100 group-hover:text-primary-500 transition-colors">
181
+ {relatedTag.name}
182
+ </span>
183
+ <span class="ml-2 text-xs px-2 py-1 bg-slate-100 dark:bg-slate-700 text-slate-600 dark:text-slate-400 rounded-full">
184
+ {relatedTag.count}
185
+ </span>
186
+ </a>
187
+ ))}
188
+ </div>
189
+ </section>
190
+ )}
191
+ </PageLayout>
@@ -0,0 +1,167 @@
1
+ ---
2
+ import { getCollection } from 'astro:content';
3
+ import PageLayout from '@jet-w/astro-blog/layouts/PageLayout.astro';
4
+ import TagCloud from '@jet-w/astro-blog/components/blog/TagCloud.astro';
5
+
6
+ // 从文章集合动态获取所有标签
7
+ const allPosts = await getCollection('posts', ({ data }) => !data.draft);
8
+
9
+ // 统计标签
10
+ const tagMap = new Map<string, { name: string; count: number }>();
11
+
12
+ allPosts.forEach(post => {
13
+ (post.data.tags || []).forEach(tag => {
14
+ const slug = tag.toLowerCase().replace(/\s+/g, '-');
15
+ if (tagMap.has(slug)) {
16
+ tagMap.get(slug)!.count++;
17
+ } else {
18
+ tagMap.set(slug, { name: tag, count: 1 });
19
+ }
20
+ });
21
+ });
22
+
23
+ // 转换为数组格式
24
+ const allTags = Array.from(tagMap.entries()).map(([slug, { name, count }]) => ({
25
+ name,
26
+ count,
27
+ slug
28
+ }));
29
+
30
+ // 按文章数量排序
31
+ const sortedTags = [...allTags].sort((a, b) => b.count - a.count);
32
+ const totalPosts = allTags.reduce((sum, tag) => sum + tag.count, 0);
33
+
34
+ // 按首字母分组
35
+ type TagType = typeof allTags[0];
36
+ const groupedByLetter = allTags.reduce((acc: Record<string, TagType[]>, tag) => {
37
+ const firstChar = tag.name.charAt(0).toUpperCase();
38
+ // 判断是否为中文字符
39
+ const isChinese = /[\u4e00-\u9fa5]/.test(firstChar);
40
+ const key = isChinese ? '中文' : firstChar;
41
+ if (!acc[key]) {
42
+ acc[key] = [];
43
+ }
44
+ acc[key].push(tag);
45
+ return acc;
46
+ }, {} as Record<string, TagType[]>);
47
+
48
+ // 排序分组(中文放最后)
49
+ const sortedGroups = Object.entries(groupedByLetter).sort(([a], [b]) => {
50
+ if (a === '中文') return 1;
51
+ if (b === '中文') return -1;
52
+ return a.localeCompare(b);
53
+ });
54
+ ---
55
+
56
+ <PageLayout
57
+ title="标签"
58
+ description="按标签浏览文章"
59
+ showSidebar={true}
60
+ >
61
+ <!-- 页面头部 -->
62
+ <div class="text-center mb-12">
63
+ <h1 class="text-4xl font-bold text-slate-900 dark:text-slate-100 mb-4">
64
+ 文章标签
65
+ </h1>
66
+ <p class="text-xl text-slate-600 dark:text-slate-400 mb-8">
67
+ 按标签分类浏览所有文章
68
+ </p>
69
+
70
+ <!-- 统计信息 -->
71
+ <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">
72
+ <span>共 {allTags.length} 个标签</span>
73
+ <span>•</span>
74
+ <span>{totalPosts} 篇文章</span>
75
+ </div>
76
+ </div>
77
+
78
+ <!-- 标签云 -->
79
+ <section class="mb-16">
80
+ <h2 class="text-2xl font-bold text-slate-900 dark:text-slate-100 mb-8">
81
+ 标签云
82
+ </h2>
83
+ <TagCloud tags={allTags} />
84
+ </section>
85
+
86
+ <!-- 热门标签 -->
87
+ <section class="mb-16">
88
+ <h2 class="text-2xl font-bold text-slate-900 dark:text-slate-100 mb-8">
89
+ 热门标签
90
+ </h2>
91
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
92
+ {sortedTags.slice(0, 9).map((tag) => (
93
+ <a
94
+ href={`/tags/${tag.slug}`}
95
+ class="group card hover:shadow-lg transform hover:-translate-y-1 transition-all duration-300"
96
+ >
97
+ <div class="flex items-center justify-between">
98
+ <div>
99
+ <h3 class="text-lg font-semibold text-slate-900 dark:text-slate-100 group-hover:text-primary-500 transition-colors">
100
+ {tag.name}
101
+ </h3>
102
+ <p class="text-sm text-slate-600 dark:text-slate-400">
103
+ {tag.count} 篇文章
104
+ </p>
105
+ </div>
106
+ <div class="text-3xl font-bold text-primary-500 opacity-80">
107
+ #{tag.count}
108
+ </div>
109
+ </div>
110
+ <div class="mt-4 h-2 bg-slate-200 dark:bg-slate-700 rounded-full overflow-hidden">
111
+ <div
112
+ class="h-full bg-primary-500 rounded-full transition-all duration-500 group-hover:bg-primary-600"
113
+ style={`width: ${Math.round((tag.count / Math.max(...sortedTags.map(t => t.count))) * 100)}%`}
114
+ ></div>
115
+ </div>
116
+ </a>
117
+ ))}
118
+ </div>
119
+ </section>
120
+
121
+ <!-- 所有标签 -->
122
+ <section class="mb-16">
123
+ <h2 class="text-2xl font-bold text-slate-900 dark:text-slate-100 mb-8">
124
+ 所有标签
125
+ </h2>
126
+ <div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
127
+ {sortedTags.map((tag) => (
128
+ <a
129
+ href={`/tags/${tag.slug}`}
130
+ class="flex items-center justify-between p-4 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg hover:shadow-md hover:border-primary-300 dark:hover:border-primary-600 transition-all duration-200 group"
131
+ >
132
+ <span class="font-medium text-slate-900 dark:text-slate-100 group-hover:text-primary-500 transition-colors">
133
+ {tag.name}
134
+ </span>
135
+ <span class="text-sm px-2 py-1 bg-slate-100 dark:bg-slate-700 text-slate-600 dark:text-slate-400 rounded-full">
136
+ {tag.count}
137
+ </span>
138
+ </a>
139
+ ))}
140
+ </div>
141
+ </section>
142
+
143
+ <!-- 按字母分组 -->
144
+ <section>
145
+ <h2 class="text-2xl font-bold text-slate-900 dark:text-slate-100 mb-8">
146
+ 按首字母分组
147
+ </h2>
148
+ {sortedGroups.map(([letter, tags]) => (
149
+ <div class="mb-6">
150
+ <h3 class="text-lg font-semibold text-slate-900 dark:text-slate-100 mb-3 border-b border-slate-200 dark:border-slate-700 pb-2">
151
+ {letter}
152
+ </h3>
153
+ <div class="flex flex-wrap gap-2">
154
+ {tags.sort((a, b) => b.count - a.count).map((tag) => (
155
+ <a
156
+ href={`/tags/${tag.slug}`}
157
+ class="inline-flex items-center px-3 py-1 bg-slate-100 dark:bg-slate-800 hover:bg-primary-100 dark:hover:bg-primary-900/30 text-slate-700 dark:text-slate-300 hover:text-primary-700 dark:hover:text-primary-300 rounded-full transition-colors text-sm"
158
+ >
159
+ {tag.name}
160
+ <span class="ml-2 text-xs opacity-75">({tag.count})</span>
161
+ </a>
162
+ ))}
163
+ </div>
164
+ </div>
165
+ ))}
166
+ </section>
167
+ </PageLayout>
@@ -0,0 +1,78 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
3
+ content: [
4
+ './src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}',
5
+ './content/**/*.{md,mdx}',
6
+ './node_modules/@jet-w/astro-blog/src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'
7
+ ],
8
+ darkMode: 'class',
9
+ safelist: [
10
+ 'bg-primary-100', 'bg-primary-900/30', 'text-primary-300', 'text-primary-500', 'text-primary-700',
11
+ 'bg-secondary-100', 'bg-secondary-900/30', 'text-secondary-300', 'text-secondary-500', 'text-secondary-700',
12
+ 'bg-accent-100', 'bg-accent-900/30', 'text-accent-300', 'text-accent-500', 'text-accent-700',
13
+ 'bg-green-100', 'bg-green-900/30', 'text-green-300', 'text-green-700',
14
+ 'bg-yellow-100', 'bg-yellow-900/30', 'text-yellow-300', 'text-yellow-700',
15
+ 'bg-red-100', 'bg-red-900/30', 'text-red-300', 'text-red-700',
16
+ 'bg-cyan-100', 'bg-cyan-900/30', 'text-cyan-300', 'text-cyan-700',
17
+ ],
18
+ theme: {
19
+ extend: {
20
+ colors: {
21
+ primary: {
22
+ 50: '#eff6ff',
23
+ 100: '#dbeafe',
24
+ 200: '#bfdbfe',
25
+ 300: '#93c5fd',
26
+ 400: '#60a5fa',
27
+ 500: '#3b82f6',
28
+ 600: '#2563eb',
29
+ 700: '#1d4ed8',
30
+ 800: '#1e40af',
31
+ 900: '#1e3a8a',
32
+ },
33
+ secondary: {
34
+ 50: '#eef2ff',
35
+ 100: '#e0e7ff',
36
+ 200: '#c7d2fe',
37
+ 300: '#a5b4fc',
38
+ 400: '#818cf8',
39
+ 500: '#6366f1',
40
+ 600: '#4f46e5',
41
+ 700: '#4338ca',
42
+ 800: '#3730a3',
43
+ 900: '#312e81',
44
+ },
45
+ accent: {
46
+ 50: '#f5f3ff',
47
+ 100: '#ede9fe',
48
+ 200: '#ddd6fe',
49
+ 300: '#c4b5fd',
50
+ 400: '#a78bfa',
51
+ 500: '#8b5cf6',
52
+ 600: '#7c3aed',
53
+ 700: '#6d28d9',
54
+ 800: '#5b21b6',
55
+ 900: '#4c1d95',
56
+ }
57
+ },
58
+ fontFamily: {
59
+ 'sans': ['"Inter"', '"Noto Sans SC"', '"PingFang SC"', '"Microsoft YaHei"', 'system-ui', 'sans-serif'],
60
+ 'mono': ['"JetBrains Mono"', '"Fira Code"', '"Monaco"', 'monospace'],
61
+ },
62
+ typography: {
63
+ DEFAULT: {
64
+ css: {
65
+ maxWidth: 'none',
66
+ color: 'rgb(30 41 59)',
67
+ '[data-theme="dark"] &': {
68
+ color: 'rgb(241 245 249)',
69
+ },
70
+ },
71
+ },
72
+ },
73
+ },
74
+ },
75
+ plugins: [
76
+ require('@tailwindcss/typography'),
77
+ ],
78
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "astro/tsconfigs/strict",
3
+ "compilerOptions": {
4
+ "baseUrl": ".",
5
+ "paths": {
6
+ "@/*": ["./src/*"]
7
+ }
8
+ }
9
+ }