Git Product home page Git Product logo

Comments (96)

amark avatar amark commented on August 22, 2024

I have already been working on this, been developing a jquery extension for tracking caret position differences in updated contenteditable containers. However it has been a while, and I never finished it - I'll look at it again, and try and comment an update here - hopefully it would be able to work. No attempt for images, however.

from sharejs.

josephg avatar josephg commented on August 22, 2024

Cool :)

from sharejs.

amark avatar amark commented on August 22, 2024

Hey, sorry, I worked on this for a little bit but didn't finish it (got stuck) and then went back to working on other things for the last few months. Computer crashed, lost the old work I had done, so I had to start afresh. I was checking out your Diff algorithm in textarea.js, and I noticed there is a bug if it is used on strings of HTML:

To simplify, take the function and make it have a callback so we can play with it in the console.

applyChange = function(doc, oldval, newval) {
    var commonEnd, commonStart;
    if (oldval === newval) return;
    commonStart = 0;
    while (oldval.charAt(commonStart) === newval.charAt(commonStart)) {
      commonStart++;
    }
    commonEnd = 0;
    while (oldval.charAt(oldval.length - 1 - commonEnd) === newval.charAt(newval.length - 1 - commonEnd) && commonEnd + commonStart < oldval.length && commonEnd + commonStart < newval.length) {
      commonEnd++;
    }
    if (oldval.length !== commonStart + commonEnd) {
      doc(commonStart, oldval.length - commonStart - commonEnd);
    }
    if (newval.length !== commonStart + commonEnd) {
      return doc(commonStart, newval.slice(commonStart, (newval.length - commonEnd)));
    }
  };

All that did was erase ".del" and ".insert", so we can now do:

applyChange(function(start,length){
console.log(start);
console.log(length);
},"hello world","hello");
// 5
// 6

So now we can see our diff:

var text = "hello world";
var textOld = "hello"
applyChange(function(start,length){
console.log(start);
console.log(length);
console.log(text.slice(start,start+length));
},text,textOld);
// 5
// 6
// " world"

Okay, good it works.
But, it FAILS on HTML, under specific conditions:

var text = "Hello <b>world <i>oops!</i></b>";
var textOld = "Hello <b>world </b>"
applyChange(function(start,length){
console.log(start);
console.log(length);
console.log(text.slice(start,start+length));
},text,textOld);
// 16
// 12
// "i>oops!</i><"
// should be: "<i>oops!</i>"

I know that the Diff algo is suppose to work on text, so I shouldn't assume that you intended it to work on HTML strings - therefore I'm not posting this as an issue, but you should definitely be aware that anybody trying to use ShareJS to transfer HTML string differences will get mismatching deltas.

Anyways, I just wanted to post this now, before trying to fix it - I'm sure it isn't that hard, I'm going to go attempt to do that right now... but I'm a super slow coder :P, so I just wanted to alert you first in case I forget.

Also, I originally was working on rich text with ShareJS but now I'm working on it with my own setup, I calculate the offset of the caret with respect to the HTML nodes from both left and right directions, which requires transferring a lot more information than a single offset inside of a string. I as having difficulty rebuilding the caret position into the correct node based solely off of a string offset (which is what I was trying to do before), so I gave it up. I'm sure it is possible, and is probably a lot more efficient than what I'm doing... but I'm having better success with my method, so I'm not sure how compatible it will be with ShareJS. But I'll definitely try. :)

Thanks!

from sharejs.

josephg avatar josephg commented on August 22, 2024

Thanks for letting me know, but that algorithm isn't meant to be used for rich text, or html editing. (Unless you're using sharejs to edit the html as text, in which case it won't really matter.) Its not even a proper diff algorithm - its just a hack to get around html's awful & inconsistant events.

Also, please make new issues for bug reports instead of tacking them on other issues.

from sharejs.

mreinstein avatar mreinstein commented on August 22, 2024

Hey Joseph,

I've been thinking about a way to support rich text, but it would be a pretty drastic departure from traditional OT, and I'm not even entirely sure it's possible given browser capabilities. I figured given your very vocal position on wanting unbloated/complicated code as much as possible that I'd at least present the idea.

What I'm thinking is something along the lines of the way real time video games work (a subset of the dead reckoning algo) basically the central server maintains a clock which is sync'd to clients over a socket protocol. The clients would submit operations to the server, providing operations and an associated timestamp. The server would receive these, broadcast the client change to all clients, and locally apply updates to the doc in order. Both the server and client would maintain a buffer of previous states, so that they could roll back, apply changes, and then roll forward. Both client and server would run this same algorithm, but the server's instance would be the authoratative copy that gets persisted.

The biggest challenge I can foresee to this right now is it would require the clients to have some way to identify "i'm changing this particular DOM node or nodes, and heres how to get to it in the document". kind of a 2 fold problem:

  • mutation support is limited in most browsers - maybe this could be circumvented by picking up key and mouse events on the document and maintaining state that way
  • not sure how to efficiently identify a series of DOM nodes in a document in such a way that it could be sent to other clients. maybe one solution is to have some kind of move list "start at the top node, go this sibling, go down another node, go to 4th sibling, etc"

