fix: Add ability to convert between checklist and other types of list

This commit is contained in:
Tom Moor
2022-03-23 00:27:22 -07:00
parent 7f15eb287d
commit 8aa25fd7d6
21 changed files with 147 additions and 100 deletions

View File

@@ -1,8 +1,9 @@
import { NodeType } from "prosemirror-model"; import { NodeType } from "prosemirror-model";
import { EditorState, Transaction } from "prosemirror-state"; import { EditorState } from "prosemirror-state";
import { Dispatch } from "../types";
export default function backspaceToParagraph(type: NodeType) { export default function backspaceToParagraph(type: NodeType) {
return (state: EditorState, dispatch: (tr: Transaction) => void) => { return (state: EditorState, dispatch: Dispatch) => {
const { $from, from, to, empty } = state.selection; const { $from, from, to, empty } = state.selection;
// if the selection has anything in it then use standard delete behavior // if the selection has anything in it then use standard delete behavior

View File

@@ -0,0 +1,49 @@
import { EditorState } from "prosemirror-state";
import { liftTarget } from "prosemirror-transform";
import { Dispatch } from "../types";
const clearNodes = () => (state: EditorState, dispatch?: Dispatch) => {
const { tr } = state;
const { selection } = tr;
const { ranges } = selection;
if (!dispatch) {
return true;
}
ranges.forEach(({ $from, $to }) => {
state.doc.nodesBetween($from.pos, $to.pos, (node, pos) => {
if (node.type.isText) {
return;
}
const { doc, mapping } = tr;
const $mappedFrom = doc.resolve(mapping.map(pos));
const $mappedTo = doc.resolve(mapping.map(pos + node.nodeSize));
const nodeRange = $mappedFrom.blockRange($mappedTo);
if (!nodeRange) {
return;
}
const targetLiftDepth = liftTarget(nodeRange);
if (node.type.isTextblock) {
const { defaultType } = $mappedFrom.parent.contentMatchAt(
$mappedFrom.index()
);
tr.setNodeMarkup(nodeRange.start, defaultType);
}
if (targetLiftDepth || targetLiftDepth === 0) {
tr.lift(nodeRange, targetLiftDepth);
}
});
});
dispatch(tr);
return true;
};
export default clearNodes;

View File

@@ -17,13 +17,9 @@ limitations under the License.
// This file is based on the implementation found here: // This file is based on the implementation found here:
// https://bitbucket.org/atlassian/design-system-mirror/src/master/editor/editor-core/src/plugins/text-formatting/commands/text-formatting.ts // https://bitbucket.org/atlassian/design-system-mirror/src/master/editor/editor-core/src/plugins/text-formatting/commands/text-formatting.ts
import { import { Selection, EditorState, TextSelection } from "prosemirror-state";
Selection,
EditorState,
Transaction,
TextSelection,
} from "prosemirror-state";
import isMarkActive from "../queries/isMarkActive"; import isMarkActive from "../queries/isMarkActive";
import { Dispatch } from "../types";
function hasCode(state: EditorState, pos: number) { function hasCode(state: EditorState, pos: number) {
const { code_inline } = state.schema.marks; const { code_inline } = state.schema.marks;
@@ -35,7 +31,7 @@ function hasCode(state: EditorState, pos: number) {
} }
export default function moveLeft() { export default function moveLeft() {
return (state: EditorState, dispatch: (tr: Transaction) => void): boolean => { return (state: EditorState, dispatch: Dispatch): boolean => {
const { code_inline } = state.schema.marks; const { code_inline } = state.schema.marks;
const { empty, $cursor } = state.selection as TextSelection; const { empty, $cursor } = state.selection as TextSelection;
if (!empty || !$cursor) { if (!empty || !$cursor) {

View File

@@ -17,11 +17,12 @@ limitations under the License.
// This file is based on the implementation found here: // This file is based on the implementation found here:
// https://bitbucket.org/atlassian/design-system-mirror/src/master/editor/editor-core/src/plugins/text-formatting/commands/text-formatting.ts // https://bitbucket.org/atlassian/design-system-mirror/src/master/editor/editor-core/src/plugins/text-formatting/commands/text-formatting.ts
import { EditorState, Transaction, TextSelection } from "prosemirror-state"; import { EditorState, TextSelection } from "prosemirror-state";
import isMarkActive from "../queries/isMarkActive"; import isMarkActive from "../queries/isMarkActive";
import { Dispatch } from "../types";
export default function moveRight() { export default function moveRight() {
return (state: EditorState, dispatch: (tr: Transaction) => void): boolean => { return (state: EditorState, dispatch: Dispatch): boolean => {
const { code_inline } = state.schema.marks; const { code_inline } = state.schema.marks;
const { empty, $cursor } = state.selection as TextSelection; const { empty, $cursor } = state.selection as TextSelection;
if (!empty || !$cursor) { if (!empty || !$cursor) {

View File

@@ -1,10 +1,11 @@
import { NodeType } from "prosemirror-model"; import { NodeType } from "prosemirror-model";
import { EditorState, TextSelection, Transaction } from "prosemirror-state"; import { EditorState, TextSelection } from "prosemirror-state";
import { findBlockNodes } from "prosemirror-utils"; import { findBlockNodes } from "prosemirror-utils";
import findCollapsedNodes from "../queries/findCollapsedNodes"; import findCollapsedNodes from "../queries/findCollapsedNodes";
import { Dispatch } from "../types";
export default function splitHeading(type: NodeType) { export default function splitHeading(type: NodeType) {
return (state: EditorState, dispatch: (tr: Transaction) => void): boolean => { return (state: EditorState, dispatch: Dispatch): boolean => {
const { $from, from, $to, to } = state.selection; const { $from, from, $to, to } = state.selection;
// check we're in a matching heading node // check we're in a matching heading node

View File

@@ -1,14 +1,15 @@
import { setBlockType } from "prosemirror-commands"; import { setBlockType } from "prosemirror-commands";
import { NodeType } from "prosemirror-model"; import { NodeType } from "prosemirror-model";
import { EditorState, Transaction } from "prosemirror-state"; import { EditorState } from "prosemirror-state";
import isNodeActive from "../queries/isNodeActive"; import isNodeActive from "../queries/isNodeActive";
import { Dispatch } from "../types";
export default function toggleBlockType( export default function toggleBlockType(
type: NodeType, type: NodeType,
toggleType: NodeType, toggleType: NodeType,
attrs = {} attrs = {}
) { ) {
return (state: EditorState, dispatch: (tr: Transaction) => void) => { return (state: EditorState, dispatch?: Dispatch) => {
const isActive = isNodeActive(type, attrs)(state); const isActive = isNodeActive(type, attrs)(state);
if (isActive) { if (isActive) {

View File

@@ -1,14 +1,18 @@
import { NodeType } from "prosemirror-model"; import { NodeType } from "prosemirror-model";
import { wrapInList, liftListItem } from "prosemirror-schema-list"; import { wrapInList, liftListItem } from "prosemirror-schema-list";
import { EditorState, Transaction } from "prosemirror-state"; import { EditorState } from "prosemirror-state";
import { findParentNode } from "prosemirror-utils"; import { findParentNode } from "prosemirror-utils";
import chainTransactions from "../lib/chainTransactions";
import isList from "../queries/isList"; import isList from "../queries/isList";
import { Dispatch } from "../types";
import clearNodes from "./clearNodes";
export default function toggleList(listType: NodeType, itemType: NodeType) { export default function toggleList(listType: NodeType, itemType: NodeType) {
return (state: EditorState, dispatch: (tr: Transaction) => void) => { return (state: EditorState, dispatch?: Dispatch) => {
const { schema, selection } = state; const { schema, selection } = state;
const { $from, $to } = selection; const { $from, $to } = selection;
const range = $from.blockRange($to); const range = $from.blockRange($to);
const { tr } = state;
if (!range) { if (!range) {
return false; return false;
@@ -27,7 +31,6 @@ export default function toggleList(listType: NodeType, itemType: NodeType) {
isList(parentList.node, schema) && isList(parentList.node, schema) &&
listType.validContent(parentList.node.content) listType.validContent(parentList.node.content)
) { ) {
const { tr } = state;
tr.setNodeMarkup(parentList.pos, listType); tr.setNodeMarkup(parentList.pos, listType);
if (dispatch) { if (dispatch) {
@@ -38,6 +41,15 @@ export default function toggleList(listType: NodeType, itemType: NodeType) {
} }
} }
return wrapInList(listType)(state, dispatch); const canWrapInList = wrapInList(listType)(state);
if (canWrapInList) {
return wrapInList(listType)(state, dispatch);
}
return chainTransactions(clearNodes(), wrapInList(listType))(
state,
dispatch
);
}; };
} }

View File

@@ -1,13 +1,14 @@
import { wrapIn, lift } from "prosemirror-commands"; import { wrapIn, lift } from "prosemirror-commands";
import { NodeType } from "prosemirror-model"; import { NodeType } from "prosemirror-model";
import { EditorState, Transaction } from "prosemirror-state"; import { EditorState } from "prosemirror-state";
import isNodeActive from "../queries/isNodeActive"; import isNodeActive from "../queries/isNodeActive";
import { Dispatch } from "../types";
export default function toggleWrap( export default function toggleWrap(
type: NodeType, type: NodeType,
attrs?: Record<string, any> attrs?: Record<string, any>
) { ) {
return (state: EditorState, dispatch: (tr: Transaction) => void) => { return (state: EditorState, dispatch?: Dispatch) => {
const isActive = isNodeActive(type)(state); const isActive = isNodeActive(type)(state);
if (isActive) { if (isActive) {

View File

@@ -1,22 +1,16 @@
import { PluginSimple } from "markdown-it"; import { PluginSimple } from "markdown-it";
import { InputRule } from "prosemirror-inputrules"; import { InputRule } from "prosemirror-inputrules";
import { NodeType, MarkType, Schema } from "prosemirror-model"; import { NodeType, MarkType, Schema } from "prosemirror-model";
import { EditorState, Plugin, Transaction } from "prosemirror-state"; import { EditorState, Plugin } from "prosemirror-state";
import { EditorView } from "prosemirror-view"; import { EditorView } from "prosemirror-view";
import { Editor } from "../../../app/editor"; import { Editor } from "../../../app/editor";
import { Dispatch } from "../types";
export type Command = ( export type Command = (state: EditorState, dispatch: Dispatch) => boolean;
state: EditorState,
dispatch: (tr: Transaction) => void
) => boolean;
export type CommandFactory = ( export type CommandFactory = (
attrs?: Record<string, any> attrs?: Record<string, any>
) => ( ) => (state: EditorState, dispatch: Dispatch, view: EditorView) => boolean;
state: EditorState,
dispatch: (tr: Transaction) => void,
view: EditorView
) => boolean;
export default class Extension { export default class Extension {
options: any; options: any;

View File

@@ -0,0 +1,18 @@
import { EditorState, Transaction } from "prosemirror-state";
import { Dispatch } from "../types";
export default function chainTransactions(
...commands: ((state: EditorState, dispatch?: Dispatch) => boolean)[]
) {
return (state: EditorState, dispatch?: Dispatch): boolean => {
const dispatcher = (tr: Transaction): void => {
state = state.apply(tr);
dispatch?.(tr);
};
const last = commands.pop();
const reduced = commands.reduce((result, command) => {
return result || command(state, dispatcher);
}, false);
return reduced && last !== undefined && last(state, dispatch);
};
}

View File

@@ -9,12 +9,13 @@ import {
Node, Node,
Mark as ProsemirrorMark, Mark as ProsemirrorMark,
} from "prosemirror-model"; } from "prosemirror-model";
import { Transaction, EditorState, Plugin } from "prosemirror-state"; import { EditorState, Plugin } from "prosemirror-state";
import { Decoration, DecorationSet } from "prosemirror-view"; import { Decoration, DecorationSet } from "prosemirror-view";
import * as React from "react"; import * as React from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import { isInternalUrl } from "../../utils/urls"; import { isInternalUrl } from "../../utils/urls";
import findLinkNodes from "../queries/findLinkNodes"; import findLinkNodes from "../queries/findLinkNodes";
import { Dispatch } from "../types";
import Mark from "./Mark"; import Mark from "./Mark";
const LINK_INPUT_REGEX = /\[([^[]+)]\((\S+)\)$/; const LINK_INPUT_REGEX = /\[([^[]+)]\((\S+)\)$/;
@@ -103,7 +104,7 @@ export default class Link extends Mark {
keys({ type }: { type: MarkType }) { keys({ type }: { type: MarkType }) {
return { return {
"Mod-k": (state: EditorState, dispatch: (tr: Transaction) => void) => { "Mod-k": (state: EditorState, dispatch: Dispatch) => {
if (state.selection.empty) { if (state.selection.empty) {
this.options.onKeyboardShortcut(); this.options.onKeyboardShortcut();
return true; return true;

View File

@@ -1,9 +1,10 @@
import { wrappingInputRule } from "prosemirror-inputrules"; import { wrappingInputRule } from "prosemirror-inputrules";
import { NodeSpec, Node as ProsemirrorNode, NodeType } from "prosemirror-model"; import { NodeSpec, Node as ProsemirrorNode, NodeType } from "prosemirror-model";
import { EditorState, Transaction } from "prosemirror-state"; import { EditorState } from "prosemirror-state";
import toggleWrap from "../commands/toggleWrap"; import toggleWrap from "../commands/toggleWrap";
import { MarkdownSerializerState } from "../lib/markdown/serializer"; import { MarkdownSerializerState } from "../lib/markdown/serializer";
import isNodeActive from "../queries/isNodeActive"; import isNodeActive from "../queries/isNodeActive";
import { Dispatch } from "../types";
import Node from "./Node"; import Node from "./Node";
export default class Blockquote extends Node { export default class Blockquote extends Node {
@@ -37,10 +38,7 @@ export default class Blockquote extends Node {
return { return {
"Ctrl->": toggleWrap(type), "Ctrl->": toggleWrap(type),
"Mod-]": toggleWrap(type), "Mod-]": toggleWrap(type),
"Shift-Enter": ( "Shift-Enter": (state: EditorState, dispatch: Dispatch) => {
state: EditorState,
dispatch: (tr: Transaction) => void
) => {
if (!isNodeActive(type)(state)) { if (!isNodeActive(type)(state)) {
return false; return false;
} }

View File

@@ -38,7 +38,7 @@ import toggleBlockType from "../commands/toggleBlockType";
import { MarkdownSerializerState } from "../lib/markdown/serializer"; import { MarkdownSerializerState } from "../lib/markdown/serializer";
import Prism, { LANGUAGES } from "../plugins/Prism"; import Prism, { LANGUAGES } from "../plugins/Prism";
import isInCode from "../queries/isInCode"; import isInCode from "../queries/isInCode";
import { ToastType } from "../types"; import { Dispatch, ToastType } from "../types";
import Node from "./Node"; import Node from "./Node";
const PERSISTENCE_KEY = "rme-code-language"; const PERSISTENCE_KEY = "rme-code-language";
@@ -146,10 +146,7 @@ export default class CodeFence extends Node {
keys({ type, schema }: { type: NodeType; schema: Schema }) { keys({ type, schema }: { type: NodeType; schema: Schema }) {
return { return {
"Shift-Ctrl-\\": toggleBlockType(type, schema.nodes.paragraph), "Shift-Ctrl-\\": toggleBlockType(type, schema.nodes.paragraph),
"Shift-Enter": ( "Shift-Enter": (state: EditorState, dispatch: Dispatch) => {
state: EditorState,
dispatch: (tr: Transaction) => void
) => {
if (!isInCode(state)) { if (!isInCode(state)) {
return false; return false;
} }
@@ -172,7 +169,7 @@ export default class CodeFence extends Node {
dispatch(tr.insertText(newText, selection.from, selection.to)); dispatch(tr.insertText(newText, selection.from, selection.to));
return true; return true;
}, },
Tab: (state: EditorState, dispatch: (tr: Transaction) => void) => { Tab: (state: EditorState, dispatch: Dispatch) => {
if (!isInCode(state)) { if (!isInCode(state)) {
return false; return false;
} }

View File

@@ -1,11 +1,11 @@
import Token from "markdown-it/lib/token"; import Token from "markdown-it/lib/token";
import { NodeSpec, NodeType, Node as ProsemirrorNode } from "prosemirror-model"; import { NodeSpec, NodeType, Node as ProsemirrorNode } from "prosemirror-model";
import { EditorState, Transaction } from "prosemirror-state"; import { EditorState } from "prosemirror-state";
import * as React from "react"; import * as React from "react";
import DisabledEmbed from "../components/DisabledEmbed"; import DisabledEmbed from "../components/DisabledEmbed";
import { MarkdownSerializerState } from "../lib/markdown/serializer"; import { MarkdownSerializerState } from "../lib/markdown/serializer";
import embedsRule from "../rules/embeds"; import embedsRule from "../rules/embeds";
import { ComponentProps } from "../types"; import { ComponentProps, Dispatch } from "../types";
import Node from "./Node"; import Node from "./Node";
const cache = {}; const cache = {};
@@ -117,7 +117,7 @@ export default class Embed extends Node {
commands({ type }: { type: NodeType }) { commands({ type }: { type: NodeType }) {
return (attrs: Record<string, any>) => ( return (attrs: Record<string, any>) => (
state: EditorState, state: EditorState,
dispatch: (tr: Transaction) => void dispatch: Dispatch
) => { ) => {
dispatch( dispatch(
state.tr.replaceSelectionWith(type.create(attrs)).scrollIntoView() state.tr.replaceSelectionWith(type.create(attrs)).scrollIntoView()

View File

@@ -2,9 +2,10 @@ import nameToEmoji from "gemoji/name-to-emoji.json";
import Token from "markdown-it/lib/token"; import Token from "markdown-it/lib/token";
import { InputRule } from "prosemirror-inputrules"; import { InputRule } from "prosemirror-inputrules";
import { NodeSpec, Node as ProsemirrorNode, NodeType } from "prosemirror-model"; import { NodeSpec, Node as ProsemirrorNode, NodeType } from "prosemirror-model";
import { EditorState, TextSelection, Transaction } from "prosemirror-state"; import { EditorState, TextSelection } from "prosemirror-state";
import { MarkdownSerializerState } from "../lib/markdown/serializer"; import { MarkdownSerializerState } from "../lib/markdown/serializer";
import emojiRule from "../rules/emoji"; import emojiRule from "../rules/emoji";
import { Dispatch } from "../types";
import Node from "./Node"; import Node from "./Node";
export default class Emoji extends Node { export default class Emoji extends Node {
@@ -63,7 +64,7 @@ export default class Emoji extends Node {
commands({ type }: { type: NodeType }) { commands({ type }: { type: NodeType }) {
return (attrs: Record<string, string>) => ( return (attrs: Record<string, string>) => (
state: EditorState, state: EditorState,
dispatch: (tr: Transaction) => void dispatch: Dispatch
) => { ) => {
const { selection } = state; const { selection } = state;
const position = const position =

View File

@@ -1,8 +1,9 @@
import { NodeSpec, NodeType } from "prosemirror-model"; import { NodeSpec, NodeType } from "prosemirror-model";
import { EditorState, Transaction } from "prosemirror-state"; import { EditorState } from "prosemirror-state";
import { isInTable } from "prosemirror-tables"; import { isInTable } from "prosemirror-tables";
import { MarkdownSerializerState } from "../lib/markdown/serializer"; import { MarkdownSerializerState } from "../lib/markdown/serializer";
import breakRule from "../rules/breaks"; import breakRule from "../rules/breaks";
import { Dispatch } from "../types";
import Node from "./Node"; import Node from "./Node";
export default class HardBreak extends Node { export default class HardBreak extends Node {
@@ -27,7 +28,7 @@ export default class HardBreak extends Node {
} }
commands({ type }: { type: NodeType }) { commands({ type }: { type: NodeType }) {
return () => (state: EditorState, dispatch: (tr: Transaction) => void) => { return () => (state: EditorState, dispatch: Dispatch) => {
dispatch(state.tr.replaceSelectionWith(type.create()).scrollIntoView()); dispatch(state.tr.replaceSelectionWith(type.create()).scrollIntoView());
return true; return true;
}; };
@@ -35,10 +36,7 @@ export default class HardBreak extends Node {
keys({ type }: { type: NodeType }) { keys({ type }: { type: NodeType }) {
return { return {
"Shift-Enter": ( "Shift-Enter": (state: EditorState, dispatch: Dispatch) => {
state: EditorState,
dispatch: (tr: Transaction) => void
) => {
if (!isInTable(state)) { if (!isInTable(state)) {
return false; return false;
} }

View File

@@ -1,8 +1,9 @@
import Token from "markdown-it/lib/token"; import Token from "markdown-it/lib/token";
import { InputRule } from "prosemirror-inputrules"; import { InputRule } from "prosemirror-inputrules";
import { NodeSpec, NodeType, Node as ProsemirrorNode } from "prosemirror-model"; import { NodeSpec, NodeType, Node as ProsemirrorNode } from "prosemirror-model";
import { EditorState, Transaction } from "prosemirror-state"; import { EditorState } from "prosemirror-state";
import { MarkdownSerializerState } from "../lib/markdown/serializer"; import { MarkdownSerializerState } from "../lib/markdown/serializer";
import { Dispatch } from "../types";
import Node from "./Node"; import Node from "./Node";
export default class HorizontalRule extends Node { export default class HorizontalRule extends Node {
@@ -31,7 +32,7 @@ export default class HorizontalRule extends Node {
commands({ type }: { type: NodeType }) { commands({ type }: { type: NodeType }) {
return (attrs: Record<string, any>) => ( return (attrs: Record<string, any>) => (
state: EditorState, state: EditorState,
dispatch: (tr: Transaction) => void dispatch: Dispatch
) => { ) => {
dispatch( dispatch(
state.tr.replaceSelectionWith(type.create(attrs)).scrollIntoView() state.tr.replaceSelectionWith(type.create(attrs)).scrollIntoView()
@@ -42,7 +43,7 @@ export default class HorizontalRule extends Node {
keys({ type }: { type: NodeType }) { keys({ type }: { type: NodeType }) {
return { return {
"Mod-_": (state: EditorState, dispatch: (tr: Transaction) => void) => { "Mod-_": (state: EditorState, dispatch: Dispatch) => {
dispatch(state.tr.replaceSelectionWith(type.create()).scrollIntoView()); dispatch(state.tr.replaceSelectionWith(type.create()).scrollIntoView());
return true; return true;
}, },

View File

@@ -7,7 +7,6 @@ import {
TextSelection, TextSelection,
NodeSelection, NodeSelection,
EditorState, EditorState,
Transaction,
} from "prosemirror-state"; } from "prosemirror-state";
import * as React from "react"; import * as React from "react";
import ImageZoom from "react-medium-image-zoom"; import ImageZoom from "react-medium-image-zoom";
@@ -16,7 +15,7 @@ import getDataTransferFiles from "../../utils/getDataTransferFiles";
import insertFiles, { Options } from "../commands/insertFiles"; import insertFiles, { Options } from "../commands/insertFiles";
import { MarkdownSerializerState } from "../lib/markdown/serializer"; import { MarkdownSerializerState } from "../lib/markdown/serializer";
import uploadPlaceholderPlugin from "../lib/uploadPlaceholder"; import uploadPlaceholderPlugin from "../lib/uploadPlaceholder";
import { ComponentProps } from "../types"; import { ComponentProps, Dispatch } from "../types";
import Node from "./Node"; import Node from "./Node";
/** /**
@@ -366,17 +365,11 @@ export default class Image extends Node {
return true; return true;
}, },
deleteImage: () => ( deleteImage: () => (state: EditorState, dispatch: Dispatch) => {
state: EditorState,
dispatch: (tr: Transaction) => void
) => {
dispatch(state.tr.deleteSelection()); dispatch(state.tr.deleteSelection());
return true; return true;
}, },
alignRight: () => ( alignRight: () => (state: EditorState, dispatch: Dispatch) => {
state: EditorState,
dispatch: (tr: Transaction) => void
) => {
if (!(state.selection instanceof NodeSelection)) { if (!(state.selection instanceof NodeSelection)) {
return false; return false;
} }
@@ -389,10 +382,7 @@ export default class Image extends Node {
dispatch(state.tr.setNodeMarkup(selection.from, undefined, attrs)); dispatch(state.tr.setNodeMarkup(selection.from, undefined, attrs));
return true; return true;
}, },
alignLeft: () => ( alignLeft: () => (state: EditorState, dispatch: Dispatch) => {
state: EditorState,
dispatch: (tr: Transaction) => void
) => {
if (!(state.selection instanceof NodeSelection)) { if (!(state.selection instanceof NodeSelection)) {
return false; return false;
} }
@@ -436,10 +426,7 @@ export default class Image extends Node {
inputElement.click(); inputElement.click();
return true; return true;
}, },
alignCenter: () => ( alignCenter: () => (state: EditorState, dispatch: Dispatch) => {
state: EditorState,
dispatch: (tr: Transaction) => void
) => {
if (!(state.selection instanceof NodeSelection)) { if (!(state.selection instanceof NodeSelection)) {
return false; return false;
} }
@@ -450,7 +437,7 @@ export default class Image extends Node {
}, },
createImage: (attrs: Record<string, any>) => ( createImage: (attrs: Record<string, any>) => (
state: EditorState, state: EditorState,
dispatch: (tr: Transaction) => void dispatch: Dispatch
) => { ) => {
const { selection } = state; const { selection } = state;
const position = const position =

View File

@@ -16,6 +16,7 @@ import { MarkdownSerializerState } from "../lib/markdown/serializer";
import getParentListItem from "../queries/getParentListItem"; import getParentListItem from "../queries/getParentListItem";
import isInList from "../queries/isInList"; import isInList from "../queries/isInList";
import isList from "../queries/isList"; import isList from "../queries/isList";
import { Dispatch } from "../types";
import Node from "./Node"; import Node from "./Node";
export default class ListItem extends Node { export default class ListItem extends Node {
@@ -199,10 +200,7 @@ export default class ListItem extends Node {
"Shift-Tab": liftListItem(type), "Shift-Tab": liftListItem(type),
"Mod-]": sinkListItem(type), "Mod-]": sinkListItem(type),
"Mod-[": liftListItem(type), "Mod-[": liftListItem(type),
"Shift-Enter": ( "Shift-Enter": (state: EditorState, dispatch: Dispatch) => {
state: EditorState,
dispatch: (tr: Transaction) => void
) => {
if (!isInList(state)) { if (!isInList(state)) {
return false; return false;
} }
@@ -214,10 +212,7 @@ export default class ListItem extends Node {
dispatch(tr.split(selection.to)); dispatch(tr.split(selection.to));
return true; return true;
}, },
"Alt-ArrowUp": ( "Alt-ArrowUp": (state: EditorState, dispatch: Dispatch) => {
state: EditorState,
dispatch: (tr: Transaction) => void
) => {
if (!state.selection.empty) { if (!state.selection.empty) {
return false; return false;
} }
@@ -248,10 +243,7 @@ export default class ListItem extends Node {
); );
return true; return true;
}, },
"Alt-ArrowDown": ( "Alt-ArrowDown": (state: EditorState, dispatch: Dispatch) => {
state: EditorState,
dispatch: (tr: Transaction) => void
) => {
if (!state.selection.empty) { if (!state.selection.empty) {
return false; return false;
} }

View File

@@ -1,10 +1,5 @@
import { NodeSpec, Node as ProsemirrorNode, Schema } from "prosemirror-model"; import { NodeSpec, Node as ProsemirrorNode, Schema } from "prosemirror-model";
import { import { EditorState, Plugin, TextSelection } from "prosemirror-state";
EditorState,
Plugin,
TextSelection,
Transaction,
} from "prosemirror-state";
import { import {
addColumnAfter, addColumnAfter,
addColumnBefore, addColumnBefore,
@@ -27,6 +22,7 @@ import {
import { Decoration, DecorationSet } from "prosemirror-view"; import { Decoration, DecorationSet } from "prosemirror-view";
import { MarkdownSerializerState } from "../lib/markdown/serializer"; import { MarkdownSerializerState } from "../lib/markdown/serializer";
import tablesRule from "../rules/tables"; import tablesRule from "../rules/tables";
import { Dispatch } from "../types";
import Node from "./Node"; import Node from "./Node";
export default class Table extends Node { export default class Table extends Node {
@@ -67,7 +63,7 @@ export default class Table extends Node {
}: { }: {
rowsCount: number; rowsCount: number;
colsCount: number; colsCount: number;
}) => (state: EditorState, dispatch: (tr: Transaction) => void) => { }) => (state: EditorState, dispatch: Dispatch) => {
const offset = state.tr.selection.anchor + 1; const offset = state.tr.selection.anchor + 1;
const nodes = createTable(schema, rowsCount, colsCount); const nodes = createTable(schema, rowsCount, colsCount);
const tr = state.tr.replaceSelectionWith(nodes).scrollIntoView(); const tr = state.tr.replaceSelectionWith(nodes).scrollIntoView();
@@ -83,7 +79,7 @@ export default class Table extends Node {
}: { }: {
index: number; index: number;
alignment: string; alignment: string;
}) => (state: EditorState, dispatch: (tr: Transaction) => void) => { }) => (state: EditorState, dispatch: Dispatch) => {
const cells = getCellsInColumn(index)(state.selection) || []; const cells = getCellsInColumn(index)(state.selection) || [];
let transaction = state.tr; let transaction = state.tr;
cells.forEach(({ pos }) => { cells.forEach(({ pos }) => {
@@ -99,7 +95,7 @@ export default class Table extends Node {
deleteColumn: () => deleteColumn, deleteColumn: () => deleteColumn,
addRowAfter: ({ index }: { index: number }) => ( addRowAfter: ({ index }: { index: number }) => (
state: EditorState, state: EditorState,
dispatch: (tr: Transaction) => void dispatch: Dispatch
) => { ) => {
if (index === 0) { if (index === 0) {
// A little hack to avoid cloning the heading row by cloning the row // A little hack to avoid cloning the heading row by cloning the row
@@ -123,7 +119,7 @@ export default class Table extends Node {
return { return {
Tab: goToNextCell(1), Tab: goToNextCell(1),
"Shift-Tab": goToNextCell(-1), "Shift-Tab": goToNextCell(-1),
Enter: (state: EditorState, dispatch: (tr: Transaction) => void) => { Enter: (state: EditorState, dispatch: Dispatch) => {
if (!isInTable(state)) { if (!isInTable(state)) {
return false; return false;
} }

View File

@@ -1,5 +1,5 @@
import { Node as ProsemirrorNode } from "prosemirror-model"; import { Node as ProsemirrorNode } from "prosemirror-model";
import { EditorState } from "prosemirror-state"; import { EditorState, Transaction } from "prosemirror-state";
import * as React from "react"; import * as React from "react";
import { DefaultTheme } from "styled-components"; import { DefaultTheme } from "styled-components";
@@ -34,3 +34,5 @@ export type ComponentProps = {
isEditable: boolean; isEditable: boolean;
getPos: () => number; getPos: () => number;
}; };
export type Dispatch = (tr: Transaction) => void;