courrier refactor rebuild 2

This commit is contained in:
alma 2025-04-27 10:47:10 +02:00
parent 02c9e7054d
commit 51a92f27dd
3 changed files with 225 additions and 145 deletions

View File

@ -80,6 +80,7 @@
word-wrap: break-word; word-wrap: break-word;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
line-height: 1.5; line-height: 1.5;
font-size: 14px;
} }
/* Preserve email structure */ /* Preserve email structure */
@ -101,15 +102,20 @@
border-collapse: collapse; border-collapse: collapse;
margin: 16px 0; margin: 16px 0;
table-layout: fixed; table-layout: fixed;
max-width: 100%; border: 1px solid #ddd;
}
/* Table wrapper for overflow handling */
.email-content-display div:has(> table) {
overflow-x: auto; overflow-x: auto;
display: block; max-width: 100%;
margin: 16px 0;
} }
.email-content-display td, .email-content-display td,
.email-content-display th { .email-content-display th {
padding: 8px; padding: 8px;
border: 1px solid #e5e7eb; border: 1px solid #ddd;
word-break: break-word; word-break: break-word;
overflow-wrap: break-word; overflow-wrap: break-word;
max-width: 100%; max-width: 100%;
@ -121,32 +127,148 @@
.email-content-display blockquote table { .email-content-display blockquote table {
font-size: 12px; font-size: 12px;
margin: 8px 0; margin: 8px 0;
width: 100% !important;
border: 1px solid #ddd;
} }
.email-content-display .quoted-content td, .email-content-display .quoted-content td,
.email-content-display .quoted-content th, .email-content-display .quoted-content th,
.email-content-display blockquote td, .email-content-display blockquote td,
.email-content-display blockquote th { .email-content-display blockquote th {
padding: 4px; padding: 6px;
font-size: 12px; font-size: 12px;
border: 1px solid #ddd;
} }
/* Fix for tables in Quill editor */ /* Quote blocks for email replies */
.email-content-display blockquote,
.email-content-display .quoted-content {
margin: 16px 0;
padding: 8px 16px;
border-left: 2px solid #ddd;
color: #505050;
background-color: #f9f9f9;
border-radius: 4px;
font-size: 13px;
}
/* Special classes used in the email formatting functions */
.email-content-display .reply-body {
width: 100%;
font-family: Arial, sans-serif;
margin-top: 20px;
}
.email-content-display .quote-header {
color: #555;
font-size: 13px;
margin: 20px 0 10px 0;
font-weight: 400;
}
.email-content-display .quoted-content {
font-size: 13px;
line-height: 1.5;
}
.email-content-display .email-original-content {
margin-top: 10px;
padding-top: 10px;
}
/* Fix styles for the content in both preview and compose */
.email-content-display[contenteditable="false"] {
/* Same styles as contentEditable=true to ensure consistency */
white-space: pre-wrap;
word-break: break-word;
}
/* Quill editor customizations for email composition */
.ql-editor {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 1.5;
padding: 12px;
overflow-y: auto !important;
}
/* Quote formatting for forwarded/replied emails */
.ql-editor blockquote {
border-left: 2px solid #ddd !important;
padding: 10px 0 10px 15px !important;
margin: 8px 0 !important;
color: #505050 !important;
background-color: #f9f9f9 !important;
border-radius: 4px !important;
font-size: 13px !important;
}
/* Table formatting in the editor */
.ql-editor table { .ql-editor table {
width: 100% !important; width: 100% !important;
border-collapse: collapse; border-collapse: collapse !important;
table-layout: fixed; table-layout: fixed !important;
margin: 10px 0; margin: 10px 0 !important;
border: 1px solid #ddd !important;
} }
.ql-editor td, .ql-editor td,
.ql-editor th { .ql-editor th {
border: 1px solid #ccc; border: 1px solid #ddd !important;
padding: 4px 8px; padding: 6px 8px !important;
overflow-wrap: break-word; overflow-wrap: break-word !important;
word-break: break-word; word-break: break-word !important;
min-width: 30px; min-width: 30px !important;
font-size: 13px; font-size: 13px !important;
}
/* Fix toolbar button styling */
.ql-toolbar.ql-snow {
border-top: none;
border-left: none;
border-right: none;
border-bottom: 1px solid #e5e7eb;
}
.ql-container.ql-snow {
border: none;
}
/* Style for "On [date], [person] wrote:" line */
.ql-editor div[style*="font-weight: 400"] {
margin-top: 20px !important;
margin-bottom: 8px !important;
color: #555 !important;
font-size: 13px !important;
}
/* Support for RTL content */
.email-content-display[dir="rtl"],
.email-content-display [dir="rtl"] {
text-align: right;
}
/* Remove any padding/margins from the first and last elements */
.email-content-display > *:first-child {
margin-top: 0;
}
.email-content-display > *:last-child {
margin-bottom: 0;
}
/* Forwarded message header styling */
.email-content-display div {
color: #555;
}
/* Forwarded message styling */
.email-content-display div[style*="forwarded message"],
.email-content-display div[class*="forwarded-message"],
.email-content-display div[class*="forwarded_message"] {
color: #555;
font-family: Arial, sans-serif;
margin-bottom: 15px;
} }
/* Buttons */ /* Buttons */
@ -190,109 +312,3 @@
margin-bottom: 16px; margin-bottom: 16px;
} }
/* Quote blocks for email replies */
.email-content-display blockquote {
margin: 16px 0;
padding: 8px 16px;
border-left: 3px solid #e5e7eb;
color: #4b5563;
background-color: #f9fafb;
}
/* Support for RTL content */
.email-content-display[dir="rtl"],
.email-content-display [dir="rtl"] {
text-align: right;
}
/* Remove any padding/margins from the first and last elements */
.email-content-display > *:first-child {
margin-top: 0;
}
.email-content-display > *:last-child {
margin-bottom: 0;
}
/* Forwarded message header styling */
.email-content-display div {
color: #555;
}
/* Forwarded message styling */
.email-content-display div[style*="forwarded message"],
.email-content-display div[class*="forwarded-message"],
.email-content-display div[class*="forwarded_message"] {
color: #555;
font-family: Arial, sans-serif;
margin-bottom: 15px;
}
/* Special classes used in the email formatting functions */
.email-content-display .reply-body {
width: 100%;
font-family: Arial, sans-serif;
}
.email-content-display .quote-header {
color: #555;
font-size: 13px;
margin: 20px 0 10px 0;
font-weight: 500;
}
.email-content-display .quoted-content {
font-size: 13px;
}
.email-content-display .email-original-content {
margin-top: 10px;
padding-top: 10px;
}
/* Fix styles for the content in both preview and compose */
.email-content-display[contenteditable="false"] {
/* Same styles as contentEditable=true to ensure consistency */
white-space: pre-wrap;
word-break: break-word;
}
/* Quill editor customizations for email composition */
.ql-editor {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 1.5;
}
/* Quote formatting for forwarded/replied emails */
.ql-editor blockquote {
border-left: 3px solid #ddd;
padding-left: 10px;
margin: 8px 0;
color: #555;
}
/* Forward message formatting */
.ql-editor .forward-header {
margin-bottom: 10px;
color: #333;
font-family: Arial, sans-serif;
}
/* Make sure the quoted content is properly indented */
.ql-editor .email-original-content {
margin-top: 10px;
}
/* Fix toolbar button styling */
.ql-toolbar.ql-snow {
border-top: none;
border-left: none;
border-right: none;
border-bottom: 1px solid #e5e7eb;
}
.ql-container.ql-snow {
border: none;
}

