Replace currently selected nodes with paragraph containing inner text

Hey guys. Hope you can help me with my problem. I’ve looked around and couldn’t find a solution for my task and I can’t wrap my head around how to achieve it using the documentation. Basically what I want to do is implement a “clear” button. How I thought I might achieve this is by getting the war text of my selection, create a simple paragraph, insert the text in there and then replace the current selection with my new paragraph node. Here is my attempt:

replaceSelectionWithText (editor, schema) {
  const selection = editor.state.doc.cut(editor.selection.from,
  const node = schema.nodes.paragraph.create()
  node.text = selection
  const transaction =

I’m missing some crucial knowledge on how prosemirror handles things internally and how to get the information I want from the editor object

Not only does this property not exist on paragraph (or any non-text) node, nodes are also not mutable, so you’re not supposed to write to them. Instead, pass their content when you create them with the create method.

Okay so I would pass the content while creating the paragraph node. But I still can’t seem to get it working. The selected content is removed and an empty p-tag is inserted in place of the selection.

replaceSelectionWithText (schema) {
    const selection = editor.state.doc.cut(editor.selection.from,
    const node = schema.nodes.paragraph.create({ content: selection })
    const transaction =

Yeah, just doing things without reading the docs won’t get you very far, I’m afraid. cut takes child node indices, and you’re passing it document positions. NodeType.create's arguments look nothing like what you’re passing it here. Fitting a slice (which is what you’d get from the selection’s content method) into a node is probably easiest done with a Transform object, since it’s sides might not fit in trivially otherwise. (You can create a transform with a paragraph node as top element.) Or, if you just care about the text content, the textBetween method might be useful. If you feed the resulting string into schema.text you get a text node that you can pass to a NodeType's create method.

I dove deep into the documentation and changed my approach quite a bit. So instead of replacing the whole selection with a paragraph containing the selected text, I have individual approaches to replace each selected node depending on the type. It works well for most cases but I do have a problem with the startPosition of my node-loop not being correct after certain steps. What I mean is, if, for example, I have a bullet-list, with the current approach the amount of indices is changes, since I don’t’ have nested paragraphs in nested list-items anymore, but only paragraphs for each item. If the next node I want to replace it, for example a heading, the start position is not correct anymore and the replacement is incorrect. Is it possible to get the correct position after the first transaction-part? Here is my approach:

commands () {
    return {
      reset: () => {
        const { from, to } = this.editor.selection
        const editor = this.editor
        let transaction =
        transaction = transaction.removeMark(from, to)
        editor.state.doc.nodesBetween(from, to, (node, startPos) => {
          if (node.isBlock && ( === 'anchor' || === 'heading' || === 'blockquote' || === 'code_block')) {
            const text = node.textContent
            const textNode = editor.schema.text(text)
            const paragraphNode = editor.schema.nodes.paragraph.create(null, textNode)
            transaction = transaction.replaceWith(startPos, startPos + node.nodeSize, paragraphNode)
          } else if (node.isBlock && ( === 'bullet_list' || === 'ordered_list')) {
            const paragraphNodes = []
            node.forEach((childNode) => {
              paragraphNodes.push(editor.schema.nodes.paragraph.create(null, editor.schema.text(childNode.textContent)))
            transaction = transaction.replaceWith(startPos, startPos + node.nodeSize, paragraphNodes)

So instead of reevaluating the position of the nodes, I simply went around the problem. I push the nodes in reverse order in an array and then go through them bottom to top. This way the position is not compromised and my paragraph replacements are in the correct spot.

(For reference, can transform a pre-transaction position into a position transformed for the changes in the transaction.)

Maybe we encountered similar, i solve it in this way.

export function convertToParagraph(view, nodeList, tr) {
    let sizeRange = 0;
    const startSize = tr.doc.nodeSize; => {
        const nodeTypeName =;
        const range = tr.doc.resolve(listItem.pos + sizeRange).blockRange(tr.doc.resolve(listItem.pos + listItem.node.nodeSize + sizeRange));

        if (view.commands()[`${nodeTypeName}.paragraph`]) {
            if (!view.commands()[`${nodeTypeName}.paragraph`](tr, range)) return false;
        } else {
            throw new Error(`can not convert ${nodeTypeName} to paragraph correct`);
        const afterSize = tr.doc.nodeSize;
        sizeRange = afterSize - startSize;
        return null;

It might help