courrier preview

This commit is contained in:
alma 2025-05-01 10:06:48 +02:00
parent 636343019c
commit 0a13a74b8b
6 changed files with 38 additions and 65 deletions

View File

@ -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<EmailContentDisplayProps> = ({
// 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);

View File

@ -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<HTMLDivElement, RichTextEditorProps>(({
// 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

View File

@ -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 {

View File

@ -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

View File

@ -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 = `<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; line-height: 1.6; color: #333; max-width: 100%; overflow-x: auto; overflow-wrap: break-word; word-wrap: break-word;">${fixedContent}</div>`;
// CRITICAL FIX: Use a single wrapper with all necessary styles for better email client compatibility
return `<div class="email-content" ${dirAttribute} style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; line-height: 1.6; color: #333; max-width: 100%; overflow-x: auto; overflow-wrap: break-word; word-wrap: break-word;">${fixedContent}</div>`;
// 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, '&amp;')
@ -142,8 +111,10 @@ export function formatEmailContent(email: any): string {
.replace(/<br><\/p>/g, '</p>') // Fix any <br></p> combinations
.replace(/<p><br>/g, '<p>'); // Fix any <p><br> combinations
// CRITICAL FIX: Use consistent structure with HTML emails for better compatibility
return `<div class="email-content plain-text" ${dirAttribute} style="font-family: -apple-system, BlinkMacSystemFont, Menlo, Monaco, Consolas, 'Courier New', monospace; white-space: pre-wrap; line-height: 1.5; color: #333; padding: 15px; max-width: 100%; overflow-wrap: break-word;"><p>${formattedText}</p></div>`;
const styledPlainText = `<div style="font-family: -apple-system, BlinkMacSystemFont, Menlo, Monaco, Consolas, 'Courier New', monospace; white-space: pre-wrap; line-height: 1.5; color: #333; padding: 15px; max-width: 100%; overflow-wrap: break-word;"><p>${formattedText}</p></div>`;
// Apply correct text direction using the centralized utility
return applyTextDirection(styledPlainText, textContent);
}
// Default case: empty or unrecognized content

View File

@ -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