courrier preview

This commit is contained in:
alma 2025-04-30 23:13:14 +02:00
parent ad03dba3bf
commit f9e0b323ce
2 changed files with 126 additions and 85 deletions

View File

@ -79,80 +79,74 @@ const RichEmailEditor: React.FC<RichEmailEditorProps> = ({
theme: 'snow',
});
// Set initial content (sanitized)
// Set initial content properly
if (initialContent) {
try {
// First, ensure we preserve the raw HTML structure
const preservedContent = sanitizeHtml(initialContent);
console.log('Setting initial content in editor', {
length: initialContent.length,
startsWithHtml: initialContent.trim().startsWith('<')
});
// Check if there are tables in the content
const hasTables = preservedContent.includes('<table');
// Make sure content is properly sanitized before injecting it
const cleanContent = initialContent
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '') // Remove scripts
.replace(/on\w+="[^"]*"/g, '') // Remove event handlers
.replace(/(javascript|jscript|vbscript|mocha):/gi, 'removed:'); // Remove protocol handlers
// For content with tables, we need special handling
if (hasTables && preserveFormatting && tableModule) {
// First, set the content directly to the root
quillRef.current.root.innerHTML = preservedContent;
// Initialize better table module after content is set
setTimeout(() => {
try {
// Clean up any existing tables first
const tables = quillRef.current.root.querySelectorAll('table');
tables.forEach((table: HTMLTableElement) => {
// Add required data attributes that the module expects
if (!table.getAttribute('data-table')) {
table.setAttribute('data-table', 'true');
}
});
// Initialize the module now that content is already in place
const betterTableModule = {
operationMenu: {
items: {
unmergeCells: {
text: 'Unmerge cells'
}
}
}
};
// Force a refresh
quillRef.current.update();
// Ensure the cursor and scroll position is at the top of the editor
quillRef.current.setSelection(0, 0);
// Also scroll the container to the top
if (editorRef.current) {
editorRef.current.scrollTop = 0;
// Also find and scroll parent containers that might have scroll
const scrollContainer = editorRef.current.closest('.ql-container');
if (scrollContainer) {
scrollContainer.scrollTop = 0;
}
// One more check for nested scroll containers (like overflow divs)
const parentScrollContainer = editorRef.current.closest('.rich-email-editor-container');
if (parentScrollContainer) {
parentScrollContainer.scrollTop = 0;
}
}
} catch (tableErr) {
console.error('Error initializing table module:', tableErr);
}
}, 100);
} else {
// For content without tables, use the standard paste method
quillRef.current.clipboard.dangerouslyPasteHTML(0, preservedContent);
quillRef.current.setSelection(0, 0);
// First, directly set the content
if (editorRef.current) {
editorRef.current.innerHTML = cleanContent;
}
// Then let Quill parse and format it correctly
setTimeout(() => {
// Only proceed if editor ref is still available
if (!editorRef.current) return;
// Get the content from the editor element
const content = editorRef.current.innerHTML;
// Clear the editor
quillRef.current.setText('');
// Insert clean content
quillRef.current.clipboard.dangerouslyPasteHTML(0, content);
// Set cursor at the beginning (before the quoted content)
quillRef.current.setSelection(0, 0);
// Ensure the cursor and scroll position is at the top of the editor
if (editorRef.current) {
editorRef.current.scrollTop = 0;
// Find and scroll parent containers that might have scroll
const scrollable = [
editorRef.current.closest('.ql-container'),
editorRef.current.closest('.rich-email-editor-container'),
editorRef.current.closest('.overflow-y-auto'),
document.querySelector('.overflow-y-auto')
];
scrollable.forEach(el => {
if (el instanceof HTMLElement) {
el.scrollTop = 0;
}
});
}
}, 100);
} catch (err) {
console.error('Error setting initial content:', err);
// Fallback method if the above fails
// Fallback: just set text
quillRef.current.setText('');
quillRef.current.clipboard.dangerouslyPasteHTML(sanitizeHtml(initialContent));
quillRef.current.setSelection(0, 0);
// Try simplest approach
try {
quillRef.current.clipboard.dangerouslyPasteHTML(initialContent);
} catch (e) {
console.error('Fallback failed too:', e);
// Last resort: strip all HTML
quillRef.current.setText(initialContent.replace(/<[^>]*>/g, ''));
}
}
}

View File

