A Document Revisions System in JavaScript
Tags: javascript, frontend, webdev, documentmanagemenet
Published: 2022-08-02 20:04:53

[Quill](https://quilljs.com) 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y6236ie9lxurj969pugy.png)

	<!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>
	
Lots of customizations of the toolbar are possible.
	
## How to Get The Content

*Plain Text*	

	quill.getText()

*HTML*

	quill.root.innerHTML
	
*Delta*

Yielding the change as [Quill Delta](https://github.com/quilljs/delta/),

	quill.getContents()

	
## 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.");
	        }
      });
	
*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');
	        }
      });
	
*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
        }
      });
	
## Comparing Document Revisions

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

	quill.getContents()
	  
Then you can compare the revisions using:
	  
	  var diff = oldContent.diff(newContent);
	
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
	      };
	    }
	  }
	
Compose the old content with the colored difference  
	  
	  var adjusted = oldContent.compose(diff);
	  
And display the tracked changes in the editor,

    quill_diff.setContents(adjusted);



![Quill Document Revisions](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v4lux8jgf00ponawwosr.png)

This code is adapted from a [Codepen](https://codepen.io/percipient24) by [Joe Pietruch](https://percipient24.com).

## The superstate State Library

[superstate](https://superstate.dev) 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>
	')

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>
	') 
	
And you publish it to the state, with *publish*

	text.publish()
	
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
	]) 
	
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 })]) 

## 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.