mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 08:13:50 +00:00
fix: report a missing archive mailbox instead of failing silently #578
This commit is contained in:
@@ -31,7 +31,7 @@ import { usePolicyStore } from "@/stores/policy-store";
|
||||
import type { UnifiedAccountClient } from "@/lib/unified-mailbox";
|
||||
import { connectedAccountsGrew } from "@/lib/unified-mailbox";
|
||||
import { KeyboardShortcutsModal } from "@/components/keyboard-shortcuts-modal";
|
||||
import { useEmailStore, buildUnifiedAccountClients } from "@/stores/email-store";
|
||||
import { useEmailStore, buildUnifiedAccountClients, ArchiveMailboxNotFoundError } from "@/stores/email-store";
|
||||
import { toast } from "@/stores/toast-store";
|
||||
import { MailboxShareDialog } from "@/components/layout/mailbox-share-dialog";
|
||||
import { ShareNotificationToaster } from "@/components/layout/share-notification-toaster";
|
||||
@@ -704,6 +704,10 @@ export function MailApp({ linkSegments }: MailAppProps = {}) {
|
||||
await batchArchive(client);
|
||||
} catch (error) {
|
||||
console.error("Failed to batch archive:", error);
|
||||
if (error instanceof ArchiveMailboxNotFoundError) {
|
||||
const { toast } = await import('sonner');
|
||||
toast.error(t('email_viewer.archive_mailbox_not_found'));
|
||||
}
|
||||
}
|
||||
} else if (selectedEmail) {
|
||||
handleArchive();
|
||||
@@ -2205,7 +2209,11 @@ export function MailApp({ linkSegments }: MailAppProps = {}) {
|
||||
const currentMailboxes = readMailboxes();
|
||||
|
||||
const archiveMailbox = currentMailboxes.find(m => m.role === "archive" || m.name.toLowerCase() === "archive");
|
||||
if (!archiveMailbox) return;
|
||||
if (!archiveMailbox) {
|
||||
const { toast } = await import('sonner');
|
||||
toast.error(t('email_viewer.archive_mailbox_not_found'));
|
||||
return;
|
||||
}
|
||||
|
||||
const { archiveMode } = useSettingsStore.getState();
|
||||
|
||||
|
||||
@@ -292,6 +292,7 @@
|
||||
"forward": "إعادة توجيه",
|
||||
"delete": "حذف",
|
||||
"archive": "أرشفة",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "تمييز بنجمة",
|
||||
"unstar": "إزالة النجمة",
|
||||
"mark_unread": "تمييز كغير مقروءة",
|
||||
|
||||
@@ -292,6 +292,7 @@
|
||||
"forward": "Reenvia",
|
||||
"delete": "Suprimeix",
|
||||
"archive": "Arxiva",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Marca amb estrella",
|
||||
"unstar": "Treu l'estrella",
|
||||
"mark_unread": "Marca com a no llegit",
|
||||
|
||||
@@ -292,6 +292,7 @@
|
||||
"forward": "Přeposlat",
|
||||
"delete": "Odstranit",
|
||||
"archive": "Archivovat",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Označit hvězdičkou",
|
||||
"unstar": "Odebrat hvězdičku",
|
||||
"mark_unread": "Označit jako nepřečtené",
|
||||
|
||||
@@ -292,6 +292,7 @@
|
||||
"forward": "Videresend",
|
||||
"delete": "Slet",
|
||||
"archive": "Arkivér",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Stjernemarkér",
|
||||
"unstar": "Fjern stjernemarkering",
|
||||
"mark_unread": "Markér som ulæst",
|
||||
|
||||
@@ -292,6 +292,7 @@
|
||||
"forward": "Weiterleiten",
|
||||
"delete": "Löschen",
|
||||
"archive": "Archivieren",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Stern hinzufügen",
|
||||
"unstar": "Stern entfernen",
|
||||
"mark_unread": "Als ungelesen markieren",
|
||||
|
||||
@@ -292,6 +292,7 @@
|
||||
"forward": "Forward",
|
||||
"delete": "Delete",
|
||||
"archive": "Archive",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Star",
|
||||
"unstar": "Unstar",
|
||||
"mark_unread": "Mark as unread",
|
||||
|
||||
@@ -292,6 +292,7 @@
|
||||
"forward": "Reenviar",
|
||||
"delete": "Eliminar",
|
||||
"archive": "Archivar",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Destacar",
|
||||
"unstar": "Quitar destacado",
|
||||
"mark_unread": "Marcar como no leído",
|
||||
|
||||
@@ -292,6 +292,7 @@
|
||||
"forward": "ارسال مجدد",
|
||||
"delete": "حذف",
|
||||
"archive": "بایگانی",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "ستارهدار کردن",
|
||||
"unstar": "حذف ستاره",
|
||||
"mark_unread": "علامتگذاری خوانده نشده",
|
||||
|
||||
@@ -292,6 +292,7 @@
|
||||
"forward": "Transférer",
|
||||
"delete": "Supprimer",
|
||||
"archive": "Archiver",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Marquer comme favori",
|
||||
"unstar": "Retirer des favoris",
|
||||
"mark_unread": "Marquer comme non lu",
|
||||
|
||||
@@ -244,6 +244,7 @@
|
||||
"forward": "העבר",
|
||||
"delete": "מחיקה",
|
||||
"archive": "ארכיון",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "כוכב",
|
||||
"unstar": "הסר כוכב",
|
||||
"mark_unread": "סמן כלא נקרא",
|
||||
|
||||
@@ -292,6 +292,7 @@
|
||||
"forward": "Továbbítás",
|
||||
"delete": "Törlés",
|
||||
"archive": "Archiválás",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Csillagozás",
|
||||
"unstar": "Csillagozás megszüntetése",
|
||||
"mark_unread": "Megjelölés olvasatlanként",
|
||||
|
||||
@@ -292,6 +292,7 @@
|
||||
"forward": "Inoltra",
|
||||
"delete": "Elimina",
|
||||
"archive": "Archivia",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Aggiungi stella",
|
||||
"unstar": "Rimuovi stella",
|
||||
"mark_unread": "Segna come non letto",
|
||||
|
||||
@@ -292,6 +292,7 @@
|
||||
"forward": "転送",
|
||||
"delete": "削除",
|
||||
"archive": "アーカイブ",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "スターを付ける",
|
||||
"unstar": "スターを外す",
|
||||
"mark_unread": "未読にする",
|
||||
|
||||
@@ -292,6 +292,7 @@
|
||||
"forward": "전달",
|
||||
"delete": "삭제",
|
||||
"archive": "보관",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "별표 달기",
|
||||
"unstar": "별표 해제",
|
||||
"mark_unread": "읽지 않은 상태로 표시",
|
||||
|
||||
@@ -292,6 +292,7 @@
|
||||
"forward": "Pārsūtīt",
|
||||
"delete": "Dzēst",
|
||||
"archive": "Arhivēt",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Pievienot zvaigznīti",
|
||||
"unstar": "Noņemt zvaigznīti",
|
||||
"mark_unread": "Atzīmēt kā nelasītu",
|
||||
|
||||
@@ -292,6 +292,7 @@
|
||||
"forward": "Урагшаа",
|
||||
"delete": "Устгах",
|
||||
"archive": "Архив",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "од",
|
||||
"unstar": "Одгүй болгох",
|
||||
"mark_unread": "Уншаагүй гэж тэмдэглэх",
|
||||
|
||||
@@ -292,6 +292,7 @@
|
||||
"forward": "Videresend",
|
||||
"delete": "Slett",
|
||||
"archive": "Arkiver",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Stjernemerk",
|
||||
"unstar": "Fjern stjerne",
|
||||
"mark_unread": "Merk som ulest",
|
||||
|
||||
@@ -292,6 +292,7 @@
|
||||
"forward": "Doorsturen",
|
||||
"delete": "Verwijderen",
|
||||
"archive": "Archiveren",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Ster toevoegen",
|
||||
"unstar": "Ster verwijderen",
|
||||
"mark_unread": "Markeren als ongelezen",
|
||||
|
||||
@@ -292,6 +292,7 @@
|
||||
"forward": "Przekaż dalej",
|
||||
"delete": "Usuń",
|
||||
"archive": "Archiwizuj",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Oznacz gwiazdką",
|
||||
"unstar": "Usuń gwiazdkę",
|
||||
"mark_unread": "Oznacz jako nieprzeczytane",
|
||||
|
||||
@@ -292,6 +292,7 @@
|
||||
"forward": "Encaminhar",
|
||||
"delete": "Excluir",
|
||||
"archive": "Arquivar",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Adicionar Estrela",
|
||||
"unstar": "Remover Estrela",
|
||||
"mark_unread": "Marcar como não lido",
|
||||
|
||||
@@ -292,6 +292,7 @@
|
||||
"forward": "Redirecționează",
|
||||
"delete": "Șterge",
|
||||
"archive": "Arhivează",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Stea",
|
||||
"unstar": "Anulează marcarea cu stea",
|
||||
"mark_unread": "Marcați ca necitit",
|
||||
|
||||
@@ -292,6 +292,7 @@
|
||||
"forward": "Переслать",
|
||||
"delete": "Удалить",
|
||||
"archive": "Архивировать",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Пометить",
|
||||
"unstar": "Снять пометку",
|
||||
"mark_unread": "Отметить непрочитанным",
|
||||
|
||||
@@ -292,6 +292,7 @@
|
||||
"forward": "Preposlať",
|
||||
"delete": "Odstrániť",
|
||||
"archive": "Archivovať",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Označiť hviezdičkou",
|
||||
"unstar": "Odstrániť hviezdičku",
|
||||
"mark_unread": "Označiť ako neprečítané",
|
||||
|
||||
@@ -292,6 +292,7 @@
|
||||
"forward": "İlet",
|
||||
"delete": "Sil",
|
||||
"archive": "Arşivle",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Yıldızla",
|
||||
"unstar": "Yıldızı Kaldır",
|
||||
"mark_unread": "Okunmadı olarak işaretle",
|
||||
|
||||
@@ -292,6 +292,7 @@
|
||||
"forward": "вперед",
|
||||
"delete": "Видалити",
|
||||
"archive": "Архів",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "зірка",
|
||||
"unstar": "Зняти зірочку",
|
||||
"mark_unread": "Позначити як непрочитане",
|
||||
|
||||
@@ -289,6 +289,7 @@
|
||||
"forward": "轉寄",
|
||||
"delete": "刪除",
|
||||
"archive": "封存",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "加上星號",
|
||||
"unstar": "移除星號",
|
||||
"mark_unread": "標示為未讀",
|
||||
|
||||
@@ -292,6 +292,7 @@
|
||||
"forward": "转发",
|
||||
"delete": "删除",
|
||||
"archive": "归档",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "加星标",
|
||||
"unstar": "取消星标",
|
||||
"mark_unread": "标记为未读",
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { useEmailStore, ArchiveMailboxNotFoundError } from '../email-store';
|
||||
import { useAuthStore } from '../auth-store';
|
||||
import { useSettingsStore } from '@/stores/settings-store';
|
||||
import type { Email, Mailbox } from '@/lib/jmap/types';
|
||||
import type { IJMAPClient } from '@/lib/jmap/client-interface';
|
||||
|
||||
// #578: archiving with no archive folder used to return silently, leaving the
|
||||
// user with a shortcut/button that did nothing. The store must now report it.
|
||||
|
||||
function makeMailbox(overrides: Partial<Mailbox> = {}): Mailbox {
|
||||
return {
|
||||
id: 'inbox',
|
||||
name: 'Inbox',
|
||||
sortOrder: 0,
|
||||
totalEmails: 0,
|
||||
unreadEmails: 0,
|
||||
totalThreads: 0,
|
||||
unreadThreads: 0,
|
||||
myRights: {
|
||||
mayReadItems: true,
|
||||
mayAddItems: true,
|
||||
mayRemoveItems: true,
|
||||
maySetSeen: true,
|
||||
maySetKeywords: true,
|
||||
mayCreateChild: true,
|
||||
mayRename: true,
|
||||
mayDelete: true,
|
||||
maySubmit: true,
|
||||
},
|
||||
isSubscribed: true,
|
||||
isShared: false,
|
||||
...overrides,
|
||||
} as Mailbox;
|
||||
}
|
||||
|
||||
function makeEmail(overrides: Partial<Email> = {}): Email {
|
||||
return {
|
||||
id: 'email-1',
|
||||
threadId: 'thread-1',
|
||||
subject: 'Hi',
|
||||
receivedAt: new Date().toISOString(),
|
||||
keywords: {},
|
||||
mailboxIds: {},
|
||||
...overrides,
|
||||
} as Email;
|
||||
}
|
||||
|
||||
describe('batchArchive without an archive mailbox (#578)', () => {
|
||||
let client: IJMAPClient;
|
||||
|
||||
beforeEach(() => {
|
||||
client = {
|
||||
batchArchiveEmails: vi.fn().mockResolvedValue(undefined),
|
||||
getEmails: vi.fn().mockResolvedValue({ emails: [], hasMore: false, total: 0 }),
|
||||
getMailboxes: vi.fn().mockResolvedValue([]),
|
||||
} as unknown as IJMAPClient;
|
||||
|
||||
useAuthStore.setState({
|
||||
activeAccountId: 'account-a',
|
||||
getClientForAccount: (id: string) => (id === 'account-a' ? client : undefined) as never,
|
||||
} as never);
|
||||
useSettingsStore.setState({ archiveMode: 'single' } as never);
|
||||
|
||||
useEmailStore.setState({
|
||||
isUnifiedView: false,
|
||||
unifiedRole: null,
|
||||
viewingAccountId: null,
|
||||
selectedMailbox: 'a-inbox',
|
||||
error: null,
|
||||
mailboxes: [
|
||||
makeMailbox({ id: 'a-inbox', role: 'inbox' }),
|
||||
makeMailbox({ id: 'a-trash', name: 'Trash', role: 'trash' }),
|
||||
],
|
||||
accountMailboxes: {},
|
||||
emails: [makeEmail({ id: 'e1', mailboxIds: { 'a-inbox': true } })],
|
||||
selectedEmailIds: new Set(['e1']),
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects with ArchiveMailboxNotFoundError and records the error', async () => {
|
||||
await expect(useEmailStore.getState().batchArchive(client)).rejects.toBeInstanceOf(
|
||||
ArchiveMailboxNotFoundError,
|
||||
);
|
||||
|
||||
expect(useEmailStore.getState().error).toMatch(/archive mailbox not found/i);
|
||||
expect(client.batchArchiveEmails).not.toHaveBeenCalled();
|
||||
// Nothing was archived, so the selection must survive.
|
||||
expect(useEmailStore.getState().selectedEmailIds.has('e1')).toBe(true);
|
||||
});
|
||||
});
|
||||
+18
-1
@@ -566,6 +566,19 @@ function resolveActionClient(passedClient: IJMAPClient): IJMAPClient {
|
||||
return c ?? passedClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown by archive actions when no archive folder exists in the target
|
||||
* account. Callers map it to the translated
|
||||
* `email_viewer.archive_mailbox_not_found` toast; a silent return left the
|
||||
* user with a shortcut/button that did nothing. (#578)
|
||||
*/
|
||||
export class ArchiveMailboxNotFoundError extends Error {
|
||||
constructor() {
|
||||
super('Archive mailbox not found - cannot archive email');
|
||||
this.name = 'ArchiveMailboxNotFoundError';
|
||||
}
|
||||
}
|
||||
|
||||
function resolveActionMailboxes(): Mailbox[] {
|
||||
const state = useEmailStore.getState();
|
||||
if (state.viewingAccountId) {
|
||||
@@ -3087,7 +3100,11 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
|
||||
const archiveMailbox = mailboxes.find(m =>
|
||||
isArchive(m) && (viewAccountId ? m.accountId === viewAccountId : !m.isShared)
|
||||
);
|
||||
if (!archiveMailbox) return;
|
||||
if (!archiveMailbox) {
|
||||
const error = new ArchiveMailboxNotFoundError();
|
||||
set({ error: error.message });
|
||||
throw error;
|
||||
}
|
||||
|
||||
const mode = useSettingsStore.getState().archiveMode;
|
||||
const archiveId = archiveMailbox.originalId || archiveMailbox.id;
|
||||
|
||||
Reference in New Issue
Block a user