import { RawCommands } from '../types.js' import { isiOS } from '../utilities/isiOS.js' import { isMacOS } from '../utilities/isMacOS.js' function normalizeKeyName(name: string) { const parts = name.split(/-(?!$)/) let result = parts[parts.length - 1] if (result === 'Space') { result = ' ' } let alt let ctrl let shift let meta for (let i = 0; i < parts.length - 1; i += 1) { const mod = parts[i] if (/^(cmd|meta|m)$/i.test(mod)) { meta = true } else if (/^a(lt)?$/i.test(mod)) { alt = true } else if (/^(c|ctrl|control)$/i.test(mod)) { ctrl = true } else if (/^s(hift)?$/i.test(mod)) { shift = true } else if (/^mod$/i.test(mod)) { if (isiOS() || isMacOS()) { meta = true } else { ctrl = true } } else { throw new Error(`Unrecognized modifier name: ${mod}`) } } if (alt) { result = `Alt-${result}` } if (ctrl) { result = `Ctrl-${result}` } if (meta) { result = `Meta-${result}` } if (shift) { result = `Shift-${result}` } return result } declare module '@tiptap/core' { interface Commands { keyboardShortcut: { /** * Trigger a keyboard shortcut. */ keyboardShortcut: (name: string) => ReturnType, } } } export const keyboardShortcut: RawCommands['keyboardShortcut'] = name => ({ editor, view, tr, dispatch, }) => { const keys = normalizeKeyName(name).split(/-(?!$)/) const key = keys.find(item => !['Alt', 'Ctrl', 'Meta', 'Shift'].includes(item)) const event = new KeyboardEvent('keydown', { key: key === 'Space' ? ' ' : key, altKey: keys.includes('Alt'), ctrlKey: keys.includes('Ctrl'), metaKey: keys.includes('Meta'), shiftKey: keys.includes('Shift'), bubbles: true, cancelable: true, }) const capturedTransaction = editor.captureTransaction(() => { view.someProp('handleKeyDown', f => f(view, event)) }) capturedTransaction?.steps.forEach(step => { const newStep = step.map(tr.mapping) if (newStep && dispatch) { tr.maybeStep(newStep) } }) return true }