diff --git a/components/email/ComposeEmail.tsx b/components/email/ComposeEmail.tsx index 4c9aef4a..27c52690 100644 --- a/components/email/ComposeEmail.tsx +++ b/components/email/ComposeEmail.tsx @@ -6,8 +6,6 @@ import { X, Paperclip, ChevronDown, ChevronUp, SendHorizontal, Loader2 } from 'l import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Card, CardContent, CardHeader, CardTitle, CardFooter } from '@/components/ui/card'; -import { decodeEmail } from '@/lib/mail-parser-wrapper'; -import DOMPurify from 'isomorphic-dompurify'; interface ComposeEmailProps { initialEmail?: EmailMessage | null; @@ -39,8 +37,6 @@ export default function ComposeEmail({ const [bcc, setBcc] = useState(''); const [subject, setSubject] = useState(''); const [body, setBody] = useState(''); - - // UI state const [showCc, setShowCc] = useState(false); const [showBcc, setShowBcc] = useState(false); const [sending, setSending] = useState(false); @@ -50,13 +46,13 @@ export default function ComposeEmail({ type: string; }>>([]); - const editorRef = useRef(null); - const attachmentInputRef = useRef(null); - + // Refs + const editorRef = useRef(null); + const attachmentInputRef = useRef(null); + // Initialize the form when replying to or forwarding an email useEffect(() => { if (initialEmail && type !== 'new') { - // If it's a forward, use the same approach as Panel 3 if (type === 'forward') { initializeForwardedEmail(); } else { @@ -93,8 +89,6 @@ export default function ComposeEmail({ } }, [initialEmail, type]); - // Helper functions for formatting the forwarded message - moved outside try block - // Format date for the forwarded message header const formatDate = (date: Date | null): string => { if (!date) return ''; @@ -132,31 +126,7 @@ export default function ComposeEmail({ ).join(', '); }; - // Handle editor input - const handleEditorInput = (e: React.FormEvent) => { - // Only store the content of the editable area, not the entire HTML with CSS - // This prevents breaking complex CSS when editing - if (editorRef.current) { - // If we're in forward mode and the editor contains our wrapper structure - const editableContent = editorRef.current.querySelector('.editable-content'); - if (type === 'forward' && editableContent) { - // Only update the editable portion, preserving the CSS and header - const headerSection = editorRef.current.querySelector('.forwarded-header'); - const styleSection = editorRef.current.querySelector('.email-styles'); - - // Combine the preserved sections with the updated editable content - const updatedContent = (styleSection?.outerHTML || '') + - (headerSection?.outerHTML || '') + - editableContent.innerHTML; - setBody(updatedContent); - } else { - // For new emails or replies, we can use the entire content - setBody(e.currentTarget.innerHTML); - } - } - }; - - // Modified initializeForwardedEmail to separate CSS from content + // Initialize forwarded email const initializeForwardedEmail = async () => { if (!initialEmail) { console.error('No email available for forwarding'); @@ -164,7 +134,6 @@ export default function ComposeEmail({ return; } - // Helper functions remain the same try { setSending(true); // Use sending state to show loading @@ -176,82 +145,65 @@ export default function ComposeEmail({ setSubject(formattedSubject); - // Create a forwarded message header with proper formatting - const headerContent = ` -
-

---------- Forwarded message ---------

-

From: ${formatSender(initialEmail.from)}

-

Date: ${formatDate(initialEmail.date)}

-

Subject: ${initialEmail.subject || ''}

-

To: ${formatRecipients(initialEmail.to)}

