"use client" import { useMemo, useState } from "react" import { Star, Info } from "lucide-react" import { useMessage } from "@/lib/api/hooks/use-mail-queries" import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip" import { cn } from "@/lib/utils" import { mailFlagIsStarred, messageIsSpam } from "@/lib/mail-flags" import { avatarColor, cleanSenderName, senderInitial, } from "@/lib/sender-display" import { MailDateText } from "@/components/gmail/mail-date-text" import type { ApiMessageFull, Recipient } from "@/lib/api/types" import { resolveMessageFrom } from "@/lib/mail-message-participants" import { buildMessageHeaderDetails, type MessageHeaderDetails, } from "@/lib/mail-message-header-details" import type { EmailAttachment } from "@/lib/email-data" import { ContactHoverCard } from "@/components/gmail/contact-hover-card" import { EmailViewMessageToolbar } from "@/components/gmail/email-view/email-view-toolbar" import { MessageBodyContent } from "@/components/gmail/email-view/message-body-content" import { MessageAttachmentsSection } from "@/components/gmail/email-view/message-attachments" import { MAIL_MESSAGE_HOVER_CLASS, MAIL_TOOLTIP_CONTENT_CLASS, } from "@/lib/mail-chrome-classes" import { repairMimeBodies } from "@/lib/mail-mime-body" export function formatApiMessageBody( full: { body_html?: string; body_text?: string } | null | undefined, snippet: string | undefined, loading: boolean ): string { const snippetHtml = snippet?.trim() ? `

${snippet.trim()}

` : "" if (loading) { return snippetHtml } const repaired = repairMimeBodies(full?.body_text, full?.body_html) const html = repaired.bodyHtml?.trim() if (html) return html const text = repaired.bodyText?.trim() if (text) { const escaped = text .replace(/&/g, "&") .replace(//g, ">") return `
${escaped}
` } if (full) { const s = snippet?.trim() if (s) { return `

${s}

` } return `

Ce message n’a pas de contenu.

` } const s = snippet?.trim() return s ? `

${s}

` : "" } /** Prior message in a thread: loads full body on expand via GET /mail/messages/:id. */ export function ThreadPriorMessage({ message, isExpanded, onToggle, onPrintConversation, onReply, onForward, selfEmails, selfDisplayName, collapseQuotedReplies = false, }: { message: ApiMessageFull isExpanded: boolean onToggle: () => void onPrintConversation?: () => void onReply?: () => void onForward?: () => void selfEmails: string[] selfDisplayName?: string collapseQuotedReplies?: boolean }) { const [detailsOpen, setDetailsOpen] = useState(false) const loadFull = isExpanded || detailsOpen const { data: fullMessage, isPending } = useMessage(loadFull ? message.id : null) const merged = fullMessage ?? message const resolved = useMemo( () => resolveMessageFrom(merged.from, { selfEmails, selfDisplayName }), [merged.from, selfEmails, selfDisplayName] ) const headerDetails = useMemo( () => buildMessageHeaderDetails(merged, { selfEmails, selfDisplayName, subject: message.subject, }), [merged, selfEmails, selfDisplayName, message.subject] ) const body = useMemo( () => formatApiMessageBody( fullMessage, message.snippet, isExpanded && isPending && !fullMessage ), [fullMessage, message.snippet, isExpanded, isPending] ) const isSpam = messageIsSpam(merged.flags, merged.labels) if (!isExpanded) { return ( ) } return ( ) } export function CollapsedMessage({ message, senderName: senderNameProp, senderEmail: senderEmailProp, onClick, }: { message: ApiMessageFull senderName?: string senderEmail?: string onClick: () => void }) { const senderName = senderNameProp ?? message.from[0]?.name ?? "" const senderAddr = senderEmailProp ?? message.from[0]?.address ?? "" const name = cleanSenderName(senderName || senderAddr) const color = avatarColor(name) return (
{ if (e.key === "Enter" || e.key === " ") { e.preventDefault() onClick() } }} className={cn("group flex w-full cursor-pointer items-center gap-3 px-4 py-3 text-left transition-colors", MAIL_MESSAGE_HOVER_CLASS)} >
{senderInitial(name)}
{name}

{message.snippet}

) } export function ExpandedMessage({ sender, senderEmail, headerDetails, dateIso, body, isSpam, isLast, starred, attachments = [], onToggleStar, onCollapse, onPrintConversation, onReply, onForward, detailsOpen, onDetailsOpenChange, collapseQuotedReplies = false, messageId, }: { sender: string senderEmail: string headerDetails: MessageHeaderDetails messageId: string dateIso: string body: string isSpam: boolean isLast: boolean starred: boolean attachments?: EmailAttachment[] onToggleStar?: () => void onCollapse?: () => void onPrintConversation?: () => void onReply?: () => void onForward?: () => void detailsOpen?: boolean onDetailsOpenChange?: (open: boolean) => void collapseQuotedReplies?: boolean }) { return (
0 ? "pb-0" : "pb-4" )} data-selectable-text >
{attachments.length > 0 && ( )}
) } export function SpamWhyBanner({ onNotSpam }: { onNotSpam?: () => void }) { return (

Pourquoi ce message est-il dans le spam ?{" "} Ce message est semblable à des messages identifiés comme spam par le passé.

{onNotSpam && ( )}
Les filtres peuvent se tromper. Si le message est légitime, signalez-le comme non-spam pour l'améliorer.
) }