@jet-w/astro-blog 0.2.0 → 0.2.1
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-HVQKQN6B.js → chunk-6D3XRDNY.js} +1 -1
- package/dist/{chunk-ATRISB7B.js → chunk-A2E2VSAQ.js} +43 -3
- package/dist/{chunk-AZHCNNAC.js → chunk-TJTPX2WP.js} +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.js +2 -2
- package/dist/{i18n-5H4W145i.d.ts → i18n-PgMCFBw0.d.ts} +20 -0
- package/dist/index.d.ts +21 -3
- package/dist/index.js +20 -3
- package/dist/integration.d.ts +1 -1
- package/dist/integration.js +2 -2
- package/package.json +1 -1
- package/src/components/blog/FloatingToc.vue +11 -3
- package/src/components/blog/Hero.astro +17 -2
- package/src/components/blog/NavigationTabs.vue +46 -15
- package/src/components/blog/PostCard.astro +28 -10
- package/src/components/blog/RelatedPosts.astro +23 -7
- package/src/components/blog/TableOfContents.astro +10 -4
- package/src/components/blog/TagCloud.astro +4 -3
- package/src/components/home/FeaturedPostsSection.astro +22 -6
- package/src/components/home/QuickNavSection.astro +33 -4
- package/src/components/home/RecentPostsSection.astro +22 -6
- package/src/components/home/StatsSection.astro +24 -6
- package/src/components/layout/Header.astro +9 -5
- package/src/components/layout/Sidebar.astro +14 -11
- package/src/components/ui/SearchBox.vue +13 -5
- package/src/components/ui/SearchInterface.vue +49 -25
- package/src/pages/archives/[year]/[month].astro +36 -17
- package/src/pages/archives/index.astro +36 -20
- package/src/pages/categories/[category].astro +33 -16
- package/src/pages/categories/index.astro +37 -14
- package/src/pages/posts/[...slug].astro +125 -18
- package/src/pages/posts/index.astro +59 -37
- package/src/pages/posts/page/[page].astro +65 -27
- package/src/pages/search.astro +50 -14
- package/src/pages/slides/index.astro +25 -6
- package/src/pages/tags/[tag].astro +32 -15
- package/src/pages/tags/index.astro +39 -16
- package/src/plugins/remark-containers.mjs +351 -322
- package/src/plugins/remark-protect-code.mjs +69 -0
- package/src/styles/global.css +35 -1
- package/templates/default/.claude/ralph-loop.local.md +48 -0
- package/templates/default/astro.config.mjs +13 -4
- package/templates/default/content/posts/blog_docs_en/{get-started → 01.get-started}/01-intro.md +1 -1
- package/templates/default/content/posts/blog_docs_en/{get-started → 01.get-started}/02-install.md +1 -1
- package/templates/default/content/posts/blog_docs_en/{get-started → 01.get-started}/03-create-post.md +1 -1
- package/templates/default/content/posts/blog_docs_en/{get-started → 01.get-started}/04-structure.md +1 -1
- package/templates/default/content/posts/blog_docs_en/01.get-started/05-deploy.md +208 -0
- package/templates/default/content/posts/blog_docs_en/{get-started → 01.get-started}/README.md +1 -1
- package/templates/default/content/posts/blog_docs_en/02.guide/02-containers.md +245 -0
- package/templates/default/content/posts/blog_docs_en/{guide/markdown → 02.guide}/03-code-blocks.md +2 -1
- package/templates/default/content/posts/blog_docs_en/{guide/features/01-mermaid.md → 02.guide/03-mermaid.md} +1 -1
- package/templates/default/content/posts/blog_docs_en/{guide/features → 02.guide}/04-icons.md +4 -2
- package/templates/default/content/posts/blog_docs_en/{guide/features/02-latex.md → 02.guide/06-latex.md} +1 -1
- package/templates/default/content/posts/blog_docs_en/{guide/features/03-video.md → 02.guide/07-video.md} +1 -1
- package/templates/default/content/posts/blog_docs_en/02.guide/08-slides.md +359 -0
- package/templates/default/content/posts/blog_docs_en/{guide/markdown → 02.guide}/README.md +22 -3
- package/templates/default/content/posts/blog_docs_en/{config → 03.config}/01-site.md +1 -1
- package/templates/default/content/posts/blog_docs_en/{config → 03.config}/02-sidebar.md +1 -1
- package/templates/default/content/posts/blog_docs_en/{config → 03.config}/03-i18n.md +88 -24
- package/templates/default/content/posts/blog_docs_en/{config → 03.config}/README.md +1 -1
- package/templates/default/content/posts/blog_docs_en/README.md +2 -1
- package/templates/default/content/posts/blog_docs_zh/01.get-started/01-intro.md +81 -0
- package/templates/default/content/posts/blog_docs_zh/01.get-started/02-install.md +137 -0
- package/templates/default/content/posts/blog_docs_zh/01.get-started/03-create-post.md +176 -0
- package/templates/default/content/posts/blog_docs_zh/01.get-started/04-structure.md +173 -0
- package/templates/default/content/posts/blog_docs_zh/01.get-started/05-deploy.md +208 -0
- package/templates/default/content/posts/blog_docs_zh/01.get-started/README.md +52 -0
- package/templates/default/content/posts/blog_docs_zh/02.guide/02-containers.md +245 -0
- package/templates/default/content/posts/blog_docs_zh/02.guide/03-code-blocks.md +206 -0
- package/templates/default/content/posts/blog_docs_zh/02.guide/03-mermaid.md +194 -0
- package/templates/default/content/posts/blog_docs_zh/02.guide/04-icons.md +229 -0
- package/templates/default/content/posts/blog_docs_zh/02.guide/06-latex.md +233 -0
- package/templates/default/content/posts/blog_docs_zh/02.guide/07-video.md +184 -0
- package/templates/default/content/posts/blog_docs_zh/02.guide/08-slides.md +359 -0
- package/templates/default/content/posts/blog_docs_zh/02.guide/README.md +213 -0
- package/templates/default/content/posts/blog_docs_zh/03.config/01-site.md +208 -0
- package/templates/default/content/posts/blog_docs_zh/03.config/02-sidebar.md +240 -0
- package/templates/default/content/posts/blog_docs_zh/03.config/03-i18n.md +348 -0
- package/templates/default/content/posts/blog_docs_zh/03.config/README.md +85 -0
- package/templates/default/content/posts/blog_docs_zh/README.md +78 -0
- package/templates/default/src/config/locales/en/index.ts +5 -1
- package/templates/default/src/config/locales/en/menu.ts +3 -1
- package/templates/default/src/config/locales/en/sidebar.ts +18 -2
- package/templates/default/src/config/locales/en/site.ts +1 -1
- package/templates/default/src/config/locales/en/ui.ts +29 -0
- package/templates/default/src/config/locales/zh-CN/index.ts +5 -1
- package/templates/default/src/config/locales/zh-CN/menu.ts +7 -5
- package/templates/default/src/config/locales/zh-CN/sidebar.ts +22 -6
- package/templates/default/src/config/locales/zh-CN/site.ts +2 -2
- package/templates/default/src/config/locales/zh-CN/ui.ts +29 -0
- package/templates/default/src/config/site.ts +2 -2
- package/templates/default/src/content.config.ts +15 -3
- package/templates/default/content/posts/blog_docs/01-quick-start.md +0 -162
- package/templates/default/content/posts/blog_docs/02-frontmatter.md +0 -277
- package/templates/default/content/posts/blog_docs/03-markdown-basic.md +0 -350
- package/templates/default/content/posts/blog_docs/04-containers.md +0 -331
- package/templates/default/content/posts/blog_docs/05-code-blocks.md +0 -388
- package/templates/default/content/posts/blog_docs/06-mermaid.md +0 -431
- package/templates/default/content/posts/blog_docs/07-video.md +0 -243
- package/templates/default/content/posts/blog_docs/08-latex.md +0 -382
- package/templates/default/content/posts/blog_docs/09-icons.md +0 -326
- package/templates/default/content/posts/blog_docs/10-sidebar.md +0 -445
- package/templates/default/content/posts/blog_docs/11-config.md +0 -334
- package/templates/default/content/posts/blog_docs/12-i18n.md +0 -355
- package/templates/default/content/posts/blog_docs/12-slides.mdx +0 -552
- package/templates/default/content/posts/blog_docs/README.md +0 -152
- package/templates/default/content/posts/blog_docs_en/get-started/05-deploy.md +0 -197
- package/templates/default/content/posts/blog_docs_en/guide/README.md +0 -59
- package/templates/default/content/posts/blog_docs_en/guide/features/README.md +0 -51
- package/templates/default/content/posts/blog_docs_en/guide/markdown/02-containers.md +0 -226
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
v-model="searchQuery"
|
|
8
8
|
@input="handleSearch"
|
|
9
9
|
type="text"
|
|
10
|
-
placeholder="搜索文章标题、内容、标签..."
|
|
10
|
+
:placeholder="props.placeholder || '搜索文章标题、内容、标签...'"
|
|
11
11
|
class="w-full pl-12 pr-4 py-4 text-lg bg-white dark:bg-slate-800 border-2 border-slate-300 dark:border-slate-600 rounded-xl focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-colors shadow-sm"
|
|
12
12
|
autofocus
|
|
13
13
|
/>
|
|
@@ -33,41 +33,41 @@
|
|
|
33
33
|
<div class="flex flex-wrap gap-4">
|
|
34
34
|
<!-- 标签筛选 -->
|
|
35
35
|
<div class="flex items-center space-x-2">
|
|
36
|
-
<label class="text-sm font-medium text-slate-700 dark:text-slate-300"
|
|
36
|
+
<label class="text-sm font-medium text-slate-700 dark:text-slate-300">{{ props.tags || '标签' }}:</label>
|
|
37
37
|
<select
|
|
38
38
|
v-model="selectedTag"
|
|
39
39
|
@change="handleSearch"
|
|
40
40
|
class="text-sm px-3 py-1 bg-white dark:bg-slate-800 border border-slate-300 dark:border-slate-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
|
|
41
41
|
>
|
|
42
|
-
<option value=""
|
|
42
|
+
<option value="">{{ props.all || '全部' }}</option>
|
|
43
43
|
<option v-for="tag in allTags" :key="tag" :value="tag">{{ tag }}</option>
|
|
44
44
|
</select>
|
|
45
45
|
</div>
|
|
46
46
|
|
|
47
47
|
<!-- 分类筛选 -->
|
|
48
48
|
<div class="flex items-center space-x-2">
|
|
49
|
-
<label class="text-sm font-medium text-slate-700 dark:text-slate-300"
|
|
49
|
+
<label class="text-sm font-medium text-slate-700 dark:text-slate-300">{{ props.categories || '分类' }}:</label>
|
|
50
50
|
<select
|
|
51
51
|
v-model="selectedCategory"
|
|
52
52
|
@change="handleSearch"
|
|
53
53
|
class="text-sm px-3 py-1 bg-white dark:bg-slate-800 border border-slate-300 dark:border-slate-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
|
|
54
54
|
>
|
|
55
|
-
<option value=""
|
|
55
|
+
<option value="">{{ props.all || '全部' }}</option>
|
|
56
56
|
<option v-for="category in allCategories" :key="category" :value="category">{{ category }}</option>
|
|
57
57
|
</select>
|
|
58
58
|
</div>
|
|
59
59
|
|
|
60
60
|
<!-- 排序 -->
|
|
61
61
|
<div class="flex items-center space-x-2">
|
|
62
|
-
<label class="text-sm font-medium text-slate-700 dark:text-slate-300"
|
|
62
|
+
<label class="text-sm font-medium text-slate-700 dark:text-slate-300">{{ props.sortLabel || '排序' }}:</label>
|
|
63
63
|
<select
|
|
64
64
|
v-model="sortBy"
|
|
65
65
|
@change="handleSearch"
|
|
66
66
|
class="text-sm px-3 py-1 bg-white dark:bg-slate-800 border border-slate-300 dark:border-slate-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
|
|
67
67
|
>
|
|
68
|
-
<option value="relevance"
|
|
69
|
-
<option value="date"
|
|
70
|
-
<option value="title"
|
|
68
|
+
<option value="relevance">{{ props.relevance || '相关性' }}</option>
|
|
69
|
+
<option value="date">{{ props.publishTime || '发布时间' }}</option>
|
|
70
|
+
<option value="title">{{ props.titleLabel || '标题' }}</option>
|
|
71
71
|
</select>
|
|
72
72
|
</div>
|
|
73
73
|
</div>
|
|
@@ -75,15 +75,14 @@
|
|
|
75
75
|
<!-- 搜索状态 -->
|
|
76
76
|
<div class="flex items-center justify-between text-sm text-slate-600 dark:text-slate-400">
|
|
77
77
|
<div>
|
|
78
|
-
<span v-if="isSearching"
|
|
78
|
+
<span v-if="isSearching">{{ props.searching || '正在搜索...' }}</span>
|
|
79
79
|
<span v-else-if="searchQuery">
|
|
80
|
-
找到 {{ searchResults.length }}
|
|
81
|
-
<span v-if="searchQuery">包含 "{{ searchQuery }}"</span>
|
|
80
|
+
{{ props.foundResults || '找到' }} {{ searchResults.length }} {{ props.containing || '个结果包含' }} "{{ searchQuery }}"
|
|
82
81
|
</span>
|
|
83
|
-
<span v-else
|
|
82
|
+
<span v-else>{{ props.enterKeywordToSearch || '输入关键词开始搜索' }}</span>
|
|
84
83
|
</div>
|
|
85
|
-
<div v-if="
|
|
86
|
-
|
|
84
|
+
<div v-if="searchTimeValue">
|
|
85
|
+
{{ props.searchTime || '搜索用时' }}: {{ searchTimeValue }}ms
|
|
87
86
|
</div>
|
|
88
87
|
</div>
|
|
89
88
|
|
|
@@ -104,7 +103,7 @@
|
|
|
104
103
|
|
|
105
104
|
<div class="flex flex-wrap items-center gap-4 text-sm text-slate-500 dark:text-slate-400">
|
|
106
105
|
<time :datetime="result.date">{{ formatDate(result.date) }}</time>
|
|
107
|
-
<span v-if="result.readingTime">{{ result.readingTime }}
|
|
106
|
+
<span v-if="result.readingTime">{{ result.readingTime }} {{ props.readMinutes || '分钟阅读' }}</span>
|
|
108
107
|
<div class="flex flex-wrap gap-1">
|
|
109
108
|
<span
|
|
110
109
|
v-for="tag in result.tags.slice(0, 3)"
|
|
@@ -119,7 +118,7 @@
|
|
|
119
118
|
|
|
120
119
|
<!-- 相关度评分 -->
|
|
121
120
|
<div class="lg:w-20 text-center">
|
|
122
|
-
<div class="text-xs text-slate-500 dark:text-slate-400 mb-1"
|
|
121
|
+
<div class="text-xs text-slate-500 dark:text-slate-400 mb-1">{{ props.relevanceLabel || '相关度' }}</div>
|
|
123
122
|
<div class="flex items-center justify-center">
|
|
124
123
|
<div class="w-12 h-2 bg-slate-200 dark:bg-slate-700 rounded-full overflow-hidden">
|
|
125
124
|
<div
|
|
@@ -138,16 +137,16 @@
|
|
|
138
137
|
<div v-else-if="searchQuery && !isSearching" class="text-center py-16">
|
|
139
138
|
<div class="text-6xl mb-4">🔍</div>
|
|
140
139
|
<h3 class="text-xl font-semibold text-slate-900 dark:text-slate-100 mb-2">
|
|
141
|
-
没有找到相关结果
|
|
140
|
+
{{ props.noResultsTitle || '没有找到相关结果' }}
|
|
142
141
|
</h3>
|
|
143
142
|
<p class="text-slate-600 dark:text-slate-400 mb-6">
|
|
144
|
-
尝试使用不同的关键词或调整筛选条件
|
|
143
|
+
{{ props.noResultsDesc || '尝试使用不同的关键词或调整筛选条件' }}
|
|
145
144
|
</p>
|
|
146
145
|
<button
|
|
147
146
|
@click="clearSearch"
|
|
148
147
|
class="btn-secondary"
|
|
149
148
|
>
|
|
150
|
-
清除搜索
|
|
149
|
+
{{ props.clearSearch || '清除搜索' }}
|
|
151
150
|
</button>
|
|
152
151
|
</div>
|
|
153
152
|
|
|
@@ -162,7 +161,7 @@
|
|
|
162
161
|
? 'bg-slate-100 dark:bg-slate-800 text-slate-400 border-slate-200 dark:border-slate-700'
|
|
163
162
|
: 'bg-white dark:bg-slate-800 text-slate-700 dark:text-slate-300 border-slate-300 dark:border-slate-600 hover:bg-slate-50 dark:hover:bg-slate-700'"
|
|
164
163
|
>
|
|
165
|
-
上一页
|
|
164
|
+
{{ props.previousPage || '上一页' }}
|
|
166
165
|
</button>
|
|
167
166
|
|
|
168
167
|
<div class="flex items-center space-x-1">
|
|
@@ -187,7 +186,7 @@
|
|
|
187
186
|
? 'bg-slate-100 dark:bg-slate-800 text-slate-400 border-slate-200 dark:border-slate-700'
|
|
188
187
|
: 'bg-white dark:bg-slate-800 text-slate-700 dark:text-slate-300 border-slate-300 dark:border-slate-600 hover:bg-slate-50 dark:hover:bg-slate-700'"
|
|
189
188
|
>
|
|
190
|
-
下一页
|
|
189
|
+
{{ props.nextPage || '下一页' }}
|
|
191
190
|
</button>
|
|
192
191
|
</nav>
|
|
193
192
|
</div>
|
|
@@ -198,13 +197,38 @@
|
|
|
198
197
|
import { ref, computed, onMounted } from 'vue'
|
|
199
198
|
import type { SearchResult } from '@jet-w/astro-blog/types'
|
|
200
199
|
|
|
200
|
+
const props = defineProps<{
|
|
201
|
+
placeholder?: string;
|
|
202
|
+
noResults?: string;
|
|
203
|
+
tags?: string;
|
|
204
|
+
categories?: string;
|
|
205
|
+
sortLabel?: string;
|
|
206
|
+
relevance?: string;
|
|
207
|
+
publishTime?: string;
|
|
208
|
+
titleLabel?: string;
|
|
209
|
+
all?: string;
|
|
210
|
+
searching?: string;
|
|
211
|
+
foundResults?: string;
|
|
212
|
+
containing?: string;
|
|
213
|
+
enterKeywordToSearch?: string;
|
|
214
|
+
searchTime?: string;
|
|
215
|
+
readMinutes?: string;
|
|
216
|
+
relevanceLabel?: string;
|
|
217
|
+
noResultsTitle?: string;
|
|
218
|
+
noResultsDesc?: string;
|
|
219
|
+
clearSearch?: string;
|
|
220
|
+
previousPage?: string;
|
|
221
|
+
nextPage?: string;
|
|
222
|
+
dateLocale?: string;
|
|
223
|
+
}>();
|
|
224
|
+
|
|
201
225
|
const searchQuery = ref('')
|
|
202
226
|
const selectedTag = ref('')
|
|
203
227
|
const selectedCategory = ref('')
|
|
204
228
|
const sortBy = ref('relevance')
|
|
205
229
|
const searchResults = ref<(SearchResult & { score: number; date: string; readingTime?: number })[]>([])
|
|
206
230
|
const isSearching = ref(false)
|
|
207
|
-
const
|
|
231
|
+
const searchTimeValue = ref(0)
|
|
208
232
|
const currentPage = ref(1)
|
|
209
233
|
const resultsPerPage = 10
|
|
210
234
|
|
|
@@ -339,7 +363,7 @@ const performSearch = () => {
|
|
|
339
363
|
|
|
340
364
|
searchResults.value = results
|
|
341
365
|
isSearching.value = false
|
|
342
|
-
|
|
366
|
+
searchTimeValue.value = Math.round(performance.now() - startTime)
|
|
343
367
|
currentPage.value = 1
|
|
344
368
|
}, 200)
|
|
345
369
|
}
|
|
@@ -364,7 +388,7 @@ const highlightText = (text: string) => {
|
|
|
364
388
|
}
|
|
365
389
|
|
|
366
390
|
const formatDate = (date: string) => {
|
|
367
|
-
return new Intl.DateTimeFormat('zh-CN', {
|
|
391
|
+
return new Intl.DateTimeFormat(props.dateLocale || 'zh-CN', {
|
|
368
392
|
year: 'numeric',
|
|
369
393
|
month: 'long',
|
|
370
394
|
day: 'numeric'
|
|
@@ -3,6 +3,9 @@ import { getCollection } from 'astro:content';
|
|
|
3
3
|
import PageLayout from '@jet-w/astro-blog/layouts/PageLayout.astro';
|
|
4
4
|
import PostCard from '@jet-w/astro-blog/components/blog/PostCard.astro';
|
|
5
5
|
import Pagination from '@jet-w/astro-blog/components/ui/Pagination.astro';
|
|
6
|
+
import { i18nConfig as virtualI18nConfig } from 'virtual:astro-blog-i18n';
|
|
7
|
+
import { defaultI18nConfig } from '../../../config/i18n';
|
|
8
|
+
import { getLocaleFromPath, getLocaleConfig, getLocalePrefix, filterPostsByLocale } from '../../../utils/i18n';
|
|
6
9
|
|
|
7
10
|
export async function getStaticPaths() {
|
|
8
11
|
const allPosts = await getCollection('posts', ({ data }) => !data.draft);
|
|
@@ -35,8 +38,18 @@ export async function getStaticPaths() {
|
|
|
35
38
|
|
|
36
39
|
const { year, month, count } = Astro.props;
|
|
37
40
|
|
|
41
|
+
// Get i18n config
|
|
42
|
+
const i18nConfig = virtualI18nConfig || defaultI18nConfig;
|
|
43
|
+
const currentLocale = getLocaleFromPath(Astro.url.pathname, i18nConfig);
|
|
44
|
+
const localeConfig = getLocaleConfig(currentLocale, i18nConfig);
|
|
45
|
+
const ui = localeConfig.ui;
|
|
46
|
+
const localePrefix = getLocalePrefix(currentLocale, i18nConfig);
|
|
47
|
+
|
|
38
48
|
// 获取该月的所有文章
|
|
39
|
-
const
|
|
49
|
+
const allPostsCollection = await getCollection('posts', ({ data }) => !data.draft);
|
|
50
|
+
|
|
51
|
+
// Filter posts by current locale
|
|
52
|
+
const allPosts = filterPostsByLocale(allPostsCollection, currentLocale, i18nConfig);
|
|
40
53
|
|
|
41
54
|
const filteredPosts = allPosts
|
|
42
55
|
.filter(post => {
|
|
@@ -62,25 +75,28 @@ const posts = filteredPosts.slice(startIndex, endIndex);
|
|
|
62
75
|
|
|
63
76
|
// 月份名称
|
|
64
77
|
const monthNames = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'];
|
|
65
|
-
const
|
|
78
|
+
const monthNamesEn = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
|
79
|
+
const monthName = currentLocale.startsWith('en') ? monthNamesEn[month - 1] : monthNames[month - 1];
|
|
80
|
+
const archiveTitle = currentLocale.startsWith('en') ? `${monthName} ${year}` : `${year}年${monthName}`;
|
|
66
81
|
---
|
|
67
82
|
|
|
68
83
|
<PageLayout
|
|
69
|
-
title={
|
|
70
|
-
description={`${
|
|
84
|
+
title={`${ui.archives}: ${archiveTitle}`}
|
|
85
|
+
description={`${ui.archives} ${archiveTitle}`}
|
|
71
86
|
showSidebar={true}
|
|
87
|
+
i18nConfig={i18nConfig}
|
|
72
88
|
>
|
|
73
89
|
<!-- 面包屑导航 -->
|
|
74
90
|
<nav class="flex items-center space-x-2 text-sm text-slate-600 dark:text-slate-400 mb-8">
|
|
75
|
-
<a href=
|
|
91
|
+
<a href={`${localePrefix}/`} class="hover:text-primary-500 transition-colors">{ui.home}</a>
|
|
76
92
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
77
93
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
|
78
94
|
</svg>
|
|
79
|
-
<a href=
|
|
95
|
+
<a href={`${localePrefix}/archives`} class="hover:text-primary-500 transition-colors">{ui.archives}</a>
|
|
80
96
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
81
97
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
|
82
98
|
</svg>
|
|
83
|
-
<span class="text-slate-900 dark:text-slate-100">{
|
|
99
|
+
<span class="text-slate-900 dark:text-slate-100">{archiveTitle}</span>
|
|
84
100
|
</nav>
|
|
85
101
|
|
|
86
102
|
<!-- 页面头部 -->
|
|
@@ -92,22 +108,22 @@ const monthName = monthNames[month - 1];
|
|
|
92
108
|
</div>
|
|
93
109
|
|
|
94
110
|
<h1 class="text-4xl font-bold text-slate-900 dark:text-slate-100 mb-4">
|
|
95
|
-
{
|
|
111
|
+
{archiveTitle}
|
|
96
112
|
</h1>
|
|
97
113
|
|
|
98
114
|
<p class="text-xl text-slate-600 dark:text-slate-400 mb-8">
|
|
99
|
-
|
|
115
|
+
{totalPosts} {ui.posts}
|
|
100
116
|
</p>
|
|
101
117
|
|
|
102
118
|
<!-- 归档统计 -->
|
|
103
119
|
<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>{
|
|
120
|
+
<span>{archiveTitle}</span>
|
|
105
121
|
<span>•</span>
|
|
106
|
-
<span>{totalPosts}
|
|
122
|
+
<span>{totalPosts} {ui.posts}</span>
|
|
107
123
|
{totalPages > 1 && (
|
|
108
124
|
<>
|
|
109
125
|
<span>•</span>
|
|
110
|
-
<span
|
|
126
|
+
<span>{totalPages} {ui.page}</span>
|
|
111
127
|
</>
|
|
112
128
|
)}
|
|
113
129
|
</div>
|
|
@@ -129,6 +145,9 @@ const monthName = monthNames[month - 1];
|
|
|
129
145
|
image: post.data.image
|
|
130
146
|
}}
|
|
131
147
|
layout="horizontal"
|
|
148
|
+
localePrefix={localePrefix}
|
|
149
|
+
locale={localeConfig.locale.dateLocale}
|
|
150
|
+
ui={ui}
|
|
132
151
|
/>
|
|
133
152
|
))}
|
|
134
153
|
</div>
|
|
@@ -136,13 +155,13 @@ const monthName = monthNames[month - 1];
|
|
|
136
155
|
<div class="text-center py-16">
|
|
137
156
|
<div class="text-6xl mb-4">📅</div>
|
|
138
157
|
<h3 class="text-xl font-semibold text-slate-900 dark:text-slate-100 mb-2">
|
|
139
|
-
|
|
158
|
+
{ui.noPostsFound}
|
|
140
159
|
</h3>
|
|
141
160
|
<p class="text-slate-600 dark:text-slate-400 mb-6">
|
|
142
|
-
{
|
|
161
|
+
{ui.noPostsFound}
|
|
143
162
|
</p>
|
|
144
|
-
<a href=
|
|
145
|
-
|
|
163
|
+
<a href={`${localePrefix}/archives`} class="btn-secondary">
|
|
164
|
+
{ui.archives}
|
|
146
165
|
</a>
|
|
147
166
|
</div>
|
|
148
167
|
)}
|
|
@@ -152,7 +171,7 @@ const monthName = monthNames[month - 1];
|
|
|
152
171
|
<Pagination
|
|
153
172
|
currentPage={currentPage}
|
|
154
173
|
totalPages={totalPages}
|
|
155
|
-
baseUrl={
|
|
174
|
+
baseUrl={`${localePrefix}/archives/${year}/${String(month).padStart(2, '0')}`}
|
|
156
175
|
/>
|
|
157
176
|
)}
|
|
158
177
|
</PageLayout>
|
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { getCollection } from 'astro:content';
|
|
3
3
|
import PageLayout from '@jet-w/astro-blog/layouts/PageLayout.astro';
|
|
4
|
+
import { i18nConfig as virtualI18nConfig } from 'virtual:astro-blog-i18n';
|
|
5
|
+
import { defaultI18nConfig } from '../../config/i18n';
|
|
6
|
+
import { getLocaleFromPath, getLocaleConfig, getLocalePrefix, filterPostsByLocale } from '../../utils/i18n';
|
|
7
|
+
|
|
8
|
+
// Get i18n config
|
|
9
|
+
const i18nConfig = virtualI18nConfig || defaultI18nConfig;
|
|
10
|
+
const currentLocale = getLocaleFromPath(Astro.url.pathname, i18nConfig);
|
|
11
|
+
const localeConfig = getLocaleConfig(currentLocale, i18nConfig);
|
|
12
|
+
const ui = localeConfig.ui;
|
|
13
|
+
const localePrefix = getLocalePrefix(currentLocale, i18nConfig);
|
|
4
14
|
|
|
5
15
|
// 获取所有非草稿文章
|
|
6
|
-
const
|
|
16
|
+
const allPostsCollection = await getCollection('posts', ({ data }) => !data.draft);
|
|
17
|
+
|
|
18
|
+
// Filter posts by current locale
|
|
19
|
+
const allPosts = filterPostsByLocale(allPostsCollection, currentLocale, i18nConfig);
|
|
7
20
|
|
|
8
21
|
// 按年月分组文章
|
|
9
22
|
interface ArchiveMonth {
|
|
@@ -26,7 +39,9 @@ interface ArchiveYear {
|
|
|
26
39
|
const archiveData = new Map<number, Map<number, ArchiveMonth>>();
|
|
27
40
|
|
|
28
41
|
// 月份名称
|
|
29
|
-
const
|
|
42
|
+
const monthNamesZh = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'];
|
|
43
|
+
const monthNamesEn = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
|
44
|
+
const monthNames = currentLocale === 'zh-CN' ? monthNamesZh : monthNamesEn;
|
|
30
45
|
|
|
31
46
|
allPosts.forEach(post => {
|
|
32
47
|
// pubDate 现在通过 schema transform 统一包含 date 或 pubDate 的值
|
|
@@ -82,17 +97,18 @@ const totalPosts = archives.reduce((sum, y) => sum + y.totalCount, 0);
|
|
|
82
97
|
---
|
|
83
98
|
|
|
84
99
|
<PageLayout
|
|
85
|
-
title=
|
|
86
|
-
description=
|
|
100
|
+
title={ui.archives}
|
|
101
|
+
description={ui.archives}
|
|
87
102
|
showSidebar={true}
|
|
103
|
+
i18nConfig={i18nConfig}
|
|
88
104
|
>
|
|
89
105
|
<!-- 面包屑导航 -->
|
|
90
106
|
<nav class="flex items-center space-x-2 text-sm text-slate-600 dark:text-slate-400 mb-8">
|
|
91
|
-
<a href=
|
|
107
|
+
<a href={`${localePrefix}/`} class="hover:text-primary-500 transition-colors">{ui.home}</a>
|
|
92
108
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
93
109
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
|
94
110
|
</svg>
|
|
95
|
-
<span class="text-slate-900 dark:text-slate-100"
|
|
111
|
+
<span class="text-slate-900 dark:text-slate-100">{ui.archives}</span>
|
|
96
112
|
</nav>
|
|
97
113
|
|
|
98
114
|
<!-- 页面头部 -->
|
|
@@ -104,11 +120,11 @@ const totalPosts = archives.reduce((sum, y) => sum + y.totalCount, 0);
|
|
|
104
120
|
</div>
|
|
105
121
|
|
|
106
122
|
<h1 class="text-4xl font-bold text-slate-900 dark:text-slate-100 mb-4">
|
|
107
|
-
|
|
123
|
+
{ui.archives}
|
|
108
124
|
</h1>
|
|
109
125
|
|
|
110
126
|
<p class="text-xl text-slate-600 dark:text-slate-400">
|
|
111
|
-
|
|
127
|
+
{totalPosts} {ui.posts} • {archives.length} {currentLocale === 'zh-CN' ? '年' : 'years'}
|
|
112
128
|
</p>
|
|
113
129
|
</div>
|
|
114
130
|
|
|
@@ -123,9 +139,9 @@ const totalPosts = archives.reduce((sum, y) => sum + y.totalCount, 0);
|
|
|
123
139
|
<div class="relative flex items-center mb-6">
|
|
124
140
|
<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
141
|
<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}年
|
|
142
|
+
{yearData.year}{currentLocale === 'zh-CN' ? '年' : ''}
|
|
127
143
|
<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}
|
|
144
|
+
{yearData.totalCount} {ui.posts}
|
|
129
145
|
</span>
|
|
130
146
|
</h2>
|
|
131
147
|
</div>
|
|
@@ -140,14 +156,14 @@ const totalPosts = archives.reduce((sum, y) => sum + y.totalCount, 0);
|
|
|
140
156
|
<div class="bg-white dark:bg-slate-800 rounded-lg border border-slate-200 dark:border-slate-700 overflow-hidden">
|
|
141
157
|
<!-- 月份标题 -->
|
|
142
158
|
<a
|
|
143
|
-
href={
|
|
159
|
+
href={`${localePrefix}/archives/${yearData.year}/${String(monthData.month).padStart(2, '0')}`}
|
|
144
160
|
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
161
|
>
|
|
146
162
|
<span class="font-semibold text-slate-900 dark:text-slate-100">
|
|
147
163
|
{monthData.monthName}
|
|
148
164
|
</span>
|
|
149
165
|
<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}
|
|
166
|
+
{monthData.count} {ui.posts}
|
|
151
167
|
</span>
|
|
152
168
|
</a>
|
|
153
169
|
|
|
@@ -155,23 +171,23 @@ const totalPosts = archives.reduce((sum, y) => sum + y.totalCount, 0);
|
|
|
155
171
|
<div class="divide-y divide-slate-100 dark:divide-slate-700">
|
|
156
172
|
{monthData.posts.slice(0, 5).map((post) => (
|
|
157
173
|
<a
|
|
158
|
-
href={
|
|
174
|
+
href={`${localePrefix}/posts/${post.id.toLowerCase()}`}
|
|
159
175
|
class="flex items-center justify-between p-4 hover:bg-slate-50 dark:hover:bg-slate-700/30 transition-colors"
|
|
160
176
|
>
|
|
161
177
|
<span class="text-slate-700 dark:text-slate-300 hover:text-primary-500 transition-colors line-clamp-1">
|
|
162
178
|
{post.title}
|
|
163
179
|
</span>
|
|
164
180
|
<time class="text-xs text-slate-500 dark:text-slate-400 ml-4 flex-shrink-0">
|
|
165
|
-
{post.pubDate.getDate()}日
|
|
181
|
+
{post.pubDate.getDate()}{currentLocale === 'zh-CN' ? '日' : ''}
|
|
166
182
|
</time>
|
|
167
183
|
</a>
|
|
168
184
|
))}
|
|
169
185
|
{monthData.posts.length > 5 && (
|
|
170
186
|
<a
|
|
171
|
-
href={
|
|
187
|
+
href={`${localePrefix}/archives/${yearData.year}/${String(monthData.month).padStart(2, '0')}`}
|
|
172
188
|
class="block p-4 text-center text-sm text-primary-500 hover:bg-slate-50 dark:hover:bg-slate-700/30 transition-colors"
|
|
173
189
|
>
|
|
174
|
-
|
|
190
|
+
{currentLocale === 'zh-CN' ? `查看全部 ${monthData.count} 篇文章` : `View all ${monthData.count} posts`} →
|
|
175
191
|
</a>
|
|
176
192
|
)}
|
|
177
193
|
</div>
|
|
@@ -188,13 +204,13 @@ const totalPosts = archives.reduce((sum, y) => sum + y.totalCount, 0);
|
|
|
188
204
|
<div class="text-center py-16">
|
|
189
205
|
<div class="text-6xl mb-4">📅</div>
|
|
190
206
|
<h3 class="text-xl font-semibold text-slate-900 dark:text-slate-100 mb-2">
|
|
191
|
-
|
|
207
|
+
{ui.noPostsFound}
|
|
192
208
|
</h3>
|
|
193
209
|
<p class="text-slate-600 dark:text-slate-400 mb-6">
|
|
194
|
-
还没有发布任何文章
|
|
210
|
+
{currentLocale === 'zh-CN' ? '还没有发布任何文章' : 'No posts published yet'}
|
|
195
211
|
</p>
|
|
196
|
-
<a href=
|
|
197
|
-
|
|
212
|
+
<a href={`${localePrefix}/`} class="btn-primary">
|
|
213
|
+
{ui.home}
|
|
198
214
|
</a>
|
|
199
215
|
</div>
|
|
200
216
|
)}
|
|
@@ -3,6 +3,9 @@ import { getCollection } from 'astro:content';
|
|
|
3
3
|
import PageLayout from '@jet-w/astro-blog/layouts/PageLayout.astro';
|
|
4
4
|
import PostCard from '@jet-w/astro-blog/components/blog/PostCard.astro';
|
|
5
5
|
import Pagination from '@jet-w/astro-blog/components/ui/Pagination.astro';
|
|
6
|
+
import { i18nConfig as virtualI18nConfig } from 'virtual:astro-blog-i18n';
|
|
7
|
+
import { defaultI18nConfig } from '../../config/i18n';
|
|
8
|
+
import { getLocaleFromPath, getLocaleConfig, getLocalePrefix, filterPostsByLocale } from '../../utils/i18n';
|
|
6
9
|
|
|
7
10
|
export async function getStaticPaths() {
|
|
8
11
|
const allPosts = await getCollection('posts', ({ data }) => !data.draft);
|
|
@@ -30,8 +33,18 @@ export async function getStaticPaths() {
|
|
|
30
33
|
|
|
31
34
|
const { categorySlug, categoryName } = Astro.props;
|
|
32
35
|
|
|
36
|
+
// Get i18n config
|
|
37
|
+
const i18nConfig = virtualI18nConfig || defaultI18nConfig;
|
|
38
|
+
const currentLocale = getLocaleFromPath(Astro.url.pathname, i18nConfig);
|
|
39
|
+
const localeConfig = getLocaleConfig(currentLocale, i18nConfig);
|
|
40
|
+
const ui = localeConfig.ui;
|
|
41
|
+
const localePrefix = getLocalePrefix(currentLocale, i18nConfig);
|
|
42
|
+
|
|
33
43
|
// 获取所有文章并筛选包含该分类的文章
|
|
34
|
-
const
|
|
44
|
+
const allPostsCollection = await getCollection('posts', ({ data }) => !data.draft);
|
|
45
|
+
|
|
46
|
+
// Filter posts by current locale
|
|
47
|
+
const allPosts = filterPostsByLocale(allPostsCollection, currentLocale, i18nConfig);
|
|
35
48
|
|
|
36
49
|
// 筛选包含该分类的文章
|
|
37
50
|
const filteredPosts = allPosts
|
|
@@ -75,17 +88,18 @@ const relatedCategories = Array.from(relatedCategoryMap.entries())
|
|
|
75
88
|
---
|
|
76
89
|
|
|
77
90
|
<PageLayout
|
|
78
|
-
title={
|
|
79
|
-
description={
|
|
91
|
+
title={`${ui.inCategory}: ${categoryName}`}
|
|
92
|
+
description={`${ui.inCategory} ${categoryName}`}
|
|
80
93
|
showSidebar={true}
|
|
94
|
+
i18nConfig={i18nConfig}
|
|
81
95
|
>
|
|
82
96
|
<!-- 面包屑导航 -->
|
|
83
97
|
<nav class="flex items-center space-x-2 text-sm text-slate-600 dark:text-slate-400 mb-8">
|
|
84
|
-
<a href=
|
|
98
|
+
<a href={`${localePrefix}/`} class="hover:text-primary-500 transition-colors">{ui.home}</a>
|
|
85
99
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
86
100
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
|
87
101
|
</svg>
|
|
88
|
-
<a href=
|
|
102
|
+
<a href={`${localePrefix}/categories`} class="hover:text-primary-500 transition-colors">{ui.allCategories}</a>
|
|
89
103
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
90
104
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
|
91
105
|
</svg>
|
|
@@ -105,18 +119,18 @@ const relatedCategories = Array.from(relatedCategoryMap.entries())
|
|
|
105
119
|
</h1>
|
|
106
120
|
|
|
107
121
|
<p class="text-xl text-slate-600 dark:text-slate-400 mb-8 max-w-2xl mx-auto">
|
|
108
|
-
|
|
122
|
+
{totalPosts} {ui.posts}
|
|
109
123
|
</p>
|
|
110
124
|
|
|
111
125
|
<!-- 分类统计 -->
|
|
112
126
|
<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
127
|
<span>{categoryName}</span>
|
|
114
128
|
<span>•</span>
|
|
115
|
-
<span>{totalPosts}
|
|
129
|
+
<span>{totalPosts} {ui.posts}</span>
|
|
116
130
|
{totalPages > 1 && (
|
|
117
131
|
<>
|
|
118
132
|
<span>•</span>
|
|
119
|
-
<span
|
|
133
|
+
<span>{totalPages} {ui.page}</span>
|
|
120
134
|
</>
|
|
121
135
|
)}
|
|
122
136
|
</div>
|
|
@@ -138,6 +152,9 @@ const relatedCategories = Array.from(relatedCategoryMap.entries())
|
|
|
138
152
|
image: post.data.image
|
|
139
153
|
}}
|
|
140
154
|
layout="horizontal"
|
|
155
|
+
localePrefix={localePrefix}
|
|
156
|
+
locale={localeConfig.locale.dateLocale}
|
|
157
|
+
ui={ui}
|
|
141
158
|
/>
|
|
142
159
|
))}
|
|
143
160
|
</div>
|
|
@@ -145,13 +162,13 @@ const relatedCategories = Array.from(relatedCategoryMap.entries())
|
|
|
145
162
|
<div class="text-center py-16">
|
|
146
163
|
<div class="text-6xl mb-4">📂</div>
|
|
147
164
|
<h3 class="text-xl font-semibold text-slate-900 dark:text-slate-100 mb-2">
|
|
148
|
-
|
|
165
|
+
{ui.noPostsFound}
|
|
149
166
|
</h3>
|
|
150
167
|
<p class="text-slate-600 dark:text-slate-400 mb-6">
|
|
151
|
-
|
|
168
|
+
{ui.noPostsFound}
|
|
152
169
|
</p>
|
|
153
|
-
<a href=
|
|
154
|
-
|
|
170
|
+
<a href={`${localePrefix}/categories`} class="btn-secondary">
|
|
171
|
+
{ui.allCategories}
|
|
155
172
|
</a>
|
|
156
173
|
</div>
|
|
157
174
|
)}
|
|
@@ -161,7 +178,7 @@ const relatedCategories = Array.from(relatedCategoryMap.entries())
|
|
|
161
178
|
<Pagination
|
|
162
179
|
currentPage={currentPage}
|
|
163
180
|
totalPages={totalPages}
|
|
164
|
-
baseUrl={
|
|
181
|
+
baseUrl={`${localePrefix}/categories/${categorySlug}`}
|
|
165
182
|
/>
|
|
166
183
|
)}
|
|
167
184
|
|
|
@@ -169,12 +186,12 @@ const relatedCategories = Array.from(relatedCategoryMap.entries())
|
|
|
169
186
|
{relatedCategories.length > 0 && (
|
|
170
187
|
<section class="mt-16 pt-8 border-t border-slate-200 dark:border-slate-700">
|
|
171
188
|
<h2 class="text-2xl font-bold text-slate-900 dark:text-slate-100 mb-6">
|
|
172
|
-
|
|
189
|
+
{ui.categories}
|
|
173
190
|
</h2>
|
|
174
191
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
175
192
|
{relatedCategories.map((relatedCategory) => (
|
|
176
193
|
<a
|
|
177
|
-
href={
|
|
194
|
+
href={`${localePrefix}/categories/${relatedCategory.slug}`}
|
|
178
195
|
class="group card hover:shadow-lg transform hover:-translate-y-1 transition-all duration-300"
|
|
179
196
|
>
|
|
180
197
|
<div class="flex items-center justify-between">
|
|
@@ -183,7 +200,7 @@ const relatedCategories = Array.from(relatedCategoryMap.entries())
|
|
|
183
200
|
{relatedCategory.name}
|
|
184
201
|
</h3>
|
|
185
202
|
<p class="text-sm text-slate-600 dark:text-slate-400">
|
|
186
|
-
{relatedCategory.count}
|
|
203
|
+
{relatedCategory.count} {ui.posts}
|
|
187
204
|
</p>
|
|
188
205
|
</div>
|
|
189
206
|
<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">
|