@atlaskit/editor-plugin-show-diff
Version:
ShowDiff plugin for @atlaskit/editor-core
331 lines (325 loc) • 13.6 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.groupDeletedColumnChanges = exports.CELL_CONTENT_OFFSET = void 0;
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _document = require("@atlaskit/editor-common/utils/document");
var _model = require("@atlaskit/editor-prosemirror/model");
var _tableMap = require("@atlaskit/editor-tables/table-map");
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, 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 = !0, u = !1; 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 = !0, 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; }
var CELL_CONTENT_OFFSET = exports.CELL_CONTENT_OFFSET = 2;
var tablesIn = function tablesIn(doc) {
var tables = [];
doc.descendants(function (node, pos) {
if (node.type.name === 'table') {
tables.push({
node: node,
pos: pos
});
return false;
}
return true;
});
return tables;
};
var tableEnd = function tableEnd(table) {
return table.pos + table.node.nodeSize;
};
var documentsDifferOnlyByTable = function documentsDifferOnlyByTable(originalDoc, newDoc, before, after) {
try {
return originalDoc.replace(before.pos, tableEnd(before), new _model.Slice(_model.Fragment.from(after.node), 0, 0)).eq(newDoc);
} catch (_unused) {
return false;
}
};
var columnOffsets = function columnOffsets(map, table) {
return Array.from({
length: map.width
}, function (_, column) {
return map.positionAt(0, column, table);
});
};
var positionMatchedColumns = function positionMatchedColumns(_ref) {
var before = _ref.before,
after = _ref.after,
afterToBefore = _ref.afterToBefore,
beforeOffsets = _ref.beforeOffsets;
var afterMap = _tableMap.TableMap.get(after.node);
var surviving = new Set();
for (var column = 0; column < afterMap.width; column++) {
var offset = afterMap.positionAt(0, column, after.node);
var mapped = afterToBefore.mapResult(after.pos + CELL_CONTENT_OFFSET + offset);
var beforeColumn = beforeOffsets.indexOf(mapped.pos - before.pos - CELL_CONTENT_OFFSET);
if (mapped.deleted || beforeColumn === -1) {
return new Set();
}
surviving.add(beforeColumn);
}
return surviving;
};
var headerLocalIds = function headerLocalIds(table) {
var _table$node$firstChil, _table$node$firstChil2;
return ((_table$node$firstChil = (_table$node$firstChil2 = table.node.firstChild) === null || _table$node$firstChil2 === void 0 ? void 0 : _table$node$firstChil2.children) !== null && _table$node$firstChil !== void 0 ? _table$node$firstChil : []).map(function (cell) {
var _cell$attrs$localId;
return (_cell$attrs$localId = cell.attrs.localId) !== null && _cell$attrs$localId !== void 0 ? _cell$attrs$localId : undefined;
});
};
/**
* Cells keep their `localId` when a column is deleted, so the first row's ids name each remaining
* column even when the steps replaced the whole table. Missing or repeated ids, such as in content
* pasted from elsewhere, cannot name a column.
*/
var localIdMatchedColumns = function localIdMatchedColumns(before, after) {
var beforeIds = headerLocalIds(before);
if (beforeIds.some(function (id) {
return !id;
}) || new Set(beforeIds).size !== beforeIds.length) {
return undefined;
}
var surviving = new Set();
var previousColumn = -1;
var _iterator = _createForOfIteratorHelper(headerLocalIds(after)),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var id = _step.value;
var column = id ? beforeIds.indexOf(id) : -1;
if (column <= previousColumn) {
return undefined;
}
surviving.add(column);
previousColumn = column;
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
return surviving;
};
var IGNORED_CELL_ATTRS = ['localId', 'colwidth'];
/** Rows may also have been deleted, so the remaining cells only need to appear in order. */
var isColumnKept = function isColumnKept(before, beforeColumn, after, afterColumn) {
var beforeRow = 0;
return after.node.children.every(function (afterRow) {
var afterCell = afterRow.maybeChild(afterColumn);
while (beforeRow < before.node.childCount) {
var beforeCell = before.node.child(beforeRow++).maybeChild(beforeColumn);
if (afterCell && beforeCell && (0, _document.areNodesEqualIgnoreAttrs)(beforeCell, afterCell, IGNORED_CELL_ATTRS)) {
return true;
}
}
return false;
});
};
/**
* Pairs each remaining column with an equal original column, keeping their order, and takes each
* pair from the `direction` end first. Returns the original column of each remaining column.
*/
var pairColumnsInOrder = function pairColumnsInOrder(beforeWidth, afterWidth, isMatch, direction) {
var step = direction === 'left' ? 1 : -1;
var pairing = [];
var beforeColumn = direction === 'left' ? 0 : beforeWidth - 1;
var afterColumn = direction === 'left' ? 0 : afterWidth - 1;
for (; afterColumn >= 0 && afterColumn < afterWidth; afterColumn += step) {
while (beforeColumn >= 0 && beforeColumn < beforeWidth && !isMatch(beforeColumn, afterColumn)) {
beforeColumn += step;
}
if (beforeColumn < 0 || beforeColumn >= beforeWidth) {
return undefined;
}
pairing.push(beforeColumn);
beforeColumn += step;
}
return direction === 'left' ? pairing : pairing.reverse();
};
/**
* For tables whose cells have no usable ids, such as content created before cells had ids. Every
* valid pairing of remaining columns to original ones lies between the leftmost and the rightmost
* pairing, so the columns are only reported when those two agree. Otherwise, such as when several
* original columns are identical, which of them were removed cannot be known.
*/
var uniquelyContentMatchedColumns = function uniquelyContentMatchedColumns(before, after, beforeWidth, afterWidth) {
var isMatch = function isMatch(beforeColumn, afterColumn) {
return isColumnKept(before, beforeColumn, after, afterColumn);
};
var leftmost = pairColumnsInOrder(beforeWidth, afterWidth, isMatch, 'left');
var rightmost = pairColumnsInOrder(beforeWidth, afterWidth, isMatch, 'right');
if (!leftmost || !rightmost || leftmost.some(function (column, index) {
return column !== rightmost[index];
})) {
return undefined;
}
return new Set(leftmost);
};
var findDeletedColumns = function findDeletedColumns(before, after, afterToBefore) {
var _localIdMatchedColumn;
var beforeMap = _tableMap.TableMap.get(before.node);
var afterMap = _tableMap.TableMap.get(after.node);
// Merged or malformed tables do not have reliable column indices.
if (beforeMap.hasMergedCells() || afterMap.hasMergedCells() || beforeMap.problems || afterMap.problems) {
return [];
}
var beforeOffsets = columnOffsets(beforeMap, before.node);
var positionMatched = positionMatchedColumns({
before: before,
after: after,
afterToBefore: afterToBefore,
beforeOffsets: beforeOffsets
});
var surviving = positionMatched.size === afterMap.width ? positionMatched : (_localIdMatchedColumn = localIdMatchedColumns(before, after)) !== null && _localIdMatchedColumn !== void 0 ? _localIdMatchedColumn : uniquelyContentMatchedColumns(before, after, beforeMap.width, afterMap.width);
if (!surviving) {
return [];
}
return beforeOffsets.flatMap(function (_, column) {
return surviving.has(column) ? [] : [column];
});
};
var findCounterpart = function findCounterpart(_ref2) {
var table = _ref2.table,
index = _ref2.index,
originalTables = _ref2.originalTables,
newTables = _ref2.newTables,
mapping = _ref2.mapping;
var from = mapping.map(table.pos, -1);
var to = mapping.map(tableEnd(table), 1);
var inside = mapping.mapResult(table.pos + 1);
var candidates = newTables.filter(function (candidate) {
return inside.deleted ? candidate.pos >= from && tableEnd(candidate) <= to : candidate.pos < inside.pos && inside.pos < tableEnd(candidate);
});
if (candidates.length === 1) {
return candidates[0];
}
return originalTables.length === newTables.length ? newTables[index] : undefined;
};
var orient = function orient(_ref3) {
var table = _ref3.table,
counterpart = _ref3.counterpart,
mapping = _ref3.mapping,
isInverted = _ref3.isInverted;
return {
before: isInverted ? counterpart : table,
after: isInverted ? table : counterpart,
afterToBefore: (isInverted ? mapping.invert() : mapping).invert()
};
};
var changesInTable = function changesInTable(changes, table) {
return changes.filter(function (change) {
return change.fromA < tableEnd(table) && change.toA > table.pos;
});
};
var isNarrowingSafe = function isNarrowingSafe(_ref4) {
var replacedChanges = _ref4.replacedChanges,
table = _ref4.table,
counterpart = _ref4.counterpart,
originalDoc = _ref4.originalDoc,
newDoc = _ref4.newDoc;
var staysInside = replacedChanges.every(function (change) {
return change.fromA >= table.pos && change.toA <= tableEnd(table) && change.fromB >= counterpart.pos && change.toB <= tableEnd(counterpart);
});
return staysInside || documentsDifferOnlyByTable(originalDoc, newDoc, table, counterpart);
};
var planTableChange = function planTableChange(_ref5) {
var table = _ref5.table,
index = _ref5.index,
originalTables = _ref5.originalTables,
newTables = _ref5.newTables,
changes = _ref5.changes,
originalDoc = _ref5.originalDoc,
newDoc = _ref5.newDoc,
mapping = _ref5.mapping,
isInverted = _ref5.isInverted;
var counterpart = findCounterpart({
table: table,
index: index,
originalTables: originalTables,
newTables: newTables,
mapping: mapping
});
if (!counterpart) {
return undefined;
}
var _orient = orient({
table: table,
counterpart: counterpart,
mapping: mapping,
isInverted: isInverted
}),
before = _orient.before,
after = _orient.after,
afterToBefore = _orient.afterToBefore;
if (_tableMap.TableMap.get(before.node).width <= _tableMap.TableMap.get(after.node).width) {
return undefined;
}
var replacedChanges = changesInTable(changes, table);
if (!replacedChanges.length) {
return undefined;
}
if (!isNarrowingSafe({
replacedChanges: replacedChanges,
table: table,
counterpart: counterpart,
originalDoc: originalDoc,
newDoc: newDoc
})) {
return undefined;
}
var spans = replacedChanges.flatMap(function (change) {
return [].concat((0, _toConsumableArray2.default)(change.deleted), (0, _toConsumableArray2.default)(change.inserted));
});
return {
replacedChanges: replacedChanges,
tableChange: {
deletedColumns: findDeletedColumns(before, after, afterToBefore),
fromA: table.pos,
toA: tableEnd(table),
fromB: counterpart.pos,
toB: tableEnd(counterpart),
deleted: spans,
inserted: spans
}
};
};
/** Groups a deleted column's cell changes into one whole-table change. */
var groupDeletedColumnChanges = exports.groupDeletedColumnChanges = function groupDeletedColumnChanges(changes, originalDoc, newDoc, mapping, isInverted) {
var originalTables = tablesIn(originalDoc);
var newTables = tablesIn(newDoc);
var plans = [];
var replaced = new Set();
originalTables.forEach(function (table, index) {
var plan = planTableChange({
table: table,
index: index,
originalTables: originalTables,
newTables: newTables,
changes: changes.filter(function (change) {
return !replaced.has(change);
}),
originalDoc: originalDoc,
newDoc: newDoc,
mapping: mapping,
isInverted: isInverted
});
if (!plan) {
return;
}
plans.push(plan);
plan.replacedChanges.forEach(function (change) {
return replaced.add(change);
});
});
if (!plans.length) {
return (0, _toConsumableArray2.default)(changes);
}
return [].concat((0, _toConsumableArray2.default)(changes.filter(function (change) {
return !replaced.has(change);
})), (0, _toConsumableArray2.default)(plans.map(function (plan) {
return plan.tableChange;
}))).sort(function (left, right) {
return left.fromB - right.fromB;
});
};