112 lines
4.1 KiB
TypeScript
112 lines
4.1 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { Link } from 'react-router-dom';
|
|
import { ArrowRight, Calendar } from 'lucide-react';
|
|
import SEOHead from '../components/seo/SEOHead';
|
|
import { useLanguage } from '../contexts/LanguageContext';
|
|
import { loadManifest, type ContentMeta } from '../lib/content';
|
|
|
|
export default function BlogListPage() {
|
|
const { language } = useLanguage();
|
|
const [posts, setPosts] = useState<ContentMeta[]>([]);
|
|
|
|
useEffect(() => {
|
|
loadManifest('blog').then(manifest => {
|
|
setPosts(manifest[language] || []);
|
|
});
|
|
}, [language]);
|
|
|
|
const featured = posts[0];
|
|
const rest = posts.slice(1);
|
|
|
|
return (
|
|
<>
|
|
<SEOHead title="Blog" description="TexPixel blog — updates, tutorials, and insights" path="/blog" />
|
|
|
|
<div className="max-w-5xl mx-auto py-16 lg:py-20 px-6">
|
|
{/* Header */}
|
|
<div className="mb-12">
|
|
<h1 className="font-display text-4xl lg:text-5xl font-bold text-ink tracking-tight mb-4">
|
|
{language === 'en' ? 'Blog' : '博客'}
|
|
</h1>
|
|
<p className="text-ink-secondary text-lg max-w-xl">
|
|
{language === 'en'
|
|
? 'Updates, tutorials, and insights on formula recognition and LaTeX.'
|
|
: '关于公式识别和 LaTeX 的更新、教程和见解。'}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Featured post */}
|
|
{featured && (
|
|
<Link
|
|
to={`/blog/${featured.slug}`}
|
|
className="card block p-8 mb-8 group hover:border-coral-200"
|
|
>
|
|
<div className="flex items-center gap-2 text-xs text-ink-muted mb-3">
|
|
<Calendar size={13} />
|
|
<time>{featured.date}</time>
|
|
</div>
|
|
<h2 className="font-display text-2xl lg:text-3xl font-bold text-ink mb-3 group-hover:text-coral-600 transition-colors">
|
|
{featured.title}
|
|
</h2>
|
|
<p className="text-ink-secondary leading-relaxed mb-4 max-w-2xl">
|
|
{featured.description}
|
|
</p>
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex gap-2">
|
|
{featured.tags.map(tag => (
|
|
<span key={tag} className="text-xs bg-coral-50 text-coral-600 px-2.5 py-1 rounded-full font-medium">
|
|
{tag}
|
|
</span>
|
|
))}
|
|
</div>
|
|
<span className="inline-flex items-center gap-1 text-sm font-medium text-coral-500 group-hover:gap-2 transition-all">
|
|
{language === 'en' ? 'Read more' : '阅读全文'}
|
|
<ArrowRight size={14} />
|
|
</span>
|
|
</div>
|
|
</Link>
|
|
)}
|
|
|
|
{/* Rest of posts */}
|
|
{rest.length > 0 && (
|
|
<div className="grid md:grid-cols-2 gap-5">
|
|
{rest.map((post) => (
|
|
<Link
|
|
key={post.slug}
|
|
to={`/blog/${post.slug}`}
|
|
className="card p-6 group hover:border-coral-200"
|
|
>
|
|
<div className="text-xs text-ink-muted mb-2">
|
|
<time>{post.date}</time>
|
|
</div>
|
|
<h2 className="font-display text-lg font-semibold text-ink mb-2 group-hover:text-coral-600 transition-colors">
|
|
{post.title}
|
|
</h2>
|
|
<p className="text-ink-secondary text-sm leading-relaxed mb-3">{post.description}</p>
|
|
{post.tags.length > 0 && (
|
|
<div className="flex gap-2">
|
|
{post.tags.map(tag => (
|
|
<span key={tag} className="text-[11px] bg-cream-200 text-ink-secondary px-2 py-0.5 rounded-full">
|
|
{tag}
|
|
</span>
|
|
))}
|
|
</div>
|
|
)}
|
|
</Link>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{/* Empty state */}
|
|
{posts.length === 0 && (
|
|
<div className="card p-12 text-center">
|
|
<p className="text-ink-muted text-sm">
|
|
{language === 'en' ? 'No posts yet. Check back soon!' : '暂无文章,敬请期待!'}
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</>
|
|
);
|
|
}
|