courrier refactor rebuild 2
This commit is contained in:
parent
19cff1ce7c
commit
02c9e7054d
@ -97,15 +97,56 @@
|
|||||||
|
|
||||||
/* Tables */
|
/* Tables */
|
||||||
.email-content-display table {
|
.email-content-display table {
|
||||||
width: 100%;
|
width: 100% !important;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
margin: 16px 0;
|
margin: 16px 0;
|
||||||
|
table-layout: fixed;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.email-content-display td,
|
.email-content-display td,
|
||||||
.email-content-display th {
|
.email-content-display th {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
border: 1px solid #e5e7eb;
|
border: 1px solid #e5e7eb;
|
||||||
|
word-break: break-word;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
max-width: 100%;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make sure quoted content tables are properly displayed */
|
||||||
|
.email-content-display .quoted-content table,
|
||||||
|
.email-content-display blockquote table {
|
||||||
|
font-size: 12px;
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-content-display .quoted-content td,
|
||||||
|
.email-content-display .quoted-content th,
|
||||||
|
.email-content-display blockquote td,
|
||||||
|
.email-content-display blockquote th {
|
||||||
|
padding: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fix for tables in Quill editor */
|
||||||
|
.ql-editor table {
|
||||||
|
width: 100% !important;
|
||||||
|
border-collapse: collapse;
|
||||||
|
table-layout: fixed;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ql-editor td,
|
||||||
|
.ql-editor th {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 4px 8px;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
word-break: break-word;
|
||||||
|
min-width: 30px;
|
||||||
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Buttons */
|
/* Buttons */
|
||||||
|
|||||||
@ -331,14 +331,14 @@ export default function ComposeEmail(props: ComposeEmailAllProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Message Body */}
|
{/* Message Body */}
|
||||||
<div className="flex-1 min-h-[200px] flex flex-col">
|
<div className="flex-1 min-h-[200px] flex flex-col overflow-hidden">
|
||||||
<Label htmlFor="message" className="flex-none block text-sm font-medium text-gray-700 mb-2">Message</Label>
|
<Label htmlFor="message" className="flex-none block text-sm font-medium text-gray-700 mb-2">Message</Label>
|
||||||
<div className="flex-1 border border-gray-300 rounded-md overflow-hidden">
|
<div className="flex-1 border border-gray-300 rounded-md overflow-hidden">
|
||||||
<RichEmailEditor
|
<RichEmailEditor
|
||||||
initialContent={emailContent}
|
initialContent={emailContent}
|
||||||
onChange={setEmailContent}
|
onChange={setEmailContent}
|
||||||
minHeight="200px"
|
minHeight="200px"
|
||||||
maxHeight="calc(100vh - 400px)"
|
maxHeight="none"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -617,14 +617,14 @@ function LegacyAdapter({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Message Body */}
|
{/* Message Body */}
|
||||||
<div className="flex-1 min-h-[200px] flex flex-col">
|
<div className="flex-1 min-h-[200px] flex flex-col overflow-hidden">
|
||||||
<Label htmlFor="message" className="flex-none block text-sm font-medium text-gray-700 mb-2">Message</Label>
|
<Label htmlFor="message" className="flex-none block text-sm font-medium text-gray-700 mb-2">Message</Label>
|
||||||
<div className="flex-1 border border-gray-300 rounded-md overflow-hidden">
|
<div className="flex-1 border border-gray-300 rounded-md overflow-hidden">
|
||||||
<RichEmailEditor
|
<RichEmailEditor
|
||||||
initialContent={composeBody}
|
initialContent={composeBody}
|
||||||
onChange={setComposeBody}
|
onChange={setComposeBody}
|
||||||
minHeight="200px"
|
minHeight="200px"
|
||||||
maxHeight="calc(100vh - 400px)"
|
maxHeight="none"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -20,6 +20,7 @@ const RichEmailEditor: React.FC<RichEmailEditorProps> = ({
|
|||||||
maxHeight = 'calc(100vh - 400px)',
|
maxHeight = 'calc(100vh - 400px)',
|
||||||
}) => {
|
}) => {
|
||||||
const editorRef = useRef<HTMLDivElement>(null);
|
const editorRef = useRef<HTMLDivElement>(null);
|
||||||
|
const toolbarRef = useRef<HTMLDivElement>(null);
|
||||||
const quillRef = useRef<any>(null);
|
const quillRef = useRef<any>(null);
|
||||||
const [isReady, setIsReady] = useState(false);
|
const [isReady, setIsReady] = useState(false);
|
||||||
|
|
||||||
@ -27,11 +28,11 @@ const RichEmailEditor: React.FC<RichEmailEditorProps> = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Import Quill dynamically (client-side only)
|
// Import Quill dynamically (client-side only)
|
||||||
const initializeQuill = async () => {
|
const initializeQuill = async () => {
|
||||||
if (!editorRef.current) return;
|
if (!editorRef.current || !toolbarRef.current) return;
|
||||||
|
|
||||||
const Quill = (await import('quill')).default;
|
const Quill = (await import('quill')).default;
|
||||||
|
|
||||||
// Define custom formats/modules as needed for email
|
// Define custom formats/modules with table support
|
||||||
const emailToolbarOptions = [
|
const emailToolbarOptions = [
|
||||||
['bold', 'italic', 'underline', 'strike'],
|
['bold', 'italic', 'underline', 'strike'],
|
||||||
[{ 'color': [] }, { 'background': [] }],
|
[{ 'color': [] }, { 'background': [] }],
|
||||||
@ -42,11 +43,16 @@ const RichEmailEditor: React.FC<RichEmailEditorProps> = ({
|
|||||||
['clean'],
|
['clean'],
|
||||||
];
|
];
|
||||||
|
|
||||||
// Create new Quill instance with the DOM element
|
// Create new Quill instance with the DOM element and custom toolbar
|
||||||
const editorElement = editorRef.current;
|
const editorElement = editorRef.current;
|
||||||
quillRef.current = new Quill(editorElement, {
|
quillRef.current = new Quill(editorElement, {
|
||||||
modules: {
|
modules: {
|
||||||
toolbar: emailToolbarOptions
|
toolbar: {
|
||||||
|
container: toolbarRef.current,
|
||||||
|
handlers: {
|
||||||
|
// Add any custom toolbar handlers here
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
placeholder: placeholder,
|
placeholder: placeholder,
|
||||||
theme: 'snow',
|
theme: 'snow',
|
||||||
@ -54,7 +60,10 @@ const RichEmailEditor: React.FC<RichEmailEditorProps> = ({
|
|||||||
|
|
||||||
// Set initial content (sanitized)
|
// Set initial content (sanitized)
|
||||||
if (initialContent) {
|
if (initialContent) {
|
||||||
quillRef.current.clipboard.dangerouslyPasteHTML(sanitizeHtml(initialContent));
|
// Properly handle table content in the sanitized HTML
|
||||||
|
const cleanContent = sanitizeHtml(initialContent);
|
||||||
|
// Use clipboard API to ensure tables and complex HTML are rendered correctly
|
||||||
|
quillRef.current.clipboard.dangerouslyPasteHTML(cleanContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add change listener
|
// Add change listener
|
||||||
@ -63,10 +72,18 @@ const RichEmailEditor: React.FC<RichEmailEditorProps> = ({
|
|||||||
onChange(html);
|
onChange(html);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Improve editor layout
|
||||||
|
const editorContainer = editorElement.closest('.ql-container');
|
||||||
|
if (editorContainer) {
|
||||||
|
editorContainer.classList.add('email-editor-container');
|
||||||
|
}
|
||||||
|
|
||||||
setIsReady(true);
|
setIsReady(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
initializeQuill();
|
initializeQuill().catch(err => {
|
||||||
|
console.error('Failed to initialize Quill editor:', err);
|
||||||
|
});
|
||||||
|
|
||||||
// Clean up on unmount
|
// Clean up on unmount
|
||||||
return () => {
|
return () => {
|
||||||
@ -81,49 +98,130 @@ const RichEmailEditor: React.FC<RichEmailEditorProps> = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (quillRef.current && isReady) {
|
if (quillRef.current && isReady) {
|
||||||
const currentContent = quillRef.current.root.innerHTML;
|
const currentContent = quillRef.current.root.innerHTML;
|
||||||
|
// Only update if content changed to avoid editor position reset
|
||||||
if (initialContent !== currentContent) {
|
if (initialContent !== currentContent) {
|
||||||
|
// Preserve cursor position if possible
|
||||||
|
const selection = quillRef.current.getSelection();
|
||||||
quillRef.current.clipboard.dangerouslyPasteHTML(sanitizeHtml(initialContent));
|
quillRef.current.clipboard.dangerouslyPasteHTML(sanitizeHtml(initialContent));
|
||||||
|
if (selection) {
|
||||||
|
quillRef.current.setSelection(selection);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [initialContent, isReady]);
|
}, [initialContent, isReady]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rich-email-editor-container">
|
<div className="rich-email-editor-wrapper">
|
||||||
{/* Quill container */}
|
{/* Custom toolbar container */}
|
||||||
<div
|
<div ref={toolbarRef} className="ql-toolbar ql-snow">
|
||||||
ref={editorRef}
|
<span className="ql-formats">
|
||||||
className="quill-editor"
|
<button className="ql-bold"></button>
|
||||||
style={{
|
<button className="ql-italic"></button>
|
||||||
height: 'auto',
|
<button className="ql-underline"></button>
|
||||||
minHeight: minHeight,
|
<button className="ql-strike"></button>
|
||||||
maxHeight: maxHeight
|
</span>
|
||||||
}}
|
<span className="ql-formats">
|
||||||
/>
|
<select className="ql-color"></select>
|
||||||
|
<select className="ql-background"></select>
|
||||||
|
</span>
|
||||||
|
<span className="ql-formats">
|
||||||
|
<button className="ql-list" value="ordered"></button>
|
||||||
|
<button className="ql-list" value="bullet"></button>
|
||||||
|
</span>
|
||||||
|
<span className="ql-formats">
|
||||||
|
<button className="ql-indent" value="-1"></button>
|
||||||
|
<button className="ql-indent" value="+1"></button>
|
||||||
|
</span>
|
||||||
|
<span className="ql-formats">
|
||||||
|
<select className="ql-align"></select>
|
||||||
|
</span>
|
||||||
|
<span className="ql-formats">
|
||||||
|
<button className="ql-link"></button>
|
||||||
|
</span>
|
||||||
|
<span className="ql-formats">
|
||||||
|
<button className="ql-clean"></button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Loading indicator */}
|
{/* Editor container with improved scrolling */}
|
||||||
{!isReady && (
|
<div className="rich-email-editor-container">
|
||||||
<div className="flex items-center justify-center py-8">
|
<div
|
||||||
<div className="h-6 w-6 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
|
ref={editorRef}
|
||||||
</div>
|
className="quill-editor"
|
||||||
)}
|
/>
|
||||||
|
|
||||||
|
{/* Loading indicator */}
|
||||||
|
{!isReady && (
|
||||||
|
<div className="flex items-center justify-center py-8">
|
||||||
|
<div className="h-6 w-6 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Custom styles for email context */}
|
{/* Custom styles for email context */}
|
||||||
<style jsx>{`
|
<style jsx>{`
|
||||||
|
.rich-email-editor-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 6px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.rich-email-editor-container {
|
.rich-email-editor-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: 6px;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: auto;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quill-editor {
|
.quill-editor {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex: 1;
|
min-height: ${minHeight};
|
||||||
|
max-height: ${maxHeight};
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide the editor until it's ready */
|
/* Hide the editor until it's ready */
|
||||||
.quill-editor ${!isReady ? '{ display: none; }' : ''}
|
.quill-editor ${!isReady ? '{ display: none; }' : ''}
|
||||||
|
|
||||||
|
/* Hide duplicate toolbar */
|
||||||
|
:global(.ql-toolbar.ql-snow + .ql-toolbar.ql-snow) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.ql-container) {
|
||||||
|
border: none !important;
|
||||||
|
height: auto !important;
|
||||||
|
min-height: ${minHeight};
|
||||||
|
max-height: none !important;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.ql-editor) {
|
||||||
|
padding: 12px;
|
||||||
|
min-height: ${minHeight};
|
||||||
|
overflow-y: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fix table rendering */
|
||||||
|
:global(.ql-editor table) {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.ql-editor td),
|
||||||
|
:global(.ql-editor th) {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 4px 8px;
|
||||||
|
min-width: 40px;
|
||||||
|
}
|
||||||
`}</style>
|
`}</style>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user