View File

@ -60,10 +60,24 @@ const RichEmailEditor: React.FC<RichEmailEditorProps> = ({
// Set initial content (sanitized) // Set initial content (sanitized)
if (initialContent) { if (initialContent) {
// Properly handle table content in the sanitized HTML try {
const cleanContent = sanitizeHtml(initialContent); // First, ensure we preserve the raw HTML structure
// Use clipboard API to ensure tables and complex HTML are rendered correctly const preservedContent = sanitizeHtml(initialContent);
quillRef.current.clipboard.dangerouslyPasteHTML(cleanContent);
// Use root's innerHTML for complete reset to avoid Quill's automatic formatting
quillRef.current.root.innerHTML = '';
// Now use clipboard API to insert the content with proper Quill delta conversion
quillRef.current.clipboard.dangerouslyPasteHTML(0, preservedContent);
// Force update to ensure content is rendered
quillRef.current.update();
} catch (err) {
console.error('Error setting initial content:', err);
// Fallback method if the above fails
quillRef.current.setText('');
quillRef.current.clipboard.dangerouslyPasteHTML(sanitizeHtml(initialContent));
}
} }
// Add change listener // Add change listener
@ -100,11 +114,27 @@ const RichEmailEditor: React.FC<RichEmailEditorProps> = ({
const currentContent = quillRef.current.root.innerHTML; const currentContent = quillRef.current.root.innerHTML;
// Only update if content changed to avoid editor position reset // Only update if content changed to avoid editor position reset
if (initialContent !== currentContent) { if (initialContent !== currentContent) {
try {
// Preserve cursor position if possible // Preserve cursor position if possible
const selection = quillRef.current.getSelection(); const selection = quillRef.current.getSelection();
quillRef.current.clipboard.dangerouslyPasteHTML(sanitizeHtml(initialContent));
// First clear the content
quillRef.current.root.innerHTML = '';
// Then insert the new content at position 0
quillRef.current.clipboard.dangerouslyPasteHTML(0, sanitizeHtml(initialContent));
// Force update
quillRef.current.update();
// Restore selection if possible
if (selection) { if (selection) {
quillRef.current.setSelection(selection); setTimeout(() => quillRef.current.setSelection(selection), 10);
}
} catch (err) {
console.error('Error updating content:', err);
// Fallback update method
quillRef.current.clipboard.dangerouslyPasteHTML(sanitizeHtml(initialContent));
} }
} }
} }
@ -208,19 +238,53 @@ const RichEmailEditor: React.FC<RichEmailEditorProps> = ({
padding: 12px; padding: 12px;
min-height: ${minHeight}; min-height: ${minHeight};
overflow-y: auto !important; overflow-y: auto !important;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
font-size: 14px;
line-height: 1.5;
}
/* Force blockquote styling */
:global(.ql-editor blockquote) {
border-left: 2px solid #ddd !important;
margin: 0 !important;
padding: 10px 0 10px 15px !important;
color: #505050 !important;
background-color: #f9f9f9 !important;
border-radius: 4px !important;
font-size: 13px !important;
} }
/* Fix table rendering */ /* Fix table rendering */
:global(.ql-editor table) { :global(.ql-editor table) {
width: 100%; width: 100% !important;
border-collapse: collapse; border-collapse: collapse !important;
table-layout: fixed !important;
margin: 10px 0 !important;
border: 1px solid #ddd !important;
} }
:global(.ql-editor td), :global(.ql-editor td),
:global(.ql-editor th) { :global(.ql-editor th) {
border: 1px solid #ccc; border: 1px solid #ddd !important;
padding: 4px 8px; padding: 6px 8px !important;
min-width: 40px; overflow-wrap: break-word !important;
word-break: break-word !important;
min-width: 30px !important;
font-size: 13px !important;
}
/* Fix quoted paragraphs */
:global(.ql-editor blockquote p) {
margin-bottom: 8px !important;
margin-top: 0 !important;
}
/* Fix for reply headers */
:global(.ql-editor div[style*="font-weight: 400"]) {
margin-top: 20px !important;
margin-bottom: 8px !important;
color: #555 !important;
font-size: 13px !important;
} }
`}</style> `}</style>
</div> </div>

