Fix rich text editor cursor jumping and add auto-list conversion
- Fix cursor jumping to start when typing in editor by tracking editing state - Only sync innerHTML from value on initial load, not during active editing - Add auto-conversion: typing "- " or "* " at line start creates bullet list - Add auto-conversion: typing "1. " at line start creates numbered list
This commit is contained in:
+45
-3
@@ -9,21 +9,35 @@ function RichTextEditor({ value, onChange, placeholder = "Add details...", colla
|
|||||||
const [showCopyMenu, setShowCopyMenu] = useStateD(false);
|
const [showCopyMenu, setShowCopyMenu] = useStateD(false);
|
||||||
const [copyMenuPos, setCopyMenuPos] = useStateD({ x: 0, y: 0 });
|
const [copyMenuPos, setCopyMenuPos] = useStateD({ x: 0, y: 0 });
|
||||||
const toolbarRef = useRefD(null);
|
const toolbarRef = useRefD(null);
|
||||||
|
const isEditingRef = useRefD(false);
|
||||||
|
const initializedRef = useRefD(false);
|
||||||
|
|
||||||
// Initialize editor content
|
// Initialize editor content only when value changes externally (not from our own edits)
|
||||||
useEffectD(() => {
|
useEffectD(() => {
|
||||||
if (editorRef.current && value !== undefined) {
|
if (editorRef.current && value !== undefined && !isEditingRef.current) {
|
||||||
const html = MarkdownUtils.toHtml(value || '');
|
const html = MarkdownUtils.toHtml(value || '');
|
||||||
if (editorRef.current.innerHTML !== html) {
|
// Only set innerHTML on first load or when value is reset externally
|
||||||
|
if (!initializedRef.current || editorRef.current.innerHTML === '') {
|
||||||
editorRef.current.innerHTML = html;
|
editorRef.current.innerHTML = html;
|
||||||
|
initializedRef.current = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
|
// Reset initialized flag when dialog closes/opens (value becomes null/undefined)
|
||||||
|
useEffectD(() => {
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
initializedRef.current = false;
|
||||||
|
}
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
const handleInput = useCallbackD(() => {
|
const handleInput = useCallbackD(() => {
|
||||||
if (editorRef.current) {
|
if (editorRef.current) {
|
||||||
|
isEditingRef.current = true;
|
||||||
const md = MarkdownUtils.toMarkdown(editorRef.current.innerHTML);
|
const md = MarkdownUtils.toMarkdown(editorRef.current.innerHTML);
|
||||||
onChange?.(md || null);
|
onChange?.(md || null);
|
||||||
|
// Reset editing flag after a short delay to allow state updates
|
||||||
|
setTimeout(() => { isEditingRef.current = false; }, 100);
|
||||||
}
|
}
|
||||||
}, [onChange]);
|
}, [onChange]);
|
||||||
|
|
||||||
@@ -38,6 +52,34 @@ function RichTextEditor({ value, onChange, placeholder = "Add details...", colla
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
execCmd('insertText', ' ');
|
execCmd('insertText', ' ');
|
||||||
}
|
}
|
||||||
|
// Auto-convert "- " or "* " at line start to bullet list
|
||||||
|
if (e.key === ' ') {
|
||||||
|
const sel = window.getSelection();
|
||||||
|
if (sel.rangeCount > 0) {
|
||||||
|
const range = sel.getRangeAt(0);
|
||||||
|
const node = range.startContainer;
|
||||||
|
if (node.nodeType === Node.TEXT_NODE) {
|
||||||
|
const text = node.textContent;
|
||||||
|
const offset = range.startOffset;
|
||||||
|
// Check if cursor is right after "- " or "* " at line start
|
||||||
|
if (offset === 1 && (text[0] === '-' || text[0] === '*')) {
|
||||||
|
e.preventDefault();
|
||||||
|
// Remove the "-" or "*" character
|
||||||
|
node.textContent = text.slice(1);
|
||||||
|
// Convert to bullet list
|
||||||
|
execCmd('insertUnorderedList');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Also check for "1. " to convert to numbered list
|
||||||
|
if (offset === 2 && text[0] === '1' && text[1] === '.') {
|
||||||
|
e.preventDefault();
|
||||||
|
node.textContent = text.slice(2);
|
||||||
|
execCmd('insertOrderedList');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// Ctrl/Cmd + B/I/K shortcuts
|
// Ctrl/Cmd + B/I/K shortcuts
|
||||||
if (e.ctrlKey || e.metaKey) {
|
if (e.ctrlKey || e.metaKey) {
|
||||||
if (e.key === 'b') { e.preventDefault(); execCmd('bold'); }
|
if (e.key === 'b') { e.preventDefault(); execCmd('bold'); }
|
||||||
|
|||||||
@@ -4646,20 +4646,36 @@ function RichTextEditor({
|
|||||||
y: 0
|
y: 0
|
||||||
});
|
});
|
||||||
const toolbarRef = useRefD(null);
|
const toolbarRef = useRefD(null);
|
||||||
|
const isEditingRef = useRefD(false);
|
||||||
|
const initializedRef = useRefD(false);
|
||||||
|
|
||||||
// Initialize editor content
|
// Initialize editor content only when value changes externally (not from our own edits)
|
||||||
useEffectD(() => {
|
useEffectD(() => {
|
||||||
if (editorRef.current && value !== undefined) {
|
if (editorRef.current && value !== undefined && !isEditingRef.current) {
|
||||||
const html = MarkdownUtils.toHtml(value || '');
|
const html = MarkdownUtils.toHtml(value || '');
|
||||||
if (editorRef.current.innerHTML !== html) {
|
// Only set innerHTML on first load or when value is reset externally
|
||||||
|
if (!initializedRef.current || editorRef.current.innerHTML === '') {
|
||||||
editorRef.current.innerHTML = html;
|
editorRef.current.innerHTML = html;
|
||||||
|
initializedRef.current = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
|
// Reset initialized flag when dialog closes/opens (value becomes null/undefined)
|
||||||
|
useEffectD(() => {
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
initializedRef.current = false;
|
||||||
|
}
|
||||||
|
}, [value]);
|
||||||
const handleInput = useCallbackD(() => {
|
const handleInput = useCallbackD(() => {
|
||||||
if (editorRef.current) {
|
if (editorRef.current) {
|
||||||
|
isEditingRef.current = true;
|
||||||
const md = MarkdownUtils.toMarkdown(editorRef.current.innerHTML);
|
const md = MarkdownUtils.toMarkdown(editorRef.current.innerHTML);
|
||||||
onChange?.(md || null);
|
onChange?.(md || null);
|
||||||
|
// Reset editing flag after a short delay to allow state updates
|
||||||
|
setTimeout(() => {
|
||||||
|
isEditingRef.current = false;
|
||||||
|
}, 100);
|
||||||
}
|
}
|
||||||
}, [onChange]);
|
}, [onChange]);
|
||||||
const execCmd = useCallbackD((cmd, val = null) => {
|
const execCmd = useCallbackD((cmd, val = null) => {
|
||||||
@@ -4672,6 +4688,34 @@ function RichTextEditor({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
execCmd('insertText', ' ');
|
execCmd('insertText', ' ');
|
||||||
}
|
}
|
||||||
|
// Auto-convert "- " or "* " at line start to bullet list
|
||||||
|
if (e.key === ' ') {
|
||||||
|
const sel = window.getSelection();
|
||||||
|
if (sel.rangeCount > 0) {
|
||||||
|
const range = sel.getRangeAt(0);
|
||||||
|
const node = range.startContainer;
|
||||||
|
if (node.nodeType === Node.TEXT_NODE) {
|
||||||
|
const text = node.textContent;
|
||||||
|
const offset = range.startOffset;
|
||||||
|
// Check if cursor is right after "- " or "* " at line start
|
||||||
|
if (offset === 1 && (text[0] === '-' || text[0] === '*')) {
|
||||||
|
e.preventDefault();
|
||||||
|
// Remove the "-" or "*" character
|
||||||
|
node.textContent = text.slice(1);
|
||||||
|
// Convert to bullet list
|
||||||
|
execCmd('insertUnorderedList');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Also check for "1. " to convert to numbered list
|
||||||
|
if (offset === 2 && text[0] === '1' && text[1] === '.') {
|
||||||
|
e.preventDefault();
|
||||||
|
node.textContent = text.slice(2);
|
||||||
|
execCmd('insertOrderedList');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// Ctrl/Cmd + B/I/K shortcuts
|
// Ctrl/Cmd + B/I/K shortcuts
|
||||||
if (e.ctrlKey || e.metaKey) {
|
if (e.ctrlKey || e.metaKey) {
|
||||||
if (e.key === 'b') {
|
if (e.key === 'b') {
|
||||||
|
|||||||
Reference in New Issue
Block a user