Append content at the end of a block

I have a simple schema which consists exclusively of a flat list of blocks of a single type and a few marks like link, em:

  doc: {
    content: "heading+"
  },

My whole doc usually has one single block and looks like this:

  <h1>Title</h1>

Now I have a string of text representing an html fragment (without a block tag) to be added at the end of the first block, say

   toAppend = '<strong> of the document</strong>'

And I want my new document to look like this:

  <h1>Title<strong> of the document</strong></h1>

The cursor/selection is not to be trusted, this is: I should initialize it somehow and the cursor position should be just after the e of Title.

How should I do this?

Right now I am parsing the input fragment into a new heading block, injecting that block manually in the doc state as

const newState = state;
const docToAppend = myManualHtml2DocCoverstion('<strong> of the document</strong>'); // returns a doc that would serialize to `<h1><strong> of the document</strong></h1>`
newState.doc.content.content.push(docToAppend.content.content[0]);

I then try to set the cursor at the beginning of the second block with

newState.selection = TextSelection.create(
      newState.doc, 
      newState.doc.content.content[0].nodeSize)

and trying to simulate a joinBackward command. But I am having an issue with it because, internally $cursor.parentOffset > 0.

Is this the recommended approach to achieve what I want?

PS. I am indeed trying to simulate an editor in which each block is, itself an independent prosemirror editor.

I think parsing the content as a slice (via parseSlice) and directly inserting it at the end of the target block via replace would be easier.

1 Like

It worked! Thx!

As a ref for others, this is how my code looks now:

    const sliceNode = this.getSlice(content); // sliceNode is a DOM Node `h1` element
    const slice = editor.parser.parseSlice(sliceNode);
    
    const end = state.doc.content.content[0].nodeSize; // in my case I know this is the end of the document
    const replace = state.tr.replace(end-1, end-1, slice); // i substract 1 to prevent a new block from being added
    
    const newState = state.apply(replace);
    editor.view.updateState(newState);

@marijn, should I use replace also to remove a block :thinking:? Currently, I am using

newState = state;
newState.doc.content.content.splice(1, 1); 
editor.view.updateState(newState);

As the docs will also tell you: don’t mutate these objects. You’ll just break stuff.

(I don’t have time to walk you through all this, but you might want to look into the Transform.delete method here.)

1 Like

I understand thanks. I have browse the docs for a while now.

I think my blind spot is around how to convert between blocks and selection and how to programatically control the selection, since most commands start from it.

I am starting to understand I should sum the block sizes and use the setSelection(TextSelection.create(x, *sum of previous nodeSizes*, * size of this node*)) Is this correct?

BTW, congrats on maintaing this all by youself!

It sounds like commands are too high-level for what you’re trying to do—they are meant to be bound directly to keys or menu items, and perform a specific user action. The methods on Transaction and Transform are probably more useful for the custom things you’re trying to do (and don’t require messing with the selection unless you actually want to).

1 Like