/** * Centralized Email Content Utilities * * This file contains all core functions for email content processing: * - Content extraction * - HTML sanitization * - Text direction handling * - URL fixing * * Other modules should import from this file rather than implementing their own versions. */ import { sanitizeHtml } from './dom-purify-config'; import { detectTextDirection } from './text-direction'; import { EmailContent } from '@/types/email'; import { processCidReferences } from './email-utils'; /** * Extract content from various possible email formats * Centralized implementation to reduce duplication across the codebase */ export function extractEmailContent(email: any): { text: string; html: string; isHtml: boolean; direction: 'ltr' | 'rtl'; } { // Default empty values let textContent = ''; let htmlContent = ''; let isHtml = false; let direction: 'ltr' | 'rtl' = 'ltr'; // Early exit if no email if (!email) { console.log('extractEmailContent: No email provided'); return { text: '', html: '', isHtml: false, direction: 'ltr' }; } try { // Extract based on common formats if (email.content && typeof email.content === 'object') { // Standard format with content object textContent = email.content.text || ''; htmlContent = email.content.html || ''; isHtml = email.content.isHtml || !!htmlContent; direction = email.content.direction || 'ltr'; // Handle complex email formats where content might be nested if (!textContent && !htmlContent) { // Try to find content in deeper nested structure if (email.content.body) { if (typeof email.content.body === 'string') { // Determine if body is HTML or text if (isHtmlContent(email.content.body)) { htmlContent = email.content.body; isHtml = true; } else { textContent = email.content.body; isHtml = false; } } else if (typeof email.content.body === 'object' && email.content.body) { // Some email formats nest content inside body htmlContent = email.content.body.html || ''; textContent = email.content.body.text || ''; isHtml = email.content.body.isHtml || !!htmlContent; direction = email.content.body.direction || 'ltr'; } } // Check for data property which some email services use if (!textContent && !htmlContent && email.content.data) { if (typeof email.content.data === 'string') { // Check if data looks like HTML if (isHtmlContent(email.content.data)) { htmlContent = email.content.data; isHtml = true; } else { textContent = email.content.data; isHtml = false; } } } } } else if (typeof email.content === 'string') { // Check if content is likely HTML if (isHtmlContent(email.content)) { htmlContent = email.content; isHtml = true; } else { textContent = email.content; isHtml = false; } } else { // Check other common properties htmlContent = email.html || ''; textContent = email.text || ''; isHtml = email.isHtml || !!htmlContent; direction = email.direction || 'ltr'; // If still no content, check for less common properties if (!htmlContent && !textContent) { // Try additional properties that some email clients use htmlContent = email.body?.html || email.bodyHtml || email.htmlBody || ''; textContent = email.body?.text || email.bodyText || email.plainText || ''; isHtml = email.body?.isHtml || !!htmlContent; direction = email.body?.direction || 'ltr'; } } } catch (error) { console.error('Error extracting email content:', error); } // Ensure we always have at least some text content if (!textContent && htmlContent) { textContent = extractTextFromHtml(htmlContent); } // Log extraction results console.log('Extracted email content:', { hasHtml: !!htmlContent, htmlLength: htmlContent?.length || 0, hasText: !!textContent, textLength: textContent?.length || 0, isHtml, direction }); return { text: textContent, html: htmlContent, isHtml, direction }; } /** * Extract plain text from HTML content */ export function extractTextFromHtml(html: string): string { if (!html) return ''; try { // Use DOM API if available if (typeof window !== 'undefined' && typeof document !== 'undefined') { const tempDiv = document.createElement('div'); tempDiv.innerHTML = html; return tempDiv.textContent || tempDiv.innerText || ''; } else { // Simple regex fallback for non-browser environments return html.replace(/<[^>]*>/g, ' ') .replace(/ /g, ' ') .replace(/</g, '<') .replace(/>/g, '>') .replace(/&/g, '&') .replace(/\s+/g, ' ') .trim(); } } catch (e) { console.error('Error extracting text from HTML:', e); // Fallback to basic strip return html.replace(/<[^>]*>/g, ' ').trim(); } } /** * Check if a string is likely HTML content */ export function isHtmlContent(content: string): boolean { if (!content) return false; return content.trim().startsWith('<') && (content.includes('') || content.includes('
')); } /** * Format and standardize email content for display following email industry standards. * This is the main entry point for rendering email content. */ export function formatEmailContent(email: any): string { if (!email) { console.log('formatEmailContent: No email provided'); return ''; } try { // Extract content from email const { text, html, isHtml, direction } = extractEmailContent(email); console.log('formatEmailContent processing:', { hasHtml: !!html, htmlLength: html?.length || 0, hasText: !!text, textLength: text?.length || 0, emailType: typeof email === 'string' ? 'string' : 'object', isHtml, direction }); // If we have HTML content, sanitize and standardize it if (html) { // Process HTML content const processed = processHtmlContent(html, { sanitize: true }); console.log('HTML content processed:', { processedLength: processed.sanitizedContent?.length || 0, isEmpty: !processed.sanitizedContent || processed.sanitizedContent.trim().length === 0 }); // Apply styling return `
${processed.sanitizedContent}
`; } // If we only have text content, format it properly else if (text) { console.log('Using plain text formatting'); return formatPlainTextToHtml(text); } // Default case: empty or unrecognized content return '
No content available
'; } catch (error) { console.error('formatEmailContent: Error formatting email content:', error); return `

Error displaying email content

${error instanceof Error ? error.message : 'Unknown error'}

`; } } /** * Process HTML content to ensure safe rendering and proper formatting */ export function processHtmlContent( htmlContent: string, options?: { sanitize?: boolean; blockExternalContent?: boolean; preserveReplyFormat?: boolean; attachments?: Array<{ filename?: string; name?: string; contentType?: string; content?: string; contentId?: string; }>; } | string // Support for legacy textContent parameter ): { sanitizedContent: string; hasImages: boolean; hasExternalContent: boolean; direction: 'ltr' | 'rtl'; } { // Handle legacy string parameter (textContent) if (typeof options === 'string') { options = { sanitize: true }; } console.log('Processing HTML content:', { contentLength: htmlContent?.length || 0, startsWithHtml: htmlContent?.startsWith(']*)>/g, '') .replace(/]*)>/g, '') // Ensure blockquote styling is preserved .replace(/]*)>/g, ''); } // Fix common email client quirks without breaking cid: URLs sanitizedContent = sanitizedContent // Fix for Outlook WebVML content .replace(/