Files
CICD/src/modules/textbooks/components/textbook-content-layout.tsx
SpecialX 57807def37
Some checks failed
CI / build-and-test (push) Failing after 3m50s
CI / deploy (push) Has been skipped
完整性更新
现在已经实现了大部分基础功能
2026-01-08 11:14:03 +08:00

139 lines
5.7 KiB
TypeScript

"use client"
import { useState } from "react"
import ReactMarkdown from "react-markdown"
import remarkBreaks from "remark-breaks"
import remarkGfm from "remark-gfm"
import { Chapter, KnowledgePoint } from "../types"
import { ChapterSidebarList } from "./chapter-sidebar-list"
import { KnowledgePointPanel } from "./knowledge-point-panel"
import { ScrollArea } from "@/shared/components/ui/scroll-area"
import { Button } from "@/shared/components/ui/button"
import { Edit2, Save } from "lucide-react"
import { CreateChapterDialog } from "./create-chapter-dialog"
import { updateChapterContentAction } from "../actions"
import { toast } from "sonner"
import { Textarea } from "@/shared/components/ui/textarea"
interface TextbookContentLayoutProps {
chapters: Chapter[]
knowledgePoints: KnowledgePoint[]
textbookId: string
}
export function TextbookContentLayout({ chapters, knowledgePoints, textbookId }: TextbookContentLayoutProps) {
const [selectedChapter, setSelectedChapter] = useState<Chapter | null>(null)
const [isEditing, setIsEditing] = useState(false)
const [editContent, setEditContent] = useState("")
const [isSaving, setIsSaving] = useState(false)
// Sync edit content when selection changes
const handleSelectChapter = (chapter: Chapter) => {
setSelectedChapter(chapter)
setEditContent(chapter.content || "")
setIsEditing(false)
}
const handleSaveContent = async () => {
if (!selectedChapter) return
setIsSaving(true)
const result = await updateChapterContentAction(selectedChapter.id, editContent, textbookId)
setIsSaving(false)
if (result.success) {
toast.success(result.message)
setIsEditing(false)
setSelectedChapter((prev) => (prev ? { ...prev, content: editContent } : prev))
} else {
toast.error(result.message)
}
}
return (
<div className="grid grid-cols-12 gap-6 h-[calc(100vh-140px)]">
{/* Left Sidebar: TOC (3 cols) */}
<div className="col-span-3 border-r pr-6 flex flex-col h-full">
<div className="flex items-center justify-between mb-4 px-2">
<h3 className="font-semibold">Chapters</h3>
<CreateChapterDialog textbookId={textbookId} />
</div>
<ScrollArea className="flex-1 px-2">
<ChapterSidebarList
chapters={chapters}
selectedChapterId={selectedChapter?.id}
onSelectChapter={handleSelectChapter}
textbookId={textbookId}
/>
</ScrollArea>
</div>
{/* Middle: Content Viewer/Editor (6 cols) */}
<div className="col-span-6 flex flex-col h-full px-2 min-h-0">
{selectedChapter ? (
<>
<div className="flex items-center justify-between mb-4 pb-2 border-b">
<h2 className="text-xl font-bold tracking-tight">{selectedChapter.title}</h2>
<div className="flex gap-2">
{isEditing ? (
<>
<Button size="sm" variant="ghost" onClick={() => setIsEditing(false)} disabled={isSaving}>
Cancel
</Button>
<Button size="sm" onClick={handleSaveContent} disabled={isSaving}>
<Save className="mr-2 h-4 w-4" />
{isSaving ? "Saving..." : "Save"}
</Button>
</>
) : (
<Button size="sm" variant="outline" onClick={() => setIsEditing(true)}>
<Edit2 className="mr-2 h-4 w-4" />
Edit Content
</Button>
)}
</div>
</div>
<ScrollArea className="flex-1 min-h-0">
<div className="p-4 min-h-full">
{isEditing ? (
<Textarea
className="min-h-[500px] font-mono text-sm"
value={editContent}
onChange={(e) => setEditContent(e.target.value)}
placeholder="# Write markdown content here..."
/>
) : (
<div className="prose prose-sm dark:prose-invert max-w-none">
{selectedChapter.content ? (
<ReactMarkdown remarkPlugins={[remarkGfm, remarkBreaks]}>
{selectedChapter.content}
</ReactMarkdown>
) : (
<div className="text-muted-foreground italic py-8 text-center">
No content available. Click edit to add content.
</div>
)}
</div>
)}
</div>
</ScrollArea>
</>
) : (
<div className="h-full flex items-center justify-center text-muted-foreground">
Select a chapter from the left sidebar to view its content.
</div>
)}
</div>
{/* Right Sidebar: Knowledge Points (3 cols) */}
<div className="col-span-3 border-l pl-6 flex flex-col h-full">
<KnowledgePointPanel
knowledgePoints={knowledgePoints}
selectedChapterId={selectedChapter?.id || null}
textbookId={textbookId}
/>
</div>
</div>
)
}