Anyway, I know this is kind of a half baked idea but i think if the dom change identification problem could be solved this might be feasible, and probably has applications beyond sharejs.

Would love to hear any thoughts you have, and thanks for the awesomeness that is sharejs. : )

from sharejs.

amark avatar amark commented on August 22, 2024

I am implementing this, without a master server. Already written all the Range code for it, and a bunch of stuff. Probably several more weeks of work. But it works nicely. If Joseph doesn't care for it, @mreinstein give me a message and we can talk more separately.

from sharejs.

mreinstein avatar mreinstein commented on August 22, 2024

@amark,

How are you doing it without a master server? Browsers dont have any peer to peer mechanism that I'm aware of. You'd need to distribute changes from one client to all the others, so I assume there needs to be a server to act as the "reflector" to get these between browsers. I have a pretty good handle on the state management piece, its mostly identifying and sending the dom changes that has me a bit stumped. Would love to hear more!

from sharejs.

amark avatar amark commented on August 22, 2024

Of course I have a relay server, but it doesn't act as the ultimate authority -- just like another client. Please message me (it won't allow me to message you) separately, I do not want to bombard Joseph with notifications or go offtopic for this issue.

from sharejs.

bergie avatar bergie commented on August 22, 2024

Rich text support in ShareJS would be great for Create. Let me know if I can help (testing, experimenting) somehow. We have an issue tracking this.

from sharejs.

josephg avatar josephg commented on August 22, 2024

Cheers guys. As you predicted, I don't want to add a different concurrency algorithm to sharejs. Its possible to do rich text using ShareJS's OT implementation, its just going to be a bit of work. Best of luck getting your thing working though, that sounds neat.

from sharejs.

dlee avatar dlee commented on August 22, 2024

@josephg Any updates on a rich text editor using ShareJS's OT implementation?

from sharejs.

josephg avatar josephg commented on August 22, 2024

I haven't written any code for rich text. Patches welcome!

from sharejs.

wmertens avatar wmertens commented on August 22, 2024

Does #112 have everything this issue wants?

from sharejs.

scrummitch avatar scrummitch commented on August 22, 2024

Ok I think I have something pretty useful but its kind of buggy at the moment.

I am using an iframe to do my text editing and sharejs is listening to that document.
Setting text needs to be used with .innerHTML but when I call this function it resets all the ranges in the document to the start.

The replaceText function looks like:

   // Text replacer, important to move the cursors when you pull a .html()
replaceText = function(newText) {
    var anchor, focus, scrollTop, _ref, _ref1, _ref2;
    scrollTop = elem.scrollTop;

    var caret = getCaret();

    if (elem.scrollTop !== scrollTop) {
        elem.scrollTop = scrollTop;
    }

    elem.innerHTML = newText;

    return setCaret(caret.selectionStart, caret.selectionEnd);
}

   // And the Caret get/set functions
getCaret = function() {
    var iframe = $('.wysihtml5-sandbox')[0],
        caret  = iframe.contentWindow.getSelection();

    return {
        selectionStart     : caret.baseOffset,
        selectionEnd       : caret.extentOffset,
        isCollapsed        : caret.isCollapsed,
        caretPosition      : caret.focusOffset,
        selectionDirection : (caret.baseOffset < caret.extentOffset) ? 'forward' : 'backward',
        original: function() {
            return caret;
        },
        clone: caret,
    }
}

   setCaret = function(start, end) {
    var iframe = $('.wysihtml5-sandbox')[0],
        el = iframe.contentDocument.body;

    var range = document.createRange(),
        sel   = window.getSelection();

    var startContainer = range.startContainer;
    var endContainer = range.endContainer;

    range.setStart(startContainer, start);

    if (end) {
        range.setEnd(endContainer, end);
    }
    else {
        range.collapse(true);
    }

    sel.removeAllRanges();
    sel.addRange(range);
    el.focus();
}

Using this we can determine the line number (dom node) and the cursor position from the start.

Thoughts? It seems to be working great for me. Sometimes multi-line edits fail and merge together but sharejs does most of the patching right.

from sharejs.

ajb avatar ajb commented on August 22, 2024

@scrummitch i imagine that would run into issues when one user is typing and another user is also typing above her in the document. when user #2 inserts a character, user #1's cursor shifts from its current position. does that make sense?

from sharejs.

mb21 avatar mb21 commented on August 22, 2024

Have you guys read this Stackoverflow discussion? I got the impression that you cannot do HTML editing with traditional OT but have to either use a different markup notation (like Google Wave did) or do something like in this paper by Davis et al. Which approach do you think is more promising for sharejs?

from sharejs.

wmertens avatar wmertens commented on August 22, 2024

On Jan 29, 2013, at 13:14 , mb21 [email protected] wrote:

Have you guys read this Stackoverflow discussion? I got the impression that you cannot do HTML editing with traditional OT but have to either use a different markup notation (like Google Wave did) or do something like in this paper by Davis et al. Which approach do you think is more promising for sharejs?

There are trade-offs on both sides.

