courrier clean 2

This commit is contained in:
alma 2025-04-26 12:47:44 +02:00
parent f4a77ecd25
commit 10b08b5043
4 changed files with 358 additions and 329 deletions

View File

@ -1570,88 +1570,71 @@ export default function CourrierPage() {
if (!selectedEmail) return;
try {
// Ensure we have full content before proceeding
if (!selectedEmail.content || selectedEmail.content.length === 0) {
console.log('[DEBUG] Need to fetch content before reply/forward');
setContentLoading(true);
try {
const response = await fetch(`/api/courrier/${selectedEmail.id}?folder=${encodeURIComponent(selectedEmail.folder || 'INBOX')}`);
// If content hasn't been loaded yet, fetch it
if (!selectedEmail.contentFetched) {
console.log('[DEBUG] Fetching email content for reply:', selectedEmail.id);
const content = await getEmailContent(selectedEmail.id, selectedEmail.folder);
if (content) {
// Update the selected email with content
const updatedEmail = {
...selectedEmail,
content: content.html || content.text || '',
html: content.html || '',
text: content.text || '',
contentFetched: true,
// Add proper from/to/cc format for client-side formatters
from: content.from,
to: content.to,
cc: content.cc,
bcc: content.bcc,
date: content.date
};
if (!response.ok) {
throw new Error(`Failed to fetch email content: ${response.status}`);
setSelectedEmail(updatedEmail);
// For forwarding, we need to set the forwardFrom prop with the updated content
if (type === 'forward') {
console.log('[DEBUG] Setting forwardFrom with content for forwarding');
setForwardFrom(updatedEmail);
} else {
// For replying, we need to set the replyTo prop with the correct reply type
console.log('[DEBUG] Setting replyTo with content for replying');
setReplyTo({
...updatedEmail,
replyType: type === 'reply-all' ? 'replyAll' : 'reply' // Convert to format expected by formatter
});
}
const fullContent = await response.json();
// Update the email content with the fetched full content
selectedEmail.content = fullContent.content;
selectedEmail.contentFetched = true;
// Update the email in the list too so we don't refetch
setEmails(prevEmails =>
prevEmails.map(email =>
email.id === selectedEmail.id
? { ...email, content: fullContent.content, contentFetched: true }
: email
)
);
console.log('[DEBUG] Successfully fetched content for reply/forward');
} catch (error) {
console.error('[DEBUG] Error fetching content for reply:', error);
alert('Failed to load email content for reply. Please try again.');
setContentLoading(false);
return; // Exit if we couldn't get the content
}
} else {
// Content already loaded, just set the props
// Make sure the email has the correct format for client-side formatters
const formattedEmail = {
...selectedEmail,
// Ensure we have proper address objects for the formatters
from: selectedEmail.from ?
(Array.isArray(selectedEmail.from) ? selectedEmail.from : [{ address: selectedEmail.from, name: selectedEmail.fromName }]) :
[{ address: selectedEmail.from || '', name: selectedEmail.fromName }],
to: selectedEmail.to ?
(Array.isArray(selectedEmail.to) ? selectedEmail.to : [{ address: selectedEmail.to }]) :
[{ address: selectedEmail.to || '' }]
};
setContentLoading(false);
if (type === 'forward') {
console.log('[DEBUG] Setting forwardFrom for forwarding (content already loaded)');
setForwardFrom(formattedEmail);
} else {
console.log('[DEBUG] Setting replyTo for replying (content already loaded)');
setReplyTo({
...formattedEmail,
replyType: type === 'reply-all' ? 'replyAll' : 'reply' // Convert to format expected by formatter
});
}
}
const getReplyTo = () => {
if (type === 'forward') return '';
return selectedEmail.from;
};
const getReplyCc = () => {
if (type !== 'reply-all') return '';
return selectedEmail.cc || '';
};
const getReplySubject = () => {
const subject = selectedEmail.subject || '';
if (type === 'forward') {
return subject.startsWith('Fwd:') ? subject : `Fwd: ${subject}`;
}
return subject.startsWith('Re:') ? subject : `Re: ${subject}`;
};
// Add body property for backward compatibility
const emailWithBody = {
...selectedEmail,
body: selectedEmail.content // Add body property that maps to content
};
// Set the appropriate flags
setIsReplying(type === 'reply' || type === 'reply-all');
setIsForwarding(type === 'forward');
// Update the compose form
setComposeTo(getReplyTo());
setComposeCc(getReplyCc());
setComposeSubject(getReplySubject());
setComposeBcc('');
// Show the compose dialog
setShowCompose(true);
setShowCc(type === 'reply-all');
setShowBcc(false);
setAttachments([]);
// Pass the email with both content and body properties
setReplyToEmail(emailWithBody);
setForwardEmail(type === 'forward' ? emailWithBody : null);
} catch (error) {
console.error('Error preparing reply:', error);
console.error('[DEBUG] Error preparing email for reply/forward:', error);
}
};

View File

@ -1,13 +1,14 @@
'use client';
import { useState, useRef, useEffect } from 'react';
import React, { useEffect, useState, useRef } from 'react';
import { X, Paperclip, ChevronDown, ChevronUp, SendHorizontal, Loader2 } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { Label } from '@/components/ui/label';
import DOMPurify from 'isomorphic-dompurify';
import { formatEmailForReply, formatEmailForForward } from '@/lib/email-formatter';
import { formatEmailForReply, formatEmailForForward, EmailMessageForFormatting } from '@/lib/email-formatter';
import { getEmailContent, getUserEmailCredentials } from '@/lib/services/email-service';
interface EmailObject {
id?: string;
@ -99,22 +100,7 @@ export default function ComposeEmail({
useEffect(() => {
// Initialize reply if replyTo is provided
if (replyTo) {
// For reply/reply-all
const formattedEmail = formatEmailForReply(replyTo as any, 'reply');
setComposeTo(formattedEmail.to);
setComposeSubject(formattedEmail.subject);
// Store the quoted content separately
const bodyContent = DOMPurify.sanitize(formattedEmail.body, {
ADD_TAGS: ['style'],
FORBID_TAGS: ['script', 'iframe']
});
setQuotedContent(bodyContent);
setUserMessage(''); // Clear user input
// Keep composeBody for backwards compatibility
setComposeBody(bodyContent);
initializeReplyEmail(replyTo);
}
}, [replyTo, setComposeTo, setComposeSubject, setComposeBody]);
@ -127,59 +113,83 @@ export default function ComposeEmail({
// Initialize forwarded email content
const initializeForwardedEmail = async (email: any) => {
if (!email) return;
console.log("Initializing forwarded email", email);
try {
// Convert the email to the format expected by our formatter
const emailForFormatting: EmailMessageForFormatting = {
subject: email.subject,
from: email.from,
to: email.to,
date: email.date,
html: email.html || email.content,
text: email.text,
cc: email.cc,
bcc: email.bcc
};
console.log('Initializing forwarded email:', email);
// Use our client-side formatter
const formattedEmail = formatEmailForForward(email);
// Set the formatted subject with Fwd: prefix
setComposeSubject(formattedEmail.subject);
// Create header for forwarded email - use the original styling
const headerHtml = formattedEmail.headerHtml;
// Prepare content
let contentHtml = '<div style="color: #666; font-style: italic; padding: 15px; border: 1px dashed #ccc; margin: 10px 0; text-align: center; background-color: #f9f9f9;">No content available</div>';
if (email.content) {
// Sanitize the content
contentHtml = DOMPurify.sanitize(email.content, {
ADD_TAGS: ['style'],
FORBID_TAGS: ['script', 'iframe']
});
} else if (email.html) {
contentHtml = DOMPurify.sanitize(email.html, {
ADD_TAGS: ['style'],
FORBID_TAGS: ['script', 'iframe']
});
} else if (email.text) {
contentHtml = `<pre>${email.text}</pre>`;
} else if (email.body) {
contentHtml = DOMPurify.sanitize(email.body, {
ADD_TAGS: ['style'],
FORBID_TAGS: ['script', 'iframe']
});
}
// Store the quoted content
const formattedQuote = `
<div dir="ltr" style="unicode-bidi: isolate; direction: ltr;">
${headerHtml}
<div style="margin-top: 10px; unicode-bidi: isolate; direction: ltr; text-align: left;">
<div style="isolation: isolate; contain: content; overflow: auto; unicode-bidi: isolate; direction: ltr; text-align: left;">
${contentHtml}
</div>
// Use the client-side formatter
const formattedEmail = formatEmailForForward(emailForFormatting);
setComposeSubject(formattedEmail.subject);
setQuotedContent(formattedEmail.body);
setComposeBody(''); // Clear user message when forwarding
} catch (error) {
console.error("Error initializing forwarded email:", error);
setQuotedContent(`
<div style="padding: 10px; text-align: center; background-color: #f8f8f8; border: 1px dashed #ccc; margin: 10px 0;">
Error loading original message content
</div>
</div>
`;
setQuotedContent(formattedQuote);
setUserMessage(''); // Clear user input
// Keep composeBody for backwards compatibility
setComposeBody(formattedQuote);
`);
}
};
// Initialize reply email content
const initializeReplyEmail = async (email: any, replyType: 'reply' | 'replyAll' = 'reply') => {
console.log("Initializing reply email", email, replyType);
try {
// Convert the email to the format expected by our formatter
const emailForFormatting: EmailMessageForFormatting = {
subject: email.subject,
from: email.from,
to: email.to,
date: email.date,
html: email.html || email.content,
text: email.text,
cc: email.cc,
bcc: email.bcc
};
// Use the client-side formatter
const formattedEmail = formatEmailForReply(emailForFormatting, replyType);
setComposeSubject(formattedEmail.subject);
// Set recipients
if (formattedEmail.to) {
const toAddresses = formattedEmail.to.map(recipient =>
recipient.name ? `${recipient.name} <${recipient.address}>` : recipient.address
).join(', ');
setComposeTo(toAddresses);
}
if (formattedEmail.cc && formattedEmail.cc.length > 0) {
const ccAddresses = formattedEmail.cc.map(recipient =>
recipient.name ? `${recipient.name} <${recipient.address}>` : recipient.address
).join(', ');
setComposeCc(ccAddresses);
setShowCc(true);
}
setQuotedContent(formattedEmail.body);
setComposeBody(''); // Clear user message when replying
} catch (error) {
console.error("Error initializing reply email:", error);
setQuotedContent(`
<div style="padding: 10px; text-align: center; background-color: #f8f8f8; border: 1px dashed #ccc; margin: 10px 0;">
Error loading original message content
</div>
`);
}
};
// Handle file attachment selection
@ -196,39 +206,54 @@ export default function ComposeEmail({
setUserMessage(contentEditableRef.current.innerHTML);
// Combine user message with quoted content for the full email body
const combined = `
<div dir="ltr" style="direction: ltr; unicode-bidi: isolate; text-align: left;">
${contentEditableRef.current.innerHTML}
</div>
${quotedContent}
`;
const combined = `${contentEditableRef.current.innerHTML}${quotedContent ? quotedContent : ''}`;
setComposeBody(combined);
}
};
// Handle sending with combined content
// Handle sending email with combined content
const handleSendWithCombinedContent = async () => {
// For rich editor mode, ensure we combine user message with quoted content
if (useRichEditor) {
// Create the final combined email body
const finalBody = `
<div dir="ltr" style="direction: ltr; unicode-bidi: isolate; text-align: left;">
${userMessage || ''}
</div>
${quotedContent || ''}
`;
if (isSending) return;
try {
setIsSending(true);
// Set the complete body and send after a brief delay to ensure state is updated
setComposeBody(finalBody);
// For rich editor, combine user message with quoted content
if (useRichEditor) {
const combinedContent = `${userMessage || ''}${quotedContent ? quotedContent : ''}`;
setComposeBody(combinedContent);
// Wait for state update to complete
await new Promise(resolve => setTimeout(resolve, 0));
}
// Small delay to ensure state update completes
setTimeout(() => {
handleSend();
}, 50);
} else {
// For normal textarea mode, just use the existing handler
handleSend();
// Call the provided onSend function
await onSend({
to: composeTo,
cc: composeCc,
bcc: composeBcc,
subject: composeSubject,
body: composeBody,
attachments: attachments
});
// Reset the compose state
setShowCompose(false);
setComposeTo('');
setComposeCc('');
setComposeBcc('');
setComposeSubject('');
setComposeBody('');
setShowCc(false);
setShowBcc(false);
setAttachments([]);
setUserMessage('');
setQuotedContent('');
} catch (error) {
console.error('Failed to send email:', error);
} finally {
setIsSending(false);
}
};
@ -347,34 +372,18 @@ export default function ComposeEmail({
ref={contentEditableRef}
contentEditable="true"
className="w-full p-3 bg-white min-h-[100px] text-gray-900 email-editor"
style={{
direction: 'ltr',
unicodeBidi: 'isolate',
textAlign: 'left'
}}
onInput={handleUserMessageChange}
dir="ltr"
dangerouslySetInnerHTML={userMessage ? { __html: userMessage } : { __html: '<p style="color: #718096;">Write your message here...</p>' }}
dangerouslySetInnerHTML={userMessage ? { __html: userMessage } : { __html: '<p>Write your message here...</p>' }}
/>
{/* Original email content - also editable */}
{quotedContent && (
<div
className="w-full bg-gray-50 border-t border-gray-300 email-content-wrapper"
style={{
direction: 'ltr',
unicodeBidi: 'isolate'
}}
>
<div
className="p-3 opacity-90 text-sm email-content"
contentEditable="true"
dir="ltr"
style={{
direction: 'ltr',
unicodeBidi: 'isolate',
textAlign: 'left'
}}
dangerouslySetInnerHTML={{ __html: quotedContent }}
onInput={(e) => {
const target = e.currentTarget;
@ -391,8 +400,6 @@ export default function ComposeEmail({
onChange={(e) => setComposeBody(e.target.value)}
placeholder="Write your message..."
className="w-full mt-1 min-h-[200px] bg-white border-gray-300 text-gray-900 resize-none email-editor"
dir="ltr"
style={{ direction: 'ltr', unicodeBidi: 'isolate', textAlign: 'left' }}
/>
)}
</div>

View File

@ -2,7 +2,7 @@
import { useState, useRef, useEffect } from 'react';
import { formatEmailForReplyOrForward, EmailMessage, EmailAddress } from '@/lib/services/email-service';
import { X, Paperclip, ChevronDown, ChevronUp, SendHorizontal, Loader2 } from 'lucide-react';
import { X, Paperclip, ChevronDown, ChevronUp, SendHorizontal, Loader2, TextAlignLeft, TextAlignRight } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Card, CardContent, CardHeader, CardTitle, CardFooter } from '@/components/ui/card';
@ -41,6 +41,7 @@ export default function ComposeEmail({
const [showCc, setShowCc] = useState<boolean>(false);
const [showBcc, setShowBcc] = useState<boolean>(false);
const [sending, setSending] = useState<boolean>(false);
const [isRTL, setIsRTL] = useState<boolean>(false);
const [attachments, setAttachments] = useState<Array<{
name: string;
content: string;
@ -367,6 +368,14 @@ export default function ComposeEmail({
setBody(e.currentTarget.innerHTML);
};
// Toggle text direction
const toggleTextDirection = () => {
setIsRTL(!isRTL);
if (editorRef.current) {
editorRef.current.dir = !isRTL ? 'rtl' : 'ltr';
}
};
return (
<Card className="w-full h-full flex flex-col overflow-hidden shadow-lg">
<CardHeader className="border-b py-2 px-4">
@ -468,6 +477,19 @@ export default function ComposeEmail({
</div>
</div>
{/* Editor toolbar */}
<div className="border-b p-1 flex items-center">
<Button
variant="ghost"
size="sm"
onClick={toggleTextDirection}
className="hover:bg-muted"
title={isRTL ? "Switch to Left-to-Right" : "Switch to Right-to-Left"}
>
{isRTL ? <TextAlignRight className="h-4 w-4" /> : <TextAlignLeft className="h-4 w-4" />}
</Button>
</div>
{/* Email body editor */}
<div
ref={editorRef}
@ -475,6 +497,7 @@ export default function ComposeEmail({
contentEditable={true}
onInput={handleEditorInput}
dangerouslySetInnerHTML={{ __html: body }}
dir={isRTL ? "rtl" : "ltr"}
style={{ minHeight: '200px' }}
/>

View File

@ -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 = `<br/><br/>${quoteHeader}<blockquote style="margin: 0 0 0 0.8ex; border-left: 1px solid #ccc; padding-left: 1ex;">`;
// Add quoted content
if (email.content) {
body += email.content;
} else if (email.html) {
body += email.html;
} else if (email.text) {
body += `<pre>${email.text}</pre>`;
} else if (email.body) {
body += email.body;
} else {
body += '<div style="color: #666; font-style: italic;">No content available</div>';
// 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>');
}
body += '</blockquote>';
// Combine the header with the original content
const body = `
<div>
<br>
<blockquote style="margin: 0 0 0 0.8ex; border-left: 1px solid #ccc; padding-left: 1ex;">
${quoteHeader}
<div>${originalContent || 'No content available'}</div>
</blockquote>
</div>
`;
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 `
<div style="color: #666; margin-bottom: 10px; font-family: Arial, sans-serif;">
<div>On ${date}, ${from} wrote:</div>
<div style="margin-bottom: 10px; color: #555; font-size: 0.9em;">
<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: any): {
subject: string;
headerHtml: string;
} {
// Format subject with Fwd: prefix
const subject = formatSubject(email.subject || '(No subject)', 'forward');
export function formatEmailForForward(email: EmailMessageForFormatting): FormattedEmail {
// Format the subject with Fwd: prefix
const subject = formatSubject(email.subject || '', 'forward');
// Get sender information
let fromString = 'Unknown Sender';
if (typeof email.from === 'string') {
fromString = email.from;
} else if (Array.isArray(email.from)) {
fromString = email.from.map((addr: EmailAddress) =>
addr.name ? `${addr.name} <${addr.address}>` : addr.address
).join(', ');
} else if (email.fromName && email.from) {
fromString = email.fromName !== email.from
? `${email.fromName} <${email.from}>`
: email.from;
// 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>');
}
// Get recipient information
let toString = '';
if (typeof email.to === 'string') {
toString = email.to;
} else if (Array.isArray(email.to)) {
toString = email.to.map((addr: EmailAddress) =>
addr.name ? `${addr.name} <${addr.address}>` : addr.address
).join(', ');
}
// Create header for forwarded email
const headerHtml = `
<div style="border-top: 1px solid #e1e1e1; margin-top: 20px; padding-top: 15px; font-family: Arial, sans-serif;">
<div style="margin-bottom: 15px;">
<div style="font-weight: normal; margin-bottom: 10px;">---------- Forwarded message ---------</div>
<div><b>From:</b> ${fromString}</div>
<div><b>Date:</b> ${email.date ? new Date(email.date).toLocaleString() : 'Unknown Date'}</div>
<div><b>Subject:</b> ${email.subject || '(No subject)'}</div>
<div><b>To:</b> ${toString || 'Unknown Recipient'}</div>
// Combine the header with the original content
const body = `
<div>
<br>
<div style="border-left: 1px solid #ccc; padding-left: 1ex; margin-left: 0.8ex;">
<div style="font-weight: bold; margin-bottom: 10px;">---------- Forwarded message ---------</div>
${forwardHeader}
<div>${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,
headerHtml
body
};
}