slab / quill

Quill is a modern WYSIWYG editor built for compatibility and extensibility
https://quilljs.com
BSD 3-Clause "New" or "Revised" License
43.77k stars 3.4k forks source link

Deleting parent list leaves nested lists orphaned #4230

Open raghavsethi opened 5 months ago

raghavsethi commented 5 months ago

Steps for Reproduction

  1. Visit empty playground
  2. Create nested list with at least two levels
  3. Delete the first level

Expected behavior:

The remaining levels should dedent when their parent is deleted.

Here is an extremely hacky demonstration of what the behavior should be.

Actual behavior:

The remaining levels are orphaned (i.e. are indented as if they are nested one level deeper than they are).

Version:

2.0.2

Request

I realize that nested lists are a long-running source of interesting behavior, so I don't actually expect this to be fixed. However, this behavior is important to my use case so I'd love some advice on how to achieve this. I have a super hacky implementation below, and it's kinda wild that updating the class names actually causes Quill to generate the right deltas - so I'd love to know what the 'canonical' way of doing something like this would be.

Here's the working demo.

class ListDedentModifier {
    constructor(quill) {
        this.quill = quill;
        this.quill.on("text-change", this.onTextChange.bind(this));
    }

    onTextChange(delta) {
        for (const op of delta.ops) {
            if (op.attributes && op.attributes.list === null) {
                const cursorStart = this.quill.getSelection().index;
                const [line] = this.quill.getLine(cursorStart);
                const next = line.next;
                for (const child of next.domNode.childNodes ?? []) {
                    const className = child.className;
                    const indentMatch = className.match(/ql-indent-(\d+)/);
                    const currentIndentLevel = parseInt(indentMatch[1], 10);
                    const newIndentLevel = Math.max(0, currentIndentLevel - 1);
                    child.className = `ql-indent-${newIndentLevel}`;
                }
            }
        }
    }
}

Quill.register("modules/listModifier", ListDedentModifier);
luin commented 5 months ago

I would do something pretty similar but one missing case in your demo is deleting. Also instead of updating class names directly, you could go with quill.updateContents() to update the contents directly.

raghavsethi commented 5 months ago

@luin Thanks for the response, would you mind helping me understand how to use updateContents to achieve this? It seems pretty challenging to go from a tree of Blot nodes to deltas that apply on positions. IIUC the hard bit is that you have to construct a delta for each list item (which can be further nested) since each item is on a different line.

I'm happy to go read a bunch of code and figure it out but some pointers would be pretty helpful!