courrier preview
This commit is contained in:
parent
636343019c
commit
0a13a74b8b
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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, '&')
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user