mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-03 08:06:59 +00:00
fix: only offer archiving when the archive folder can take the mail #578
The list resolved the Archive folder with a loose name match ("Archives",
"Archived" or any name containing "archive"), while the store's archive
actions only accept the `archive` role or a folder named exactly
"Archive". With a role-less "Archives" folder the swipe and bulk archive
were offered but silently did nothing.
`findArchiveMailbox` now uses the webmail's rules (role first, then the
exact name, case-insensitive) and both store archive actions resolve
through it, so the list offers archiving only when it will run and
otherwise shows the existing "no Archive folder" message.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
buildMailboxTree,
|
||||
findArchiveMailbox,
|
||||
flattenVisible,
|
||||
mailboxAccountId,
|
||||
mailboxesForSiblingOf,
|
||||
@@ -205,3 +206,28 @@ describe('role-folder deduplication (#771)', () => {
|
||||
expect(tree.find((n) => n.id === 'proj')?.children.map((n) => n.id)).toEqual(['proj-sent']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findArchiveMailbox (#578)', () => {
|
||||
it('prefers the archive role over a folder named Archive', () => {
|
||||
const found = findArchiveMailbox([
|
||||
own('named', 'Archive'),
|
||||
own('role', 'Old mail', { role: 'archive' }),
|
||||
]);
|
||||
expect(found?.id).toBe('role');
|
||||
});
|
||||
|
||||
it('falls back to a role-less folder named exactly "Archive", any case', () => {
|
||||
expect(findArchiveMailbox([own('inbox', 'Inbox', { role: 'inbox' }), own('a', 'ARCHIVE')])?.id)
|
||||
.toBe('a');
|
||||
});
|
||||
|
||||
it('does not treat "Archives" or folders merely containing "archive" as the Archive', () => {
|
||||
// The store's archive actions only file into a folder this resolver
|
||||
// returns; a looser match here offered an archive that did nothing.
|
||||
expect(findArchiveMailbox([
|
||||
own('plural', 'Archives'),
|
||||
own('past', 'Archived'),
|
||||
own('proj', 'Project archive 2019'),
|
||||
])).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -272,15 +272,16 @@ export function findTrashMailbox(mailboxes: Mailbox[]): Mailbox | undefined {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The Archive folder: the `archive` role, else a role-less folder named
|
||||
* exactly "Archive" (any case), as the webmail matches it. Whatever offers
|
||||
* the archive action must resolve through this, like the store's archive
|
||||
* actions do: a looser list-only match used to offer archiving into e.g.
|
||||
* "Archives", which the store then silently ignored. (#578)
|
||||
*/
|
||||
export function findArchiveMailbox(mailboxes: Mailbox[]): Mailbox | undefined {
|
||||
const roleMatch = mailboxes.find((m) => m.role === 'archive');
|
||||
if (roleMatch) return roleMatch;
|
||||
|
||||
const names = ['archive', 'archives', 'archived'];
|
||||
return mailboxes.find((m) => {
|
||||
const lower = m.name.toLowerCase();
|
||||
return names.includes(lower) || names.some((n) => lower.includes(n));
|
||||
});
|
||||
return mailboxes.find((m) => m.role === 'archive')
|
||||
?? mailboxes.find((m) => m.name.toLowerCase() === 'archive');
|
||||
}
|
||||
|
||||
export function findJunkMailbox(mailboxes: Mailbox[]): Mailbox | undefined {
|
||||
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
} from '../api/email';
|
||||
import { useNetworkStore } from './network-store';
|
||||
import { JMAPMethodError } from '../api/jmap-result';
|
||||
import { mailboxesForSiblingOf, mailboxesOfAccount, findJunkMailbox } from '../lib/mailbox-tree';
|
||||
import { mailboxesForSiblingOf, mailboxesOfAccount, findJunkMailbox, findArchiveMailbox } from '../lib/mailbox-tree';
|
||||
import { toWildcardQuery } from '../lib/search-utils';
|
||||
import { orderForMailbox, sanitizeSortLevels, type SortLevel } from '../lib/message-list-order';
|
||||
import { buildListSort, markKeywordSortUnsupported } from '../lib/keyword-sort-polarity';
|
||||
@@ -1251,9 +1251,7 @@ export const useEmailStore = create<EmailState>()(
|
||||
// Archive into the *same account's* Archive folder — a shared mailbox's
|
||||
// messages can't be filed into the user's own.
|
||||
const scoped = actionMailboxes(state, viewed);
|
||||
const archiveMailbox = scoped.find(
|
||||
(m) => m.role === 'archive' || m.name.toLowerCase() === 'archive',
|
||||
);
|
||||
const archiveMailbox = findArchiveMailbox(scoped);
|
||||
if (!archiveMailbox) return;
|
||||
const archive = refFor(state.mailboxes, archiveMailbox.id);
|
||||
if (email.mailboxIds?.[archive.id]) return;
|
||||
@@ -1379,9 +1377,7 @@ export const useEmailStore = create<EmailState>()(
|
||||
archiveEmailsBatch: async (emailIds) => {
|
||||
const state = get();
|
||||
const scoped = mailboxesForSiblingOf(state.mailboxes, state.currentMailboxId);
|
||||
const archiveMailbox = scoped.find(
|
||||
(m) => m.role === 'archive' || m.name.toLowerCase() === 'archive',
|
||||
);
|
||||
const archiveMailbox = findArchiveMailbox(scoped);
|
||||
if (!archiveMailbox) return;
|
||||
const archive = refFor(state.mailboxes, archiveMailbox.id);
|
||||
const targets = state.emails.filter(
|
||||
|
||||
Reference in New Issue
Block a user