mail page rest
This commit is contained in:
parent
8d9694ba95
commit
f4502f15ff
@ -78,10 +78,14 @@ interface EmailAttachment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ParsedEmail {
|
interface ParsedEmail {
|
||||||
text: string;
|
text: string | null;
|
||||||
html: string;
|
html: string | null;
|
||||||
attachments: EmailAttachment[];
|
attachments: Array<{
|
||||||
headers?: string;
|
filename: string;
|
||||||
|
contentType: string;
|
||||||
|
encoding: string;
|
||||||
|
content: string;
|
||||||
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EmailMessage {
|
interface EmailMessage {
|
||||||
@ -99,47 +103,91 @@ interface EmailMessage {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseFullEmail(emailRaw: string): ParsedEmail | EmailMessage {
|
function parseFullEmail(content: string): ParsedEmail {
|
||||||
// Check if this is a multipart message by looking for boundary definition
|
try {
|
||||||
const boundaryMatch = emailRaw.match(/boundary="?([^"\r\n;]+)"?/i) ||
|
// First, try to parse the email headers
|
||||||
emailRaw.match(/boundary=([^\r\n;]+)/i);
|
const headers = parseEmailHeaders(content);
|
||||||
|
|
||||||
if (boundaryMatch) {
|
|
||||||
const boundary = boundaryMatch[1].trim();
|
|
||||||
|
|
||||||
// Check if there's a preamble before the first boundary
|
// If it's a multipart email, process each part
|
||||||
let mainHeaders = '';
|
if (headers.contentType?.includes('multipart')) {
|
||||||
let mainContent = emailRaw;
|
const boundary = extractBoundary(headers.contentType);
|
||||||
|
if (!boundary) {
|
||||||
// Extract the headers before the first boundary if they exist
|
throw new Error('No boundary found in multipart content');
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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 parts = content.split(boundary);
|
||||||
const emailInfo = parseEmailHeaders(headers);
|
const result: ParsedEmail = {
|
||||||
return {
|
text: null,
|
||||||
subject: extractHeader(headers, 'Subject'),
|
html: null,
|
||||||
from: extractHeader(headers, 'From'),
|
attachments: []
|
||||||
to: extractHeader(headers, 'To'),
|
};
|
||||||
date: extractHeader(headers, 'Date'),
|
|
||||||
contentType: emailInfo.contentType,
|
for (const part of parts) {
|
||||||
text: emailInfo.contentType.includes('text/plain') ? body : null,
|
if (!part.trim()) continue;
|
||||||
html: emailInfo.contentType.includes('text/html') ? body : null,
|
|
||||||
attachments: [], // Add empty attachments array for single part messages
|
const partHeaders = parseEmailHeaders(part);
|
||||||
raw: {
|
const partContent = part.split('\r\n\r\n')[1] || '';
|
||||||
headers,
|
|
||||||
body
|
// 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);
|
return cleanHtml(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add this helper function
|
function renderEmailContent(email: Email) {
|
||||||
const renderEmailContent = (email: Email) => {
|
try {
|
||||||
const decodedContent = decodeMimeContent(email.body);
|
// First, parse the full email to get headers and body
|
||||||
if (email.body.includes('Content-Type: text/html')) {
|
const parsed = parseFullEmail(email.body);
|
||||||
return <div dangerouslySetInnerHTML={{ __html: decodedContent }} />;
|
|
||||||
|
// 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
|
// Add this helper function
|
||||||
const decodeEmailContent = (content: string, charset: string = 'utf-8') => {
|
const decodeEmailContent = (content: string, charset: string = 'utf-8') => {
|
||||||
@ -868,61 +973,7 @@ export default function CourrierPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="prose max-w-none">
|
<div className="prose max-w-none">
|
||||||
{(() => {
|
{renderEmailContent(selectedEmail)}
|
||||||
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>;
|
|
||||||
}
|
|
||||||
})()}
|
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</>
|
</>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user