diff --git a/components/layout/sidebar.tsx b/components/layout/sidebar.tsx index 2111b3e..cceb7db 100644 --- a/components/layout/sidebar.tsx +++ b/components/layout/sidebar.tsx @@ -237,7 +237,7 @@ function MailboxTreeItem({ const isRenaming = renamingMailboxId === node.id; const isCreatingChild = creatingSubfolder?.parentId === node.id; - const { isDragging: globalDragging } = useDragDropContext(); + const { isDragging: globalDragging, startMailboxDrag, endDrag: globalEndDrag, dragType, draggedMailboxId } = useDragDropContext(); const { dropHandlers, isValidDropTarget, isInvalidDropTarget } = useMailboxDrop({ mailbox: node, onSuccess: (count, mailboxName) => { @@ -258,11 +258,44 @@ function MailboxTreeItem({ }, }); + const canDrag = !isVirtualNode && !node.isShared && !node.id.startsWith('shared-') && + !node.id.startsWith('temp-') && node.myRights?.mayRename; + + const handleFolderDragStart = useCallback((e: React.DragEvent) => { + if (!canDrag) { + e.preventDefault(); + return; + } + e.dataTransfer.effectAllowed = "move"; + e.dataTransfer.setData("application/x-mailbox-id", node.id); + e.dataTransfer.setData("text/plain", node.name); + + const preview = document.createElement("div"); + preview.style.cssText = ` + position: fixed; top: -9999px; left: 0; padding: 6px 12px; + background-color: var(--color-primary, #3b82f6); color: white; + border-radius: 6px; font-size: 13px; font-weight: 500; white-space: nowrap; + `; + preview.textContent = node.name; + document.body.appendChild(preview); + e.dataTransfer.setDragImage(preview, 0, 0); + requestAnimationFrame(() => preview.remove()); + + startMailboxDrag(node.id); + }, [canDrag, node.id, node.name, startMailboxDrag]); + + const handleFolderDragEnd = useCallback(() => { + globalEndDrag(); + }, [globalEndDrag]); + return ( <>
onMailboxContextMenu?.(e, node)} + draggable={canDrag} + onDragStart={handleFolderDragStart} + onDragEnd={handleFolderDragEnd} className={cn( "group w-full flex items-center px-2 py-1 lg:py-1 max-lg:py-3 max-lg:min-h-[44px] text-sm transition-all duration-200", isVirtualNode @@ -272,7 +305,8 @@ function MailboxTreeItem({ : "hover:bg-muted text-foreground", node.depth === 0 && !isVirtualNode && "font-medium", isValidDropTarget && "bg-primary/20 ring-2 ring-primary ring-inset", - isInvalidDropTarget && "bg-destructive/10 ring-2 ring-destructive/30 ring-inset opacity-50" + isInvalidDropTarget && "bg-destructive/10 ring-2 ring-destructive/30 ring-inset opacity-50", + globalDragging && dragType === 'mailbox' && draggedMailboxId === node.id && "opacity-40" )} > {hasChildren && ( @@ -699,6 +733,7 @@ export function Sidebar({ const sidebarRef = useRef(null); const t = useTranslations('sidebar'); const tFolder = useTranslations('sidebar.folder_management'); + const { dragType, endDrag: globalEndDrag } = useDragDropContext(); const { client } = useAuthStore(); const { emptyFolder, createMailbox, renameMailbox, moveMailbox, deleteMailbox } = useEmailStore(); const { sidebarWidth, updateSetting } = useSettingsStore(); @@ -991,7 +1026,32 @@ export function Sidebar({ )} {/* Mailbox List */} -
+
{ + if (dragType === 'mailbox') { + e.preventDefault(); + e.dataTransfer.dropEffect = "move"; + } + }} + onDrop={async (e) => { + if (dragType !== 'mailbox') return; + e.preventDefault(); + const mailboxId = e.dataTransfer.getData("application/x-mailbox-id"); + if (!mailboxId || !client) return; + + const mb = mailboxes.find(m => m.id === mailboxId); + if (mb && mb.parentId) { + try { + await moveMailbox(client, mailboxId, null); + toast.success(tFolder('folder_moved_to_root')); + } catch { + toast.error(tFolder('move_error')); + } + } + globalEndDrag(); + }} + >
{mailboxes.length === 0 ? (