UNPKG

3.43 kBJavaScriptView Raw
1import Parchment from 'parchment';
2import Quill from '../core/quill';
3import Module from '../core/module';
4
5
6class History extends Module {
7 constructor(quill, options) {
8 super(quill, options);
9 this.lastRecorded = 0;
10 this.ignoreChange = false;
11 this.clear();
12 this.quill.on(Quill.events.EDITOR_CHANGE, (eventName, delta, oldDelta, source) => {
13 if (eventName !== Quill.events.TEXT_CHANGE || this.ignoreChange) return;
14 if (!this.options.userOnly || source === Quill.sources.USER) {
15 this.record(delta, oldDelta);
16 } else {
17 this.transform(delta);
18 }
19 });
20 this.quill.keyboard.addBinding({ key: 'Z', shortKey: true }, this.undo.bind(this));
21 this.quill.keyboard.addBinding({ key: 'Z', shortKey: true, shiftKey: true }, this.redo.bind(this));
22 if (/Win/i.test(navigator.platform)) {
23 this.quill.keyboard.addBinding({ key: 'Y', shortKey: true }, this.redo.bind(this));
24 }
25 }
26
27 change(source, dest) {
28 if (this.stack[source].length === 0) return;
29 let delta = this.stack[source].pop();
30 this.lastRecorded = 0;
31 this.ignoreChange = true;
32 this.quill.updateContents(delta[source], Quill.sources.USER);
33 this.ignoreChange = false;
34 let index = getLastChangeIndex(delta[source]);
35 this.quill.setSelection(index);
36 this.stack[dest].push(delta);
37 }
38
39 clear() {
40 this.stack = { undo: [], redo: [] };
41 }
42
43 cutoff() {
44 this.lastRecorded = 0;
45 }
46
47 record(changeDelta, oldDelta) {
48 if (changeDelta.ops.length === 0) return;
49 this.stack.redo = [];
50 let undoDelta = this.quill.getContents().diff(oldDelta);
51 let timestamp = Date.now();
52 if (this.lastRecorded + this.options.delay > timestamp && this.stack.undo.length > 0) {
53 let delta = this.stack.undo.pop();
54 undoDelta = undoDelta.compose(delta.undo);
55 changeDelta = delta.redo.compose(changeDelta);
56 } else {
57 this.lastRecorded = timestamp;
58 }
59 this.stack.undo.push({
60 redo: changeDelta,
61 undo: undoDelta
62 });
63 if (this.stack.undo.length > this.options.maxStack) {
64 this.stack.undo.shift();
65 }
66 }
67
68 redo() {
69 this.change('redo', 'undo');
70 }
71
72 transform(delta) {
73 this.stack.undo.forEach(function(change) {
74 change.undo = delta.transform(change.undo, true);
75 change.redo = delta.transform(change.redo, true);
76 });
77 this.stack.redo.forEach(function(change) {
78 change.undo = delta.transform(change.undo, true);
79 change.redo = delta.transform(change.redo, true);
80 });
81 }
82
83 undo() {
84 this.change('undo', 'redo');
85 }
86}
87History.DEFAULTS = {
88 delay: 1000,
89 maxStack: 100,
90 userOnly: false
91};
92
93function endsWithNewlineChange(delta) {
94 let lastOp = delta.ops[delta.ops.length - 1];
95 if (lastOp == null) return false;
96 if (lastOp.insert != null) {
97 return typeof lastOp.insert === 'string' && lastOp.insert.endsWith('\n');
98 }
99 if (lastOp.attributes != null) {
100 return Object.keys(lastOp.attributes).some(function(attr) {
101 return Parchment.query(attr, Parchment.Scope.BLOCK) != null;
102 });
103 }
104 return false;
105}
106
107function getLastChangeIndex(delta) {
108 let deleteLength = delta.reduce(function(length, op) {
109 length += (op.delete || 0);
110 return length;
111 }, 0);
112 let changeIndex = delta.length() - deleteLength;
113 if (endsWithNewlineChange(delta)) {
114 changeIndex -= 1;
115 }
116 return changeIndex;
117}
118
119
120export { History as default, getLastChangeIndex };