Slate.js bindings

Greetings, Yjs team!

Is there any plans developing & supporting bindings for slate.js editor?

1 Like

Hey @pavlohibey,

It would be awesome to support Slate. Most of the Yjs modules have been funded or are a result of my contracting gigs. I would be happy to do it. But I need funding to develop and maintain the Slate editor binding.

Hopefully, we can add Slate to the Yjs ecosystem at some point.

4 Likes

Hey @dmonad,

Thanks for the great work on Yjs ecosystem.

I happen to use Slate editor in one of my project, and very interested in contribution to this Slate binding. As I understand, the binding module mainly serves to sync the editor state with Yjs data structure. For Slate, its internal state is JSON with the format as an example:
[
{ type: ā€˜heading.h1ā€™, children: [{ text: ā€˜Great Headingā€™ }] },
{
type: ā€˜paragraphā€™,
children: [{ text: 'Start collaborating ā€™ }],
}
]

From another post you replied, the json can be modeled using Y.Array and Y.Map in a nested way. I am not sure what it looks like for Slate json above, do we need to convert all nested object in JSON into Y.Map and put them into Y.Array as children for their parent, like this:

Y.Array [
Y.Map {
type: ā€˜heading.h1ā€™
children: Y.Array [Y.Map {text: ā€˜Great Headingā€™}]
},
Y.Map {
type: ā€˜paragraphā€™
children: Y.Array [Y.Map {text: 'Start collaborating '}]
},
]

Hope I made it clear ~ I am just trying to make the initial model right, so next what I can do is for every slate operation, locate the corresponding portion in this model and update using Yjs api.

Thanks

Hi @6thfdwp,

It would be awesome to have someone working on this :slight_smile:

Exactly. I think the best way to model this would be using Y.XmlElement and Y.XmlFragment. The y-prosemirror binding also uses these types instead of simply Y.Map and Y.Array.

If you think of Slate nodes as an XML model you could express the following

{ type: ā€˜heading.h1ā€™, children: [{ text: ā€˜Great Headingā€™ }] },

as

<heading.h1>Gread Heading</heading.h1>

Or in Yjs types:

const node = new Y.XmlElement('heading.h1')
const innerContent = new Y.Text()
innerContent.insert(0, 'Great Heading')
node.insert(0, [ytext])
// node.setAttribute('color', 'blue') // in case you want to add attributes to the model.

The advantage of using Y.XmlElement instead of YMap and Y.Array is that one Slate node can be nicely represented as a single Yjs type. This also saves a bit of bandwidth. If you define the schema correctly, you could use the same Yjs type for the ProseMirror binding (syncing Slate and ProseMirror).

Furthermore, bold and italic text can be nicely represented using formatting attributes:

const ytext = new Y.Text()
ytext.insert(0, 'Hello World!', { bold: true })
ytext.format(0, 5, { italic: true }) // format "Hello" italic

Thanks for the reply @dmonad, really helpful! Itā€™s good to get right direction in the first place.
So Y.XmlFragment is like a container to host a list of Y.XmlElement or Y.Text ?

I will look a bit more into ProseMirror binding, though I am not familiar with ProseMirror model representation, can get some ideas from it. If you donā€™t mind I will try to first get some initial implementation done for the sync logic in my local editor, and then hook up with network provider to make sure itā€™s all working, finally can extract it into a separate module like other binding.

It will take a bit more time to get it fully working, but could be exciting :smiley:, especially as you said with same Yjs type, two different editors can sync with each other, sounds awesome!

Exactly. A Y.XmlFragment is like a Y.XmlElement, but without attributes and without a node-name. The top node of a ProseMirror document doesnā€™t have any attributes, so the natural choice is to use a Y.XmlFragment. But you could also use a Y.XmlElement.

The ProseMirror binding is pretty complicated because it does a diff and then applies the differences to Yjs.

As a start you could simply start with designing the children of the top-paragraph as Y.XmlElements and then gradually work on granularity. The Gutenberg Binding is still incomplete because it actually just replaces the whole paragraph when something changes. But it works pretty nicely.

Looking forward to status updates from you :slight_smile:

1 Like

@6thfdwp Iā€™m accually nearly finished with a slatejs binding. I am currently using nested maps / arrays thought. Will oss it once Iā€™m finished with testing and some minor bug fixes.

2 Likes

Look forward to seeing your version when you send pull request to Yjs teem!

Sorry for the delay @6thfdwp,

we now have something working and are even using it in production without any issue (no cursors thought). Iā€™m kinda busy atm but I will open source it at the end of this or at the beginning of next week

Cool. I am experimenting the Y.XmlElement and Y.Text to model the Slate state suggested by dmonad get some text insert / delete operations to sync with Yjs.
Still trying to figure out the right way to transform like bold the text (Slate emits 4 operations in this case) Would be nice to learn from your implementation for these scenarios :smiley:

I took a lot of inspiration from https://github.com/cudr/slate-collaborative. Looking at their codebase might help you. You also have to think about the order in which you apply the events since multiple deletions / inserts at the same time can cause conflicts if you donā€™t watch out. Otherwise I just listen for slate events and apply it to the yjs doc and vise versa (like they do in the above-mentioned automerge example).

Hope that helps

Yes, thanks for the link. I am also using this one as inspiration. More edge cases than I thought, kind of get sync from Slate to Yjs working, plan to start looking into applying Yjs changes back.

@BitPhinix You mentioned open sourcing your slate integration a few weeks ago. Did this ever go through? Very interested in this. Thank you.

Iā€™m really busy atm but managed to squeeze some time in and publish it as a package. Havenā€™t tested it though but I will put more effort in once I find the time. We use the code in this package (but not the package directly since it is still part of your mono repo atm) in production @ barbra.io so it should be relatively bug-free.

If you find any bugs (or want to write tests), feel free to contribute :slight_smile:

The repo is: https://github.com/BitPhinix/slate-yjs
The npm package: https://www.npmjs.com/package/slate-yjs

I also added a demo project using the bindings, docs will follow soon:

2 Likes

Thanks for sharing @BitPhinix!

Iā€™m excited to see another editor made collaborative with Yjs :slight_smile: Thanks for putting in the work and open-sourcing it. If you donā€™t mind, Iā€™d like to add the demo to the yjs-demos repository. I think this will get a lot of people excited.

FYI: I tried out barbra and I love it. I wish I had something like this when I was a student.

Thanks for the feedback @dmonad :slight_smile:. I really appreciate you checking out Barbra.

Sure, adding it to yjs-demos would be awesome. I didnā€™t even knew that repository.

@dmonad ā€“ regarding your comment upthread:

The advantage of using Y.XmlElement instead of YMap and Y.Array is that one Slate node can be nicely represented as a single Yjs type. This also saves a bit of bandwidth.

Can you provide any detail/insight about the ā€œsaves a bit of bandwidthā€ part? (e.g., to first order, is this a 2x effect, or is it a 10% effect?)

Thanks!

You basically end up with two types instead of one. But because of compression of the update messages the network overhead is insignificant.

Regarding memory overhead, you would consume double the amount of memory. But the majority of memory used will be consumed by the type content (which is the same whether you use YXml or YArray+YMap). So even in this case the overhead is insignificant (unless you have a lot of nodes without content).

My recommendation is to use YXml if applicable. I think it models a lot of use-cases very well (mostly rich-text documents) But you should use whatever you feel most comfortable with. The difference in performance is insignificant.

1 Like

Hi @dmonad,

Iā€™m actually planning to also add a demo directly to the slate-yjs repo. Can I use your hosted y-websocket server running at demos.yjs.dev?