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

@@ -0,0 +1,122 @@
import { useState } from 'react';
import type { Lang } from '../i18n/utils';
interface ShareButtonsProps {
lang: Lang;
title: string;
url?: string;
}
export default function ShareButtons({ lang, title, url }: ShareButtonsProps) {
const [copied, setCopied] = useState(false);
const shareUrl = url || (typeof window !== 'undefined' ? window.location.href : '');
const shareText = lang === 'zh' ? '分享这篇文章' : 'Share this article';
const copyText = lang === 'zh' ? '复制链接' : 'Copy link';
const copiedText = lang === 'zh' ? '已复制!' : 'Copied!';
const handleCopyLink = async () => {
try {
await navigator.clipboard.writeText(shareUrl);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (err) {
console.error('Failed to copy link:', err);
}
};
const shareLinks = [
{
name: 'Twitter',
url: `https://twitter.com/intent/tweet?text=${encodeURIComponent(title)}&url=${encodeURIComponent(shareUrl)}`,
icon: (
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"/>
</svg>
),
color: 'hover:bg-blue-500/10 hover:text-blue-500'
},
{
name: 'LinkedIn',
url: `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(shareUrl)}`,
icon: (
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>
</svg>
),
color: 'hover:bg-blue-600/10 hover:text-blue-600'
},
{
name: 'Facebook',
url: `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareUrl)}`,
icon: (
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/>
</svg>
),
color: 'hover:bg-blue-700/10 hover:text-blue-700'
}
];
return (
<div className="bg-card/50 backdrop-blur-sm rounded-2xl border border-border p-6">
<h3 className="text-lg font-semibold text-foreground mb-4 flex items-center">
<svg className="w-5 h-5 mr-2 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.367 2.684 3 3 0 00-5.367-2.684z" />
</svg>
{shareText}
</h3>
<div className="flex flex-wrap gap-3">
{/* Social Share Buttons */}
{shareLinks.map((link) => (
<a
key={link.name}
href={link.url}
target="_blank"
rel="noopener noreferrer"
className={`
flex items-center justify-center w-12 h-12 rounded-full
bg-muted text-muted-foreground transition-all duration-200
${link.color}
`}
title={`Share on ${link.name}`}
>
{link.icon}
</a>
))}
{/* Copy Link Button */}
<button
onClick={handleCopyLink}
className="
flex items-center justify-center w-12 h-12 rounded-full
bg-muted text-muted-foreground transition-all duration-200
hover:bg-purple-500/10 hover:text-purple-500
relative
"
title={copyText}
>
{copied ? (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7" />
</svg>
) : (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
</svg>
)}
</button>
</div>
{/* Copy Success Message */}
{copied && (
<div className="mt-3 text-sm text-purple-500 flex items-center">
<svg className="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7" />
</svg>
{copiedText}
</div>
)}
</div>
);
}