ProseMirror / prosemirror

The ProseMirror WYSIWYM editor
http://prosemirror.net/
MIT License
7.53k stars 334 forks source link

Unexpected behavior when hitting backspace on list item with children? #1467

Open kasrak opened 1 month ago

kasrak commented 1 month ago

I have the following doc:

hello
- first
  - second

The cursor is preceding the f. I'd like to hit backspace in order to bring the "xfirst" line up to the same line as "hello." Instead what happens is that the "hello" node gets selected, then deleted.

Here's a Loom showing the behavior in ProseMirror, Google Docs, and Notion:

https://www.loom.com/share/ad856a801d134478afc23de142c4c2e8

When the list only has a single level (i.e. no sublists), hitting backspace works as I'd expect.

marijnh commented 1 month ago

The commands you have bound to backspace (probably some combination of joinBackward and selectNodeBackward) can't find any other operation that would leave the document in a valid state in this situation, so it fall back to selecting the node before the cursor. What would you expect to happen here?

kasrak commented 1 month ago

Thank you for the fast response!

I think either the Google Docs & Notion behavior (in the linked Loom screen cap) would be reasonable:

I was testing this in the "Basic example" playground (https://prosemirror.net/examples/basic/), so apologies if I missed how to configure the editor to change this behavior.

marijnh commented 1 month ago

ProseMirror schemas tend to work quite differently from Google Docs/Notion documents. Does your schema have a way to represent the kind of documents this creates in those editors? Because typically, orphan indented list items just cannot be represented in ProseMirror schemas.

kasrak commented 1 month ago

A way to keep the doc compatible with the schema is to de-dent the list item (and any of its descendants) -- I've got that working with the below key handler now.

But I noticed another related issue that I'm trying to solve: if I hit backspace at the beginning of a paragraph (screenshot 1) that's below a list, the paragraph gets added as a second list item (screenshot 2). I'd expect the paragraph text to be merged into the first list item (screenshot 3).

Backspace: ({ editor }) => {
  const { $from, empty } = editor.state.selection;
  if ($from.parentOffset !== 0 || !empty) {
      return false;
  }

  // If the cursor is at the beginning of a list item, de-dent.
  const parentNode = $from.node(-1);
  if (parentNode?.type.name === "listItem") {
      return editor.commands.liftListItem("listItem");
  }

  // TODO: If the cursor is at the beginning of a block that follows
  // a list, merge the block content into the last list item.
}

Screenshot 1: starting state

image

Screenshot 2: what happens after hitting backspace

image

Screenshot 3: what I'd like to happen

image