courrier clean 2$
This commit is contained in:
parent
e3b946f7e9
commit
3e6b8cae1f
@ -109,19 +109,13 @@ function isLegacyProps(props: ComposeEmailAllProps): props is LegacyComposeEmail
|
|||||||
return 'showCompose' in props && 'setShowCompose' in props;
|
return 'showCompose' in props && 'setShowCompose' in props;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a utility function to preprocess email content
|
// Configure DOMPurify to preserve certain attributes
|
||||||
function preprocessEmailContent(content: string): string {
|
DOMPurify.addHook('afterSanitizeAttributes', function(node) {
|
||||||
if (!content) return '';
|
// Preserve direction attributes
|
||||||
|
if (node.hasAttribute('dir')) {
|
||||||
// Sanitize HTML content
|
node.setAttribute('dir', node.getAttribute('dir') || 'ltr');
|
||||||
const sanitized = DOMPurify.sanitize(content);
|
}
|
||||||
|
});
|
||||||
// Fix common RTL/LTR issues by ensuring consistent direction
|
|
||||||
// Wrap content in a div with explicit direction if needed
|
|
||||||
const processed = sanitized.replace(/<div dir=(["'])rtl\1/g, '<div dir="ltr"');
|
|
||||||
|
|
||||||
return processed;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ComposeEmail(props: ComposeEmailAllProps) {
|
export default function ComposeEmail(props: ComposeEmailAllProps) {
|
||||||
// Handle legacy props by adapting them to new component
|
// Handle legacy props by adapting them to new component
|
||||||
@ -137,10 +131,7 @@ export default function ComposeEmail(props: ComposeEmailAllProps) {
|
|||||||
const [cc, setCc] = useState<string>('');
|
const [cc, setCc] = useState<string>('');
|
||||||
const [bcc, setBcc] = useState<string>('');
|
const [bcc, setBcc] = useState<string>('');
|
||||||
const [subject, setSubject] = useState<string>('');
|
const [subject, setSubject] = useState<string>('');
|
||||||
const [body, setBody] = useState<string>('');
|
const [emailContent, setEmailContent] = 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 [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);
|
||||||
@ -153,7 +144,6 @@ export default function ComposeEmail(props: ComposeEmailAllProps) {
|
|||||||
|
|
||||||
// Refs
|
// Refs
|
||||||
const editorRef = useRef<HTMLDivElement>(null);
|
const editorRef = useRef<HTMLDivElement>(null);
|
||||||
const originalContentRef = useRef<HTMLDivElement>(null);
|
|
||||||
const attachmentInputRef = useRef<HTMLInputElement>(null);
|
const attachmentInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
// Initialize the form when replying to or forwarding an email
|
// Initialize the form when replying to or forwarding an email
|
||||||
@ -179,8 +169,8 @@ export default function ComposeEmail(props: ComposeEmailAllProps) {
|
|||||||
// For forwarding, use the dedicated formatter
|
// For forwarding, use the dedicated formatter
|
||||||
const { subject, content } = formatForwardedEmail(formatterEmail);
|
const { subject, content } = formatForwardedEmail(formatterEmail);
|
||||||
setSubject(subject);
|
setSubject(subject);
|
||||||
setBody(content);
|
// Apply the formatted content directly without additional sanitization
|
||||||
setUserMessage(content);
|
setEmailContent(content);
|
||||||
} else {
|
} else {
|
||||||
// For reply/reply-all, use the reply formatter
|
// For reply/reply-all, use the reply formatter
|
||||||
const { to, cc, subject, content } = formatReplyEmail(formatterEmail, type as 'reply' | 'reply-all');
|
const { to, cc, subject, content } = formatReplyEmail(formatterEmail, type as 'reply' | 'reply-all');
|
||||||
@ -190,8 +180,8 @@ export default function ComposeEmail(props: ComposeEmailAllProps) {
|
|||||||
setShowCc(true);
|
setShowCc(true);
|
||||||
}
|
}
|
||||||
setSubject(subject);
|
setSubject(subject);
|
||||||
setBody(content);
|
// Apply the formatted content directly without additional sanitization
|
||||||
setUserMessage(content);
|
setEmailContent(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Focus editor after initializing
|
// Focus editor after initializing
|
||||||
@ -199,10 +189,10 @@ export default function ComposeEmail(props: ComposeEmailAllProps) {
|
|||||||
if (editorRef.current) {
|
if (editorRef.current) {
|
||||||
editorRef.current.focus();
|
editorRef.current.focus();
|
||||||
|
|
||||||
// Place cursor at the beginning
|
try {
|
||||||
const selection = window.getSelection();
|
// Place cursor at the beginning
|
||||||
if (selection) {
|
const selection = window.getSelection();
|
||||||
try {
|
if (selection) {
|
||||||
const range = document.createRange();
|
const range = document.createRange();
|
||||||
|
|
||||||
if (editorRef.current.firstChild) {
|
if (editorRef.current.firstChild) {
|
||||||
@ -212,9 +202,9 @@ export default function ComposeEmail(props: ComposeEmailAllProps) {
|
|||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
console.error('Error positioning cursor:', e);
|
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error positioning cursor:', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
@ -224,43 +214,6 @@ export default function ComposeEmail(props: ComposeEmailAllProps) {
|
|||||||
}
|
}
|
||||||
}, [initialEmail, type]);
|
}, [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(', ');
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle attachment selection
|
// Handle attachment selection
|
||||||
const handleAttachmentClick = () => {
|
const handleAttachmentClick = () => {
|
||||||
attachmentInputRef.current?.click();
|
attachmentInputRef.current?.click();
|
||||||
@ -271,12 +224,6 @@ export default function ComposeEmail(props: ComposeEmailAllProps) {
|
|||||||
const files = e.target.files;
|
const files = e.target.files;
|
||||||
if (!files || files.length === 0) return;
|
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
|
// Read files as data URLs
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
@ -308,50 +255,20 @@ export default function ComposeEmail(props: ComposeEmailAllProps) {
|
|||||||
setAttachments(current => current.filter((_, i) => i !== index));
|
setAttachments(current => current.filter((_, i) => i !== index));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle editing of original content
|
// Handle editor input without re-sanitizing content
|
||||||
const handleOriginalContentInput = (e: React.FormEvent<HTMLDivElement>) => {
|
const handleEditorInput = () => {
|
||||||
if (originalContentRef.current) {
|
if (editorRef.current) {
|
||||||
const content = originalContentRef.current.innerHTML;
|
// Capture innerHTML directly without reapplying DOMPurify
|
||||||
setOriginalContent(content);
|
setEmailContent(editorRef.current.innerHTML);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Toggle original content editing
|
// Toggle text direction for the entire editor
|
||||||
const toggleEditOriginalContent = () => {
|
const toggleTextDirection = () => {
|
||||||
setEditingOriginalContent(!editingOriginalContent);
|
setIsRTL(!isRTL);
|
||||||
|
|
||||||
// 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
|
// Send email without modifying pre-formatted content
|
||||||
const handleOriginalContentClick = () => {
|
|
||||||
if (!editingOriginalContent) {
|
|
||||||
toggleEditOriginalContent();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Modified send handler to use the entire body content
|
|
||||||
const handleSend = async () => {
|
const handleSend = async () => {
|
||||||
if (!to) {
|
if (!to) {
|
||||||
alert('Please specify at least one recipient');
|
alert('Please specify at least one recipient');
|
||||||
@ -361,13 +278,12 @@ export default function ComposeEmail(props: ComposeEmailAllProps) {
|
|||||||
try {
|
try {
|
||||||
setSending(true);
|
setSending(true);
|
||||||
|
|
||||||
// Use the complete edited content as is
|
|
||||||
await onSend({
|
await onSend({
|
||||||
to,
|
to,
|
||||||
cc: cc || undefined,
|
cc: cc || undefined,
|
||||||
bcc: bcc || undefined,
|
bcc: bcc || undefined,
|
||||||
subject,
|
subject,
|
||||||
body: userMessage, // Use the full edited message
|
body: emailContent, // Use the raw edited content
|
||||||
attachments
|
attachments
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -380,23 +296,6 @@ export default function ComposeEmail(props: ComposeEmailAllProps) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle editor input for the entire content
|
|
||||||
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 (
|
return (
|
||||||
<Card className="w-full max-w-4xl mx-auto">
|
<Card className="w-full max-w-4xl mx-auto">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
@ -509,14 +408,15 @@ export default function ComposeEmail(props: ComposeEmailAllProps) {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Email editor with a single editable area for the entire message */}
|
{/* Email editor with a single editable area */}
|
||||||
<div className="border rounded-md overflow-hidden">
|
<div className="border rounded-md overflow-hidden">
|
||||||
<div
|
<div
|
||||||
ref={editorRef}
|
ref={editorRef}
|
||||||
contentEditable={!sending}
|
contentEditable={!sending}
|
||||||
className="w-full p-4 min-h-[300px] focus:outline-none"
|
className="w-full p-4 min-h-[300px] focus:outline-none"
|
||||||
onInput={handleEditorInput}
|
onInput={handleEditorInput}
|
||||||
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(body) }}
|
// Use dangerouslySetInnerHTML without sanitizing again
|
||||||
|
dangerouslySetInnerHTML={{ __html: emailContent }}
|
||||||
dir={isRTL ? 'rtl' : 'ltr'}
|
dir={isRTL ? 'rtl' : 'ltr'}
|
||||||
style={{
|
style={{
|
||||||
textAlign: isRTL ? 'right' : 'left'
|
textAlign: isRTL ? 'right' : 'left'
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user