This commit is contained in:
Linus Rath
2026-09-01 23:25:30 +02:00
32 changed files with 167 additions and 14 deletions
+9 -1
View File
@@ -392,6 +392,13 @@ export function ContactsApp({ linkSegments }: ContactsAppProps = {}) {
const handleCreateNew = () => {
setSelectedContact(null);
// When a specific address book is being viewed, create the new contact in
// that book instead of falling back to the account's default book.
if (typeof activeCategory === "object" && "addressBookId" in activeCategory) {
setDefaultBookIdForCreate(activeCategory.addressBookId);
} else {
setDefaultBookIdForCreate(undefined);
}
setView("create");
};
@@ -962,7 +969,8 @@ export function ContactsApp({ linkSegments }: ContactsAppProps = {}) {
} : undefined}
onCreateContactInBook={(book) => {
setDefaultBookIdForCreate(book.id);
handleCreateNew();
setSelectedContact(null);
setView("create");
}}
onDeleteAddressBook={client ? async (book) => {
const ok = await confirmDialog({
+7 -1
View File
@@ -156,6 +156,11 @@ export function EmailList({
// open it shrinks the list and would shove every row downwards. Feed each
// height change back into scrollTop so the rows stay put on screen (and
// slide back when the toolbar collapses again).
//
// Except at the very top: there are no rows above to hold steady, so the
// compensation just scrolls the first message underneath the toolbar and
// reads as the toolbar covering the message you selected. Let the list
// move down there instead.
useEffect(() => {
const toolbar = batchToolbarRef.current;
if (!toolbar || typeof ResizeObserver === 'undefined') return;
@@ -166,7 +171,8 @@ export function EmailList({
lastHeight = height;
const list = parentRef.current;
if (!list || delta === 0) return;
list.scrollTop += delta;
if (delta > 0 && list.scrollTop <= 0) return;
list.scrollTop = Math.max(0, list.scrollTop + delta);
});
observer.observe(toolbar);
return () => observer.disconnect();
+32 -12
View File
@@ -2364,6 +2364,20 @@ export function MailApp({ linkSegments }: MailAppProps = {}) {
// Pro sidebar: user clicked a folder under a specific account group.
// accountId === null means the active account; non-null means a viewing
// override that fetches via that account's JMAP client.
// When "Clear search when switching folders" is on, a folder click drops
// any active text query + advanced filters and browses the folder instead
// of re-running the search there (#553 is the default: search follows you
// across folders). Returns true when it cleared, so the caller browses
// rather than re-searching. Reads the current query/filters from the
// closure to decide whether there was anything to clear.
const clearSearchIfFolderChangeResets = (): boolean => {
if (!useSettingsStore.getState().clearSearchOnFolderChange) return false;
if (isFilterEmpty(searchFilters) && !searchQuery) return false;
setSearchQuery("");
clearSearchFilters();
return true;
};
const handleAccountMailboxSelect = async (accountId: string | null, mailboxId: string) => {
const viewingClient = accountId
? useAuthStore.getState().getClientForAccount(accountId) ?? client
@@ -2378,11 +2392,13 @@ export function MailApp({ linkSegments }: MailAppProps = {}) {
setTabletListVisible(true);
}
if (viewingClient) {
// Keep an active search applied when switching folders (#553); the
// store actions resolve the viewing account's client internally.
if (!isFilterEmpty(searchFilters)) {
// Keep an active search applied when switching folders (#553), unless
// the user opted to reset search on folder change. The store actions
// resolve the viewing account's client internally.
const searchCleared = clearSearchIfFolderChangeResets();
if (!searchCleared && !isFilterEmpty(searchFilters)) {
await advancedSearch(viewingClient);
} else if (searchQuery) {
} else if (!searchCleared && searchQuery) {
await searchEmails(viewingClient, searchQuery);
} else {
await fetchEmails(viewingClient, mailboxId);
@@ -2432,9 +2448,10 @@ export function MailApp({ linkSegments }: MailAppProps = {}) {
}
const populated = await buildPopulatedUnifiedAccounts();
const searchCleared = clearSearchIfFolderChangeResets();
// Keep an active search across the switch and re-run it in this view
// (mirrors normal mailboxes), preserving advanced filters; otherwise browse.
if (client && (!isFilterEmpty(searchFilters) || searchQuery)) {
if (client && !searchCleared && (!isFilterEmpty(searchFilters) || searchQuery)) {
useEmailStore.setState({ isUnifiedView: true, unifiedRole: role, crossView: null });
if (!isFilterEmpty(searchFilters)) {
await advancedSearch(client);
@@ -2465,9 +2482,10 @@ export function MailApp({ linkSegments }: MailAppProps = {}) {
}
const populated = await buildPopulatedUnifiedAccounts();
const searchCleared = clearSearchIfFolderChangeResets();
// Keep an active search across the switch and re-run it in this view
// (mirrors normal mailboxes), preserving advanced filters; otherwise browse.
if (client && (!isFilterEmpty(searchFilters) || searchQuery)) {
if (client && !searchCleared && (!isFilterEmpty(searchFilters) || searchQuery)) {
useEmailStore.setState({ isUnifiedView: true, crossView: view, unifiedRole: null });
if (!isFilterEmpty(searchFilters)) {
await advancedSearch(client);
@@ -2501,13 +2519,15 @@ export function MailApp({ linkSegments }: MailAppProps = {}) {
}
if (client) {
// If there's an active search, re-run it in the new mailbox. Advanced
// filters must go through advancedSearch (which also includes the text
// query) — falling back to fetchEmails would silently drop them while
// the UI still shows them as active (#553).
if (!isFilterEmpty(searchFilters)) {
// If there's an active search, re-run it in the new mailbox unless the
// user opted to reset search on folder change. Advanced filters must go
// through advancedSearch (which also includes the text query) — falling
// back to fetchEmails would silently drop them while the UI still shows
// them as active (#553).
const searchCleared = clearSearchIfFolderChangeResets();
if (!searchCleared && !isFilterEmpty(searchFilters)) {
await advancedSearch(client);
} else if (searchQuery) {
} else if (!searchCleared && searchQuery) {
await searchEmails(client, searchQuery);
} else {
await fetchEmails(client, mailboxId);
+8
View File
@@ -25,6 +25,7 @@ export function ReadingSettings() {
returnToListAfterAction,
swipeRightAction,
swipeLeftAction,
clearSearchOnFolderChange,
showPreview,
mailLayout,
disableThreading,
@@ -245,6 +246,13 @@ export function ReadingSettings() {
/>
</SettingItem>
<SettingItem label={t('clear_search_on_folder_change.label')} description={t('clear_search_on_folder_change.description')}>
<ToggleSwitch
checked={clearSearchOnFolderChange}
onChange={(checked) => updateSetting('clearSearchOnFolderChange', checked)}
/>
</SettingItem>
{!isSettingHidden('showPreview') && (
<SettingItem
label={t('show_preview.label')}
+4
View File
@@ -1243,6 +1243,10 @@
}
},
"email_behavior": {
"clear_search_on_folder_change": {
"label": "مسح البحث عند تبديل المجلدات",
"description": "النقر على مجلد (البريد الوارد، المرسل…) يمسح البحث النشط ويعرض ذلك المجلد. عند الإيقاف، يبقى البحث ويُعاد تشغيله في المجلد الذي تنتقل إليه."
},
"request_read_receipt": {
"label": "طلب إيصالات القراءة افتراضيًا",
"description": "تفعيل طلب إيصال القراءة مسبقًا عند إنشاء رسالة جديدة."
+4
View File
@@ -1243,6 +1243,10 @@
}
},
"email_behavior": {
"clear_search_on_folder_change": {
"label": "Esborra la cerca en canviar de carpeta",
"description": "En clicar una carpeta (Safata d'entrada, Enviats…) s'esborra la cerca activa i es mostra aquesta carpeta. Si està desactivat, la cerca es manté i es torna a executar a la carpeta triada."
},
"request_read_receipt": {
"label": "Sol·licita confirmacions de lectura per defecte",
"description": "Activa per defecte la sol·licitud de confirmació de lectura en redactar un missatge nou."
+4
View File
@@ -1240,6 +1240,10 @@
}
},
"email_behavior": {
"clear_search_on_folder_change": {
"label": "Vymazat hledání při přepnutí složky",
"description": "Kliknutí na složku (Doručená pošta, Odeslaná…) vymaže aktivní hledání a zobrazí složku. Když je vypnuto, hledání zůstává a spustí se znovu ve zvolené složce."
},
"request_read_receipt": {
"label": "Standardně vyžadovat potvrzení o přečtení",
"description": "Při psaní nové zprávy předem zapnout žádost o potvrzení o přečtení."
+4
View File
@@ -1243,6 +1243,10 @@
}
},
"email_behavior": {
"clear_search_on_folder_change": {
"label": "Ryd søgningen ved skift af mappe",
"description": "Klik på en mappe (Indbakke, Sendt …) rydder den aktive søgning og viser mappen. Når det er slået fra, bevares søgningen og køres igen i den valgte mappe."
},
"request_read_receipt": {
"label": "Anmod om læsekvittering som standard",
"description": "Aktivér anmodningen om læsekvittering på forhånd, når du skriver en ny besked."
+4
View File
@@ -1240,6 +1240,10 @@
}
},
"email_behavior": {
"clear_search_on_folder_change": {
"label": "Suche beim Ordnerwechsel löschen",
"description": "Ein Klick auf einen Ordner (Posteingang, Gesendet …) löscht die aktive Suche und zeigt den Ordner. Ist die Option aus, bleibt die Suche bestehen und wird im neuen Ordner erneut ausgeführt."
},
"request_read_receipt": {
"label": "Lesebestätigung standardmäßig anfordern",
"description": "Beim Verfassen einer neuen Nachricht die Anforderung einer Lesebestätigung vorab aktivieren."
+4
View File
@@ -1243,6 +1243,10 @@
}
},
"email_behavior": {
"clear_search_on_folder_change": {
"label": "Clear search when switching folders",
"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."
},
"request_read_receipt": {
"label": "Request read receipts by default",
"description": "Pre-enable the read-receipt request when composing a new message."
+4
View File
@@ -1240,6 +1240,10 @@
}
},
"email_behavior": {
"clear_search_on_folder_change": {
"label": "Borrar la búsqueda al cambiar de carpeta",
"description": "Al hacer clic en una carpeta (Bandeja de entrada, Enviados…) se borra la búsqueda activa y se muestra esa carpeta. Si está desactivado, la búsqueda se mantiene y se vuelve a ejecutar en la carpeta a la que cambies."
},
"request_read_receipt": {
"label": "Solicitar confirmaciones de lectura de forma predeterminada",
"description": "Activar previamente la solicitud de confirmación de lectura al redactar un mensaje nuevo."
+4
View File
@@ -1243,6 +1243,10 @@
}
},
"email_behavior": {
"clear_search_on_folder_change": {
"label": "پاک کردن جستجو هنگام تغییر پوشه",
"description": "کلیک روی یک پوشه (صندوق ورودی، ارسال‌شده…) جستجوی فعال را پاک می‌کند و آن پوشه را نشان می‌دهد. وقتی خاموش باشد، جستجو باقی می‌ماند و در پوشهٔ جدید دوباره اجرا می‌شود."
},
"request_read_receipt": {
"label": "درخواست تأیید خواندن",
"description": "پیش‌فعال کردن تأیید خواندن"
+4
View File
@@ -1240,6 +1240,10 @@
}
},
"email_behavior": {
"clear_search_on_folder_change": {
"label": "Effacer la recherche lors du changement de dossier",
"description": "Cliquer sur un dossier (Boîte de réception, Envoyés…) efface la recherche active et affiche ce dossier. Désactivé, la recherche reste appliquée et est relancée dans le dossier choisi."
},
"request_read_receipt": {
"label": "Demander un accusé de lecture par défaut",
"description": "Activer au préalable la demande d''accusé de lecture lors de la rédaction d''un nouveau message."
+4
View File
@@ -1205,6 +1205,10 @@
}
},
"email_behavior": {
"clear_search_on_folder_change": {
"label": "נקה את החיפוש בעת מעבר בין תיקיות",
"description": "לחיצה על תיקייה (דואר נכנס, נשלח…) מנקה את החיפוש הפעיל ומציגה את התיקייה. כשהאפשרות כבויה, החיפוש נשאר ומופעל מחדש בתיקייה שאליה עברת."
},
"title": "התנהגות אימייל",
"description": "קבע את אופן הטיפול בהודעות דוא\"ל",
"mark_read": {
+4
View File
@@ -1243,6 +1243,10 @@
}
},
"email_behavior": {
"clear_search_on_folder_change": {
"label": "Keresés törlése mappaváltáskor",
"description": "Egy mappára kattintva (Beérkezett, Elküldött…) törlődik az aktív keresés, és a mappa jelenik meg. Kikapcsolva a keresés megmarad, és újra lefut a választott mappában."
},
"request_read_receipt": {
"label": "Olvasási visszaigazolás kérése alapértelmezés szerint",
"description": "Az olvasási visszaigazolás kérésének előre bekapcsolása új üzenet írásakor."
+4
View File
@@ -1240,6 +1240,10 @@
}
},
"email_behavior": {
"clear_search_on_folder_change": {
"label": "Cancella la ricerca quando cambi cartella",
"description": "Facendo clic su una cartella (Posta in arrivo, Inviati…) la ricerca attiva viene cancellata e si apre quella cartella. Se disattivato, la ricerca resta attiva e viene rieseguita nella cartella scelta."
},
"request_read_receipt": {
"label": "Richiedi conferme di lettura per impostazione predefinita",
"description": "Attiva in anticipo la richiesta di conferma di lettura durante la composizione di un nuovo messaggio."
+4
View File
@@ -1240,6 +1240,10 @@
}
},
"email_behavior": {
"clear_search_on_folder_change": {
"label": "フォルダー切り替え時に検索をクリア",
"description": "フォルダー(受信トレイ、送信済みなど)をクリックすると、有効な検索をクリアしてそのフォルダーを表示します。オフの場合、検索は保持され、切り替え先のフォルダーで再実行されます。"
},
"request_read_receipt": {
"label": "既定で開封確認を要求する",
"description": "新しいメッセージの作成時に、開封確認の要求をあらかじめ有効にします。"
+4
View File
@@ -1240,6 +1240,10 @@
}
},
"email_behavior": {
"clear_search_on_folder_change": {
"label": "폴더 전환 시 검색 지우기",
"description": "폴더(받은편지함, 보낸편지함 등)를 클릭하면 활성 검색을 지우고 해당 폴더를 표시합니다. 꺼져 있으면 검색이 유지되고 이동한 폴더에서 다시 실행됩니다."
},
"request_read_receipt": {
"label": "기본적으로 읽음 확인 요청",
"description": "새 메시지를 작성할 때 읽음 확인 요청을 미리 켭니다."
+4
View File
@@ -1240,6 +1240,10 @@
}
},
"email_behavior": {
"clear_search_on_folder_change": {
"label": "Notīrīt meklēšanu, mainot mapi",
"description": "Noklikšķinot uz mapes (Iesūtne, Nosūtītie…), aktīvā meklēšana tiek notīrīta un tiek atvērta šī mape. Kad izslēgts, meklēšana paliek un tiek atkārtota izvēlētajā mapē."
},
"request_read_receipt": {
"label": "Pēc noklusējuma pieprasīt lasīšanas apstiprinājumus",
"description": "Sastādot jaunu ziņojumu, iepriekš ieslēgt lasīšanas apstiprinājuma pieprasījumu."
+4
View File
@@ -1243,6 +1243,10 @@
}
},
"email_behavior": {
"clear_search_on_folder_change": {
"label": "Хавтас солих үед хайлтыг арилгах",
"description": "Хавтас дээр дарахад (Ирсэн, Илгээсэн…) идэвхтэй хайлт арилж, тухайн хавтас нээгдэнэ. Унтраасан үед хайлт хадгалагдаж, шилжсэн хавтаст дахин ажиллана."
},
"request_read_receipt": {
"label": "Анхдагчаар уншсан баримтыг хүсэх",
"description": "Шинэ мессеж бичихдээ уншсан хүлээн авах хүсэлтийг урьдчилан идэвхжүүлнэ."
+4
View File
@@ -1243,6 +1243,10 @@
}
},
"email_behavior": {
"clear_search_on_folder_change": {
"label": "Tøm søket ved bytte av mappe",
"description": "Å klikke på en mappe (Innboks, Sendt …) tømmer det aktive søket og viser mappen. Når det er av, beholdes søket og kjøres på nytt i mappen du bytter til."
},
"request_read_receipt": {
"label": "Be om lesebekreftelser som standard",
"description": "Be automatisk om lesebekreftelse når du skriver en ny melding."
+4
View File
@@ -1240,6 +1240,10 @@
}
},
"email_behavior": {
"clear_search_on_folder_change": {
"label": "Zoekopdracht wissen bij wisselen van map",
"description": "Op een map klikken (Postvak IN, Verzonden…) wist de actieve zoekopdracht en toont die map. Uitgeschakeld blijft de zoekopdracht staan en wordt opnieuw uitgevoerd in de gekozen map."
},
"request_read_receipt": {
"label": "Standaard om leesbevestiging vragen",
"description": "De aanvraag voor een leesbevestiging vooraf inschakelen bij het opstellen van een nieuw bericht."
+4
View File
@@ -1240,6 +1240,10 @@
}
},
"email_behavior": {
"clear_search_on_folder_change": {
"label": "Czyść wyszukiwanie przy zmianie folderu",
"description": "Kliknięcie folderu (Odebrane, Wysłane…) czyści aktywne wyszukiwanie i otwiera ten folder. Gdy wyłączone, wyszukiwanie pozostaje i jest ponawiane w wybranym folderze."
},
"request_read_receipt": {
"label": "Domyślnie żądaj potwierdzeń przeczytania",
"description": "Włącz wcześniej żądanie potwierdzenia przeczytania podczas tworzenia nowej wiadomości."
+4
View File
@@ -1240,6 +1240,10 @@
}
},
"email_behavior": {
"clear_search_on_folder_change": {
"label": "Limpar a pesquisa ao mudar de pasta",
"description": "Clicar numa pasta (Caixa de entrada, Enviados…) limpa a pesquisa ativa e mostra essa pasta. Se desativado, a pesquisa mantém-se e volta a ser executada na pasta para a qual mudar."
},
"request_read_receipt": {
"label": "Solicitar confirmações de leitura por padrão",
"description": "Ativar previamente a solicitação de confirmação de leitura ao redigir uma nova mensagem."
+4
View File
@@ -1243,6 +1243,10 @@
}
},
"email_behavior": {
"clear_search_on_folder_change": {
"label": "Șterge căutarea la schimbarea folderului",
"description": "Un clic pe un folder (Inbox, Trimise…) șterge căutarea activă și afișează acel folder. Dezactivat, căutarea rămâne și se reia în folderul ales."
},
"request_read_receipt": {
"label": "Solicitați confirmări de citire în mod implicit",
"description": "Activați în prealabil solicitarea de confirmare de citire atunci când redactați un mesaj nou."
+4
View File
@@ -1240,6 +1240,10 @@
}
},
"email_behavior": {
"clear_search_on_folder_change": {
"label": "Очищать поиск при переходе между папками",
"description": "Щелчок по папке («Входящие», «Отправленные»…) очищает активный поиск и открывает эту папку. Если выключено, поиск сохраняется и выполняется заново в выбранной папке."
},
"request_read_receipt": {
"label": "Запрашивать уведомления о прочтении по умолчанию",
"description": "Заранее включать запрос уведомления о прочтении при создании нового сообщения."
+4
View File
@@ -1243,6 +1243,10 @@
}
},
"email_behavior": {
"clear_search_on_folder_change": {
"label": "Vymazať hľadanie pri prepnutí priečinka",
"description": "Kliknutie na priečinok (Doručená pošta, Odoslaná…) vymaže aktívne hľadanie a zobrazí priečinok. Keď je vypnuté, hľadanie zostáva a spustí sa znova vo zvolenom priečinku."
},
"title": "Správanie e-mailov",
"description": "Nakonfigurujte spôsob spracovania e-mailov",
"request_read_receipt": {
+4
View File
@@ -1240,6 +1240,10 @@
}
},
"email_behavior": {
"clear_search_on_folder_change": {
"label": "Klasör değiştirirken aramayı temizle",
"description": "Bir klasöre tıklamak (Gelen Kutusu, Gönderilenler…) etkin aramayı temizler ve o klasörü gösterir. Kapalıyken arama korunur ve geçtiğiniz klasörde yeniden çalıştırılır."
},
"request_read_receipt": {
"label": "Varsayılan olarak okundu bilgisi iste",
"description": "Yeni bir ileti yazarken okundu bilgisi isteğini önceden etkinleştir."
+4
View File
@@ -1240,6 +1240,10 @@
}
},
"email_behavior": {
"clear_search_on_folder_change": {
"label": "Очищати пошук під час перемикання тек",
"description": "Клацання теки («Вхідні», «Надіслані»…) очищає активний пошук і відкриває цю теку. Якщо вимкнено, пошук лишається й повторюється у вибраній теці."
},
"request_read_receipt": {
"label": "Запитувати сповіщення про прочитання за замовчуванням",
"description": "Заздалегідь вмикати запит сповіщення про прочитання під час створення нового повідомлення."
+4
View File
@@ -1240,6 +1240,10 @@
}
},
"email_behavior": {
"clear_search_on_folder_change": {
"label": "切換資料夾時清除搜尋",
"description": "點擊資料夾(收件匣、寄件備份等)會清除目前的搜尋並瀏覽該資料夾。關閉時,搜尋會保留並在切換到的資料夾中重新執行。"
},
"request_read_receipt": {
"label": "預設請求已讀回執",
"description": "撰寫新郵件時預先啟用已讀回執請求。"
+4
View File
@@ -1240,6 +1240,10 @@
}
},
"email_behavior": {
"clear_search_on_folder_change": {
"label": "切换文件夹时清除搜索",
"description": "点击文件夹(收件箱、已发送等)会清除当前搜索并浏览该文件夹。关闭时,搜索会保留并在切换到的文件夹中重新运行。"
},
"request_read_receipt": {
"label": "默认请求已读回执",
"description": "撰写新邮件时预先启用已读回执请求。"
+3
View File
@@ -321,6 +321,7 @@ interface SettingsState {
deleteAction: DeleteAction;
permanentlyDeleteJunk: boolean; // Permanently delete emails from junk/spam instead of moving to trash
returnToListAfterAction: boolean; // After delete / mark-unread in an open message, return to the list instead of opening the next message
clearSearchOnFolderChange: boolean; // Reset the search query + advanced filters when switching folders, instead of re-running the search in the newly selected folder (#553 keeps it applied when this is off)
showPreview: boolean;
mailLayout: MailLayout;
emailsPerPage: number;
@@ -557,6 +558,7 @@ const DEFAULT_SETTINGS = {
deleteAction: 'trash' as DeleteAction,
permanentlyDeleteJunk: false,
returnToListAfterAction: true,
clearSearchOnFolderChange: false,
showPreview: true,
mailLayout: 'split' as MailLayout,
emailsPerPage: 50,
@@ -782,6 +784,7 @@ export const useSettingsStore = create<SettingsState>()(
markAsReadDelay: state.markAsReadDelay,
deleteAction: state.deleteAction,
returnToListAfterAction: state.returnToListAfterAction,
clearSearchOnFolderChange: state.clearSearchOnFolderChange,
showPreview: state.showPreview,
mailLayout: state.mailLayout,
emailsPerPage: state.emailsPerPage,