diff --git a/apps/client/src/widgets/collections/board/card.tsx b/apps/client/src/widgets/collections/board/card.tsx index ffd3d0b67..b80510f61 100644 --- a/apps/client/src/widgets/collections/board/card.tsx +++ b/apps/client/src/widgets/collections/board/card.tsx @@ -7,6 +7,13 @@ import { ContextMenuEvent } from "../../../menus/context_menu"; import { openNoteContextMenu } from "./context_menu"; import { t } from "../../../services/i18n"; +export interface CardDragData { + noteId: string; + branchId: string; + index: number; + fromColumn: string; +} + export default function Card({ api, note, @@ -22,7 +29,7 @@ export default function Card({ index: number, isDragging: boolean }) { - const { branchIdToEdit, setBranchIdToEdit, setDraggedCard } = useContext(BoardViewContext); + const { branchIdToEdit, setBranchIdToEdit } = useContext(BoardViewContext); const isEditing = branch.branchId === branchIdToEdit; const colorClass = note.getColorClass() || ''; const editorRef = useRef(null); @@ -31,13 +38,9 @@ export default function Card({ const handleDragStart = useCallback((e: DragEvent) => { e.dataTransfer!.effectAllowed = 'move'; - e.dataTransfer!.setData('text/plain', note.noteId); - setDraggedCard({ noteId: note.noteId, branchId: branch.branchId, fromColumn: column, index }); - }, [note.noteId, branch.branchId, column, index, setDraggedCard]); - - const handleDragEnd = useCallback(() => { - setDraggedCard(null); - }, [setDraggedCard]); + const data: CardDragData = { noteId: note.noteId, branchId: branch.branchId, fromColumn: column, index }; + e.dataTransfer!.setData('text/plain', JSON.stringify(data)); + }, [note.noteId, branch.branchId, column, index]); const handleContextMenu = useCallback((e: ContextMenuEvent) => { openNoteContextMenu(api, e, note.noteId, branch.branchId, column); @@ -65,7 +68,6 @@ export default function Card({ className={`board-note ${colorClass} ${isDragging ? 'dragging' : ''} ${isEditing ? "editing" : ""} ${isArchived ? "archived" : ""}`} draggable="true" onDragStart={handleDragStart} - onDragEnd={handleDragEnd} onContextMenu={handleContextMenu} onClick={!isEditing ? handleOpen : undefined} > diff --git a/apps/client/src/widgets/collections/board/column.tsx b/apps/client/src/widgets/collections/board/column.tsx index 44aca3fd9..c38b49ffe 100644 --- a/apps/client/src/widgets/collections/board/column.tsx +++ b/apps/client/src/widgets/collections/board/column.tsx @@ -8,8 +8,10 @@ import { ContextMenuEvent } from "../../../menus/context_menu"; import Icon from "../../react/Icon"; import { t } from "../../../services/i18n"; import BoardApi from "./api"; -import Card from "./card"; +import Card, { CardDragData } from "./card"; import { JSX } from "preact/jsx-runtime"; +import froca from "../../../services/froca"; +import { DragData } from "../../note_tree"; interface DragContext { column: string; @@ -149,7 +151,7 @@ function AddNewItem({ column, api }: { column: string, api: BoardApi }) { } function useDragging({ column, columnIndex, columnItems }: DragContext) { - const { api, draggedColumn, setDraggedColumn, setDropTarget, setDropPosition, draggedCard, dropPosition, setDraggedCard } = useContext(BoardViewContext); + const { api, parentNote, draggedColumn, setDraggedColumn, setDropTarget, setDropPosition, dropPosition } = useContext(BoardViewContext); const handleColumnDragStart = useCallback((e: DragEvent) => { e.dataTransfer!.effectAllowed = 'move'; @@ -204,39 +206,73 @@ function useDragging({ column, columnIndex, columnItems }: DragContext) { setDropTarget(null); setDropPosition(null); - if (draggedCard && dropPosition) { - const targetIndex = dropPosition.index; + const data = e.dataTransfer?.getData("text"); + if (!data) return; + const draggedCard = JSON.parse(data) as CardDragData | DragData[]; + + if (Array.isArray(draggedCard)) { + // From note tree. + const { noteId, branchId } = draggedCard[0]; + const targetNote = await froca.getNote(noteId, true); + const parentNoteId = parentNote?.noteId; + if (!parentNoteId || !dropPosition) return; + + const targetIndex = dropPosition.index - 1; const targetItems = columnItems || []; + const targetBranch = targetIndex >= 0 ? targetItems[targetIndex].branch : null; - if (draggedCard.fromColumn !== column) { - // Moving to a different column - await api?.changeColumn(draggedCard.noteId, column); + await api?.changeColumn(noteId, column); - // If there are items in the target column, reorder - if (targetItems.length > 0 && targetIndex < targetItems.length) { - const targetBranch = targetItems[targetIndex].branch; - await branches.moveBeforeBranch([ draggedCard.branchId ], targetBranch.branchId); + const parents = targetNote?.getParentNoteIds(); + if (!parents?.includes(parentNoteId)) { + if (!targetBranch) { + // First. + await branches.cloneNoteToParentNote(noteId, parentNoteId); + } else { + await branches.cloneNoteAfter(noteId, targetBranch.branchId); } - } else if (draggedCard.index !== targetIndex) { - // Reordering within the same column - let targetBranchId: string | null = null; + } else if (targetBranch) { + await branches.moveAfterBranch([ branchId ], targetBranch.branchId); + } + } else { + // From within the board. + if (draggedCard && dropPosition) { + const targetIndex = dropPosition.index; + const targetItems = columnItems || []; - if (targetIndex < targetItems.length) { - // Moving before an existing item - const adjustedIndex = draggedCard.index < targetIndex ? targetIndex : targetIndex; - if (adjustedIndex < targetItems.length) { - targetBranchId = targetItems[adjustedIndex].branch.branchId; - await branches.moveBeforeBranch([ draggedCard.branchId ], targetBranchId); + const note = froca.getNoteFromCache(draggedCard.noteId); + if (!note) return; + + if (draggedCard.fromColumn !== column || !draggedCard.index) { + // Moving to a different column + await api?.changeColumn(draggedCard.noteId, column); + + // If there are items in the target column, reorder + if (targetItems.length > 0 && targetIndex < targetItems.length) { + const targetBranch = targetItems[targetIndex].branch; + await branches.moveBeforeBranch([ draggedCard.branchId ], targetBranch.branchId); + } + } else if (draggedCard.index !== targetIndex) { + // Reordering within the same column + let targetBranchId: string | null = null; + + if (targetIndex < targetItems.length) { + // Moving before an existing item + const adjustedIndex = draggedCard.index < targetIndex ? targetIndex : targetIndex; + if (adjustedIndex < targetItems.length) { + targetBranchId = targetItems[adjustedIndex].branch.branchId; + await branches.moveBeforeBranch([ draggedCard.branchId ], targetBranchId); + } + } else if (targetIndex > 0) { + // Moving to the end - place after the last item + const lastItem = targetItems[targetItems.length - 1]; + await branches.moveAfterBranch([ draggedCard.branchId ], lastItem.branch.branchId); } - } else if (targetIndex > 0) { - // Moving to the end - place after the last item - const lastItem = targetItems[targetItems.length - 1]; - await branches.moveAfterBranch([ draggedCard.branchId ], lastItem.branch.branchId); } } } - setDraggedCard(null); - }, [ api, draggedCard, draggedColumn, dropPosition, columnItems, column, setDraggedCard, setDropTarget, setDropPosition ]); + + }, [ api, draggedColumn, dropPosition, columnItems, column, setDropTarget, setDropPosition ]); return { handleColumnDragStart, handleColumnDragEnd, handleDragOver, handleDragLeave, handleDrop }; } diff --git a/apps/client/src/widgets/collections/board/index.tsx b/apps/client/src/widgets/collections/board/index.tsx index 765ffb1f8..79f400355 100644 --- a/apps/client/src/widgets/collections/board/index.tsx +++ b/apps/client/src/widgets/collections/board/index.tsx @@ -12,6 +12,7 @@ import { onWheelHorizontalScroll } from "../../widget_utils"; import Column from "./column"; import BoardApi from "./api"; import FormTextArea from "../../react/FormTextArea"; +import FNote from "../../../entities/fnote"; export interface BoardViewData { columns?: BoardColumnData[]; @@ -23,6 +24,7 @@ export interface BoardColumnData { interface BoardViewContextData { api?: BoardApi; + parentNote?: FNote; branchIdToEdit?: string; columnNameToEdit?: string; setColumnNameToEdit?: Dispatch>; @@ -31,8 +33,6 @@ interface BoardViewContextData { setDraggedColumn: (column: { column: string, index: number } | null) => void; dropPosition: { column: string, index: number } | null; setDropPosition: (position: { column: string, index: number } | null) => void; - draggedCard: { noteId: string, branchId: string, fromColumn: string, index: number } | null; - setDraggedCard: (card: { noteId: string, branchId: string, fromColumn: string, index: number } | null) => void; setDropTarget: (target: string | null) => void, dropTarget: string | null } @@ -56,6 +56,7 @@ export default function BoardView({ note: parentNote, noteIds, viewConfig, saveC }, [ byColumn, columns, parentNote, statusAttribute, viewConfig, saveConfig, setBranchIdToEdit ]); const boardViewContext = useMemo(() => ({ api, + parentNote, branchIdToEdit, setBranchIdToEdit, columnNameToEdit, setColumnNameToEdit, draggedColumn, setDraggedColumn, @@ -64,6 +65,7 @@ export default function BoardView({ note: parentNote, noteIds, viewConfig, saveC dropTarget, setDropTarget }), [ api, + parentNote, branchIdToEdit, setBranchIdToEdit, columnNameToEdit, setColumnNameToEdit, draggedColumn, setDraggedColumn,