feat: cross-account "All accounts" views + full group/shared-account support
Add cross-account aggregate mail views and make group/shared (delegated) accounts first-class in every aggregate view. (The unified mailbox, the "All Mail" view, and "include group inboxes" already exist on main; this branch adds the cross-account views and the shared-account correctness work.) New views (admin-gated + per-user toggle, nested under Unified Mailbox): - Cross-account "All accounts": All unread / All starred / All mail across every connected account, including shared/group folders. Each list labels the source folder of every message. Source reference on aggregated emails (the core of the shared-account work): - Replace the overloaded `accountId` with two explicit, always-set fields: `sourceClientAccountId` (the login the mail is reachable through) and `sourceAccountId` (the owning JMAP account). `accountId` stays display-only. - Resolution is branch-free everywhere: pick the client by sourceClientAccountId, pass sourceAccountId as the JMAP accountId (no-op for personal), read the owner's mailbox list cached by JMAP id. No capability scan. Shared/group-account correctness across all aggregate views: - Route open (click + auto-fetch), thread/conversation open + reply-refresh, mark read, star, move, delete (account-scoped trash), archive (owner-routed createMailbox / fetchAccountMailboxes), and spam + undo via the source ref. - Add accountId params to toggleStar / batchMarkAsRead / batchDeleteEmails / createMailbox where missing. - Fix local unread/total counter math for shared folders via emailInMailbox() (matches namespaced shared ids and bare own ids). - Keep the unified/cross virtual selection on background mailbox refresh (no jump back to inbox after deleting in All Drafts/Junk). Junk UX: - In "All Junk" the spam action becomes "not spam" in the viewer, context menu, and list hover icons; undo routes shared mail back to its own inbox. Admin: - Policy gates crossUnread/Starred/AllViewEnabled, each noting the matching per-user toggle (allMailViewEnabled clarified too). i18n / docs / tests: - locales (19): cross-view labels + descriptions and hover not_spam, translated in all shipped languages. - FEATURES.md + README.md document the new views and group-account support. - Tests for shared-account routing (single + batch + undoSpam), decoration, and unified-selection preservation.
This commit is contained in:
+8
-8
@@ -1273,19 +1273,19 @@ export class JMAPClient implements IJMAPClient {
|
||||
]);
|
||||
}
|
||||
|
||||
async batchMarkAsRead(emailIds: string[], read: boolean = true): Promise<void> {
|
||||
async batchMarkAsRead(emailIds: string[], read: boolean = true, accountId?: string): Promise<void> {
|
||||
if (emailIds.length === 0) return;
|
||||
|
||||
const updates = Object.fromEntries(emailIds.map(id => [id, { "keywords/$seen": read }]));
|
||||
await this.request([
|
||||
["Email/set", { accountId: this.accountId, update: updates }, "0"],
|
||||
["Email/set", { accountId: accountId || this.accountId, update: updates }, "0"],
|
||||
]);
|
||||
}
|
||||
|
||||
async toggleStar(emailId: string, starred: boolean): Promise<void> {
|
||||
async toggleStar(emailId: string, starred: boolean, accountId?: string): Promise<void> {
|
||||
await this.request([
|
||||
["Email/set", {
|
||||
accountId: this.accountId,
|
||||
accountId: accountId || this.accountId,
|
||||
update: {
|
||||
[emailId]: {
|
||||
"keywords/$flagged": starred,
|
||||
@@ -1391,12 +1391,12 @@ export class JMAPClient implements IJMAPClient {
|
||||
]);
|
||||
}
|
||||
|
||||
async batchDeleteEmails(emailIds: string[]): Promise<void> {
|
||||
async batchDeleteEmails(emailIds: string[], accountId?: string): Promise<void> {
|
||||
if (emailIds.length === 0) return;
|
||||
|
||||
await this.request([
|
||||
["Email/set", {
|
||||
accountId: this.accountId,
|
||||
accountId: accountId || this.accountId,
|
||||
destroy: emailIds,
|
||||
}, "0"],
|
||||
]);
|
||||
@@ -1709,7 +1709,7 @@ export class JMAPClient implements IJMAPClient {
|
||||
]);
|
||||
}
|
||||
|
||||
async createMailbox(name: string, parentId?: string): Promise<Mailbox> {
|
||||
async createMailbox(name: string, parentId?: string, accountId?: string): Promise<Mailbox> {
|
||||
const createId = `new-${Date.now()}`;
|
||||
const createData: Record<string, unknown> = { name };
|
||||
if (parentId) {
|
||||
@@ -1718,7 +1718,7 @@ export class JMAPClient implements IJMAPClient {
|
||||
|
||||
const response = await this.request([
|
||||
["Mailbox/set", {
|
||||
accountId: this.accountId,
|
||||
accountId: accountId || this.accountId,
|
||||
create: { [createId]: createData },
|
||||
}, "0"],
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user