159 lines
4.1 KiB
TypeScript
159 lines
4.1 KiB
TypeScript
"use client"
|
|
|
|
import {
|
|
useCallback,
|
|
useEffect,
|
|
useMemo,
|
|
useRef,
|
|
useState,
|
|
type CSSProperties,
|
|
} from "react"
|
|
import { useTheme } from "next-themes"
|
|
import {
|
|
emailPreviewBaseCss,
|
|
emailPreviewDarkOverrideCss,
|
|
emailPreviewLightOverrideCss,
|
|
emailPreviewWrapperCss,
|
|
} from "@/lib/email-preview-dark-styles"
|
|
import {
|
|
prepareEmailHtmlForIframe,
|
|
injectEmailHtmlIntoDocument,
|
|
} from "@/lib/mail-html-iframe"
|
|
import { buildEmailPreviewCsp } from "@/lib/mail-remote-content"
|
|
|
|
const EMAIL_PREVIEW_IFRAME_STYLE: CSSProperties = {
|
|
display: "block",
|
|
background: "transparent",
|
|
}
|
|
|
|
function documentIsDark(): boolean {
|
|
return document.documentElement.classList.contains("dark")
|
|
}
|
|
|
|
function measureIframeContentHeight(doc: Document): number {
|
|
const body = doc.body
|
|
const root = doc.documentElement
|
|
if (!body) return 60
|
|
const heights = [
|
|
body.scrollHeight,
|
|
body.offsetHeight,
|
|
root?.scrollHeight ?? 0,
|
|
root?.clientHeight ?? 0,
|
|
]
|
|
return Math.max(60, ...heights) + 2
|
|
}
|
|
|
|
export function SandboxedContent({
|
|
html,
|
|
blockRemoteContent,
|
|
restrictPopups = false,
|
|
senderEmail,
|
|
cidUrlMap,
|
|
}: {
|
|
html: string
|
|
blockRemoteContent: boolean
|
|
restrictPopups?: boolean
|
|
senderEmail?: string
|
|
cidUrlMap?: Record<string, string>
|
|
}) {
|
|
const iframeRef = useRef<HTMLIFrameElement>(null)
|
|
const [height, setHeight] = useState(120)
|
|
|
|
const sandboxValue = restrictPopups
|
|
? "allow-same-origin"
|
|
: "allow-same-origin allow-popups"
|
|
|
|
const { resolvedTheme } = useTheme()
|
|
const isDark =
|
|
resolvedTheme === "dark" ||
|
|
((resolvedTheme === "system" || resolvedTheme === undefined) &&
|
|
typeof document !== "undefined" &&
|
|
documentIsDark())
|
|
|
|
const parsedEmail = useMemo(
|
|
() =>
|
|
prepareEmailHtmlForIframe(html, {
|
|
blockRemoteContent,
|
|
isDark,
|
|
senderEmail,
|
|
cidUrlMap,
|
|
}),
|
|
[html, blockRemoteContent, isDark, senderEmail, cidUrlMap]
|
|
)
|
|
|
|
const themeCss = useMemo(() => {
|
|
if (!blockRemoteContent) return emailPreviewWrapperCss()
|
|
return `${emailPreviewBaseCss(isDark)}${
|
|
isDark ? emailPreviewDarkOverrideCss() : emailPreviewLightOverrideCss()
|
|
}`
|
|
}, [blockRemoteContent, isDark])
|
|
|
|
const cspContent = useMemo(
|
|
() => buildEmailPreviewCsp(blockRemoteContent),
|
|
[blockRemoteContent]
|
|
)
|
|
|
|
const injectContent = useCallback(() => {
|
|
const iframe = iframeRef.current
|
|
if (!iframe) return
|
|
|
|
const doc = iframe.contentDocument
|
|
if (!doc) return
|
|
|
|
injectEmailHtmlIntoDocument(doc, {
|
|
csp: cspContent,
|
|
documentBaseHref: parsedEmail.documentBaseHref,
|
|
resolveBaseHref: parsedEmail.resolveBaseHref,
|
|
headMarkup: parsedEmail.headMarkup,
|
|
bodyHtml: parsedEmail.bodyHtml,
|
|
wrapperCss: themeCss,
|
|
})
|
|
|
|
const syncHeight = () => {
|
|
const liveDoc = iframe.contentDocument
|
|
if (!liveDoc) return
|
|
const next = measureIframeContentHeight(liveDoc)
|
|
setHeight((prev) => (prev === next ? prev : next))
|
|
}
|
|
|
|
const resizeObserver = new ResizeObserver(syncHeight)
|
|
|
|
if (doc.body) {
|
|
resizeObserver.observe(doc.body)
|
|
for (const img of doc.images) {
|
|
if (!img.complete) {
|
|
img.addEventListener("load", syncHeight, { once: true })
|
|
img.addEventListener("error", syncHeight, { once: true })
|
|
}
|
|
}
|
|
for (const link of doc.querySelectorAll('link[rel~="stylesheet"]')) {
|
|
link.addEventListener("load", syncHeight, { once: true })
|
|
link.addEventListener("error", syncHeight, { once: true })
|
|
}
|
|
syncHeight()
|
|
requestAnimationFrame(syncHeight)
|
|
setTimeout(syncHeight, 250)
|
|
setTimeout(syncHeight, 1000)
|
|
}
|
|
|
|
return () => resizeObserver.disconnect()
|
|
}, [parsedEmail, themeCss, cspContent])
|
|
|
|
useEffect(() => {
|
|
const cleanup = injectContent()
|
|
return () => cleanup?.()
|
|
}, [injectContent])
|
|
|
|
return (
|
|
<iframe
|
|
key={blockRemoteContent ? "remote-blocked" : "remote-allowed"}
|
|
ref={iframeRef}
|
|
sandbox={sandboxValue}
|
|
title="Contenu du message"
|
|
className="w-full border-0 bg-transparent"
|
|
style={{ ...EMAIL_PREVIEW_IFRAME_STYLE, height: `${height}px` }}
|
|
tabIndex={-1}
|
|
/>
|
|
)
|
|
}
|