refactor blog category and tag pages to use new TaxonomyPageLayout add utility functions for processing taxonomy page data pass lang prop to GlassHeader component
309 lines
9.9 KiB
TypeScript
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
|
|
};
|
|
} |