courrier clean 2
This commit is contained in:
parent
f4a77ecd25
commit
10b08b5043
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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' }}
|
||||
/>
|
||||
|
||||
|
||||
@ -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} <${fromEmail}></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
|
||||
};
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user