mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 08:13:50 +00:00
96 lines
3.5 KiB
TypeScript
96 lines
3.5 KiB
TypeScript
import type { Email, Mailbox } from "./jmap/types";
|
|
import { buildMailboxTree, type MailboxNode } from "./utils";
|
|
|
|
/** Header node that wraps the user's own folders when another account leads. */
|
|
export const OWN_ACCOUNT_MOVE_NODE_PREFIX = "own-account-";
|
|
|
|
/**
|
|
* JMAP accountId of the account a message lives in, for scoping its Move menu.
|
|
*
|
|
* Stamped messages (unified and search views) name their account. Otherwise
|
|
* the folders the message is in decide: a shared account's mailboxIds are
|
|
* namespaced exactly like its folders in the merged list. The open folder is
|
|
* the last resort, for a message whose folders are not loaded.
|
|
*/
|
|
export function resolveMoveOwnerAccountId(
|
|
email: Pick<Email, "sourceAccountId" | "mailboxIds"> | null | undefined,
|
|
mailboxes: Mailbox[],
|
|
selectedMailboxId: string | null | undefined,
|
|
): string | undefined {
|
|
if (email?.sourceAccountId) return email.sourceAccountId;
|
|
const inIds = email?.mailboxIds ?? {};
|
|
const container = mailboxes.find((m) => inIds[m.id]);
|
|
if (container?.accountId) return container.accountId;
|
|
return mailboxes.find((m) => m.id === selectedMailboxId)?.accountId;
|
|
}
|
|
|
|
/**
|
|
* The folders a Move menu offers, as a tree, plus the ids that can be picked.
|
|
*
|
|
* The message's own account leads. For a message in a shared/group account the
|
|
* menus used to list the user's own folders first and the shared account's
|
|
* last, under a header below the menu's scroll area, so picking a familiar
|
|
* name like "Spam" moved the message into the personal account and out of the
|
|
* shared one (#1149). The shared account's folders now come first and the
|
|
* user's own folders follow under their account name, so a deliberate
|
|
* cross-account move is still one pick away but no longer the obvious one.
|
|
*/
|
|
export function buildMoveTargets(
|
|
mailboxes: Mailbox[],
|
|
opts: { currentMailboxId?: string | null; ownerAccountId?: string },
|
|
): { tree: MailboxNode[]; targetIds: Set<string> } {
|
|
const targetIds = new Set(
|
|
mailboxes
|
|
.filter(
|
|
(m) =>
|
|
m.id !== opts.currentMailboxId &&
|
|
m.role !== "drafts" &&
|
|
!m.id.startsWith("shared-") &&
|
|
m.myRights?.mayAddItems
|
|
)
|
|
.map((m) => m.id)
|
|
);
|
|
|
|
const filterTree = (nodes: MailboxNode[]): MailboxNode[] =>
|
|
nodes.reduce<MailboxNode[]>((acc, node) => {
|
|
const children = filterTree(node.children);
|
|
if (targetIds.has(node.id) || children.length > 0) {
|
|
acc.push({ ...node, children });
|
|
}
|
|
return acc;
|
|
}, []);
|
|
|
|
const tree = filterTree(buildMailboxTree(mailboxes));
|
|
|
|
const ownerNode = opts.ownerAccountId
|
|
? tree.find((n) => n.isShared && n.id === `shared-account-${opts.ownerAccountId}`)
|
|
: undefined;
|
|
if (!ownerNode) return { tree, targetIds };
|
|
|
|
const own = tree.filter((n) => !n.isShared);
|
|
const otherShared = tree.filter((n) => n.isShared && n !== ownerNode);
|
|
const ordered: MailboxNode[] = [ownerNode];
|
|
if (own.length > 0) {
|
|
const ownAccountId = own[0].accountId ?? "";
|
|
const ownName = mailboxes.find((m) => !m.isShared && m.accountName)?.accountName ?? ownAccountId;
|
|
ordered.push({
|
|
id: `${OWN_ACCOUNT_MOVE_NODE_PREFIX}${ownAccountId}`,
|
|
name: ownName,
|
|
sortOrder: 0,
|
|
totalEmails: 0,
|
|
unreadEmails: 0,
|
|
totalThreads: 0,
|
|
unreadThreads: 0,
|
|
myRights: { ...ownerNode.myRights },
|
|
isSubscribed: true,
|
|
accountId: ownAccountId,
|
|
accountName: ownName,
|
|
isShared: false,
|
|
children: own,
|
|
depth: 0,
|
|
});
|
|
}
|
|
ordered.push(...otherShared);
|
|
return { tree: ordered, targetIds };
|
|
}
|