fix: render HTML-only emails and redesign external content prompt
This commit is contained in:
@@ -2296,8 +2296,12 @@ export function EmailViewer({
|
|||||||
htmlContent = email.bodyValues[email.htmlBody[0].partId].value;
|
htmlContent = email.bodyValues[email.htmlBody[0].partId].value;
|
||||||
// Prefer textBody when HTML is auto-generated minimal wrapper (no rich formatting).
|
// Prefer textBody when HTML is auto-generated minimal wrapper (no rich formatting).
|
||||||
// Server-generated HTML from text/plain emails often lacks <br> tags, collapsing newlines.
|
// Server-generated HTML from text/plain emails often lacks <br> tags, collapsing newlines.
|
||||||
const hasTextBody = email.textBody?.[0]?.partId && email.bodyValues[email.textBody[0].partId];
|
// Per RFC 8621, an HTML-only email exposes the same partId in both htmlBody and textBody —
|
||||||
if (hasTextBody && htmlContent) {
|
// in that case there is no real plain-text alternative, so always render the HTML.
|
||||||
|
const textPartId = email.textBody?.[0]?.partId;
|
||||||
|
const htmlPartId = email.htmlBody[0].partId;
|
||||||
|
const hasDistinctTextBody = !!textPartId && textPartId !== htmlPartId && !!email.bodyValues[textPartId];
|
||||||
|
if (hasDistinctTextBody && htmlContent) {
|
||||||
useHtmlVersion = hasMeaningfulHtmlBody(htmlContent);
|
useHtmlVersion = hasMeaningfulHtmlBody(htmlContent);
|
||||||
} else {
|
} else {
|
||||||
useHtmlVersion = !!htmlContent;
|
useHtmlVersion = !!htmlContent;
|
||||||
@@ -4562,34 +4566,50 @@ export function EmailViewer({
|
|||||||
<div className="flex flex-col gap-3 isolate">
|
<div className="flex flex-col gap-3 isolate">
|
||||||
{/* External Content Controls */}
|
{/* External Content Controls */}
|
||||||
{hasBlockedContent && !allowExternalContent && externalContentPolicy !== 'allow' && (
|
{hasBlockedContent && !allowExternalContent && externalContentPolicy !== 'allow' && (
|
||||||
<div className="flex items-center gap-3 flex-wrap md:justify-center rounded-md px-3 py-1 bg-muted/50 dark:bg-muted/30">
|
<div className="flex items-start gap-3 py-1">
|
||||||
{externalContentPolicy === 'ask' && (
|
<div className="w-10 h-10 rounded-full bg-info/15 text-info flex items-center justify-center flex-shrink-0 shadow-sm">
|
||||||
<button
|
<Image className="w-5 h-5" />
|
||||||
onClick={() => setAllowExternalContent(true)}
|
</div>
|
||||||
className="flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground bg-transparent hover:bg-transparent transition-colors min-h-[44px] md:min-h-0"
|
<div className="flex-1 min-w-0 space-y-2">
|
||||||
>
|
<div>
|
||||||
<Image className="w-3.5 h-3.5" />
|
<div className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">
|
||||||
{t('load_external_content')}
|
External Content
|
||||||
</button>
|
</div>
|
||||||
)}
|
<div className="text-sm font-medium text-foreground break-words">
|
||||||
{email.from?.[0]?.email && (
|
{t('external_content_warning')}
|
||||||
<button
|
</div>
|
||||||
onClick={() => {
|
</div>
|
||||||
const senderEmail = email.from?.[0]?.email;
|
<div className="flex flex-wrap items-center gap-1.5 pt-0.5">
|
||||||
if (senderEmail) {
|
{externalContentPolicy === 'ask' && (
|
||||||
if (trustedSendersAddressBook && client) {
|
<button
|
||||||
addToTrustedSendersBook(client, senderEmail).catch(console.error);
|
onClick={() => setAllowExternalContent(true)}
|
||||||
} else {
|
className="inline-flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground px-3 py-1.5 rounded-md border border-border hover:bg-muted transition-colors min-h-[36px]"
|
||||||
addTrustedSender(senderEmail);
|
>
|
||||||
}
|
<Image className="w-3.5 h-3.5" />
|
||||||
setAllowExternalContent(true);
|
{t('load_external_content')}
|
||||||
}
|
</button>
|
||||||
}}
|
)}
|
||||||
className="flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground bg-transparent hover:bg-transparent transition-colors min-h-[44px] md:min-h-0"
|
{email.from?.[0]?.email && (
|
||||||
>
|
<button
|
||||||
{t('trust_sender')}
|
onClick={() => {
|
||||||
</button>
|
const senderEmail = email.from?.[0]?.email;
|
||||||
)}
|
if (senderEmail) {
|
||||||
|
if (trustedSendersAddressBook && client) {
|
||||||
|
addToTrustedSendersBook(client, senderEmail).catch(console.error);
|
||||||
|
} else {
|
||||||
|
addTrustedSender(senderEmail);
|
||||||
|
}
|
||||||
|
setAllowExternalContent(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="inline-flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground px-3 py-1.5 rounded-md border border-border hover:bg-muted transition-colors min-h-[36px]"
|
||||||
|
>
|
||||||
|
<ShieldCheck className="w-3.5 h-3.5" />
|
||||||
|
{t('trust_sender')}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -331,8 +331,12 @@ function EmailCard({
|
|||||||
htmlContent = email.bodyValues[email.htmlBody[0].partId].value;
|
htmlContent = email.bodyValues[email.htmlBody[0].partId].value;
|
||||||
// Prefer textBody when HTML is auto-generated minimal wrapper (no rich formatting).
|
// Prefer textBody when HTML is auto-generated minimal wrapper (no rich formatting).
|
||||||
// Server-generated HTML from text/plain emails often lacks <br> tags, collapsing newlines.
|
// Server-generated HTML from text/plain emails often lacks <br> tags, collapsing newlines.
|
||||||
const hasTextBody = email.textBody?.[0]?.partId && email.bodyValues[email.textBody[0].partId];
|
// Per RFC 8621, an HTML-only email exposes the same partId in both htmlBody and textBody —
|
||||||
if (hasTextBody && htmlContent) {
|
// in that case there is no real plain-text alternative, so always render the HTML.
|
||||||
|
const textPartId = email.textBody?.[0]?.partId;
|
||||||
|
const htmlPartId = email.htmlBody[0].partId;
|
||||||
|
const hasDistinctTextBody = !!textPartId && textPartId !== htmlPartId && !!email.bodyValues[textPartId];
|
||||||
|
if (hasDistinctTextBody && htmlContent) {
|
||||||
useHtmlVersion = hasMeaningfulHtmlBody(htmlContent);
|
useHtmlVersion = hasMeaningfulHtmlBody(htmlContent);
|
||||||
} else {
|
} else {
|
||||||
useHtmlVersion = !!htmlContent;
|
useHtmlVersion = !!htmlContent;
|
||||||
|
|||||||
Reference in New Issue
Block a user