chore: Editor refactor (#3286)

* cleanup

* add context

* EventEmitter allows removal of toolbar props from extensions

* Move to 'packages' of extensions
Remove EmojiTrigger extension

* types

* iteration

* fix render flashing

* fix: Missing nodes in collection descriptions
This commit is contained in:
Tom Moor
2022-03-30 19:10:34 -07:00
committed by GitHub
parent c5b9a742c0
commit 6f2a4488e8
30 changed files with 517 additions and 581 deletions

View File

@@ -7,6 +7,7 @@ import { Decoration, DecorationSet, EditorView } from "prosemirror-view";
import * as React from "react";
import ReactDOM from "react-dom";
import Extension from "../lib/Extension";
import { EventType } from "../types";
const MAX_MATCH = 500;
const OPEN_REGEX = /^\/(\w+)?$/;
@@ -65,7 +66,7 @@ export default class BlockMenuTrigger extends Extension {
new Plugin({
props: {
handleClick: () => {
this.options.onClose();
this.editor.events.emit(EventType.blockMenuClose);
return false;
},
handleKeyDown: (view, event) => {
@@ -79,9 +80,9 @@ export default class BlockMenuTrigger extends Extension {
const { pos } = view.state.selection.$from;
return run(view, pos, pos, OPEN_REGEX, (state, match) => {
if (match) {
this.options.onOpen(match[1]);
this.editor.events.emit(EventType.blockMenuOpen, match[1]);
} else {
this.options.onClose();
this.editor.events.emit(EventType.blockMenuClose);
}
return null;
});
@@ -125,7 +126,7 @@ export default class BlockMenuTrigger extends Extension {
decorations.push(
Decoration.widget(parent.pos, () => {
button.addEventListener("click", () => {
this.options.onOpen("");
this.editor.events.emit(EventType.blockMenuOpen, "");
});
return button;
})
@@ -176,7 +177,7 @@ export default class BlockMenuTrigger extends Extension {
state.selection.$from.parent.type.name === "paragraph" &&
!isInTable(state)
) {
this.options.onOpen(match[1]);
this.editor.events.emit(EventType.blockMenuOpen, match[1]);
}
return null;
}),
@@ -186,7 +187,7 @@ export default class BlockMenuTrigger extends Extension {
// /word<space>
new InputRule(CLOSE_REGEX, (state, match) => {
if (match) {
this.options.onClose();
this.editor.events.emit(EventType.blockMenuClose);
}
return null;
}),

View File

@@ -1,93 +0,0 @@
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;
}),
];
}
}

View File

@@ -16,8 +16,8 @@ export default class Keys extends Extension {
keys(): Record<string, Command> {
const onCancel = () => {
if (this.options.onCancel) {
this.options.onCancel();
if (this.editor.props.onCancel) {
this.editor.props.onCancel();
return true;
}
return false;
@@ -32,15 +32,15 @@ export default class Keys extends Extension {
"Mod-Escape": onCancel,
"Shift-Escape": onCancel,
"Mod-s": () => {
if (this.options.onSave) {
this.options.onSave();
if (this.editor.props.onSave) {
this.editor.props.onSave({ done: false });
return true;
}
return false;
},
"Mod-Enter": (state: EditorState) => {
if (!isInCode(state) && this.options.onSaveAndExit) {
this.options.onSaveAndExit();
if (!isInCode(state) && this.editor.props.onSave) {
this.editor.props.onSave({ done: true });
return true;
}
return false;
@@ -52,10 +52,6 @@ export default class Keys extends Extension {
return [
new Plugin({
props: {
handleDOMEvents: {
blur: this.options.onBlur,
focus: this.options.onFocus,
},
// we can't use the keys bindings for this as we want to preventDefault
// on the original keyboard event when handled
handleKeyDown: (view, event) => {