From 1a0ad907096e73a012343f486610cfaaf0ee1942 Mon Sep 17 00:00:00 2001
From: Ahmet Kilinc
Date: Sat, 19 Apr 2025 01:28:44 +0100
Subject: [PATCH 1/8] draft fixes: - added cc and bcc when saving drafts - save
drafts less aggresively
---
apps/mail/components/create/create-email.tsx | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/apps/mail/components/create/create-email.tsx b/apps/mail/components/create/create-email.tsx
index 753a586dce..624ac687c8 100644
--- a/apps/mail/components/create/create-email.tsx
+++ b/apps/mail/components/create/create-email.tsx
@@ -255,12 +255,14 @@ export function CreateEmail({
const saveDraft = React.useCallback(async () => {
if (!hasUnsavedChanges) return;
- if (!toEmails.length && !subjectInput && !messageContent) return;
+ if (!toEmails.length || !subjectInput || !messageContent) return;
try {
setIsLoading(true);
const draftData = {
to: toEmails.join(', '),
+ cc: ccEmails.join(', '),
+ bcc: bccEmails.join(', '),
subject: subjectInput,
message: messageContent || '',
attachments: attachments,
From 1622ccabb8450b2a8053bee79660c2f97e9ce935 Mon Sep 17 00:00:00 2001
From: Ahmet Kilinc
Date: Sat, 19 Apr 2025 02:29:07 +0100
Subject: [PATCH 2/8] some fixes for saving attachments to draft
---
apps/mail/app/api/driver/google.ts | 49 ++++++++++++++++++++++++++++--
1 file changed, 46 insertions(+), 3 deletions(-)
diff --git a/apps/mail/app/api/driver/google.ts b/apps/mail/app/api/driver/google.ts
index 73d39d00cb..4215a55fbb 100644
--- a/apps/mail/app/api/driver/google.ts
+++ b/apps/mail/app/api/driver/google.ts
@@ -824,14 +824,57 @@ export const driver = async (config: IConfig): Promise => {
}
},
createDraft: async (data: any) => {
- const mimeMessage = [
+ // Generate a unique boundary for multipart messages
+ const boundary = `boundary_${Date.now()}`;
+
+ // Start building MIME message parts
+ const messageParts = [
`From: me`,
`To: ${data.to}`,
+ data.cc ? `Cc: ${data.cc}` : '',
+ data.bcc ? `Bcc: ${data.bcc}` : '',
`Subject: ${data.subject}`,
+ `MIME-Version: 1.0`,
+ `Content-Type: multipart/mixed; boundary=${boundary}`,
+ '',
+ `--${boundary}`,
'Content-Type: text/html; charset=utf-8',
'',
- data.message,
- ].join('\n');
+ data.message || '',
+ ];
+
+ // Add attachments if present
+ if (data.attachments?.length > 0) {
+ for (const attachment of data.attachments) {
+ const base64Data = await new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.onload = () => {
+ const base64 = (reader.result as string).split(',')[1];
+ if (base64) {
+ resolve(base64);
+ } else {
+ reject(new Error('Failed to read file as base64'));
+ }
+ };
+ reader.onerror = () => reject(new Error('Failed to read file'));
+ reader.readAsDataURL(attachment);
+ });
+
+ messageParts.push(
+ `--${boundary}`,
+ `Content-Type: ${attachment.type}`,
+ `Content-Transfer-Encoding: base64`,
+ `Content-Disposition: attachment; filename="${attachment.name}"`,
+ '',
+ base64Data,
+ );
+ }
+ }
+
+ // Close the multipart message
+ messageParts.push(`--${boundary}--`);
+
+ const mimeMessage = messageParts.filter(Boolean).join('\n');
const encodedMessage = Buffer.from(mimeMessage)
.toString('base64')
From eea882722f492a572a814e60b70bab2f03a62d8f Mon Sep 17 00:00:00 2001
From: Ahmet Kilinc
Date: Sat, 19 Apr 2025 02:30:52 +0100
Subject: [PATCH 3/8] fix for empty draft loading
---
apps/mail/components/create/create-email.tsx | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
diff --git a/apps/mail/components/create/create-email.tsx b/apps/mail/components/create/create-email.tsx
index 624ac687c8..ee0df71062 100644
--- a/apps/mail/components/create/create-email.tsx
+++ b/apps/mail/components/create/create-email.tsx
@@ -173,12 +173,24 @@ export function CreateEmail({
if (draft.content) {
try {
- const json = generateJSON(draft.content, [Document, Paragraph, Text, Bold]);
- setDefaultValue(json);
setMessageContent(draft.content);
+ setResetEditorKey((prev) => prev + 1);
+ setTimeout(() => {
+ try {
+ const json = generateJSON(draft.content, [Document, Paragraph, Text, Bold]);
+ setDefaultValue(json);
+ } catch (error) {
+ console.error('Error parsing draft content:', error);
+ setDefaultValue(createEmptyDocContent());
+ }
+ }, 0);
} catch (error) {
- console.error('Error parsing draft content:', error);
+ console.error('Error setting draft content:', error);
+ setDefaultValue(createEmptyDocContent());
}
+ } else {
+ setDefaultValue(createEmptyDocContent());
+ setMessageContent('');
}
setHasUnsavedChanges(false);
From 3a2860eaaa33d7532a0d22f3e429363b79e98414 Mon Sep 17 00:00:00 2001
From: Ahmet Kilinc
Date: Sat, 19 Apr 2025 13:48:03 +0100
Subject: [PATCH 4/8] fix draft list recipient name/address
---
apps/mail/components/draft/drafts-list.tsx | 22 ++++++++++++++++++++--
1 file changed, 20 insertions(+), 2 deletions(-)
diff --git a/apps/mail/components/draft/drafts-list.tsx b/apps/mail/components/draft/drafts-list.tsx
index a659058218..48a1a54136 100644
--- a/apps/mail/components/draft/drafts-list.tsx
+++ b/apps/mail/components/draft/drafts-list.tsx
@@ -19,7 +19,13 @@ import { ChevronDown } from 'lucide-react';
import { Button } from '../ui/button';
import { toast } from 'sonner';
-const Draft = ({ message, onClick }: ThreadProps) => {
+import { ParsedMessage } from '@/types';
+
+interface DraftProps extends Omit {
+ message: ParsedMessage;
+}
+
+const Draft = ({ message, onClick }: DraftProps) => {
const [mail] = useMail();
const [searchValue] = useSearchValue();
@@ -51,7 +57,19 @@ const Draft = ({ message, onClick }: ThreadProps) => {
)}
>
- {highlightText(message.sender.name, searchValue.highlight)}
+ {message.to.some(
+ (to: { name: string; email: string }) =>
+ to.name.includes('no-sender') || to.email.includes('no-sender'),
+ )
+ ? 'No recipient'
+ : highlightText(
+ message.to
+ .map((to: { name: string; email: string }) =>
+ to.name === 'No Sender Name' ? to.email : `${to.name} <${to.email}>`,
+ )
+ .join(', '),
+ searchValue.highlight,
+ )}
From 397090cab97ecc7f3e81c70972775501c113d4c2 Mon Sep 17 00:00:00 2001
From: Ahmet Kilinc
Date: Sat, 19 Apr 2025 13:49:47 +0100
Subject: [PATCH 5/8] also show 'No Recipient' if empty
---
apps/mail/components/draft/drafts-list.tsx | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/apps/mail/components/draft/drafts-list.tsx b/apps/mail/components/draft/drafts-list.tsx
index 48a1a54136..89c84332ed 100644
--- a/apps/mail/components/draft/drafts-list.tsx
+++ b/apps/mail/components/draft/drafts-list.tsx
@@ -57,7 +57,8 @@ const Draft = ({ message, onClick }: DraftProps) => {
)}
>
- {message.to.some(
+ {!message.to?.length ||
+ message.to.some(
(to: { name: string; email: string }) =>
to.name.includes('no-sender') || to.email.includes('no-sender'),
)
From cc52f7362d30929bd482dad974689f72fc770623 Mon Sep 17 00:00:00 2001
From: Ahmet Kilinc
Date: Sat, 19 Apr 2025 14:37:00 +0100
Subject: [PATCH 6/8] remove comments
---
apps/mail/app/api/driver/google.ts | 4 ----
1 file changed, 4 deletions(-)
diff --git a/apps/mail/app/api/driver/google.ts b/apps/mail/app/api/driver/google.ts
index 4215a55fbb..a5166390dc 100644
--- a/apps/mail/app/api/driver/google.ts
+++ b/apps/mail/app/api/driver/google.ts
@@ -824,10 +824,8 @@ export const driver = async (config: IConfig): Promise => {
}
},
createDraft: async (data: any) => {
- // Generate a unique boundary for multipart messages
const boundary = `boundary_${Date.now()}`;
- // Start building MIME message parts
const messageParts = [
`From: me`,
`To: ${data.to}`,
@@ -843,7 +841,6 @@ export const driver = async (config: IConfig): Promise => {
data.message || '',
];
- // Add attachments if present
if (data.attachments?.length > 0) {
for (const attachment of data.attachments) {
const base64Data = await new Promise((resolve, reject) => {
@@ -871,7 +868,6 @@ export const driver = async (config: IConfig): Promise => {
}
}
- // Close the multipart message
messageParts.push(`--${boundary}--`);
const mimeMessage = messageParts.filter(Boolean).join('\n');
From 0214edbe552e3c625bbd17eddfe69f8097f74a15 Mon Sep 17 00:00:00 2001
From: Ahmet Kilinc
Date: Mon, 21 Apr 2025 02:19:46 +0100
Subject: [PATCH 7/8] switch to mimetext for draft saving to keep formatting
consistent
---
apps/mail/app/api/driver/google.ts | 46 ++++++++++++------------------
1 file changed, 18 insertions(+), 28 deletions(-)
diff --git a/apps/mail/app/api/driver/google.ts b/apps/mail/app/api/driver/google.ts
index 4902777eaf..60842fd8a7 100644
--- a/apps/mail/app/api/driver/google.ts
+++ b/apps/mail/app/api/driver/google.ts
@@ -828,22 +828,18 @@ export const driver = async (config: IConfig): Promise => {
}
},
createDraft: async (data: any) => {
- const boundary = `boundary_${Date.now()}`;
-
- const messageParts = [
- `From: me`,
- `To: ${data.to}`,
- data.cc ? `Cc: ${data.cc}` : '',
- data.bcc ? `Bcc: ${data.bcc}` : '',
- `Subject: ${data.subject}`,
- `MIME-Version: 1.0`,
- `Content-Type: multipart/mixed; boundary=${boundary}`,
- '',
- `--${boundary}`,
- 'Content-Type: text/html; charset=utf-8',
- '',
- data.message || '',
- ];
+ const msg = createMimeMessage();
+ msg.setSender('me');
+ msg.setTo(data.to);
+
+ if (data.cc) msg.setCc(data.cc);
+ if (data.bcc) msg.setBcc(data.bcc);
+
+ msg.setSubject(data.subject);
+ msg.addMessage({
+ contentType: 'text/html',
+ data: data.message || ''
+ });
if (data.attachments?.length > 0) {
for (const attachment of data.attachments) {
@@ -861,21 +857,15 @@ export const driver = async (config: IConfig): Promise => {
reader.readAsDataURL(attachment);
});
- messageParts.push(
- `--${boundary}`,
- `Content-Type: ${attachment.type}`,
- `Content-Transfer-Encoding: base64`,
- `Content-Disposition: attachment; filename="${attachment.name}"`,
- '',
- base64Data,
- );
+ msg.addAttachment({
+ filename: attachment.name,
+ contentType: attachment.type,
+ data: base64Data
+ });
}
}
- messageParts.push(`--${boundary}--`);
-
- const mimeMessage = messageParts.filter(Boolean).join('\n');
-
+ const mimeMessage = msg.asRaw();
const encodedMessage = Buffer.from(mimeMessage)
.toString('base64')
.replace(/\+/g, '-')
From 3253ec378fbfb633b3768df1d345e6ef55d05953 Mon Sep 17 00:00:00 2001
From: Ahmet Kilinc
Date: Mon, 21 Apr 2025 02:48:23 +0100
Subject: [PATCH 8/8] add message title to draft list
---
apps/mail/components/draft/drafts-list.tsx | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/apps/mail/components/draft/drafts-list.tsx b/apps/mail/components/draft/drafts-list.tsx
index 89c84332ed..114161d488 100644
--- a/apps/mail/components/draft/drafts-list.tsx
+++ b/apps/mail/components/draft/drafts-list.tsx
@@ -2,9 +2,10 @@
import type { InitialThread, ThreadProps, MailListProps, MailSelectMode } from '@/types';
import { EmptyState, type FolderType } from '@/components/mail/empty-state';
-import { useCallback, useEffect, useRef, useState } from 'react';
+import React, { useCallback, useEffect, useRef, useState } from 'react';
import { Virtuoso, type VirtuosoHandle } from 'react-virtuoso';
import { cn, defaultPageSize, formatDate } from '@/lib/utils';
+import { extractTextFromHTML } from '@/actions/extractText';
import { useSearchValue } from '@/hooks/use-search-value';
import { markAsRead, markAsUnread } from '@/actions/mail';
import { highlightText } from '@/lib/email-utils.client';
@@ -28,6 +29,7 @@ interface DraftProps extends Omit {
const Draft = ({ message, onClick }: DraftProps) => {
const [mail] = useMail();
const [searchValue] = useSearchValue();
+ const [bodyText, setBodyText] = React.useState('');
const isMailSelected = message.id === mail.selected;
const isMailBulkSelected = mail.bulkSelected.includes(message.id);
@@ -94,6 +96,15 @@ const Draft = ({ message, onClick }: DraftProps) => {
>
{highlightText(message.subject, searchValue.highlight)}
+
+ {highlightText(message.title || 'No content', searchValue.highlight)}
+
);