Cut and paste a paragraph from one line to anther does not retain the copied node's attributes

Issue: Paragraph 1: Hello world
It has an alignment Center Paragraph 2: It has an empty line When Paragraph 1 is cut and pasted to the second line, the paragraph attributes are not retained (eg: alignment). Research upon this behavior led to the conclusion that the ProseMirror does not retain the attributes but only the marks inside the node. We have come up with two solutions:

Solution 1: Using appendTransaction:

import { Plugin, PluginKey, NodeSelection } from "prosemirror-state";

class FullParagraphSelectionPlugin extends Plugin {
  constructor() {
    super({
      key: new PluginKey("fullParagraphSelection"),
      appendTransaction: (transactions, oldState, newState) => {
        const { selection, doc } = newState;
        // Skip if the selection is empty
        if (selection.empty) return;

        const { $from, $to } = selection;
        // Ensure the selection is within the same parent node and that parent is a paragraph
        if (
          $from.parent === $to.parent &&
          $from.parent.type.name === "paragraph" &&
          $from.parentOffset === 0 &&
          $to.parentOffset === $to.parent.content.size &&
          $from.pos !== $to.pos // non-empty selection check
        ) {
          // Get the position before the paragraph node
          const nodePos = $from.before();
          // Replace the current selection with a NodeSelection for the entire paragraph
          return newState.tr.setSelection(NodeSelection.create(doc, nodePos));
        }
      }
    });
  }
}

export default FullParagraphSelectionPlugin;

Soultion 2: Using handlePaste:

import { Plugin, PluginKey } from 'prosemirror-state';

const SPEC = {
  key: new PluginKey('preserveAttributesPlugin'),
  props: {
    handlePaste(view, event, slice) {
      let tr = view.state.tr;
      const currentNode = view.state.doc.nodeAt(
        view.state.selection.$from.before(1)
      );
      tr = tr.delete(
        view.state.selection.$from.before(1),
        view.state.selection.$from.after(1)
      );
      slice.content.forEach((node) => {
        if (node.type.name === 'paragraph') {
          tr.replaceSelectionWith(
            node.type.create({ ...node.attrs }, node.content, node.marks)
          );
        } else {
          tr.replaceSelectionWith(node);
        }
      });

      view.dispatch(tr);
      return true;
    },
  },
};

class preserveAttributesPlugin extends Plugin {
  constructor() {
    super(SPEC);
  }
}

export default preserveAttributesPlugin;

@marijn Can you analyze the code and suggest which solution will be more impactful with less complications? Would like everyone’s suggestion on this. Appreciate your Support! Thanks!

Maybe you are looking for this node flag: definingForContent

1 Like