UNPKG

@atlaskit/editor-plugin-show-diff

Version:

ShowDiff plugin for @atlaskit/editor-core

324 lines (319 loc) • 13.1 kB
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray"; 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; } import { areNodesEqualIgnoreAttrs } from '@atlaskit/editor-common/utils/document'; import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model'; import { TableMap } from '@atlaskit/editor-tables/table-map'; export var 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 Slice(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.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 && 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.get(before.node); var afterMap = 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.get(before.node).width <= 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(_toConsumableArray(change.deleted), _toConsumableArray(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. */ export var 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 _toConsumableArray(changes); } return [].concat(_toConsumableArray(changes.filter(function (change) { return !replaced.has(change); })), _toConsumableArray(plans.map(function (plan) { return plan.tableChange; }))).sort(function (left, right) { return left.fromB - right.fromB; }); };