panel 2 courier api restore

This commit is contained in:
alma 2025-04-26 08:27:31 +02:00
parent f56304775a
commit c1b1e74519
3 changed files with 92 additions and 201 deletions

View File

@ -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('<html') || emailContent.includes('<body')) {
resultObj.html = emailContent;
} else {
// Convert plain text to HTML
resultObj.html = emailContent
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\n/g, '<br>');
}
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 }
);
}

View File

@ -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 = `
<div style="border-top: 1px solid #e5e7eb; margin-top: 20px; padding-top: 20px; color: #6b7280; font-size: 14px;">
<p>---------- Forwarded message ---------</p>
<p>From: ${decoded.from || ''}</p>
<p>Date: ${formatDate(decoded.date ? new Date(decoded.date) : null)}</p>
<p>Subject: ${decoded.subject || ''}</p>
<p>To: ${decoded.to || ''}</p>
</div>
`;
} else {
emailHeader = `
<div style="border-top: 1px solid #e5e7eb; margin-top: 20px; padding-top: 20px; color: #6b7280; font-size: 14px;">
<p>On ${formatDate(decoded.date ? new Date(decoded.date) : null)}, ${decoded.from || ''} wrote:</p>
</div>
`;
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 ? `
<div style="border-top: 1px solid #e5e7eb; padding-top: 20px; margin-top: 20px; color: #6b7280; font-size: 0.875rem;">
---------- Forwarded message ---------<br/>
From: ${emailToProcess.from}<br/>
Date: ${new Date(emailToProcess.date).toLocaleString()}<br/>
Subject: ${emailToProcess.subject}<br/>
To: ${emailToProcess.to}<br/>
${emailToProcess.cc ? `Cc: ${emailToProcess.cc}<br/>` : ''}
</div>
<div style="margin-top: 10px; color: #374151;">
${emailContent}
</div>
` : `
<div style="border-top: 1px solid #e5e7eb; padding-top: 20px; margin-top: 20px; color: #6b7280; font-size: 0.875rem;">
On ${new Date(emailToProcess.date).toLocaleString()}, ${emailToProcess.from} wrote:
</div>
<blockquote style="margin: 10px 0 0 10px; padding-left: 1em; border-left: 2px solid #e5e7eb; color: #374151;">
${emailContent}
</blockquote>
`;
// Set the content in the compose area with proper structure
const formattedContent = `
<div class="compose-area" contenteditable="true" style="min-height: 100px; padding: 10px;">
<div style="min-height: 20px;"><br/></div>
${quotedContent}
</div>
`;
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 = `<pre style="white-space: pre-wrap; font-family: inherit;">${decoded.text}</pre>`;
} else {
emailContent = '<div>No content available</div>';
}
// Set the content in the compose area with proper structure
const wrappedContent = `
<div class="compose-area" contenteditable="true" style="min-height: 100px; padding: 10px;">
<div class="cursor-position" style="min-height: 20px; cursor: text;"><br/></div>
${emailHeader}
<div class="email-content" style="margin-top: 10px; border: 1px solid #e5e7eb; padding: 15px; border-radius: 4px; max-height: 500px; overflow-y: auto;">
${emailContent}
</div>
</div>
`;
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 = `
<div class="compose-area" contenteditable="true">
<br/>
<div style="color: #64748b;">
---------- Original Message ---------<br/>
${emailToProcess.subject ? `Subject: ${emailToProcess.subject}<br/>` : ''}
${emailToProcess.from ? `From: ${emailToProcess.from}<br/>` : ''}
${emailToProcess.date ? `Date: ${new Date(emailToProcess.date).toLocaleString()}<br/>` : ''}
</div>
<div style="color: #64748b; border-left: 2px solid #e5e7eb; padding-left: 10px; margin: 10px 0;">
${emailToProcess.preview || 'No content available'}
</div>
</div>
`;
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);

View File

@ -19,7 +19,7 @@ export async function decodeComposeContent(content: string): Promise<ParsedConte
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ emailContent: content }),
body: JSON.stringify({ email: content }),
});
if (!response.ok) {