From fbfababb22a4847f0026a109a29725f21c144aa9 Mon Sep 17 00:00:00 2001 From: alma Date: Thu, 1 May 2025 17:52:18 +0200 Subject: [PATCH] courrier preview --- components/email/RichEmailEditor.tsx | 280 ++++++++++++++------------- 1 file changed, 147 insertions(+), 133 deletions(-) diff --git a/components/email/RichEmailEditor.tsx b/components/email/RichEmailEditor.tsx index bc9f0a22..1b1c8f25 100644 --- a/components/email/RichEmailEditor.tsx +++ b/components/email/RichEmailEditor.tsx @@ -24,10 +24,16 @@ interface RichEmailEditorProps { // Register better table module function registerTableModule() { try { - Quill.register({ - 'modules/better-table': QuillBetterTable - }, true); - return true; + // Only attempt to register if the module exists + if (typeof QuillBetterTable !== 'undefined') { + Quill.register({ + 'modules/better-table': QuillBetterTable + }, true); + return true; + } else { + console.warn('QuillBetterTable module is not available, skipping registration'); + return false; + } } catch (error) { console.error('Error registering table module:', error); return false; @@ -114,17 +120,17 @@ function cleanupTableStructures(htmlContent: string, isReplyOrForward: boolean = } // For regular content, just add width attributes to make quill-better-table happy else { - // Skip simple tables that are likely to work fine with Quill - // Check more conditions to identify simple tables - const isSimpleTable = - table.rows.length <= 3 && - table.querySelectorAll('td, th').length <= 6 && - !table.querySelector('table') && // No nested tables - !table.innerHTML.includes('rowspan') && // No rowspan - !table.innerHTML.includes('colspan'); // No colspan - - if (isSimpleTable) { - console.log('Preserving simple table structure'); + // Skip simple tables that are likely to work fine with Quill + // Check more conditions to identify simple tables + const isSimpleTable = + table.rows.length <= 3 && + table.querySelectorAll('td, th').length <= 6 && + !table.querySelector('table') && // No nested tables + !table.innerHTML.includes('rowspan') && // No rowspan + !table.innerHTML.includes('colspan'); // No colspan + + if (isSimpleTable) { + console.log('Preserving simple table structure'); // Add width attribute table.setAttribute('width', '100%'); @@ -136,20 +142,20 @@ function cleanupTableStructures(htmlContent: string, isReplyOrForward: boolean = } }); } else { - // Convert complex tables to divs - const replacementDiv = document.createElement('div'); - replacementDiv.className = 'converted-table'; - replacementDiv.style.border = '1px solid #ddd'; - replacementDiv.style.margin = '10px 0'; - replacementDiv.style.padding = '10px'; - - // Copy the table's innerHTML - replacementDiv.innerHTML = table.innerHTML; - - // Replace the table with the div - if (table.parentNode) { - table.parentNode.replaceChild(replacementDiv, table); - convertedCount++; + // Convert complex tables to divs + const replacementDiv = document.createElement('div'); + replacementDiv.className = 'converted-table'; + replacementDiv.style.border = '1px solid #ddd'; + replacementDiv.style.margin = '10px 0'; + replacementDiv.style.padding = '10px'; + + // Copy the table's innerHTML + replacementDiv.innerHTML = table.innerHTML; + + // Replace the table with the div + if (table.parentNode) { + table.parentNode.replaceChild(replacementDiv, table); + convertedCount++; } } } @@ -166,51 +172,6 @@ function cleanupTableStructures(htmlContent: string, isReplyOrForward: boolean = } } -// Clean up existing editor before creating a new one -const cleanupExistingEditor = () => { - try { - // Clear any existing timeouts - if (quillInitTimeoutRef.current) { - clearTimeout(quillInitTimeoutRef.current); - quillInitTimeoutRef.current = null; - } - - // Remove existing Quill instance if it exists - if (quillRef.current) { - console.log('Cleaning up existing Quill editor'); - - // Remove event listeners - try { - quillRef.current.off('text-change'); - quillRef.current.off('selection-change'); - - // Get the container of the editor - const editorContainer = document.querySelector('#quill-editor'); - if (editorContainer instanceof HTMLElement) { - // Clear the content - editorContainer.innerHTML = ''; - } - } catch (err) { - console.warn('Error removing Quill event listeners:', err); - } - - // Set to null to ensure garbage collection - quillRef.current = null; - } - - // Also ensure the editor element is empty - const editorElement = document.querySelector('#quill-editor'); - if (editorElement) { - editorElement.innerHTML = ''; - } - - return true; - } catch (error) { - console.error('Error cleaning up editor:', error); - return false; - } -}; - // Define toolbar options for consistency const emailToolbarOptions = [ ['bold', 'italic', 'underline', 'strike'], @@ -238,9 +199,47 @@ const RichEmailEditor: React.FC = ({ const editorRef = useRef(null); const toolbarRef = useRef(null); const quillRef = useRef(null); + const quillInitTimeoutRef = useRef(null); const [isReady, setIsReady] = useState(false); const [isReplyOrForward, setIsReplyOrForward] = useState(false); - const quillInitTimeoutRef = useRef(null); + + // Helper function to clean up existing editor + const cleanupEditor = () => { + try { + // Clear any existing timeouts + if (quillInitTimeoutRef.current) { + clearTimeout(quillInitTimeoutRef.current); + quillInitTimeoutRef.current = null; + } + + // Remove existing Quill instance if it exists + if (quillRef.current) { + console.log('Cleaning up existing Quill editor'); + + // Remove event listeners + try { + quillRef.current.off('text-change'); + quillRef.current.off('selection-change'); + + // Get the container of the editor + if (editorRef.current) { + // Clear the content + editorRef.current.innerHTML = ''; + } + } catch (err) { + console.warn('Error removing Quill event listeners:', err); + } + + // Set to null to ensure garbage collection + quillRef.current = null; + } + + return true; + } catch (error) { + console.error('Error cleaning up editor:', error); + return false; + } + }; // Initialize editor effect useEffect(() => { @@ -251,7 +250,7 @@ const RichEmailEditor: React.FC = ({ const initEditor = async () => { try { // First cleanup any existing editor instances to prevent memory leaks - cleanupExistingEditor(); + cleanupEditor(); if (!editorRef.current) { console.error('Editor reference is not available'); @@ -268,29 +267,24 @@ const RichEmailEditor: React.FC = ({ registerTableModule(); console.log('Better Table module registered successfully'); + // Create a modules configuration that works + const modules = { + toolbar: emailToolbarOptions, + keyboard: { + bindings: customKeyBindings, + } + }; + // Set up Quill with configurations const quill = new Quill(editorRef.current, { - modules: { - toolbar: emailToolbarOptions, - betterTable: { - operationMenu: { - items: { - unmergeCells: { - text: 'Unmerge cells', - }, - }, - }, - }, - keyboard: { - bindings: customKeyBindings, - }, - }, + modules, theme: 'snow', placeholder: placeholder || 'Write your message here...', formats: allowedFormats, }); - + // Store the instance for cleanup + quillRef.current = quill; editorInstance = quill; // Process and set initial content if available @@ -304,12 +298,9 @@ const RichEmailEditor: React.FC = ({ // Use dangerouslyPasteHTML which accepts string content directly quill.clipboard.dangerouslyPasteHTML(processedContent); - // Emit initial content + // Emit initial content with the string HTML if (onChange) { - onChange({ - html: quill.root.innerHTML, - text: quill.getText() - }); + onChange(quill.root.innerHTML); } initialContentSet = true; @@ -319,9 +310,19 @@ const RichEmailEditor: React.FC = ({ // Fallback to direct HTML setting if conversion fails try { quill.root.innerHTML = handleBlockedContent(initialContent); + + // Still emit the change even with the fallback approach + if (onChange) { + onChange(quill.root.innerHTML); + } } catch (innerErr) { console.error('Fallback content setting failed:', innerErr); quill.root.innerHTML = '

Error loading content. Please start typing or paste content manually.

'; + + // Emit the fallback content + if (onChange) { + onChange(quill.root.innerHTML); + } } } } @@ -341,6 +342,9 @@ const RichEmailEditor: React.FC = ({ quill.setSelection(0, 0); }, 100); } + + // Mark editor as ready + setIsReady(true); } catch (error) { console.error('Error initializing editor:', error); } @@ -352,7 +356,7 @@ const RichEmailEditor: React.FC = ({ console.warn('Editor initialization timed out after 3 seconds'); // Force cleanup and try one more time with minimal settings - cleanupExistingEditor(); + cleanupEditor(); try { // Create a simple editor without complex modules @@ -370,13 +374,18 @@ const RichEmailEditor: React.FC = ({ onChange(html); }); + quillRef.current = fallbackQuill; editorInstance = fallbackQuill; + setIsReady(true); } catch (fallbackError) { console.error('Fallback editor initialization also failed:', fallbackError); } } }, 3000); + // Store timeout reference to allow cleanup + quillInitTimeoutRef.current = initializationTimeout; + // Initialize the editor initEditor(); @@ -386,6 +395,11 @@ const RichEmailEditor: React.FC = ({ clearTimeout(initializationTimeout); } + if (quillInitTimeoutRef.current) { + clearTimeout(quillInitTimeoutRef.current); + quillInitTimeoutRef.current = null; + } + if (editorInstance) { try { // Remove event listeners @@ -401,7 +415,7 @@ const RichEmailEditor: React.FC = ({ } // Final cleanup of any Quill instances - cleanupExistingEditor(); + cleanupEditor(); }; }, [initialContent, onChange, placeholder, autofocus, mode, allowedFormats, customKeyBindings]); @@ -562,41 +576,41 @@ const RichEmailEditor: React.FC = ({ // Pre-process to handle blocked content const preProcessedContent = handleBlockedContent(initialContent); - - // Process HTML content using centralized utility + + // 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(' = ({ const cleanedContent = cleanupTableStructures(sanitizedContent, true); // Set content without table handling - if (quillRef.current && quillRef.current.root) { - quillRef.current.root.innerHTML = cleanedContent; + if (quillRef.current && quillRef.current.root) { + quillRef.current.root.innerHTML = cleanedContent; // Delay applying formatting to ensure Quill is fully ready setTimeout(() => { @@ -648,12 +662,12 @@ const RichEmailEditor: React.FC = ({ // Set selection to beginning quillRef.current.setSelection(0, 0); - } catch (formatError) { - console.error('Error applying formatting:', formatError); + } catch (formatError) { + console.error('Error applying formatting:', formatError); } } - } } + } } catch (err) { console.error('Error updating content:', err); // Safer fallback that avoids clipboard API