159 lines
9.6 KiB
JavaScript
159 lines
9.6 KiB
JavaScript
// 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;
|