mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-09-01 13:39:22 +03:00
Clone 3.5.1 from github
This commit is contained in:
parent
5d281486d0
commit
091c4ec811
65 changed files with 6957 additions and 7 deletions
237
vendors/knockout/src/binding/editDetection/arrayToDomNodeChildren.js
vendored
Normal file
237
vendors/knockout/src/binding/editDetection/arrayToDomNodeChildren.js
vendored
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
(function () {
|
||||
// Objective:
|
||||
// * Given an input array, a container DOM node, and a function from array elements to arrays of DOM nodes,
|
||||
// map the array elements to arrays of DOM nodes, concatenate together all these arrays, and use them to populate the container DOM node
|
||||
// * Next time we're given the same combination of things (with the array possibly having mutated), update the container DOM node
|
||||
// so that its children is again the concatenation of the mappings of the array elements, but don't re-map any array elements that we
|
||||
// previously mapped - retain those nodes, and just insert/delete other ones
|
||||
|
||||
// "callbackAfterAddingNodes" will be invoked after any "mapping"-generated nodes are inserted into the container node
|
||||
// You can use this, for example, to activate bindings on those nodes.
|
||||
|
||||
function mapNodeAndRefreshWhenChanged(containerNode, mapping, valueToMap, callbackAfterAddingNodes, index) {
|
||||
// Map this array value inside a dependentObservable so we re-map when any dependency changes
|
||||
var mappedNodes = [];
|
||||
var dependentObservable = ko.dependentObservable(function() {
|
||||
var newMappedNodes = mapping(valueToMap, index, ko.utils.fixUpContinuousNodeArray(mappedNodes, containerNode)) || [];
|
||||
|
||||
// On subsequent evaluations, just replace the previously-inserted DOM nodes
|
||||
if (mappedNodes.length > 0) {
|
||||
ko.utils.replaceDomNodes(mappedNodes, newMappedNodes);
|
||||
if (callbackAfterAddingNodes)
|
||||
ko.dependencyDetection.ignore(callbackAfterAddingNodes, null, [valueToMap, newMappedNodes, index]);
|
||||
}
|
||||
|
||||
// Replace the contents of the mappedNodes array, thereby updating the record
|
||||
// of which nodes would be deleted if valueToMap was itself later removed
|
||||
mappedNodes.length = 0;
|
||||
ko.utils.arrayPushAll(mappedNodes, newMappedNodes);
|
||||
}, null, { disposeWhenNodeIsRemoved: containerNode, disposeWhen: function() { return !ko.utils.anyDomNodeIsAttachedToDocument(mappedNodes); } });
|
||||
return { mappedNodes : mappedNodes, dependentObservable : (dependentObservable.isActive() ? dependentObservable : undefined) };
|
||||
}
|
||||
|
||||
var lastMappingResultDomDataKey = ko.utils.domData.nextKey(),
|
||||
deletedItemDummyValue = ko.utils.domData.nextKey();
|
||||
|
||||
ko.utils.setDomNodeChildrenFromArrayMapping = function (domNode, array, mapping, options, callbackAfterAddingNodes, editScript) {
|
||||
array = array || [];
|
||||
if (typeof array.length == "undefined") // Coerce single value into array
|
||||
array = [array];
|
||||
|
||||
options = options || {};
|
||||
var lastMappingResult = ko.utils.domData.get(domNode, lastMappingResultDomDataKey);
|
||||
var isFirstExecution = !lastMappingResult;
|
||||
|
||||
// Build the new mapping result
|
||||
var newMappingResult = [];
|
||||
var lastMappingResultIndex = 0;
|
||||
var currentArrayIndex = 0;
|
||||
|
||||
var nodesToDelete = [];
|
||||
var itemsToMoveFirstIndexes = [];
|
||||
var itemsForBeforeRemoveCallbacks = [];
|
||||
var itemsForMoveCallbacks = [];
|
||||
var itemsForAfterAddCallbacks = [];
|
||||
var mapData;
|
||||
var countWaitingForRemove = 0;
|
||||
|
||||
function itemAdded(value) {
|
||||
mapData = { arrayEntry: value, indexObservable: ko.observable(currentArrayIndex++) };
|
||||
newMappingResult.push(mapData);
|
||||
if (!isFirstExecution) {
|
||||
itemsForAfterAddCallbacks.push(mapData);
|
||||
}
|
||||
}
|
||||
|
||||
function itemMovedOrRetained(oldPosition) {
|
||||
mapData = lastMappingResult[oldPosition];
|
||||
if (currentArrayIndex !== mapData.indexObservable.peek())
|
||||
itemsForMoveCallbacks.push(mapData);
|
||||
// Since updating the index might change the nodes, do so before calling fixUpContinuousNodeArray
|
||||
mapData.indexObservable(currentArrayIndex++);
|
||||
ko.utils.fixUpContinuousNodeArray(mapData.mappedNodes, domNode);
|
||||
newMappingResult.push(mapData);
|
||||
}
|
||||
|
||||
function callCallback(callback, items) {
|
||||
if (callback) {
|
||||
for (var i = 0, n = items.length; i < n; i++) {
|
||||
ko.utils.arrayForEach(items[i].mappedNodes, function(node) {
|
||||
callback(node, i, items[i].arrayEntry);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isFirstExecution) {
|
||||
ko.utils.arrayForEach(array, itemAdded);
|
||||
} else {
|
||||
if (!editScript || (lastMappingResult && lastMappingResult['_countWaitingForRemove'])) {
|
||||
// Compare the provided array against the previous one
|
||||
var lastArray = ko.utils.arrayMap(lastMappingResult, function (x) { return x.arrayEntry; }),
|
||||
compareOptions = {
|
||||
'dontLimitMoves': options['dontLimitMoves'],
|
||||
'sparse': true
|
||||
};
|
||||
editScript = ko.utils.compareArrays(lastArray, array, compareOptions);
|
||||
}
|
||||
|
||||
for (var i = 0, editScriptItem, movedIndex, itemIndex; editScriptItem = editScript[i]; i++) {
|
||||
movedIndex = editScriptItem['moved'];
|
||||
itemIndex = editScriptItem['index'];
|
||||
switch (editScriptItem['status']) {
|
||||
case "deleted":
|
||||
while (lastMappingResultIndex < itemIndex) {
|
||||
itemMovedOrRetained(lastMappingResultIndex++);
|
||||
}
|
||||
if (movedIndex === undefined) {
|
||||
mapData = lastMappingResult[lastMappingResultIndex];
|
||||
|
||||
// Stop tracking changes to the mapping for these nodes
|
||||
if (mapData.dependentObservable) {
|
||||
mapData.dependentObservable.dispose();
|
||||
mapData.dependentObservable = undefined;
|
||||
}
|
||||
|
||||
// Queue these nodes for later removal
|
||||
if (ko.utils.fixUpContinuousNodeArray(mapData.mappedNodes, domNode).length) {
|
||||
if (options['beforeRemove']) {
|
||||
newMappingResult.push(mapData);
|
||||
countWaitingForRemove++;
|
||||
if (mapData.arrayEntry === deletedItemDummyValue) {
|
||||
mapData = null;
|
||||
} else {
|
||||
itemsForBeforeRemoveCallbacks.push(mapData);
|
||||
}
|
||||
}
|
||||
if (mapData) {
|
||||
nodesToDelete.push.apply(nodesToDelete, mapData.mappedNodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
lastMappingResultIndex++;
|
||||
break;
|
||||
|
||||
case "added":
|
||||
while (currentArrayIndex < itemIndex) {
|
||||
itemMovedOrRetained(lastMappingResultIndex++);
|
||||
}
|
||||
if (movedIndex !== undefined) {
|
||||
itemsToMoveFirstIndexes.push(newMappingResult.length);
|
||||
itemMovedOrRetained(movedIndex);
|
||||
} else {
|
||||
itemAdded(editScriptItem['value']);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (currentArrayIndex < array.length) {
|
||||
itemMovedOrRetained(lastMappingResultIndex++);
|
||||
}
|
||||
|
||||
// Record that the current view may still contain deleted items
|
||||
// because it means we won't be able to use a provided editScript.
|
||||
newMappingResult['_countWaitingForRemove'] = countWaitingForRemove;
|
||||
}
|
||||
|
||||
// Store a copy of the array items we just considered so we can difference it next time
|
||||
ko.utils.domData.set(domNode, lastMappingResultDomDataKey, newMappingResult);
|
||||
|
||||
// Call beforeMove first before any changes have been made to the DOM
|
||||
callCallback(options['beforeMove'], itemsForMoveCallbacks);
|
||||
|
||||
// Next remove nodes for deleted items (or just clean if there's a beforeRemove callback)
|
||||
ko.utils.arrayForEach(nodesToDelete, options['beforeRemove'] ? ko.cleanNode : ko.removeNode);
|
||||
|
||||
var i, j, lastNode, nodeToInsert, mappedNodes, activeElement;
|
||||
|
||||
// Since most browsers remove the focus from an element when it's moved to another location,
|
||||
// save the focused element and try to restore it later.
|
||||
try {
|
||||
activeElement = domNode.ownerDocument.activeElement;
|
||||
} catch(e) {
|
||||
// IE9 throws if you access activeElement during page load (see issue #703)
|
||||
}
|
||||
|
||||
// Try to reduce overall moved nodes by first moving the ones that were marked as moved by the edit script
|
||||
if (itemsToMoveFirstIndexes.length) {
|
||||
while ((i = itemsToMoveFirstIndexes.shift()) != undefined) {
|
||||
mapData = newMappingResult[i];
|
||||
for (lastNode = undefined; i; ) {
|
||||
if ((mappedNodes = newMappingResult[--i].mappedNodes) && mappedNodes.length) {
|
||||
lastNode = mappedNodes[mappedNodes.length-1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (j = 0; nodeToInsert = mapData.mappedNodes[j]; lastNode = nodeToInsert, j++) {
|
||||
ko.virtualElements.insertAfter(domNode, nodeToInsert, lastNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Next add/reorder the remaining items (will include deleted items if there's a beforeRemove callback)
|
||||
for (i = 0; mapData = newMappingResult[i]; i++) {
|
||||
// Get nodes for newly added items
|
||||
if (!mapData.mappedNodes)
|
||||
ko.utils.extend(mapData, mapNodeAndRefreshWhenChanged(domNode, mapping, mapData.arrayEntry, callbackAfterAddingNodes, mapData.indexObservable));
|
||||
|
||||
// Put nodes in the right place if they aren't there already
|
||||
for (j = 0; nodeToInsert = mapData.mappedNodes[j]; lastNode = nodeToInsert, j++) {
|
||||
ko.virtualElements.insertAfter(domNode, nodeToInsert, lastNode);
|
||||
}
|
||||
|
||||
// Run the callbacks for newly added nodes (for example, to apply bindings, etc.)
|
||||
if (!mapData.initialized && callbackAfterAddingNodes) {
|
||||
callbackAfterAddingNodes(mapData.arrayEntry, mapData.mappedNodes, mapData.indexObservable);
|
||||
mapData.initialized = true;
|
||||
lastNode = mapData.mappedNodes[mapData.mappedNodes.length - 1]; // get the last node again since it may have been changed by a preprocessor
|
||||
}
|
||||
}
|
||||
|
||||
// Restore the focused element if it had lost focus
|
||||
if (activeElement && domNode.ownerDocument.activeElement != activeElement) {
|
||||
activeElement.focus();
|
||||
}
|
||||
|
||||
// If there's a beforeRemove callback, call it after reordering.
|
||||
// Note that we assume that the beforeRemove callback will usually be used to remove the nodes using
|
||||
// some sort of animation, which is why we first reorder the nodes that will be removed. If the
|
||||
// callback instead removes the nodes right away, it would be more efficient to skip reordering them.
|
||||
// Perhaps we'll make that change in the future if this scenario becomes more common.
|
||||
callCallback(options['beforeRemove'], itemsForBeforeRemoveCallbacks);
|
||||
|
||||
// Replace the stored values of deleted items with a dummy value. This provides two benefits: it marks this item
|
||||
// as already "removed" so we won't call beforeRemove for it again, and it ensures that the item won't match up
|
||||
// with an actual item in the array and appear as "retained" or "moved".
|
||||
for (i = 0; i < itemsForBeforeRemoveCallbacks.length; ++i) {
|
||||
itemsForBeforeRemoveCallbacks[i].arrayEntry = deletedItemDummyValue;
|
||||
}
|
||||
|
||||
// Finally call afterMove and afterAdd callbacks
|
||||
callCallback(options['afterMove'], itemsForMoveCallbacks);
|
||||
callCallback(options['afterAdd'], itemsForAfterAddCallbacks);
|
||||
}
|
||||
})();
|
||||
|
||||
ko.exportSymbol('utils.setDomNodeChildrenFromArrayMapping', ko.utils.setDomNodeChildrenFromArrayMapping);
|
||||
102
vendors/knockout/src/binding/editDetection/compareArrays.js
vendored
Normal file
102
vendors/knockout/src/binding/editDetection/compareArrays.js
vendored
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
// Go through the items that have been added and deleted and try to find matches between them.
|
||||
ko.utils.findMovesInArrayComparison = function (left, right, limitFailedCompares) {
|
||||
if (left.length && right.length) {
|
||||
var failedCompares, l, r, leftItem, rightItem;
|
||||
for (failedCompares = l = 0; (!limitFailedCompares || failedCompares < limitFailedCompares) && (leftItem = left[l]); ++l) {
|
||||
for (r = 0; rightItem = right[r]; ++r) {
|
||||
if (leftItem['value'] === rightItem['value']) {
|
||||
leftItem['moved'] = rightItem['index'];
|
||||
rightItem['moved'] = leftItem['index'];
|
||||
right.splice(r, 1); // This item is marked as moved; so remove it from right list
|
||||
failedCompares = r = 0; // Reset failed compares count because we're checking for consecutive failures
|
||||
break;
|
||||
}
|
||||
}
|
||||
failedCompares += r;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ko.utils.compareArrays = (function () {
|
||||
var statusNotInOld = 'added', statusNotInNew = 'deleted';
|
||||
|
||||
// Simple calculation based on Levenshtein distance.
|
||||
function compareArrays(oldArray, newArray, options) {
|
||||
// For backward compatibility, if the third arg is actually a bool, interpret
|
||||
// it as the old parameter 'dontLimitMoves'. Newer code should use { dontLimitMoves: true }.
|
||||
options = (typeof options === 'boolean') ? { 'dontLimitMoves': options } : (options || {});
|
||||
oldArray = oldArray || [];
|
||||
newArray = newArray || [];
|
||||
|
||||
if (oldArray.length < newArray.length)
|
||||
return compareSmallArrayToBigArray(oldArray, newArray, statusNotInOld, statusNotInNew, options);
|
||||
else
|
||||
return compareSmallArrayToBigArray(newArray, oldArray, statusNotInNew, statusNotInOld, options);
|
||||
}
|
||||
|
||||
function compareSmallArrayToBigArray(smlArray, bigArray, statusNotInSml, statusNotInBig, options) {
|
||||
var myMin = Math.min,
|
||||
myMax = Math.max,
|
||||
editDistanceMatrix = [],
|
||||
smlIndex, smlIndexMax = smlArray.length,
|
||||
bigIndex, bigIndexMax = bigArray.length,
|
||||
compareRange = (bigIndexMax - smlIndexMax) || 1,
|
||||
maxDistance = smlIndexMax + bigIndexMax + 1,
|
||||
thisRow, lastRow,
|
||||
bigIndexMaxForRow, bigIndexMinForRow;
|
||||
|
||||
for (smlIndex = 0; smlIndex <= smlIndexMax; smlIndex++) {
|
||||
lastRow = thisRow;
|
||||
editDistanceMatrix.push(thisRow = []);
|
||||
bigIndexMaxForRow = myMin(bigIndexMax, smlIndex + compareRange);
|
||||
bigIndexMinForRow = myMax(0, smlIndex - 1);
|
||||
for (bigIndex = bigIndexMinForRow; bigIndex <= bigIndexMaxForRow; bigIndex++) {
|
||||
if (!bigIndex)
|
||||
thisRow[bigIndex] = smlIndex + 1;
|
||||
else if (!smlIndex) // Top row - transform empty array into new array via additions
|
||||
thisRow[bigIndex] = bigIndex + 1;
|
||||
else if (smlArray[smlIndex - 1] === bigArray[bigIndex - 1])
|
||||
thisRow[bigIndex] = lastRow[bigIndex - 1]; // copy value (no edit)
|
||||
else {
|
||||
var northDistance = lastRow[bigIndex] || maxDistance; // not in big (deletion)
|
||||
var westDistance = thisRow[bigIndex - 1] || maxDistance; // not in small (addition)
|
||||
thisRow[bigIndex] = myMin(northDistance, westDistance) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var editScript = [], meMinusOne, notInSml = [], notInBig = [];
|
||||
for (smlIndex = smlIndexMax, bigIndex = bigIndexMax; smlIndex || bigIndex;) {
|
||||
meMinusOne = editDistanceMatrix[smlIndex][bigIndex] - 1;
|
||||
if (bigIndex && meMinusOne === editDistanceMatrix[smlIndex][bigIndex-1]) {
|
||||
notInSml.push(editScript[editScript.length] = { // added
|
||||
'status': statusNotInSml,
|
||||
'value': bigArray[--bigIndex],
|
||||
'index': bigIndex });
|
||||
} else if (smlIndex && meMinusOne === editDistanceMatrix[smlIndex - 1][bigIndex]) {
|
||||
notInBig.push(editScript[editScript.length] = { // deleted
|
||||
'status': statusNotInBig,
|
||||
'value': smlArray[--smlIndex],
|
||||
'index': smlIndex });
|
||||
} else {
|
||||
--bigIndex;
|
||||
--smlIndex;
|
||||
if (!options['sparse']) {
|
||||
editScript.push({
|
||||
'status': "retained",
|
||||
'value': bigArray[bigIndex] });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set a limit on the number of consecutive non-matching comparisons; having it a multiple of
|
||||
// smlIndexMax keeps the time complexity of this algorithm linear.
|
||||
ko.utils.findMovesInArrayComparison(notInBig, notInSml, !options['dontLimitMoves'] && smlIndexMax * 10);
|
||||
|
||||
return editScript.reverse();
|
||||
}
|
||||
|
||||
return compareArrays;
|
||||
})();
|
||||
|
||||
ko.exportSymbol('utils.compareArrays', ko.utils.compareArrays);
|
||||
Loading…
Add table
Add a link
Reference in a new issue