Release 0.8.0

I’ve just released version 0.8.0. The goal of this release was to simplify the structure of the library, as described in issue #305. I think this succeeded gloriously, leading to much more cleanly decoupled modules, but it did have some costs. Firstly, there’s a long list of breaking changes (see below). And secondly, creating a basic editor is now somewhat more work, since there’s no longer a default schema or a magic system that automatically activates menu items and key bindings based on your schema.

You can see the new patches (all 130 of them) on github. Get the new version from NPM.

Release notes

Breaking changes

The src/ directory no longer uses ES6 modules, and is now CommonJS-based instead. With ES6 support—except for modules—being pretty much complete in browsers and node, that was the only thing holding us back from running the code directly, without compilation.

There is no longer a default schema in the model module. The new schema-basic module exports the schema, node types, and mark types that used to make up the default schema.

The schema option is no longer optional (though it is implied if you pass a doc option).

The Command abstraction was entirely removed. When the docs talk about commands now, they refer to plain functions that take an editor instance as argument and return a boolean indicating whether they could perform their action.

Keymaps now map keys to such command functions. The basic keymap for an editor is no longer inferred from commands, but is now determined by the keymap option. The default value contains a minimal set of bindings not related to any schema elements.

Defining new options is no longer a thing. Modules that used to use defineOption now export plugins.

Changing options at run-time through setOption is no longer supported, since the remaining built-in options don’t require this.

The docFormat option, getContent/setContent editor methods, and the format module have been dropped. You need to explicitly convert between formats yourself now.

The DOM parsing and serializing from the format module was moved into the model module, as the toDOM and parseDOM methods.

The conversions to and from text and HTML were dropped. HTML can be done by going through the DOM representation, the text format conversions were never very well defined to begin with.

Declaring the way a node or mark type is parsed or serialized was simplified. See the new toDOM, matchDOMTag and matchDOMStyle properties.

The SchemaItem class, along with the register and updateAttrs static methods on that class, have been removed. NodeType and MarkType no longer share a superclass, and registering values on such types is no longer a thing.

Textblock is no longer a class that you derive node type classes from. Just use Block, and the isTextblock getter will return the right value based on the node type’s content.

Event handlers are no longer registered with node-style on methods, but attached to specific values representing an event source. The subscription module is used for this. The event emitters for an editor instance are grouped in its on property. So now you’d say pm.on.change.add(...) to register a change handler.

The event-like node type methods handleClick, handleDoubleClick, and handleContextMenu are no longer supported. Instead, you’re expected to use the click, clickOn, doubleClick, doubleClickOn, and contextMenu event emitters.

The removed event on marked ranges was replaced by an onRemove option.

The apply method on an editor now always returns the transform, whereas it used to return false if no change was made. Its scroll property (containing a commonly used options object) was removed.

The checkPos method on editor objects was removed.

The rank argument to addKeymap was renamed priority and its meaning was inverted.

The setMark method on editor instances was removed. Its role is mostly taken over by the toggleMark command.

The functionality from the ui/update module was moved into the edit module and is now available through the scheduleDOMUpdate, unscheduleDOMUpdate, and updateScheduler methods.

Selection objects now contain resolved positions, because that’s what you need 99% of the time when you access them. Their old properties are still there, in addition to $from, $to, $anchor, and $head properties. The constructors of the selection classes expect resolved positions now.

The findDiffStart and findDiffEnd functions were moved to methods on Fragment.

Some transformation methods are now less vague in their parameters. wrap requires the range to wrap and the entire set of wrappers as argument, lift expects a range and target depth.

The findWrapping and liftTarget functions are used to compute these before trying to apply the transformation. They replace canWrap and canLift.

The structure of the markdown parser and serializer was changed to no longer rely on SchemaItem.register. Adjusting the parser and serializer now works differently (you explicitly create an object rather than relying on information attached to node types).

The autoinput module was removed. The inputrules module now exports a plugin that can be used to add input rules, along with a number of basic input rules and helper functions to create schema-specific input rules.

The menu module is now a single module, which exports the menu primitives along with the menuBar and tooltipMenu plugins.

Menu construction is no longer entangled with command definitions. The MenuCommand class was replaced with a MenuItem class, which directly declares the things it would previously get from a command. The concept of a MenuGroup was dropped (we just use arrays, since they are always static). Some helper functions for creating menu items, along with some basic icons, are exported from the menu module.

The ui module is now a single module, which exports the Tooltip class and the prompt-related functionality. Now that command parameters no longer exist, the interface for creating a prompt was also changed.

The events emitted by the collab module (which now exports a plugin) are now subscriptions on the plugin state object, named mustSend and receivedTransform.

Step.register was renamed to Step.jsonID.

All non-essential CSS rules were removed from the core.

Bug fixes

Several issues where the code didn’t properly enforce the content constraints introduced in 0.7.0 were fixed.

When pasting content into an empty textblock, the parent node it was originally copied from is now restored, when possible.

