feat: enhance email deletion and spam handling with improved parameterization
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"WebFetch(domain:github.com)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
+13
-16
@@ -758,8 +758,8 @@ export default function Home() {
|
|||||||
if (isMobile) setActiveView('viewer');
|
if (isMobile) setActiveView('viewer');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = async () => {
|
const handleDelete = async (emailToDelete: Email | null = selectedEmail) => {
|
||||||
if (!client || !selectedEmail) return;
|
if (!client || !emailToDelete) return;
|
||||||
|
|
||||||
// Check if we're currently in the trash or junk folder
|
// Check if we're currently in the trash or junk folder
|
||||||
const currentMailbox = mailboxes.find(m => m.id === selectedMailbox);
|
const currentMailbox = mailboxes.find(m => m.id === selectedMailbox);
|
||||||
@@ -778,7 +778,7 @@ export default function Home() {
|
|||||||
if (!confirmed) return;
|
if (!confirmed) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await deleteEmail(client, selectedEmail.id, true);
|
await deleteEmail(client, emailToDelete.id, true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to permanently delete email:", error);
|
console.error("Failed to permanently delete email:", error);
|
||||||
}
|
}
|
||||||
@@ -787,7 +787,7 @@ export default function Home() {
|
|||||||
const trashMailbox = mailboxes.find(m => m.role === 'trash' && !m.isShared);
|
const trashMailbox = mailboxes.find(m => m.role === 'trash' && !m.isShared);
|
||||||
if (trashMailbox) {
|
if (trashMailbox) {
|
||||||
try {
|
try {
|
||||||
await moveToMailbox(client, selectedEmail.id, trashMailbox.id);
|
await moveToMailbox(client, emailToDelete.id, trashMailbox.id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to move email to trash:", error);
|
console.error("Failed to move email to trash:", error);
|
||||||
}
|
}
|
||||||
@@ -860,10 +860,10 @@ export default function Home() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMarkAsSpam = async () => {
|
const handleMarkAsSpam = async (emailToMark: Email | null = selectedEmail) => {
|
||||||
if (!client || !selectedEmail) return;
|
if (!client || !emailToMark) return;
|
||||||
|
|
||||||
const emailId = selectedEmail.id;
|
const emailId = emailToMark.id;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await markAsSpam(client, emailId);
|
await markAsSpam(client, emailId);
|
||||||
@@ -891,11 +891,11 @@ export default function Home() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUndoSpam = async () => {
|
const handleUndoSpam = async (emailToRestore: Email | null = selectedEmail) => {
|
||||||
if (!client || !selectedEmail) return;
|
if (!client || !emailToRestore) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await undoSpam(client, selectedEmail.id);
|
await undoSpam(client, emailToRestore.id);
|
||||||
|
|
||||||
const toastInstance = (await import('sonner')).toast;
|
const toastInstance = (await import('sonner')).toast;
|
||||||
toastInstance.success(t('email_viewer.spam.toast_not_spam_success'));
|
toastInstance.success(t('email_viewer.spam.toast_not_spam_success'));
|
||||||
@@ -1715,8 +1715,7 @@ export default function Home() {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onDelete={async (email) => {
|
onDelete={async (email) => {
|
||||||
selectEmail(email);
|
await handleDelete(email);
|
||||||
await handleDelete();
|
|
||||||
}}
|
}}
|
||||||
onArchive={async (email) => {
|
onArchive={async (email) => {
|
||||||
await handleArchive(email);
|
await handleArchive(email);
|
||||||
@@ -1730,12 +1729,10 @@ export default function Home() {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onMarkAsSpam={async (email) => {
|
onMarkAsSpam={async (email) => {
|
||||||
selectEmail(email);
|
await handleMarkAsSpam(email);
|
||||||
await handleMarkAsSpam();
|
|
||||||
}}
|
}}
|
||||||
onUndoSpam={async (email) => {
|
onUndoSpam={async (email) => {
|
||||||
selectEmail(email);
|
await handleUndoSpam(email);
|
||||||
await handleUndoSpam();
|
|
||||||
}}
|
}}
|
||||||
onEditDraft={(email) => {
|
onEditDraft={(email) => {
|
||||||
handleEditDraft(email);
|
handleEditDraft(email);
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ export function EmailList({
|
|||||||
|
|
||||||
setIsProcessing(true);
|
setIsProcessing(true);
|
||||||
try {
|
try {
|
||||||
await batchDelete(client);
|
await batchDelete(client, isInTrash);
|
||||||
} finally {
|
} finally {
|
||||||
setTimeout(() => setIsProcessing(false), 500);
|
setTimeout(() => setIsProcessing(false), 500);
|
||||||
}
|
}
|
||||||
|
|||||||
+41
-14
@@ -87,7 +87,7 @@ interface EmailStore {
|
|||||||
|
|
||||||
// Batch operations
|
// Batch operations
|
||||||
batchMarkAsRead: (client: IJMAPClient, read: boolean) => Promise<void>;
|
batchMarkAsRead: (client: IJMAPClient, read: boolean) => Promise<void>;
|
||||||
batchDelete: (client: IJMAPClient) => Promise<void>;
|
batchDelete: (client: IJMAPClient, permanent?: boolean) => Promise<void>;
|
||||||
batchMoveToMailbox: (client: IJMAPClient, mailboxId: string) => Promise<void>;
|
batchMoveToMailbox: (client: IJMAPClient, mailboxId: string) => Promise<void>;
|
||||||
|
|
||||||
// Spam operations
|
// Spam operations
|
||||||
@@ -1044,32 +1044,59 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
batchDelete: async (client) => {
|
batchDelete: async (client, permanent = false) => {
|
||||||
const { selectedEmailIds, emails, mailboxes } = get();
|
const { selectedEmailIds, emails, mailboxes, selectedMailbox } = get();
|
||||||
if (selectedEmailIds.size === 0) return;
|
if (selectedEmailIds.size === 0) return;
|
||||||
|
|
||||||
set({ isLoading: true, error: null });
|
set({ isLoading: true, error: null });
|
||||||
try {
|
try {
|
||||||
const emailIdsArray = Array.from(selectedEmailIds);
|
const emailIdsArray = Array.from(selectedEmailIds);
|
||||||
|
|
||||||
if (get().isUnifiedView) {
|
// Determine if the current folder forces permanent deletion.
|
||||||
// Group emails by accountId for cross-account operations
|
const currentMailbox = mailboxes.find(m => m.id === selectedMailbox);
|
||||||
const emailsByAccount = new Map<string, string[]>();
|
const isInTrash = currentMailbox?.role === 'trash';
|
||||||
for (const emailId of emailIdsArray) {
|
const permanentlyDeleteJunk = useSettingsStore.getState().permanentlyDeleteJunk;
|
||||||
const email = emails.find(e => e.id === emailId);
|
const isInJunk = currentMailbox?.role === 'junk';
|
||||||
const acctId = email?.accountId || '__default__';
|
const forceDestroy = permanent || isInTrash || (isInJunk && permanentlyDeleteJunk);
|
||||||
if (!emailsByAccount.has(acctId)) emailsByAccount.set(acctId, []);
|
|
||||||
emailsByAccount.get(acctId)!.push(emailId);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Group emails by accountId (handles unified view and search results spanning accounts).
|
||||||
|
const emailsByAccount = new Map<string, string[]>();
|
||||||
|
for (const emailId of emailIdsArray) {
|
||||||
|
const email = emails.find(e => e.id === emailId);
|
||||||
|
const acctId = email?.accountId || '__default__';
|
||||||
|
if (!emailsByAccount.has(acctId)) emailsByAccount.set(acctId, []);
|
||||||
|
emailsByAccount.get(acctId)!.push(emailId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const getClient = (acctId: string) =>
|
||||||
|
acctId === '__default__' ? client : useAuthStore.getState().getClientForAccount(acctId);
|
||||||
|
|
||||||
|
if (forceDestroy) {
|
||||||
const promises = Array.from(emailsByAccount.entries()).map(async ([acctId, ids]) => {
|
const promises = Array.from(emailsByAccount.entries()).map(async ([acctId, ids]) => {
|
||||||
const acctClient = acctId === '__default__' ? client : useAuthStore.getState().getClientForAccount(acctId);
|
const acctClient = getClient(acctId);
|
||||||
if (!acctClient) return;
|
if (!acctClient) return;
|
||||||
await acctClient.batchDeleteEmails(ids);
|
await acctClient.batchDeleteEmails(ids);
|
||||||
});
|
});
|
||||||
await Promise.allSettled(promises);
|
await Promise.allSettled(promises);
|
||||||
} else {
|
} else {
|
||||||
await client.batchDeleteEmails(emailIdsArray);
|
// Move to trash per account.
|
||||||
|
const promises = Array.from(emailsByAccount.entries()).map(async ([acctId, ids]) => {
|
||||||
|
const acctClient = getClient(acctId);
|
||||||
|
if (!acctClient) return;
|
||||||
|
const trashMailbox = mailboxes.find(mb => {
|
||||||
|
if (mb.role !== 'trash') return false;
|
||||||
|
if (acctId === '__default__') return !mb.isShared;
|
||||||
|
return mb.accountId === acctId;
|
||||||
|
});
|
||||||
|
if (!trashMailbox) {
|
||||||
|
// No trash available for this account — fall back to destroy so the action isn't silently dropped.
|
||||||
|
await acctClient.batchDeleteEmails(ids);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const trashId = trashMailbox.originalId || trashMailbox.id;
|
||||||
|
await acctClient.batchMoveEmails(ids, trashId, trashMailbox.accountId);
|
||||||
|
});
|
||||||
|
await Promise.allSettled(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove deleted emails from local state
|
// Remove deleted emails from local state
|
||||||
|
|||||||
Reference in New Issue
Block a user