DEV Community

Cover image for A Document Revisions System in JavaScript
Yoram Kornatzky
Yoram Kornatzky

Posted on • Edited on • Originally published at yoramkornatzky.com

A Document Revisions System in JavaScript

Quill is a rich text editor that supplies deltas of change. So you can build quite a sophisticated system of document revisions within the browser.

Let's first go over how you use Quill.

Quill in JavaScript

Quill Editor

<!DOCTYPE html>
<html lang="en">
  <head>

    <!-- Include stylesheet -->
    <link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">

  </head>

  <body>

    <!-- Create the editor container -->
    <div id="editor" style="height: 375px;">
      <p>Hello World!</p>
      <p>Some initial <strong>bold</strong> text</p>
      <p><br></p>
    </div>

    <!-- Include the Quill library -->
    <script src="https://cdn.quilljs.com/1.3.6/quill.js"></script>

    <!-- Initialize Quill editor -->
    <script>
      var quill = new Quill('#editor', {
        theme: 'snow'
      });
  </script>

  <body>
</html>
Enter fullscreen mode Exit fullscreen mode

Lots of customizations of the toolbar are possible.

How to Get The Content

Plain Text

quill.getText()
Enter fullscreen mode Exit fullscreen mode

HTML

quill.root.innerHTML
Enter fullscreen mode Exit fullscreen mode

Delta

Yielding the change as Quill Delta,

quill.getContents()
Enter fullscreen mode Exit fullscreen mode

Events

One can listen to events, and what you usually want is the HTML,

Text Change

  quill.on('text-change', function(delta, oldDelta, source) {     
        if (source == 'api') {
          console.log("An API call triggered this change.");
        } else if (source == 'user') {
          console.log("A user action triggered this change.");
        }
  });
Enter fullscreen mode Exit fullscreen mode

Selection Change

  quill.on('selection-change', function(range, oldRange, source) {
        if (range) {
          if (range.length == 0) {
            console.log('User cursor is on', range.index);
          } else {
            var text = quill.getText(range.index, range.length);
            console.log('User has highlighted', text);
          }
        } else {
          console.log('Cursor not in the editor');
        }
  });
Enter fullscreen mode Exit fullscreen mode

Editor Change

  quill.on('editor-change', function(eventName, ...args) {
    if (eventName === 'text-change') {
      // args[0] will be delta
    } else if (eventName === 'selection-change') {
      // args[0] will be old range
    }
  });
Enter fullscreen mode Exit fullscreen mode

Comparing Document Revisions

If you have stored the content of different document revisions as a delta,

quill.getContents()
Enter fullscreen mode Exit fullscreen mode

Then you can compare the revisions using:

  var diff = oldContent.diff(newContent);
Enter fullscreen mode Exit fullscreen mode

Thereby obtaining a delta again.

To color the difference, use:

  for (var i = 0; i < diff.ops.length; i++) {
    var op = diff.ops[i];
    // if the change was an insertion
    if (op.hasOwnProperty('insert')) {
      // color it green
      op.attributes = {
        background: "#cce8cc",
        color: "#003700"
      };
    }
    // if the change was a deletion 
    if (op.hasOwnProperty('delete')) {
      // keep the text
      op.retain = op.delete;
      delete op.delete;
      // but color it red and struckthrough
      op.attributes = {
        background: "#e8cccc",
        color: "#370000",
        strike: true
      };
    }
  }
Enter fullscreen mode Exit fullscreen mode

Compose the old content with the colored difference

  var adjusted = oldContent.compose(diff);
Enter fullscreen mode Exit fullscreen mode

And display the tracked changes in the editor,

quill_diff.setContents(adjusted);
Enter fullscreen mode Exit fullscreen mode

Quill Document Revisions

This code is adapted from a Codepen by Joe Pietruch.

The superstate State Library

superstate is a tiny efficient state library that distinguishes a state and a draft.

First, you define the initial state,

const text = superstate('
    <p>Hello World!</p>
    <p>Some initial <strong>bold</strong> text </p><p><br></p>
')
Enter fullscreen mode Exit fullscreen mode

Then you create a draft with sketch

text.sketch('
    <p>Hello World!</p>
    <p>Some initial <strong>bold</strong> text </p>
    <p>And an <em>italic</em> scene</p><p><br></p>
') 
Enter fullscreen mode Exit fullscreen mode

And you publish it to the state, with publish

text.publish()
Enter fullscreen mode Exit fullscreen mode

Using superstate, together with the above code for comparing document revisions, we can revise a document, track changes, and then save the new revision to the state.

Persistence

When you work in the browser, you must auto-save your work if it is more than a few minutes worth of work.

You can do this with superstate using the localStorage adapter,

import { superstate } from '@superstate/core'
import { ls } from '@superstate/adapters'

const text = superstate(').use([
  ls('doc'), // 'doc' is the localStorage key
]) 
Enter fullscreen mode Exit fullscreen mode

And now, every change to the state is saved to local storage. This does not apply to drafts unless you create the state with:

const count = superstate(0).use([ls('count', { draft: true })]) 
Enter fullscreen mode Exit fullscreen mode




What is Missing for a Full System of Revisions?

We must add a component to accept/reject a highlighted change within the editor.

This design will be part of a whole document management system, with workflows of writing and revision.

Top comments (0)