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:
Ilia Sharin
2026-08-20 11:53:53 -04:00
parent 290905e3d9
commit f9ae92f605
2 changed files with 92 additions and 6 deletions
+45 -3
View File
@@ -9,21 +9,35 @@ function RichTextEditor({ value, onChange, placeholder = "Add details...", colla
const [showCopyMenu, setShowCopyMenu] = useStateD(false);
const [copyMenuPos, setCopyMenuPos] = useStateD({ x: 0, y: 0 });
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(() => {
if (editorRef.current && value !== undefined) {
if (editorRef.current && value !== undefined && !isEditingRef.current) {
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;
initializedRef.current = true;
}
}
}, [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(() => {
if (editorRef.current) {
isEditingRef.current = true;
const md = MarkdownUtils.toMarkdown(editorRef.current.innerHTML);
onChange?.(md || null);
// Reset editing flag after a short delay to allow state updates
setTimeout(() => { isEditingRef.current = false; }, 100);
}
}, [onChange]);
@@ -38,6 +52,34 @@ function RichTextEditor({ value, onChange, placeholder = "Add details...", colla
e.preventDefault();
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
if (e.ctrlKey || e.metaKey) {
if (e.key === 'b') { e.preventDefault(); execCmd('bold'); }
+47 -3
View File
@@ -4646,20 +4646,36 @@ function RichTextEditor({
y: 0
});
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(() => {
if (editorRef.current && value !== undefined) {
if (editorRef.current && value !== undefined && !isEditingRef.current) {
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;
initializedRef.current = true;
}
}
}, [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(() => {
if (editorRef.current) {
isEditingRef.current = true;
const md = MarkdownUtils.toMarkdown(editorRef.current.innerHTML);
onChange?.(md || null);
// Reset editing flag after a short delay to allow state updates
setTimeout(() => {
isEditingRef.current = false;
}, 100);
}
}, [onChange]);
const execCmd = useCallbackD((cmd, val = null) => {
@@ -4672,6 +4688,34 @@ function RichTextEditor({
e.preventDefault();
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
if (e.ctrlKey || e.metaKey) {
if (e.key === 'b') {