clearFormatting for lists

I am using the following code to clear the formatting:

import { liftTarget } from 'prosemirror-transform';

export const FORMATTING_NODE_TYPES = [
    'heading',
    'code_block',
    'blockquote',
    'ordered_list',
    'bullet_list',
    'list_item',
];
export const FORMATTING_MARK_TYPES = [
    'link',
    'em',
    'code',
    'strike',
    'strong',
    'underline',
];

export function clearFormatting(state, dispatch) {
    const { tr } = state;

    FORMATTING_MARK_TYPES.forEach(mark => {
        const { from, to } = tr.selection;
        if (state.schema.marks[mark]) {
            tr.removeMark(from, to, state.schema.marks[mark]);
        }
    });

    FORMATTING_NODE_TYPES.forEach(nodeName => {
        const formattedNodeType = state.schema.nodes[nodeName];
        const { $from, $to } = tr.selection;
        tr.doc.nodesBetween($from.pos, $to.pos, (node, pos) => {
            if (node.type === formattedNodeType) {
                if (formattedNodeType.isTextblock) {
                    tr.setNodeMarkup(pos, state.schema.nodes.paragraph);
                    return false;
                } else {
                    let fromPos = tr.doc.resolve(pos + 1);
                    let toPos = tr.doc.resolve(pos + node.nodeSize - 1);
                    const nodeRange = fromPos.blockRange(toPos);

                    if (nodeRange) {
                        const targetLiftDepth = liftTarget(nodeRange);
                        if (targetLiftDepth || targetLiftDepth === 0) {
                            tr.lift(nodeRange, targetLiftDepth);
                        }
                    }
                }
            }
            return true;
        });
    });

    tr.setStoredMarks([]);

    if (dispatch) {
        dispatch(tr);
    }
    return true;
}

But for lists this doesn’t work as expected. Because if I have 6 list items and I select from the 2nd list item to the 5th list item and I run the clearFormatting command. It only clears the format for the first 2 list items. What am I doing wrong?

List isn’t really a format (mark) of the texts. Instead, it’d defined as the structure of the texts. That said, you should implement the transform that convert list into paragraph. Note that the actual implementation depends on the specs of the list, list_item and paragraph nodes. For instance, the implementation could be quite different depends on whether nested lists is supported.

I’ve wrote one implementation that unwraps list into paragraphs for non-nested list nodes.

Is there an update to what implementation you ended up using @rsdrsd

I ended up with

  function (state: EditorState, dispatch: any, editorView: EditorView) => {
    let { tr }: { tr: Transaction } = editorView.state;
    const { $from, $to }: { $from: ResolvedPos; $to: ResolvedPos } = tr.selection;

    // remove marks
    tr.removeMark($from.pos, $to.pos);

    // traverse all nodes within selection recursively and deal with each
    tr.doc.nodesBetween($from.pos, $to.pos, (node: ProsemirrorNode, pos: number) => {
      const formattedNodeType: NodeType = state.schema.nodes[node.type.name];

      if (
        (formattedNodeType && formattedNodeType.name !== 'paragraph') ||
        formattedNodeType.name !== 'text' ||
        // dont want to perform any lift on ordered or bullet lists
        !isListNode(formattedNodeType) // is not a bullet or ordered list node
      ) {
        let fromPos = tr.doc.resolve(tr.mapping.map(pos + 1));
        let toPos = tr.doc.resolve(tr.mapping.map(pos + node.nodeSize - 1));
        const nodeRange = fromPos.blockRange(toPos);

        if (nodeRange) {
          const targetLiftDepth: number | undefined | null = liftTarget(nodeRange);

          if (formattedNodeType.isTextblock) {
            // remove headers
            tr.setNodeMarkup(nodeRange.start, state.schema.nodes.paragraph);
            return false;
          } else if (targetLiftDepth || targetLiftDepth === 0) {
            // remove list_items, links, blockquotes
            tr.lift(nodeRange, targetLiftDepth);
            return false;
          }
        }
      }
    });
    tr.setStoredMarks([]);
    editorView.dispatch(tr);
    return true;
  }

i found that doing

        let fromPos = tr.doc.resolve(tr.mapping.map(pos + 1));
        let toPos = tr.doc.resolve(tr.mapping.map(pos + node.nodeSize - 1));

helped me to traverse all nodes within selection recursively and deal with each and not cause any replace step errors.