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