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
This commit is contained in:
joyzhao
2025-06-19 17:22:27 +08:00
parent 7951121c7f
commit 21fba08741
7 changed files with 262 additions and 383 deletions

View File

@@ -1,12 +1,8 @@
---
import BlogLayout from '../../../layouts/BlogLayout.astro';
import BlogList from '../../../components/blog/BlogList.astro';
import CategoryCard from '../../../components/blog/CategoryCard.astro';
import TagCard from '../../../components/blog/TagCard.astro';
import TaxonomyPageLayout from '../../../layouts/TaxonomyPageLayout.astro';
import { type Lang } from '@/i18n/utils';
import { defaultLang } from '@/i18n/ui';
import { type BlogPost } from '@/types';
import { filterPostsByCategory, sortPostsByDate, extractCategories, extractTags } from '@/utils/blog-utils';
import { getTaxonomyPageData } from '@/utils/blog-utils';
// Generate static paths for dynamic routing
export async function getStaticPaths() {
@@ -56,35 +52,9 @@ export interface Props {
const { category } = Astro.params;
const decodedCategory = category ? decodeURIComponent(category) : '';
// Read all blog posts using import.meta.glob
const allPosts = await import.meta.glob('../posts/*.md', { eager: true });
// Process blog post data
const blogPosts: BlogPost[] = Object.values(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',
};
});
// Filter posts by category
const filteredPosts = filterPostsByCategory(blogPosts, decodedCategory);
// Sort posts by date
const sortedBlogPosts = sortPostsByDate(filteredPosts);
// Get taxonomy page data using the utility function
const { posts: sortedBlogPosts, categories, tags, allPosts } =
await getTaxonomyPageData(lang, { category: decodedCategory });
// Find category name matching the current category ID
let categoryName = decodedCategory;
@@ -118,70 +88,19 @@ Object.values(allPosts).forEach((post: any) => {
// Page title and description
const title = `${categoryName} - Blog | Joy Zhao`;
const description = `Explore articles about ${categoryName}. Dive into my thoughts on ${categoryName} and related topics.`;
// Extract categories and tags from all posts for sidebar
const allPostsArray = Object.values(allPosts).map((post: any) => ({
category: post.frontmatter.category || [],
categoryId: post.frontmatter.categoryId || [],
tags: post.frontmatter.tags || [],
tagId: post.frontmatter.tagId || []
}));
// Get categories and tags for sidebar
// Get categories and tags for sidebar
const categories = extractCategories(allPostsArray);
const tags = extractTags(allPostsArray);
---
<BlogLayout title={title} description={description}>
<main class="min-h-screen">
<!-- Header Section -->
<div class="container mx-auto px-4 pt-24 pb-12">
<div class="text-center mb-16">
<h1 class="text-5xl md:text-6xl font-bold bg-gradient-to-r from-foreground via-purple-600 to-purple-800 dark:from-foreground dark:via-purple-200 dark:to-purple-300 bg-clip-text text-transparent mb-6">
Category: <span class="text-purple-500">{categoryName}</span>
</h1>
<p class="text-xl text-muted-foreground max-w-3xl mx-auto">
Explore articles about {categoryName}. Found {sortedBlogPosts.length} article{sortedBlogPosts.length !== 1 ? 's' : ''}.
</p>
</div>
</div>
<!-- Main Content -->
<div class="container mx-auto px-4 pb-20">
<div class="grid grid-cols-1 lg:grid-cols-4 gap-8">
<!-- Sidebar -->
<div class="lg:col-span-1 space-y-8">
<!-- Categories card -->
<CategoryCard lang={lang} currentCategory={decodedCategory} />
<!-- Tags card -->
<TagCard lang={lang} />
</div>
<!-- Blog Posts -->
<div class="lg:col-span-3">
{sortedBlogPosts.length > 0 ? (
<BlogList posts={sortedBlogPosts} lang={lang} category={decodedCategory} />
) : (
<div class="bg-card/50 backdrop-blur-sm rounded-2xl p-8 border border-border text-center">
<h2 class="text-2xl font-semibold mb-4">No articles found</h2>
<p class="text-muted-foreground mb-6">There are no articles in this category yet. Check back later or explore other categories.</p>
<a href={`/${lang}/blog`} class="inline-flex items-center px-4 py-2 rounded-md bg-purple-500 text-white hover:bg-purple-600 transition-colors">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path>
</svg>
Back to all posts
</a>
</div>
)}
</div>
</div>
</div>
</main>
</BlogLayout>
<TaxonomyPageLayout
lang={lang}
title={title}
description={description}
taxonomyType="category"
currentTaxonomy={decodedCategory}
displayName={categoryName}
posts={sortedBlogPosts}
categories={categories}
tags={tags}
/>
<style>
.line-clamp-3 {

View File

@@ -1,12 +1,8 @@
---
import BlogLayout from '../../../layouts/BlogLayout.astro';
import BlogList from '../../../components/blog/BlogList.astro';
import CategoryCard from '../../../components/blog/CategoryCard.astro';
import TagCard from '../../../components/blog/TagCard.astro';
import TaxonomyPageLayout from '../../../layouts/TaxonomyPageLayout.astro';
import { type Lang } from '@/i18n/utils';
import { defaultLang } from '@/i18n/ui';
import { type BlogPost } from '@/types';
import { filterPostsByTag, sortPostsByDate, extractCategories, extractTags } from '@/utils/blog-utils';
import { getTaxonomyPageData } from '@/utils/blog-utils';
// Generate static paths for dynamic routing
export async function getStaticPaths() {
@@ -48,47 +44,9 @@ export interface Props {
const { tag } = Astro.params;
const decodedTag = tag ? decodeURIComponent(tag) : '';
// Read all blog posts using import.meta.glob
const allPosts = await import.meta.glob('../posts/*.md', { eager: true });
// Process blog post data
const blogPosts: BlogPost[] = Object.values(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',
};
});
// Filter posts by tag
const filteredPosts = filterPostsByTag(blogPosts, decodedTag);
// Sort posts by date
const sortedBlogPosts = sortPostsByDate(filteredPosts);
// Extract categories and tags from all posts for sidebar
const allPostsArray = Object.values(allPosts).map((post: any) => ({
category: post.frontmatter.category || [],
categoryId: post.frontmatter.categoryId || [],
tags: post.frontmatter.tags || [],
tagId: post.frontmatter.tagId || []
}));
// Get categories and tags for sidebar
const categories = extractCategories(allPostsArray);
const tags = extractTags(allPostsArray);
// Get taxonomy page data using the utility function
const { posts: sortedBlogPosts, categories, tags, allPosts } =
await getTaxonomyPageData(lang, { tag: decodedTag });
// Find tag name matching the current tag ID
let tagName = decodedTag;
@@ -119,54 +77,17 @@ const title = `#${tagName} - Blog | Joy Zhao`;
const description = `Explore articles tagged with #${tagName}. Dive into my thoughts on ${tagName} and related topics.`;
---
<BlogLayout title={title} description={description}>
<main class="min-h-screen">
<!-- Header Section -->
<section class="container mx-auto px-4 py-12">
<div class="text-center mb-16">
<h1 class="text-5xl md:text-6xl font-bold bg-gradient-to-r from-foreground via-purple-600 to-purple-800 dark:from-foreground dark:via-purple-200 dark:to-purple-300 bg-clip-text text-transparent mb-6">
Tag: <span class="text-purple-500">#{tagName}</span>
</h1>
<p class="text-xl text-muted-foreground max-w-3xl mx-auto">
Explore articles tagged with #{tagName}. Found {sortedBlogPosts.length} article{sortedBlogPosts.length !== 1 ? 's' : ''}.
</p>
</div>
</section>
<!-- Content Section -->
<section class="container mx-auto px-4 pb-16">
<div class="grid grid-cols-1 lg:grid-cols-4 gap-8">
<!-- Sidebar -->
<div class="lg:col-span-1 space-y-8">
<!-- Categories card -->
<CategoryCard lang={lang} />
<!-- Tags card -->
<TagCard lang={lang} currentTag={decodedTag} />
</div>
<!-- Main content -->
<div class="lg:col-span-3">
{sortedBlogPosts.length > 0 ? (
<BlogList posts={sortedBlogPosts} lang={lang} tag={decodedTag} />
) : (
<div class="bg-card/50 backdrop-blur-sm rounded-2xl p-8 border border-border text-center">
<h2 class="text-2xl font-semibold mb-4">No articles found</h2>
<p class="text-muted-foreground mb-6">There are no articles with this tag yet. Check back later or explore other tags.</p>
<a href={`/${lang}/blog`} class="inline-flex items-center px-4 py-2 rounded-md bg-purple-500 text-white hover:bg-purple-600 transition-colors">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path>
</svg>
Back to all posts
</a>
</div>
)}
</div>
</div>
</section>
</main>
</BlogLayout>
<TaxonomyPageLayout
lang={lang}
title={title}
description={description}
taxonomyType="tag"
currentTaxonomy={decodedTag}
displayName={tagName}
posts={sortedBlogPosts}
categories={categories}
tags={tags}
/>
<style>
.line-clamp-3 {