1 | import Parchment from 'parchment';
|
2 | import Quill from '../core/quill';
|
3 | import Module from '../core/module';
|
4 |
|
5 |
|
6 | class 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 | }
|
87 | History.DEFAULTS = {
|
88 | delay: 1000,
|
89 | maxStack: 100,
|
90 | userOnly: false
|
91 | };
|
92 |
|
93 | function 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 |
|
107 | function 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 |
|
120 | export { History as default, getLastChangeIndex };
|