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:
142
src/components/blog/PostMeta.astro
Normal file
142
src/components/blog/PostMeta.astro
Normal file
@@ -0,0 +1,142 @@
|
||||
---
|
||||
import { type Lang } from '@/i18n/utils';
|
||||
import { useTranslations } from '@/i18n/utils';
|
||||
|
||||
export interface Props {
|
||||
lang: Lang;
|
||||
publishDate?: string;
|
||||
readingTime?: number;
|
||||
tags?: string[];
|
||||
category?: string | string[];
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
lang,
|
||||
publishDate,
|
||||
readingTime,
|
||||
tags,
|
||||
category,
|
||||
className = ''
|
||||
} = Astro.props;
|
||||
|
||||
const t = useTranslations(lang);
|
||||
|
||||
/**
|
||||
* Format date according to locale
|
||||
*/
|
||||
const formatDate = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString(lang === 'zh' ? 'zh-CN' : 'en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get reading time text based on language
|
||||
*/
|
||||
const getReadingTimeText = (minutes: number) => {
|
||||
if (lang === 'zh') {
|
||||
return `${minutes} 分钟阅读`;
|
||||
}
|
||||
return `${minutes} min read`;
|
||||
};
|
||||
---
|
||||
|
||||
<div class={`space-y-6 ${className}`} data-component="PostMeta">
|
||||
|
||||
<!-- Primary Meta Info: Date and Reading Time -->
|
||||
<div class="flex flex-wrap items-center gap-4 text-sm md:text-base text-muted-foreground">
|
||||
<!-- Publish Date -->
|
||||
{publishDate && (
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-4 h-4 md:w-5 md:h-5 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
|
||||
<line x1="16" y1="2" x2="16" y2="6"></line>
|
||||
<line x1="8" y1="2" x2="8" y2="6"></line>
|
||||
<line x1="3" y1="10" x2="21" y2="10"></line>
|
||||
</svg>
|
||||
<time datetime={publishDate} class="font-medium">
|
||||
{formatDate(publishDate)}
|
||||
</time>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<!-- Reading Time -->
|
||||
{readingTime && (
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-4 h-4 md:w-5 md:h-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<polyline points="12,6 12,12 16,14"></polyline>
|
||||
</svg>
|
||||
<span class="font-medium">{getReadingTimeText(readingTime)}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<!-- Secondary Meta Info: Category and Tags -->
|
||||
{(category || (tags && tags.length > 0)) && (
|
||||
<div class="space-y-3">
|
||||
|
||||
<!-- Category Section -->
|
||||
{category && (
|
||||
<div class="space-y-2">
|
||||
<div class="text-xs font-medium text-muted-foreground uppercase tracking-wide">
|
||||
分类
|
||||
</div>
|
||||
<div class="flex gap-2 flex-wrap">
|
||||
{Array.isArray(category) ? (
|
||||
category.map((cat) => (
|
||||
<span class="inline-flex items-center px-3 py-1.5 rounded-full text-xs font-medium bg-gradient-to-r from-purple-500 to-purple-600 text-white shadow-sm hover:shadow-md transition-all duration-200 hover:scale-105">
|
||||
{cat}
|
||||
</span>
|
||||
))
|
||||
) : (
|
||||
<span class="inline-flex items-center px-3 py-1.5 rounded-full text-xs font-medium bg-gradient-to-r from-purple-500 to-purple-600 text-white shadow-sm hover:shadow-md transition-all duration-200 hover:scale-105">
|
||||
{category}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<!-- Tags Section -->
|
||||
{tags && tags.length > 0 && (
|
||||
<div class="space-y-2">
|
||||
<div class="text-xs font-medium text-muted-foreground uppercase tracking-wide">
|
||||
标签
|
||||
</div>
|
||||
<div class="flex gap-2 flex-wrap">
|
||||
{tags.map((tag) => (
|
||||
<span class="inline-flex items-center px-2.5 py-1 rounded-md text-xs font-medium bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700 transition-colors border border-gray-200 dark:border-gray-600">
|
||||
# {tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* Ensure smooth transitions for hover effects */
|
||||
.transition-colors {
|
||||
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 640px) {
|
||||
.flex-wrap {
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.text-sm {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user