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

@@ -33,12 +33,13 @@ import rust from "refractor/lang/rust";
import sql from "refractor/lang/sql";
import typescript from "refractor/lang/typescript";
import yaml from "refractor/lang/yaml";
import { Dictionary } from "~/hooks/useDictionary";
import toggleBlockType from "../commands/toggleBlockType";
import { MarkdownSerializerState } from "../lib/markdown/serializer";
import Prism, { LANGUAGES } from "../plugins/Prism";
import isInCode from "../queries/isInCode";
import { Dispatch, ToastType } from "../types";
import { Dispatch } from "../types";
import Node from "./Node";
const PERSISTENCE_KEY = "rme-code-language";
@@ -67,6 +68,13 @@ const DEFAULT_LANGUAGE = "javascript";
].forEach(refractor.register);
export default class CodeFence extends Node {
constructor(options: {
dictionary: Dictionary;
onShowToast: (message: string) => void;
}) {
super(options);
}
get languageOptions() {
return Object.entries(LANGUAGES);
}
@@ -194,10 +202,7 @@ export default class CodeFence extends Node {
const node = view.state.doc.nodeAt(result.pos);
if (node) {
copy(node.textContent);
this.options.onShowToast(
this.options.dictionary.codeCopied,
ToastType.Info
);
this.options.onShowToast(this.options.dictionary.codeCopied);
}
}
};

View File

@@ -2,12 +2,17 @@ import nameToEmoji from "gemoji/name-to-emoji.json";
import Token from "markdown-it/lib/token";
import { InputRule } from "prosemirror-inputrules";
import { NodeSpec, Node as ProsemirrorNode, NodeType } from "prosemirror-model";
import { EditorState, TextSelection } from "prosemirror-state";
import { EditorState, TextSelection, Plugin } from "prosemirror-state";
import { MarkdownSerializerState } from "../lib/markdown/serializer";
import { run } from "../plugins/BlockMenuTrigger";
import isInCode from "../queries/isInCode";
import emojiRule from "../rules/emoji";
import { Dispatch } from "../types";
import { Dispatch, EventType } from "../types";
import Node from "./Node";
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 Emoji extends Node {
get name() {
return "emoji";
@@ -61,6 +66,57 @@ export default class Emoji extends Node {
return [emojiRule];
}
get plugins() {
return [
new Plugin({
props: {
handleClick: () => {
this.editor.events.emit(EventType.emojiMenuClose);
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.editor.events.emit(EventType.emojiMenuOpen, match[1]);
} else {
this.editor.events.emit(EventType.emojiMenuClose);
}
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;
},
},
}),
];
}
commands({ type }: { type: NodeType }) {
return (attrs: Record<string, string>) => (
state: EditorState,
@@ -100,6 +156,29 @@ export default class Emoji extends Node {
return tr;
}),
// main regex should match only:
// :word
new InputRule(OPEN_REGEX, (state, match) => {
if (
match &&
state.selection.$from.parent.type.name === "paragraph" &&
!isInCode(state)
) {
this.editor.events.emit(EventType.emojiMenuOpen, 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.editor.events.emit(EventType.emojiMenuClose);
}
return null;
}),
];
}

View File

@@ -14,7 +14,6 @@ import toggleBlockType from "../commands/toggleBlockType";
import { Command } from "../lib/Extension";
import headingToSlug, { headingToPersistenceKey } from "../lib/headingToSlug";
import { MarkdownSerializerState } from "../lib/markdown/serializer";
import { ToastType } from "../types";
import Node from "./Node";
export default class Heading extends Node {
@@ -180,10 +179,7 @@ export default class Heading extends Node {
const urlWithoutHash = window.location.href.split("#")[0];
copy(urlWithoutHash + hash);
this.options.onShowToast(
this.options.dictionary.linkCopied,
ToastType.Info
);
this.options.onShowToast(this.options.dictionary.linkCopied);
};
keys({ type, schema }: { type: NodeType; schema: Schema }) {

View File

@@ -5,6 +5,8 @@ import {
isTableSelected,
isRowSelected,
getCellsInColumn,
selectRow,
selectTable,
} from "prosemirror-utils";
import { DecorationSet, Decoration } from "prosemirror-view";
import Node from "./Node";
@@ -72,7 +74,7 @@ export default class TableCell extends Node {
grip.addEventListener("mousedown", (event) => {
event.preventDefault();
event.stopImmediatePropagation();
this.options.onSelectTable(state);
this.editor.view.dispatch(selectTable(state.tr));
});
return grip;
})
@@ -97,7 +99,7 @@ export default class TableCell extends Node {
grip.addEventListener("mousedown", (event) => {
event.preventDefault();
event.stopImmediatePropagation();
this.options.onSelectRow(index, state);
this.editor.view.dispatch(selectRow(index)(state.tr));
});
return grip;
})

View File

@@ -1,7 +1,11 @@
import Token from "markdown-it/lib/token";
import { NodeSpec } from "prosemirror-model";
import { Plugin } from "prosemirror-state";
import { isColumnSelected, getCellsInRow } from "prosemirror-utils";
import {
isColumnSelected,
getCellsInRow,
selectColumn,
} from "prosemirror-utils";
import { DecorationSet, Decoration } from "prosemirror-view";
import Node from "./Node";
@@ -72,7 +76,7 @@ export default class TableHeadCell extends Node {
grip.addEventListener("mousedown", (event) => {
event.preventDefault();
event.stopImmediatePropagation();
this.options.onSelectColumn(index, state);
this.editor.view.dispatch(selectColumn(index)(state.tr));
});
return grip;
})