Fixed inline attachment images not displaying in the email preview by modifying EmailPreview.svelte.

The email HTML references embedded images via `cid:` URIs (e.g., `src="cid:ii_19c6d5f8d5eee7bd6d91"`), but the component never resolved those `cid:` references to actual image data, even though `postal-mime` already parses inline attachments with their `contentId` and binary `content`.
The `emailHtml` derived value now calls `resolveContentIdReferences()` before rendering, so inline/embedded images display correctly in the iframe preview.
This commit is contained in:
wayneshn
2026-03-14 22:22:08 +01:00
parent bb1e4b901a
commit 98f7712607

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import PostalMime, { type Email } from 'postal-mime';
import PostalMime, { type Attachment, type Email } from 'postal-mime';
import type { Buffer } from 'buffer';
import { t } from '$lib/translations';
import { encode } from 'html-entities';
@@ -13,11 +13,57 @@
let parsedEmail: Email | null = $state(null);
let isLoading = $state(true);
/** Converts an ArrayBuffer to a base64-encoded string. */
function arrayBufferToBase64(buffer: ArrayBuffer): string {
const bytes = new Uint8Array(buffer);
let binary = '';
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
/**
* Replaces `cid:` references in HTML with inline base64 data URIs
* sourced from the parsed email attachments. This ensures that images
* embedded as MIME parts (disposition: inline) render correctly in the
* iframe preview.
*/
function resolveContentIdReferences(html: string, attachments: Attachment[]): string {
if (!attachments || attachments.length === 0) return html;
const cidMap = new Map<string, string>();
for (const attachment of attachments) {
if (!attachment.contentId) continue;
const cid = attachment.contentId.replace(/^<|>$/g, '');
let base64Content: string;
if (typeof attachment.content === 'string') {
base64Content = attachment.content;
} else {
base64Content = arrayBufferToBase64(attachment.content);
}
cidMap.set(cid, `data:${attachment.mimeType};base64,${base64Content}`);
}
if (cidMap.size === 0) return html;
return html.replace(/cid:([^\s"']+)/gi, (match, cid) => {
return cidMap.get(cid) ?? match;
});
}
// By adding a <base> tag, all relative and absolute links in the HTML document
// will open in a new tab by default.
let emailHtml = $derived(() => {
if (parsedEmail && parsedEmail.html) {
return `<base target="_blank" />${parsedEmail.html}`;
const resolvedHtml = resolveContentIdReferences(
parsedEmail.html,
parsedEmail.attachments
);
return `<base target="_blank" />${resolvedHtml}`;
} else if (parsedEmail && parsedEmail.text) {
// display raw text email body in html
const safeHtmlContent: string = encode(parsedEmail.text);