Introductions, and a Question. Inserting editable-preformatted nodes

Hello All! New to ProseMirror by way of TipTap.

I have to say, I’m floored with how wonderful the API’s for both projects are. Kudos to the creators!

My use case is a pedestrian one but I’m struggling with how to insert a snippet of text programmatically.

A little bit of context, I’m creating an app that compiles existing assets like text and links that I then allow users to insert into an editor via ProseMirror/TipTap commands. I’m able to insert a paragraph but I can’t seem to make the content editable. I’m assuming it’s to do with the way I’ve configured the NodeSpec.

Here’s the aforementioned NodeSpec

{
  group: "block",
  content: "paragraph",
  attrs: {
    snippetText: {
      default: "Nothing to see here"
    }
  },
  parseDOM: [
    {
      tag: "p.snippet",
      attrs: dom => ({
        snippetText: dom.innerText
      })
    }
  ],
  toDOM: node => {
    return ["p", { class: "snippet" }, node.attrs.snippetText];
  }
};

And here is my command…

  command({ type, attrs }) {
  let { snippetText } = attrs;
   return (state, dispatch) => {
     const { from, to } = state.selection;
     let snippet = type.create(attrs);
     dispatch(state.tr.insert(to, snippet));
     return true;
   };
}

The insertion works, however, the Node is uneditable as it has the attribute contenteditable=false.

19%20PM

I’m sure it’s something I’m missing and I feel horrible for even asking but does anyone have an idea as to why the inserted Node behaves that way?

Nodes that don’t have children are set as uneditable—ProseMirror only concerns itself with editing the part of the DOM that described by the schema. The content of leaf nodes (nodes with no children) falls outside of that.

In this case, you could set the node’s content to be text*, drop the attribute, and have its toDOM return ["p", {class: "snippet"}, 0] to get the effect that I think you’re looking for.

@marijn, thanks for taking the time to field my question.

So if I wish to programmatically pre-existing text, it doesn’t have to be specified in the toDOM method?

To be more specific, my application takes saved text from our Database and allows the user to inject into their editor as a “snippet”.

If I understand your answer correctly, to accomplish this I’d need some child element for my dynamically added snippet to remain editable.

[["p", {class: "snippet"}, node.attrs.snippetText], 0]]

Is that ^^ correct-ish?

Well, that’s not right at all… :sweat_smile: Hole has to be the only child element.

My understanding conceptually is that I’d need to insert a paragraph, in my toDOM method which contains my snippet as a child element and the snippet itself also has a paragraph as a child element with the schema you described ["p",{}, 0] to remain editable

toDOM just renders content, it doesn’t create content. If you want editable text inside of a node, put it in there as child nodes, not as an attribute.

I think I understand. The following is my command code now,

   return (state, dispatch) => {
      const { from, to } = state.selection;
  let snippet = type.create(attrs);
  snippet.content.append(
    state.schema.nodes.text.createAndFill({}, snippet.attrs.snippetText)
  );
  dispatch(state.tr.insert(to, snippet));
  return true;
};

Am I headed down the right track?

Not really, I think. You’re still using an attribute—you don’t need an attribute. The text can be stored as the node content, you don’t need to put it anywhere else.