Front-End
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.
<!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.
Plain Text
quill.getText()
HTML
quill.root.innerHTML
Delta
Yielding the change as Quill Delta,
quill.getContents()
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
}
});
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);
This code is adapted from a Codepen by Joe Pietruch.
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>
')
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.
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 })])
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.
Yoram Kornatzky