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.
This commit is contained in:
joyzhao
2025-06-19 16:28:59 +08:00
parent 429b13985f
commit 7951121c7f
11 changed files with 609 additions and 695 deletions

207
src/utils/blog-utils.ts Normal file
View File

@@ -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<string, any>): Promise<BlogPost[]> {
// 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<string, string> {
const allCategories = new Set<string>();
const categoryMap = new Map<string, string>(); // 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<string, string> {
const allTags = new Set<string>();
const tagMap = new Map<string, string>(); // 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`;
}