diff --git a/app/globals.css b/app/globals.css index e5c3dec4..db8efa83 100644 --- a/app/globals.css +++ b/app/globals.css @@ -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; -} - diff --git a/components/email/RichEmailEditor.tsx b/components/email/RichEmailEditor.tsx index 0834136a..d83566de 100644 --- a/components/email/RichEmailEditor.tsx +++ b/components/email/RichEmailEditor.tsx @@ -60,10 +60,24 @@ const RichEmailEditor: React.FC = ({ // 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 = ({ 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 = ({ 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; } `} diff --git a/lib/utils/email-formatter.ts b/lib/utils/email-formatter.ts index a6ee4571..e5718b4c 100644 --- a/lib/utils/email-formatter.ts +++ b/lib/utils/email-formatter.ts @@ -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 = `
On ${formattedDate}, ${fromText} wrote:
`; + const quoteHeader = `
On ${formattedDate}, ${fromText} wrote:
`; // 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 = ` -
-
-
${quoteHeader}
-
+
+
+ ${quoteHeader} +
${quotedContent}
-
`; return {