"use client" import { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties, } 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" import { MAIL_ICON_BTN, MAIL_INVITATION_CARD_CLASS, MAIL_MENU_SURFACE_WIDE_CLASS, MAIL_MESSAGE_HOVER_CLASS, MAIL_PREVIEW_SCROLL_CLASS, MAIL_REPLY_BAR_CLASS, MAIL_REPLY_BUTTON_CLASS, MAIL_TOOLTIP_CONTENT_CLASS, } from "@/lib/mail-chrome-classes" import { useTheme } from "next-themes" import { emailPreviewBaseCss, emailPreviewDarkOverrideCss, emailPreviewLightOverrideCss, emailPreviewSubjectCss, preprocessEmailHtmlForTheme, } from "@/lib/email-preview-dark-styles" 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 /** Fil complet (mode message isolé hors conversation). */ threadRoot?: Email | null /** Affiche uniquement le message courant avec option d’ouvrir le fil. */ isSingleMessageView?: boolean } 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 = MAIL_MENU_SURFACE_WIDE_CLASS const EMAIL_PREVIEW_IFRAME_STYLE: React.CSSProperties = { display: "block", background: "transparent", } function documentIsDark(): boolean { return document.documentElement.classList.contains("dark") } /* ── 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 { resolvedTheme } = useTheme() const injectContent = useCallback(() => { const iframe = iframeRef.current if (!iframe) return const doc = iframe.contentDocument if (!doc) return const cspMeta = isSpam ? `` : `` const isDark = documentIsDark() const processedHtml = preprocessEmailHtmlForTheme(html, isDark) const themeOverrides = isDark ? emailPreviewDarkOverrideCss() : emailPreviewLightOverrideCss() doc.open() doc.write(` ${cspMeta} ${processedHtml} `) 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, resolvedTheme]) useEffect(() => { const cleanup = injectContent() return () => cleanup?.() }, [injectContent]) return (