'use client'; import React, { useEffect, useState, useRef } from 'react'; import DOMPurify from 'isomorphic-dompurify'; import { parseRawEmail } from '@/lib/utils/email-mime-decoder'; interface EmailContentDisplayProps { content: string; type?: 'html' | 'text' | 'auto'; className?: string; showQuotedText?: boolean; } /** * Component for displaying properly formatted email content * Handles MIME decoding, sanitization, and proper rendering */ const EmailContentDisplay: React.FC = ({ content, type = 'auto', className = '', showQuotedText = true }) => { const [processedContent, setProcessedContent] = useState<{ html: string; text: string; isHtml: boolean; }>({ html: '', text: '', isHtml: false }); const containerRef = useRef(null); // Process and sanitize email content useEffect(() => { if (!content) { setProcessedContent({ html: '', text: '', isHtml: false }); return; } try { // Check if this is raw email content const isRawEmail = content.includes('Content-Type:') || content.includes('MIME-Version:') || content.includes('From:') && content.includes('To:'); if (isRawEmail) { // Parse raw email content const parsed = parseRawEmail(content); // Check which content to use based on type and availability const useHtml = (type === 'html' || (type === 'auto' && parsed.html)) && !!parsed.html; if (useHtml) { // Sanitize HTML content const sanitizedHtml = DOMPurify.sanitize(parsed.html, { ADD_TAGS: ['table', 'thead', 'tbody', 'tr', 'td', 'th'], ADD_ATTR: ['target', 'rel', 'colspan', 'rowspan'], ALLOW_DATA_ATTR: false }); setProcessedContent({ html: sanitizedHtml, text: parsed.text, isHtml: true }); } else { // Format plain text with line breaks const formattedText = parsed.text.replace(/\n/g, '
'); setProcessedContent({ html: formattedText, text: parsed.text, isHtml: false }); } } else { // Treat as direct content (not raw email) const isHtmlContent = content.includes('') || content.includes(''); setProcessedContent({ html: formattedText, text: content, isHtml: false }); } } } catch (err) { console.error('Error processing email content:', err); // Fallback to plain text setProcessedContent({ html: content.replace(/\n/g, '
'), text: content, isHtml: false }); } }, [content, type]); // Process quoted content visibility and fix table styling useEffect(() => { if (!containerRef.current || !processedContent.html) return; const container = containerRef.current; // Handle quoted text visibility if (!showQuotedText) { // Add toggle buttons for quoted text sections const quotedSections = container.querySelectorAll('blockquote'); quotedSections.forEach((quote, index) => { // Check if this quoted section already has a toggle if (quote.previousElementSibling?.classList.contains('quoted-toggle-btn')) { return; } // Create toggle button const toggleBtn = document.createElement('button'); toggleBtn.innerText = '▼ Show quoted text'; toggleBtn.className = 'quoted-toggle-btn'; toggleBtn.style.cssText = 'background: none; border: none; color: #666; font-size: 12px; cursor: pointer; padding: 4px 0; display: block;'; // Hide quoted section initially quote.style.display = 'none'; // Add click handler toggleBtn.addEventListener('click', () => { const isHidden = quote.style.display === 'none'; quote.style.display = isHidden ? 'block' : 'none'; toggleBtn.innerText = isHidden ? '▲ Hide quoted text' : '▼ Show quoted text'; }); // Insert before the blockquote quote.parentNode?.insertBefore(toggleBtn, quote); }); } // Process tables and ensure they're properly formatted const tables = container.querySelectorAll('table'); tables.forEach(table => { // Cast to HTMLTableElement to access style property const tableElement = table as HTMLTableElement; // Only apply styling if the table doesn't already have border styles if (!tableElement.hasAttribute('border') && (!tableElement.style.border || tableElement.style.border === '')) { // Apply proper table styling tableElement.style.width = '100%'; tableElement.style.borderCollapse = 'collapse'; tableElement.style.margin = '10px 0'; tableElement.style.border = '1px solid #ddd'; } const cells = table.querySelectorAll('td, th'); cells.forEach(cell => { // Cast to HTMLTableCellElement to access style property const cellElement = cell as HTMLTableCellElement; // Only apply styling if the cell doesn't already have border styles if (!cellElement.style.border || cellElement.style.border === '') { cellElement.style.border = '1px solid #ddd'; cellElement.style.padding = '6px'; } }); }); }, [processedContent.html, showQuotedText]); return (
); }; export default EmailContentDisplay;