Inline node gets split upon update

I’m experimenting a bit with inline nodes.

inline-split-upon-edit

The video shows a sectionref inline node, which can have two views. The default (blue-greenish one) shows the reference node as editable text. The other view (i.e. title) instead of showing the editable text, shows a read-only/non-editable title value of the reference.

Question: what could be the reason for when I’m showing the sectionref as editable text, that whenever I have my cursor somewhere in that text and I start typing - the inline node is effectively ‘split’ in two halves (apart from other weird keyboard behavior)?

My intent is to be able to change the highlighted text, once selected.

I don’t know. What does your schema and serialized HTML for these look like? It’s possible that the browser decides to split the node when you type into it (you can test this by creating a plain contenteditable element with the same HTML structure inside it, and seeing what happens there when you type into the element).

Editable HTML is:

<p>
    Lorem ipsum dolor sit amet, consectetur
    <span
      data-id="show-modal"
      data-action="open"
      class="sectionref text"
      contenteditable="true"
      >adipiscing</span
    >
    elit. In sed ornare lectus, a rutrum lacus.
  </p>

Non-editable looks like this:

<p>
  Lorem ipsum dolor sit amet, consectetur
  <span
    data-id="show-modal"
    data-action="open"
    class="sectionref title"
    contenteditable="false"
    >Tempor deserunt</span
  >
  elit. In sed ornare lectus, a rutrum lacus.
</p>

When I take editable representation in plain HTML, and edit the span - nothing is split. Doing the same in the PM editor results in something like:

<p>
  Lorem ipsum dolor sit amet, consectetur 
  <span 
    data-id="show-modal" 
    data-action="open" 
    class="sectionref text" 
    contenteditable="true"
  >adipi</span>
  bca
  <span 
    data-id="show-modal" 
    data-action="open" 
    class="sectionref text" 
    contenteditable="true"
  >scing</span> 
  elit. In sed ornare lectus, a rutrum lacus.
</p>

This is the serialized schema:

{
  "topNode": "doc",
  "nodes": {
    "content": [
      "paragraph",
      { "content": "inline*", "group": "block", "parseDOM": [{ "tag": "p" }] },
      "doc",
      { "content": "block+" },
      "text",
      { "group": "inline" },
      "sectionref",
      {
        "content": "inline",
        "group": "inline",
        "inline": true,
        "attrs": {
          "refid": { "default": null },
          "show": { "default": "text" },
          "text": { "default": "" }
        },
        "parseDOM": [{ "tag": "sectionref" }]
      }
    ]
  },
  "marks": {
    "content": [
      "link",
      {
        "inclusive": false,
        "attrs": {
          "href": { "default": null },
          "target": { "default": "_blank" }
        },
        "parseDOM": [{ "tag": "a[href]" }]
      },
      "bold",
      {
        "parseDOM": [
          { "tag": "strong" },
          { "tag": "b" },
          { "style": "font-weight" }
        ]
      },
      "italic",
      {
        "parseDOM": [
          { "tag": "em" },
          { "tag": "i" },
          { "style": "font-style=italic" }
        ]
      },
      "underline",
      {
        "parseDOM": [
          { "tag": "u" },
          { "style": "text-decoration", "consuming": false }
        ]
      },
      "highlight",
      {
        "attrs": { "color": { "default": null } },
        "parseDOM": [{ "tag": "mark" }]
      }
    ]
  }
}

Odd. Does removing the contenteditable=true from the element (it is already in editable context) make any difference?

No - I’m afraid it does not make a difference. I’m using (tiptap) NodeView for the element - any chance I’m messing things up at that end? Adding that piece of code below for completness sake (and yes, I know that addNodeView is not PM)

addNodeView() {
    return ({ node }) => {
      const dom = document.createElement('span');
      dom.setAttribute('data-id', 'show-modal');
      dom.setAttribute('data-action', 'open');
      dom.classList.add('sectionref', node.attrs.show);

      if (node.attrs.show === 'title') {
        fetchTitle(dom, node.attrs.refid);
      }

      return {
        dom,
        contentDOM: dom
      };
    };
  }

additional n00b question - for the above; when I start typing ‘inside’ of the highlighted (reference text) the node type at that position according to the below, is emitted as paragraph:

    const resolved = editor.state.doc.resolve(editor.state.selection.from);
    console.log(resolved.node().type);

It is not directly pointing to the inline node sectionref. Is this anticipated?

edit

when doing the above with selection.from-1 it does say sectionref as the type.

Pretty sure the custom (tiptap) NodeView has something to do with it. I’ve updated the nodeview like so:

  addNodeView() {
    return ({ node }) => {
      const dom = document.createElement('span');
      dom.setAttribute('data-id', 'show-modal');
      dom.setAttribute('data-action', 'open');
      dom.classList.add('sectionref', node.attrs.show);

      if (node.attrs.show === 'title') {
        fetchTitle(dom, node.attrs.refid);
      }

      return {
        dom,
        contentDOM: dom,
        update: (updatedNode) => {
          if (updatedNode.type !== this.type) {
            return false;
          }

          console.log('updating...', updatedNode);
          return true;
        }
      };
    };

Editing shows:

As far as I can tell this all looks okay to me, and I don’t know why tiptap/PM is not ‘editing’ the sectionref, but instead performing a split.

Let me (also) try to get some help from the tiptap discord channel.

@marijn - when I remove the contentDOM from the above, and manually build the dom, I’m seeing that editing the sectionref, doesn’t split the node, but then again - it doesn’t (obviously) actually update anything in the content model (only the HTML view). Is that then something I have to start handcoding myself?

Changing:

content: 'inline' to
content: 'text*'

Seems to have resolved this issue.

No clue why though :grinning: