courrier clean 2$

This commit is contained in:
alma 2025-04-26 21:16:03 +02:00
parent 773c7759c4
commit bc5809520d
2 changed files with 64 additions and 60 deletions

View File

@ -18,6 +18,19 @@ import {
sanitizeHtml
} from '@/lib/utils/email-formatter';
/**
* CENTRAL EMAIL COMPOSER COMPONENT
*
* This is the unified, centralized email composer component used throughout the application.
* It handles new emails, replies, and forwards with proper text direction.
*
* All code that needs to compose emails should import this component from:
* @/components/email/ComposeEmail
*
* It uses the centralized email formatter from @/lib/utils/email-formatter.ts
* for consistent handling of email content and text direction.
*/
// Define EmailMessage interface locally instead of importing from server-only file
interface EmailAddress {
name: string;
@ -246,31 +259,33 @@ export default function ComposeEmail(props: ComposeEmailAllProps) {
// Handle editor input
const handleEditorInput = () => {
if (editorRef.current) {
// Store the current selection/cursor position
const selection = window.getSelection();
const range = selection?.getRangeAt(0);
const offset = range?.startOffset || 0;
const container = range?.startContainer;
if (!editorRef.current) return;
// Store the current selection/cursor position
const selection = window.getSelection();
const range = selection?.getRangeAt(0);
const offset = range?.startOffset || 0;
const container = range?.startContainer;
// Capture the content
setEmailContent(editorRef.current.innerHTML);
// Try to restore the cursor position after React updates
setTimeout(() => {
if (!selection || !range || !container || !editorRef.current) return;
// Capture the content
setEmailContent(editorRef.current.innerHTML);
// Try to restore the cursor position after React updates
setTimeout(() => {
try {
if (selection && range && container && editorRef.current && editorRef.current.contains(container)) {
const newRange = document.createRange();
newRange.setStart(container, offset);
newRange.collapse(true);
selection.removeAllRanges();
selection.addRange(newRange);
}
} catch (e) {
console.error('Error restoring cursor position:', e);
try {
if (editorRef.current.contains(container)) {
const newRange = document.createRange();
newRange.setStart(container, offset);
newRange.collapse(true);
selection.removeAllRanges();
selection.addRange(newRange);
}
}, 0);
}
} catch (e) {
console.error('Error restoring cursor position:', e);
}
}, 0);
};
// Send email
@ -402,7 +417,7 @@ export default function ComposeEmail(props: ComposeEmailAllProps) {
<label htmlFor="body" className="text-sm font-medium">Message</label>
</div>
{/* Email editor - standard LTR for English content */}
{/* Email editor - uses auto direction to detect content language */}
<div className="border rounded-md overflow-hidden">
<div
ref={editorRef}
@ -410,10 +425,9 @@ export default function ComposeEmail(props: ComposeEmailAllProps) {
className="w-full p-4 min-h-[300px] focus:outline-none"
onInput={handleEditorInput}
dangerouslySetInnerHTML={{ __html: emailContent }}
dir="ltr"
dir="auto"
style={{
textAlign: 'left',
direction: 'ltr',
textAlign: 'initial',
}}
/>
</div>

View File

@ -1,12 +1,11 @@
/**
* CENTRAL EMAIL FORMATTER
* CENTRAL EMAIL FORMATTING UTILITY
*
* This is the primary and only email formatting utility to be used.
* All email formatting should go through here to ensure consistent
* handling of text direction and HTML sanitization.
* This is the centralized email formatting utility used throughout the application.
* It provides consistent handling of email content, sanitization, and text direction.
*
* IMPORTANT: This formatter is configured for English-only content
* and enforces left-to-right text direction.
* All code that needs to format email content should import from this file.
* Text direction is standardized to LTR (left-to-right) for consistency.
*/
import DOMPurify from 'isomorphic-dompurify';
@ -16,38 +15,29 @@ DOMPurify.removeAllHooks();
// Configure DOMPurify for English-only content (always LTR)
DOMPurify.addHook('afterSanitizeAttributes', function(node) {
// Force LTR direction on all elements that can have a dir attribute
// We no longer force LTR direction on all elements
// This allows the natural text direction to be preserved
if (node instanceof HTMLElement) {
node.setAttribute('dir', 'ltr');
// Handle style attribute
if (node.hasAttribute('style')) {
let style = node.getAttribute('style') || '';
// Remove any RTL-related styles
style = style.replace(/direction\s*:\s*rtl\s*;?/gi, '');
style = style.replace(/text-align\s*:\s*right\s*;?/gi, '');
style = style.replace(/unicode-bidi\s*:[^;]*;?/gi, '');
// Add explicit LTR styles
style = style.trim();
if (style && !style.endsWith(';')) style += ';';
style += ' direction: ltr; text-align: left;';
node.setAttribute('style', style);
} else {
// If no style exists, add default LTR styles
node.setAttribute('style', 'direction: ltr; text-align: left;');
// 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 add certain attributes and forbid others
// Configure DOMPurify to preserve direction attributes
DOMPurify.setConfig({
ADD_ATTR: ['dir'],
FORBID_ATTR: ['lang', 'bidi']
ALLOWED_ATTR: ['style', 'class', 'id', 'dir']
});
// Note: We ensure LTR text direction is applied in the component level
// when rendering email content
// Interface definitions
export interface EmailAddress {
name: string;
@ -147,7 +137,7 @@ export function formatForwardedEmail(email: EmailMessage): {
const toString = formatEmailAddresses(email.to || []);
const dateString = formatEmailDate(email.date);
// Get and sanitize original content (sanitization enforces LTR)
// Get and sanitize original content (sanitization preserves content direction)
const originalContent = sanitizeHtml(email.content || email.html || email.text || '');
// Check if the content already has a forwarded message header
@ -177,7 +167,7 @@ export function formatForwardedEmail(email: EmailMessage): {
<div><b>Subject:</b> ${email.subject || ''}</div>
<div><b>To:</b> ${toString}</div>
</div>
<div class="email-original-content">
<div class="email-original-content" dir="auto">
${originalContent}
</div>
</div>
@ -223,7 +213,7 @@ export function formatReplyEmail(email: EmailMessage, type: 'reply' | 'reply-all
// Create quote header
const quoteHeader = `<div style="font-weight: 500;">On ${formattedDate}, ${fromText} wrote:</div>`;
// Get and sanitize original content (sanitization enforces LTR)
// Get and sanitize original content (sanitization preserves content direction)
const quotedContent = sanitizeHtml(email.html || email.content || email.text || '');
// Format recipients
@ -246,7 +236,7 @@ export function formatReplyEmail(email: EmailMessage, type: 'reply' | 'reply-all
<div class="reply-body">
<div class="quote-header" style="color: #555; font-size: 13px; margin: 20px 0 10px 0;">${quoteHeader}</div>
<blockquote style="margin: 0; padding: 10px 0 10px 15px; border-left: 3px solid #ddd; color: #555; background-color: #f8f8f8; border-radius: 4px;">
<div class="quoted-content" style="font-size: 13px;">
<div class="quoted-content" style="font-size: 13px;" dir="auto">
${quotedContent}
</div>
</blockquote>