slate-history
Version:
An operation-based history implementation for Slate editors.
670 lines (651 loc) • 25.6 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('slate')) :
typeof define === 'function' && define.amd ? define(['exports', 'slate'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.SlateHistory = {}, global.Slate));
})(this, (function (exports, slate) { 'use strict';
// 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;
}));