ProseMirror / prosemirror

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

Copy and paste of ul list item results in ol list item #883

Open kiejo opened 5 years ago

kiejo commented 5 years ago

Issue details

Copying and pasting a list item wrapped in a ul results in a list item wrapped in a ol surrounded by two empty paragraphs instead of resulting in the copied content.

Steps to reproduce

  1. Go to https://gainful-baboon.glitch.me/
  2. Put the caret before "Item"
  3. Click the "Select parent node" button in the toolbar twice
  4. Press Ctrl+X to cut the selection
  5. Press Ctrl+V to paste the content

ProseMirror version

prosemirror-view 1.6.8 prosemirror-model 1.6.4

Affected platforms

Screenshots / Screencast (Optional)

Before cut cut After paste paste

ocavue commented 1 year ago

I can explain this issue. When the listItem node is been selected (e.g. a NodeSelection), ProseMirror will only write <li> ... </li> into the clipboard when coping, so it loses the context about the outer node (orderedList or bulletList).

Here is the data ProseMirror write into clipboard:

image
kiejo commented 1 year ago

I found a way to customize the default behavior and solve the issue by using a custom transformCopied function:

function transformCopied(slice, view) {
  // If a single list item node is copied, we want to wrap it in its parent so that
  // pasting the slice results in the correct list type (e.g. ordered vs unordered list)
  if (slice.openStart == 0 && slice.openEnd == 0 && slice.content.childCount == 1) {
    const copiedNode = slice.content.firstChild
    if (copiedNode.type.spec.isListItem) {
      // Search for the parent of the copied list item
      let copiedNodeParent
      view.state.doc.descendants((node, pos, parent) => {
        if (copiedNodeParent)
          return false
        if (node == copiedNode) {
          copiedNodeParent = parent
          return false
        }
      })

      // Create a new Slice that includes the parent of the list item
      if (copiedNodeParent)
        return new Slice(Fragment.from(copiedNodeParent.type.create(null, slice.content)), 1, 1)
    }
  }

  return slice
}

Depending on the schema used for the list item, copiedNode.type.spec.isListItem needs to be adjusted (e.g. copiedNode.type.name == 'list_item'). In case the attributes of the parent list should be kept as part of the copy, one can use copiedNodeParent.copy(slice.content) instead of copiedNodeParent.type.create(null, slice.content).

I'm not sure if this is the best or recommended way to achieve this, but it solves the issue for me.