Decoration throws error after inserting new line

Hi Marijn, thanks for your work on the project. I’m recently implementing a comment plug in using decorations inside Tiptap editor. It works fine (the initial rendering) until I insert new line to the commented word and there would be an error from decoration.

My code is below:

import { Extension } from '@tiptap/core'
import { Plugin, PluginKey } from 'prosemirror-state'
import { DecorationSet, Decoration } from 'prosemirror-view';

export interface Comment {
    commentId: string;
    startOffset: number;
    endOffset: number;
    textData?: string;
}

export interface Comments {
    comments: Comment[];
    decos?: DecorationSet;
}

const applyDecorations = (doc: any, comments: Comment[]) => {
    const decorations: Decoration[] = [];
    const docSize = doc.content.size;
    comments.forEach(comment => {
        // if position is outside of doc size range Decoration would throw an error
        if (comment.startOffset <= 0) {
            comment.startOffset = 1;
        }
        if (comment.endOffset >= docSize) {
            comment.endOffset = docSize - 1;
        }
        const className = comment.commentId === COMMENT_TO_ADD_ID ? 'comment-in-progress' : 'comment';
        const deco = Decoration.inline(comment.startOffset, comment.endOffset, { class: className });
        if (deco) {
            decorations.push(deco);
        }
    });
    if (decorations.length === 0) {
        return DecorationSet.empty;
    };
    return DecorationSet.create(doc, decorations);
}

declare module '@tiptap/core' {
    interface Commands<ReturnType> {
        commentWhileEditing: {
            setUpdatedComments: (comments: Comment[]) => ReturnType,
        }
    }
  }

export const CommentWhileEditingExtension = Extension.create<Comments>({
    name: 'commentWhileEditing',
    config: {},

    addCommands(){
        return {
            setUpdatedComments: (comments: Comment[]) => ({ state, dispatch, tr, ...rest }) => {
                this.options.comments = [
                    ...comments,
                ];
                return false;
            }
        }
    },

    addProseMirrorPlugins() {
        const extensionThis = this;

        return [
            new Plugin({
                key: new PluginKey('commentWhileEditing'),
                state: {
                    init(config: any, editorState) {
                        const { doc } = editorState;
                        const { comments } = extensionThis.options;
                        if (comments?.length) {
                            const decorations = applyDecorations(doc, comments);
                            return { decos: decorations, comments};
                        }
                        return { decos: DecorationSet.empty, comments};
                    },
                    apply(transaction, prev) {
                        const {doc, docChanged} = transaction;
                        const { comments, decos } = extensionThis.options;
                        if (!docChanged) {
                            return {decos: prev.decos, comments};
                        }
                        if (comments?.length) {
                            const decorations = prev.decos.map(transaction.mapping, transaction.doc);
                            return { decos: decorations, comments };
                        }
                        return { decos, comments };
                    },
                },
                props: {
                    decorations(state) {
                        return this.getState(state).decos;
                    }
                }
            })
        ];
    }
});

The decorations update is based on const decorations = prev.decos.map(transaction.mapping, transaction.doc) which I get the idea from this code example

I only need to render the comments based on startOffset and endOffset, no updates are needed. When I insert characters to commented words it works fine (startOffset and endOffset will extend accordingly) but when I insert new line (by typing return) it would throw an error and the behavior gets random (cursor moving randomly, font will change).

Error log:

this.members[i].localsInner is not a function
    at DecorationGroup.locals (index.js:3927:1)
    at iterDeco (index.js:1880:1)
    at NodeViewDesc.updateChildren (index.js:1245:1)
    at NodeViewDesc.updateInner (index.js:1342:1)
    at NodeViewDesc.update (index.js:1334:1)
    at ViewTreeUpdater.updateNextNode (index.js:1762:1)
    at index.js:1263:1
    at iterDeco (index.js:1885:1)
    at NodeViewDesc.updateChildren (index.js:1245:1)
    at NodeViewDesc.updateInner (index.js:1342:1)

init decorations:

After inserting new line:

I’ve tried some ways to debug and cannot fix it. Really appreciate if anyone can show some direction to debug this issue. Thanks in advance!

If you can reduce this to a smaller and tiptap-free bit of code, I can probably find time to look into it.

Thanks for the reply Marijn! I’ll see if I can reproduce the same with tiptap-free version.