diff --git a/src/rich-text/commands/index.ts b/src/rich-text/commands/index.ts index 8dcab672..f5de8b39 100644 --- a/src/rich-text/commands/index.ts +++ b/src/rich-text/commands/index.ts @@ -1,7 +1,12 @@ import { setBlockType, toggleMark, wrapIn } from "prosemirror-commands"; import { redo, undo } from "prosemirror-history"; import { Mark, MarkType, NodeType, Schema } from "prosemirror-model"; -import { EditorState, TextSelection, Transaction } from "prosemirror-state"; +import { + Command, + EditorState, + TextSelection, + Transaction, +} from "prosemirror-state"; import { liftTarget } from "prosemirror-transform"; import { EditorView } from "prosemirror-view"; import { @@ -35,8 +40,20 @@ import { _t } from "../../shared/localization"; export * from "./tables"; -//TODO -function toggleWrapIn(nodeType: NodeType) { +/** + * Builds a command which wraps/unwraps the current selection with the passed in node type + * @param nodeType the type of node to wrap the selection in + * @returns A command to toggle the wrapper node + * Commands are functions that take a state and an optional + * transaction dispatch function and... + * + * - determine whether they apply to this state + * - if not, return false + * - if `dispatch` was passed, perform their effect, possibly by + * passing a transaction to `dispatch` + * - return true + */ +export function toggleWrapIn(nodeType: NodeType): Command { const nodeCheck = nodeTypeActive(nodeType); const wrapInCommand = wrapIn(nodeType); diff --git a/src/rich-text/schema.ts b/src/rich-text/schema.ts index 635c53a4..7a8d2409 100644 --- a/src/rich-text/schema.ts +++ b/src/rich-text/schema.ts @@ -169,7 +169,7 @@ const nodes: { }, list_item: { - content: "paragraph block*", + content: "block+", defining: true, parseDOM: [{ tag: "li" }], toDOM() { diff --git a/test/matchers.ts b/test/matchers.ts index 3c692ba3..1f7e4ddf 100644 --- a/test/matchers.ts +++ b/test/matchers.ts @@ -133,33 +133,23 @@ function expectNodeTree(doc: ProsemirrorNode, tree: CompareTree): void { * @returns a CompareTree to be used with `toMatchNodeTree` */ export function createBasicNodeTree(input: string): CompareTree { - const branches = input - .split(">") - .map((x) => x.trim()) - .reverse(); + const branches = input.split(">").map((x) => x.trim()); + if (!branches.length) return {}; - let branch = branches.pop(); const root: CompareTree = { - "type.name": branch, + "type.name": "doc", }; - - branch = branches.pop(); - let node: CompareTree = root; - while (branch) { - if (isNaN(Number(branch))) { - const child = { - "type.name": branch, - }; - node.content = [child]; - - node = child; - branch = branches.pop(); - } else { - node.childCount = Number(branch); - return root; - } + let tree = root; + + for (const branch of branches) { + const child = { + "type.name": branch, + }; + tree.content = [child]; + tree = child; } + return root; } diff --git a/test/rich-text/commands/index.test.ts b/test/rich-text/commands/index.test.ts index b23828c6..03c99e85 100644 --- a/test/rich-text/commands/index.test.ts +++ b/test/rich-text/commands/index.test.ts @@ -3,6 +3,7 @@ import { exitInclusiveMarkCommand, insertHorizontalRuleCommand, toggleHeadingLevel, + toggleWrapIn, } from "../../../src/rich-text/commands"; import { applySelection, @@ -194,6 +195,70 @@ describe("commands", () => { }); }); + describe("toggleWrapIn", () => { + it("should apply blockquote within paragraph", () => { + const state = applySelection(createState("
quote
", []), 3); + expect(state.doc).toMatchNodeTreeString("paragraph>text"); + + const toggleBlockQuote = toggleWrapIn( + state.schema.nodes.blockquote + ); + const { newState, isValid } = executeTransaction( + state, + toggleBlockQuote + ); + + expect(isValid).toBeTruthy(); + expect(newState.doc).toMatchNodeTreeString( + "blockquote>paragraph>text" + ); + }); + + it("should remove blockquote within blockquote", () => { + const state = applySelection( + createState("quote", []), + 3 + ); + expect(state.doc).toMatchNodeTreeString( + "blockquote>paragraph>text" + ); + + const toggleBlockQuote = toggleWrapIn( + state.schema.nodes.blockquote + ); + const { newState, isValid } = executeTransaction( + state, + toggleBlockQuote + ); + + expect(isValid).toBeTruthy(); + expect(newState.doc).toMatchNodeTreeString("paragraph>text"); + }); + + it("should toggle blockquote within list item", () => { + const state = applySelection( + createState("
blockquote in list
paragraph in list
code in list
+