/ritzy

Collaborative web-based rich text editor

Primary LanguageJavaScriptApache License 2.0Apache-2.0

Ritzy Editor

Join%20Chat license Apache blue ritzy ritzy

About

Demo / Short Introduction (TL;DR)

Finally, a good, open source, cross-browser [1], rich text, real-time collaborative editor for the web! Here is a demo:

[1] IE10+ and evergreen browsers supported

Longer Introduction

The Ritzy editor is a rich text, real-time character-by-character collaborative browser-based editor. It shuns use of the contentEditable attribute in favor of a custom editor surface and layout engine, exactly like the approach implemented by Google Docs.

In addition, and also like Google Docs, Ritzy is built with real-time collaborative editing support from the ground up, unlike most browser-based editors that require third-party plugins or that do not support it at all.

Ritzy implements all the basic expected text editing functionality, such as bold, underline, strikethrough, superscript, subscript, selection via keyboard and mouse, cut, copy, and paste. More capabilities will be added as needed or as third-party contributions are received.

Limitations and Target Audience

Unlike Google Docs, Ritzy does not (currently) support complex page-based layout needed to build a word processor. It will be most useful for those developers who wish to add collaborative rich-text data entry fields to their browser applications. However, some layout capabilities are planned to be added over time, such as bulleted and numbered lists, styles, and other such features.

JavaScript Surface and Layout Engine

The contentEditable attribute used by most editors allows the editor to delegate the capture of user input and the display of the editor contents and selections to the browser. This is "easy" and performs very well, but is limited and broken by browser capabilities and incompatibilities in contentEditable implementations, and by the underlying HTML data model which is not suited for collaborative editing. Instead, Ritzy implements a custom surface and layout engine like Google Docs:

Let’s start by talking about the editing surface, which processes all user input and makes the application feel like a regular editor. To you, the new editor looks like a fairly normal text box. But from the browser’s perspective, it’s a webpage with JavaScript that responds to any user action by dynamically changing what to display on each line. For example, the cursor you see is actually a thin, 2 pixel-wide div element that we manually place on the screen. When you click somewhere, we find the x and y coordinates of your click and draw the cursor at that position. This lets us do basic things like slanting the cursor for italicized text, and it also allows more powerful capabilities like showing multiple collaborators’ cursors simultaneously, in the same document.

Pros and Cons

This approach is more flexible than contentEditable. The logic is consistent across browsers, and there are no browser-specific workarounds for the document model. The document model is only ever modified through explicit application action (rather than by the browser as happens with contentEditable), ensuring that the content of the internal document model is repeatable and consistent.

The document model is not HTML — it is completely independent of the editor surface. Therefore it should be easier to support applications that need to customize the editor surface with new controls and/or behavior. Examples of this would be inline spelling error notations or comments.

The downside is that having a custom editor surface unmanaged by the browser requires significant complexity to do things the browser would normally provide for free, such as: cursor motion and positioning (even blinking the cursor!), dealing with accessibility concerns, non-left-to-right text orientations, user inputs that are not raised as application events by the browser, and other such capabilities.

Real-time Character-by-character Collaborative Editing

Ritzy’s real-time collaborative editing uses a different approach than Google Docs, which based on public information is based on operational transform (OT). Operational transforms require implementing a transform for every operation, and dealing specially with lots of corner cases, especially as the complexity of the model increases.

Instead, Ritzy uses operation-based conflict free replicated data types (CRDTs) to implement the concurrency control required for real-time character-by-character collaborative editing. Just like OT, CRDTs allow changes to happen in different orders on each instance, but the final editor state to converge.

See Collaborative Editing below for more technical details.

Other Features

Beyond basic text editing, some other features implemented by Ritzy:

  • Paragraph flow control, including wrapping support for long words/URLs.

  • Selection and keyboard navigation behavior mimicking common platforms like Microsoft Word and Google Docs.

  • Multiple author colored/labeled cursor and selection tracking in real-time.

  • Cursor blink control simulating browser function (blink while waiting, no blink during operations).

  • Cursor slant when traversing italic text.

  • Automatically scrolling the document window horizontally and vertically to keep the cursor visible.

  • Cut/copy/paste of clipboard data, including conversion between rich text and HTML.

  • Focus/blur support with appropriate styling for cursors (invisible) and selections (gray).

  • API to control the editor and to obtain selections as HTML, text, or rich text (though more work is needed here).

Design and Implementation

Editor Surface

The editor uses Facebook’s React to manage rendering for the editor surface. React is perfect for this purpose as most user input and selection operations alter the surface only slightly — to insert or remove characters, to highlight selections, and to position the cursor. For each of these, React can instruct the browser to make the minimum number of required changes to the DOM that represents the editor surface. Since modifying the DOM is an expensive operation performance-wise, React is key to Ritzy’s smooth performance. React’s virtual DOM / state abstraction also makes code maintenance simpler.

Layout

Managing the layout in JavaScript requires knowledge of the x-y positions of individual characters, for example to position the cursor when the user clicks on text, or to wrap text within the editor’s bounding box.

Ritzy prefers using Opentype.js to obtain the required text metrics from the underlying font, such as advance widths for the glyphs that represent each character.

When the browser/OS platform supports linear subpixel positioning and faithfully follows the font’s instructions for it’s text rendering, the font metrics are sufficient to calculate x-y positions. However, on some browsers on some platforms at some font sizes, for various complicated reasons the font metrics are ignored in favor of hinting or other mechanisms. In these situations, the layout engine falls back to a slower but reliable mechanism using the canvas measureText function.

