diff --git a/components/email/EmailContentDisplay.tsx b/components/email/EmailContentDisplay.tsx index 3c23aa89..7f4e9c4d 100644 --- a/components/email/EmailContentDisplay.tsx +++ b/components/email/EmailContentDisplay.tsx @@ -5,6 +5,9 @@ import { EmailContent } from '@/types/email'; import { detectTextDirection, applyTextDirection } from '@/lib/utils/text-direction'; import DOMPurify from 'isomorphic-dompurify'; +// Configure DOMPurify to not interfere with our direction handling +DOMPurify.removeHooks('afterSanitizeAttributes'); + interface EmailContentDisplayProps { content: EmailContent | null | undefined; className?: string; @@ -96,7 +99,12 @@ const EmailContentDisplay: React.FC = ({ // Sanitize HTML content and apply proper direction const sanitizedHTML = useMemo(() => { - const clean = DOMPurify.sanitize(processedHTML); + // Use DOMPurify with explicit config to avoid global configuration issues + const clean = DOMPurify.sanitize(processedHTML, { + ADD_ATTR: ['dir'], // Ensure dir attributes are preserved + ALLOWED_ATTR: ['style', 'class', 'id', 'dir'], // Allow direction attributes + FORBID_TAGS: ['script', 'iframe', 'object'] // Only strip dangerous elements + }); // Apply text direction consistently using our utility return applyTextDirection(clean, safeContent.text); diff --git a/components/ui/rich-text-editor.tsx b/components/ui/rich-text-editor.tsx index 6b2c755a..99cf6186 100644 --- a/components/ui/rich-text-editor.tsx +++ b/components/ui/rich-text-editor.tsx @@ -27,6 +27,10 @@ interface RichTextEditorProps { initialDirection?: 'ltr' | 'rtl'; } +// Before using DOMPurify, configure it to not add dir attributes automatically +// This will prevent conflicts with our explicit direction handling +DOMPurify.removeHooks('afterSanitizeAttributes'); + /** * Unified rich text editor component with proper RTL support * Handles email composition with appropriate text direction detection @@ -51,8 +55,12 @@ const RichTextEditor = forwardRef(({ // Initialize editor with clean content useEffect(() => { if (internalEditorRef.current) { - // Clean the initial content - const cleanContent = DOMPurify.sanitize(initialContent); + // Clean the initial content but preserve existing dir attributes + const cleanContent = DOMPurify.sanitize(initialContent, { + ADD_ATTR: ['dir'], // Ensure dir attributes are preserved + FORBID_ATTR: ['onerror', 'onload', 'onclick'] // Only strip dangerous attributes + }); + internalEditorRef.current.innerHTML = cleanContent; // Set initial direction diff --git a/hooks/use-email-fetch.ts b/hooks/use-email-fetch.ts index 5e9c96e0..f6a4c175 100644 --- a/hooks/use-email-fetch.ts +++ b/hooks/use-email-fetch.ts @@ -100,7 +100,7 @@ export function useEmailFetch({ onEmailLoaded, onError }: UseEmailFetchProps = { // We should still keep the text version, will be extracted if needed } - // Clean HTML content if present + // Clean HTML content if present - use centralized sanitization if (htmlContent) { htmlContent = sanitizeHtml(htmlContent); } @@ -108,7 +108,7 @@ export function useEmailFetch({ onEmailLoaded, onError }: UseEmailFetchProps = { // Determine if content is HTML const isHtml = !!htmlContent; - // Detect text direction + // Detect text direction - use centralized direction detection const direction = data.content?.direction || detectTextDirection(textContent); return { diff --git a/lib/utils/email-adapters.ts b/lib/utils/email-adapters.ts index 6bdbf96e..3ec66a96 100644 --- a/lib/utils/email-adapters.ts +++ b/lib/utils/email-adapters.ts @@ -1,5 +1,4 @@ import { EmailMessage, EmailContent, EmailAddress, LegacyEmailMessage } from '@/types/email'; -import { sanitizeHtml } from './email-utils'; import { detectTextDirection } from './text-direction'; /** @@ -203,9 +202,9 @@ function normalizeContent(email: LegacyEmailMessage): EmailContent { } } - // If we have HTML content, sanitize it + // If we have HTML content, store it without sanitizing (sanitization will be done at display time) if (isHtml && htmlContent) { - normalizedContent.html = sanitizeHtml(htmlContent); + normalizedContent.html = htmlContent; } // Determine text direction diff --git a/lib/utils/email-content.ts b/lib/utils/email-content.ts index 56b61adc..947003cd 100644 --- a/lib/utils/email-content.ts +++ b/lib/utils/email-content.ts @@ -1,4 +1,6 @@ import DOMPurify from 'dompurify'; +import { detectTextDirection, applyTextDirection } from './text-direction'; +import { sanitizeHtml } from './email-utils'; /** * Format and standardize email content for display following email industry standards. @@ -72,34 +74,8 @@ export function formatEmailContent(email: any): string { } } - // CRITICAL FIX: Configure DOMPurify for maximum compatibility with email content - // This is a more permissive configuration that preserves common email HTML - const sanitizedContent = DOMPurify.sanitize(content, { - ADD_TAGS: [ - 'style', 'table', 'thead', 'tbody', 'tfoot', 'tr', 'td', 'th', - 'caption', 'col', 'colgroup', 'div', 'span', 'img', 'br', 'hr', - 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'blockquote', 'pre', - 'ul', 'ol', 'li', 'dl', 'dt', 'dd', 'a', 'b', 'i', 'u', 'em', - 'strong', 'del', 'ins', 'sub', 'sup', 'small', 'mark', 'q', - 'section', 'article', 'header', 'footer', 'aside', 'nav', 'figure', - 'figcaption', 'address', 'main', 'center', 'font' - ], - ADD_ATTR: [ - 'style', 'class', 'id', 'href', 'src', 'alt', 'title', 'width', 'height', - 'border', 'cellspacing', 'cellpadding', 'bgcolor', 'color', 'dir', 'lang', - 'align', 'valign', 'span', 'colspan', 'rowspan', 'target', 'rel', - 'background', 'data-*', 'face', 'size', 'hspace', 'vspace', - 'marginheight', 'marginwidth', 'frameborder' - ], - ALLOW_DATA_ATTR: true, - ALLOW_UNKNOWN_PROTOCOLS: true, // Allow cid: and other protocols - ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|cid|mailto|tel|callto|sms|bitcoin|data):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i, - WHOLE_DOCUMENT: false, - RETURN_DOM: false, - USE_PROFILES: { html: true, svg: false, svgFilters: false }, // Allow standard HTML - FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'form', 'input', 'textarea', 'select', 'button'], - FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover', 'onmouseout'] - }); + // Use the centralized sanitizeHtml function + const sanitizedContent = sanitizeHtml(content); // Fix common email client quirks let fixedContent = sanitizedContent @@ -112,21 +88,14 @@ export function formatEmailContent(email: any): string { return `src="data:image/png;base64,${p1.replace(/\s+/g, '')}"`; }); - // Check for RTL content and set appropriate direction - const rtlLangPattern = /[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]/; - const containsRtlText = rtlLangPattern.test(textContent); - const dirAttribute = containsRtlText ? 'dir="rtl"' : 'dir="ltr"'; + // Use the centralized text direction utility + const styledContent = `
${fixedContent}
`; - // CRITICAL FIX: Use a single wrapper with all necessary styles for better email client compatibility - return ``; + // Apply correct text direction using the centralized utility + return applyTextDirection(styledContent, textContent); } // If we only have text content, format it properly else if (textContent) { - // Check for RTL content and set appropriate direction - const rtlLangPattern = /[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]/; - const containsRtlText = rtlLangPattern.test(textContent); - const dirAttribute = containsRtlText ? 'dir="rtl"' : 'dir="ltr"'; - // Escape HTML characters to prevent XSS const escapedText = textContent .replace(/&/g, '&') @@ -142,8 +111,10 @@ export function formatEmailContent(email: any): string { .replace(/
<\/p>/g, '

') // Fix any

combinations .replace(/


/g, '

'); // Fix any


combinations - // CRITICAL FIX: Use consistent structure with HTML emails for better compatibility - return `

