UNPKG

9.4 kBJavaScriptView Raw
1(function (global, factory) {
2 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('slate')) :
3 typeof define === 'function' && define.amd ? define(['exports', 'slate'], factory) :
4 (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.SlateHistory = {}, global.Slate));
5})(this, (function (exports, slate) { 'use strict';
6
7 /*!
8 * is-plain-object <https://github.com/jonschlinkert/is-plain-object>
9 *
10 * Copyright (c) 2014-2017, Jon Schlinkert.
11 * Released under the MIT License.
12 */
13
14 function isObject(o) {
15 return Object.prototype.toString.call(o) === '[object Object]';
16 }
17
18 function isPlainObject(o) {
19 var ctor,prot;
20
21 if (isObject(o) === false) return false;
22
23 // If has modified constructor
24 ctor = o.constructor;
25 if (ctor === undefined) return true;
26
27 // If has modified prototype
28 prot = ctor.prototype;
29 if (isObject(prot) === false) return false;
30
31 // If constructor does not have an Object-specific method
32 if (prot.hasOwnProperty('isPrototypeOf') === false) {
33 return false;
34 }
35
36 // Most likely a plain Object
37 return true;
38 }
39
40 // eslint-disable-next-line no-redeclare
41 var History = {
42 /**
43 * Check if a value is a `History` object.
44 */
45 isHistory: function isHistory(value) {
46 return isPlainObject(value) && Array.isArray(value.redos) && Array.isArray(value.undos) && (value.redos.length === 0 || slate.Operation.isOperationList(value.redos[0].operations)) && (value.undos.length === 0 || slate.Operation.isOperationList(value.undos[0].operations));
47 }
48 };
49
50 /**
51 * Weakmaps for attaching state to the editor.
52 */
53 var HISTORY = new WeakMap();
54 var SAVING = new WeakMap();
55 var MERGING = new WeakMap();
56 // eslint-disable-next-line no-redeclare
57 var HistoryEditor = {
58 /**
59 * Check if a value is a `HistoryEditor` object.
60 */
61 isHistoryEditor: function isHistoryEditor(value) {
62 return History.isHistory(value.history) && slate.Editor.isEditor(value);
63 },
64 /**
65 * Get the merge flag's current value.
66 */
67 isMerging: function isMerging(editor) {
68 return MERGING.get(editor);
69 },
70 /**
71 * Get the saving flag's current value.
72 */
73 isSaving: function isSaving(editor) {
74 return SAVING.get(editor);
75 },
76 /**
77 * Redo to the previous saved state.
78 */
79 redo: function redo(editor) {
80 editor.redo();
81 },
82 /**
83 * Undo to the previous saved state.
84 */
85 undo: function undo(editor) {
86 editor.undo();
87 },
88 /**
89 * Apply a series of changes inside a synchronous `fn`, without merging any of
90 * the new operations into previous save point in the history.
91 */
92 withoutMerging: function withoutMerging(editor, fn) {
93 var prev = HistoryEditor.isMerging(editor);
94 MERGING.set(editor, false);
95 fn();
96 MERGING.set(editor, prev);
97 },
98 /**
99 * Apply a series of changes inside a synchronous `fn`, without saving any of
100 * their operations into the history.
101 */
102 withoutSaving: function withoutSaving(editor, fn) {
103 var prev = HistoryEditor.isSaving(editor);
104 SAVING.set(editor, false);
105 fn();
106 SAVING.set(editor, prev);
107 }
108 };
109
110 function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; }
111 function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
112 function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
113 /**
114 * The `withHistory` plugin keeps track of the operation history of a Slate
115 * editor as operations are applied to it, using undo and redo stacks.
116 *
117 * If you are using TypeScript, you must extend Slate's CustomTypes to use
118 * this plugin.
119 *
120 * See https://docs.slatejs.org/concepts/11-typescript to learn how.
121 */
122 var withHistory = function withHistory(editor) {
123 var e = editor;
124 var apply = e.apply;
125 e.history = {
126 undos: [],
127 redos: []
128 };
129 e.redo = function () {
130 var history = e.history;
131 var redos = history.redos;
132 if (redos.length > 0) {
133 var batch = redos[redos.length - 1];
134 if (batch.selectionBefore) {
135 slate.Transforms.setSelection(e, batch.selectionBefore);
136 }
137 HistoryEditor.withoutSaving(e, function () {
138 slate.Editor.withoutNormalizing(e, function () {
139 var _iterator = _createForOfIteratorHelper(batch.operations),
140 _step;
141 try {
142 for (_iterator.s(); !(_step = _iterator.n()).done;) {
143 var op = _step.value;
144 e.apply(op);
145 }
146 } catch (err) {
147 _iterator.e(err);
148 } finally {
149 _iterator.f();
150 }
151 });
152 });
153 history.redos.pop();
154 e.writeHistory('undos', batch);
155 }
156 };
157 e.undo = function () {
158 var history = e.history;
159 var undos = history.undos;
160 if (undos.length > 0) {
161 var batch = undos[undos.length - 1];
162 HistoryEditor.withoutSaving(e, function () {
163 slate.Editor.withoutNormalizing(e, function () {
164 var inverseOps = batch.operations.map(slate.Operation.inverse).reverse();
165 var _iterator2 = _createForOfIteratorHelper(inverseOps),
166 _step2;
167 try {
168 for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
169 var op = _step2.value;
170 e.apply(op);
171 }
172 } catch (err) {
173 _iterator2.e(err);
174 } finally {
175 _iterator2.f();
176 }
177 if (batch.selectionBefore) {
178 slate.Transforms.setSelection(e, batch.selectionBefore);
179 }
180 });
181 });
182 e.writeHistory('redos', batch);
183 history.undos.pop();
184 }
185 };
186 e.apply = function (op) {
187 var operations = e.operations,
188 history = e.history;
189 var undos = history.undos;
190 var lastBatch = undos[undos.length - 1];
191 var lastOp = lastBatch && lastBatch.operations[lastBatch.operations.length - 1];
192 var save = HistoryEditor.isSaving(e);
193 var merge = HistoryEditor.isMerging(e);
194 if (save == null) {
195 save = shouldSave(op);
196 }
197 if (save) {
198 if (merge == null) {
199 if (lastBatch == null) {
200 merge = false;
201 } else if (operations.length !== 0) {
202 merge = true;
203 } else {
204 merge = shouldMerge(op, lastOp);
205 }
206 }
207 if (lastBatch && merge) {
208 lastBatch.operations.push(op);
209 } else {
210 var batch = {
211 operations: [op],
212 selectionBefore: e.selection
213 };
214 e.writeHistory('undos', batch);
215 }
216 while (undos.length > 100) {
217 undos.shift();
218 }
219 history.redos = [];
220 }
221 apply(op);
222 };
223 e.writeHistory = function (stack, batch) {
224 e.history[stack].push(batch);
225 };
226 return e;
227 };
228 /**
229 * Check whether to merge an operation into the previous operation.
230 */
231 var shouldMerge = function shouldMerge(op, prev) {
232 if (prev && op.type === 'insert_text' && prev.type === 'insert_text' && op.offset === prev.offset + prev.text.length && slate.Path.equals(op.path, prev.path)) {
233 return true;
234 }
235 if (prev && op.type === 'remove_text' && prev.type === 'remove_text' && op.offset + op.text.length === prev.offset && slate.Path.equals(op.path, prev.path)) {
236 return true;
237 }
238 return false;
239 };
240 /**
241 * Check whether an operation needs to be saved to the history.
242 */
243 var shouldSave = function shouldSave(op, prev) {
244 if (op.type === 'set_selection') {
245 return false;
246 }
247 return true;
248 };
249
250 exports.HISTORY = HISTORY;
251 exports.History = History;
252 exports.HistoryEditor = HistoryEditor;
253 exports.MERGING = MERGING;
254 exports.SAVING = SAVING;
255 exports.withHistory = withHistory;
256
257}));