/** * CENTRAL EMAIL FORMATTER * * This is the primary and only email formatting utility to be used. * All email formatting should go through here to ensure consistent * handling of text direction and HTML sanitization. */ import DOMPurify from 'isomorphic-dompurify'; // Configure DOMPurify to preserve direction attributes DOMPurify.addHook('afterSanitizeAttributes', function(node) { // Preserve direction attributes if (node.hasAttribute('dir')) { node.setAttribute('dir', node.getAttribute('dir') || 'ltr'); } // Preserve text-align in styles if (node.hasAttribute('style')) { const style = node.getAttribute('style') || ''; if (style.includes('text-align')) { // Keep existing alignment } else if (node.hasAttribute('dir') && node.getAttribute('dir') === 'rtl') { node.setAttribute('style', style + '; text-align: right;'); } } }); // 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 * This is the ONLY function that should be used for cleaning HTML content */ export function cleanHtmlContent(content: string): string { if (!content) return ''; // First sanitize the HTML with our configured DOMPurify const sanitized = DOMPurify.sanitize(content); // Process content to ensure consistent direction let processed = sanitized; // Replace RTL attributes with LTR if needed // We're now more careful to only modify direction attributes if needed 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 - CENTRAL IMPLEMENTATION * All other formatting functions should be deprecated in favor of this one */ 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 || ''); // Check if the content already has a forwarded message header const hasExistingHeader = originalContent.includes('---------- Forwarded message ---------'); // If there's already a forwarded message header, don't add another one if (hasExistingHeader) { // Just wrap the content in appropriate styling without adding another header const content = `
${originalContent}
`; return { subject, content }; } // Create formatted content with explicit LTR formatting const content = `
---------- Forwarded message ---------
From: ${fromString}
Date: ${dateString}
Subject: ${email.subject || ''}
To: ${toString}
`; return { subject, content }; } /** * Format an email for reply or reply-all - CENTRAL IMPLEMENTATION * All other formatting functions should be deprecated in favor of this one */ 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 = `
${quoteHeader}
${quotedContent}
`; return { to, cc: cc || undefined, subject, content }; } /** * COMPATIBILITY LAYER: For backward compatibility with the old email-formatter.ts * These functions map to our new implementation but preserve the old interface */ // For compatibility with old code that might be using the other email-formatter.ts export function formatEmailForReplyOrForward( email: EmailMessage, type: 'reply' | 'reply-all' | 'forward' ): { to: string; cc?: string; subject: string; body: string; } { if (type === 'forward') { const { subject, content } = formatForwardedEmail(email); return { to: '', subject, body: content }; } else { const { to, cc, subject, content } = formatReplyEmail(email, type as 'reply' | 'reply-all'); return { to, cc, subject, body: content }; } }