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 { detectTextDirection, applyTextDirection } from '@/lib/utils/text-direction';
|
||||||
import DOMPurify from 'isomorphic-dompurify';
|
import DOMPurify from 'isomorphic-dompurify';
|
||||||
|
|
||||||
|
// Configure DOMPurify to not interfere with our direction handling
|
||||||
|
DOMPurify.removeHooks('afterSanitizeAttributes');
|
||||||
|
|
||||||
interface EmailContentDisplayProps {
|
interface EmailContentDisplayProps {
|
||||||
content: EmailContent | null | undefined;
|
content: EmailContent | null | undefined;
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -96,7 +99,12 @@ const EmailContentDisplay: React.FC<EmailContentDisplayProps> = ({
|
|||||||
|
|
||||||
// Sanitize HTML content and apply proper direction
|
// Sanitize HTML content and apply proper direction
|
||||||
const sanitizedHTML = useMemo(() => {
|
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
|
// Apply text direction consistently using our utility
|
||||||
return applyTextDirection(clean, safeContent.text);
|
return applyTextDirection(clean, safeContent.text);
|
||||||
|
|||||||
@ -27,6 +27,10 @@ interface RichTextEditorProps {
|
|||||||
initialDirection?: 'ltr' | 'rtl';
|
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
|
* Unified rich text editor component with proper RTL support
|
||||||
* Handles email composition with appropriate text direction detection
|
* Handles email composition with appropriate text direction detection
|
||||||
@ -51,8 +55,12 @@ const RichTextEditor = forwardRef<HTMLDivElement, RichTextEditorProps>(({
|
|||||||
// Initialize editor with clean content
|
// Initialize editor with clean content
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (internalEditorRef.current) {
|
if (internalEditorRef.current) {
|
||||||
// Clean the initial content
|
// Clean the initial content but preserve existing dir attributes
|
||||||
const cleanContent = DOMPurify.sanitize(initialContent);
|
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;
|
internalEditorRef.current.innerHTML = cleanContent;
|
||||||
|
|
||||||
// Set initial direction
|
// 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
|
// 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) {
|
if (htmlContent) {
|
||||||
htmlContent = sanitizeHtml(htmlContent);
|
htmlContent = sanitizeHtml(htmlContent);
|
||||||
}
|
}
|
||||||
@ -108,7 +108,7 @@ export function useEmailFetch({ onEmailLoaded, onError }: UseEmailFetchProps = {
|
|||||||
// Determine if content is HTML
|
// Determine if content is HTML
|
||||||
const isHtml = !!htmlContent;
|
const isHtml = !!htmlContent;
|
||||||
|
|
||||||
// Detect text direction
|
// Detect text direction - use centralized direction detection
|
||||||
const direction = data.content?.direction || detectTextDirection(textContent);
|
const direction = data.content?.direction || detectTextDirection(textContent);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { EmailMessage, EmailContent, EmailAddress, LegacyEmailMessage } from '@/types/email';
|
import { EmailMessage, EmailContent, EmailAddress, LegacyEmailMessage } from '@/types/email';
|
||||||
import { sanitizeHtml } from './email-utils';
|
|
||||||
import { detectTextDirection } from './text-direction';
|
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) {
|
if (isHtml && htmlContent) {
|
||||||
normalizedContent.html = sanitizeHtml(htmlContent);
|
normalizedContent.html = htmlContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine text direction
|
// Determine text direction
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
import DOMPurify from 'dompurify';
|
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.
|
* 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
|
// Use the centralized sanitizeHtml function
|
||||||
// This is a more permissive configuration that preserves common email HTML
|
const sanitizedContent = sanitizeHtml(content);
|
||||||
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']
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fix common email client quirks
|
// Fix common email client quirks
|
||||||
let fixedContent = sanitizedContent
|
let fixedContent = sanitizedContent
|
||||||
@ -112,21 +88,14 @@ export function formatEmailContent(email: any): string {
|
|||||||
return `src="data:image/png;base64,${p1.replace(/\s+/g, '')}"`;
|
return `src="data:image/png;base64,${p1.replace(/\s+/g, '')}"`;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check for RTL content and set appropriate direction
|
// Use the centralized text direction utility
|
||||||
const rtlLangPattern = /[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]/;
|
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>`;
|
||||||
const containsRtlText = rtlLangPattern.test(textContent);
|
|
||||||
const dirAttribute = containsRtlText ? 'dir="rtl"' : 'dir="ltr"';
|
|
||||||
|
|
||||||
// CRITICAL FIX: Use a single wrapper with all necessary styles for better email client compatibility
|
// Apply correct text direction using the centralized utility
|
||||||
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>`;
|
return applyTextDirection(styledContent, textContent);
|
||||||
}
|
}
|
||||||
// If we only have text content, format it properly
|
// If we only have text content, format it properly
|
||||||
else if (textContent) {
|
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
|
// Escape HTML characters to prevent XSS
|
||||||
const escapedText = textContent
|
const escapedText = textContent
|
||||||
.replace(/&/g, '&')
|
.replace(/&/g, '&')
|
||||||
@ -142,8 +111,10 @@ export function formatEmailContent(email: any): string {
|
|||||||
.replace(/<br><\/p>/g, '</p>') // Fix any <br></p> combinations
|
.replace(/<br><\/p>/g, '</p>') // Fix any <br></p> combinations
|
||||||
.replace(/<p><br>/g, '<p>'); // Fix any <p><br> combinations
|
.replace(/<p><br>/g, '<p>'); // Fix any <p><br> combinations
|
||||||
|
|
||||||
// CRITICAL FIX: Use consistent structure with HTML emails for better compatibility
|
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>`;
|
||||||
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>`;
|
|
||||||
|
// Apply correct text direction using the centralized utility
|
||||||
|
return applyTextDirection(styledPlainText, textContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default case: empty or unrecognized content
|
// Default case: empty or unrecognized content
|
||||||
|
|||||||
@ -38,21 +38,8 @@ function formatDateRelative(date: Date): string {
|
|||||||
// Reset any existing hooks to start clean
|
// Reset any existing hooks to start clean
|
||||||
DOMPurify.removeAllHooks();
|
DOMPurify.removeAllHooks();
|
||||||
|
|
||||||
// Configure DOMPurify for English-only content (always LTR)
|
// IMPORTANT: We do NOT add any hooks that modify direction attributes
|
||||||
DOMPurify.addHook('afterSanitizeAttributes', function(node) {
|
// Direction will be handled explicitly by the text-direction.ts utility
|
||||||
// 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
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Configure DOMPurify to preserve direction attributes
|
// Configure DOMPurify to preserve direction attributes
|
||||||
DOMPurify.setConfig({
|
DOMPurify.setConfig({
|
||||||
@ -60,7 +47,7 @@ DOMPurify.setConfig({
|
|||||||
ALLOWED_ATTR: ['style', 'class', 'id', 'dir']
|
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
|
// when rendering email content
|
||||||
|
|
||||||
// Interface definitions
|
// Interface definitions
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user