ianstormtaylor / slate

A completely customizable framework for building rich text editors. (Currently in beta.)
http://slatejs.org
MIT License
29.6k stars 3.23k forks source link

Collaborative editing #259

Closed davolokh closed 4 years ago

davolokh commented 8 years ago

@yatskevich and me did a research and tried to design an appropriate architecture for supporting collaborative editing. There are some key points with explanation below.

The main idea is to implement a kind of Redux-like behavior within Slate. <Editor store={Store} … /> The goal of having Store is ability to have clear separation and understandable flow.

UseCase:

This way allows us to have ability to manage operations in any suitable way (CRDT, OT, etc.) within Store.

Let’s see how to handle text paste operation in more details:

  1. Action pasteText dispatched to Store (data: {position, text}) No need to get current state, call transform() and return new state. Just dispatch an Action.
  2. Store has a Reducer for handling such an Action If we’re talking about ColloborativeStore - one type, if about SimpleStore - another one. We can even have few implementation of CollaborativeStores (CRDT and OT).

CollaborativeStore:

SimpleStore:

In our mind such architecture allows Slate to be more flexible and extensible. But this is not a completely analyzed proposal - just an idea.

And a few pictures for clarification.

Workflow Chart: fullsizerender

Implementation example fullsizerender 5

pastText in depth: fullsizerender 3

P.S. Possibly, Store should send Transfroms (not Actions) to Server and get Transforms back.

@ianstormtaylor: Could you please take a look at this and share your opinion?

GermanJablo commented 3 years ago

TinyMCE explained everything they had to do to achieve an OT with E2E encryption. Could someone please tell me if the solutions posted here meet that goal? Thank you very much for the help!

TheSpyder commented 3 years ago

Not really. It's a lot easier to do E2E with CRDT, but I don't think any of those frameworks offer it today. We made it hard for ourselves by deciding to go the OT route - and then add E2E which I don't believe any other OT-based editors offer - but we're really happy with the result.

dmonad commented 3 years ago

Both Skiff and Serenity Notes built E2E note-taking apps with the Yjs CRDT. The latter is now open-source and might provide a good starting point.

GermanJablo commented 3 years ago

Thanks to both of you. I'm going to review those two tools you mention. I am more interested in OT than in CRDT because it preserves the intention better. Although as far as I know TinyMCE is the only OT with E2EE, and I am still not in a position to pay its price.

dmonad commented 3 years ago

it preserves the intention better

I can say with some authority that this is not the case. OT has other advantages, but intention-preservation is not something that OT does particularly well.

GermanJablo commented 3 years ago

Well, until now I had heard otherwise. I will do some tests to verify it. Another problem I have is that I want my product to have a version history. Also, I am not an expert but I understand that CRDT consumes more memory (since it does not "erase" characters). However it confuses me that some say it is faster / efficient than OT and others not. Were those the advantages of OT you were referring to?

LionsAd commented 3 years ago

@german-jablo You can decide for yourself:

OT is like a navigation system:

go 5 blocks left, go three blocks up, …

Once you’ve already moved 1 block left, the instructions read:

go 4 blocks left, go three blocks up, …

CRDT is an address, which never changes:

Go to client1-40 street

OT and CRDT are in the end equivalent ways and can actually transformed into each other (a paper has proven that).

And it makes intuitive sense:

If you have the address, then you can always get the directions of how to get to that address - regardless of how the state changes.

If you have directions, you can ensure that if the state (your position) changes that you still find the same address by transforming the operations how to get there.

I personally find it intuitively easier to “name” each character with a client unique ID.

It feels much easier to think of objects that change in relation to each other, than to transform operation stacks, but to each their own.

As for E2EE: We have an interview with Serenity Notes author and E2EE up at Tag1.com/yjs, but the secret nutshell is:

Just sync the whole document always.

Feels excessive, but if you think of the megabytes of data transferred on YouTube etc. it’s actually not a big deal.

Hope that helps!

mitar commented 3 years ago

There is also a middle ground: https://github.com/campadrenalin/ConcurrenTree