Several improvements in the handling of composition and input events (mostly used on mobile platforms). Fixes problem where you’d get a strange selection after a complex composition event.

Make by-word deletion work properly on astral plane characters.

Fix leaked spacer node when the menu bar was disabled while it was floating.

New features

Plugins are objects used to attach (and detach) a specific piece of functionality to an editor. Modules that extend the editor now export values of this type. The plugins option is the easiest way to enable plugins.

The contextAtCoords method provides more precise information about a given position (including the exact nodes around the position) than the existing posAtCoords method.

The EditorTransform abstraction is now more closely integrated with the editor selection, and you can call setSelection on it to update the selection during a transform.

The new applyAndScroll method on editor transforms provides a convenient shortcut for the common case of applying a transformation and scrolling the selection into view.

The new methods addActiveMark and removeActiveMark provide explicit control over the active stored marks.

The edit module now exports an object commands containing a number of command functions and functions that produce command functions. Most of the old command objects have an equivalent here.

The forEach method on nodes and fragments now also passes the node’s index to its callback.

Nodes now have a textBetween method that retrieves the text between two positions.

A new NodeRange abstraction (created with the blockRange method on positions) is used to specify the range that some of the transformation methods act on.

Node types got two new variants of their create method: createChecked, which raises an error if the given content isn’t valid (full) content for the node, and createAndFill, which will automatically insert required nodes to make the content valid.

The fixContent method on node types was removed.

You can now pass a second argument to the Schema constructor, and access its value under the schema object’s data property, to store arbitrary user data in a schema.

The transform module now exports an insertPoint function for finding the position at which a node can be inserted.

The OrderedMap class received a new method, addBefore.

A new module, example-setup provides a plugin that makes it easy to set up a simple editor with the basic schema and the key bindings, menu items, and input rules that used to be the default.

The list node types in the basic schema now require the first child of a list item to be a regular paragraph. The list-related commands are now aware of this restriction.


What replaces Custom.register('parseDOM', 'div' ... and Custom.prototype.serializeDOM ?

Edit: matchDOMTag and toDOM look like what I’m looking for:

Just wanted to chime in and say that the restructure of commands and usage of keymap has allowed me to drastically cleanup my integration of ProseMirror. I have a handful of behavior differences, particularly around lists, and it is a huge help to be able to chain commands. This allowed me to inject my special handlers before the defaults and then fallback to the defaults for non special cases.

Thanks for letting me know! That’s the effect I was hoping for.

@marijn: Is there any reason to suspect that transformations that were serialized to JSON in PM 0.7.0 would possibly not be applicable on an instance running PM 0.8.1?

Transformations don’t have a toJSON method, but if you mean their steps, their JSON format should be compatible between 0.7 and 0.8 (but not between 0.6 and 0.7).

Yes, sorry. The steps. Thanks, that’s very helpful!

before PM 0.8.0, I used to be able to do other operations with dom nodes that were created. For example put them into a formula rendering system or a citation creation system. Now that serializeDOM has been exchanged with ```toDOM``, I no longer have access to the dom node that is being created, do I?

I used to do:

let dom = serializer.renderAs(node, "span", {
    class: 'equation',
katexRender(node.attrs.equation, dom, {throwOnError: false})
dom.setAttribute('contenteditable', 'false')
return dom

For leaf nodes, you can still return any DOM node from toDOM, and your example looks like it’s a leaf node.

I see, thanks.

How about turning the collaborative mode off and then on again in order to set the document?

I see I can do collabEditing.detach() to get rid of it. But I can find no way of reattaching it or of setting the version when reattaching it.

You can do collabEditing.config({version: X}).attach(pm).


And then I was wondering why you access the collab the way you do. It seems like what was previously available under pm.mod.collab can now be found under pm.plugin.Collab, yet you seem to be using let collab = collabEditing.get(pm) to get it. Is this just a preference or is there a reason why one shouldn’t access it under pm.plugin.Collab?

And one thing that seems slightly unpractical: Earlier one could listen to the signal from the collab module when it received new transforms. Now one has to register a listener. The main difference here is that this listener has to be reregistered every time the collab module is readded, which wasn’t the case when signals were used.

I wonder: would it not be possible to allow for setDoc and changing the version of the collab plugin without having to remove it? Maybe there could be a setting for the collab module to ignore the setDoc?

It’s not documented, which means you shouldn’t use it. The Collab property name is generated at run-time (in a way that prevents collisions), and it isn’t predictable. Always go through the plugin object to get its state.

I’m not sure what you mean. What did you mean to type where it says ‘readded’? The lifetime of subscriptions is the same as the lifetime of the old event handlers – they live as long as the collab object.

Ah ok. That makes sense.

Ok, somehow that didn’t trigger any bugs in our previous version. At any rate, I found a way around it.

I think our code is working again after the transition to PM 0.8.2 . I just hope the amounts of changes needed will start getting less in future releases.