Release 0.23.0 (possibly to be 1.0.0)

It’s been two and a half month since 0.22.0, but finally 0.23.0 is here. It comes with quite a long list of changes, some of them breaking, since I’ve been reviewing all the parts that I wasn’t happy with yet and cleaning them up. I also worked on the docs, where I noticed and removed some methods that seemed superfluous.

Unless something really bad is found, I’m going to release this, maybe with some minor fixes, as 1.0.0 in two weeks (end of September). If you’re using ProseMirror, trying to upgrade and reporting anything that goes wrong would be very helpful.

You’ll find a new guide, more examples, and a somewhat more polished reference manual on the website. Those probably contain typos still, since I only read them once after I wrote them, so if you read them, issues or pull requests are appreciated.

Release notes

prosemirror-model 0.23.0 (2017-09-13)

Breaking changes

ResolvedPos.marks no longer takes a parameter (you probably want marksAcross if you were passing true there).

Attribute and mark constraints in content expressions are no longer supported (this also means the prosemirror-schema-table package, which relied on them, is no longer supported). In this release, mark constraints are still (approximately) recognized with a warning, when present.

ContentMatch objects lost a number of methods: matchNode, matchToEnd, findWrappingFor (which can be easily emulated using the remaining API), and allowsMark, which is now the responsibility of node types instead.

ContentMatch.validEnd is now a property rather than a method.

ContentMatch.findWrapping now returns an array of plain node types, with no attribute information (since this is no longer necessary).

The compute method for attributes is no longer supported.

Fragments no longer have an offsetAt method.

DOMParser.schemaRules is no longer public (use fromSchema and get the resulting parser’s rules property instead).

The DOM parser option topStart has been replaced by topMatch.

The DOMSerializer methods nodesFromSchema and marksFromSchema are no longer public (construct a serializer with fromSchema and read its nodes and marks properties instead).

Bug fixes

Fix issue where whitespace at node boundaries was sometimes dropped during content parsing.

Attribute default values of undefined are now allowed.

New features

contentElement in parse rules may now be a function.

The new method ResolvedPos.marksAcross can be used to find the set of marks that should be preserved after a deletion.

Content expressions are now a regular language, meaning all the operators can be nested and composed as desired, and a bunch of constraints on what could appear next to what have been lifted.

The starting content match for a node type now lives in NodeType.contentMatch.

Allowed marks are now specified per node, rather than in content expressions, using the marks property on the node spec.

Node types received new methods allowsMarkType, allowsMarks, and allowedMarks, which tell you about the marks that node supports.

The style property on parse rules may now have the form "font-style=italic" to only match styles that have the value after the equals sign.

prosemirror-transform 0.23.0 (2017-09-13)

Breaking changes

Step.toJSON no longer has a default implementation.

Steps no longer have an offset method. Map them through a map created with StepMap.offset instead.

The clearMarkup method on Transform is no longer supported (you probably needed clearIncompatible anyway).

Bug fixes

Pasting a list item at the start of a non-empty textblock now wraps the textblock in a list.

Marks on open nodes at the left of a slice are no longer dropped by Transform.replace.

New features

StepMap now has a static method offset, which can be used to create a map that offsets all positions by a given distance.

Transform objects now have a clearIncompatible method that can help make sure a node’s content matches another node type.

prosemirror-view 0.23.0 (2017-09-13)

Breaking changes

The onFocus, onBlur, and handleContextMenu props are no longer supported. You can achieve their effect with the handleDOMEvents prop.

Bug fixes

Fixes occasional crash when reading the selection in Firefox.

Putting a table cell on the clipboard now properly wraps it in a table.

The view will no longer scroll into view when receiving a state that isn’t derived from its previous state.

New features

Transactions caused by a paste now have their “paste” meta property set to true.

Adds a new view prop, handleScrollToSelection to override the behavior of scrolling the selection into view.

The new editor prop clipboardTextSerializer allows you to override the way a piece of document is converted to clipboard text.

Adds the editor prop clipboardTextParser, which can be used to define your own parsing strategy for clipboard text content.

DecorationSet.find now supports passing a predicate to filter decorations by spec.

prosemirror-inputrules 0.23.0 (2017-09-13)

Breaking changes

Schema-specific rule-builders blockQuoteRule, orderedListRule, bulletListRule, codeBlockRule, and headingRule, along with the allInputRules array, are no longer included in this module.

prosemirror-commands 0.23.0 (2017-09-13)

Breaking changes

joinForward and joinBackward no longer fall back to selecting the next node when no other behavior is possible. There are now separate commands selectNodeForward and selectNodeBackward that do this, which the base keymap binds as fallback behavior.

baseKeymap no longer binds keys for joinUp, joinDown, lift, and selectParentNode.

New features

New commands selectNodeForward and selectNodeBackward added.

prosemirror-schema-list 0.23.0 (2017-09-13)

Bug fixes

The splitListItem command now splits the parent list item when executed in a (trailing) empty list item in a nested list.

5 Likes

Hey, it looks like I can no longer import buildKeymap from the example setup. Is that on purpose and is there some other way to do that now?

That looks like a mistake I made while changing the module syntax to import/export. I’ve released prosemirror-example-setup 0.23.1 with a fix.

1 Like

Thanks for the update! :slight_smile: I’m in the process of upgrading to 0.23 and so far there seems to be one breaking change which I’m not sure how to handle yet:

What is the reason that this method is no longer supported? I was so far using it to generate unique guids for certain node types, which worked well. As nodes can get created as part of normal editing without custom commands being involved, I’m not sure how I would go about ensuring that newly created nodes always have a unique guid attribute. Any suggestions how I could handle this without the compute method?

I am facing exactly the same problem as @kiejo and as first thought I considered to re-architect that part of my app to get rid of those unique computed guids, but have yet to come up with a solution …

