/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol */
var __assign = function() {
__assign = Object.assign || function __assign(t) {
var arguments$1 = arguments;
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments$1[i];
for (var p in s) { if (Object.prototype.hasOwnProperty.call(s, p)) { t[p] = s[p]; } }
}
return t;
};
return __assign.apply(this, arguments);
};
function __spreadArray(to, from, pack) {
if (pack || arguments.length === 2) { for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) { ar = Array.prototype.slice.call(from, 0, i); }
ar[i] = from[i];
}
} }
return to.concat(ar || Array.prototype.slice.call(from));
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
var Diff = /** @class */ (function () {
function Diff(options) {
if (options === void 0) { options = {}; }
var _this = this;
Object.entries(options).forEach(function (_a) {
var key = _a[0], value = _a[1];
return (_this[key] = value);
});
}
Diff.prototype.toString = function () {
return JSON.stringify(this);
};
Diff.prototype.setValue = function (aKey, aValue) {
this[aKey] = aValue;
return this;
};
return Diff;
}());
function checkElementType(element) {
var arguments$1 = arguments;
var elementTypeNames = [];
for (var _i = 1; _i < arguments.length; _i++) {
elementTypeNames[_i - 1] = arguments$1[_i];
}
if (typeof element === "undefined" || element === null) {
return false;
}
return elementTypeNames.some(function (elementTypeName) {
var _a, _b;
// We need to check if the specified type is defined
// because otherwise instanceof throws an exception.
return typeof ((_b = (_a = element === null || element === void 0 ? void 0 : element.ownerDocument) === null || _a === void 0 ? void 0 : _a.defaultView) === null || _b === void 0 ? void 0 : _b[elementTypeName]) ===
"function" &&
element instanceof
element.ownerDocument.defaultView[elementTypeName];
});
}
function objToNode(objNode, insideSvg, options) {
var node;
if (objNode.nodeName === "#text") {
node = options.document.createTextNode(objNode.data);
}
else if (objNode.nodeName === "#comment") {
node = options.document.createComment(objNode.data);
}
else {
if (insideSvg) {
node = options.document.createElementNS("http://www.w3.org/2000/svg", objNode.nodeName);
if (objNode.nodeName === "foreignObject") {
insideSvg = false;
}
}
else if (objNode.nodeName.toLowerCase() === "svg") {
node = options.document.createElementNS("http://www.w3.org/2000/svg", "svg");
insideSvg = true;
}
else {
node = options.document.createElement(objNode.nodeName);
}
if (objNode.attributes) {
Object.entries(objNode.attributes).forEach(function (_a) {
var key = _a[0], value = _a[1];
return node.setAttribute(key, value);
});
}
if (objNode.childNodes) {
node = node;
objNode.childNodes.forEach(function (childNode) {
return node.appendChild(objToNode(childNode, insideSvg, options));
});
}
if (options.valueDiffing) {
if (objNode.value &&
checkElementType(node, "HTMLButtonElement", "HTMLDataElement", "HTMLInputElement", "HTMLLIElement", "HTMLMeterElement", "HTMLOptionElement", "HTMLProgressElement", "HTMLParamElement")) {
node.value = objNode.value;
}
if (objNode.checked && checkElementType(node, "HTMLInputElement")) {
node.checked = objNode.checked;
}
if (objNode.selected &&
checkElementType(node, "HTMLOptionElement")) {
node.selected = objNode.selected;
}
}
}
return node;
}
// ===== Apply a diff =====
var getFromRoute = function (node, route) {
route = route.slice();
while (route.length > 0) {
var c = route.splice(0, 1)[0];
node = node.childNodes[c];
}
return node;
};
function applyDiff(tree, diff, options) {
var action = diff[options._const.action];
var route = diff[options._const.route];
var node;
if (![options._const.addElement, options._const.addTextElement].includes(action)) {
// For adding nodes, we calculate the route later on. It's different because it includes the position of the newly added item.
node = getFromRoute(tree, route);
}
var newNode;
var reference;
var nodeArray;
// pre-diff hook
var info = {
diff: diff,
node: node
};
if (options.preDiffApply(info)) {
return true;
}
switch (action) {
case options._const.addAttribute:
if (!node || !checkElementType(node, "Element")) {
return false;
}
node.setAttribute(diff[options._const.name], diff[options._const.value]);
break;
case options._const.modifyAttribute:
if (!node || !checkElementType(node, "Element")) {
return false;
}
node.setAttribute(diff[options._const.name], diff[options._const.newValue]);
if (checkElementType(node, "HTMLInputElement") &&
diff[options._const.name] === "value") {
node.value = diff[options._const.newValue];
}
break;
case options._const.removeAttribute:
if (!node || !checkElementType(node, "Element")) {
return false;
}
node.removeAttribute(diff[options._const.name]);
break;
case options._const.modifyTextElement:
if (!node || !checkElementType(node, "Text")) {
return false;
}
options.textDiff(node, node.data, diff[options._const.oldValue], diff[options._const.newValue]);
if (checkElementType(node.parentNode, "HTMLTextAreaElement")) {
node.parentNode.value = diff[options._const.newValue];
}
break;
case options._const.modifyValue:
if (!node || typeof node.value === "undefined") {
return false;
}
node.value = diff[options._const.newValue];
break;
case options._const.modifyComment:
if (!node || !checkElementType(node, "Comment")) {
return false;
}
options.textDiff(node, node.data, diff[options._const.oldValue], diff[options._const.newValue]);
break;
case options._const.modifyChecked:
if (!node || typeof node.checked === "undefined") {
return false;
}
node.checked = diff[options._const.newValue];
break;
case options._const.modifySelected:
if (!node || typeof node.selected === "undefined") {
return false;
}
node.selected = diff[options._const.newValue];
break;
case options._const.replaceElement: {
var insideSvg = diff[options._const.newValue].nodeName.toLowerCase() === "svg" ||
node.parentNode.namespaceURI === "http://www.w3.org/2000/svg";
node.parentNode.replaceChild(objToNode(diff[options._const.newValue], insideSvg, options), node);
break;
}
case options._const.relocateGroup:
nodeArray = __spreadArray([], new Array(diff[options._const.groupLength]), true).map(function () {
return node.removeChild(node.childNodes[diff[options._const.from]]);
});
nodeArray.forEach(function (childNode, index) {
if (index === 0) {
reference =
node.childNodes[diff[options._const.to]];
}
node.insertBefore(childNode, reference || null);
});
break;
case options._const.removeElement:
node.parentNode.removeChild(node);
break;
case options._const.addElement: {
var parentRoute = route.slice();
var c = parentRoute.splice(parentRoute.length - 1, 1)[0];
node = getFromRoute(tree, parentRoute);
if (!checkElementType(node, "Element")) {
return false;
}
node.insertBefore(objToNode(diff[options._const.element], node.namespaceURI === "http://www.w3.org/2000/svg", options), node.childNodes[c] || null);
break;
}
case options._const.removeTextElement: {
if (!node || node.nodeType !== 3) {
return false;
}
var parentNode = node.parentNode;
parentNode.removeChild(node);
if (checkElementType(parentNode, "HTMLTextAreaElement")) {
parentNode.value = "";
}
break;
}
case options._const.addTextElement: {
var parentRoute = route.slice();
var c = parentRoute.splice(parentRoute.length - 1, 1)[0];
newNode = options.document.createTextNode(diff[options._const.value]);
node = getFromRoute(tree, parentRoute);
if (!node.childNodes) {
return false;
}
node.insertBefore(newNode, node.childNodes[c] || null);
if (checkElementType(node.parentNode, "HTMLTextAreaElement")) {
node.parentNode.value = diff[options._const.value];
}
break;
}
default:
console.log("unknown action");
}
// if a new node was created, we might be interested in its
// post diff hook
options.postDiffApply({
diff: info.diff,
node: info.node,
newNode: newNode
});
return true;
}
function applyDOM(tree, diffs, options) {
return diffs.every(function (diff) {
return applyDiff(tree, diff, options);
});
}
// ===== Undo a diff =====
function swap(obj, p1, p2) {
var tmp = obj[p1];
obj[p1] = obj[p2];
obj[p2] = tmp;
}
function undoDiff(tree, diff, options) {
switch (diff[options._const.action]) {
case options._const.addAttribute:
diff[options._const.action] = options._const.removeAttribute;
applyDiff(tree, diff, options);
break;
case options._const.modifyAttribute:
swap(diff, options._const.oldValue, options._const.newValue);
applyDiff(tree, diff, options);
break;
case options._const.removeAttribute:
diff[options._const.action] = options._const.addAttribute;
applyDiff(tree, diff, options);
break;
case options._const.modifyTextElement:
swap(diff, options._const.oldValue, options._const.newValue);
applyDiff(tree, diff, options);
break;
case options._const.modifyValue:
swap(diff, options._const.oldValue, options._const.newValue);
applyDiff(tree, diff, options);
break;
case options._const.modifyComment:
swap(diff, options._const.oldValue, options._const.newValue);
applyDiff(tree, diff, options);
break;
case options._const.modifyChecked:
swap(diff, options._const.oldValue, options._const.newValue);
applyDiff(tree, diff, options);
break;
case options._const.modifySelected:
swap(diff, options._const.oldValue, options._const.newValue);
applyDiff(tree, diff, options);
break;
case options._const.replaceElement:
swap(diff, options._const.oldValue, options._const.newValue);
applyDiff(tree, diff, options);
break;
case options._const.relocateGroup:
swap(diff, options._const.from, options._const.to);
applyDiff(tree, diff, options);
break;
case options._const.removeElement:
diff[options._const.action] = options._const.addElement;
applyDiff(tree, diff, options);
break;
case options._const.addElement:
diff[options._const.action] = options._const.removeElement;
applyDiff(tree, diff, options);
break;
case options._const.removeTextElement:
diff[options._const.action] = options._const.addTextElement;
applyDiff(tree, diff, options);
break;
case options._const.addTextElement:
diff[options._const.action] = options._const.removeTextElement;
applyDiff(tree, diff, options);
break;
default:
console.log("unknown action");
}
}
function undoDOM(tree, diffs, options) {
diffs = diffs.slice();
diffs.reverse();
diffs.forEach(function (diff) {
undoDiff(tree, diff, options);
});
}
var elementDescriptors = function (el) {
var output = [];
output.push(el.nodeName);
if (el.nodeName !== "#text" && el.nodeName !== "#comment") {
el = el;
if (el.attributes) {
if (el.attributes["class"]) {
output.push("".concat(el.nodeName, ".").concat(el.attributes["class"].replace(/ /g, ".")));
}
if (el.attributes.id) {
output.push("".concat(el.nodeName, "#").concat(el.attributes.id));
}
}
}
return output;
};
var findUniqueDescriptors = function (li) {
var uniqueDescriptors = {};
var duplicateDescriptors = {};
li.forEach(function (node) {
elementDescriptors(node).forEach(function (descriptor) {
var inUnique = descriptor in uniqueDescriptors;
var inDupes = descriptor in duplicateDescriptors;
if (!inUnique && !inDupes) {
uniqueDescriptors[descriptor] = true;
}
else if (inUnique) {
delete uniqueDescriptors[descriptor];
duplicateDescriptors[descriptor] = true;
}
});
});
return uniqueDescriptors;
};
var uniqueInBoth = function (l1, l2) {
var l1Unique = findUniqueDescriptors(l1);
var l2Unique = findUniqueDescriptors(l2);
var inBoth = {};
Object.keys(l1Unique).forEach(function (key) {
if (l2Unique[key]) {
inBoth[key] = true;
}
});
return inBoth;
};
var removeDone = function (tree) {
delete tree.outerDone;
delete tree.innerDone;
delete tree.valueDone;
if (tree.childNodes) {
return tree.childNodes.every(removeDone);
}
else {
return true;
}
};
var cleanNode = function (diffNode) {
if (Object.prototype.hasOwnProperty.call(diffNode, "data")) {
var textNode = {
nodeName: diffNode.nodeName === "#text" ? "#text" : "#comment",
data: diffNode.data
};
return textNode;
}
else {
var elementNode = {
nodeName: diffNode.nodeName
};
diffNode = diffNode;
if (Object.prototype.hasOwnProperty.call(diffNode, "attributes")) {
elementNode.attributes = __assign({}, diffNode.attributes);
}
if (Object.prototype.hasOwnProperty.call(diffNode, "checked")) {
elementNode.checked = diffNode.checked;
}
if (Object.prototype.hasOwnProperty.call(diffNode, "value")) {
elementNode.value = diffNode.value;
}
if (Object.prototype.hasOwnProperty.call(diffNode, "selected")) {
elementNode.selected = diffNode.selected;
}
if (Object.prototype.hasOwnProperty.call(diffNode, "childNodes")) {
elementNode.childNodes = diffNode.childNodes.map(function (diffChildNode) {
return cleanNode(diffChildNode);
});
}
return elementNode;
}
};
var isEqual = function (e1, e2) {
if (!["nodeName", "value", "checked", "selected", "data"].every(function (element) {
if (e1[element] !== e2[element]) {
return false;
}
return true;
})) {
return false;
}
if (Object.prototype.hasOwnProperty.call(e1, "data")) {
// Comment or Text
return true;
}
e1 = e1;
e2 = e2;
if (Boolean(e1.attributes) !== Boolean(e2.attributes)) {
return false;
}
if (Boolean(e1.childNodes) !== Boolean(e2.childNodes)) {
return false;
}
if (e1.attributes) {
var e1Attributes = Object.keys(e1.attributes);
var e2Attributes = Object.keys(e2.attributes);
if (e1Attributes.length !== e2Attributes.length) {
return false;
}
if (!e1Attributes.every(function (attribute) {
if (e1.attributes[attribute] !==
e2.attributes[attribute]) {
return false;
}
return true;
})) {
return false;
}
}
if (e1.childNodes) {
if (e1.childNodes.length !== e2.childNodes.length) {
return false;
}
if (!e1.childNodes.every(function (childNode, index) {
return isEqual(childNode, e2.childNodes[index]);
})) {
return false;
}
}
return true;
};
var roughlyEqual = function (e1, e2, uniqueDescriptors, sameSiblings, preventRecursion) {
if (preventRecursion === void 0) { preventRecursion = false; }
if (!e1 || !e2) {
return false;
}
if (e1.nodeName !== e2.nodeName) {
return false;
}
if (["#text", "#comment"].includes(e1.nodeName)) {
// Note that we initially don't care what the text content of a node is,
// the mere fact that it's the same tag and "has text" means it's roughly
// equal, and then we can find out the true text difference later.
return preventRecursion
? true
: e1.data === e2.data;
}
e1 = e1;
e2 = e2;
if (e1.nodeName in uniqueDescriptors) {
return true;
}
if (e1.attributes && e2.attributes) {
if (e1.attributes.id) {
if (e1.attributes.id !== e2.attributes.id) {
return false;
}
else {
var idDescriptor = "".concat(e1.nodeName, "#").concat(e1.attributes.id);
if (idDescriptor in uniqueDescriptors) {
return true;
}
}
}
if (e1.attributes["class"] &&
e1.attributes["class"] === e2.attributes["class"]) {
var classDescriptor = "".concat(e1.nodeName, ".").concat(e1.attributes["class"].replace(/ /g, "."));
if (classDescriptor in uniqueDescriptors) {
return true;
}
}
}
if (sameSiblings) {
return true;
}
var nodeList1 = e1.childNodes ? e1.childNodes.slice().reverse() : [];
var nodeList2 = e2.childNodes ? e2.childNodes.slice().reverse() : [];
if (nodeList1.length !== nodeList2.length) {
return false;
}
if (preventRecursion) {
return nodeList1.every(function (element, index) {
return element.nodeName === nodeList2[index].nodeName;
});
}
else {
// note: we only allow one level of recursion at any depth. If 'preventRecursion'
// was not set, we must explicitly force it to true for child iterations.
var childUniqueDescriptors_1 = uniqueInBoth(nodeList1, nodeList2);
return nodeList1.every(function (element, index) {
return roughlyEqual(element, nodeList2[index], childUniqueDescriptors_1, true, true);
});
}
};
/**
* based on https://en.wikibooks.org/wiki/Algorithm_implementation/Strings/Longest_common_substring#JavaScript
*/
var findCommonSubsets = function (c1, c2, marked1, marked2) {
var lcsSize = 0;
var index = [];
var c1Length = c1.length;
var c2Length = c2.length;
var // set up the matching table
matches = __spreadArray([], new Array(c1Length + 1), true).map(function () { return []; });
var uniqueDescriptors = uniqueInBoth(c1, c2);
var // If all of the elements are the same tag, id and class, then we can
// consider them roughly the same even if they have a different number of
// children. This will reduce removing and re-adding similar elements.
subsetsSame = c1Length === c2Length;
if (subsetsSame) {
c1.some(function (element, i) {
var c1Desc = elementDescriptors(element);
var c2Desc = elementDescriptors(c2[i]);
if (c1Desc.length !== c2Desc.length) {
subsetsSame = false;
return true;
}
c1Desc.some(function (description, i) {
if (description !== c2Desc[i]) {
subsetsSame = false;
return true;
}
});
if (!subsetsSame) {
return true;
}
});
}
// fill the matches with distance values
for (var c1Index = 0; c1Index < c1Length; c1Index++) {
var c1Element = c1[c1Index];
for (var c2Index = 0; c2Index < c2Length; c2Index++) {
var c2Element = c2[c2Index];
if (!marked1[c1Index] &&
!marked2[c2Index] &&
roughlyEqual(c1Element, c2Element, uniqueDescriptors, subsetsSame)) {
matches[c1Index + 1][c2Index + 1] = matches[c1Index][c2Index]
? matches[c1Index][c2Index] + 1
: 1;
if (matches[c1Index + 1][c2Index + 1] >= lcsSize) {
lcsSize = matches[c1Index + 1][c2Index + 1];
index = [c1Index + 1, c2Index + 1];
}
}
else {
matches[c1Index + 1][c2Index + 1] = 0;
}
}
}
if (lcsSize === 0) {
return false;
}
return {
oldValue: index[0] - lcsSize,
newValue: index[1] - lcsSize,
length: lcsSize
};
};
var makeBooleanArray = function (n, v) {
return __spreadArray([], new Array(n), true).map(function () { return v; });
};
/**
* Generate arrays that indicate which node belongs to which subset,
* or whether it's actually an orphan node, existing in only one
* of the two trees, rather than somewhere in both.
*
* So if t1 = ![]()