TheSpyder commented 3 years ago

I'm not going to get into a mud-slinging match but I feel like someone needs to defend OT in this thread. I stand by what I have said in my blog posts - for simple cases (plain text, JSON) yes OT and CRDT are equivalent and CRDT is usually the better choice.

For a rich text editor, however, OT offers preservation of intent. That's a lot more than just mathematical equivalence. With Slate's 9 operations there is a matrix of 81 transforms to implement, but the payoff is that user intent is preserved. The key example I keep returning to is splitting a node. In Slate it's a split node operation, not a combination of inserts and deletes that a plain JSON OT or CRDT model would use to describe it. That offers very useful context when deciding how to transform conflicting operations.

As for E2EE: We have an interview with Serenity Notes author and E2EE up at Tag1.com/yjs, but the secret nutshell is:

Just sync the whole document always

Feels excessive, but if you think of the megabytes of data transferred on YouTube etc. it’s actually not a big deal.

It is a big deal, and TinyMCE encryption is done at the operation level. Open up our demo and monitor the websocket connection - only tiny amounts of data are sent and received.

GermanJablo commented 3 years ago

I appreciate everyone's contribution. @TheSpyder What you are saying sounds like the holy grail of the RTC. From what little I understand, I think that the solution TinyMCE is working on is the best on the market in RTC, be it OT or CRDT. I think not many appreciate it because the research you did is very technical.

It would be great if you could publish for more clumsy people like me the basics of the French paper you used and how you combined Jupiter / Soct5 to achieve the result.

By the way, do you have plans to integrate RTC in the core version?

TheSpyder commented 3 years ago

What you are saying sounds like the holy grail of the RTC.

I am not the first - there are others who have successfully built an OT-based collaborative editor with Slate. Some have posted in this thread. They are the ones who inspired us to do it.

I think not many appreciate it because the research you did is very technical.

It would be great if you could publish for more clumsy people like me the basics of the French paper you used and how you combined Jupiter / Soct5 to achieve the result.

Ah. I am not Tim, who wrote that post; I am Andrew, author of the earlier posts. I will let him know there is interest in a deep-dive.

By the way, do you have plans to integrate RTC in the core version?

That's still TBD. For now, our only announced plan is to include it with our premium offering; it will be cloud-only at launch with an on-prem version available later (we're hoping it will be in beta at launch).

GermanJablo commented 3 years ago

I am not the first - there are others who have successfully built an OT-based collaborative editor with Slate. Some have posted in this thread. They are the ones who inspired us to do it.

Yes, but if I'm not mistaken there are none that are (1) E2EE compatible (2) that only store changes and (3) allow a version history.

For everything else, thank you very much!

dmonad commented 3 years ago

@TheSpyder

For a rich text editor, however, OT offers preservation of intent.

I know what you mean. But no conflict-resolution algorithm that exists can preserve the actual intent of the user because the algorithm doesn't understand what the user wants to do. All conflict-resolution approaches offer different tradeoffs when it comes to intent-preservation. Even CRDTs can offer a high degree of intent-preservation.

Your article made me kinda sad when I read it: https://www.tiny.cloud/blog/real-time-collaboration-ot-vs-crdt/ Citing from the article:

If you can show me a rich text editor with support for CRDT, I can show you why I struggle to call it a "rich" text editor. Perhaps we should call them moderately wealthy text editors.

Maybe you should have done a bit more research. Your split-node problem has been solved in Yjs since 2016. You completely misrepresent all the work that so many people put into different CRDT-based editor bindings. You just happened to use a very bad implementation of a CRDT to make the assumption that all CRDT implementations are bad.

None of the 10 editors that Yjs supports has the behavior that you are describing: https://github.com/yjs/yjs-demos + https://www.tiptap.dev/ + https://remirror.io/

