UNPKG

slate-history

Version:

An operation-based history implementation for Slate editors.

667 lines (648 loc) • 24 kB
'use strict'; var slate = require('slate'); // eslint-disable-next-line no-redeclare var History = { /** * Check if a value is a `History` object. */ isHistory: function isHistory(value) { return slate.isObject(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)); } }; /** * Weakmaps for attaching state to the editor. */ var HISTORY = new WeakMap(); var SAVING = new WeakMap(); var MERGING = new WeakMap(); var SPLITTING_ONCE = new WeakMap(); // eslint-disable-next-line no-redeclare var HistoryEditor = { /** * Check if a value is a `HistoryEditor` object. */ isHistoryEditor: function isHistoryEditor(value) { return History.isHistory(value.history) && slate.Editor.isEditor(value); }, /** * Get the merge flag's current value. */ isMerging: function isMerging(editor) { return MERGING.get(editor); }, /** * Get the splitting once flag's current value. */ isSplittingOnce: function isSplittingOnce(editor) { return SPLITTING_ONCE.get(editor); }, setSplittingOnce: function setSplittingOnce(editor, value) { SPLITTING_ONCE.set(editor, value); }, /** * Get the saving flag's current value. */ isSaving: function isSaving(editor) { return SAVING.get(editor); }, /** * Redo to the previous saved state. */ redo: function redo(editor) { editor.redo(); }, /** * Undo to the previous saved state. */ undo: function undo(editor) { editor.undo(); }, /** * Apply a series of changes inside a synchronous `fn`, These operations will * be merged into the previous history. */ withMerging: function withMerging(editor, fn) { var prev = HistoryEditor.isMerging(editor); MERGING.set(editor, true); try { fn(); } finally { MERGING.set(editor, prev); } }, /** * Apply a series of changes inside a synchronous `fn`, ensuring that the first * operation starts a new batch in the history. Subsequent operations will be * merged as usual. */ withNewBatch: function withNewBatch(editor, fn) { var prevMerging = HistoryEditor.isMerging(editor); var prevSplitting = HistoryEditor.isSplittingOnce(editor); MERGING.set(editor, true); SPLITTING_ONCE.set(editor, true); try { fn(); } finally { MERGING.set(editor, prevMerging); if (!prevSplitting) { SPLITTING_ONCE["delete"](editor); } } }, /** * Apply a series of changes inside a synchronous `fn`, without merging any of * the new operations into previous save point in the history. */ withoutMerging: function withoutMerging(editor, fn) { var prev = HistoryEditor.isMerging(editor); MERGING.set(editor, false); try { fn(); } finally { MERGING.set(editor, prev); } }, /** * Apply a series of changes inside a synchronous `fn`, without saving any of * their operations into the history. */ withoutSaving: function withoutSaving(editor, fn) { var prev = HistoryEditor.isSaving(editor); SAVING.set(editor, false); try { fn(); } finally { SAVING.set(editor, prev); } } }; function unwrapExports (x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } function createCommonjsModule(fn, module) { return module = { exports: {} }, fn(module, module.exports), module.exports; } var _typeof_1 = createCommonjsModule(function (module) { function _typeof(o) { "@babel/helpers - typeof"; return module.exports = _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, module.exports.__esModule = true, module.exports["default"] = module.exports, _typeof(o); } module.exports = _typeof, module.exports.__esModule = true, module.exports["default"] = module.exports; }); unwrapExports(_typeof_1); var toPrimitive_1 = createCommonjsModule(function (module) { var _typeof = _typeof_1["default"]; function toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } module.exports = toPrimitive, module.exports.__esModule = true, module.exports["default"] = module.exports; }); unwrapExports(toPrimitive_1); var toPropertyKey_1 = createCommonjsModule(function (module) { var _typeof = _typeof_1["default"]; function toPropertyKey(t) { var i = toPrimitive_1(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } module.exports = toPropertyKey, module.exports.__esModule = true, module.exports["default"] = module.exports; }); unwrapExports(toPropertyKey_1); var defineProperty = createCommonjsModule(function (module) { function _defineProperty(e, r, t) { return (r = toPropertyKey_1(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: true, configurable: true, writable: true }) : e[r] = t, e; } module.exports = _defineProperty, module.exports.__esModule = true, module.exports["default"] = module.exports; }); var _defineProperty = unwrapExports(defineProperty); function _createForOfIteratorHelper$1(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray$1(r)) || e) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: true } : { done: false, value: r[_n++] }; }, e: function e(r) { throw r; }, 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 o, a = true, u = false; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = true, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; } function _unsupportedIterableToArray$1(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray$1(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray$1(r, a) : void 0; } } function _arrayLikeToArray$1(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), true).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } var isChildOf = function isChildOf(path, parent) { return path.length === parent.length + 1 && slate.Path.isParent(parent, path); }; var mapPath = function mapPath(path, by) { return slate.Path.transform(path, by); }; var mapSlot = function mapSlot(path, by) { if ((by.type === 'remove_node' || by.type === 'merge_node') && slate.Path.equals(by.path, path)) { return path; } if (by.type === 'move_node' && slate.Path.equals(by.path, path)) { return slate.Path.transform(path, { type: 'insert_node', path: by.newPath, node: { text: '' } }); } return mapPath(path, by); }; var mapOffset = function mapOffset(_ref, by, side) { var path = _ref.path, offset = _ref.offset; if (by.type === 'set_selection' || by.type === 'set_node') return { path: path, offset: offset }; if (by.type === 'insert_node' || by.type === 'remove_node' || by.type === 'split_node' || by.type === 'merge_node') { if (isChildOf(by.path, path)) { var index = by.path[by.path.length - 1]; if (by.type === 'insert_node' || by.type === 'split_node') { var at = by.type === 'split_node' ? index + 1 : index; if (at < offset || at === offset && side === 'after') offset++; } else if (by.type === 'remove_node') { if (index < offset) offset--; } else if (index < offset) { offset--; } else if (index === offset) { return null; } var mapped = mapPath(path, by); return mapped && { path: mapped, offset: offset }; } } if (by.type === 'move_node') { var from = isChildOf(by.path, path); var to = slate.Path.transform(by.path, by); var into = to !== null && isChildOf(to, path); if (from || into) return null; } var point = slate.Point.transform({ path: path, offset: offset }, by, { affinity: side === 'after' ? 'forward' : 'backward' }); return point && { path: point.path, offset: point.offset }; }; var transformRemoveText = function transformRemoveText(op, by) { var end = op.offset + op.text.length; if (by.type !== 'set_selection' && slate.Path.equals(by.path, op.path)) { if (by.type === 'insert_text' && by.offset > op.offset && by.offset < end) { var split = by.offset - op.offset; return [_objectSpread(_objectSpread({}, op), {}, { text: op.text.slice(0, split) }), _objectSpread(_objectSpread({}, op), {}, { offset: op.offset + by.text.length, text: op.text.slice(split) })]; } if (by.type === 'remove_text') { var removedStart = by.offset; var removedEnd = by.offset + by.text.length; var text = ''; for (var i = op.offset; i < end; i++) { if (i < removedStart || i >= removedEnd) text += op.text[i - op.offset]; } if (!text) return []; var offset = op.offset - Math.max(0, Math.min(op.offset, removedEnd) - removedStart); return [_objectSpread(_objectSpread({}, op), {}, { offset: offset, text: text })]; } if (by.type === 'split_node' && by.position > op.offset && by.position < end) { var _split = by.position - op.offset; return [_objectSpread(_objectSpread({}, op), {}, { text: op.text.slice(0, _split) }), _objectSpread(_objectSpread({}, op), {}, { path: slate.Path.next(op.path), offset: 0, text: op.text.slice(_split) })]; } } var start = mapOffset({ path: op.path, offset: op.offset }, by, 'after'); return start ? [_objectSpread(_objectSpread({}, op), {}, { path: start.path, offset: start.offset })] : []; }; var unchanged = function unchanged(op, next) { var a = op; var b = next; return a.text === b.text && a.offset === b.offset && a.position === b.position && (a.path === b.path || slate.Path.equals(a.path, b.path)) && (a.newPath === b.newPath || slate.Path.equals(a.newPath, b.newPath)); }; var transformOperation = function transformOperation(op, by, side) { var result = transform(op, by, side); if ((result === null || result === void 0 ? void 0 : result.length) === 1 && unchanged(op, result[0])) return [op]; return result; }; var transform = function transform(op, by, side) { switch (op.type) { case 'set_selection': return [op]; case 'set_node': case 'remove_node': { var path = mapPath(op.path, by); return path ? [_objectSpread(_objectSpread({}, op), {}, { path: path })] : []; } case 'insert_node': { if (by.type === 'insert_node' && slate.Path.equals(by.path, op.path) && side === 'before') { return [op]; } var _path = mapSlot(op.path, by); return _path ? [_objectSpread(_objectSpread({}, op), {}, { path: _path })] : []; } case 'move_node': { var _path2 = mapPath(op.path, by); if (!_path2) return []; var newPath = mapSlot(op.newPath, by); return newPath ? [_objectSpread(_objectSpread({}, op), {}, { path: _path2, newPath: newPath })] : null; } case 'insert_text': { var at = mapOffset({ path: op.path, offset: op.offset }, by, side); return at ? [_objectSpread(_objectSpread({}, op), {}, { path: at.path, offset: at.offset })] : []; } case 'remove_text': return transformRemoveText(op, by); case 'split_node': { if (!mapPath(op.path, by)) return []; var _at = mapOffset({ path: op.path, offset: op.position }, by, 'before'); return _at ? [_objectSpread(_objectSpread({}, op), {}, { path: _at.path, position: _at.offset })] : null; } case 'merge_node': { var _path3 = mapPath(op.path, by); if (!_path3) return null; var previous = mapOffset({ path: slate.Path.previous(op.path), offset: op.position }, by, 'after'); if (!previous || !slate.Path.equals(previous.path, slate.Path.previous(_path3))) { return null; } return [_objectSpread(_objectSpread({}, op), {}, { path: _path3, position: previous.offset })]; } } }; var opposite = function opposite(side) { return side === 'after' ? 'before' : 'after'; }; var _transformOperations = function transformOperations(ops, by, side) { if (ops.length === 0 || by.length === 0) return [ops, by]; if (ops.length > 1) { var first = _transformOperations([ops[0]], by, side); if (!first) return null; var rest = _transformOperations(ops.slice(1), first[1], side); if (!rest) return null; return [first[0].concat(rest[0]), rest[1]]; } if (by.length > 1) { var _first = _transformOperations(ops, [by[0]], side); if (!_first) return null; var _rest = _transformOperations(_first[0], by.slice(1), side); if (!_rest) return null; return [_rest[0], _first[1].concat(_rest[1])]; } var op = transformOperation(ops[0], by[0], side); var other = transformOperation(by[0], ops[0], opposite(side)); return op && other ? [op, other] : null; }; var transformRange = function transformRange(range, ops) { var current = range; var _iterator = _createForOfIteratorHelper$1(ops), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var op = _step.value; if (!current) return null; current = slate.Range.transform(current, op, { affinity: 'inward' }); } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } return current; }; var same = function same(a, b) { return a.length === b.length && a.every(function (op, index) { return op === b[index]; }); }; var rebaseRedos = function rebaseRedos(redos, op) { var unsaved = [op]; for (var i = redos.length - 1; i >= 0 && unsaved.length; i--) { var batch = redos[i]; var result = _transformOperations(batch.operations, unsaved, 'after'); if (!result || !result[0].length) { redos.splice(0, i + 1); return; } batch.selectionBefore = transformRange(batch.selectionBefore, unsaved); if (!same(result[0], batch.operations)) batch.operations = result[0]; unsaved = result[1]; } }; var rebaseUndos = function rebaseUndos(undos, op) { var unsaved = [op]; for (var i = undos.length - 1; i >= 0 && unsaved.length; i--) { var batch = undos[i]; var inverse = batch.operations.map(slate.Operation.inverse).reverse(); var result = _transformOperations(inverse, unsaved, 'after'); if (!result) { unsaved = batch.operations.concat(unsaved); undos.splice(i, 1); continue; } unsaved = result[1]; if (!result[0].length) { undos.splice(i, 1); continue; } if (!same(result[0], inverse)) { batch.operations = result[0].map(slate.Operation.inverse).reverse(); } batch.selectionBefore = transformRange(batch.selectionBefore, unsaved); } }; var rebaseHistory = function rebaseHistory(history, op) { rebaseRedos(history.redos, op); rebaseUndos(history.undos, op); }; function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: true } : { done: false, value: r[_n++] }; }, e: function e(r) { throw r; }, 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 o, a = true, u = false; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = true, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } /** * The `withHistory` plugin keeps track of the operation history of a Slate * editor as operations are applied to it, using undo and redo stacks. * * If you are using TypeScript, you must extend Slate's CustomTypes to use * this plugin. * * See https://docs.slatejs.org/concepts/11-typescript to learn how. */ var withHistory = function withHistory(editor) { var e = editor; var apply = e.apply, normalize = e.normalize, setChildren = e.setChildren; var isSettled = true; var isReplaying = false; e.history = { undos: [], redos: [] }; e.redo = function () { var history = e.history; var redos = history.redos; if (redos.length > 0) { var batch = redos[redos.length - 1]; if (batch.selectionBefore) { slate.Transforms.select(e, batch.selectionBefore); } isReplaying = true; try { HistoryEditor.withoutSaving(e, function () { slate.Editor.withoutNormalizing(e, function () { var _iterator = _createForOfIteratorHelper(batch.operations), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var op = _step.value; e.apply(op); } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } }); }); } finally { isReplaying = false; } history.redos.pop(); e.writeHistory('undos', batch); } }; e.undo = function () { var history = e.history; var undos = history.undos; if (undos.length > 0) { var batch = undos[undos.length - 1]; isReplaying = true; try { HistoryEditor.withoutSaving(e, function () { slate.Editor.withoutNormalizing(e, function () { var inverseOps = batch.operations.map(slate.Operation.inverse).reverse(); var _iterator2 = _createForOfIteratorHelper(inverseOps), _step2; try { for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { var op = _step2.value; e.apply(op); } } catch (err) { _iterator2.e(err); } finally { _iterator2.f(); } if (batch.selectionBefore) { slate.Transforms.select(e, batch.selectionBefore); } }); }); } finally { isReplaying = false; } e.writeHistory('redos', batch); history.undos.pop(); } }; e.apply = function (op) { var operations = e.operations, history = e.history; var undos = history.undos; var lastBatch = undos[undos.length - 1]; var lastOp = lastBatch && lastBatch.operations[lastBatch.operations.length - 1]; var save = HistoryEditor.isSaving(e); var merge = HistoryEditor.isMerging(e); if (save == null) { save = shouldSave(op); } if (save) { if (merge == null) { if (lastBatch == null) { merge = false; } else if (operations.includes(lastOp)) { merge = true; } else { merge = shouldMerge(op, lastOp); } } if (!merge && !isSettled && lastBatch && operations.includes(lastOp)) { merge = true; } if (HistoryEditor.isSplittingOnce(e)) { merge = false; HistoryEditor.setSplittingOnce(e, undefined); } if (lastBatch && merge) { lastBatch.operations.push(op); } else { var batch = { operations: [op], selectionBefore: e.selection }; e.writeHistory('undos', batch); } while (undos.length > 100) { undos.shift(); } history.redos = []; } else if (!isReplaying && op.type !== 'set_selection') { rebaseHistory(history, op); } isSettled = false; apply(op); }; e.normalize = function (options) { normalize(options); if (slate.Editor.isNormalizing(e)) { isSettled = true; } }; e.setChildren = function (children) { var previous = e.history.undos[e.history.undos.length - 1]; var count = previous === null || previous === void 0 ? void 0 : previous.operations.length; HistoryEditor.withNewBatch(e, function () { return setChildren(children); }); var current = e.history.undos[e.history.undos.length - 1]; if (current !== previous || (current === null || current === void 0 ? void 0 : current.operations.length) !== count) { HistoryEditor.setSplittingOnce(e, true); } }; e.writeHistory = function (stack, batch) { e.history[stack].push(batch); }; return e; }; /** * Check whether to merge an operation into the previous operation. */ var shouldMerge = function shouldMerge(op, prev) { if (prev && op.type === 'insert_text' && prev.type === 'insert_text' && op.offset === prev.offset + prev.text.length && slate.Path.equals(op.path, prev.path)) { return true; } if (prev && op.type === 'remove_text' && prev.type === 'remove_text' && op.offset + op.text.length === prev.offset && slate.Path.equals(op.path, prev.path)) { return true; } return false; }; /** * Check whether an operation needs to be saved to the history. */ var shouldSave = function shouldSave(op, prev) { if (op.type === 'set_selection') { return false; } return true; }; exports.HISTORY = HISTORY; exports.History = History; exports.HistoryEditor = HistoryEditor; exports.MERGING = MERGING; exports.SAVING = SAVING; exports.SPLITTING_ONCE = SPLITTING_ONCE; exports.withHistory = withHistory; //# sourceMappingURL=index.js.map