@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
|
@@ -6,9 +6,25 @@ export interface Props {
|
|
|
6
6
|
tags?: string[];
|
|
7
7
|
categories?: string[];
|
|
8
8
|
};
|
|
9
|
+
ui?: {
|
|
10
|
+
relatedPosts?: string;
|
|
11
|
+
readMore?: string;
|
|
12
|
+
minuteRead?: string;
|
|
13
|
+
postList?: string;
|
|
14
|
+
};
|
|
15
|
+
localePrefix?: string;
|
|
9
16
|
}
|
|
10
17
|
|
|
11
|
-
const {
|
|
18
|
+
const {
|
|
19
|
+
currentPost,
|
|
20
|
+
ui = {
|
|
21
|
+
relatedPosts: '相关文章',
|
|
22
|
+
readMore: '阅读更多',
|
|
23
|
+
minuteRead: '分钟',
|
|
24
|
+
postList: '查看更多文章',
|
|
25
|
+
},
|
|
26
|
+
localePrefix = '',
|
|
27
|
+
} = Astro.props;
|
|
12
28
|
|
|
13
29
|
// 模拟获取相关文章
|
|
14
30
|
const allPosts = [
|
|
@@ -83,14 +99,14 @@ const formattedDate = (date: Date) => new Intl.DateTimeFormat('zh-CN', {
|
|
|
83
99
|
{relatedPosts.length > 0 && (
|
|
84
100
|
<section class="mt-16">
|
|
85
101
|
<h2 class="text-2xl font-bold text-slate-900 dark:text-slate-100 mb-8">
|
|
86
|
-
|
|
102
|
+
{ui.relatedPosts}
|
|
87
103
|
</h2>
|
|
88
104
|
|
|
89
105
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
90
106
|
{relatedPosts.map((post) => (
|
|
91
107
|
<article class="group">
|
|
92
108
|
<a
|
|
93
|
-
href={
|
|
109
|
+
href={`${localePrefix}/posts/${post.slug}`}
|
|
94
110
|
class="card hover:shadow-lg transform hover:-translate-y-1 transition-all duration-300 block h-full"
|
|
95
111
|
>
|
|
96
112
|
<div class="flex flex-col h-full">
|
|
@@ -100,7 +116,7 @@ const formattedDate = (date: Date) => new Intl.DateTimeFormat('zh-CN', {
|
|
|
100
116
|
{formattedDate(post.pubDate)}
|
|
101
117
|
</time>
|
|
102
118
|
{post.readingTime && (
|
|
103
|
-
<span>{post.readingTime}
|
|
119
|
+
<span>{post.readingTime} {ui.minuteRead}</span>
|
|
104
120
|
)}
|
|
105
121
|
</div>
|
|
106
122
|
|
|
@@ -132,7 +148,7 @@ const formattedDate = (date: Date) => new Intl.DateTimeFormat('zh-CN', {
|
|
|
132
148
|
|
|
133
149
|
<!-- 阅读链接 -->
|
|
134
150
|
<div class="flex items-center text-primary-500 group-hover:text-primary-600 transition-colors text-sm font-medium">
|
|
135
|
-
<span
|
|
151
|
+
<span>{ui.readMore}</span>
|
|
136
152
|
<svg class="w-4 h-4 ml-1 group-hover:translate-x-1 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
137
153
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
|
138
154
|
</svg>
|
|
@@ -146,10 +162,10 @@ const formattedDate = (date: Date) => new Intl.DateTimeFormat('zh-CN', {
|
|
|
146
162
|
<!-- 查看更多按钮 -->
|
|
147
163
|
<div class="text-center mt-8">
|
|
148
164
|
<a
|
|
149
|
-
href=
|
|
165
|
+
href={`${localePrefix}/posts`}
|
|
150
166
|
class="btn-secondary inline-flex items-center space-x-2"
|
|
151
167
|
>
|
|
152
|
-
<span
|
|
168
|
+
<span>{ui.postList}</span>
|
|
153
169
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
154
170
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
|
155
171
|
</svg>
|
|
@@ -5,15 +5,21 @@ export interface Props {
|
|
|
5
5
|
text: string;
|
|
6
6
|
id: string;
|
|
7
7
|
}>;
|
|
8
|
+
tocTitle?: string;
|
|
9
|
+
progressTitle?: string;
|
|
8
10
|
}
|
|
9
11
|
|
|
10
|
-
const {
|
|
12
|
+
const {
|
|
13
|
+
headings,
|
|
14
|
+
tocTitle = 'Table of Contents',
|
|
15
|
+
progressTitle = 'Reading Progress',
|
|
16
|
+
} = Astro.props;
|
|
11
17
|
---
|
|
12
18
|
|
|
13
19
|
{headings.length > 0 && (
|
|
14
|
-
<nav class="sticky top-20 p-6" aria-label=
|
|
20
|
+
<nav class="sticky top-20 p-6" aria-label={tocTitle}>
|
|
15
21
|
<h3 class="text-lg font-semibold text-slate-900 dark:text-slate-100 mb-4 border-b border-slate-200 dark:border-slate-700 pb-2">
|
|
16
|
-
|
|
22
|
+
{tocTitle}
|
|
17
23
|
</h3>
|
|
18
24
|
|
|
19
25
|
<ol class="space-y-2 text-sm">
|
|
@@ -38,7 +44,7 @@ const { headings } = Astro.props;
|
|
|
38
44
|
|
|
39
45
|
<!-- 进度指示器 -->
|
|
40
46
|
<div class="mt-8 pt-4 border-t border-slate-200 dark:border-slate-700">
|
|
41
|
-
<div class="text-xs text-slate-500 dark:text-slate-400 mb-2"
|
|
47
|
+
<div class="text-xs text-slate-500 dark:text-slate-400 mb-2">{progressTitle}</div>
|
|
42
48
|
<div class="w-full bg-slate-200 dark:bg-slate-700 rounded-full h-2">
|
|
43
49
|
<div class="bg-primary-500 h-2 rounded-full transition-all duration-300" id="reading-progress" style="width: 0%"></div>
|
|
44
50
|
</div>
|
|
@@ -7,9 +7,10 @@ export interface Props {
|
|
|
7
7
|
}>;
|
|
8
8
|
maxSize?: number;
|
|
9
9
|
minSize?: number;
|
|
10
|
+
localePrefix?: string;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
const { tags, maxSize = 2.5, minSize = 0.8 } = Astro.props;
|
|
13
|
+
const { tags, maxSize = 2.5, minSize = 0.8, localePrefix = '' } = Astro.props;
|
|
13
14
|
|
|
14
15
|
// 计算字体大小
|
|
15
16
|
const maxCount = Math.max(...tags.map(tag => tag.count));
|
|
@@ -48,10 +49,10 @@ const shuffledTags = [...tags].sort(() => Math.random() - 0.5);
|
|
|
48
49
|
<div class="flex flex-wrap items-center justify-center gap-3 leading-relaxed">
|
|
49
50
|
{shuffledTags.map((tag, index) => (
|
|
50
51
|
<a
|
|
51
|
-
href={
|
|
52
|
+
href={`${localePrefix}/tags/${tag.slug}`}
|
|
52
53
|
class={`inline-block font-medium transition-all duration-300 hover:scale-110 ${getColorClass(index)}`}
|
|
53
54
|
style={`font-size: ${getFontSize(tag.count)}rem`}
|
|
54
|
-
title={`${tag.name} (${tag.count}
|
|
55
|
+
title={`${tag.name} (${tag.count})`}
|
|
55
56
|
>
|
|
56
57
|
{tag.name}
|
|
57
58
|
</a>
|
|
@@ -4,19 +4,33 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import PostCard from '../../components/blog/PostCard.astro';
|
|
6
6
|
import { getCollection } from 'astro:content';
|
|
7
|
+
import type { I18nConfig } from '../../config/i18n';
|
|
8
|
+
import { defaultI18nConfig } from '../../config/i18n';
|
|
9
|
+
import { i18nConfig as virtualI18nConfig } from 'virtual:astro-blog-i18n';
|
|
10
|
+
import { getLocaleFromPath, getLocaleConfig, getLocalePrefix, filterPostsByLocale } from '../../utils/i18n';
|
|
7
11
|
|
|
8
12
|
export interface Props {
|
|
9
13
|
count?: number;
|
|
14
|
+
i18nConfig?: I18nConfig;
|
|
10
15
|
}
|
|
11
16
|
|
|
12
|
-
const { count = 3 } = Astro.props;
|
|
17
|
+
const { count = 3, i18nConfig = virtualI18nConfig || defaultI18nConfig } = Astro.props;
|
|
18
|
+
|
|
19
|
+
// Get current locale from URL
|
|
20
|
+
const currentLocale = getLocaleFromPath(Astro.url.pathname, i18nConfig);
|
|
21
|
+
const localeConfig = getLocaleConfig(currentLocale, i18nConfig);
|
|
22
|
+
const ui = localeConfig.ui;
|
|
23
|
+
const localePrefix = getLocalePrefix(currentLocale, i18nConfig);
|
|
13
24
|
|
|
14
25
|
const allPosts = await getCollection('posts');
|
|
15
26
|
const publishedPosts = allPosts
|
|
16
27
|
.filter(post => !post.data.draft)
|
|
17
28
|
.sort((a, b) => (b.data.pubDate?.getTime() ?? 0) - (a.data.pubDate?.getTime() ?? 0));
|
|
18
29
|
|
|
19
|
-
|
|
30
|
+
// Filter posts by current locale
|
|
31
|
+
const localePosts = filterPostsByLocale(publishedPosts, currentLocale, i18nConfig);
|
|
32
|
+
|
|
33
|
+
const featuredPosts = localePosts.slice(0, count).map(post => ({
|
|
20
34
|
slug: post.id.toLowerCase(),
|
|
21
35
|
title: post.data.title,
|
|
22
36
|
description: post.data.description,
|
|
@@ -26,19 +40,21 @@ const featuredPosts = publishedPosts.slice(0, count).map(post => ({
|
|
|
26
40
|
author: post.data.author,
|
|
27
41
|
readingTime: 5
|
|
28
42
|
}));
|
|
43
|
+
|
|
44
|
+
const postsUrl = `${localePrefix}/posts`;
|
|
29
45
|
---
|
|
30
46
|
|
|
31
47
|
{featuredPosts.length > 0 && (
|
|
32
48
|
<section class="mb-16">
|
|
33
49
|
<div class="flex items-center justify-between mb-8">
|
|
34
50
|
<h2 class="text-2xl font-bold text-slate-900 dark:text-slate-100">
|
|
35
|
-
|
|
51
|
+
{ui.posts}
|
|
36
52
|
</h2>
|
|
37
53
|
<a
|
|
38
|
-
href=
|
|
54
|
+
href={postsUrl}
|
|
39
55
|
class="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400 font-medium text-sm flex items-center space-x-1 transition-colors"
|
|
40
56
|
>
|
|
41
|
-
<span
|
|
57
|
+
<span>{ui.allPosts}</span>
|
|
42
58
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
43
59
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
|
44
60
|
</svg>
|
|
@@ -47,7 +63,7 @@ const featuredPosts = publishedPosts.slice(0, count).map(post => ({
|
|
|
47
63
|
|
|
48
64
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
49
65
|
{featuredPosts.map((post) => (
|
|
50
|
-
<PostCard post={post} featured={true} />
|
|
66
|
+
<PostCard post={post} featured={true} localePrefix={localePrefix} locale={localeConfig.locale.dateLocale} ui={ui} />
|
|
51
67
|
))}
|
|
52
68
|
</div>
|
|
53
69
|
</section>
|
|
@@ -4,10 +4,29 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import NavigationTabs from '../../components/blog/NavigationTabs.vue';
|
|
6
6
|
import { getCollection } from 'astro:content';
|
|
7
|
+
import type { I18nConfig } from '../../config/i18n';
|
|
8
|
+
import { defaultI18nConfig } from '../../config/i18n';
|
|
9
|
+
import { i18nConfig as virtualI18nConfig } from 'virtual:astro-blog-i18n';
|
|
10
|
+
import { getLocaleFromPath, getLocaleConfig, getLocalePrefix, filterPostsByLocale } from '../../utils/i18n';
|
|
7
11
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
interface Props {
|
|
13
|
+
i18nConfig?: I18nConfig;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const { i18nConfig = virtualI18nConfig || defaultI18nConfig } = Astro.props;
|
|
17
|
+
|
|
18
|
+
// Get current locale and translations
|
|
19
|
+
const currentLocale = getLocaleFromPath(Astro.url.pathname, i18nConfig);
|
|
20
|
+
const localeConfig = getLocaleConfig(currentLocale, i18nConfig);
|
|
21
|
+
const ui = localeConfig.ui;
|
|
22
|
+
const localePrefix = getLocalePrefix(currentLocale, i18nConfig);
|
|
23
|
+
|
|
24
|
+
const allPosts = await getCollection('posts', ({ data }) => !data.draft);
|
|
25
|
+
|
|
26
|
+
// Filter posts by locale
|
|
27
|
+
const localePosts = filterPostsByLocale(allPosts, currentLocale, i18nConfig);
|
|
28
|
+
|
|
29
|
+
const publishedPosts = localePosts
|
|
11
30
|
.sort((a, b) => (b.data.pubDate?.getTime() ?? 0) - (a.data.pubDate?.getTime() ?? 0));
|
|
12
31
|
|
|
13
32
|
const posts = publishedPosts.map(post => ({
|
|
@@ -68,7 +87,7 @@ const timelineData = posts.slice(0, 10).map(post => ({
|
|
|
68
87
|
<section class="mb-16">
|
|
69
88
|
<div class="flex items-center justify-between mb-8">
|
|
70
89
|
<h2 class="text-2xl font-bold text-slate-900 dark:text-slate-100">
|
|
71
|
-
|
|
90
|
+
{ui.quickNavigation}
|
|
72
91
|
</h2>
|
|
73
92
|
</div>
|
|
74
93
|
<NavigationTabs
|
|
@@ -77,5 +96,15 @@ const timelineData = posts.slice(0, 10).map(post => ({
|
|
|
77
96
|
archives={archivesData}
|
|
78
97
|
categories={categoriesData}
|
|
79
98
|
timeline={timelineData}
|
|
99
|
+
localePrefix={localePrefix}
|
|
100
|
+
dateLocale={localeConfig.locale?.dateLocale || currentLocale}
|
|
101
|
+
ui={{
|
|
102
|
+
tags: ui.tags,
|
|
103
|
+
archives: ui.archives,
|
|
104
|
+
categories: ui.categories,
|
|
105
|
+
timeline: ui.timeline,
|
|
106
|
+
viewAllTimeline: ui.viewAllTimeline,
|
|
107
|
+
postsCount: ui.postsCount,
|
|
108
|
+
}}
|
|
80
109
|
/>
|
|
81
110
|
</section>
|
|
@@ -4,19 +4,33 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import PostCard from '../../components/blog/PostCard.astro';
|
|
6
6
|
import { getCollection } from 'astro:content';
|
|
7
|
+
import type { I18nConfig } from '../../config/i18n';
|
|
8
|
+
import { defaultI18nConfig } from '../../config/i18n';
|
|
9
|
+
import { i18nConfig as virtualI18nConfig } from 'virtual:astro-blog-i18n';
|
|
10
|
+
import { getLocaleFromPath, getLocaleConfig, getLocalePrefix, filterPostsByLocale } from '../../utils/i18n';
|
|
7
11
|
|
|
8
12
|
export interface Props {
|
|
9
13
|
count?: number;
|
|
14
|
+
i18nConfig?: I18nConfig;
|
|
10
15
|
}
|
|
11
16
|
|
|
12
|
-
const { count = 6 } = Astro.props;
|
|
17
|
+
const { count = 6, i18nConfig = virtualI18nConfig || defaultI18nConfig } = Astro.props;
|
|
18
|
+
|
|
19
|
+
// Get current locale from URL
|
|
20
|
+
const currentLocale = getLocaleFromPath(Astro.url.pathname, i18nConfig);
|
|
21
|
+
const localeConfig = getLocaleConfig(currentLocale, i18nConfig);
|
|
22
|
+
const ui = localeConfig.ui;
|
|
23
|
+
const localePrefix = getLocalePrefix(currentLocale, i18nConfig);
|
|
13
24
|
|
|
14
25
|
const allPosts = await getCollection('posts');
|
|
15
26
|
const publishedPosts = allPosts
|
|
16
27
|
.filter(post => !post.data.draft)
|
|
17
28
|
.sort((a, b) => (b.data.pubDate?.getTime() ?? 0) - (a.data.pubDate?.getTime() ?? 0));
|
|
18
29
|
|
|
19
|
-
|
|
30
|
+
// Filter posts by current locale
|
|
31
|
+
const localePosts = filterPostsByLocale(publishedPosts, currentLocale, i18nConfig);
|
|
32
|
+
|
|
33
|
+
const recentPosts = localePosts.slice(0, count).map(post => ({
|
|
20
34
|
slug: post.id.toLowerCase(),
|
|
21
35
|
title: post.data.title,
|
|
22
36
|
description: post.data.description,
|
|
@@ -26,18 +40,20 @@ const recentPosts = publishedPosts.slice(0, count).map(post => ({
|
|
|
26
40
|
author: post.data.author,
|
|
27
41
|
readingTime: 5
|
|
28
42
|
}));
|
|
43
|
+
|
|
44
|
+
const postsUrl = `${localePrefix}/posts`;
|
|
29
45
|
---
|
|
30
46
|
|
|
31
47
|
<section class="mb-16">
|
|
32
48
|
<div class="flex items-center justify-between mb-8">
|
|
33
49
|
<h2 class="text-2xl font-bold text-slate-900 dark:text-slate-100">
|
|
34
|
-
|
|
50
|
+
{ui.recentPosts}
|
|
35
51
|
</h2>
|
|
36
52
|
<a
|
|
37
|
-
href=
|
|
53
|
+
href={postsUrl}
|
|
38
54
|
class="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400 font-medium text-sm flex items-center space-x-1 transition-colors"
|
|
39
55
|
>
|
|
40
|
-
<span
|
|
56
|
+
<span>{ui.allPosts}</span>
|
|
41
57
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
42
58
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
|
43
59
|
</svg>
|
|
@@ -46,7 +62,7 @@ const recentPosts = publishedPosts.slice(0, count).map(post => ({
|
|
|
46
62
|
|
|
47
63
|
<div class="space-y-6">
|
|
48
64
|
{recentPosts.map((post) => (
|
|
49
|
-
<PostCard post={post} layout="horizontal" />
|
|
65
|
+
<PostCard post={post} layout="horizontal" localePrefix={localePrefix} />
|
|
50
66
|
))}
|
|
51
67
|
</div>
|
|
52
68
|
</section>
|
|
@@ -3,13 +3,31 @@
|
|
|
3
3
|
* 首页统计信息组件
|
|
4
4
|
*/
|
|
5
5
|
import { getCollection } from 'astro:content';
|
|
6
|
+
import type { I18nConfig } from '../../config/i18n';
|
|
7
|
+
import { defaultI18nConfig } from '../../config/i18n';
|
|
8
|
+
import { i18nConfig as virtualI18nConfig } from 'virtual:astro-blog-i18n';
|
|
9
|
+
import { getLocaleFromPath, getLocaleConfig, filterPostsByLocale } from '../../utils/i18n';
|
|
10
|
+
|
|
11
|
+
export interface Props {
|
|
12
|
+
i18nConfig?: I18nConfig;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const { i18nConfig = virtualI18nConfig || defaultI18nConfig } = Astro.props;
|
|
16
|
+
|
|
17
|
+
// Get current locale from URL
|
|
18
|
+
const currentLocale = getLocaleFromPath(Astro.url.pathname, i18nConfig);
|
|
19
|
+
const localeConfig = getLocaleConfig(currentLocale, i18nConfig);
|
|
20
|
+
const ui = localeConfig.ui;
|
|
6
21
|
|
|
7
22
|
const allPosts = await getCollection('posts');
|
|
8
23
|
const publishedPosts = allPosts.filter(post => !post.data.draft);
|
|
9
24
|
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
25
|
+
// Filter posts by current locale
|
|
26
|
+
const localePosts = filterPostsByLocale(publishedPosts, currentLocale, i18nConfig);
|
|
27
|
+
|
|
28
|
+
const totalPosts = localePosts.length;
|
|
29
|
+
const allTags = new Set(localePosts.flatMap(post => post.data.tags || []));
|
|
30
|
+
const allCategories = new Set(localePosts.flatMap(post => post.data.categories || []));
|
|
13
31
|
const totalTags = allTags.size;
|
|
14
32
|
const totalCategories = allCategories.size;
|
|
15
33
|
---
|
|
@@ -21,7 +39,7 @@ const totalCategories = allCategories.size;
|
|
|
21
39
|
{totalPosts}
|
|
22
40
|
</div>
|
|
23
41
|
<div class="text-sm text-slate-600 dark:text-slate-400">
|
|
24
|
-
|
|
42
|
+
{ui.posts}
|
|
25
43
|
</div>
|
|
26
44
|
</div>
|
|
27
45
|
<div class="card text-center">
|
|
@@ -29,7 +47,7 @@ const totalCategories = allCategories.size;
|
|
|
29
47
|
{totalTags}
|
|
30
48
|
</div>
|
|
31
49
|
<div class="text-sm text-slate-600 dark:text-slate-400">
|
|
32
|
-
|
|
50
|
+
{ui.tags}
|
|
33
51
|
</div>
|
|
34
52
|
</div>
|
|
35
53
|
<div class="card text-center">
|
|
@@ -37,7 +55,7 @@ const totalCategories = allCategories.size;
|
|
|
37
55
|
{totalCategories}
|
|
38
56
|
</div>
|
|
39
57
|
<div class="text-sm text-slate-600 dark:text-slate-400">
|
|
40
|
-
|
|
58
|
+
{ui.categories}
|
|
41
59
|
</div>
|
|
42
60
|
</div>
|
|
43
61
|
</div>
|
|
@@ -6,6 +6,7 @@ import MobileMenu from '../ui/MobileMenu.vue';
|
|
|
6
6
|
import LanguageSwitcher from '../ui/LanguageSwitcher.vue';
|
|
7
7
|
import type { I18nConfig } from '../../config/i18n';
|
|
8
8
|
import { defaultI18nConfig } from '../../config/i18n';
|
|
9
|
+
import { i18nConfig as virtualI18nConfig } from 'virtual:astro-blog-i18n';
|
|
9
10
|
import {
|
|
10
11
|
getLocaleFromPath,
|
|
11
12
|
getLocaleConfig,
|
|
@@ -17,7 +18,7 @@ export interface Props {
|
|
|
17
18
|
i18nConfig?: I18nConfig;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
const { i18nConfig = defaultI18nConfig } = Astro.props;
|
|
21
|
+
const { i18nConfig = virtualI18nConfig || defaultI18nConfig } = Astro.props;
|
|
21
22
|
|
|
22
23
|
const currentPath = Astro.url.pathname;
|
|
23
24
|
const currentLocale = getLocaleFromPath(currentPath, i18nConfig);
|
|
@@ -55,11 +56,13 @@ const localesForSwitcher = i18nConfig.locales.map(l => ({
|
|
|
55
56
|
)}
|
|
56
57
|
<div class="hidden sm:block">
|
|
57
58
|
<h1 class="text-xl font-bold text-slate-900 dark:text-slate-100">
|
|
58
|
-
{localeSiteConfig.title
|
|
59
|
+
{localeSiteConfig.title ?? siteConfig.title}
|
|
59
60
|
</h1>
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
{(localeSiteConfig.description ?? siteConfig.description) && (
|
|
62
|
+
<p class="text-xs text-slate-600 dark:text-slate-400 -mt-1">
|
|
63
|
+
{localeSiteConfig.description ?? siteConfig.description}
|
|
64
|
+
</p>
|
|
65
|
+
)}
|
|
63
66
|
</div>
|
|
64
67
|
</a>
|
|
65
68
|
</div>
|
|
@@ -85,6 +88,7 @@ const localesForSwitcher = i18nConfig.locales.map(l => ({
|
|
|
85
88
|
client:load
|
|
86
89
|
placeholder={ui.searchPlaceholder}
|
|
87
90
|
searchLabel={ui.search}
|
|
91
|
+
noResultsText={ui.noResults}
|
|
88
92
|
/>
|
|
89
93
|
</div>
|
|
90
94
|
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from '@jet-w/astro-blog/utils/sidebar';
|
|
11
11
|
import type { I18nConfig } from '../../config/i18n';
|
|
12
12
|
import { defaultI18nConfig } from '../../config/i18n';
|
|
13
|
-
import { getLocaleFromPath, getLocaleConfig, getLocalePrefix, formatDate } from '../../utils/i18n';
|
|
13
|
+
import { getLocaleFromPath, getLocaleConfig, getLocalePrefix, formatDate, filterPostsByLocale } from '../../utils/i18n';
|
|
14
14
|
|
|
15
15
|
interface Props {
|
|
16
16
|
currentPath?: string;
|
|
@@ -31,26 +31,29 @@ const sidebarConfig = localeConfig.sidebar;
|
|
|
31
31
|
// 获取所有文章
|
|
32
32
|
const allPosts = await getCollection('posts', ({ data }) => !data.draft);
|
|
33
33
|
|
|
34
|
+
// Filter posts by current locale for sidebar widgets
|
|
35
|
+
const localePosts = filterPostsByLocale(allPosts, currentLocale, i18nConfig);
|
|
36
|
+
|
|
34
37
|
// 根据当前路径过滤侧边栏组
|
|
35
38
|
const filteredGroups = filterGroupsByPath(sidebarConfig.groups, currentPath);
|
|
36
39
|
|
|
37
40
|
// 创建过滤后的配置
|
|
38
41
|
const filteredConfig = { ...sidebarConfig, groups: filteredGroups };
|
|
39
42
|
|
|
40
|
-
// 处理侧边栏配置
|
|
41
|
-
const processedGroups = await processSidebarConfig(filteredConfig,
|
|
43
|
+
// 处理侧边栏配置 - 使用按语言过滤后的文章
|
|
44
|
+
const processedGroups = await processSidebarConfig(filteredConfig, localePosts);
|
|
42
45
|
|
|
43
|
-
// 获取其他数据
|
|
46
|
+
// 获取其他数据 - 使用按语言过滤后的文章
|
|
44
47
|
const recentPosts = sidebarConfig.showRecentPosts
|
|
45
|
-
? getRecentPosts(
|
|
48
|
+
? getRecentPosts(localePosts, sidebarConfig.recentPostsCount)
|
|
46
49
|
: [];
|
|
47
50
|
|
|
48
51
|
const popularTags = sidebarConfig.showPopularTags
|
|
49
|
-
? getPopularTags(
|
|
52
|
+
? getPopularTags(localePosts, sidebarConfig.popularTagsCount)
|
|
50
53
|
: [];
|
|
51
54
|
|
|
52
55
|
const archives = sidebarConfig.showArchives
|
|
53
|
-
? getArchives(
|
|
56
|
+
? getArchives(localePosts, sidebarConfig.archivesCount)
|
|
54
57
|
: [];
|
|
55
58
|
---
|
|
56
59
|
|
|
@@ -197,7 +200,7 @@ const archives = sidebarConfig.showArchives
|
|
|
197
200
|
</a>
|
|
198
201
|
) : (
|
|
199
202
|
<a
|
|
200
|
-
href={
|
|
203
|
+
href={`${localePrefix}/posts/${item.slug?.toLowerCase()}`}
|
|
201
204
|
class="tree-file flex items-center gap-2 px-2 py-1.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors"
|
|
202
205
|
title={item.title}
|
|
203
206
|
>
|
|
@@ -238,7 +241,7 @@ const archives = sidebarConfig.showArchives
|
|
|
238
241
|
</a>
|
|
239
242
|
) : (
|
|
240
243
|
<a
|
|
241
|
-
href={
|
|
244
|
+
href={`${localePrefix}/posts/${grandChild.slug?.toLowerCase()}`}
|
|
242
245
|
class="tree-file flex items-center gap-2 px-2 py-1.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors"
|
|
243
246
|
title={grandChild.title}
|
|
244
247
|
>
|
|
@@ -289,7 +292,7 @@ const archives = sidebarConfig.showArchives
|
|
|
289
292
|
</a>
|
|
290
293
|
) : (
|
|
291
294
|
<a
|
|
292
|
-
href={
|
|
295
|
+
href={`${localePrefix}/posts/${child.slug?.toLowerCase()}`}
|
|
293
296
|
class="tree-file flex items-center gap-2 px-2 py-1.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors"
|
|
294
297
|
title={child.title}
|
|
295
298
|
>
|
|
@@ -340,7 +343,7 @@ const archives = sidebarConfig.showArchives
|
|
|
340
343
|
</a>
|
|
341
344
|
) : (
|
|
342
345
|
<a
|
|
343
|
-
href={
|
|
346
|
+
href={`${localePrefix}/posts/${node.slug?.toLowerCase()}`}
|
|
344
347
|
class="tree-file flex items-center gap-2 px-2 py-1.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors"
|
|
345
348
|
title={node.title}
|
|
346
349
|
>
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
<div class="relative">
|
|
3
3
|
<div class="relative">
|
|
4
4
|
<input
|
|
5
|
+
ref="searchInputRef"
|
|
5
6
|
v-model="searchQuery"
|
|
6
7
|
@input="handleInput"
|
|
7
8
|
@focus="handleFocus"
|
|
8
9
|
@blur="handleBlur"
|
|
9
10
|
type="text"
|
|
10
|
-
placeholder="搜索文章..."
|
|
11
|
+
:placeholder="props.placeholder || '搜索文章...'"
|
|
11
12
|
class="w-64 pl-10 pr-4 py-2 text-sm bg-slate-100 dark:bg-slate-800 border border-slate-300 dark:border-slate-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-colors"
|
|
12
13
|
/>
|
|
13
14
|
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
|
@@ -24,7 +25,7 @@
|
|
|
24
25
|
class="absolute top-full left-0 right-0 mt-2 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg shadow-lg z-50 max-h-96 overflow-y-auto"
|
|
25
26
|
>
|
|
26
27
|
<div v-if="searchResults.length === 0 && searchQuery.length > 0" class="p-4 text-center text-slate-500 dark:text-slate-400">
|
|
27
|
-
没有找到相关文章
|
|
28
|
+
{{ props.noResultsText || '没有找到相关文章' }}
|
|
28
29
|
</div>
|
|
29
30
|
<div v-else class="py-2">
|
|
30
31
|
<a
|
|
@@ -55,6 +56,12 @@
|
|
|
55
56
|
<script setup lang="ts">
|
|
56
57
|
import { ref, onMounted } from 'vue'
|
|
57
58
|
|
|
59
|
+
const props = defineProps<{
|
|
60
|
+
placeholder?: string;
|
|
61
|
+
searchLabel?: string;
|
|
62
|
+
noResultsText?: string;
|
|
63
|
+
}>();
|
|
64
|
+
|
|
58
65
|
interface SearchResult {
|
|
59
66
|
title: string
|
|
60
67
|
description: string
|
|
@@ -145,14 +152,15 @@ const highlightText = (text: string) => {
|
|
|
145
152
|
}
|
|
146
153
|
|
|
147
154
|
// 键盘快捷键支持
|
|
155
|
+
const searchInputRef = ref<HTMLInputElement | null>(null)
|
|
156
|
+
|
|
148
157
|
onMounted(() => {
|
|
149
158
|
// Ctrl/Cmd + K 打开搜索
|
|
150
159
|
document.addEventListener('keydown', (e) => {
|
|
151
160
|
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
|
|
152
161
|
e.preventDefault()
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
input.focus()
|
|
162
|
+
if (searchInputRef.value) {
|
|
163
|
+
searchInputRef.value.focus()
|
|
156
164
|
}
|
|
157
165
|
}
|
|
158
166
|
})
|