"use client"; import { useTranslations } from "next-intl"; import { Mailbox } from "@/lib/jmap/types"; import { getMailboxPath } from "@/lib/utils"; import { ContextMenu, ContextMenuItem, ContextMenuSeparator, ContextMenuHeader, } from "@/components/ui/context-menu"; import { CheckCheck, ChevronRight, MailOpen, Mails, MoreHorizontal, Trash2, FolderPlus, Pencil, FolderX, RefreshCw, Upload, Users, } from "lucide-react"; interface Position { x: number; y: number; } export type MailboxContextTarget = | { kind: "mailbox"; mailbox: Mailbox; hasChildren: boolean } | { kind: "folders-section"; accountId?: string }; const PATH_SEPARATOR = " › "; const MAX_PATH_LENGTH = 40; const MAX_SEGMENT_LENGTH = 16; function truncateSegment(name: string): string { return name.length > MAX_SEGMENT_LENGTH ? `${name.slice(0, MAX_SEGMENT_LENGTH - 1)}…` : name; } function renderPathSegments(segments: string[]): React.ReactNode { return ( {segments.map((seg, i) => ( {i > 0 && } {seg === "…" ? : {seg}} ))} ); } function renderShortenedPath(fullPath: string): React.ReactNode { const segments = fullPath.split(PATH_SEPARATOR); if (fullPath.length <= MAX_PATH_LENGTH) { return renderPathSegments(segments); } if (segments.length <= 2) { return renderPathSegments(segments.map(truncateSegment)); } return renderPathSegments([ truncateSegment(segments[0]), "…", truncateSegment(segments[segments.length - 1]), ]); } interface MailboxContextMenuProps { target: MailboxContextTarget | null; position: Position; isOpen: boolean; onClose: () => void; menuRef: React.RefObject; mailboxes: Mailbox[]; onMarkFolderRead?: (mailboxId: string) => void; onMarkFolderTreeRead?: (mailboxId: string) => void; onMarkAllFoldersRead?: () => void; onEmptyFolder?: (mailboxId: string) => void; onCreateSubfolder?: (parentId: string) => void; onCreateFolder?: (accountId?: string) => void; onRenameFolder?: (mailboxId: string) => void; onDeleteFolder?: (mailboxId: string) => void; /** Share the folder with other principals (mail:share); omitted when the server lacks it. */ onShareFolder?: (mailboxId: string) => void; onImportEmail?: (mailboxId: string) => void; onRefresh?: () => void; } export function MailboxContextMenu({ target, position, isOpen, onClose, menuRef, mailboxes, onMarkFolderRead, onMarkFolderTreeRead, onMarkAllFoldersRead, onEmptyFolder, onCreateSubfolder, onCreateFolder, onRenameFolder, onDeleteFolder, onShareFolder, onImportEmail, onRefresh, }: MailboxContextMenuProps) { const t = useTranslations("mailbox_context_menu"); const handleAction = (action: () => void) => { action(); onClose(); }; if (!target) return null; if (target.kind === "folders-section") { return ( {!target.accountId && ( <> handleAction(onMarkAllFoldersRead!)} disabled={!onMarkAllFoldersRead} /> )} handleAction(() => onCreateFolder?.(target.accountId))} disabled={!onCreateFolder} testId="mailbox-new-folder" /> handleAction(onRefresh!)} disabled={!onRefresh} /> ); } const mailbox = target.mailbox; const isTrashOrJunk = mailbox.role === "trash" || mailbox.role === "junk"; const isSystem = !!mailbox.role && ["inbox", "sent", "drafts", "trash", "junk", "archive"].includes(mailbox.role); const canRename = mailbox.myRights?.mayRename !== false && !isSystem; const canDelete = mailbox.myRights?.mayDelete !== false && !isSystem; const canCreateChild = mailbox.myRights?.mayCreateChild !== false; const canSetSeen = mailbox.myRights?.maySetSeen !== false; const canRemoveItems = mailbox.myRights?.mayRemoveItems !== false; const canAddItems = mailbox.myRights?.mayAddItems !== false; // Sharing needs the mail:share extension (handler present) and, on a folder // someone else shared with us, their permission to re-share it. const canShare = !!onShareFolder && (!mailbox.isShared || mailbox.myRights?.mayShare === true); const fullPath = getMailboxPath(mailbox, mailboxes); return ( {renderShortenedPath(fullPath)} handleAction(() => onMarkFolderRead?.(mailbox.id))} disabled={!onMarkFolderRead || !canSetSeen} /> {target.hasChildren && ( handleAction(() => onMarkFolderTreeRead?.(mailbox.id))} disabled={!onMarkFolderTreeRead || !canSetSeen} /> )} handleAction(() => onCreateSubfolder?.(mailbox.id))} disabled={!onCreateSubfolder || !canCreateChild} testId="mailbox-new-subfolder" /> handleAction(() => onRenameFolder?.(mailbox.id))} disabled={!onRenameFolder || !canRename} testId="mailbox-rename" /> {onShareFolder && ( handleAction(() => onShareFolder(mailbox.id))} disabled={!canShare} testId="mailbox-share" /> )} handleAction(() => onImportEmail?.(mailbox.id))} disabled={!onImportEmail || !canAddItems} /> handleAction(() => onEmptyFolder?.(mailbox.id))} disabled={!onEmptyFolder || mailbox.totalEmails === 0 || !canRemoveItems} destructive /> handleAction(() => onDeleteFolder?.(mailbox.id))} disabled={!onDeleteFolder || !canDelete} destructive testId="mailbox-delete" /> handleAction(onRefresh!)} disabled={!onRefresh} /> ); }