+
{/* Email body editor */}
diff --git a/lib/email-formatter.ts b/lib/email-formatter.ts
index 9cc17d1f..140d5adf 100644
--- a/lib/email-formatter.ts
+++ b/lib/email-formatter.ts
@@ -1,209 +1,225 @@
'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.
*/
-interface EmailAddress {
- name?: string;
+export interface EmailAddress {
address: string;
+ name?: string;
}
-interface FormattedEmail {
- to: string;
- cc?: 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(
- email: any,
- type: 'reply' | 'reply-all' = 'reply'
+ originalEmail: EmailMessageForFormatting,
+ type: 'reply' | 'replyAll' | 'forward' = 'reply'
): FormattedEmail {
- // Format the subject with Re: prefix
- const subject = formatSubject(email.subject || '(No subject)', type);
+ // Format the subject with Re: or Fwd: prefix
+ const subject = formatSubject(originalEmail.subject || '', type);
- // Format recipients
- let to = '';
- let cc = '';
+ // Initialize recipients based on reply type
+ let to: EmailAddress[] = [];
+ let cc: EmailAddress[] = [];
- // Process 'to' field for reply
- if (typeof email.from === 'string') {
- to = email.from;
- } else if (Array.isArray(email.from)) {
- to = email.from.map((addr: EmailAddress) =>
- addr.name && addr.name !== addr.address
- ? `${addr.name} <${addr.address}>`
- : addr.address
- ).join(', ');
- } else if (email.fromName || email.from) {
- // Handle cases where from is an object with name and address
- if (email.fromName && email.from && email.fromName !== email.from) {
- to = `${email.fromName} <${email.from}>`;
- } else {
- to = email.from;
- }
- }
-
- // For reply-all, include other recipients in cc
- if (type === 'reply-all' && email.to) {
- if (typeof email.to === 'string') {
- cc = email.to;
- } else if (Array.isArray(email.to)) {
- cc = email.to.map((addr: EmailAddress) =>
- addr.name && addr.name !== addr.address
- ? `${addr.name} <${addr.address}>`
- : addr.address
- ).join(', ');
+ 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];
}
- // Include cc recipients from original email too if available
- if (email.cc) {
- const ccList = typeof email.cc === 'string'
- ? email.cc
- : Array.isArray(email.cc)
- ? email.cc.map((addr: EmailAddress) => addr.address).join(', ')
- : '';
-
- if (ccList) {
- cc = cc ? `${cc}, ${ccList}` : ccList;
- }
+ // 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 quote header
- const quoteHeader = createQuoteHeader(email);
+ // Create the quoted content with header
+ const quoteHeader = createQuoteHeader(originalEmail);
- // Format body with quote
- let body = `
${quoteHeader}
`;
-
- // Add quoted content
- if (email.content) {
- body += email.content;
- } else if (email.html) {
- body += email.html;
- } else if (email.text) {
- body += `
${email.text}
`;
- } else if (email.body) {
- body += email.body;
- } else {
- body += '
No content available
';
+ // 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, ' ');
}
- body += '
';
+ // Combine the header with the original content
+ const body = `
+
+
+
+ ${quoteHeader}
+
${originalContent || 'No content available'}
+
+
+ `;
return {
+ subject,
to,
cc,
- subject,
body
};
}
-function formatSubject(subject: string, type: 'reply' | 'reply-all' | 'forward'): string {
- // Clean up existing prefixes first
- let cleanSubject = subject.replace(/^(Re|Fwd|FW|Forward):\s*/gi, '');
- cleanSubject = cleanSubject.trim() || '(No subject)';
+/**
+ * Format email subject with appropriate prefix
+ */
+export function formatSubject(
+ originalSubject: string,
+ type: 'reply' | 'replyAll' | 'forward'
+): string {
+ // Trim whitespace
+ let subject = originalSubject.trim();
- // Add appropriate prefix
+ // 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: ${cleanSubject}`;
+ return `Fwd: ${subject}`;
} else {
- // For reply and reply-all
- return `Re: ${cleanSubject}`;
+ return `Re: ${subject}`;
}
}
-function createQuoteHeader(email: any): string {
- let from = 'Unknown Sender';
- let date = email.date ? new Date(email.date).toLocaleString() : 'Unknown Date';
- let subject = email.subject || '(No subject)';
- let to = '';
+/**
+ * Create a formatted quote header with sender and date information
+ */
+export function createQuoteHeader(email: EmailMessageForFormatting): string {
+ let fromName = 'Unknown Sender';
+ let fromEmail = '';
- // Extract from
- if (typeof email.from === 'string') {
- from = email.from;
- } else if (Array.isArray(email.from)) {
- from = email.from.map((addr: EmailAddress) =>
- addr.name ? `${addr.name} <${addr.address}>` : addr.address
- ).join(', ');
- } else if (email.fromName || email.from) {
- from = email.fromName && email.fromName !== email.from
- ? `${email.fromName} <${email.from}>`
- : email.from;
+ // 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;
+ }
}
- // Extract to
- if (typeof email.to === 'string') {
- to = email.to;
- } else if (Array.isArray(email.to)) {
- to = email.to.map((addr: EmailAddress) =>
- addr.name ? `${addr.name} <${addr.address}>` : addr.address
- ).join(', ');
+ // 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
return `
-