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 = `
-
- `;
-
- 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 = `
+
+ `;
+
+ 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',