181 lines
5.1 KiB
TypeScript
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, '<').replace(/>/g, '>').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;
|