feat: add "All Mail" view
This commit is contained in:
+70
-7
@@ -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<string, unknown> {
|
||||
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<EmailStore>((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<EmailStore>((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<EmailStore>((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<EmailStore>((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<EmailStore>((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);
|
||||
|
||||
@@ -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<SettingsState>()(
|
||||
// (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,
|
||||
|
||||
Reference in New Issue
Block a user