From 678ee06e6b795c338eceee3400f67de4a163f0bc Mon Sep 17 00:00:00 2001 From: Yuzhong Zhang Date: Fri, 28 Aug 2026 09:24:53 +0000 Subject: [PATCH] feat(xl-ai): open AI menu with Mod-i shortcut --- packages/xl-ai/package.json | 1 + packages/xl-ai/src/AIExtension.ts | 2 + packages/xl-ai/src/plugins/ShortcutPlugin.ts | 46 ++++++++++++++++++++ 3 files changed, 49 insertions(+) create mode 100644 packages/xl-ai/src/plugins/ShortcutPlugin.ts diff --git a/packages/xl-ai/package.json b/packages/xl-ai/package.json index b031107a6d..7ccdf00137 100644 --- a/packages/xl-ai/package.json +++ b/packages/xl-ai/package.json @@ -79,6 +79,7 @@ "lodash.isequal": "^4.5.0", "lodash.merge": "^4.6.2", "prosemirror-changeset": "^2.4.1", + "prosemirror-keymap": "^1.2.3", "prosemirror-model": "^1.25.11", "prosemirror-state": "^1.4.4", "prosemirror-tables": "^1.8.5", diff --git a/packages/xl-ai/src/AIExtension.ts b/packages/xl-ai/src/AIExtension.ts index 7853ee8fd7..fb31dc311b 100644 --- a/packages/xl-ai/src/AIExtension.ts +++ b/packages/xl-ai/src/AIExtension.ts @@ -19,6 +19,7 @@ import { Plugin, PluginKey } from "prosemirror-state"; import { fixTablesKey } from "prosemirror-tables"; import { buildAIRequest, sendMessageWithAIRequest } from "./api/index.js"; import { createAgentCursorPlugin } from "./plugins/AgentCursorPlugin.js"; +import { createShortcutPlugin } from "./plugins/ShortcutPlugin.js"; import { AIRequestHelpers, InvokeAIOptions } from "./types.js"; import { AttributionMarksExtension } from "./prosemirror/AttributionMarks.js"; @@ -137,6 +138,7 @@ export const AIExtension = createExtension( createAgentCursorPlugin( editorOptions?.agentCursor || { name: "AI", color: "#8bc6ff" }, ), + createShortcutPlugin(editor), ], /** diff --git a/packages/xl-ai/src/plugins/ShortcutPlugin.ts b/packages/xl-ai/src/plugins/ShortcutPlugin.ts new file mode 100644 index 0000000000..ce1852d8a1 --- /dev/null +++ b/packages/xl-ai/src/plugins/ShortcutPlugin.ts @@ -0,0 +1,46 @@ +import { BlockNoteEditor } from "@blocknote/core"; +import { keymap } from "prosemirror-keymap"; +import { Command } from "prosemirror-state"; + +type AIMenuExtension = { + store: { state: { aiMenuState: unknown } }; + openAIMenuAtBlock: (blockId: string) => void; +}; + +// The command that will be executed when the shortcut is pressed. +const openAIMenuCommand = (editor: BlockNoteEditor): Command => { + return () => { + const ai = editor.getExtension("ai"); + if (!ai) { + return false; + } + + // Check if the AI Menu is already open. If so, do nothing. + if (ai.store.state.aiMenuState !== "closed") { + return false; // Return false to indicate the command did nothing. + } + + const cursor = editor.getTextCursorPosition(); + if ( + cursor.block.content && + Array.isArray(cursor.block.content) && // isarray check not ideal + cursor.block.content.length === 0 && + cursor.prevBlock + ) { + ai.openAIMenuAtBlock(cursor.prevBlock.id); + } else { + ai.openAIMenuAtBlock(cursor.block.id); + } + + // Return true to indicate that the key event has been handled. + return true; + }; +}; + +// A factory function to create the shortcut plugin. +export const createShortcutPlugin = (editor: BlockNoteEditor) => { + return keymap({ + // "Mod" maps to "Cmd" on Mac and "Ctrl" on Windows/Linux. + "Mod-i": openAIMenuCommand(editor), + }); +};