Serializing widgets that have state

Hello, new user here. Grateful to leverage the great work on CM6. I’ve dug in and created a widget I’m calling personField (modeled after WordWidget in the testing suite). In my case it’s decorating a single character and the intent is to have some user-selected data stored with widget (the person’s ID). All seems to be working wonderfully.

I understand that to serialize my document, I need to provide a toJSON method:

const personField = StateField.define<DecorationSet>({
      create() { return Decoration.none; },
      update(value, tr) {
        let returnValue = value.map(tr.changes);
        for (const effect of tr.effects) {
          if (effect.is(addDeco)) returnValue = value.update({ add: effect.value });
          else if (effect.is(filterDeco)) returnValue = value.update({ filter: effect.value });
        }
        return returnValue;
      },
      provide: (f) => EditorView.decorations.from(f),
      toJSON: ((value: DecorationSet, state: EditorState) => {
        console.log(`Serializing|${value.size}|${state.selection.main.from}|`);
        // eslint-disable-next-line @typescript-eslint/no-shadow
        value.between(1, state.doc.length, (from: number, to: number, value: Decoration):void => {
          console.log(`Got a person tag|${from}|${to}|${JSON.stringify(value)}`);
        });
        return 'TODO';
      }),
    });

I noticed that between seems to be the only way to get the widget positions, and that’s fine. But my typescript editor isn’t showing any useful properties on the value passed to toJSON. After reading and using the code base for a few days I’ve learned that if something is private or otherwise not exposed with Typescript types that I probably shouldn’t be using it that way! So ultimately my question is: am I going about this serialization correctly? Or more importantly, am I storing state correctly? Should I be using something other than DecorationSet as my generic? Or should I plow through and ignore Typescript? I like that last option the least (but understand if “this is the way”).

Thank you (in advance) for your time and consideration.
-Robert

Odd. In Emacs with TIDE (which uses tsserver, which I expect your editor does too), I get a whole bunch of properties for Decoration. The one you are interested in in this case is probably .spec, which is the original object you created the decoration from, and in which you could stash the serializable information you need.


I do indeed see spec, and I see that spec is an any type I have a love / hate relationship with Typescript. I found the interface I need to make Typescript happy: WidgetDecorationSpec

But now my error is:
Module ‘"@codemirror/view"’ declares ‘WidgetDecorationSpec’ locally, but it is not exported.

Right, that type isn’t exported. It probably won’t do you much good here anyway. What aspect of the decorations are you trying to serialize?

Like you said… just the information that I put into the Widget class. I can work around it. For what it’s worth (novice perpective) I’m just not completely clear on the various “*Specs” and if they’re supposed to be strictly internal or something that “I know about” or have references to

For what it’s worth, it would be really nice to have value.spec.widget resolve to whatever custom Widget type I was using. But I understand magic fairy dust may be involved there.

So last question… without the magic fairy dust… is it safe to cast spec.widget to my widget class? Thanks you for your time

OK so I think I answered my own question…

          // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
          if (value.spec.widget instanceof WordWidget) {
            console.log('yes it is');
          } else {
            console.log('not it isnt');
          }

this reports affirmative.

but perhaps this conversation is informative to you… to remember that this is how people are doing this… I respect your opinion if you say this isn’t how I should be doing it.

End note: I tried to use ProseMirror and without typescript support (honestly) I didn’t get very far at all. I appreciate the effort you put into making CM6 support TS by default.

1 Like