linkIntro

ProseMirror is a well-documented, but fairly nuanced, "toolkit for building rich-text editors." These are Bill's notes on how all the ProseMirror pieces fit together, with embedded examples where possible.


linkReference

ProseMirror data flow:



NodeView

dispatch generally: send off a transaction to EditorView to generate a new state, which replaces view.state. You can see this in this test, where we dispatch a transaction through view.dispatch (that happens in buildApplyDateSuggestion), and subsequently, the same view object has apparently mutated with a new state object

NodeView can be used to control how either a node or a plugin is rendered into the html DOM. Plugins also have views, which control how the state from the plugin is output.

update called when PM determines that something in this node or plugins view has changed, so the view is updating itself.

"This node passed to update may be anything that the editor's update algorithm might try to draw here, so you must verify that this is a node that this node view can handle [before acting on it in the update method]."


Document Position

One widespread and kind of weird thing about position in ProseMirror is that, while they refer to indexes within the ProseMirror DOM, when you're providing the index for a node, you usually give the index one before the node's actual index. This is necessary because nodeAt and nodeAfter will only return the node that you intend to refer to if they're given an index prior to that node.

tl; dr Often hard to get without mapping from starting document state through every transaction. Three options are using forEach()nodesBetween(), or descendants().

Resolving the node before a given position: apparently $pos.doc.resolve($pos.before(1))

Resolving node after an index is the default doc.nodeAt(pos), used often

Side exercise: Examining nodeAt

Dollar sign $ it would seem always/only used to indicate a ProseMirror ResolvedPos. Examples: $start, $end, $head, $anchor, $pos. It is unclear to Bill why this convention was chosen.


Plugins

Use of a transaction meta key to indicate to a plugin when it should take action (emoji popper reading meta example as triggered here, file plugin reading meta example as set here from this originating caller that wants to store info about the current local/remote UUID of the file the file-plugin is trying to display)

Why? Because it provides a signal that can be easily detected in the plugin's state.apply method, and used to change the state of the plugin's state variables

Plugins can (always do, in my experience) have a view that is used to change how the document looks

Control how document looks through marks (saved changes to nodes in the doc) or Decorations (ephemeral modifications), discussed below

View and main plugin both will often use class variables to store state. In date suggester, the plugin instance uses a local variable pluginView to keep handy the instance of its view (stored in the createDateSuggestionPlugin() closure)

In view, these class instance vars are used:


The plugin's state.apply function as well as its appendTransactions function runs every time any transaction is dispatched, which more often (in general, a lot more often) than every time the editor visibly updates. The view's update() method is, according to Jordan, called only once per render.

It can be ambiguous where to store state between the plugin, the view, and a potential React component the view mounts. In general, the less systems have to know about the variable, the better.

Legacy knowledge

State

Alternate syntax, from the key codePluginKey.getState(state)


Decorations

Have a type (inline, node, widget); they control aspects of how a node or view is shown without participating in the prosemirror dom


Transactions

From the docs: "When the user types, or otherwise interacts with the view, it generates ‘state transactions’. Every state update has to go through updateState, and every normal editing update will happen by dispatching a transaction."

setMeta used to communicate over transactions between plugins/view/whatever else

Here's a piece of the method that updates local file UUIDs to remote file UUIDs, using setMeta to tell the history plugin not to mind the transaction it's going to generate


linkLess frequently referenced, still relevant

Data structures


linkOpen questions pending further research

Why does doc.nodesBetween() and doc.forEach return pos indexes that are one before where the corresponding node appears to be within the ProseMirror document structure? It's tempting to think it would be so that doc.resolve can be called on the position that's returned, and still select the desired node? I.e., because doc.resolve chooses the next node after the index given, perhaps these iteration functions return an index one prior to the element they're iterating to account for the behavior of doc.resolve?