Fix inline email images showing twice (once in the body, once as a chip)
An inline image referenced via cid: is already rendered inside message.bodyHtml (added in the previous commit) but was also still stored as a regular attachment row, so it showed up a second time as an AttachmentChip thumbnail underneath the message — the images at top were correct, the small chips below were a duplicate of the same image. AttachmentDTO now carries isInline (true when the attachment's contentId is set), and the message thread — both the admin ticket view and the customer portal — filters those out of the separate attachment-chip list. Verified against a live inbound message with a cid-referenced image: previously rendered as both an <img> and an <a>-wrapped chip, now only the <img>. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GteWhnWKTmnXcsd5jx6H7u
This commit is contained in:
1 parent
b11ec711c5
commit
cabcc0b1d4
4 files changed
+21
-10
No files matched your search
@@ -333,9 +333,11 @@ function MessageBubble({ message }: { message: MessageDTO }) {
|
|||||||
) : (
|
) : (
|
||||||
<p className="mb-1 whitespace-pre-wrap text-sm">{message.body}</p>
|
<p className="mb-1 whitespace-pre-wrap text-sm">{message.body}</p>
|
||||||
)}
|
)}
|
||||||
{message.attachments.map((a) => (
|
{message.attachments
|
||||||
<AttachmentChip key={a.id} attachment={a} />
|
.filter((a) => !a.isInline)
|
||||||
))}
|
.map((a) => (
|
||||||
|
<AttachmentChip key={a.id} attachment={a} />
|
||||||
|
))}
|
||||||
<p className="text-[11px] text-warning-soft-text/80">
|
<p className="text-[11px] text-warning-soft-text/80">
|
||||||
{message.authorName} · {formatRelativeTime(message.createdAt)}
|
{message.authorName} · {formatRelativeTime(message.createdAt)}
|
||||||
</p>
|
</p>
|
||||||
@@ -357,9 +359,11 @@ function MessageBubble({ message }: { message: MessageDTO }) {
|
|||||||
) : (
|
) : (
|
||||||
<p className="mb-1 whitespace-pre-wrap">{message.body}</p>
|
<p className="mb-1 whitespace-pre-wrap">{message.body}</p>
|
||||||
)}
|
)}
|
||||||
{message.attachments.map((a) => (
|
{message.attachments
|
||||||
<AttachmentChip key={a.id} attachment={a} />
|
.filter((a) => !a.isInline)
|
||||||
))}
|
.map((a) => (
|
||||||
|
<AttachmentChip key={a.id} attachment={a} />
|
||||||
|
))}
|
||||||
<p className={`text-[11px] ${isAgent ? "text-white/70" : "text-text-faint"}`}>
|
<p className={`text-[11px] ${isAgent ? "text-white/70" : "text-text-faint"}`}>
|
||||||
{message.authorName} · {formatRelativeTime(message.createdAt)}
|
{message.authorName} · {formatRelativeTime(message.createdAt)}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -159,9 +159,11 @@ function MessageBubble({
|
|||||||
) : (
|
) : (
|
||||||
<p className="mb-1 whitespace-pre-wrap">{message.body}</p>
|
<p className="mb-1 whitespace-pre-wrap">{message.body}</p>
|
||||||
)}
|
)}
|
||||||
{message.attachments.map((a) => (
|
{message.attachments
|
||||||
<AttachmentChip key={a.id} attachment={a} token={token} />
|
.filter((a) => !a.isInline)
|
||||||
))}
|
.map((a) => (
|
||||||
|
<AttachmentChip key={a.id} attachment={a} token={token} />
|
||||||
|
))}
|
||||||
<p className={`text-[11px] ${isMine ? "text-white/70" : "text-text-faint"}`}>
|
<p className={`text-[11px] ${isMine ? "text-white/70" : "text-text-faint"}`}>
|
||||||
{message.authorName} · {formatRelativeTime(message.createdAt)}
|
{message.authorName} · {formatRelativeTime(message.createdAt)}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ async function getAttachmentsForMessages(messageIds: string[]): Promise<Map<stri
|
|||||||
});
|
});
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
const list = map.get(row.messageId) ?? [];
|
const list = map.get(row.messageId) ?? [];
|
||||||
list.push({ id: row.id, filename: row.filename, mimeType: row.mimeType, sizeBytes: row.sizeBytes });
|
list.push({ id: row.id, filename: row.filename, mimeType: row.mimeType, sizeBytes: row.sizeBytes, isInline: Boolean(row.contentId) });
|
||||||
map.set(row.messageId, list);
|
map.set(row.messageId, list);
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
@@ -155,6 +155,7 @@ async function appendMessage(params: {
|
|||||||
filename: a.filename,
|
filename: a.filename,
|
||||||
mimeType: a.mimeType,
|
mimeType: a.mimeType,
|
||||||
sizeBytes: a.sizeBytes,
|
sizeBytes: a.sizeBytes,
|
||||||
|
isInline: Boolean(a.contentId),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ export interface AttachmentDTO {
|
|||||||
filename: string;
|
filename: string;
|
||||||
mimeType: string;
|
mimeType: string;
|
||||||
sizeBytes: number;
|
sizeBytes: number;
|
||||||
|
// True for an inline image referenced from a message's bodyHtml (a cid:
|
||||||
|
// rewritten to this attachment's URL) — the UI uses this to skip showing
|
||||||
|
// it a second time as a separate chip below an already-rendered body.
|
||||||
|
isInline: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MessageDTO {
|
export interface MessageDTO {
|
||||||
|
|||||||
Reference in new issue
Block a user