Mix bullet and ordered list


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)
        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.