qudoco-fe/components/editor/fixed-editor.js

159 lines
9.6 KiB
JavaScript
Raw Normal View History

<EFBFBD><EFBFBD>// components/fixed-editor.js
import React, { useRef, useEffect } from "react";
import { Editor } from "@tinymce/tinymce-react";
function FixedEditor(props) {
const editorRef = useRef(null);
const contentRef = useRef(props.initialData || '');
const onChangeRef = useRef(props.onChange);
// Update refs when props change
useEffect(() => {
onChangeRef.current = props.onChange;
}, [props.onChange]);
useEffect(() => {
if (props.initialData !== undefined && editorRef.current) {
contentRef.current = props.initialData;
editorRef.current.setContent(props.initialData);
}
}, [props.initialData]);
const handleInit = (evt, editor) => {
editorRef.current = editor;
// Set initial content
if (props.initialData) {
editor.setContent(props.initialData);
contentRef.current = props.initialData;
}
// Create a debounced onChange handler
let timeoutId = null;
const debouncedOnChange = (content) => {
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(() => {
if (onChangeRef.current && content !== contentRef.current) {
contentRef.current = content;
onChangeRef.current(content);
}
}, 100);
};
// Handle all content changes
editor.on('change keyup input paste', (e) => {
const content = editor.getContent();
debouncedOnChange(content);
});
// Handle undo/redo
editor.on('Undo Redo', (e) => {
setTimeout(() => {
const content = editor.getContent();
debouncedOnChange(content);
}, 0);
});
// Prevent cursor jumping by handling focus events
editor.on('focus', (e) => {
// Save current selection
editor.lastSelection = editor.selection.getBookmark();
});
editor.on('blur', (e) => {
// Restore selection if available
if (editor.lastSelection) {
try {
editor.selection.moveToBookmark(editor.lastSelection);
} catch (error) {
// Ignore bookmark errors
}
}
});
// Handle key events to prevent jumping
editor.on('keydown', (e) => {
// Save selection before any key operation
editor.lastSelection = editor.selection.getBookmark();
});
editor.on('keyup', (e) => {
// Restore selection after key operation
if (editor.lastSelection) {
try {
editor.selection.moveToBookmark(editor.lastSelection);
} catch (error) {
// Ignore bookmark errors
}
}
});
};
return (
<Editor
onInit={handleInit}
apiKey={process.env.NEXT_PUBLIC_TINYMCE_API_KEY}
init={{
height: 400,
menubar: false,
plugins: [
'advlist', 'autolink', 'lists', 'link', 'image', 'charmap', 'preview',
'anchor', 'searchreplace', 'visualblocks', 'code', 'fullscreen',
'insertdatetime', 'media', 'table', 'code', 'help', 'wordcount'
],
toolbar: 'undo redo | blocks | ' +
'bold italic forecolor | alignleft aligncenter ' +
'alignright alignjustify | bullist numlist outdent indent | ' +
'removeformat | table | code | help',
content_style: `
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 14px;
line-height: 1.6;
color: #333;
}
.mce-content-body {
padding: 16px;
min-height: 368px;
}
`,
placeholder: 'Start typing...',
branding: false,
elementpath: false,
resize: false,
statusbar: false,
// Critical settings for stability
auto_focus: false,
forced_root_block: 'p',
entity_encoding: 'raw',
keep_styles: true,
// Disable all problematic features
verify_html: false,
cleanup: false,
cleanup_on_startup: false,
auto_resize: false,
// Content handling
paste_as_text: false,
paste_enable_default_filters: true,
paste_word_valid_elements: 'b,strong,i,em,h1,h2,h3,h4,h5,h6',
paste_retain_style_properties: 'color background-color font-size font-weight',
// Prevent automatic updates
element_format: 'html',
valid_children: '+body[style]',
extended_valid_elements: 'span[*]',
custom_elements: '~span',
// Mobile support
mobile: {
theme: 'silver',
plugins: ['lists', 'autolink', 'link', 'image', 'table'],
toolbar: 'bold italic | bullist numlist | link image'
}
}}
/>
);
}
export default FixedEditor;