From a585bcb51dee38bb60d3713e40f6d10cac099489 Mon Sep 17 00:00:00 2001 From: alma Date: Fri, 25 Apr 2025 21:42:19 +0200 Subject: [PATCH] panel 2 courier api restore --- components/ComposeEmail.tsx | 207 +++++++++++++++++------------------- 1 file changed, 96 insertions(+), 111 deletions(-) diff --git a/components/ComposeEmail.tsx b/components/ComposeEmail.tsx index 188b52d1..eb08d6a4 100644 --- a/components/ComposeEmail.tsx +++ b/components/ComposeEmail.tsx @@ -10,6 +10,8 @@ import { decodeComposeContent, encodeComposeContent } from '@/lib/compose-mime-d import { Email } from '@/app/courrier/page'; import mime from 'mime'; import { simpleParser } from 'mailparser'; +import { decodeEmail, cleanHtml } from '@/lib/mail-parser-wrapper'; +import DOMPurify from 'dompurify'; interface ComposeEmailProps { showCompose: boolean; @@ -126,124 +128,107 @@ export default function ComposeEmail({ // Format the reply/forward content const type = replyTo ? 'reply' : 'forward'; - // Use simple, reliable formatting for the quoted content - const formatEmailAddresses = (addresses: any) => { - if (!addresses) return 'Unknown'; - if (typeof addresses === 'string') return addresses; - if (Array.isArray(addresses)) { - return addresses.map(addr => addr.name || addr.address).join(', '); - } - return String(addresses); - }; - - // Extract plain text content for reliable display - let emailContent = ''; try { - // Parse the original email to get clean content - const response = await fetch('/api/parse-email', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ email: emailToProcess.content }), - }); + // Parse the email to get headers and content - using the same function as panel 3 + const decoded = await decodeEmail(emailToProcess.content); - if (response.ok) { - const data = await response.json(); - emailContent = data.text || ''; - // If no text content, try to extract from HTML - if (!emailContent && data.html) { - // Create a temporary div to extract text from HTML - const tempDiv = document.createElement('div'); - tempDiv.innerHTML = data.html; - emailContent = tempDiv.textContent || tempDiv.innerText || ''; + // Format the content based on reply type + const quotedContent = type === 'forward' ? ` +
+ ---------- Forwarded message ---------
+ From: ${decoded.from || 'Unknown Sender'}
+ Date: ${decoded.date ? decoded.date.toLocaleString() : new Date().toLocaleString()}
+ Subject: ${decoded.subject || 'No Subject'}
+ To: ${decoded.to || ''}
+ ${decoded.cc ? `Cc: ${decoded.cc}
` : ''} +
+
+ ${decoded.html ? DOMPurify.sanitize(decoded.html) : (decoded.text ? `
${decoded.text}
` : 'No content available')} +
+ ` : ` +
+ On ${decoded.date ? decoded.date.toLocaleString() : new Date().toLocaleString()}, ${decoded.from || 'Unknown Sender'} wrote: +
+
+ ${decoded.html ? DOMPurify.sanitize(decoded.html) : (decoded.text ? `
${decoded.text}
` : 'No content available')} +
+ `; + + // 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('.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('.message-content'); + messageContents.forEach(container => { + // Make sure the container is properly styled for scrolling + (container as HTMLElement).style.maxHeight = '300px'; + (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 = '10px'; + + // 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 }); // Important for preventDefault to work + + // Mark this element as having a scroll handler attached + (container as HTMLElement).setAttribute('data-scroll-handler-attached', 'true'); + } + }); + + // Update compose state + setComposeBody(formattedContent); + setLocalContent(formattedContent); + console.log('[DEBUG] Successfully set compose content with scrollable message area'); } } catch (error) { console.error('[DEBUG] Error parsing email:', error); // Fallback to simple content extraction - emailContent = emailToProcess.content.replace(/<[^>]*>/g, ''); - } - - // Format the content based on reply type - const quotedContent = type === 'forward' ? ` -
- ---------- Forwarded message ---------
- From: ${formatEmailAddresses(emailToProcess.from) || 'Unknown Sender'}
- Date: ${new Date(emailToProcess.date || Date.now()).toLocaleString()}
- Subject: ${emailToProcess.subject || 'No Subject'}
- To: ${formatEmailAddresses(emailToProcess.to) || ''}
- ${emailToProcess.cc ? `Cc: ${formatEmailAddresses(emailToProcess.cc)}
` : ''} -
-
-
${emailContent}
-
- ` : ` -
- On ${new Date(emailToProcess.date || Date.now()).toLocaleString()}, ${formatEmailAddresses(emailToProcess.from) || 'Unknown Sender'} 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('.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('.message-content'); - messageContents.forEach(container => { - // Make sure the container is properly styled for scrolling - (container as HTMLElement).style.maxHeight = '300px'; - (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 = '10px'; - - // 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 }); // Important for preventDefault to work - - // Mark this element as having a scroll handler attached - (container as HTMLElement).setAttribute('data-scroll-handler-attached', 'true'); - } - }); - - // Update compose state - setComposeBody(formattedContent); - setLocalContent(formattedContent); - console.log('[DEBUG] Successfully set compose content with scrollable message area'); + const fallbackContent = emailToProcess.content.replace(/<[^>]*>/g, ''); + composeBodyRef.current.innerHTML = ` +
+
+
Error loading original message.
+
+ Technical details: ${error instanceof Error ? error.message : 'Unknown error'} +
+
+ `; + setComposeBody(fallbackContent); + setLocalContent(fallbackContent); } } catch (error) { console.error('[DEBUG] Error initializing compose content:', error);