View File

@ -140,8 +140,8 @@ export function sanitizeHtml(html: string): string {
try { try {
// Use DOMPurify but ensure we keep all elements and attributes that might be in emails // Use DOMPurify but ensure we keep all elements and attributes that might be in emails
const clean = DOMPurify.sanitize(html, { const clean = DOMPurify.sanitize(html, {
ADD_TAGS: ['button', 'style', 'img', 'iframe', 'meta'], ADD_TAGS: ['button', 'style', 'img', 'iframe', 'meta', 'table', 'thead', 'tbody', 'tr', 'td', 'th'],
ADD_ATTR: ['target', 'rel', 'style', 'class', 'id', 'href', 'src', 'alt', 'title', 'width', 'height', 'onclick'], ADD_ATTR: ['target', 'rel', 'style', 'class', 'id', 'href', 'src', 'alt', 'title', 'width', 'height', 'onclick', 'colspan', 'rowspan'],
KEEP_CONTENT: true, KEEP_CONTENT: true,
WHOLE_DOCUMENT: false, WHOLE_DOCUMENT: false,
ALLOW_DATA_ATTR: true, ALLOW_DATA_ATTR: true,
@ -255,10 +255,11 @@ export function formatReplyEmail(email: EmailMessage, type: 'reply' | 'reply-all
}); });
// Create quote header // Create quote header
const quoteHeader = `<div style="font-weight: 500;">On ${formattedDate}, ${fromText} wrote:</div>`; const quoteHeader = `<div style="font-weight: 400; color: #555; margin: 20px 0 8px 0; font-size: 13px;">On ${formattedDate}, ${fromText} wrote:</div>`;
// Get and sanitize original content (sanitization preserves content direction) // Get and sanitize original content (sanitization preserves content direction)
const quotedContent = sanitizeHtml(email.html || email.content || email.text || ''); const originalContent = email.html || email.content || email.text || '';
const quotedContent = sanitizeHtml(originalContent);
// Format recipients // Format recipients
let to = formatEmailAddresses(email.from || []); let to = formatEmailAddresses(email.from || []);
@ -274,18 +275,17 @@ export function formatReplyEmail(email: EmailMessage, type: 'reply' | 'reply-all
cc = formatEmailAddresses(allRecipients); cc = formatEmailAddresses(allRecipients);
} }
// Format content for reply // Format content for reply with improved styling
const content = ` const content = `
<div style="min-height: 20px;"> <div style="min-height: 20px;"></div>
<div class="reply-body"> <div class="reply-body" style="font-family: Arial, sans-serif; line-height: 1.5;">
<div class="quote-header" style="color: #555; font-size: 13px; margin: 20px 0 10px 0;">${quoteHeader}</div> ${quoteHeader}
<blockquote style="margin: 0; padding: 10px 0 10px 15px; border-left: 3px solid #ddd; color: #555; background-color: #f8f8f8; border-radius: 4px;"> <blockquote style="margin: 0; padding: 10px 0 10px 15px; border-left: 2px solid #ddd; color: #505050; background-color: #f9f9f9; border-radius: 4px;">
<div class="quoted-content" style="font-size: 13px;"> <div class="quoted-content" style="font-size: 13px;">
${quotedContent} ${quotedContent}
</div> </div>
</blockquote> </blockquote>
</div> </div>
</div>
`; `;
return { return {