diff --git a/packages/editor/patches/@tiptap+extension-list-keymap+2.6.6.patch b/packages/editor/patches/@tiptap+extension-list-keymap+2.6.6.patch index 5a409c1dda6..fd80023a1b0 100644 --- a/packages/editor/patches/@tiptap+extension-list-keymap+2.6.6.patch +++ b/packages/editor/patches/@tiptap+extension-list-keymap+2.6.6.patch @@ -1,5 +1,5 @@ diff --git a/node_modules/@tiptap/extension-list-keymap/dist/index.cjs b/node_modules/@tiptap/extension-list-keymap/dist/index.cjs -index 0f8ae36..2f715b5 100644 +index 0f8ae36..adeee92 100644 --- a/node_modules/@tiptap/extension-list-keymap/dist/index.cjs +++ b/node_modules/@tiptap/extension-list-keymap/dist/index.cjs @@ -82,7 +82,9 @@ const handleBackspace = (editor, name, parentListTypes) => { @@ -13,8 +13,20 @@ index 0f8ae36..2f715b5 100644 const $listPos = editor.state.doc.resolve($anchor.before() - 1); const listDescendants = []; $listPos.node().descendants((node, pos) => { +@@ -111,6 +113,11 @@ const handleBackspace = (editor, name, parentListTypes) => { + if (!listItemPos) { + return false; + } ++ // if the current position is not at the start of the list item ++ // then join backward i.e. join within the list item ++ if (listItemPos.$pos.parentOffset !== 0) { ++ return editor.commands.joinBackward(); ++ } + const $prev = editor.state.doc.resolve(listItemPos.$pos.pos - 2); + const prevNode = $prev.node(listItemPos.depth); + const previousListItemHasSubList = listItemHasSubList(name, editor.state, prevNode); diff --git a/node_modules/@tiptap/extension-list-keymap/dist/index.js b/node_modules/@tiptap/extension-list-keymap/dist/index.js -index f7ab1e4..8645c44 100644 +index f7ab1e4..6ea03d5 100644 --- a/node_modules/@tiptap/extension-list-keymap/dist/index.js +++ b/node_modules/@tiptap/extension-list-keymap/dist/index.js @@ -78,7 +78,9 @@ const handleBackspace = (editor, name, parentListTypes) => { @@ -28,3 +40,15 @@ index f7ab1e4..8645c44 100644 const $listPos = editor.state.doc.resolve($anchor.before() - 1); const listDescendants = []; $listPos.node().descendants((node, pos) => { +@@ -107,6 +109,11 @@ const handleBackspace = (editor, name, parentListTypes) => { + if (!listItemPos) { + return false; + } ++ // if the current position is not at the start of the list item ++ // then join backward i.e. join within the list item ++ if (listItemPos.$pos.parentOffset !== 0) { ++ return editor.commands.joinBackward(); ++ } + const $prev = editor.state.doc.resolve(listItemPos.$pos.pos - 2); + const prevNode = $prev.node(listItemPos.depth); + const previousListItemHasSubList = listItemHasSubList(name, editor.state, prevNode); diff --git a/packages/editor/src/extensions/list-item/tests/__snapshots__/list-item.test.ts.snap b/packages/editor/src/extensions/list-item/tests/__snapshots__/list-item.test.ts.snap new file mode 100644 index 00000000000..98df6e7a9c2 --- /dev/null +++ b/packages/editor/src/extensions/list-item/tests/__snapshots__/list-item.test.ts.snap @@ -0,0 +1,7 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`hitting backspace at the start of first list item 1`] = `"

item1

"`; + +exports[`hitting backspace at the start of the second (or next) list item 1`] = `"
"`; + +exports[`hitting backspace at the start of the second (or next) paragraph inside the list item 1`] = `"
"`; diff --git a/packages/editor/src/extensions/list-item/tests/list-item.test.ts b/packages/editor/src/extensions/list-item/tests/list-item.test.ts new file mode 100644 index 00000000000..2d9d7c7ebd6 --- /dev/null +++ b/packages/editor/src/extensions/list-item/tests/list-item.test.ts @@ -0,0 +1,138 @@ +/* +This file is part of the Notesnook project (https://notesnook.com/) + +Copyright (C) 2023 Streetwriters (Private) Limited + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +import ListKeymap from "@tiptap/extension-list-keymap"; +import { expect, test } from "vitest"; +import { createEditor, h, li, p, ul } from "../../../../test-utils/index.js"; +import BulletList from "../../bullet-list/index.js"; +import CheckListItem from "../../check-list-item/index.js"; +import CheckList from "../../check-list/index.js"; +import OrderedList from "../../ordered-list/index.js"; +import { OutlineListItem } from "../../outline-list-item/index.js"; +import { OutlineList } from "../../outline-list/index.js"; +import { TaskItemNode } from "../../task-item/index.js"; +import { TaskListNode } from "../../task-list/index.js"; +import { ListItem } from "../index.js"; + +test("hitting backspace at the start of first list item", async () => { + const el = ul([li([p(["item1"])]), li([p(["item2"])])]); + const editorElement = h("div"); + const editor = createEditor({ + element: editorElement, + initialContent: el.outerHTML, + extensions: { + listItem: ListItem, + listKeymap: ListKeymap.configure({ + listTypes: [ + { + itemName: ListItem.name, + wrapperNames: [BulletList.name, OrderedList.name] + }, + { + itemName: TaskItemNode.name, + wrapperNames: [TaskListNode.name] + }, + { + itemName: OutlineListItem.name, + wrapperNames: [OutlineList.name] + }, + { + itemName: CheckListItem.name, + wrapperNames: [CheckList.name] + } + ] + }) + } + }); + const event = new KeyboardEvent("keydown", { key: "Backspace" }); + editor.editor.view.dom.dispatchEvent(event); + expect(editorElement.outerHTML).toMatchSnapshot(); +}); + +test("hitting backspace at the start of the second (or next) list item", async () => { + const el = ul([li([p(["item1"])]), li([p(["item2"])])]); + const editorElement = h("div"); + const editor = createEditor({ + element: editorElement, + initialContent: el.outerHTML, + extensions: { + listItem: ListItem, + listKeymap: ListKeymap.configure({ + listTypes: [ + { + itemName: ListItem.name, + wrapperNames: [BulletList.name, OrderedList.name] + }, + { + itemName: TaskItemNode.name, + wrapperNames: [TaskListNode.name] + }, + { + itemName: OutlineListItem.name, + wrapperNames: [OutlineList.name] + }, + { + itemName: CheckListItem.name, + wrapperNames: [CheckList.name] + } + ] + }) + } + }); + editor.editor.commands.setTextSelection(12); + const event = new KeyboardEvent("keydown", { key: "Backspace" }); + editor.editor.view.dom.dispatchEvent(event); + expect(editorElement.outerHTML).toMatchSnapshot(); +}); + +test("hitting backspace at the start of the second (or next) paragraph inside the list item", async () => { + const el = ul([li([p(["item 1"]), p(["item 2"])])]).outerHTML; + const editorElement = h("div"); + const editor = createEditor({ + element: editorElement, + initialContent: el, + extensions: { + listItem: ListItem, + listKeymap: ListKeymap.configure({ + listTypes: [ + { + itemName: ListItem.name, + wrapperNames: [BulletList.name, OrderedList.name] + }, + { + itemName: TaskItemNode.name, + wrapperNames: [TaskListNode.name] + }, + { + itemName: OutlineListItem.name, + wrapperNames: [OutlineList.name] + }, + { + itemName: CheckListItem.name, + wrapperNames: [CheckList.name] + } + ] + }) + } + }); + editor.editor.commands.setTextSelection(11); + const event = new KeyboardEvent("keydown", { key: "Backspace" }); + editor.editor.view.dom.dispatchEvent(event); + expect(editorElement.outerHTML).toMatchSnapshot(); +}); diff --git a/packages/editor/test-utils/index.ts b/packages/editor/test-utils/index.ts index 6227a17bd09..208577dbead 100644 --- a/packages/editor/test-utils/index.ts +++ b/packages/editor/test-utils/index.ts @@ -87,6 +87,7 @@ function elem(tag: K) { export const ul = elem("ul"); export const li = elem("li"); +export const p = elem("p"); export function text(text: string) { return document.createTextNode(text);