mirror of https://github.com/usememos/memos.git
fix: improve editor auto-scroll and Safari IME handling (#5469)
- Use `textarea-caret` for precise cursor position calculation instead of line approximation - Update `scrollToCursor` to scroll to the actual cursor position - Fix Safari double-enter issue with IME in list completion
This commit is contained in:
parent
61dbca8dc2
commit
4e34ef22bf
|
|
@ -1,4 +1,5 @@
|
|||
import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef } from "react";
|
||||
import getCaretCoordinates from "textarea-caret";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { EDITOR_HEIGHT } from "../constants";
|
||||
import type { EditorProps } from "../types";
|
||||
|
|
@ -74,9 +75,12 @@ const Editor = forwardRef(function Editor(props: EditorProps, ref: React.Forward
|
|||
getEditor: () => editorRef.current,
|
||||
focus: () => editorRef.current?.focus(),
|
||||
scrollToCursor: () => {
|
||||
if (editorRef.current) {
|
||||
editorRef.current.scrollTop = editorRef.current.scrollHeight;
|
||||
}
|
||||
const editor = editorRef.current;
|
||||
if (!editor) return;
|
||||
|
||||
const caret = getCaretCoordinates(editor, editor.selectionEnd);
|
||||
// Scroll to center cursor vertically
|
||||
editor.scrollTop = Math.max(0, caret.top - editor.clientHeight / 2);
|
||||
},
|
||||
insertText: (content = "", prefix = "", suffix = "") => {
|
||||
const editor = editorRef.current;
|
||||
|
|
@ -148,6 +152,19 @@ const Editor = forwardRef(function Editor(props: EditorProps, ref: React.Forward
|
|||
if (editorRef.current) {
|
||||
handleContentChangeCallback(editorRef.current.value);
|
||||
updateEditorHeight();
|
||||
|
||||
// Auto-scroll to keep cursor visible when typing
|
||||
// See: https://github.com/usememos/memos/issues/5469
|
||||
const editor = editorRef.current;
|
||||
const caret = getCaretCoordinates(editor, editor.selectionEnd);
|
||||
const lineHeight = parseFloat(getComputedStyle(editor).lineHeight) || 24;
|
||||
|
||||
// Scroll if cursor is near or beyond bottom edge (within 2 lines)
|
||||
const viewportBottom = editor.scrollTop + editor.clientHeight;
|
||||
if (caret.top + lineHeight * 2 > viewportBottom) {
|
||||
// Scroll to center cursor vertically
|
||||
editor.scrollTop = Math.max(0, caret.top - editor.clientHeight / 2);
|
||||
}
|
||||
}
|
||||
}, [handleContentChangeCallback, updateEditorHeight]);
|
||||
|
||||
|
|
|
|||
|
|
@ -24,15 +24,30 @@ export function useListCompletion({ editorRef, editorActions, isInIME }: UseList
|
|||
const editorActionsRef = useRef(editorActions);
|
||||
editorActionsRef.current = editorActions;
|
||||
|
||||
// Track when composition ends to handle Safari race condition
|
||||
// Safari fires keydown(Enter) immediately after compositionend, while Chrome doesn't
|
||||
// See: https://github.com/usememos/memos/issues/5469
|
||||
const lastCompositionEndRef = useRef(0);
|
||||
|
||||
useEffect(() => {
|
||||
const editor = editorRef.current;
|
||||
if (!editor) return;
|
||||
|
||||
const handleCompositionEnd = () => {
|
||||
lastCompositionEndRef.current = Date.now();
|
||||
};
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key !== "Enter" || isInIMERef.current || event.shiftKey || event.ctrlKey || event.metaKey || event.altKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Safari fix: Ignore Enter key within 100ms of composition end
|
||||
// This prevents double-enter behavior when confirming IME input in lists
|
||||
if (Date.now() - lastCompositionEndRef.current < 100) {
|
||||
return;
|
||||
}
|
||||
|
||||
const actions = editorActionsRef.current;
|
||||
const cursorPosition = actions.getCursorPosition();
|
||||
const contentBeforeCursor = actions.getContent().substring(0, cursorPosition);
|
||||
|
|
@ -51,10 +66,17 @@ export function useListCompletion({ editorRef, editorActions, isInIME }: UseList
|
|||
} else {
|
||||
const continuation = generateListContinuation(listInfo);
|
||||
actions.insertText("\n" + continuation);
|
||||
|
||||
// Auto-scroll to keep cursor visible after inserting list item
|
||||
setTimeout(() => actions.scrollToCursor(), 0);
|
||||
}
|
||||
};
|
||||
|
||||
editor.addEventListener("compositionend", handleCompositionEnd);
|
||||
editor.addEventListener("keydown", handleKeyDown);
|
||||
return () => editor.removeEventListener("keydown", handleKeyDown);
|
||||
return () => {
|
||||
editor.removeEventListener("compositionend", handleCompositionEnd);
|
||||
editor.removeEventListener("keydown", handleKeyDown);
|
||||
};
|
||||
}, []);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue