Hello,
I have a plugin that adds node decorations to empty nodes, so that I can use CSS to show that they are empty.
To update the decorations, the plugin’s apply
method uses findDiffStart
/ findDiffEnd
and then nodesBetween
to only look at the nodes that may have changed.
Surprisingly, the value returned by findDiffStart
is often larger than those returned by findDiffEnd
. I “fixed” it by taking the min and the max before calling nodesBetween
but this does not sound right…
Am I doing something wrong?
Here is the code of the plugin:
// nodes that should receive an 'empty' class if they are empty
const emptyNodeTypes = ['paragraph', 'section', 'listing', 'quote', 'example']
function isEmptyNode(node: Node) {
return emptyNodeTypes.indexOf(node.type.name) >= 0 && node.nodeSize === 2
}
export const emptyPlugin: Plugin = new Plugin({
state: {
init(_, {doc}) {
// go over the entire document and create the initial set of decorations
let emptyBlocks: Decoration[] = []
doc.descendants((node, pos, _parent, _index) => {
if (isEmptyNode(node))
emptyBlocks.push(Decoration.node(pos, pos+node.nodeSize, { class: 'empty' }))
return true
})
return DecorationSet.create(doc, emptyBlocks)
},
apply(tr, set) {
// update the decorations by going over the part of the document that was changed
// transform the decorations through the transaction
set = set.map(tr.mapping, tr.doc)
// if there is no steps, we're done
if (tr.docs.length === 0)
return set
// find the range of the changes
let diffStart = tr.docs[0].content.findDiffStart(tr.doc.content)
let diffEnd = tr.docs[0].content.findDiffEnd(tr.doc.content)
if (diffStart === null || diffEnd === null) // there was no change
return set
// in some cases start is after end!
const start = Math.min(diffStart, diffEnd.a, diffEnd.b)
const end = Math.max(diffStart, diffEnd.a, diffEnd.b) +1 // sometimes we miss the last one...
// go over the nodes in the range and record the decorations to add/remove
let add: Decoration[] = []
let remove: Decoration[] = []
tr.doc.nodesBetween(start, end, (node, pos, _parent, _index) => {
if (emptyNodeTypes.indexOf(node.type.name) >= 0) {
const deco = Decoration.node(pos, pos+node.nodeSize, { class: 'empty'})
if (node.nodeSize === 2)
add.push(deco)
else
remove.push(deco)
}
return true
})
// update the decoration set and return it
return set.remove(remove).add(tr.doc, add)
}
},
props: {
decorations(state) {
return emptyPlugin.getState(state)
}
}
})