courrier formatting
This commit is contained in:
parent
4c39241d3c
commit
3f017f82f8
@ -1,181 +0,0 @@
|
|||||||
'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;
|
|
||||||
@ -79,37 +79,26 @@ export default function EmailPanel({
|
|||||||
|
|
||||||
console.log('EmailPanel: Raw email:', email);
|
console.log('EmailPanel: Raw email:', email);
|
||||||
|
|
||||||
// If content is already an object with html/text, use it directly
|
// CRITICAL FIX: Simplify email formatting to prevent double processing
|
||||||
if (email.content && typeof email.content === 'object') {
|
// Just normalize the content structure, don't try to format content here
|
||||||
console.log('EmailPanel: Using existing content object');
|
// The actual formatting will happen in EmailPreview with formatEmailContent
|
||||||
return {
|
|
||||||
...email,
|
// If all fields are already present, just return as is
|
||||||
content: {
|
if (email.content && typeof email.content === 'object' && email.content.html && email.content.text) {
|
||||||
text: email.content.text || '',
|
return email;
|
||||||
html: email.content.html || ''
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If content is a string, convert it to object format
|
// Create a standardized email object with consistent content structure
|
||||||
if (typeof email.content === 'string') {
|
|
||||||
console.log('EmailPanel: Converting string content to object');
|
|
||||||
return {
|
|
||||||
...email,
|
|
||||||
content: {
|
|
||||||
text: email.text || email.content,
|
|
||||||
html: email.html || email.content
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to html/text properties
|
|
||||||
console.log('EmailPanel: Using html/text properties');
|
|
||||||
return {
|
return {
|
||||||
...email,
|
...email,
|
||||||
|
// Ensure content is an object with html and text properties
|
||||||
content: {
|
content: {
|
||||||
text: email.text || '',
|
text: typeof email.content === 'object' ? email.content.text :
|
||||||
html: email.html || ''
|
typeof email.text === 'string' ? email.text :
|
||||||
|
typeof email.content === 'string' ? email.content : '',
|
||||||
|
html: typeof email.content === 'object' ? email.content.html :
|
||||||
|
typeof email.html === 'string' ? email.html :
|
||||||
|
typeof email.content === 'string' ? email.content : ''
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [email]);
|
}, [email]);
|
||||||
|
|||||||
@ -107,8 +107,25 @@ export default function EmailPreview({ email, loading = false, onReply }: EmailP
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the improved, standardized email content formatter
|
// CRITICAL FIX: Send consistent input format to formatEmailContent
|
||||||
return formatEmailContent(email);
|
try {
|
||||||
|
// Log what we're sending to formatEmailContent for debugging
|
||||||
|
console.log('EmailPreview: Calling formatEmailContent with email:',
|
||||||
|
JSON.stringify({
|
||||||
|
id: email.id,
|
||||||
|
contentType: typeof email.content,
|
||||||
|
hasHtml: typeof email.content === 'object' ? !!email.content.html : false,
|
||||||
|
hasText: typeof email.content === 'object' ? !!email.content.text : false,
|
||||||
|
hasHtmlProp: !!email.html,
|
||||||
|
hasTextProp: !!email.text
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return formatEmailContent(email);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error formatting email content:', error);
|
||||||
|
return `<div class="error-message p-4 text-red-500">Error rendering email content: ${error instanceof Error ? error.message : 'Unknown error'}</div>`;
|
||||||
|
}
|
||||||
}, [email]);
|
}, [email]);
|
||||||
|
|
||||||
// Display loading state
|
// Display loading state
|
||||||
@ -219,35 +236,33 @@ export default function EmailPreview({ email, loading = false, onReply }: EmailP
|
|||||||
{/* Email content */}
|
{/* Email content */}
|
||||||
<ScrollArea className="flex-1">
|
<ScrollArea className="flex-1">
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
|
{/* IMPROVED: Simplified email content container with better styling */}
|
||||||
<div
|
<div
|
||||||
ref={editorRef}
|
ref={editorRef}
|
||||||
contentEditable={false}
|
className="email-content-container rounded-lg overflow-hidden bg-white shadow-sm"
|
||||||
className="w-full email-content-container rounded-md overflow-hidden"
|
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: '#ffffff',
|
minHeight: '300px',
|
||||||
border: '1px solid #e2e8f0',
|
border: '1px solid #e2e8f0'
|
||||||
boxShadow: '0 1px 3px rgba(0,0,0,0.05)',
|
|
||||||
minHeight: '300px'
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="email-content-body p-4 sm:p-6">
|
{/* Render the formatted content directly */}
|
||||||
{formattedContent ? (
|
{formattedContent ? (
|
||||||
<div
|
<div
|
||||||
className="email-content-rendered"
|
className="email-body"
|
||||||
dangerouslySetInnerHTML={{ __html: formattedContent }}
|
dangerouslySetInnerHTML={{ __html: formattedContent }}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="email-content-empty py-8 text-center text-muted-foreground">
|
<div className="p-8 text-center text-muted-foreground">
|
||||||
<p>This email does not contain any content.</p>
|
<p>This email does not contain any content.</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Only in development mode: Show debugging info */}
|
||||||
{process.env.NODE_ENV === 'development' && (
|
{process.env.NODE_ENV === 'development' && (
|
||||||
<details className="mt-6 text-xs text-muted-foreground border rounded-md p-2">
|
<details className="mt-4 text-xs text-muted-foreground border rounded-md p-2">
|
||||||
<summary className="cursor-pointer">Email Debug Info</summary>
|
<summary className="cursor-pointer">Email Debug Info</summary>
|
||||||
<div className="mt-2 overflow-auto max-h-40">
|
<div className="mt-2 overflow-auto max-h-40 p-2 bg-gray-50 rounded">
|
||||||
<p><strong>Email ID:</strong> {email.id}</p>
|
<p><strong>Email ID:</strong> {email.id}</p>
|
||||||
<p><strong>Content Type:</strong> {
|
<p><strong>Content Type:</strong> {
|
||||||
typeof email.content === 'object' && email.content?.html
|
typeof email.content === 'object' && email.content?.html
|
||||||
|
|||||||
@ -51,12 +51,12 @@ export function formatEmailContent(email: any): string {
|
|||||||
|
|
||||||
// If we have HTML content, sanitize and standardize it
|
// If we have HTML content, sanitize and standardize it
|
||||||
if (isHtml && content) {
|
if (isHtml && content) {
|
||||||
// Make sure we have a complete HTML structure
|
// CRITICAL FIX: Check for browser environment since DOMParser is browser-only
|
||||||
const hasHtmlTag = content.includes('<html');
|
const hasHtmlTag = content.includes('<html');
|
||||||
const hasBodyTag = content.includes('<body');
|
const hasBodyTag = content.includes('<body');
|
||||||
|
|
||||||
// Extract body content if we have a complete HTML document
|
// Extract body content if we have a complete HTML document and in browser environment
|
||||||
if (hasHtmlTag && hasBodyTag) {
|
if (hasHtmlTag && hasBodyTag && typeof window !== 'undefined' && typeof DOMParser !== 'undefined') {
|
||||||
try {
|
try {
|
||||||
// Create a DOM parser to extract just the body content
|
// Create a DOM parser to extract just the body content
|
||||||
const parser = new DOMParser();
|
const parser = new DOMParser();
|
||||||
@ -72,8 +72,8 @@ export function formatEmailContent(email: any): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sanitize with industry-standard email tags and attributes
|
// CRITICAL FIX: Configure DOMPurify for maximum compatibility with email content
|
||||||
// Use a more permissive configuration for email HTML
|
// This is a more permissive configuration that preserves common email HTML
|
||||||
const sanitizedContent = DOMPurify.sanitize(content, {
|
const sanitizedContent = DOMPurify.sanitize(content, {
|
||||||
ADD_TAGS: [
|
ADD_TAGS: [
|
||||||
'style', 'table', 'thead', 'tbody', 'tfoot', 'tr', 'td', 'th',
|
'style', 'table', 'thead', 'tbody', 'tfoot', 'tr', 'td', 'th',
|
||||||
@ -85,15 +85,18 @@ export function formatEmailContent(email: any): string {
|
|||||||
'figcaption', 'address', 'main', 'center', 'font'
|
'figcaption', 'address', 'main', 'center', 'font'
|
||||||
],
|
],
|
||||||
ADD_ATTR: [
|
ADD_ATTR: [
|
||||||
'class', 'style', 'id', 'href', 'src', 'alt', 'title', 'width', 'height',
|
'style', 'class', 'id', 'href', 'src', 'alt', 'title', 'width', 'height',
|
||||||
'border', 'cellspacing', 'cellpadding', 'bgcolor', 'color', 'dir', 'lang',
|
'border', 'cellspacing', 'cellpadding', 'bgcolor', 'color', 'dir', 'lang',
|
||||||
'align', 'valign', 'span', 'colspan', 'rowspan', 'target', 'rel',
|
'align', 'valign', 'span', 'colspan', 'rowspan', 'target', 'rel',
|
||||||
'background', 'data-*', 'face', 'size', 'bgcolor', 'hspace', 'vspace',
|
'background', 'data-*', 'face', 'size', 'hspace', 'vspace',
|
||||||
'marginheight', 'marginwidth', 'frameborder'
|
'marginheight', 'marginwidth', 'frameborder'
|
||||||
],
|
],
|
||||||
ALLOW_DATA_ATTR: true,
|
ALLOW_DATA_ATTR: true,
|
||||||
|
ALLOW_UNKNOWN_PROTOCOLS: true, // Allow cid: and other protocols
|
||||||
|
ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|cid|mailto|tel|callto|sms|bitcoin|data):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i,
|
||||||
WHOLE_DOCUMENT: false,
|
WHOLE_DOCUMENT: false,
|
||||||
RETURN_DOM: false,
|
RETURN_DOM: false,
|
||||||
|
USE_PROFILES: { html: true, svg: false, svgFilters: false }, // Allow standard HTML
|
||||||
FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'form', 'input', 'textarea', 'select', 'button'],
|
FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'form', 'input', 'textarea', 'select', 'button'],
|
||||||
FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover', 'onmouseout']
|
FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover', 'onmouseout']
|
||||||
});
|
});
|
||||||
@ -103,29 +106,19 @@ export function formatEmailContent(email: any): string {
|
|||||||
// Fix for Outlook WebVML content
|
// Fix for Outlook WebVML content
|
||||||
.replace(/<!--\[if\s+gte\s+mso/g, '<!--[if gte mso')
|
.replace(/<!--\[if\s+gte\s+mso/g, '<!--[if gte mso')
|
||||||
// Fix for broken image paths that might be relative
|
// Fix for broken image paths that might be relative
|
||||||
.replace(/(src|background)="(?!http|https|data|cid)/gi, '$1="https://');
|
.replace(/(src|background)="(?!(?:https?:|data:|cid:))/gi, '$1="https://')
|
||||||
|
// Fix for base64 images that might be broken across lines
|
||||||
// Fix for inline image references (CID)
|
.replace(/src="data:image\/[^;]+;base64,\s*([^"]+)\s*"/gi, (match, p1) => {
|
||||||
if (fixedContent.includes('cid:')) {
|
return `src="data:image/png;base64,${p1.replace(/\s+/g, '')}"`;
|
||||||
console.log('Email contains CID references - these cannot be displayed properly in the web UI');
|
});
|
||||||
// We can't actually render CID references in the web UI
|
|
||||||
// Just log a message for now - a more comprehensive fix would involve
|
|
||||||
// extracting and converting these images
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for RTL content and set appropriate direction
|
// Check for RTL content and set appropriate direction
|
||||||
const rtlLangPattern = /[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]/;
|
const rtlLangPattern = /[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]/;
|
||||||
const containsRtlText = rtlLangPattern.test(textContent);
|
const containsRtlText = rtlLangPattern.test(textContent);
|
||||||
const dirAttribute = containsRtlText ? 'dir="rtl"' : 'dir="ltr"';
|
const dirAttribute = containsRtlText ? 'dir="rtl"' : 'dir="ltr"';
|
||||||
|
|
||||||
// Wrap the content in standard email container with responsive styling
|
// CRITICAL FIX: Use a single wrapper with all necessary styles for better email client compatibility
|
||||||
return `
|
return `<div class="email-content" ${dirAttribute} style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; line-height: 1.6; color: #333; max-width: 100%; overflow-x: auto; overflow-wrap: break-word; word-wrap: break-word;">${fixedContent}</div>`;
|
||||||
<div class="email-content-wrapper" style="max-width: 100%; overflow-x: auto;">
|
|
||||||
<div class="email-content" ${dirAttribute} style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; line-height: 1.6; color: #333; max-width: 100%; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word;">
|
|
||||||
${fixedContent}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
// If we only have text content, format it properly
|
// If we only have text content, format it properly
|
||||||
else if (textContent) {
|
else if (textContent) {
|
||||||
@ -148,25 +141,15 @@ export function formatEmailContent(email: any): string {
|
|||||||
.replace(/((?:<br>){2,})/g, '</p><p>') // Convert multiple newlines to paragraphs
|
.replace(/((?:<br>){2,})/g, '</p><p>') // Convert multiple newlines to paragraphs
|
||||||
.replace(/<br><\/p>/g, '</p>') // Fix any <br></p> combinations
|
.replace(/<br><\/p>/g, '</p>') // Fix any <br></p> combinations
|
||||||
.replace(/<p><br>/g, '<p>'); // Fix any <p><br> combinations
|
.replace(/<p><br>/g, '<p>'); // Fix any <p><br> combinations
|
||||||
|
|
||||||
return `
|
// CRITICAL FIX: Use consistent structure with HTML emails for better compatibility
|
||||||
<div class="email-content-wrapper" style="max-width: 100%; overflow-x: auto;">
|
return `<div class="email-content plain-text" ${dirAttribute} style="font-family: -apple-system, BlinkMacSystemFont, Menlo, Monaco, Consolas, 'Courier New', monospace; white-space: pre-wrap; line-height: 1.5; color: #333; padding: 15px; max-width: 100%; overflow-wrap: break-word;"><p>${formattedText}</p></div>`;
|
||||||
<div class="email-content plain-text" ${dirAttribute} style="font-family: -apple-system, BlinkMacSystemFont, Menlo, Monaco, Consolas, 'Courier New', monospace; white-space: pre-wrap; line-height: 1.5; color: #333; padding: 15px; background-color: #f8f9fa; border-radius: 4px; max-width: 100%; overflow-wrap: break-word; word-wrap: break-word;">
|
|
||||||
<p>${formattedText}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default case: empty or unrecognized content
|
// Default case: empty or unrecognized content
|
||||||
return '<div class="email-content-empty">No content available</div>';
|
return '<div class="email-content-empty" style="padding: 20px; text-align: center; color: #666;">No content available</div>';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('formatEmailContent: Error formatting email content:', error);
|
console.error('formatEmailContent: Error formatting email content:', error);
|
||||||
return `
|
return `<div class="email-content-error" style="padding: 15px; color: #721c24; background-color: #f8d7da; border: 1px solid #f5c6cb; border-radius: 4px;"><p>Error displaying email content</p><p style="font-size: 12px; margin-top: 10px;">${error instanceof Error ? error.message : 'Unknown error'}</p></div>`;
|
||||||
<div class="email-content-error" style="padding: 15px; color: #721c24; background-color: #f8d7da; border: 1px solid #f5c6cb; border-radius: 4px;">
|
|
||||||
<p>Error displaying email content</p>
|
|
||||||
<p style="font-size: 12px; margin-top: 10px;">${error instanceof Error ? error.message : 'Unknown error'}</p>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user