diff --git a/web/app/components/base/prompt-editor/index.tsx b/web/app/components/base/prompt-editor/index.tsx index ea1b62fb6a..7b8a3bbd58 100644 --- a/web/app/components/base/prompt-editor/index.tsx +++ b/web/app/components/base/prompt-editor/index.tsx @@ -56,7 +56,7 @@ import { VariableValueBlockNode } from './plugins/variable-value-block/node' import { CustomTextNode } from './plugins/custom-text/node' import OnBlurBlock from './plugins/on-blur-or-focus-block' import UpdateBlock from './plugins/update-block' -import ShortcutsPopupPlugin from './plugins/shortcuts-popup-plugin' +import ShortcutsPopupPlugin, { type Hotkey } from './plugins/shortcuts-popup-plugin' import { textToEditorState } from './utils' import type { ContextBlockType, @@ -97,6 +97,7 @@ export type PromptEditorProps = { workflowVariableBlock?: WorkflowVariableBlockType hitlInputBlock?: HITLInputBlockType isSupportFileVar?: boolean + shortcutPopups?: Array<{ hotkey: Hotkey; Popup: React.ComponentType<{ onClose: () => void }> }> } const PromptEditor: FC = ({ @@ -121,6 +122,7 @@ const PromptEditor: FC = ({ workflowVariableBlock, hitlInputBlock, isSupportFileVar, + shortcutPopups = [], }) => { const { eventEmitter } = useEventEmitterContextContext() const initialConfig = { @@ -197,22 +199,11 @@ const PromptEditor: FC = ({ } ErrorBoundary={LexicalErrorBoundary} /> - - {closePortal => ( -
-
test content
- -
- )} -
+ {shortcutPopups?.map(({ hotkey, Popup }, idx) => ( + + {closePortal => } + + ))} boolean) +// Hotkey can be: +// - string: 'mod+/' +// - string[]: ['mod', '/'] +// - string[][]: [['mod', '/'], ['mod', 'shift', '/']] (any combo matches) +// - function: custom matcher +export type Hotkey = string | string[] | string[][] | ((e: KeyboardEvent) => boolean) type ShortcutPopupPluginProps = { hotkey?: Hotkey @@ -48,58 +53,79 @@ function matchHotkey(event: KeyboardEvent, hotkey?: Hotkey) { if (typeof hotkey === 'function') return hotkey(event) - const parts = hotkey.toLowerCase().split('+').map(t => t.trim()).filter(Boolean) - let expectedKey: string | null = null + const matchCombo = (tokens: string[]) => { + const parts = tokens.map(t => t.toLowerCase().trim()).filter(Boolean) + let expectedKey: string | null = null - let needMod = false - let needCtrl = false - let needMeta = false - let needAlt = false - let needShift = false + let needMod = false + let needCtrl = false + let needMeta = false + let needAlt = false + let needShift = false - for (const p of parts) { - if (p === 'mod') { - needMod = true - continue + for (const p of parts) { + if (p === 'mod') { + needMod = true + continue + } + if (CTRL_ALIASES.has(p)) { + needCtrl = true + continue + } + if (META_ALIASES.has(p)) { + needMeta = true + continue + } + if (ALT_ALIASES.has(p)) { + needAlt = true + continue + } + if (SHIFT_ALIASES.has(p)) { + needShift = true + continue + } + expectedKey = p } - if (CTRL_ALIASES.has(p)) { - needCtrl = true - continue - } - if (META_ALIASES.has(p)) { - needMeta = true - continue - } - if (ALT_ALIASES.has(p)) { - needAlt = true - continue - } - if (SHIFT_ALIASES.has(p)) { - needShift = true - continue - } - expectedKey = p - } - if (needMod && !(event.metaKey || event.ctrlKey)) - return false - if (needCtrl && !event.ctrlKey) - return false - if (needMeta && !event.metaKey) - return false - if (needAlt && !event.altKey) - return false - if (needShift && !event.shiftKey) - return false - - if (expectedKey) { - const k = event.key.toLowerCase() - const normalized = k === ' ' ? 'space' : k - if (normalized !== expectedKey) + if (needMod && !(event.metaKey || event.ctrlKey)) return false + if (needCtrl && !event.ctrlKey) + return false + if (needMeta && !event.metaKey) + return false + if (needAlt && !event.altKey) + return false + if (needShift && !event.shiftKey) + return false + + if (expectedKey) { + const k = event.key.toLowerCase() + const normalized = k === ' ' ? 'space' : k + if (normalized !== expectedKey) + return false + } + + return true } - return true + if (Array.isArray(hotkey)) { + const isNested = hotkey.length > 0 && Array.isArray((hotkey as unknown[])[0]) + if (isNested) { + const combos = hotkey as string[][] + return combos.some(tokens => matchCombo(tokens)) + } + else { + const tokens = hotkey as string[] + return matchCombo(tokens) + } + } + + const tokensFromString = hotkey + .toLowerCase() + .split('+') + .map(t => t.trim()) + .filter(Boolean) + return matchCombo(tokensFromString) } export default function ShortcutsPopupPlugin({