
Ritzy – A collaborative web-based rich text editor - kylemathews
https://github.com/ritzyed/ritzy
======
marijn
I am the author a recently-launched similar product [1], which also tries to
contain the impact of contentEditable by capturing user actions and applying
them to the document model in its own code. The main difference is that my
system does leave selection (mostly) to the browser, by enabling
contentEditable and then capturing events that actually perform changes.
Another project of mine [2] does take the approach taken by Ritzy, but for
plain-text editing. I consciously didn't go that way this time. Here's why:

\- Accessibility. Screen readers don't know what you are doing if the browser
doesn't know. They won't communicate your tag soup to the user as an editor
interface.

\- Cursor motion and selection management hard. Consider right-to-left text
and mixed bidirectional text. Getting this right is a big project on its own.
You can get the basics working easily, but there's a long tail of hard
behavior that you will at some point need.

\- Touch interfaces don't interface well with your fake selection. So you'll
have to implement your own selection-management interface. Which might not
even be possible for some things (like access to the built-in spell checker).

\- It isn't needed. Though it also requires some gymnastics, working with the
browser's native selection on modern browsers isn't all that hard.

[1]: [http://prosemirror.net](http://prosemirror.net)

[2]: [http://codemirror.net](http://codemirror.net)

~~~
gritzko
I wrote some collaborative editors and I fully agree on those points. In my
opinion, contentEditable is a codebase way too big to replicate in JavaScript
(unless you're Google).

------
Zarel
Pretty interesting, although reimplementing every single standard text editor
behavior can be difficult.

Missing standard text editor behavior that I know of:

\- right-clicking on selected text

\- dragging-and-dropping selected text

\- triple-click to select paragraph

\- OS X emacs keybindings: ctrl+n/p/f/b as arrow keys, ctrl+a/e to go to
beginning/end of line, ctrl+k/y to cut/paste, etc

~~~
onetom
i hit the same things on mac, like ctrl-a/e. couldn't jump to the beginning
and the end of the file either. i tried various shift key combinations until i
finally inserted some special character which i couldn't delete. all this
happened within ~20seconds of trying it. not sure what to conclude from it; im
still in shock. somehow the language of announcement is not in line with the
capabilities of the tool. it suggested something more mature.

------
teleclimber
I sympathize with anybody who attempts to write an editor that runs in a
browser.

If you're going to even entertain the thought of writing an editor I recommend
reading Piotrek Koszuliński's article on Medium [1]. There are many pitfalls
when writing an editor and the sooner you know of them the better.

On the plus side a new spec for a more low-level version of contentEditable is
currently being worked on.[2] It's a complicated undertaking but the task
force is making progress. There is participation from all major browser
vendors so we have reasons to be optimistic: we may make it out of the Dark
Ages of web-based rich editing one day.

[1] [https://medium.com/content-uneditable/contenteditable-the-
go...](https://medium.com/content-uneditable/contenteditable-the-good-the-bad-
and-the-ugly-261a38555e9c)

[2] [https://medium.com/content-uneditable/fixing-
contenteditable...](https://medium.com/content-uneditable/fixing-
contenteditable-1a9a5073c35d)

------
m_mueller
I've yet to see a good Wysiwyg table editor on the web, especially open source
and embeddable. So if you can deliver on that at some point, that would be a
big reason for me on why start using. I like minimalistic approaches, but so
far the minimalistic approach is usually to just _forget_ about tables, which
I don't find acceptable. Emacs Orgmode so far has the cleanest simple approach
to this and I'd love to see that replicated.

------
tomcam
A nice contribution to the browser-based editor world. I enjoyed perusing the
source, which looks pretty clear. There's not much UI, which is perfect for me
because I've been looking for a browser-based editor to study.

Tried it on a 2,000 line text file and it caused Chrome 44 on Yosemite to time
out close the tab.

~~~
espadrine
Trivial CRDT implementations tend to be memory-heavy and to scale badly,
especially after a long editing session. Decreasing the cost of tree traversal
requires smart garbage collection, balancing and merging of nodes.

~~~
gritzko
CRDT is heavy on metadata and thus requires some compression and garbage
collection techniques. In this implementation, every letter is a JavaScript
object. Performance was not an objective, I guess.

------
babby
Another interesting library built for collaborative editing is Prosemirror.
It's also in its infancy stages.

[https://github.com/ProseMirror/prosemirror](https://github.com/ProseMirror/prosemirror)

It's by the guy who created CodeMirror and TernJS.

~~~
sitkack
Which is seeking funding to go open source
[https://www.indiegogo.com/projects/prosemirror/#/story](https://www.indiegogo.com/projects/prosemirror/#/story)

~~~
marijn
Here's a direct link to ProseMirror's collaborative editing demo
[http://prosemirror.net/demo_collab.html#edit-
Example](http://prosemirror.net/demo_collab.html#edit-Example) (which is up,
and fast)

~~~
babby
I'm definitely excited for ProseMirror. If it's as quality as CodeMirror I'll
be happily integrating it into everything I build!

------
oever
What is the file format? WebODF has all the mentioned features, includeing
collaborative editing, and in addition it uses OpenDocument Format as the file
format.

[http://www.webodf.org/demo/](http://www.webodf.org/demo/)

~~~
rocketraman
The primary data structure underneath is a "causal tree" CRDT. However,
import/export to a JSON structure representing rich text is the "native"
format. Example is the source used to create the text in demo instances of the
editor:

[https://github.com/ritzyed/ritzy-
demo/blob/master/src/create...](https://github.com/ritzyed/ritzy-
demo/blob/master/src/createReplica.js)

------
amelius
Since it uses React internally, I wonder how large these documents can grow
until the experience gets sluggish. I ask this because React basically
requires all of the internal content to be "visited" upon a redraw. But
regardless of React, it is still interesting to know the constraints in terms
of performance.

Also, instead of just pointing to the code on github, I'd like to get at least
a little insight into the architecture of the project, because that is the
interesting part, in my opinion. There is a brief description in the readme on
github, but it doesn't really paint a clear picture.

~~~
brandonbloom
The techniques for addressing this are essentially the same for any display
technology: Avoid rendering stuff that is not on the screen. This works just
fine in React.

~~~
amelius
But if at every update (every keystroke of the user), the algorithm needs to
go through millions of lines of text to determine which ones to show, it is
going to be slow, even if eventually only e.g. 50 lines are shown.

~~~
brandonbloom
You can say the same thing about any text editor built with any technology.
This problem is fundamental and the tools to solve it are foundational. It's
also no different than a game that doesn't render objects behind the player or
obscured by walls. You need to choose data structures that enable efficient
visibility queries.

For a game, there's some sophisticated math involved. But for a text editor?
You only need to perform basic arithmetic on the scroll position and window
height to index in to a vector of lines.

In React, you could render exactly 50 <Line> components and choose which 50 by
exactly the same means vim chooses which 50 ncurses lines to show.

------
dangoor
This is an interesting project, and I totally understand the desire to eschew
the use of contentEditable. But, from what I've seen, handling bidi text and
accessibility essentially require using contentEditable as the input
mechanism, even if you manage the document yourself.

Does this handle RTL text input and accessibility issues?

~~~
rocketraman
It does not. See the comments from Marijn above. I don't know anything about
RTL/bidi and accessibility, so I'm not sure if these are intractable problems
for Ritzy or not.

------
pretzel
It would be nicer if this wasn't tied in to a server implementation, so it
could be a standard react component that specifies a couple of handlers as
PropTypes. Would make it much more easier for people to reuse it!

------
andrelaszlo
Here's a demo instance:

[http://demo-ritzy.rhcloud.com/editor/NyYKfTMh](http://demo-
ritzy.rhcloud.com/editor/NyYKfTMh)

------
dgreensp
The demo is down, but it doesn't even have bullets?

If you're looking for a good collaborative rich text editor on the web, look
up Quill or ProseMirror.

~~~
rocketraman
Quill is good but not great. I looked at Quill extensively before embarking on
writing Ritzy. Its collaboration features are minimal -- as far as I know
there is no way to do collaboration across two browsers i.e. there is no
working and/or available server-side component to coordinate the OT deltas
produced by Quill (both editors have to be on the same page, which is somewhat
pointless). I believe the authors were moving towards doing this via
compatibility with ShareJS, but never quite got there. Offline support will be
a pain to add with OT.

ProseMirror looks cool. I wish @Marijn the best with it! Not sure about
ProseMirror's offline capabilities. I hope he adds multiple cursors and
selections like Google Docs (and Ritzy).

------
namuol
Awesome project, but I misread the title and expected this to be a
collaborative real-time editor built _for_ React...

------
hsshah
Looks very promising. Been looking for Google Doc alternative for sometime
now.

I realize that there is a commercial service aspect that is being worked on to
provide server side hosting, however, how easy would it be to self host this
on sandstorm.io?

~~~
rocketraman
Pretty easy! Check out the demo source code:
[https://github.com/ritzyed/ritzy-demo](https://github.com/ritzyed/ritzy-demo)

------
chvid
Very well done.

On mac you use cmd-C, cmd-V instead of control-C, control-V.

There is a bug that causes new line to function incorrectly when the cursor is
on the first line of the document. New line on other lines work fine. (Safari
on Mac)

~~~
rocketraman
Yup, I noticed that bug! It's fixed now
([https://github.com/ritzyed/ritzy/commit/c42769d27730a11fe0d4...](https://github.com/ritzyed/ritzy/commit/c42769d27730a11fe0d44122d343479cf7c54d55))
(demo is not updated yet). It should be easy to add the Mac key bindings -- I
need to find a Mac to test on.

------
oleg009
You guys should publish a content-editable abstraction that fixes regular
cases first and then build stuff like collaborative editor on top.

------
lemming
This looks interesting, sadly I'm getting an internal server error from the
demo.

------
sb8244
On iPhone I can't get focus to edit the demo. That is slightly concerning.

------
nestorp
Demo is crashing :(

------
rocketraman
Author here. Thanks for posting this. I had posted it earlier as a ShowHN
([https://news.ycombinator.com/item?id=10087162](https://news.ycombinator.com/item?id=10087162)),
but without much notice, so I'm ecstatic to see lots of comments here. Great
feedback.

Some responses to the various comments:

1) This is a _very_ early release. I'm aware of all the missing functionality,
and the README is pretty clear about all the missing stuff, including
undo/redo, bullets, numbered lists, tables, and more. I don't have access to a
Mac, so I wouldn't expect any of the Mac keybindings to work. That should be
pretty easy to solve though.

2) Piotrek Koszuliński's article was really eye opening. Being completely new
to the field of editors, and even new to Javascript (this is my first major
Javascript project -- my background is mostly enterprise Java and some Scala),
I defer to experts in this field like Piotrek and Marijn, the author of
Codemirror and Prosemirror. I'm honored that Marijn even took the time to
comment :-) It's possible that Ritzy could still serve as a useful tool within
some narrower contexts that do not require handling the long tail of hard
behavior mentioned (accessibility and RTL/bidi). OR, with the appropriate
expertise, that some of that functionality could be added. Re. touch
interfaces, fair point -- my thought was that could be worked around (e.g. by
an alternate native component on touch interfaces that talked the same CRDT
language -- I believe that's the approach that Google Docs took with editing
Docs on mobile). Currently on mobile, basic cursor navigation by text,
keyboard / text input, and viewing others' changes in real-time appears to
work pretty well (better than I expected actually!). Selections don't work at
all.

@marijn: how difficult would it be to add multiple cursors and selections to
Prosemirror? I notice that currently the Prosemirror collaboration demo does
not show cursors or selections, which is pretty key to a great user
experience.

3) @gritzo (the author of Swarm.js, a key library used in Ritzy) mentions that
the implementation uses a JS object for each char. That is true and it's very
heavy. My initial goal was correctness and as a POC, with something easy to
understand and debug. Optimization can come later, if indeed there is interest
in taking this general approach further.

4) Recent changes to Ritzy (not yet pushed to the demo page) make each cursor,
line, and selection a separate React component within a hierarchy. This makes
navigation, rendering and selections pretty quick. However, the editor has
never been tested against documents larger than a few hundred lines. My gut
feeling is that the limiting factor right now is not React, but the "fat" CRDT
data structure I used. It gets particularly bad when a lot of characters are
deleted. There is no tombstone clearing logic yet.

5) It should be pretty easy to strip out the server-side stuff and make it
client-side only. You lose the "killer feature" of Docs-style collaborative
editing though. The server-side is pretty simple to self-host -- check out the
demo source code @ [https://github.com/ritzyed/ritzy-
demo](https://github.com/ritzyed/ritzy-demo) for an example.

I'll try to get the demo back up and running. Its hosted on the smallest
(free) tier of Openshift and I've done zero server-side optimization. I'm sure
it didn't take much traffic to kill it. Any other suggestions for (free)
hosting for NodeJS projects?

I'll also try to write up a design doc over the next few days for those who
requested that / are more interested in those aspects.

Thanks again for all the comments / feedback!

~~~
marijn
> how difficult would it be to add multiple cursors and selections to
> Prosemirror? I notice that currently the Prosemirror collaboration demo does
> not show cursors or selections, which is pretty key to a great user
> experience.

If you mean Sublime Text-style multiple cursors, that'd be very hard.
CodeMirror's contentEdtiable-base mobile backend does support them, but that's
only possible because it already contains a full implementation of
cursor/selection for the non-mobile version.

If, on the other hand, you mean showing the cursors from other people working
on a collaborative document, that isn't very hard, it just involves inserting
widgets and marked ranges at the right places.

~~~
rocketraman
> If, on the other hand, you mean showing the cursors from other people
> working on a collaborative document

This is what I meant. Good that its not very hard. Though I'm curious: how do
you know what the "right place" actually is i.e. don't the coordinates of the
cursor position depend on the rendering of the text which is under the
browser's control?

What about showing multiple selections from other authors like Google Docs
(and Ritzy) does?

~~~
marijn
> don't the coordinates of the cursor position depend on the rendering of the
> text which is under the browser's control?

Yes, but you can ask the browser (using `getBoundingClientRect` and
`getClientRects`, which also exist for text range object on all halfway modern
browsers.) Or you can insert the caret element inline at the correct place.

What would be hard about showing a selection from another person?

~~~
rocketraman
Not "hard" as such. Just requires a completely separate code path than the
local cursor and selection machinery. That isn't too much of a barrier
though... I look forward to its implementation in Prosemirror!

