diff --git a/app/api/parse-email/route.ts b/app/api/parse-email/route.ts index c23e3059..ad03828d 100644 --- a/app/api/parse-email/route.ts +++ b/app/api/parse-email/route.ts @@ -11,81 +11,38 @@ function getEmailAddress(address: AddressObject | AddressObject[] | undefined): export async function POST(request: Request) { try { - console.log('[DEBUG] Parse-email API called'); - const body = await request.json(); - const emailContent = body.email || body.emailContent; + const { email } = body; - if (!emailContent || typeof emailContent !== 'string') { - console.error('[DEBUG] Parse-email API error: Invalid email content'); + if (!email || typeof email !== 'string') { return NextResponse.json( { error: 'Invalid email content' }, { status: 400 } ); } - console.log('[DEBUG] Parse-email API processing email content, length:', emailContent.length); - console.log('[DEBUG] Content sample:', emailContent.substring(0, 100) + '...'); + const parsed = await simpleParser(email); - try { - const parsed = await simpleParser(emailContent); - - console.log('[DEBUG] Parse-email API successfully parsed email:', { - hasSubject: !!parsed.subject, - hasHtml: !!parsed.html, - hasText: !!parsed.text, - hasTextAsHtml: !!parsed.textAsHtml, - fromCount: parsed.from ? (Array.isArray(parsed.from) ? parsed.from.length : 1) : 0, - attachmentCount: parsed.attachments?.length || 0 - }); - - return NextResponse.json({ - subject: parsed.subject || null, - from: getEmailAddress(parsed.from), - to: getEmailAddress(parsed.to), - cc: getEmailAddress(parsed.cc), - bcc: getEmailAddress(parsed.bcc), - date: parsed.date || null, - html: parsed.html || parsed.textAsHtml || null, - text: parsed.text || null, - attachments: parsed.attachments?.map(att => ({ - filename: att.filename, - contentType: att.contentType, - size: att.size - })) || [], - headers: parsed.headers || {} - }); - } catch (parseError) { - console.error('[DEBUG] Parse-email API error parsing email:', parseError); - - // Try simpler parsing method for more resilience - try { - console.log('[DEBUG] Attempting fallback parsing method'); - const resultObj: any = { text: emailContent, html: null }; - - // Simple check if it might be HTML - if (emailContent.includes('/g, '>') - .replace(/\n/g, '
'); - } - - console.log('[DEBUG] Fallback parsing generated simple result'); - return NextResponse.json(resultObj); - } catch (fallbackError) { - console.error('[DEBUG] Even fallback parsing failed:', fallbackError); - throw parseError; // Throw the original error - } - } + return NextResponse.json({ + subject: parsed.subject || null, + from: getEmailAddress(parsed.from), + to: getEmailAddress(parsed.to), + cc: getEmailAddress(parsed.cc), + bcc: getEmailAddress(parsed.bcc), + date: parsed.date || null, + html: parsed.html || null, + text: parsed.textAsHtml || parsed.text || null, + attachments: parsed.attachments?.map(att => ({ + filename: att.filename, + contentType: att.contentType, + size: att.size + })) || [], + headers: parsed.headers || {} + }); } catch (error) { - console.error('[DEBUG] Parse-email API unhandled error:', error); + console.error('Error parsing email:', error); return NextResponse.json( - { error: 'Failed to parse email', details: error instanceof Error ? error.message : 'Unknown error' }, + { error: 'Failed to parse email' }, { status: 500 } ); } diff --git a/components/ComposeEmail.tsx b/components/ComposeEmail.tsx index a008d984..447a4fbc 100644 --- a/components/ComposeEmail.tsx +++ b/components/ComposeEmail.tsx @@ -125,7 +125,7 @@ export default function ComposeEmail({ return; } - // Check if we need to fetch full content first - same as panel 3 + // Check if we need to fetch full content first if (!emailToProcess.content || emailToProcess.content.length === 0) { console.log('[DEBUG] Need to fetch content before composing reply/forward'); @@ -156,144 +156,78 @@ export default function ComposeEmail({ } } - // Now proceed with the usual decoding - const type = replyTo ? 'reply' : 'forward'; + // Parse the original email using the API + const response = await fetch('/api/parse-email', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ email: emailToProcess.content }), + }); + + if (!response.ok) { + throw new Error(`Failed to parse email: ${response.status}`); + } - // DIRECTLY MATCH PANEL 3 IMPLEMENTATION - try { - const decoded = await decodeEmail(emailToProcess.content); - console.log('[DEBUG] Decoded email for compose:', { - hasHtml: !!decoded.html, - hasText: !!decoded.text, - from: decoded.from, - subject: decoded.subject - }); - - // Create the email container with header information - let emailHeader = ''; - if (type === 'forward') { - emailHeader = ` -
-

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

-

From: ${decoded.from || ''}

-

Date: ${formatDate(decoded.date ? new Date(decoded.date) : null)}

-

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

-

To: ${decoded.to || ''}

-
- `; - } else { - emailHeader = ` -
-

On ${formatDate(decoded.date ? new Date(decoded.date) : null)}, ${decoded.from || ''} wrote:

-
- `; + const data = await response.json(); + console.log('[DEBUG] Parsed email data:', { + hasHtml: !!data.html, + hasText: !!data.text, + from: data.from, + subject: data.subject + }); + + const emailContent = data.html || data.text || ''; + + // Format the reply/forward content + const quotedContent = forwardFrom ? ` +
+ ---------- Forwarded message ---------
+ From: ${emailToProcess.from}
+ Date: ${new Date(emailToProcess.date).toLocaleString()}
+ Subject: ${emailToProcess.subject}
+ To: ${emailToProcess.to}
+ ${emailToProcess.cc ? `Cc: ${emailToProcess.cc}
` : ''} +
+
+ ${emailContent} +
+ ` : ` +
+ On ${new Date(emailToProcess.date).toLocaleString()}, ${emailToProcess.from} wrote: +
+
+ ${emailContent} +
+ `; + + // Set the content in the compose area with proper structure + const formattedContent = ` +
+

+ ${quotedContent} +
+ `; + + if (composeBodyRef.current) { + composeBodyRef.current.innerHTML = formattedContent; + + // Place cursor at the beginning before the quoted content + const selection = window.getSelection(); + const range = document.createRange(); + const firstDiv = composeBodyRef.current.querySelector('div[style*="min-height: 20px;"]'); + if (firstDiv) { + range.setStart(firstDiv, 0); + range.collapse(true); + selection?.removeAllRanges(); + selection?.addRange(range); + (firstDiv as HTMLElement).focus(); } - - // Get the proper content with minimal sanitization to preserve structure - let emailContent = ''; - if (decoded.html) { - // Allow ALL HTML elements and attributes to fully preserve formatting - emailContent = DOMPurify.sanitize(decoded.html, { - ADD_TAGS: ['style', 'meta', 'link', 'script'], - ADD_ATTR: ['*', 'style', 'class', 'id', 'src', 'href', 'target', 'rel', 'align', 'valign', 'border', 'cellpadding', 'cellspacing', 'bgcolor', 'width', 'height'], - ALLOW_UNKNOWN_PROTOCOLS: true, - WHOLE_DOCUMENT: true, - RETURN_DOM: false, - KEEP_CONTENT: true - }); - } else if (decoded.text) { - emailContent = `
${decoded.text}
`; - } else { - emailContent = '
No content available
'; - } - - // Set the content in the compose area with proper structure - const wrappedContent = ` -
-

- ${emailHeader} - -
- `; - if (composeBodyRef.current) { - composeBodyRef.current.innerHTML = wrappedContent; - - // Place cursor at the beginning before the quoted content - const selection = window.getSelection(); - const range = document.createRange(); - const firstDiv = composeBodyRef.current.querySelector('.cursor-position'); - if (firstDiv) { - range.setStart(firstDiv, 0); - range.collapse(true); - selection?.removeAllRanges(); - selection?.addRange(range); - (firstDiv as HTMLElement).focus(); - } - - // After setting the HTML content, add event listeners for scrolling - const messageContents = composeBodyRef.current.querySelectorAll('.email-content'); - messageContents.forEach(container => { - // Make sure the container is properly styled for scrolling - (container as HTMLElement).style.maxHeight = '400px'; - (container as HTMLElement).style.overflowY = 'auto'; - (container as HTMLElement).style.border = '1px solid #e5e7eb'; - (container as HTMLElement).style.borderRadius = '4px'; - (container as HTMLElement).style.padding = '15px'; - - // Ensure wheel events are properly handled - if (!(container as HTMLElement).hasAttribute('data-scroll-handler-attached')) { - container.addEventListener('wheel', (e: Event) => { - const wheelEvent = e as WheelEvent; - const target = e.currentTarget as HTMLElement; - - // Check if we're at the boundary of the scrollable area - const isAtBottom = target.scrollHeight - target.scrollTop <= target.clientHeight + 1; - const isAtTop = target.scrollTop <= 0; - - // Only prevent default if we're not at the boundaries in the direction of scrolling - if ((wheelEvent.deltaY > 0 && !isAtBottom) || (wheelEvent.deltaY < 0 && !isAtTop)) { - e.stopPropagation(); - e.preventDefault(); // Prevent the parent container from scrolling - } - }, { passive: false }); - - // Mark this element as having a scroll handler attached - (container as HTMLElement).setAttribute('data-scroll-handler-attached', 'true'); - } - }); - - // Update compose state - setComposeBody(wrappedContent); - setLocalContent(wrappedContent); - console.log('[DEBUG] Successfully set compose content with scrollable message area'); - } - } catch (error) { - console.error('[DEBUG] Error parsing email for compose:', error); - - // Fallback to basic content display - const errorContent = ` -
-
-
- ---------- Original Message ---------
- ${emailToProcess.subject ? `Subject: ${emailToProcess.subject}
` : ''} - ${emailToProcess.from ? `From: ${emailToProcess.from}
` : ''} - ${emailToProcess.date ? `Date: ${new Date(emailToProcess.date).toLocaleString()}
` : ''} -
-
- ${emailToProcess.preview || 'No content available'} -
-
- `; - - if (composeBodyRef.current) { - composeBodyRef.current.innerHTML = errorContent; - setComposeBody(errorContent); - setLocalContent(errorContent); - } + // Update compose state + setComposeBody(formattedContent); + setLocalContent(formattedContent); + console.log('[DEBUG] Successfully set compose content'); } } catch (error) { console.error('[DEBUG] Error initializing compose content:', error); diff --git a/lib/compose-mime-decoder.ts b/lib/compose-mime-decoder.ts index 04bc1fd5..b5309f8f 100644 --- a/lib/compose-mime-decoder.ts +++ b/lib/compose-mime-decoder.ts @@ -19,7 +19,7 @@ export async function decodeComposeContent(content: string): Promise