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