Find (new) node instances and track them

It’s not entirely clear to me how I should go about this:

Say I want to track all the footnote nodes in the current document and I need to have access to their Pos values so that I can exchange them.

For the case where one of them changes position, I can use markRange to always keep their current Pos. But I need the Pos already when I first find the node.

Is there either:

a) A way to get the Pos for a particular node inside of the editor.doc structure?

or

b) A way to get notified when a new instance of a particular node type is being added to editor.doc including its Pos so that one can put a mark range around it?

and

c) A list of all Pos values of all nodes of a particular type inside of editor.doc?

Right now I am trying to adjust the code to try to find the Pos manually by walking through the editor.doc.content to the latest update, but it feels like this isn’t the right thing to do: https://github.com/fiduswriter/prosemirror-footnotes/blob/ffad5bfc161eb4e8acc33129558b41a27fa53b27/src/footnote-pm.js#L28-L55

I have now updated it to use the current version of ProseMirror. Thanks to the documentation Marijn has been working on in recent days, I figured the forEach function was giving the offsets for each node, so all I needed to do was to find the path to each element. This seems to work: https://github.com/fiduswriter/prosemirror-footnotes/blob/6bda338fd467db11ab8c51963a6622618744a42f/src/footnote-pm.js#L28-L59

I will likely go back to the inline footnotes as we usually have them, but I still think it would be good to have clarity about whether this is how we are excepted to work with this, or if instead we should try to find nodes in the dom, for example by using CSS selectors.

1 Like

I think you’ll have more luck using the "transform" event and position mapping to track nodes.

Ok, I can see how I could track the position of pre-existing footnotes through the transformations. But I still have to go through the entire document searching for footnote-nodes after pasting, dragging, etc., correct? And because there is no way to distinguish a paste-caused transform/change event, I still have to do the check every time, right?

Btw, the way I did it seems to be working now. It’s just that I assume it will get fairly slow with large documents, and it feels as if this is wrong and as if there is some part of the interface I juts haven’t understood yet. Surely, if we were supposed to walk through editor.doc, there would be some kind of interface for it to give us all the Pos-values we need for each node in there.

This is a currently working demo: http://fiduswriter.github.io/prosemirror-footnotes/

@marijn: So if I want to scan all new content entering a document, would I

  • check whether a step is of type “replace”, and if yes, then
  • create an inversion of the step using .invert()
  • run inlineNodesBetween with the from and to Pos values of the inverted step

? something like that?

The concept of ‘new content entering the document’ is kind of vague, since nodes in replaced content might be joined to other, adjacent nodes if the replace edges cut through them. Also, going from nodes inside the content in a replace step to positions in the final document is rather painful, since there’s no reliable way to map positions pointing into step.param.content to actual result document positions.

What you can do is find the replaced ranges created by a transform, map them all to the final document, and scan them (possibly merging overlapping ranges). Something like this:

function replacedRanges(transform) {
  var ranges = []
  for (var i = 0; i < transform.steps.length; i++) {
    var step = transform.steps[i], map = transform.maps[i]
    if (step.type == "replace") {
      // Could write a more complicated algorithm to insert it in
      // sorted order and join with overlapping ranges here. That way,
      // you wouldn't have to worry about scanning nodes multiple
      // times.
      ranges.push({from: step.from, to: step.to})
    }
    for (var j = 0; j < ranges.length; j++) {
      var range = ranges[j]
      range.from = map.map(range.from, -1).pos
      range.to = map.map(range.from, 1).pos
    }
  }
  return ranges
}

And then iterate over the nodes between those positions.