From 65eef4b2b8e0186b1bdb64068bef92f461a983a8 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Thu, 30 Apr 2026 15:24:09 +0200 Subject: [PATCH] fix: persist htmlBody in drafts to preserve rich formatting #236 --- components/email/email-composer.tsx | 3 ++- lib/demo/demo-client.ts | 11 ++++++++--- lib/jmap/client-interface.ts | 1 + lib/jmap/client.ts | 15 +++++++++++---- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/components/email/email-composer.tsx b/components/email/email-composer.tsx index ec6afbe5..7cf32b07 100644 --- a/components/email/email-composer.tsx +++ b/components/email/email-composer.tsx @@ -744,7 +744,8 @@ export function EmailComposer({ fromEmail, draftId || undefined, uploadedAttachments, - currentIdentity?.name || undefined + currentIdentity?.name || undefined, + plainTextMode ? undefined : body ); setDraftId(savedDraftId); diff --git a/lib/demo/demo-client.ts b/lib/demo/demo-client.ts index 935327b5..db06c1ec 100644 --- a/lib/demo/demo-client.ts +++ b/lib/demo/demo-client.ts @@ -383,6 +383,7 @@ export class DemoJMAPClient implements IJMAPClient { draftId?: string, attachments?: Array<{ blobId: string; name: string; type: string; size: number; disposition?: 'attachment' | 'inline'; cid?: string }>, _fromName?: string, + htmlBody?: string, ): Promise { const draftsMb = this.data.mailboxes.find(m => m.role === 'drafts'); const id = draftId || generateDemoId('email'); @@ -402,9 +403,13 @@ export class DemoJMAPClient implements IJMAPClient { sentAt: new Date().toISOString(), preview: body.substring(0, 200), hasAttachment: !!attachments?.length, - textBody: [{ partId: '1', blobId: generateDemoId('blob'), size: body.length, type: 'text/plain' }], - htmlBody: [], - bodyValues: { '1': { value: body } }, + textBody: [{ partId: htmlBody ? 'text' : '1', blobId: generateDemoId('blob'), size: body.length, type: 'text/plain' }], + htmlBody: htmlBody + ? [{ partId: 'html', blobId: generateDemoId('blob'), size: htmlBody.length, type: 'text/html' }] + : [], + bodyValues: htmlBody + ? { text: { value: body }, html: { value: htmlBody } } + : { '1': { value: body } }, attachments: attachments?.map(a => ({ ...a, partId: generateDemoId('part') })), messageId: `<${id}@demo.example.com>`, }; diff --git a/lib/jmap/client-interface.ts b/lib/jmap/client-interface.ts index 50490c38..041b02f3 100644 --- a/lib/jmap/client-interface.ts +++ b/lib/jmap/client-interface.ts @@ -115,6 +115,7 @@ export interface IJMAPClient { draftId?: string, attachments?: Array<{ blobId: string; name: string; type: string; size: number; disposition?: 'attachment' | 'inline'; cid?: string }>, fromName?: string, + htmlBody?: string, ): Promise; sendEmail( diff --git a/lib/jmap/client.ts b/lib/jmap/client.ts index 86175701..49ec63c2 100644 --- a/lib/jmap/client.ts +++ b/lib/jmap/client.ts @@ -1939,7 +1939,8 @@ export class JMAPClient implements IJMAPClient { fromEmail?: string, draftId?: string, attachments?: Array<{ blobId: string; name: string; type: string; size: number; disposition?: 'attachment' | 'inline'; cid?: string }>, - fromName?: string + fromName?: string, + htmlBody?: string ): Promise { const mailboxes = await this.getMailboxes(); const draftsMailbox = mailboxes.find(mb => mb.role === 'drafts'); @@ -1958,7 +1959,8 @@ export class JMAPClient implements IJMAPClient { keywords: Record; mailboxIds: Record; bodyValues: Record; - textBody: { partId: string }[]; + textBody: { partId: string; type?: string }[]; + htmlBody?: { partId: string; type: string }[]; attachments?: { blobId: string; type: string; name: string; disposition: string; cid?: string }[]; } @@ -1970,8 +1972,13 @@ export class JMAPClient implements IJMAPClient { subject, keywords: { "$draft": true }, mailboxIds: { [draftsMailbox.id]: true }, - bodyValues: { "1": { value: body } }, - textBody: [{ partId: "1" }], + bodyValues: htmlBody + ? { "text": { value: body }, "html": { value: htmlBody } } + : { "1": { value: body } }, + textBody: htmlBody + ? [{ partId: "text", type: "text/plain" }] + : [{ partId: "1" }], + ...(htmlBody ? { htmlBody: [{ partId: "html", type: "text/html" }] } : {}), }; if (attachments?.length) {