diff --git a/components/email/RichEmailEditor.tsx b/components/email/RichEmailEditor.tsx index e2f79b73..4297ef05 100644 --- a/components/email/RichEmailEditor.tsx +++ b/components/email/RichEmailEditor.tsx @@ -18,7 +18,7 @@ interface RichEmailEditorProps { /** * Clean up problematic table structures that cause issues with quill-better-table */ -function cleanupTableStructures(htmlContent: string): string { +function cleanupTableStructures(htmlContent: string, isReplyOrForward: boolean = false): string { if (!htmlContent) return htmlContent; try { @@ -40,101 +40,103 @@ function cleanupTableStructures(htmlContent: string): string { htmlContent.includes(' 0 && - (isForwardedEmail || isReplyEmail); + // For reply/forward content, force convert ALL tables to divs to avoid Quill errors + const shouldConvertAllTables = isReplyOrForward || isForwardedEmail || isReplyEmail; - if (hasComplexTables) { - console.log(`Found ${tables.length} tables in complex email content`); + if (tables.length > 0) { + console.log(`Found ${tables.length} tables in ${shouldConvertAllTables ? 'reply/forward' : 'regular'} content`); let convertedCount = 0; tables.forEach(table => { - // Special handling for tables inside blockquotes (quoted content) - if (table.closest('blockquote') || - (isReplyEmail && table.innerHTML.includes('wrote:'))) { - console.log('Preserving table inside quoted content'); + // In reply/forward mode, convert ALL tables to divs to avoid quill-better-table issues + if (shouldConvertAllTables) { + const replacementDiv = document.createElement('div'); + replacementDiv.className = 'converted-table'; + replacementDiv.style.border = '1px solid #ddd'; + replacementDiv.style.margin = '10px 0'; + replacementDiv.style.padding = '10px'; - // Add width attribute to all cells to prevent width calculation issues - const cells = table.querySelectorAll('td, th'); - cells.forEach(cell => { - if (cell instanceof HTMLTableCellElement) { - if (!cell.hasAttribute('width')) { + // Preserve the original table structure visually + // Create a simplified HTML representation of the table + let tableHtml = ''; + + // Process each row + const rows = table.querySelectorAll('tr'); + rows.forEach(row => { + const rowDiv = document.createElement('div'); + rowDiv.style.display = 'flex'; + rowDiv.style.flexWrap = 'wrap'; + rowDiv.style.marginBottom = '5px'; + + // Process each cell in the row + const cells = row.querySelectorAll('td, th'); + cells.forEach(cell => { + const cellDiv = document.createElement('div'); + cellDiv.style.flex = '1'; + cellDiv.style.padding = '5px'; + cellDiv.style.borderBottom = '1px solid #eee'; + cellDiv.innerHTML = cell.innerHTML; + rowDiv.appendChild(cellDiv); + }); + + replacementDiv.appendChild(rowDiv); + }); + + // If no rows were processed, just use the table's inner HTML + if (rows.length === 0) { + replacementDiv.innerHTML = table.innerHTML; + } + + // Replace the table with the div + if (table.parentNode) { + table.parentNode.replaceChild(replacementDiv, table); + convertedCount++; + } + } + // 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'); + // Add width attribute + table.setAttribute('width', '100%'); + + // Add width to cells + const cells = table.querySelectorAll('td, th'); + cells.forEach(cell => { + if (cell instanceof HTMLTableCellElement && !cell.hasAttribute('width')) { cell.setAttribute('width', '100'); } - cell.style.padding = '4px'; - cell.style.textAlign = 'left'; - cell.style.verticalAlign = 'top'; + }); + } 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++; } - }); - - // Apply minimal styling to ensure it renders correctly - table.setAttribute('style', 'border-collapse: collapse; width: 100%; max-width: 100%; margin: 8px 0;'); - // Add explicit width attribute to the table - table.setAttribute('width', '100%'); - return; - } - - // Preserve the main forwarded email header table - if (isForwardedEmail && - table.innerHTML.includes('From:') && - table.innerHTML.includes('Date:') && - table.innerHTML.includes('Subject:')) { - console.log('Preserving forwarded email header table'); - // Ensure the table has proper styling - table.setAttribute('style', 'margin: 10px 0; border-collapse: collapse; font-size: 13px; color: #333;'); - // Add explicit width attribute - table.setAttribute('width', '100%'); - - // Add width to cells - const cells = table.querySelectorAll('td, th'); - cells.forEach(cell => { - if (cell instanceof HTMLTableCellElement && !cell.hasAttribute('width')) { - // First column (labels) narrower - if (cell.textContent?.includes(':')) { - cell.setAttribute('width', '80'); - } else { - cell.setAttribute('width', '400'); - } - } - }); - return; - } - - // 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('style=') && // No complex styling - !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%'); - return; - } - - // 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++; + } } }); - console.log(`Converted ${convertedCount} complex tables to divs to prevent Quill errors`); + console.log(`Converted ${convertedCount} tables to divs to prevent Quill errors`); return tempDiv.innerHTML; } @@ -157,6 +159,7 @@ const RichEmailEditor: React.FC = ({ const toolbarRef = useRef(null); const quillRef = useRef(null); const [isReady, setIsReady] = useState(false); + const [isReplyOrForward, setIsReplyOrForward] = useState(false); // Initialize Quill editor when component mounts useEffect(() => { @@ -164,23 +167,41 @@ const RichEmailEditor: React.FC = ({ const initializeQuill = async () => { if (!editorRef.current || !toolbarRef.current) return; + // First, detect if content is reply/forward to determine editor mode + const contentIsReplyOrForward = initialContent ? ( + initialContent.includes('wrote:') || + initialContent.includes(' = ({ clipboard: { matchVisual: false // Disable clipboard matching for better HTML handling }, - // Don't initialize better-table yet - we'll do it after content is loaded - 'better-table': false, + // Only enable better-table for regular content, not for replies/forwards + 'better-table': tableModule && !contentIsReplyOrForward ? true : false, }, placeholder: placeholder, theme: 'snow', @@ -226,17 +247,10 @@ const RichEmailEditor: React.FC = ({ hasBlockquote: initialContent.includes(' = ({ containsQuoteHeader: sanitizedContent.includes('wrote:'), hasTable: sanitizedContent.includes(' = ({ } } else { // Special handling for reply or forwarded content - if (isReplyOrForward) { + if (contentIsReplyOrForward) { console.log('Using special handling for reply/forward content'); - // Clean up any problematic table structures, with special care for quoted content - const cleanedContent = cleanupTableStructures(sanitizedContent); + // 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 = sanitizedContent; + quillRef.current.root.innerHTML = cleanedContent; } // Set the direction for the content @@ -352,43 +369,6 @@ const RichEmailEditor: React.FC = ({ if (editorContainer) { editorContainer.classList.add('email-editor-container'); } - - // Safe initialization of better-table module if available - if (tableModule) { - try { - // Wait a small delay to ensure content is properly set before initializing table module - setTimeout(() => { - if (quillRef.current) { - // First check if content has tables and whether it's a reply/forward - const hasReplyForwardContent = - quillRef.current.root.innerHTML.includes('wrote:') || - quillRef.current.root.innerHTML.includes('blockquote') || - quillRef.current.root.innerHTML.includes('Forwarded message'); - - const hasTables = quillRef.current.root.innerHTML.includes(' = ({ }); // Check if content is reply or forward to use special handling - const isReplyOrForward = + const contentIsReplyOrForward = initialContent.includes('wrote:') || initialContent.includes(' = ({ containsQuoteHeader: sanitizedContent.includes('wrote:'), hasTable: sanitizedContent.includes(' = ({ quillRef.current.setText(textContent || 'No content available'); } } else { - // Special handling for reply or forward content - let contentToSet = sanitizedContent; - - if (isReplyOrForward) { + // Process content based on type + if (contentIsReplyOrForward) { console.log('Using special handling for reply/forward content update'); - // Clean up tables with special care for quoted content - contentToSet = cleanupTableStructures(sanitizedContent); - // Set content directly to the root element + // 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) { - // Temporarily disable the better-table module if it's initialized - if (quillRef.current.getModule('better-table')) { - quillRef.current.getModule('better-table').hideTableTools(); - } - - // Set content without table handling by the module - quillRef.current.root.innerHTML = contentToSet; + quillRef.current.root.innerHTML = cleanedContent; // Delay applying formatting to ensure Quill is fully ready setTimeout(() => { @@ -505,9 +493,11 @@ const RichEmailEditor: React.FC = ({ }, 100); } } else { - // For regular content, use Quill's normal process + // For regular content, use normal processing + const cleanedContent = cleanupTableStructures(sanitizedContent, false); + if (quillRef.current && quillRef.current.root) { - quillRef.current.root.innerHTML = contentToSet; + quillRef.current.root.innerHTML = cleanedContent; // Safely apply formatting try { @@ -549,7 +539,7 @@ const RichEmailEditor: React.FC = ({ } } } - }, [initialContent, isReady]); + }, [initialContent, isReady, isReplyOrForward]); return (