Treating the HTML document as a DOM object that you manipulate with JSON OT is a very natural mapping since you already have the DOM available. However, this makes text operations like searching etc cumbersome.

Storing attributes as ranges fixes that but converting to/from HTML becomes a slower operation.

Personally I prefer the DOM mapped object approach.

Wout.

from sharejs.

ajb avatar ajb commented on August 22, 2024

My naive opinion:

Trying to use a different markup notation seems like a pain.

contenteditable is fantastic, and anything that's supported in contenteditable needs to be able to be represented in a ShareJS document.

Etherpad approaches this by assigning a unique id attribute to each node in the DOM. Is this a feasible route for ShareJS?

from sharejs.

wmertens avatar wmertens commented on August 22, 2024

Isn't this something that should be solved by the editor? All browsers make
different HTML for the same view and the editor has to sanitize that with a
simplified DOM copy.

I believe http://aloha-editor.org/features.php does that, perhaps others as
well.

On Tue, Jan 29, 2013 at 7:14 PM, Adam Becker [email protected]:

My naive opinion:

Trying to use a different markup notation seems like a pain.

contenteditable is fantastic, and anything that's supported in
contenteditable needs to be able to be represented in a ShareJS document.

Etherpad approaches this by assigning a unique id attribute to each node
in the DOM. Is this a feasible route for ShareJS?

โ€”
Reply to this email directly or view it on GitHubhttps://github.com//issues/1#issuecomment-12848938.

from sharejs.

nornagon avatar nornagon commented on August 22, 2024

One major problem with this approach is the need to transform these
operations by each other:

