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; if (!selectedEmail) return;
try { try {
// Ensure we have full content before proceeding // If content hasn't been loaded yet, fetch it
if (!selectedEmail.content || selectedEmail.content.length === 0) { if (!selectedEmail.contentFetched) {
console.log('[DEBUG] Need to fetch content before reply/forward'); console.log('[DEBUG] Fetching email content for reply:', selectedEmail.id);
setContentLoading(true); const content = await getEmailContent(selectedEmail.id, selectedEmail.folder);
if (content) {
try { // Update the selected email with content
const response = await fetch(`/api/courrier/${selectedEmail.id}?folder=${encodeURIComponent(selectedEmail.folder || 'INBOX')}`); 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) { setSelectedEmail(updatedEmail);
throw new Error(`Failed to fetch email content: ${response.status}`);
// 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 = () => { // Show the compose dialog
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('');
setShowCompose(true); 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) { } 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'; '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 { X, Paperclip, ChevronDown, ChevronUp, SendHorizontal, Loader2 } from 'lucide-react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea'; import { Textarea } from '@/components/ui/textarea';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import DOMPurify from 'isomorphic-dompurify'; 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 { interface EmailObject {
id?: string; id?: string;
@ -99,22 +100,7 @@ export default function ComposeEmail({
useEffect(() => { useEffect(() => {
// Initialize reply if replyTo is provided // Initialize reply if replyTo is provided
if (replyTo) { if (replyTo) {
// For reply/reply-all initializeReplyEmail(replyTo);
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);
} }
}, [replyTo, setComposeTo, setComposeSubject, setComposeBody]); }, [replyTo, setComposeTo, setComposeSubject, setComposeBody]);
@ -127,59 +113,83 @@ export default function ComposeEmail({
// Initialize forwarded email content // Initialize forwarded email content
const initializeForwardedEmail = async (email: any) => { 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 the client-side formatter
const formattedEmail = formatEmailForForward(emailForFormatting);
// Use our client-side formatter
const formattedEmail = formatEmailForForward(email); setComposeSubject(formattedEmail.subject);
setQuotedContent(formattedEmail.body);
// Set the formatted subject with Fwd: prefix setComposeBody(''); // Clear user message when forwarding
setComposeSubject(formattedEmail.subject); } catch (error) {
console.error("Error initializing forwarded email:", error);
// Create header for forwarded email - use the original styling setQuotedContent(`
const headerHtml = formattedEmail.headerHtml; <div style="padding: 10px; text-align: center; background-color: #f8f8f8; border: 1px dashed #ccc; margin: 10px 0;">
Error loading original message content
// 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>
</div> </div>
</div> `);
`; }
};
setQuotedContent(formattedQuote);
setUserMessage(''); // Clear user input // Initialize reply email content
const initializeReplyEmail = async (email: any, replyType: 'reply' | 'replyAll' = 'reply') => {
// Keep composeBody for backwards compatibility console.log("Initializing reply email", email, replyType);
setComposeBody(formattedQuote); 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 // Handle file attachment selection
@ -196,39 +206,54 @@ export default function ComposeEmail({
setUserMessage(contentEditableRef.current.innerHTML); setUserMessage(contentEditableRef.current.innerHTML);
// Combine user message with quoted content for the full email body // Combine user message with quoted content for the full email body
const combined = ` const combined = `${contentEditableRef.current.innerHTML}${quotedContent ? quotedContent : ''}`;
<div dir="ltr" style="direction: ltr; unicode-bidi: isolate; text-align: left;">
${contentEditableRef.current.innerHTML}
</div>
${quotedContent}
`;
setComposeBody(combined); setComposeBody(combined);
} }
}; };
// Handle sending with combined content // Handle sending email with combined content
const handleSendWithCombinedContent = async () => { const handleSendWithCombinedContent = async () => {
// For rich editor mode, ensure we combine user message with quoted content if (isSending) return;
if (useRichEditor) {
// Create the final combined email body try {
const finalBody = ` setIsSending(true);
<div dir="ltr" style="direction: ltr; unicode-bidi: isolate; text-align: left;">
${userMessage || ''}
</div>
${quotedContent || ''}
`;
// Set the complete body and send after a brief delay to ensure state is updated // For rich editor, combine user message with quoted content
setComposeBody(finalBody); 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 // Call the provided onSend function
setTimeout(() => { await onSend({
handleSend(); to: composeTo,
}, 50); cc: composeCc,
} else { bcc: composeBcc,
// For normal textarea mode, just use the existing handler subject: composeSubject,
handleSend(); 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} ref={contentEditableRef}
contentEditable="true" contentEditable="true"
className="w-full p-3 bg-white min-h-[100px] text-gray-900 email-editor" className="w-full p-3 bg-white min-h-[100px] text-gray-900 email-editor"
style={{
direction: 'ltr',
unicodeBidi: 'isolate',
textAlign: 'left'
}}
onInput={handleUserMessageChange} onInput={handleUserMessageChange}
dir="ltr" dangerouslySetInnerHTML={userMessage ? { __html: userMessage } : { __html: '<p>Write your message here...</p>' }}
dangerouslySetInnerHTML={userMessage ? { __html: userMessage } : { __html: '<p style="color: #718096;">Write your message here...</p>' }}
/> />
{/* Original email content - also editable */} {/* Original email content - also editable */}
{quotedContent && ( {quotedContent && (
<div <div
className="w-full bg-gray-50 border-t border-gray-300 email-content-wrapper" className="w-full bg-gray-50 border-t border-gray-300 email-content-wrapper"
style={{
direction: 'ltr',
unicodeBidi: 'isolate'
}}
> >
<div <div
className="p-3 opacity-90 text-sm email-content" className="p-3 opacity-90 text-sm email-content"
contentEditable="true" contentEditable="true"
dir="ltr"
style={{
direction: 'ltr',
unicodeBidi: 'isolate',
textAlign: 'left'
}}
dangerouslySetInnerHTML={{ __html: quotedContent }} dangerouslySetInnerHTML={{ __html: quotedContent }}
onInput={(e) => { onInput={(e) => {
const target = e.currentTarget; const target = e.currentTarget;
@ -391,8 +400,6 @@ export default function ComposeEmail({
onChange={(e) => setComposeBody(e.target.value)} onChange={(e) => setComposeBody(e.target.value)}
placeholder="Write your message..." placeholder="Write your message..."
className="w-full mt-1 min-h-[200px] bg-white border-gray-300 text-gray-900 resize-none email-editor" 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> </div>

View File

@ -2,7 +2,7 @@
import { useState, useRef, useEffect } from 'react'; import { useState, useRef, useEffect } from 'react';
import { formatEmailForReplyOrForward, EmailMessage, EmailAddress } from '@/lib/services/email-service'; 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 { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Card, CardContent, CardHeader, CardTitle, CardFooter } from '@/components/ui/card'; 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 [showCc, setShowCc] = useState<boolean>(false);
const [showBcc, setShowBcc] = useState<boolean>(false); const [showBcc, setShowBcc] = useState<boolean>(false);
const [sending, setSending] = useState<boolean>(false); const [sending, setSending] = useState<boolean>(false);
const [isRTL, setIsRTL] = useState<boolean>(false);
const [attachments, setAttachments] = useState<Array<{ const [attachments, setAttachments] = useState<Array<{
name: string; name: string;
content: string; content: string;
@ -367,6 +368,14 @@ export default function ComposeEmail({
setBody(e.currentTarget.innerHTML); setBody(e.currentTarget.innerHTML);
}; };
// Toggle text direction
const toggleTextDirection = () => {
setIsRTL(!isRTL);
if (editorRef.current) {
editorRef.current.dir = !isRTL ? 'rtl' : 'ltr';
}
};
return ( return (
<Card className="w-full h-full flex flex-col overflow-hidden shadow-lg"> <Card className="w-full h-full flex flex-col overflow-hidden shadow-lg">
<CardHeader className="border-b py-2 px-4"> <CardHeader className="border-b py-2 px-4">
@ -468,6 +477,19 @@ export default function ComposeEmail({
</div> </div>
</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 */} {/* Email body editor */}
<div <div
ref={editorRef} ref={editorRef}
@ -475,6 +497,7 @@ export default function ComposeEmail({
contentEditable={true} contentEditable={true}
onInput={handleEditorInput} onInput={handleEditorInput}
dangerouslySetInnerHTML={{ __html: body }} dangerouslySetInnerHTML={{ __html: body }}
dir={isRTL ? "rtl" : "ltr"}
style={{ minHeight: '200px' }} style={{ minHeight: '200px' }}
/> />

View File

@ -1,209 +1,225 @@
'use client'; 'use client';
import DOMPurify from 'dompurify';
/** /**
* Client-side utilities for formatting email content * Client-side utilities for formatting email content
* This file contains functions for formatting email content in the browser * This file contains functions for formatting email content in the browser
* without any server dependencies. * without any server dependencies.
*/ */
interface EmailAddress { export interface EmailAddress {
name?: string;
address: string; address: string;
name?: string;
} }
interface FormattedEmail { export interface FormattedEmail {
to: string;
cc?: string;
subject: string; subject: string;
to?: EmailAddress[];
cc?: EmailAddress[];
bcc?: EmailAddress[];
body: string; 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 * Format an email for replying or forwarding
* Client-side friendly version that doesn't depend on server modules * Client-side friendly version that doesn't depend on server modules
*/ */
export function formatEmailForReply( export function formatEmailForReply(
email: any, originalEmail: EmailMessageForFormatting,
type: 'reply' | 'reply-all' = 'reply' type: 'reply' | 'replyAll' | 'forward' = 'reply'
): FormattedEmail { ): FormattedEmail {
// Format the subject with Re: prefix // Format the subject with Re: or Fwd: prefix
const subject = formatSubject(email.subject || '(No subject)', type); const subject = formatSubject(originalEmail.subject || '', type);
// Format recipients // Initialize recipients based on reply type
let to = ''; let to: EmailAddress[] = [];
let cc = ''; let cc: EmailAddress[] = [];
// Process 'to' field for reply if (type === 'reply' && originalEmail.from) {
if (typeof email.from === 'string') { to = Array.isArray(originalEmail.from) ? originalEmail.from : [originalEmail.from];
to = email.from; } else if (type === 'replyAll') {
} else if (Array.isArray(email.from)) { // To: original sender
to = email.from.map((addr: EmailAddress) => if (originalEmail.from) {
addr.name && addr.name !== addr.address to = Array.isArray(originalEmail.from) ? originalEmail.from : [originalEmail.from];
? `${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(', ');
} }
// Include cc recipients from original email too if available // CC: all other recipients
if (email.cc) { if (originalEmail.to) {
const ccList = typeof email.cc === 'string' cc = Array.isArray(originalEmail.to) ? originalEmail.to : [originalEmail.to];
? email.cc
: Array.isArray(email.cc)
? email.cc.map((addr: EmailAddress) => addr.address).join(', ')
: '';
if (ccList) {
cc = cc ? `${cc}, ${ccList}` : ccList;
}
} }
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 // Create the quoted content with header
const quoteHeader = createQuoteHeader(email); const quoteHeader = createQuoteHeader(originalEmail);
// Format body with quote // Get the original content, preferring HTML over plain text
let body = `<br/><br/>${quoteHeader}<blockquote style="margin: 0 0 0 0.8ex; border-left: 1px solid #ccc; padding-left: 1ex;">`; let originalContent = '';
if (originalEmail.html) {
// Add quoted content // Sanitize any potentially unsafe HTML
if (email.content) { originalContent = DOMPurify.sanitize(originalEmail.html);
body += email.content; } else if (originalEmail.text) {
} else if (email.html) { // Convert text to HTML by replacing newlines with br tags
body += email.html; originalContent = originalEmail.text.replace(/\n/g, '<br>');
} 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>';
} }
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 { return {
subject,
to, to,
cc, cc,
subject,
body body
}; };
} }
function formatSubject(subject: string, type: 'reply' | 'reply-all' | 'forward'): string { /**
// Clean up existing prefixes first * Format email subject with appropriate prefix
let cleanSubject = subject.replace(/^(Re|Fwd|FW|Forward):\s*/gi, ''); */
cleanSubject = cleanSubject.trim() || '(No subject)'; 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') { if (type === 'forward') {
return `Fwd: ${cleanSubject}`; return `Fwd: ${subject}`;
} else { } else {
// For reply and reply-all return `Re: ${subject}`;
return `Re: ${cleanSubject}`;
} }
} }
function createQuoteHeader(email: any): string { /**
let from = 'Unknown Sender'; * Create a formatted quote header with sender and date information
let date = email.date ? new Date(email.date).toLocaleString() : 'Unknown Date'; */
let subject = email.subject || '(No subject)'; export function createQuoteHeader(email: EmailMessageForFormatting): string {
let to = ''; let fromName = 'Unknown Sender';
let fromEmail = '';
// Extract from // Extract sender information
if (typeof email.from === 'string') { if (email.from) {
from = email.from; if (Array.isArray(email.from)) {
} else if (Array.isArray(email.from)) { fromName = email.from[0].name || email.from[0].address;
from = email.from.map((addr: EmailAddress) => fromEmail = email.from[0].address;
addr.name ? `${addr.name} <${addr.address}>` : addr.address } else {
).join(', '); fromName = email.from.name || email.from.address;
} else if (email.fromName || email.from) { fromEmail = email.from.address;
from = email.fromName && email.fromName !== email.from }
? `${email.fromName} <${email.from}>`
: email.from;
} }
// Extract to // Format the date
if (typeof email.to === 'string') { let dateFormatted = '';
to = email.to; if (email.date) {
} else if (Array.isArray(email.to)) { const date = typeof email.date === 'string' ? new Date(email.date) : email.date;
to = email.to.map((addr: EmailAddress) =>
addr.name ? `${addr.name} <${addr.address}>` : addr.address // Check if the date is valid
).join(', '); 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 ` return `
<div style="color: #666; margin-bottom: 10px; font-family: Arial, sans-serif;"> <div style="margin-bottom: 10px; color: #555; font-size: 0.9em;">
<div>On ${date}, ${from} wrote:</div> <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> </div>
<hr style="border: none; border-top: 1px solid #ddd; margin: 10px 0;">
`; `;
} }
/** /**
* Format an email for forwarding * Format an email for forwarding
*/ */
export function formatEmailForForward(email: any): { export function formatEmailForForward(email: EmailMessageForFormatting): FormattedEmail {
subject: string; // Format the subject with Fwd: prefix
headerHtml: string; const subject = formatSubject(email.subject || '', 'forward');
} {
// Format subject with Fwd: prefix
const subject = formatSubject(email.subject || '(No subject)', 'forward');
// Get sender information // Create the forward header
let fromString = 'Unknown Sender'; const forwardHeader = createQuoteHeader(email);
if (typeof email.from === 'string') {
fromString = email.from; // Get the original content, preferring HTML over plain text
} else if (Array.isArray(email.from)) { let originalContent = '';
fromString = email.from.map((addr: EmailAddress) => if (email.html) {
addr.name ? `${addr.name} <${addr.address}>` : addr.address // Sanitize any potentially unsafe HTML
).join(', '); originalContent = DOMPurify.sanitize(email.html);
} else if (email.fromName && email.from) { } else if (email.text) {
fromString = email.fromName !== email.from // Convert text to HTML by replacing newlines with br tags
? `${email.fromName} <${email.from}>` originalContent = email.text.replace(/\n/g, '<br>');
: email.from;
} }
// Get recipient information // Combine the header with the original content
let toString = ''; const body = `
if (typeof email.to === 'string') { <div>
toString = email.to; <br>
} else if (Array.isArray(email.to)) { <div style="border-left: 1px solid #ccc; padding-left: 1ex; margin-left: 0.8ex;">
toString = email.to.map((addr: EmailAddress) => <div style="font-weight: bold; margin-bottom: 10px;">---------- Forwarded message ---------</div>
addr.name ? `${addr.name} <${addr.address}>` : addr.address ${forwardHeader}
).join(', '); <div>${originalContent || '<div style="padding: 10px; text-align: center; background-color: #f8f8f8; border: 1px dashed #ccc; margin: 10px 0;">No content available</div>'}</div>
}
// 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>
</div> </div>
</div> </div>
`; `;
return { return {
subject, subject,
headerHtml body
}; };
} }