/**
* CENTRAL EMAIL FORMATTING UTILITY
*
* This is the centralized email formatting utility used throughout the application.
* It provides consistent handling of email content, sanitization, and text direction.
*
* All code that needs to format email content should import from this file.
* Text direction is preserved based on content language for proper RTL/LTR display.
*/
// Remove DOMPurify import and use centralized sanitizeHtml
import { sanitizeHtml } from './dom-sanitizer';
import { applyTextDirection } from './text-direction';
// Instead of importing, implement the formatDateRelative function directly
// import { formatDateRelative } from './date-formatter';
/**
* Format a date in a relative format
* Simple implementation for email display
*/
function formatDateRelative(date: Date): string {
if (!date) return '';
try {
return date.toLocaleString('en-US', {
weekday: 'short',
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
} catch (e) {
return date.toString();
}
}
// Remove local DOMPurify configuration - now using centralized version
// Note: We ensure proper text direction using applyTextDirection or explicit dir attributes
// in the component level when rendering email content
// Interface definitions
export interface EmailAddress {
name: string;
address: string;
}
export interface EmailMessage {
id: string;
messageId?: string;
subject: string;
from: EmailAddress[];
to: EmailAddress[];
cc?: EmailAddress[];
bcc?: EmailAddress[];
date: Date | string;
flags?: {
seen: boolean;
flagged: boolean;
answered: boolean;
deleted: boolean;
draft: boolean;
};
preview?: string;
content?: string;
html?: string;
text?: string;
hasAttachments?: boolean;
attachments?: any[];
folder?: string;
size?: number;
contentFetched?: boolean;
}
interface EmailContent {
html?: string;
text?: string;
}
/**
* Format email addresses for display
*/
export function formatEmailAddresses(addresses: EmailAddress[]): string {
if (!addresses || addresses.length === 0) return '';
return addresses.map(addr =>
addr.name && addr.name !== addr.address
? `${addr.name} <${addr.address}>`
: addr.address
).join(', ');
}
/**
* Format date for display
*/
export function formatEmailDate(date: Date | string | undefined): string {
if (!date) return '';
try {
const dateObj = typeof date === 'string' ? new Date(date) : date;
return dateObj.toLocaleString('en-US', {
weekday: 'short',
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
} catch (e) {
return typeof date === 'string' ? date : date.toString();
}
}
/**
* Format an email for forwarding - CENTRAL IMPLEMENTATION
* All other formatting functions should be deprecated in favor of this one
*/
export function formatForwardedEmail(email: EmailMessage): {
subject: string;
content: string;
} {
// Format subject with Fwd: prefix if needed
const subjectBase = email.subject || '(No subject)';
const subject = subjectBase.match(/^(Fwd|FW|Forward):/i)
? subjectBase
: `Fwd: ${subjectBase}`;
// Get sender and recipient information
const fromString = formatEmailAddresses(email.from || []);
const toString = formatEmailAddresses(email.to || []);
const dateString = formatEmailDate(email.date);
// Get and sanitize original content (sanitization preserves content direction)
const originalContent = sanitizeHtml(email.content || email.html || email.text || '');
// Check if the content already has a forwarded message header
const hasExistingHeader = originalContent.includes('---------- Forwarded message ---------');
// If there's already a forwarded message header, don't add another one
if (hasExistingHeader) {
// Just wrap the content without additional formatting
const content = `
${originalContent}
`;
return { subject, content };
}
// Create formatted content for forwarded email
const content = `
---------- Forwarded message ---------
From: ${fromString}
Date: ${dateString}
Subject: ${email.subject || ''}
To: ${toString}
${originalContent}
`;
return { subject, content };
}
/**
* Format an email for reply or reply-all - CENTRAL IMPLEMENTATION
* All other formatting functions should be deprecated in favor of this one
*/
export function formatReplyEmail(email: EmailMessage, type: 'reply' | 'reply-all'): {
to: string;
cc?: string;
subject: string;
content: string;
} {
// Format subject with Re: prefix if needed
const subjectBase = email.subject || '(No subject)';
const subject = subjectBase.match(/^Re:/i)
? subjectBase
: `Re: ${subjectBase}`;
// Get sender information for quote header
const sender = email.from[0];
const fromText = sender?.name
? `${sender.name} <${sender.address}>`
: sender?.address || 'Unknown sender';
// Format date for quote header
const date = typeof email.date === 'string' ? new Date(email.date) : email.date;
const formattedDate = date.toLocaleString('en-US', {
weekday: 'short',
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
// Create quote header
const quoteHeader = `On ${formattedDate}, ${fromText} wrote:
`;
// Get and sanitize original content (sanitization preserves content direction)
let originalContent = '';
if (email.content && typeof email.content === 'object') {
const contentObj = email.content as EmailContent;
originalContent = contentObj.html || contentObj.text || '';
} else if (typeof email.content === 'string') {
originalContent = email.content;
} else {
originalContent = email.html || email.text || '';
}
const quotedContent = sanitizeHtml(originalContent);
// Format recipients
let to = formatEmailAddresses(email.from || []);
let cc = '';
if (type === 'reply-all') {
// For reply-all, add all original recipients to CC
const allRecipients = [
...(email.to || []),
...(email.cc || [])
];
cc = formatEmailAddresses(allRecipients);
}
// Format content for reply with improved styling
const content = `
${quoteHeader}
${quotedContent}
`;
return {
to,
cc: cc || undefined,
subject,
content
};
}
/**
* COMPATIBILITY LAYER: For backward compatibility with the old email-formatter.ts
* These functions map to our new implementation but preserve the old interface
*/
export function formatEmailForReplyOrForward(
email: EmailMessage,
type: 'reply' | 'reply-all' | 'forward'
): {
to: string;
cc?: string;
subject: string;
body: string;
} {
if (type === 'forward') {
const { subject, content } = formatForwardedEmail(email);
return {
to: '',
subject,
body: content
};
} else {
const { to, cc, subject, content } = formatReplyEmail(email, type as 'reply' | 'reply-all');
return {
to,
cc,
subject,
body: content
};
}
}
/**
* Decode compose content from MIME format to HTML and text
*/
export async function decodeComposeContent(content: string): Promise<{
html: string | null;
text: string | null;
}> {
if (!content.trim()) {
return { html: null, text: null };
}
try {
const response = await fetch('/api/parse-email', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email: content }),
});
if (!response.ok) {
throw new Error('Failed to parse email');
}
const parsed = await response.json();
// Apply LTR sanitization to the parsed content
return {
html: parsed.html ? sanitizeHtml(parsed.html) : null,
text: parsed.text || null
};
} catch (error) {
console.error('Error parsing email content:', error);
// Fallback to basic content handling with sanitization
return {
html: sanitizeHtml(content),
text: content
};
}
}
/**
* Encode compose content to MIME format for sending
*/
export function encodeComposeContent(content: string): string {
if (!content.trim()) {
throw new Error('Email content is empty');
}
// Create MIME headers
const mimeHeaders = {
'MIME-Version': '1.0',
'Content-Type': 'text/html; charset="utf-8"',
'Content-Transfer-Encoding': 'quoted-printable'
};
// Combine headers and content
return Object.entries(mimeHeaders)
.map(([key, value]) => `${key}: ${value}`)
.join('\n') + '\n\n' + content;
}
/**
* COMPATIBILITY LAYER: For backward compatibility with the old email-formatter.ts
* Use the newer formatReplyEmail function instead when possible.
*
* @deprecated Use formatReplyEmail instead
*/
export function formatReplyEmailLegacy(email: any): string {
// Use our own sanitizeHtml function consistently
const sanitizedBody = sanitizeHtml(email.content || '');
// Format the reply with consistent direction handling
const replyContent = `
On ${email.date}, ${email.from} wrote:
${sanitizedBody}
`;
// Apply consistent text direction
return applyTextDirection(replyContent);
}
/**
* COMPATIBILITY LAYER: For backward compatibility with the old email-formatter.ts
* Use the newer formatForwardedEmail function instead when possible.
*
* @deprecated Use formatForwardedEmail instead
*/
export function formatForwardedEmailLegacy(email: any): string {
// Use our own sanitizeHtml function consistently
const sanitizedBody = sanitizeHtml(email.content || '');
// Format the forwarded content with consistent direction handling
const forwardedContent = `
---------- Forwarded message ---------
From: ${email.from}
Date: ${email.date}
Subject: ${email.subject}
To: ${email.to}
${sanitizedBody}
`;
// Apply consistent text direction
return applyTextDirection(forwardedContent);
}
export function formatReplyToAllEmail(email: any): string {
// For reply all, we use the same format as regular reply
return formatReplyEmailLegacy(email);
}
// Utility function to get the reply subject line
export function getReplySubject(subject: string): string {
return subject.startsWith('Re:') ? subject : `Re: ${subject}`;
}
// Utility function to get the forward subject line
export function getForwardSubject(subject: string): string {
return subject.startsWith('Fwd:') ? subject : `Fwd: ${subject}`;
}