I don’t think it did — those guids would end up being duplicated when you duplicated the node, for example by copy-pasting. The only solid way to assign unique ids, as far as I’m aware, is to have an appendTransaction handler scan the document for missing or duplicate ids and fix them.

@marijn thanks, yeah, the appendTransaction seems to be a much better approach.

My solution to this was to write a toDOM and parseDOM method which would not serialize or deserialize the guid attribute, which made it use the computed value instead. I tested this in Chrome and the nodes get unique computed values when I copy and paste.
Are there any other cases besides copy and paste that would actually need to be handled? If this handles all cases, I’m not sure I would prefer the approach of using appendTransaction to handle this.

You are likely to see some weird effects in corner cases in, for example, the replace-fitting algorithm, where it might generate nodes and then get confused when they don’t have the expected identical markup. In general, node creation taking new information out of a side channel (the random number generator) goes against the grain of this system (which tries to be as referentially transparent as possible).

That makes sense, thanks for clearing this up! In that case I’ll implement it using the suggested appendTransaction approach.

@rsaccon i’m very interested in a typical implementation of this. I tried the parseDOM/toDOM, also i tried several things with node views, and indeed, there’s always some case that screw the whole thing.

I cannot figure how the marks attribute of the node is supposed to work with v. 0.23. Say I have two mark types (comment and anchor) that I want to allow on the inline content of the text block node called title. In the definition of each of the marks I added group: "annotation".

So as part of the definition of title I add either marks: "annotation" or marks: "comment anchor" but in both cases it will refuse to put comments or anchor marks on the content of the title node. Only if I take the marks line away entirely, it will allow me to add the marks. But then there are no restrictions at all for titles, and that’s not what I wanted. I looked through the repos and documentation, but I cannot find examples for restricting marks.

The entire definition of title looks like this:

let title = {
    content: "text*",
    marks: "comment anchor",
    group: "part",
    defining: true,
    parseDOM: [{
        tag: "div.article-title"
    }],
    toDOM(node) {
        return ["div", {
            class: 'article-part article-title'
        }, 0]
    }
}

Your code was right, but there was a bug in the library breaking this. I’ve released prosemirror-model 0.23.1 with a fix.

Great! thanks so much!

We’re almost at the end of the month. One more breaking change was proposed, which I think was a good idea, and some small fixes were made, so I’ve just released 0.24.0 as a second candidate for 1.0.

Release Notes

prosemirror-transform 0.24.0 (2017-09-25)

Breaking changes

The setNodeType method on transforms is now more descriptively called setNodeMarkup. The old name will continue to work with a warning until the next release.

prosemirror-view 0.24.0 (2017-09-25)

New features

The clipboardTextParser prop is now passed a context position.

prosemirror-history 0.24.0 (2017-09-25)

New features

It is no longer necessary to manually enable the preserveItems option to the history plugin when using collaborative editing. (This behavior is now automatically enabled when necessary.)

prosemirror-model 0.23.1 (2017-09-21)

Bug fixes

NodeType.allowsMarks and allowedMarks now actually work for nodes that allow only specific marks.

prosemirror-gapcursor 0.23.1 (2017-09-19)

Bug fixes

Moving out of a table with the arrow keys now creates a gap cursor when appropriate.

4 Likes

@kapouer, @rsaccon I just implemented a very rudimentary mechanism to handle unique IDs using appendTransaction and thought I’d share it here. This mechanism assumes that the attribute with the unique ID is set to null by default.
This can probably be optimized and maybe there’s a better way to implement this, but it’s what I came up with for now and hope that it’s helpful to others.

import {Plugin} from 'prosemirror-state'

// :: (?Object) → Plugin
//
//   config::
//
//     key:: string
//     The name of the attributes which store unique ids.
//
//     generateId:: (Node) -> string
//     The function which should be used to generate new ids.
export function uniqueIds(config) {
  return new Plugin({
    state: {
      init() { return null },
      apply() { return null },
    },

    appendTransaction(transactions, oldState, newState) {
      const ids = {}
      const tr = newState.tr
      let modified = false
      newState.doc.descendants((node, pos) => {
        const {[config.key]: id, ...rest} = node.attrs
        if (typeof id != 'undefined') {
          if (id == null || ids[id]) {
            // Id is not set or already taken => generate and set a new id
            const newId = config.generateId(node)
            tr.setNodeMarkup(pos, null, {[config.key]: newId, ...rest})
            ids[newId] = true
            modified = true
          }
          else
            ids[id] = true
        }
      })

      if (modified)
        return tr
    },
  })
}

An example of using the Plugin would look like this:

EditorState.create({
  schema,
  plugins: [
    uniqueIds({
      key: 'guid',
      generateId(node) { return uuidV4() }
    })
  ]
})
1 Like

Interesting plugin! What’s the use-case for something like this?

@kiejo, looks great, thanks for creating this. @bradleyayers, when I needed something like this, I was experimenting with components (well, just prosemirror custom nodes) for scroll navigation, and to provide the dom element of the the scroll targets I dom-queried for that unique id. It is just an implementation detail, there are probably other ways to achieve the same.

2 Likes

@marijn, I stumbled upon https://github.com/ProseMirror/prosemirror-changeset. Is this used anywhere on the site or the examples ? Couldn’t find any …

These are two example use cases where we use unique document node IDs:

  • We generate meta data from the document content including the number of open and closed tasks. To efficiently keep this meta data up-to-date and in sync with all clients, we need to be able to identify the task nodes.
  • We allow our users to right click on a table inside a document to export it as CSV. Here we use a unique ID to tell the server which table we would like to export as there might be many tables inside the same document.

But there are a few more use cases, that I have in mind, which we’re planning to implement and which will make use of unique node IDs.