panel 2 courier api restore
This commit is contained in:
parent
f56304775a
commit
c1b1e74519
@ -11,81 +11,38 @@ function getEmailAddress(address: AddressObject | AddressObject[] | undefined):
|
|||||||
|
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
try {
|
try {
|
||||||
console.log('[DEBUG] Parse-email API called');
|
|
||||||
|
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
const emailContent = body.email || body.emailContent;
|
const { email } = body;
|
||||||
|
|
||||||
if (!emailContent || typeof emailContent !== 'string') {
|
if (!email || typeof email !== 'string') {
|
||||||
console.error('[DEBUG] Parse-email API error: Invalid email content');
|
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'Invalid email content' },
|
{ error: 'Invalid email content' },
|
||||||
{ status: 400 }
|
{ status: 400 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[DEBUG] Parse-email API processing email content, length:', emailContent.length);
|
const parsed = await simpleParser(email);
|
||||||
console.log('[DEBUG] Content sample:', emailContent.substring(0, 100) + '...');
|
|
||||||
|
|
||||||
try {
|
return NextResponse.json({
|
||||||
const parsed = await simpleParser(emailContent);
|
subject: parsed.subject || null,
|
||||||
|
from: getEmailAddress(parsed.from),
|
||||||
console.log('[DEBUG] Parse-email API successfully parsed email:', {
|
to: getEmailAddress(parsed.to),
|
||||||
hasSubject: !!parsed.subject,
|
cc: getEmailAddress(parsed.cc),
|
||||||
hasHtml: !!parsed.html,
|
bcc: getEmailAddress(parsed.bcc),
|
||||||
hasText: !!parsed.text,
|
date: parsed.date || null,
|
||||||
hasTextAsHtml: !!parsed.textAsHtml,
|
html: parsed.html || null,
|
||||||
fromCount: parsed.from ? (Array.isArray(parsed.from) ? parsed.from.length : 1) : 0,
|
text: parsed.textAsHtml || parsed.text || null,
|
||||||
attachmentCount: parsed.attachments?.length || 0
|
attachments: parsed.attachments?.map(att => ({
|
||||||
});
|
filename: att.filename,
|
||||||
|
contentType: att.contentType,
|
||||||
return NextResponse.json({
|
size: att.size
|
||||||
subject: parsed.subject || null,
|
})) || [],
|
||||||
from: getEmailAddress(parsed.from),
|
headers: parsed.headers || {}
|
||||||
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, '&')
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>')
|
|
||||||
.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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[DEBUG] Parse-email API unhandled error:', error);
|
console.error('Error parsing email:', error);
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'Failed to parse email', details: error instanceof Error ? error.message : 'Unknown error' },
|
{ error: 'Failed to parse email' },
|
||||||
{ status: 500 }
|
{ status: 500 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -125,7 +125,7 @@ export default function ComposeEmail({
|
|||||||
return;
|
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) {
|
if (!emailToProcess.content || emailToProcess.content.length === 0) {
|
||||||
console.log('[DEBUG] Need to fetch content before composing reply/forward');
|
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
|
// Parse the original email using the API
|
||||||
const type = replyTo ? 'reply' : 'forward';
|
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
|
const data = await response.json();
|
||||||
try {
|
console.log('[DEBUG] Parsed email data:', {
|
||||||
const decoded = await decodeEmail(emailToProcess.content);
|
hasHtml: !!data.html,
|
||||||
console.log('[DEBUG] Decoded email for compose:', {
|
hasText: !!data.text,
|
||||||
hasHtml: !!decoded.html,
|
from: data.from,
|
||||||
hasText: !!decoded.text,
|
subject: data.subject
|
||||||
from: decoded.from,
|
});
|
||||||
subject: decoded.subject
|
|
||||||
});
|
const emailContent = data.html || data.text || '';
|
||||||
|
|
||||||
// Create the email container with header information
|
// Format the reply/forward content
|
||||||
let emailHeader = '';
|
const quotedContent = forwardFrom ? `
|
||||||
if (type === 'forward') {
|
<div style="border-top: 1px solid #e5e7eb; padding-top: 20px; margin-top: 20px; color: #6b7280; font-size: 0.875rem;">
|
||||||
emailHeader = `
|
---------- Forwarded message ---------<br/>
|
||||||
<div style="border-top: 1px solid #e5e7eb; margin-top: 20px; padding-top: 20px; color: #6b7280; font-size: 14px;">
|
From: ${emailToProcess.from}<br/>
|
||||||
<p>---------- Forwarded message ---------</p>
|
Date: ${new Date(emailToProcess.date).toLocaleString()}<br/>
|
||||||
<p>From: ${decoded.from || ''}</p>
|
Subject: ${emailToProcess.subject}<br/>
|
||||||
<p>Date: ${formatDate(decoded.date ? new Date(decoded.date) : null)}</p>
|
To: ${emailToProcess.to}<br/>
|
||||||
<p>Subject: ${decoded.subject || ''}</p>
|
${emailToProcess.cc ? `Cc: ${emailToProcess.cc}<br/>` : ''}
|
||||||
<p>To: ${decoded.to || ''}</p>
|
</div>
|
||||||
</div>
|
<div style="margin-top: 10px; color: #374151;">
|
||||||
`;
|
${emailContent}
|
||||||
} else {
|
</div>
|
||||||
emailHeader = `
|
` : `
|
||||||
<div style="border-top: 1px solid #e5e7eb; margin-top: 20px; padding-top: 20px; color: #6b7280; font-size: 14px;">
|
<div style="border-top: 1px solid #e5e7eb; padding-top: 20px; margin-top: 20px; color: #6b7280; font-size: 0.875rem;">
|
||||||
<p>On ${formatDate(decoded.date ? new Date(decoded.date) : null)}, ${decoded.from || ''} wrote:</p>
|
On ${new Date(emailToProcess.date).toLocaleString()}, ${emailToProcess.from} wrote:
|
||||||
</div>
|
</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) {
|
// Update compose state
|
||||||
composeBodyRef.current.innerHTML = wrappedContent;
|
setComposeBody(formattedContent);
|
||||||
|
setLocalContent(formattedContent);
|
||||||
// Place cursor at the beginning before the quoted content
|
console.log('[DEBUG] Successfully set compose 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[DEBUG] Error initializing compose content:', error);
|
console.error('[DEBUG] Error initializing compose content:', error);
|
||||||
|
|||||||
@ -19,7 +19,7 @@ export async function decodeComposeContent(content: string): Promise<ParsedConte
|
|||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ emailContent: content }),
|
body: JSON.stringify({ email: content }),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user