diff --git a/components/desktop/__tests__/desktop-host.test.tsx b/components/desktop/__tests__/desktop-host.test.tsx
index 61f8a3b8d..cb126f335 100644
--- a/components/desktop/__tests__/desktop-host.test.tsx
+++ b/components/desktop/__tests__/desktop-host.test.tsx
@@ -220,6 +220,42 @@ describe('DesktopHost', () => {
});
});
+ // mail-25: "Search mail for '…'" on selected text in the native menu.
+ describe('Search mail for selected text', () => {
+ const searchFor = (text: string) => `mail.searchText:${JSON.stringify(text)}`;
+
+ it('runs on the mail page', async () => {
+ const shell = fakeBridge();
+ render();
+ const heard: unknown[] = [];
+ const listen = (e: Event) => { e.preventDefault(); heard.push((e as CustomEvent).detail); };
+ window.addEventListener('bulwark:mail-search', listen);
+ try {
+ shell.run(searchFor(' quarterly report \n'));
+ } finally {
+ window.removeEventListener('bulwark:mail-search', listen);
+ }
+ expect(heard).toEqual([{ text: 'quarterly report' }]);
+ expect(push).not.toHaveBeenCalled();
+ });
+
+ it('goes to Mail first from another app', async () => {
+ const shell = fakeBridge();
+ render();
+ shell.run(searchFor('report'));
+ expect(push).toHaveBeenCalledWith('/');
+ });
+
+ it('is on offer to the shell when signed in, with its label in the interface language', async () => {
+ const shell = fakeBridge();
+ render();
+ await waitFor(() => expect(shell.setState).toHaveBeenCalled());
+ expect(shell.setState.mock.lastCall![0]).toMatchObject({ 'mail.searchText': true });
+ await waitFor(() => expect(shell.setLanguages).toHaveBeenCalled());
+ expect(shell.setLanguages.mock.lastCall![0].labels.contextSearchMail).toBe('desktop_mail.context_search_mail');
+ });
+ });
+
it('hands the shell its menus in the interface language', async () => {
const shell = fakeBridge();
act(() => useLocaleStore.setState({ locale: 'fr' }));
diff --git a/components/desktop/desktop-host.tsx b/components/desktop/desktop-host.tsx
index 909ca657b..511cbd85b 100644
--- a/components/desktop/desktop-host.tsx
+++ b/components/desktop/desktop-host.tsx
@@ -19,6 +19,7 @@ import { useOfflineStore } from "@/lib/offline/state";
import { openNewComposerWindow } from "@/lib/popout/open";
import { onReturnedComposerElsewhere } from "@/lib/popout/return-to-main";
import { isPopoutWindow } from "@/lib/popout/window-role";
+import { parkMailSearch, requestMailSearch } from "@/lib/mail-search-request";
import { onBeforeSignOut, onFullSignOut } from "@/lib/sign-out-cleanup";
import { useAuthStore } from "@/stores/auth-store";
import { useEmailStore } from "@/stores/email-store";
@@ -89,6 +90,22 @@ export function DesktopHost() {
}
return;
}
+ // Search mail for the text selected under a right-click.
+ if (id.startsWith("mail.searchText:")) {
+ try {
+ const text = JSON.parse(id.slice("mail.searchText:".length)) as unknown;
+ if (typeof text === "string" && text.trim()) {
+ const request = { text: text.trim() };
+ if (!requestMailSearch(request)) {
+ parkMailSearch(request);
+ routerRef.current.push("/");
+ }
+ }
+ } catch {
+ // Only the shell sends this, and it sends JSON.
+ }
+ return;
+ }
if (!runDesktopCommand(id)) fallback[id]?.();
});
}, []);
@@ -101,6 +118,7 @@ export function DesktopHost() {
const popout = isPopoutWindow();
setDesktopBaseState({
"link.check": true,
+ "mail.searchText": signedIn && !popout,
"shortcuts.singleKeys": singleKeys,
"policy.download": canDownloadAnywhere(),
"file.print": canPrint,
diff --git a/lib/desktop/languages.ts b/lib/desktop/languages.ts
index 28945be18..6f9423ae3 100644
--- a/lib/desktop/languages.ts
+++ b/lib/desktop/languages.ts
@@ -139,6 +139,7 @@ export function shellLabels(t: (key: string, values?: Record) =>
contextCopyAddress: t('desktopUi.context.copy_address'),
contextSaveImage: t('desktopUi.context.save_image'),
contextCopyImage: t('desktopUi.context.copy_image'),
+ contextSearchMail: t('desktop_mail.context_search_mail', { text: '{text}' }),
unsavedTitle: t('desktopUi.closing.unsaved_title'),
unsavedDetail: t('desktopUi.closing.unsaved_detail'),
closeAnyway: t('desktopUi.closing.close_anyway'),
diff --git a/locales/ar/common.json b/locales/ar/common.json
index ed9cbecb0..9dfd3bc3d 100644
--- a/locales/ar/common.json
+++ b/locales/ar/common.json
@@ -6810,6 +6810,9 @@
"close": "إغلاق"
}
},
+ "desktop_mail": {
+ "context_search_mail": "البحث في البريد عن «{text}»"
+ },
"appLock": {
"section_title": "قفل التطبيق",
"section_desc": "يطلب رمز PIN بعد فترة دون استخدام، في كل نافذة وعلامة تبويب. أثناء القفل يبقى البريد على هذا الجهاز مشفرًا ولا يملك التطبيق مفتاحه.",
diff --git a/locales/ca/common.json b/locales/ca/common.json
index 5e3d4916d..4247a62b1 100644
--- a/locales/ca/common.json
+++ b/locales/ca/common.json
@@ -6810,6 +6810,9 @@
"close": "Tanca"
}
},
+ "desktop_mail": {
+ "context_search_mail": "Cerca «{text}» al correu"
+ },
"appLock": {
"section_title": "Bloqueig de l'app",
"section_desc": "Demana un PIN després d'un temps sense ús, a cada finestra i pestanya. Mentre està bloquejada, el correu d'aquest dispositiu continua xifrat i l'app no en té la clau.",
diff --git a/locales/cs/common.json b/locales/cs/common.json
index ace3e4105..eeb09ac00 100644
--- a/locales/cs/common.json
+++ b/locales/cs/common.json
@@ -6810,6 +6810,9 @@
"close": "Zavřít"
}
},
+ "desktop_mail": {
+ "context_search_mail": "Hledat v poště „{text}“"
+ },
"appLock": {
"section_title": "Zámek aplikace",
"section_desc": "Po čase bez použití žádá o PIN, v každém okně a na každé kartě. Dokud je zamčeno, zůstává pošta na tomto zařízení zašifrovaná a aplikace k ní nemá klíč.",
diff --git a/locales/da/common.json b/locales/da/common.json
index b473c8379..13b8896fb 100644
--- a/locales/da/common.json
+++ b/locales/da/common.json
@@ -6810,6 +6810,9 @@
"close": "Luk"
}
},
+ "desktop_mail": {
+ "context_search_mail": "Søg efter “{text}” i mail"
+ },
"appLock": {
"section_title": "Applås",
"section_desc": "Beder om en pinkode efter en tid uden brug, i alle vinduer og faner. Mens den er låst, forbliver mail på denne enhed krypteret, og appen har ingen nøgle til den.",
diff --git a/locales/de/common.json b/locales/de/common.json
index 9cb2097f7..a22ad6519 100644
--- a/locales/de/common.json
+++ b/locales/de/common.json
@@ -6810,6 +6810,9 @@
"close": "Schließen"
}
},
+ "desktop_mail": {
+ "context_search_mail": "E-Mails nach „{text}“ durchsuchen"
+ },
"appLock": {
"section_title": "App-Sperre",
"section_desc": "Fragt nach einer Zeit ohne Nutzung in jedem Fenster und Tab nach einer PIN. Solange gesperrt ist, bleiben E-Mails auf diesem Gerät verschlüsselt und die App hat keinen Schlüssel dafür.",
diff --git a/locales/en/common.json b/locales/en/common.json
index 84f9389a7..e95b5942e 100644
--- a/locales/en/common.json
+++ b/locales/en/common.json
@@ -6810,6 +6810,9 @@
"close": "Close"
}
},
+ "desktop_mail": {
+ "context_search_mail": "Search mail for \"{text}\""
+ },
"appLock": {
"section_title": "App lock",
"section_desc": "Asks for a PIN after a time without use, in every window and tab. While it's locked, mail on this device stays encrypted and the app has no key for it.",
diff --git a/locales/es/common.json b/locales/es/common.json
index a44fce868..a519f045b 100644
--- a/locales/es/common.json
+++ b/locales/es/common.json
@@ -6810,6 +6810,9 @@
"close": "Cerrar"
}
},
+ "desktop_mail": {
+ "context_search_mail": "Buscar «{text}» en el correo"
+ },
"appLock": {
"section_title": "Bloqueo de la app",
"section_desc": "Pide un PIN tras un tiempo sin uso, en cada ventana y pestaña. Mientras está bloqueada, el correo de este dispositivo sigue cifrado y la app no tiene la clave.",
diff --git a/locales/et/common.json b/locales/et/common.json
index ace51f3b5..c2d23a1cc 100644
--- a/locales/et/common.json
+++ b/locales/et/common.json
@@ -6810,6 +6810,9 @@
"close": "Sulge"
}
},
+ "desktop_mail": {
+ "context_search_mail": "Otsi kirjadest „{text}”"
+ },
"appLock": {
"section_title": "Rakenduse lukk",
"section_desc": "Küsib pärast kasutamata aega PIN-koodi, igas aknas ja vahekaardil. Lukus olles jääb selle seadme e-post krüpteerituks ja rakendusel pole selle võtit.",
diff --git a/locales/fa/common.json b/locales/fa/common.json
index 9961aab01..57bf0ccb8 100644
--- a/locales/fa/common.json
+++ b/locales/fa/common.json
@@ -6810,6 +6810,9 @@
"close": "بستن"
}
},
+ "desktop_mail": {
+ "context_search_mail": "جستجوی «{text}» در نامهها"
+ },
"appLock": {
"section_title": "قفل برنامه",
"section_desc": "پس از مدتی بیاستفاده، در هر پنجره و زبانه پین میخواهد. تا وقتی قفل است، ایمیلهای این دستگاه رمزگذاریشده میمانند و برنامه کلید آنها را ندارد.",
diff --git a/locales/fr/common.json b/locales/fr/common.json
index 750e84417..6c51f91bb 100644
--- a/locales/fr/common.json
+++ b/locales/fr/common.json
@@ -6810,6 +6810,9 @@
"close": "Fermer"
}
},
+ "desktop_mail": {
+ "context_search_mail": "Rechercher « {text} » dans les messages"
+ },
"appLock": {
"section_title": "Verrouillage de l'app",
"section_desc": "Demande un code PIN après un temps sans utilisation, dans chaque fenêtre et onglet. Tant que l'app est verrouillée, les e-mails sur cet appareil restent chiffrés et l'app n'a pas de clé pour les lire.",
diff --git a/locales/he/common.json b/locales/he/common.json
index 23cb11fa8..5d6b38bea 100644
--- a/locales/he/common.json
+++ b/locales/he/common.json
@@ -6811,6 +6811,9 @@
"close": "סגירה"
}
},
+ "desktop_mail": {
+ "context_search_mail": "חיפוש “{text}” בדואר"
+ },
"appLock": {
"section_title": "נעילת אפליקציה",
"section_desc": "מבקשת קוד PIN אחרי זמן ללא שימוש, בכל חלון וכרטיסייה. בזמן הנעילה הדואר במכשיר הזה נשאר מוצפן ולאפליקציה אין מפתח אליו.",
diff --git a/locales/hu/common.json b/locales/hu/common.json
index 172fa6df6..cf9764dbc 100644
--- a/locales/hu/common.json
+++ b/locales/hu/common.json
@@ -6810,6 +6810,9 @@
"close": "Bezárás"
}
},
+ "desktop_mail": {
+ "context_search_mail": "„{text}” keresése a levelek között"
+ },
"appLock": {
"section_title": "Alkalmazászár",
"section_desc": "Használat nélküli idő után PIN-kódot kér, minden ablakban és lapon. Amíg zárolva van, az eszközön lévő levelek titkosítva maradnak, és az alkalmazásnak nincs hozzájuk kulcsa.",
diff --git a/locales/it/common.json b/locales/it/common.json
index 71f13cca0..d8f1e71e7 100644
--- a/locales/it/common.json
+++ b/locales/it/common.json
@@ -6810,6 +6810,9 @@
"close": "Chiudi"
}
},
+ "desktop_mail": {
+ "context_search_mail": "Cerca «{text}» nella posta"
+ },
"appLock": {
"section_title": "Blocco dell'app",
"section_desc": "Chiede un PIN dopo un periodo senza utilizzo, in ogni finestra e scheda. Finché è bloccata, la posta su questo dispositivo resta cifrata e l'app non ha la chiave.",
diff --git a/locales/ja/common.json b/locales/ja/common.json
index 7d382e843..141ff262c 100644
--- a/locales/ja/common.json
+++ b/locales/ja/common.json
@@ -6810,6 +6810,9 @@
"close": "閉じる"
}
},
+ "desktop_mail": {
+ "context_search_mail": "メールで「{text}」を検索"
+ },
"appLock": {
"section_title": "アプリのロック",
"section_desc": "一定時間使わないと、すべてのウィンドウとタブで PIN を求めます。ロック中、このデバイスのメールは暗号化されたままで、アプリには鍵がありません。",
diff --git a/locales/ko/common.json b/locales/ko/common.json
index 9f9643ad0..dab2cdc7c 100644
--- a/locales/ko/common.json
+++ b/locales/ko/common.json
@@ -6810,6 +6810,9 @@
"close": "닫기"
}
},
+ "desktop_mail": {
+ "context_search_mail": "메일에서 “{text}” 검색"
+ },
"appLock": {
"section_title": "앱 잠금",
"section_desc": "일정 시간 사용하지 않으면 모든 창과 탭에서 PIN을 요청합니다. 잠겨 있는 동안 이 기기의 메일은 암호화된 상태로 유지되며 앱에는 키가 없습니다.",
diff --git a/locales/lv/common.json b/locales/lv/common.json
index a4bc8ef83..87bb25b8b 100644
--- a/locales/lv/common.json
+++ b/locales/lv/common.json
@@ -6810,6 +6810,9 @@
"close": "Aizvērt"
}
},
+ "desktop_mail": {
+ "context_search_mail": "Meklēt pastā “{text}”"
+ },
"appLock": {
"section_title": "Lietotnes bloķēšana",
"section_desc": "Pēc laika bez lietošanas prasa PIN kodu katrā logā un cilnē. Kamēr lietotne ir bloķēta, e-pasts šajā ierīcē paliek šifrēts, un lietotnei nav tā atslēgas.",
diff --git a/locales/mn/common.json b/locales/mn/common.json
index 152574c4e..afb0c4c73 100644
--- a/locales/mn/common.json
+++ b/locales/mn/common.json
@@ -6810,6 +6810,9 @@
"close": "Хаах"
}
},
+ "desktop_mail": {
+ "context_search_mail": "Захидлаас “{text}”-г хайх"
+ },
"appLock": {
"section_title": "Аппын түгжээ",
"section_desc": "Хэсэг хугацаанд ашиглаагүй бол цонх, таб бүрт ПИН код асууна. Түгжээтэй үед энэ төхөөрөмжийн имэйл шифрлэгдсэн хэвээр байх бөгөөд аппад түлхүүр нь байхгүй.",
diff --git a/locales/nb/common.json b/locales/nb/common.json
index 9a6c14f76..19214ee30 100644
--- a/locales/nb/common.json
+++ b/locales/nb/common.json
@@ -6810,6 +6810,9 @@
"close": "Lukk"
}
},
+ "desktop_mail": {
+ "context_search_mail": "Søk etter «{text}» i e-post"
+ },
"appLock": {
"section_title": "Applås",
"section_desc": "Ber om en PIN-kode etter en tid uten bruk, i alle vinduer og faner. Mens den er låst, forblir e-post på denne enheten kryptert, og appen har ingen nøkkel til den.",
diff --git a/locales/nl/common.json b/locales/nl/common.json
index 3c57a4007..a7f6eb62b 100644
--- a/locales/nl/common.json
+++ b/locales/nl/common.json
@@ -6810,6 +6810,9 @@
"close": "Sluit"
}
},
+ "desktop_mail": {
+ "context_search_mail": "Zoek ‘{text}’ in e-mail"
+ },
"appLock": {
"section_title": "App-vergrendeling",
"section_desc": "Vraagt na een tijd zonder gebruik om een pincode, in elk venster en tabblad. Zolang de app vergrendeld is, blijft de mail op dit apparaat versleuteld en heeft de app er geen sleutel voor.",
diff --git a/locales/pl/common.json b/locales/pl/common.json
index 20bd079ae..03e697086 100644
--- a/locales/pl/common.json
+++ b/locales/pl/common.json
@@ -6810,6 +6810,9 @@
"close": "Zamknij"
}
},
+ "desktop_mail": {
+ "context_search_mail": "Szukaj „{text}” w poczcie"
+ },
"appLock": {
"section_title": "Blokada aplikacji",
"section_desc": "Prosi o PIN po czasie bezczynności, w każdym oknie i na każdej karcie. Gdy aplikacja jest zablokowana, poczta na tym urządzeniu pozostaje zaszyfrowana, a aplikacja nie ma do niej klucza.",
diff --git a/locales/pt/common.json b/locales/pt/common.json
index 2452edf4a..ba5177940 100644
--- a/locales/pt/common.json
+++ b/locales/pt/common.json
@@ -6810,6 +6810,9 @@
"close": "Fechar"
}
},
+ "desktop_mail": {
+ "context_search_mail": "Pesquisar «{text}» no correio"
+ },
"appLock": {
"section_title": "Bloqueio da app",
"section_desc": "Pede um PIN após um tempo sem uso, em todas as janelas e separadores. Enquanto está bloqueada, o correio neste dispositivo fica cifrado e a app não tem a chave.",
diff --git a/locales/ro/common.json b/locales/ro/common.json
index 03bfef66a..8870d2cb5 100644
--- a/locales/ro/common.json
+++ b/locales/ro/common.json
@@ -6810,6 +6810,9 @@
"close": "Închidere"
}
},
+ "desktop_mail": {
+ "context_search_mail": "Căutare „{text}” în e-mail"
+ },
"appLock": {
"section_title": "Blocarea aplicației",
"section_desc": "Cere un PIN după un timp fără utilizare, în fiecare fereastră și filă. Cât timp e blocată, e-mailurile de pe acest dispozitiv rămân criptate și aplicația nu are cheia lor.",
diff --git a/locales/ru/common.json b/locales/ru/common.json
index ebb25a96f..2fd7035c3 100644
--- a/locales/ru/common.json
+++ b/locales/ru/common.json
@@ -6810,6 +6810,9 @@
"close": "Закрыть"
}
},
+ "desktop_mail": {
+ "context_search_mail": "Найти «{text}» в почте"
+ },
"appLock": {
"section_title": "Блокировка приложения",
"section_desc": "Запрашивает PIN-код после периода бездействия, в каждом окне и вкладке. Пока приложение заблокировано, почта на этом устройстве остаётся зашифрованной, и у приложения нет ключа к ней.",
diff --git a/locales/sk/common.json b/locales/sk/common.json
index 43343c99e..65792dde5 100644
--- a/locales/sk/common.json
+++ b/locales/sk/common.json
@@ -6810,6 +6810,9 @@
"close": "Zavrieť"
}
},
+ "desktop_mail": {
+ "context_search_mail": "Hľadať „{text}“ v pošte"
+ },
"appLock": {
"section_title": "Zámok aplikácie",
"section_desc": "Po čase bez používania žiada o PIN, v každom okne a na každej karte. Kým je zamknuté, pošta na tomto zariadení zostáva šifrovaná a aplikácia k nej nemá kľúč.",
diff --git a/locales/th/common.json b/locales/th/common.json
index d4aef2af2..7f4d72644 100644
--- a/locales/th/common.json
+++ b/locales/th/common.json
@@ -6818,6 +6818,9 @@
"close": "ปิด"
}
},
+ "desktop_mail": {
+ "context_search_mail": "ค้นหา “{text}” ในอีเมล"
+ },
"appLock": {
"section_title": "ล็อกแอป",
"section_desc": "ขอ PIN หลังจากไม่ได้ใช้งานระยะหนึ่ง ในทุกหน้าต่างและแท็บ ระหว่างล็อก อีเมลบนอุปกรณ์นี้ยังคงเข้ารหัสอยู่และแอปไม่มีกุญแจ",
diff --git a/locales/tr/common.json b/locales/tr/common.json
index 03393f7f0..db935cd66 100644
--- a/locales/tr/common.json
+++ b/locales/tr/common.json
@@ -6810,6 +6810,9 @@
"close": "Kapat"
}
},
+ "desktop_mail": {
+ "context_search_mail": "E-postalarda “{text}” ara"
+ },
"appLock": {
"section_title": "Uygulama kilidi",
"section_desc": "Bir süre kullanılmadığında her pencerede ve sekmede PIN ister. Kilitliyken bu cihazdaki e-postalar şifreli kalır ve uygulamada anahtarı olmaz.",
diff --git a/locales/uk/common.json b/locales/uk/common.json
index f033cd1ee..bbf719b82 100644
--- a/locales/uk/common.json
+++ b/locales/uk/common.json
@@ -6810,6 +6810,9 @@
"close": "Закрити"
}
},
+ "desktop_mail": {
+ "context_search_mail": "Шукати «{text}» у пошті"
+ },
"appLock": {
"section_title": "Блокування застосунку",
"section_desc": "Запитує PIN-код після часу без використання, у кожному вікні й вкладці. Поки застосунок заблоковано, пошта на цьому пристрої залишається зашифрованою, і застосунок не має ключа до неї.",
diff --git a/locales/vi/common.json b/locales/vi/common.json
index 9faa09bde..334fae530 100644
--- a/locales/vi/common.json
+++ b/locales/vi/common.json
@@ -6822,6 +6822,9 @@
"close": "Đóng"
}
},
+ "desktop_mail": {
+ "context_search_mail": "Tìm “{text}” trong thư"
+ },
"appLock": {
"section_title": "Khóa ứng dụng",
"section_desc": "Yêu cầu mã PIN sau một thời gian không dùng, trong mọi cửa sổ và thẻ. Khi đang khóa, thư trên thiết bị này vẫn được mã hóa và ứng dụng không có khóa của chúng.",
diff --git a/locales/zh-TW/common.json b/locales/zh-TW/common.json
index eed67bf21..b619a3497 100644
--- a/locales/zh-TW/common.json
+++ b/locales/zh-TW/common.json
@@ -6810,6 +6810,9 @@
"close": "關閉"
}
},
+ "desktop_mail": {
+ "context_search_mail": "在郵件中搜尋「{text}」"
+ },
"appLock": {
"section_title": "應用程式鎖定",
"section_desc": "一段時間未使用後,在每個視窗和分頁中要求輸入 PIN。鎖定期間,此裝置上的郵件保持加密,應用程式沒有金鑰。",
diff --git a/locales/zh/common.json b/locales/zh/common.json
index 0edeb8c81..755d5f62e 100644
--- a/locales/zh/common.json
+++ b/locales/zh/common.json
@@ -6810,6 +6810,9 @@
"close": "关闭"
}
},
+ "desktop_mail": {
+ "context_search_mail": "在邮件中搜索“{text}”"
+ },
"appLock": {
"section_title": "应用锁",
"section_desc": "一段时间未使用后,在每个窗口和标签页中要求输入 PIN。锁定期间,此设备上的邮件保持加密,应用没有密钥。",