ProseMirror, YJS and decorations tip

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!

4 Likes

Maybe I’m missing something, but …

YJS absolute positions in the plugin state

It seems that your state apply function actually stores relative positions (by converting the absolute positions to relative using absolutePositionToRelativePosition)?

You’re totally right, I meant relative. I’d rather call them YJS or ProseMirror positions tbh :slight_smile: I get the naming, it’s just easy for me to mix up.

1 Like