courrier preview
This commit is contained in:
parent
e9f104f40b
commit
0d4fc59904
@ -136,6 +136,20 @@ export default function ComposeEmail(props: ComposeEmailProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Safety timeout to prevent endless loading
|
||||||
|
const safetyTimeoutId = setTimeout(() => {
|
||||||
|
const contentState = emailContent;
|
||||||
|
if (!contentState || contentState === "") {
|
||||||
|
console.warn('Email content initialization timed out after 5 seconds, using fallback template');
|
||||||
|
// Create a basic fallback template
|
||||||
|
const { fromStr, dateStr } = getFormattedInfoForEmail(initialEmail);
|
||||||
|
const fallbackContent = type === 'forward'
|
||||||
|
? `<div style="margin: 20px 0 10px 0; color: #666;">---------- Forwarded message ----------<br>Unable to load original message content</div>`
|
||||||
|
: `<div style="margin: 20px 0 10px 0; color: #666;">On ${dateStr}, ${fromStr} wrote:<br>Unable to load original message content</div>`;
|
||||||
|
setEmailContent(fallbackContent);
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
// Get recipients based on type
|
// Get recipients based on type
|
||||||
if (type === 'reply' || type === 'reply-all') {
|
if (type === 'reply' || type === 'reply-all') {
|
||||||
// Get formatted data for reply
|
// Get formatted data for reply
|
||||||
@ -258,6 +272,9 @@ export default function ComposeEmail(props: ComposeEmailProps) {
|
|||||||
setAttachments(formattedAttachments);
|
setAttachments(formattedAttachments);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear the safety timeout if we complete successfully
|
||||||
|
return () => clearTimeout(safetyTimeoutId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error initializing compose form:', error);
|
console.error('Error initializing compose form:', error);
|
||||||
// Provide a fallback in case of error
|
// Provide a fallback in case of error
|
||||||
|
|||||||
@ -180,6 +180,22 @@ const RichEmailEditor: React.FC<RichEmailEditorProps> = ({
|
|||||||
|
|
||||||
console.log('Initializing editor in', contentIsReplyOrForward ? 'reply/forward' : 'compose', 'mode');
|
console.log('Initializing editor in', contentIsReplyOrForward ? 'reply/forward' : 'compose', 'mode');
|
||||||
|
|
||||||
|
// Clean up any existing Quill instance
|
||||||
|
if (quillRef.current) {
|
||||||
|
console.log('Cleaning up existing Quill instance before reinitializing');
|
||||||
|
quillRef.current.off('text-change');
|
||||||
|
try {
|
||||||
|
// Safely remove the Quill instance to prevent memory leaks
|
||||||
|
const editorContainer = editorRef.current.querySelector('.ql-editor');
|
||||||
|
if (editorContainer) {
|
||||||
|
editorContainer.innerHTML = '';
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Error cleaning up editor:', e);
|
||||||
|
}
|
||||||
|
quillRef.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
const Quill = (await import('quill')).default;
|
const Quill = (await import('quill')).default;
|
||||||
|
|
||||||
// Import quill-better-table conditionally based on content type
|
// Import quill-better-table conditionally based on content type
|
||||||
@ -216,161 +232,172 @@ const RichEmailEditor: React.FC<RichEmailEditorProps> = ({
|
|||||||
['clean'],
|
['clean'],
|
||||||
];
|
];
|
||||||
|
|
||||||
// Create new Quill instance with the DOM element and custom toolbar
|
try {
|
||||||
const editorElement = editorRef.current;
|
// Create new Quill instance with the DOM element and custom toolbar
|
||||||
quillRef.current = new Quill(editorElement, {
|
const editorElement = editorRef.current;
|
||||||
modules: {
|
quillRef.current = new Quill(editorElement, {
|
||||||
toolbar: {
|
modules: {
|
||||||
container: toolbarRef.current,
|
toolbar: {
|
||||||
handlers: {
|
container: toolbarRef.current,
|
||||||
// Add any custom toolbar handlers here
|
handlers: {
|
||||||
}
|
// Add any custom toolbar handlers here
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clipboard: {
|
||||||
|
matchVisual: false // Disable clipboard matching for better HTML handling
|
||||||
|
},
|
||||||
|
// Only enable better-table for regular content, not for replies/forwards
|
||||||
|
'better-table': tableModule && !contentIsReplyOrForward ? true : false,
|
||||||
},
|
},
|
||||||
clipboard: {
|
placeholder: placeholder,
|
||||||
matchVisual: false // Disable clipboard matching for better HTML handling
|
theme: 'snow',
|
||||||
},
|
});
|
||||||
// Only enable better-table for regular content, not for replies/forwards
|
|
||||||
'better-table': tableModule && !contentIsReplyOrForward ? true : false,
|
|
||||||
},
|
|
||||||
placeholder: placeholder,
|
|
||||||
theme: 'snow',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set initial content properly
|
// Set initial content properly
|
||||||
if (initialContent) {
|
if (initialContent) {
|
||||||
try {
|
try {
|
||||||
console.log('Setting initial content in editor', {
|
console.log('Setting initial content in editor', {
|
||||||
length: initialContent.length,
|
length: initialContent.length,
|
||||||
startsWithHtml: initialContent.trim().startsWith('<'),
|
startsWithHtml: initialContent.trim().startsWith('<'),
|
||||||
containsForwardedMessage: initialContent.includes('---------- Forwarded message ----------'),
|
containsForwardedMessage: initialContent.includes('---------- Forwarded message ----------'),
|
||||||
containsReplyIndicator: initialContent.includes('wrote:'),
|
containsReplyIndicator: initialContent.includes('wrote:'),
|
||||||
hasBlockquote: initialContent.includes('<blockquote')
|
hasBlockquote: initialContent.includes('<blockquote')
|
||||||
});
|
});
|
||||||
|
|
||||||
// Process HTML content using centralized utility with special settings for replies/forwards
|
// Pre-process to handle blocked content like external images
|
||||||
const processed = processHtmlContent(initialContent, {
|
const preProcessedContent = handleBlockedContent(initialContent);
|
||||||
sanitize: true,
|
|
||||||
preserveReplyFormat: contentIsReplyOrForward
|
// Process HTML content using centralized utility with special settings for replies/forwards
|
||||||
});
|
const processed = processHtmlContent(preProcessedContent, {
|
||||||
const sanitizedContent = processed.sanitizedContent;
|
sanitize: true,
|
||||||
const direction = processed.direction; // Use direction from processed result
|
preserveReplyFormat: contentIsReplyOrForward
|
||||||
|
});
|
||||||
// Log sanitized content details for debugging
|
const sanitizedContent = processed.sanitizedContent;
|
||||||
console.log('Sanitized content details:', {
|
const direction = processed.direction; // Use direction from processed result
|
||||||
length: sanitizedContent.length,
|
|
||||||
isEmpty: sanitizedContent.trim().length === 0,
|
// Log sanitized content details for debugging
|
||||||
startsWithDiv: sanitizedContent.trim().startsWith('<div'),
|
console.log('Sanitized content details:', {
|
||||||
containsForwardedMessage: sanitizedContent.includes('---------- Forwarded message ----------'),
|
length: sanitizedContent.length,
|
||||||
containsQuoteHeader: sanitizedContent.includes('wrote:'),
|
isEmpty: sanitizedContent.trim().length === 0,
|
||||||
hasTable: sanitizedContent.includes('<table'),
|
startsWithDiv: sanitizedContent.trim().startsWith('<div'),
|
||||||
hasBlockquote: sanitizedContent.includes('<blockquote'),
|
containsForwardedMessage: sanitizedContent.includes('---------- Forwarded message ----------'),
|
||||||
isReplyOrForward: contentIsReplyOrForward,
|
containsQuoteHeader: sanitizedContent.includes('wrote:'),
|
||||||
firstNChars: sanitizedContent.substring(0, 100).replace(/\n/g, '\\n')
|
hasTable: sanitizedContent.includes('<table'),
|
||||||
});
|
hasBlockquote: sanitizedContent.includes('<blockquote'),
|
||||||
|
isReplyOrForward: contentIsReplyOrForward,
|
||||||
// Check if sanitized content is valid
|
firstNChars: sanitizedContent.substring(0, 100).replace(/\n/g, '\\n')
|
||||||
if (sanitizedContent.trim().length === 0) {
|
});
|
||||||
console.warn('Sanitized content is empty after processing, using fallback approach');
|
|
||||||
// Try to extract text content if HTML processing failed
|
// Check if sanitized content is valid
|
||||||
|
if (sanitizedContent.trim().length === 0) {
|
||||||
|
console.warn('Sanitized content is empty after processing, using fallback approach');
|
||||||
|
// Try to extract text content if HTML processing failed
|
||||||
|
try {
|
||||||
|
const tempDiv = document.createElement('div');
|
||||||
|
tempDiv.innerHTML = preProcessedContent;
|
||||||
|
const textContent = tempDiv.textContent || tempDiv.innerText || 'Empty content';
|
||||||
|
|
||||||
|
// Set text directly to ensure something displays
|
||||||
|
quillRef.current.setText(textContent);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Text extraction fallback failed:', e);
|
||||||
|
quillRef.current.setText('Error loading content');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Special handling for reply or forwarded content
|
||||||
|
if (contentIsReplyOrForward) {
|
||||||
|
console.log('Using special handling for reply/forward content');
|
||||||
|
|
||||||
|
// For reply/forward content, convert ALL tables to divs
|
||||||
|
const cleanedContent = cleanupTableStructures(sanitizedContent, true);
|
||||||
|
|
||||||
|
// Use direct innerHTML setting with minimal processing for reply/forward content
|
||||||
|
quillRef.current.root.innerHTML = cleanedContent;
|
||||||
|
} else {
|
||||||
|
// For regular content, use normal processing
|
||||||
|
const cleanedContent = cleanupTableStructures(sanitizedContent, false);
|
||||||
|
|
||||||
|
// Use direct innerHTML setting for regular content
|
||||||
|
quillRef.current.root.innerHTML = cleanedContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the direction for the content
|
||||||
|
if (quillRef.current && quillRef.current.format) {
|
||||||
|
quillRef.current.format('direction', direction);
|
||||||
|
if (direction === 'rtl') {
|
||||||
|
quillRef.current.format('align', 'right');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('Cannot format content: editor not fully initialized');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set cursor at the beginning
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error setting initial content:', err);
|
||||||
|
|
||||||
|
// Enhanced fallback mechanism for complex content
|
||||||
try {
|
try {
|
||||||
|
// First try to extract text from HTML
|
||||||
const tempDiv = document.createElement('div');
|
const tempDiv = document.createElement('div');
|
||||||
tempDiv.innerHTML = initialContent;
|
tempDiv.innerHTML = initialContent;
|
||||||
const textContent = tempDiv.textContent || tempDiv.innerText || 'Empty content';
|
const textContent = tempDiv.textContent || tempDiv.innerText || '';
|
||||||
|
|
||||||
// Set text directly to ensure something displays
|
if (textContent.trim()) {
|
||||||
quillRef.current.setText(textContent);
|
console.log('Using extracted text fallback, length:', textContent.length);
|
||||||
|
quillRef.current.setText(textContent);
|
||||||
|
} else {
|
||||||
|
// If text extraction fails or returns empty, provide a message
|
||||||
|
console.log('Using empty content fallback');
|
||||||
|
quillRef.current.setText('Unable to load original content');
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Text extraction fallback failed:', e);
|
console.error('All fallbacks failed:', e);
|
||||||
quillRef.current.setText('Error loading content');
|
quillRef.current.setText('Error loading content');
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Special handling for reply or forwarded content
|
|
||||||
if (contentIsReplyOrForward) {
|
|
||||||
console.log('Using special handling for reply/forward content');
|
|
||||||
|
|
||||||
// For reply/forward content, convert ALL tables to divs
|
|
||||||
const cleanedContent = cleanupTableStructures(sanitizedContent, true);
|
|
||||||
|
|
||||||
// Use direct innerHTML setting with minimal processing for reply/forward content
|
|
||||||
quillRef.current.root.innerHTML = cleanedContent;
|
|
||||||
} else {
|
|
||||||
// For regular content, use normal processing
|
|
||||||
const cleanedContent = cleanupTableStructures(sanitizedContent, false);
|
|
||||||
|
|
||||||
// Use direct innerHTML setting for regular content
|
|
||||||
quillRef.current.root.innerHTML = cleanedContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the direction for the content
|
|
||||||
if (quillRef.current && quillRef.current.format) {
|
|
||||||
quillRef.current.format('direction', direction);
|
|
||||||
if (direction === 'rtl') {
|
|
||||||
quillRef.current.format('align', 'right');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.warn('Cannot format content: editor not fully initialized');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set cursor at the beginning
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Error setting initial content:', err);
|
|
||||||
|
|
||||||
// Enhanced fallback mechanism for complex content
|
|
||||||
try {
|
|
||||||
// First try to extract text from HTML
|
|
||||||
const tempDiv = document.createElement('div');
|
|
||||||
tempDiv.innerHTML = initialContent;
|
|
||||||
const textContent = tempDiv.textContent || tempDiv.innerText || '';
|
|
||||||
|
|
||||||
if (textContent.trim()) {
|
|
||||||
console.log('Using extracted text fallback, length:', textContent.length);
|
|
||||||
quillRef.current.setText(textContent);
|
|
||||||
} else {
|
|
||||||
// If text extraction fails or returns empty, provide a message
|
|
||||||
console.log('Using empty content fallback');
|
|
||||||
quillRef.current.setText('Unable to load original content');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('All fallbacks failed:', e);
|
|
||||||
quillRef.current.setText('Error loading content');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add change listener
|
||||||
|
quillRef.current.on('text-change', () => {
|
||||||
|
const html = quillRef.current.root.innerHTML;
|
||||||
|
onChange(html);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Improve editor layout
|
||||||
|
const editorContainer = editorElement.closest('.ql-container');
|
||||||
|
if (editorContainer) {
|
||||||
|
editorContainer.classList.add('email-editor-container');
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsReady(true);
|
||||||
|
} catch (initError) {
|
||||||
|
console.error('Critical error initializing editor:', initError);
|
||||||
|
// Provide fallback in UI
|
||||||
|
if (editorRef.current) {
|
||||||
|
editorRef.current.innerHTML = '<div style="padding: 15px; color: #555;">Error loading editor. Please try again or use plain text mode.</div>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add change listener
|
|
||||||
quillRef.current.on('text-change', () => {
|
|
||||||
const html = quillRef.current.root.innerHTML;
|
|
||||||
onChange(html);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Improve editor layout
|
|
||||||
const editorContainer = editorElement.closest('.ql-container');
|
|
||||||
if (editorContainer) {
|
|
||||||
editorContainer.classList.add('email-editor-container');
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsReady(true);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
initializeQuill().catch(err => {
|
initializeQuill().catch(err => {
|
||||||
@ -382,10 +409,50 @@ const RichEmailEditor: React.FC<RichEmailEditorProps> = ({
|
|||||||
if (quillRef.current) {
|
if (quillRef.current) {
|
||||||
// Clean up any event listeners or resources
|
// Clean up any event listeners or resources
|
||||||
quillRef.current.off('text-change');
|
quillRef.current.off('text-change');
|
||||||
|
quillRef.current = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Add utility function to handle blocked content
|
||||||
|
/**
|
||||||
|
* Pre-process content to handle blocked images and other resources
|
||||||
|
*/
|
||||||
|
function handleBlockedContent(htmlContent: string): string {
|
||||||
|
if (!htmlContent) return htmlContent;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const tempDiv = document.createElement('div');
|
||||||
|
tempDiv.innerHTML = htmlContent;
|
||||||
|
|
||||||
|
// Replace CID and other problematic image sources
|
||||||
|
const images = tempDiv.querySelectorAll('img');
|
||||||
|
images.forEach(img => {
|
||||||
|
const src = img.getAttribute('src') || '';
|
||||||
|
|
||||||
|
// Handle CID attachments that would be blocked
|
||||||
|
if (src.startsWith('cid:')) {
|
||||||
|
console.log('Replacing CID image source:', src);
|
||||||
|
img.setAttribute('src', 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="80" viewBox="0 0 100 80"><rect width="100" height="80" fill="%23eee"/><text x="50%" y="50%" font-family="sans-serif" font-size="12" text-anchor="middle" dominant-baseline="middle" fill="%23999">[Image: Attachment]</text></svg>');
|
||||||
|
img.setAttribute('data-original-src', src);
|
||||||
|
img.style.maxWidth = '300px';
|
||||||
|
img.style.border = '1px dashed #ddd';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle tracking pixels and potentially blocked remote content
|
||||||
|
if (src.includes('open?') || src.includes('tracking') || src.includes('pixel')) {
|
||||||
|
console.log('Removing tracking pixel:', src);
|
||||||
|
img.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return tempDiv.innerHTML;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error handling blocked content:', error);
|
||||||
|
return htmlContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update content from props if changed externally - using a simpler approach
|
// Update content from props if changed externally - using a simpler approach
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (quillRef.current && isReady && initialContent) {
|
if (quillRef.current && isReady && initialContent) {
|
||||||
@ -411,112 +478,134 @@ const RichEmailEditor: React.FC<RichEmailEditorProps> = ({
|
|||||||
initialContent.includes('Forwarded message') ||
|
initialContent.includes('Forwarded message') ||
|
||||||
initialContent.includes('---------- Forwarded message ----------');
|
initialContent.includes('---------- Forwarded message ----------');
|
||||||
|
|
||||||
// If content type changed (from reply to regular or vice versa), we need to reload
|
// If content type changed (from reply to regular or vice versa), we need to reload
|
||||||
if (contentIsReplyOrForward !== isReplyOrForward) {
|
if (contentIsReplyOrForward !== isReplyOrForward) {
|
||||||
console.log('Content type changed from', isReplyOrForward ? 'reply/forward' : 'regular',
|
console.log('Content type changed from', isReplyOrForward ? 'reply/forward' : 'regular',
|
||||||
'to', contentIsReplyOrForward ? 'reply/forward' : 'regular',
|
'to', contentIsReplyOrForward ? 'reply/forward' : 'regular',
|
||||||
'- reloading editor');
|
'- reloading editor');
|
||||||
setIsReplyOrForward(contentIsReplyOrForward);
|
setIsReplyOrForward(contentIsReplyOrForward);
|
||||||
// Force a complete re-initialization of the editor by unmounting
|
// Force a complete re-initialization of the editor by unmounting
|
||||||
if (quillRef.current) {
|
if (quillRef.current) {
|
||||||
quillRef.current.off('text-change');
|
quillRef.current.off('text-change');
|
||||||
quillRef.current = null;
|
|
||||||
}
|
|
||||||
setIsReady(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process HTML content using centralized utility
|
|
||||||
const processed = processHtmlContent(initialContent, {
|
|
||||||
sanitize: true,
|
|
||||||
preserveReplyFormat: contentIsReplyOrForward
|
|
||||||
});
|
|
||||||
const sanitizedContent = processed.sanitizedContent;
|
|
||||||
const direction = processed.direction; // Use direction from processed result
|
|
||||||
|
|
||||||
// Log sanitized content details for debugging
|
|
||||||
console.log('Sanitized content details:', {
|
|
||||||
length: sanitizedContent.length,
|
|
||||||
isEmpty: sanitizedContent.trim().length === 0,
|
|
||||||
startsWithDiv: sanitizedContent.trim().startsWith('<div'),
|
|
||||||
containsForwardedMessage: sanitizedContent.includes('---------- Forwarded message ----------'),
|
|
||||||
containsQuoteHeader: sanitizedContent.includes('wrote:'),
|
|
||||||
hasTable: sanitizedContent.includes('<table'),
|
|
||||||
hasBlockquote: sanitizedContent.includes('<blockquote'),
|
|
||||||
isReplyOrForward: contentIsReplyOrForward,
|
|
||||||
firstNChars: sanitizedContent.substring(0, 100).replace(/\n/g, '\\n')
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check if content is valid HTML
|
|
||||||
if (sanitizedContent.trim().length === 0) {
|
|
||||||
console.warn('Sanitized content is empty, using original content');
|
|
||||||
// If sanitized content is empty, try to extract text from original
|
|
||||||
const tempDiv = document.createElement('div');
|
|
||||||
tempDiv.innerHTML = initialContent;
|
|
||||||
const textContent = tempDiv.textContent || tempDiv.innerText || '';
|
|
||||||
|
|
||||||
// Create simple HTML with text content
|
|
||||||
if (quillRef.current) {
|
|
||||||
quillRef.current.setText(textContent || 'No content available');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Process content based on type
|
|
||||||
if (contentIsReplyOrForward) {
|
|
||||||
console.log('Using special handling for reply/forward content update');
|
|
||||||
|
|
||||||
// For reply/forward content, convert ALL tables to divs
|
|
||||||
const cleanedContent = cleanupTableStructures(sanitizedContent, true);
|
|
||||||
|
|
||||||
// Set content without table handling
|
|
||||||
if (quillRef.current && quillRef.current.root) {
|
|
||||||
quillRef.current.root.innerHTML = cleanedContent;
|
|
||||||
|
|
||||||
// Delay applying formatting to ensure Quill is fully ready
|
// Clear content to prevent flashing
|
||||||
setTimeout(() => {
|
try {
|
||||||
if (quillRef.current) {
|
quillRef.current.root.innerHTML = '<div>Loading...</div>';
|
||||||
try {
|
} catch (e) {
|
||||||
// Set the direction for the content
|
console.warn('Error clearing editor content:', e);
|
||||||
quillRef.current.format('direction', direction);
|
}
|
||||||
if (direction === 'rtl') {
|
|
||||||
quillRef.current.format('align', 'right');
|
quillRef.current = null;
|
||||||
}
|
}
|
||||||
|
setIsReady(false);
|
||||||
// Force update
|
|
||||||
quillRef.current.update();
|
// Force a small delay before reinitializing to ensure cleanup completes
|
||||||
|
setTimeout(() => {
|
||||||
// Set selection to beginning
|
// Explicitly empty out the editor DOM node to ensure clean start
|
||||||
quillRef.current.setSelection(0, 0);
|
if (editorRef.current) {
|
||||||
} catch (innerError) {
|
while (editorRef.current.firstChild) {
|
||||||
console.error('Error applying delayed formatting:', innerError);
|
editorRef.current.removeChild(editorRef.current.firstChild);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, 100);
|
}
|
||||||
|
}, 50);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre-process to handle blocked content
|
||||||
|
const preProcessedContent = handleBlockedContent(initialContent);
|
||||||
|
|
||||||
|
// Process HTML content using centralized utility
|
||||||
|
const processed = processHtmlContent(preProcessedContent, {
|
||||||
|
sanitize: true,
|
||||||
|
preserveReplyFormat: contentIsReplyOrForward
|
||||||
|
});
|
||||||
|
const sanitizedContent = processed.sanitizedContent;
|
||||||
|
const direction = processed.direction; // Use direction from processed result
|
||||||
|
|
||||||
|
// Log sanitized content details for debugging
|
||||||
|
console.log('Sanitized content details:', {
|
||||||
|
length: sanitizedContent.length,
|
||||||
|
isEmpty: sanitizedContent.trim().length === 0,
|
||||||
|
startsWithDiv: sanitizedContent.trim().startsWith('<div'),
|
||||||
|
containsForwardedMessage: sanitizedContent.includes('---------- Forwarded message ----------'),
|
||||||
|
containsQuoteHeader: sanitizedContent.includes('wrote:'),
|
||||||
|
hasTable: sanitizedContent.includes('<table'),
|
||||||
|
hasBlockquote: sanitizedContent.includes('<blockquote'),
|
||||||
|
isReplyOrForward: contentIsReplyOrForward,
|
||||||
|
firstNChars: sanitizedContent.substring(0, 100).replace(/\n/g, '\\n')
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if content is valid HTML
|
||||||
|
if (sanitizedContent.trim().length === 0) {
|
||||||
|
console.warn('Sanitized content is empty, using original content');
|
||||||
|
// If sanitized content is empty, try to extract text from original
|
||||||
|
const tempDiv = document.createElement('div');
|
||||||
|
tempDiv.innerHTML = preProcessedContent;
|
||||||
|
const textContent = tempDiv.textContent || tempDiv.innerText || '';
|
||||||
|
|
||||||
|
// Create simple HTML with text content
|
||||||
|
if (quillRef.current) {
|
||||||
|
quillRef.current.setText(textContent || 'No content available');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// For regular content, use normal processing
|
// Process content based on type
|
||||||
const cleanedContent = cleanupTableStructures(sanitizedContent, false);
|
if (contentIsReplyOrForward) {
|
||||||
|
console.log('Using special handling for reply/forward content update');
|
||||||
if (quillRef.current && quillRef.current.root) {
|
|
||||||
quillRef.current.root.innerHTML = cleanedContent;
|
|
||||||
|
|
||||||
// Safely apply formatting
|
// For reply/forward content, convert ALL tables to divs
|
||||||
try {
|
const cleanedContent = cleanupTableStructures(sanitizedContent, true);
|
||||||
quillRef.current.format('direction', direction);
|
|
||||||
if (direction === 'rtl') {
|
// Set content without table handling
|
||||||
quillRef.current.format('align', 'right');
|
if (quillRef.current && quillRef.current.root) {
|
||||||
|
quillRef.current.root.innerHTML = cleanedContent;
|
||||||
|
|
||||||
|
// Delay applying formatting to ensure Quill is fully ready
|
||||||
|
setTimeout(() => {
|
||||||
|
if (quillRef.current) {
|
||||||
|
try {
|
||||||
|
// Set the direction for the content
|
||||||
|
quillRef.current.format('direction', direction);
|
||||||
|
if (direction === 'rtl') {
|
||||||
|
quillRef.current.format('align', 'right');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force update
|
||||||
|
quillRef.current.update();
|
||||||
|
|
||||||
|
// Set selection to beginning
|
||||||
|
quillRef.current.setSelection(0, 0);
|
||||||
|
} catch (innerError) {
|
||||||
|
console.error('Error applying delayed formatting:', innerError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For regular content, use normal processing
|
||||||
|
const cleanedContent = cleanupTableStructures(sanitizedContent, false);
|
||||||
|
|
||||||
|
if (quillRef.current && quillRef.current.root) {
|
||||||
|
quillRef.current.root.innerHTML = cleanedContent;
|
||||||
|
|
||||||
|
// Safely apply formatting
|
||||||
|
try {
|
||||||
|
quillRef.current.format('direction', direction);
|
||||||
|
if (direction === 'rtl') {
|
||||||
|
quillRef.current.format('align', 'right');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force update
|
||||||
|
quillRef.current.update();
|
||||||
|
|
||||||
|
// Set selection to beginning
|
||||||
|
quillRef.current.setSelection(0, 0);
|
||||||
|
} catch (formatError) {
|
||||||
|
console.error('Error applying formatting:', formatError);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Force update
|
|
||||||
quillRef.current.update();
|
|
||||||
|
|
||||||
// Set selection to beginning
|
|
||||||
quillRef.current.setSelection(0, 0);
|
|
||||||
} catch (formatError) {
|
|
||||||
console.error('Error applying formatting:', formatError);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error updating content:', err);
|
console.error('Error updating content:', err);
|
||||||
// Safer fallback that avoids clipboard API
|
// Safer fallback that avoids clipboard API
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user