Hey Everyone, since a ton of people are using YJS now I thought I’d share a workaround for the decorations mapping issue ( when external changes are coming into YJS it replaces the whole document, causing DecorationSet.map to return an empty array, effectively deleting decorations ).
Just use YJS absolute positions in the plugin state, and create decorations from that. In case of prosemirror-image-plugin:
const createDecorations = (state: EditorState) => {
// @ts-ignore
const updatedState: Array<{ id: object; pos: any }> = imagePluginKey.getState(state);
const YState = ySyncPluginKey.getState(state);
const decors = updatedState.map((i) => {
const pos = relativePositionToAbsolutePosition(YState.doc, YState.type, i.pos, YState.binding.mapping);
const widget = document.createElement('placeholder');
const deco = typeof pos === "number"? Decoration.widget(pos, widget, {
id: i.id
}): undefined;
return deco;
});
// @ts-ignore
return DecorationSet.create(state.doc, decors.filter(i=>i)) || DecorationSet.empty;
};
export const createState = <T extends Schema>() => ({
init() {
return [];
},
// @ts-ignore
apply(tr: Transaction<T>, value: StateValue[], oldState: EditorState<T>, newState: EditorState<T>): StateValue[] {
const action: ImagePluginAction = tr.getMeta(imagePluginKey);
if (action?.type === 'add') {
const YState = ySyncPluginKey.getState(newState);
const relPos = absolutePositionToRelativePosition(action.pos, YState.type, YState.binding.mapping);
return [...value, { id: action.id as object, pos: relPos }];
} else if (action?.type === 'remove') {
return value.filter((i) => i.id !== action.id);
}
return value;
}
});
use the creareState
functions in the plugin’s state, and createDecorations
in the decorations field.
Good luck!