Custom NodeView containing an editable node

I’m trying to create this type of node in ProseMirror:

(image) (text node) (select image dropdown)

Example data representation:

{
  "type": "custom",
  "attrs": {
    "image-uri": "image.jpg",
  },
  "content": [
    {
      "type": "text",
      "text": "hello world"
    }
  ]
}

The dropdown allows a user to select the image being shown and sets the attrs.image-uri. But I want the dropdown and the image to be completely ignored by the editor (no cursor selection, deletion, etc). And I want to be able to interact with the text node as normal (ability to split, toggle mark, etc).

I’ve tried using a custom node view but it appears that children of a contentEditable=true element is interact-able. I found that ::before and ::after elements are the exception to this and I may try to use that - but that seems limited and hacky as I would have to determine boundaries to be able to attach click / hover handlers on the dropdown.

Another approach I’ve considered is trying to overlay the dropdown and image on top of the editor so that they aren’t children of the top-level contentEditable=true element. In this case what’s the best way to determine when the top position of this node changes if the user inserts content above it?

I’ve found a few older threads about this but none seem to be conclusive:

Thanks!

1 Like

In your custom Nodeview, you could set all your additional elements to contentEditable=false, to avoid having a visible blinking text cursor. And you just set contentDOM to define the children you want to interact as normal. About your second question, not sure whether I understood it properly, but you could use decorations to inject your dropdowns overlays, right at the custom node, then I don’t see a need to track position changes as you described it.

In your custom Nodeview, you could set all your additional elements to contentEditable=false, to avoid having a visible blinking text cursor. And you just set contentDOM to define the children you want to interact as normal.

It appears that setting the additional elements to contentEditable=false still allows them to be selectable and deletable…

About your second question, not sure whether I understood it properly, but you could use decorations to inject your dropdowns overlays, right at the custom node, then I don’t see a need to track position changes as you described it.

Same problem as above. I modified the linting example (ProseMirror lint example) to always show the arrow decorations. In Chrome at least, it’s possible to get into a very weird state by hitting backspace a few times:

In the first image the dom looks like this:

<p>
  “This is a sentence”
  <span class=“pro barrow ProseMirror-widget” contenteditable=“false”>
    <img src=“http://prosemirror.net/img/bouncing_arrow.gif”>
  </span>
  …
</p>

After hitting backspace a few times the span was removed leaving just the image somehow?

<p>
  “This is a sentence”
  <img src=“http://prosemirror.net/img/bouncing_arrow.gif”>
  …
</p>

It seems like this is just a content-editable problem that has to be worked around?

ok, I see. But as you suggested (and as I currently have at my own experiment), just putting content into :before or :after might do the trick.

Hmm seems like the only way is to use an overlay. This is what Quill does for multi-cursor. https://quilljs.com/0.20/docs/modules/multi-cursors/.

But what’s the best way to listen for position changes of a given node?

If you put content into a non-editable display:block element, the browser will mostly not interact with it during editing. Inline elements in between editable content are more problematic. They will be involved in the cursor motion in the surrounding editable element.

Selecting across things is always possible when they are children of an editable node.

@davidye were you able to get the nodeview editing working the way you want? And if so how did you go about it?

Yes, I was able to get this working by calling coordsAtPos() to render elements with position: absolute on top of the prose-mirror element.

We did run into some issues with this approach though, see: Invalid position when calling coordsAtPos()