feat: enhance textbook reader with anchor text support and improve knowledge point management
This commit is contained in:
@@ -35,9 +35,10 @@ interface SortableChapterItemProps {
|
||||
textbookId: string
|
||||
onDelete: (chapter: Chapter) => void
|
||||
onCreateSub: (parentId: string) => void
|
||||
canEdit?: boolean
|
||||
}
|
||||
|
||||
function SortableChapterItem({ chapter, level, selectedId, onSelect, textbookId, onDelete, onCreateSub }: SortableChapterItemProps) {
|
||||
function SortableChapterItem({ chapter, level, selectedId, onSelect, textbookId, onDelete, onCreateSub, canEdit = true }: SortableChapterItemProps) {
|
||||
const [isOpen, setIsOpen] = useState(level === 0)
|
||||
const hasChildren = chapter.children && chapter.children.length > 0
|
||||
const isSelected = chapter.id === selectedId
|
||||
@@ -49,7 +50,7 @@ function SortableChapterItem({ chapter, level, selectedId, onSelect, textbookId,
|
||||
transform,
|
||||
transition,
|
||||
isDragging,
|
||||
} = useSortable({ id: chapter.id })
|
||||
} = useSortable({ id: chapter.id, disabled: !canEdit })
|
||||
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
@@ -66,9 +67,11 @@ function SortableChapterItem({ chapter, level, selectedId, onSelect, textbookId,
|
||||
isSelected ? "bg-accent text-accent-foreground font-medium" : "hover:bg-muted/50 text-muted-foreground hover:text-foreground",
|
||||
isDragging && "opacity-50"
|
||||
)}>
|
||||
<div {...attributes} {...listeners} className="mr-1 cursor-grab opacity-0 group-hover:opacity-100 transition-opacity p-1 hover:bg-muted rounded">
|
||||
<GripVertical className="h-3.5 w-3.5 text-muted-foreground/50" />
|
||||
</div>
|
||||
{canEdit && (
|
||||
<div {...attributes} {...listeners} className="mr-1 cursor-grab opacity-0 group-hover:opacity-100 transition-opacity p-1 hover:bg-muted rounded">
|
||||
<GripVertical className="h-3.5 w-3.5 text-muted-foreground/50" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasChildren ? (
|
||||
<CollapsibleTrigger asChild>
|
||||
@@ -103,7 +106,7 @@ function SortableChapterItem({ chapter, level, selectedId, onSelect, textbookId,
|
||||
<span className="truncate text-sm">{chapter.title}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center opacity-0 group-hover:opacity-100 transition-opacity ml-2">
|
||||
<div className={cn("flex items-center opacity-0 group-hover:opacity-100 transition-opacity ml-2", !canEdit && "hidden")}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
@@ -142,6 +145,7 @@ function SortableChapterItem({ chapter, level, selectedId, onSelect, textbookId,
|
||||
textbookId={textbookId}
|
||||
onDelete={onDelete}
|
||||
onCreateSub={onCreateSub}
|
||||
canEdit={canEdit}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -151,14 +155,15 @@ function SortableChapterItem({ chapter, level, selectedId, onSelect, textbookId,
|
||||
)
|
||||
}
|
||||
|
||||
function RecursiveSortableList({ items, level, selectedId, onSelect, textbookId, onDelete, onCreateSub }: {
|
||||
function RecursiveSortableList({ items, level, selectedId, onSelect, textbookId, onDelete, onCreateSub, canEdit = true }: {
|
||||
items: Chapter[],
|
||||
level: number,
|
||||
selectedId?: string,
|
||||
onSelect: (c: Chapter) => void,
|
||||
textbookId: string,
|
||||
onDelete: (c: Chapter) => void,
|
||||
onCreateSub: (pid: string) => void
|
||||
onCreateSub: (pid: string) => void,
|
||||
canEdit?: boolean
|
||||
}) {
|
||||
return (
|
||||
<SortableContext items={items.map(i => i.id)} strategy={verticalListSortingStrategy}>
|
||||
@@ -172,6 +177,7 @@ function RecursiveSortableList({ items, level, selectedId, onSelect, textbookId,
|
||||
textbookId={textbookId}
|
||||
onDelete={onDelete}
|
||||
onCreateSub={onCreateSub}
|
||||
canEdit={canEdit}
|
||||
/>
|
||||
))}
|
||||
</SortableContext>
|
||||
@@ -183,9 +189,10 @@ interface ChapterSidebarListProps {
|
||||
selectedChapterId?: string
|
||||
onSelectChapter: (chapter: Chapter) => void
|
||||
textbookId: string
|
||||
canEdit?: boolean
|
||||
}
|
||||
|
||||
export function ChapterSidebarList({ chapters, selectedChapterId, onSelectChapter, textbookId }: ChapterSidebarListProps) {
|
||||
export function ChapterSidebarList({ chapters, selectedChapterId, onSelectChapter, textbookId, canEdit = true }: ChapterSidebarListProps) {
|
||||
const [showCreateDialog, setShowCreateDialog] = useState(false)
|
||||
const [createParentId, setCreateParentId] = useState<string | undefined>(undefined)
|
||||
|
||||
@@ -300,8 +307,9 @@ export function ChapterSidebarList({ chapters, selectedChapterId, onSelectChapte
|
||||
setShowCreateDialog(true)
|
||||
}
|
||||
|
||||
return (
|
||||
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
|
||||
// If not editable, we can skip dnd logic
|
||||
if (!canEdit) {
|
||||
return (
|
||||
<RecursiveSortableList
|
||||
items={chapters}
|
||||
level={0}
|
||||
@@ -310,6 +318,22 @@ export function ChapterSidebarList({ chapters, selectedChapterId, onSelectChapte
|
||||
textbookId={textbookId}
|
||||
onDelete={handleDeleteRequest}
|
||||
onCreateSub={handleCreateSubRequest}
|
||||
canEdit={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<DndContext id="chapter-sidebar-dnd" sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
|
||||
<RecursiveSortableList
|
||||
items={chapters}
|
||||
level={0}
|
||||
selectedId={selectedChapterId}
|
||||
onSelect={onSelectChapter}
|
||||
textbookId={textbookId}
|
||||
onDelete={handleDeleteRequest}
|
||||
onCreateSub={handleCreateSubRequest}
|
||||
canEdit={true}
|
||||
/>
|
||||
|
||||
<CreateChapterDialog
|
||||
|
||||
Reference in New Issue
Block a user