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!