mail page rest
This commit is contained in:
parent
dc081b1b6d
commit
eb8e2c96cf
@ -97,242 +97,101 @@ 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) {
|
||||
if (!email.body) return null;
|
||||
|
||||
try {
|
||||
// Parse the email content using our MIME decoder
|
||||
const parsed = parseFullEmail(email.body);
|
||||
// Split email into headers and 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
|
||||
if (parsed.html) {
|
||||
return (
|
||||
<div className="email-content">
|
||||
<div className="prose max-w-none" dangerouslySetInnerHTML={{ __html: parsed.html }} />
|
||||
{parsed.attachments && parsed.attachments.length > 0 && (
|
||||
<div className="mt-4">
|
||||
<h3 className="text-sm font-medium mb-2">Attachments:</h3>
|
||||
<ul className="space-y-2">
|
||||
{parsed.attachments.map((attachment, index) => (
|
||||
<li key={index} className="flex items-center gap-2">
|
||||
<Paperclip className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="text-sm">{attachment.filename}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// Parse headers using our MIME decoder
|
||||
const headerInfo = parseEmailHeaders(headersPart);
|
||||
const boundary = extractBoundary(headersPart);
|
||||
|
||||
// If we have text content, render it
|
||||
if (parsed.text) {
|
||||
return (
|
||||
<div className="email-content">
|
||||
<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>
|
||||
))}
|
||||
// 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 (
|
||||
<div className="email-content">
|
||||
<div className="prose max-w-none" dangerouslySetInnerHTML={{ __html: cleanHtml(htmlContent) }} />
|
||||
{attachments.length > 0 && (
|
||||
<div className="mt-4">
|
||||
<h3 className="text-sm font-medium mb-2">Attachments:</h3>
|
||||
<ul className="space-y-2">
|
||||
{attachments.map((attachment, index) => (
|
||||
<li key={index} className="flex items-center gap-2">
|
||||
<Paperclip className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="text-sm">{attachment.filename}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{parsed.attachments && parsed.attachments.length > 0 && (
|
||||
<div className="mt-4">
|
||||
<h3 className="text-sm font-medium mb-2">Attachments:</h3>
|
||||
<ul className="space-y-2">
|
||||
{parsed.attachments.map((attachment, index) => (
|
||||
<li key={index} className="flex items-center gap-2">
|
||||
<Paperclip className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="text-sm">{attachment.filename}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
// Fall back to text content
|
||||
if (textContent) {
|
||||
return (
|
||||
<div className="email-content">
|
||||
<div className="whitespace-pre-wrap font-sans text-base leading-relaxed">
|
||||
{textContent.split('\n').map((line: string, i: number) => (
|
||||
<p key={i} className="mb-2">{line}</p>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
{attachments.length > 0 && (
|
||||
<div className="mt-4">
|
||||
<h3 className="text-sm font-medium mb-2">Attachments:</h3>
|
||||
<ul className="space-y-2">
|
||||
{attachments.map((attachment, index) => (
|
||||
<li key={index} className="flex items-center gap-2">
|
||||
<Paperclip className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="text-sm">{attachment.filename}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</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');
|
||||
// If it's a simple email, try to decode it
|
||||
const decodedBody = decodeQuotedPrintable(body, headerInfo.charset);
|
||||
const cleanedContent = cleanHtml(decodedBody);
|
||||
|
||||
return (
|
||||
<div className="email-content">
|
||||
<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>
|
||||
))}
|
||||
</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() {
|
||||
const router = useRouter();
|
||||
const { data: session } = useSession();
|
||||
@ -1121,39 +1061,44 @@ export default function CourrierPage() {
|
||||
console.log('First 200 chars of body:', email.body.substring(0, 200));
|
||||
|
||||
try {
|
||||
const parsed = parseFullEmail(email.body);
|
||||
console.log('Parsed content:', {
|
||||
hasText: !!parsed.body,
|
||||
hasHtml: !!parsed.headers,
|
||||
textPreview: parsed.body?.substring(0, 100) || 'No text',
|
||||
htmlPreview: parsed.headers?.substring(0, 100) || 'No HTML'
|
||||
});
|
||||
|
||||
// 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 preview = '';
|
||||
if (parsed.body) {
|
||||
preview = parsed.body;
|
||||
console.log('Using text content for preview');
|
||||
} else if (parsed.headers) {
|
||||
preview = parsed.headers
|
||||
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
|
||||
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
|
||||
.replace(/<[^>]+>/g, ' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
console.log('Using HTML content for preview');
|
||||
|
||||
// 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')) {
|
||||
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) {
|
||||
console.log('No preview from parsed content, using raw body');
|
||||
preview = email.body
|
||||
.replace(/<[^>]+>/g, ' ')
|
||||
.replace(/ |‌|»|«|>/g, ' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
preview = decodeQuotedPrintable(body, headerInfo.charset);
|
||||
if (headerInfo.contentType.includes('text/html')) {
|
||||
preview = cleanHtml(preview);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Final preview before cleaning:', preview.substring(0, 100) + '...');
|
||||
|
||||
// Clean up the preview
|
||||
preview = preview
|
||||
.replace(/^>+/gm, '')
|
||||
@ -1177,12 +1122,15 @@ export default function CourrierPage() {
|
||||
preview += '...';
|
||||
}
|
||||
|
||||
console.log('Final preview:', preview);
|
||||
return preview;
|
||||
|
||||
} catch (e) {
|
||||
console.error('Error generating preview:', e);
|
||||
return 'No preview available';
|
||||
} catch (error) {
|
||||
console.error('Error generating email preview:', error);
|
||||
return email.body
|
||||
.replace(/<[^>]+>/g, ' ')
|
||||
.replace(/ |‌|»|«|>/g, ' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
.substring(0, 100)
|
||||
.trim() + '...';
|
||||
}
|
||||
};
|
||||
|
||||
@ -1346,88 +1294,24 @@ export default function CourrierPage() {
|
||||
|
||||
// Add handleReply function
|
||||
const handleReply = async (type: 'reply' | 'replyAll' | 'forward') => {
|
||||
// First, ensure we have the full email content
|
||||
if (!selectedEmail) {
|
||||
setError('No email selected');
|
||||
return;
|
||||
}
|
||||
if (!selectedEmail) return;
|
||||
|
||||
if (!selectedEmail.body) {
|
||||
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 '';
|
||||
const getReplyTo = () => {
|
||||
if (type === 'forward') return '';
|
||||
return selectedEmail.from;
|
||||
};
|
||||
|
||||
const getReplyCc = (): string => {
|
||||
if (!selectedEmail) return '';
|
||||
if (type === 'forward' || type === 'reply') return '';
|
||||
const getReplyCc = () => {
|
||||
if (type !== 'replyAll') return '';
|
||||
return selectedEmail.cc || '';
|
||||
};
|
||||
|
||||
const getReplyBody = () => {
|
||||
if (!selectedEmail?.body) return '';
|
||||
|
||||
const parsed = parseFullEmail(selectedEmail.body);
|
||||
if (!parsed) return '';
|
||||
|
||||
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');
|
||||
const getReplySubject = () => {
|
||||
const subject = selectedEmail.subject || '';
|
||||
if (type === 'forward') {
|
||||
return subject.startsWith('Fwd:') ? subject : `Fwd: ${subject}`;
|
||||
}
|
||||
return subject.startsWith('Re:') ? subject : `Re: ${subject}`;
|
||||
};
|
||||
|
||||
// Prepare the reply email
|
||||
@ -1435,7 +1319,7 @@ export default function CourrierPage() {
|
||||
to: getReplyTo(),
|
||||
cc: getReplyCc(),
|
||||
subject: getReplySubject(),
|
||||
body: getReplyBody()
|
||||
body: getReplyBody(selectedEmail, type)
|
||||
};
|
||||
|
||||
// Update the compose form with the reply content
|
||||
|
||||
Loading…
Reference in New Issue
Block a user