/** * 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 '
${sanitizedContent}`; // Format plain text reply if available let formattedTextContent = ''; if (originalTextContent) { const lines = originalTextContent.split(/\r\n|\r|\n/); formattedTextContent = `On ${date}, ${sender} wrote:\n\n${lines.map(line => `> ${line}`).join('\n')}`; } const result = { to, cc: cc || undefined, subject, content: { html: replyStructure, text: formattedTextContent || originalTextContent, isHtml: true, direction: typeof email.content === 'object' && email.content ? email.content.direction || 'ltr' : 'ltr', }, attachments: email.attachments?.map(att => { // Create properly typed attachment if ('name' in att) { return { filename: att.filename || att.name || 'attachment', contentType: att.contentType || 'application/octet-stream', content: att.content }; } return { filename: att.filename || 'attachment', contentType: att.contentType || 'application/octet-stream', content: att.content }; }) }; console.log('formatReplyEmail result:', { to: result.to, subject: result.subject, hasHtml: !!result.content.html, htmlLength: result.content.html?.length || 0, hasText: !!result.content.text, textLength: result.content.text?.length || 0 }); return result; } /** * Format email for forwarding */ export function formatForwardedEmail(originalEmail: EmailMessage | LegacyEmailMessage | null): FormattedEmail { console.log('formatForwardedEmail called:', { emailId: originalEmail?.id }); if (!originalEmail) { console.warn('formatForwardedEmail: No original email provided'); return { to: '', subject: '', content: { text: '', html: '', isHtml: false, direction: 'ltr' } }; } // Adapt legacy format if needed const email = 'content' in originalEmail ? originalEmail : adaptLegacyEmail(originalEmail); // Format subject with Fwd: prefix const subject = email.subject ? (email.subject.toLowerCase().startsWith('fwd:') ? email.subject : `Fwd: ${email.subject}`) : 'Fwd: '; // Get original email info for headers const { fromStr, toStr, ccStr, dateStr } = getFormattedHeaderInfo(email); console.log('Forward header info:', { fromStr, toStr, dateStr, subject }); // Original sent date const date = dateStr; // Extract raw content from the original email const { text: originalTextContent, html: originalHtmlContent } = extractEmailContent(email.content); // First sanitize the original content const sanitizedContent = sanitizeHtml(originalHtmlContent || originalTextContent); // Build the forward structure with sanitized but not yet fully formatted content const forwardStructure = `
| From: | ${fromStr} |
| Date: | ${date} |
| Subject: | ${email.subject || ''} |
| To: | ${toStr} |
| Cc: | ${ccStr} |
${sanitizedContent}`; // Format plain text version let formattedTextContent = ''; if (originalTextContent) { formattedTextContent = ` ---------- Forwarded message ---------- From: ${fromStr} Date: ${date} Subject: ${email.subject || ''} To: ${toStr} ${ccStr ? `Cc: ${ccStr}\n` : ''} ${originalTextContent} `.trim(); } const result = { to: '', subject, content: { html: forwardStructure, text: formattedTextContent || originalTextContent, isHtml: true, direction: typeof email.content === 'object' && email.content ? email.content.direction || 'ltr' : 'ltr', }, attachments: email.attachments?.map(att => { // Create properly typed attachment if ('name' in att) { return { filename: att.filename || att.name || 'attachment', contentType: att.contentType || 'application/octet-stream', content: att.content }; } return { filename: att.filename || 'attachment', contentType: att.contentType || 'application/octet-stream', content: att.content }; }) }; console.log('formatForwardedEmail result:', { subject: result.subject, hasHtml: !!result.content.html, htmlLength: result.content.html?.length || 0, hasText: !!result.content.text, textLength: result.content.text?.length || 0 }); return result; } /** * 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'); } }