Personally, I find the debate pointless to compare generic CRDTs vs OT. CRDTs can do everything that OT can do, and vice versa. Most "researchers" in this field have done half-assed research only to support their argument. Every CRDT paper that claims that it is "faster" than OT doesn't talk about the tradeoffs that they had to make. Every paper that claims that OT is clearly superior to CRDT is wrong because OT-operations can be represented on top of a CRDT. Yjs, for example, has full support for the OT RichText type (it transforms the OT operation to a CRDT operation to offer a stronger consistency model).

So instead of mud-slinging, we should probably just compare implementations by their features and performance metrics. None of your numerous posts describes a single valid argument against the Yjs CRDT.


@german-jablo

If TinyMCE's OT approach does everything you are looking for, go ahead and use it. I just wanted to make clear that intent-preservation is not something that OT is inherently better at.

Another problem I have is that I want my product to have a version history. Also, I am not an expert but I understand that CRDT consumes more memory (since it does not "erase" characters). However, it confuses me that some say it is faster / efficient than OT and others not. Were those the advantages of OT you were referring to?

The slate-yjs binding currently doesn't support versions and tracking changes (only y-prosemirror does). CRDTs in general don't have to consume much more memory than OT (although some certainly do). It's part of the same fallacy that @TheSpyder ran into. They looked at a single bad implementation and judged that all implementations consume too much memory. Yjs has excellent performance metrics even for huge documents. You can write the Bible into a Yjs document while using less than 5MB of ram.


Just to be clear. I have nothing against OT. Let's just stop with these pointless debates of citing papers from researchers that only want to popularize their approach. If you can't reproduce a "bad behavior" in a specific implementation, then you shouldn't make an argument that a certain thing is not possible. If OT works for you, that's good for you. Keep using it.

TheSpyder commented 3 years ago

I'm sorry you feel that way. But if we can't keep this on topic I'm going to lock the conversation. If you want to have a go at me please feel free to do it privately.

Every example you linked to - including ot-richtext - is either plain text, based on Quill, or based on Prosemirror. These are the editors I was describing that only support a subset of HTML (although prosemirror is better than it was when we made the call to use Slate+OT for TinyMCE in 2019). I have repeatedly said CRDT is perfect for that context and if those editors are sufficient more power to you.

This conversation is about Slate. Slate has a much more flexible document model, and while it isn't an officially supported yjs editor there are yjs bindings that do demonstrate this specific issue. And it's not like this is the only issue that comes up, it was just the easiest to show in a blog post. https://www.loom.com/share/1450a0c84f9b4cf58b4aedec6a0cc00a

dmonad commented 3 years ago

You are committing the same fallacy again. Just because you see one counter-example, you are judging that this is an inherent problem. I'm familiar with the Slate data model. Yjs has data types that enable you to linearize the content, similarly to how you do it. Slate-yjs just doesn't use this feature yet. This is not a hard problem to solve, as it can be seen on hand of numerous complex editor bindings that don't show the same behavior. OT doesn't have inherently better intention-preservation than CRDTs. This is just a wrong statement to make.

TheSpyder commented 3 years ago

And you seem to have missed the conclusion of my article. If slate-yjs can be that good, please help this community by guiding it to get there. I'll help in any way I can. It would make my life a lot easier if TinyMCE could connect yjs to our Slate model instead of our custom OT solution :)

BitPhinix commented 3 years ago

@dmonad I'm really curious on how you would go about linearizing the content. Could you share some pointers?

dmonad commented 3 years ago

Sure, I'm happy to help. In order to solve the split-node problem on text-nodes you can make use of the formatting attributes.

Assuming you have the text "Hello World", where "Hello" is bold and "World" is bold and italic:

{
    "object": "text",
    "leaves": [
     {
        "object": "leaf",
        "text": "Hello",
        "marks": [italic]
      }, {
        "object": "leaf",
        "text": "World",
        "marks": [bold, italic]
      }
    ]
}

You can represent this text object using a Y.Text and formatting attributes (this is basically equivalent to the idea of marks):

const ytext = new Y.Text()

ytext.insert(0, 'Hello World', { italic: true }) // insert "Hello World" as italic text
ytext.format(6, 5, { bold: true }) // assign bold formatting attributes to the word "World"

