Home

PubWeave - Collaborative Preprint Text Editor


Archived Content

This is an archived version of the original post. For the most up-to-date content, please visit PubWeave - Collaborative Preprint Text Editor .

Introduction

PubWeave is built using a Ruby on Rails / React stack and serves as a blog and preprint platform and, in later stages, will evolve into a modular article building tool. PubWeave’s aim is to aid academics in showcasing, sharing, and collaborating on their forthcoming articles.

In terms of development environments, the backend uses Ruby on Rails, while the frontend uses React, a choice which facilitated the bootstrapping of a custom application. In this article, we will focus primarily on the editor component of the platform, as it constitutes a core functionality of PubWeave.

Discovering Editor.js

Editor.js is an open-source, block-style, rich text editor that proved to be an excellent fit for our needs. It is highly extendable through plugins and offers basic tools and a user-friendly interface for text editing. Additionally, it boasts a large and growing user base that has built extensions catering to a wide range of content types.

Editor.js interface

Editor.js is easy to work with since it outputs a JSON file with a well-defined structure. Each plugin used within the editor adheres to this same structure:

Editor.js block structure

Uploading this data to the server is straightforward. Whenever a user modifies a section of the article, both the frontend and the server can effortlessly detect disparities in the article’s structure and handle them accordingly.

Building the real time collaboration feature

Since the editor itself doesn’t support real time collaboration (yet), we had to build our own.

To implement this functionality, we used websockets to establish a real-time connection between the frontend and backend. We kept track of all users currently on the editor screen, and using the Editor.js API, identified the specific section each collaborator was editing at any given moment.

On the server side, we maintained a list of all actively edited sections, along with their respective users. While this part was relatively straightforward, we still needed a method for displaying and rendering this information within the editor itself.

One option was to simply display the collaborator’s caret in the editor at the location where they were writing text, but given that it is a “block-oriented” editor and the content itself is organized into blocks, it felt more intuitive to “block off” the entire section that a particular collaborator was editing. While the rendering of this “blocking off” component proved to be very easy, the challenging part was adding text to the editor retroactively through an API (as opposed to manual typing).

Collaborator conundrum

This issue proved to be a bit difficult to solve due to the nature of the editor itself. You see, React really prefers controlled components, where a parent component manages its state. However, in our case, Editor.js lacks the ability to function as a controlled component, since it prefers to maintain its state internally and requires every modification to be carried out through its API functions like editor.update() or editor.clear(). While there was a React version of the library, we opted to use the original JavaScript one, which still lacked the desired functionality.

As a result, all content visible on the editor screen can only be altered and accessed through the API.

Workflow diagram

Consequently, when faced with a scenario in which we receive a modification via a websocket from another collaborator and wish to render it using the editor API, the editor immediately returns the content as if the change originated from the current instance.

The core issue was that the change hook we invoke in Editor API lacks the option to silently insert the change. Consequently, we found it necessary to address this behavior by introducing additional React states within the app. This approach prevented the inadvertent notification to the server of a change that did not occur in reality, a behavior that would otherwise cause cascading recursive calls across all active collaborative sessions.

Future improvements

Lately, an intriguing data structure has been garnering popularity, and for good reason: CRDT, which stands for conflict-free replicated data type, is a data structure that is designed to be replicated across multiple computers on a network. It allows independent, concurrent updates to replicas, features an algorithm for automatic conflict resolution, and ensures eventual convergence despite potential differences in states.

The Yjs library, implemented by various editors, is itself an implementation of CRDT. There exists an interesting Medium article on the topic for those interested in toying with it. A prominent example of an editor utilizing Yjs is Plate, which ships with an integrated collaboration feature in its editor. Furthermore, Plate offers a comprehensive solution for collaborative editing by providing a server-side implementation, named Hocuspocus,. While we couldn’t implement it because our backend is written in Rails, it stands as an excellent starting “plate” for those building from scratch.

Concluding thoughts

Editor.js performs extremely well, up until the moment when one attempts to implement features beyond its API’s capabilities. Rich text editors are still in their infancy, lacking a perfect or even a great solution for collaboration functionalities. However, this project demonstrates the feasibility of constructing such a tool. While it may be basic at present, with continued refinement, it holds the potential to evolve into a superb platform for collaborators to seamlessly share and edit their articles.