Marks without rendered DOM nodes

Is it possible to create marks that do not have a toDOM method? We want to store data attrs on ranges of text using marks so we can map them and use them in some of our decoration plugins.

Currently, we are returning a span element for each mark.

entity: {
      attrs: {
        sample: { default: 'sample' },
      },
      toDOM: () => ['span'],
},

We have noticed: When documents become extremely large the marks begin to slow things down.

Is it possible to use marks without rendering anything in the DOM?

4 Likes

This isn’t currently possible (the display assumes each mark has a corresponding DOM node), but there’s no deep reason why it is that way, it’s just how the implementation currently works.

Hello @marijn

We have documents where every single word has a Mark so that we can assign data to the Mark attrs (The toDom is not required. Currently we assign an empty span).

This works well for the most part. However… we have noticed that if a user does not divide their work into paragraphs our Decorators take a really long time to render with a cache layer in place. (Smaller paragraphs work fine)

Could this be related to the number of marks in the document? (It seems like a rendering issue) I have been reading the src of prosemirror-model and have found the serializer. serializeNodeAndMarks

I am struggling to find where to implement this… Do you have any suggestions?

This is the only thing blocking us from releasing proseMirror fully in our production application.

Any help would be really appreciated.

Thanks for all the hard work on this, the Framework is great.

Hi. I assume you’re talking about the rendering in the editor? That is done, for the most part, by viewdesc.js in the prosemirror-view module. In principle, rendering should be incremental and small changes should render quickly. But it’s possible that there’s some case where the incremental renderer gets confused and does too much work. Or is this about the initial render? A minimal demo that shows the slowness would be useful.

Hmm, this seems to be a general issue with contenteditable – it’s getting very slow if there are a lot of elements in there. Having marks that don’t render as elements in the DOM would also be something that would interest me.

See how slow typing is here:

<!docType html>
<html>
  <head><title>test many elements</title></head>
  <body>
    <div id="editor" contenteditable="true"></div>
    <script>
      let editor = document.getElementById('editor'), x = 0

      while (x<10000) {
        let span = document.createElement('span')
        span.innerHTML = 'hello'
        editor.appendChild(span)
        editor.appendChild(document.createTextNode(' '))
        x++
      }

    </script>
  </body>
</html>

In comparison to here:

<!docType html>
<html>
  <head><title>test single long text</title></head>
  <body>
    <div id="editor" contenteditable="true"></div>
    <script>
      let editor = document.getElementById('editor'), x = 0, text = ''

      while (x<10000) {
        text += 'hello '
        x++
      }
      editor.appendChild(document.createTextNode(text))
    </script>
  </body>
</html>

So when I said…

… that wasn’t correct. The DOM has to encode all the information in the document, since during editing it may get parsed and compared to the previous document. If you don’t render marks at all, they will disappear in that case.

If possible, it might be better to keep a separate data structure for this (maintained by a plugin that maps it to stay consistent with the document across changes), rather than trying to encode it as marks.

2 Likes

@marijn A separate data structure that is still part of the editorState would be a great addition!

The main reason we use marks at the moment… instead of our own custom data structure is that we need to support collaborative editing.

A workaround we are exploring at the minute is to break paragraph content into chunks (10 words per chunk etc…) This seems to speed things up dramatically. Does the incremental rendering rely on parent DOM nodes at the moment?

This is also of interest to us. An official way to interchange the state of various plugins collaboratively and in a single json bundle, and also to save the state of them. Currently we have our own code to do this for comments, caret positions, etc., but every time there is another plugin that needs to share it’s state, I tend to look for a way to rethink it in a way so it turns into part of the document instead so that we don’t have to maintain code related to interchange and save/load the plugin state.

I wasn’t suggesting this as an addition to the library—I was suggesting you create a plugin that maintains this the way you need it.

@johanneswilm @jstleger0 any insights on how did you guys solve it finally ?

one of the ways I am thinking to solve it is that if a user is in a particular paragraph then only add attributes to that paragraph otherwise clear the attributes and reduce dom size

@jabberthonky It may be possible to only run prosemirror as an editor on the current paragraph. That will reduce the contenteditable element. I believe Microsoft Word online does that. On the other hand suddenly you have to deal with users setting the caret somewhere completely different, selecting multiple paragraphs, etc. . I don’t have much of a solution for this. We have been able to optimize several other things on the page (apart from prosemirror) and so currently the experience is still acceptable for our users.