Skip to content

Commit

Permalink
fix(rich-text-editor): allow any block node inside list items (#157)
Browse files Browse the repository at this point in the history
fixes #63
  • Loading branch information
KyleMit committed Jul 25, 2022
1 parent aa50811 commit f59b72f
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 26 deletions.
23 changes: 20 additions & 3 deletions src/rich-text/commands/index.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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);

Expand Down
2 changes: 1 addition & 1 deletion src/rich-text/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ const nodes: {
},

list_item: {
content: "paragraph block*",
content: "block+",
defining: true,
parseDOM: [{ tag: "li" }],
toDOM() {
Expand Down
34 changes: 12 additions & 22 deletions test/matchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
65 changes: 65 additions & 0 deletions test/rich-text/commands/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
exitInclusiveMarkCommand,
insertHorizontalRuleCommand,
toggleHeadingLevel,
toggleWrapIn,
} from "../../../src/rich-text/commands";
import {
applySelection,
Expand Down Expand Up @@ -194,6 +195,70 @@ describe("commands", () => {
});
});

describe("toggleWrapIn", () => {
it("should apply blockquote within paragraph", () => {
const state = applySelection(createState("<p>quote</p>", []), 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("<blockquote>quote</blockquote>", []),
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("<ul><li>list</li></ul>", []),
3
);
const resolvedNode = state.selection.$from;
// default li child is paragraph
expect(resolvedNode.node().type.name).toBe("paragraph");

const toggleBlockQuote = toggleWrapIn(
state.schema.nodes.blockquote
);
const { newState, isValid } = executeTransaction(
state,
toggleBlockQuote
);

expect(isValid).toBeTruthy();
expect(newState.doc).toMatchNodeTreeString(
"bullet_list>list_item>blockquote>paragraph>text"
);
});
});

describe("insertHorizontalRuleCommand", () => {
it("should not insert while in a table", () => {
const state = applySelection(
Expand Down
17 changes: 17 additions & 0 deletions test/shared/markdown-parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,23 @@ console.log("test");
expect(doc.content[0].type).toContain("_list");
expect(doc.content[0].attrs.tight).toBe(isTight);
});

it.each([
[`- > blockquote in list`, "bullet_list>list_item>blockquote"],
[`- paragraph in list`, "bullet_list>list_item>paragraph"],
[`- # heading in list`, "bullet_list>list_item>heading"],
[
`- ~~~\n code in list\n ~~~`,
"bullet_list>list_item>code_block",
],
[`- - list in list`, "bullet_list>list_item>bullet_list"],
])(
"should parse lists with direct block children",
(input, expected) => {
const doc = markdownParser.parse(input);
expect(doc).toMatchNodeTreeString(expected);
}
);
});

describe("reference links", () => {
Expand Down
30 changes: 30 additions & 0 deletions test/shared/markdown-serializer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,36 @@ describe("markdown-serializer", () => {

it.todo("should serialize elements from complex html markup");

it.each([
[
`<ul><li><blockquote><p>blockquote in list</p></blockquote></li></ul>`,
`- > blockquote in list`,
],
[`<ul><li><p>paragraph in list</p></li></ul>`, `- paragraph in list`],
[`<ul><li><h1>heading in list</h1></li></ul>`, `- # heading in list`],
[
`<ul>
<li>
<pre class="hljs"><code>code in list</code></pre>
</li>
</ul>`,
`- code in list`,
],
[
`<ul>
<li>
<ul>
<li>list in list</li>
</ul>
</li>
</ul>`,
`- - list in list`,
],
])("should serialize lists containing block children", (input, output) => {
const view = nonMarkdownRichView(input);
expect(view.content).toBe(output);
});

// TODO lots of complicated cases in the spec
// see https://spec.commonmark.org/0.30/#reference-link
// see https://spec.commonmark.org/0.30/#link-reference-definitions
Expand Down

0 comments on commit f59b72f

Please sign in to comment.