Hi,
I am implementing a menu that should allow me to swap easily between bullet or ordered list.
I’ve managed to get something that works unless we start mixing types of list:
- here is the starting point, an ordered list followed by a bullet list

- when I apply a bullet list, it will incorrectly nest the list. Instead it should have removed the existing level (using
liftListItem
) then rewrapped them with the correct type (using wrapInList
). The lift part is probably incorrect.

The issue is that I didn’t yet find a way to iterate on the selection to detect all types of list it may contain.
I am doing const innerNode = state.selection.$from.node(1);
, which will correctly get the parent “bullet” or “ordered” list if there is only one type of list. If there is a mix in the selection, it will get only the 1st list.
selection.ranges
gives only one range, while I would have expected a list I could iterate on.
What features should I explore to bypass this issue? I probably miss something about selections and nodes.
I am also using liftListItem
which is probably too granular, instead I want to lift all items of all the selected lists before applying the new type. I think I can even reproduce the issue when creating two lists of the same type one after the other.
You can iterate through a NodeRange
using its parent
, startIndex
, and endIndex
properties (i.e. calling parent.childAt(...)
on the covered indices).
1 Like
Cool I should be able to figure something out.
Example code for googlers:
// I select 2 top-level lists
const range = state.selection.$from.blockRange(state.selection.$to)
console.log({
range, parent: range?.parent,
// this is the first list
first: range?.parent?.child(range?.startIndex),
// this is the last list
last: range?.parent?.child(range?.endIndex - 1)
})
// iterating from start to end index let me do some operations on each list
// eg removing each list node
// and replacing by a new one (eg turning the 2 lists into a big bullet list)
Something I couldn’t figure yet: now I can have a NodeRange
whose child are lists. I iterate over this range as show earlier, so I get a Node
for each list.
Now, I wonder how to build a new NodeRange
out of this list Node
, that correspond to the list items.
My goal is to reuse the logic of liftOutOfList
, which takes a NodeRange
as parameter corresponding to the list. But what I have is a NodeRange
corresponding to multiple lists, and by iterating over it a Node
for each list.
export function liftOutOfListTr(tr: Transaction, range: NodeRange): Transaction | false {
let list = range.parent
I’ve tried something like const itemRange = new NodeRange(list.resolve(0), list.resolve(list.nodeSize), 1)
but it doesn’t really work, list.resolve(0)
seems to point to the list itself, not it’s first children. I need a resolved position to call blockRange
.