feat(i18n): implement comprehensive blog post enhancements

- Add avatar to personal info in data.ts
- Remove redundant headings from blog posts
- Reorder imports in utils.ts for consistency
- Implement new blog layout components including:
  - PostMeta for displaying post metadata
  - TableOfContents for navigation
  - BlogNavigation for post pagination
  - ShareButtons for social sharing
  - AuthorCard for author information
- Enhance BlogPostLayout with:
  - Improved typography and spacing
  - Responsive sidebar layout
  - Dark mode support
  - Better code block styling
- Remove outdated i18n guide documentation
- Add comprehensive styling for all new components
This commit is contained in:
joyzhao
2025-06-17 19:37:36 +08:00
parent d22174e0dc
commit e5497e5e6d
18 changed files with 894 additions and 387 deletions

View File

@@ -31,9 +31,12 @@ const lang = Astro.currentLocale as Lang || defaultLang;
/>
</head>
<body class="min-h-screen bg-background font-sans antialiased selection:bg-purple-500/20 selection:text-purple-500">
<div
class="fixed inset-0 -z-10 h-full w-full bg-background bg-[radial-gradient(ellipse_80%_80%_at_50%_-20%,rgba(120,119,198,0.3),rgba(255,255,255,0))]"
>
<!-- Enhanced background with gradient overlay for better visual consistency -->
<div class="fixed inset-0 -z-10 h-full w-full bg-background">
<!-- Base radial gradient -->
<div class="absolute inset-0 bg-[radial-gradient(ellipse_80%_80%_at_50%_-20%,rgba(120,119,198,0.3),rgba(255,255,255,0))] dark:bg-[radial-gradient(ellipse_80%_80%_at_50%_-20%,rgba(120,119,198,0.2),rgba(0,0,0,0))]"></div>
<!-- Additional subtle gradient for blog pages -->
<div class="absolute inset-0 bg-gradient-to-br from-purple-50/30 via-transparent to-blue-50/20 dark:from-purple-950/20 dark:via-transparent dark:to-blue-950/10"></div>
</div>
<!-- Glass Header with navigation -->
<GlassHeader lang={lang} client:load />

View File

