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;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-size: 14px;
}
/* Preserve email structure */
@ -101,15 +102,20 @@
border-collapse: collapse;
margin: 16px 0;
table-layout: fixed;
max-width: 100%;
border: 1px solid #ddd;
}
/* Table wrapper for overflow handling */
.email-content-display div:has(> table) {
overflow-x: auto;
display: block;
max-width: 100%;
margin: 16px 0;
}
.email-content-display td,
.email-content-display th {
padding: 8px;
border: 1px solid #e5e7eb;
border: 1px solid #ddd;
word-break: break-word;
overflow-wrap: break-word;
max-width: 100%;
@ -121,32 +127,148 @@
.email-content-display blockquote table {
font-size: 12px;
margin: 8px 0;
width: 100% !important;
border: 1px solid #ddd;
}
.email-content-display .quoted-content td,
.email-content-display .quoted-content th,
.email-content-display blockquote td,
.email-content-display blockquote th {
padding: 4px;
padding: 6px;
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 {
width: 100% !important;
border-collapse: collapse;
table-layout: fixed;
margin: 10px 0;
border-collapse: collapse !important;
table-layout: fixed !important;
margin: 10px 0 !important;
border: 1px solid #ddd !important;
}
.ql-editor td,
.ql-editor th {
border: 1px solid #ccc;
padding: 4px 8px;
overflow-wrap: break-word;
word-break: break-word;
min-width: 30px;
font-size: 13px;
border: 1px solid #ddd !important;
padding: 6px 8px !important;
overflow-wrap: break-word !important;
word-break: break-word !important;
min-width: 30px !important;
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 */
@ -190,109 +312,3 @@
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)
if (initialContent) {
// Properly handle table content in the sanitized HTML
const cleanContent = sanitizeHtml(initialContent);
// Use clipboard API to ensure tables and complex HTML are rendered correctly
quillRef.current.clipboard.dangerouslyPasteHTML(cleanContent);
try {
// First, ensure we preserve the raw HTML structure
const preservedContent = sanitizeHtml(initialContent);
// 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
@ -100,11 +114,27 @@ const RichEmailEditor: React.FC<RichEmailEditorProps> = ({
const currentContent = quillRef.current.root.innerHTML;
// Only update if content changed to avoid editor position reset
if (initialContent !== currentContent) {
// Preserve cursor position if possible
const selection = quillRef.current.getSelection();
quillRef.current.clipboard.dangerouslyPasteHTML(sanitizeHtml(initialContent));
if (selection) {
quillRef.current.setSelection(selection);
try {
// Preserve cursor position if possible
const selection = quillRef.current.getSelection();
// 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) {
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;
min-height: ${minHeight};
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 */
:global(.ql-editor table) {
width: 100%;
border-collapse: collapse;
width: 100% !important;
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) {
border: 1px solid #ccc;
padding: 4px 8px;
min-width: 40px;
border: 1px solid #ddd !important;
padding: 6px 8px !important;
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>
</div>

View File

@ -140,8 +140,8 @@ export function sanitizeHtml(html: string): string {
try {
// Use DOMPurify but ensure we keep all elements and attributes that might be in emails
const clean = DOMPurify.sanitize(html, {
ADD_TAGS: ['button', 'style', 'img', 'iframe', 'meta'],
ADD_ATTR: ['target', 'rel', 'style', 'class', 'id', 'href', 'src', 'alt', 'title', 'width', 'height', 'onclick'],
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', 'colspan', 'rowspan'],
KEEP_CONTENT: true,
WHOLE_DOCUMENT: false,
ALLOW_DATA_ATTR: true,
@ -255,10 +255,11 @@ export function formatReplyEmail(email: EmailMessage, type: 'reply' | 'reply-all
});
// 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)
const quotedContent = sanitizeHtml(email.html || email.content || email.text || '');
const originalContent = email.html || email.content || email.text || '';
const quotedContent = sanitizeHtml(originalContent);
// Format recipients
let to = formatEmailAddresses(email.from || []);
@ -274,18 +275,17 @@ export function formatReplyEmail(email: EmailMessage, type: 'reply' | 'reply-all
cc = formatEmailAddresses(allRecipients);
}
// Format content for reply
// Format content for reply with improved styling
const content = `
<div style="min-height: 20px;">
<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 style="min-height: 20px;"></div>
<div class="reply-body" style="font-family: Arial, sans-serif; line-height: 1.5;">
${quoteHeader}
<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;">
${quotedContent}
</div>
</blockquote>
</div>
</div>
`;
return {