Compare commits

..

1 Commits

Author SHA1 Message Date
joyzhao
21539772ef feat(blog): 添加博客工具函数以处理文章数据和分类标签 2026-01-12 14:43:05 +08:00

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

@@ -0,0 +1,309 @@
/**
* 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`;
}
/**
* Process raw blog post data from import.meta.glob
* @param allPosts - Raw posts data from import.meta.glob
* @param lang - Current language
* @returns Processed blog posts array
*/
export function processRawBlogPosts(allPosts: Record<string, any>[] | any[], lang: Lang): BlogPost[] {
// 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";
// Default read time text based on language
const defaultReadTime = lang === 'zh' ? '5 分钟阅读' : '5 min read';
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 || defaultReadTime,
url: post.url || '',
};
});
return posts;
}
/**
* Extract post metadata for categories and tags
* @param posts - Array of blog posts
* @returns Array of post metadata
*/
export function extractPostMetadata(posts: any[]) {
return posts.map((post: any) => ({
category: post.frontmatter?.category || [],
categoryId: post.frontmatter?.categoryId || [],
tags: post.frontmatter?.tags || [],
tagId: post.frontmatter?.tagId || []
}));
}
/**
* Get all blog posts with processing for a taxonomy page (category or tag)
* @param lang - Current language
* @param options - Additional options for processing
* @returns Object containing processed posts and metadata
*/
export async function getTaxonomyPageData(lang: Lang, options?: {
category?: string;
tag?: string;
}) {
// Read all blog posts
// Note: import.meta.glob only accepts literal strings, not variables
let allPosts;
// Use specific glob patterns based on language
if (lang === 'zh') {
allPosts = await import.meta.glob('/src/pages/zh/blog/posts/*.{md,mdx}', { eager: true });
} else {
allPosts = await import.meta.glob('/src/pages/blog/posts/*.{md,mdx}', { eager: true });
}
// Process blog post data
const blogPosts = processRawBlogPosts(Object.values(allPosts), lang);
// Apply filters if specified
let filteredPosts = blogPosts;
if (options?.category) {
filteredPosts = filterPostsByCategory(filteredPosts, options.category);
}
if (options?.tag) {
filteredPosts = filterPostsByTag(filteredPosts, options.tag);
}
// Sort posts by date
const sortedBlogPosts = sortPostsByDate(filteredPosts);
// Extract categories and tags from all posts for sidebar
const allPostsMetadata = extractPostMetadata(Object.values(allPosts));
// Get categories and tags for sidebar
const categories = extractCategories(allPostsMetadata);
const tags = extractTags(allPostsMetadata);
return {
posts: sortedBlogPosts,
categories,
tags,
allPosts
};
}