From 5686b9fb7dd45eb762397b78d273ee5f3ac1f6db Mon Sep 17 00:00:00 2001 From: alma Date: Sat, 26 Apr 2025 20:31:21 +0200 Subject: [PATCH] courrier clean 2$ --- app/courrier/page.tsx | 14 ++++---- app/globals.css | 17 ++++----- components/email/ComposeEmail.tsx | 59 ++++++++++++++++++++++++------- lib/utils/email-formatter.ts | 30 ++++++---------- 4 files changed, 72 insertions(+), 48 deletions(-) diff --git a/app/courrier/page.tsx b/app/courrier/page.tsx index 230a8a7c..ae4dddd0 100644 --- a/app/courrier/page.tsx +++ b/app/courrier/page.tsx @@ -59,7 +59,7 @@ import { formatReplyEmail, formatEmailForReplyOrForward, EmailMessage as FormatterEmailMessage, - cleanHtmlContent + sanitizeHtml } from '@/lib/utils/email-formatter'; export interface Account { @@ -168,7 +168,7 @@ function EmailContent({ email }: { email: Email }) { email.content = fullContent.content; // Render the content using the centralized cleaner - const sanitizedHtml = cleanHtmlContent(fullContent.content); + const sanitizedHtml = sanitizeHtml(fullContent.content); setContent(
')) { // Content is likely HTML, sanitize using the centralized cleaner - const sanitizedHtml = cleanHtmlContent(formattedEmail); + const sanitizedHtml = sanitizeHtml(formattedEmail); setContent(
')) { @@ -453,15 +453,15 @@ function EmailPreview({ email }: { email: Email }) { try { // If we have the content already, extract preview from it if (email.content) { - // Use cleanHtmlContent to safely extract text from HTML - const cleanContent = cleanHtmlContent(email.content); + // Use sanitizeHtml to safely extract text from HTML + const cleanContent = sanitizeHtml(email.content); const plainText = cleanContent.replace(/<[^>]*>/g, ' ').trim(); if (mounted) { setPreview(plainText.substring(0, 150) + '...'); } } else { // Use the centralized cleaner instead of decodeEmail - const cleanContent = cleanHtmlContent(email.content || ''); + const cleanContent = sanitizeHtml(email.content || ''); const plainText = cleanContent.replace(/<[^>]*>/g, ' ').trim(); if (mounted) { diff --git a/app/globals.css b/app/globals.css index b870f517..4b2d1293 100644 --- a/app/globals.css +++ b/app/globals.css @@ -88,7 +88,15 @@ .email-content .header { margin-bottom: 1em; } .email-content .footer { font-size: 0.875rem; color: #6b7280; margin-top: 1em; } -/* Force email content direction */ +/* Email editor styles */ +.email-editor { + /* Allow text direction to be controlled by component */ + direction: inherit; + unicode-bidi: isolate; + text-align: inherit; +} + +/* Email content wrapper should still force LTR for quoted content */ .email-content-wrapper { direction: ltr !important; unicode-bidi: isolate !important; @@ -101,10 +109,3 @@ text-align: left !important; } -/* Email editor styles */ -.email-editor { - direction: ltr !important; - unicode-bidi: isolate !important; - text-align: left !important; -} - diff --git a/components/email/ComposeEmail.tsx b/components/email/ComposeEmail.tsx index 432af0bf..68d90eae 100644 --- a/components/email/ComposeEmail.tsx +++ b/components/email/ComposeEmail.tsx @@ -16,7 +16,8 @@ import { formatForwardedEmail, formatReplyEmail, formatEmailForReplyOrForward, - EmailMessage as FormatterEmailMessage + EmailMessage as FormatterEmailMessage, + sanitizeHtml } from '@/lib/utils/email-formatter'; // Define EmailMessage interface locally instead of importing from server-only file @@ -87,6 +88,7 @@ interface LegacyComposeEmailProps { interface ComposeEmailProps { initialEmail?: EmailMessage | null; type?: 'new' | 'reply' | 'reply-all' | 'forward'; + initialRTL?: boolean; onClose: () => void; onSend: (emailData: { to: string; @@ -110,14 +112,6 @@ function isLegacyProps(props: ComposeEmailAllProps): props is LegacyComposeEmail return 'showCompose' in props && 'setShowCompose' in props; } -// Configure DOMPurify to preserve certain attributes -DOMPurify.addHook('afterSanitizeAttributes', function(node) { - // Preserve direction attributes - if (node.hasAttribute('dir')) { - node.setAttribute('dir', node.getAttribute('dir') || 'ltr'); - } -}); - export default function ComposeEmail(props: ComposeEmailAllProps) { // Handle legacy props by adapting them to new component if (isLegacyProps(props)) { @@ -125,7 +119,7 @@ export default function ComposeEmail(props: ComposeEmailAllProps) { } // Continue with modern implementation for new props - const { initialEmail, type = 'new', onClose, onSend } = props; + const { initialEmail, type = 'new', initialRTL, onClose, onSend } = props; // Email form state const [to, setTo] = useState(''); @@ -136,7 +130,7 @@ export default function ComposeEmail(props: ComposeEmailAllProps) { const [showCc, setShowCc] = useState(false); const [showBcc, setShowBcc] = useState(false); const [sending, setSending] = useState(false); - const [isRTL, setIsRTL] = useState(false); + const [isRTL, setIsRTL] = useState(initialRTL || false); const [attachments, setAttachments] = useState(null); const attachmentInputRef = useRef(null); + // Initialize RTL state from prop if provided + useEffect(() => { + if (initialRTL !== undefined) { + setIsRTL(initialRTL); + } + }, [initialRTL]); + // Initialize the form when replying to or forwarding an email useEffect(() => { if (initialEmail && type !== 'new') { @@ -264,7 +265,19 @@ export default function ComposeEmail(props: ComposeEmailAllProps) { // Toggle text direction for the entire editor const toggleTextDirection = () => { - setIsRTL(!isRTL); + // Toggle the RTL state + const newRTL = !isRTL; + setIsRTL(newRTL); + + // Apply the direction to the editor content immediately + if (editorRef.current) { + // Preserve the content but update the direction attributes + editorRef.current.dir = newRTL ? 'rtl' : 'ltr'; + editorRef.current.style.textAlign = newRTL ? 'right' : 'left'; + editorRef.current.style.direction = newRTL ? 'rtl' : 'ltr'; + + // No need to modify the content, just let the user edit with proper directionality + } }; // Send email without modifying pre-formatted content @@ -418,7 +431,9 @@ export default function ComposeEmail(props: ComposeEmailAllProps) { dangerouslySetInnerHTML={{ __html: emailContent }} dir={isRTL ? 'rtl' : 'ltr'} style={{ - textAlign: isRTL ? 'right' : 'left' + textAlign: isRTL ? 'right' : 'left', + direction: isRTL ? 'rtl' : 'ltr', + unicodeBidi: 'isolate' }} />
@@ -514,6 +529,23 @@ function LegacyAdapter({ replyTo, forwardFrom }: LegacyComposeEmailProps) { + // Check if the user has RTL preference + const [preferRTL, setPreferRTL] = useState(false); + + // Detect RTL content in body on initialization + useEffect(() => { + if (!composeBody) return; + + // Better RTL detection based on common RTL languages (Arabic, Hebrew, Persian, Urdu, etc.) + const rtlRegex = /[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]/; + + // If there are RTL characters, set preferRTL to true + if (rtlRegex.test(composeBody)) { + setPreferRTL(true); + console.log('RTL text detected - setting editor to RTL mode'); + } + }, [composeBody]); + // Determine the type from the original email or subject const determineType = (): 'new' | 'reply' | 'reply-all' | 'forward' => { if (originalEmail) { @@ -581,6 +613,7 @@ function LegacyAdapter({ { onCancel?.(); setShowCompose(false); diff --git a/lib/utils/email-formatter.ts b/lib/utils/email-formatter.ts index 364f9cac..c52b1bb5 100644 --- a/lib/utils/email-formatter.ts +++ b/lib/utils/email-formatter.ts @@ -93,25 +93,15 @@ export function formatEmailDate(date: Date | string | undefined): string { } /** - * Clean HTML content to prevent RTL/LTR issues - * This is the ONLY function that should be used for cleaning HTML content + * Sanitize HTML content before processing or displaying + * @param content HTML content to sanitize + * @returns Sanitized HTML */ -export function cleanHtmlContent(content: string): string { +export function sanitizeHtml(content: string): string { if (!content) return ''; - // First sanitize the HTML with our configured DOMPurify - const sanitized = DOMPurify.sanitize(content); - - // Process content to ensure consistent direction - let processed = sanitized; - - // Replace RTL attributes with LTR if needed - // We're now more careful to only modify direction attributes if needed - processed = processed.replace(/dir\s*=\s*["']rtl["']/gi, 'dir="ltr"'); - processed = processed.replace(/style\s*=\s*["']([^"']*)direction\s*:\s*rtl;?([^"']*)["']/gi, - (match, before, after) => `style="${before}direction: ltr;${after}"`); - - return processed; + // Sanitize the HTML using our configured DOMPurify + return DOMPurify.sanitize(content); } /** @@ -133,8 +123,8 @@ export function formatForwardedEmail(email: EmailMessage): { const toString = formatEmailAddresses(email.to || []); const dateString = formatEmailDate(email.date); - // Get and clean original content - const originalContent = cleanHtmlContent(email.content || email.html || email.text || ''); + // Get and sanitize original content + const originalContent = sanitizeHtml(email.content || email.html || email.text || ''); // Check if the content already has a forwarded message header const hasExistingHeader = originalContent.includes('---------- Forwarded message ---------'); @@ -209,8 +199,8 @@ export function formatReplyEmail(email: EmailMessage, type: 'reply' | 'reply-all // Create quote header const quoteHeader = `
On ${formattedDate}, ${fromText} wrote:
`; - // Get and clean original content - const quotedContent = cleanHtmlContent(email.html || email.content || email.text || ''); + // Get and sanitize original content + const quotedContent = sanitizeHtml(email.html || email.content || email.text || ''); // Format recipients let to = formatEmailAddresses(email.from || []);