Offline, Peer-to-Peer, Collaborative Editing using Yjs

Comparing structure-sharing trees should actually be doable really efficiently (since you can skip all the shared nodes right away). (There was an implementation of this in a very early ProseMirror system that relied on it for its redrawing algorithm, and it wasn’t very complicated.)

1 Like

Thanks @saranrapjs for sharing that use-case. That is a good reason to preserve transaction steps.

I have been looking at some of the projects that compute diffs between states and they didn’t seem suitable. You are right that this should be easily doable by leveraging object identity. I will look into this tomorrow.

ive got a green field project, and its pretty seamless how this and tiptap, work together. i even wired up dexiejs with indexeddb and observable and can sync multiple browser windows. mightbe worth looking it with the other communication protocols.

keep up the good work

1 Like

I looked into the Yjs implementation and first of all it is awesome! @dmonad got the CRDT technology working stable and also optimized it to avoid a big data footprint. Congratulations.

But what I think is the crucial feature is that it works serverless. No central instance you need to trust and end-to-end encryption is also doable.

Because of that I believe the technology is worth taking a closer look. Even though the existing sync mechanism works great as well, from my understanding it is more difficult to have the single steps getting applied in the right order and the whole history needs to be remembered in case an older version of the document has been used as a starting point for edits. Please correct me, if I’m wrong.

That said I agree with @marijn that it is worth using the Prosemirror State as the basis for synchronization, since this is the heart of the philosophy behind the project and why it is the best solution for rich text editing available.

This is just my personal opinion I wanted to share. Anyway all I see here is exceptional great work on all sides and would love to see development being continued.

2 Likes

Awesome stuff, @dmonad! It’s great to see these 2 open source frameworks coming together.

I can’t wait to dig into the details :smile:

Thanks for you kind words @disarticulate @holtwick and @jhnsnc :slight_smile:

I do use the prosemirror state. But for me, the question was if it makes sense to use ProseMirror transforms to represent document changes. Currently, I simply replace the document state. This is easier to do for me. I didn’t see any immediate benefit in Transforms, because they are mainly used to calculate change maps and to provide undo-redo functionality. y-prosemirror has an equivalent to change maps and undo functionality, that work better in p2p scenarios. But @saranrapjs brought up a good point for ProseMirro transforms.

Today I started to adapt the code to use ProseMirror transforms instead. So don’t worry, you will get your transforms :wink: I also think it makes sense to support existing plugins.

4 Likes

I’m also using TipTap; did you create an extension with the plugin field with ySyncPlugin(type), yCursorPlugin(), yUndoPlugin()?

So far, I’ve only integrated the ySyncPlugin. With tiptap, I have to first:

  1. create the editor = new Editor from tiptap
  2. vue.$nextTick(() => editor.registerPlugin(ySyncPlugin(type))

There’s some other setup required, like when reloading, as indicated by @dmonad, i editor.clearContent() to remove, then follow the sync examples in the yjs documentation.

My general goal is to have different editor views, like print mode, another other integrated “living” document types.

1 Like

Thanks for clarifing and working on the plugin @dmonad.

@bhl I wrote a simple extension, which currently is just tracking updates. Maybe it is a starting point for your TipTap extension:

import { Extension } from 'tiptap'
import { redo, undo, ySyncPlugin, yUndoPlugin } from 'y-prosemirror'
import { keymap } from 'prosemirror-keymap'
import * as Y from 'yjs'

const ydoc = new Y.Doc()

ydoc.on('update', (updateMessage: Uint8Array, origin: any, doc) => {
    console.log('update', updateMessage, origin)
})

// const provider = new WebsocketProvider('wss://demos.yjs.dev', 'prosemirror', ydoc)

const type = ydoc.getXmlFragment('prosemirror')

export default class RealtimeExtension extends Extension {

    get plugins() {
        return [
            ySyncPlugin(type),
            // yCursorPlugin(provider.awareness),
            yUndoPlugin(),
            keymap({
                'Mod-z': undo,
                'Mod-y': redo,
                'Mod-Shift-z': redo
            })
        ]
    }   

}
2 Likes

@dmonad I am working on a new app which uses Prosemirror and has full offline support. The missing piece was providing offline support for Prosemirror. So this is a fantastic and most welcome addition.

The use of Web RTC and peer-to-peer is impressive, however if I understand correctly this relies on at least one PC up and running and accessible over the Internet at all times, in order for other devices to come and go and all instances keep in sync.

I can envisage this becoming a problem in the real world. Instead I’d like to (optionally) see the ability to have a central server which was always up to date as these clients come an go. Ideally all data on this server would be encrypted by a key only the end-users know, therefore maintaining data privacy.

Thoughts?

PS. I have looked at Yjs ages ago, clearly it’s time to revisit. Keep up the great work.

@nevf Isn’t this solved by websockets? There’s a yjs websocket client and server library. And as mentioned above, there’s a prosemirror demo of those libraries:

  • Yjs Prosemirror Example is a simple ProseMirror demo using y-websocket, without versions and offline editing. Use it to inspect network traffic.

@bhl Thanks for that. I did see a mention of a websocket client, but missed that there was a server. I also was under the impression there was only the webrtc implementation - my mistake.

I’ve just tried the websocket demo and it appears as though offline edits aren’t saved in the Browser (indexeddb). So if you close a Tab with websocket demo open, but you are offline then a) When you re-open the Tab you don’t see any content, b) when you go back online any edits you did offline before closing the Tab are lost.

