neurapress/src/components/editor/ArticleList.tsx
2025-01-29 20:22:05 +08:00

190 lines
5.6 KiB
TypeScript

'use client'
import { useState, useEffect } from 'react'
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button'
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
SheetTrigger,
} from '@/components/ui/sheet'
import { ScrollArea } from '@/components/ui/scroll-area'
import { FileText, Trash2, Menu, Plus, Save } from 'lucide-react'
import { useToast } from '@/components/ui/use-toast'
import { ToastAction } from '@/components/ui/toast'
interface Article {
id: string
title: string
content: string
template: string
createdAt: number
updatedAt: number
}
interface ArticleListProps {
onSelect: (article: Article) => void
currentContent?: string
onNew?: () => void
}
export function ArticleList({ onSelect, currentContent, onNew }: ArticleListProps) {
const { toast } = useToast()
const [articles, setArticles] = useState<Article[]>([])
// 加载文章列表
useEffect(() => {
const savedArticles = localStorage.getItem('wechat_articles')
if (savedArticles) {
try {
const parsed = JSON.parse(savedArticles)
setArticles(Array.isArray(parsed) ? parsed : [])
} catch (error) {
console.error('Failed to parse saved articles:', error)
}
}
}, [])
// 保存当前文章
const saveCurrentArticle = () => {
if (!currentContent) {
toast({
variant: "destructive",
title: "保存失败",
description: "当前没有可保存的内容",
duration: 2000
})
return
}
const title = currentContent.split('\n')[0]?.replace(/^#*\s*/, '') || '未命名文章'
const newArticle: Article = {
id: Date.now().toString(),
title,
content: currentContent,
template: 'creative', // 默认模板
createdAt: Date.now(),
updatedAt: Date.now()
}
const updatedArticles = [newArticle, ...articles]
setArticles(updatedArticles)
localStorage.setItem('wechat_articles', JSON.stringify(updatedArticles))
toast({
title: "保存成功",
description: `已保存文章:${title}`,
duration: 2000
})
}
// 删除文章
const deleteArticle = (id: string) => {
const updatedArticles = articles.filter(article => article.id !== id)
setArticles(updatedArticles)
localStorage.setItem('wechat_articles', JSON.stringify(updatedArticles))
toast({
title: "删除成功",
description: "文章已删除",
duration: 2000
})
}
// 新建文章
const createNewArticle = () => {
// 如果有外部传入的新建处理函数,优先使用
if (onNew) {
onNew()
return
}
// 默认的新建文章处理
const newArticle: Article = {
id: Date.now().toString(),
title: '新文章',
content: '# 新文章\n\n开始写作...',
template: 'creative',
createdAt: Date.now(),
updatedAt: Date.now()
}
onSelect(newArticle)
toast({
title: "新建成功",
description: "已创建新文章",
duration: 2000
})
}
return (
<Sheet>
<SheetTrigger asChild>
<Button variant="ghost" size="icon" className="relative">
<Menu className="h-5 w-5" />
<span className="sr-only"></span>
{articles.length > 0 && (
<span className="absolute -top-1 -right-1 w-4 h-4 bg-primary text-[10px] text-primary-foreground rounded-full flex items-center justify-center">
{articles.length}
</span>
)}
</Button>
</SheetTrigger>
<SheetContent side="left" className="w-[300px] sm:w-[400px]">
<SheetHeader>
<SheetTitle></SheetTitle>
<SheetDescription className="flex gap-2">
<Button onClick={createNewArticle} className="flex-1">
<Plus className="h-4 w-4 mr-2" />
</Button>
<Button onClick={saveCurrentArticle} className="flex-1">
<Save className="h-4 w-4 mr-2" />
</Button>
</SheetDescription>
</SheetHeader>
<ScrollArea className="h-[calc(100vh-8rem)] mt-4">
<div className="space-y-2">
{articles.map(article => (
<div
key={article.id}
className="flex items-center justify-between p-2 rounded-md hover:bg-muted group"
>
<button
onClick={() => onSelect(article)}
className="flex items-center gap-2 flex-1 text-left"
>
<FileText className="h-4 w-4 text-muted-foreground" />
<div className="flex-1 min-w-0">
<div className="font-medium truncate">{article.title}</div>
<div className="text-xs text-muted-foreground">
{new Date(article.updatedAt).toLocaleString()}
</div>
</div>
</button>
<Button
variant="ghost"
size="icon"
className="opacity-0 group-hover:opacity-100 transition-opacity"
onClick={() => deleteArticle(article.id)}
>
<Trash2 className="h-4 w-4 text-destructive" />
<span className="sr-only"></span>
</Button>
</div>
))}
{articles.length === 0 && (
<div className="text-center text-muted-foreground py-8">
</div>
)}
</div>
</ScrollArea>
</SheetContent>
</Sheet>
)
}