/** * Unified Email Utilities * * This file provides backward compatibility for email utilities. * New code should import directly from the specialized modules: * - email-content.ts (content processing) * - text-direction.ts (direction handling) * - dom-purify-config.ts (sanitization) */ // Import from specialized modules import { sanitizeHtml } from './dom-purify-config'; import { detectTextDirection, applyTextDirection } from './text-direction'; import { extractEmailContent, formatEmailContent, processHtmlContent, formatPlainTextToHtml, isHtmlContent, extractTextFromHtml } from './email-content'; import { EmailMessage, EmailContent, EmailAddress, LegacyEmailMessage } from '@/types/email'; import { adaptLegacyEmail } from '@/lib/utils/email-adapters'; import { decodeInfomaniakEmail, adaptMimeEmail, isMimeFormat } from './email-mime-decoder'; import { format } from 'date-fns'; // Re-export important functions for backward compatibility export { sanitizeHtml, extractEmailContent, formatEmailContent, processHtmlContent, formatPlainTextToHtml, detectTextDirection, applyTextDirection }; /** * Standard interface for formatted email responses */ export interface FormattedEmail { to: string; cc?: string; subject: string; content: EmailContent; attachments?: Array<{ filename: string; contentType: string; content?: string; }>; } /** * Format email addresses for display * Can handle both array of EmailAddress objects or a string */ export function formatEmailAddresses(addresses: EmailAddress[] | string | undefined): string { if (!addresses) return ''; // If already a string, return as is if (typeof addresses === 'string') { return addresses; } // If array, format each address if (Array.isArray(addresses) && addresses.length > 0) { return addresses.map(addr => addr.name && addr.name !== addr.address ? `${addr.name} <${addr.address}>` : addr.address ).join(', '); } return ''; } /** * 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(); } } /** * Normalize email content to our standard format regardless of input format */ export function normalizeEmailContent(email: any): EmailMessage { if (!email) { throw new Error('Cannot normalize null or undefined email'); } // First check if this is a MIME format email that needs decoding if (email.content && isMimeFormat(email.content)) { try { console.log('Detected MIME format email, decoding...'); // We need to force cast here due to type incompatibility between EmailMessage and the mime result const adaptedEmail = adaptMimeEmail(email); return { ...adaptedEmail, flags: adaptedEmail.flags || [] // Ensure flags is always an array } as EmailMessage; } catch (error) { console.error('Error decoding MIME email:', error); // Continue with regular normalization if MIME decoding fails } } // Check if it's already in the standardized format if (email.content && typeof email.content === 'object' && (email.content.html !== undefined || email.content.text !== undefined)) { // Already in the correct format return email as EmailMessage; } // Otherwise, adapt from legacy format // We need to force cast here due to type incompatibility const adaptedEmail = adaptLegacyEmail(email); return { ...adaptedEmail, flags: adaptedEmail.flags || [] // Ensure flags is always an array } as EmailMessage; } /** * Render normalized email content into HTML for display */ export function renderEmailContent(content: EmailContent | null): string { if (!content) { return '
No content available
'; } // Create a simple object that can be processed by formatEmailContent const emailObj = { content }; // Use the centralized formatting function return formatEmailContent(emailObj); } /** * Get recipient addresses from an email for reply or forward */ function getRecipientAddresses(email: any, type: 'reply' | 'reply-all'): { to: string; cc: string } { // Format the recipients const to = Array.isArray(email.from) ? email.from.map((addr: any) => { if (typeof addr === 'string') return addr; return addr.address ? addr.address : ''; }).filter(Boolean).join(', ') : typeof email.from === 'string' ? email.from : ''; // For reply-all, include other recipients in CC let cc = ''; if (type === 'reply-all') { const toRecipients = Array.isArray(email.to) ? email.to.map((addr: any) => { if (typeof addr === 'string') return addr; return addr.address ? addr.address : ''; }).filter(Boolean) : typeof email.to === 'string' ? [email.to] : []; const ccRecipients = Array.isArray(email.cc) ? email.cc.map((addr: any) => { if (typeof addr === 'string') return addr; return addr.address ? addr.address : ''; }).filter(Boolean) : typeof email.cc === 'string' ? [email.cc] : []; cc = [...toRecipients, ...ccRecipients].join(', '); } return { to, cc }; } /** * Get formatted header information for reply or forward */ function getFormattedHeaderInfo(email: any): { fromStr: string; toStr: string; ccStr: string; dateStr: string; subject: string; } { // Format the subject const subject = email.subject && !email.subject.startsWith('Re:') && !email.subject.startsWith('Fwd:') ? email.subject : email.subject || ''; // Format the date const dateStr = email.date ? new Date(email.date).toLocaleString() : 'Unknown Date'; // Format sender const fromStr = Array.isArray(email.from) ? email.from.map((addr: any) => { if (typeof addr === 'string') return addr; return addr.name ? `${addr.name} <${addr.address}>` : addr.address; }).join(', ') : typeof email.from === 'string' ? email.from : 'Unknown Sender'; // Format recipients const toStr = Array.isArray(email.to) ? email.to.map((addr: any) => { if (typeof addr === 'string') return addr; return addr.name ? `${addr.name} <${addr.address}>` : addr.address; }).join(', ') : typeof email.to === 'string' ? email.to : ''; // Format CC const ccStr = Array.isArray(email.cc) ? email.cc.map((addr: any) => { if (typeof addr === 'string') return addr; return addr.name ? `${addr.name} <${addr.address}>` : addr.address; }).join(', ') : typeof email.cc === 'string' ? email.cc : ''; return { fromStr, toStr, ccStr, dateStr, subject }; } /** * Format email for reply */ export function formatReplyEmail(originalEmail: EmailMessage | LegacyEmailMessage | null, type: 'reply' | 'reply-all' = 'reply'): FormattedEmail { if (!originalEmail) { return { to: '', cc: '', subject: '', content: { text: '', html: '', isHtml: false, direction: 'ltr' as const } }; } // Extract recipient addresses const { to, cc } = getRecipientAddresses(originalEmail, type); // Get header information const { fromStr, dateStr, subject } = getFormattedHeaderInfo(originalEmail); // Extract content using the centralized extraction function const { text, html } = extractEmailContent(originalEmail); // Create a clearer reply header with separator line const replyHeader = `
On ${dateStr}, ${fromStr} wrote:
`; // Use the original HTML content if available, otherwise format the text const contentHtml = html || (text ? `

