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.
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
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.
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
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
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
Resolving the node before a given position: apparently
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.
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
In view, these class instance vars are used:
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.
Alternate syntax, from the key
Have a type (inline, node, widget); they control aspects of how a node or view is shown without participating in the prosemirror dom
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
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?