mail page rest
This commit is contained in:
parent
8d9694ba95
commit
f4502f15ff
@ -78,10 +78,14 @@ interface EmailAttachment {
|
||||
}
|
||||
|
||||
interface ParsedEmail {
|
||||
text: string;
|
||||
html: string;
|
||||
attachments: EmailAttachment[];
|
||||
headers?: string;
|
||||
text: string | null;
|
||||
html: string | null;
|
||||
attachments: Array<{
|
||||
filename: string;
|
||||
contentType: string;
|
||||
encoding: string;
|
||||
content: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
interface EmailMessage {
|
||||
@ -99,47 +103,91 @@ interface EmailMessage {
|
||||
};
|
||||
}
|
||||
|
||||
function parseFullEmail(emailRaw: string): ParsedEmail | EmailMessage {
|
||||
// Check if this is a multipart message by looking for boundary definition
|
||||
const boundaryMatch = emailRaw.match(/boundary="?([^"\r\n;]+)"?/i) ||
|
||||
emailRaw.match(/boundary=([^\r\n;]+)/i);
|
||||
|
||||
if (boundaryMatch) {
|
||||
const boundary = boundaryMatch[1].trim();
|
||||
function parseFullEmail(content: string): ParsedEmail {
|
||||
try {
|
||||
// First, try to parse the email headers
|
||||
const headers = parseEmailHeaders(content);
|
||||
|
||||
// Check if there's a preamble before the first boundary
|
||||
let mainHeaders = '';
|
||||
let mainContent = emailRaw;
|
||||
|
||||
// Extract the headers before the first boundary if they exist
|
||||
const firstBoundaryPos = emailRaw.indexOf('--' + boundary);
|
||||
if (firstBoundaryPos > 0) {
|
||||
const headerSeparatorPos = emailRaw.indexOf('\r\n\r\n');
|
||||
if (headerSeparatorPos > 0 && headerSeparatorPos < firstBoundaryPos) {
|
||||
mainHeaders = emailRaw.substring(0, headerSeparatorPos);
|
||||
// If it's a multipart email, process each part
|
||||
if (headers.contentType?.includes('multipart')) {
|
||||
const boundary = extractBoundary(headers.contentType);
|
||||
if (!boundary) {
|
||||
throw new Error('No boundary found in multipart content');
|
||||
}
|
||||
}
|
||||
|
||||
return processMultipartEmail(emailRaw, boundary, mainHeaders);
|
||||
} else {
|
||||
// Split headers and body
|
||||
const [headers, body] = emailRaw.split(/\r?\n\r?\n/, 2);
|
||||
|
||||
// If no boundary is found, treat as a single part message
|
||||
const emailInfo = parseEmailHeaders(headers);
|
||||
return {
|
||||
subject: extractHeader(headers, 'Subject'),
|
||||
from: extractHeader(headers, 'From'),
|
||||
to: extractHeader(headers, 'To'),
|
||||
date: extractHeader(headers, 'Date'),
|
||||
contentType: emailInfo.contentType,
|
||||
text: emailInfo.contentType.includes('text/plain') ? body : null,
|
||||
html: emailInfo.contentType.includes('text/html') ? body : null,
|
||||
attachments: [], // Add empty attachments array for single part messages
|
||||
raw: {
|
||||
headers,
|
||||
body
|
||||
const parts = content.split(boundary);
|
||||
const result: ParsedEmail = {
|
||||
text: null,
|
||||
html: null,
|
||||
attachments: []
|
||||
};
|
||||
|
||||
for (const part of parts) {
|
||||
if (!part.trim()) continue;
|
||||
|
||||
const partHeaders = parseEmailHeaders(part);
|
||||
const partContent = part.split('\r\n\r\n')[1] || '';
|
||||
|
||||
// Handle HTML content
|
||||
if (partHeaders.contentType?.includes('text/html')) {
|
||||
const decoded = decodeMIME(
|
||||
partContent,
|
||||
partHeaders.encoding || '7bit',
|
||||
partHeaders.charset || 'utf-8'
|
||||
);
|
||||
result.html = cleanHtml(decoded);
|
||||
}
|
||||
// Handle plain text content
|
||||
else if (partHeaders.contentType?.includes('text/plain')) {
|
||||
const decoded = decodeMIME(
|
||||
partContent,
|
||||
partHeaders.encoding || '7bit',
|
||||
partHeaders.charset || 'utf-8'
|
||||
);
|
||||
result.text = decoded;
|
||||
}
|
||||
// Handle attachments
|
||||
else if (partHeaders.contentType && !partHeaders.contentType.includes('text/')) {
|
||||
const filename = extractFilename(partHeaders.contentType) || 'attachment';
|
||||
result.attachments.push({
|
||||
filename,
|
||||
contentType: partHeaders.contentType,
|
||||
encoding: partHeaders.encoding || '7bit',
|
||||
content: partContent
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// If it's not multipart, handle as a single part
|
||||
const body = content.split('\r\n\r\n')[1] || '';
|
||||
const decoded = decodeMIME(
|
||||
body,
|
||||
headers.encoding || '7bit',
|
||||
headers.charset || 'utf-8'
|
||||
);
|
||||
|
||||
if (headers.contentType?.includes('text/html')) {
|
||||
return {
|
||||
html: cleanHtml(decoded),
|
||||
text: null,
|
||||
attachments: []
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
html: null,
|
||||
text: decoded,
|
||||
attachments: []
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Error parsing email:', e);
|
||||
return {
|
||||
html: null,
|
||||
text: content,
|
||||
attachments: []
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -251,14 +299,71 @@ function decodeMimeContent(content: string): string {
|
||||
return cleanHtml(content);
|
||||
}
|
||||
|
||||
// Add this helper function
|
||||
const renderEmailContent = (email: Email) => {
|
||||
const decodedContent = decodeMimeContent(email.body);
|
||||
if (email.body.includes('Content-Type: text/html')) {
|
||||
return <div dangerouslySetInnerHTML={{ __html: decodedContent }} />;
|
||||
function renderEmailContent(email: Email) {
|
||||
try {
|
||||
// First, parse the full email to get headers and body
|
||||
const parsed = parseFullEmail(email.body);
|
||||
|
||||
// If we have HTML content, render it
|
||||
if (parsed.html) {
|
||||
return (
|
||||
<div
|
||||
className="prose max-w-none"
|
||||
dangerouslySetInnerHTML={{ __html: parsed.html }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// If we have text content, render it
|
||||
if (parsed.text) {
|
||||
return (
|
||||
<div className="whitespace-pre-wrap font-sans text-base leading-relaxed">
|
||||
{parsed.text.split('\n').map((line, i) => (
|
||||
<p key={i} className="mb-2">{line}</p>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// If we have attachments, display them
|
||||
if (parsed.attachments && parsed.attachments.length > 0) {
|
||||
return (
|
||||
<div className="mt-6 border-t border-gray-200 pt-6">
|
||||
<h3 className="text-sm font-semibold text-gray-900 mb-4">Attachments</h3>
|
||||
<div className="space-y-2">
|
||||
{parsed.attachments.map((attachment, index) => (
|
||||
<div key={index} className="flex items-center space-x-2 p-2 border rounded">
|
||||
<Paperclip className="h-4 w-4 text-gray-400" />
|
||||
<span className="text-sm text-gray-600 truncate">
|
||||
{attachment.filename}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// If we couldn't parse the content, try to decode and clean the raw body
|
||||
const decodedBody = decodeMIME(email.body, 'quoted-printable', 'utf-8');
|
||||
const cleanedContent = cleanHtml(decodedBody);
|
||||
|
||||
return (
|
||||
<div className="whitespace-pre-wrap font-sans text-base leading-relaxed">
|
||||
{cleanedContent.split('\n').map((line, i) => (
|
||||
<p key={i} className="mb-2">{line}</p>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
} catch (e) {
|
||||
console.error('Error rendering email content:', e);
|
||||
return (
|
||||
<div className="text-red-500">
|
||||
Error rendering email content. Please try again later.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return <div className="whitespace-pre-wrap">{decodedContent}</div>;
|
||||
};
|
||||
}
|
||||
|
||||
// Add this helper function
|
||||
const decodeEmailContent = (content: string, charset: string = 'utf-8') => {
|
||||
@ -868,61 +973,7 @@ export default function CourrierPage() {
|
||||
</div>
|
||||
|
||||
<div className="prose max-w-none">
|
||||
{(() => {
|
||||
try {
|
||||
const parsed = parseFullEmail(selectedEmail.body);
|
||||
return (
|
||||
<div>
|
||||
{/* Display HTML content if available, otherwise fallback to text */}
|
||||
{parsed.html ? (
|
||||
<div
|
||||
className="prose prose-sm sm:prose lg:prose-lg xl:prose-xl dark:prose-invert max-w-none"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: parsed.html
|
||||
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
|
||||
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
|
||||
.replace(/<base[^>]*>/gi, '')
|
||||
.replace(/<meta[^>]*>/gi, '')
|
||||
.replace(/<link[^>]*>/gi, '')
|
||||
.replace(/<title[^>]*>[\s\S]*?<\/title>/gi, '')
|
||||
.replace(/<head[^>]*>[\s\S]*?<\/head>/gi, '')
|
||||
.replace(/<body[^>]*>/gi, '')
|
||||
.replace(/<\/body>/gi, '')
|
||||
.replace(/<html[^>]*>/gi, '')
|
||||
.replace(/<\/html>/gi, '')
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div className="whitespace-pre-wrap font-sans text-base leading-relaxed">
|
||||
{(parsed.text || '').split('\n').map((line, i) => (
|
||||
<p key={i} className="mb-2">{line}</p>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Display attachments if present */}
|
||||
{parsed.attachments && parsed.attachments.length > 0 && (
|
||||
<div className="mt-6 border-t border-gray-200 pt-6">
|
||||
<h3 className="text-sm font-semibold text-gray-900 mb-4">Attachments</h3>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{parsed.attachments.map((attachment, index) => (
|
||||
<div key={index} className="flex items-center space-x-2 p-2 border rounded">
|
||||
<Paperclip className="h-4 w-4 text-gray-400" />
|
||||
<span className="text-sm text-gray-600 truncate">
|
||||
{attachment.filename}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
} catch (e) {
|
||||
console.error('Error parsing email:', e);
|
||||
return <div className="text-gray-500">Error displaying email content</div>;
|
||||
}
|
||||
})()}
|
||||
{renderEmailContent(selectedEmail)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user