mirror of https://github.com/usememos/memos
feat: add slash commands tooltip to InsertMenu
parent
f9dd7ad853
commit
d537591005
@ -1,72 +0,0 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { detectLastListItem, generateListContinuation } from "@/utils/markdown-list-detection";
|
||||
import { EditorRefActions } from ".";
|
||||
|
||||
interface UseListAutoCompletionOptions {
|
||||
editorRef: React.RefObject<HTMLTextAreaElement>;
|
||||
editorActions: EditorRefActions;
|
||||
isInIME: boolean;
|
||||
}
|
||||
|
||||
export function useListAutoCompletion({ editorRef, editorActions, isInIME }: UseListAutoCompletionOptions) {
|
||||
// Use refs to avoid stale closures in event handlers
|
||||
const isInIMERef = useRef(isInIME);
|
||||
isInIMERef.current = isInIME;
|
||||
|
||||
const editorActionsRef = useRef(editorActions);
|
||||
editorActionsRef.current = editorActions;
|
||||
|
||||
useEffect(() => {
|
||||
const editor = editorRef.current;
|
||||
if (!editor) return;
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
// Only handle Enter key
|
||||
if (event.key !== "Enter") return;
|
||||
|
||||
// Don't handle if in IME composition (for Asian languages)
|
||||
if (isInIMERef.current) return;
|
||||
|
||||
// Don't handle if modifier keys are pressed (user wants manual control)
|
||||
if (event.shiftKey || event.ctrlKey || event.metaKey || event.altKey) return;
|
||||
|
||||
const actions = editorActionsRef.current;
|
||||
const cursorPosition = actions.getCursorPosition();
|
||||
const contentBeforeCursor = actions.getContent().substring(0, cursorPosition);
|
||||
|
||||
// Detect if we're on a list item
|
||||
const listInfo = detectLastListItem(contentBeforeCursor);
|
||||
|
||||
if (listInfo.type) {
|
||||
event.preventDefault();
|
||||
|
||||
// Check if current list item is empty (GitHub-style behavior)
|
||||
// Extract the current line
|
||||
const lines = contentBeforeCursor.split("\n");
|
||||
const currentLine = lines[lines.length - 1];
|
||||
|
||||
// Check if line only contains list marker (no content after it)
|
||||
const isEmptyListItem =
|
||||
/^(\s*)([-*+])\s*$/.test(currentLine) || // Empty unordered list
|
||||
/^(\s*)([-*+])\s+\[([ xX])\]\s*$/.test(currentLine) || // Empty task list
|
||||
/^(\s*)(\d+)[.)]\s*$/.test(currentLine); // Empty ordered list
|
||||
|
||||
if (isEmptyListItem) {
|
||||
// Remove the empty list marker and exit list mode
|
||||
const lineStartPos = cursorPosition - currentLine.length;
|
||||
actions.removeText(lineStartPos, currentLine.length);
|
||||
} else {
|
||||
// Continue the list with the next item
|
||||
const continuation = generateListContinuation(listInfo);
|
||||
actions.insertText("\n" + continuation);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
editor.addEventListener("keydown", handleKeyDown);
|
||||
|
||||
return () => {
|
||||
editor.removeEventListener("keydown", handleKeyDown);
|
||||
};
|
||||
}, []); // Editor ref is stable; state accessed via refs to avoid stale closures
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { detectLastListItem, generateListContinuation } from "@/utils/markdown-list-detection";
|
||||
import { EditorRefActions } from ".";
|
||||
|
||||
interface UseListCompletionOptions {
|
||||
editorRef: React.RefObject<HTMLTextAreaElement>;
|
||||
editorActions: EditorRefActions;
|
||||
isInIME: boolean;
|
||||
}
|
||||
|
||||
// Patterns to detect empty list items
|
||||
const EMPTY_LIST_PATTERNS = [
|
||||
/^(\s*)([-*+])\s*$/, // Empty unordered list
|
||||
/^(\s*)([-*+])\s+\[([ xX])\]\s*$/, // Empty task list
|
||||
/^(\s*)(\d+)[.)]\s*$/, // Empty ordered list
|
||||
];
|
||||
|
||||
const isEmptyListItem = (line: string) => EMPTY_LIST_PATTERNS.some((pattern) => pattern.test(line));
|
||||
|
||||
export function useListCompletion({ editorRef, editorActions, isInIME }: UseListCompletionOptions) {
|
||||
const isInIMERef = useRef(isInIME);
|
||||
isInIMERef.current = isInIME;
|
||||
|
||||
const editorActionsRef = useRef(editorActions);
|
||||
editorActionsRef.current = editorActions;
|
||||
|
||||
useEffect(() => {
|
||||
const editor = editorRef.current;
|
||||
if (!editor) return;
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key !== "Enter" || isInIMERef.current || event.shiftKey || event.ctrlKey || event.metaKey || event.altKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
const actions = editorActionsRef.current;
|
||||
const cursorPosition = actions.getCursorPosition();
|
||||
const contentBeforeCursor = actions.getContent().substring(0, cursorPosition);
|
||||
const listInfo = detectLastListItem(contentBeforeCursor);
|
||||
|
||||
if (!listInfo.type) return;
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
const lines = contentBeforeCursor.split("\n");
|
||||
const currentLine = lines[lines.length - 1];
|
||||
|
||||
if (isEmptyListItem(currentLine)) {
|
||||
const lineStartPos = cursorPosition - currentLine.length;
|
||||
actions.removeText(lineStartPos, currentLine.length);
|
||||
} else {
|
||||
const continuation = generateListContinuation(listInfo);
|
||||
actions.insertText("\n" + continuation);
|
||||
}
|
||||
};
|
||||
|
||||
editor.addEventListener("keydown", handleKeyDown);
|
||||
return () => editor.removeEventListener("keydown", handleKeyDown);
|
||||
}, []);
|
||||
}
|
||||
Loading…
Reference in New Issue