Skip to main content

A New Way to Create Reports

Gone are the days of losing context whenever you want to see what your markdown looks like when rendered. Here's a walkthrough of all the features of the new WYSIWYG report editor, with some fun facts about the engineering process.
Created on January 8|Last edited on February 8

Basic rich text editing

Start a paragraph with a markdown-inspired annotation like #, >, -, or ``` to get a heading, quote, list, or code block. Select any text with your mouse and click one of the formatting buttons that pops up to add styles like bold or italic. You can also use familiar keyboard shortcuts like ⌘+b and ⌘+i. Hit / to open a searchable menu of all the available block types. Here's a quick preview of the basic types:

Heading 2

Heading 3

  • Bulleted
  • List
  1. Numbered
  2. List
Block quote
# Code block

Looks good, but how much work actually went into this?

The backbone of this editor is a library called Slate, made by Ian Storm Taylor, founder of Segment. An unfortunate side effect of using a library is that it's hard for outsiders to tell how much was already done by the library and how much work we had to put in ourselves. It's especially hard to explain what Slate comes with because it's not an ordinary plug and play library. It's more of a framework around content-editable that turns it into a sane and consistent API, like what React does for raw HTML/JS editing. Its key feature is composing all possible document transformations out of a set of 9 atomic operations (insert node, insert next, merge node, move node, remove node, remove text, set node, set selection, and split node), which makes it possible to implement cursor synchronization, undo history, and real-time collaboration.
Like how React doesn't come with any components out of the box, Slate doesn't come with any block types (like paragraphs and headers) out of the box. It's your responsibility to create the styling and behavior of each block type you need, which gives you full control over the editing experience. Because Slate is still in beta and was completely rewritten a few months ago, it's hard to find any high quality open source plug-and-play Slate modules. So we ended up essentially writing all the blocks from scratch. Also because it's in beta, Slate is rather buggy and poorly documented, so we often had to patch core bugs and read Slate's source code to understand what was going on. But despite its flaws, Slate is some of the best software I've ever read and was a huge shortcut to getting this out the door.
To illustrate the level of detail we had to work with, consider two common commands, enter and backspace. In most cases, we want enter to insert a new block of the same type as the current block, and backspace to delete the previous character. So Slate does this as a sane default. But in practice, you actually expect different behavior in different contexts. For example, when you hit enter in a bulleted list, you expected another list item.
  • Try hitting enter here!
But when you hit enter in a heading, you want the new block to be a normal paragraph.

Try hitting enter here!

When you hit backspace at the beginning of a list item, you expect the list item to be converted into a normal paragraph.
  • Try deleting the bullet!
But when you hit backspace at the beginning of a header, you expect the header to merge with the previous block.

Try hitting backspace at the start of this line!

We had to go through every command in every possible context to make sure it does something sane. Many behaviors aren't standardized across popular text editors, so we had to make our own judgement calls. Try messing with this numbered list here. What happens when you delete the first item? What about from the middle?
  1. Try
  2. Messing
  3. With
  4. This
  5. List

Okay, good job. What else you got?

Every feature below was implemented from scratch. No existing open source library had the extensibility or polish that we wanted.
You've seen it already, but there's more than meets the eye. The hovering toolbar, inspired by Dropbox Paper, is the home of all context-aware buttons. Right now it's primarily used for formatting and links, but it's intended to be reused for editing anything in the future, like tables and latex.
A lot of details went into this toolbar. Here's some of the things we considered:
  • Even though there's different toolbars for different contexts, only one toolbar is allowed to be open at a time, for focus reasons. This is done by having all the toolbars actually just be one toolbar that morphs into different forms and moves around based on what you've selected or hovered over.
  • The toolbar repositions itself automatically to never appear off screen.
  • The toolbar is meant for the mouse, so it hides when you start typing. But if you make a selection using your keyboard and then move your mouse, it'll appear again.
  • Slate's default formatting detection is buggy; it sometimes says your selection is bolded when it's only partially bolded. This toolbar fixes that behavior; your selection is considered bold only if all the text in your selection is bold.
  • Link detection is treated differently. The toolbar considers your selection linked if any text in your selection is linked, making it easier to remove links.
  • When hovering over a link, the toolbar waits 400ms before appearing, so that it doesn't intrusively pop in and out as you move your cursor around the page.
  • Once the link editor is opened, you can move your mouse from the link to the editor, but move it anywhere else and the editor will close. Unless you've already clicked on the editor, in which case it'll stay open until you hit enter or escape.
  • Browsers only support one selection at a time, so when you're inserting a link, the toolbar needs to remember what your selection was while you're typing the link. In the future we may want to add a highlight on the selection so that the user knows that it remembered.

