893 lines
28 KiB
TypeScript
893 lines
28 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useRef, useEffect } from 'react';
|
|
// Remove direct import of server components
|
|
import {
|
|
X, Paperclip, ChevronDown, ChevronUp, SendHorizontal, Loader2,
|
|
AlignLeft, AlignRight
|
|
} 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';
|
|
import DOMPurify from 'isomorphic-dompurify';
|
|
|
|
// Define EmailMessage interface locally instead of importing from server-only file
|
|
interface EmailAddress {
|
|
name: string;
|
|
address: string;
|
|
}
|
|
|
|
interface EmailMessage {
|
|
id: string;
|
|
messageId?: string;
|
|
subject: string;
|
|
from: EmailAddress[];
|
|
to: EmailAddress[];
|
|
cc?: EmailAddress[];
|
|
bcc?: EmailAddress[];
|
|
date: Date | string;
|
|
flags?: {
|
|
seen: boolean;
|
|
flagged: boolean;
|
|
answered: boolean;
|
|
deleted: boolean;
|
|
draft: boolean;
|
|
};
|
|
preview?: string;
|
|
content?: string;
|
|
html?: string;
|
|
text?: string;
|
|
hasAttachments?: boolean;
|
|
attachments?: any[];
|
|
folder?: string;
|
|
size?: number;
|
|
contentFetched?: boolean;
|
|
}
|
|
|
|
// Simplified formatEmailForReplyOrForward that doesn't rely on server code
|
|
function formatEmailForReplyOrForward(
|
|
email: EmailMessage,
|
|
type: 'reply' | 'reply-all' | 'forward'
|
|
): {
|
|
to: string;
|
|
cc?: string;
|
|
subject: string;
|
|
body: string;
|
|
} {
|
|
// Format subject
|
|
let subject = email.subject || '';
|
|
if (type === 'reply' || type === 'reply-all') {
|
|
if (!subject.startsWith('Re:')) {
|
|
subject = `Re: ${subject}`;
|
|
}
|
|
} else if (type === 'forward') {
|
|
if (!subject.startsWith('Fwd:')) {
|
|
subject = `Fwd: ${subject}`;
|
|
}
|
|
}
|
|
|
|
// Create quote header
|
|
const date = typeof email.date === 'string'
|
|
? new Date(email.date)
|
|
: email.date;
|
|
|
|
const formattedDate = date.toLocaleString('en-US', {
|
|
weekday: 'short',
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
});
|
|
|
|
const sender = email.from[0];
|
|
const fromText = sender?.name
|
|
? `${sender.name} <${sender.address}>`
|
|
: sender?.address || 'Unknown sender';
|
|
|
|
const quoteHeader = `<div style="font-weight: 500;">On ${formattedDate}, ${fromText} wrote:</div>`;
|
|
|
|
// Format content
|
|
const quotedContent = email.html || email.content || email.text || '';
|
|
|
|
// Format recipients
|
|
let to = '';
|
|
let cc = '';
|
|
|
|
if (type === 'reply') {
|
|
// Reply to sender only
|
|
to = email.from.map(addr => `${addr.name} <${addr.address}>`).join(', ');
|
|
} else if (type === 'reply-all') {
|
|
// Reply to sender and all recipients
|
|
to = email.from.map(addr => `${addr.name} <${addr.address}>`).join(', ');
|
|
|
|
// Add all original recipients to CC
|
|
const allRecipients = [
|
|
...(email.to || []),
|
|
...(email.cc || [])
|
|
];
|
|
|
|
cc = allRecipients
|
|
.map(addr => `${addr.name} <${addr.address}>`)
|
|
.join(', ');
|
|
} else if (type === 'forward') {
|
|
// Forward doesn't set recipients
|
|
to = '';
|
|
|
|
// Format forward differently
|
|
const formattedDate = typeof email.date === 'string'
|
|
? new Date(email.date).toLocaleString()
|
|
: email.date.toLocaleString();
|
|
|
|
const fromText = email.from.map(f => f.name ? `${f.name} <${f.address}>` : f.address).join(', ');
|
|
const toText = email.to.map(t => t.name ? `${t.name} <${t.address}>` : t.address).join(', ');
|
|
|
|
return {
|
|
to: '',
|
|
subject,
|
|
body: `
|
|
<div class="forwarded-message" style="margin-top: 20px;">
|
|
<div style="background-color: #f5f5f5; border-left: 3px solid #ddd; padding: 12px; margin-bottom: 15px; border-radius: 4px;">
|
|
<p style="margin: 0 0 8px 0; font-weight: 500; color: #555; font-size: 14px;">---------- Forwarded message ---------</p>
|
|
<p style="margin: 0 0 6px 0; font-size: 13px;"><span style="font-weight: 600; color: #444;">From:</span> ${fromText}</p>
|
|
<p style="margin: 0 0 6px 0; font-size: 13px;"><span style="font-weight: 600; color: #444;">Date:</span> ${formattedDate}</p>
|
|
<p style="margin: 0 0 6px 0; font-size: 13px;"><span style="font-weight: 600; color: #444;">Subject:</span> ${email.subject || ''}</p>
|
|
<p style="margin: 0 0 6px 0; font-size: 13px;"><span style="font-weight: 600; color: #444;">To:</span> ${toText}</p>
|
|
</div>
|
|
<div class="forwarded-content" style="border-left: 2px solid #ddd; padding-left: 15px; color: #333;">
|
|
${quotedContent ? quotedContent : '<p>No content available</p>'}
|
|
</div>
|
|
</div>
|
|
`
|
|
};
|
|
}
|
|
|
|
// Format body with improved styling for replies
|
|
const body = `
|
|
<div class="reply-body">
|
|
<br/>
|
|
<div class="quote-header" style="color: #555; font-size: 13px; margin: 20px 0 10px 0;">${quoteHeader}</div>
|
|
<blockquote style="margin: 0; padding: 10px 0 10px 15px; border-left: 3px solid #ddd; color: #555; background-color: #f8f8f8; border-radius: 4px;">
|
|
<div class="quoted-content" style="font-size: 13px;">
|
|
${quotedContent}
|
|
</div>
|
|
</blockquote>
|
|
</div>`;
|
|
|
|
return {
|
|
to,
|
|
cc: cc || undefined,
|
|
subject,
|
|
body
|
|
};
|
|
}
|
|
|
|
// Legacy interface for backward compatibility with old ComposeEmail component
|
|
interface LegacyComposeEmailProps {
|
|
showCompose: boolean;
|
|
setShowCompose: (show: boolean) => void;
|
|
composeTo: string;
|
|
setComposeTo: (to: string) => void;
|
|
composeCc: string;
|
|
setComposeCc: (cc: string) => void;
|
|
composeBcc: string;
|
|
setComposeBcc: (bcc: string) => void;
|
|
composeSubject: string;
|
|
setComposeSubject: (subject: string) => void;
|
|
composeBody: string;
|
|
setComposeBody: (body: string) => void;
|
|
showCc: boolean;
|
|
setShowCc: (show: boolean) => void;
|
|
showBcc: boolean;
|
|
setShowBcc: (show: boolean) => void;
|
|
attachments: any[];
|
|
setAttachments: (attachments: any[]) => void;
|
|
handleSend: () => Promise<void>;
|
|
originalEmail?: {
|
|
content: string;
|
|
type: 'reply' | 'reply-all' | 'forward';
|
|
};
|
|
onSend: (email: any) => Promise<void>;
|
|
onCancel: () => void;
|
|
replyTo?: any | null;
|
|
forwardFrom?: any | null;
|
|
}
|
|
|
|
// New interface for the modern ComposeEmail component
|
|
interface ComposeEmailProps {
|
|
initialEmail?: EmailMessage | null;
|
|
type?: 'new' | 'reply' | 'reply-all' | 'forward';
|
|
onClose: () => void;
|
|
onSend: (emailData: {
|
|
to: string;
|
|
cc?: string;
|
|
bcc?: string;
|
|
subject: string;
|
|
body: string;
|
|
attachments?: Array<{
|
|
name: string;
|
|
content: string;
|
|
type: string;
|
|
}>;
|
|
}) => Promise<void>;
|
|
}
|
|
|
|
// Union type to handle both new and legacy props
|
|
type ComposeEmailAllProps = ComposeEmailProps | LegacyComposeEmailProps;
|
|
|
|
// Type guard to check if props are legacy
|
|
function isLegacyProps(props: ComposeEmailAllProps): props is LegacyComposeEmailProps {
|
|
return 'showCompose' in props && 'setShowCompose' in props;
|
|
}
|
|
|
|
export default function ComposeEmail(props: ComposeEmailAllProps) {
|
|
// Handle legacy props by adapting them to new component
|
|
if (isLegacyProps(props)) {
|
|
return <LegacyAdapter {...props} />;
|
|
}
|
|
|
|
// Continue with modern implementation for new props
|
|
const { initialEmail, type = 'new', onClose, onSend } = props;
|
|
|
|
// Email form state
|
|
const [to, setTo] = useState<string>('');
|
|
const [cc, setCc] = useState<string>('');
|
|
const [bcc, setBcc] = useState<string>('');
|
|
const [subject, setSubject] = useState<string>('');
|
|
const [body, setBody] = useState<string>('');
|
|
const [userMessage, setUserMessage] = useState<string>('');
|
|
const [originalContent, setOriginalContent] = useState<string>('');
|
|
const [editingOriginalContent, setEditingOriginalContent] = useState<boolean>(false);
|
|
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;
|
|
type: string;
|
|
}>>([]);
|
|
|
|
// Refs
|
|
const editorRef = useRef<HTMLDivElement>(null);
|
|
const originalContentRef = useRef<HTMLDivElement>(null);
|
|
const attachmentInputRef = useRef<HTMLInputElement>(null);
|
|
|
|
// Initialize the form when replying to or forwarding an email
|
|
useEffect(() => {
|
|
if (initialEmail && type !== 'new') {
|
|
if (type === 'forward') {
|
|
initializeForwardedEmail();
|
|
} else {
|
|
// For reply/reply-all, continue using formatEmailForReplyOrForward
|
|
const formattedEmail = formatEmailForReplyOrForward(initialEmail, type as 'reply' | 'reply-all');
|
|
|
|
setTo(formattedEmail.to);
|
|
|
|
if (formattedEmail.cc) {
|
|
setCc(formattedEmail.cc);
|
|
setShowCc(true);
|
|
}
|
|
|
|
setSubject(formattedEmail.subject);
|
|
|
|
// Make the entire content editable, just like with forwarded emails
|
|
const bodyContent = formattedEmail.body;
|
|
setUserMessage(bodyContent);
|
|
setBody(bodyContent);
|
|
}
|
|
|
|
// Focus editor after initializing
|
|
setTimeout(() => {
|
|
if (editorRef.current) {
|
|
editorRef.current.focus();
|
|
|
|
// Place cursor at the beginning of the content
|
|
const selection = window.getSelection();
|
|
const range = document.createRange();
|
|
|
|
range.setStart(editorRef.current, 0);
|
|
range.collapse(true);
|
|
|
|
selection?.removeAllRanges();
|
|
selection?.addRange(range);
|
|
}
|
|
}, 100);
|
|
}
|
|
}, [initialEmail, type]);
|
|
|
|
// Format date for the forwarded message header
|
|
const formatDate = (date: Date | null): string => {
|
|
if (!date) return '';
|
|
try {
|
|
return date.toLocaleString('en-US', {
|
|
weekday: 'short',
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
});
|
|
} catch (e) {
|
|
return date.toString();
|
|
}
|
|
};
|
|
|
|
// Format sender address in a readable format
|
|
const formatSender = (from: Array<{name?: string, address: string}> | undefined): string => {
|
|
if (!from || from.length === 0) return 'Unknown';
|
|
return from.map(sender =>
|
|
sender.name && sender.name !== sender.address
|
|
? `${sender.name} <${sender.address}>`
|
|
: sender.address
|
|
).join(', ');
|
|
};
|
|
|
|
// Format recipient addresses in a readable format
|
|
const formatRecipients = (recipients: Array<{name?: string, address: string}> | undefined): string => {
|
|
if (!recipients || recipients.length === 0) return '';
|
|
return recipients.map(recipient =>
|
|
recipient.name && recipient.name !== recipient.address
|
|
? `${recipient.name} <${recipient.address}>`
|
|
: recipient.address
|
|
).join(', ');
|
|
};
|
|
|
|
// Initialize forwarded email with clear structure and style preservation
|
|
const initializeForwardedEmail = async () => {
|
|
console.log('Starting initializeForwardedEmail');
|
|
if (!initialEmail) {
|
|
console.error('No email available for forwarding');
|
|
setBody('<div style="color: #666; font-style: italic;">No email available for forwarding</div>');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Format subject with Fwd: prefix if needed
|
|
const subjectBase = initialEmail.subject || '(No subject)';
|
|
const subjectRegex = /^(Fwd|FW|Forward):\s*/i;
|
|
const subject = subjectRegex.test(subjectBase)
|
|
? subjectBase
|
|
: `Fwd: ${subjectBase}`;
|
|
|
|
setSubject(subject);
|
|
|
|
// For forwarded emails, we'll use a completely different approach
|
|
// Just save the original HTML content and use it directly
|
|
// This preserves all formatting without trying to parse it
|
|
|
|
// Set message parts for the editor
|
|
const content = initialEmail.content || initialEmail.html || initialEmail.text || '';
|
|
setOriginalContent(content);
|
|
setUserMessage(''); // Start with empty user message
|
|
setBody(''); // Will be constructed when sending
|
|
|
|
// Log for debugging
|
|
console.log('Set originalContent:', content.substring(0, 100) + '...');
|
|
} catch (error) {
|
|
console.error('Error formatting forwarded email:', error);
|
|
setBody('<div style="color: #666; font-style: italic;">Error formatting forwarded email content</div>');
|
|
}
|
|
};
|
|
|
|
// Handle attachment selection
|
|
const handleAttachmentClick = () => {
|
|
attachmentInputRef.current?.click();
|
|
};
|
|
|
|
// Process selected files
|
|
const handleFileSelection = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const files = e.target.files;
|
|
if (!files || files.length === 0) return;
|
|
|
|
// Convert selected files to attachments
|
|
const newAttachments = Array.from(files).map(file => ({
|
|
file,
|
|
uploading: true
|
|
}));
|
|
|
|
// Read files as data URLs
|
|
for (const file of files) {
|
|
const reader = new FileReader();
|
|
|
|
reader.onload = (event) => {
|
|
const content = event.target?.result as string;
|
|
|
|
setAttachments(current => [
|
|
...current,
|
|
{
|
|
name: file.name,
|
|
content: content.split(',')[1], // Remove data:mime/type;base64, prefix
|
|
type: file.type
|
|
}
|
|
]);
|
|
};
|
|
|
|
reader.readAsDataURL(file);
|
|
}
|
|
|
|
// Reset file input
|
|
if (e.target) {
|
|
e.target.value = '';
|
|
}
|
|
};
|
|
|
|
// Remove attachment
|
|
const removeAttachment = (index: number) => {
|
|
setAttachments(current => current.filter((_, i) => i !== index));
|
|
};
|
|
|
|
// Handle editing of original content
|
|
const handleOriginalContentInput = (e: React.FormEvent<HTMLDivElement>) => {
|
|
if (originalContentRef.current) {
|
|
const content = originalContentRef.current.innerHTML;
|
|
setOriginalContent(content);
|
|
}
|
|
};
|
|
|
|
// Toggle original content editing
|
|
const toggleEditOriginalContent = () => {
|
|
setEditingOriginalContent(!editingOriginalContent);
|
|
|
|
// If we're starting to edit, make sure the content is ready and focused
|
|
if (!editingOriginalContent) {
|
|
setTimeout(() => {
|
|
if (originalContentRef.current) {
|
|
originalContentRef.current.focus();
|
|
|
|
// Place cursor at the beginning
|
|
const selection = window.getSelection();
|
|
const range = document.createRange();
|
|
|
|
if (originalContentRef.current.firstChild) {
|
|
range.setStart(originalContentRef.current.firstChild, 0);
|
|
} else {
|
|
range.setStart(originalContentRef.current, 0);
|
|
}
|
|
range.collapse(true);
|
|
|
|
selection?.removeAllRanges();
|
|
selection?.addRange(range);
|
|
}
|
|
}, 100);
|
|
}
|
|
};
|
|
|
|
// Handling click on original content even when not in edit mode
|
|
const handleOriginalContentClick = () => {
|
|
if (!editingOriginalContent) {
|
|
toggleEditOriginalContent();
|
|
}
|
|
};
|
|
|
|
// Modified send handler to combine user message with forwarded content
|
|
const handleSend = async () => {
|
|
if (!to) {
|
|
alert('Please specify at least one recipient');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
setSending(true);
|
|
|
|
// Prepare the complete email body
|
|
let finalBody = body;
|
|
|
|
if (type === 'forward' && originalContent) {
|
|
// For forwarded emails, construct the email in a standard format
|
|
const fromString = initialEmail?.from?.map(addr =>
|
|
addr.name ? `${addr.name} <${addr.address}>` : addr.address
|
|
).join(', ') || '';
|
|
|
|
const toString = initialEmail?.to?.map(addr =>
|
|
addr.name ? `${addr.name} <${addr.address}>` : addr.address
|
|
).join(', ') || '';
|
|
|
|
const dateString = initialEmail?.date
|
|
? typeof initialEmail.date === 'string'
|
|
? new Date(initialEmail.date).toLocaleString()
|
|
: initialEmail.date.toLocaleString()
|
|
: '';
|
|
|
|
// Combine user message with forwarded header and content
|
|
finalBody = `
|
|
${userMessage}
|
|
<br><br>
|
|
<div style="border-top: 1px solid #ccc; margin-top: 20px; padding-top: 10px;">
|
|
<div style="font-family: Arial, sans-serif; color: #333;">
|
|
<div style="margin-bottom: 15px;">
|
|
<div>---------- Forwarded message ---------</div>
|
|
<div><b>From:</b> ${fromString}</div>
|
|
<div><b>Date:</b> ${dateString}</div>
|
|
<div><b>Subject:</b> ${initialEmail?.subject || ''}</div>
|
|
<div><b>To:</b> ${toString}</div>
|
|
</div>
|
|
<div>${originalContent}</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
await onSend({
|
|
to,
|
|
cc: cc || undefined,
|
|
bcc: bcc || undefined,
|
|
subject,
|
|
body: finalBody,
|
|
attachments
|
|
});
|
|
|
|
onClose();
|
|
} catch (error) {
|
|
console.error('Error sending email:', error);
|
|
alert('Failed to send email. Please try again.');
|
|
} finally {
|
|
setSending(false);
|
|
}
|
|
};
|
|
|
|
// Handle editor input
|
|
const handleEditorInput = (e: React.FormEvent<HTMLDivElement>) => {
|
|
if (editorRef.current) {
|
|
const content = editorRef.current.innerHTML;
|
|
setUserMessage(content);
|
|
setBody(content);
|
|
}
|
|
};
|
|
|
|
// Toggle text direction
|
|
const toggleTextDirection = () => {
|
|
setIsRTL(!isRTL);
|
|
if (editorRef.current) {
|
|
editorRef.current.dir = !isRTL ? 'rtl' : 'ltr';
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Card className="w-full max-w-4xl mx-auto">
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center justify-between">
|
|
<span>{type === 'new' ? 'New Message' : type === 'forward' ? 'Forward Email' : 'Reply to Email'}</span>
|
|
<Button variant="ghost" size="icon" onClick={onClose}>
|
|
<X className="h-5 w-5" />
|
|
</Button>
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
{/* Recipients, Subject fields remain the same */}
|
|
<div className="p-3 border-b space-y-2">
|
|
<div className="flex items-center">
|
|
<span className="w-16 text-sm font-medium">To:</span>
|
|
<Input
|
|
value={to}
|
|
onChange={(e) => setTo(e.target.value)}
|
|
className="flex-1 border-0 shadow-none h-8 focus-visible:ring-0"
|
|
placeholder="recipient@example.com"
|
|
/>
|
|
</div>
|
|
|
|
{showCc && (
|
|
<div className="flex items-center">
|
|
<span className="w-16 text-sm font-medium">Cc:</span>
|
|
<Input
|
|
value={cc}
|
|
onChange={(e) => setCc(e.target.value)}
|
|
className="flex-1 border-0 shadow-none h-8 focus-visible:ring-0"
|
|
placeholder="cc@example.com"
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{showBcc && (
|
|
<div className="flex items-center">
|
|
<span className="w-16 text-sm font-medium">Bcc:</span>
|
|
<Input
|
|
value={bcc}
|
|
onChange={(e) => setBcc(e.target.value)}
|
|
className="flex-1 border-0 shadow-none h-8 focus-visible:ring-0"
|
|
placeholder="bcc@example.com"
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{/* CC/BCC controls */}
|
|
<div className="flex items-center text-xs">
|
|
<button
|
|
className="text-primary hover:underline mr-3 flex items-center"
|
|
onClick={() => setShowCc(!showCc)}
|
|
>
|
|
{showCc ? (
|
|
<>
|
|
<ChevronUp className="h-3 w-3 mr-0.5" />
|
|
Hide Cc
|
|
</>
|
|
) : (
|
|
<>
|
|
<ChevronDown className="h-3 w-3 mr-0.5" />
|
|
Show Cc
|
|
</>
|
|
)}
|
|
</button>
|
|
|
|
<button
|
|
className="text-primary hover:underline flex items-center"
|
|
onClick={() => setShowBcc(!showBcc)}
|
|
>
|
|
{showBcc ? (
|
|
<>
|
|
<ChevronUp className="h-3 w-3 mr-0.5" />
|
|
Hide Bcc
|
|
</>
|
|
) : (
|
|
<>
|
|
<ChevronDown className="h-3 w-3 mr-0.5" />
|
|
Show Bcc
|
|
</>
|
|
)}
|
|
</button>
|
|
</div>
|
|
|
|
<div className="flex items-center">
|
|
<span className="w-16 text-sm font-medium">Subject:</span>
|
|
<Input
|
|
value={subject}
|
|
onChange={(e) => setSubject(e.target.value)}
|
|
className="flex-1 border-0 shadow-none h-8 focus-visible:ring-0"
|
|
placeholder="Subject"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Updated Email Body Editor */}
|
|
<div className="space-y-2">
|
|
<div className="flex items-center justify-between">
|
|
<label htmlFor="body" className="text-sm font-medium">Message</label>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={toggleTextDirection}
|
|
className="h-8 px-2 text-xs"
|
|
>
|
|
{isRTL ? <AlignLeft className="h-4 w-4 mr-1" /> : <AlignRight className="h-4 w-4 mr-1" />}
|
|
{isRTL ? 'LTR' : 'RTL'}
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Email editor with clear separation between user message and original content */}
|
|
<div className="border rounded-md overflow-hidden">
|
|
{/* User's editable message area */}
|
|
<div
|
|
ref={editorRef}
|
|
contentEditable={!sending}
|
|
className="w-full p-4 min-h-[100px] focus:outline-none"
|
|
onInput={handleEditorInput}
|
|
style={{
|
|
direction: isRTL ? 'rtl' : 'ltr',
|
|
textAlign: isRTL ? 'right' : 'left'
|
|
}}
|
|
/>
|
|
|
|
{/* Original content display with visual separation */}
|
|
{type !== 'new' && originalContent && (
|
|
<div className="border-t">
|
|
<div className="px-4 py-2 bg-gray-50 text-xs font-medium text-gray-500 flex justify-between items-center">
|
|
<span>{type === 'forward' ? 'Forwarded content (original)' : 'Original message'}</span>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={toggleEditOriginalContent}
|
|
className="h-6 px-2 text-xs"
|
|
>
|
|
{editingOriginalContent ? 'Done Editing' : 'Edit Content'}
|
|
</Button>
|
|
</div>
|
|
<div className="p-4 bg-gray-50 border-t overflow-auto max-h-[400px]">
|
|
{editingOriginalContent ? (
|
|
<div
|
|
ref={originalContentRef}
|
|
contentEditable={true}
|
|
className="forwarded-content text-sm outline-none"
|
|
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(originalContent) }}
|
|
onInput={handleOriginalContentInput}
|
|
/>
|
|
) : (
|
|
<div
|
|
className="forwarded-content text-sm cursor-text"
|
|
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(originalContent) }}
|
|
onClick={handleOriginalContentClick}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Attachments section remains the same */}
|
|
{attachments.length > 0 && (
|
|
<div className="border-t p-2">
|
|
<div className="text-sm font-medium mb-1">Attachments:</div>
|
|
<div className="flex flex-wrap gap-2">
|
|
{attachments.map((attachment, index) => (
|
|
<div key={index} className="flex items-center gap-1 text-sm bg-muted px-2 py-1 rounded">
|
|
<Paperclip className="h-3 w-3" />
|
|
<span>{attachment.name}</span>
|
|
<button
|
|
onClick={() => removeAttachment(index)}
|
|
className="ml-1 text-muted-foreground hover:text-destructive"
|
|
>
|
|
<X className="h-3 w-3" />
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
|
|
<CardFooter className="border-t p-3 flex justify-between">
|
|
<div>
|
|
<input
|
|
type="file"
|
|
ref={attachmentInputRef}
|
|
className="hidden"
|
|
onChange={handleFileSelection}
|
|
multiple
|
|
/>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={handleAttachmentClick}
|
|
>
|
|
<Paperclip className="h-4 w-4 mr-1" />
|
|
Attach
|
|
</Button>
|
|
</div>
|
|
|
|
<Button
|
|
size="sm"
|
|
onClick={handleSend}
|
|
disabled={sending}
|
|
>
|
|
{sending ? (
|
|
<>
|
|
<Loader2 className="h-4 w-4 mr-1 animate-spin" />
|
|
Sending...
|
|
</>
|
|
) : (
|
|
<>
|
|
<SendHorizontal className="h-4 w-4 mr-1" />
|
|
Send
|
|
</>
|
|
)}
|
|
</Button>
|
|
</CardFooter>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
// Adapter component for legacy props
|
|
function LegacyAdapter({
|
|
showCompose,
|
|
setShowCompose,
|
|
composeTo,
|
|
setComposeTo,
|
|
composeCc,
|
|
setComposeCc,
|
|
composeBcc,
|
|
setComposeBcc,
|
|
composeSubject,
|
|
setComposeSubject,
|
|
composeBody,
|
|
setComposeBody,
|
|
showCc,
|
|
setShowCc,
|
|
showBcc,
|
|
setShowBcc,
|
|
attachments,
|
|
setAttachments,
|
|
handleSend,
|
|
originalEmail,
|
|
onSend,
|
|
onCancel,
|
|
replyTo,
|
|
forwardFrom
|
|
}: LegacyComposeEmailProps) {
|
|
// Determine the type from the original email or subject
|
|
const determineType = (): 'new' | 'reply' | 'reply-all' | 'forward' => {
|
|
if (originalEmail) {
|
|
return originalEmail.type;
|
|
}
|
|
|
|
if (composeSubject.startsWith('Re:')) {
|
|
return 'reply';
|
|
}
|
|
|
|
if (composeSubject.startsWith('Fwd:')) {
|
|
return 'forward';
|
|
}
|
|
|
|
return 'new';
|
|
};
|
|
|
|
// Convert legacy attachments format to new format
|
|
const convertAttachments = () => {
|
|
return (attachments || []).map((att: any) => ({
|
|
name: att.name || 'attachment',
|
|
content: typeof att.content === 'string' ? att.content : '',
|
|
type: att.type || 'application/octet-stream'
|
|
}));
|
|
};
|
|
|
|
// Create an EmailMessage compatible object from composeBody
|
|
// This is crucial for displaying original content in replies/forwards
|
|
const createEmailMessageFromContent = (): EmailMessage | null => {
|
|
const type = determineType();
|
|
|
|
// Only create an email object if we're replying or forwarding
|
|
if (type === 'new' || !composeBody) {
|
|
return null;
|
|
}
|
|
|
|
// For forwarded content, we need to preserve all the original formatting
|
|
// The composeBody already contains the formatted message with headers
|
|
return {
|
|
id: 'temp-id',
|
|
messageId: '',
|
|
subject: composeSubject,
|
|
from: [{ name: '', address: '' }],
|
|
to: [{ name: '', address: '' }],
|
|
date: new Date(),
|
|
// Always use the full composeBody to ensure nested forwards are preserved
|
|
content: composeBody,
|
|
html: composeBody,
|
|
hasAttachments: false
|
|
};
|
|
};
|
|
|
|
// If not showing compose, return null
|
|
if (!showCompose) {
|
|
return null;
|
|
}
|
|
|
|
// Create email message from content if available
|
|
const emailForCompose = createEmailMessageFromContent();
|
|
const type = determineType();
|
|
|
|
return (
|
|
<div className="fixed inset-0 bg-gray-600/30 backdrop-blur-sm z-50 flex items-center justify-center">
|
|
<div className="w-full max-w-2xl max-h-[90vh] bg-white rounded-xl shadow-xl overflow-auto mx-4">
|
|
<ComposeEmail
|
|
initialEmail={emailForCompose}
|
|
type={type}
|
|
onClose={() => {
|
|
onCancel?.();
|
|
setShowCompose(false);
|
|
}}
|
|
onSend={async (emailData: {
|
|
to: string;
|
|
cc?: string;
|
|
bcc?: string;
|
|
subject: string;
|
|
body: string;
|
|
attachments?: Array<{
|
|
name: string;
|
|
content: string;
|
|
type: string;
|
|
}>;
|
|
}) => {
|
|
// Update legacy state before sending
|
|
setComposeTo(emailData.to);
|
|
if (emailData.cc) setComposeCc(emailData.cc);
|
|
if (emailData.bcc) setComposeBcc(emailData.bcc);
|
|
setComposeSubject(emailData.subject);
|
|
setComposeBody(emailData.body);
|
|
|
|
// Call the legacy onSend function
|
|
await onSend(emailData);
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|