- 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.
159 lines
5.0 KiB
TypeScript
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>
|
|
)
|
|
}
|