To use the Opentype.js mechanism, all fonts displayed by Ritzy must be available as TrueType or OpenType font files. Note that Opentype.js does not currently support WOFF font files, but usually TrueType or OpenType equivalents are available. In addition, the font is loaded into memory twice: by the browser and by Ritzy.

Collaborative Editing

Collaborative editing requires each editor client to "report" changes i.e. operations such as inserting or deleting characters, or changing character attributes, to peers. Peers in turn, accept these changes and display them on their own editor surfaces, while themselves dealing with user input and reporting their own changes.

To handle this concurrency, Ritzy uses a CRDT-based causal trees approach created by Victor Grishchenko, running on the Swarm.js library created by the same author. Each client possesses a causal trees "replica" of the current state of the rich text within the Ritzy editor.

Essentially, with causal trees, every character has a unique id, and all operations and positioning is relative to these ids. This greatly simplifies simultaneous operations that are complex with index-based approaches, at the cost of significantly greater disk and memory requirements. This is generally not an issue for text content on modern machines.

Ritzy requires a NodeJS or io.js server running and bidirectionally connected to each editor client via WebSockets or a long-polling mechanism. The server is responsible for receiving changes from all editors and transmitting them back to other editors. A default server implementation is provided as part of the Ritzy project. Currently, Ritzy does not operate stand-alone, though it should not be too difficult to add this capability (see Roadmap).

Unlike other collaborative editing techniques such as OT and diff-match-patch, the causal trees approach is highly amenable to offline editing, therefore offline editing is intended to be a valid use case for Ritzy.

Ritzy Integration

ES6

The editor uses JavaScript ES6. Ensure your consuming client and server-side code transpiles to ES5 via babel or similar transpiler, and contains the appropriate ES6 polyfills. See the Ritzy demo for an example.

Client-Side

See client.js or the Ritzy demo for an example of creating and using the editor.

It should be possible to create multiple editors on one page, though this is not yet a tested configuration.

Server-Side

The server-side integration mechanism for most applications employing Ritzy will be to create a Ritzy Swarm.js peer within their server-side application, which will be responsible for receiving all updates to text replicas. The application can then use that text replica for any purpose.

See server.js or the Ritzy demo for an example of this.

Currently, Swarm.js peers only run within JavaScript environments, but the author plans to support other languages in the future.

Developers

Roadmap

The following is a tentative list of features and capabilities that will be added over time. Contributions are welcome.

  • Tests (many, see GitHub issue xx) (hard!).

  • Once tests are in place, refactoring to make the editor code more modular / easier to understand (hard).

  • Performance improvements. Performance is not bad right now, but much can be done to improve it further. Some ideas:

    • Create finer-grained React components (see refactor above) to avoid re-rendering the entire editor on updates (this should be a big win).

    • Use immutable collections as much as possible e.g. http://facebook.github.io/immutable-js/

    • Implement shouldComponentUpdate and/or PureRenderMixin to avoid unnecessary component renders, taking advantage of reference equality with immutable data structures.

    • Implement some type of indexing for characters.

    • Cache frequently used / slow operations where possible.

    • Server-side performance improvements. Currently the initial load can become very slow as the replica continues to grow.

  • Undo/redo

  • Styles for content e.g. headings, lists, etc. (medium).

  • Expose an API for programmatic access to the editor and contents (medium):

    • Get/set contents using the native data model for proper concurrency control

    • Get contents as HTML

    • Insert HTML at a particular position specified by the native data model

    • Event callbacks for inserts, deletions, changes, and selections

    • Command and status support for text attributes e.g. to support a toolbar

    • See Historic Editing APIs for comparison/implementation with contentEditable-based APIs

  • Better input handling for non-English languages (medium to hard?).

  • A skinnable and/or replaceable toolbar that leverages the editor API (medium).

  • Make Ritzy work apart from a shared replica and server implementation. Create a local-only replica with the same API (medium).

  • Test and support editor fonts other than OpenSans (easy to medium?).

  • Handle font size as a character attribute (medium to hard).

  • Reduce the number of dependencies and lower download size as much as is possible without sacrificing clarity and maintainability.

  • Search/replace

  • Figures and tables (TODO).

  • Bullets and numbering (TODO).

  • Inline images (TODO).

  • Right-click menu support (medium).

  • Color-coded authoring display (medium).

  • Text highlighting (easy to medium?).

  • History/timeline/revision view (hard).

  • Drag and drop support (medium).

Commercial Features (Future)

In addition to the editor which will remain free and open source, VIVO Systems, the organization behind Ritzy, is considering offering the Ritzy editor as a service. Because it is intended for real-time collaboration, a server-side component is required by Ritzy.

Note
A simple but working server-side component is bundled with the free and open source Ritzy editor. See Server-Side.

The commercial server-side solution will handle storage, communications, security, availability, and provide a simple but powerful server-side API for developers to interact with the editors under their control, and the data they contain. Some of the features of this API may include:

  • Create, archive, and destroy text replicas.

  • User identification and specification of authoring labels.

  • Set and modify access control.

  • Get editor contents (snapshot + real-time bidirectional push).

    • Integration with various server-side libraries e.g. Akka, Vert.X, RxJava, Kafka, etc.

  • Set or modify editor contents.

  • Show server feedback on editor surface e.g. comments/errors/word highlights.

  • Get revision history.

  • Get editing statistics e.g. authors, character count overall and by author, word count overall and by author, time spent editing overall and by author, and so forth.

  • Isomorphic rendering of editor’s server-side and client-side for performance.

Please let us know if your company or startup may be interested in such a service.

Support

Support is provided on an as-available basis via GitHub issues.

Contact raman@vivosys.com @ VIVO Systems for paid support or enhancements.