Files
zhaoguiyang.site/src/utils/blog-utils.ts
joyzhao 21fba08741 feat(taxonomy): add unified taxonomy page layout and utilities
refactor blog category and tag pages to use new TaxonomyPageLayout
add utility functions for processing taxonomy page data
pass lang prop to GlassHeader component
2025-06-19 17:22:27 +08:00

309 lines
9.9 KiB
TypeScript

/**
* 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
};
}