Best method for collecting all marks in a block?

Hello all,

My team is in the process of figuring out some details for a commenting system built on top of our existing editor. One of the things we’re aiming to do is collect all marks and group them by block. For the purposes of discussion it’s a schema more or less like this:

doc > page > block > text

Inline nodes (text) can have any number of marks: strong, em, underline, and the latest addition, comment.

I’m trying to find the best way to gather all the marks in a node so that we can show a comment count indicator on the right hand side of the block, much like notion or medium’s comment UIs.

Screen Shot 2020-06-10 at 10.31.19 PM

So far we haven’t found an obvious solution other than iterating through descendants.

Here’s the code we’re using to collect the marks:

const { doc } = editorView.state

doc.forEach(page => {
  page.forEach(block => {
    const commentIds = new Set()
    // gather comments for this block
    // TODO: find a better way to do this in PM
    // (looked in docs, it's not obvious if there is a better one)
    block.nodesBetween(0, block.content.size, inline => {
      // `marks` is always an array
      inline.marks.forEach(mark => {
        if (mark.type.name === 'comment') {
          // we use a set to collect comments, as a single comment may be broken
          // up into multiple marks if other intersecting marks are present
          commentIds.add(mark.attrs.id)
        }
      })
      return false
    })
  })
})

My question is: is this the best (most performant way) to do this? Is there a better method available?

Hoping for some insight from the community before committing to this as it feels like it could be better but I haven’t found a better approach by reading the docs so far – I was looking for a method that returned all marks in a block as an array, but that does not appear to exist (marksAcross does something very different, the marks property of a node is only populated my marks that span the entire block).

Having a reliably performant method for this (that could avoid deeply nested looping on each state update for example) would be nice, particularly for doing further document analysis, like gathering all links in a document, or grouping element types and displaying them in a table.

1 Like

Yes. You could also just call block.forEach if you’re only interested in direct children. But one way or another, you’re going to have to iterate over the child nodes if you want to collect marks.

1 Like

Im using the same method seems to work fine. Here is my code for something similar

node.forEach(child => {
 const [fontSizeMark] = child.marks.filter((m: Mark) => m.type === markType)
})

Maybe you can cache the count for each block and only update it when the block itself is changed?