diff --git a/components/email/ComposeEmail.tsx b/components/email/ComposeEmail.tsx index b5b367b1..5366bf19 100644 --- a/components/email/ComposeEmail.tsx +++ b/components/email/ComposeEmail.tsx @@ -17,6 +17,7 @@ import { } from "@/components/ui/dropdown-menu"; import RichEmailEditor from '@/components/email/RichEmailEditor'; import { detectTextDirection } from '@/lib/utils/text-direction'; +import EmailContentDisplay from './EmailContentDisplay'; // Import from the centralized utils import { @@ -346,6 +347,10 @@ export default function ComposeEmail(props: ComposeEmailProps) { } }; + const handleContentChange = (html: string) => { + setEmailContent(html); + }; + return (
{/* Header */} @@ -480,11 +485,11 @@ export default function ComposeEmail(props: ComposeEmailProps) { {/* Message Body */} { - setEmailContent(html); - }} + onChange={handleContentChange} placeholder="Write your message here..." - minHeight="320px" + preserveFormatting={type === 'forward' || type === 'reply' || type === 'reply-all'} + minHeight="200px" + maxHeight="calc(100vh - 400px)" /> {/* Attachments */} diff --git a/components/email/RichEmailEditor.tsx b/components/email/RichEmailEditor.tsx index 4b6093cd..eadbb879 100644 --- a/components/email/RichEmailEditor.tsx +++ b/components/email/RichEmailEditor.tsx @@ -1,6 +1,6 @@ 'use client'; -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useRef, useState, useMemo } from 'react'; import 'quill/dist/quill.snow.css'; import { sanitizeHtml } from '@/lib/utils/dom-purify-config'; import { detectTextDirection } from '@/lib/utils/text-direction'; @@ -28,6 +28,15 @@ const RichEmailEditor: React.FC = ({ const quillRef = useRef(null); const [isReady, setIsReady] = useState(false); + // Determine if content is pre-formatted (for forward/reply) + const isPreFormattedContent = useMemo(() => { + return preserveFormatting && initialContent && ( + (initialContent.includes('---------- Forwarded message ----------') || + initialContent.includes('wrote:')) && + initialContent.includes(' { // Import Quill dynamically (client-side only) @@ -36,21 +45,32 @@ const RichEmailEditor: React.FC = ({ const Quill = (await import('quill')).default; - // Import quill-better-table + // Check if content already appears to be pre-formatted as a reply or forward + const isPreFormattedContent = initialContent && ( + (initialContent.includes('---------- Forwarded message ----------') || + 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, + // Don't initialize better-table if this is pre-formatted content + 'better-table': isPreFormattedContent ? false : tableModule, }, placeholder: placeholder, theme: 'snow', @@ -93,7 +113,8 @@ const RichEmailEditor: React.FC = ({ startsWithHtml: initialContent.trim().startsWith('<'), containsForwardedMessage: initialContent.includes('---------- Forwarded message ----------'), containsReplyIndicator: initialContent.includes('wrote:'), - hasBlockquote: initialContent.includes(' = ({ quillRef.current.setText('Error loading content'); } } else { - // Use direct innerHTML setting for the initial content - quillRef.current.root.innerHTML = sanitizedContent; - - // Set the direction for the content - if (quillRef.current && quillRef.current.format) { + // Special handling for reply/forward content + if (isPreFormattedContent && preserveFormatting) { + console.log('Setting pre-formatted reply/forward content with special handling'); + + // First clear the editor and add a line break for the user to type in + quillRef.current.setText('\n\n'); + + // Set cursor at the top + quillRef.current.setSelection(0, 0); + + // Now append the quoted content as a read-only section + const quoteIndex = quillRef.current.getText().length; + quillRef.current.clipboard.dangerouslyPasteHTML(quoteIndex, sanitizedContent); + + // Set direction for the editor quillRef.current.format('direction', direction); if (direction === 'rtl') { quillRef.current.format('align', 'right'); } } else { - console.warn('Cannot format content: editor not fully initialized'); + // Use direct innerHTML setting for the initial content + quillRef.current.root.innerHTML = sanitizedContent; + + // 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'); + } } } @@ -267,6 +309,17 @@ const RichEmailEditor: React.FC = ({ console.log('Content appears to be pre-formatted as reply/forward, using as-is'); // Just do basic sanitization without additional processing sanitizedContent = sanitizeHtml(initialContent); + + // Disable formatting operations that might conflict with pre-formatted content + try { + // Disable better-table module to prevent errors with forwarded tables + if (quillRef.current.getModule('better-table')) { + // Try to disable better-table module operations + quillRef.current.getModule('better-table').tableSelection = null; + } + } catch (err) { + console.warn('Error disabling table module:', err); + } } else { // Full processing for regular content sanitizedContent = processHtmlContent(initialContent); @@ -297,31 +350,52 @@ const RichEmailEditor: React.FC = ({ quillRef.current.setText(textContent || 'No content available'); } } else { - // SIMPLIFIED: Set content directly to the root element rather than using clipboard - if (quillRef.current && quillRef.current.root) { - // First set the content - quillRef.current.root.innerHTML = sanitizedContent; + // Special handling for reply/forward content + if (isPreFormattedContent && preserveFormatting) { + console.log('Setting pre-formatted reply/forward content with special handling'); - // Then safely apply formatting only if quillRef is valid - try { - if (quillRef.current && quillRef.current.format && quillRef.current.root.innerHTML.trim().length > 0) { - // Set the direction for the content - quillRef.current.format('direction', direction); - if (direction === 'rtl') { - quillRef.current.format('align', 'right'); + // First clear the editor and add a line break for the user to type in + quillRef.current.setText('\n\n'); + + // Set cursor at the top + quillRef.current.setSelection(0, 0); + + // Now append the quoted content as a read-only section + const quoteIndex = quillRef.current.getText().length; + quillRef.current.clipboard.dangerouslyPasteHTML(quoteIndex, sanitizedContent); + + // Set direction for the editor + quillRef.current.format('direction', direction); + if (direction === 'rtl') { + quillRef.current.format('align', 'right'); + } + } else { + // SIMPLIFIED: Set content directly to the root element rather than using clipboard + if (quillRef.current && quillRef.current.root) { + // First set the content + quillRef.current.root.innerHTML = sanitizedContent; + + // Then safely apply formatting only if quillRef is valid + try { + if (quillRef.current && quillRef.current.format && quillRef.current.root.innerHTML.trim().length > 0) { + // 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); + } else { + console.warn('Skipping format - either editor not ready or content empty'); } - - // Force update - quillRef.current.update(); - - // Set selection to beginning - quillRef.current.setSelection(0, 0); - } else { - console.warn('Skipping format - either editor not ready or content empty'); + } catch (formatError) { + console.error('Error applying formatting:', formatError); + // Continue without formatting if there's an error } - } catch (formatError) { - console.error('Error applying formatting:', formatError); - // Continue without formatting if there's an error } } }