courrier preview
This commit is contained in:
parent
193a265109
commit
d88fc133d2
@ -352,7 +352,13 @@ export default function ComposeEmail(props: ComposeEmailProps) {
|
||||
ref={editorRef}
|
||||
initialContent={emailContent}
|
||||
initialDirection={detectTextDirection(emailContent)}
|
||||
onChange={setEmailContent}
|
||||
onChange={(html) => {
|
||||
// Store the content
|
||||
setEmailContent(html);
|
||||
|
||||
// But don't update direction on every keystroke
|
||||
// The RichTextEditor will handle direction changes internally
|
||||
}}
|
||||
className="min-h-[320px] border rounded-md bg-white text-gray-800 flex-1"
|
||||
placeholder="Write your message here..."
|
||||
/>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import { EmailContent } from '@/types/email';
|
||||
import { detectTextDirection } from '@/lib/utils/text-direction';
|
||||
import { detectTextDirection, applyTextDirection } from '@/lib/utils/text-direction';
|
||||
import DOMPurify from 'isomorphic-dompurify';
|
||||
|
||||
interface EmailContentDisplayProps {
|
||||
@ -94,13 +94,16 @@ const EmailContentDisplay: React.FC<EmailContentDisplayProps> = ({
|
||||
return htmlToDisplay;
|
||||
}, [htmlToDisplay, showQuotedText]);
|
||||
|
||||
// Sanitize HTML content before rendering
|
||||
// Sanitize HTML content and apply proper direction
|
||||
const sanitizedHTML = useMemo(() => {
|
||||
return DOMPurify.sanitize(processedHTML);
|
||||
}, [processedHTML]);
|
||||
const clean = DOMPurify.sanitize(processedHTML);
|
||||
|
||||
// Apply text direction consistently using our utility
|
||||
return applyTextDirection(clean, safeContent.text);
|
||||
}, [processedHTML, safeContent.text]);
|
||||
|
||||
return (
|
||||
<div className={`email-content-display ${className}`} dir={safeContent.direction}>
|
||||
<div className={`email-content-display ${className}`}>
|
||||
<div
|
||||
className="email-content-inner"
|
||||
dangerouslySetInnerHTML={{ __html: sanitizedHTML }}
|
||||
@ -121,10 +124,6 @@ const EmailContentDisplay: React.FC<EmailContentDisplayProps> = ({
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.email-content-display[dir="rtl"] {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.email-content-inner img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
@ -139,7 +138,8 @@ const EmailContentDisplay: React.FC<EmailContentDisplayProps> = ({
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.email-content-display[dir="rtl"] .email-content-inner blockquote {
|
||||
/* RTL blockquote styling will be handled by the direction attribute now */
|
||||
[dir="rtl"] blockquote {
|
||||
padding-left: 0;
|
||||
padding-right: 15px;
|
||||
border-left: none;
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { EmailMessage, EmailContent, EmailAddress, LegacyEmailMessage } from '@/types/email';
|
||||
import { sanitizeHtml } from './email-utils';
|
||||
import { detectTextDirection } from './text-direction';
|
||||
|
||||
/**
|
||||
* Adapts a legacy email format to the standardized EmailMessage format
|
||||
@ -224,17 +225,7 @@ function normalizeContent(email: LegacyEmailMessage): EmailContent {
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects the text direction (LTR or RTL) based on the content
|
||||
*/
|
||||
function detectTextDirection(text: string): 'ltr' | 'rtl' {
|
||||
// Simple RTL detection for common RTL languages
|
||||
// This is a basic implementation and can be enhanced
|
||||
const rtlChars = /[\u0591-\u07FF\u200F\u202B\u202E\uFB1D-\uFDFD\uFE70-\uFEFC]/;
|
||||
return rtlChars.test(text) ? 'rtl' : 'ltr';
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes email addresses to the EmailAddress format
|
||||
* Normalizes addresses to EmailAddress objects
|
||||
*/
|
||||
function normalizeAddresses(addresses: string | EmailAddress[] | undefined): EmailAddress[] {
|
||||
if (!addresses) {
|
||||
|
||||
@ -9,6 +9,8 @@
|
||||
*/
|
||||
|
||||
import DOMPurify from 'isomorphic-dompurify';
|
||||
import { sanitizeHtml } from './email-utils';
|
||||
import { applyTextDirection } from './text-direction';
|
||||
// Instead of importing, implement the formatDateRelative function directly
|
||||
// import { formatDateRelative } from './date-formatter';
|
||||
|
||||
@ -133,59 +135,6 @@ export function formatEmailDate(date: Date | string | undefined): string {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize HTML content before processing or displaying
|
||||
* Implements email industry standards for proper, consistent, and secure rendering
|
||||
*
|
||||
* @param html HTML content to sanitize
|
||||
* @returns Sanitized HTML with preserved styling and structure
|
||||
*/
|
||||
export function sanitizeHtml(html: string): string {
|
||||
if (!html) return '';
|
||||
|
||||
try {
|
||||
// Use DOMPurify with comprehensive email HTML standards
|
||||
const clean = DOMPurify.sanitize(html, {
|
||||
ADD_TAGS: [
|
||||
'html', 'head', 'body', 'style', 'link', 'meta', 'title',
|
||||
'table', 'caption', 'col', 'colgroup', 'thead', 'tbody', 'tfoot', 'tr', 'td', 'th',
|
||||
'div', 'span', 'img', 'br', 'hr', 'section', 'article', 'header', 'footer',
|
||||
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'blockquote', 'pre', 'code',
|
||||
'ul', 'ol', 'li', 'dl', 'dt', 'dd', 'a', 'b', 'i', 'u', 'em',
|
||||
'strong', 'del', 'ins', 'mark', 'small', 'sub', 'sup', 'q', 'abbr'
|
||||
],
|
||||
ADD_ATTR: [
|
||||
'style', 'class', 'id', 'name', 'href', 'src', 'alt', 'title', 'width', 'height',
|
||||
'border', 'cellspacing', 'cellpadding', 'bgcolor', 'background', 'color',
|
||||
'align', 'valign', 'dir', 'lang', 'target', 'rel', 'charset', 'media',
|
||||
'colspan', 'rowspan', 'scope', 'span', 'size', 'face', 'hspace', 'vspace',
|
||||
'data-*'
|
||||
],
|
||||
KEEP_CONTENT: true,
|
||||
WHOLE_DOCUMENT: false,
|
||||
ALLOW_DATA_ATTR: true,
|
||||
ALLOW_UNKNOWN_PROTOCOLS: true, // Needed for some email clients
|
||||
FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'form', 'input', 'button', 'select', 'textarea'],
|
||||
FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover', 'onmouseout'],
|
||||
FORCE_BODY: false
|
||||
});
|
||||
|
||||
// Fix common email rendering issues
|
||||
return clean
|
||||
// Fix for Outlook WebVML content
|
||||
.replace(/<!--\[if\s+gte\s+mso/g, '<!--[if gte mso')
|
||||
// Fix for broken image paths that might be relative
|
||||
.replace(/(src|background)="(?!http|data|https|cid)/gi, '$1="https://');
|
||||
} catch (e) {
|
||||
console.error('Error sanitizing HTML:', e);
|
||||
// Fall back to a basic sanitization approach
|
||||
return html
|
||||
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
|
||||
.replace(/on\w+="[^"]*"/g, '')
|
||||
.replace(/(javascript|jscript|vbscript|mocha):/gi, 'removed:');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format an email for forwarding - CENTRAL IMPLEMENTATION
|
||||
* All other formatting functions should be deprecated in favor of this one
|
||||
@ -423,46 +372,58 @@ export function encodeComposeContent(content: string): string {
|
||||
.join('\n') + '\n\n' + content;
|
||||
}
|
||||
|
||||
// Legacy email formatter functions - renamed to avoid conflicts
|
||||
/**
|
||||
* COMPATIBILITY LAYER: For backward compatibility with the old email-formatter.ts
|
||||
* Use the newer formatReplyEmail function instead when possible.
|
||||
*
|
||||
* @deprecated Use formatReplyEmail instead
|
||||
*/
|
||||
export function formatReplyEmailLegacy(email: any): string {
|
||||
const originalSender = email.sender?.name || email.sender?.email || 'Unknown Sender';
|
||||
const originalDate = formatDateRelative(new Date(email.date));
|
||||
|
||||
// Use our own sanitizeHtml function consistently
|
||||
const sanitizedBody = sanitizeHtml(email.content || '');
|
||||
|
||||
return `
|
||||
<p></p>
|
||||
<p>On ${originalDate}, ${originalSender} wrote:</p>
|
||||
<blockquote class="quoted-content">
|
||||
${sanitizedBody}
|
||||
</blockquote>
|
||||
`.trim();
|
||||
// Format the reply with consistent direction handling
|
||||
const replyContent = `
|
||||
<br/>
|
||||
<br/>
|
||||
<blockquote>
|
||||
On ${email.date}, ${email.from} wrote:
|
||||
<br/>
|
||||
${sanitizedBody}
|
||||
</blockquote>
|
||||
`;
|
||||
|
||||
// Apply consistent text direction
|
||||
return applyTextDirection(replyContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* COMPATIBILITY LAYER: For backward compatibility with the old email-formatter.ts
|
||||
* Use the newer formatForwardedEmail function instead when possible.
|
||||
*
|
||||
* @deprecated Use formatForwardedEmail instead
|
||||
*/
|
||||
export function formatForwardedEmailLegacy(email: any): string {
|
||||
const originalSender = email.sender?.name || email.sender?.email || 'Unknown Sender';
|
||||
const originalRecipients = email.to?.map((recipient: any) =>
|
||||
recipient.name || recipient.email
|
||||
).join(', ') || 'Unknown Recipients';
|
||||
const originalDate = formatDateRelative(new Date(email.date));
|
||||
const originalSubject = email.subject || 'No Subject';
|
||||
|
||||
// Use our own sanitizeHtml function consistently
|
||||
const sanitizedBody = sanitizeHtml(email.content || '');
|
||||
|
||||
return `
|
||||
<p></p>
|
||||
<p>---------- Forwarded message ---------</p>
|
||||
<p><strong>From:</strong> ${originalSender}</p>
|
||||
<p><strong>Date:</strong> ${originalDate}</p>
|
||||
<p><strong>Subject:</strong> ${originalSubject}</p>
|
||||
<p><strong>To:</strong> ${originalRecipients}</p>
|
||||
<br>
|
||||
<div class="email-original-content">
|
||||
${sanitizedBody}
|
||||
</div>
|
||||
`.trim();
|
||||
// Format the forwarded content with consistent direction handling
|
||||
const forwardedContent = `
|
||||
<br/>
|
||||
<br/>
|
||||
<div>
|
||||
---------- Forwarded message ---------<br/>
|
||||
From: ${email.from}<br/>
|
||||
Date: ${email.date}<br/>
|
||||
Subject: ${email.subject}<br/>
|
||||
To: ${email.to}<br/>
|
||||
<br/>
|
||||
${sanitizedBody}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Apply consistent text direction
|
||||
return applyTextDirection(forwardedContent);
|
||||
}
|
||||
|
||||
export function formatReplyToAllEmail(email: any): string {
|
||||
|
||||
@ -17,21 +17,12 @@ import {
|
||||
} from '@/types/email';
|
||||
import { adaptLegacyEmail } from '@/lib/utils/email-adapters';
|
||||
import { decodeInfomaniakEmail, adaptMimeEmail, isMimeFormat } from './email-mime-decoder';
|
||||
import { detectTextDirection } from '@/lib/utils/text-direction';
|
||||
import { detectTextDirection, applyTextDirection } from '@/lib/utils/text-direction';
|
||||
|
||||
// Reset any existing hooks to start clean
|
||||
DOMPurify.removeAllHooks();
|
||||
|
||||
// Configure DOMPurify for auto text direction
|
||||
DOMPurify.addHook('afterSanitizeAttributes', function(node) {
|
||||
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');
|
||||
}
|
||||
}
|
||||
});
|
||||
// Remove the hook that adds dir="auto" - we'll handle direction explicitly instead
|
||||
|
||||
// Configure DOMPurify to preserve direction attributes
|
||||
DOMPurify.setConfig({
|
||||
@ -119,11 +110,14 @@ export function sanitizeHtml(html: string): string {
|
||||
});
|
||||
|
||||
// Fix common email rendering issues
|
||||
return clean
|
||||
const fixedHtml = clean
|
||||
// Fix for Outlook WebVML content
|
||||
.replace(/<!--\[if\s+gte\s+mso/g, '<!--[if gte mso')
|
||||
// Fix for broken image paths that might be relative
|
||||
.replace(/(src|background)="(?!http|data|https|cid)/gi, '$1="https://');
|
||||
|
||||
// We don't manually add direction here anymore - applyTextDirection will handle it
|
||||
return fixedHtml;
|
||||
} catch (e) {
|
||||
console.error('Error sanitizing HTML:', e);
|
||||
// Fall back to a basic sanitization approach
|
||||
@ -190,30 +184,34 @@ export function normalizeEmailContent(email: any): EmailMessage {
|
||||
/**
|
||||
* Render normalized email content into HTML for display
|
||||
*/
|
||||
export function renderEmailContent(content: EmailContent): string {
|
||||
console.log('renderEmailContent received:', JSON.stringify(content, null, 2));
|
||||
|
||||
export function renderEmailContent(content: EmailContent | null): string {
|
||||
if (!content) {
|
||||
console.log('No content provided to renderEmailContent');
|
||||
return '<div class="email-content-empty">No content available</div>';
|
||||
}
|
||||
|
||||
const safeContent = {
|
||||
text: content.text || '',
|
||||
html: content.html,
|
||||
isHtml: content.isHtml,
|
||||
direction: content.direction || 'ltr'
|
||||
};
|
||||
|
||||
// If we have HTML content and isHtml flag is true, use it
|
||||
if (content.isHtml && content.html) {
|
||||
console.log('Rendering HTML content, length:', content.html.length);
|
||||
return `<div class="email-content" dir="${content.direction || 'ltr'}">${content.html}</div>`;
|
||||
if (safeContent.isHtml && safeContent.html) {
|
||||
// Apply text direction consistently using the utility
|
||||
return applyTextDirection(safeContent.html, safeContent.text);
|
||||
}
|
||||
|
||||
// Otherwise, format the text content with basic HTML
|
||||
const text = content.text || '';
|
||||
console.log('Rendering text content, length:', text.length);
|
||||
const text = safeContent.text;
|
||||
const formattedText = text
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/\n/g, '<br>');
|
||||
|
||||
return `<div class="email-content plain-text" dir="${content.direction || 'ltr'}">${formattedText}</div>`;
|
||||
// Apply text direction consistently
|
||||
return applyTextDirection(formattedText, text);
|
||||
}
|
||||
|
||||
// Add interface for email formatting functions
|
||||
@ -305,33 +303,35 @@ export function formatReplyEmail(originalEmail: EmailMessage | LegacyEmailMessag
|
||||
|
||||
// Extract original content
|
||||
const originalTextContent =
|
||||
originalEmail.content?.text ||
|
||||
(typeof originalEmail.content === 'string' ? originalEmail.content : '');
|
||||
typeof originalEmail?.content === 'object' ? originalEmail.content.text :
|
||||
typeof originalEmail?.content === 'string' ? originalEmail.content :
|
||||
originalEmail?.text || '';
|
||||
|
||||
const originalHtmlContent =
|
||||
originalEmail.content?.html ||
|
||||
originalEmail.html ||
|
||||
(typeof originalEmail.content === 'string' && originalEmail.content.includes('<')
|
||||
typeof originalEmail?.content === 'object' ? originalEmail.content.html :
|
||||
originalEmail?.html ||
|
||||
(typeof originalEmail?.content === 'string' && originalEmail?.content.includes('<')
|
||||
? originalEmail.content
|
||||
: '');
|
||||
|
||||
// Get the direction from the original email
|
||||
const originalDirection =
|
||||
originalEmail.content?.direction ||
|
||||
(originalTextContent ? detectTextDirection(originalTextContent) : 'ltr');
|
||||
typeof originalEmail?.content === 'object' ? originalEmail.content.direction :
|
||||
detectTextDirection(originalTextContent);
|
||||
|
||||
// Create HTML content that preserves the directionality
|
||||
const htmlContent = `
|
||||
// Create content with appropriate quote formatting
|
||||
const replyBody = `
|
||||
<br/>
|
||||
<br/>
|
||||
<div class="email-original-content" dir="${originalDirection}">
|
||||
<blockquote style="border-left: 2px solid #ddd; padding-left: 10px; margin: 10px 0; color: #505050;">
|
||||
<p>On ${dateStr}, ${fromStr} wrote:</p>
|
||||
${originalHtmlContent || originalTextContent.replace(/\n/g, '<br>')}
|
||||
</blockquote>
|
||||
</div>
|
||||
<blockquote style="border-left: 2px solid #ddd; padding-left: 10px; margin: 10px 0; color: #505050;">
|
||||
<p>On ${dateStr}, ${fromStr} wrote:</p>
|
||||
${originalHtmlContent || originalTextContent.replace(/\n/g, '<br>')}
|
||||
</blockquote>
|
||||
`;
|
||||
|
||||
// Apply consistent text direction
|
||||
const htmlContent = applyTextDirection(replyBody);
|
||||
|
||||
// Create plain text content
|
||||
const textContent = `
|
||||
|
||||
@ -406,45 +406,49 @@ export function formatForwardedEmail(originalEmail: EmailMessage | LegacyEmailMe
|
||||
|
||||
// Extract original content
|
||||
const originalTextContent =
|
||||
originalEmail.content?.text ||
|
||||
(typeof originalEmail.content === 'string' ? originalEmail.content : '');
|
||||
typeof originalEmail?.content === 'object' ? originalEmail.content.text :
|
||||
typeof originalEmail?.content === 'string' ? originalEmail.content :
|
||||
originalEmail?.text || '';
|
||||
|
||||
const originalHtmlContent =
|
||||
originalEmail.content?.html ||
|
||||
originalEmail.html ||
|
||||
(typeof originalEmail.content === 'string' && originalEmail.content.includes('<')
|
||||
typeof originalEmail?.content === 'object' ? originalEmail.content.html :
|
||||
originalEmail?.html ||
|
||||
(typeof originalEmail?.content === 'string' && originalEmail?.content.includes('<')
|
||||
? originalEmail.content
|
||||
: '');
|
||||
|
||||
// Get the direction from the original email
|
||||
const originalDirection =
|
||||
originalEmail.content?.direction ||
|
||||
(originalTextContent ? detectTextDirection(originalTextContent) : 'ltr');
|
||||
typeof originalEmail?.content === 'object' ? originalEmail.content.direction :
|
||||
detectTextDirection(originalTextContent);
|
||||
|
||||
// Create HTML content that preserves the directionality
|
||||
const htmlContent = `
|
||||
// Create forwarded content with header information
|
||||
const forwardBody = `
|
||||
<br/>
|
||||
<br/>
|
||||
<div class="email-forwarded-content">
|
||||
<p>---------- Forwarded message ---------</p>
|
||||
<p><strong>From:</strong> ${fromStr}</p>
|
||||
<p><strong>Date:</strong> ${dateStr}</p>
|
||||
<p><strong>Subject:</strong> ${originalEmail.subject || ''}</p>
|
||||
<p><strong>Subject:</strong> ${originalEmail?.subject || ''}</p>
|
||||
<p><strong>To:</strong> ${toStr}</p>
|
||||
${ccStr ? `<p><strong>Cc:</strong> ${ccStr}</p>` : ''}
|
||||
<div style="margin-top: 15px; border-top: 1px solid #eee; padding-top: 15px;" dir="${originalDirection}">
|
||||
<div style="margin-top: 15px; border-top: 1px solid #eee; padding-top: 15px;">
|
||||
${originalHtmlContent || originalTextContent.replace(/\n/g, '<br>')}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Apply consistent text direction
|
||||
const htmlContent = applyTextDirection(forwardBody);
|
||||
|
||||
// Create plain text content
|
||||
const textContent = `
|
||||
|
||||
---------- Forwarded message ---------
|
||||
From: ${fromStr}
|
||||
Date: ${dateStr}
|
||||
Subject: ${originalEmail.subject || ''}
|
||||
Subject: ${originalEmail?.subject || ''}
|
||||
To: ${toStr}
|
||||
${ccStr ? `Cc: ${ccStr}\n` : ''}
|
||||
|
||||
@ -475,10 +479,21 @@ export function formatEmailForReplyOrForward(
|
||||
subject: string;
|
||||
content: EmailContent;
|
||||
} {
|
||||
// Use our dedicated formatters but ensure the return is properly typed
|
||||
if (type === 'forward') {
|
||||
const { subject, content } = formatForwardedEmail(email);
|
||||
return { subject, content };
|
||||
const formatted = formatForwardedEmail(email);
|
||||
return {
|
||||
to: formatted.to,
|
||||
subject: formatted.subject,
|
||||
content: formatted.content
|
||||
};
|
||||
} else {
|
||||
return formatReplyEmail(email, type);
|
||||
const formatted = formatReplyEmail(email, type);
|
||||
return {
|
||||
to: formatted.to,
|
||||
cc: formatted.cc,
|
||||
subject: formatted.subject,
|
||||
content: formatted.content
|
||||
};
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user