courrier refactor rebuild 2
This commit is contained in:
parent
02c9e7054d
commit
51a92f27dd
256
app/globals.css
256
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;
|
||||
}
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user