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:
@@ -4,7 +4,7 @@ import { Email } from "@/lib/jmap/types";
|
||||
import { useSettingsStore } from "@/stores/settings-store";
|
||||
import type { HoverAction } from "@/stores/settings-store";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Trash2, Star, Mail, MailOpen, Archive, Tag, ShieldAlert } from "lucide-react";
|
||||
import { Trash2, Star, Mail, MailOpen, Archive, Tag, ShieldAlert, ShieldCheck } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useIsMobile } from "@/hooks/use-media-query";
|
||||
|
||||
@@ -17,6 +17,10 @@ interface EmailHoverActionsProps {
|
||||
onArchive?: () => void;
|
||||
onSetColorTag?: (color: string | null) => void;
|
||||
onMarkAsSpam?: () => void;
|
||||
// When the email lives in a junk folder (incl. the aggregate "All Junk" view)
|
||||
// the spam quick-action flips to "not spam".
|
||||
isInJunk?: boolean;
|
||||
onUndoSpam?: () => void;
|
||||
}
|
||||
|
||||
const ACTION_CONFIG: Record<HoverAction, {
|
||||
@@ -72,6 +76,8 @@ export function EmailHoverActions({
|
||||
onArchive,
|
||||
onSetColorTag,
|
||||
onMarkAsSpam,
|
||||
isInJunk = false,
|
||||
onUndoSpam,
|
||||
}: EmailHoverActionsProps) {
|
||||
const hoverActions = useSettingsStore((state) => state.hoverActions);
|
||||
const hoverActionsMode = useSettingsStore((state) => state.hoverActionsMode);
|
||||
@@ -106,7 +112,8 @@ export function EmailHoverActions({
|
||||
onSetColorTag?.(null);
|
||||
break;
|
||||
case "spam":
|
||||
onMarkAsSpam?.();
|
||||
if (isInJunk) onUndoSpam?.();
|
||||
else onMarkAsSpam?.();
|
||||
break;
|
||||
}
|
||||
};
|
||||
@@ -116,20 +123,28 @@ export function EmailHoverActions({
|
||||
if (!config) return null;
|
||||
const Icon = config.icon;
|
||||
|
||||
// In a junk context the spam action becomes "not spam".
|
||||
const isNotSpam = actionId === "spam" && isInJunk;
|
||||
const DisplayIcon = actionId === "markRead"
|
||||
? (isUnread ? MailOpen : Mail)
|
||||
: actionId === "star" && isStarred
|
||||
? Star
|
||||
: Icon;
|
||||
: isNotSpam
|
||||
? ShieldCheck
|
||||
: Icon;
|
||||
const title = isNotSpam ? t("not_spam") : t(config.titleKey);
|
||||
const className = isNotSpam
|
||||
? "hover:text-green-600 dark:hover:text-green-400"
|
||||
: config.className;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={actionId}
|
||||
onClick={(e) => handleAction(e, actionId)}
|
||||
title={t(config.titleKey)}
|
||||
title={title}
|
||||
className={cn(
|
||||
"p-1.5 rounded-md transition-colors duration-100 text-muted-foreground hover:bg-black/5 dark:hover:bg-white/10",
|
||||
config.className,
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<DisplayIcon
|
||||
|
||||
Reference in New Issue
Block a user