Should I use a state based plugin or a view based one?

Context

We’re in the process of doing a proof of concept for pagination (e.g. painting page dividers + #page) using Tiptap / Prosemirror. In order to do this, we’re using decorations + custom plugin. Our north star looks like adding something like this for page breaks every 1056px:



High level idea

  • We know the dimensions of the page we want to support
  • Whenever the state of the document changes, we want to compute the size of it using scrollHeight.
  • Based on that number, we can calculate # pages
  • Finally, we know “approximately” where to insert the widget / decoration via coordsAtPos and posAtCoords APIs.
    • The trick is to use coordsAtPos(0) to solve above problem.

Question

We’ve seen different ways of implementing plugins. Some of them are state based: (e.g. 1), but some of them are view based (e.g. 2). We would like to take an informed decision on this. What would be the best approach to use in this case and why?

Thanks so much for building ProseMirror! cc/ @marijn

Decorations that rely on DOM measurements are definitely tricky in that they create a data dependency cycle (the rendering of the doc uses decorations as input, the measuring needs the doc to be rendered, and the decorations are computed from the measures). So you’ll probably need to schedule your own re-measures somewhere outside the editor’s own update cycle.

Decorations have to go into the state (or be recomputed on the fly), so you’ll need a state field to store them. The general approach here would be to, in the state field apply method, try to preserve the old page break decorations by mapping them, and in a plugin view update method, see if the page breaks have to be re-checked/recomputed, and if so, schedule a process that measures the doc and determines appropriate page breaks, compare those to the existing page breaks, and update a transaction that updates the state if they differ.

1 Like

Thanks for the reply!

Even if we forget about the scheduling problem part of this, I think there is a bigger problem that we just recently found. This is, tables. We’re relying on this tiptap custom extension that allow us to paint table elements in the doc. Following above approach causes a bunch of scenarios to pain divider / pagebreak inside a cell, reason being that posAtCoords is content agnostic, so it can return a pos inside of a table cell, and that’s where we paint the divider which is no bueno.

  • Any high level thoughts around this? Why do you think it’s so hard to get this right? The more we’ve thought about this, the more we feel we won’t be able to ship this 100% right, not even close to it. So many unknowns, so many edge cases, etc.

  • Do you have any insights on how google docs / microsoft word on the web is able to do this? This is definitely an interesting problem.

Thanks! cc/ @marijn

Google docs (and probably also the online MS office tools) do their own layout entirely, rather than relying on the browser’s DOM/CSS. And probably included pagination in their layout design from the start.

Thanks for the reply Marijn!

When you say “do their own layout entirely”, from the technical point of view, can you high level describe this a bit more? What does that mean? Are you talking about using a framework that let’s them paint the layout like that without too much of DOM manipulation? Is there any resource I can read that dives deeper into it?

Again, thanks so much for engaging with the community! cc/ @marijn

They compute where things are in JavaScript, rather then letting DOM/CSS control the positioning. Of course, I don’t know a lot, since none of this is open source.

2 Likes

I’ve tried simply inspecting google docs html and their positioning of elements is extensively customized.

I was excited to see the recent prosemirror flat list implementation, which is one part of what Google Docs does: ProseMirror Flat list (Alpha) - #5 by Saul-Mirone

This better sets up the editor to be lazily rendered (occlusion culling, better support for real time editing), in a way that gets around limitations of DOM hierarchies. I havent inspected how they implement tables, but I imagine something similar for rows or possibly not necessary?

@jair, If the resolved position is within a row, then it seems like simply programatically moving that to the start / end of a row would alleviate this particular problem (of many.) This would then necessitate creating a virtual table wrapper if a table is split. Tables not being split at all is probably a fine place to start.

I absolutely agree there are a lot of edge cases to think through to get this working well.