diff --git a/app/globals.css b/app/globals.css index fc2f7bd0..5e1139c4 100644 --- a/app/globals.css +++ b/app/globals.css @@ -74,3 +74,17 @@ } } +/* Email specific styles */ +.email-content table { width: 100%; border-collapse: collapse; } +.email-content table.table-container { width: auto; margin-bottom: 20px; } +.email-content td, .email-content th { padding: 8px; border: 1px solid #e5e7eb; } +.email-content img { max-width: 100%; height: auto; } +.email-content div[style] { max-width: 100% !important; } +.email-content * { max-width: 100% !important; word-wrap: break-word; } +.email-content font { font-family: inherit; } +.email-content .total-row td { border-top: 1px solid #e5e7eb; } +.email-content a { color: #3b82f6; text-decoration: underline; } +.email-content p { margin-bottom: 0.75em; } +.email-content .header { margin-bottom: 1em; } +.email-content .footer { font-size: 0.875rem; color: #6b7280; margin-top: 1em; } + diff --git a/components/ComposeEmail.tsx b/components/ComposeEmail.tsx index 7d450aeb..442d57e9 100644 --- a/components/ComposeEmail.tsx +++ b/components/ComposeEmail.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useRef, useEffect, useState } from 'react'; +import { useRef, useEffect, useState, useCallback } from 'react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; @@ -84,6 +84,7 @@ export default function ComposeEmail({ const composeBodyRef = useRef(null); const [localContent, setLocalContent] = useState(''); const [isLoading, setIsLoading] = useState(false); + const contentRef = useRef(null); useEffect(() => { if (replyTo || forwardFrom) { @@ -172,14 +173,6 @@ export default function ComposeEmail({

To: ${decoded.to || ''}


@@ -189,7 +182,9 @@ export default function ComposeEmail({

On ${formatDate(decoded.date)}, ${decoded.from || ''} wrote:

- ${decoded.html || `
${decoded.text || ''}
`} +
`; @@ -300,16 +295,16 @@ export default function ComposeEmail({ // Ensure wheel events are properly handled if (!(div as HTMLElement).hasAttribute('data-scroll-handler-attached')) { - div.addEventListener('wheel', (e: Event) => { - const wheelEvent = e as WheelEvent; - const target = e.currentTarget as HTMLElement; + div.addEventListener('wheel', function(this: HTMLElement, ev: Event) { + const e = ev as WheelEvent; + const target = this; // Check if we're at the boundary of the scrollable area const isAtBottom = target.scrollHeight - target.scrollTop <= target.clientHeight + 1; const isAtTop = target.scrollTop <= 0; // Only prevent default if we're not at the boundaries in the direction of scrolling - if ((wheelEvent.deltaY > 0 && !isAtBottom) || (wheelEvent.deltaY < 0 && !isAtTop)) { + if ((e.deltaY > 0 && !isAtBottom) || (e.deltaY < 0 && !isAtTop)) { e.stopPropagation(); e.preventDefault(); // Prevent the parent container from scrolling } diff --git a/lib/mail-parser-wrapper.ts b/lib/mail-parser-wrapper.ts index efb2204c..1216719b 100644 --- a/lib/mail-parser-wrapper.ts +++ b/lib/mail-parser-wrapper.ts @@ -105,12 +105,14 @@ export function cleanHtml(html: string): string { try { // Enhanced configuration to preserve more HTML elements for complex emails return DOMPurify.sanitize(html, { - ADD_TAGS: ['style', 'meta', 'link', 'table', 'thead', 'tbody', 'tr', 'td', 'th', 'hr'], - ADD_ATTR: ['*', 'colspan', 'rowspan', 'cellpadding', 'cellspacing', 'border', 'bgcolor', 'width', 'height', 'align', 'valign', 'class', 'id', 'style'], + ADD_TAGS: ['style', 'meta', 'link', 'table', 'thead', 'tbody', 'tr', 'td', 'th', 'hr', 'font', 'div', 'span', 'a', 'img', 'b', 'strong', 'i', 'em', 'u', 'br', 'p', 'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'pre', 'code', 'center', 'section', 'header', 'footer', 'article', 'nav'], + ADD_ATTR: ['*', 'colspan', 'rowspan', 'cellpadding', 'cellspacing', 'border', 'bgcolor', 'width', 'height', 'align', 'valign', 'class', 'id', 'style', 'color', 'face', 'size', 'background', 'src', 'href', 'target', 'rel', 'alt', 'title', 'name'], ALLOW_UNKNOWN_PROTOCOLS: true, WHOLE_DOCUMENT: true, KEEP_CONTENT: true, - RETURN_DOM: false + RETURN_DOM: false, + FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'form', 'input', 'button', 'select', 'option', 'textarea', 'canvas', 'video', 'audio'], + FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover', 'onmouseout', 'onchange', 'onsubmit'] }); } catch (error) { console.error('Error cleaning HTML:', error);