@ -242,10 +242,23 @@ export function formatForwardedEmail(email: EmailMessage): {
const toString = formatEmailAddresses(email.to || []);
const dateString = formatEmailDate(email.date);
// Get original content as HTML
const originalContent = email.content.isHtml && email.content.html
? email.content.html
: formatPlainTextToHtml(email.content.text);
// Get original content - use the raw content if possible to preserve formatting
let originalContent = '';
if (email.content) {
if (email.content.isHtml && email.content.html) {
originalContent = email.content.html;
} else if (email.content.text) {
// Format plain text with basic HTML formatting
originalContent = formatPlainTextToHtml(email.content.text);
}
} else if (email.html) {
originalContent = email.html;
} else if (email.text) {
originalContent = formatPlainTextToHtml(email.text);
} else {
originalContent = '<p>No content</p>';
}
// Check if the content already has a forwarded message header
const hasExistingHeader = originalContent.includes('---------- Forwarded message ---------');
@ -263,7 +276,7 @@ export function formatForwardedEmail(email: EmailMessage): {
} else {
// Create formatted content for forwarded email
htmlContent = `
<div style="min-height: 20px;">
<div style="min-height: 20px;"></div>
<div style="border-top: 1px solid #ccc; margin-top: 10px; padding-top: 10px;">
<div style="font-family: Arial, sans-serif; color: #333;">
<div style="margin-bottom: 15px;">
@ -278,16 +291,22 @@ export function formatForwardedEmail(email: EmailMessage): {
</div>
</div>
</div>
</div>
`;
}
// Ensure we have clean HTML
const cleanedHtml = DOMPurify.sanitize(htmlContent, {
ADD_TAGS: ['style'],
ADD_ATTR: ['target', 'rel', 'href', 'src', 'style', 'class', 'id'],
ALLOW_DATA_ATTR: true
});
// Create normalized content with HTML and extracted text
const content: EmailContent = {
html: sanitizeHtml(htmlContent),
html: cleanedHtml,
text: '', // Will be extracted when composing
isHtml: true,
direction: email.content.direction || 'ltr'
direction: email.content?.direction || 'ltr'
};
// Extract text from HTML if in browser environment
@ -322,13 +341,13 @@ export function formatReplyEmail(email: EmailMessage, type: 'reply' | 'reply-all
let cc = undefined;
if (type === 'reply-all' && (email.to || email.cc)) {
const allRecipients = [
...(email.to || []),
...(email.cc || [])
...(typeof email.to === 'string' ? [{name: '', address: email.to}] : (email.to || [])),
...(typeof email.cc === 'string' ? [{name: '', address: email.cc}] : (email.cc || []))
];
// Remove duplicates, then convert to string
const uniqueRecipients = [...new Map(allRecipients.map(addr =>
[addr.address, addr]
[typeof addr === 'string' ? addr : addr.address, addr]
)).values()];
cc = formatEmailAddresses(uniqueRecipients);
@ -338,14 +357,35 @@ export function formatReplyEmail(email: EmailMessage, type: 'reply' | 'reply-all
const subjectBase = email.subject || '(No subject)';
const subject = subjectBase.match(/^Re:/i) ? subjectBase : `Re: ${subjectBase}`;
// Get original content as HTML
const originalContent = email.content.isHtml && email.content.html
? email.content.html
: formatPlainTextToHtml(email.content.text);
// Get original content - use the raw content if possible to preserve formatting
let originalContent = '';
if (email.content) {
if (email.content.isHtml && email.content.html) {
originalContent = email.content.html;
} else if (email.content.text) {
// Format plain text with basic HTML formatting
originalContent = formatPlainTextToHtml(email.content.text);
}
} else if (email.html) {
originalContent = email.html;
} else if (email.text) {
originalContent = formatPlainTextToHtml(email.text);
} else {
originalContent = '<p>No content</p>';
}
// Format sender info
const sender = email.from && email.from.length > 0 ? email.from[0] : undefined;
const senderName = sender ? (sender.name || sender.address) : 'Unknown Sender';
let senderName = 'Unknown Sender';
if (email.from) {
if (Array.isArray(email.from) && email.from.length > 0) {
const sender = email.from[0];
senderName = typeof sender === 'string' ? sender : (sender.name || sender.address);
} else if (typeof email.from === 'string') {
senderName = email.from;
}
}
const formattedDate = formatEmailDate(email.date);
// Create the reply content with attribution line
@ -361,12 +401,19 @@ export function formatReplyEmail(email: EmailMessage, type: 'reply' | 'reply-all
</div>
`;
// Ensure we have clean HTML
const cleanedHtml = DOMPurify.sanitize(htmlContent, {
ADD_TAGS: ['style'],
ADD_ATTR: ['target', 'rel', 'href', 'src', 'style', 'class', 'id'],
ALLOW_DATA_ATTR: true
});
// Create normalized content with HTML and extracted text
const content: EmailContent = {
html: sanitizeHtml(htmlContent),
html: cleanedHtml,
text: '', // Will be extracted when composing
isHtml: true,
direction: email.content.direction || 'ltr'
direction: email.content?.direction || 'ltr'
};
// Extract text from HTML if in browser environment