123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478 |
- "use strict";
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.AttributeMap = exports.OpIterator = exports.Op = void 0;
- const diff = require("fast-diff");
- const cloneDeep = require("lodash.clonedeep");
- const isEqual = require("lodash.isequal");
- const AttributeMap_1 = require("./AttributeMap");
- exports.AttributeMap = AttributeMap_1.default;
- const Op_1 = require("./Op");
- exports.Op = Op_1.default;
- const OpIterator_1 = require("./OpIterator");
- exports.OpIterator = OpIterator_1.default;
- const NULL_CHARACTER = String.fromCharCode(0); // Placeholder char for embed in diff()
- const getEmbedTypeAndData = (a, b) => {
- if (typeof a !== 'object' || a === null) {
- throw new Error(`cannot retain a ${typeof a}`);
- }
- if (typeof b !== 'object' || b === null) {
- throw new Error(`cannot retain a ${typeof b}`);
- }
- const embedType = Object.keys(a)[0];
- if (!embedType || embedType !== Object.keys(b)[0]) {
- throw new Error(`embed types not matched: ${embedType} != ${Object.keys(b)[0]}`);
- }
- return [embedType, a[embedType], b[embedType]];
- };
- class Delta {
- constructor(ops) {
- // Assume we are given a well formed ops
- if (Array.isArray(ops)) {
- this.ops = ops;
- }
- else if (ops != null && Array.isArray(ops.ops)) {
- this.ops = ops.ops;
- }
- else {
- this.ops = [];
- }
- }
- static registerEmbed(embedType, handler) {
- this.handlers[embedType] = handler;
- }
- static unregisterEmbed(embedType) {
- delete this.handlers[embedType];
- }
- static getHandler(embedType) {
- const handler = this.handlers[embedType];
- if (!handler) {
- throw new Error(`no handlers for embed type "${embedType}"`);
- }
- return handler;
- }
- insert(arg, attributes) {
- const newOp = {};
- if (typeof arg === 'string' && arg.length === 0) {
- return this;
- }
- newOp.insert = arg;
- if (attributes != null &&
- typeof attributes === 'object' &&
- Object.keys(attributes).length > 0) {
- newOp.attributes = attributes;
- }
- return this.push(newOp);
- }
- delete(length) {
- if (length <= 0) {
- return this;
- }
- return this.push({ delete: length });
- }
- retain(length, attributes) {
- if (typeof length === 'number' && length <= 0) {
- return this;
- }
- const newOp = { retain: length };
- if (attributes != null &&
- typeof attributes === 'object' &&
- Object.keys(attributes).length > 0) {
- newOp.attributes = attributes;
- }
- return this.push(newOp);
- }
- push(newOp) {
- let index = this.ops.length;
- let lastOp = this.ops[index - 1];
- newOp = cloneDeep(newOp);
- if (typeof lastOp === 'object') {
- if (typeof newOp.delete === 'number' &&
- typeof lastOp.delete === 'number') {
- this.ops[index - 1] = { delete: lastOp.delete + newOp.delete };
- return this;
- }
- // Since it does not matter if we insert before or after deleting at the same index,
- // always prefer to insert first
- if (typeof lastOp.delete === 'number' && newOp.insert != null) {
- index -= 1;
- lastOp = this.ops[index - 1];
- if (typeof lastOp !== 'object') {
- this.ops.unshift(newOp);
- return this;
- }
- }
- if (isEqual(newOp.attributes, lastOp.attributes)) {
- if (typeof newOp.insert === 'string' &&
- typeof lastOp.insert === 'string') {
- this.ops[index - 1] = { insert: lastOp.insert + newOp.insert };
- if (typeof newOp.attributes === 'object') {
- this.ops[index - 1].attributes = newOp.attributes;
- }
- return this;
- }
- else if (typeof newOp.retain === 'number' &&
- typeof lastOp.retain === 'number') {
- this.ops[index - 1] = { retain: lastOp.retain + newOp.retain };
- if (typeof newOp.attributes === 'object') {
- this.ops[index - 1].attributes = newOp.attributes;
- }
- return this;
- }
- }
- }
- if (index === this.ops.length) {
- this.ops.push(newOp);
- }
- else {
- this.ops.splice(index, 0, newOp);
- }
- return this;
- }
- chop() {
- const lastOp = this.ops[this.ops.length - 1];
- if (lastOp && typeof lastOp.retain === 'number' && !lastOp.attributes) {
- this.ops.pop();
- }
- return this;
- }
- filter(predicate) {
- return this.ops.filter(predicate);
- }
- forEach(predicate) {
- this.ops.forEach(predicate);
- }
- map(predicate) {
- return this.ops.map(predicate);
- }
- partition(predicate) {
- const passed = [];
- const failed = [];
- this.forEach((op) => {
- const target = predicate(op) ? passed : failed;
- target.push(op);
- });
- return [passed, failed];
- }
- reduce(predicate, initialValue) {
- return this.ops.reduce(predicate, initialValue);
- }
- changeLength() {
- return this.reduce((length, elem) => {
- if (elem.insert) {
- return length + Op_1.default.length(elem);
- }
- else if (elem.delete) {
- return length - elem.delete;
- }
- return length;
- }, 0);
- }
- length() {
- return this.reduce((length, elem) => {
- return length + Op_1.default.length(elem);
- }, 0);
- }
- slice(start = 0, end = Infinity) {
- const ops = [];
- const iter = new OpIterator_1.default(this.ops);
- let index = 0;
- while (index < end && iter.hasNext()) {
- let nextOp;
- if (index < start) {
- nextOp = iter.next(start - index);
- }
- else {
- nextOp = iter.next(end - index);
- ops.push(nextOp);
- }
- index += Op_1.default.length(nextOp);
- }
- return new Delta(ops);
- }
- compose(other) {
- const thisIter = new OpIterator_1.default(this.ops);
- const otherIter = new OpIterator_1.default(other.ops);
- const ops = [];
- const firstOther = otherIter.peek();
- if (firstOther != null &&
- typeof firstOther.retain === 'number' &&
- firstOther.attributes == null) {
- let firstLeft = firstOther.retain;
- while (thisIter.peekType() === 'insert' &&
- thisIter.peekLength() <= firstLeft) {
- firstLeft -= thisIter.peekLength();
- ops.push(thisIter.next());
- }
- if (firstOther.retain - firstLeft > 0) {
- otherIter.next(firstOther.retain - firstLeft);
- }
- }
- const delta = new Delta(ops);
- while (thisIter.hasNext() || otherIter.hasNext()) {
- if (otherIter.peekType() === 'insert') {
- delta.push(otherIter.next());
- }
- else if (thisIter.peekType() === 'delete') {
- delta.push(thisIter.next());
- }
- else {
- const length = Math.min(thisIter.peekLength(), otherIter.peekLength());
- const thisOp = thisIter.next(length);
- const otherOp = otherIter.next(length);
- if (otherOp.retain) {
- const newOp = {};
- if (typeof thisOp.retain === 'number') {
- newOp.retain =
- typeof otherOp.retain === 'number' ? length : otherOp.retain;
- }
- else {
- if (typeof otherOp.retain === 'number') {
- if (thisOp.retain == null) {
- newOp.insert = thisOp.insert;
- }
- else {
- newOp.retain = thisOp.retain;
- }
- }
- else {
- const action = thisOp.retain == null ? 'insert' : 'retain';
- const [embedType, thisData, otherData] = getEmbedTypeAndData(thisOp[action], otherOp.retain);
- const handler = Delta.getHandler(embedType);
- newOp[action] = {
- [embedType]: handler.compose(thisData, otherData, action === 'retain'),
- };
- }
- }
- // Preserve null when composing with a retain, otherwise remove it for inserts
- const attributes = AttributeMap_1.default.compose(thisOp.attributes, otherOp.attributes, typeof thisOp.retain === 'number');
- if (attributes) {
- newOp.attributes = attributes;
- }
- delta.push(newOp);
- // Optimization if rest of other is just retain
- if (!otherIter.hasNext() &&
- isEqual(delta.ops[delta.ops.length - 1], newOp)) {
- const rest = new Delta(thisIter.rest());
- return delta.concat(rest).chop();
- }
- // Other op should be delete, we could be an insert or retain
- // Insert + delete cancels out
- }
- else if (typeof otherOp.delete === 'number' &&
- (typeof thisOp.retain === 'number' ||
- (typeof thisOp.retain === 'object' && thisOp.retain !== null))) {
- delta.push(otherOp);
- }
- }
- }
- return delta.chop();
- }
- concat(other) {
- const delta = new Delta(this.ops.slice());
- if (other.ops.length > 0) {
- delta.push(other.ops[0]);
- delta.ops = delta.ops.concat(other.ops.slice(1));
- }
- return delta;
- }
- diff(other, cursor) {
- if (this.ops === other.ops) {
- return new Delta();
- }
- const strings = [this, other].map((delta) => {
- return delta
- .map((op) => {
- if (op.insert != null) {
- return typeof op.insert === 'string' ? op.insert : NULL_CHARACTER;
- }
- const prep = delta === other ? 'on' : 'with';
- throw new Error('diff() called ' + prep + ' non-document');
- })
- .join('');
- });
- const retDelta = new Delta();
- const diffResult = diff(strings[0], strings[1], cursor, true);
- const thisIter = new OpIterator_1.default(this.ops);
- const otherIter = new OpIterator_1.default(other.ops);
- diffResult.forEach((component) => {
- let length = component[1].length;
- while (length > 0) {
- let opLength = 0;
- switch (component[0]) {
- case diff.INSERT:
- opLength = Math.min(otherIter.peekLength(), length);
- retDelta.push(otherIter.next(opLength));
- break;
- case diff.DELETE:
- opLength = Math.min(length, thisIter.peekLength());
- thisIter.next(opLength);
- retDelta.delete(opLength);
- break;
- case diff.EQUAL:
- opLength = Math.min(thisIter.peekLength(), otherIter.peekLength(), length);
- const thisOp = thisIter.next(opLength);
- const otherOp = otherIter.next(opLength);
- if (isEqual(thisOp.insert, otherOp.insert)) {
- retDelta.retain(opLength, AttributeMap_1.default.diff(thisOp.attributes, otherOp.attributes));
- }
- else {
- retDelta.push(otherOp).delete(opLength);
- }
- break;
- }
- length -= opLength;
- }
- });
- return retDelta.chop();
- }
- eachLine(predicate, newline = '\n') {
- const iter = new OpIterator_1.default(this.ops);
- let line = new Delta();
- let i = 0;
- while (iter.hasNext()) {
- if (iter.peekType() !== 'insert') {
- return;
- }
- const thisOp = iter.peek();
- const start = Op_1.default.length(thisOp) - iter.peekLength();
- const index = typeof thisOp.insert === 'string'
- ? thisOp.insert.indexOf(newline, start) - start
- : -1;
- if (index < 0) {
- line.push(iter.next());
- }
- else if (index > 0) {
- line.push(iter.next(index));
- }
- else {
- if (predicate(line, iter.next(1).attributes || {}, i) === false) {
- return;
- }
- i += 1;
- line = new Delta();
- }
- }
- if (line.length() > 0) {
- predicate(line, {}, i);
- }
- }
- invert(base) {
- const inverted = new Delta();
- this.reduce((baseIndex, op) => {
- if (op.insert) {
- inverted.delete(Op_1.default.length(op));
- }
- else if (typeof op.retain === 'number' && op.attributes == null) {
- inverted.retain(op.retain);
- return baseIndex + op.retain;
- }
- else if (op.delete || typeof op.retain === 'number') {
- const length = (op.delete || op.retain);
- const slice = base.slice(baseIndex, baseIndex + length);
- slice.forEach((baseOp) => {
- if (op.delete) {
- inverted.push(baseOp);
- }
- else if (op.retain && op.attributes) {
- inverted.retain(Op_1.default.length(baseOp), AttributeMap_1.default.invert(op.attributes, baseOp.attributes));
- }
- });
- return baseIndex + length;
- }
- else if (typeof op.retain === 'object' && op.retain !== null) {
- const slice = base.slice(baseIndex, baseIndex + 1);
- const baseOp = new OpIterator_1.default(slice.ops).next();
- const [embedType, opData, baseOpData] = getEmbedTypeAndData(op.retain, baseOp.insert);
- const handler = Delta.getHandler(embedType);
- inverted.retain({ [embedType]: handler.invert(opData, baseOpData) }, AttributeMap_1.default.invert(op.attributes, baseOp.attributes));
- return baseIndex + 1;
- }
- return baseIndex;
- }, 0);
- return inverted.chop();
- }
- transform(arg, priority = false) {
- priority = !!priority;
- if (typeof arg === 'number') {
- return this.transformPosition(arg, priority);
- }
- const other = arg;
- const thisIter = new OpIterator_1.default(this.ops);
- const otherIter = new OpIterator_1.default(other.ops);
- const delta = new Delta();
- while (thisIter.hasNext() || otherIter.hasNext()) {
- if (thisIter.peekType() === 'insert' &&
- (priority || otherIter.peekType() !== 'insert')) {
- delta.retain(Op_1.default.length(thisIter.next()));
- }
- else if (otherIter.peekType() === 'insert') {
- delta.push(otherIter.next());
- }
- else {
- const length = Math.min(thisIter.peekLength(), otherIter.peekLength());
- const thisOp = thisIter.next(length);
- const otherOp = otherIter.next(length);
- if (thisOp.delete) {
- // Our delete either makes their delete redundant or removes their retain
- continue;
- }
- else if (otherOp.delete) {
- delta.push(otherOp);
- }
- else {
- const thisData = thisOp.retain;
- const otherData = otherOp.retain;
- let transformedData = typeof otherData === 'object' && otherData !== null
- ? otherData
- : length;
- if (typeof thisData === 'object' &&
- thisData !== null &&
- typeof otherData === 'object' &&
- otherData !== null) {
- const embedType = Object.keys(thisData)[0];
- if (embedType === Object.keys(otherData)[0]) {
- const handler = Delta.getHandler(embedType);
- if (handler) {
- transformedData = {
- [embedType]: handler.transform(thisData[embedType], otherData[embedType], priority),
- };
- }
- }
- }
- // We retain either their retain or insert
- delta.retain(transformedData, AttributeMap_1.default.transform(thisOp.attributes, otherOp.attributes, priority));
- }
- }
- }
- return delta.chop();
- }
- transformPosition(index, priority = false) {
- priority = !!priority;
- const thisIter = new OpIterator_1.default(this.ops);
- let offset = 0;
- while (thisIter.hasNext() && offset <= index) {
- const length = thisIter.peekLength();
- const nextType = thisIter.peekType();
- thisIter.next();
- if (nextType === 'delete') {
- index -= Math.min(length, index - offset);
- continue;
- }
- else if (nextType === 'insert' && (offset < index || !priority)) {
- index += length;
- }
- offset += length;
- }
- return index;
- }
- }
- Delta.Op = Op_1.default;
- Delta.OpIterator = OpIterator_1.default;
- Delta.AttributeMap = AttributeMap_1.default;
- Delta.handlers = {};
- exports.default = Delta;
- if (typeof module === 'object') {
- module.exports = Delta;
- module.exports.default = Delta;
- }
- //# sourceMappingURL=Delta.js.map
|