@jet-w/astro-blog 0.1.6 → 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-6D3XRDNY.js +145 -0
- package/dist/chunk-A2E2VSAQ.js +246 -0
- package/dist/{chunk-GYLSY3OJ.js → chunk-TJTPX2WP.js} +1 -1
- package/dist/config/index.d.ts +3 -47
- package/dist/config/index.js +18 -2
- package/dist/i18n-PgMCFBw0.d.ts +222 -0
- package/dist/index.d.ts +204 -7
- package/dist/index.js +255 -3
- package/dist/integration.d.ts +9 -1
- package/dist/integration.js +2 -1
- package/dist/{sidebar-DNdiCKBw.d.ts → sidebar-Da-W_4Lr.d.ts} +1 -1
- package/dist/utils/sidebar.d.ts +1 -1
- 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/Footer.astro +36 -20
- package/src/components/layout/Header.astro +75 -17
- package/src/components/layout/Sidebar.astro +40 -25
- package/src/components/ui/LanguageSwitcher.vue +183 -0
- package/src/components/ui/SearchBox.vue +13 -5
- package/src/components/ui/SearchInterface.vue +49 -25
- package/src/layouts/BaseLayout.astro +77 -52
- package/src/layouts/PageLayout.astro +22 -27
- package/src/layouts/SlidesLayout.astro +14 -2
- 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/rss.xml.ts +18 -6
- 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 +33 -4
- package/templates/default/content/posts/blog_docs_en/01.get-started/01-intro.md +81 -0
- package/templates/default/content/posts/blog_docs_en/01.get-started/02-install.md +137 -0
- package/templates/default/content/posts/blog_docs_en/01.get-started/03-create-post.md +176 -0
- package/templates/default/content/posts/blog_docs_en/01.get-started/04-structure.md +173 -0
- package/templates/default/content/posts/blog_docs_en/01.get-started/05-deploy.md +208 -0
- package/templates/default/content/posts/blog_docs_en/01.get-started/README.md +52 -0
- package/templates/default/content/posts/blog_docs_en/02.guide/02-containers.md +245 -0
- package/templates/default/content/posts/blog_docs_en/02.guide/03-code-blocks.md +207 -0
- package/templates/default/content/posts/blog_docs_en/02.guide/03-mermaid.md +194 -0
- package/templates/default/content/posts/blog_docs_en/02.guide/04-icons.md +229 -0
- package/templates/default/content/posts/blog_docs_en/02.guide/06-latex.md +233 -0
- package/templates/default/content/posts/blog_docs_en/02.guide/07-video.md +184 -0
- package/templates/default/content/posts/blog_docs_en/02.guide/08-slides.md +359 -0
- package/templates/default/content/posts/blog_docs_en/02.guide/README.md +213 -0
- package/templates/default/content/posts/blog_docs_en/03.config/01-site.md +208 -0
- package/templates/default/content/posts/blog_docs_en/03.config/02-sidebar.md +240 -0
- package/templates/default/content/posts/blog_docs_en/03.config/03-i18n.md +349 -0
- package/templates/default/content/posts/blog_docs_en/03.config/README.md +85 -0
- package/templates/default/content/posts/blog_docs_en/README.md +79 -0
- 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/package-lock.json +9667 -0
- package/templates/default/package.json +1 -1
- package/templates/default/src/config/footer.ts +14 -11
- package/templates/default/src/config/locales/en/footer.ts +17 -0
- package/templates/default/src/config/locales/en/index.ts +20 -0
- package/templates/default/src/config/locales/en/menu.ts +14 -0
- package/templates/default/src/config/locales/en/sidebar.ts +34 -0
- package/templates/default/src/config/locales/en/site.ts +7 -0
- package/templates/default/src/config/locales/en/ui.ts +29 -0
- package/templates/default/src/config/locales/index.ts +7 -0
- package/templates/default/src/config/locales/zh-CN/footer.ts +17 -0
- package/templates/default/src/config/locales/zh-CN/index.ts +20 -0
- package/templates/default/src/config/locales/zh-CN/menu.ts +14 -0
- package/templates/default/src/config/locales/zh-CN/sidebar.ts +34 -0
- package/templates/default/src/config/locales/zh-CN/site.ts +7 -0
- package/templates/default/src/config/locales/zh-CN/ui.ts +29 -0
- package/templates/default/src/config/sidebar.ts +10 -12
- package/templates/default/src/config/site.ts +2 -2
- package/templates/default/src/content.config.ts +15 -3
- package/templates/default/src/env.d.ts +7 -0
- package/dist/chunk-MQXPSOYB.js +0 -124
- 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-slides.mdx +0 -552
- package/templates/default/content/posts/blog_docs/README.md +0 -151
|
@@ -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>
|
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { siteConfig, footerConfig, defaultIcons } from '@jet-w/astro-blog/config';
|
|
3
|
+
import type { I18nConfig } from '../../config/i18n';
|
|
4
|
+
import { defaultI18nConfig } from '../../config/i18n';
|
|
5
|
+
import { getLocaleFromPath, getLocaleConfig, getLocalePrefix } from '../../utils/i18n';
|
|
6
|
+
|
|
7
|
+
export interface Props {
|
|
8
|
+
i18nConfig?: I18nConfig;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { i18nConfig = defaultI18nConfig } = Astro.props;
|
|
12
|
+
|
|
13
|
+
const currentLocale = getLocaleFromPath(Astro.url.pathname, i18nConfig);
|
|
14
|
+
const localeConfig = getLocaleConfig(currentLocale, i18nConfig);
|
|
15
|
+
const ui = localeConfig.ui;
|
|
16
|
+
|
|
17
|
+
// Use locale-specific configs
|
|
18
|
+
const localeSiteConfig = localeConfig.site;
|
|
19
|
+
const localeFooterConfig = localeConfig.footer;
|
|
3
20
|
|
|
4
21
|
const currentYear = new Date().getFullYear();
|
|
5
22
|
|
|
@@ -7,43 +24,44 @@ function getIcon(link: { type: string; icon?: string }): string {
|
|
|
7
24
|
return link.icon || defaultIcons[link.type] || '';
|
|
8
25
|
}
|
|
9
26
|
|
|
10
|
-
//
|
|
11
|
-
const
|
|
27
|
+
// Get locale prefix for RSS link
|
|
28
|
+
const localePrefix = getLocalePrefix(currentLocale, i18nConfig);
|
|
29
|
+
const rssUrl = localeFooterConfig.rssUrl || footerConfig.rssUrl || `${localePrefix}/rss.xml`;
|
|
30
|
+
|
|
31
|
+
const copyright = (localeFooterConfig.copyright || footerConfig.copyright)
|
|
12
32
|
.replace('{year}', String(currentYear))
|
|
13
|
-
.replace('{author}', siteConfig.author);
|
|
33
|
+
.replace('{author}', localeSiteConfig.author || siteConfig.author);
|
|
14
34
|
---
|
|
15
35
|
|
|
16
36
|
<footer class="bg-slate-50 dark:bg-slate-800 border-t border-slate-200 dark:border-slate-700 mt-auto w-full min-w-0">
|
|
17
37
|
<div class="container mx-auto px-4 py-6">
|
|
18
38
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
19
|
-
<!-- 站点信息 -->
|
|
20
39
|
<div>
|
|
21
40
|
<div class="flex items-center space-x-3 mb-3">
|
|
22
|
-
{siteConfig.avatar && (
|
|
41
|
+
{(localeSiteConfig.avatar || siteConfig.avatar) && (
|
|
23
42
|
<img
|
|
24
|
-
src={siteConfig.avatar}
|
|
25
|
-
alt={siteConfig.title}
|
|
43
|
+
src={localeSiteConfig.avatar || siteConfig.avatar}
|
|
44
|
+
alt={localeSiteConfig.title || siteConfig.title}
|
|
26
45
|
class="w-8 h-8 rounded-full"
|
|
27
46
|
/>
|
|
28
47
|
)}
|
|
29
48
|
<div>
|
|
30
49
|
<h3 class="text-base font-semibold text-slate-900 dark:text-slate-100">
|
|
31
|
-
{siteConfig.title}
|
|
50
|
+
{localeSiteConfig.title || siteConfig.title}
|
|
32
51
|
</h3>
|
|
33
52
|
<p class="text-xs text-slate-600 dark:text-slate-400">
|
|
34
|
-
{siteConfig.description}
|
|
53
|
+
{localeSiteConfig.description || siteConfig.description}
|
|
35
54
|
</p>
|
|
36
55
|
</div>
|
|
37
56
|
</div>
|
|
38
57
|
</div>
|
|
39
58
|
|
|
40
|
-
<!-- 快速链接 -->
|
|
41
59
|
<div>
|
|
42
60
|
<h4 class="text-sm font-semibold text-slate-900 dark:text-slate-100 mb-3">
|
|
43
|
-
{footerConfig.quickLinksTitle}
|
|
61
|
+
{localeFooterConfig.quickLinksTitle || footerConfig.quickLinksTitle || ui.quickLinks}
|
|
44
62
|
</h4>
|
|
45
63
|
<div class="flex flex-wrap gap-x-4 gap-y-1">
|
|
46
|
-
{footerConfig.quickLinks.map((item) => (
|
|
64
|
+
{(localeFooterConfig.quickLinks || footerConfig.quickLinks).map((item) => (
|
|
47
65
|
<a
|
|
48
66
|
href={item.href}
|
|
49
67
|
class="text-sm text-slate-600 dark:text-slate-400 hover:text-primary-500 transition-colors"
|
|
@@ -54,13 +72,12 @@ const copyright = footerConfig.copyright
|
|
|
54
72
|
</div>
|
|
55
73
|
</div>
|
|
56
74
|
|
|
57
|
-
<!-- 社交链接 -->
|
|
58
75
|
<div>
|
|
59
76
|
<h4 class="text-sm font-semibold text-slate-900 dark:text-slate-100 mb-3">
|
|
60
|
-
{footerConfig.contactTitle}
|
|
77
|
+
{localeFooterConfig.contactTitle || footerConfig.contactTitle || ui.contact}
|
|
61
78
|
</h4>
|
|
62
79
|
<div class="flex space-x-4">
|
|
63
|
-
{footerConfig.socialLinks.map((link) => (
|
|
80
|
+
{(localeFooterConfig.socialLinks || footerConfig.socialLinks).map((link) => (
|
|
64
81
|
<a
|
|
65
82
|
href={link.url}
|
|
66
83
|
target={link.type === 'email' ? undefined : '_blank'}
|
|
@@ -74,11 +91,11 @@ const copyright = footerConfig.copyright
|
|
|
74
91
|
</a>
|
|
75
92
|
))}
|
|
76
93
|
|
|
77
|
-
{footerConfig.showRss && (
|
|
94
|
+
{(localeFooterConfig.showRss ?? footerConfig.showRss) && (
|
|
78
95
|
<a
|
|
79
|
-
href={
|
|
96
|
+
href={rssUrl}
|
|
80
97
|
class="text-slate-600 dark:text-slate-400 hover:text-primary-500 transition-colors"
|
|
81
|
-
aria-label=
|
|
98
|
+
aria-label={ui.rssFeed}
|
|
82
99
|
>
|
|
83
100
|
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
|
|
84
101
|
<path d="M3.429 2.486c9.36 0 16.943 7.583 16.943 16.943h-3.257c0-7.563-6.123-13.686-13.686-13.686V2.486zM3.429 9.114c5.647 0 10.229 4.581 10.229 10.229h-3.257c0-3.844-3.128-6.972-6.972-6.972V9.114zM6.686 16.486c0 1.8-1.457 3.257-3.257 3.257S0.172 18.286 0.172 16.486s1.457-3.257 3.257-3.257 3.257 1.457 3.257 3.257z"/>
|
|
@@ -89,12 +106,11 @@ const copyright = footerConfig.copyright
|
|
|
89
106
|
</div>
|
|
90
107
|
</div>
|
|
91
108
|
|
|
92
|
-
<!-- 版权信息 -->
|
|
93
109
|
<div class="border-t border-slate-200 dark:border-slate-700 pt-4 mt-6">
|
|
94
110
|
<div class="flex flex-col sm:flex-row justify-between items-center text-sm text-slate-600 dark:text-slate-400">
|
|
95
111
|
<p>{copyright}</p>
|
|
96
112
|
<p class="mt-2 sm:mt-0">
|
|
97
|
-
Powered by <a href={footerConfig.poweredBy.url} class="link" target="_blank" rel="noopener noreferrer">{footerConfig.poweredBy.text}</a>
|
|
113
|
+
Powered by <a href={(localeFooterConfig.poweredBy || footerConfig.poweredBy).url} class="link" target="_blank" rel="noopener noreferrer">{(localeFooterConfig.poweredBy || footerConfig.poweredBy).text}</a>
|
|
98
114
|
</p>
|
|
99
115
|
</div>
|
|
100
116
|
</div>
|
|
@@ -3,37 +3,72 @@ import { siteConfig } from '@jet-w/astro-blog/config';
|
|
|
3
3
|
import ThemeToggle from '../ui/ThemeToggle.vue';
|
|
4
4
|
import SearchBox from '../ui/SearchBox.vue';
|
|
5
5
|
import MobileMenu from '../ui/MobileMenu.vue';
|
|
6
|
+
import LanguageSwitcher from '../ui/LanguageSwitcher.vue';
|
|
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 {
|
|
11
|
+
getLocaleFromPath,
|
|
12
|
+
getLocaleConfig,
|
|
13
|
+
removeLocalePrefix,
|
|
14
|
+
isMultiLanguageEnabled,
|
|
15
|
+
} from '../../utils/i18n';
|
|
16
|
+
|
|
17
|
+
export interface Props {
|
|
18
|
+
i18nConfig?: I18nConfig;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { i18nConfig = virtualI18nConfig || defaultI18nConfig } = Astro.props;
|
|
6
22
|
|
|
7
23
|
const currentPath = Astro.url.pathname;
|
|
24
|
+
const currentLocale = getLocaleFromPath(currentPath, i18nConfig);
|
|
25
|
+
const localeConfig = getLocaleConfig(currentLocale, i18nConfig);
|
|
26
|
+
const ui = localeConfig.ui;
|
|
27
|
+
|
|
28
|
+
// Use locale-specific site config and menu
|
|
29
|
+
const localeSiteConfig = localeConfig.site;
|
|
30
|
+
const menu = localeConfig.menu;
|
|
31
|
+
|
|
32
|
+
// Get base path without locale prefix for language switcher
|
|
33
|
+
const basePath = removeLocalePrefix(currentPath, i18nConfig);
|
|
34
|
+
|
|
35
|
+
// Check if multi-language is enabled
|
|
36
|
+
const showLanguageSwitcher = isMultiLanguageEnabled(i18nConfig);
|
|
37
|
+
|
|
38
|
+
// Prepare locales for language switcher
|
|
39
|
+
const localesForSwitcher = i18nConfig.locales.map(l => ({
|
|
40
|
+
code: l.code,
|
|
41
|
+
name: l.name,
|
|
42
|
+
}));
|
|
8
43
|
---
|
|
9
44
|
|
|
10
45
|
<header class="sticky top-0 z-50 bg-white/80 dark:bg-slate-900/80 backdrop-blur-sm border-b border-slate-200 dark:border-slate-700">
|
|
11
46
|
<div class="container mx-auto px-4">
|
|
12
47
|
<nav class="flex items-center justify-between h-16">
|
|
13
|
-
<!-- Logo 和站点名称 -->
|
|
14
48
|
<div class="flex items-center space-x-4">
|
|
15
49
|
<a href="/" class="flex items-center space-x-3 hover:opacity-80 transition-opacity">
|
|
16
|
-
{siteConfig.avatar && (
|
|
50
|
+
{(localeSiteConfig.avatar || siteConfig.avatar) && (
|
|
17
51
|
<img
|
|
18
|
-
src={siteConfig.avatar}
|
|
19
|
-
alt={siteConfig.title}
|
|
52
|
+
src={localeSiteConfig.avatar || siteConfig.avatar}
|
|
53
|
+
alt={localeSiteConfig.title || siteConfig.title}
|
|
20
54
|
class="w-8 h-8 rounded-full"
|
|
21
55
|
/>
|
|
22
56
|
)}
|
|
23
57
|
<div class="hidden sm:block">
|
|
24
58
|
<h1 class="text-xl font-bold text-slate-900 dark:text-slate-100">
|
|
25
|
-
{siteConfig.title}
|
|
59
|
+
{localeSiteConfig.title ?? siteConfig.title}
|
|
26
60
|
</h1>
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
+
)}
|
|
30
66
|
</div>
|
|
31
67
|
</a>
|
|
32
68
|
</div>
|
|
33
69
|
|
|
34
|
-
<!-- 桌面端导航 -->
|
|
35
70
|
<div class="hidden md:flex items-center space-x-8">
|
|
36
|
-
{
|
|
71
|
+
{menu.map((item) => (
|
|
37
72
|
<a
|
|
38
73
|
href={item.href}
|
|
39
74
|
class={`text-sm font-medium transition-colors hover:text-primary-500 ${
|
|
@@ -47,22 +82,45 @@ const currentPath = Astro.url.pathname;
|
|
|
47
82
|
))}
|
|
48
83
|
</div>
|
|
49
84
|
|
|
50
|
-
<!-- 右侧功能区 -->
|
|
51
85
|
<div class="flex items-center">
|
|
52
|
-
<!-- 搜索框 -->
|
|
53
86
|
<div class="hidden sm:block mr-2">
|
|
54
|
-
<SearchBox
|
|
87
|
+
<SearchBox
|
|
88
|
+
client:load
|
|
89
|
+
placeholder={ui.searchPlaceholder}
|
|
90
|
+
searchLabel={ui.search}
|
|
91
|
+
noResultsText={ui.noResults}
|
|
92
|
+
/>
|
|
55
93
|
</div>
|
|
56
94
|
|
|
95
|
+
{showLanguageSwitcher && (
|
|
96
|
+
<div class="hidden sm:block mr-2">
|
|
97
|
+
<LanguageSwitcher
|
|
98
|
+
client:load
|
|
99
|
+
locales={localesForSwitcher}
|
|
100
|
+
currentLocale={currentLocale}
|
|
101
|
+
currentPath={basePath}
|
|
102
|
+
defaultLocale={i18nConfig.defaultLocale}
|
|
103
|
+
prefixDefaultLocale={i18nConfig.routing.prefixDefaultLocale}
|
|
104
|
+
/>
|
|
105
|
+
</div>
|
|
106
|
+
)}
|
|
107
|
+
|
|
57
108
|
<div class="hidden sm:block mr-2">
|
|
58
|
-
<!-- 主题切换 -->
|
|
59
109
|
<ThemeToggle client:load />
|
|
60
110
|
</div>
|
|
61
|
-
|
|
111
|
+
|
|
62
112
|
<div class="md:hidden">
|
|
63
|
-
<MobileMenu
|
|
113
|
+
<MobileMenu
|
|
114
|
+
client:load
|
|
115
|
+
navigation={menu}
|
|
116
|
+
locales={showLanguageSwitcher ? localesForSwitcher : []}
|
|
117
|
+
currentLocale={currentLocale}
|
|
118
|
+
currentPath={basePath}
|
|
119
|
+
defaultLocale={i18nConfig.defaultLocale}
|
|
120
|
+
prefixDefaultLocale={i18nConfig.routing.prefixDefaultLocale}
|
|
121
|
+
/>
|
|
64
122
|
</div>
|
|
65
123
|
</div>
|
|
66
124
|
</nav>
|
|
67
125
|
</div>
|
|
68
|
-
</header>
|
|
126
|
+
</header>
|