Neah/components/email/EmailContentDisplay.tsx
2025-04-30 15:38:01 +02:00

181 lines
5.1 KiB
TypeScript

'use client';
import React, { useEffect, useState } from 'react';
import DOMPurify from 'dompurify';
import { formatEmailContent } from '@/lib/utils/email-content';
import { sanitizeHtml } from '@/lib/utils/email-formatter';
/**
* Interface for email content types
*/
interface ProcessedContent {
html: string;
text: string;
isHtml: boolean;
}
/**
* Interface for component props
*/
interface EmailContentDisplayProps {
content: string;
type?: 'html' | 'text' | 'auto';
isRawEmail?: boolean;
className?: string;
}
/**
* Parse raw email content into HTML and text parts
* This is a helper function used when processing raw email formats
*/
function parseRawEmail(rawContent: string): { html: string; text: string } {
// Simple parser for demonstration - in production, use a proper MIME parser
const hasHtmlPart = rawContent.includes('<html') ||
rawContent.includes('<body') ||
rawContent.includes('Content-Type: text/html');
if (hasHtmlPart) {
// Extract HTML part
let htmlPart = '';
const htmlMatch = rawContent.match(/<html[\s\S]*<\/html>/i) ||
rawContent.match(/<body[\s\S]*<\/body>/i);
if (htmlMatch) {
htmlPart = htmlMatch[0];
} else {
// Fallback extraction
const parts = rawContent.split(/(?:--boundary|\r\n\r\n)/);
htmlPart = parts.find(part =>
part.includes('Content-Type: text/html') ||
part.includes('<html') ||
part.includes('<body')
) || '';
}
return {
html: htmlPart,
text: rawContent.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim()
};
}
return {
html: '',
text: rawContent
};
}
/**
* EmailContentDisplay component - displays formatted email content
* with proper security, styling and support for different email formats
*/
const EmailContentDisplay: React.FC<EmailContentDisplayProps> = ({
content,
type = 'auto',
isRawEmail = false,
className = ''
}) => {
const [processedContent, setProcessedContent] = useState<ProcessedContent>({
html: '',
text: '',
isHtml: false
});
// Process the email content when it changes
useEffect(() => {
if (!content) {
setProcessedContent({
html: '',
text: '',
isHtml: false
});
return;
}
try {
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) {
// Use the enhanced sanitizeHtml function from email-formatter
const sanitizedHtml = sanitizeHtml(parsed.html);
setProcessedContent({
html: sanitizedHtml,
text: parsed.text,
isHtml: true
});
} else {
// Format plain text properly
const formattedText = formatEmailContent({ text: parsed.text });
setProcessedContent({
html: formattedText,
text: parsed.text,
isHtml: false
});
}
} else {
// Treat as direct content (not raw email)
const isHtmlContent = type === 'html' || (
type === 'auto' && (
content.includes('<html') ||
content.includes('<body') ||
content.includes('<div') ||
content.includes('<p>') ||
content.includes('<br')
)
);
if (isHtmlContent) {
// Use the enhanced sanitizeHtml function
const sanitizedHtml = sanitizeHtml(content);
setProcessedContent({
html: sanitizedHtml,
text: content.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim(),
isHtml: true
});
} else {
// Format plain text properly using formatEmailContent
const formattedText = formatEmailContent({ text: content });
setProcessedContent({
html: formattedText,
text: content,
isHtml: false
});
}
}
} catch (err) {
console.error('Error processing email content:', err);
// Fallback to plain text with basic formatting
setProcessedContent({
html: `<div style="white-space: pre-wrap; font-family: monospace;">${content.replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\n/g, '<br />')}</div>`,
text: content,
isHtml: false
});
}
}, [content, type, isRawEmail]);
return (
<div className={`email-content-container ${className}`}>
<div
className="email-content-viewer"
style={{
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
fontSize: '15px',
lineHeight: '1.6',
color: '#1A202C',
overflow: 'auto'
}}
dangerouslySetInnerHTML={{ __html: processedContent.html }}
/>
</div>
);
};
export default EmailContentDisplay;