diff --git a/web/app/dashboard/knowledge/_components/knowledge-directory-panel.tsx b/web/app/dashboard/knowledge/_components/knowledge-directory-panel.tsx index 088a4bd..c0d7ec8 100644 --- a/web/app/dashboard/knowledge/_components/knowledge-directory-panel.tsx +++ b/web/app/dashboard/knowledge/_components/knowledge-directory-panel.tsx @@ -6,6 +6,7 @@ import { MouseSensor, TouchSensor, closestCenter, + useDroppable, useSensor, useSensors, type DragEndEvent, @@ -61,7 +62,11 @@ import { } from "@/lib/api/admin"; import { useI18n } from "@/i18n/provider"; import { cn } from "@/lib/utils"; -import { findDirectoryParentId, moveDirectoryWithinParent } from "./knowledge-directory-sort"; +import { + findDirectoryParentId, + moveDirectoryToParent, + moveDirectoryWithinParent, +} from "./knowledge-directory-sort"; type KnowledgeDirectoryPanelProps = { knowledgeBaseId: number; @@ -84,6 +89,7 @@ const DIRECTORY_PANEL_WIDTH_STORAGE_KEY = "knowledge-directory-panel-width"; const DIRECTORY_PANEL_MIN_WIDTH = 180; const DIRECTORY_PANEL_MAX_WIDTH = 360; const DIRECTORY_PANEL_DEFAULT_WIDTH = 224; +const ROOT_DIRECTORY_DROP_ID = "knowledge-directory-root"; function rootDirectoryOptions(items: KnowledgeDirectory[]): DirectoryOption[] { return items.map((item) => ({ value: String(item.id), label: item.name })); @@ -305,14 +311,29 @@ export function KnowledgeDirectoryPanel({ return; } const activeId = Number(active.id); + if (!Number.isFinite(activeId)) { + return; + } + if (over.id === ROOT_DIRECTORY_DROP_ID) { + await handleDirectoryMoveToParent(activeId, 0); + return; + } const overId = Number(over.id); - if (!Number.isFinite(activeId) || !Number.isFinite(overId)) { + if (!Number.isFinite(overId)) { return; } const activeParentId = findDirectoryParentId(directories, activeId); const overParentId = findDirectoryParentId(directories, overId); - if (activeParentId === null || overParentId === null || activeParentId !== overParentId) { + if (activeParentId === null || overParentId === null) { + return; + } + if (activeParentId !== overParentId) { + await handleDirectoryMoveToParent(activeId, overId); + return; + } + if (activeParentId === 0 && overParentId === 0 && event.delta.x > 24) { + await handleDirectoryMoveToParent(activeId, overId); return; } @@ -341,6 +362,42 @@ export function KnowledgeDirectoryPanel({ } } + async function handleDirectoryMoveToParent(activeId: number, targetParentId: number) { + const previousDirectories = directories; + const moved = moveDirectoryToParent(previousDirectories, activeId, targetParentId); + if (!moved.changed || !moved.item) { + return; + } + + setDirectories(moved.items); + if (moved.parentId > 0) { + setExpandedIds((current) => new Set(current).add(moved.parentId)); + } + setSorting(true); + try { + await updateKnowledgeDirectory({ + id: moved.item.id, + knowledgeBaseId, + parentId: moved.parentId, + name: moved.item.name, + remark: moved.item.remark || "", + }); + await updateKnowledgeDirectorySort({ + knowledgeBaseId, + parentId: moved.parentId, + ids: moved.orderedIds, + }); + toast.success(t("knowledge.directoryMoved")); + await loadDirectories(); + onChanged?.(); + } catch (error) { + setDirectories(previousDirectories); + toast.error(error instanceof Error ? error.message : t("knowledge.directoryMoveFailed")); + } finally { + setSorting(false); + } + } + return ( <>
onSelectDirectory(null)} /> - } - label={t("knowledge.rootContent")} - selected={selectedDirectoryId === 0} - onClick={() => onSelectDirectory(0)} - /> void handleDirectoryDragEnd(event)} > + onSelectDirectory(0)} + t={t} + /> item.id)} strategy={verticalListSortingStrategy} @@ -490,16 +546,18 @@ type DirectoryStaticRowProps = { icon: ReactNode; label: string; selected: boolean; + draggingOver?: boolean; onClick: () => void; }; -function DirectoryStaticRow({ icon, label, selected, onClick }: DirectoryStaticRowProps) { +function DirectoryStaticRow({ icon, label, selected, draggingOver, onClick }: DirectoryStaticRowProps) { return (