For example, given the following text:
- List item
non list
- List item again
If the user delete the whole line of “non list”, I would like both list items to be merged into one.
There’s an autoJoin
function that I can use to wrap most of the commands, e.g. backspace. However, it doesn’t seem to handle all of the cases. For example, cut with ctrl+x
doesn’t seem to be handled by the keymap, and I probably don’t want to override the default browser behavior. Similarly, what if “non list” is actually some image block that the user can drag away, causing the 2 lists to be merged together?
Will the right approach here be writing a plugin that does ‘appendTransaction’ to normalized all disjoined lists?
2 Likes
Did anyone figure out how to use autoJoin
inside appendTransaction
?
appendTransaction: (transactions, oldState, newState) => {
for (const transaction of transactions) {
autoJoin( // hmm...
Here’s a working solution 
import { Transaction, Plugin } from "prosemirror-state"
import { NodeType } from "prosemirror-model"
import { canJoin } from "prosemirror-transform"
// Ripped out from prosemirror-commands wrapDispatchForJoin
function autoJoin(
tr: Transaction, // An old transaction
newTr: Transaction, // The latest state
nodeType: NodeType // The node type to join
) {
if (!tr.isGeneric) return false
// Find all ranges where we might want to join.
let ranges: Array<number> = []
for (let i = 0; i < tr.mapping.maps.length; i++) {
let map = tr.mapping.maps[i]
for (let j = 0; j < ranges.length; j++) ranges[j] = map.map(ranges[j])
map.forEach((_s, _e, from, to) => ranges.push(from, to))
}
// Figure out which joinable points exist inside those ranges,
// by checking all node boundaries in their parent nodes.
let joinable = []
for (let i = 0; i < ranges.length; i += 2) {
let from = ranges[i],
to = ranges[i + 1]
let $from = tr.doc.resolve(from),
depth = $from.sharedDepth(to),
parent = $from.node(depth)
for (
let index = $from.indexAfter(depth), pos = $from.after(depth + 1);
pos <= to;
++index
) {
let after = parent.maybeChild(index)
if (!after) break
if (index && joinable.indexOf(pos) == -1) {
let before = parent.child(index - 1)
if (before.type == after.type && before.type === nodeType)
joinable.push(pos)
}
pos += after.nodeSize
}
}
let joined = false
// Join the joinable points
joinable.sort((a, b) => a - b)
for (let i = joinable.length - 1; i >= 0; i--) {
if (canJoin(tr.doc, joinable[i])) {
newTr.join(joinable[i])
joined = true
}
}
return joined
}
export class AutojoinPlugin extends Plugin {
constructor(nodeType: NodeType) {
super({
appendTransaction(transactions, oldState, newState) {
// Create a new transaction.
let newTr = newState.tr
let joined = false
for (const transaction of transactions) {
const anotherJoin = autoJoin(transaction, newTr, nodeType)
joined = anotherJoin || joined
}
if (joined) {
return newTr
}
},
})
}
}
2 Likes