@@ -1,19 +1,32 @@
|
||||
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, Plugin } from "prosemirror-state";
|
||||
import { run } from "../extensions/BlockMenuTrigger";
|
||||
import {
|
||||
NodeSpec,
|
||||
Node as ProsemirrorNode,
|
||||
NodeType,
|
||||
Schema,
|
||||
} from "prosemirror-model";
|
||||
import { EditorState, TextSelection } from "prosemirror-state";
|
||||
import Suggestion from "../extensions/Suggestion";
|
||||
import { MarkdownSerializerState } from "../lib/markdown/serializer";
|
||||
import isInCode from "../queries/isInCode";
|
||||
import { SuggestionsMenuType } from "../plugins/Suggestions";
|
||||
import emojiRule from "../rules/emoji";
|
||||
import { Dispatch, EventType } from "../types";
|
||||
import Node from "./Node";
|
||||
import { Dispatch } from "../types";
|
||||
|
||||
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 Suggestion {
|
||||
get type() {
|
||||
return "node";
|
||||
}
|
||||
|
||||
get defaultOptions() {
|
||||
return {
|
||||
type: SuggestionsMenuType.Emoji,
|
||||
openRegex: /(?:^|\s):([0-9a-zA-Z_+-]+)?$/,
|
||||
closeRegex: /(?:^|\s):(([0-9a-zA-Z_+-]*\s+)|(\s+[0-9a-zA-Z_+-]+)|[^0-9a-zA-Z_+-]+)$/,
|
||||
enabledInTable: true,
|
||||
};
|
||||
}
|
||||
|
||||
export default class Emoji extends Node {
|
||||
get name() {
|
||||
return "emoji";
|
||||
}
|
||||
@@ -63,58 +76,7 @@ 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
|
||||
match ? true : null
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
},
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
commands({ type }: { type: NodeType }) {
|
||||
commands({ type }: { type: NodeType; schema: Schema }) {
|
||||
return (attrs: Record<string, string>) => (
|
||||
state: EditorState,
|
||||
dispatch: Dispatch
|
||||
@@ -135,50 +97,6 @@ export default class Emoji extends Node {
|
||||
};
|
||||
}
|
||||
|
||||
inputRules({ type }: { type: NodeType }): InputRule[] {
|
||||
return [
|
||||
new InputRule(/^:([a-zA-Z0-9_+-]+):$/, (state, match, start, end) => {
|
||||
const [okay, markup] = match;
|
||||
const { tr } = state;
|
||||
if (okay) {
|
||||
tr.replaceWith(
|
||||
start - 1,
|
||||
end,
|
||||
type.create({
|
||||
"data-name": markup,
|
||||
markup,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
toMarkdown(state: MarkdownSerializerState, node: ProsemirrorNode) {
|
||||
const name = node.attrs["data-name"];
|
||||
if (name) {
|
||||
|
||||
@@ -15,6 +15,7 @@ import toggleBlockType from "../commands/toggleBlockType";
|
||||
import { Command } from "../lib/Extension";
|
||||
import headingToSlug, { headingToPersistenceKey } from "../lib/headingToSlug";
|
||||
import { MarkdownSerializerState } from "../lib/markdown/serializer";
|
||||
import { FoldingHeadersPlugin } from "../plugins/FoldingHeaders";
|
||||
import Node from "./Node";
|
||||
|
||||
export default class Heading extends Node {
|
||||
@@ -264,7 +265,7 @@ export default class Heading extends Node {
|
||||
},
|
||||
});
|
||||
|
||||
return [plugin];
|
||||
return [new FoldingHeadersPlugin(this.editor.props.id), plugin];
|
||||
}
|
||||
|
||||
inputRules({ type }: { type: NodeType }) {
|
||||
|
||||
@@ -1,19 +1,32 @@
|
||||
import Token from "markdown-it/lib/token";
|
||||
import { InputRule } from "prosemirror-inputrules";
|
||||
import { NodeSpec, Node as ProsemirrorNode, NodeType } from "prosemirror-model";
|
||||
import { EditorState, TextSelection, Plugin } from "prosemirror-state";
|
||||
import { run } from "../extensions/BlockMenuTrigger";
|
||||
import {
|
||||
NodeSpec,
|
||||
Node as ProsemirrorNode,
|
||||
NodeType,
|
||||
Schema,
|
||||
} from "prosemirror-model";
|
||||
import { EditorState, TextSelection } from "prosemirror-state";
|
||||
import Suggestion from "../extensions/Suggestion";
|
||||
import { MarkdownSerializerState } from "../lib/markdown/serializer";
|
||||
import isInCode from "../queries/isInCode";
|
||||
import { SuggestionsMenuType } from "../plugins/Suggestions";
|
||||
import mentionRule from "../rules/mention";
|
||||
import { Dispatch, EventType } from "../types";
|
||||
import Node from "./Node";
|
||||
import { Dispatch } from "../types";
|
||||
|
||||
// ported from https://github.com/tc39/proposal-regexp-unicode-property-escapes#unicode-aware-version-of-w
|
||||
const OPEN_REGEX = /(?:^|\s)@([\p{L}\p{M}\d]+)?$/u;
|
||||
const CLOSE_REGEX = /(?:^|\s)@(([\p{L}\p{M}\d]*\s+)|(\s+[\p{L}\p{M}\d]+))$/u;
|
||||
export default class Mention extends Suggestion {
|
||||
get type() {
|
||||
return "node";
|
||||
}
|
||||
|
||||
get defaultOptions() {
|
||||
return {
|
||||
type: SuggestionsMenuType.Mention,
|
||||
// ported from https://github.com/tc39/proposal-regexp-unicode-property-escapes#unicode-aware-version-of-w
|
||||
openRegex: /(?:^|\s)@([\p{L}\p{M}\d]+)?$/u,
|
||||
closeRegex: /(?:^|\s)@(([\p{L}\p{M}\d]*\s+)|(\s+[\p{L}\p{M}\d]+))$/u,
|
||||
enabledInTable: true,
|
||||
};
|
||||
}
|
||||
|
||||
export default class Mention extends Node {
|
||||
get name() {
|
||||
return "mention";
|
||||
}
|
||||
@@ -66,61 +79,7 @@ export default class Mention extends Node {
|
||||
return [mentionRule];
|
||||
}
|
||||
|
||||
get plugins() {
|
||||
return [
|
||||
new Plugin({
|
||||
props: {
|
||||
handleClick: () => {
|
||||
this.editor.events.emit(EventType.mentionMenuClose);
|
||||
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.mentionMenuOpen,
|
||||
match[1]
|
||||
);
|
||||
} else {
|
||||
this.editor.events.emit(EventType.mentionMenuClose);
|
||||
}
|
||||
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
|
||||
match ? true : null
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
},
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
commands({ type }: { type: NodeType }) {
|
||||
commands({ type }: { type: NodeType; schema: Schema }) {
|
||||
return (attrs: Record<string, string>) => (
|
||||
state: EditorState,
|
||||
dispatch: Dispatch
|
||||
@@ -141,33 +100,6 @@ export default class Mention extends Node {
|
||||
};
|
||||
}
|
||||
|
||||
inputRules(): InputRule[] {
|
||||
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.editor.events.emit(EventType.mentionMenuOpen, 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.mentionMenuClose);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
toMarkdown(state: MarkdownSerializerState, node: ProsemirrorNode) {
|
||||
const mType = node.attrs.type;
|
||||
const mId = node.attrs.modelId;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import BlockMenuTrigger from "../extensions/BlockMenuTrigger";
|
||||
import BlockMenu from "../extensions/BlockMenu";
|
||||
import ClipboardTextSerializer from "../extensions/ClipboardTextSerializer";
|
||||
import DateTime from "../extensions/DateTime";
|
||||
import Folding from "../extensions/Folding";
|
||||
import History from "../extensions/History";
|
||||
import Keys from "../extensions/Keys";
|
||||
import MaxLength from "../extensions/MaxLength";
|
||||
@@ -106,8 +105,7 @@ export const richExtensions: Nodes = [
|
||||
TableRow,
|
||||
Highlight,
|
||||
TemplatePlaceholder,
|
||||
Folding,
|
||||
BlockMenuTrigger,
|
||||
BlockMenu,
|
||||
Math,
|
||||
MathBlock,
|
||||
PreventTab,
|
||||
|
||||
Reference in New Issue
Block a user