feat(unified-mailbox): enable search in the unified views
Enable text AND advanced search in all Unified Mailbox views (the per-role mailboxes and the folder-selected All mail / Unread / Starred cross views). The search input was hard-disabled for every unified view; the store fan-out already supported text search. - page.tsx: the search text input and the advanced-filter toggle are enabled for all unified views (only the scheduled view stays disabled). Clear-search also restores a cross view (not just per-role). - Advanced filters now apply in cross views too: new advancedSearchCrossViewEmails ANDs the advanced filter (text + field conditions from buildJMAPFilter, built without an inMailbox clause) onto the cross-view membership. Per-role unified views keep using advancedSearchUnifiedEmails. Both honor the filter on the first page, on load-more, and on the folder-switch re-run. Fixes: an active Starred filter not applying after switching into a cross view, and the Unread filter in the Unread view returning nothing. - Search persistence on folder switch: an active search is kept and re-run in the target view, preserving advanced filters. handleMailboxSelect picks advancedSearch when filters are set (normal, per-role unified, and cross views, after setting the unified state), text searchEmails when only a query is set, and browses otherwise. The scheduled view is the only view that resets the search on enter (unavailable there; setScheduledView clears searchQuery + searchFilters). Account scope is intentionally left unrestricted in search (it already fanned out across all accounts); the per-view folder selection still applies via crossIncludedMailboxIds.
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
buildCrossFilter,
|
||||
getCrossUnreadTotal,
|
||||
fetchCrossViewEmails,
|
||||
advancedSearchCrossViewEmails,
|
||||
resolveSourceFolderName,
|
||||
type UnifiedAccountClient,
|
||||
} from '@/lib/unified-mailbox';
|
||||
@@ -203,3 +204,28 @@ describe('fetchCrossViewEmails', () => {
|
||||
expect(result.errors.get('bad')).toBe('boom');
|
||||
});
|
||||
});
|
||||
|
||||
describe('advancedSearchCrossViewEmails', () => {
|
||||
it('ANDs the advanced filter onto the cross-view membership', async () => {
|
||||
const advancedSearchEmails = vi.fn().mockResolvedValue({ emails: [], total: 0, hasMore: false });
|
||||
const a = makeAccount({ accountId: 'a', mailboxes: [mb('inbox', 'inbox')] }, { advancedSearchEmails });
|
||||
|
||||
await advancedSearchCrossViewEmails([a], 'all', { hasKeyword: '$flagged' }, 50, 0);
|
||||
|
||||
const [filter] = advancedSearchEmails.mock.calls[0];
|
||||
expect(filter).toEqual({
|
||||
operator: 'AND',
|
||||
conditions: [{ inMailbox: 'inbox' }, { hasKeyword: '$flagged' }],
|
||||
});
|
||||
});
|
||||
|
||||
it('uses only the membership filter when the extra filter is empty', async () => {
|
||||
const advancedSearchEmails = vi.fn().mockResolvedValue({ emails: [], total: 0, hasMore: false });
|
||||
const a = makeAccount({ accountId: 'a', mailboxes: [mb('inbox', 'inbox')] }, { advancedSearchEmails });
|
||||
|
||||
await advancedSearchCrossViewEmails([a], 'all', {}, 50, 0);
|
||||
|
||||
const [filter] = advancedSearchEmails.mock.calls[0];
|
||||
expect(filter).toEqual({ inMailbox: 'inbox' });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -465,6 +465,29 @@ export async function searchCrossViewEmails(
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Like `searchCrossViewEmails`, but applies an advanced filter (text + field
|
||||
* conditions from `buildJMAPFilter`, built WITHOUT an `inMailbox` clause) on top
|
||||
* of the cross-view membership. `extraFilter` may be empty ({}), in which case
|
||||
* only the membership filter is used (equivalent to a plain browse).
|
||||
*/
|
||||
export async function advancedSearchCrossViewEmails(
|
||||
accounts: UnifiedAccountClient[],
|
||||
view: CrossView,
|
||||
extraFilter: Record<string, unknown>,
|
||||
limit: number,
|
||||
position: number,
|
||||
): Promise<UnifiedFetchResult> {
|
||||
const hasExtra = Object.keys(extraFilter).length > 0;
|
||||
return fanOutCrossQuery(accounts, (account, jmapAccountId, ids) => {
|
||||
const membership = buildCrossFilter(view, ids);
|
||||
const filter = hasExtra
|
||||
? { operator: 'AND', conditions: [membership, extraFilter] }
|
||||
: membership;
|
||||
return account.client.advancedSearchEmails(filter, jmapAccountId, limit, position);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of unified roles that exist in at least one account's
|
||||
* mailboxes.
|
||||
|
||||
Reference in New Issue
Block a user