Of course this may well be resolved using the y-indexeddb provider. Any idea?

Yeah, I think based off http://y-js.org/, you also need a database adapter for persistence while offline. Indexeddb is one way to do that.

1 Like

Hi @nevf, Yjs by itself is just a small CRDT implementation. There are several modules around Yjs that allow you to do different things.

Editor bindings, like y-prosemirror, y-codemirror, or y-quill make a specific editor collaborative. Connectors, like y-webrtc, or y-websocket handle how to sync to other peers. And Persistence adapters handle how to persist data to a database to make it available offline (e.g. y-indexeddb for the browser, or y-leveldb for the server).

The idea is to make Yjs as modular and unopinionated as possible and build a huge ecosystem around it. There is a list of stable extensions for Yjs here: https://github.com/yjs/yjs Yjs version 13 is pretty new and I haven’t ported everything yet, so please bear with me.

You can make the demos in https://github.com/yjs/yjs-demos offline ready by defining a service worker (e.g. pwabuilder) and including y-indexeddb.

FYI y-websocket-client/server are now bundled into a single repository (y-websocket).

Awesome, thanks for sharing the tiptap demo @holtwick. Lets put that into https://github.com/yjs/yjs-demos

1 Like

Sure @dmonad I added a pull request with a very basic sample for TipTap: https://github.com/yjs/yjs-demos/pull/7 cc @philippkuehn

1 Like

@dmonad Thanks for the extra info. I assume switching between connectors and adapters requires no changes to the code using these, is that right?

I have other more general questions about Yjs which I assume would best be posted in https://discuss.yjs.dev

OMG this is awesome! :heart:

@nevf Correct, that would be the best place to ask questions about Yjs.

Thanks @philippkuehn ^^ I demonstrated this last week at FOSDEM. After an initial issue with the network everyone in the lecture room synced up. https://fosdem.org/2020/schedule/event/yjs_shared_editing/

@dmonad, The yjs ecosystem is incredibly impressive work. I was able to very quickly get a yjs websocket server running along aside the client side prosemirror-yjs. There’s another thread that discusses CRDT and ramifications on automatic conflict resolution and I agree with you that automatic merging is preferable if revisions / change tracking is easily accessible / auditable / viewable. With that said, I was excited to see a minimal implementation in the prosemirror-versions demo.

Since my application has a custom comment / annotation plugin that works via decorations / prosemirror position map, I am extra curious as to where you are now with regards to a functional prosemirror decoration implementation (but also general support of prosemirror plugins as discussed in this thread)? This is critical for me to be able to adopt this library, and I’d like to know what to expect / timeline. Thanks so much!

4 Likes