Ckeditor5 error with undoing multiline paste operation

283 Views Asked by At

I have a plugin for my ckeditor build which should convert pasted content with formulas, separated by '(' ')', '$$' etc. into math-formulas from ckeditor5-math (https://github.com/isaul32/ckeditor5-math). I changed the AutoMath Plugin so that it supports text with the separators.

I have run into a problem where undoing (ctrl-z) the operation works fine for single-line content, but not for multiline content.

To reproduce the issue, I have built a similar plugin which does not require the math plugin. This plugin converts text enclosed by '&' to bold text. To reproduce this issue with an editor instance it is required to have the cursor inside a word (not after or before the end of the text, I don't know why that doesn't work, if you know why, help is appreciated^^) and paste it from the clipboard. The content will inside the '&' will be marked bold, however if you undo this operation twice, an model-position-path-incorrect-format error will be thrown.

example to paste:

aa &bb& cc
dd

ee &ff& gg

Undoing the operation twice results in this error:

Uncaught CKEditorError: model-position-path-incorrect-format {"path":[]}
Read more: https://ckeditor.com/docs/ckeditor5/latest/support/error-codes.html#error-model-position-path-incorrect-form

Unfortunately, I haven't found a way to fix this issue, and have not found a similar issue. I know it has to do with the batches that are operated, and that maybe the position parent has to do something with it, that I should cache the position of the parent. However, I do not know how.

Below my code for an example to reproduce:

import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import Undo from '@ckeditor/ckeditor5-undo/src/undo';
import LiveRange from '@ckeditor/ckeditor5-engine/src/model/liverange';
import LivePosition from '@ckeditor/ckeditor5-engine/src/model/liveposition';
import global from '@ckeditor/ckeditor5-utils/src/dom/global';

export default class Test extends Plugin {
    static get requires() {
        return [Undo];
    }

    static get pluginName() {
        return 'Test';
    }

    constructor(editor) {
        super(editor);

        this._timeoutId = null;
        this._positionToInsert = null;
    }

    init() {
        const editor = this.editor;
        const modelDocument = editor.model.document;
        const view = editor.editing.view;
        //change < Clipboard > to < 'ClipboardPipeline' > because in version upgrade from 26 to 27
        //the usage of this call changed
        this.listenTo(editor.plugins.get('ClipboardPipeline'), 'inputTransformation', (evt, data) => {
            const firstRange = modelDocument.selection.getFirstRange();

            const leftLivePosition = LivePosition.fromPosition(firstRange.start);
            leftLivePosition.stickiness = 'toPrevious';

            const rightLivePosition = LivePosition.fromPosition(firstRange.end);
            rightLivePosition.stickiness = 'toNext';

            modelDocument.once('change:data', () => {
                this._boldBetweenPositions(leftLivePosition, rightLivePosition);

                leftLivePosition.detach();
                rightLivePosition.detach();
            }, {priority: 'high'});
        });

        editor.commands.get('undo').on('execute', () => {
            if (this._timeoutId) {
                global.window.clearTimeout(this._timeoutId);

                this._timeoutId = null;
            }
        }, {priority: 'high'});
    }


    _boldBetweenPositions(leftPosition, rightPosition) {

        const editor = this.editor;
        const equationRange = new LiveRange(leftPosition, rightPosition);

        // With timeout user can undo conversation if wants to use plain text
        this._timeoutId = global.window.setTimeout(() => {
            this._timeoutId = null;

            let walker = equationRange.getWalker({ignoreElementEnd: true});
            let nodeArray = [];
            for (const node of walker) { // remember nodes, because when they are changed model-textproxy-wrong-length error occurs
                nodeArray.push(node);
            }
            editor.model.change(writer => {
                for (let node of nodeArray) {
                    let text = node.item.data;
                    if (node.item.is('$textProxy') && text !== undefined && text.match(/&/g)) {

                        let finishedFormulas = this._split(text);


                        const realRange = writer.createRange(node.previousPosition, node.nextPosition);
                        writer.remove(realRange);

                        for (let i = finishedFormulas.length - 1; i >= 0; i--) {
                            if (i % 2 === 0) {
                                writer.insertText(finishedFormulas[i], node.previousPosition);
                            } else {
                                writer.insertText(finishedFormulas[i], {bold: true}, node.previousPosition);
                            }
                        }
                    }
                }
            });
        }, 100);
    }

    _split(text) {
        let mathFormsAndText = text.split(/(&)/g);
        let mathTextArray = [];
        for (let i = 0; i < mathFormsAndText.length; i++) {
            if (i % 4 === 0) {
                mathTextArray.push(mathFormsAndText[i]);

            } else if (i % 2 === 0) {
                mathTextArray.push(mathFormsAndText[i]);
            }
        }
        return mathTextArray;
    }
}

Let me know if I can clarify anything.

0

There are 0 best solutions below