diff --git a/app/(main)/[locale]/page.tsx b/app/(main)/[locale]/page.tsx index dd2bfa40..1d081eb9 100644 --- a/app/(main)/[locale]/page.tsx +++ b/app/(main)/[locale]/page.tsx @@ -11,8 +11,9 @@ import type { ComposerDraftData } from "@/components/email/email-composer"; import { ProtocolAccountPicker } from "@/components/protocol/protocol-account-picker"; import { ThreadConversationView } from "@/components/email/thread-conversation-view"; import { MobileHeader } from "@/components/layout/mobile-header"; -import { ThreadGroup, Email, isUnifiedMailboxId, UNIFIED_ROLE_BY_ID } from "@/lib/jmap/types"; +import { ThreadGroup, Email, isUnifiedMailboxId, UNIFIED_ROLE_BY_ID, ALL_MAIL_MAILBOX_ID } from "@/lib/jmap/types"; import { useAccountStore } from "@/stores/account-store"; +import { usePolicyStore } from "@/stores/policy-store"; import type { UnifiedAccountClient } from "@/lib/unified-mailbox"; import { KeyboardShortcutsModal } from "@/components/keyboard-shortcuts-modal"; import { useEmailStore, buildUnifiedAccountClients } from "@/stores/email-store"; @@ -341,7 +342,10 @@ export default function Home() { useProMultiAccountMailboxes(); const enableUnifiedMailbox = useSettingsStore((s) => s.enableUnifiedMailbox); + const enableAllMailView = useSettingsStore((s) => s.enableAllMailView); const delayedSendSupported = client?.hasDelayedSend() ?? true; + const allMailViewEnabled = usePolicyStore((s) => s.isFeatureEnabled('allMailViewEnabled')); + const showAllMailMailbox = allMailViewEnabled && enableAllMailView; const activeEmails = isScheduledView ? scheduledEmails : emails; const activeHasMore = isScheduledView ? scheduledHasMore : hasMoreEmails; const activeIsLoading = isScheduledView ? isLoadingScheduled : isLoading; @@ -2213,7 +2217,11 @@ export default function Home() { } // Get current mailbox name for mobile header - const currentMailboxName = isScheduledView ? t('sidebar.scheduled') : mailboxes.find(m => m.id === selectedMailbox)?.name || "Inbox"; + const currentMailboxName = isScheduledView + ? t('sidebar.scheduled') + : selectedMailbox === ALL_MAIL_MAILBOX_ID + ? t('sidebar.mailboxes.all_mail') + : mailboxes.find(m => m.id === selectedMailbox)?.name || "Inbox"; const isFocusedMailLayout = mailLayout === 'focus'; const isHorizontalMailLayout = mailLayout === 'horizontal' && !isMobile && !isTablet; const hasViewerContent = showComposer || Boolean(conversationThread) || Boolean(selectedEmail); @@ -2475,6 +2483,7 @@ export default function Home() { selectedKeyword={selectedKeyword} scheduledTotal={scheduledTotal} showScheduledMailbox={delayedSendSupported} + showAllMailMailbox={showAllMailMailbox} onMailboxSelect={handleMailboxSelect} onTagSelect={handleTagSelect} onUnreadFilterClick={handleUnreadFilterClick} diff --git a/app/(main)/[locale]/settings/page.tsx b/app/(main)/[locale]/settings/page.tsx index dee48ff7..4e3538be 100644 --- a/app/(main)/[locale]/settings/page.tsx +++ b/app/(main)/[locale]/settings/page.tsx @@ -183,6 +183,7 @@ const tabSearchPaths: Record = { 'settings.appearance.hide_account_switcher', 'settings.appearance.show_rail_account_list', 'settings.appearance.unified_mailbox', + 'settings.appearance.all_mail', 'settings.appearance.colorful_sidebar_icons', 'settings.email_behavior.mail_layout', ], diff --git a/app/(main)/admin/_tabs/policy.tsx b/app/(main)/admin/_tabs/policy.tsx index f6e12307..ede0e736 100644 --- a/app/(main)/admin/_tabs/policy.tsx +++ b/app/(main)/admin/_tabs/policy.tsx @@ -21,6 +21,7 @@ const FEATURE_GATE_LABELS: Partial void; scheduledTotal?: number; showScheduledMailbox?: boolean; + /** Gated "All Mail" virtual folder that merges all of the account's folders. */ + showAllMailMailbox?: boolean; className?: string; /** * Multi-account (Pro) mode props. When `multiAccountMode` is true, the @@ -678,6 +681,7 @@ export function Sidebar({ onRefreshMailboxes, scheduledTotal = 0, showScheduledMailbox = false, + showAllMailMailbox = false, className, multiAccountMode = false, accountMailboxes, @@ -978,6 +982,16 @@ export function Sidebar({ {/* Mailbox List */}
+ {showAllMailMailbox && ( + } + label={t('mailboxes.all_mail')} + depth={0} + isSelected={!selectedKeyword && selectedMailbox === '__all_mail__'} + onClick={() => onMailboxSelect?.('__all_mail__')} + isCollapsed={isCollapsed} + /> + )} {showUnified && (
s.accounts); const mailboxes = useEmailStore(s => s.mailboxes); const hasGroupInboxes = useMemo(() => mailboxes.some(m => m.isShared), [mailboxes]); + const allMailViewAllowed = isFeatureEnabled('allMailViewEnabled'); + + // Own (non-shared) folders and the current All Mail selection. `null` = + // never configured, which defaults to all non-special (no-role) folders. + const ownMailboxes = useMemo(() => mailboxes.filter(m => !m.isShared), [mailboxes]); + const allMailSelected = new Set( + allMailFolderIds === null + ? ownMailboxes.filter(m => !m.role).map(m => m.id) + : allMailFolderIds + ); + const toggleAllMailFolder = (id: string) => { + const next = new Set(allMailSelected); + if (next.has(id)) next.delete(id); + else next.add(id); + updateSetting('allMailFolderIds', ownMailboxes.filter(m => next.has(m.id)).map(m => m.id)); + }; return ( @@ -209,6 +226,56 @@ export function LayoutSettings() {
)} + {allMailViewAllowed && !isSettingHidden('enableAllMailView') && ( + + updateSetting('enableAllMailView', v)} + /> + + )} + + {allMailViewAllowed && enableAllMailView && ( +
+
+
{t('all_mail.folders_label')}
+
{t('all_mail.folders_description')}
+
+ {ownMailboxes.length === 0 ? ( +

{t('all_mail.no_folders')}

+ ) : ( +
+ {ownMailboxes.map((mb) => { + const checked = allMailSelected.has(mb.id); + return ( + + ); + })} +
+ )} +
+ )} + = Object.fro export function isUnifiedMailboxId(id: string): boolean { return id in UNIFIED_ROLE_BY_ID; } + +/** + * Virtual mailbox id for the gated "All Mail" view: every folder of a single + * account merged into one date-sorted list. Distinct from the unified mailbox + * ids above, which merge one role across multiple accounts. Which folders are + * included is a per-user setting (see `allMailFolderIds`). + */ +export const ALL_MAIL_MAILBOX_ID = '__all_mail__'; diff --git a/locales/cs/common.json b/locales/cs/common.json index b3f20740..63a36fc0 100644 --- a/locales/cs/common.json +++ b/locales/cs/common.json @@ -914,6 +914,13 @@ "description": "Zahrnout do sjednoceného zobrazení také sdílené/skupinové schránky." } }, + "all_mail": { + "label": "All Mail", + "description": "Show an \"All Mail\" entry above your folders that merges messages from across this account's folders into one list.", + "folders_label": "Folders in All Mail", + "folders_description": "Choose which folders are merged into the All Mail view.", + "no_folders": "No folders available." + }, "colorful_sidebar_icons": { "label": "Barevné ikony postranního panelu", "description": "Obarví ikony složek a štítků podle typu (modré Doručené, červený Spam, zelené Odeslané atd.). Vypněte pro jednobarevný postranní panel." diff --git a/locales/da/common.json b/locales/da/common.json index 3ce84079..192e6bf1 100644 --- a/locales/da/common.json +++ b/locales/da/common.json @@ -917,6 +917,13 @@ "description": "Inkluder også delte/gruppepostkasser i den samlede visning." } }, + "all_mail": { + "label": "All Mail", + "description": "Show an \"All Mail\" entry above your folders that merges messages from across this account's folders into one list.", + "folders_label": "Folders in All Mail", + "folders_description": "Choose which folders are merged into the All Mail view.", + "no_folders": "No folders available." + }, "colorful_sidebar_icons": { "label": "Farverige sidepane-ikoner", "description": "Farvelæg mappe- og tag-ikoner efter type (blå indbakke, rød spam, grøn sendt osv.). Deaktivér for et monokromt sidepanel." diff --git a/locales/de/common.json b/locales/de/common.json index c64d8de4..f64d6269 100644 --- a/locales/de/common.json +++ b/locales/de/common.json @@ -914,6 +914,13 @@ "description": "Gemeinsam genutzte/Gruppenpostfächer ebenfalls in die vereinheitlichte Ansicht aufnehmen." } }, + "all_mail": { + "label": "All Mail", + "description": "Show an \"All Mail\" entry above your folders that merges messages from across this account's folders into one list.", + "folders_label": "Folders in All Mail", + "folders_description": "Choose which folders are merged into the All Mail view.", + "no_folders": "No folders available." + }, "colorful_sidebar_icons": { "label": "Farbige Seitenleistensymbole", "description": "Ordner- und Tag-Symbole nach Typ einfärben (blauer Posteingang, roter Spam, grüner Gesendet usw.). Für eine monochrome Seitenleiste deaktivieren." diff --git a/locales/en/common.json b/locales/en/common.json index f69c0410..23a25a60 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -917,6 +917,13 @@ "description": "Also merge shared/group inboxes into the unified view." } }, + "all_mail": { + "label": "All Mail", + "description": "Show an \"All Mail\" entry above your folders that merges messages from across this account's folders into one list.", + "folders_label": "Folders in All Mail", + "folders_description": "Choose which folders are merged into the All Mail view.", + "no_folders": "No folders available." + }, "colorful_sidebar_icons": { "label": "Colorful Sidebar Icons", "description": "Tint folder and tag icons by type (blue Inbox, red Junk, green Sent, etc.). Disable for a monochrome sidebar." diff --git a/locales/es/common.json b/locales/es/common.json index 34762180..88448bd9 100644 --- a/locales/es/common.json +++ b/locales/es/common.json @@ -914,6 +914,13 @@ "description": "Incluir también los buzones compartidos o de grupo en la vista unificada." } }, + "all_mail": { + "label": "All Mail", + "description": "Show an \"All Mail\" entry above your folders that merges messages from across this account's folders into one list.", + "folders_label": "Folders in All Mail", + "folders_description": "Choose which folders are merged into the All Mail view.", + "no_folders": "No folders available." + }, "colorful_sidebar_icons": { "label": "Iconos de barra lateral a color", "description": "Colorea los iconos de carpetas y etiquetas según su tipo (azul para Bandeja de entrada, rojo para Spam, verde para Enviados, etc.). Desactívalo para una barra lateral monocroma." diff --git a/locales/fr/common.json b/locales/fr/common.json index 0cfdffd8..75c840df 100644 --- a/locales/fr/common.json +++ b/locales/fr/common.json @@ -914,6 +914,13 @@ "description": "Inclure également les boîtes partagées ou de groupe dans la vue unifiée." } }, + "all_mail": { + "label": "All Mail", + "description": "Show an \"All Mail\" entry above your folders that merges messages from across this account's folders into one list.", + "folders_label": "Folders in All Mail", + "folders_description": "Choose which folders are merged into the All Mail view.", + "no_folders": "No folders available." + }, "colorful_sidebar_icons": { "label": "Icônes colorées dans la barre latérale", "description": "Colore les icônes de dossiers et d'étiquettes par type (Boîte de réception en bleu, Indésirable en rouge, Envoyés en vert, etc.). Désactivez pour une barre latérale monochrome." diff --git a/locales/hu/common.json b/locales/hu/common.json index 75a2a101..f7abc249 100644 --- a/locales/hu/common.json +++ b/locales/hu/common.json @@ -917,6 +917,13 @@ "description": "Megosztott/csoportos postafiókok egyesítése az egységes nézetbe." } }, + "all_mail": { + "label": "All Mail", + "description": "Show an \"All Mail\" entry above your folders that merges messages from across this account's folders into one list.", + "folders_label": "Folders in All Mail", + "folders_description": "Choose which folders are merged into the All Mail view.", + "no_folders": "No folders available." + }, "colorful_sidebar_icons": { "label": "Színes oldalsáv ikonok", "description": "Mappa és címke ikonok színezése típus szerint (kék Beérkező, piros Spam, zöld Elküldött, stb.). Kapcsold ki az egyszínű oldalsávhoz." diff --git a/locales/it/common.json b/locales/it/common.json index f09d17d3..d1de5459 100644 --- a/locales/it/common.json +++ b/locales/it/common.json @@ -914,6 +914,13 @@ "description": "Includi anche le caselle condivise o di gruppo nella vista unificata." } }, + "all_mail": { + "label": "All Mail", + "description": "Show an \"All Mail\" entry above your folders that merges messages from across this account's folders into one list.", + "folders_label": "Folders in All Mail", + "folders_description": "Choose which folders are merged into the All Mail view.", + "no_folders": "No folders available." + }, "colorful_sidebar_icons": { "label": "Icone colorate nella barra laterale", "description": "Colora le icone di cartelle ed etichette per tipo (Posta in arrivo blu, Spam rosso, Inviati verde, ecc.). Disattiva per una barra laterale monocromatica." diff --git a/locales/ja/common.json b/locales/ja/common.json index f621380b..f508c373 100644 --- a/locales/ja/common.json +++ b/locales/ja/common.json @@ -914,6 +914,13 @@ "description": "共有/グループ受信トレイも統合ビューに含めます。" } }, + "all_mail": { + "label": "All Mail", + "description": "Show an \"All Mail\" entry above your folders that merges messages from across this account's folders into one list.", + "folders_label": "Folders in All Mail", + "folders_description": "Choose which folders are merged into the All Mail view.", + "no_folders": "No folders available." + }, "colorful_sidebar_icons": { "label": "カラフルなサイドバーアイコン", "description": "フォルダーとタグのアイコンを種類別に色分けします(受信トレイは青、迷惑メールは赤、送信済みは緑など)。モノクロのサイドバーにするには無効にしてください。" diff --git a/locales/ko/common.json b/locales/ko/common.json index 2e869301..749710b9 100644 --- a/locales/ko/common.json +++ b/locales/ko/common.json @@ -914,6 +914,13 @@ "description": "공유/그룹 받은편지함도 통합 보기에 포함합니다." } }, + "all_mail": { + "label": "All Mail", + "description": "Show an \"All Mail\" entry above your folders that merges messages from across this account's folders into one list.", + "folders_label": "Folders in All Mail", + "folders_description": "Choose which folders are merged into the All Mail view.", + "no_folders": "No folders available." + }, "colorful_sidebar_icons": { "label": "컬러풀한 사이드바 아이콘", "description": "폴더와 태그 아이콘을 유형별로 색상 표시합니다(받은편지함 파란색, 스팸 빨간색, 보낸편지함 녹색 등). 모노크롬 사이드바를 원하면 비활성화하세요." diff --git a/locales/lv/common.json b/locales/lv/common.json index 1dec5ee1..030dab89 100644 --- a/locales/lv/common.json +++ b/locales/lv/common.json @@ -914,6 +914,13 @@ "description": "Iekļaut apvienotajā skatā arī koplietotās/grupas pastkastes." } }, + "all_mail": { + "label": "All Mail", + "description": "Show an \"All Mail\" entry above your folders that merges messages from across this account's folders into one list.", + "folders_label": "Folders in All Mail", + "folders_description": "Choose which folders are merged into the All Mail view.", + "no_folders": "No folders available." + }, "colorful_sidebar_icons": { "label": "Krāsainas sānjoslas ikonas", "description": "Iekrāsojiet mapju un birku ikonas pēc to veida (zila Iesūtne, sarkana Mēstules, zaļa Nosūtītie utt.). Atspējojiet, lai iegūtu vienkrāsainu sānjoslu." diff --git a/locales/nl/common.json b/locales/nl/common.json index f4f0de1a..c442c882 100644 --- a/locales/nl/common.json +++ b/locales/nl/common.json @@ -914,6 +914,13 @@ "description": "Gedeelde/groepspostvakken ook in de gecombineerde weergave opnemen." } }, + "all_mail": { + "label": "All Mail", + "description": "Show an \"All Mail\" entry above your folders that merges messages from across this account's folders into one list.", + "folders_label": "Folders in All Mail", + "folders_description": "Choose which folders are merged into the All Mail view.", + "no_folders": "No folders available." + }, "colorful_sidebar_icons": { "label": "Gekleurde zijbalkpictogrammen", "description": "Kleur map- en tagpictogrammen op type (blauw Postvak IN, rood Spam, groen Verzonden, enz.). Schakel uit voor een monochrome zijbalk." diff --git a/locales/pl/common.json b/locales/pl/common.json index f01e902d..ebaf193e 100644 --- a/locales/pl/common.json +++ b/locales/pl/common.json @@ -914,6 +914,13 @@ "description": "Dodaj również udostępnione/grupowe skrzynki do widoku wspólnego." } }, + "all_mail": { + "label": "All Mail", + "description": "Show an \"All Mail\" entry above your folders that merges messages from across this account's folders into one list.", + "folders_label": "Folders in All Mail", + "folders_description": "Choose which folders are merged into the All Mail view.", + "no_folders": "No folders available." + }, "colorful_sidebar_icons": { "label": "Kolorowe ikony paska bocznego", "description": "Koloruj ikony folderów i tagów według typu (niebieska Skrzynka odbiorcza, czerwona Spam, zielona Wysłane itp.). Wyłącz, aby uzyskać monochromatyczny pasek boczny." diff --git a/locales/pt/common.json b/locales/pt/common.json index b5053b27..79a18875 100644 --- a/locales/pt/common.json +++ b/locales/pt/common.json @@ -914,6 +914,13 @@ "description": "Incluir também as caixas partilhadas ou de grupo na vista unificada." } }, + "all_mail": { + "label": "All Mail", + "description": "Show an \"All Mail\" entry above your folders that merges messages from across this account's folders into one list.", + "folders_label": "Folders in All Mail", + "folders_description": "Choose which folders are merged into the All Mail view.", + "no_folders": "No folders available." + }, "colorful_sidebar_icons": { "label": "Ícones coloridos na barra lateral", "description": "Colorir ícones de pastas e etiquetas por tipo (Caixa de entrada azul, Spam vermelho, Enviados verde, etc.). Desative para uma barra lateral monocromática." diff --git a/locales/ru/common.json b/locales/ru/common.json index 85e1751a..0a534fda 100644 --- a/locales/ru/common.json +++ b/locales/ru/common.json @@ -914,6 +914,13 @@ "description": "Также объединять общие/групповые ящики в едином представлении." } }, + "all_mail": { + "label": "All Mail", + "description": "Show an \"All Mail\" entry above your folders that merges messages from across this account's folders into one list.", + "folders_label": "Folders in All Mail", + "folders_description": "Choose which folders are merged into the All Mail view.", + "no_folders": "No folders available." + }, "colorful_sidebar_icons": { "label": "Цветные значки боковой панели", "description": "Окрашивать значки папок и тегов по типу (синий «Входящие», красный «Спам», зелёный «Отправленные» и т. д.). Отключите для монохромной боковой панели." diff --git a/locales/tr/common.json b/locales/tr/common.json index 08ee0cfa..8ad57c42 100644 --- a/locales/tr/common.json +++ b/locales/tr/common.json @@ -914,6 +914,13 @@ "description": "Paylaşılan/grup gelen kutularını da birleşik görünüme dahil et." } }, + "all_mail": { + "label": "All Mail", + "description": "Show an \"All Mail\" entry above your folders that merges messages from across this account's folders into one list.", + "folders_label": "Folders in All Mail", + "folders_description": "Choose which folders are merged into the All Mail view.", + "no_folders": "No folders available." + }, "colorful_sidebar_icons": { "label": "Renkli Kenar Çubuğu Simgeleri", "description": "Klasör ve etiket simgelerini türe göre renklendir (mavi Gelen Kutusu, kırmızı İstem Dışı vb.). Tek renkli kenar çubuğu için kapatın." diff --git a/locales/uk/common.json b/locales/uk/common.json index d79f41ca..0a364713 100644 --- a/locales/uk/common.json +++ b/locales/uk/common.json @@ -914,6 +914,13 @@ "description": "Також об'єднувати спільні/групові скриньки у спільному перегляді." } }, + "all_mail": { + "label": "All Mail", + "description": "Show an \"All Mail\" entry above your folders that merges messages from across this account's folders into one list.", + "folders_label": "Folders in All Mail", + "folders_description": "Choose which folders are merged into the All Mail view.", + "no_folders": "No folders available." + }, "colorful_sidebar_icons": { "label": "Кольорові значки бічної панелі", "description": "Забарвлюйте значки папок і тегів за типом (синя «Вхідні», червоний «Спам», зелена «Надіслані» тощо). Вимкніть для монохромної бічної панелі." diff --git a/locales/zh/common.json b/locales/zh/common.json index 670bfa06..718afd4e 100644 --- a/locales/zh/common.json +++ b/locales/zh/common.json @@ -914,6 +914,13 @@ "description": "在统一视图中也合并共享/群组收件箱。" } }, + "all_mail": { + "label": "All Mail", + "description": "Show an \"All Mail\" entry above your folders that merges messages from across this account's folders into one list.", + "folders_label": "Folders in All Mail", + "folders_description": "Choose which folders are merged into the All Mail view.", + "no_folders": "No folders available." + }, "colorful_sidebar_icons": { "label": "彩色侧边栏图标", "description": "按类型为文件夹和标签图标着色(蓝色收件箱、红色垃圾邮件、绿色已发送等)。禁用以获得单色侧边栏。" diff --git a/stores/email-store.ts b/stores/email-store.ts index 2b381a83..53b25249 100644 --- a/stores/email-store.ts +++ b/stores/email-store.ts @@ -1,5 +1,5 @@ import { create } from "zustand"; -import { Email, Mailbox, StateChange, ScheduledEmail, SendEmailResult } from "@/lib/jmap/types"; +import { Email, Mailbox, StateChange, ScheduledEmail, SendEmailResult, ALL_MAIL_MAILBOX_ID } from "@/lib/jmap/types"; import type { UnifiedMailboxRole } from "@/lib/jmap/types"; import type { IJMAPClient } from "@/lib/jmap/client-interface"; import { useSettingsStore } from "@/stores/settings-store"; @@ -315,6 +315,33 @@ function resolveActionMailboxes(): Mailbox[] { return state.mailboxes; } +/** + * Resolves the JMAP mailbox ids that make up the gated "All Mail" view for the + * active/viewing account. Honors the per-user `allMailFolderIds` setting; when + * unset (null) it defaults to every non-special (no-role) folder. Shared + * folders are excluded - All Mail is scoped to a single account. Returns + * JMAP-side ids (originalId for namespaced mailboxes). + */ +function resolveAllMailJmapIds(): string[] { + const mailboxes = resolveActionMailboxes().filter((mb) => !mb.isShared); + const configured = useSettingsStore.getState().allMailFolderIds; + const selected = configured === null + ? mailboxes.filter((mb) => !mb.role) + : mailboxes.filter((mb) => configured.includes(mb.id)); + return selected.map((mb) => mb.originalId || mb.id); +} + +/** + * Builds the JMAP Email/query filter for the All Mail view from a set of + * mailbox ids - an OR of `inMailbox` conditions (or a single condition). + */ +function buildAllMailFilter(jmapMailboxIds: string[]): Record { + if (jmapMailboxIds.length === 1) { + return { inMailbox: jmapMailboxIds[0] }; + } + return { operator: 'OR', conditions: jmapMailboxIds.map((id) => ({ inMailbox: id })) }; +} + /** * Resolves the JMAP client, mailbox list, and JMAP accountId to use for a * single-email action. @@ -658,6 +685,7 @@ export const useEmailStore = create((set, get) => ({ // doesn't exist in the fetched list (e.g. after an account switch) const currentSelectedMailbox = get().selectedMailbox; const selectionValid = currentSelectedMailbox === VIRTUAL_SCHEDULED_MAILBOX_ID + || currentSelectedMailbox === ALL_MAIL_MAILBOX_ID || (currentSelectedMailbox && mailboxes.some(m => m.id === currentSelectedMailbox)); const loadingPatch = isInitialLoad ? { isLoading: false } : {}; if (!selectionValid) { @@ -715,6 +743,24 @@ export const useEmailStore = create((set, get) => ({ await get().fetchScheduledEmails(client); return; } + if (targetMailboxId === ALL_MAIL_MAILBOX_ID) { + const jmapIds = resolveAllMailJmapIds(); + if (jmapIds.length === 0) { + set({ emails: [], hasMoreEmails: false, totalEmails: 0, isLoading: false }); + return; + } + const emailsPerPage = useSettingsStore.getState().emailsPerPage; + const result = await resolveActionClient(client).advancedSearchEmails( + buildAllMailFilter(jmapIds), undefined, emailsPerPage, 0, + ); + set({ + emails: annotateScheduledEmails(result.emails, get().scheduledSubmissionByEmailId), + hasMoreEmails: result.hasMore, + totalEmails: result.total, + isLoading: false, + }); + return; + } const effectiveClient = resolveActionClient(client); // Find the mailbox to get its accountId (for shared folder support) @@ -822,7 +868,21 @@ export const useEmailStore = create((set, get) => ({ const { searchFilters } = get(); const hasFilters = !isFilterEmpty(searchFilters); - if (searchQuery || hasFilters) { + if (selectedMailbox === ALL_MAIL_MAILBOX_ID) { + if (searchQuery || hasFilters) { + // Search within All Mail spans the whole account (no inMailbox). + result = hasFilters + ? await effectiveClient.advancedSearchEmails(buildJMAPFilter(searchQuery, searchFilters, undefined), undefined, emailsPerPage, position) + : await effectiveClient.searchEmails(searchQuery, undefined, undefined, emailsPerPage, position); + } else { + const jmapIds = resolveAllMailJmapIds(); + if (jmapIds.length === 0) { + set({ hasMoreEmails: false, isLoadingMore: false }); + return; + } + result = await effectiveClient.advancedSearchEmails(buildAllMailFilter(jmapIds), undefined, emailsPerPage, position); + } + } else if (searchQuery || hasFilters) { const mailboxes = resolveActionMailboxes(); const mailbox = mailboxes.find(mb => mb.id === selectedMailbox); const jmapMailboxId = mailbox?.originalId || selectedMailbox; @@ -1484,14 +1544,16 @@ export const useEmailStore = create((set, get) => ({ return; } - // Get the current mailbox to scope the search + // Get the current mailbox to scope the search. In the All Mail view the + // search spans every folder of the account (no inMailbox constraint). const selectedMailbox = get().selectedMailbox; + const isAllMail = selectedMailbox === ALL_MAIL_MAILBOX_ID; const mailboxes = resolveActionMailboxes(); const mailbox = mailboxes.find(mb => mb.id === selectedMailbox); // Use originalId for shared mailboxes - const jmapMailboxId = mailbox?.originalId || selectedMailbox; + const jmapMailboxId = isAllMail ? undefined : (mailbox?.originalId || selectedMailbox); // Only pass accountId for shared mailboxes, not for primary account - const accountId = mailbox?.isShared ? mailbox.accountId : undefined; + const accountId = isAllMail ? undefined : (mailbox?.isShared ? mailbox.accountId : undefined); const result = await resolveActionClient(client).searchEmails(query, jmapMailboxId, accountId, emailsPerPage, 0); const externals = await emailHooks.onProvideSearchResults.transform([] as ExternalSearchResult[], { query, filters: get().searchFilters }); @@ -1559,9 +1621,10 @@ export const useEmailStore = create((set, get) => ({ return; } + const isAllMail = selectedMailbox === ALL_MAIL_MAILBOX_ID; const mailbox = mailboxes.find(mb => mb.id === selectedMailbox); - const jmapMailboxId = mailbox?.originalId || selectedMailbox; - const accountId = mailbox?.isShared ? mailbox.accountId : undefined; + const jmapMailboxId = isAllMail ? undefined : (mailbox?.originalId || selectedMailbox); + const accountId = isAllMail ? undefined : (mailbox?.isShared ? mailbox.accountId : undefined); const filter = buildJMAPFilter(searchQuery, searchFilters, jmapMailboxId); const result = await resolveActionClient(client).advancedSearchEmails(filter, accountId, emailsPerPage, 0); diff --git a/stores/settings-store.ts b/stores/settings-store.ts index 3a0a44b2..38f44c63 100644 --- a/stores/settings-store.ts +++ b/stores/settings-store.ts @@ -215,6 +215,13 @@ interface SettingsState { enableUnifiedMailbox: boolean; includeGroupInUnified: boolean; + // All Mail view (gated): user toggle (like the unified mailbox) plus the set + // of folder ids merged into the virtual "All Mail" mailbox. `null` = never + // configured, in which case the view defaults to all non-special (no-role) + // folders of the active account. + enableAllMailView: boolean; + allMailFolderIds: string[] | null; + // Email Display disableThreading: boolean; // Show emails as individual messages instead of grouped by conversation @@ -398,6 +405,10 @@ const DEFAULT_SETTINGS = { enableUnifiedMailbox: false, includeGroupInUnified: false, + // All Mail view (gated) + enableAllMailView: false, + allMailFolderIds: null as string[] | null, + // Email Display disableThreading: false, @@ -570,6 +581,8 @@ export const useSettingsStore = create()( // (see DEVICE_LOCAL_SETTING_KEYS) and must not be synced. enableUnifiedMailbox: state.enableUnifiedMailbox, includeGroupInUnified: state.includeGroupInUnified, + enableAllMailView: state.enableAllMailView, + allMailFolderIds: state.allMailFolderIds, senderFavicons: state.senderFavicons, showAvatarsInJunk: state.showAvatarsInJunk, colorfulSidebarIcons: state.colorfulSidebarIcons,