Our team was tasked with scoping a new feature in the story editor as part of the Verge redesign project, called “Quick Posts”. Originally framed as an editorial experiment, myself and another team member advocated to build this new entry type in Vue as a means of moving us forward in our big rewrite project. Product originally wanted us to do a quicker, dirtier version in our old tech, because we weren’t totally sure if this would stick around in the long term. But because quick posts are a simplified version of the our main compose screen, and only a few select folks would be writing these, I thought it was the best way to build on the autosave work I had completed in the fall with very limited risk.
Because of the experimental nature of the feature, the dev plan I wrote was ever evolving as the product plan shifted. I created engineering monthly goals for myself and other team members, given the sizable amount of work and changes to the features. This project also gave us the opporunity to integrate our rich text module into Vue for the first time, so I wrote deep dives on how it worked with our old stack, how it enables multi-user editing, and how it might work with Vue.
Quill in Vue
Like React, Vue creates a virtual dom, which puts it into direct conflict with anything that wants to manipulate the dom—one of the core features of any rich text editor. Particularly one with its own internal state and document model.
Quill uses a document model called Parchment which essentially maps HTML in the editor to meaningful properties and content for Quill to consume. Unlike a “wysiwyg” editor, the output of Quill is not HTML, it’s a document with an array of instructions and attributes that can be used to generate any HTML, JSON or other output on the fly. It’s also what enables composing content together, which enables multi-user editing and version control diffing.
Reactive properties can also cause problems with a system designed to use native Web APIs, in particular native dom node events. Quill must compose any outside changes together with existing content, so simply binding content to Vuex state is not possible. Neither is using regular input event bindings for changes—Quill relies on a
contenteditable div element, which is honestly an entire long case study (or three) in and of itself.
Luckily, Quill does have its own limited set of APIs for setting and getting content. I created a
Quill vue component with an empty
div for quill to initialize inside of, which happens on mount. When the Quill instance is marked as
ready, we initialize whatever contents we have in the vuex state, set up the cursors of other active users, set up listeners for Quill change events, and set up vue watchers for incoming changes from the server or formatting events from the toolbar, so we can compose them into the existing content.
Thankfully, this is fairly straight forward with simple Quill fields that only contain text. When we tackle the regular story compose experience, we will have to deal with object inserts, like placing an Instagram embed in the middle of a story. This is where a lot of wonkyness with contenteditable and the selection api really rear their heads, and will have to be a story for another day.
All of our stories use almost the exact same schema, and we use the existence of specific content fields to differentiate them. For instance, map-based stories (like those found on Eater) have map points. Despite the extremely stripped down-ness of quick posts, we did add a new field to the schema—attachments. Each post can have a single attachment: an image, an embed, a related link or external link (galleries coming soon). We often just use basic text fields for things like links, but in this case, I added an internal linking capability so that a user can input a url and we can find the story (even from other networks) and we’ll save just the ID and an optional override name. This way our audience layer can display internal links with whatever data they want to pull—the original story title, images, the dek, and so forth.
I spent a lot of time thinking about how we can push our platform forward, given our reliance on unmaintained tools and frameworks like Ractive, while building new features, while supporting the old ones, with a limited number of engineers. No easy task. This is the reason I pushed for Vue quick entries; given our resources, we must be very strategic in how we use every new feature, or even updates to old ones, to continue to move in the direction we all want to head. Every time I touch code, I consider if there is any small refactor that might move us forward, no matter how small the change might be; or how large.
It turned out to be a good thing we went with Vue, as quick posts have transformed into a first class feature, with The Verge heavily pinning their editorial strategy on it. We worked very closely with the Verge to align our tooling with their hopes and dreams, a productive process that led to a win all around.