courrier clean 2
This commit is contained in:
parent
e3791fa583
commit
0c5437a24f
@ -174,7 +174,7 @@ export default function ComposeEmail({
|
||||
setUserMessage(content);
|
||||
|
||||
// Combine user message with quoted content for the full email body
|
||||
const combined = `${content}${quotedContent ? quotedContent : ''}`;
|
||||
const combined = `${content}${quotedContent ? `<div class="original-content">${quotedContent}</div>` : ''}`;
|
||||
setComposeBody(combined);
|
||||
}
|
||||
};
|
||||
@ -189,8 +189,8 @@ export default function ComposeEmail({
|
||||
// For rich editor, combine user message with quoted content
|
||||
if (useRichEditor) {
|
||||
// Wrap the content with proper direction styles
|
||||
const userContent = userMessage ? `<div class="force-ltr">${userMessage}</div>` : '';
|
||||
const quotedWithStyles = quotedContent ? `<div class="force-ltr">${quotedContent}</div>` : '';
|
||||
const userContent = userMessage ? `<div class="force-ltr user-message">${userMessage}</div>` : '';
|
||||
const quotedWithStyles = quotedContent ? `<div class="force-ltr quoted-content" style="margin-top: 20px; border-top: 1px solid #e2e2e2; padding-top: 10px;">${quotedContent}</div>` : '';
|
||||
const combinedContent = `${userContent}${quotedWithStyles}`;
|
||||
|
||||
setComposeBody(combinedContent);
|
||||
@ -341,12 +341,12 @@ export default function ComposeEmail({
|
||||
<Label htmlFor="message" className="block text-sm font-medium text-gray-700">Message</Label>
|
||||
|
||||
{useRichEditor ? (
|
||||
<div className="mt-1 border border-gray-300 rounded-md overflow-hidden">
|
||||
{/* User input area - completely separate from quoted content */}
|
||||
<div className="mt-1 border border-gray-300 rounded-md overflow-hidden flex flex-col">
|
||||
{/* User input area - clearly separated from quoted content */}
|
||||
<div
|
||||
ref={contentEditableRef}
|
||||
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-[150px] text-gray-900 email-editor"
|
||||
onInput={handleUserMessageChange}
|
||||
onFocus={() => {
|
||||
// Clear placeholder text when user focuses if they haven't started typing
|
||||
@ -364,39 +364,33 @@ export default function ComposeEmail({
|
||||
style={{ direction: 'ltr', textAlign: 'left' }}
|
||||
/>
|
||||
|
||||
{/* Original email content - also editable */}
|
||||
{/* Original email content with clear visual separation */}
|
||||
{quotedContent && (
|
||||
<div
|
||||
className="w-full bg-gray-50 border-t border-gray-300 email-content-wrapper"
|
||||
style={{ direction: 'ltr' }}
|
||||
>
|
||||
<>
|
||||
<div className="px-3 py-2 bg-gray-100 border-t border-gray-300 text-xs text-gray-500 font-medium">
|
||||
{composeSubject.startsWith('Re:') ? 'Original message' : 'Forwarded message'}
|
||||
</div>
|
||||
<div
|
||||
className="p-3 opacity-90 text-sm email-content force-ltr"
|
||||
contentEditable="true"
|
||||
dangerouslySetInnerHTML={{ __html: quotedContent }}
|
||||
onInput={(e) => {
|
||||
const target = e.currentTarget;
|
||||
// Force LTR on any content added/edited using setAttribute
|
||||
target.querySelectorAll('*').forEach(el => {
|
||||
el.setAttribute('style', 'direction: ltr !important; text-align: left !important;');
|
||||
// Also add the class to ensure styles are applied
|
||||
el.classList.add('force-ltr');
|
||||
});
|
||||
setQuotedContent(target.innerHTML);
|
||||
// Update the combined content
|
||||
if (contentEditableRef.current) {
|
||||
const combined = `${contentEditableRef.current.innerHTML}${target.innerHTML}`;
|
||||
setComposeBody(combined);
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
direction: 'ltr',
|
||||
textAlign: 'left',
|
||||
unicodeBidi: 'isolate',
|
||||
writingMode: 'horizontal-tb'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
className="w-full bg-gray-50 border-t border-gray-200 email-content-wrapper"
|
||||
style={{ direction: 'ltr' }}
|
||||
>
|
||||
<div
|
||||
className="p-3 text-sm email-content force-ltr"
|
||||
dangerouslySetInnerHTML={{ __html: quotedContent }}
|
||||
contentEditable="false"
|
||||
style={{
|
||||
direction: 'ltr',
|
||||
textAlign: 'left',
|
||||
unicodeBidi: 'isolate',
|
||||
writingMode: 'horizontal-tb',
|
||||
color: '#555',
|
||||
borderLeft: '3px solid #ccc',
|
||||
paddingLeft: '10px',
|
||||
margin: '0 10px',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
@ -409,7 +403,7 @@ export default function ComposeEmail({
|
||||
style={{ direction: 'ltr', textAlign: 'left' }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -2,7 +2,10 @@
|
||||
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import { formatEmailForReplyOrForward, EmailMessage, EmailAddress } from '@/lib/services/email-service';
|
||||
import { X, Paperclip, ChevronDown, ChevronUp, SendHorizontal, Loader2, TextAlignLeft, TextAlignRight } from 'lucide-react';
|
||||
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';
|
||||
@ -38,6 +41,8 @@ export default function ComposeEmail({
|
||||
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 [showCc, setShowCc] = useState<boolean>(false);
|
||||
const [showBcc, setShowBcc] = useState<boolean>(false);
|
||||
const [sending, setSending] = useState<boolean>(false);
|
||||
@ -69,7 +74,16 @@ export default function ComposeEmail({
|
||||
}
|
||||
|
||||
setSubject(formattedEmail.subject);
|
||||
setBody(formattedEmail.body);
|
||||
|
||||
// Split the formatted body to separate user's message area from quoted content
|
||||
const bodyContent = formattedEmail.body;
|
||||
setBody(bodyContent);
|
||||
|
||||
// Set the original content for the quoted part
|
||||
const quoteMatch = bodyContent.match(/<blockquote[^>]*>([\s\S]*?)<\/blockquote>/i);
|
||||
if (quoteMatch && quoteMatch[0]) {
|
||||
setOriginalContent(quoteMatch[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// Focus editor after initializing
|
||||
@ -198,92 +212,23 @@ export default function ComposeEmail({
|
||||
|
||||
// Check if we have content to forward
|
||||
if (initialEmail.content || initialEmail.html || initialEmail.text) {
|
||||
try {
|
||||
// Use the parse-email API endpoint which centralizes our email parsing logic
|
||||
const response = await fetch('/api/parse-email', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
email: initialEmail.content || initialEmail.html || initialEmail.text || ''
|
||||
}),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const parsedEmail = await response.json();
|
||||
|
||||
// Use the parsed HTML content if available
|
||||
if (parsedEmail.html) {
|
||||
contentHtml = parsedEmail.html;
|
||||
console.log('Successfully parsed HTML content');
|
||||
} else if (parsedEmail.text) {
|
||||
// Text-only content is wrapped in pre-formatted styling
|
||||
contentHtml = `<div style="white-space: pre-wrap; font-family: monospace; padding: 10px; background-color: #f9f9f9; border-radius: 4px;">${parsedEmail.text}</div>`;
|
||||
console.log('Using text content');
|
||||
} else {
|
||||
console.warn('API returned success but no content');
|
||||
}
|
||||
} else {
|
||||
console.error('API returned error:', await response.text());
|
||||
throw new Error('API call failed');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error parsing email:', error);
|
||||
|
||||
// Fallback processing - using our cleanHtml utility directly
|
||||
if (initialEmail.html) {
|
||||
// Import the cleanHtml function dynamically if needed
|
||||
const { cleanHtml } = await import('@/lib/mail-parser-wrapper');
|
||||
contentHtml = cleanHtml(initialEmail.html, {
|
||||
preserveStyles: true,
|
||||
scopeStyles: true,
|
||||
addWrapper: true
|
||||
});
|
||||
console.log('Using direct HTML cleaning fallback');
|
||||
} else if (initialEmail.content) {
|
||||
contentHtml = DOMPurify.sanitize(initialEmail.content, {
|
||||
ADD_TAGS: ['style'],
|
||||
FORBID_TAGS: ['script', 'iframe']
|
||||
});
|
||||
console.log('Using DOMPurify sanitized content');
|
||||
} else if (initialEmail.text) {
|
||||
contentHtml = `<div style="white-space: pre-wrap; font-family: monospace; padding: 10px; background-color: #f9f9f9; border-radius: 4px;">${initialEmail.text}</div>`;
|
||||
console.log('Using plain text fallback');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.warn('No email content available for forwarding');
|
||||
// Use the most complete version of content available
|
||||
contentHtml = DOMPurify.sanitize(
|
||||
initialEmail.content || initialEmail.html ||
|
||||
`<pre style="white-space: pre-wrap;">${initialEmail.text || ''}</pre>`
|
||||
);
|
||||
}
|
||||
|
||||
// Combine the header and content - using containment
|
||||
const forwardedContent = `
|
||||
${headerHtml}
|
||||
<div style="margin-top: 10px; border-left: 2px solid #e1e1e1; padding-left: 15px;">
|
||||
<div style="isolation: isolate; contain: content; overflow: auto;">
|
||||
${contentHtml}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
// Store the complete forwarded content in state
|
||||
const forwardedContent = `${headerHtml}<div class="forwarded-content">${contentHtml}</div>`;
|
||||
setOriginalContent(forwardedContent);
|
||||
|
||||
// Set the complete body
|
||||
setBody(`<div class="user-message"></div>${forwardedContent}`);
|
||||
|
||||
console.log('Setting body with forwarded content');
|
||||
setBody(forwardedContent);
|
||||
} catch (error) {
|
||||
console.error('Error initializing forwarded email:', error);
|
||||
|
||||
// Provide a minimal template even in error case
|
||||
setBody(`
|
||||
<div style="border-top: 1px solid #e1e1e1; margin-top: 20px; padding-top: 15px; font-family: Arial, sans-serif; color: #333;">
|
||||
<div style="margin-bottom: 15px;">
|
||||
<div style="font-weight: normal; margin-bottom: 10px;">---------- Forwarded message ---------</div>
|
||||
<div><b>From:</b> ${initialEmail.from ? formatSender(initialEmail.from) : 'Unknown'}</div>
|
||||
<div><b>Date:</b> ${new Date().toLocaleString()}</div>
|
||||
<div><b>Subject:</b> ${initialEmail.subject || '(No subject)'}</div>
|
||||
<div><b>To:</b> ${initialEmail.to ? formatRecipients(initialEmail.to) : ''}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 10px; padding: 15px; color: #d32f2f; font-style: italic; border: 1px dashed #d32f2f; margin: 15px 0; text-align: center; background-color: #fff8f8; border-radius: 4px;">
|
||||
Error loading original message content. The original message may still be viewable in your inbox.
|
||||
</div>
|
||||
`);
|
||||
console.error('Error formatting forwarded email:', error);
|
||||
setBody('<div style="color: #666; font-style: italic;">Error formatting forwarded email content</div>');
|
||||
}
|
||||
};
|
||||
|
||||
@ -364,8 +309,14 @@ export default function ComposeEmail({
|
||||
|
||||
// Handle editor input
|
||||
const handleEditorInput = (e: React.FormEvent<HTMLDivElement>) => {
|
||||
// Store the HTML content for use in the send function
|
||||
setBody(e.currentTarget.innerHTML);
|
||||
if (editorRef.current) {
|
||||
const content = editorRef.current.innerHTML;
|
||||
setUserMessage(content);
|
||||
|
||||
// Combine user message with original content for the full email
|
||||
const combined = `<div class="user-message" style="direction: ${isRTL ? 'rtl' : 'ltr'}; text-align: ${isRTL ? 'right' : 'left'};">${content}</div>${originalContent}`;
|
||||
setBody(combined);
|
||||
}
|
||||
};
|
||||
|
||||
// Toggle text direction
|
||||
@ -377,23 +328,17 @@ export default function ComposeEmail({
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="w-full h-full flex flex-col overflow-hidden shadow-lg">
|
||||
<CardHeader className="border-b py-2 px-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<CardTitle className="text-lg">
|
||||
{type === 'new' ? 'New Message' :
|
||||
type === 'reply' ? 'Reply' :
|
||||
type === 'reply-all' ? 'Reply All' :
|
||||
'Forward'}
|
||||
</CardTitle>
|
||||
<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-4 w-4" />
|
||||
<X className="h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="p-0 flex-1 flex flex-col overflow-hidden">
|
||||
{/* Email header fields */}
|
||||
<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>
|
||||
@ -477,31 +422,52 @@ 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>
|
||||
{/* 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 message input area */}
|
||||
<div
|
||||
ref={editorRef}
|
||||
contentEditable={!sending}
|
||||
className="w-full p-4 min-h-[200px] focus:outline-none"
|
||||
onInput={handleEditorInput}
|
||||
style={{
|
||||
direction: isRTL ? 'rtl' : 'ltr',
|
||||
textAlign: isRTL ? 'right' : 'left'
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Original content display with visual separation - only shown for replies/forwards */}
|
||||
{type !== 'new' && originalContent && (
|
||||
<div className="border-t pt-2">
|
||||
<div className="px-4 py-2 bg-gray-50 text-xs font-medium text-gray-500">
|
||||
{type === 'forward' ? 'Forwarded content' : 'Original message'}
|
||||
</div>
|
||||
<div
|
||||
className="p-4 bg-gray-50 text-sm original-content"
|
||||
dangerouslySetInnerHTML={{ __html: originalContent }}
|
||||
style={{ opacity: 0.85 }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Email body editor */}
|
||||
<div
|
||||
ref={editorRef}
|
||||
className="flex-1 overflow-auto p-4 focus:outline-none email-content"
|
||||
contentEditable={true}
|
||||
onInput={handleEditorInput}
|
||||
dangerouslySetInnerHTML={{ __html: body }}
|
||||
dir={isRTL ? "rtl" : "ltr"}
|
||||
style={{ minHeight: '200px' }}
|
||||
/>
|
||||
|
||||
{/* Attachments list */}
|
||||
{/* Attachments section remains the same */}
|
||||
{attachments.length > 0 && (
|
||||
<div className="border-t p-2">
|
||||
<div className="text-sm font-medium mb-1">Attachments:</div>
|
||||
|
||||
@ -673,32 +673,36 @@ export function formatEmailForReplyOrForward(
|
||||
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 specialized body for forward
|
||||
// Return specialized body for forward with improved styling
|
||||
return {
|
||||
to: '',
|
||||
subject,
|
||||
body: `
|
||||
<div class="forwarded-message">
|
||||
<p>---------- Forwarded message ---------</p>
|
||||
<p>From: ${fromText}</p>
|
||||
<p>Date: ${formattedDate}</p>
|
||||
<p>Subject: ${email.subject || ''}</p>
|
||||
<p>To: ${toText}</p>
|
||||
<br>
|
||||
${email.html || email.text ? (email.html || `<pre>${email.text || ''}</pre>`) : '<p>No content available</p>'}
|
||||
<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;">
|
||||
${email.html || email.text ? (email.html || `<pre style="white-space: pre-wrap; font-family: inherit; margin: 0;">${email.text || ''}</pre>`) : '<p>No content available</p>'}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
};
|
||||
}
|
||||
|
||||
// Format the email body with quote
|
||||
// Format the email body with improved quote styling for replies
|
||||
const body = `
|
||||
<div>
|
||||
<div class="reply-body">
|
||||
<br/>
|
||||
<br/>
|
||||
<div>${quoteHeader}</div>
|
||||
<blockquote style="border-left: 2px solid #ccc; padding-left: 10px; margin-left: 10px; color: #777;">
|
||||
${quotedContent}
|
||||
<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>`;
|
||||
|
||||
@ -754,5 +758,5 @@ function createQuoteHeader(email: EmailMessage): string {
|
||||
? `${sender.name} <${sender.address}>`
|
||||
: sender?.address || 'Unknown sender';
|
||||
|
||||
return `<div>On ${formattedDate}, ${fromText} wrote:</div>`;
|
||||
return `<div style="font-weight: 500;">On ${formattedDate}, ${fromText} wrote:</div>`;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user