Or using the delta notation:

ytext.applyDelta([
  { insert: 'Hello ', attributes: { italic, true } },
  { insert: 'World', attributes: { italic: true, bold: true } }
])

A formatting attribute can be any JSON-encodable key-value pair. You can granularly remove or update attributes without replacing the underlying text. These are meta-properties that you can assign to ranges of content in the Yjs document.

ytext.toDelta() // => [{ insert: 'Hello ', attributes: { italic, true } }, { insert: 'World', attributes: { italic: true, bold: true } }]

In order to solve the split-node scenario that Andrew described, you just need to map Y.Text with formatting attributes to a Slate text node. I recommend working with the Y.Text delta events that should map nicely to Slate's operations, and vice versa.

I'll help in any way I can. It would make my life a lot easier if TinyMCE could connect yjs to our Slate model instead of our custom OT solution :)

@TheSpyder That would be great :) Let me know when you need help. For Yjs-specific discussions, I'm also available on the discussion board https://discuss.yjs.dev/

BrentFarese commented 3 years ago

@dmonad and @TheSpyder or anyone else on this thread. We use Slate for Aline and are going to get to collaborative this year for sure. As with everyone, we have looked at OT vs. CRDT. YJS looks very interesting and we have considered using it.

We would definitely sponsor some open source work to improve official YJS-Slate bindings that would help the community (and allow us to use the bindings for Aline). We might be able to commit some resources ourselves in a couple of months too.

Is anyone interested in participating/co-sponsoring that type of work? I think it could be valuable to both Slate and YJS to extend the reach of both projects. @dmonad have you done anything like that in the past?

Thanks!

BrentFarese commented 3 years ago

Can we also list Slate bindings in YJS docs (maybe designate as WIP if they're not ready yet)?

dmonad commented 3 years ago

Hi @BrentFarese,

Thanks so much for offering this! @BitPhinix did an awesome job on the current slate-yjs binding. It would be great if you could sponsor him to work improve this implementation a bit.

We finance Yjs additions with our open-collective. We can open a separate project for slate-yjs if @BitPhinix or someone else is interested in working on this. I don't have the time currently and can only offer my feedback.

Can we also list Slate bindings in YJS docs (maybe designate as WIP if they're not ready yet)?

Yeah, I thought I already added it. Will do it in a bit.

BitPhinix commented 3 years ago

Thanks @dmonad! Opening a slate-yjs open collective project would be really helpful indeed.

I'd be happy to work on improving the current slate-yjs binding

BrentFarese commented 3 years ago

@dmonad and/or @BitPhinix let me know the link to the open collective when set up and we would be glad to contribute.

hanspagel commented 3 years ago

Hi @BrentFarese! I’m running the y-collective together with Kevin, and we’re eager to bring new people from the Yjs eco system on board. 🙃

I’ve just created a slate-yjs project: https://opencollective.com/y-collective/projects/slate-yjs In other words, it’s open to collect donations. I’m in contact with @BitPhinix anyway, so I’ll discuss with him all further details, but that’s probably out of scope of this issue. If any of you wants to reach out in private, my inbox is open: humans@tiptap.dev. Happy to connect everyone with everyone and make the Yjs ecosystem a little bit better every day. ✌️

mitar commented 2 years ago

So instead of mud-slinging, we should probably just compare implementations by their features and performance metrics.

For everyone on this thread wanting to learn more, this is a great writeup about CRDT performance, if you haven't yet seen it. In the similar spirit that it is really hard to compare things by one implementation of some approach, but you really have to take time to understand how underlying things work and then at the end really do comparable benchmarks. This is a good suite by @dmonad who already commented above.

stevenfabre commented 1 year ago

Hi everyone,

Just wanted to let you know that Liveblocks now offers a Yjs provider which makes it very easy to enable collaborative editing for Slate. Here is the quick start documentation for Slate and Yjs: https://liveblocks.io/docs/get-started/yjs-slate-react

Let me know if you have any feedback!