The slash menu

You've seen this one too, but did you know you can use it anywhere, not only at the start of new paragraphs? If you use it at the end of an existing block it'll insert a new block, but if you use it at the beginning of an existing block it'll convert the block to the new type. Try using it in the middle of a block!

Drag and drop

See the little drag handle on the right when you mouse over a block? Try using it to re-order blocks. A lot went into making that look clean.
Like in Notion, the drag handle also serves as an easy way to select content. Just click the drag handle to select all the content in the block! This is especially useful for panel grids and markdown blocks, as you'll see later.
Our implementation of drag and drop comes with a unique perk that I haven't seen in other editors. You can drag a block outside the browser into a report in another window to copy it over! Try it out by opening this page in a new window and dragging blocks between them.

Collapsing headings

Hover over a heading and you'll see a little caret on the left. Inspired by Paper, this button lets you fold entire sections. It works in read-only mode too, though folds are only saved in edit mode.
Since there's multiple levels of headings, there's also multiple levels of folding. See what happens when you fold these different levels of headings!

Medium heading

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Small heading

Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.

But wait, there's more!

We implemented collapsing headings a little differently than Paper. When you collapse a heading, the heading actually slurps up all the content and considers it children of itself. This means that when you drag and drop a collapsed heading, you're dragging all the collapsed content with it! This is useful for large-scale restructuring of your report.

Code

Start a block with three backticks (```) to create a code block.
# Code blocks by default are syntax-highlighted as Python.
# You can change the language using the dropdown in the top right.

memo = [0, 1]
def fibonacci(n):
if n <= len(memo):
return memo[n-1]
else:
temp_fib = fibonacci(n-1) + fibonacci(n-2)
memo.append(temp_fib)
return temp_fib

Images

Type /image and hit enter to insert an image placeholder like this one:
Drag an image here

You can click that block or drag an image from your desktop onto it to replace it with an actual image. Or you can just drag an image from your desktop onto anywhere in the document and that'll work as expected too. Here's an example image if you don't want to find one yourself.

Images are saved on our servers and automatically get shrunken if they're too large, thanks to CVP.

Panel grids

All this would be for null if reports couldn't support their main purpose, to explore and present data. So here it is, the tried and true panel grid.

Run set
11

This panel grid actually comes with a new feature: undo/redo. Try making changes to the chart and then hitting ⌘+z and ⌘+⇧+z!
While it looks like an effortless migration, this was actually the hardest part of the reports project. Our panel grids are stored in memory as normalized data in our Redux architecture. But Slate requires its data to be a denormalized JSON blob. Translating between these two worlds was a mess, and it's probably brittle. If you'd like to learn how it works, search our codebase for the phrase "insane, indecipherable state machine".
Eventually we'd like to have individual panels be their own blocks, for greater flexibility. But that'll have to wait!

Latex

Type "$$" to open up an inline LaTeX\LaTeX editor. Or add a LaTeX block in the slash menu:
z=xreal+iyimaginarycomplex numberz = \overbrace{ \underbrace{x}_\text{real} + i \underbrace{y}_\text{imaginary} }^\text{complex number}

Hit shift+enter to line break without exiting the LaTeX editor. Try typing invalid LaTeX to see how it's handled.

Markdown conversion

If you're wondering what happens to all your existing reports, good news. Altay wrote a function that converts Markdown to Slate, so you can easily migrate your existing reports. Try it out here:

Lorem Ipsum.

Dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

xkcd


Note that you can also just leave it as markdown if you want and it'll behave the same as it used to. This is particularly useful for features that we haven't implemented in Slate yet, like tables and LaTeX. For this reason, Markdown blocks are also available in the slash menu.

Closing remarks

I hope you find this to be a much more pleasant editing experience than the old one. It's still in its early stages so there's bound to be bugs, especially in environments that aren't Chrome on MacOS.
This editor was a ton of fun to build, and I learned even more than I expected. If you're interested in contributing, let me know!
Last fun fact: I added a persistent blank line at the bottom of the report. You can't get rid of it. Trust me, you want it.