document: {text: "this is some text"}
op A: [{p:["text",8],sd:"some"}] --> {text: "this is text"}
op B: [{p:["text"],od:"this is some text",oi:["this ",{b:"is"}," some
text"]}] --> {text:["this ",{b:"is"}," some text"]}
intended final document: {text: ["this ",{b:"is"}," text"]}
actual final document, with current JSON OT: {text:["this ",{b:"is"}," some
text"]}

i.e., op A is lost.

the JSON OT implementation doesn't have any way to split text and maintain
intention.

Rich text definitely needs a dedicated OT type.

j

On Wed, Jan 30, 2013 at 5:48 AM, wmertens [email protected] wrote:

Isn't this something that should be solved by the editor? All browsers
make
different HTML for the same view and the editor has to sanitize that with
a
simplified DOM copy.

I believe http://aloha-editor.org/features.php does that, perhaps others
as
well.

On Tue, Jan 29, 2013 at 7:14 PM, Adam Becker [email protected]:

My naive opinion:

Trying to use a different markup notation seems like a pain.

contenteditable is fantastic, and anything that's supported in
contenteditable needs to be able to be represented in a ShareJS
document.

Etherpad approaches this by assigning a unique id attribute to each node
in the DOM. Is this a feasible route for ShareJS?

โ€”
Reply to this email directly or view it on GitHub<
https://github.com/josephg/ShareJS/issues/1#issuecomment-12848938>.

โ€”
Reply to this email directly or view it on GitHubhttps://github.com//issues/1#issuecomment-12850694.

from sharejs.

mb21 avatar mb21 commented on August 22, 2024

I agree that if this is going to be useful, it needs to work with the contentEditable attribute, i.e. you get an HTML DOM (or string). Otherwise, the editor would have to implement its own layout engine as Google Docs did, which is a non-option for everyone without Google's resources.

The question is whether it's feasible, performance-wise, to parse all the HTML every time a change is made and then transform it to some JSON structure where you can do the OT stuff. The (more complicated) alternative would be to somehow keep track of all (or most of) the HTML elements (e.g. by assigning id attributes), so you could somehow have a pointer from every HTML paragraph to its corresponding JSON object and vice versa.

from sharejs.

wmertens avatar wmertens commented on August 22, 2024

On Jan 30, 2013, at 10:26 , mb21 [email protected] wrote:

I agree that if this is going to be useful, it needs to work with the contentEditable attribute, i.e. you get an HTML DOM (or string). Otherwise, the editor would have to implement its own layout engine as Google Docs did, which is a non-option for everyone without Google's resources.

The question is whether it's feasible, performance-wise, to parse all the HTML every time a change is made and then transform it to some JSON structure where you can do the OT stuff. The (more complicated) alternative would be to somehow keep track of all (or most of) the HTML elements (e.g. by assigning id attributes), so you could somehow have a pointer from every HTML paragraph to its corresponding JSON object and vice versa.

Well, that's pretty much what http://aloha-editor.org/ has to do anyway (minus the OT) so I would say it's not something to worry about.

The bigger problem, like Joseph pointed out, is coming up with an OT for rich-text-represented-as-DOM. E.g. when you make a word in a paragraph bold, you're splitting the paragraph and punching a sub-object in between. When at the same time someone fixes a spelling mistake in that word, that operation needs to be re-pathed into the sub-object.

Wout.

from sharejs.

mb21 avatar mb21 commented on August 22, 2024

@wmertens You mean parsing all the HTML is what editors like aloha already do? Yeah, guess you're right. So the currently most promising approach is to parse all the HTML to JSON, then use some modification of the traditional OT on that.

Has anyone read the paper by Davis et al. more carefully than I did? Seems like their data structure is implementable in JSON...

P.S.

However, this makes text operations like searching etc cumbersome.

But you could still do that on the HTML, e.g. TinyMCE has that functionality already.

from sharejs.

devongovett avatar devongovett commented on August 22, 2024

What's the best way to do rich text today? Has anyone been successful at it?

I've seen several threads on the mailing list as well as this thread, but nothing definitive. To summarize, one implementation using the old ShareJS uses multiple text fragments with the params stored on each one, but it seems unmaintained. There's also the old etherpad type from 0.6 that hasn't made it to 0.7 yet, and I'm not sure how well this works. There's also the Wave model, which I haven't seen anyone port that to ShareJS yet.

It seems like the predominant method people have converged on is to store a string, as well as separate ranges of attributes, kinda like how Apple's NSAttributedString is implemented. Is this a fair characterization? If so, could we just use the JSON type to synchronize this or would a separate type be needed?

I'd really love to see rich text support added to ShareJS, or a separate library on top of ShareJS to add support. If no one else has done this, I'm happy to work on it, I'd just love to see if we might come to a consensus on how best to implement this and to see the work that has already been done by others before I begin. Thanks!

from sharejs.

cypha avatar cypha commented on August 22, 2024

Will 0.7 support rich text? It's the one feature I'm waiting for.

from sharejs.

mb21 avatar mb21 commented on August 22, 2024

@devongovett any updates on this? Seems like you were right about storing plain text and formatting ranges separately; e.g. How the Medium Editor Works

from sharejs.

devongovett avatar devongovett commented on August 22, 2024

@mb21 yeah see the thread on #312. I'm hoping to open source some of my work (at least the OT type if not the rich text editor itself) after the product I'm working on launches hopefully next month. I'll keep everyone posted. ๐Ÿ˜„

from sharejs.

mb21 avatar mb21 commented on August 22, 2024

@devongovett thanks, looking forward to that! So you're using contenteditable to for input but the OT type has plain-text and formatting-ranges separately, right?

from sharejs.

josephg avatar josephg commented on August 22, 2024

@jhchen is coming by the office tomorrow to chat about getting Quill working with sharejs.

from sharejs.

luisherranz avatar luisherranz commented on August 22, 2024

Hey, @josephg, may we know how did the chat with @jhchen go? Any hope getting sharejs working with quill?

from sharejs.

morgante avatar morgante commented on August 22, 2024

@devongovett Any progress on that?

We're using ShareJS for our editor and our basic approach right now is to have a JSON representation of the all block-level elements, with specific operations allowed on them. (Ex. images don't have inserts, just replace URL).

The only issue is with inline formatting (ex. bold). Allowing them via content-editable is (predictably) super buggy when we're using a plain text OT type. Hence our (terrible) solution is to do locking on each block-level element and to apply full-text updates.

Anyone have alternatives at this juncture? Might Markdown provide a solution?

from sharejs.

jhchen avatar jhchen commented on August 22, 2024

Joseph and I have been having very productive discussions. The plan is to define rich text ottype that both Quill and ShareJS can use. I'm in the process of making those additions and there will hopefully be something to release soon.

from sharejs.

luisherranz avatar luisherranz commented on August 22, 2024

Those are superb news ๐Ÿ‘

from sharejs.

cklokmose avatar cklokmose commented on August 22, 2024

I have been working on something that may provide a generalized solution to rich-text editing with ShareJS. You can find my project where I do it here: https://github.com/cklokmose/Webstrate

Basically I am storing a representation of the DOM as JsonML in ShareJS and mapping MutationObserver events from the DOM to operations, and operations to changes in the DOM. Hence, it will not only support rich-text editing but editing anything expressed in the DOM (e.g. SVG).

from sharejs.

jhchen avatar jhchen commented on August 22, 2024

Hey guys, I pushed some code that is ready for public criticism (which would be very welcome): https://github.com/ottypes/rich-text. There are a few nontrivial todos (filed under its Github issues) but once they are resolved I will update Quill to use this ottype. For now there is a suit of tests (borrowed from another OT library that this and sharejs will succeed) and a passing fuzzer for reassurance that it actually works.

from sharejs.

luisherranz avatar luisherranz commented on August 22, 2024

Excellent work. I am no expert in OT but I read everything and all made sense to me. Sorry not to be of more help here.
Once you have it working with Quill I can try to create a Meteor app to test it out.

from sharejs.

morgante avatar morgante commented on August 22, 2024

@jhchen, what's the status on implementing this with Quill? I'd love to integrate this into a project, but it'd be great to see a reference implementation.

from sharejs.

jhchen avatar jhchen commented on August 22, 2024

There are some final work I need to do for rich-text but and then I'll implement the changes necessary changes to Quill to use rich-text along with a guide on migrating from the old format to the new.

from sharejs.

luisherranz avatar luisherranz commented on August 22, 2024

That's so great @jhchen.

from sharejs.

mitar avatar mitar commented on August 22, 2024

@jhchen: Anywhere to test this new things? I would love to beta test Quill + ShareJS and rich-text editing.

from sharejs.

luisherranz avatar luisherranz commented on August 22, 2024

Same here ๐Ÿ‘

from sharejs.

jhchen avatar jhchen commented on August 22, 2024

Quill has just completely switched over to the rich-text ottype so you can take a peek at the demos there. For example the two editors on http://quilljs.com/examples/ are passing the rich-text back and forth. Hooking everything up with ShareJS is still forthcoming though.

from sharejs.

mitar avatar mitar commented on August 22, 2024

Awesome. This really looks cool. Looking forward to ShareJS integration.

from sharejs.

luisherranz avatar luisherranz commented on August 22, 2024

@jhchen I am thinking... if ShareJs is "OT & webshockets" and you have now included OT in Quill... would it be possible to integrate this new Quill with a reactive framework like Meteor which includes webshockets by default, without the need of ShareJS?

Maybe it will be easy to write those OT commands to the mongoDB of meteor and then use the Quill API and the reactivity of meteor to process new OT commands in all connected browsers. What do you think?

from sharejs.

jhchen avatar jhchen commented on August 22, 2024

ShareJS is still a crucial piece. rich-text has the primitives (compose, transform) that can be used for OT but there still needs to be some engine to coordinate and utilize all this which is one of ShareJS's roles. This looks to be something meteor is thinking about though: https://trello.com/c/tkBErvIk/39-operational-transformation

from sharejs.

mitar avatar mitar commented on August 22, 2024

Can you point me to where in ShareJS code is that engine? I was looking at that but it seems that ShareJS just pushes all that logic to LiveDB and then leaves to LiveDB to transforms operations?

Could be that server-side logic be extracted somehow so that it could be integrated with Meteor?

from sharejs.

luisherranz avatar luisherranz commented on August 22, 2024

@jhchen I'm still new to OT but if you already implemented the OT commands and you can fire events like "text-change" so Meteor can store them as a list in a MongoDB. And then Meteor can send those DB changes to all connected browsers so they trigger the same commands and everything is synchronised... why is still ShareJS crucial? What is left that I am not seeing? Conflict resolution?

from sharejs.

mitar avatar mitar commented on August 22, 2024

No, server does not just redistribute operations, but server is one which decides which operation is primary, and then transforms the others accordingly, and then send things to other clients. See this section on Wikipedia.

from sharejs.

mitar avatar mitar commented on August 22, 2024

So what would be needed, that that transformation engine would be plugged into a Meteor method which is receiving new operation from the client, and then possibly transformed, and then stored into a collection, which is then send to all subscribed clients.

from sharejs.

jhchen avatar jhchen commented on August 22, 2024

Yes @mitar is correct. The part of sharejs that does this on the back end is in livedb. I'd also add the client also needs to perform OT and this can be found in the sharejs repo.

from sharejs.

mitar avatar mitar commented on August 22, 2024

Hm, is there a difference between this function on the client and server? Could we use the same function also on the server side in Meteor? Could that function be extracted into some separate module/library?

from sharejs.

jhchen avatar jhchen commented on August 22, 2024

Yes ottypes is the common/extracted functionality.

from sharejs.

mitar avatar mitar commented on August 22, 2024

But the code to transform these is not?

from sharejs.

jhchen avatar jhchen commented on August 22, 2024

The transform function itself is a part of ottypes (ex. rich-text's). It is the engine's role (ShareJS) to know when to use this. I'm not as familiar with ShareJS's implementation but usually the logic is slightly different between client and server. This is separate from other stuff like transport, messaging, or persistence but I believe this is already extracted from ShareJS (browserchannel, livedb).

from sharejs.

mitar avatar mitar commented on August 22, 2024

So Meteor can provide transport, messaging, and persistence. I would just like to better understand the logic clients and servers have to apply based on message/ops they receive. It seems that ShareJS does transforms at client here, and for server it passes it to the LiveDB?

What I would ideally like is a module/package, which would expose two generic functions: what to do with each op which you get from the server on a client and how it gets transformed so that one can pass this further to Quill. And what to do on the server side when you receive a new op from the client and would like to store that in to the MongoDB (from where other subscription would take an op and send it to other clients).

from sharejs.

luisherranz avatar luisherranz commented on August 22, 2024

As @mitar says... with Meteor taking care of transport, messaging and persistence maybe it is not the best idea to use ShareJS anymore because that would duplicate a lot of things.

If I understood it right, what it is left then is the "consistency": how to apply the transform function (which is part of ottypes and not of ShareJS).

BTW, I think the transform logic should be the same both in the client and in the server.
That's the whole point of having a copy of the DB in the browser (minimongo), isn't it?

I am still trying to learn more about OT. I found this interesting article:
http://www.codecommit.com/blog/java/understanding-and-applying-operational-transformation

from sharejs.

mb21 avatar mb21 commented on August 22, 2024

btw, check out DerbyJS, which is similar to Meteor, but they actually wrap ShareJS, so a lot of OT stuff should be built in. However, it's still alpha or beta software...

from sharejs.

devongovett avatar devongovett commented on August 22, 2024

Why would you rewrite ShareJS when you can just use it? If you look at the ShareJS and livedb code, there is A LOT more complexity there than you guys are giving it credit for. There are so many edge cases to handle in this type of system, you're never going to get it right the first time (no offense). At the end of the day, you'll wish you just used ShareJS rather than reinventing the world.

from sharejs.

devongovett avatar devongovett commented on August 22, 2024

Also, this discussion should be taking place somewhere else (e.g mailing list), not on the rich text support ticket.

from sharejs.

luisherranz avatar luisherranz commented on August 22, 2024

Yes, I agree. We could start this conversation in the meteor google group or something.

from sharejs.

josephg avatar josephg commented on August 22, 2024

๐Ÿ‘ Please ask about how sharejs works on the sharejs mailing list instead of in this issue. Your interesting conversation has made a mess of this issue, and now I have the difficult decision of making the issue more useful by deleting the conversation above, or leaving the mess here. I care a lot about this topic, and I'm happy to discuss it - but this isn't the place.

I'm going to answer the question briefly here, and delete any further off-topic discussion on this issue here.

You should think of OT as having two parts: the OT type, and 'concurrency control' (aka everything else).

  • The OT type is like a better diff-match-patch. Unlike diff-match-patch, the OT type is aware of the kind of data you're editing and what the user intention of that change was. This is why we need different code for each type of data. The most important thing the ot type defines is the transform function, which defines what happens when two concurrent edits happen.
  • Concurrency control is all of the rest of a version control system. It decides when to call the transform function, it sends operations between clients (or server/client) and manages the database interaction to save the operations. Sharejs calls transform in both the server and the browser. The code is in livedb lib/ot.js and sharejs lib/client/doc.js.

All the ot types are explicitly defined in the ot types github org with the hope that they can be reused in other ot-compatible tools. I did that specifically so you can reuse them in things like meteor. Please do!

from sharejs.

wamatt avatar wamatt commented on August 22, 2024

Interesting discussion (esp earlier part of the thread). I realize I'm a bit late to the party and much development has happened, but thought I'd share a thought on rich text OT.

Now this may seem a nuts, but what about mapping Bold, Italics etc to extended characters (sets) in UTF16? This could possibly allow the OT and data side to remain as is. Then in the presentation layer it would render the correct markup based on the character encoding.

from sharejs.

josephg avatar josephg commented on August 22, 2024

Thats not super clean - you'd still have stray close-bold / open-bold annotation boundaries next to each other in many cases. But that probably doesn't matter. More importantly, it'd be quite hard to embed images, do headings, select fonts and all of that. I think the stuff @jhchen is working on is a better long term solution.

from sharejs.

devongovett avatar devongovett commented on August 22, 2024

Also, a solution like that would have the side effect of affecting the length of the string, which wouldn't be that desirable. It would make indexing into the string next to impossible without traversing the entire string or some serious caching.

from sharejs.

wamatt avatar wamatt commented on August 22, 2024

Yeah ok, you both makes some good points that I hadn't considered. Thanks

from sharejs.

mitar avatar mitar commented on August 22, 2024

One other alternative approach is ConcurrenTree. The basic idea is that instead of having a markup language, you have a parallel array of meta information for each character. So first array is string of characters, the second array is meta information. So first element should be bold, italics, whatever. The interesting trick is that deletion is just marking a character as deleted. So it is really easy to merge concurrent edits because nothing really gets deleted and you can easy "patch" things together. (Of course you can then have periodic compacting to minimize data consumption, if you care about that.)

I find this approach very interesting, but sadly it is not being actively developed.

from sharejs.

mitar avatar mitar commented on August 22, 2024

See also documentation and technical description (and critique of OT).

from sharejs.

jasonsbrooks avatar jasonsbrooks commented on August 22, 2024

@jhchen @josephg I might be looking in the wrong place (and sorry if I am!), but have there been any further updates to Quill + ShareJS, or any Rich Text support?

from sharejs.

jhchen avatar jhchen commented on August 22, 2024

Rich text support has been added with the rich-text ottype which Quill now uses and is a compatible ottype with ShareJS. Actually hooking everything up in demo/example form is still something I hope to get to at some point.

from sharejs.

magnetic-pi avatar magnetic-pi commented on August 22, 2024

@jhchen Any update on this? I've been watching your dev branch. Can you give a hint of what solution you were thinking of implementing? I don't need a write up, but if you have a quick example that'd be great. I'm in SF as well if you wanted to meet up and collaborate on this.

Quill is awesome btw! I really appreciate the effort you put into it.

from sharejs.

charford avatar charford commented on August 22, 2024

@jhchen I'm feeling a lot like http://xkcd.com/979/ at the moment. Any updates on this? I'm SUPER PUMPED to get QuillJS and ShareJS working together. Thanks for all your work so far!

from sharejs.

leanderme avatar leanderme commented on August 22, 2024

@jhchen could you _briefly_ explain what steps would need to be done to hook up QuillJs with ShareJs? Just to give me an idea on where to start. Any help is greatly appreciated.

from sharejs.

jhchen avatar jhchen commented on August 22, 2024

Most if not all the pieces are there. ShareJS using the rich-text ottype is pretty much everything you need in the back end. Since Quill also uses the rich-text type, you should just be able to listen on text-change events on Quill and just pass those to ShareJS. Similarly pass any updates from ShareJS to Quill's updateContents function.

Last time I tried this though there were synchronization bugs. My suspicion is the cause is due to events being asynchronous and non-blocking in Quill--so you may make an API call to Quill based on contents that are out of date. For example Quill has the contents 'a' and the ShareJS client gets an update. At the same time the user types another letter in Quill. ShareJS doesn't know about the new letter so doesn't do any conflict resolution. The instructions being passed to Quill will be incorrect since ShareJS made them based on an older state of the document. There is a bit of code in Quill that targets this case but it is either not working in all cases or some other module is interfering.

But for a demo the pieces are all there for realtime rich text editing. But for something more production quality I need to find some time to look deeper into this.

from sharejs.

butsjoh avatar butsjoh commented on August 22, 2024

@jhchen Care to elaborate why quill is using the https://www.npmjs.com/package/rich-text package and not the https://www.npmjs.com/package/ottypes package which sharejs is using?

Maybe you can share :) a light on this as well @josephg

from sharejs.

sferoze avatar sferoze commented on August 22, 2024

ShareJS + Quill + Meteor is an awesome powerful combination.

from sharejs.

butsjoh avatar butsjoh commented on August 22, 2024

@sferoze Care to explain howto ? :)

from sharejs.

sferoze avatar sferoze commented on August 22, 2024

@butsjoh oops sorry if I gave the impression that I already figured it out. I am working on it right now. I am going off @jhchen last response and trying it out. Although he mentioned that there were bugs soo it might not be the right way to integrate with Meteor.

Mostly though I am enthusiastic about the combination of Quill, ShareJS and Meteor. Although I don't have it working.

from sharejs.

pollen8 avatar pollen8 commented on August 22, 2024

I've set up a sample application of how a possible quill/sharejs integration would work. I'm very new to sharejs and quill so may have made some terrible faux pas, so if people want to fork and improve things that would be great: https://github.com/pollen8/plume

from sharejs.

jpgilchrist avatar jpgilchrist commented on August 22, 2024

@jhchen Just checking for an update on this. I know you are awfully busy. Keep up the good work ๐Ÿ‘

from sharejs.

adamziel avatar adamziel commented on August 22, 2024

+1

from sharejs.

marcelklehr avatar marcelklehr commented on August 22, 2024

Hey, I have a completely different approach to rich text editing. The question I was asking myself when dealing with etherpad and seeing how shareJS wants to integrate rich text support in a similar fashion: Why not sync the DOM directly? Adding support for images, videos and even tables should be a piece of cake, because these things are natural to the DOM.

To try that route I wrote dom-ot, operational transforms for DOM tree operations. And to prove to you that it really works: Have a demo: http://warp.der-analphabet.de

I'm interested to see if this approach is any good and will try to pursue this path further if it is.
Cheers!

from sharejs.

wmertens avatar wmertens commented on August 22, 2024

Wow nice!

The problem used to be that different browsers produced quite different DOM
when editing, IIRC. Did you test with many browsers? Your solution works
great on Mobile Chrome in any case, unlike many non-wysiwyg editorsโ€ฆ

On Sat, Jul 4, 2015, 20:27 Marcel Klehr [email protected] wrote:

Hey, I have a completely different approach to rich text editing. The
question I was asking myself when dealing with etherpad and seeing how
shareJS wants to integrate rich text support in a similar fashion: Why not
sync the DOM directly? Adding support for images, videos and even tables
should be a piece of cake, because these things are natural to the DOM.

To try that route I wrote dom-ot https://github.com/marcelklehr/dom-ot,
operational transforms for DOM tree operations. And to prove to you that it
really works: Have a demo: http://warp.der-analphabet.de

I'm interested to see if this approach is any good and will try to pursue
this path further if it is.
Cheers!

โ€”
Reply to this email directly or view it on GitHub
#1 (comment).

Wout.
(typed on mobile, excuse terseness)

from sharejs.

cklokmose avatar cklokmose commented on August 22, 2024

Hi Marcel,

Iโ€™ve been working on something similar, but where Iโ€™ve simply mapped changes to the DOM to a JSON representation in JsonML using the Json0 OT type.
You can find it here: https://github.com/cklokmose/Webstrates https://github.com/cklokmose/Webstrates
Iโ€™ve written an academic paper about what we have achieved with working on a synchronized and persisted DOM that I will circulate as soon as I have the final version for the publisher ready.

Iโ€™d be happy to exchange ideas.

Cheers,
Clemens

On 04/07/2015, at 20.27, Marcel Klehr [email protected] wrote:

Hey, I have a completely different approach to rich text editing. The question I was asking myself when dealing with etherpad and seeing how shareJS wants to integrate rich text support in a similar fashion: Why not sync the DOM directly? Adding support for images, videos and even tables should be a piece of cake, because these things are natural to the DOM.

To try that route I wrote dom-ot https://github.com/marcelklehr/dom-ot, operational transforms for DOM tree operations. And to prove to you that it really works: Have a demo: http://warp.der-analphabet.de http://warp.der-analphabet.de/
I'm interested to see if this approach is any good and will try to pursue this path further if it is.
Cheers!

โ€”
Reply to this email directly or view it on GitHub #1 (comment).

from sharejs.

mizzao avatar mizzao commented on August 22, 2024

A while ago, I integrated ShareJS 0.6 with Meteor: https://github.com/mizzao/meteor-sharejs

It looks like ShareJS 0.7 with Quill is the next target. Having good real-time rich text editing capabilities integrated with the rest of Meteor would be badass.

from sharejs.

mitar avatar mitar commented on August 22, 2024

Yea!

from sharejs.

jonlachlan avatar jonlachlan commented on August 22, 2024

I have created an example of successful live-text editing using Quill on Meteor. This doesn't use ShareJS, just Quill.

https://github.com/jonlachlan/quill-meteor-example

In my particular use-case, I wanted users to have the autonomy to click "Save" to apply their changes, rather than live-editing. And since multiple people might be editing a document at the same time, I didn't want new edits to overwrite another user's work in progress .The template in the example successfully applies changes from down the wire, while maintaining all unsaved edits for the user.

If you want to have live edits (i.e., save as you type), then just uncomment the on text-changes code.

For me, the key insight is client/quill.js line 65:

editor.updateContents(localChanges.transform(remoteChanges, 0));

This uses ottypes (which Quill is built on) to transform the positions of any edits that were made from the server based on what changes were made locally -- this way one set of edits doesn't overwrite another.

This operational transform stuff is quite a bit beyond my experience level, so please approach with all appropriate caveats.

from sharejs.

mitar avatar mitar commented on August 22, 2024

Hm, I think you should also be transforming operations on the server side as well? To make a canonical version there? But I am also not an expert on operational transform.

from sharejs.

jonlachlan avatar jonlachlan commented on August 22, 2024

Right now I'm assuming that the client has the latest version, along with unsaved local edits. That's probably not a good assumption. On the save event, it sends over the whole Delta, not just the changes. So there's a chance that a new update is being sent over the wire, which could get overwritten if the client doesn't register it "in time".

(Edit) So you're right, I think that some way of handling each individual change centrally on the server is the way to go. Perhaps you'd want to extend Minimongo so that you get optimistic updates on the client, then some kind of server process that looks at timestamps etc.

from sharejs.

jonlachlan avatar jonlachlan commented on August 22, 2024

I added a button to the example to demonstrate live editing. It seems like this could be an acceptable level of functionality for many use cases. What do you think?
http://quill-reactive-ot.meteor.com/

from sharejs.

mitar avatar mitar commented on August 22, 2024

I do not think you have to extend Minimongo. Just have custom methods for sending to the server, and then server runs OT operations on its copy, and once it does that, you get a canonical version, which is then pushed to all clients, where clients use OT to resolve conflicts once more.

Also, I think that the design goal of OT was also that you can have delays. So imagine offline use, you edit something without Internet connectivity, you connect back, the idea is that you can reconnect and get all changes in. I do not think that people use it in that way though, I do not think that even Google Docs supports that (but Google Wave did plan to support that).

from sharejs.

jonlachlan avatar jonlachlan commented on August 22, 2024

I just tried a server side method call, but if you type quickly the messages start to get out of order, and on the client these inconsistencies mean that there are incorrect diffs, i.e., if I type "abc" but then I receive from the server "a" then "ac", my client now thinks there's been another edit, so my screen says "acabc", then the "b" message comes in, so now my server says it's "abc" by on my screen I see "abcacabc". At this point if I type another letter "d", then I'm sending "abcacabcd" to the server, because that's what's in my editor.

My next thought is to use a job queue to send the deltas to the server, in order to ensure that the server handles the updates in order. https://atmospherejs.com/?q=queue

from sharejs.

mitar avatar mitar commented on August 22, 2024

I do not think the queue is the problem here. I think server side has simply to apply OT as well. So instead of getting OT messages and just passing them to all clients, server gets OT messages from all clients, applies them to its own version (I do not think order matters), and then send to clients back OT messages based on that version. So in some way if you think about it as diffs. Instead of sending diffs directly between clients (diff between client A version and client A change might not apply cleanly to client B version), you send diff to server (so client A version sends its diff to server which applies, and then sends to client B a diff based on their common history). Something like that.

BTW, for queues I like meteor-job-collection.

from sharejs.

jonlachlan avatar jonlachlan commented on August 22, 2024

How do you ensure that server method calls are processed in order?

from sharejs.

mitar avatar mitar commented on August 22, 2024

I do think they have to be? They have to be in order of each client I think only. And that can be just a incremental counter for each client message?

from sharejs.

mitar avatar mitar commented on August 22, 2024

(BTW, I am far from completely understand OT. This is just what I got when reading around on the web and looking at ShareJS code.)

from sharejs.

nornagon avatar nornagon commented on August 22, 2024

FYI for anyone who finds this: https://github.com/ottypes/rich-text

from sharejs.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.