From 1042fad6d04ee60aa9c9e5b6fdbae1d82463099f Mon Sep 17 00:00:00 2001 From: rustmailer Date: Sat, 22 Nov 2025 17:02:20 +0800 Subject: [PATCH] feat: Add folder sync selection and All Mail exclusion logic --- .../accounts/components/sync-folders.tsx | 67 +++++++++++++++++-- 1 file changed, 63 insertions(+), 4 deletions(-) diff --git a/web/src/features/accounts/components/sync-folders.tsx b/web/src/features/accounts/components/sync-folders.tsx index 1969a4e..88a5d1e 100644 --- a/web/src/features/accounts/components/sync-folders.tsx +++ b/web/src/features/accounts/components/sync-folders.tsx @@ -27,7 +27,7 @@ import { } from '@/components/ui/dialog' import { Button } from '@/components/ui/button' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' -import { Loader2 } from 'lucide-react' +import { Loader2, CheckSquare, Square } from 'lucide-react' import { useCallback, useMemo, useState } from 'react' import { AccountModel } from '../data/schema' import { toast } from '@/hooks/use-toast' @@ -50,6 +50,7 @@ export function SyncFoldersDialog({ currentRow, open, onOpenChange }: Props) { const [selectedFolders, setSelectedFolders] = useState(currentRow.sync_folders || []); const [isSubmitting, setIsSubmitting] = useState(false); const queryClient = useQueryClient(); + const { data: mailboxes, isLoading } = useQuery({ queryKey: ['account-mailboxes', currentRow.id], queryFn: () => list_mailboxes(currentRow.id, true), @@ -64,21 +65,56 @@ export function SyncFoldersDialog({ currentRow, open, onOpenChange }: Props) { .map(mailbox => mailbox.id.toString()); }, [mailboxes, selectedFolders]); - // Convert data to tree structure const treeData = useMemo(() => { if (!mailboxes) return []; return buildTree(mailboxes, undefined, true, true); }, [mailboxes]); const handleSelectItems = useCallback((selectedItems: TreeDataItem[]) => { + const allMailboxes = mailboxes || []; const selected = selectedItems .map(item => mailboxes?.find(m => m.id === parseInt(item.id, 10))?.name) .filter(Boolean) as string[]; + const allMailSelected = selected.some(selectedName => { + const mailbox = allMailboxes.find(m => m.name === selectedName); + if (!mailbox) return false; + return mailbox.attributes.some(a => a.attr === 'All'); + }); + + if (allMailSelected) { + toast({ + title: 'Heads Up: "All Mail" Folder Selected', + description: 'Selecting folders with the "All Mail" attribute will likely lead to duplicating messages already synced from folders like Inbox and Sent. This may consume significantly more storage space.', + action: OK, + }); + } setSelectedFolders(selected); }, [mailboxes]); + const handleSelectAll = useCallback(() => { + if (!mailboxes) return; + const validFolderNames = mailboxes + .filter(mailbox => { + const isAllMail = mailbox.attributes.some(a => a.attr === 'All'); + if (isAllMail) return false; + return true; + }) + .map(m => m.name); + setSelectedFolders(validFolderNames); + if (validFolderNames.length < mailboxes.length) { + toast({ + description: "Selected standard folders. 'All Mail' was skipped to avoid duplicates.", + }); + } + }, [mailboxes]); + + const handleDeselectAll = useCallback(() => { + setSelectedFolders([]); + }, []); + + const updateMutation = useMutation({ mutationFn: (data: Record) => update_account(currentRow?.id ?? '', data), onSuccess: handleSuccess, @@ -96,6 +132,7 @@ export function SyncFoldersDialog({ currentRow, open, onOpenChange }: Props) { setIsSubmitting(false); onOpenChange(false); } + function handleError(error: AxiosError) { const errorMessage = (error.response?.data as { message?: string })?.message || error.message || @@ -111,7 +148,6 @@ export function SyncFoldersDialog({ currentRow, open, onOpenChange }: Props) { console.error(error); } - const handleSubmit = async () => { if (selectedFolders.length === 0) { toast({ @@ -138,7 +174,29 @@ export function SyncFoldersDialog({ currentRow, open, onOpenChange }: Props) {
-
+
+
+ + +
{selectedFolders.length} folder(s) selected
@@ -160,6 +218,7 @@ export function SyncFoldersDialog({ currentRow, open, onOpenChange }: Props) { )} {!isLoading && ( 0 ? `tree-${selectedFolders.length}-${selectedFolders[0]}` : 'tree-empty'} data={treeData} multiple expandAll