From 6c9f2d86a6ca1d55df035703317ecc6e672d3566 Mon Sep 17 00:00:00 2001 From: alma Date: Thu, 1 May 2025 10:47:24 +0200 Subject: [PATCH] courrier preview --- components/email/ComposeEmail.tsx | 4 +- components/email/EmailContentDisplay.tsx | 86 ++------ components/ui/rich-text-editor.tsx | 36 ++-- hooks/use-email-fetch.ts | 69 +++--- lib/utils/email-adapters.ts | 91 +++----- lib/utils/email-utils.ts | 254 +++++++++-------------- lib/utils/text-direction.ts | 115 ++++++++++ 7 files changed, 320 insertions(+), 335 deletions(-) diff --git a/components/email/ComposeEmail.tsx b/components/email/ComposeEmail.tsx index 0f6a765b..d5eebcc6 100644 --- a/components/email/ComposeEmail.tsx +++ b/components/email/ComposeEmail.tsx @@ -16,7 +16,7 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import RichTextEditor from '@/components/ui/rich-text-editor'; -import { detectTextDirection } from '@/lib/utils/text-direction'; +import { processContentWithDirection } from '@/lib/utils/text-direction'; // Import from the centralized utils import { @@ -351,7 +351,7 @@ export default function ComposeEmail(props: ComposeEmailProps) { { // Store the content setEmailContent(html); diff --git a/components/email/EmailContentDisplay.tsx b/components/email/EmailContentDisplay.tsx index e23c9506..738b88de 100644 --- a/components/email/EmailContentDisplay.tsx +++ b/components/email/EmailContentDisplay.tsx @@ -2,8 +2,7 @@ import React, { useMemo } from 'react'; import { EmailContent } from '@/types/email'; -import { detectTextDirection, applyTextDirection } from '@/lib/utils/text-direction'; -import { sanitizeHtml, getDOMPurify } from '@/lib/utils/dom-purify-config'; +import { processContentWithDirection } from '@/lib/utils/text-direction'; interface EmailContentDisplayProps { content: EmailContent | null | undefined; @@ -24,99 +23,58 @@ const EmailContentDisplay: React.FC = ({ type = 'auto', debug = false }) => { - // Create a safe content object with fallback values for missing properties - const safeContent = useMemo(() => { + // Process content with centralized utility + const processedContent = useMemo(() => { + // Default empty content if (!content) { return { text: '', - html: undefined, - isHtml: false, + html: '
No content available
', direction: 'ltr' as const }; } - return { - text: content.text || '', - html: content.html, - isHtml: content.isHtml, - // If direction is missing, detect it from the text content - direction: content.direction || detectTextDirection(content.text) - }; - }, [content]); - // Determine what content to display based on type preference and available content - const htmlToDisplay = useMemo(() => { - // If no content is available, show a placeholder - if (!safeContent.text && !safeContent.html) { - return '
No content available
'; - } - - // If type is explicitly set to text, or we don't have HTML and auto mode - if (type === 'text' || (type === 'auto' && !safeContent.isHtml)) { - // Format plain text with line breaks for HTML display - if (safeContent.text) { - const formattedText = safeContent.text - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/\n/g, '
'); - - return formattedText; - } - } - - // Otherwise use HTML content if available - if (safeContent.isHtml && safeContent.html) { - return safeContent.html; - } - - // Fallback to text content if there's no HTML - if (safeContent.text) { - const formattedText = safeContent.text + // For text-only display, convert plain text to HTML first + if (type === 'text') { + const textContent = content.text || ''; + const formattedText = textContent .replace(/&/g, '&') .replace(//g, '>') .replace(/\n/g, '
'); - return formattedText; + return processContentWithDirection(formattedText); } - return '
No content available
'; - }, [safeContent, type]); + // For auto mode, let the centralized function handle the content + return processContentWithDirection(content); + }, [content, type]); // Handle quoted text display - const processedHTML = useMemo(() => { + const displayHTML = useMemo(() => { if (!showQuotedText) { // This is simplified - a more robust approach would parse and handle // quoted sections more intelligently - return htmlToDisplay.replace(/]*>[\s\S]*?<\/blockquote>/gi, + return processedContent.html.replace(/]*>[\s\S]*?<\/blockquote>/gi, '
[Quoted text hidden]
'); } - return htmlToDisplay; - }, [htmlToDisplay, showQuotedText]); - - // Sanitize HTML content and apply proper direction - const sanitizedHTML = useMemo(() => { - // First sanitize the HTML with our centralized utility - const cleanHtml = sanitizeHtml(processedHTML); - - // Then apply text direction consistently - return applyTextDirection(cleanHtml, safeContent.text); - }, [processedHTML, safeContent.text]); + return processedContent.html; + }, [processedContent.html, showQuotedText]); return (
{/* Debug output if enabled */} {debug && (
-

Content Type: {safeContent.isHtml ? 'HTML' : 'Text'}

-

Direction: {safeContent.direction}

-

HTML Length: {safeContent.html?.length || 0}

-

Text Length: {safeContent.text?.length || 0}

+

Content Type: {content?.isHtml ? 'HTML' : 'Text'}

+

Direction: {processedContent.direction}

+

HTML Length: {content?.html?.length || 0}

+

Text Length: {content?.text?.length || 0}

)} diff --git a/components/ui/rich-text-editor.tsx b/components/ui/rich-text-editor.tsx index 309cd233..3d863ad7 100644 --- a/components/ui/rich-text-editor.tsx +++ b/components/ui/rich-text-editor.tsx @@ -1,7 +1,7 @@ 'use client'; import React, { useState, useEffect, useRef, forwardRef, useImperativeHandle } from 'react'; -import { detectTextDirection } from '@/lib/utils/text-direction'; +import { processContentWithDirection } from '@/lib/utils/text-direction'; import { getDOMPurify } from '@/lib/utils/dom-purify-config'; interface RichTextEditorProps { @@ -44,8 +44,13 @@ const RichTextEditor = forwardRef(({ initialDirection }, ref) => { const internalEditorRef = useRef(null); + + // Process initial content to get its direction + const processedInitialContent = processContentWithDirection(initialContent); + + // Set initial direction either from prop or detected from content const [direction, setDirection] = useState<'ltr' | 'rtl'>( - initialDirection || detectTextDirection(initialContent) + initialDirection || processedInitialContent.direction ); // Forward the ref to parent components @@ -54,12 +59,10 @@ const RichTextEditor = forwardRef(({ // Initialize editor with clean content useEffect(() => { if (internalEditorRef.current) { - // Clean the initial content using our centralized config - const cleanContent = DOMPurify.sanitize(initialContent, { - ADD_ATTR: ['dir'] // Ensure dir attributes are preserved - }); + // Using centralized processing for initial content + const { html: cleanContent } = processContentWithDirection(initialContent); - internalEditorRef.current.innerHTML = cleanContent; + internalEditorRef.current.innerHTML = cleanContent || ''; // Set initial direction internalEditorRef.current.setAttribute('dir', direction); @@ -75,15 +78,20 @@ const RichTextEditor = forwardRef(({ // Handle content changes and detect direction changes const handleInput = (e: React.FormEvent) => { - if (onChange && e.currentTarget.innerHTML !== initialContent) { - onChange(e.currentTarget.innerHTML); + const newContent = e.currentTarget.innerHTML; + + // Notify parent of changes + if (onChange && newContent !== initialContent) { + onChange(newContent); } - // Re-detect direction on significant content changes - // Only do this when the content length has changed significantly - const newContent = e.currentTarget.innerText; - if (newContent.length > 5 && newContent.length % 10 === 0) { - const newDirection = detectTextDirection(newContent); + // Only perform direction detection periodically to avoid constant recalculation + // Get the text content for direction detection + const newTextContent = e.currentTarget.innerText; + if (newTextContent.length > 5 && newTextContent.length % 10 === 0) { + // Process the content to determine direction + const { direction: newDirection } = processContentWithDirection(newTextContent); + if (newDirection !== direction) { setDirection(newDirection); e.currentTarget.setAttribute('dir', newDirection); diff --git a/hooks/use-email-fetch.ts b/hooks/use-email-fetch.ts index f6a4c175..baa42954 100644 --- a/hooks/use-email-fetch.ts +++ b/hooks/use-email-fetch.ts @@ -1,8 +1,7 @@ import { useState, useEffect, useCallback, useRef } from 'react'; import { useToast } from './use-toast'; import { EmailMessage, EmailContent } from '@/types/email'; -import { detectTextDirection } from '@/lib/utils/text-direction'; -import { sanitizeHtml } from '@/lib/utils/email-utils'; +import { processContentWithDirection } from '@/lib/utils/text-direction'; interface EmailFetchState { email: EmailMessage | null; @@ -76,46 +75,40 @@ export function useEmailFetch({ onEmailLoaded, onError }: UseEmailFetchProps = { // Create a valid email message object with required fields const processContent = (data: any): EmailContent => { - // Determine the text content - using all possible paths - let textContent = ''; - if (typeof data.content === 'string') { - textContent = data.content; - } else if (data.content?.text) { - textContent = data.content.text; - } else if (data.text) { - textContent = data.text; - } else if (data.plainText) { - textContent = data.plainText; - } - - // Determine the HTML content - using all possible paths - let htmlContent = undefined; - if (data.content?.html) { - htmlContent = data.content.html; - } else if (data.html) { - htmlContent = data.html; - } else if (typeof data.content === 'string' && data.content.includes('<')) { - // If the content string appears to be HTML - htmlContent = data.content; - // We should still keep the text version, will be extracted if needed + // Extract initial content from all possible sources + let initialContent: any = {}; + + if (typeof data.content === 'object' && data.content) { + // Use content object directly if available + initialContent = data.content; + } else { + // Build content object from separate properties + if (typeof data.content === 'string') { + // Check if content appears to be HTML + if (data.content.includes('<') && + (data.content.includes('')); - htmlContent = isHtml ? email.content : ''; - textContent = isHtml ? '' : email.content; - } else if (email.html) { - isHtml = true; - htmlContent = email.html; - textContent = email.text || email.plainText || ''; - } else if (email.text || email.plainText) { - isHtml = false; - htmlContent = ''; - textContent = email.text || email.plainText || ''; - } else if (email.formattedContent) { - // Assume formattedContent is already HTML - isHtml = true; - htmlContent = email.formattedContent; - textContent = ''; - } - - // Step 2: Set the normalized content properties - normalizedContent.isHtml = isHtml; - - // Always ensure we have text content - if (textContent) { - normalizedContent.text = textContent; - } else if (htmlContent) { - // Extract text from HTML if we don't have plain text - if (typeof document !== 'undefined') { - // Browser environment - const tempDiv = document.createElement('div'); - tempDiv.innerHTML = htmlContent; - normalizedContent.text = tempDiv.textContent || tempDiv.innerText || ''; + if (email.content.trim().startsWith('<') && + (email.content.includes(''))) { + initialContent.html = email.content; } else { - // Server environment - do simple strip - normalizedContent.text = htmlContent - .replace(/<[^>]*>/g, '') - .replace(/ /g, ' ') - .replace(/\s+/g, ' ') - .trim(); + initialContent.text = email.content; } + } else { + // Extract from separate properties + if (email.html) initialContent.html = email.html; + if (email.text) initialContent.text = email.text; + else if (email.plainText) initialContent.text = email.plainText; + else if (email.formattedContent) initialContent.html = email.formattedContent; } - // If we have HTML content, store it without sanitizing (sanitization will be done at display time) - if (isHtml && htmlContent) { - normalizedContent.html = htmlContent; - } + // Use the centralized content processor + const processedContent = processContentWithDirection(initialContent); - // Determine text direction - normalizedContent.direction = detectTextDirection(normalizedContent.text); - - return normalizedContent; + return { + html: processedContent.html, + text: processedContent.text, + isHtml: !!processedContent.html, + direction: processedContent.direction + }; } catch (error) { console.error('Error normalizing email content:', error); diff --git a/lib/utils/email-utils.ts b/lib/utils/email-utils.ts index 9b8f2f7d..a631b9f4 100644 --- a/lib/utils/email-utils.ts +++ b/lib/utils/email-utils.ts @@ -4,11 +4,11 @@ * This file contains all email-related utility functions: * - Content normalization * - Email formatting (replies, forwards) - * - Text direction detection + * - Text direction handling */ -// Import from centralized DOMPurify configuration instead of configuring directly -import { sanitizeHtml, getDOMPurify } from './dom-purify-config'; +// Import from centralized configuration +import { sanitizeHtml } from './dom-purify-config'; import { EmailMessage, EmailContent, @@ -17,7 +17,13 @@ import { } from '@/types/email'; import { adaptLegacyEmail } from '@/lib/utils/email-adapters'; import { decodeInfomaniakEmail, adaptMimeEmail, isMimeFormat } from './email-mime-decoder'; -import { detectTextDirection, applyTextDirection } from '@/lib/utils/text-direction'; +import { + detectTextDirection, + applyTextDirection, + extractEmailContent, + processContentWithDirection +} from '@/lib/utils/text-direction'; +import { format } from 'date-fns'; // Export the sanitizeHtml function from the centralized config export { sanitizeHtml }; @@ -32,25 +38,6 @@ export interface FormattedEmail { content: EmailContent; } -/** - * Utility type that combines EmailMessage and LegacyEmailMessage - * to allow access to properties that might exist in either type - */ -type AnyEmailMessage = { - id: string; - subject: string | undefined; - from: any; - to: any; - cc?: any; - date: any; - content?: any; - html?: string; - text?: string; - attachments?: any[]; - flags?: any; - [key: string]: any; -}; - /** * Format email addresses for display * Can handle both array of EmailAddress objects or a string @@ -130,7 +117,12 @@ export function normalizeEmailContent(email: any): EmailMessage { if (email.content && isMimeFormat(email.content)) { try { console.log('Detected MIME format email, decoding...'); - return adaptMimeEmail(email); + // 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 @@ -144,8 +136,13 @@ export function normalizeEmailContent(email: any): EmailMessage { return email as EmailMessage; } - // Otherwise, adapt from legacy format and cast to EmailMessage - return adaptLegacyEmail(email as LegacyEmailMessage) as unknown 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; } /** @@ -156,47 +153,17 @@ export function renderEmailContent(content: EmailContent | null): string { return ''; } - const safeContent = { - text: content.text || '', - html: content.html || '', - isHtml: !!content.isHtml, - direction: content.direction || 'ltr' - }; + // Use the centralized content processing function + const processed = processContentWithDirection(content); - // If we have HTML content and isHtml flag is true, use it - if (safeContent.isHtml && safeContent.html) { - return sanitizeHtml(safeContent.html); - } - - // Otherwise, convert text to HTML with proper line breaks - if (safeContent.text) { - return `
${formatPlainTextToHtml(safeContent.text)}
`; - } - - return ''; + // Return the processed HTML with proper direction + return processed.html || ''; } /** - * Format email for reply + * Get recipient addresses from an email for reply or forward */ -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 - } - }; - } - - // Cast to AnyEmailMessage for property access - const email = originalEmail as AnyEmailMessage; - +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) => { @@ -231,15 +198,28 @@ export function formatReplyEmail(originalEmail: EmailMessage | LegacyEmailMessag 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:') - ? `Re: ${email.subject}` + const subject = email.subject && !email.subject.startsWith('Re:') && !email.subject.startsWith('Fwd:') + ? email.subject : email.subject || ''; - // Format the content - const originalDate = email.date ? new Date(email.date) : new Date(); - const dateStr = originalDate.toLocaleString(); + // 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; @@ -249,6 +229,7 @@ export function formatReplyEmail(originalEmail: EmailMessage | LegacyEmailMessag ? email.from : 'Unknown Sender'; + // Format recipients const toStr = Array.isArray(email.to) ? email.to.map((addr: any) => { if (typeof addr === 'string') return addr; @@ -257,24 +238,46 @@ export function formatReplyEmail(originalEmail: EmailMessage | LegacyEmailMessag : 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 + : ''; - // Extract original content - const originalTextContent = - typeof email.content === 'object' && email.content?.text ? email.content.text : - typeof email.content === 'string' ? email.content : - email.text || ''; - - const originalHtmlContent = - typeof email.content === 'object' && email.content?.html ? email.content.html : - email.html || - (typeof email.content === 'string' && email.content.includes('<') - ? email.content - : ''); + return { fromStr, toStr, ccStr, dateStr, subject }; +} - // Get the direction from the original email - const originalDirection = - typeof email.content === 'object' && email.content?.direction ? email.content.direction : - detectTextDirection(originalTextContent); +/** + * 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 centralized utility + const { text: originalTextContent, html: originalHtmlContent } = extractEmailContent(originalEmail); // Create content with appropriate quote formatting const replyBody = ` @@ -286,12 +289,11 @@ export function formatReplyEmail(originalEmail: EmailMessage | LegacyEmailMessag `; - // Apply consistent text direction - const htmlContent = applyTextDirection(replyBody); + // Process the content with proper direction + const processed = processContentWithDirection(replyBody); // Create plain text content const textContent = ` - On ${dateStr}, ${fromStr} wrote: > ${originalTextContent.split('\n').join('\n> ')} `; @@ -299,12 +301,12 @@ On ${dateStr}, ${fromStr} wrote: return { to, cc, - subject, + subject: subject.startsWith('Re:') ? subject : `Re: ${subject}`, content: { text: textContent, - html: htmlContent, + html: processed.html, isHtml: true, - direction: 'ltr' as const // Reply is LTR, but original content keeps its direction in the blockquote + direction: processed.direction } }; } @@ -326,61 +328,11 @@ export function formatForwardedEmail(originalEmail: EmailMessage | LegacyEmailMe }; } - // Cast to AnyEmailMessage for property access - const email = originalEmail as AnyEmailMessage; + // Get header information + const { fromStr, toStr, ccStr, dateStr, subject } = getFormattedHeaderInfo(originalEmail); - // Format the subject - const subject = email.subject && !email.subject.startsWith('Fwd:') - ? `Fwd: ${email.subject}` - : email.subject || ''; - - // Format from, to, cc for the header - 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'; - - 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 - : ''; - - 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 - : ''; - - const dateStr = email.date ? new Date(email.date).toLocaleString() : 'Unknown Date'; - - // Extract original content - const originalTextContent = - typeof email.content === 'object' && email.content?.text ? email.content.text : - typeof email.content === 'string' ? email.content : - email.text || ''; - - const originalHtmlContent = - typeof email.content === 'object' && email.content?.html ? email.content.html : - email.html || - (typeof email.content === 'string' && email.content.includes('<') - ? email.content - : ''); - - // Get the direction from the original email - const originalDirection = - typeof email.content === 'object' && email.content?.direction ? email.content.direction : - detectTextDirection(originalTextContent); + // Extract content using centralized utility + const { text: originalTextContent, html: originalHtmlContent } = extractEmailContent(originalEmail); // Create forwarded content with header information const forwardBody = ` @@ -390,7 +342,7 @@ export function formatForwardedEmail(originalEmail: EmailMessage | LegacyEmailMe

---------- Forwarded message ---------

From: ${fromStr}

Date: ${dateStr}

-

Subject: ${email.subject || ''}

+

Subject: ${subject || ''}

To: ${toStr}

${ccStr ? `

Cc: ${ccStr}

` : ''}
@@ -399,8 +351,8 @@ export function formatForwardedEmail(originalEmail: EmailMessage | LegacyEmailMe
`; - // Apply consistent text direction - const htmlContent = applyTextDirection(forwardBody); + // Process the content with proper direction + const processed = processContentWithDirection(forwardBody); // Create plain text content const textContent = ` @@ -408,7 +360,7 @@ export function formatForwardedEmail(originalEmail: EmailMessage | LegacyEmailMe ---------- Forwarded message --------- From: ${fromStr} Date: ${dateStr} -Subject: ${email.subject || ''} +Subject: ${subject || ''} To: ${toStr} ${ccStr ? `Cc: ${ccStr}\n` : ''} @@ -417,12 +369,12 @@ ${originalTextContent} return { to: '', - subject, + subject: subject.startsWith('Fwd:') ? subject : `Fwd: ${subject}`, content: { text: textContent, - html: htmlContent, + html: processed.html, isHtml: true, - direction: 'ltr' as const // Forward is LTR, but original content keeps its direction + direction: 'ltr' as const } }; } @@ -440,4 +392,4 @@ export function formatEmailForReplyOrForward( } else { return formatReplyEmail(email, type as 'reply' | 'reply-all'); } -} \ No newline at end of file +} diff --git a/lib/utils/text-direction.ts b/lib/utils/text-direction.ts index ede73ccb..ec5d4ddb 100644 --- a/lib/utils/text-direction.ts +++ b/lib/utils/text-direction.ts @@ -5,6 +5,9 @@ * to ensure consistent behavior across the application. */ +import { sanitizeHtml } from './dom-purify-config'; +import { EmailContent } from '@/types/email'; + /** * Detects if text contains RTL characters and should be displayed right-to-left * Uses a comprehensive regex pattern that covers Arabic, Hebrew, and other RTL scripts @@ -61,4 +64,116 @@ export function applyTextDirection(htmlContent: string, textContent?: string): s // Otherwise, wrap the content with a direction-aware container return ``; +} + +/** + * Extracts content from various possible email formats + * Reduces duplication across the codebase for content extraction + */ +export function extractEmailContent(email: any): { text: string; html: string } { + // Default empty values + let textContent = ''; + let htmlContent = ''; + + // Extract based on common formats + if (email) { + if (typeof email.content === 'object' && email.content) { + textContent = email.content.text || ''; + htmlContent = email.content.html || ''; + } else if (typeof email.content === 'string') { + // Check if content is likely HTML + if (email.content.includes('<') && ( + email.content.includes(']*>/g, '') + .replace(/ /g, ' ') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/&/g, '&'); + } + + // Detect direction from text + const direction = detectTextDirection(textContent); + + // Sanitize HTML if present + if (htmlContent) { + // Sanitize HTML first + htmlContent = sanitizeHtml(htmlContent); + + // Then apply direction + htmlContent = applyTextDirection(htmlContent, textContent); + } else if (textContent) { + // Convert plain text to HTML with proper direction + htmlContent = `
${textContent.replace(/\n/g, '
')}
`; + } + + // Return processed content + return { + text: textContent, + html: htmlContent, + direction + }; } \ No newline at end of file