courrier clean 2$

This commit is contained in:
alma 2025-04-26 20:31:21 +02:00
parent 0bb4fac9f9
commit 5686b9fb7d
4 changed files with 72 additions and 48 deletions

View File

@ -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(
<div
className="email-content prose prose-sm max-w-none dark:prose-invert"
@ -199,7 +199,7 @@ function EmailContent({ email }: { email: Email }) {
// Check if content is already HTML
if (formattedEmail.startsWith('<') && formattedEmail.endsWith('>')) {
// Content is likely HTML, sanitize using the centralized cleaner
const sanitizedHtml = cleanHtmlContent(formattedEmail);
const sanitizedHtml = sanitizeHtml(formattedEmail);
setContent(
<div
className="email-content prose prose-sm max-w-none dark:prose-invert"
@ -209,7 +209,7 @@ function EmailContent({ email }: { email: Email }) {
setDebugInfo('Rendered existing HTML content with centralized formatter');
} else {
// For plain text or complex formats, use the centralized formatter
const cleanedContent = cleanHtmlContent(formattedEmail);
const cleanedContent = sanitizeHtml(formattedEmail);
// If it looks like HTML, render it as HTML
if (cleanedContent.includes('<') && cleanedContent.includes('>')) {
@ -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) {

View File

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

View File

@ -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<string>('');
@ -136,7 +130,7 @@ export default function ComposeEmail(props: ComposeEmailAllProps) {
const [showCc, setShowCc] = useState<boolean>(false);
const [showBcc, setShowBcc] = useState<boolean>(false);
const [sending, setSending] = useState<boolean>(false);
const [isRTL, setIsRTL] = useState<boolean>(false);
const [isRTL, setIsRTL] = useState<boolean>(initialRTL || false);
const [attachments, setAttachments] = useState<Array<{
name: string;
content: string;
@ -147,6 +141,13 @@ export default function ComposeEmail(props: ComposeEmailAllProps) {
const editorRef = useRef<HTMLDivElement>(null);
const attachmentInputRef = useRef<HTMLInputElement>(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'
}}
/>
</div>
@ -514,6 +529,23 @@ function LegacyAdapter({
replyTo,
forwardFrom
}: LegacyComposeEmailProps) {
// Check if the user has RTL preference
const [preferRTL, setPreferRTL] = useState<boolean>(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({
<ComposeEmail
initialEmail={emailForCompose}
type={type}
initialRTL={preferRTL}
onClose={() => {
onCancel?.();
setShowCompose(false);

View File

@ -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 = `<div style="font-weight: 500; direction: ltr; text-align: left;" dir="ltr">On ${formattedDate}, ${fromText} wrote:</div>`;
// 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 || []);