diff --git a/components/email/email-viewer.tsx b/components/email/email-viewer.tsx index 30e931b4f..6146b8f05 100644 --- a/components/email/email-viewer.tsx +++ b/components/email/email-viewer.tsx @@ -8,6 +8,7 @@ import { applyNewTabToAnchor, emailIframeCsp, escapeHtml, isOpenableLinkHref, pl import { getRenderableHtmlBody } from "@/lib/email-body-selection"; import { collectReferencedCids, isEmbeddedInBody } from "@/lib/attachment-visibility"; import { collapsePlainTextQuotes, setupQuoteCollapse } from "@/lib/quote-collapse"; +import { DARK_PENDING_CLASS, applyEmailDarkMode, emailColorScheme, emailDarkModeCss, emailHasNativeDarkMode as htmlHasNativeDarkMode, resolveColorSchemeMedia } from "@/lib/email-dark-mode"; import { fitEmailBodyWidth } from "@/lib/email-fit-width"; import { withBasePath } from "@/lib/browser-navigation"; import { buildContactsPath, buildMailPath } from "@/lib/deep-links"; @@ -2228,7 +2229,7 @@ export function EmailViewer({ // Detect if the email HTML has native dark mode support const emailHasNativeDarkMode = useMemo(() => { if (!effectiveEmailContent.isHtml) return false; - return /prefers-color-scheme\s*:\s*dark/i.test(effectiveEmailContent.html); + return htmlHasNativeDarkMode(effectiveEmailContent.html); }, [effectiveEmailContent.html, effectiveEmailContent.isHtml]); const emailAlwaysLightMode = useSettingsStore((state) => state.emailAlwaysLightMode); @@ -2240,50 +2241,12 @@ export function EmailViewer({ const emailIframeSrcDoc = useMemo(() => { if (!effectiveEmailContent.isHtml) return ''; - // If email has native dark mode, let it handle its own theming - // Otherwise, use CSS filter inversion for dark mode (preserves layout) - // Re-invert leaf media elements so they appear normal. - // - // Background-COLOR containers (bgcolor, background: shorthand) are - // re-inverted so their fill flips to a dark tone, but skipped when they - // wrap media: the inner image already gets its own re-invert, and a filter - // on the container would double-invert it. Nested color containers must NOT - // stack another invert either - each filter toggles the inversion, so an - // odd number of stacked filters (body + outer + inner) lands back on - // light-on-light; the descendant rule disables filter on a color container - // nested inside another. - // - // Background-IMAGE containers are different: a real photo and whatever is - // composited over it (headline text, logos) was designed by the sender to - // read together, so the whole subtree should return to its original - // light-mode appearance - even when it wraps media. We therefore re-invert - // these unconditionally (no :has guard) and cancel the per-image re-invert - // for media inside them, otherwise those images would flip back to inverted. - const darkModeCSS = isDark && !emailHasNativeDarkMode ? ` - html { background: #121212; } - body { filter: invert(1) hue-rotate(180deg); background: #ededed; } - img, video, svg, canvas, object, embed, input[type="image"] { - filter: invert(1) hue-rotate(180deg); - } - [style*="background:"]:not(:has(img, video, svg, canvas, object, embed)), - [bgcolor]:not(:has(img, video, svg, canvas, object, embed)) { - filter: invert(1) hue-rotate(180deg); - } - :where([style*="background:"], [bgcolor]) - :where([style*="background:"], [bgcolor]):not(:has(img, video, svg, canvas, object, embed)) { - filter: none !important; - } - [style*="background-image"], - [background] { - filter: invert(1) hue-rotate(180deg); - } - [style*="background-image"] :where(img, video, svg, canvas, object, embed, input[type="image"]), - [background] :where(img, video, svg, canvas, object, embed, input[type="image"]) { - filter: none; - } - ` : ''; - - const colorScheme = isDark && emailHasNativeDarkMode ? 'light dark' : 'light'; + // Dark mode rewrites the mail's colours after load (applyEmailDarkMode in + // handleIframeLoad); this only sets the fallback colours and keeps the + // body hidden until that pass ran. A mail with its own dark styles gets + // them pinned to the app theme rather than the OS. + const darkModeCSS = isDark ? emailDarkModeCss() : ''; + const colorScheme = emailColorScheme(isDark, emailHasNativeDarkMode); // Bare HTML emails (no
${effectiveEmailContent.html}