Commit 4b110d0d authored by Johnny's avatar Johnny

fix: cursor position after slash commands

parent 15646a89
import type { SlashCommandsProps } from "../types"; import type { SlashCommandsProps } from "../types";
import type { EditorRefActions } from ".";
import { SuggestionsPopup } from "./SuggestionsPopup"; import { SuggestionsPopup } from "./SuggestionsPopup";
import { useSuggestions } from "./useSuggestions"; import { useSuggestions } from "./useSuggestions";
const SlashCommands = ({ editorRef, editorActions, commands }: SlashCommandsProps) => { const SlashCommands = ({ editorRef, editorActions, commands }: SlashCommandsProps) => {
const handleCommandAutocomplete = (cmd: (typeof commands)[0], word: string, index: number, actions: EditorRefActions) => {
// Remove trigger char + word, then insert command output
actions.removeText(index, word.length);
actions.insertText(cmd.run());
// Position cursor relative to insertion point, if specified
if (cmd.cursorOffset) {
actions.setCursorPosition(index + cmd.cursorOffset);
}
};
const { position, suggestions, selectedIndex, isVisible, handleItemSelect } = useSuggestions({ const { position, suggestions, selectedIndex, isVisible, handleItemSelect } = useSuggestions({
editorRef, editorRef,
editorActions, editorActions,
triggerChar: "/", triggerChar: "/",
items: commands, items: commands,
filterItems: (items, query) => (!query ? items : items.filter((cmd) => cmd.name.toLowerCase().startsWith(query))), filterItems: (items, query) => (!query ? items : items.filter((cmd) => cmd.name.toLowerCase().startsWith(query))),
onAutocomplete: (cmd, word, index, actions) => { onAutocomplete: handleCommandAutocomplete,
actions.removeText(index, word.length);
actions.insertText(cmd.run());
if (cmd.cursorOffset) {
actions.setCursorPosition(actions.getCursorPosition() + cmd.cursorOffset);
}
},
}); });
if (!isVisible || !position) return null; if (!isVisible || !position) return null;
......
...@@ -98,7 +98,7 @@ const Editor = forwardRef(function Editor(props: EditorProps, ref: React.Forward ...@@ -98,7 +98,7 @@ const Editor = forwardRef(function Editor(props: EditorProps, ref: React.Forward
editor.value = editor.value.slice(0, start) + editor.value.slice(start + length); editor.value = editor.value.slice(0, start) + editor.value.slice(start + length);
editor.focus(); editor.focus();
editor.selectionEnd = start; editor.setSelectionRange(start, start);
updateContent(); updateContent();
}, },
setContent: (text: string) => { setContent: (text: string) => {
...@@ -116,8 +116,11 @@ const Editor = forwardRef(function Editor(props: EditorProps, ref: React.Forward ...@@ -116,8 +116,11 @@ const Editor = forwardRef(function Editor(props: EditorProps, ref: React.Forward
return editor.value.slice(editor.selectionStart, editor.selectionEnd); return editor.value.slice(editor.selectionStart, editor.selectionEnd);
}, },
setCursorPosition: (startPos: number, endPos?: number) => { setCursorPosition: (startPos: number, endPos?: number) => {
const endPosition = Number.isNaN(endPos) ? startPos : (endPos as number); const editor = editorRef.current;
editorRef.current?.setSelectionRange(startPos, endPosition); if (!editor) return;
// setSelectionRange requires valid arguments; default to startPos if endPos is undefined
const endPosition = endPos !== undefined && !Number.isNaN(endPos) ? endPos : startPos;
editor.setSelectionRange(startPos, endPosition);
}, },
getCursorLineNumber: () => { getCursorLineNumber: () => {
const editor = editorRef.current; const editor = editorRef.current;
......
...@@ -35,6 +35,7 @@ export function useSuggestions<T>({ ...@@ -35,6 +35,7 @@ export function useSuggestions<T>({
}: UseSuggestionsOptions<T>): UseSuggestionsReturn<T> { }: UseSuggestionsOptions<T>): UseSuggestionsReturn<T> {
const [position, setPosition] = useState<Position | null>(null); const [position, setPosition] = useState<Position | null>(null);
const [selectedIndex, setSelectedIndex] = useState(0); const [selectedIndex, setSelectedIndex] = useState(0);
const isProcessingRef = useRef(false);
const selectedRef = useRef(selectedIndex); const selectedRef = useRef(selectedIndex);
selectedRef.current = selectedIndex; selectedRef.current = selectedIndex;
...@@ -66,9 +67,14 @@ export function useSuggestions<T>({ ...@@ -66,9 +67,14 @@ export function useSuggestions<T>({
console.warn("useSuggestions: editorActions not available"); console.warn("useSuggestions: editorActions not available");
return; return;
} }
isProcessingRef.current = true;
const [word, index] = getCurrentWord(); const [word, index] = getCurrentWord();
onAutocomplete(item, word, index, editorActions.current); onAutocomplete(item, word, index, editorActions.current);
hide(); hide();
// Re-enable input handling after all DOM operations complete
queueMicrotask(() => {
isProcessingRef.current = false;
});
}; };
const handleNavigation = (e: KeyboardEvent, selected: number, suggestionsCount: number) => { const handleNavigation = (e: KeyboardEvent, selected: number, suggestionsCount: number) => {
...@@ -107,6 +113,8 @@ export function useSuggestions<T>({ ...@@ -107,6 +113,8 @@ export function useSuggestions<T>({
}; };
const handleInput = () => { const handleInput = () => {
if (isProcessingRef.current) return;
const editor = editorRef.current; const editor = editorRef.current;
if (!editor) return; if (!editor) return;
......
...@@ -120,7 +120,7 @@ const AttachmentList = ({ attachments }: AttachmentListProps) => { ...@@ -120,7 +120,7 @@ const AttachmentList = ({ attachments }: AttachmentListProps) => {
<div className="p-2 flex flex-col gap-1"> <div className="p-2 flex flex-col gap-1">
{mediaItems.length > 0 && <MediaGrid attachments={mediaItems} onImageClick={handleImageClick} />} {mediaItems.length > 0 && <MediaGrid attachments={mediaItems} onImageClick={handleImageClick} />}
{mediaItems.length > 0 && docItems.length > 0 && <div className="border-t border-border opacity-60" />} {mediaItems.length > 0 && docItems.length > 0 && <div className="border-t mt-1 border-border opacity-60" />}
{docItems.length > 0 && <DocsList attachments={docItems} />} {docItems.length > 0 && <DocsList attachments={docItems} />}
</div> </div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment