feat: include group inboxes in unified mailbox view #328
This commit is contained in:
+40
-6
@@ -251,8 +251,16 @@ function resolveActionMailboxes(): Mailbox[] {
|
||||
* fresh mailbox list so the helpers can resolve the role mailbox per account.
|
||||
* Accounts whose mailbox fetch fails are skipped - the unified result will
|
||||
* surface that in its per-account error map.
|
||||
*
|
||||
* When `includeGroup` is true, also emits one synthetic entry per shared
|
||||
* owner account reachable through each logged-in client. The shared entries
|
||||
* are flagged with `isShared: true` so `lib/unified-mailbox.ts` routes JMAP
|
||||
* requests via `originalId` + owner accountId.
|
||||
*/
|
||||
async function buildUnifiedAccountClients(): Promise<UnifiedAccountClient[]> {
|
||||
export async function buildUnifiedAccountClients(
|
||||
opts: { includeGroup?: boolean } = {},
|
||||
): Promise<UnifiedAccountClient[]> {
|
||||
const { includeGroup = false } = opts;
|
||||
const authAccounts = useAccountStore.getState().accounts.filter((a) => a.isConnected);
|
||||
const allClients = useAuthStore.getState().getAllConnectedClients();
|
||||
const built: UnifiedAccountClient[] = [];
|
||||
@@ -260,8 +268,31 @@ async function buildUnifiedAccountClients(): Promise<UnifiedAccountClient[]> {
|
||||
const c = allClients.get(a.id);
|
||||
if (!c) continue;
|
||||
try {
|
||||
const mailboxes = await c.getMailboxes();
|
||||
built.push({ accountId: a.id, accountLabel: a.label || a.email, client: c, mailboxes });
|
||||
const mailboxes = includeGroup ? await c.getAllMailboxes() : await c.getMailboxes();
|
||||
const ownMailboxes = includeGroup
|
||||
? mailboxes.filter((m) => !m.isShared)
|
||||
: mailboxes;
|
||||
built.push({ accountId: a.id, accountLabel: a.label || a.email, client: c, mailboxes: ownMailboxes, isShared: false });
|
||||
|
||||
if (includeGroup) {
|
||||
const sharedByOwner = new Map<string, Mailbox[]>();
|
||||
for (const m of mailboxes) {
|
||||
if (!m.isShared || !m.accountId || m.accountId === a.id) continue;
|
||||
const list = sharedByOwner.get(m.accountId) ?? [];
|
||||
list.push(m);
|
||||
sharedByOwner.set(m.accountId, list);
|
||||
}
|
||||
for (const [ownerId, ownerMailboxes] of sharedByOwner) {
|
||||
const label = ownerMailboxes.find((m) => m.accountName)?.accountName || ownerId;
|
||||
built.push({
|
||||
accountId: ownerId,
|
||||
accountLabel: label,
|
||||
client: c,
|
||||
mailboxes: ownerMailboxes,
|
||||
isShared: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
/* skip account on mailbox fetch failure */
|
||||
}
|
||||
@@ -583,8 +614,9 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
|
||||
set({ isLoadingMore: true, error: null });
|
||||
try {
|
||||
const emailsPerPage = useSettingsStore.getState().emailsPerPage;
|
||||
const includeGroup = useSettingsStore.getState().includeGroupInUnified;
|
||||
const position = emails.length;
|
||||
const built = await buildUnifiedAccountClients();
|
||||
const built = await buildUnifiedAccountClients({ includeGroup });
|
||||
const { searchFilters } = get();
|
||||
const hasFilters = !isFilterEmpty(searchFilters);
|
||||
const result = hasFilters
|
||||
@@ -1261,7 +1293,8 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
|
||||
const emailsPerPage = useSettingsStore.getState().emailsPerPage;
|
||||
|
||||
if (isUnifiedView && unifiedRole) {
|
||||
const built = await buildUnifiedAccountClients();
|
||||
const includeGroup = useSettingsStore.getState().includeGroupInUnified;
|
||||
const built = await buildUnifiedAccountClients({ includeGroup });
|
||||
const result = await searchUnifiedEmails(built, unifiedRole, query, emailsPerPage, 0);
|
||||
const externals = await emailHooks.onProvideSearchResults.transform([] as ExternalSearchResult[], { query, filters: get().searchFilters });
|
||||
set({
|
||||
@@ -1327,7 +1360,8 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
|
||||
const emailsPerPage = useSettingsStore.getState().emailsPerPage;
|
||||
|
||||
if (isUnifiedView && unifiedRole) {
|
||||
const built = await buildUnifiedAccountClients();
|
||||
const includeGroup = useSettingsStore.getState().includeGroupInUnified;
|
||||
const built = await buildUnifiedAccountClients({ includeGroup });
|
||||
const result = await advancedSearchUnifiedEmails(
|
||||
built,
|
||||
unifiedRole,
|
||||
|
||||
@@ -202,6 +202,7 @@ interface SettingsState {
|
||||
|
||||
// Unified Mailbox
|
||||
enableUnifiedMailbox: boolean;
|
||||
includeGroupInUnified: boolean;
|
||||
|
||||
// Email Display
|
||||
disableThreading: boolean; // Show emails as individual messages instead of grouped by conversation
|
||||
@@ -375,6 +376,7 @@ const DEFAULT_SETTINGS = {
|
||||
|
||||
// Unified Mailbox
|
||||
enableUnifiedMailbox: false,
|
||||
includeGroupInUnified: false,
|
||||
|
||||
// Email Display
|
||||
disableThreading: false,
|
||||
@@ -543,6 +545,7 @@ export const useSettingsStore = create<SettingsState>()(
|
||||
// proInterface is intentionally omitted - it's a per-device choice
|
||||
// (see DEVICE_LOCAL_SETTING_KEYS) and must not be synced.
|
||||
enableUnifiedMailbox: state.enableUnifiedMailbox,
|
||||
includeGroupInUnified: state.includeGroupInUnified,
|
||||
senderFavicons: state.senderFavicons,
|
||||
showAvatarsInJunk: state.showAvatarsInJunk,
|
||||
colorfulSidebarIcons: state.colorfulSidebarIcons,
|
||||
|
||||
Reference in New Issue
Block a user