Custom node's toDOM not updating the node's related dom element attribute

In my example , i defined this custom node which is a block node (h1) with an inline content and which has an attribute “data-before-content” which is responsible of displaying a css rule’s ::before dynamic content :

    const editorTitle = {
        name: "editorTitle",
        content: "inline*",
        isTextblock: true,
       attrs: {
         class: { default: "editorTitleId" },
         "data-before-content": { default: undefined },
       },
       group: "block",
       parseDOM: [
      {
       priority: 51, // must be higher than the default image spec
       tag: "h1[class][data-before-content]",
       getAttrs(dom) {
         const nodeClass = dom.getAttribute("class");
         if (nodeClass != "editorTitleId") {
          return false;
         }
        return {
           class: nodeClass,
          "data-before-content": dom.getAttribute("data-before-content"),
        };
      },
    },
  ],
  toDOM(node) {
    const attrs = node.attrs;
    if (
      !node.content.content[0] ||
      (node.content.content[0] && !node.content.content[0].text)
    ) {
      attrs["data-before-content"] = "Untitled item";
    } else {
      attrs["data-before-content"] = undefined;
    }
    return ["h1", { ...attrs }, 0];
  },
};

the css rule : h1.editorTitleId::before { content: attr(data-before-content); opacity: 0.5; }

When editing the editorTitle content, the toDom is executed and changes the “data-before-content” value but the dom related element attribute never get updated.

I reproduced the bug in this codesandbox

As you can see in the demo code, i’ve tried to update the view state inside the dispatchTransaction as the following using view.updateState(newState); or even setState(newState); but the problem is still there in either cases (i prefer just update the view state though and not the react’s)

      dispatchTransaction={transaction => {
          const { view } = useProseMirrorComp;
          let newState = view.state.apply(transaction);
          proseMirrorOnChangeHandler(state, newState, mySchema, props);
          view.updateState(newState);
          // setState(newState);
        }}

toDom only displays the correct “data-before-content” attribute in the initial display whether the editorTitle has content or not (by changing the textPageContent value in the code):

  1. with content : when let textPageContent = '<h1 class="editorTitleId">4</h1><p></p>'; “data-before-content” will be equal to an empty string in this case and only the editorTitle content will be displayed

  2. without content : when let textPageContent = ""; ==> “data-before-content” will be equal to ‘Untitled item’

So why the toDom runs but doesn’t update the dom element attribute ? what am i doing wrong ?! Cheers

If you want to update the node.attrs every time the content is changed, I believe you will have to use a custom node view which computes the new attrs and dispatches a transaction to update the node.

1 Like

Mutating node.attrs like that is never okay (nodes are immutable), and doing it in a toDOM method is an extra strange thing. I think the problem originates there.

yeah that’s what i did later but i still wonder why the toDom isn’t doing it correctly but i also had issues with the node view as in this thread

i wasn’t the first who did that , i was inspired by this example which i picked from this thread. And BTW , if the nodes are immutable then why we can pass attrubutes in the toDom returned array ?!

I don’t see any code writing to attrs objects in that example.

I don’t understand this question. Maybe you’re confusing DOM attributes and ProseMirror node attributes?

well, i think by talking about writing to attrs objects you mean this part of the code inside the toDom:

     if (
        !node.content.content[0] ||
        (node.content.content[0] && !node.content.content[0].text)
      ) {
        attrs["data-before-content"] = "Untitled item";
      } else {
        attrs["data-before-content"] = undefined;
      }

Actually i didn’t expect the change in the node attributes to be consequence of that affectation exactly , i just changed the node attributes locally there and then passed the attrs in the array (ok, i shouldn’t have done that) Now i changed that part of code to this :

              let dataBeforeContent;
              if (
                    !node.content.content[0] ||
                    (node.content.content[0] && !node.content.content[0].text)
              ) {
                dataBeforeContent = "Untitled item";
              } else {
                dataBeforeContent = undefined;
              }

return ["h1", { ...attrs, "data-before-content": dataBeforeContent }, 0];

There is no more attributes overriting now , right ? but the toDom still doesn’t update the node