import { InputRule } from "prosemirror-inputrules"; import { Plugin } from "prosemirror-state"; import Extension from "../lib/Extension"; import isInCode from "../queries/isInCode"; import { run } from "./BlockMenuTrigger"; const OPEN_REGEX = /(?:^|\s):([0-9a-zA-Z_+-]+)?$/; const CLOSE_REGEX = /(?:^|\s):(([0-9a-zA-Z_+-]*\s+)|(\s+[0-9a-zA-Z_+-]+)|[^0-9a-zA-Z_+-]+)$/; export default class EmojiTrigger extends Extension { get name() { return "emojimenu"; } get plugins() { return [ new Plugin({ props: { handleClick: () => { this.options.onClose(); return false; }, handleKeyDown: (view, event) => { // Prosemirror input rules are not triggered on backspace, however // we need them to be evaluted for the filter trigger to work // correctly. This additional handler adds inputrules-like handling. if (event.key === "Backspace") { // timeout ensures that the delete has been handled by prosemirror // and any characters removed, before we evaluate the rule. setTimeout(() => { const { pos } = view.state.selection.$from; return run(view, pos, pos, OPEN_REGEX, (state, match) => { if (match) { this.options.onOpen(match[1]); } else { this.options.onClose(); } return null; }); }); } // If the query is active and we're navigating the block menu then // just ignore the key events in the editor itself until we're done if ( event.key === "Enter" || event.key === "ArrowUp" || event.key === "ArrowDown" || event.key === "Tab" ) { const { pos } = view.state.selection.$from; return run(view, pos, pos, OPEN_REGEX, (state, match) => { // just tell Prosemirror we handled it and not to do anything return match ? true : null; }); } return false; }, }, }), ]; } inputRules() { return [ // main regex should match only: // :word new InputRule(OPEN_REGEX, (state, match) => { if ( match && state.selection.$from.parent.type.name === "paragraph" && !isInCode(state) ) { this.options.onOpen(match[1]); } return null; }), // invert regex should match some of these scenarios: // :word // : // :word // :) new InputRule(CLOSE_REGEX, (state, match) => { if (match) { this.options.onClose(); } return null; }), ]; } }