94 lines
2.8 KiB
TypeScript
94 lines
2.8 KiB
TypeScript
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:
|
|
// :<space>word
|
|
// :<space>
|
|
// :word<space>
|
|
// :)
|
|
new InputRule(CLOSE_REGEX, (state, match) => {
|
|
if (match) {
|
|
this.options.onClose();
|
|
}
|
|
return null;
|
|
}),
|
|
];
|
|
}
|
|
}
|