feat: iframe-based email rendering with smart dark mode support

- Render HTML emails in sandboxed iframe (srcdoc) for true-to-life
  display with complete CSS isolation from app styles
- Detect emails with native dark mode (prefers-color-scheme) and
  let them handle their own theming
- Apply CSS filter inversion for dark mode on emails without native
  support, with re-inversion for images/media to preserve appearance
- Add per-email light/dark toggle button (Sun/Moon icon) next to
  email size, resets on email change (not persisted)
- Fix HTML reply/forward to include original email HTML content
- Send replies as multipart/alternative (text + HTML)
- Add drag-and-drop file attachments with overlay indicator
- Auto-resize composer textarea to avoid double scrolling
- Pin attachments section and bottom toolbar outside scroll area
- Collapsible attachment list (show 3, toggle for more)
This commit is contained in:
Linus Rath
2026-03-15 17:42:41 +01:00
parent 0965fbc7c1
commit 9bffb72338
7 changed files with 287 additions and 64 deletions
+3 -1
View File
@@ -423,6 +423,7 @@ export default function Home() {
bcc: string[];
subject: string;
body: string;
htmlBody?: string;
draftId?: string;
fromEmail?: string;
fromName?: string;
@@ -431,7 +432,7 @@ export default function Home() {
if (!client) return;
try {
await sendEmail(client, data.to, data.subject, data.body, data.cc, data.bcc, data.identityId, data.fromEmail, data.draftId, data.fromName);
await sendEmail(client, data.to, data.subject, data.body, data.cc, data.bcc, data.identityId, data.fromEmail, data.draftId, data.fromName, data.htmlBody);
setShowComposer(false);
// Refresh the current mailbox to update the UI
@@ -1349,6 +1350,7 @@ export default function Home() {
cc: selectedEmail.cc,
subject: selectedEmail.subject,
body: selectedEmail.bodyValues?.[selectedEmail.textBody?.[0]?.partId || '']?.value || selectedEmail.preview || '',
htmlBody: selectedEmail.bodyValues?.[selectedEmail.htmlBody?.[0]?.partId || '']?.value || undefined,
receivedAt: selectedEmail.receivedAt
} : undefined)}
initialDraftText={composerDraftText}
+20
View File
@@ -242,6 +242,26 @@ body {
list-style-type: decimal;
}
/* Reply quoted HTML - preserves original inline styles/colors */
.email-reply-quote {
overflow-wrap: break-word;
word-wrap: break-word;
max-width: none;
}
.email-reply-quote p {
margin: 0.5rem 0;
}
.email-reply-quote img {
max-width: 100%;
height: auto;
}
.email-reply-quote a {
text-decoration: underline;
}
/* Only style tables that are actual data tables, not layout tables */
.email-content table.data-table,
.email-content table[border="1"] {