`; + const styledPlainText = `

${formattedText}

`; + + // Apply correct text direction using the centralized utility + return applyTextDirection(styledPlainText, textContent); } // Default case: empty or unrecognized content diff --git a/lib/utils/email-formatter.ts b/lib/utils/email-formatter.ts index 10cddddf..0eb9bd8a 100644 --- a/lib/utils/email-formatter.ts +++ b/lib/utils/email-formatter.ts @@ -38,21 +38,8 @@ function formatDateRelative(date: Date): string { // Reset any existing hooks to start clean DOMPurify.removeAllHooks(); -// Configure DOMPurify for English-only content (always LTR) -DOMPurify.addHook('afterSanitizeAttributes', function(node) { - // We no longer force LTR direction on all elements - // This allows the natural text direction to be preserved - if (node instanceof HTMLElement) { - // Only set direction if not already specified - if (!node.hasAttribute('dir')) { - // Add dir attribute only if not present - node.setAttribute('dir', 'auto'); - } - - // Don't forcibly modify text alignment or direction in style attributes - // This allows the component to control text direction instead - } -}); +// IMPORTANT: We do NOT add any hooks that modify direction attributes +// Direction will be handled explicitly by the text-direction.ts utility // Configure DOMPurify to preserve direction attributes DOMPurify.setConfig({ @@ -60,7 +47,7 @@ DOMPurify.setConfig({ ALLOWED_ATTR: ['style', 'class', 'id', 'dir'] }); -// Note: We ensure LTR text direction is applied in the component level +// Note: We ensure proper text direction is applied via the applyTextDirection utility // when rendering email content // Interface definitions