courrier preview
This commit is contained in:
parent
6498f7ca65
commit
487fd96490
@ -17,7 +17,6 @@ import {
|
|||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import RichEmailEditor from '@/components/email/RichEmailEditor';
|
import RichEmailEditor from '@/components/email/RichEmailEditor';
|
||||||
import { detectTextDirection } from '@/lib/utils/text-direction';
|
import { detectTextDirection } from '@/lib/utils/text-direction';
|
||||||
import EmailContentDisplay from './EmailContentDisplay';
|
|
||||||
|
|
||||||
// Import from the centralized utils
|
// Import from the centralized utils
|
||||||
import {
|
import {
|
||||||
@ -317,39 +316,12 @@ export default function ComposeEmail(props: ComposeEmailProps) {
|
|||||||
setSending(true);
|
setSending(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Generate the final email content
|
|
||||||
let finalContent = emailContent;
|
|
||||||
|
|
||||||
// For reply or forward, combine the user's new content with the quoted content
|
|
||||||
if (type === 'reply' || type === 'reply-all' || type === 'forward') {
|
|
||||||
// Get the new content from the editor
|
|
||||||
const editorContent = document.querySelector('.ql-editor')?.innerHTML || '';
|
|
||||||
|
|
||||||
// Get the original header (either reply or forward header)
|
|
||||||
const headerContent = type === 'forward'
|
|
||||||
? emailContent.split('<blockquote')[0] || '---------- Forwarded message ----------'
|
|
||||||
: emailContent.split('<blockquote')[0] || `On ${new Date().toLocaleString()}, Someone wrote:`;
|
|
||||||
|
|
||||||
// Get the quoted content
|
|
||||||
const quotedMatch = emailContent.match(/<blockquote[^>]*>([\s\S]*?)<\/blockquote>/i);
|
|
||||||
const quotedContent = quotedMatch ? quotedMatch[0] : '';
|
|
||||||
|
|
||||||
// Combine them
|
|
||||||
finalContent = `
|
|
||||||
<div>
|
|
||||||
${editorContent}
|
|
||||||
</div>
|
|
||||||
${headerContent}
|
|
||||||
${quotedContent}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
await onSend({
|
await onSend({
|
||||||
to,
|
to,
|
||||||
cc: cc || undefined,
|
cc: cc || undefined,
|
||||||
bcc: bcc || undefined,
|
bcc: bcc || undefined,
|
||||||
subject,
|
subject,
|
||||||
body: finalContent,
|
body: emailContent,
|
||||||
fromAccount: selectedAccount?.id,
|
fromAccount: selectedAccount?.id,
|
||||||
attachments
|
attachments
|
||||||
});
|
});
|
||||||
@ -374,10 +346,6 @@ export default function ComposeEmail(props: ComposeEmailProps) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleContentChange = (html: string) => {
|
|
||||||
setEmailContent(html);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full max-h-[80vh] bg-white border rounded-md shadow-md">
|
<div className="flex flex-col h-full max-h-[80vh] bg-white border rounded-md shadow-md">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
@ -510,75 +478,14 @@ export default function ComposeEmail(props: ComposeEmailProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Message Body */}
|
{/* Message Body */}
|
||||||
{(type === 'forward' || type === 'reply' || type === 'reply-all') ? (
|
<RichEmailEditor
|
||||||
<div className="flex flex-col w-full email-compose-container">
|
initialContent={emailContent}
|
||||||
{/* Editable area for user's reply */}
|
onChange={(html) => {
|
||||||
<div className="border-b pb-2 mb-2">
|
setEmailContent(html);
|
||||||
<RichEmailEditor
|
}}
|
||||||
initialContent=""
|
placeholder="Write your message here..."
|
||||||
onChange={handleContentChange}
|
minHeight="320px"
|
||||||
placeholder="Write your message here..."
|
/>
|
||||||
minHeight="150px"
|
|
||||||
maxHeight="250px"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Read-only display of original email with professional formatting */}
|
|
||||||
<div className="original-email-content">
|
|
||||||
{type === 'reply' || type === 'reply-all' ? (
|
|
||||||
<div className="reply-header text-gray-600 text-sm border-b pb-1 mb-2">
|
|
||||||
{/* Get the reply header from the email content */}
|
|
||||||
{initialEmail && (
|
|
||||||
<div dangerouslySetInnerHTML={{
|
|
||||||
__html: DOMPurify.sanitize(
|
|
||||||
emailContent.split('<blockquote')[0] ||
|
|
||||||
`On ${new Date().toLocaleString()}, Someone wrote:`
|
|
||||||
)
|
|
||||||
}} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : type === 'forward' ? (
|
|
||||||
<div className="forward-header text-gray-600 text-sm border-b pb-1 mb-2">
|
|
||||||
{/* Get the forward header from the email content */}
|
|
||||||
{initialEmail && (
|
|
||||||
<div dangerouslySetInnerHTML={{
|
|
||||||
__html: DOMPurify.sanitize(
|
|
||||||
emailContent.split('<blockquote')[0] ||
|
|
||||||
'---------- Forwarded message ----------'
|
|
||||||
)
|
|
||||||
}} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{/* Display the original content with the same quality as Panel 3 */}
|
|
||||||
<div className="quoted-content bg-gray-50 rounded p-2">
|
|
||||||
{initialEmail && initialEmail.content && (
|
|
||||||
<EmailContentDisplay
|
|
||||||
content={initialEmail.content}
|
|
||||||
className="overflow-auto max-h-[50vh]"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Hidden field to store the complete content for sending */}
|
|
||||||
<input
|
|
||||||
type="hidden"
|
|
||||||
id="complete-email-content"
|
|
||||||
value={emailContent}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
// Regular editor for new emails
|
|
||||||
<RichEmailEditor
|
|
||||||
initialContent={emailContent}
|
|
||||||
onChange={handleContentChange}
|
|
||||||
placeholder="Write your message here..."
|
|
||||||
minHeight="200px"
|
|
||||||
maxHeight="calc(100vh - 400px)"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Attachments */}
|
{/* Attachments */}
|
||||||
{attachments.length > 0 && (
|
{attachments.length > 0 && (
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useEffect, useRef, useState, useMemo } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import 'quill/dist/quill.snow.css';
|
import 'quill/dist/quill.snow.css';
|
||||||
import { sanitizeHtml } from '@/lib/utils/dom-purify-config';
|
import { sanitizeHtml } from '@/lib/utils/dom-purify-config';
|
||||||
import { detectTextDirection } from '@/lib/utils/text-direction';
|
import { detectTextDirection } from '@/lib/utils/text-direction';
|
||||||
@ -28,15 +28,6 @@ const RichEmailEditor: React.FC<RichEmailEditorProps> = ({
|
|||||||
const quillRef = useRef<any>(null);
|
const quillRef = useRef<any>(null);
|
||||||
const [isReady, setIsReady] = useState(false);
|
const [isReady, setIsReady] = useState(false);
|
||||||
|
|
||||||
// Determine if content is pre-formatted (for forward/reply)
|
|
||||||
const isPreFormattedContent = useMemo(() => {
|
|
||||||
return preserveFormatting && initialContent && (
|
|
||||||
(initialContent.includes('---------- Forwarded message ----------') ||
|
|
||||||
initialContent.includes('wrote:')) &&
|
|
||||||
initialContent.includes('<blockquote')
|
|
||||||
);
|
|
||||||
}, [initialContent, preserveFormatting]);
|
|
||||||
|
|
||||||
// Initialize Quill editor when component mounts
|
// Initialize Quill editor when component mounts
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Import Quill dynamically (client-side only)
|
// Import Quill dynamically (client-side only)
|
||||||
@ -45,32 +36,21 @@ const RichEmailEditor: React.FC<RichEmailEditorProps> = ({
|
|||||||
|
|
||||||
const Quill = (await import('quill')).default;
|
const Quill = (await import('quill')).default;
|
||||||
|
|
||||||
// Check if content already appears to be pre-formatted as a reply or forward
|
// Import quill-better-table
|
||||||
const isPreFormattedContent = initialContent && (
|
|
||||||
(initialContent.includes('---------- Forwarded message ----------') ||
|
|
||||||
initialContent.includes('wrote:')) &&
|
|
||||||
initialContent.includes('<blockquote')
|
|
||||||
);
|
|
||||||
|
|
||||||
// Import quill-better-table only if not dealing with pre-formatted content
|
|
||||||
let tableModule = null;
|
let tableModule = null;
|
||||||
if (!isPreFormattedContent) {
|
try {
|
||||||
try {
|
const QuillBetterTable = await import('quill-better-table');
|
||||||
const QuillBetterTable = await import('quill-better-table');
|
|
||||||
|
|
||||||
// Register the table module if available
|
// Register the table module if available
|
||||||
if (QuillBetterTable && QuillBetterTable.default) {
|
if (QuillBetterTable && QuillBetterTable.default) {
|
||||||
Quill.register({
|
Quill.register({
|
||||||
'modules/better-table': QuillBetterTable.default
|
'modules/better-table': QuillBetterTable.default
|
||||||
}, true);
|
}, true);
|
||||||
tableModule = QuillBetterTable.default;
|
tableModule = QuillBetterTable.default;
|
||||||
console.log('Better Table module registered successfully');
|
console.log('Better Table module registered successfully');
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.warn('Table module not available:', err);
|
|
||||||
}
|
}
|
||||||
} else {
|
} catch (err) {
|
||||||
console.log('Pre-formatted content detected, disabling table module for compatibility');
|
console.warn('Table module not available:', err);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define custom formats/modules with table support
|
// Define custom formats/modules with table support
|
||||||
@ -98,8 +78,8 @@ const RichEmailEditor: React.FC<RichEmailEditorProps> = ({
|
|||||||
clipboard: {
|
clipboard: {
|
||||||
matchVisual: false // Disable clipboard matching for better HTML handling
|
matchVisual: false // Disable clipboard matching for better HTML handling
|
||||||
},
|
},
|
||||||
// Don't initialize better-table if this is pre-formatted content
|
// Don't initialize better-table yet - we'll do it after content is loaded
|
||||||
'better-table': isPreFormattedContent ? false : tableModule,
|
'better-table': false,
|
||||||
},
|
},
|
||||||
placeholder: placeholder,
|
placeholder: placeholder,
|
||||||
theme: 'snow',
|
theme: 'snow',
|
||||||
@ -113,8 +93,7 @@ const RichEmailEditor: React.FC<RichEmailEditorProps> = ({
|
|||||||
startsWithHtml: initialContent.trim().startsWith('<'),
|
startsWithHtml: initialContent.trim().startsWith('<'),
|
||||||
containsForwardedMessage: initialContent.includes('---------- Forwarded message ----------'),
|
containsForwardedMessage: initialContent.includes('---------- Forwarded message ----------'),
|
||||||
containsReplyIndicator: initialContent.includes('wrote:'),
|
containsReplyIndicator: initialContent.includes('wrote:'),
|
||||||
hasBlockquote: initialContent.includes('<blockquote'),
|
hasBlockquote: initialContent.includes('<blockquote')
|
||||||
preserveFormatting: preserveFormatting
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Detect text direction
|
// Detect text direction
|
||||||
@ -166,38 +145,17 @@ const RichEmailEditor: React.FC<RichEmailEditorProps> = ({
|
|||||||
quillRef.current.setText('Error loading content');
|
quillRef.current.setText('Error loading content');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Special handling for reply/forward content
|
// Use direct innerHTML setting for the initial content
|
||||||
if (isPreFormattedContent && preserveFormatting) {
|
quillRef.current.root.innerHTML = sanitizedContent;
|
||||||
console.log('Setting pre-formatted reply/forward content with special handling');
|
|
||||||
|
|
||||||
// First clear the editor and add a line break for the user to type in
|
// Set the direction for the content
|
||||||
quillRef.current.setText('\n\n');
|
if (quillRef.current && quillRef.current.format) {
|
||||||
|
|
||||||
// Set cursor at the top
|
|
||||||
quillRef.current.setSelection(0, 0);
|
|
||||||
|
|
||||||
// Now append the quoted content as a read-only section
|
|
||||||
const quoteIndex = quillRef.current.getText().length;
|
|
||||||
quillRef.current.clipboard.dangerouslyPasteHTML(quoteIndex, sanitizedContent);
|
|
||||||
|
|
||||||
// Set direction for the editor
|
|
||||||
quillRef.current.format('direction', direction);
|
quillRef.current.format('direction', direction);
|
||||||
if (direction === 'rtl') {
|
if (direction === 'rtl') {
|
||||||
quillRef.current.format('align', 'right');
|
quillRef.current.format('align', 'right');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Use direct innerHTML setting for the initial content
|
console.warn('Cannot format content: editor not fully initialized');
|
||||||
quillRef.current.root.innerHTML = sanitizedContent;
|
|
||||||
|
|
||||||
// Set the direction for the content
|
|
||||||
if (quillRef.current && quillRef.current.format) {
|
|
||||||
quillRef.current.format('direction', direction);
|
|
||||||
if (direction === 'rtl') {
|
|
||||||
quillRef.current.format('align', 'right');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.warn('Cannot format content: editor not fully initialized');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -309,17 +267,6 @@ const RichEmailEditor: React.FC<RichEmailEditorProps> = ({
|
|||||||
console.log('Content appears to be pre-formatted as reply/forward, using as-is');
|
console.log('Content appears to be pre-formatted as reply/forward, using as-is');
|
||||||
// Just do basic sanitization without additional processing
|
// Just do basic sanitization without additional processing
|
||||||
sanitizedContent = sanitizeHtml(initialContent);
|
sanitizedContent = sanitizeHtml(initialContent);
|
||||||
|
|
||||||
// Disable formatting operations that might conflict with pre-formatted content
|
|
||||||
try {
|
|
||||||
// Disable better-table module to prevent errors with forwarded tables
|
|
||||||
if (quillRef.current.getModule('better-table')) {
|
|
||||||
// Try to disable better-table module operations
|
|
||||||
quillRef.current.getModule('better-table').tableSelection = null;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.warn('Error disabling table module:', err);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Full processing for regular content
|
// Full processing for regular content
|
||||||
sanitizedContent = processHtmlContent(initialContent);
|
sanitizedContent = processHtmlContent(initialContent);
|
||||||
@ -350,52 +297,31 @@ const RichEmailEditor: React.FC<RichEmailEditorProps> = ({
|
|||||||
quillRef.current.setText(textContent || 'No content available');
|
quillRef.current.setText(textContent || 'No content available');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Special handling for reply/forward content
|
// SIMPLIFIED: Set content directly to the root element rather than using clipboard
|
||||||
if (isPreFormattedContent && preserveFormatting) {
|
if (quillRef.current && quillRef.current.root) {
|
||||||
console.log('Setting pre-formatted reply/forward content with special handling');
|
// First set the content
|
||||||
|
quillRef.current.root.innerHTML = sanitizedContent;
|
||||||
|
|
||||||
// First clear the editor and add a line break for the user to type in
|
// Then safely apply formatting only if quillRef is valid
|
||||||
quillRef.current.setText('\n\n');
|
try {
|
||||||
|
if (quillRef.current && quillRef.current.format && quillRef.current.root.innerHTML.trim().length > 0) {
|
||||||
// Set cursor at the top
|
// Set the direction for the content
|
||||||
quillRef.current.setSelection(0, 0);
|
quillRef.current.format('direction', direction);
|
||||||
|
if (direction === 'rtl') {
|
||||||
// Now append the quoted content as a read-only section
|
quillRef.current.format('align', 'right');
|
||||||
const quoteIndex = quillRef.current.getText().length;
|
|
||||||
quillRef.current.clipboard.dangerouslyPasteHTML(quoteIndex, sanitizedContent);
|
|
||||||
|
|
||||||
// Set direction for the editor
|
|
||||||
quillRef.current.format('direction', direction);
|
|
||||||
if (direction === 'rtl') {
|
|
||||||
quillRef.current.format('align', 'right');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// SIMPLIFIED: Set content directly to the root element rather than using clipboard
|
|
||||||
if (quillRef.current && quillRef.current.root) {
|
|
||||||
// First set the content
|
|
||||||
quillRef.current.root.innerHTML = sanitizedContent;
|
|
||||||
|
|
||||||
// Then safely apply formatting only if quillRef is valid
|
|
||||||
try {
|
|
||||||
if (quillRef.current && quillRef.current.format && quillRef.current.root.innerHTML.trim().length > 0) {
|
|
||||||
// Set the direction for the content
|
|
||||||
quillRef.current.format('direction', direction);
|
|
||||||
if (direction === 'rtl') {
|
|
||||||
quillRef.current.format('align', 'right');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force update
|
|
||||||
quillRef.current.update();
|
|
||||||
|
|
||||||
// Set selection to beginning
|
|
||||||
quillRef.current.setSelection(0, 0);
|
|
||||||
} else {
|
|
||||||
console.warn('Skipping format - either editor not ready or content empty');
|
|
||||||
}
|
}
|
||||||
} catch (formatError) {
|
|
||||||
console.error('Error applying formatting:', formatError);
|
// Force update
|
||||||
// Continue without formatting if there's an error
|
quillRef.current.update();
|
||||||
|
|
||||||
|
// Set selection to beginning
|
||||||
|
quillRef.current.setSelection(0, 0);
|
||||||
|
} else {
|
||||||
|
console.warn('Skipping format - either editor not ready or content empty');
|
||||||
}
|
}
|
||||||
|
} catch (formatError) {
|
||||||
|
console.error('Error applying formatting:', formatError);
|
||||||
|
// Continue without formatting if there's an error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user