feat: add a setting to clear the search when switching folders

This commit is contained in:
Linus Rath
2026-09-24 19:36:41 +02:00
parent b1fb2e9b22
commit 4e6426171a
4 changed files with 49 additions and 4 deletions
@@ -32,6 +32,7 @@ export function ReadingSettings() {
const deleteAction = useSettingsStore((s) => s.deleteAction);
const permanentlyDeleteJunk = useSettingsStore((s) => s.permanentlyDeleteJunk);
const showPreview = useSettingsStore((s) => s.showPreview);
const clearSearchOnFolderChange = useSettingsStore((s) => s.clearSearchOnFolderChange);
const disableThreading = useSettingsStore((s) => s.disableThreading);
const includeGroupInUnified = useSettingsStore((s) => s.includeGroupInUnified);
const autoSelectReplyIdentity = useSettingsStore((s) => s.autoSelectReplyIdentity);
@@ -184,6 +185,10 @@ export function ReadingSettings() {
<ToggleSwitch checked={permanentlyDeleteJunk} onChange={(v) => update('permanentlyDeleteJunk', v)} />
</SettingItem>
<SettingItem label={t('settings.email_behavior.clear_search_on_folder_change.label', "Clear search when switching folders")} description={t('settings.email_behavior.clear_search_on_folder_change.description', "Clicking a folder (Inbox, Sent, …) clears the active search and browses that folder. When off, the search stays applied and re-runs in the folder you switch to.")}>
<ToggleSwitch checked={clearSearchOnFolderChange} onChange={(v) => update('clearSearchOnFolderChange', v)} />
</SettingItem>
<SettingItem label={t('settings.email_behavior.show_preview.label', "Show Preview Text")} description={t('settings.email_behavior.show_preview.description', "Display email preview in the list")}>
<ToggleSwitch checked={showPreview} onChange={(v) => update('showPreview', v)} />
</SettingItem>
+30
View File
@@ -236,6 +236,36 @@ describe('email-store', () => {
expect(useEmailStore.getState().emailStates).toEqual({ 'mb-empty': 'em-state-0' });
expect(mockGetEmails).not.toHaveBeenCalled();
});
it('drops the search and browses the folder with "clear search when switching folders"', async () => {
useSettingsStore.getState().updateSetting('clearSearchOnFolderChange', true);
try {
useEmailStore.setState({
currentMailboxId: 'mb-1',
mailboxes: [
{ id: 'mb-1', name: 'Inbox', isShared: false } as any,
{ id: 'mb-2', name: 'Receipts', isShared: false } as any,
],
searchQuery: 'invoice',
filters: { isUnread: true },
emails: [{ id: 'hit' } as any],
});
mockQueryEmails.mockResolvedValue({ ids: [], total: 0, queryState: 'q' });
await useEmailStore.getState().selectMailbox('mb-2');
const state = useEmailStore.getState();
expect(state.searchQuery).toBe('');
expect(state.filters).toEqual({});
// The search hits neither stay on screen nor become Inbox's snapshot.
expect(state.emails).toEqual([]);
expect(state.mailboxSnapshots['mb-1']).toBeUndefined();
expect(mockQueryEmails.mock.calls[0][0]).toBe('mb-2');
expect(mockQueryEmails.mock.calls[0][1].filter).toBeUndefined();
} finally {
useSettingsStore.getState().updateSetting('clearSearchOnFolderChange', false);
}
});
});
describe('loadMoreEmails', () => {
+10 -4
View File
@@ -752,6 +752,11 @@ export const useEmailStore = create<EmailState>()(
};
}
// "Clear search when switching folders": drop the query and filters and
// browse the folder, instead of re-running the search there.
const clearSearch = !baseView && useSettingsStore.getState().clearSearchOnFolderChange;
const browse = baseView || clearSearch;
const incoming = mailboxSnapshots[mailboxId];
// Swap to the new mailbox's cached view immediately. If there's no
// snapshot, fall through to the offline cache as a second-best seed;
@@ -759,11 +764,11 @@ export const useEmailStore = create<EmailState>()(
// a blank list — better than the previous flash to "Loading…".
// With a search/filter active the search is kept and re-run in the new
// folder (#553), so the current results stay on screen until it lands.
let seededEmails: Email[] = baseView ? incoming?.emails ?? [] : state.emails;
let seededTotal = baseView ? incoming?.total ?? 0 : state.totalEmails;
const seededQueryState = baseView ? incoming?.queryState : undefined;
let seededEmails: Email[] = browse ? incoming?.emails ?? [] : state.emails;
let seededTotal = browse ? incoming?.total ?? 0 : state.totalEmails;
const seededQueryState = browse ? incoming?.queryState : undefined;
if (baseView && seededEmails.length === 0) {
if (browse && seededEmails.length === 0) {
const cacheStore = useOfflineCacheStore.getState();
if (!cacheStore.hydrated) await cacheStore.hydrate();
if (cacheStore.totalCount() > 0) {
@@ -786,6 +791,7 @@ export const useEmailStore = create<EmailState>()(
}
set({
...(clearSearch ? { searchQuery: '', filters: {} } : {}),
currentMailboxId: mailboxId,
emails: seededEmails,
totalEmails: seededTotal,
+4
View File
@@ -197,6 +197,9 @@ interface PersistedSettings {
showAvatarsInJunk: boolean;
// Show the "/ total" part of the folder counts in the drawer (#498).
showFolderTotalCount: boolean;
// Opening a folder drops an active search and filters and browses it,
// instead of re-running the search there (#553, the default).
clearSearchOnFolderChange: boolean;
// Unified views span every logged-in account instead of just the active
// one (own + its shared/group folders). Off by default like the webmail.
unifiedCrossAccount: boolean;
@@ -366,6 +369,7 @@ const DEFAULT_PERSISTED: PersistedSettings = {
messageListOrderScope: 'inbox',
showAvatarsInJunk: false,
showFolderTotalCount: true,
clearSearchOnFolderChange: false,
unifiedCrossAccount: false,
mailAttachmentAction: 'preview',
attachmentPosition: 'beside-sender',