diff --git a/components/email/ComposeEmail.tsx b/components/email/ComposeEmail.tsx
index da2919f7..c544235a 100644
--- a/components/email/ComposeEmail.tsx
+++ b/components/email/ComposeEmail.tsx
@@ -11,6 +11,13 @@ import { Input } from '@/components/ui/input';
import { Card, CardContent, CardHeader, CardTitle, CardFooter } from '@/components/ui/card';
import DOMPurify from 'isomorphic-dompurify';
+// Import the new email formatter utilities
+import {
+ formatForwardedEmail,
+ formatReplyEmail,
+ EmailMessage as FormatterEmailMessage
+} from '@/lib/utils/email-formatter';
+
// Define EmailMessage interface locally instead of importing from server-only file
interface EmailAddress {
name: string;
@@ -44,124 +51,6 @@ interface EmailMessage {
contentFetched?: boolean;
}
-// Simplified formatEmailForReplyOrForward that doesn't rely on server code
-function formatEmailForReplyOrForward(
- email: EmailMessage,
- type: 'reply' | 'reply-all' | 'forward'
-): {
- to: string;
- cc?: string;
- subject: string;
- body: string;
-} {
- // Format subject
- let subject = email.subject || '';
- if (type === 'reply' || type === 'reply-all') {
- if (!subject.startsWith('Re:')) {
- subject = `Re: ${subject}`;
- }
- } else if (type === 'forward') {
- if (!subject.startsWith('Fwd:')) {
- subject = `Fwd: ${subject}`;
- }
- }
-
- // Create quote header
- const date = typeof email.date === 'string'
- ? new Date(email.date)
- : email.date;
-
- const formattedDate = date.toLocaleString('en-US', {
- weekday: 'short',
- year: 'numeric',
- month: 'short',
- day: 'numeric',
- hour: '2-digit',
- minute: '2-digit'
- });
-
- const sender = email.from[0];
- const fromText = sender?.name
- ? `${sender.name} <${sender.address}>`
- : sender?.address || 'Unknown sender';
-
- const quoteHeader = `
On ${formattedDate}, ${fromText} wrote:
`;
-
- // Format content
- const quotedContent = email.html || email.content || email.text || '';
-
- // Format recipients
- let to = '';
- let cc = '';
-
- if (type === 'reply') {
- // Reply to sender only
- to = email.from.map(addr => `${addr.name} <${addr.address}>`).join(', ');
- } else if (type === 'reply-all') {
- // Reply to sender and all recipients
- to = email.from.map(addr => `${addr.name} <${addr.address}>`).join(', ');
-
- // Add all original recipients to CC
- const allRecipients = [
- ...(email.to || []),
- ...(email.cc || [])
- ];
-
- cc = allRecipients
- .map(addr => `${addr.name} <${addr.address}>`)
- .join(', ');
- } else if (type === 'forward') {
- // Forward doesn't set recipients
- to = '';
-
- // Format forward differently
- const formattedDate = typeof email.date === 'string'
- ? new Date(email.date).toLocaleString()
- : email.date.toLocaleString();
-
- const fromText = email.from.map(f => f.name ? `${f.name} <${f.address}>` : f.address).join(', ');
- const toText = email.to.map(t => t.name ? `${t.name} <${t.address}>` : t.address).join(', ');
-
- return {
- to: '',
- subject,
- body: `
-
-
-
---------- Forwarded message ---------
-
From: ${fromText}
-
Date: ${formattedDate}
-
Subject: ${email.subject || ''}
-
To: ${toText}
-
-
- ${quotedContent ? quotedContent : '
No content available
'}
-
-
- `
- };
- }
-
- // Format body with improved styling for replies
- const body = `
-
-
-
-
-
- ${quotedContent}
-
-
-
`;
-
- return {
- to,
- cc: cc || undefined,
- subject,
- body
- };
-}
-
// Legacy interface for backward compatibility with old ComposeEmail component
interface LegacyComposeEmailProps {
showCompose: boolean;
@@ -270,99 +159,70 @@ export default function ComposeEmail(props: ComposeEmailAllProps) {
// Initialize the form when replying to or forwarding an email
useEffect(() => {
if (initialEmail && type !== 'new') {
- // For all types of emails, format with a consistent approach
- let formattedContent = '';
-
- if (type === 'forward') {
- // Format subject with Fwd: prefix if needed
- const subjectBase = initialEmail.subject || '(No subject)';
- const subjectRegex = /^(Fwd|FW|Forward):\s*/i;
- const subject = subjectRegex.test(subjectBase)
- ? subjectBase
- : `Fwd: ${subjectBase}`;
-
- setSubject(subject);
-
- // Format forwarded content
- const fromString = initialEmail.from?.map(addr =>
- addr.name ? `${addr.name} <${addr.address}>` : addr.address
- ).join(', ') || '';
-
- const toString = initialEmail.to?.map(addr =>
- addr.name ? `${addr.name} <${addr.address}>` : addr.address
- ).join(', ') || '';
-
- const dateString = typeof initialEmail.date === 'string'
- ? new Date(initialEmail.date).toLocaleString()
- : initialEmail.date.toLocaleString();
-
- // Preprocess original content to avoid direction issues
- const originalContent = preprocessEmailContent(
- initialEmail.content || initialEmail.html || initialEmail.text || ''
- );
-
- formattedContent = `
-
-
-
-
-
---------- Forwarded message ---------
-
From: ${fromString}
-
Date: ${dateString}
-
Subject: ${initialEmail.subject || ''}
-
To: ${toString}
-
-
${originalContent}
-
-
- `;
- } else {
- // For reply/reply-all
- const formattedEmail = formatEmailForReplyOrForward(initialEmail, type as 'reply' | 'reply-all');
-
- setTo(formattedEmail.to);
-
- if (formattedEmail.cc) {
- setCc(formattedEmail.cc);
- setShowCc(true);
+ try {
+ const formatterEmail: FormatterEmailMessage = {
+ id: initialEmail.id,
+ messageId: initialEmail.messageId,
+ subject: initialEmail.subject,
+ from: initialEmail.from || [],
+ to: initialEmail.to || [],
+ cc: initialEmail.cc || [],
+ bcc: initialEmail.bcc || [],
+ date: initialEmail.date,
+ content: initialEmail.content,
+ html: initialEmail.html,
+ text: initialEmail.text,
+ hasAttachments: initialEmail.hasAttachments || false
+ };
+
+ if (type === 'forward') {
+ // For forwarding, use the dedicated formatter
+ const { subject, content } = formatForwardedEmail(formatterEmail);
+ setSubject(subject);
+ setBody(content);
+ setUserMessage(content);
+ } else {
+ // For reply/reply-all, use the reply formatter
+ const { to, cc, subject, content } = formatReplyEmail(formatterEmail, type as 'reply' | 'reply-all');
+ setTo(to);
+ if (cc) {
+ setCc(cc);
+ setShowCc(true);
+ }
+ setSubject(subject);
+ setBody(content);
+ setUserMessage(content);
}
- setSubject(formattedEmail.subject);
-
- // Process content to fix direction issues
- formattedContent = preprocessEmailContent(formattedEmail.body);
- }
-
- // Set the entire content as one editable area
- // Force LTR direction for quoted content
- setBody(formattedContent);
- setUserMessage(formattedContent);
-
- // Focus editor after initializing
- setTimeout(() => {
- if (editorRef.current) {
- editorRef.current.focus();
- editorRef.current.dir = isRTL ? 'rtl' : 'ltr';
-
- // Place cursor at the beginning of the content
- const selection = window.getSelection();
- if (selection) {
- const range = document.createRange();
+ // Focus editor after initializing
+ setTimeout(() => {
+ if (editorRef.current) {
+ editorRef.current.focus();
- // Find the first text node or element node
- let firstNode = editorRef.current.firstChild;
- if (firstNode) {
- range.setStart(firstNode, 0);
- range.collapse(true);
-
- selection.removeAllRanges();
- selection.addRange(range);
+ // Place cursor at the beginning
+ const selection = window.getSelection();
+ if (selection) {
+ try {
+ const range = document.createRange();
+
+ if (editorRef.current.firstChild) {
+ range.setStart(editorRef.current.firstChild, 0);
+ range.collapse(true);
+
+ selection.removeAllRanges();
+ selection.addRange(range);
+ }
+ } catch (e) {
+ console.error('Error positioning cursor:', e);
+ }
}
}
- }
- }, 100);
+ }, 100);
+ } catch (error) {
+ console.error('Error formatting email:', error);
+ }
}
- }, [initialEmail, type, isRTL]);
+ }, [initialEmail, type]);
// Format date for the forwarded message header
const formatDate = (date: Date | null): string => {
@@ -401,43 +261,6 @@ export default function ComposeEmail(props: ComposeEmailAllProps) {
).join(', ');
};
- // Initialize forwarded email with clear structure and style preservation
- const initializeForwardedEmail = async () => {
- console.log('Starting initializeForwardedEmail');
- if (!initialEmail) {
- console.error('No email available for forwarding');
- setBody('No email available for forwarding
');
- return;
- }
-
- try {
- // Format subject with Fwd: prefix if needed
- const subjectBase = initialEmail.subject || '(No subject)';
- const subjectRegex = /^(Fwd|FW|Forward):\s*/i;
- const subject = subjectRegex.test(subjectBase)
- ? subjectBase
- : `Fwd: ${subjectBase}`;
-
- setSubject(subject);
-
- // For forwarded emails, we'll use a completely different approach
- // Just save the original HTML content and use it directly
- // This preserves all formatting without trying to parse it
-
- // Set message parts for the editor
- const content = initialEmail.content || initialEmail.html || initialEmail.text || '';
- setOriginalContent(content);
- setUserMessage(''); // Start with empty user message
- setBody(''); // Will be constructed when sending
-
- // Log for debugging
- console.log('Set originalContent:', content.substring(0, 100) + '...');
- } catch (error) {
- console.error('Error formatting forwarded email:', error);
- setBody('Error formatting forwarded email content
');
- }
- };
-
// Handle attachment selection
const handleAttachmentClick = () => {
attachmentInputRef.current?.click();
diff --git a/lib/utils/email-formatter.ts b/lib/utils/email-formatter.ts
new file mode 100644
index 00000000..8c894190
--- /dev/null
+++ b/lib/utils/email-formatter.ts
@@ -0,0 +1,208 @@
+/**
+ * Utilities for email formatting with proper text direction handling
+ */
+
+import DOMPurify from 'isomorphic-dompurify';
+
+// Interface definitions
+export interface EmailAddress {
+ name: string;
+ address: string;
+}
+
+export interface EmailMessage {
+ id: string;
+ messageId?: string;
+ subject: string;
+ from: EmailAddress[];
+ to: EmailAddress[];
+ cc?: EmailAddress[];
+ bcc?: EmailAddress[];
+ date: Date | string;
+ flags?: {
+ seen: boolean;
+ flagged: boolean;
+ answered: boolean;
+ deleted: boolean;
+ draft: boolean;
+ };
+ preview?: string;
+ content?: string;
+ html?: string;
+ text?: string;
+ hasAttachments?: boolean;
+ attachments?: any[];
+ folder?: string;
+ size?: number;
+ contentFetched?: boolean;
+}
+
+/**
+ * Format email addresses for display
+ */
+export function formatEmailAddresses(addresses: EmailAddress[]): string {
+ if (!addresses || addresses.length === 0) return '';
+
+ return addresses.map(addr =>
+ addr.name && addr.name !== addr.address
+ ? `${addr.name} <${addr.address}>`
+ : addr.address
+ ).join(', ');
+}
+
+/**
+ * Format date for display
+ */
+export function formatEmailDate(date: Date | string | undefined): string {
+ if (!date) return '';
+
+ try {
+ const dateObj = typeof date === 'string' ? new Date(date) : date;
+ return dateObj.toLocaleString('en-US', {
+ weekday: 'short',
+ year: 'numeric',
+ month: 'short',
+ day: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit'
+ });
+ } catch (e) {
+ return typeof date === 'string' ? date : date.toString();
+ }
+}
+
+/**
+ * Clean HTML content to prevent RTL/LTR issues
+ */
+export function cleanHtmlContent(content: string): string {
+ if (!content) return '';
+
+ // First sanitize the HTML
+ const sanitized = DOMPurify.sanitize(content);
+
+ // Process content to ensure consistent direction
+ let processed = sanitized;
+
+ // Replace RTL attributes with LTR
+ processed = processed.replace(/dir\s*=\s*["']rtl["']/gi, 'dir="ltr"');
+ processed = processed.replace(/style\s*=\s*["']([^"']*)direction\s*:\s*rtl;?([^"']*)["']/gi,
+ (match, before, after) => `style="${before}direction: ltr;${after}"`);
+
+ return processed;
+}
+
+/**
+ * Format an email for forwarding
+ */
+export function formatForwardedEmail(email: EmailMessage): {
+ subject: string;
+ content: string;
+} {
+ // Format subject with Fwd: prefix if needed
+ const subjectBase = email.subject || '(No subject)';
+ const subject = subjectBase.match(/^(Fwd|FW|Forward):/i)
+ ? subjectBase
+ : `Fwd: ${subjectBase}`;
+
+ // Get sender and recipient information
+ const fromString = formatEmailAddresses(email.from || []);
+ const toString = formatEmailAddresses(email.to || []);
+ const dateString = formatEmailDate(email.date);
+
+ // Get and clean original content
+ const originalContent = cleanHtmlContent(email.content || email.html || email.text || '');
+
+ // Create formatted content with explicit LTR formatting
+ const content = `
+
+
+
+
+
---------- Forwarded message ---------
+
From: ${fromString}
+
Date: ${dateString}
+
Subject: ${email.subject || ''}
+
To: ${toString}
+
+
+ ${originalContent}
+
+
+
+ `;
+
+ return { subject, content };
+}
+
+/**
+ * Format an email for reply or reply-all
+ */
+export function formatReplyEmail(email: EmailMessage, type: 'reply' | 'reply-all'): {
+ to: string;
+ cc?: string;
+ subject: string;
+ content: string;
+} {
+ // Format subject with Re: prefix if needed
+ const subjectBase = email.subject || '(No subject)';
+ const subject = subjectBase.match(/^Re:/i)
+ ? subjectBase
+ : `Re: ${subjectBase}`;
+
+ // Get sender information for quote header
+ const sender = email.from[0];
+ const fromText = sender?.name
+ ? `${sender.name} <${sender.address}>`
+ : sender?.address || 'Unknown sender';
+
+ // Format date for quote header
+ const date = typeof email.date === 'string' ? new Date(email.date) : email.date;
+ const formattedDate = date.toLocaleString('en-US', {
+ weekday: 'short',
+ year: 'numeric',
+ month: 'short',
+ day: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit'
+ });
+
+ // Create quote header
+ const quoteHeader = `On ${formattedDate}, ${fromText} wrote:
`;
+
+ // Get and clean original content
+ const quotedContent = cleanHtmlContent(email.html || email.content || email.text || '');
+
+ // Format recipients
+ let to = formatEmailAddresses(email.from || []);
+ let cc = '';
+
+ if (type === 'reply-all') {
+ // For reply-all, add all original recipients to CC
+ const allRecipients = [
+ ...(email.to || []),
+ ...(email.cc || [])
+ ];
+
+ cc = formatEmailAddresses(allRecipients);
+ }
+
+ // Format content with explicit LTR for quoted parts
+ const content = `
+
+
+
+
+
+ ${quotedContent}
+
+
+
+ `;
+
+ return {
+ to,
+ cc: cc || undefined,
+ subject,
+ content
+ };
+}
\ No newline at end of file