ultisuite-client/components/gmail/email-view/message-body-content.tsx
R3D347HR4Y 8a02c10ba3 Add environment configuration and update email view components
- Created a .cursorignore file to manage local environment files.
- Updated .env.example to reflect changes in the public app URL.
- Modified the gmail workspace configuration to include the drive-suite path.
- Enhanced email view components to support attachment handling and fallback for plain text bodies.
- Improved user experience by updating attachment display logic and integrating inline attachment support.
2026-06-04 00:12:43 +02:00

159 lines
5.0 KiB
TypeScript

"use client"
import { useEffect, useMemo, useState } from "react"
import { cn } from "@/lib/utils"
import { splitQuotedHtml } from "@/lib/mail-quoted-content"
import { htmlHasRemoteContent } from "@/lib/mail-remote-content"
import { findContactByEmail } from "@/lib/contacts/find-contact"
import { useContactsList } from "@/lib/contacts/use-contacts-list"
import { useSelfMailEmails } from "@/lib/hooks/use-self-mail-emails"
import { normalizeMailAddress } from "@/lib/mail-message-participants"
import {
isMessageRemoteContentAllowed,
isTrustedSenderEmail,
useTrustedSendersStore,
} from "@/lib/stores/trusted-senders-store"
import { useMessageAttachmentCidMap } from "@/lib/api/hooks/use-message-attachment-cid-map"
import { useReindexMessageAttachments } from "@/lib/api/hooks/use-reindex-message-attachments"
import { useInlineCidUrls } from "@/lib/hooks/use-inline-cid-urls"
import { SandboxedContent } from "@/components/gmail/email-view/sandboxed-content"
import { RemoteContentBanner } from "@/components/gmail/email-view/remote-content-banner"
export function MessageBodyContent({
html,
isSpam,
senderEmail,
messageId,
collapseQuotedReplies = false,
plainTextFallback,
}: {
html: string
isSpam: boolean
senderEmail: string
messageId: string
/** Hide included prior messages when the thread already lists them. */
collapseQuotedReplies?: boolean
plainTextFallback?: string
}) {
const [showQuoted, setShowQuoted] = useState(false)
const selfEmails = useSelfMailEmails()
const { contacts } = useContactsList()
const trustedSenderEmails = useTrustedSendersStore((s) => s.trustedSenderEmails)
const allowedMessageIds = useTrustedSendersStore((s) => s.allowedMessageIds)
const trustSender = useTrustedSendersStore((s) => s.trustSender)
const allowMessageRemoteContent = useTrustedSendersStore(
(s) => s.allowMessageRemoteContent
)
const isFromSelf = useMemo(() => {
const norm = normalizeMailAddress(senderEmail)
if (!norm) return false
return selfEmails.some((s) => normalizeMailAddress(s) === norm)
}, [senderEmail, selfEmails])
const isContact = Boolean(findContactByEmail(contacts, senderEmail))
const isTrusted = isTrustedSenderEmail(trustedSenderEmails, senderEmail)
const isMessageAllowed = isMessageRemoteContentAllowed(
allowedMessageIds,
messageId
)
const { mainHtml, quotedHtml } = useMemo(() => {
if (!collapseQuotedReplies) {
return { mainHtml: html, quotedHtml: null as string | null }
}
return splitQuotedHtml(html)
}, [html, collapseQuotedReplies])
const { data: cidMap } = useMessageAttachmentCidMap(messageId)
const {
mutate: reindexInlineAttachments,
isPending: reindexPending,
isSuccess: reindexDone,
isError: reindexFailed,
} = useReindexMessageAttachments(messageId)
const cidUrlMap = useInlineCidUrls(cidMap)
useEffect(() => {
if (!/cid:/i.test(mainHtml)) return
if (Object.keys(cidMap ?? {}).length > 0) return
if (reindexPending || reindexDone || reindexFailed) return
reindexInlineAttachments()
}, [
mainHtml,
cidMap,
reindexPending,
reindexDone,
reindexFailed,
reindexInlineAttachments,
])
const hasRemoteContent = useMemo(
() =>
htmlHasRemoteContent(mainHtml) ||
(quotedHtml ? htmlHasRemoteContent(quotedHtml) : false),
[mainHtml, quotedHtml]
)
const remoteContentAllowed =
isFromSelf || isContact || isTrusted || isMessageAllowed
const blockRemoteContent = isFromSelf
? false
: isSpam || (hasRemoteContent && !remoteContentAllowed)
const showRemoteBanner =
!isFromSelf && !isSpam && hasRemoteContent && !remoteContentAllowed
const hasHiddenQuote = Boolean(quotedHtml) && !showQuoted
const sandboxProps = {
blockRemoteContent,
restrictPopups: isSpam,
senderEmail,
cidUrlMap,
plainTextFallback,
messageId,
}
return (
<div className="min-w-0">
{showRemoteBanner ? (
<RemoteContentBanner
onShowOnce={() => allowMessageRemoteContent(messageId)}
onAlwaysShow={() => {
trustSender(senderEmail)
allowMessageRemoteContent(messageId)
}}
/>
) : null}
<SandboxedContent html={mainHtml} previewPart="body" {...sandboxProps} />
{hasHiddenQuote ? (
<div className="mt-2">
<button
type="button"
onClick={() => setShowQuoted(true)}
className={cn(
"inline-flex h-6 min-w-[2rem] items-center justify-center rounded-full",
"border border-border bg-muted/80 px-2 text-sm font-medium leading-none text-muted-foreground",
"hover:bg-accent hover:text-foreground"
)}
aria-label="Afficher les messages cités inclus"
>
</button>
</div>
) : null}
{quotedHtml && showQuoted ? (
<div className="mt-2 border-t border-border/60 pt-2">
<SandboxedContent
html={quotedHtml}
previewPart="quoted"
{...sandboxProps}
/>
</div>
) : null}
</div>
)
}