mail page fix
This commit is contained in:
parent
192ae462de
commit
2891ec3e11
@ -97,6 +97,14 @@ interface ParsedEmailMetadata {
|
||||
};
|
||||
}
|
||||
|
||||
function splitEmailHeadersAndBody(emailBody: string): { headers: string; body: string } {
|
||||
const [headers, ...bodyParts] = emailBody.split('\r\n\r\n');
|
||||
return {
|
||||
headers: headers || '',
|
||||
body: bodyParts.join('\r\n\r\n')
|
||||
};
|
||||
}
|
||||
|
||||
function renderEmailContent(email: Email) {
|
||||
if (!email.body) {
|
||||
console.warn('No email body provided');
|
||||
@ -317,145 +325,65 @@ const initialSidebarItems = [
|
||||
}
|
||||
];
|
||||
|
||||
function getReplyBody(email: Email | null, type: 'reply' | 'replyAll' | 'forward'): string {
|
||||
if (!email?.body) return '';
|
||||
function getReplyBody(email: Email, type: 'reply' | 'reply-all' | 'forward'): string {
|
||||
const { headers, body } = splitEmailHeadersAndBody(email.body);
|
||||
const { contentType, encoding, charset } = parseEmailHeaders(headers);
|
||||
const isHtml = contentType.includes('text/html');
|
||||
const isMultipart = contentType.includes('multipart');
|
||||
const isQuotedPrintable = encoding === 'quoted-printable';
|
||||
|
||||
try {
|
||||
// Split email into headers and body
|
||||
const [headersPart, ...bodyParts] = email.body.split('\r\n\r\n');
|
||||
if (!headersPart || bodyParts.length === 0) {
|
||||
throw new Error('Invalid email format: missing headers or body');
|
||||
}
|
||||
|
||||
const body = bodyParts.join('\r\n\r\n');
|
||||
|
||||
// Parse headers using Infomaniak MIME decoder
|
||||
const headerInfo = parseEmailHeaders(headersPart);
|
||||
const boundary = extractBoundary(headersPart);
|
||||
|
||||
let content = '';
|
||||
let isHtml = false;
|
||||
|
||||
// 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');
|
||||
if (!partHeaders || partBodyParts.length === 0) continue;
|
||||
|
||||
const partBody = partBodyParts.join('\r\n\r\n');
|
||||
const contentType = extractHeader(partHeaders, 'Content-Type').toLowerCase();
|
||||
const encoding = extractHeader(partHeaders, 'Content-Transfer-Encoding').toLowerCase();
|
||||
const charset = extractHeader(partHeaders, 'charset') || 'utf-8';
|
||||
|
||||
try {
|
||||
let decodedContent = '';
|
||||
if (encoding === 'base64') {
|
||||
decodedContent = decodeBase64(partBody, charset);
|
||||
} else if (encoding === 'quoted-printable') {
|
||||
decodedContent = decodeQuotedPrintable(partBody, charset);
|
||||
} else {
|
||||
decodedContent = convertCharset(partBody, charset);
|
||||
}
|
||||
|
||||
if (contentType.includes('text/html')) {
|
||||
// For HTML content, preserve the HTML structure
|
||||
content = decodedContent
|
||||
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
|
||||
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
|
||||
.replace(/<meta[^>]*>/gi, '')
|
||||
.replace(/<link[^>]*>/gi, '')
|
||||
.replace(/<base[^>]*>/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, '');
|
||||
isHtml = true;
|
||||
break; // Prefer HTML content
|
||||
} else if (contentType.includes('text/plain') && !content) {
|
||||
content = decodedContent;
|
||||
}
|
||||
} catch (partError) {
|
||||
console.error('Error processing email part:', partError);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no content found or not multipart, try to decode the whole body
|
||||
if (!content) {
|
||||
const encoding = extractHeader(headersPart, 'Content-Transfer-Encoding').toLowerCase();
|
||||
const charset = extractHeader(headersPart, 'charset') || 'utf-8';
|
||||
|
||||
if (encoding === 'base64') {
|
||||
content = decodeBase64(body, charset);
|
||||
} else if (encoding === 'quoted-printable') {
|
||||
content = decodeQuotedPrintable(body, charset);
|
||||
} else {
|
||||
content = convertCharset(body, charset);
|
||||
}
|
||||
|
||||
if (headerInfo.contentType.includes('text/html')) {
|
||||
content = content
|
||||
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
|
||||
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
|
||||
.replace(/<meta[^>]*>/gi, '')
|
||||
.replace(/<link[^>]*>/gi, '')
|
||||
.replace(/<base[^>]*>/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, '');
|
||||
isHtml = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
});
|
||||
|
||||
const from = email.fromName || email.from;
|
||||
const subject = email.subject || '(No subject)';
|
||||
|
||||
if (type === 'forward') {
|
||||
return `---------- Forwarded message ---------
|
||||
From: ${from}
|
||||
Date: ${formattedDate}
|
||||
Subject: ${subject}
|
||||
To: ${email.to}
|
||||
|
||||
${content}`;
|
||||
} else {
|
||||
if (isHtml) {
|
||||
// For HTML content, wrap each line in a blockquote
|
||||
const quotedHtml = content
|
||||
.split('\n')
|
||||
.map(line => `<blockquote>${line}</blockquote>`)
|
||||
.join('\n');
|
||||
|
||||
return `\n\nOn ${formattedDate}, ${from} wrote:\n${quotedHtml}`;
|
||||
} else {
|
||||
return `\n\nOn ${formattedDate}, ${from} wrote:\n> ${content.split('\n').join('\n> ')}`;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error formatting reply:', error);
|
||||
return '';
|
||||
let content = body;
|
||||
if (isQuotedPrintable) {
|
||||
content = decodeQuotedPrintable(content, charset);
|
||||
}
|
||||
|
||||
if (isMultipart) {
|
||||
const parts = content.split('--boundary');
|
||||
content = parts.find((part: string) => part.includes('text/html')) || parts.find((part: string) => part.includes('text/plain')) || '';
|
||||
const partHeaders = content.split('\n\n')[0];
|
||||
const partContent = content.split('\n\n').slice(1).join('\n\n');
|
||||
const { contentType: partContentType, encoding: partEncoding, charset: partCharset } = parseEmailHeaders(partHeaders);
|
||||
content = partContent;
|
||||
if (partEncoding === 'quoted-printable') {
|
||||
content = decodeQuotedPrintable(content, partCharset);
|
||||
}
|
||||
}
|
||||
|
||||
if (isHtml) {
|
||||
// Preserve HTML structure while cleaning potentially dangerous elements
|
||||
content = cleanHtml(content);
|
||||
} else {
|
||||
// Convert plain text to HTML while preserving formatting
|
||||
content = content
|
||||
.replace(/\n/g, '<br>')
|
||||
.replace(/\t/g, ' ')
|
||||
.replace(/ /g, ' ');
|
||||
}
|
||||
|
||||
if (type === 'forward') {
|
||||
return `
|
||||
<div class="forwarded-message">
|
||||
<p>---------- Forwarded message ---------</p>
|
||||
<p>From: ${email.from}</p>
|
||||
<p>Date: ${new Date(email.date).toLocaleString()}</p>
|
||||
<p>Subject: ${email.subject}</p>
|
||||
<p>To: ${email.to}</p>
|
||||
<br>
|
||||
<div class="prose">
|
||||
${content}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="reply-message">
|
||||
<p>On ${new Date(email.date).toLocaleString()}, ${email.from} wrote:</p>
|
||||
<blockquote class="prose">
|
||||
${content}
|
||||
</blockquote>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
export default function CourrierPage() {
|
||||
@ -1120,15 +1048,6 @@ export default function CourrierPage() {
|
||||
|
||||
// Update the email list item to match header checkbox alignment
|
||||
const renderEmailListItem = (email: Email) => {
|
||||
console.log('=== Email List Item Debug ===');
|
||||
console.log('Email ID:', email.id);
|
||||
console.log('Subject:', email.subject);
|
||||
console.log('Body length:', email.body.length);
|
||||
console.log('First 100 chars of body:', email.body.substring(0, 100));
|
||||
|
||||
const preview = generateEmailPreview(email);
|
||||
console.log('Generated preview:', preview);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={email.id}
|
||||
@ -1149,19 +1068,6 @@ export default function CourrierPage() {
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<span className={`text-sm truncate ${!email.read ? 'font-semibold text-gray-900' : 'text-gray-600'}`}>
|
||||
{currentView === 'Sent' ? email.to : (
|
||||
(() => {
|
||||
const fromMatch = email.from.match(/^([^<]+)\s*<([^>]+)>$/);
|
||||
return fromMatch ? fromMatch[1].trim() : email.from;
|
||||
})()
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 flex-shrink-0">
|
||||
<span className="text-xs text-gray-500 whitespace-nowrap">
|
||||
{formatDate(email.date)}
|
||||
</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
@ -1170,13 +1076,19 @@ export default function CourrierPage() {
|
||||
>
|
||||
<Star className={`h-4 w-4 ${email.starred ? 'fill-yellow-400 text-yellow-400' : ''}`} />
|
||||
</Button>
|
||||
<span className={`text-sm truncate ${!email.read ? 'font-semibold text-gray-900' : 'text-gray-600'}`}>
|
||||
{email.fromName || email.from}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-xs text-gray-500 whitespace-nowrap">
|
||||
{formatDate(email.date)}
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="text-sm text-gray-900 truncate">
|
||||
{email.subject || '(No subject)'}
|
||||
</h3>
|
||||
<div className="text-xs text-gray-500 truncate">
|
||||
{preview}
|
||||
<div className="text-xs text-gray-500 line-clamp-2">
|
||||
{generateEmailPreview(email)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1765,15 +1677,15 @@ export default function CourrierPage() {
|
||||
</div>
|
||||
|
||||
{/* Message Body */}
|
||||
<div>
|
||||
<Label htmlFor="message" className="block text-sm font-medium text-gray-700">Message</Label>
|
||||
<div className="flex-1 overflow-auto">
|
||||
<div
|
||||
id="message"
|
||||
contentEditable
|
||||
className="prose max-w-none min-h-[200px] p-4 focus:outline-none"
|
||||
dangerouslySetInnerHTML={{ __html: composeBody }}
|
||||
onInput={(e) => setComposeBody(e.currentTarget.innerHTML)}
|
||||
className="w-full mt-1 min-h-[200px] bg-white border border-gray-300 text-gray-900 p-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
style={{ whiteSpace: 'pre-wrap' }}
|
||||
onInput={(e) => {
|
||||
const content = e.currentTarget.innerHTML;
|
||||
setComposeBody(content);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user