diff --git a/components/ComposeEmail.tsx b/components/ComposeEmail.tsx index 79d205dd..18764a55 100644 --- a/components/ComposeEmail.tsx +++ b/components/ComposeEmail.tsx @@ -122,13 +122,20 @@ export default function ComposeEmail({ console.log('[DEBUG] Attempting to fetch email content directly'); try { // Fetch the email content if not available - const response = await fetch(`/api/courrier/${emailToProcess.id}?folder=${encodeURIComponent(emailToProcess.folder || 'INBOX')}`); + const response = await fetch(`/api/courrier/${emailToProcess.id}?folder=${encodeURIComponent(emailToProcess.folder || 'INBOX')}&fetchFull=true`); if (!response.ok) { throw new Error(`Failed to fetch email content: ${response.status}`); } const fullContent = await response.json(); + console.log('[DEBUG] API response for full email content:', { + hasContent: !!fullContent?.content, + hasBody: !!fullContent?.body, + hasHtml: !!fullContent?.html, + hasText: !!fullContent?.text, + contentLength: fullContent?.content?.length || 0 + }); // Update the email content with the fetched full content if (fullContent && fullContent.content) { @@ -144,7 +151,23 @@ export default function ComposeEmail({ console.log('[DEBUG] Successfully fetched TEXT for reply/forward'); emailToProcess.content = fullContent.text; } else { - throw new Error('No content in fetched email'); + console.error('[DEBUG] No usable content found in API response'); + + // Try using fullContent directly if it's a string + if (typeof fullContent === 'string' && fullContent.length > 0) { + console.log('[DEBUG] Using fullContent string directly'); + emailToProcess.content = fullContent; + } else { + throw new Error('No content in fetched email'); + } + } + + // Ensure we actually have content + if (!emailToProcess.content || emailToProcess.content.trim().length === 0) { + console.error('[DEBUG] Content still empty after fetch, using fallback'); + // Use any available preview or raw data + emailToProcess.content = emailToProcess.preview || + (fullContent.raw ? fullContent.raw : 'Email content unavailable'); } } catch (fetchError) { console.error('[DEBUG] Error fetching email content:', fetchError); @@ -173,7 +196,12 @@ export default function ComposeEmail({ } } - console.log('[DEBUG] Sending content to parse-email API, length:', emailToProcess!.content.length); + // Add more debug logging to track content fetching + console.log('[DEBUG] Sending content to parse-email API, content type:', + typeof emailToProcess!.content, + 'length:', emailToProcess!.content.length, + 'starts with:', emailToProcess!.content.substring(0, 50) + ); let emailContent; let parseSuccess = false; @@ -202,7 +230,21 @@ export default function ComposeEmail({ throw new Error(data.error || 'Failed to parse email'); } + // Prioritize HTML content if available, fallback to text emailContent = data.html || data.text || ''; + + // Add a fallback check if both HTML and text are empty + if (!emailContent.trim()) { + // Try to extract content from the raw source + console.log('[DEBUG] Empty content from parser, using raw content as fallback'); + emailContent = emailToProcess.content; + + // If content looks like HTML, use it directly, otherwise wrap in pre tags + if (!emailContent.startsWith('<') || !emailContent.endsWith('>')) { + emailContent = `
${emailContent}
`; + } + } + parseSuccess = true; } catch (error) { console.error('[DEBUG] API parse error:', error); @@ -211,117 +253,128 @@ export default function ComposeEmail({ // If content looks like HTML, use it directly, otherwise wrap in pre tags if (!emailContent.startsWith('<') || !emailContent.endsWith('>')) { - emailContent = `
${emailContent}
`; + emailContent = `
${emailContent}
`; } } - if (!emailContent) { - console.warn('[DEBUG] No content available after parsing'); - emailContent = '
No content available
'; + if (!emailContent || !emailContent.trim()) { + console.warn('[DEBUG] No content available after parsing, trying direct content'); + // Final fallback: Try to use direct content or preview + emailContent = emailToProcess.content || emailToProcess.preview || + (emailToProcess.body ? + `
${emailToProcess.body}
` : + '
No content available in the original message
'); } // Format the reply/forward content - const contentLength = emailToProcess && emailToProcess.content ? emailToProcess.content.length : 0; - console.log('[DEBUG] Sending content to parse-email API, length:', contentLength); - - let formattedContent; - try { - if (emailToProcess && emailToProcess.content) { - // Process email 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); - }; - - const quotedContent = forwardFrom ? ` -
- ---------- 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 - 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 }); - - // 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 formatQuotedContent = (content: string) => { + // First try to clean up any problematic formatting + let cleanedContent = content; + + // If content is very short or empty, add a clear message + if (!cleanedContent || cleanedContent.trim().length < 10) { + return `
Original message was empty or could not be loaded
`; } - } catch (error) { - console.error('[DEBUG] Error formatting email content:', error); - emailContent = '
Error parsing original message content.
'; + + // If we're working with plain text, make it readable + if (!cleanedContent.includes('<') || !cleanedContent.includes('>')) { + return `
${cleanedContent}
`; + } + + // For HTML content, ensure it's properly contained + return cleanedContent; + }; + + // Process email 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); + }; + + const quotedContent = forwardFrom ? ` +
+ ---------- 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)}
` : ''} +
+
+ ${formatQuotedContent(emailContent)} +
+ ` : ` +
+ On ${new Date(emailToProcess?.date || Date.now()).toLocaleString()}, ${formatEmailAddresses(emailToProcess?.from) || 'Unknown Sender'} wrote: +
+
+ ${formatQuotedContent(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 }); + + // 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 initializing compose content:', error); @@ -462,6 +515,27 @@ export default function ComposeEmail({ } }; + // Add focus handling for better UX + const handleComposeAreaClick = (e: React.MouseEvent) => { + // If the click is directly on the compose area and not on any child element + if (e.target === e.currentTarget) { + // Find the cursor position element + const cursorPosition = e.currentTarget.querySelector('.cursor-position'); + if (cursorPosition) { + // Focus the cursor position element + (cursorPosition as HTMLElement).focus(); + + // Set cursor at the beginning + const selection = window.getSelection(); + const range = document.createRange(); + range.setStart(cursorPosition, 0); + range.collapse(true); + selection?.removeAllRanges(); + selection?.addRange(range); + } + } + }; + if (!showCompose) return null; return ( @@ -562,6 +636,7 @@ export default function ComposeEmail({ ref={composeBodyRef} contentEditable="true" onInput={handleInput} + onClick={handleComposeAreaClick} className="flex-1 w-full bg-white border border-gray-300 rounded-md p-4 text-black overflow-y-auto focus:outline-none focus:ring-1 focus:ring-blue-500" style={{ direction: 'ltr',