-
`; + // Process the email content using the API + const emailContent = initialEmail.content || ''; + let processedContent = ''; - // Process content - let contentBody = ''; - let styleContent = ''; - - // Check if email content exists - if (!initialEmail.content || initialEmail.content.trim() === '') { - contentBody = '
No content available
'; - } else { + // Only attempt to parse if we have content + if (emailContent.trim()) { try { - // Parse content to extract styles and make content editable - const content = initialEmail.content; - - // Extract style tags to preserve them - const styleRegex = /]*>([\s\S]*?)<\/style>/gi; - const styles: string[] = []; - let styleMatch; - - // Find all style tags and collect them - while ((styleMatch = styleRegex.exec(content)) !== null) { - styles.push(styleMatch[0]); - } - - // Combine all styles into one non-editable section - if (styles.length > 0) { - styleContent = ``; - } - - // Use DOMPurify to sanitize the rest of the HTML content - const sanitizedContent = DOMPurify.sanitize(content, { - ADD_TAGS: ['style', 'meta', 'link', 'table', 'thead', 'tbody', 'tr', 'td', 'th', 'hr', 'font', 'div', 'span', 'a', 'img', 'b', 'strong', 'i', 'em', 'u', 'br', 'p', 'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'pre', 'code', 'center', 'section', 'header', 'footer', 'article', 'nav', 'keyframes'], - ADD_ATTR: ['*', 'colspan', 'rowspan', 'cellpadding', 'cellspacing', 'border', 'bgcolor', 'width', 'height', 'align', 'valign', 'class', 'id', 'style', 'color', 'face', 'size', 'background', 'src', 'href', 'target', 'rel', 'alt', 'title', 'name', 'animation', 'animation-name', 'animation-duration', 'animation-fill-mode'], - ALLOW_UNKNOWN_PROTOCOLS: true, - WHOLE_DOCUMENT: true, - KEEP_CONTENT: true, - FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'form', 'input', 'button', 'select', 'option', 'textarea', 'canvas', 'video', 'audio'], - FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover', 'onmouseout', 'onchange', 'onsubmit'], - USE_PROFILES: { html: true, svg: false, svgFilters: false, mathMl: false }, - FORCE_BODY: true + // Send to server API for parsing + const response = await fetch('/api/parse-email', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ email: emailContent }), }); - // Remove style tags from sanitized content (we'll add them back separately) - let contentWithoutStyles = sanitizedContent.replace(/]*>[\s\S]*?<\/style>/gi, ''); + const data = await response.json(); + if (!response.ok) { + throw new Error(data.error || 'Failed to parse email'); + } - // Wrap the remaining content in an editable div - contentBody = `
${contentWithoutStyles}
`; - } catch (e) { - console.error('Error sanitizing HTML content:', e); - contentBody = '
Error processing original content
'; + processedContent = data.html || data.text || ''; + } catch (error) { + console.error('Error parsing email content:', error); + processedContent = '
Error processing original content
'; } + } else { + processedContent = '
No content available
'; } - // Set the complete forwarded email with styles preserved separately - setBody(styleContent + headerContent + contentBody); + // Create a clean, well-formatted forwarded message + const forwardedContent = ` +
+ ---------- Forwarded message ---------
+ From: ${formatSender(initialEmail.from)}
+ Date: ${formatDate(initialEmail.date)}
+ Subject: ${initialEmail.subject || ''}
+ To: ${formatRecipients(initialEmail.to)}
+ ${initialEmail.cc && initialEmail.cc.length ? `Cc: ${formatRecipients(initialEmail.cc)}
` : ''} +
+
+ ${processedContent} +
+`; + + // Set the formatted content + setBody(forwardedContent); } catch (error) { console.error('Error initializing forwarded email:', error); - // Still provide the headers even if there's an error with the content - const errorHeaderContent = ` -
-

---------- Forwarded message ---------

-

From: ${initialEmail.from ? formatSender(initialEmail.from) : 'Unknown'}

-

Date: ${initialEmail.date ? formatDate(initialEmail.date) : ''}

-

Subject: ${initialEmail.subject || ''}

-

To: ${initialEmail.to ? formatRecipients(initialEmail.to) : ''}

+ const errorContent = ` +
+ ---------- Forwarded message ---------
+ From: ${initialEmail.from ? formatSender(initialEmail.from) : 'Unknown'}
+ Date: ${initialEmail.date ? formatDate(initialEmail.date) : ''}
+ Subject: ${initialEmail.subject || ''}
+ To: ${initialEmail.to ? formatRecipients(initialEmail.to) : ''}
-
Error loading forwarded content
`; - setBody(errorHeaderContent); +
Error loading forwarded content
`; + setBody(errorContent); } finally { setSending(false); } @@ -314,39 +266,12 @@ export default function ComposeEmail({ try { setSending(true); - // Get the email content - let emailBody = ''; - if (editorRef.current) { - // For forwarded emails, make sure to include both the style and content sections - if (type === 'forward') { - // Gather all parts: styles, header, and editable content - const styleSection = editorRef.current.querySelector('.email-styles'); - const headerSection = editorRef.current.querySelector('.forwarded-header'); - const editableContent = editorRef.current.querySelector('.editable-content'); - - // Combine all sections for the final email body - emailBody = - (styleSection?.outerHTML || '') + - (headerSection?.outerHTML || '') + - (editableContent?.innerHTML || editorRef.current.innerHTML); - - // Remove contenteditable attributes as they're not needed in the sent email - emailBody = emailBody.replace(/contenteditable="[^"]*"/g, ''); - } else { - // For new emails or replies, use the entire content - emailBody = editorRef.current.innerHTML; - } - } else { - // Fallback to using body state - emailBody = body; - } - await onSend({ to, cc: cc || undefined, bcc: bcc || undefined, subject, - body: emailBody, + body: editorRef.current?.innerHTML || body, attachments }); @@ -359,6 +284,12 @@ export default function ComposeEmail({ } }; + // Handle editor input + const handleEditorInput = (e: React.FormEvent) => { + // Store the HTML content for use in the send function + setBody(e.currentTarget.innerHTML); + }; + return (