courrier refactor rebuild 2

This commit is contained in:
alma 2025-04-27 10:40:43 +02:00
parent 19cff1ce7c
commit 02c9e7054d
3 changed files with 170 additions and 31 deletions

View File

@ -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 */

View File

@ -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>

View File

@ -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>
); );