mail page fix

This commit is contained in:
alma 2025-04-21 17:47:18 +02:00
parent d55d2915cf
commit 0e7f48079d

View File

@ -63,6 +63,7 @@ interface Email {
cc?: string;
bcc?: string;
flags?: string[];
raw: string;
}
interface Attachment {
@ -327,64 +328,81 @@ const initialSidebarItems = [
];
function getReplyBody(email: Email, type: 'reply' | 'reply-all' | 'forward'): string {
const { headers, body } = splitEmailHeadersAndBody(email.body);
if (!email.raw) return '';
const { headers, body } = splitEmailHeadersAndBody(email.raw);
const { contentType, encoding, charset } = parseEmailHeaders(headers);
const isHtml = contentType.includes('text/html');
const isMultipart = contentType.includes('multipart');
const isQuotedPrintable = encoding === 'quoted-printable';
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);
let content = '';
if (contentType.includes('multipart/')) {
const boundary = contentType.match(/boundary="([^"]+)"/)?.[1];
if (boundary) {
const parts = body.split('--' + boundary).filter(part => part.trim());
// Find HTML part first, fallback to text part
const htmlPart = parts.find(part => part.toLowerCase().includes('content-type: text/html'));
const textPart = parts.find(part => part.toLowerCase().includes('content-type: text/plain'));
const selectedPart = htmlPart || textPart;
if (selectedPart) {
const partHeaders = selectedPart.split('\r\n\r\n')[0];
const partBody = selectedPart.split('\r\n\r\n').slice(1).join('\r\n\r\n');
const { encoding: partEncoding } = parseEmailHeaders(partHeaders);
content = partEncoding === 'quoted-printable'
? decodeQuotedPrintable(partBody, charset)
: partBody;
}
}
}
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, '&nbsp;&nbsp;&nbsp;&nbsp;')
.replace(/ /g, '&nbsp;&nbsp;');
content = encoding === 'quoted-printable'
? decodeQuotedPrintable(body, charset)
: body;
}
// Convert plain text to HTML if needed
if (!contentType.includes('text/html')) {
content = content
.split('\n')
.map(line => `<p>${line}</p>`)
.join('');
}
// Sanitize HTML content
content = DOMPurify.sanitize(content, {
ALLOWED_TAGS: [
'p', 'br', 'div', 'span', 'b', 'i', 'u', 'strong', 'em',
'blockquote', 'ul', 'ol', 'li', 'a', 'h1', 'h2', 'h3', 'h4',
'table', 'thead', 'tbody', 'tr', 'td', 'th'
],
ALLOWED_ATTR: ['href', 'style', 'class'],
});
const date = new Date(email.date).toLocaleString();
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>
<br/>
<br/>
---------- Forwarded message ----------<br/>
From: ${email.from}<br/>
Date: ${date}<br/>
Subject: ${email.subject}<br/>
To: ${Array.isArray(email.to) ? email.to.join(', ') : email.to}<br/>
<br/>
${content}
`;
}
return `
<div class="reply-message">
<p>On ${new Date(email.date).toLocaleString()}, ${email.from} wrote:</p>
<blockquote class="prose">
} else {
return `
<br/>
<br/>
On ${date}, ${email.from} wrote:<br/>
<blockquote style="border-left: 2px solid #ccc; padding-left: 1em; margin-left: 0.5em;">
${content}
</blockquote>
</div>
`;
`;
}
}
export default function CourrierPage() {
@ -555,7 +573,8 @@ export default function CourrierPage() {
folder: email.folder || currentView,
cc: email.cc,
bcc: email.bcc,
flags: email.flags || []
flags: email.flags || [],
raw: email.body || ''
}));
// Only update unread count if we're in the Inbox folder
@ -1444,7 +1463,8 @@ export default function CourrierPage() {
folder: email.folder || newMailbox,
cc: email.cc,
bcc: email.bcc,
flags: email.flags || []
flags: email.flags || [],
raw: email.body || ''
}));
setEmails(processedEmails);
@ -1680,26 +1700,13 @@ export default function CourrierPage() {
<div
contentEditable
className="prose max-w-none min-h-[200px] p-4 focus:outline-none border rounded-md"
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(composeBody) }}
onInput={(e) => {
// Preserve formatting by using a temporary div to clean the HTML
const tempDiv = document.createElement('div');
tempDiv.innerHTML = e.currentTarget.innerHTML;
// Remove any potentially dangerous elements/attributes while preserving formatting
const cleanHtml = DOMPurify.sanitize(tempDiv.innerHTML, {
ALLOWED_TAGS: ['p', 'br', 'div', 'span', 'b', 'i', 'u', 'strong', 'em', 'blockquote', 'ul', 'ol', 'li', 'a'],
ALLOWED_ATTR: ['href', 'style', 'class'],
});
setComposeBody(cleanHtml);
suppressContentEditableWarning
onInput={(e: React.FormEvent<HTMLDivElement>) => {
setComposeBody((e.target as HTMLDivElement).innerHTML);
}}
style={{
minHeight: '200px',
overflowY: 'auto',
lineHeight: '1.5',
}}
/>
>
{composeBody}
</div>
</div>
</div>
</div>