@@ -1,24 +1,49 @@
---
import type { MarkdownLayoutProps } from 'astro';
import { type Lang } from '@/i18n/utils';
import { defaultLang } from '@/i18n/ui';
import GlassHeader from '@/components/GlassHeader';
import Footer from '@/components/Footer';
import AuthorCard from '@/components/AuthorCard';
import TableOfContents from '@/components/layout/TableOfContents.astro';
import BlogNavigation from '@/components/layout/BlogNavigation.astro';
import PostMeta from '@/components/blog/PostMeta.astro';
import "../styles/global.css";
export interface Props {
// Define the frontmatter structure
interface FrontmatterProps {
title: string;
description?: string;
publishDate?: string;
date?: string; // Alternative field name for publish date
author?: string;
tags?: string[];
category?: string | string[];
readingTime?: number;
readTime?: string; // Alternative field name for reading time
}
// Use Astro's MarkdownLayoutProps for proper type safety
export type Props = MarkdownLayoutProps<FrontmatterProps>;
// Access frontmatter data correctly for markdown layouts
const { frontmatter } = Astro.props;
const {
title,
description = 'Explore my latest thoughts on coding, tech trends, and developer life.',
description,
publishDate,
author = 'Zhao Guiyang'
} = Astro.props;
date,
tags,
category,
readTime,
} = frontmatter;
const lang = Astro.currentLocale as Lang || defaultLang;
// Handle different field names for backward compatibility
const finalPublishDate = publishDate || date;
const finalReadingTime = readTime ? parseInt(readTime.replace(/\D/g, '')) : undefined;
---
<!doctype html>
@@ -45,33 +70,63 @@ const lang = Astro.currentLocale as Lang || defaultLang;
<!-- Glass Header with navigation -->
<GlassHeader lang={lang} client:load />
<!-- Main content with proper spacing for fixed header -->
<div class="pt-16">
<main class="container mx-auto px-4 py-8 max-w-4xl">
<!-- Blog post header -->
<header class="mb-8 text-center">
<h1 class="text-4xl font-bold text-foreground mb-4">{title}</h1>
{publishDate && (
<div class="text-muted-foreground mb-2">
<time datetime={publishDate}>{new Date(publishDate).toLocaleDateString(lang === 'zh' ? 'zh-CN' : 'en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
})}</time>
</div>
)}
{author && (
<div class="text-muted-foreground">
By {author}
</div>
)}
</header>
<!-- Blog post content with typography styles -->
<article class="prose prose-lg dark:prose-invert max-w-none prose-headings:text-foreground prose-p:text-muted-foreground prose-strong:text-foreground prose-code:text-foreground prose-pre:bg-muted prose-blockquote:border-l-primary prose-blockquote:text-muted-foreground prose-a:text-primary hover:prose-a:text-primary/80 prose-li:text-muted-foreground prose-img:rounded-lg prose-img:shadow-lg">
<slot />
</article>
</main>
<div class="pt-16 sm:pt-20">
<div class="container mx-auto px-4 sm:px-6 lg:px-8 py-6 sm:py-8">
<div class="max-w-7xl mx-auto">
<div class="grid grid-cols-1 xl:grid-cols-4 gap-6 lg:gap-8">
<!-- Main Content -->
<main class="xl:col-span-3 order-2 xl:order-1">
<!-- Blog post header -->
<header class="mb-10">
<h1 class="text-3xl sm:text-4xl lg:text-5xl font-bold text-foreground mb-6 leading-tight tracking-tight">
{title}
</h1>
{description && (
<p class="text-lg sm:text-xl text-muted-foreground mb-8 leading-relaxed max-w-4xl">
{description}
</p>
)}
<PostMeta
lang={lang}
publishDate={finalPublishDate}
readingTime={finalReadingTime}
tags={tags}
category={category}
className="justify-start"
/>
</header>
<!-- Blog post content with typography styles -->
<article class="prose prose-lg dark:prose-invert max-w-none prose-headings:scroll-mt-24 prose-headings:font-bold prose-headings:tracking-tight prose-h1:text-2xl sm:prose-h1:text-3xl prose-h2:text-xl sm:prose-h2:text-2xl prose-h3:text-lg sm:prose-h3:text-xl prose-h4:text-base sm:prose-h4:text-lg prose-p:text-base sm:prose-p:text-lg prose-p:leading-relaxed prose-p:mb-6 prose-a:text-primary prose-a:no-underline hover:prose-a:underline prose-strong:text-foreground prose-code:text-sm prose-code:px-2 prose-code:py-1 prose-code:rounded prose-pre:bg-muted prose-pre:border prose-pre:text-sm prose-blockquote:border-l-primary prose-blockquote:bg-muted/30 prose-blockquote:text-base prose-li:text-base sm:prose-li:text-lg prose-li:leading-relaxed">
<slot />
</article>
<!-- Blog Navigation -->
<div class="mt-8 sm:mt-12">
<BlogNavigation />
</div>
</main>
<!-- Sidebar -->
<aside class="xl:col-span-1 order-1 xl:order-2">
<div class="xl:sticky xl:top-24 space-y-6 sm:space-y-8 xl:space-y-12 xl:max-h-[calc(100vh-6rem)] xl:overflow-y-auto">
<!-- Table of Contents -->
<div class="bg-card/50 backdrop-blur-sm rounded-2xl border border-border h-auto xl:h-[400px] lg:h-[500px] overflow-y-auto">
<TableOfContents lang={lang} />
</div>
<!-- Author Card -->
<AuthorCard lang={lang} />
</div>
</aside>
</div>
</div>
</div>
</div>
<!-- Footer -->
@@ -122,4 +177,147 @@ const lang = Astro.currentLocale as Lang || defaultLang;
background-color var(--transition-standard),
color var(--transition-standard);
}
/* Enhanced prose styles for better readability */
.prose {
line-height: 1.75;
font-size: 1rem;
}
@media (min-width: 640px) {
.prose {
font-size: 1.125rem;
}
}
.prose h1,
.prose h2,
.prose h3,
.prose h4 {
scroll-margin-top: 6rem;
font-weight: 700;
letter-spacing: -0.025em;
margin-top: 2rem;
margin-bottom: 1rem;
}
/* Hide the first H1 in markdown content to avoid duplicate titles */
.prose h1:first-of-type {
display: none;
}
.prose h1 {
font-size: 1.5rem;
line-height: 1.2;
}
@media (min-width: 640px) {
.prose h1 {
font-size: 1.875rem;
}
}
.prose h2 {
font-size: 1.25rem;
line-height: 1.3;
}
@media (min-width: 640px) {
.prose h2 {
font-size: 1.5rem;
}
}
.prose h3 {
font-size: 1.125rem;
line-height: 1.4;
}
@media (min-width: 640px) {
.prose h3 {
font-size: 1.25rem;
}
}
.prose h4 {
font-size: 1rem;
line-height: 1.5;
}
@media (min-width: 640px) {
.prose h4 {
font-size: 1.125rem;
}
}
.prose p {
margin-bottom: 1.5rem;
line-height: 1.75;
}
.prose li {
margin-bottom: 0.5rem;
line-height: 1.75;
}
.prose img {
margin: 2rem auto;
border-radius: 0.75rem;
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
.prose pre {
border-radius: 0.75rem;
border: 1px solid hsl(var(--border));
font-size: 0.875rem;
line-height: 1.5;
}
.prose code {
font-size: 0.875rem;
padding: 0.125rem 0.25rem;
border-radius: 0.25rem;
}
.prose blockquote {
border-left: 4px solid hsl(var(--primary));
background: hsl(var(--muted) / 0.3);
border-radius: 0 0.5rem 0.5rem 0;
padding: 1rem 1.5rem;
margin: 1.5rem 0;
font-size: 1rem;
}
@media (min-width: 640px) {
.prose blockquote {
font-size: 1.125rem;
}
}
/* Responsive sidebar */
@media (max-width: 1023px) {
aside {
order: -1;
margin-bottom: 2rem;
}
.sticky {
position: static !important;
}
}
/* Line clamp utilities */
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.line-clamp-3 {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
</style>