JEFF SISSON: Hi.
My name's Jeff Sisson, and I work at "The New York Times"
as part of a team that builds the tools the newsroom uses
to publish our journalism.
I hope to describe how our team uses
open source technologies and Firestore to create
a completely unique rich text editor for "The New York
Times."
But first, I want to describe a radical position
I've come to believe as a byproduct of our work
on tools for the newsroom.
Hear me out.
Everyone deserves their own collaborative rich text editor.
So let me explain what I mean here.
Rich text editors, like Google Docs,
have made what you see is what you get style editing
a seamless experience.
This actually makes it harder to appreciate
some of the complexity that underlies a pleasant rich text
editor.
Most people just want it to work.
They want to work in an editor that
produces HTML-like documents, which
may include bold, italics, strikethrough, other emphasis.
And they want to compose using paragraph, lists, possibly even
tables or images or other more complex layouts.
Even when just editing plain text,
there are lots of subtleties that editors like Google Docs
or Word handle for you.
There's live-as-you-type text suggestions for grammar
or spelling errors or the ability for others to comment
on the document or tracking the changes that you've made
to a document over time.
And it's now even a common assumption
that if you're editing a rich text document in a browser,
it must be networked, meaning anyone
can view the changes as you're making them
and contribute their own changes live.
So all of this is obviously a lot.
It's reasonable to wonder why anyone
would want to create their own rich text editor that
does all of these things rather than relying
on one of the many capable text editors that exist,
like Google Docs or Word or even tools like Medium or WordPress.
A little bit more detail on our work at "The New York Times"
where the tools that we maintain for editors, producers,
and reporters facilitate publishing articles
and our coverage of the news.
So "The New York Times" has a huge newsroom.
At any time of day, someone is working
to report on world events in articles
that are often published while that news is still happening.
We've had a number of different CMS's over the years.
And when we set out to build a new article editor,
we knew that we wanted it to be collaborative first.
Photo editors should be able to place photos,
while other editors are polishing a draft
of the text ready to publish.
And, yet, other editors may be readying
new quotes or new information for later publishing.
And all of this needs to be happening
for users that are located in bureaus around the globe split
amongst many different time zones.
Enter Oak.
So what is Oak?
Oak is the name of the rich text editor
we built that powers every article
published by "The New York Times."
What are we looking at in this screenshot?
In the white canvas in the center,
you can see that the basic skeleton of an empty "New York
Times" article exists.
You have a headline, a summary, some bylines, and a timestamp.
Below that, we have placeholder texts
that commands you to begin writing where
the innards of the article go.
So things like paragraphs, images, videos,
interactive graphics, div text, scrolling text,
and image combinations, contributor bylines and much,
much more.
This is the basic canvas for Oak's rich text blocks.
And to the left of that canvas, there are extra textual tools,
like Spell Check, and validation for "New York Times'" specific
writing rules, and a space for writing drafts.
All of these things together are workflow tools
that are specifically tuned to how "The New York Times"
newsroom works.
And very few of these features exist
in other off-the-rack rich text editors.
So why did we build Oak from scratch
rather than building something on top
of a different proprietary editor?
"The New York Times" has a specific editorial process
that is very unique to itself.
We use a style guide that governs
how articles are written.
There are rules for how obituary headlines are written--
rules for the use of courtesy titles such as Mr. and Ms.
And rules for the use of date lines, like WASHINGTON,
in all caps at the top of an article
to indicate that a reporter was actually working
from a specific location.
These editorial nuances are part of what contributes
to "The Times's" unique voice.
And building our own editor allowed
us to incorporate these rules into how the editor works
so that the newsroom can see feedback for an article
as it's being written and edited.
Before it's published, reporters may
contribute a first draft of an article,
then hand off to a first editor who
may work with the second editor or copy editor.
Some highly sensitive articles are even
edited with a privacy mode so that only parts of the newsroom
that are working on them can see the articles prior
to publication.
All of these rules for around who can access the story
and edit it at different parts of an article's editorial
lifecycle were only able to be built into Oak
because we were building an editor from scratch.
And our editor also aims for what you see is what you mean,
fidelity.
And what does that mean?
So when editors are working in the Oak article editor,
all of the styles for headlines, paragraphs, and other elements
that you see as a reader are also shown to them,
meaning that the newsroom has instant feedback on exactly
how their story will look when it's published.
Many rich text editors are focused primarily
on the editing.
And so this ability to sort of see the shape of the article
as it's being written has helped to change the kinds of articles
that we publish.
All of these features together are
difficult to achieve in off-the-rack rich text
editors, which is part of why most of Oak
was built from scratch to accommodate our newsroom's
needs.
We want our reporters, editors, and producers
to focus on contributing journalism
and let the rich text editor itself
do much of the heavy lifting around editorial workflow
and document production.
But we didn't attempt to build our writer alone.
The main technical component that
allows "The New York Times" and, of course, you
if you want to create the rich text editor of our dreams
is called ProseMirror.
It's a set of open source JavaScript libraries developed
by [? Maryne ?] [? Haverbeck ?] that do the hard parts of rich
text editing in web browsers.
The browser technology that most rich text editors rely on
is something called content editable.
And as a software spec, it's actually
very messy and inconsistent.
How input events, like pastes, key presses, and selections
are interpreted vary wildly between different browsers.
And there are many long-standing bugs and gotchas even
on modern browsers like Google Chrome.
ProseMirror abstracts around many
of these inconsistencies, which allows
developers to work with straightforward primitives.
You have a documents that's represented
by a node, a selection that uses a selection class.
And changes to a document are represented
by transforms and transactions.
ProseMirror also handles painting some of these updates
to a rich text document into the DOM.
When a document changes, whether that document is changed
by text that you've typed into it
or via a remote collaborating peer,
ProseMirror will make sure the DOM reflects the latest
state of the document.
In ProseMirror, everything is a node, even the top level
document.
Each top level document node contains one or more children,
each of which may contain their own children
and have attributes.
This document here on screen represents a very simple
paragraph with the text Hello world bolded and italicized.
But documents, of course, can be much more complex.
So every article in "The New York Times" publishes
contains many types of different nodes.
And the purpose of the Oak editor
is to allow editors in the newsroom
the flexibility to produce new combinations of nodes that
may share some of the same node patterns
with other articles that may use different types of nodes
or different orders of nodes to emphasize
a particular structure of a story within an article.
Here, you can see what our readers
would see for an article that was created in Oak
and appears on our website about pollution in China.
And here's that same article as it appears in the Oak editor
along with a view of the node data structure that
underlies the article.
All of the rules for what types of document can contain
and their order live in something
called a schema definition that ProseMirror understands
and is able to enforce as you make changes to the document.
So whenever I'm reading a "New York Times" article,
I often think about it and its underlying node structure
and schema with a sort of X-ray view in my head.
This article about curbing pollution starts with a header.
And the header contains itself headline, summary,
a leading media note, and a byline node.
And the header is followed by a paragraph
that contains text itself contains
a link, and so on and so forth.
So ProseMirror schema allows you to impose
really far-fetched constraints on your document shape.
It has its own language which is a subset of RegEx that
can mandate complex rules of adjacency
for different types of nodes or constrain how many nodes
of a specific type can be added in a specific position
or in a row.
This fine-grained control over what
is valid for a rich text document
unlocks many fascinating opportunities
for creating rich text editors with experiences
that are tuned to different kinds of documents
that they were built for.
So just to take some examples, you
might have a Haiku editor that has live feedback as you're
typing about the number of syllables per line
and constrains the lines as you type them.
Or an Oulipo editor where two people
are working on the same document at the same time
but only see a shared strand of text between them
so they compose stories as part of one holistic whole.
Or maybe you'd have a highly structured FAQ editor
that offers suggestions about the types of headings
as you type them.
Or an email editor that intersperses emoji or color
as you're writing them.
With ProseMirror, the different kinds
of schemas that you can produce are really unlimited which
is extremely powerful.
With a schema in place for your document,
it's time to start editing.
Every change to a document, whether it's
typing text into a paragraph, dragging or dropping an image,
or italicizing existing text produces a data structure
in ProseMirror that's called a step.
The basic recipe for how you go from one document state
to the next document state is by adding a step to it.
Steps are created from an existing document
and referred to an indexing system,
whereby, each letter or leaf node within a document
can be addressed with a number.
So in our example, Hello world doc, the H in Hello
is at index two.
And similarly, the d in world starts at index 12.
So if I was to type in exclamation point
into a ProseMirror managed editor
with this document in it, the exclamation point
would be represented as a replace step, which
is a specific class of step at position 12 containing
a string which contains the exclamation point.
ProseMirror's collaborative plugin
tracks the local state of the document
using a monotonic counter that starts at zero.
So if you have an empty document,
you have a document with a step counter of zero.
For each step produced in the local document,
the counter increments by one.
Once the step is created, the ProseMirror collaborative
plugin requires you to implement what's
called an authority server in order for that step
to be synced remotely.
The authority server's job is either
to accept or reject incoming steps from multiple clients
according to whether those clients are attempting
to insert steps in or out of order.
Clients whose steps are acknowledged
by the authority's server continue on their merry way.
Clients whose steps are rejected because their steps were
submitted out of order and don't reflect their other peers steps
need to perform an operation that basically
operates like a git rebase.
The ProseMirror collaborative plugin
will take into account any steps that the local client hasn't
yet fetched and change the steps to reflect those other peers
as steps.
Once this rebase operation is complete,
the client can attempt to resend their steps back
to the authority server.
So while ProseMirror's collaborative editing
architecture requires a central authority server in order
to order incoming steps from all connected clients,
it actually only provides a reference implementation,
which means you're on the hook to provide
your own implementation.
This reference implementation is only persisted in memory
and can't really scale.
And so when we set out to build collaborative editing in Oak,
we knew we needed to build an authority server.
We evaluated several different databases
and backend architectures to support collaborative editing.
But ultimately we settled on Firestore.
Part of Firestore's appeal is that in addition
to providing traditional database functionality,
so as it reads and writes, it also
comes with a client-compatible event bus built in.
For Oak, which is a browser-based application,
this means that we're able to read and write
changes from the database directly in the browser.
Oak uses redux and redux-saga to manage its local state.
And because Firestore is an event bus as well,
we can treat Firestore as if it was just another local
to the browser event sync.
Steps arrived from Firestore and wrapped in redux actions
that mutate the local state.
This vastly simplified the architecture
we needed to build our editor.
We didn't need to build out a CRUD API, an event bus.
And all the client-sided [? after ?] code,
you need to integrate with the custom back end.
In the Oak editor, each time a ProseMirror instance
produces a new step, we serialize that step's data
to JSON and attempt to write that step
to Firestore using the latest known step ID plus 1
as the Firestore document key.
For an empty document starting at step ID 0,
we would attempt to write our first step
with a Firestore key of one.
And for the second step, we would
attempt to write with a Firestore key of two and so on.
If a Firestore document exists with the next most step ID key,
we end the transaction.
This signals to our client code that we
need to rebase our local steps to reflect steps we haven't yet
fetched from Firestore before attempting to resend
our local steps again.
But if the insert succeeds, our Firestore listener
will see the steps as they're inserted.
And we will locally use this to confirm them
so that subsequent steps use the new document state
and step ID as the starting point.
When reading steps, whether you're
confirming a step you yourself inserted or receiving
a step from another collaborator,
the implementation looks exactly the same-- a Firestore
collection listener.
When the steps collection is updated,
we fetch any steps greater than the last step ID and apply them
to the local state of the document.
Because the listener executes whether or not
the steps you are attempting to insert have been successful,
rebasing other client steps are handled using the same listener
as well.
The Firestore listener acts like an event stream for any step
that's been confirmed remotely by Firestore,
whether those steps are your own or someone else's.
And here, I have a demo of that Oak editor
that's been modified to sort of show this process in action.
This is a simplified version of the Oak article editor.
Normally, Firestore's APIs are nearly instantaneous.
So I've changed it so that calls to Firestore
are artificially slower than they would be in real life
to make it easier to see what's going on.
I've also added this text at the top here
that illustrates communication with Firestore under the hood.
And I've hidden other parts of the app as well--
set up two tabs-- one on the left and one
on the right-- that are connected to the same Oak
article that was just created, meaning the step counter here
starts at zero.
When I type the word hello in the headline in the left tab,
a redux saga detects that there are
steps that haven't been sent and attempts
to send them with a Firestore transaction--
one for each letter.
Once the Firestore transaction completes,
the Firestore collection listener in the right tab
detects the latest steps and applies them to the document,
bringing the steps collection up to five steps stored.
Because this editor has been artificially slowed down,
we can actually simulate the case
where both tabs attempt to execute a Firestore
transaction at the same time.
I'll type the word world in the left tab
and hi in the right tab.
The left will win, which causes the right tab
to detect a conflict and rebase its steps.
After that, it re-attempts a transaction
inserting its steps, bringing both tabs up
to sync with each other.
Oak articles, of course, are not just limited to text.
Because every change is a step, even pasting something
like a link to an embedded tweet is
modeled as a step that will be sent to
and stored in Firestore in sync with both tabs.
So ProseMirror itself doesn't mandate
how collaborative editing is implemented.
And you're free to choose whichever
backend suits your needs.
Firestore was a good fit for "The New York Times"
because we needed to build a collaborative editor really
quickly without worrying too much about how
the backend would scale.
While our daily number of articles and production
is relatively modest, the true scale for our collaborative app
is determined by the rate at which most people can type,
which can add up quickly.
Choosing Firestore allowed us to not worry about
whether or not our database would
handle our anticipated load.
I think Firestore is also a good fit for anyone building
collaborative editing that's part of a broader
client-side heavy browser app.
It's really freeing to think of Firestore as just another event
source within your browser-based app
where mutations to your app state
may come from remote changes or local changes.
And you can focus your client code
on what those mutations are doing rather
than how they're produced.
Finally, Firestore allowed us to simplify
our overall architecture.
We didn't need to build out a CRED API
or worry about backend race conditions.
We were mostly able to focus on our apps business logic.
I hope everyone is sufficiently enthralled and inspired
to build a million tiny, beautiful, collaborative rich
text editors for whatever purposes
shoot your [? windsor ?] fancy.
I want to give a shout out to the Oak and publishing teams
at "The New York Times."
Have to say that the project of building a collaborative editor
was truly a multi-year collaborative
effort in its own right.
And I'm proud of the work we all jammed on together.
Thank you and go build the rich text editor of your dreams.