mail page rest
This commit is contained in:
parent
dc081b1b6d
commit
eb8e2c96cf
@ -97,196 +97,54 @@ interface ParsedEmailMetadata {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseFullEmail(emailContent: string): ParsedEmailContent {
|
|
||||||
if (!emailContent) return { headers: '', body: '' };
|
|
||||||
|
|
||||||
// Split headers and body
|
|
||||||
const headerEnd = emailContent.indexOf('\r\n\r\n');
|
|
||||||
if (headerEnd === -1) return { headers: '', body: emailContent };
|
|
||||||
|
|
||||||
const headers = emailContent.substring(0, headerEnd);
|
|
||||||
const body = emailContent.substring(headerEnd + 4);
|
|
||||||
|
|
||||||
// Parse headers
|
|
||||||
const headerInfo = parseEmailHeaders(headers);
|
|
||||||
const boundary = extractBoundary(headers);
|
|
||||||
|
|
||||||
// Initialize result object
|
|
||||||
const result: ParsedEmailContent = {
|
|
||||||
headers,
|
|
||||||
body: '',
|
|
||||||
html: undefined,
|
|
||||||
text: undefined,
|
|
||||||
attachments: []
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle multipart content
|
|
||||||
if (boundary && headerInfo.contentType.startsWith('multipart/')) {
|
|
||||||
const parts = body.split(`--${boundary}`);
|
|
||||||
parts
|
|
||||||
.filter(part => part.trim() && !part.includes('--'))
|
|
||||||
.forEach(part => {
|
|
||||||
const partHeaderEnd = part.indexOf('\r\n\r\n');
|
|
||||||
if (partHeaderEnd === -1) return;
|
|
||||||
|
|
||||||
const partHeaders = part.substring(0, partHeaderEnd);
|
|
||||||
const partBody = part.substring(partHeaderEnd + 4);
|
|
||||||
const partInfo = parseEmailHeaders(partHeaders);
|
|
||||||
|
|
||||||
let decodedContent = partBody;
|
|
||||||
if (partInfo.encoding === 'quoted-printable') {
|
|
||||||
decodedContent = decodeQuotedPrintable(partBody, partInfo.charset);
|
|
||||||
} else if (partInfo.encoding === 'base64') {
|
|
||||||
decodedContent = decodeBase64(partBody, partInfo.charset);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle different content types
|
|
||||||
if (partInfo.contentType.includes('text/html')) {
|
|
||||||
result.html = cleanHtml(decodedContent);
|
|
||||||
} else if (partInfo.contentType.includes('text/plain')) {
|
|
||||||
result.text = decodedContent;
|
|
||||||
} else if (partInfo.contentType.includes('application/') || partInfo.contentType.includes('image/')) {
|
|
||||||
// Handle attachments
|
|
||||||
const filename = extractFilename(partHeaders) || `attachment-${Date.now()}`;
|
|
||||||
result.attachments?.push({
|
|
||||||
filename,
|
|
||||||
content: decodedContent,
|
|
||||||
contentType: partInfo.contentType
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set the body to the text content if available, otherwise use HTML
|
|
||||||
result.body = result.text || result.html || '';
|
|
||||||
} else {
|
|
||||||
// Handle single part content
|
|
||||||
let decodedBody = body;
|
|
||||||
if (headerInfo.encoding === 'quoted-printable') {
|
|
||||||
decodedBody = decodeQuotedPrintable(body, headerInfo.charset);
|
|
||||||
} else if (headerInfo.encoding === 'base64') {
|
|
||||||
decodedBody = decodeBase64(body, headerInfo.charset);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (headerInfo.contentType.includes('text/html')) {
|
|
||||||
result.html = cleanHtml(decodedBody);
|
|
||||||
result.body = result.html;
|
|
||||||
} else if (headerInfo.contentType.includes('text/plain')) {
|
|
||||||
result.text = decodedBody;
|
|
||||||
result.body = result.text;
|
|
||||||
} else {
|
|
||||||
result.body = decodedBody;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractTextFromHtml(html: string): string {
|
|
||||||
// Remove scripts and style tags
|
|
||||||
html = html.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
|
|
||||||
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '');
|
|
||||||
|
|
||||||
// Convert <br> and <p> to newlines
|
|
||||||
html = html.replace(/<br[^>]*>/gi, '\n')
|
|
||||||
.replace(/<p[^>]*>/gi, '\n')
|
|
||||||
.replace(/<\/p>/gi, '\n');
|
|
||||||
|
|
||||||
// Remove all other HTML tags
|
|
||||||
html = html.replace(/<[^>]+>/g, '');
|
|
||||||
|
|
||||||
// Decode HTML entities
|
|
||||||
html = html.replace(/ /g, ' ')
|
|
||||||
.replace(/&/g, '&')
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>')
|
|
||||||
.replace(/"/g, '"');
|
|
||||||
|
|
||||||
// Clean up whitespace
|
|
||||||
return html.replace(/\n\s*\n/g, '\n\n').trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
function decodeMIME(text: string, encoding?: string, charset: string = 'utf-8'): string {
|
|
||||||
if (!text) return '';
|
|
||||||
|
|
||||||
// Normalize encoding and charset
|
|
||||||
encoding = (encoding || '').toLowerCase();
|
|
||||||
charset = (charset || 'utf-8').toLowerCase();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Handle different encoding types
|
|
||||||
if (encoding === 'quoted-printable') {
|
|
||||||
return decodeQuotedPrintable(text, charset);
|
|
||||||
} else if (encoding === 'base64') {
|
|
||||||
return decodeBase64(text, charset);
|
|
||||||
} else if (encoding === '7bit' || encoding === '8bit' || encoding === 'binary') {
|
|
||||||
// For these encodings, we still need to handle the character set
|
|
||||||
return convertCharset(text, charset);
|
|
||||||
} else {
|
|
||||||
// Unknown encoding, return as is but still handle charset
|
|
||||||
return convertCharset(text, charset);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error decoding MIME:', error);
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractHtmlBody(html: string): string {
|
|
||||||
const bodyMatch = html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
|
|
||||||
return bodyMatch ? bodyMatch[1] : html;
|
|
||||||
}
|
|
||||||
|
|
||||||
function decodeMimeContent(content: string): string {
|
|
||||||
if (!content) return '';
|
|
||||||
|
|
||||||
// Check if this is a multipart message
|
|
||||||
if (content.includes('Content-Type: multipart/')) {
|
|
||||||
const boundary = content.match(/boundary="([^"]+)"/)?.[1];
|
|
||||||
if (boundary) {
|
|
||||||
const parts = content.split('--' + boundary);
|
|
||||||
let htmlContent = '';
|
|
||||||
let textContent = '';
|
|
||||||
|
|
||||||
parts.forEach(part => {
|
|
||||||
if (part.includes('Content-Type: text/html')) {
|
|
||||||
const match = part.match(/\r?\n\r?\n([\s\S]+?)(?=\r?\n--)/);
|
|
||||||
if (match) {
|
|
||||||
htmlContent = cleanHtml(match[1]);
|
|
||||||
}
|
|
||||||
} else if (part.includes('Content-Type: text/plain')) {
|
|
||||||
const match = part.match(/\r?\n\r?\n([\s\S]+?)(?=\r?\n--)/);
|
|
||||||
if (match) {
|
|
||||||
textContent = cleanHtml(match[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Prefer HTML content if available
|
|
||||||
return htmlContent || textContent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If not multipart or no boundary found, clean the content directly
|
|
||||||
return cleanHtml(content);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderEmailContent(email: Email) {
|
function renderEmailContent(email: Email) {
|
||||||
if (!email.body) return null;
|
if (!email.body) return null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Parse the email content using our MIME decoder
|
// Split email into headers and body
|
||||||
const parsed = parseFullEmail(email.body);
|
const [headersPart, ...bodyParts] = email.body.split('\r\n\r\n');
|
||||||
|
const body = bodyParts.join('\r\n\r\n');
|
||||||
|
|
||||||
// If we have HTML content, render it
|
// Parse headers using our MIME decoder
|
||||||
if (parsed.html) {
|
const headerInfo = parseEmailHeaders(headersPart);
|
||||||
|
const boundary = extractBoundary(headersPart);
|
||||||
|
|
||||||
|
// If it's a multipart email
|
||||||
|
if (boundary) {
|
||||||
|
const parts = body.split(`--${boundary}`);
|
||||||
|
let htmlContent = '';
|
||||||
|
let textContent = '';
|
||||||
|
let attachments: { filename: string; content: string }[] = [];
|
||||||
|
|
||||||
|
for (const part of parts) {
|
||||||
|
if (!part.trim()) continue;
|
||||||
|
|
||||||
|
const [partHeaders, ...partBodyParts] = part.split('\r\n\r\n');
|
||||||
|
const partBody = partBodyParts.join('\r\n\r\n');
|
||||||
|
const partHeaderInfo = parseEmailHeaders(partHeaders);
|
||||||
|
|
||||||
|
if (partHeaderInfo.contentType.includes('text/html')) {
|
||||||
|
htmlContent = decodeQuotedPrintable(partBody, partHeaderInfo.charset);
|
||||||
|
} else if (partHeaderInfo.contentType.includes('text/plain')) {
|
||||||
|
textContent = decodeQuotedPrintable(partBody, partHeaderInfo.charset);
|
||||||
|
} else if (partHeaderInfo.contentType.includes('attachment')) {
|
||||||
|
attachments.push({
|
||||||
|
filename: extractFilename(partHeaders),
|
||||||
|
content: decodeBase64(partBody, partHeaderInfo.charset)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefer HTML content if available
|
||||||
|
if (htmlContent) {
|
||||||
return (
|
return (
|
||||||
<div className="email-content">
|
<div className="email-content">
|
||||||
<div className="prose max-w-none" dangerouslySetInnerHTML={{ __html: parsed.html }} />
|
<div className="prose max-w-none" dangerouslySetInnerHTML={{ __html: cleanHtml(htmlContent) }} />
|
||||||
{parsed.attachments && parsed.attachments.length > 0 && (
|
{attachments.length > 0 && (
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<h3 className="text-sm font-medium mb-2">Attachments:</h3>
|
<h3 className="text-sm font-medium mb-2">Attachments:</h3>
|
||||||
<ul className="space-y-2">
|
<ul className="space-y-2">
|
||||||
{parsed.attachments.map((attachment, index) => (
|
{attachments.map((attachment, index) => (
|
||||||
<li key={index} className="flex items-center gap-2">
|
<li key={index} className="flex items-center gap-2">
|
||||||
<Paperclip className="h-4 w-4 text-muted-foreground" />
|
<Paperclip className="h-4 w-4 text-muted-foreground" />
|
||||||
<span className="text-sm">{attachment.filename}</span>
|
<span className="text-sm">{attachment.filename}</span>
|
||||||
@ -299,20 +157,20 @@ function renderEmailContent(email: Email) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have text content, render it
|
// Fall back to text content
|
||||||
if (parsed.text) {
|
if (textContent) {
|
||||||
return (
|
return (
|
||||||
<div className="email-content">
|
<div className="email-content">
|
||||||
<div className="whitespace-pre-wrap font-sans text-base leading-relaxed">
|
<div className="whitespace-pre-wrap font-sans text-base leading-relaxed">
|
||||||
{parsed.text.split('\n').map((line, i) => (
|
{textContent.split('\n').map((line: string, i: number) => (
|
||||||
<p key={i} className="mb-2">{line}</p>
|
<p key={i} className="mb-2">{line}</p>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{parsed.attachments && parsed.attachments.length > 0 && (
|
{attachments.length > 0 && (
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<h3 className="text-sm font-medium mb-2">Attachments:</h3>
|
<h3 className="text-sm font-medium mb-2">Attachments:</h3>
|
||||||
<ul className="space-y-2">
|
<ul className="space-y-2">
|
||||||
{parsed.attachments.map((attachment, index) => (
|
{attachments.map((attachment, index) => (
|
||||||
<li key={index} className="flex items-center gap-2">
|
<li key={index} className="flex items-center gap-2">
|
||||||
<Paperclip className="h-4 w-4 text-muted-foreground" />
|
<Paperclip className="h-4 w-4 text-muted-foreground" />
|
||||||
<span className="text-sm">{attachment.filename}</span>
|
<span className="text-sm">{attachment.filename}</span>
|
||||||
@ -324,15 +182,16 @@ function renderEmailContent(email: Email) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If we couldn't parse the content, try to decode and clean the raw body
|
// If it's a simple email, try to decode it
|
||||||
const decodedBody = decodeMIME(email.body, 'quoted-printable', 'utf-8');
|
const decodedBody = decodeQuotedPrintable(body, headerInfo.charset);
|
||||||
const cleanedContent = cleanHtml(decodedBody);
|
const cleanedContent = cleanHtml(decodedBody);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="email-content">
|
<div className="email-content">
|
||||||
<div className="whitespace-pre-wrap font-sans text-base leading-relaxed">
|
<div className="whitespace-pre-wrap font-sans text-base leading-relaxed">
|
||||||
{cleanedContent.split('\n').map((line, i) => (
|
{cleanedContent.split('\n').map((line: string, i: number) => (
|
||||||
<p key={i} className="mb-2">{line}</p>
|
<p key={i} className="mb-2">{line}</p>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -382,6 +241,87 @@ const initialSidebarItems = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
function getReplyBody(email: Email | null, type: 'reply' | 'replyAll' | 'forward'): string {
|
||||||
|
if (!email?.body) return '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Split email into headers and body
|
||||||
|
const [headersPart, ...bodyParts] = email.body.split('\r\n\r\n');
|
||||||
|
const body = bodyParts.join('\r\n\r\n');
|
||||||
|
|
||||||
|
// Parse headers using our MIME decoder
|
||||||
|
const headerInfo = parseEmailHeaders(headersPart);
|
||||||
|
const boundary = extractBoundary(headersPart);
|
||||||
|
|
||||||
|
let content = '';
|
||||||
|
|
||||||
|
// If it's a multipart email
|
||||||
|
if (boundary) {
|
||||||
|
const parts = body.split(`--${boundary}`);
|
||||||
|
|
||||||
|
for (const part of parts) {
|
||||||
|
if (!part.trim()) continue;
|
||||||
|
|
||||||
|
const [partHeaders, ...partBodyParts] = part.split('\r\n\r\n');
|
||||||
|
const partBody = partBodyParts.join('\r\n\r\n');
|
||||||
|
const partHeaderInfo = parseEmailHeaders(partHeaders);
|
||||||
|
|
||||||
|
if (partHeaderInfo.contentType.includes('text/plain')) {
|
||||||
|
content = decodeQuotedPrintable(partBody, partHeaderInfo.charset);
|
||||||
|
break;
|
||||||
|
} else if (partHeaderInfo.contentType.includes('text/html') && !content) {
|
||||||
|
content = cleanHtml(decodeQuotedPrintable(partBody, partHeaderInfo.charset));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no content found or not multipart, try to decode the whole body
|
||||||
|
if (!content) {
|
||||||
|
content = decodeQuotedPrintable(body, headerInfo.charset);
|
||||||
|
if (headerInfo.contentType.includes('text/html')) {
|
||||||
|
content = cleanHtml(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format the reply
|
||||||
|
const date = new Date(email.date);
|
||||||
|
const formattedDate = date.toLocaleString('en-GB', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: '2-digit',
|
||||||
|
year: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
hour12: false
|
||||||
|
});
|
||||||
|
|
||||||
|
let replyHeader = '';
|
||||||
|
if (type === 'forward') {
|
||||||
|
replyHeader = `\n\n---------- Forwarded message ----------\n`;
|
||||||
|
replyHeader += `From: ${email.from}\n`;
|
||||||
|
replyHeader += `Date: ${formattedDate}\n`;
|
||||||
|
replyHeader += `Subject: ${email.subject}\n`;
|
||||||
|
replyHeader += `To: ${email.to}\n`;
|
||||||
|
if (email.cc) {
|
||||||
|
replyHeader += `Cc: ${email.cc}\n`;
|
||||||
|
}
|
||||||
|
replyHeader += `\n`;
|
||||||
|
} else {
|
||||||
|
replyHeader = `\n\nOn ${formattedDate}, ${email.from} wrote:\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add reply prefix to each line
|
||||||
|
const prefixedContent = content
|
||||||
|
.split('\n')
|
||||||
|
.map(line => `> ${line}`)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
return replyHeader + prefixedContent;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting reply body:', error);
|
||||||
|
return email.body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default function CourrierPage() {
|
export default function CourrierPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
@ -1121,38 +1061,43 @@ export default function CourrierPage() {
|
|||||||
console.log('First 200 chars of body:', email.body.substring(0, 200));
|
console.log('First 200 chars of body:', email.body.substring(0, 200));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const parsed = parseFullEmail(email.body);
|
// Split email into headers and body
|
||||||
console.log('Parsed content:', {
|
const [headersPart, ...bodyParts] = email.body.split('\r\n\r\n');
|
||||||
hasText: !!parsed.body,
|
const body = bodyParts.join('\r\n\r\n');
|
||||||
hasHtml: !!parsed.headers,
|
|
||||||
textPreview: parsed.body?.substring(0, 100) || 'No text',
|
// Parse headers using our MIME decoder
|
||||||
htmlPreview: parsed.headers?.substring(0, 100) || 'No HTML'
|
const headerInfo = parseEmailHeaders(headersPart);
|
||||||
});
|
const boundary = extractBoundary(headersPart);
|
||||||
|
|
||||||
let preview = '';
|
let preview = '';
|
||||||
if (parsed.body) {
|
|
||||||
preview = parsed.body;
|
// If it's a multipart email
|
||||||
console.log('Using text content for preview');
|
if (boundary) {
|
||||||
} else if (parsed.headers) {
|
const parts = body.split(`--${boundary}`);
|
||||||
preview = parsed.headers
|
|
||||||
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
|
for (const part of parts) {
|
||||||
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
|
if (!part.trim()) continue;
|
||||||
.replace(/<[^>]+>/g, ' ')
|
|
||||||
.replace(/\s+/g, ' ')
|
const [partHeaders, ...partBodyParts] = part.split('\r\n\r\n');
|
||||||
.trim();
|
const partBody = partBodyParts.join('\r\n\r\n');
|
||||||
console.log('Using HTML content for preview');
|
const partHeaderInfo = parseEmailHeaders(partHeaders);
|
||||||
|
|
||||||
|
if (partHeaderInfo.contentType.includes('text/plain')) {
|
||||||
|
preview = decodeQuotedPrintable(partBody, partHeaderInfo.charset);
|
||||||
|
break;
|
||||||
|
} else if (partHeaderInfo.contentType.includes('text/html') && !preview) {
|
||||||
|
preview = cleanHtml(decodeQuotedPrintable(partBody, partHeaderInfo.charset));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If no preview from multipart, try to decode the whole body
|
||||||
if (!preview) {
|
if (!preview) {
|
||||||
console.log('No preview from parsed content, using raw body');
|
preview = decodeQuotedPrintable(body, headerInfo.charset);
|
||||||
preview = email.body
|
if (headerInfo.contentType.includes('text/html')) {
|
||||||
.replace(/<[^>]+>/g, ' ')
|
preview = cleanHtml(preview);
|
||||||
.replace(/ |‌|»|«|>/g, ' ')
|
}
|
||||||
.replace(/\s+/g, ' ')
|
|
||||||
.trim();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Final preview before cleaning:', preview.substring(0, 100) + '...');
|
|
||||||
|
|
||||||
// Clean up the preview
|
// Clean up the preview
|
||||||
preview = preview
|
preview = preview
|
||||||
@ -1177,12 +1122,15 @@ export default function CourrierPage() {
|
|||||||
preview += '...';
|
preview += '...';
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Final preview:', preview);
|
|
||||||
return preview;
|
return preview;
|
||||||
|
} catch (error) {
|
||||||
} catch (e) {
|
console.error('Error generating email preview:', error);
|
||||||
console.error('Error generating preview:', e);
|
return email.body
|
||||||
return 'No preview available';
|
.replace(/<[^>]+>/g, ' ')
|
||||||
|
.replace(/ |‌|»|«|>/g, ' ')
|
||||||
|
.replace(/\s+/g, ' ')
|
||||||
|
.substring(0, 100)
|
||||||
|
.trim() + '...';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1346,88 +1294,24 @@ export default function CourrierPage() {
|
|||||||
|
|
||||||
// Add handleReply function
|
// Add handleReply function
|
||||||
const handleReply = async (type: 'reply' | 'replyAll' | 'forward') => {
|
const handleReply = async (type: 'reply' | 'replyAll' | 'forward') => {
|
||||||
// First, ensure we have the full email content
|
if (!selectedEmail) return;
|
||||||
if (!selectedEmail) {
|
|
||||||
setError('No email selected');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!selectedEmail.body) {
|
const getReplyTo = () => {
|
||||||
try {
|
|
||||||
// Fetch the full email content
|
|
||||||
const response = await fetch(`/api/mail/${selectedEmail.id}`);
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Failed to fetch email content');
|
|
||||||
}
|
|
||||||
const emailData = await response.json();
|
|
||||||
|
|
||||||
// Update the selected email with the full content
|
|
||||||
setSelectedEmail(prev => {
|
|
||||||
if (!prev) return null;
|
|
||||||
return {
|
|
||||||
...prev,
|
|
||||||
body: emailData.body,
|
|
||||||
to: emailData.to,
|
|
||||||
cc: emailData.cc,
|
|
||||||
bcc: emailData.bcc
|
|
||||||
};
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching email content:', error);
|
|
||||||
setError('Failed to load email content. Please try again.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper functions for reply composition
|
|
||||||
const getReplySubject = (): string => {
|
|
||||||
if (!selectedEmail) return '';
|
|
||||||
const prefix = type === 'forward' ? 'Fwd:' : 'Re:';
|
|
||||||
return `${prefix} ${selectedEmail.subject}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getReplyTo = (): string => {
|
|
||||||
if (!selectedEmail) return '';
|
|
||||||
if (type === 'forward') return '';
|
if (type === 'forward') return '';
|
||||||
return selectedEmail.from;
|
return selectedEmail.from;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getReplyCc = (): string => {
|
const getReplyCc = () => {
|
||||||
if (!selectedEmail) return '';
|
if (type !== 'replyAll') return '';
|
||||||
if (type === 'forward' || type === 'reply') return '';
|
|
||||||
return selectedEmail.cc || '';
|
return selectedEmail.cc || '';
|
||||||
};
|
};
|
||||||
|
|
||||||
const getReplyBody = () => {
|
const getReplySubject = () => {
|
||||||
if (!selectedEmail?.body) return '';
|
const subject = selectedEmail.subject || '';
|
||||||
|
if (type === 'forward') {
|
||||||
const parsed = parseFullEmail(selectedEmail.body);
|
return subject.startsWith('Fwd:') ? subject : `Fwd: ${subject}`;
|
||||||
if (!parsed) return '';
|
}
|
||||||
|
return subject.startsWith('Re:') ? subject : `Re: ${subject}`;
|
||||||
const body = parsed.body;
|
|
||||||
|
|
||||||
// Convert HTML to plain text if needed
|
|
||||||
const plainText = body
|
|
||||||
.replace(/<br\s*\/?>/gi, '\n')
|
|
||||||
.replace(/<div[^>]*>/gi, '\n')
|
|
||||||
.replace(/<\/div>/gi, '')
|
|
||||||
.replace(/<p[^>]*>/gi, '\n')
|
|
||||||
.replace(/<\/p>/gi, '')
|
|
||||||
.replace(/ /g, ' ')
|
|
||||||
.replace(/>/g, '>')
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/&/g, '&')
|
|
||||||
.replace(/"/g, '"')
|
|
||||||
.replace(/<[^>]+>/g, '')
|
|
||||||
.replace(/^\s+$/gm, '')
|
|
||||||
.replace(/\n{3,}/g, '\n\n')
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
// Add reply prefix to each line
|
|
||||||
return plainText
|
|
||||||
.split('\n')
|
|
||||||
.map(line => `> ${line}`)
|
|
||||||
.join('\n');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Prepare the reply email
|
// Prepare the reply email
|
||||||
@ -1435,7 +1319,7 @@ export default function CourrierPage() {
|
|||||||
to: getReplyTo(),
|
to: getReplyTo(),
|
||||||
cc: getReplyCc(),
|
cc: getReplyCc(),
|
||||||
subject: getReplySubject(),
|
subject: getReplySubject(),
|
||||||
body: getReplyBody()
|
body: getReplyBody(selectedEmail, type)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update the compose form with the reply content
|
// Update the compose form with the reply content
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user