refact: update ui
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import { ArrowLeft, Calendar } from 'lucide-react';
|
||||
import SEOHead from '../components/seo/SEOHead';
|
||||
import { useLanguage } from '../contexts/LanguageContext';
|
||||
import { loadContent, type ContentItem } from '../lib/content';
|
||||
@@ -19,8 +20,13 @@ export default function BlogDetailPage() {
|
||||
|
||||
if (!content) {
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto py-16 px-6">
|
||||
<p className="text-gray-600">{language === 'en' ? 'Loading...' : '加载中...'}</p>
|
||||
<div className="max-w-3xl mx-auto py-20 px-6">
|
||||
<div className="animate-pulse space-y-4">
|
||||
<div className="h-4 bg-cream-200 rounded w-24" />
|
||||
<div className="h-8 bg-cream-200 rounded w-3/4" />
|
||||
<div className="h-4 bg-cream-200 rounded w-full" />
|
||||
<div className="h-4 bg-cream-200 rounded w-5/6" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -34,10 +40,44 @@ export default function BlogDetailPage() {
|
||||
type="article"
|
||||
publishedTime={content.meta.date}
|
||||
/>
|
||||
<div className="max-w-4xl mx-auto py-16 px-6">
|
||||
<div className="text-sm text-gray-400 mb-4">{content.meta.date}</div>
|
||||
<div className="max-w-3xl mx-auto py-16 lg:py-20 px-6">
|
||||
{/* Back link */}
|
||||
<Link
|
||||
to="/blog"
|
||||
className="inline-flex items-center gap-1.5 text-sm text-ink-muted hover:text-ink transition-colors mb-8"
|
||||
>
|
||||
<ArrowLeft size={14} />
|
||||
{language === 'en' ? 'All posts' : '所有文章'}
|
||||
</Link>
|
||||
|
||||
{/* Article header */}
|
||||
<header className="mb-10">
|
||||
<div className="flex items-center gap-2 text-sm text-ink-muted mb-4">
|
||||
<Calendar size={14} />
|
||||
<time>{content.meta.date}</time>
|
||||
</div>
|
||||
{content.meta.tags && content.meta.tags.length > 0 && (
|
||||
<div className="flex gap-2 mb-4">
|
||||
{content.meta.tags.map((tag: string) => (
|
||||
<span key={tag} className="text-xs bg-coral-50 text-coral-600 px-2.5 py-1 rounded-full font-medium">
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
|
||||
{/* Article body */}
|
||||
<article
|
||||
className="prose prose-gray max-w-none"
|
||||
className="prose prose-lg prose-warm max-w-none
|
||||
prose-headings:font-display prose-headings:tracking-tight
|
||||
prose-h1:text-3xl prose-h1:font-bold
|
||||
prose-h2:text-2xl prose-h2:font-semibold prose-h2:mt-10
|
||||
prose-a:text-coral-600 prose-a:no-underline hover:prose-a:underline
|
||||
prose-code:text-coral-600 prose-code:bg-coral-50 prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded-md prose-code:text-sm
|
||||
prose-pre:bg-ink prose-pre:text-cream-100 prose-pre:rounded-xl
|
||||
prose-img:rounded-xl
|
||||
prose-blockquote:border-coral-300 prose-blockquote:bg-coral-50/30 prose-blockquote:rounded-r-xl prose-blockquote:py-1"
|
||||
dangerouslySetInnerHTML={{ __html: content.html }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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';
|
||||
@@ -14,27 +15,96 @@ export default function BlogListPage() {
|
||||
});
|
||||
}, [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-4xl mx-auto py-16 px-6">
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-8">{language === 'en' ? 'Blog' : '博客'}</h1>
|
||||
<div className="space-y-6">
|
||||
{posts.map((post) => (
|
||||
<Link key={post.slug} to={`/blog/${post.slug}`} className="block p-6 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors">
|
||||
<div className="text-xs text-gray-400 mb-2">{post.date}</div>
|
||||
<h2 className="text-lg font-semibold text-gray-900">{post.title}</h2>
|
||||
<p className="text-gray-600 mt-1 text-sm">{post.description}</p>
|
||||
{post.tags.length > 0 && (
|
||||
<div className="flex gap-2 mt-3">
|
||||
{post.tags.map(tag => (
|
||||
<span key={tag} className="text-xs bg-gray-200 text-gray-600 px-2 py-0.5 rounded">{tag}</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</Link>
|
||||
))}
|
||||
|
||||
<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>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import { ArrowLeft } from 'lucide-react';
|
||||
import SEOHead from '../components/seo/SEOHead';
|
||||
import { useLanguage } from '../contexts/LanguageContext';
|
||||
import { loadContent, type ContentItem } from '../lib/content';
|
||||
@@ -19,8 +20,12 @@ export default function DocDetailPage() {
|
||||
|
||||
if (!content) {
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto py-16 px-6">
|
||||
<p className="text-gray-600">{language === 'en' ? 'Loading...' : '加载中...'}</p>
|
||||
<div className="max-w-3xl mx-auto py-20 px-6">
|
||||
<div className="animate-pulse space-y-4">
|
||||
<div className="h-4 bg-cream-200 rounded w-24" />
|
||||
<div className="h-8 bg-cream-200 rounded w-3/4" />
|
||||
<div className="h-4 bg-cream-200 rounded w-full" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -32,9 +37,27 @@ export default function DocDetailPage() {
|
||||
description={content.meta.description}
|
||||
path={`/docs/${slug}`}
|
||||
/>
|
||||
<div className="max-w-4xl mx-auto py-16 px-6">
|
||||
<div className="max-w-3xl mx-auto py-16 lg:py-20 px-6">
|
||||
{/* Back link */}
|
||||
<Link
|
||||
to="/docs"
|
||||
className="inline-flex items-center gap-1.5 text-sm text-ink-muted hover:text-ink transition-colors mb-8"
|
||||
>
|
||||
<ArrowLeft size={14} />
|
||||
{language === 'en' ? 'All docs' : '所有文档'}
|
||||
</Link>
|
||||
|
||||
{/* Doc body */}
|
||||
<article
|
||||
className="prose prose-gray max-w-none"
|
||||
className="prose prose-lg prose-warm max-w-none
|
||||
prose-headings:font-display prose-headings:tracking-tight
|
||||
prose-h1:text-3xl prose-h1:font-bold
|
||||
prose-h2:text-2xl prose-h2:font-semibold prose-h2:mt-10
|
||||
prose-a:text-sage-700 prose-a:no-underline hover:prose-a:underline
|
||||
prose-code:text-sage-700 prose-code:bg-sage-50 prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded-md prose-code:text-sm
|
||||
prose-pre:bg-ink prose-pre:text-cream-100 prose-pre:rounded-xl
|
||||
prose-img:rounded-xl
|
||||
prose-blockquote:border-sage-300 prose-blockquote:bg-sage-50/30 prose-blockquote:rounded-r-xl prose-blockquote:py-1"
|
||||
dangerouslySetInnerHTML={{ __html: content.html }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ArrowRight, BookOpen, FileText } from 'lucide-react';
|
||||
import SEOHead from '../components/seo/SEOHead';
|
||||
import { useLanguage } from '../contexts/LanguageContext';
|
||||
import { loadManifest, type ContentMeta } from '../lib/content';
|
||||
@@ -17,16 +18,54 @@ export default function DocsListPage() {
|
||||
return (
|
||||
<>
|
||||
<SEOHead title="Documentation" description="TexPixel documentation and guides" path="/docs" />
|
||||
<div className="max-w-4xl mx-auto py-16 px-6">
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-8">{language === 'en' ? 'Documentation' : '文档'}</h1>
|
||||
|
||||
<div className="max-w-5xl mx-auto py-16 lg:py-20 px-6">
|
||||
{/* Header */}
|
||||
<div className="mb-12">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1.5 bg-sage-50 border border-sage-100 rounded-full text-xs font-medium text-sage-700 mb-6">
|
||||
<BookOpen size={13} />
|
||||
{language === 'en' ? 'Documentation' : '文档中心'}
|
||||
</div>
|
||||
<h1 className="font-display text-4xl lg:text-5xl font-bold text-ink tracking-tight mb-4">
|
||||
{language === 'en' ? 'Learn TexPixel' : '了解 TexPixel'}
|
||||
</h1>
|
||||
<p className="text-ink-secondary text-lg max-w-xl">
|
||||
{language === 'en'
|
||||
? 'Everything you need to get started with formula recognition.'
|
||||
: '公式识别入门所需的一切。'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Docs grid */}
|
||||
<div className="space-y-4">
|
||||
{docs.map((doc) => (
|
||||
<Link key={doc.slug} to={`/docs/${doc.slug}`} className="block p-6 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors">
|
||||
<h2 className="text-lg font-semibold text-gray-900">{doc.title}</h2>
|
||||
<p className="text-gray-600 mt-1 text-sm">{doc.description}</p>
|
||||
<Link
|
||||
key={doc.slug}
|
||||
to={`/docs/${doc.slug}`}
|
||||
className="card p-6 flex items-start gap-5 group hover:border-sage-200 block"
|
||||
>
|
||||
<div className="w-11 h-11 bg-sage-50 rounded-xl flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform">
|
||||
<FileText size={20} className="text-sage-600" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h2 className="font-display text-lg font-semibold text-ink mb-1 group-hover:text-sage-700 transition-colors">
|
||||
{doc.title}
|
||||
</h2>
|
||||
<p className="text-ink-secondary text-sm leading-relaxed">{doc.description}</p>
|
||||
</div>
|
||||
<ArrowRight size={16} className="text-ink-muted group-hover:text-sage-600 transition-colors mt-1 flex-shrink-0" />
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Empty state */}
|
||||
{docs.length === 0 && (
|
||||
<div className="card p-12 text-center">
|
||||
<p className="text-ink-muted text-sm">
|
||||
{language === 'en' ? 'Documentation coming soon.' : '文档即将发布。'}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user