From 7951121c7f3dcb1683ad7d0aa64a6bd2a7e03aab Mon Sep 17 00:00:00 2001 From: joyzhao Date: Thu, 19 Jun 2025 16:28:59 +0800 Subject: [PATCH] refactor(blog): extract blog utilities into shared module Move common blog functionality like post filtering, sorting and data extraction into a centralized utils module. Replace Astro.glob with import.meta.glob for better performance. Update all blog components and pages to use the new utilities. --- src/components/blog/BlogList.astro | 76 ++----- src/components/blog/CategoryCard.astro | 70 ++---- src/components/blog/TagCard.astro | 65 ++---- src/components/layout/BlogNavigation.astro | 18 +- src/pages/blog/categories/[category].astro | 202 +++++++---------- src/pages/blog/index.astro | 52 +---- src/pages/blog/tags/[tag].astro | 199 +++++++---------- src/pages/zh/blog/categories/[category].astro | 207 +++++++----------- src/pages/zh/blog/index.astro | 54 ++--- src/pages/zh/blog/tags/[tag].astro | 154 +++++-------- src/utils/blog-utils.ts | 207 ++++++++++++++++++ 11 files changed, 609 insertions(+), 695 deletions(-) create mode 100644 src/utils/blog-utils.ts diff --git a/src/components/blog/BlogList.astro b/src/components/blog/BlogList.astro index 636bbdd..d130c29 100644 --- a/src/components/blog/BlogList.astro +++ b/src/components/blog/BlogList.astro @@ -1,5 +1,6 @@ --- import { type BlogPost, type Lang } from '@/types'; +import { getBlogBaseUrl, filterPostsByCategory, filterPostsByTag, sortPostsByDate } from '@/utils/blog-utils'; interface Props { posts?: BlogPost[]; @@ -9,31 +10,30 @@ interface Props { tag?: string; } -// 获取组件属性 +// Get component props const { lang, baseUrl, category, tag, posts: providedPosts } = Astro.props; -// 如果没有提供 posts,则直接从文件系统读取 +// If posts are not provided, read from file system let posts: BlogPost[] = []; if (providedPosts) { - // 使用提供的文章列表 + // Use provided posts posts = providedPosts; } else { - // 根据当前语言读取博客文章 - let allPosts = []; - if (lang === 'zh') { - // 读取中文博客文章 - allPosts = await Astro.glob('../zh/blog/posts/*.md'); - } else { - // 读取英文博客文章 - allPosts = await Astro.glob('../blog/posts/*.md'); - } + // Read blog posts based on current language + const postsGlob = { + en: await import.meta.glob('../../pages/blog/posts/*.md', { eager: true }), + zh: await import.meta.glob('../../pages/zh/blog/posts/*.md', { eager: true }) + }; - // 处理博客文章数据 - posts = allPosts.map((post) => { + // Get posts for current language + const allPosts = lang === 'zh' ? Object.values(postsGlob.zh) : Object.values(postsGlob.en); + + // Process blog post data + posts = allPosts.map((post: any) => { const slug = post.url?.split('/').filter(Boolean).pop() || ''; - // 获取文章的默认图片,如果frontmatter中没有指定 + // Default image if not specified in frontmatter const defaultImage = "https://images.unsplash.com/photo-1516321318423-f06f85e504b3?w=400&h=250&fit=crop&crop=center"; return { @@ -50,52 +50,18 @@ if (providedPosts) { }; }); - // 按日期排序 - posts = posts - .filter(post => post.date) // 过滤掉没有日期的文章 - .sort((a, b) => { - const dateA = new Date(a.date).getTime(); - const dateB = new Date(b.date).getTime(); - return dateB - dateA; // 降序排列,最新的文章在前 - }); + // Sort posts by date + posts = sortPostsByDate(posts); } -// 如果指定了分类,则过滤文章 +// Filter posts by category if specified if (category) { - posts = posts.filter(post => { - // 优先检查文章是否包含当前分类ID - if (post.categoryId && Array.isArray(post.categoryId)) { - return post.categoryId.some(catId => - catId.toLowerCase() === category.toLowerCase() - ); - } - // 如果没有 categoryId,则检查 category - else if (post.category && Array.isArray(post.category)) { - return post.category.some(cat => - cat.toLowerCase() === category.toLowerCase() - ); - } - return false; - }); + posts = filterPostsByCategory(posts, category); } -// 如果指定了标签,则过滤文章 +// Filter posts by tag if specified if (tag) { - posts = posts.filter(post => { - // 优先检查文章是否包含当前标签ID - if (post.tagId && Array.isArray(post.tagId)) { - return post.tagId.some(tagId => - tagId.toLowerCase() === tag.toLowerCase() - ); - } - // 如果没有 tagId,则检查 tags - else if (post.tags && Array.isArray(post.tags)) { - return post.tags.some(postTag => - postTag.toLowerCase() === tag.toLowerCase() - ); - } - return false; - }); + posts = filterPostsByTag(posts, tag); } // 调整基础URL diff --git a/src/components/blog/CategoryCard.astro b/src/components/blog/CategoryCard.astro index 96588e4..0e2e1b0 100644 --- a/src/components/blog/CategoryCard.astro +++ b/src/components/blog/CategoryCard.astro @@ -1,76 +1,42 @@ --- -// 导入必要的依赖 import { type Lang } from '@/i18n/utils'; import { defaultLang } from '@/i18n/ui'; +import { getBlogBaseUrl, extractCategories } from '@/utils/blog-utils'; -// 定义组件属性 +// Define component props interface Props { lang?: Lang; currentCategory?: string; } -// 获取组件属性 +// Get component props const { lang = defaultLang, currentCategory = '' } = Astro.props; -// 确定基础URL路径 -const baseUrl = lang === defaultLang ? '/blog' : `/${lang}/blog`; +// Determine base URL path +const baseUrl = getBlogBaseUrl(lang); -// 读取所有博客文章 - 根据语言选择不同的静态路径 -let allPosts = []; -if (lang === 'zh') { - allPosts = await Astro.glob('/src/pages/zh/blog/posts/*.md'); -} else { - allPosts = await Astro.glob('/src/pages/blog/posts/*.md'); -} +// Read all blog posts - choose different static paths based on language +const postsGlob = { + en: await import.meta.glob('/src/pages/blog/posts/*.md', { eager: true }), + zh: await import.meta.glob('/src/pages/zh/blog/posts/*.md', { eager: true }) +}; -// 收集所有分类 -const allCategories = new Set(); +// Get posts for current language +const allPosts = lang === 'zh' ? Object.values(postsGlob.zh) : Object.values(postsGlob.en); -// 处理所有文章的分类 -const categoryMap = new Map(); // 存储 categoryId 到 category 名称的映射 +// Extract categories from posts +const categoryMap = extractCategories(allPosts); -allPosts.forEach(post => { - // 处理分类 - if (post.frontmatter?.category) { - const categories = Array.isArray(post.frontmatter.category) - ? post.frontmatter.category - : [post.frontmatter.category]; - - const categoryIds = Array.isArray(post.frontmatter.categoryId) - ? post.frontmatter.categoryId - : post.frontmatter.categoryId ? [post.frontmatter.categoryId] : []; - - // 如果有 categoryId,则使用 categoryId 和 category 的映射 - if (categoryIds.length > 0 && categoryIds.length === categories.length) { - categories.forEach((cat, index) => { - if (cat && categoryIds[index]) { - allCategories.add(cat); - categoryMap.set(cat, categoryIds[index]); - } - }); - } else { - // 如果没有 categoryId 或长度不匹配,则使用 category 名称作为 ID - categories.forEach(cat => { - if (cat) { - allCategories.add(cat); - categoryMap.set(cat, cat.toLowerCase()); - } - }); - } - } -}); +// Convert to array and sort +const categories = Array.from(categoryMap.keys()).sort(); - -// 转换为数组并排序 -const categories = Array.from(allCategories).sort(); - -// 多语言标题 +// Multilingual titles const titles = { en: 'Categories', zh: '分类' }; -// 获取当前语言的标题 +// Get title for current language const title = titles[lang] || titles[defaultLang]; --- diff --git a/src/components/blog/TagCard.astro b/src/components/blog/TagCard.astro index 07cd490..503cd64 100644 --- a/src/components/blog/TagCard.astro +++ b/src/components/blog/TagCard.astro @@ -1,71 +1,42 @@ --- -// 导入必要的依赖 import { type Lang } from '@/i18n/utils'; import { defaultLang } from '@/i18n/ui'; +import { getBlogBaseUrl, extractTags } from '@/utils/blog-utils'; -// 定义组件属性 +// Define component props interface Props { lang?: Lang; currentTag?: string; } -// 获取组件属性 +// Get component props const { lang = defaultLang, currentTag = '' } = Astro.props; -// 确定基础URL路径 -const baseUrl = lang === defaultLang ? '/blog' : `/${lang}/blog`; +// Determine base URL path +const baseUrl = getBlogBaseUrl(lang); -// 读取所有博客文章 - 根据语言选择不同的静态路径 -let allPosts = []; -if (lang === 'zh') { - allPosts = await Astro.glob('/src/pages/zh/blog/posts/*.md'); -} else { - allPosts = await Astro.glob('/src/pages/blog/posts/*.md'); -} +// Read all blog posts - choose different static paths based on language +const postsGlob = { + en: await import.meta.glob('/src/pages/blog/posts/*.md', { eager: true }), + zh: await import.meta.glob('/src/pages/zh/blog/posts/*.md', { eager: true }) +}; -// 收集所有标签 -const allTags = new Set(); -const tagMap = new Map(); // 存储 tag 到 tagId 的映射 +// Get posts for current language +const allPosts = lang === 'zh' ? Object.values(postsGlob.zh) : Object.values(postsGlob.en); -// 处理所有文章的标签 -allPosts.forEach(post => { - // 处理标签 - if (post.frontmatter?.tags && Array.isArray(post.frontmatter.tags)) { - const tags = post.frontmatter.tags; - const tagIds = Array.isArray(post.frontmatter.tagId) - ? post.frontmatter.tagId - : post.frontmatter.tagId ? [post.frontmatter.tagId] : []; - - // 如果有 tagId,则使用 tagId 和 tag 的映射 - if (tagIds.length > 0 && tagIds.length === tags.length) { - tags.forEach((tag, index) => { - if (tag && tagIds[index]) { - allTags.add(tag); - tagMap.set(tag, tagIds[index]); - } - }); - } else { - // 如果没有 tagId 或长度不匹配,则使用 tag 名称作为 ID - tags.forEach(tag => { - if (tag) { - allTags.add(tag); - tagMap.set(tag, tag.toLowerCase()); - } - }); - } - } -}); +// Extract tags from posts +const tagMap = extractTags(allPosts); -// 转换为数组,添加 # 前缀,并排序 -const tags = Array.from(allTags).map(tag => `# ${tag}`).sort(); +// Convert to array, add # prefix, and sort +const tags = Array.from(tagMap.keys()).map(tag => `# ${tag}`).sort(); -// 多语言标题 +// Multilingual titles const titles = { en: '# Tags', zh: '# 标签' }; -// 获取当前语言的标题 +// Get title for current language const title = titles[lang] || titles[defaultLang]; --- diff --git a/src/components/layout/BlogNavigation.astro b/src/components/layout/BlogNavigation.astro index 65af95b..09feac3 100644 --- a/src/components/layout/BlogNavigation.astro +++ b/src/components/layout/BlogNavigation.astro @@ -1,16 +1,23 @@ --- import { type BlogPost as BaseBlogPost } from '@/types'; +import { type Lang } from '@/i18n/utils'; +// Get current path and language information const currentPath = Astro.url.pathname; const isZh = currentPath.includes('/zh/'); const lang = isZh ? 'zh' : 'en'; const currentSlug = currentPath.split('/').filter(Boolean).pop(); -const enPosts = await Astro.glob('../../pages/blog/posts/*.md'); -const zhPosts = await Astro.glob('../../pages/zh/blog/posts/*.md'); -const allPosts = isZh ? zhPosts : enPosts; +// Read blog posts using import.meta.glob +const postsGlob = { + en: await import.meta.glob('../../pages/blog/posts/*.md', { eager: true }), + zh: await import.meta.glob('../../pages/zh/blog/posts/*.md', { eager: true }) +}; +// Get posts for current language +const allPosts = isZh ? Object.values(postsGlob.zh) : Object.values(postsGlob.en); +// Define BlogPost interface for navigation interface BlogPost { title: string; slug: string; @@ -26,7 +33,8 @@ interface BlogPost { }; } -const posts: BlogPost[] = allPosts.map((post) => { +// Process blog posts for navigation +const posts: BlogPost[] = allPosts.map((post: any) => { const slug = post.url?.split('/').filter(Boolean).pop() || ''; return { title: post.frontmatter.title, @@ -44,7 +52,7 @@ const posts: BlogPost[] = allPosts.map((post) => { }; }); - +// Sort posts by date const sortedPosts = posts .filter(post => post.frontmatter.date || post.frontmatter.pubDate) .sort((a, b) => { diff --git a/src/pages/blog/categories/[category].astro b/src/pages/blog/categories/[category].astro index 4321dc0..4a8edc0 100644 --- a/src/pages/blog/categories/[category].astro +++ b/src/pages/blog/categories/[category].astro @@ -6,48 +6,49 @@ import TagCard from '../../../components/blog/TagCard.astro'; import { type Lang } from '@/i18n/utils'; import { defaultLang } from '@/i18n/ui'; import { type BlogPost } from '@/types'; +import { filterPostsByCategory, sortPostsByDate, extractCategories, extractTags } from '@/utils/blog-utils'; -// 为动态路由生成静态路径 +// Generate static paths for dynamic routing export async function getStaticPaths() { - const allPosts = await Astro.glob('../posts/*.md'); + const allPosts = await import.meta.glob('../posts/*.md', { eager: true }); - // 收集所有分类ID或分类 + // Collect all category IDs or categories const uniqueCategories = new Set(); - allPosts.forEach(post => { - // 优先使用 categoryId 作为路由标识符 + Object.values(allPosts).forEach((post: any) => { + // Prioritize categoryId as route identifier if (post.frontmatter?.categoryId) { const categoryIds = Array.isArray(post.frontmatter.categoryId) ? post.frontmatter.categoryId : [post.frontmatter.categoryId]; - categoryIds.forEach(categoryId => { + categoryIds.forEach((categoryId: string) => { if (categoryId) uniqueCategories.add(categoryId.toLowerCase()); }); } - // 如果没有 categoryId,则使用 category 作为后备 + // If no categoryId, use category as fallback else if (post.frontmatter?.category) { const categories = Array.isArray(post.frontmatter.category) ? post.frontmatter.category : [post.frontmatter.category]; - categories.forEach(category => { + categories.forEach((category: string) => { if (category) uniqueCategories.add(category.toLowerCase()); }); } }); - // 为每个分类生成路径 + // Generate paths for each category return Array.from(uniqueCategories).map(category => ({ params: { category: encodeURIComponent(category) }, props: { category } })); } -// 获取当前语言环境 +// Get current language environment const lang = Astro.currentLocale as Lang || defaultLang; -// 获取当前分类(从URL参数) +// Get current category (from URL parameters) export interface Props { category: string; } @@ -55,129 +56,94 @@ export interface Props { const { category } = Astro.params; const decodedCategory = category ? decodeURIComponent(category) : ''; +// Read all blog posts using import.meta.glob +const allPosts = await import.meta.glob('../posts/*.md', { eager: true }); -// 使用Astro.glob读取所有博客文章 -const allPosts = await Astro.glob('../posts/*.md'); - -// 处理博客文章数据 -const blogPosts: BlogPost[] = allPosts - .filter(post => { - // 优先检查文章是否包含当前分类ID - if (post.frontmatter?.categoryId) { - const postCategoryIds = Array.isArray(post.frontmatter.categoryId) - ? post.frontmatter.categoryId - : [post.frontmatter.categoryId]; - - return postCategoryIds.some(catId => - catId.toLowerCase() === decodedCategory.toLowerCase() - ); - } - // 如果没有 categoryId,则检查 category - else if (post.frontmatter?.category) { - const postCategories = Array.isArray(post.frontmatter.category) - ? post.frontmatter.category - : [post.frontmatter.category]; - - return postCategories.some(cat => - cat.toLowerCase() === decodedCategory.toLowerCase() - ); - } - return false; - }) - .map((post) => { - const slug = post.url?.split('/').filter(Boolean).pop() || ''; - - // 获取文章的默认图片,如果frontmatter中没有指定 - const defaultImage = "https://images.unsplash.com/photo-1516321318423-f06f85e504b3?w=400&h=250&fit=crop&crop=center"; - - return { - title: post.frontmatter.title, - description: post.frontmatter.description || '', - image: post.frontmatter.image || defaultImage, - slug: slug, - tags: post.frontmatter.tags || [], - date: post.frontmatter.date || post.frontmatter.pubDate || '', - readTime: post.frontmatter.readTime || post.frontmatter.readingTime || '5 min read', - }; - }); - -// 按日期排序 -const sortedBlogPosts = blogPosts - .filter(post => post.date) // 过滤掉没有日期的文章 - .sort((a, b) => { - const dateA = new Date(a.date).getTime(); - const dateB = new Date(b.date).getTime(); - return dateB - dateA; // 降序排列,最新的文章在前 - }); - -// 从所有博客文章中提取分类和标签(用于侧边栏) -const allCategories = new Set(); -const allTags = new Set(); - -// 收集所有文章的分类和标签 -allPosts.forEach(post => { - // 处理分类 - if (post.frontmatter?.category) { - const categories = Array.isArray(post.frontmatter.category) - ? post.frontmatter.category - : [post.frontmatter.category]; - - categories.forEach(cat => { - if (cat) allCategories.add(cat); - }); - } +// Process blog post data +const blogPosts: BlogPost[] = Object.values(allPosts).map((post: any) => { + const slug = post.url?.split('/').filter(Boolean).pop() || ''; - // 处理标签 - if (post.frontmatter?.tags && Array.isArray(post.frontmatter.tags)) { - post.frontmatter.tags.forEach(tag => { - if (tag) allTags.add(tag); - }); - } + // Default image if not specified in frontmatter + const defaultImage = "https://images.unsplash.com/photo-1516321318423-f06f85e504b3?w=400&h=250&fit=crop&crop=center"; + + return { + title: post.frontmatter.title, + description: post.frontmatter.description || '', + image: post.frontmatter.image || defaultImage, + slug: slug, + tags: post.frontmatter.tags || [], + tagId: post.frontmatter.tagId || [], + category: Array.isArray(post.frontmatter.category) ? post.frontmatter.category : post.frontmatter.category ? [post.frontmatter.category] : [], + categoryId: Array.isArray(post.frontmatter.categoryId) ? post.frontmatter.categoryId : post.frontmatter.categoryId ? [post.frontmatter.categoryId] : [], + date: post.frontmatter.date || post.frontmatter.pubDate || '', + readTime: post.frontmatter.readTime || post.frontmatter.readingTime || '5 min read', + }; }); -// 转换为数组并排序 -const categories = Array.from(allCategories).sort(); -const tags = Array.from(allTags).map(tag => `# ${tag}`).sort(); +// Filter posts by category +const filteredPosts = filterPostsByCategory(blogPosts, decodedCategory); -// 查找与当前分类ID匹配的分类名称 -let displayCategoryName = ""; +// Sort posts by date +const sortedBlogPosts = sortPostsByDate(filteredPosts); -// 从博客文章中查找匹配的分类名称 -for (const post of allPosts) { - if (post.frontmatter?.categoryId && Array.isArray(post.frontmatter.categoryId) && - post.frontmatter?.category && Array.isArray(post.frontmatter.category)) { - // 查找分类ID和分类名称的索引匹配 - const categoryIndex = post.frontmatter.categoryId.findIndex(id => +// Find category name matching the current category ID +let categoryName = decodedCategory; + +// Try to find matching category name from all posts +Object.values(allPosts).forEach((post: any) => { + // Check categoryId + if (post.frontmatter?.categoryId) { + const categoryIds = Array.isArray(post.frontmatter.categoryId) + ? post.frontmatter.categoryId + : [post.frontmatter.categoryId]; + + const matchingCategoryIdIndex = categoryIds.findIndex((id: string) => id.toLowerCase() === decodedCategory.toLowerCase() ); - if (categoryIndex !== -1 && categoryIndex < post.frontmatter.category.length) { - displayCategoryName = post.frontmatter.category[categoryIndex]; - break; + if (matchingCategoryIdIndex !== -1 && post.frontmatter.category) { + // If matching categoryId is found and post has category attribute + const categories = Array.isArray(post.frontmatter.category) + ? post.frontmatter.category + : [post.frontmatter.category]; + + // Ensure categories array length matches categoryIds + if (categories.length > matchingCategoryIdIndex) { + categoryName = categories[matchingCategoryIdIndex]; + } } } -} +}); -// 如果没有找到匹配的分类名称,则使用分类ID并格式化(首字母大写) -if (!displayCategoryName) { - displayCategoryName = decodedCategory.charAt(0).toUpperCase() + decodedCategory.slice(1); -} +// Page title and description +const title = `${categoryName} - Blog | Joy Zhao`; +const description = `Explore articles about ${categoryName}. Dive into my thoughts on ${categoryName} and related topics.`; -// 动态生成页面标题和描述 -const pageTitle = `${displayCategoryName} - Blog | Joy Zhao`; -const pageDescription = `Explore articles about ${displayCategoryName}. Dive into my thoughts on ${displayCategoryName} and related topics.`; +// Extract categories and tags from all posts for sidebar +const allPostsArray = Object.values(allPosts).map((post: any) => ({ + category: post.frontmatter.category || [], + categoryId: post.frontmatter.categoryId || [], + tags: post.frontmatter.tags || [], + tagId: post.frontmatter.tagId || [] +})); + +// Get categories and tags for sidebar + +// Get categories and tags for sidebar +const categories = extractCategories(allPostsArray); +const tags = extractTags(allPostsArray); --- - +

- Category: {displayCategoryName} + Category: {categoryName}

- Explore articles about {displayCategoryName}. Found {sortedBlogPosts.length} article{sortedBlogPosts.length !== 1 ? 's' : ''}. + Explore articles about {categoryName}. Found {sortedBlogPosts.length} article{sortedBlogPosts.length !== 1 ? 's' : ''}.

@@ -187,23 +153,23 @@ const pageDescription = `Explore articles about ${displayCategoryName}. Dive int
- - + + - - + +
{sortedBlogPosts.length > 0 ? ( - + ) : (

No articles found

There are no articles in this category yet. Check back later or explore other categories.

- + diff --git a/src/pages/blog/index.astro b/src/pages/blog/index.astro index f9f589f..2fadc26 100644 --- a/src/pages/blog/index.astro +++ b/src/pages/blog/index.astro @@ -6,18 +6,19 @@ import TagCard from '../../components/blog/TagCard.astro'; import { type BlogPost } from '@/types'; import { type Lang } from '@/i18n/utils'; import { defaultLang } from '@/i18n/ui'; +import { sortPostsByDate } from '@/utils/blog-utils'; -// 使用Astro.currentLocale获取当前语言环境 +// Get current language environment using Astro.currentLocale const lang = Astro.currentLocale as Lang || defaultLang; -// 使用Astro.glob读取所有博客文章 -const allPosts = await Astro.glob('./posts/*.md'); +// Read all blog posts using import.meta.glob +const allPosts = await import.meta.glob('./posts/*.md', { eager: true }); -// 处理博客文章数据 -const blogPosts: BlogPost[] = allPosts.map((post) => { +// Process blog post data +const blogPosts: BlogPost[] = Object.values(allPosts).map((post: any) => { const slug = post.url?.split('/').filter(Boolean).pop() || ''; - // 获取文章的默认图片,如果frontmatter中没有指定 + // Default image if not specified in frontmatter const defaultImage = "https://images.unsplash.com/photo-1516321318423-f06f85e504b3?w=400&h=250&fit=crop&crop=center"; return { @@ -34,43 +35,8 @@ const blogPosts: BlogPost[] = allPosts.map((post) => { }; }); -// 按日期排序 -const sortedBlogPosts = blogPosts - .filter(post => post.date) // 过滤掉没有日期的文章 - .sort((a, b) => { - const dateA = new Date(a.date).getTime(); - const dateB = new Date(b.date).getTime(); - return dateB - dateA; // 降序排列,最新的文章在前 - }); - -// 从博客文章中提取分类和标签 -const allCategories = new Set(); -const allTags = new Set(); - -// 收集所有文章的分类和标签 -allPosts.forEach(post => { - // 处理分类 - if (post.frontmatter?.category) { - const categories = Array.isArray(post.frontmatter.category) - ? post.frontmatter.category - : [post.frontmatter.category]; - - categories.forEach(category => { - if (category) allCategories.add(category); - }); - } - - // 处理标签 - if (post.frontmatter?.tags && Array.isArray(post.frontmatter.tags)) { - post.frontmatter.tags.forEach(tag => { - if (tag) allTags.add(tag); - }); - } -}); - -// 转换为数组并排序 -const categories = Array.from(allCategories).sort(); -const tags = Array.from(allTags).map(tag => `# ${tag}`).sort(); +// Sort posts by date +const sortedBlogPosts = sortPostsByDate(blogPosts); --- diff --git a/src/pages/blog/tags/[tag].astro b/src/pages/blog/tags/[tag].astro index b7f488d..f91ab6a 100644 --- a/src/pages/blog/tags/[tag].astro +++ b/src/pages/blog/tags/[tag].astro @@ -6,40 +6,41 @@ import TagCard from '../../../components/blog/TagCard.astro'; import { type Lang } from '@/i18n/utils'; import { defaultLang } from '@/i18n/ui'; import { type BlogPost } from '@/types'; +import { filterPostsByTag, sortPostsByDate, extractCategories, extractTags } from '@/utils/blog-utils'; -// 为动态路由生成静态路径 +// Generate static paths for dynamic routing export async function getStaticPaths() { - const allPosts = await Astro.glob('../posts/*.md'); + const allPosts = await import.meta.glob('../posts/*.md', { eager: true }); - // 收集所有标签ID或标签 + // Collect all tags const uniqueTags = new Set(); - allPosts.forEach(post => { - // 优先使用 tagId 作为路由标识符 + Object.values(allPosts).forEach((post: any) => { + // Prioritize tagId as route identifier if (post.frontmatter?.tagId && Array.isArray(post.frontmatter.tagId)) { - post.frontmatter.tagId.forEach(tagId => { + post.frontmatter.tagId.forEach((tagId: string) => { if (tagId) uniqueTags.add(tagId.toLowerCase()); }); - } - // 如果没有 tagId,则使用 tags 作为后备 + } + // If no tagId, use tags as fallback else if (post.frontmatter?.tags && Array.isArray(post.frontmatter.tags)) { - post.frontmatter.tags.forEach(tag => { + post.frontmatter.tags.forEach((tag: string) => { if (tag) uniqueTags.add(tag.toLowerCase()); }); } }); - // 为每个标签生成路径,对包含特殊字符的标签进行编码 + // Generate paths for each tag return Array.from(uniqueTags).map(tag => ({ params: { tag: encodeURIComponent(tag) }, props: { tag } })); } -// 获取当前语言环境 +// Get current language environment const lang = Astro.currentLocale as Lang || defaultLang; -// 获取当前标签(从URL参数) +// Get current tag (from URL parameters) export interface Props { tag: string; } @@ -47,151 +48,113 @@ export interface Props { const { tag } = Astro.params; const decodedTag = tag ? decodeURIComponent(tag) : ''; +// Read all blog posts using import.meta.glob +const allPosts = await import.meta.glob('../posts/*.md', { eager: true }); -// 使用Astro.glob读取所有博客文章 -const allPosts = await Astro.glob('../posts/*.md'); - -// 处理博客文章数据 -const blogPosts: BlogPost[] = allPosts - .filter(post => { - // 优先检查文章是否包含当前标签ID - if (post.frontmatter?.tagId && Array.isArray(post.frontmatter.tagId)) { - return post.frontmatter.tagId.some(postTagId => - postTagId.toLowerCase() === decodedTag.toLowerCase() - ); - } - // 如果没有 tagId,则检查 tags - else if (post.frontmatter?.tags && Array.isArray(post.frontmatter.tags)) { - return post.frontmatter.tags.some(postTag => - postTag.toLowerCase() === decodedTag.toLowerCase() - ); - } - return false; - }) - .map((post) => { - const slug = post.url?.split('/').filter(Boolean).pop() || ''; - - // 获取文章的默认图片,如果frontmatter中没有指定 - const defaultImage = "https://images.unsplash.com/photo-1516321318423-f06f85e504b3?w=400&h=250&fit=crop&crop=center"; - - return { - title: post.frontmatter.title, - description: post.frontmatter.description || '', - image: post.frontmatter.image || defaultImage, - slug: slug, - tags: post.frontmatter.tags || [], - date: post.frontmatter.date || post.frontmatter.pubDate || '', - readTime: post.frontmatter.readTime || post.frontmatter.readingTime || '5 min read', - }; - }); - -// 按日期排序 -const sortedBlogPosts = blogPosts - .filter(post => post.date) // 过滤掉没有日期的文章 - .sort((a, b) => { - const dateA = new Date(a.date).getTime(); - const dateB = new Date(b.date).getTime(); - return dateB - dateA; // 降序排列,最新的文章在前 - }); - -// 从所有博客文章中提取分类和标签(用于侧边栏) -const allCategories = new Set(); -const allTags = new Set(); - -// 收集所有文章的分类和标签 -allPosts.forEach(post => { - // 处理分类 - if (post.frontmatter?.category) { - const categories = Array.isArray(post.frontmatter.category) - ? post.frontmatter.category - : [post.frontmatter.category]; - - categories.forEach(cat => { - if (cat) allCategories.add(cat); - }); - } +// Process blog post data +const blogPosts: BlogPost[] = Object.values(allPosts).map((post: any) => { + const slug = post.url?.split('/').filter(Boolean).pop() || ''; - // 处理标签 - if (post.frontmatter?.tags && Array.isArray(post.frontmatter.tags)) { - post.frontmatter.tags.forEach(postTag => { - if (postTag) allTags.add(postTag); - }); - } - // 同时收集标签ID(用于内部路由) - if (post.frontmatter?.tagId && Array.isArray(post.frontmatter.tagId)) { - // 这里我们不添加到 allTags 中,因为 tagId 只用于路由,不用于显示 - } + // Default image if not specified in frontmatter + const defaultImage = "https://images.unsplash.com/photo-1516321318423-f06f85e504b3?w=400&h=250&fit=crop&crop=center"; + + return { + title: post.frontmatter.title, + description: post.frontmatter.description || '', + image: post.frontmatter.image || defaultImage, + slug: slug, + tags: post.frontmatter.tags || [], + tagId: post.frontmatter.tagId || [], + category: Array.isArray(post.frontmatter.category) ? post.frontmatter.category : post.frontmatter.category ? [post.frontmatter.category] : [], + categoryId: Array.isArray(post.frontmatter.categoryId) ? post.frontmatter.categoryId : post.frontmatter.categoryId ? [post.frontmatter.categoryId] : [], + date: post.frontmatter.date || post.frontmatter.pubDate || '', + readTime: post.frontmatter.readTime || post.frontmatter.readingTime || '5 min read', + }; }); -// 转换为数组并排序 -const categories = Array.from(allCategories).sort(); -const tags = Array.from(allTags).map(postTag => `# ${postTag}`).sort(); +// Filter posts by tag +const filteredPosts = filterPostsByTag(blogPosts, decodedTag); -// 查找与当前标签ID匹配的标签名称 -let displayTagName = ""; +// Sort posts by date +const sortedBlogPosts = sortPostsByDate(filteredPosts); -// 从博客文章中查找匹配的标签名称 -for (const post of allPosts) { +// Extract categories and tags from all posts for sidebar +const allPostsArray = Object.values(allPosts).map((post: any) => ({ + category: post.frontmatter.category || [], + categoryId: post.frontmatter.categoryId || [], + tags: post.frontmatter.tags || [], + tagId: post.frontmatter.tagId || [] +})); + +// Get categories and tags for sidebar +const categories = extractCategories(allPostsArray); +const tags = extractTags(allPostsArray); + +// Find tag name matching the current tag ID +let tagName = decodedTag; + +// Try to find matching tag name from all posts +Object.values(allPosts).forEach((post: any) => { + // Check tagId if (post.frontmatter?.tagId && Array.isArray(post.frontmatter.tagId) && post.frontmatter?.tags && Array.isArray(post.frontmatter.tags)) { - // 查找标签ID和标签名称的索引匹配 - const tagIndex = post.frontmatter.tagId.findIndex(id => + // Find matching index between tagId and tag name + const tagIndex = post.frontmatter.tagId.findIndex((id: string) => id.toLowerCase() === decodedTag.toLowerCase() ); if (tagIndex !== -1 && tagIndex < post.frontmatter.tags.length) { - displayTagName = post.frontmatter.tags[tagIndex]; - break; + tagName = post.frontmatter.tags[tagIndex]; } } +}); + +// If no matching tag name is found, use tag ID and format it (capitalize first letter) +if (tagName === decodedTag) { + tagName = decodedTag.charAt(0).toUpperCase() + decodedTag.slice(1); } -// 如果没有找到匹配的标签名称,则使用标签ID并格式化(首字母大写) -if (!displayTagName) { - displayTagName = decodedTag.charAt(0).toUpperCase() + decodedTag.slice(1); -} - -// 动态生成页面标题和描述 -const pageTitle = `# ${displayTagName} - Blog | Joy Zhao`; -const pageDescription = `Explore articles tagged with # ${displayTagName}. Dive into my thoughts on ${displayTagName} and related topics.`; +// Generate page title and description +const title = `#${tagName} - Blog | Joy Zhao`; +const description = `Explore articles tagged with #${tagName}. Dive into my thoughts on ${tagName} and related topics.`; --- - +
-
+

- Tag: # {displayTagName} + Tag: #{tagName}

- Explore articles tagged with # {displayTagName}. Found {sortedBlogPosts.length} article{sortedBlogPosts.length !== 1 ? 's' : ''}. + Explore articles tagged with #{tagName}. Found {sortedBlogPosts.length} article{sortedBlogPosts.length !== 1 ? 's' : ''}.

-
+ - -
diff --git a/src/pages/zh/blog/categories/[category].astro b/src/pages/zh/blog/categories/[category].astro index 1fdcdf2..9b85723 100644 --- a/src/pages/zh/blog/categories/[category].astro +++ b/src/pages/zh/blog/categories/[category].astro @@ -6,48 +6,49 @@ import TagCard from '../../../../components/blog/TagCard.astro'; import { type Lang } from '@/i18n/utils'; import { defaultLang } from '@/i18n/ui'; import { type BlogPost } from '@/types'; +import { filterPostsByCategory, sortPostsByDate, extractCategories, extractTags } from '@/utils/blog-utils'; -// 为动态路由生成静态路径 +// Generate static paths for dynamic routing export async function getStaticPaths() { - const allPosts = await Astro.glob('../posts/*.md'); + const allPosts = await import.meta.glob('../posts/*.md', { eager: true }); - // 收集所有分类ID或分类 + // Collect all category IDs or categories const uniqueCategories = new Set(); - allPosts.forEach(post => { - // 优先使用 categoryId 作为路由标识符 + Object.values(allPosts).forEach((post: any) => { + // Prioritize categoryId as route identifier if (post.frontmatter?.categoryId) { const categoryIds = Array.isArray(post.frontmatter.categoryId) ? post.frontmatter.categoryId : [post.frontmatter.categoryId]; - categoryIds.forEach(categoryId => { + categoryIds.forEach((categoryId: string) => { if (categoryId) uniqueCategories.add(categoryId.toLowerCase()); }); } - // 如果没有 categoryId,则使用 category 作为后备 + // If no categoryId, use category as fallback else if (post.frontmatter?.category) { const categories = Array.isArray(post.frontmatter.category) ? post.frontmatter.category : [post.frontmatter.category]; - categories.forEach(category => { + categories.forEach((category: string) => { if (category) uniqueCategories.add(category.toLowerCase()); }); } }); - // 为每个分类生成路径 + // Generate paths for each category return Array.from(uniqueCategories).map(category => ({ params: { category: encodeURIComponent(category) }, props: { category } })); } -// 获取当前语言环境 +// Get current language environment const lang = Astro.currentLocale as Lang || defaultLang; -// 获取当前分类(从URL参数) +// Get current category (from URL parameters) export interface Props { category: string; } @@ -55,155 +56,113 @@ export interface Props { const { category } = Astro.params; const decodedCategory = category ? decodeURIComponent(category) : ''; +// Read all blog posts using import.meta.glob +const allPosts = await import.meta.glob('../posts/*.md', { eager: true }); -// 使用Astro.glob读取所有中文博客文章 -const allPosts = await Astro.glob('../posts/*.md'); - -// 处理博客文章数据 -const blogPosts: BlogPost[] = allPosts - .filter(post => { - // 优先检查文章是否包含当前分类ID - if (post.frontmatter?.categoryId) { - const postCategoryIds = Array.isArray(post.frontmatter.categoryId) - ? post.frontmatter.categoryId - : [post.frontmatter.categoryId]; - - return postCategoryIds.some(catId => - catId.toLowerCase() === decodedCategory.toLowerCase() - ); - } - // 如果没有 categoryId,则检查 category - else if (post.frontmatter?.category) { - const postCategories = Array.isArray(post.frontmatter.category) - ? post.frontmatter.category - : [post.frontmatter.category]; - - return postCategories.some(cat => - cat.toLowerCase() === decodedCategory.toLowerCase() - ); - } - return false; - }) - .map((post) => { - const slug = post.url?.split('/').filter(Boolean).pop() || ''; - - // 获取文章的默认图片,如果frontmatter中没有指定 - const defaultImage = "https://images.unsplash.com/photo-1516321318423-f06f85e504b3?w=400&h=250&fit=crop&crop=center"; - - return { - title: post.frontmatter.title, - description: post.frontmatter.description || '', - image: post.frontmatter.image || defaultImage, - slug: slug, - tags: post.frontmatter.tags || [], - date: post.frontmatter.date || post.frontmatter.pubDate || '', - readTime: post.frontmatter.readTime || post.frontmatter.readingTime || '5分钟阅读', - }; - }); - -// 按日期排序 -const sortedBlogPosts = blogPosts - .filter(post => post.date) // 过滤掉没有日期的文章 - .sort((a, b) => { - const dateA = new Date(a.date).getTime(); - const dateB = new Date(b.date).getTime(); - return dateB - dateA; // 降序排列,最新的文章在前 - }); - -// 从所有博客文章中提取分类和标签(用于侧边栏) -const allCategories = new Set(); -const allTags = new Set(); - -// 收集所有文章的分类和标签 -allPosts.forEach(post => { - // 处理分类 - if (post.frontmatter?.category) { - const categories = Array.isArray(post.frontmatter.category) - ? post.frontmatter.category - : [post.frontmatter.category]; - - categories.forEach(cat => { - if (cat) allCategories.add(cat); - }); - } +// Process blog post data +const blogPosts: BlogPost[] = Object.values(allPosts).map((post: any) => { + const slug = post.url?.split('/').filter(Boolean).pop() || ''; - // 处理标签 - if (post.frontmatter?.tags && Array.isArray(post.frontmatter.tags)) { - post.frontmatter.tags.forEach(tag => { - if (tag) allTags.add(tag); - }); - } + // Default image if not specified in frontmatter + const defaultImage = "https://images.unsplash.com/photo-1516321318423-f06f85e504b3?w=400&h=250&fit=crop&crop=center"; + + return { + title: post.frontmatter.title, + description: post.frontmatter.description || '', + image: post.frontmatter.image || defaultImage, + slug: slug, + tags: post.frontmatter.tags || [], + tagId: post.frontmatter.tagId || [], + category: Array.isArray(post.frontmatter.category) ? post.frontmatter.category : post.frontmatter.category ? [post.frontmatter.category] : [], + categoryId: Array.isArray(post.frontmatter.categoryId) ? post.frontmatter.categoryId : post.frontmatter.categoryId ? [post.frontmatter.categoryId] : [], + date: post.frontmatter.date || post.frontmatter.pubDate || '', + readTime: post.frontmatter.readTime || post.frontmatter.readingTime || '5 min read', + }; }); -// 转换为数组并排序 -const categories = Array.from(allCategories).sort(); -const tags = Array.from(allTags).map(tag => `# ${tag}`).sort(); +// Filter posts by category +const filteredPosts = filterPostsByCategory(blogPosts, decodedCategory); -// 查找与当前分类ID匹配的分类名称 -let displayCategoryName = ""; +// Sort posts by date +const sortedBlogPosts = sortPostsByDate(filteredPosts); -// 从博客文章中查找匹配的分类名称 -for (const post of allPosts) { +// Extract categories and tags from all posts for sidebar +const allPostsArray = Object.values(allPosts).map((post: any) => ({ + category: post.frontmatter.category || [], + categoryId: post.frontmatter.categoryId || [], + tags: post.frontmatter.tags || [], + tagId: post.frontmatter.tagId || [] +})); + +// Get categories and tags for sidebar +const categories = extractCategories(allPostsArray); +const tags = extractTags(allPostsArray); + +// Find category name matching the current category ID +let categoryName = decodedCategory; + +// Try to find matching category name from all posts +Object.values(allPosts).forEach((post: any) => { + // Check categoryId if (post.frontmatter?.categoryId && Array.isArray(post.frontmatter.categoryId) && post.frontmatter?.category && Array.isArray(post.frontmatter.category)) { - // 查找分类ID和分类名称的索引匹配 - const categoryIndex = post.frontmatter.categoryId.findIndex(id => + // Find matching index between categoryId and category name + const categoryIndex = post.frontmatter.categoryId.findIndex((id: string) => id.toLowerCase() === decodedCategory.toLowerCase() ); if (categoryIndex !== -1 && categoryIndex < post.frontmatter.category.length) { - displayCategoryName = post.frontmatter.category[categoryIndex]; - break; + categoryName = post.frontmatter.category[categoryIndex]; } } +}); + +// If no matching category name is found, use category ID and format it (capitalize first letter) +if (categoryName === decodedCategory) { + categoryName = decodedCategory.charAt(0).toUpperCase() + decodedCategory.slice(1); } -// 如果没有找到匹配的分类名称,则使用分类ID并格式化(首字母大写) -if (!displayCategoryName) { - displayCategoryName = decodedCategory.charAt(0).toUpperCase() + decodedCategory.slice(1); -} - -// 动态生成页面标题和描述 -const pageTitle = `${displayCategoryName} - 博客 | 赵桂阳`; -const pageDescription = `探索关于${displayCategoryName}的文章。深入了解我对${displayCategoryName}和相关主题的思考。`; +// Generate page title and description +const title = `${categoryName} - 博客 | Joy Zhao`; +const description = `探索关于${categoryName}的文章。深入了解我对${categoryName}及相关主题的思考。`; --- - +
- -
+ +

- 分类: {displayCategoryName} + 分类: {categoryName}

- 探索关于{displayCategoryName}的文章。找到 {sortedBlogPosts.length} 篇文章。 + 探索关于{categoryName}的文章。找到 {sortedBlogPosts.length} 篇文章。

-
+ - -
diff --git a/src/pages/zh/blog/index.astro b/src/pages/zh/blog/index.astro index 68a8dd7..832dd02 100644 --- a/src/pages/zh/blog/index.astro +++ b/src/pages/zh/blog/index.astro @@ -6,15 +6,16 @@ import TagCard from '../../../components/blog/TagCard.astro'; import { type BlogPost } from '@/types'; import { type Lang } from '@/i18n/utils'; import { defaultLang } from '@/i18n/ui'; +import { sortPostsByDate, extractCategories, extractTags } from '@/utils/blog-utils'; // 使用Astro.currentLocale获取当前语言环境 const lang = Astro.currentLocale as Lang || defaultLang; -// 使用Astro.glob读取所有中文博客文章 -const allPosts = await Astro.glob('./posts/*.md'); +// 使用import.meta.glob读取所有中文博客文章 +const allPosts = await import.meta.glob('./posts/*.md', { eager: true }); // 处理博客文章数据 -const blogPosts: BlogPost[] = allPosts.map((post) => { +const blogPosts: BlogPost[] = Object.values(allPosts).map((post: any) => { const slug = post.url?.split('/').filter(Boolean).pop() || ''; // 获取文章的默认图片,如果frontmatter中没有指定 @@ -34,43 +35,20 @@ const blogPosts: BlogPost[] = allPosts.map((post) => { }; }); -// 按日期排序 -const sortedBlogPosts = blogPosts - .filter(post => post.date) // 过滤掉没有日期的文章 - .sort((a, b) => { - const dateA = new Date(a.date).getTime(); - const dateB = new Date(b.date).getTime(); - return dateB - dateA; // 降序排列,最新的文章在前 - }); +// 使用工具函数按日期排序 +const sortedBlogPosts = sortPostsByDate(blogPosts); -// 从博客文章中提取分类和标签 -const allCategories = new Set(); -const allTags = new Set(); +// 提取所有文章的分类和标签信息(用于侧边栏) +const allPostsArray = Object.values(allPosts).map((post: any) => ({ + category: post.frontmatter.category || [], + categoryId: post.frontmatter.categoryId || [], + tags: post.frontmatter.tags || [], + tagId: post.frontmatter.tagId || [] +})); -// 收集所有文章的分类和标签 -allPosts.forEach(post => { - // 处理分类 - if (post.frontmatter?.category) { - const categories = Array.isArray(post.frontmatter.category) - ? post.frontmatter.category - : [post.frontmatter.category]; - - categories.forEach(category => { - if (category) allCategories.add(category); - }); - } - - // 处理标签 - if (post.frontmatter?.tags && Array.isArray(post.frontmatter.tags)) { - post.frontmatter.tags.forEach(tag => { - if (tag) allTags.add(tag); - }); - } -}); - -// 转换为数组并排序 -const categories = Array.from(allCategories).sort(); -const tags = Array.from(allTags).map(tag => `# ${tag}`).sort(); +// 使用工具函数提取分类和标签 +const categories = extractCategories(allPostsArray); +const tags = extractTags(allPostsArray); --- diff --git a/src/pages/zh/blog/tags/[tag].astro b/src/pages/zh/blog/tags/[tag].astro index 8f0aca4..53a48fd 100644 --- a/src/pages/zh/blog/tags/[tag].astro +++ b/src/pages/zh/blog/tags/[tag].astro @@ -6,24 +6,25 @@ import TagCard from '../../../../components/blog/TagCard.astro'; import { type Lang } from '@/i18n/utils'; import { defaultLang } from '@/i18n/ui'; import { type BlogPost } from '@/types'; +import { filterPostsByTag, sortPostsByDate, extractCategories, extractTags, getBlogBaseUrl } from '@/utils/blog-utils'; // 为动态路由生成静态路径 export async function getStaticPaths() { - const allPosts = await Astro.glob('../posts/*.md'); + const allPosts = await import.meta.glob('../posts/*.md', { eager: true }); // 收集所有标签ID或标签 const uniqueTags = new Set(); - allPosts.forEach(post => { + Object.values(allPosts).forEach((post: any) => { // 优先使用 tagId 作为路由标识符 if (post.frontmatter?.tagId && Array.isArray(post.frontmatter.tagId)) { - post.frontmatter.tagId.forEach(tagId => { + post.frontmatter.tagId.forEach((tagId: string) => { if (tagId) uniqueTags.add(tagId.toLowerCase()); }); } // 如果没有 tagId,则使用 tags 作为后备 else if (post.frontmatter?.tags && Array.isArray(post.frontmatter.tags)) { - post.frontmatter.tags.forEach(tag => { + post.frontmatter.tags.forEach((tag: string) => { if (tag) uniqueTags.add(tag.toLowerCase()); }); } @@ -47,143 +48,106 @@ export interface Props { const { tag } = Astro.params; const decodedTag = tag ? decodeURIComponent(tag) : ''; - -// 使用Astro.glob读取所有中文博客文章 -const allPosts = await Astro.glob('../posts/*.md'); +// 使用import.meta.glob读取所有中文博客文章 +const allPosts = await import.meta.glob('../posts/*.md', { eager: true }); // 处理博客文章数据 -const blogPosts: BlogPost[] = allPosts - .filter(post => { - // 优先检查文章是否包含当前标签ID - if (post.frontmatter?.tagId && Array.isArray(post.frontmatter.tagId)) { - return post.frontmatter.tagId.some(postTagId => - postTagId.toLowerCase() === decodedTag.toLowerCase() - ); - } - // 如果没有 tagId,则检查 tags - else if (post.frontmatter?.tags && Array.isArray(post.frontmatter.tags)) { - return post.frontmatter.tags.some(postTag => - postTag.toLowerCase() === decodedTag.toLowerCase() - ); - } - return false; - }) - .map((post) => { - const slug = post.url?.split('/').filter(Boolean).pop() || ''; - - // 获取文章的默认图片,如果frontmatter中没有指定 - const defaultImage = "https://images.unsplash.com/photo-1516321318423-f06f85e504b3?w=400&h=250&fit=crop&crop=center"; - - return { - title: post.frontmatter.title, - description: post.frontmatter.description || '', - image: post.frontmatter.image || defaultImage, - slug: slug, - tags: post.frontmatter.tags || [], - date: post.frontmatter.date || post.frontmatter.pubDate || '', - readTime: post.frontmatter.readTime || post.frontmatter.readingTime || '5 分钟阅读', - }; - }); - -// 按日期排序 -const sortedBlogPosts = blogPosts - .filter(post => post.date) // 过滤掉没有日期的文章 - .sort((a, b) => { - const dateA = new Date(a.date).getTime(); - const dateB = new Date(b.date).getTime(); - return dateB - dateA; // 降序排列,最新的文章在前 - }); - -// 从所有博客文章中提取分类和标签(用于侧边栏) -const allCategories = new Set(); -const allTags = new Set(); - -// 收集所有文章的分类和标签 -allPosts.forEach(post => { - // 处理分类 - if (post.frontmatter?.category) { - const categories = Array.isArray(post.frontmatter.category) - ? post.frontmatter.category - : [post.frontmatter.category]; - - categories.forEach(cat => { - if (cat) allCategories.add(cat); - }); - } +const blogPosts: BlogPost[] = Object.values(allPosts).map((post: any) => { + const slug = post.url?.split('/').filter(Boolean).pop() || ''; - // 处理标签 - if (post.frontmatter?.tags && Array.isArray(post.frontmatter.tags)) { - post.frontmatter.tags.forEach(postTag => { - if (postTag) allTags.add(postTag); - }); - } - // 同时收集标签ID(用于内部路由) - if (post.frontmatter?.tagId && Array.isArray(post.frontmatter.tagId)) { - // 这里我们不添加到 allTags 中,因为 tagId 只用于路由,不用于显示 - } + // 获取文章的默认图片,如果frontmatter中没有指定 + const defaultImage = "https://images.unsplash.com/photo-1516321318423-f06f85e504b3?w=400&h=250&fit=crop&crop=center"; + + return { + title: post.frontmatter.title, + description: post.frontmatter.description || '', + image: post.frontmatter.image || defaultImage, + slug: slug, + tags: post.frontmatter.tags || [], + tagId: post.frontmatter.tagId || [], + category: Array.isArray(post.frontmatter.category) ? post.frontmatter.category : post.frontmatter.category ? [post.frontmatter.category] : [], + categoryId: Array.isArray(post.frontmatter.categoryId) ? post.frontmatter.categoryId : post.frontmatter.categoryId ? [post.frontmatter.categoryId] : [], + date: post.frontmatter.date || post.frontmatter.pubDate || '', + readTime: post.frontmatter.readTime || post.frontmatter.readingTime || '5 分钟阅读', + }; }); -// 转换为数组并排序 -const categories = Array.from(allCategories).sort(); -const tags = Array.from(allTags).map(postTag => `# ${postTag}`).sort(); +// 使用工具函数过滤与当前标签相关的文章 +const filteredPosts = filterPostsByTag(blogPosts, decodedTag); + +// 使用工具函数按日期排序 +const sortedBlogPosts = sortPostsByDate(filteredPosts); + +// 提取所有文章的分类和标签信息(用于侧边栏) +const allPostsArray = Object.values(allPosts).map((post: any) => ({ + category: post.frontmatter.category || [], + categoryId: post.frontmatter.categoryId || [], + tags: post.frontmatter.tags || [], + tagId: post.frontmatter.tagId || [] +})); + +// 使用工具函数提取分类和标签 +const categories = extractCategories(allPostsArray); +const tags = extractTags(allPostsArray); // 查找与当前标签ID匹配的标签名称 -let displayTagName = ""; +let tagName = ""; // 从博客文章中查找匹配的标签名称 -for (const post of allPosts) { - if (post.frontmatter?.tagId && Array.isArray(post.frontmatter.tagId) && - post.frontmatter?.tags && Array.isArray(post.frontmatter.tags)) { +for (const post of Object.values(allPosts)) { + const frontmatter = (post as any).frontmatter; + if (frontmatter?.tagId && Array.isArray(frontmatter.tagId) && + frontmatter?.tags && Array.isArray(frontmatter.tags)) { // 查找标签ID和标签名称的索引匹配 - const tagIndex = post.frontmatter.tagId.findIndex(id => + const tagIndex = frontmatter.tagId.findIndex((id: string) => id.toLowerCase() === decodedTag.toLowerCase() ); - if (tagIndex !== -1 && tagIndex < post.frontmatter.tags.length) { - displayTagName = post.frontmatter.tags[tagIndex]; + if (tagIndex !== -1 && tagIndex < frontmatter.tags.length) { + tagName = frontmatter.tags[tagIndex]; break; } } } // 如果没有找到匹配的标签名称,则使用标签ID并格式化(首字母大写) -if (!displayTagName) { - displayTagName = decodedTag.charAt(0).toUpperCase() + decodedTag.slice(1); +if (!tagName) { + tagName = decodedTag.charAt(0).toUpperCase() + decodedTag.slice(1); } // 动态生成页面标题和描述 -const pageTitle = `# ${displayTagName} - 博客 | Joy Zhao`; -const pageDescription = `浏览带有 # ${displayTagName} 标签的文章。深入了解我关于 ${displayTagName} 及相关主题的想法。`; +const pageTitle = `# ${tagName} - 博客 | Joy Zhao`; +const pageDescription = `浏览带有 # ${tagName} 标签的文章。深入了解我关于 ${tagName} 及相关主题的想法。`; ---
- +

- 标签: # {displayTagName} + 标签: # {tagName}

- 浏览带有 # {displayTagName} 标签的文章。找到 {sortedBlogPosts.length} 篇文章。 + 浏览带有 # {tagName} 标签的文章。找到 {sortedBlogPosts.length} 篇文章。

- +
- +
- +
{sortedBlogPosts.length > 0 ? ( diff --git a/src/utils/blog-utils.ts b/src/utils/blog-utils.ts new file mode 100644 index 0000000..c1cd571 --- /dev/null +++ b/src/utils/blog-utils.ts @@ -0,0 +1,207 @@ +/** + * Blog utility functions + * This file contains common functions used across blog components + */ + +import { type BlogPost, type Lang } from '@/types'; +import { defaultLang } from '@/i18n/ui'; + +/** + * Get blog posts based on language + * @param lang - Current language + * @param postsGlob - Object containing imported posts from glob + * @returns Processed blog posts array + */ +export async function getBlogPosts(lang: Lang, postsGlob: Record): Promise { + // Get posts based on language + let allPosts = []; + + if (lang === 'zh') { + allPosts = postsGlob.zh; + } else { + allPosts = postsGlob.en; + } + + // Process blog post data + const posts: BlogPost[] = allPosts.map((post: any) => { + const slug = post.url?.split('/').filter(Boolean).pop() || ''; + + // Default image if not specified in frontmatter + const defaultImage = "https://images.unsplash.com/photo-1516321318423-f06f85e504b3?w=400&h=250&fit=crop&crop=center"; + + return { + title: post.frontmatter.title, + description: post.frontmatter.description || '', + image: post.frontmatter.image || defaultImage, + slug: slug, + tags: post.frontmatter.tags || [], + tagId: post.frontmatter.tagId || [], + category: Array.isArray(post.frontmatter.category) ? post.frontmatter.category : post.frontmatter.category ? [post.frontmatter.category] : [], + categoryId: Array.isArray(post.frontmatter.categoryId) ? post.frontmatter.categoryId : post.frontmatter.categoryId ? [post.frontmatter.categoryId] : [], + date: post.frontmatter.date || post.frontmatter.pubDate || '', + readTime: post.frontmatter.readTime || post.frontmatter.readingTime || '5 min read', + url: post.url || '', + }; + }); + + return posts; +} + +/** + * Sort blog posts by date (newest first) + * @param posts - Array of blog posts + * @returns Sorted array of blog posts + */ +export function sortPostsByDate(posts: BlogPost[]): BlogPost[] { + return posts + .filter(post => post.date) // Filter out posts without dates + .sort((a, b) => { + const dateA = new Date(a.date).getTime(); + const dateB = new Date(b.date).getTime(); + return dateB - dateA; // Descending order, newest first + }); +} + +/** + * Filter posts by category + * @param posts - Array of blog posts + * @param category - Category to filter by + * @returns Filtered array of blog posts + */ +export function filterPostsByCategory(posts: BlogPost[], category: string): BlogPost[] { + if (!category) return posts; + + return posts.filter(post => { + // First check if post contains the current category ID + if (post.categoryId && Array.isArray(post.categoryId)) { + return post.categoryId.some(catId => + catId.toLowerCase() === category.toLowerCase() + ); + } + // If no categoryId, check category + else if (post.category && Array.isArray(post.category)) { + return post.category.some(cat => + cat.toLowerCase() === category.toLowerCase() + ); + } + return false; + }); +} + +/** + * Filter posts by tag + * @param posts - Array of blog posts + * @param tag - Tag to filter by + * @returns Filtered array of blog posts + */ +export function filterPostsByTag(posts: BlogPost[], tag: string): BlogPost[] { + if (!tag) return posts; + + return posts.filter(post => { + // First check if post contains the current tag ID + if (post.tagId && Array.isArray(post.tagId)) { + return post.tagId.some(tagId => + tagId.toLowerCase() === tag.toLowerCase() + ); + } + // If no tagId, check tags + else if (post.tags && Array.isArray(post.tags)) { + return post.tags.some(postTag => + postTag.toLowerCase() === tag.toLowerCase() + ); + } + return false; + }); +} + +/** + * Extract categories from posts + * @param posts - Array of blog posts + * @returns Map of category names to category IDs + */ +export function extractCategories(posts: any[]): Map { + const allCategories = new Set(); + const categoryMap = new Map(); // Map of category name to ID + + posts.forEach(post => { + // Process categories + if (post.frontmatter?.category) { + const categories = Array.isArray(post.frontmatter.category) + ? post.frontmatter.category + : [post.frontmatter.category]; + + const categoryIds = Array.isArray(post.frontmatter.categoryId) + ? post.frontmatter.categoryId + : post.frontmatter.categoryId ? [post.frontmatter.categoryId] : []; + + // If categoryId exists, use mapping between categoryId and category + if (categoryIds.length > 0 && categoryIds.length === categories.length) { + categories.forEach((cat: string, index: number) => { + if (cat && categoryIds[index]) { + allCategories.add(cat); + categoryMap.set(cat, categoryIds[index]); + } + }); + } else { + // If no categoryId or lengths don't match, use category name as ID + categories.forEach((cat: string) => { + if (cat) { + allCategories.add(cat); + categoryMap.set(cat, cat.toLowerCase()); + } + }); + } + } + }); + + return categoryMap; +} + +/** + * Extract tags from posts + * @param posts - Array of blog posts + * @returns Map of tag names to tag IDs + */ +export function extractTags(posts: any[]): Map { + const allTags = new Set(); + const tagMap = new Map(); // Map of tag name to ID + + posts.forEach(post => { + // Process tags + if (post.frontmatter?.tags && Array.isArray(post.frontmatter.tags)) { + const tags = post.frontmatter.tags; + const tagIds = Array.isArray(post.frontmatter.tagId) + ? post.frontmatter.tagId + : post.frontmatter.tagId ? [post.frontmatter.tagId] : []; + + // If tagId exists, use mapping between tagId and tag + if (tagIds.length > 0 && tagIds.length === tags.length) { + tags.forEach((tag: string, index: number) => { + if (tag && tagIds[index]) { + allTags.add(tag); + tagMap.set(tag, tagIds[index]); + } + }); + } else { + // If no tagId or lengths don't match, use tag name as ID + tags.forEach((tag: string) => { + if (tag) { + allTags.add(tag); + tagMap.set(tag, tag.toLowerCase()); + } + }); + } + } + }); + + return tagMap; +} + +/** + * Get base URL for blog based on language + * @param lang - Current language + * @returns Base URL string + */ +export function getBlogBaseUrl(lang: Lang): string { + return lang === defaultLang ? '/blog' : `/${lang}/blog`; +} \ No newline at end of file