${text.replace(/\n/g, '

')}

` : '

No content available

'); // Wrap the original content in proper styling without losing the HTML structure const cleanHtml = ` ${replyHeader}
${contentHtml}
`; // Plain text version const plainText = ` On ${dateStr}, ${fromStr} wrote: ------------------------------------------------------------------- ${text.split('\n').join('\n> ')} `; return { to, cc, subject: subject.startsWith('Re:') ? subject : `Re: ${subject}`, content: { text: plainText.trim(), html: cleanHtml, isHtml: true, direction: 'ltr' } }; } /** * Format email for forwarding */ export function formatForwardedEmail(originalEmail: EmailMessage | LegacyEmailMessage | null): FormattedEmail { if (!originalEmail) { return { to: '', subject: '', content: { text: '', html: '', isHtml: false, direction: 'ltr' as const } }; } // Get header information const { fromStr, toStr, ccStr, dateStr, subject } = getFormattedHeaderInfo(originalEmail); // Extract content using the centralized extraction function const { text, html } = extractEmailContent(originalEmail); // Create a traditional forward format with dashed separator const forwardHeader = `
---------------------------- Forwarded Message ----------------------------
${ccStr ? ` ` : ''}
From: ${fromStr}
Date: ${dateStr}
Subject: ${subject || ''}
To: ${toStr}
Cc: ${ccStr}
----------------------------------------------------------------------
`; // Use the original HTML content if available, otherwise format the text const contentHtml = html || (text ? `

${text.replace(/\n/g, '

')}

` : '

No content available

'); const cleanHtml = `${forwardHeader}${contentHtml}`; // Plain text version - with clearer formatting const plainText = ` ---------------------------- Forwarded Message ---------------------------- From: ${fromStr} Date: ${dateStr} Subject: ${subject || ''} To: ${toStr} ${ccStr ? `Cc: ${ccStr}` : ''} ---------------------------------------------------------------------- ${text} `; // Check if original has attachments const attachments = originalEmail.attachments || []; return { to: '', subject: subject.startsWith('Fwd:') ? subject : `Fwd: ${subject}`, content: { text: plainText.trim(), html: cleanHtml, isHtml: true, direction: 'ltr' }, // Only include attachments if they exist attachments: attachments.length > 0 ? attachments.map(att => ({ filename: att.filename || 'attachment', contentType: att.contentType || 'application/octet-stream', content: att.content })) : undefined }; } /** * Format an email for reply or reply-all - canonical implementation */ export function formatEmailForReplyOrForward( email: EmailMessage | LegacyEmailMessage | null, type: 'reply' | 'reply-all' | 'forward' ): FormattedEmail { // Use our dedicated formatters if (type === 'forward') { return formatForwardedEmail(email); } else { return formatReplyEmail(email, type as 'reply' | 'reply-all'); } }