"use client" import { useCallback, useEffect, useMemo, useRef, useState } from "react" import { Star, Reply, ReplyAll, Forward, MoreVertical, Printer, ExternalLink, ChevronDown, Info, TriangleAlert, Trash2, Mail, Ban, ShieldAlert, Fish, Flag, SlidersHorizontal, Languages, Download, Code2, MessageCircleWarning, HardDrive, File, FileText, Image as ImageIcon, } from "lucide-react" import { Button } from "@/components/ui/button" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip" import { cn } from "@/lib/utils" import { avatarColor, cleanSenderName, senderInitial, } from "@/lib/sender-display" import { MailDateText } from "@/components/gmail/mail-date-text" import type { Email, ConversationMessage, EmailAttachment, EmailAttachmentKind, } from "@/lib/email-data" import type { FolderTreeNode, LabelRowItem } from "@/lib/sidebar-nav-data" import { attachmentPreviewTooltip, resolveAttachmentKind, shouldUseAttachmentPillsInPreview, } from "@/lib/attachment-display" import { useComposeActions, useComposeDrafts, useComposeWindows, DEFAULT_IDENTITIES, type ThreadComposeKind, type ComposeOpenPreset, savedThreadDraftToComposePreset, } from "@/lib/compose-context" import { buildThreadComposePreset, withTouchFullscreenComposePreset, } from "@/lib/thread-compose-preset" import { openConversationPrint } from "@/lib/print-conversation" import { resolveParsedCalendarInvitation } from "@/lib/resolve-email-calendar-invitation" import { ComposeWindow } from "@/components/gmail/compose-modal" import { CalendarInvitationPreview } from "@/components/gmail/calendar-invitation-preview" import { ContactHoverCard } from "./contact-hover-card" import { MailLabelPillStrip } from "./mail-label-pills" interface EmailViewProps { email: Email onToggleStar: (id: string) => void isStarred: boolean onNavigateToLabel?: (label: string) => void /** Message spam : bannière + pastille sujet ; bouton « non-spam » */ onNotSpam?: () => void /** Si défini, les pastilles libellé dont la fonction retourne false sont masquées (préférences barre latérale). */ showLabelChip?: (label: string) => boolean labelBgByText?: Map emailLabelToSidebarFolderId?: Record getNavItemPrefs?: (id: string) => { messages: string } folderTree?: FolderTreeNode[] labelRows?: readonly LabelRowItem[] /** Id dossier / libellé courant — masque la pastille du dossier actif (comme en liste). */ currentFolderId?: string } const LABEL_DISPLAY_NAMES: Record = { inbox: "Boîte de réception", starred: "Suivis", snoozed: "En attente", important: "Important", sent: "Messages envoyés", drafts: "Brouillons", spam: "Spam", trash: "Corbeille", } const MESSAGE_MORE_MENU_CLASS = "min-w-[280px] rounded-lg border border-[#dadce0] bg-white p-0 py-1 text-[#3c4043] shadow-lg [&_[data-slot=dropdown-menu-item]]:gap-3 [&_[data-slot=dropdown-menu-item]]:rounded-none [&_[data-slot=dropdown-menu-item]]:px-3 [&_[data-slot=dropdown-menu-item]]:py-2 [&_[data-slot=dropdown-menu-item]]:text-sm [&_[data-slot=dropdown-menu-item]:focus]:bg-[#f1f3f4] [&_[data-slot=dropdown-menu-separator]]:mx-0 [&_[data-slot=dropdown-menu-separator]]:my-1 [&_[data-slot=dropdown-menu-separator]]:bg-[#eceff1]" /** Scroll zone du corps du message (preview remplit le panneau parent). */ const EMAIL_PREVIEW_SCROLL_CLASS = "min-h-0 flex-1 overflow-y-auto overflow-x-hidden overscroll-y-contain outline-none " + "[scrollbar-color:#9aa0a6_#ffffff] [scrollbar-width:auto] " + "[&::-webkit-scrollbar]:w-2.5 [&::-webkit-scrollbar]:border-0 [&::-webkit-scrollbar]:bg-white " + "[&::-webkit-scrollbar-track]:border-0 [&::-webkit-scrollbar-track]:bg-white [&::-webkit-scrollbar-track]:shadow-none " + "[&::-webkit-scrollbar-thumb]:rounded-none [&::-webkit-scrollbar-thumb]:border-0 [&::-webkit-scrollbar-thumb]:shadow-none " + "[&::-webkit-scrollbar-thumb]:bg-[#9aa0a6] hover:[&::-webkit-scrollbar-thumb]:bg-[#5f6368] " + "[&::-webkit-scrollbar-corner]:border-0 [&::-webkit-scrollbar-corner]:bg-white" const REPLY_BAR_SURFACE_CLASS = "bg-[linear-gradient(to_bottom,rgba(255,255,255,0)_0%,#ffffff_0.75rem,#ffffff_100%)] pt-3" /* ── Sandboxed iframe for HTML body ── */ function SandboxedContent({ html, isSpam, }: { html: string isSpam: boolean }) { const iframeRef = useRef(null) const [height, setHeight] = useState(120) const sandboxValue = isSpam ? "allow-same-origin" : "allow-same-origin allow-popups" const injectContent = useCallback(() => { const iframe = iframeRef.current if (!iframe) return const doc = iframe.contentDocument if (!doc) return const cspMeta = isSpam ? `` : `` doc.open() doc.write(` ${cspMeta} ${html} `) doc.close() const resizeObserver = new ResizeObserver(() => { const body = iframe.contentDocument?.body if (body) { setHeight(Math.max(60, body.scrollHeight + 2)) } }) if (doc.body) { resizeObserver.observe(doc.body) setHeight(Math.max(60, doc.body.scrollHeight + 2)) } return () => resizeObserver.disconnect() }, [html, isSpam]) useEffect(() => { const cleanup = injectContent() return () => cleanup?.() }, [injectContent]) return (