Neah/lib/email-formatter.ts
2025-04-26 13:02:31 +02:00

225 lines
6.9 KiB
TypeScript

'use client';
import DOMPurify from 'dompurify';
/**
* Client-side utilities for formatting email content
* This file contains functions for formatting email content in the browser
* without any server dependencies.
*/
export interface EmailAddress {
address: string;
name?: string;
}
export interface FormattedEmail {
subject: string;
to?: EmailAddress[];
cc?: EmailAddress[];
bcc?: EmailAddress[];
body: string;
}
export interface EmailMessageForFormatting {
subject?: string;
from?: EmailAddress | EmailAddress[];
to?: EmailAddress | EmailAddress[];
date?: Date | string;
html?: string;
text?: string;
cc?: EmailAddress | EmailAddress[];
bcc?: EmailAddress | EmailAddress[];
}
/**
* Format an email for replying or forwarding
* Client-side friendly version that doesn't depend on server modules
*/
export function formatEmailForReply(
originalEmail: EmailMessageForFormatting,
type: 'reply' | 'replyAll' | 'forward' = 'reply'
): FormattedEmail {
// Format the subject with Re: or Fwd: prefix
const subject = formatSubject(originalEmail.subject || '', type);
// Initialize recipients based on reply type
let to: EmailAddress[] = [];
let cc: EmailAddress[] = [];
if (type === 'reply' && originalEmail.from) {
to = Array.isArray(originalEmail.from) ? originalEmail.from : [originalEmail.from];
} else if (type === 'replyAll') {
// To: original sender
if (originalEmail.from) {
to = Array.isArray(originalEmail.from) ? originalEmail.from : [originalEmail.from];
}
// CC: all other recipients
if (originalEmail.to) {
cc = Array.isArray(originalEmail.to) ? originalEmail.to : [originalEmail.to];
}
if (originalEmail.cc) {
const existingCc = Array.isArray(originalEmail.cc) ? originalEmail.cc : [originalEmail.cc];
cc = [...cc, ...existingCc];
}
// Remove duplicates and self from CC (would need user's email here)
// This is simplified - in a real app you'd filter out the current user
cc = cc.filter((value, index, self) =>
index === self.findIndex((t) => t.address === value.address)
);
}
// Create the quoted content with header
const quoteHeader = createQuoteHeader(originalEmail);
// Get the original content, preferring HTML over plain text
let originalContent = '';
if (originalEmail.html) {
// Sanitize any potentially unsafe HTML
originalContent = DOMPurify.sanitize(originalEmail.html);
} else if (originalEmail.text) {
// Convert text to HTML by replacing newlines with br tags
originalContent = originalEmail.text.replace(/\n/g, '<br>');
}
// Combine the header with the original content, ensuring proper direction
const body = `
<div dir="ltr" style="text-align: left;">
<br>
<blockquote style="margin: 0 0 0 0.8ex; border-left: 1px solid #ccc; padding-left: 1ex;">
${quoteHeader}
<div style="direction: ltr; text-align: left;">${originalContent || 'No content available'}</div>
</blockquote>
</div>
`;
return {
subject,
to,
cc,
body
};
}
/**
* Format email subject with appropriate prefix
*/
export function formatSubject(
originalSubject: string,
type: 'reply' | 'replyAll' | 'forward'
): string {
// Trim whitespace
let subject = originalSubject.trim();
// Remove existing prefixes to avoid duplication
subject = subject.replace(/^(Re|Fwd):\s*/gi, '');
// Add appropriate prefix based on action type
if (type === 'forward') {
return `Fwd: ${subject}`;
} else {
return `Re: ${subject}`;
}
}
/**
* Create a formatted quote header with sender and date information
*/
export function createQuoteHeader(email: EmailMessageForFormatting): string {
let fromName = 'Unknown Sender';
let fromEmail = '';
// Extract sender information
if (email.from) {
if (Array.isArray(email.from)) {
fromName = email.from[0].name || email.from[0].address;
fromEmail = email.from[0].address;
} else {
fromName = email.from.name || email.from.address;
fromEmail = email.from.address;
}
}
// Format the date
let dateFormatted = '';
if (email.date) {
const date = typeof email.date === 'string' ? new Date(email.date) : email.date;
// Check if the date is valid
if (!isNaN(date.getTime())) {
dateFormatted = date.toLocaleString('en-US', {
weekday: 'short',
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
}
}
// Generate recipients string
let recipients = '';
if (email.to) {
if (Array.isArray(email.to)) {
recipients = email.to.map(r => r.name || r.address).join(', ');
} else {
recipients = email.to.name || email.to.address;
}
}
// Create the header HTML with explicit LTR direction
return `
<div style="margin-bottom: 10px; color: #555; font-size: 0.9em; direction: ltr; text-align: left;">
<div><strong>From:</strong> ${fromName} &lt;${fromEmail}&gt;</div>
${dateFormatted ? `<div><strong>Date:</strong> ${dateFormatted}</div>` : ''}
<div><strong>Subject:</strong> ${email.subject || 'No Subject'}</div>
<div><strong>To:</strong> ${recipients || 'No Recipients'}</div>
${email.cc ? `<div><strong>Cc:</strong> ${Array.isArray(email.cc) ?
email.cc.map(r => r.name || r.address).join(', ') :
(email.cc.name || email.cc.address)}</div>` : ''}
</div>
<hr style="border: none; border-top: 1px solid #ddd; margin: 10px 0;">
`;
}
/**
* Format an email for forwarding
*/
export function formatEmailForForward(email: EmailMessageForFormatting): FormattedEmail {
// Format the subject with Fwd: prefix
const subject = formatSubject(email.subject || '', 'forward');
// Create the forward header
const forwardHeader = createQuoteHeader(email);
// Get the original content, preferring HTML over plain text
let originalContent = '';
if (email.html) {
// Sanitize any potentially unsafe HTML
originalContent = DOMPurify.sanitize(email.html);
} else if (email.text) {
// Convert text to HTML by replacing newlines with br tags
originalContent = email.text.replace(/\n/g, '<br>');
}
// Combine the header with the original content, ensuring proper direction
const body = `
<div dir="ltr" style="text-align: left;">
<br>
<div style="border-left: 1px solid #ccc; padding-left: 1ex; margin-left: 0.8ex; direction: ltr; text-align: left;">
<div style="font-weight: bold; margin-bottom: 10px; direction: ltr; text-align: left;">---------- Forwarded message ---------</div>
${forwardHeader}
<div style="direction: ltr; text-align: left;">${originalContent || '<div style="padding: 10px; text-align: center; background-color: #f8f8f8; border: 1px dashed #ccc; margin: 10px 0;">No content available</div>'}</div>
</div>
</div>
`;
return {
subject,
body
};
}