mirror of
https://github.com/mtan93/homebridge.git
synced 2026-03-09 13:21:58 +00:00
* No compilation step * Beginnings of web interface * Simple express server; React-based frontend * CommonJS style across codebase; auto-converts to RequireJS for browser * Using diffsync for realtime UI * "Provider" -> "Plugin" * Plugins expose one or more Providers
4170 lines
123 KiB
JavaScript
4170 lines
123 KiB
JavaScript
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.diffsync=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
|
// Copyright Joyent, Inc. and other Node contributors.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
// copy of this software and associated documentation files (the
|
|
// "Software"), to deal in the Software without restriction, including
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
// persons to whom the Software is furnished to do so, subject to the
|
|
// following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included
|
|
// in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
|
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
function EventEmitter() {
|
|
this._events = this._events || {};
|
|
this._maxListeners = this._maxListeners || undefined;
|
|
}
|
|
module.exports = EventEmitter;
|
|
|
|
// Backwards-compat with node 0.10.x
|
|
EventEmitter.EventEmitter = EventEmitter;
|
|
|
|
EventEmitter.prototype._events = undefined;
|
|
EventEmitter.prototype._maxListeners = undefined;
|
|
|
|
// By default EventEmitters will print a warning if more than 10 listeners are
|
|
// added to it. This is a useful default which helps finding memory leaks.
|
|
EventEmitter.defaultMaxListeners = 10;
|
|
|
|
// Obviously not all Emitters should be limited to 10. This function allows
|
|
// that to be increased. Set to zero for unlimited.
|
|
EventEmitter.prototype.setMaxListeners = function(n) {
|
|
if (!isNumber(n) || n < 0 || isNaN(n))
|
|
throw TypeError('n must be a positive number');
|
|
this._maxListeners = n;
|
|
return this;
|
|
};
|
|
|
|
EventEmitter.prototype.emit = function(type) {
|
|
var er, handler, len, args, i, listeners;
|
|
|
|
if (!this._events)
|
|
this._events = {};
|
|
|
|
// If there is no 'error' event listener then throw.
|
|
if (type === 'error') {
|
|
if (!this._events.error ||
|
|
(isObject(this._events.error) && !this._events.error.length)) {
|
|
er = arguments[1];
|
|
if (er instanceof Error) {
|
|
throw er; // Unhandled 'error' event
|
|
}
|
|
throw TypeError('Uncaught, unspecified "error" event.');
|
|
}
|
|
}
|
|
|
|
handler = this._events[type];
|
|
|
|
if (isUndefined(handler))
|
|
return false;
|
|
|
|
if (isFunction(handler)) {
|
|
switch (arguments.length) {
|
|
// fast cases
|
|
case 1:
|
|
handler.call(this);
|
|
break;
|
|
case 2:
|
|
handler.call(this, arguments[1]);
|
|
break;
|
|
case 3:
|
|
handler.call(this, arguments[1], arguments[2]);
|
|
break;
|
|
// slower
|
|
default:
|
|
len = arguments.length;
|
|
args = new Array(len - 1);
|
|
for (i = 1; i < len; i++)
|
|
args[i - 1] = arguments[i];
|
|
handler.apply(this, args);
|
|
}
|
|
} else if (isObject(handler)) {
|
|
len = arguments.length;
|
|
args = new Array(len - 1);
|
|
for (i = 1; i < len; i++)
|
|
args[i - 1] = arguments[i];
|
|
|
|
listeners = handler.slice();
|
|
len = listeners.length;
|
|
for (i = 0; i < len; i++)
|
|
listeners[i].apply(this, args);
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
EventEmitter.prototype.addListener = function(type, listener) {
|
|
var m;
|
|
|
|
if (!isFunction(listener))
|
|
throw TypeError('listener must be a function');
|
|
|
|
if (!this._events)
|
|
this._events = {};
|
|
|
|
// To avoid recursion in the case that type === "newListener"! Before
|
|
// adding it to the listeners, first emit "newListener".
|
|
if (this._events.newListener)
|
|
this.emit('newListener', type,
|
|
isFunction(listener.listener) ?
|
|
listener.listener : listener);
|
|
|
|
if (!this._events[type])
|
|
// Optimize the case of one listener. Don't need the extra array object.
|
|
this._events[type] = listener;
|
|
else if (isObject(this._events[type]))
|
|
// If we've already got an array, just append.
|
|
this._events[type].push(listener);
|
|
else
|
|
// Adding the second element, need to change to array.
|
|
this._events[type] = [this._events[type], listener];
|
|
|
|
// Check for listener leak
|
|
if (isObject(this._events[type]) && !this._events[type].warned) {
|
|
var m;
|
|
if (!isUndefined(this._maxListeners)) {
|
|
m = this._maxListeners;
|
|
} else {
|
|
m = EventEmitter.defaultMaxListeners;
|
|
}
|
|
|
|
if (m && m > 0 && this._events[type].length > m) {
|
|
this._events[type].warned = true;
|
|
console.error('(node) warning: possible EventEmitter memory ' +
|
|
'leak detected. %d listeners added. ' +
|
|
'Use emitter.setMaxListeners() to increase limit.',
|
|
this._events[type].length);
|
|
if (typeof console.trace === 'function') {
|
|
// not supported in IE 10
|
|
console.trace();
|
|
}
|
|
}
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
|
|
|
|
EventEmitter.prototype.once = function(type, listener) {
|
|
if (!isFunction(listener))
|
|
throw TypeError('listener must be a function');
|
|
|
|
var fired = false;
|
|
|
|
function g() {
|
|
this.removeListener(type, g);
|
|
|
|
if (!fired) {
|
|
fired = true;
|
|
listener.apply(this, arguments);
|
|
}
|
|
}
|
|
|
|
g.listener = listener;
|
|
this.on(type, g);
|
|
|
|
return this;
|
|
};
|
|
|
|
// emits a 'removeListener' event iff the listener was removed
|
|
EventEmitter.prototype.removeListener = function(type, listener) {
|
|
var list, position, length, i;
|
|
|
|
if (!isFunction(listener))
|
|
throw TypeError('listener must be a function');
|
|
|
|
if (!this._events || !this._events[type])
|
|
return this;
|
|
|
|
list = this._events[type];
|
|
length = list.length;
|
|
position = -1;
|
|
|
|
if (list === listener ||
|
|
(isFunction(list.listener) && list.listener === listener)) {
|
|
delete this._events[type];
|
|
if (this._events.removeListener)
|
|
this.emit('removeListener', type, listener);
|
|
|
|
} else if (isObject(list)) {
|
|
for (i = length; i-- > 0;) {
|
|
if (list[i] === listener ||
|
|
(list[i].listener && list[i].listener === listener)) {
|
|
position = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (position < 0)
|
|
return this;
|
|
|
|
if (list.length === 1) {
|
|
list.length = 0;
|
|
delete this._events[type];
|
|
} else {
|
|
list.splice(position, 1);
|
|
}
|
|
|
|
if (this._events.removeListener)
|
|
this.emit('removeListener', type, listener);
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
EventEmitter.prototype.removeAllListeners = function(type) {
|
|
var key, listeners;
|
|
|
|
if (!this._events)
|
|
return this;
|
|
|
|
// not listening for removeListener, no need to emit
|
|
if (!this._events.removeListener) {
|
|
if (arguments.length === 0)
|
|
this._events = {};
|
|
else if (this._events[type])
|
|
delete this._events[type];
|
|
return this;
|
|
}
|
|
|
|
// emit removeListener for all listeners on all events
|
|
if (arguments.length === 0) {
|
|
for (key in this._events) {
|
|
if (key === 'removeListener') continue;
|
|
this.removeAllListeners(key);
|
|
}
|
|
this.removeAllListeners('removeListener');
|
|
this._events = {};
|
|
return this;
|
|
}
|
|
|
|
listeners = this._events[type];
|
|
|
|
if (isFunction(listeners)) {
|
|
this.removeListener(type, listeners);
|
|
} else {
|
|
// LIFO order
|
|
while (listeners.length)
|
|
this.removeListener(type, listeners[listeners.length - 1]);
|
|
}
|
|
delete this._events[type];
|
|
|
|
return this;
|
|
};
|
|
|
|
EventEmitter.prototype.listeners = function(type) {
|
|
var ret;
|
|
if (!this._events || !this._events[type])
|
|
ret = [];
|
|
else if (isFunction(this._events[type]))
|
|
ret = [this._events[type]];
|
|
else
|
|
ret = this._events[type].slice();
|
|
return ret;
|
|
};
|
|
|
|
EventEmitter.listenerCount = function(emitter, type) {
|
|
var ret;
|
|
if (!emitter._events || !emitter._events[type])
|
|
ret = 0;
|
|
else if (isFunction(emitter._events[type]))
|
|
ret = 1;
|
|
else
|
|
ret = emitter._events[type].length;
|
|
return ret;
|
|
};
|
|
|
|
function isFunction(arg) {
|
|
return typeof arg === 'function';
|
|
}
|
|
|
|
function isNumber(arg) {
|
|
return typeof arg === 'number';
|
|
}
|
|
|
|
function isObject(arg) {
|
|
return typeof arg === 'object' && arg !== null;
|
|
}
|
|
|
|
function isUndefined(arg) {
|
|
return arg === void 0;
|
|
}
|
|
|
|
},{}],2:[function(require,module,exports){
|
|
module.exports = {
|
|
Client: require('./src/client'),
|
|
Server: require('./src/server'),
|
|
COMMANDS: require('./src/commands'),
|
|
InMemoryDataAdapter: require('./src/adapter')
|
|
};
|
|
|
|
},{"./src/adapter":43,"./src/client":44,"./src/commands":45,"./src/server":46}],3:[function(require,module,exports){
|
|
|
|
var Pipe = require('../pipe').Pipe;
|
|
|
|
var Context = function Context(){
|
|
};
|
|
|
|
Context.prototype.setResult = function(result) {
|
|
this.result = result;
|
|
this.hasResult = true;
|
|
return this;
|
|
};
|
|
|
|
Context.prototype.exit = function() {
|
|
this.exiting = true;
|
|
return this;
|
|
};
|
|
|
|
Context.prototype.switchTo = function(next, pipe) {
|
|
if (typeof next === 'string' || next instanceof Pipe) {
|
|
this.nextPipe = next;
|
|
} else {
|
|
this.next = next;
|
|
if (pipe) {
|
|
this.nextPipe = pipe;
|
|
}
|
|
}
|
|
return this;
|
|
};
|
|
|
|
Context.prototype.push = function(child, name) {
|
|
child.parent = this;
|
|
if (typeof name !== 'undefined') {
|
|
child.childName = name;
|
|
}
|
|
child.root = this.root || this;
|
|
child.options = child.options || this.options;
|
|
if (!this.children) {
|
|
this.children = [child];
|
|
this.nextAfterChildren = this.next || null;
|
|
this.next = child;
|
|
} else {
|
|
this.children[this.children.length - 1].next = child;
|
|
this.children.push(child);
|
|
}
|
|
child.next = this;
|
|
return this;
|
|
};
|
|
|
|
exports.Context = Context;
|
|
|
|
},{"../pipe":17}],4:[function(require,module,exports){
|
|
var Context = require('./context').Context;
|
|
|
|
var DiffContext = function DiffContext(left, right) {
|
|
this.left = left;
|
|
this.right = right;
|
|
this.pipe = 'diff';
|
|
};
|
|
|
|
DiffContext.prototype = new Context();
|
|
|
|
exports.DiffContext = DiffContext;
|
|
|
|
},{"./context":3}],5:[function(require,module,exports){
|
|
var Context = require('./context').Context;
|
|
|
|
var PatchContext = function PatchContext(left, delta) {
|
|
this.left = left;
|
|
this.delta = delta;
|
|
this.pipe = 'patch';
|
|
};
|
|
|
|
PatchContext.prototype = new Context();
|
|
|
|
exports.PatchContext = PatchContext;
|
|
|
|
},{"./context":3}],6:[function(require,module,exports){
|
|
var Context = require('./context').Context;
|
|
|
|
var ReverseContext = function ReverseContext(delta) {
|
|
this.delta = delta;
|
|
this.pipe = 'reverse';
|
|
};
|
|
|
|
ReverseContext.prototype = new Context();
|
|
|
|
exports.ReverseContext = ReverseContext;
|
|
|
|
},{"./context":3}],7:[function(require,module,exports){
|
|
// use as 2nd parameter for JSON.parse to revive Date instances
|
|
module.exports = function dateReviver(key, value) {
|
|
var parts;
|
|
if (typeof value === 'string') {
|
|
parts = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d*))?(Z|([+\-])(\d{2}):(\d{2}))$/.exec(value);
|
|
if (parts) {
|
|
return new Date(Date.UTC(+parts[1], +parts[2] - 1, +parts[3], +parts[4], +parts[5], +parts[6], +(parts[7] || 0)));
|
|
}
|
|
}
|
|
return value;
|
|
};
|
|
|
|
},{}],8:[function(require,module,exports){
|
|
var Processor = require('./processor').Processor;
|
|
var Pipe = require('./pipe').Pipe;
|
|
var DiffContext = require('./contexts/diff').DiffContext;
|
|
var PatchContext = require('./contexts/patch').PatchContext;
|
|
var ReverseContext = require('./contexts/reverse').ReverseContext;
|
|
|
|
var trivial = require('./filters/trivial');
|
|
var nested = require('./filters/nested');
|
|
var arrays = require('./filters/arrays');
|
|
var dates = require('./filters/dates');
|
|
var texts = require('./filters/texts');
|
|
|
|
var DiffPatcher = function DiffPatcher(options) {
|
|
this.processor = new Processor(options);
|
|
this.processor.pipe(new Pipe('diff').append(
|
|
nested.collectChildrenDiffFilter,
|
|
trivial.diffFilter,
|
|
dates.diffFilter,
|
|
texts.diffFilter,
|
|
nested.objectsDiffFilter,
|
|
arrays.diffFilter
|
|
).shouldHaveResult());
|
|
this.processor.pipe(new Pipe('patch').append(
|
|
nested.collectChildrenPatchFilter,
|
|
arrays.collectChildrenPatchFilter,
|
|
trivial.patchFilter,
|
|
texts.patchFilter,
|
|
nested.patchFilter,
|
|
arrays.patchFilter
|
|
).shouldHaveResult());
|
|
this.processor.pipe(new Pipe('reverse').append(
|
|
nested.collectChildrenReverseFilter,
|
|
arrays.collectChildrenReverseFilter,
|
|
trivial.reverseFilter,
|
|
texts.reverseFilter,
|
|
nested.reverseFilter,
|
|
arrays.reverseFilter
|
|
).shouldHaveResult());
|
|
};
|
|
|
|
DiffPatcher.prototype.options = function() {
|
|
return this.processor.options.apply(this.processor, arguments);
|
|
};
|
|
|
|
DiffPatcher.prototype.diff = function(left, right) {
|
|
return this.processor.process(new DiffContext(left, right));
|
|
};
|
|
|
|
DiffPatcher.prototype.patch = function(left, delta) {
|
|
return this.processor.process(new PatchContext(left, delta));
|
|
};
|
|
|
|
DiffPatcher.prototype.reverse = function(delta) {
|
|
return this.processor.process(new ReverseContext(delta));
|
|
};
|
|
|
|
DiffPatcher.prototype.unpatch = function(right, delta) {
|
|
return this.patch(right, this.reverse(delta));
|
|
};
|
|
|
|
exports.DiffPatcher = DiffPatcher;
|
|
|
|
},{"./contexts/diff":4,"./contexts/patch":5,"./contexts/reverse":6,"./filters/arrays":10,"./filters/dates":11,"./filters/nested":13,"./filters/texts":14,"./filters/trivial":15,"./pipe":17,"./processor":18}],9:[function(require,module,exports){
|
|
|
|
exports.isBrowser = typeof window !== 'undefined';
|
|
|
|
},{}],10:[function(require,module,exports){
|
|
var DiffContext = require('../contexts/diff').DiffContext;
|
|
var PatchContext = require('../contexts/patch').PatchContext;
|
|
var ReverseContext = require('../contexts/reverse').ReverseContext;
|
|
|
|
var lcs = require('./lcs');
|
|
|
|
var ARRAY_MOVE = 3;
|
|
|
|
var isArray = (typeof Array.isArray === 'function') ?
|
|
// use native function
|
|
Array.isArray :
|
|
// use instanceof operator
|
|
function(a) {
|
|
return a instanceof Array;
|
|
};
|
|
|
|
var arrayIndexOf = typeof Array.prototype.indexOf === 'function' ?
|
|
function(array, item) {
|
|
return array.indexOf(item);
|
|
} : function(array, item) {
|
|
var length = array.length;
|
|
for (var i = 0; i < length; i++) {
|
|
if (array[i] === item) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
};
|
|
|
|
function arraysHaveMatchByRef(array1, array2, len1, len2) {
|
|
for (var index1 = 0; index1 < len1; index1++) {
|
|
var val1 = array1[index1];
|
|
for (var index2 = 0; index2 < len2; index2++) {
|
|
var val2 = array2[index2];
|
|
if (val1 === val2) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function matchItems(array1, array2, index1, index2, context) {
|
|
var value1 = array1[index1];
|
|
var value2 = array2[index2];
|
|
if (value1 === value2) {
|
|
return true;
|
|
}
|
|
if (typeof value1 !== 'object' || typeof value2 !== 'object') {
|
|
return false;
|
|
}
|
|
var objectHash = context.objectHash;
|
|
if (!objectHash) {
|
|
// no way to match objects was provided, try match by position
|
|
return context.matchByPosition && index1 === index2;
|
|
}
|
|
var hash1;
|
|
var hash2;
|
|
if (typeof index1 === 'number') {
|
|
context.hashCache1 = context.hashCache1 || [];
|
|
hash1 = context.hashCache1[index1];
|
|
if (typeof hash1 === 'undefined') {
|
|
context.hashCache1[index1] = hash1 = objectHash(value1, index1);
|
|
}
|
|
} else {
|
|
hash1 = objectHash(value1);
|
|
}
|
|
if (typeof hash1 === 'undefined') {
|
|
return false;
|
|
}
|
|
if (typeof index2 === 'number') {
|
|
context.hashCache2 = context.hashCache2 || [];
|
|
hash2 = context.hashCache2[index2];
|
|
if (typeof hash2 === 'undefined') {
|
|
context.hashCache2[index2] = hash2 = objectHash(value2, index2);
|
|
}
|
|
} else {
|
|
hash2 = objectHash(value2);
|
|
}
|
|
if (typeof hash2 === 'undefined') {
|
|
return false;
|
|
}
|
|
return hash1 === hash2;
|
|
}
|
|
|
|
var diffFilter = function arraysDiffFilter(context) {
|
|
if (!context.leftIsArray) {
|
|
return;
|
|
}
|
|
|
|
var matchContext = {
|
|
objectHash: context.options && context.options.objectHash,
|
|
matchByPosition: context.options && context.options.matchByPosition
|
|
};
|
|
var commonHead = 0;
|
|
var commonTail = 0;
|
|
var index;
|
|
var index1;
|
|
var index2;
|
|
var array1 = context.left;
|
|
var array2 = context.right;
|
|
var len1 = array1.length;
|
|
var len2 = array2.length;
|
|
|
|
var child;
|
|
|
|
if (len1 > 0 && len2 > 0 && !matchContext.objectHash &&
|
|
typeof matchContext.matchByPosition !== 'boolean') {
|
|
matchContext.matchByPosition = !arraysHaveMatchByRef(array1, array2, len1, len2);
|
|
}
|
|
|
|
// separate common head
|
|
while (commonHead < len1 && commonHead < len2 &&
|
|
matchItems(array1, array2, commonHead, commonHead, matchContext)) {
|
|
index = commonHead;
|
|
child = new DiffContext(context.left[index], context.right[index]);
|
|
context.push(child, index);
|
|
commonHead++;
|
|
}
|
|
// separate common tail
|
|
while (commonTail + commonHead < len1 && commonTail + commonHead < len2 &&
|
|
matchItems(array1, array2, len1 - 1 - commonTail, len2 - 1 - commonTail, matchContext)) {
|
|
index1 = len1 - 1 - commonTail;
|
|
index2 = len2 - 1 - commonTail;
|
|
child = new DiffContext(context.left[index1], context.right[index2]);
|
|
context.push(child, index2);
|
|
commonTail++;
|
|
}
|
|
var result;
|
|
if (commonHead + commonTail === len1) {
|
|
if (len1 === len2) {
|
|
// arrays are identical
|
|
context.setResult(undefined).exit();
|
|
return;
|
|
}
|
|
// trivial case, a block (1 or more consecutive items) was added
|
|
result = result || {
|
|
_t: 'a'
|
|
};
|
|
for (index = commonHead; index < len2 - commonTail; index++) {
|
|
result[index] = [array2[index]];
|
|
}
|
|
context.setResult(result).exit();
|
|
return;
|
|
}
|
|
if (commonHead + commonTail === len2) {
|
|
// trivial case, a block (1 or more consecutive items) was removed
|
|
result = result || {
|
|
_t: 'a'
|
|
};
|
|
for (index = commonHead; index < len1 - commonTail; index++) {
|
|
result['_' + index] = [array1[index], 0, 0];
|
|
}
|
|
context.setResult(result).exit();
|
|
return;
|
|
}
|
|
// reset hash cache
|
|
delete matchContext.hashCache1;
|
|
delete matchContext.hashCache2;
|
|
|
|
// diff is not trivial, find the LCS (Longest Common Subsequence)
|
|
var trimmed1 = array1.slice(commonHead, len1 - commonTail);
|
|
var trimmed2 = array2.slice(commonHead, len2 - commonTail);
|
|
var seq = lcs.get(
|
|
trimmed1, trimmed2,
|
|
matchItems,
|
|
matchContext
|
|
);
|
|
var removedItems = [];
|
|
result = result || {
|
|
_t: 'a'
|
|
};
|
|
for (index = commonHead; index < len1 - commonTail; index++) {
|
|
if (arrayIndexOf(seq.indices1, index - commonHead) < 0) {
|
|
// removed
|
|
result['_' + index] = [array1[index], 0, 0];
|
|
removedItems.push(index);
|
|
}
|
|
}
|
|
|
|
var detectMove = true;
|
|
if (context.options && context.options.arrays && context.options.arrays.detectMove === false) {
|
|
detectMove = false;
|
|
}
|
|
var includeValueOnMove = false;
|
|
if (context.options && context.options.arrays && context.options.arrays.includeValueOnMove) {
|
|
includeValueOnMove = true;
|
|
}
|
|
|
|
var removedItemsLength = removedItems.length;
|
|
for (index = commonHead; index < len2 - commonTail; index++) {
|
|
var indexOnArray2 = arrayIndexOf(seq.indices2, index - commonHead);
|
|
if (indexOnArray2 < 0) {
|
|
// added, try to match with a removed item and register as position move
|
|
var isMove = false;
|
|
if (detectMove && removedItemsLength > 0) {
|
|
for (var removeItemIndex1 = 0; removeItemIndex1 < removedItemsLength; removeItemIndex1++) {
|
|
index1 = removedItems[removeItemIndex1];
|
|
if (matchItems(trimmed1, trimmed2, index1 - commonHead,
|
|
index - commonHead, matchContext)) {
|
|
// store position move as: [originalValue, newPosition, ARRAY_MOVE]
|
|
result['_' + index1].splice(1, 2, index, ARRAY_MOVE);
|
|
if (!includeValueOnMove) {
|
|
// don't include moved value on diff, to save bytes
|
|
result['_' + index1][0] = '';
|
|
}
|
|
|
|
index2 = index;
|
|
child = new DiffContext(context.left[index1], context.right[index2]);
|
|
context.push(child, index2);
|
|
removedItems.splice(removeItemIndex1, 1);
|
|
isMove = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!isMove) {
|
|
// added
|
|
result[index] = [array2[index]];
|
|
}
|
|
} else {
|
|
// match, do inner diff
|
|
index1 = seq.indices1[indexOnArray2] + commonHead;
|
|
index2 = seq.indices2[indexOnArray2] + commonHead;
|
|
child = new DiffContext(context.left[index1], context.right[index2]);
|
|
context.push(child, index2);
|
|
}
|
|
}
|
|
|
|
context.setResult(result).exit();
|
|
|
|
};
|
|
diffFilter.filterName = 'arrays';
|
|
|
|
var compare = {
|
|
numerically: function(a, b) {
|
|
return a - b;
|
|
},
|
|
numericallyBy: function(name) {
|
|
return function(a, b) {
|
|
return a[name] - b[name];
|
|
};
|
|
}
|
|
};
|
|
|
|
var patchFilter = function nestedPatchFilter(context) {
|
|
if (!context.nested) {
|
|
return;
|
|
}
|
|
if (context.delta._t !== 'a') {
|
|
return;
|
|
}
|
|
var index, index1;
|
|
|
|
var delta = context.delta;
|
|
var array = context.left;
|
|
|
|
// first, separate removals, insertions and modifications
|
|
var toRemove = [];
|
|
var toInsert = [];
|
|
var toModify = [];
|
|
for (index in delta) {
|
|
if (index !== '_t') {
|
|
if (index[0] === '_') {
|
|
// removed item from original array
|
|
if (delta[index][2] === 0 || delta[index][2] === ARRAY_MOVE) {
|
|
toRemove.push(parseInt(index.slice(1), 10));
|
|
} else {
|
|
throw new Error('only removal or move can be applied at original array indices' +
|
|
', invalid diff type: ' + delta[index][2]);
|
|
}
|
|
} else {
|
|
if (delta[index].length === 1) {
|
|
// added item at new array
|
|
toInsert.push({
|
|
index: parseInt(index, 10),
|
|
value: delta[index][0]
|
|
});
|
|
} else {
|
|
// modified item at new array
|
|
toModify.push({
|
|
index: parseInt(index, 10),
|
|
delta: delta[index]
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// remove items, in reverse order to avoid sawing our own floor
|
|
toRemove = toRemove.sort(compare.numerically);
|
|
for (index = toRemove.length - 1; index >= 0; index--) {
|
|
index1 = toRemove[index];
|
|
var indexDiff = delta['_' + index1];
|
|
var removedValue = array.splice(index1, 1)[0];
|
|
if (indexDiff[2] === ARRAY_MOVE) {
|
|
// reinsert later
|
|
toInsert.push({
|
|
index: indexDiff[1],
|
|
value: removedValue
|
|
});
|
|
}
|
|
}
|
|
|
|
// insert items, in reverse order to avoid moving our own floor
|
|
toInsert = toInsert.sort(compare.numericallyBy('index'));
|
|
var toInsertLength = toInsert.length;
|
|
for (index = 0; index < toInsertLength; index++) {
|
|
var insertion = toInsert[index];
|
|
array.splice(insertion.index, 0, insertion.value);
|
|
}
|
|
|
|
// apply modifications
|
|
var toModifyLength = toModify.length;
|
|
var child;
|
|
if (toModifyLength > 0) {
|
|
for (index = 0; index < toModifyLength; index++) {
|
|
var modification = toModify[index];
|
|
child = new PatchContext(context.left[modification.index], modification.delta);
|
|
context.push(child, modification.index);
|
|
}
|
|
}
|
|
|
|
if (!context.children) {
|
|
context.setResult(context.left).exit();
|
|
return;
|
|
}
|
|
context.exit();
|
|
};
|
|
patchFilter.filterName = 'arrays';
|
|
|
|
var collectChildrenPatchFilter = function collectChildrenPatchFilter(context) {
|
|
if (!context || !context.children) {
|
|
return;
|
|
}
|
|
if (context.delta._t !== 'a') {
|
|
return;
|
|
}
|
|
var length = context.children.length;
|
|
var child;
|
|
for (var index = 0; index < length; index++) {
|
|
child = context.children[index];
|
|
context.left[child.childName] = child.result;
|
|
}
|
|
context.setResult(context.left).exit();
|
|
};
|
|
collectChildrenPatchFilter.filterName = 'arraysCollectChildren';
|
|
|
|
var reverseFilter = function arraysReverseFilter(context) {
|
|
if (!context.nested) {
|
|
if (context.delta[2] === ARRAY_MOVE) {
|
|
context.newName = '_' + context.delta[1];
|
|
context.setResult([context.delta[0], parseInt(context.childName.substr(1), 10), ARRAY_MOVE]).exit();
|
|
}
|
|
return;
|
|
}
|
|
if (context.delta._t !== 'a') {
|
|
return;
|
|
}
|
|
var name, child;
|
|
for (name in context.delta) {
|
|
if (name === '_t') {
|
|
continue;
|
|
}
|
|
child = new ReverseContext(context.delta[name]);
|
|
context.push(child, name);
|
|
}
|
|
context.exit();
|
|
};
|
|
reverseFilter.filterName = 'arrays';
|
|
|
|
var reverseArrayDeltaIndex = function(delta, index, itemDelta) {
|
|
if (typeof index === 'string' && index[0] === '_') {
|
|
return parseInt(index.substr(1), 10);
|
|
} else if (isArray(itemDelta) && itemDelta[2] === 0) {
|
|
return '_' + index;
|
|
}
|
|
|
|
var reverseIndex = +index;
|
|
for (var deltaIndex in delta) {
|
|
var deltaItem = delta[deltaIndex];
|
|
if (isArray(deltaItem)) {
|
|
if (deltaItem[2] === ARRAY_MOVE) {
|
|
var moveFromIndex = parseInt(deltaIndex.substr(1), 10);
|
|
var moveToIndex = deltaItem[1];
|
|
if (moveToIndex === +index) {
|
|
return moveFromIndex;
|
|
}
|
|
if (moveFromIndex <= reverseIndex && moveToIndex > reverseIndex) {
|
|
reverseIndex++;
|
|
} else if (moveFromIndex >= reverseIndex && moveToIndex < reverseIndex) {
|
|
reverseIndex--;
|
|
}
|
|
} else if (deltaItem[2] === 0) {
|
|
var deleteIndex = parseInt(deltaIndex.substr(1), 10);
|
|
if (deleteIndex <= reverseIndex) {
|
|
reverseIndex++;
|
|
}
|
|
} else if (deltaItem.length === 1 && deltaIndex <= reverseIndex) {
|
|
reverseIndex--;
|
|
}
|
|
}
|
|
}
|
|
|
|
return reverseIndex;
|
|
};
|
|
|
|
var collectChildrenReverseFilter = function collectChildrenReverseFilter(context) {
|
|
if (!context || !context.children) {
|
|
return;
|
|
}
|
|
if (context.delta._t !== 'a') {
|
|
return;
|
|
}
|
|
var length = context.children.length;
|
|
var child;
|
|
var delta = {
|
|
_t: 'a'
|
|
};
|
|
|
|
for (var index = 0; index < length; index++) {
|
|
child = context.children[index];
|
|
var name = child.newName;
|
|
if (typeof name === 'undefined') {
|
|
name = reverseArrayDeltaIndex(context.delta, child.childName, child.result);
|
|
}
|
|
if (delta[name] !== child.result) {
|
|
delta[name] = child.result;
|
|
}
|
|
}
|
|
context.setResult(delta).exit();
|
|
};
|
|
collectChildrenReverseFilter.filterName = 'arraysCollectChildren';
|
|
|
|
exports.diffFilter = diffFilter;
|
|
exports.patchFilter = patchFilter;
|
|
exports.collectChildrenPatchFilter = collectChildrenPatchFilter;
|
|
exports.reverseFilter = reverseFilter;
|
|
exports.collectChildrenReverseFilter = collectChildrenReverseFilter;
|
|
|
|
},{"../contexts/diff":4,"../contexts/patch":5,"../contexts/reverse":6,"./lcs":12}],11:[function(require,module,exports){
|
|
var diffFilter = function datesDiffFilter(context) {
|
|
if (context.left instanceof Date) {
|
|
if (context.right instanceof Date) {
|
|
if (context.left.getTime() !== context.right.getTime()) {
|
|
context.setResult([context.left, context.right]);
|
|
} else {
|
|
context.setResult(undefined);
|
|
}
|
|
} else {
|
|
context.setResult([context.left, context.right]);
|
|
}
|
|
context.exit();
|
|
} else if (context.right instanceof Date) {
|
|
context.setResult([context.left, context.right]).exit();
|
|
}
|
|
};
|
|
diffFilter.filterName = 'dates';
|
|
|
|
exports.diffFilter = diffFilter;
|
|
|
|
},{}],12:[function(require,module,exports){
|
|
/*
|
|
|
|
LCS implementation that supports arrays or strings
|
|
|
|
reference: http://en.wikipedia.org/wiki/Longest_common_subsequence_problem
|
|
|
|
*/
|
|
|
|
var defaultMatch = function(array1, array2, index1, index2) {
|
|
return array1[index1] === array2[index2];
|
|
};
|
|
|
|
var lengthMatrix = function(array1, array2, match, context) {
|
|
var len1 = array1.length;
|
|
var len2 = array2.length;
|
|
var x, y;
|
|
|
|
// initialize empty matrix of len1+1 x len2+1
|
|
var matrix = [len1 + 1];
|
|
for (x = 0; x < len1 + 1; x++) {
|
|
matrix[x] = [len2 + 1];
|
|
for (y = 0; y < len2 + 1; y++) {
|
|
matrix[x][y] = 0;
|
|
}
|
|
}
|
|
matrix.match = match;
|
|
// save sequence lengths for each coordinate
|
|
for (x = 1; x < len1 + 1; x++) {
|
|
for (y = 1; y < len2 + 1; y++) {
|
|
if (match(array1, array2, x - 1, y - 1, context)) {
|
|
matrix[x][y] = matrix[x - 1][y - 1] + 1;
|
|
} else {
|
|
matrix[x][y] = Math.max(matrix[x - 1][y], matrix[x][y - 1]);
|
|
}
|
|
}
|
|
}
|
|
return matrix;
|
|
};
|
|
|
|
var backtrack = function(matrix, array1, array2, index1, index2, context) {
|
|
if (index1 === 0 || index2 === 0) {
|
|
return {
|
|
sequence: [],
|
|
indices1: [],
|
|
indices2: []
|
|
};
|
|
}
|
|
|
|
if (matrix.match(array1, array2, index1 - 1, index2 - 1, context)) {
|
|
var subsequence = backtrack(matrix, array1, array2, index1 - 1, index2 - 1, context);
|
|
subsequence.sequence.push(array1[index1 - 1]);
|
|
subsequence.indices1.push(index1 - 1);
|
|
subsequence.indices2.push(index2 - 1);
|
|
return subsequence;
|
|
}
|
|
|
|
if (matrix[index1][index2 - 1] > matrix[index1 - 1][index2]) {
|
|
return backtrack(matrix, array1, array2, index1, index2 - 1, context);
|
|
} else {
|
|
return backtrack(matrix, array1, array2, index1 - 1, index2, context);
|
|
}
|
|
};
|
|
|
|
var get = function(array1, array2, match, context) {
|
|
context = context || {};
|
|
var matrix = lengthMatrix(array1, array2, match || defaultMatch, context);
|
|
var result = backtrack(matrix, array1, array2, array1.length, array2.length, context);
|
|
if (typeof array1 === 'string' && typeof array2 === 'string') {
|
|
result.sequence = result.sequence.join('');
|
|
}
|
|
return result;
|
|
};
|
|
|
|
exports.get = get;
|
|
|
|
},{}],13:[function(require,module,exports){
|
|
var DiffContext = require('../contexts/diff').DiffContext;
|
|
var PatchContext = require('../contexts/patch').PatchContext;
|
|
var ReverseContext = require('../contexts/reverse').ReverseContext;
|
|
|
|
var collectChildrenDiffFilter = function collectChildrenDiffFilter(context) {
|
|
if (!context || !context.children) {
|
|
return;
|
|
}
|
|
var length = context.children.length;
|
|
var child;
|
|
var result = context.result;
|
|
for (var index = 0; index < length; index++) {
|
|
child = context.children[index];
|
|
if (typeof child.result === 'undefined') {
|
|
continue;
|
|
}
|
|
result = result || {};
|
|
result[child.childName] = child.result;
|
|
}
|
|
if (result && context.leftIsArray) {
|
|
result._t = 'a';
|
|
}
|
|
context.setResult(result).exit();
|
|
};
|
|
collectChildrenDiffFilter.filterName = 'collectChildren';
|
|
|
|
var objectsDiffFilter = function objectsDiffFilter(context) {
|
|
if (context.leftIsArray || context.leftType !== 'object') {
|
|
return;
|
|
}
|
|
|
|
var name, child;
|
|
for (name in context.left) {
|
|
child = new DiffContext(context.left[name], context.right[name]);
|
|
context.push(child, name);
|
|
}
|
|
for (name in context.right) {
|
|
if (typeof context.left[name] === 'undefined') {
|
|
child = new DiffContext(undefined, context.right[name]);
|
|
context.push(child, name);
|
|
}
|
|
}
|
|
|
|
if (!context.children || context.children.length === 0) {
|
|
context.setResult(undefined).exit();
|
|
return;
|
|
}
|
|
context.exit();
|
|
};
|
|
objectsDiffFilter.filterName = 'objects';
|
|
|
|
var patchFilter = function nestedPatchFilter(context) {
|
|
if (!context.nested) {
|
|
return;
|
|
}
|
|
if (context.delta._t) {
|
|
return;
|
|
}
|
|
var name, child;
|
|
for (name in context.delta) {
|
|
child = new PatchContext(context.left[name], context.delta[name]);
|
|
context.push(child, name);
|
|
}
|
|
context.exit();
|
|
};
|
|
patchFilter.filterName = 'objects';
|
|
|
|
var collectChildrenPatchFilter = function collectChildrenPatchFilter(context) {
|
|
if (!context || !context.children) {
|
|
return;
|
|
}
|
|
if (context.delta._t) {
|
|
return;
|
|
}
|
|
var length = context.children.length;
|
|
var child;
|
|
for (var index = 0; index < length; index++) {
|
|
child = context.children[index];
|
|
if (context.left.hasOwnProperty(child.childName) && child.result === undefined) {
|
|
delete context.left[child.childName];
|
|
} else if (context.left[child.childName] !== child.result) {
|
|
context.left[child.childName] = child.result;
|
|
}
|
|
}
|
|
context.setResult(context.left).exit();
|
|
};
|
|
collectChildrenPatchFilter.filterName = 'collectChildren';
|
|
|
|
var reverseFilter = function nestedReverseFilter(context) {
|
|
if (!context.nested) {
|
|
return;
|
|
}
|
|
if (context.delta._t) {
|
|
return;
|
|
}
|
|
var name, child;
|
|
for (name in context.delta) {
|
|
child = new ReverseContext(context.delta[name]);
|
|
context.push(child, name);
|
|
}
|
|
context.exit();
|
|
};
|
|
reverseFilter.filterName = 'objects';
|
|
|
|
var collectChildrenReverseFilter = function collectChildrenReverseFilter(context) {
|
|
if (!context || !context.children) {
|
|
return;
|
|
}
|
|
if (context.delta._t) {
|
|
return;
|
|
}
|
|
var length = context.children.length;
|
|
var child;
|
|
var delta = {};
|
|
for (var index = 0; index < length; index++) {
|
|
child = context.children[index];
|
|
if (delta[child.childName] !== child.result) {
|
|
delta[child.childName] = child.result;
|
|
}
|
|
}
|
|
context.setResult(delta).exit();
|
|
};
|
|
collectChildrenReverseFilter.filterName = 'collectChildren';
|
|
|
|
exports.collectChildrenDiffFilter = collectChildrenDiffFilter;
|
|
exports.objectsDiffFilter = objectsDiffFilter;
|
|
exports.patchFilter = patchFilter;
|
|
exports.collectChildrenPatchFilter = collectChildrenPatchFilter;
|
|
exports.reverseFilter = reverseFilter;
|
|
exports.collectChildrenReverseFilter = collectChildrenReverseFilter;
|
|
|
|
},{"../contexts/diff":4,"../contexts/patch":5,"../contexts/reverse":6}],14:[function(require,module,exports){
|
|
/* global diff_match_patch */
|
|
var TEXT_DIFF = 2;
|
|
var DEFAULT_MIN_LENGTH = 60;
|
|
var cachedDiffPatch = null;
|
|
|
|
var getDiffMatchPatch = function() {
|
|
/*jshint camelcase: false */
|
|
|
|
if (!cachedDiffPatch) {
|
|
var instance;
|
|
if (typeof diff_match_patch !== 'undefined') {
|
|
// already loaded, probably a browser
|
|
instance = typeof diff_match_patch === 'function' ?
|
|
new diff_match_patch() : new diff_match_patch.diff_match_patch();
|
|
} else if (typeof require === 'function') {
|
|
try {
|
|
var dmpModuleName = 'diff_match_patch_uncompressed';
|
|
var dmp = require('../../public/external/' + dmpModuleName);
|
|
instance = new dmp.diff_match_patch();
|
|
} catch (err) {
|
|
instance = null;
|
|
}
|
|
}
|
|
if (!instance) {
|
|
var error = new Error('text diff_match_patch library not found');
|
|
error.diff_match_patch_not_found = true;
|
|
throw error;
|
|
}
|
|
cachedDiffPatch = {
|
|
diff: function(txt1, txt2) {
|
|
return instance.patch_toText(instance.patch_make(txt1, txt2));
|
|
},
|
|
patch: function(txt1, patch) {
|
|
var results = instance.patch_apply(instance.patch_fromText(patch), txt1);
|
|
for (var i = 0; i < results[1].length; i++) {
|
|
if (!results[1][i]) {
|
|
var error = new Error('text patch failed');
|
|
error.textPatchFailed = true;
|
|
}
|
|
}
|
|
return results[0];
|
|
}
|
|
};
|
|
}
|
|
return cachedDiffPatch;
|
|
};
|
|
|
|
var diffFilter = function textsDiffFilter(context) {
|
|
if (context.leftType !== 'string') {
|
|
return;
|
|
}
|
|
var minLength = (context.options && context.options.textDiff &&
|
|
context.options.textDiff.minLength) || DEFAULT_MIN_LENGTH;
|
|
if (context.left.length < minLength ||
|
|
context.right.length < minLength) {
|
|
context.setResult([context.left, context.right]).exit();
|
|
return;
|
|
}
|
|
// large text, use a text-diff algorithm
|
|
var diff = getDiffMatchPatch().diff;
|
|
context.setResult([diff(context.left, context.right), 0, TEXT_DIFF]).exit();
|
|
};
|
|
diffFilter.filterName = 'texts';
|
|
|
|
var patchFilter = function textsPatchFilter(context) {
|
|
if (context.nested) {
|
|
return;
|
|
}
|
|
if (context.delta[2] !== TEXT_DIFF) {
|
|
return;
|
|
}
|
|
|
|
// text-diff, use a text-patch algorithm
|
|
var patch = getDiffMatchPatch().patch;
|
|
context.setResult(patch(context.left, context.delta[0])).exit();
|
|
};
|
|
patchFilter.filterName = 'texts';
|
|
|
|
var textDeltaReverse = function(delta) {
|
|
var i, l, lines, line, lineTmp, header = null,
|
|
headerRegex = /^@@ +\-(\d+),(\d+) +\+(\d+),(\d+) +@@$/,
|
|
lineHeader, lineAdd, lineRemove;
|
|
lines = delta.split('\n');
|
|
for (i = 0, l = lines.length; i < l; i++) {
|
|
line = lines[i];
|
|
var lineStart = line.slice(0, 1);
|
|
if (lineStart === '@') {
|
|
header = headerRegex.exec(line);
|
|
lineHeader = i;
|
|
lineAdd = null;
|
|
lineRemove = null;
|
|
|
|
// fix header
|
|
lines[lineHeader] = '@@ -' + header[3] + ',' + header[4] + ' +' + header[1] + ',' + header[2] + ' @@';
|
|
} else if (lineStart === '+') {
|
|
lineAdd = i;
|
|
lines[i] = '-' + lines[i].slice(1);
|
|
if (lines[i - 1].slice(0, 1) === '+') {
|
|
// swap lines to keep default order (-+)
|
|
lineTmp = lines[i];
|
|
lines[i] = lines[i - 1];
|
|
lines[i - 1] = lineTmp;
|
|
}
|
|
} else if (lineStart === '-') {
|
|
lineRemove = i;
|
|
lines[i] = '+' + lines[i].slice(1);
|
|
}
|
|
}
|
|
return lines.join('\n');
|
|
};
|
|
|
|
var reverseFilter = function textsReverseFilter(context) {
|
|
if (context.nested) {
|
|
return;
|
|
}
|
|
if (context.delta[2] !== TEXT_DIFF) {
|
|
return;
|
|
}
|
|
|
|
// text-diff, use a text-diff algorithm
|
|
context.setResult([textDeltaReverse(context.delta[0]), 0, TEXT_DIFF]).exit();
|
|
};
|
|
reverseFilter.filterName = 'texts';
|
|
|
|
exports.diffFilter = diffFilter;
|
|
exports.patchFilter = patchFilter;
|
|
exports.reverseFilter = reverseFilter;
|
|
|
|
},{}],15:[function(require,module,exports){
|
|
var isArray = (typeof Array.isArray === 'function') ?
|
|
// use native function
|
|
Array.isArray :
|
|
// use instanceof operator
|
|
function(a) {
|
|
return a instanceof Array;
|
|
};
|
|
|
|
var diffFilter = function trivialMatchesDiffFilter(context) {
|
|
if (context.left === context.right) {
|
|
context.setResult(undefined).exit();
|
|
return;
|
|
}
|
|
if (typeof context.left === 'undefined') {
|
|
if (typeof context.right === 'function') {
|
|
throw new Error('functions are not supported');
|
|
}
|
|
context.setResult([context.right]).exit();
|
|
return;
|
|
}
|
|
if (typeof context.right === 'undefined') {
|
|
context.setResult([context.left, 0, 0]).exit();
|
|
return;
|
|
}
|
|
if (typeof context.left === 'function' || typeof context.right === 'function') {
|
|
throw new Error('functions are not supported');
|
|
}
|
|
context.leftType = context.left === null ? 'null' : typeof context.left;
|
|
context.rightType = context.right === null ? 'null' : typeof context.right;
|
|
if (context.leftType !== context.rightType) {
|
|
context.setResult([context.left, context.right]).exit();
|
|
return;
|
|
}
|
|
if (context.leftType === 'boolean' || context.leftType === 'number') {
|
|
context.setResult([context.left, context.right]).exit();
|
|
return;
|
|
}
|
|
if (context.leftType === 'object') {
|
|
context.leftIsArray = isArray(context.left);
|
|
}
|
|
if (context.rightType === 'object') {
|
|
context.rightIsArray = isArray(context.right);
|
|
}
|
|
if (context.leftIsArray !== context.rightIsArray) {
|
|
context.setResult([context.left, context.right]).exit();
|
|
return;
|
|
}
|
|
};
|
|
diffFilter.filterName = 'trivial';
|
|
|
|
var patchFilter = function trivialMatchesPatchFilter(context) {
|
|
if (typeof context.delta === 'undefined') {
|
|
context.setResult(context.left).exit();
|
|
return;
|
|
}
|
|
context.nested = !isArray(context.delta);
|
|
if (context.nested) {
|
|
return;
|
|
}
|
|
if (context.delta.length === 1) {
|
|
context.setResult(context.delta[0]).exit();
|
|
return;
|
|
}
|
|
if (context.delta.length === 2) {
|
|
context.setResult(context.delta[1]).exit();
|
|
return;
|
|
}
|
|
if (context.delta.length === 3 && context.delta[2] === 0) {
|
|
context.setResult(undefined).exit();
|
|
return;
|
|
}
|
|
};
|
|
patchFilter.filterName = 'trivial';
|
|
|
|
var reverseFilter = function trivialReferseFilter(context) {
|
|
if (typeof context.delta === 'undefined') {
|
|
context.setResult(context.delta).exit();
|
|
return;
|
|
}
|
|
context.nested = !isArray(context.delta);
|
|
if (context.nested) {
|
|
return;
|
|
}
|
|
if (context.delta.length === 1) {
|
|
context.setResult([context.delta[0], 0, 0]).exit();
|
|
return;
|
|
}
|
|
if (context.delta.length === 2) {
|
|
context.setResult([context.delta[1], context.delta[0]]).exit();
|
|
return;
|
|
}
|
|
if (context.delta.length === 3 && context.delta[2] === 0) {
|
|
context.setResult([context.delta[0]]).exit();
|
|
return;
|
|
}
|
|
};
|
|
reverseFilter.filterName = 'trivial';
|
|
|
|
exports.diffFilter = diffFilter;
|
|
exports.patchFilter = patchFilter;
|
|
exports.reverseFilter = reverseFilter;
|
|
|
|
},{}],16:[function(require,module,exports){
|
|
|
|
var environment = require('./environment');
|
|
|
|
var DiffPatcher = require('./diffpatcher').DiffPatcher;
|
|
exports.DiffPatcher = DiffPatcher;
|
|
|
|
exports.create = function(options){
|
|
return new DiffPatcher(options);
|
|
};
|
|
|
|
exports.dateReviver = require('./date-reviver');
|
|
|
|
var defaultInstance;
|
|
|
|
exports.diff = function() {
|
|
if (!defaultInstance) {
|
|
defaultInstance = new DiffPatcher();
|
|
}
|
|
return defaultInstance.diff.apply(defaultInstance, arguments);
|
|
};
|
|
|
|
exports.patch = function() {
|
|
if (!defaultInstance) {
|
|
defaultInstance = new DiffPatcher();
|
|
}
|
|
return defaultInstance.patch.apply(defaultInstance, arguments);
|
|
};
|
|
|
|
exports.unpatch = function() {
|
|
if (!defaultInstance) {
|
|
defaultInstance = new DiffPatcher();
|
|
}
|
|
return defaultInstance.unpatch.apply(defaultInstance, arguments);
|
|
};
|
|
|
|
exports.reverse = function() {
|
|
if (!defaultInstance) {
|
|
defaultInstance = new DiffPatcher();
|
|
}
|
|
return defaultInstance.reverse.apply(defaultInstance, arguments);
|
|
};
|
|
|
|
if (environment.isBrowser) {
|
|
exports.homepage = '{{package-homepage}}';
|
|
exports.version = '{{package-version}}';
|
|
} else {
|
|
var packageInfoModuleName = '../package.json';
|
|
var packageInfo = require(packageInfoModuleName);
|
|
exports.homepage = packageInfo.homepage;
|
|
exports.version = packageInfo.version;
|
|
|
|
var formatterModuleName = './formatters';
|
|
var formatters = require(formatterModuleName);
|
|
exports.formatters = formatters;
|
|
// shortcut for console
|
|
exports.console = formatters.console;
|
|
}
|
|
|
|
},{"./date-reviver":7,"./diffpatcher":8,"./environment":9}],17:[function(require,module,exports){
|
|
var Pipe = function Pipe(name) {
|
|
this.name = name;
|
|
this.filters = [];
|
|
};
|
|
|
|
Pipe.prototype.process = function(input) {
|
|
if (!this.processor) {
|
|
throw new Error('add this pipe to a processor before using it');
|
|
}
|
|
var debug = this.debug;
|
|
var length = this.filters.length;
|
|
var context = input;
|
|
for (var index = 0; index < length; index++) {
|
|
var filter = this.filters[index];
|
|
if (debug) {
|
|
this.log('filter: ' + filter.filterName);
|
|
}
|
|
filter(context);
|
|
if (typeof context === 'object' && context.exiting) {
|
|
context.exiting = false;
|
|
break;
|
|
}
|
|
}
|
|
if (!context.next && this.resultCheck) {
|
|
this.resultCheck(context);
|
|
}
|
|
};
|
|
|
|
Pipe.prototype.log = function(msg) {
|
|
console.log('[jsondiffpatch] ' + this.name + ' pipe, ' + msg);
|
|
};
|
|
|
|
Pipe.prototype.append = function() {
|
|
this.filters.push.apply(this.filters, arguments);
|
|
return this;
|
|
};
|
|
|
|
Pipe.prototype.prepend = function() {
|
|
this.filters.unshift.apply(this.filters, arguments);
|
|
return this;
|
|
};
|
|
|
|
Pipe.prototype.indexOf = function(filterName) {
|
|
if (!filterName) {
|
|
throw new Error('a filter name is required');
|
|
}
|
|
for (var index = 0; index < this.filters.length; index++) {
|
|
var filter = this.filters[index];
|
|
if (filter.filterName === filterName) {
|
|
return index;
|
|
}
|
|
}
|
|
throw new Error('filter not found: ' + filterName);
|
|
};
|
|
|
|
Pipe.prototype.list = function() {
|
|
var names = [];
|
|
for (var index = 0; index < this.filters.length; index++) {
|
|
var filter = this.filters[index];
|
|
names.push(filter.filterName);
|
|
}
|
|
return names;
|
|
};
|
|
|
|
Pipe.prototype.after = function(filterName) {
|
|
var index = this.indexOf(filterName);
|
|
var params = Array.prototype.slice.call(arguments, 1);
|
|
if (!params.length) {
|
|
throw new Error('a filter is required');
|
|
}
|
|
params.unshift(index + 1, 0);
|
|
Array.prototype.splice.apply(this.filters, params);
|
|
return this;
|
|
};
|
|
|
|
Pipe.prototype.before = function(filterName) {
|
|
var index = this.indexOf(filterName);
|
|
var params = Array.prototype.slice.call(arguments, 1);
|
|
if (!params.length) {
|
|
throw new Error('a filter is required');
|
|
}
|
|
params.unshift(index, 0);
|
|
Array.prototype.splice.apply(this.filters, params);
|
|
return this;
|
|
};
|
|
|
|
Pipe.prototype.clear = function() {
|
|
this.filters.length = 0;
|
|
return this;
|
|
};
|
|
|
|
Pipe.prototype.shouldHaveResult = function(should) {
|
|
if (should === false) {
|
|
this.resultCheck = null;
|
|
return;
|
|
}
|
|
if (this.resultCheck) {
|
|
return;
|
|
}
|
|
var pipe = this;
|
|
this.resultCheck = function(context) {
|
|
if (!context.hasResult) {
|
|
console.log(context);
|
|
var error = new Error(pipe.name + ' failed');
|
|
error.noResult = true;
|
|
throw error;
|
|
}
|
|
};
|
|
return this;
|
|
};
|
|
|
|
exports.Pipe = Pipe;
|
|
|
|
},{}],18:[function(require,module,exports){
|
|
|
|
var Processor = function Processor(options){
|
|
this.selfOptions = options;
|
|
this.pipes = {};
|
|
};
|
|
|
|
Processor.prototype.options = function(options) {
|
|
if (options) {
|
|
this.selfOptions = options;
|
|
}
|
|
return this.selfOptions;
|
|
};
|
|
|
|
Processor.prototype.pipe = function(name, pipe) {
|
|
if (typeof name === 'string') {
|
|
if (typeof pipe === 'undefined') {
|
|
return this.pipes[name];
|
|
} else {
|
|
this.pipes[name] = pipe;
|
|
}
|
|
}
|
|
if (name && name.name) {
|
|
pipe = name;
|
|
if (pipe.processor === this) { return pipe; }
|
|
this.pipes[pipe.name] = pipe;
|
|
}
|
|
pipe.processor = this;
|
|
return pipe;
|
|
};
|
|
|
|
Processor.prototype.process = function(input, pipe) {
|
|
var context = input;
|
|
context.options = this.options();
|
|
var nextPipe = pipe || input.pipe || 'default';
|
|
var lastPipe, lastContext;
|
|
while (nextPipe) {
|
|
if (typeof context.nextAfterChildren !== 'undefined') {
|
|
// children processed and coming back to parent
|
|
context.next = context.nextAfterChildren;
|
|
context.nextAfterChildren = null;
|
|
}
|
|
|
|
if (typeof nextPipe === 'string') {
|
|
nextPipe = this.pipe(nextPipe);
|
|
}
|
|
nextPipe.process(context);
|
|
lastContext = context;
|
|
lastPipe = nextPipe;
|
|
nextPipe = null;
|
|
if (context) {
|
|
if (context.next) {
|
|
context = context.next;
|
|
nextPipe = lastContext.nextPipe || context.pipe || lastPipe;
|
|
}
|
|
}
|
|
}
|
|
return context.hasResult ? context.result : undefined;
|
|
};
|
|
|
|
exports.Processor = Processor;
|
|
|
|
},{}],19:[function(require,module,exports){
|
|
/**
|
|
* lodash 3.2.0 (Custom Build) <https://lodash.com/>
|
|
* Build: `lodash modern modularize exports="npm" -o ./`
|
|
* Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
|
|
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
|
|
* Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
|
* Available under MIT license <https://lodash.com/license>
|
|
*/
|
|
var baseAssign = require('lodash._baseassign'),
|
|
createAssigner = require('lodash._createassigner'),
|
|
keys = require('lodash.keys');
|
|
|
|
/**
|
|
* A specialized version of `_.assign` for customizing assigned values without
|
|
* support for argument juggling, multiple sources, and `this` binding `customizer`
|
|
* functions.
|
|
*
|
|
* @private
|
|
* @param {Object} object The destination object.
|
|
* @param {Object} source The source object.
|
|
* @param {Function} customizer The function to customize assigned values.
|
|
* @returns {Object} Returns `object`.
|
|
*/
|
|
function assignWith(object, source, customizer) {
|
|
var index = -1,
|
|
props = keys(source),
|
|
length = props.length;
|
|
|
|
while (++index < length) {
|
|
var key = props[index],
|
|
value = object[key],
|
|
result = customizer(value, source[key], key, object, source);
|
|
|
|
if ((result === result ? (result !== value) : (value === value)) ||
|
|
(value === undefined && !(key in object))) {
|
|
object[key] = result;
|
|
}
|
|
}
|
|
return object;
|
|
}
|
|
|
|
/**
|
|
* Assigns own enumerable properties of source object(s) to the destination
|
|
* object. Subsequent sources overwrite property assignments of previous sources.
|
|
* If `customizer` is provided it is invoked to produce the assigned values.
|
|
* The `customizer` is bound to `thisArg` and invoked with five arguments:
|
|
* (objectValue, sourceValue, key, object, source).
|
|
*
|
|
* **Note:** This method mutates `object` and is based on
|
|
* [`Object.assign`](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object.assign).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @alias extend
|
|
* @category Object
|
|
* @param {Object} object The destination object.
|
|
* @param {...Object} [sources] The source objects.
|
|
* @param {Function} [customizer] The function to customize assigned values.
|
|
* @param {*} [thisArg] The `this` binding of `customizer`.
|
|
* @returns {Object} Returns `object`.
|
|
* @example
|
|
*
|
|
* _.assign({ 'user': 'barney' }, { 'age': 40 }, { 'user': 'fred' });
|
|
* // => { 'user': 'fred', 'age': 40 }
|
|
*
|
|
* // using a customizer callback
|
|
* var defaults = _.partialRight(_.assign, function(value, other) {
|
|
* return _.isUndefined(value) ? other : value;
|
|
* });
|
|
*
|
|
* defaults({ 'user': 'barney' }, { 'age': 36 }, { 'user': 'fred' });
|
|
* // => { 'user': 'barney', 'age': 36 }
|
|
*/
|
|
var assign = createAssigner(function(object, source, customizer) {
|
|
return customizer
|
|
? assignWith(object, source, customizer)
|
|
: baseAssign(object, source);
|
|
});
|
|
|
|
module.exports = assign;
|
|
|
|
},{"lodash._baseassign":20,"lodash._createassigner":22,"lodash.keys":26}],20:[function(require,module,exports){
|
|
/**
|
|
* lodash 3.2.0 (Custom Build) <https://lodash.com/>
|
|
* Build: `lodash modern modularize exports="npm" -o ./`
|
|
* Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
|
|
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
|
|
* Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
|
* Available under MIT license <https://lodash.com/license>
|
|
*/
|
|
var baseCopy = require('lodash._basecopy'),
|
|
keys = require('lodash.keys');
|
|
|
|
/**
|
|
* The base implementation of `_.assign` without support for argument juggling,
|
|
* multiple sources, and `customizer` functions.
|
|
*
|
|
* @private
|
|
* @param {Object} object The destination object.
|
|
* @param {Object} source The source object.
|
|
* @returns {Object} Returns `object`.
|
|
*/
|
|
function baseAssign(object, source) {
|
|
return source == null
|
|
? object
|
|
: baseCopy(source, keys(source), object);
|
|
}
|
|
|
|
module.exports = baseAssign;
|
|
|
|
},{"lodash._basecopy":21,"lodash.keys":26}],21:[function(require,module,exports){
|
|
/**
|
|
* lodash 3.0.1 (Custom Build) <https://lodash.com/>
|
|
* Build: `lodash modern modularize exports="npm" -o ./`
|
|
* Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
|
|
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
|
|
* Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
|
* Available under MIT license <https://lodash.com/license>
|
|
*/
|
|
|
|
/**
|
|
* Copies properties of `source` to `object`.
|
|
*
|
|
* @private
|
|
* @param {Object} source The object to copy properties from.
|
|
* @param {Array} props The property names to copy.
|
|
* @param {Object} [object={}] The object to copy properties to.
|
|
* @returns {Object} Returns `object`.
|
|
*/
|
|
function baseCopy(source, props, object) {
|
|
object || (object = {});
|
|
|
|
var index = -1,
|
|
length = props.length;
|
|
|
|
while (++index < length) {
|
|
var key = props[index];
|
|
object[key] = source[key];
|
|
}
|
|
return object;
|
|
}
|
|
|
|
module.exports = baseCopy;
|
|
|
|
},{}],22:[function(require,module,exports){
|
|
/**
|
|
* lodash 3.1.1 (Custom Build) <https://lodash.com/>
|
|
* Build: `lodash modern modularize exports="npm" -o ./`
|
|
* Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
|
|
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
|
|
* Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
|
* Available under MIT license <https://lodash.com/license>
|
|
*/
|
|
var bindCallback = require('lodash._bindcallback'),
|
|
isIterateeCall = require('lodash._isiterateecall'),
|
|
restParam = require('lodash.restparam');
|
|
|
|
/**
|
|
* Creates a function that assigns properties of source object(s) to a given
|
|
* destination object.
|
|
*
|
|
* **Note:** This function is used to create `_.assign`, `_.defaults`, and `_.merge`.
|
|
*
|
|
* @private
|
|
* @param {Function} assigner The function to assign values.
|
|
* @returns {Function} Returns the new assigner function.
|
|
*/
|
|
function createAssigner(assigner) {
|
|
return restParam(function(object, sources) {
|
|
var index = -1,
|
|
length = object == null ? 0 : sources.length,
|
|
customizer = length > 2 ? sources[length - 2] : undefined,
|
|
guard = length > 2 ? sources[2] : undefined,
|
|
thisArg = length > 1 ? sources[length - 1] : undefined;
|
|
|
|
if (typeof customizer == 'function') {
|
|
customizer = bindCallback(customizer, thisArg, 5);
|
|
length -= 2;
|
|
} else {
|
|
customizer = typeof thisArg == 'function' ? thisArg : undefined;
|
|
length -= (customizer ? 1 : 0);
|
|
}
|
|
if (guard && isIterateeCall(sources[0], sources[1], guard)) {
|
|
customizer = length < 3 ? undefined : customizer;
|
|
length = 1;
|
|
}
|
|
while (++index < length) {
|
|
var source = sources[index];
|
|
if (source) {
|
|
assigner(object, source, customizer);
|
|
}
|
|
}
|
|
return object;
|
|
});
|
|
}
|
|
|
|
module.exports = createAssigner;
|
|
|
|
},{"lodash._bindcallback":23,"lodash._isiterateecall":24,"lodash.restparam":25}],23:[function(require,module,exports){
|
|
/**
|
|
* lodash 3.0.1 (Custom Build) <https://lodash.com/>
|
|
* Build: `lodash modern modularize exports="npm" -o ./`
|
|
* Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
|
|
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
|
|
* Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
|
* Available under MIT license <https://lodash.com/license>
|
|
*/
|
|
|
|
/**
|
|
* A specialized version of `baseCallback` which only supports `this` binding
|
|
* and specifying the number of arguments to provide to `func`.
|
|
*
|
|
* @private
|
|
* @param {Function} func The function to bind.
|
|
* @param {*} thisArg The `this` binding of `func`.
|
|
* @param {number} [argCount] The number of arguments to provide to `func`.
|
|
* @returns {Function} Returns the callback.
|
|
*/
|
|
function bindCallback(func, thisArg, argCount) {
|
|
if (typeof func != 'function') {
|
|
return identity;
|
|
}
|
|
if (thisArg === undefined) {
|
|
return func;
|
|
}
|
|
switch (argCount) {
|
|
case 1: return function(value) {
|
|
return func.call(thisArg, value);
|
|
};
|
|
case 3: return function(value, index, collection) {
|
|
return func.call(thisArg, value, index, collection);
|
|
};
|
|
case 4: return function(accumulator, value, index, collection) {
|
|
return func.call(thisArg, accumulator, value, index, collection);
|
|
};
|
|
case 5: return function(value, other, key, object, source) {
|
|
return func.call(thisArg, value, other, key, object, source);
|
|
};
|
|
}
|
|
return function() {
|
|
return func.apply(thisArg, arguments);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* This method returns the first argument provided to it.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @category Utility
|
|
* @param {*} value Any value.
|
|
* @returns {*} Returns `value`.
|
|
* @example
|
|
*
|
|
* var object = { 'user': 'fred' };
|
|
*
|
|
* _.identity(object) === object;
|
|
* // => true
|
|
*/
|
|
function identity(value) {
|
|
return value;
|
|
}
|
|
|
|
module.exports = bindCallback;
|
|
|
|
},{}],24:[function(require,module,exports){
|
|
/**
|
|
* lodash 3.0.9 (Custom Build) <https://lodash.com/>
|
|
* Build: `lodash modern modularize exports="npm" -o ./`
|
|
* Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
|
|
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
|
|
* Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
|
* Available under MIT license <https://lodash.com/license>
|
|
*/
|
|
|
|
/** Used to detect unsigned integer values. */
|
|
var reIsUint = /^\d+$/;
|
|
|
|
/**
|
|
* Used as the [maximum length](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-number.max_safe_integer)
|
|
* of an array-like value.
|
|
*/
|
|
var MAX_SAFE_INTEGER = 9007199254740991;
|
|
|
|
/**
|
|
* The base implementation of `_.property` without support for deep paths.
|
|
*
|
|
* @private
|
|
* @param {string} key The key of the property to get.
|
|
* @returns {Function} Returns the new function.
|
|
*/
|
|
function baseProperty(key) {
|
|
return function(object) {
|
|
return object == null ? undefined : object[key];
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Gets the "length" property value of `object`.
|
|
*
|
|
* **Note:** This function is used to avoid a [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792)
|
|
* that affects Safari on at least iOS 8.1-8.3 ARM64.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to query.
|
|
* @returns {*} Returns the "length" value.
|
|
*/
|
|
var getLength = baseProperty('length');
|
|
|
|
/**
|
|
* Checks if `value` is array-like.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is array-like, else `false`.
|
|
*/
|
|
function isArrayLike(value) {
|
|
return value != null && isLength(getLength(value));
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is a valid array-like index.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to check.
|
|
* @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
|
|
* @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
|
|
*/
|
|
function isIndex(value, length) {
|
|
value = (typeof value == 'number' || reIsUint.test(value)) ? +value : -1;
|
|
length = length == null ? MAX_SAFE_INTEGER : length;
|
|
return value > -1 && value % 1 == 0 && value < length;
|
|
}
|
|
|
|
/**
|
|
* Checks if the provided arguments are from an iteratee call.
|
|
*
|
|
* @private
|
|
* @param {*} value The potential iteratee value argument.
|
|
* @param {*} index The potential iteratee index or key argument.
|
|
* @param {*} object The potential iteratee object argument.
|
|
* @returns {boolean} Returns `true` if the arguments are from an iteratee call, else `false`.
|
|
*/
|
|
function isIterateeCall(value, index, object) {
|
|
if (!isObject(object)) {
|
|
return false;
|
|
}
|
|
var type = typeof index;
|
|
if (type == 'number'
|
|
? (isArrayLike(object) && isIndex(index, object.length))
|
|
: (type == 'string' && index in object)) {
|
|
var other = object[index];
|
|
return value === value ? (value === other) : (other !== other);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is a valid array-like length.
|
|
*
|
|
* **Note:** This function is based on [`ToLength`](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength).
|
|
*
|
|
* @private
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
|
|
*/
|
|
function isLength(value) {
|
|
return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`.
|
|
* (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is an object, else `false`.
|
|
* @example
|
|
*
|
|
* _.isObject({});
|
|
* // => true
|
|
*
|
|
* _.isObject([1, 2, 3]);
|
|
* // => true
|
|
*
|
|
* _.isObject(1);
|
|
* // => false
|
|
*/
|
|
function isObject(value) {
|
|
// Avoid a V8 JIT bug in Chrome 19-20.
|
|
// See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
|
|
var type = typeof value;
|
|
return !!value && (type == 'object' || type == 'function');
|
|
}
|
|
|
|
module.exports = isIterateeCall;
|
|
|
|
},{}],25:[function(require,module,exports){
|
|
/**
|
|
* lodash 3.6.1 (Custom Build) <https://lodash.com/>
|
|
* Build: `lodash modern modularize exports="npm" -o ./`
|
|
* Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
|
|
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
|
|
* Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
|
* Available under MIT license <https://lodash.com/license>
|
|
*/
|
|
|
|
/** Used as the `TypeError` message for "Functions" methods. */
|
|
var FUNC_ERROR_TEXT = 'Expected a function';
|
|
|
|
/* Native method references for those with the same name as other `lodash` methods. */
|
|
var nativeMax = Math.max;
|
|
|
|
/**
|
|
* Creates a function that invokes `func` with the `this` binding of the
|
|
* created function and arguments from `start` and beyond provided as an array.
|
|
*
|
|
* **Note:** This method is based on the [rest parameter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @category Function
|
|
* @param {Function} func The function to apply a rest parameter to.
|
|
* @param {number} [start=func.length-1] The start position of the rest parameter.
|
|
* @returns {Function} Returns the new function.
|
|
* @example
|
|
*
|
|
* var say = _.restParam(function(what, names) {
|
|
* return what + ' ' + _.initial(names).join(', ') +
|
|
* (_.size(names) > 1 ? ', & ' : '') + _.last(names);
|
|
* });
|
|
*
|
|
* say('hello', 'fred', 'barney', 'pebbles');
|
|
* // => 'hello fred, barney, & pebbles'
|
|
*/
|
|
function restParam(func, start) {
|
|
if (typeof func != 'function') {
|
|
throw new TypeError(FUNC_ERROR_TEXT);
|
|
}
|
|
start = nativeMax(start === undefined ? (func.length - 1) : (+start || 0), 0);
|
|
return function() {
|
|
var args = arguments,
|
|
index = -1,
|
|
length = nativeMax(args.length - start, 0),
|
|
rest = Array(length);
|
|
|
|
while (++index < length) {
|
|
rest[index] = args[start + index];
|
|
}
|
|
switch (start) {
|
|
case 0: return func.call(this, rest);
|
|
case 1: return func.call(this, args[0], rest);
|
|
case 2: return func.call(this, args[0], args[1], rest);
|
|
}
|
|
var otherArgs = Array(start + 1);
|
|
index = -1;
|
|
while (++index < start) {
|
|
otherArgs[index] = args[index];
|
|
}
|
|
otherArgs[start] = rest;
|
|
return func.apply(this, otherArgs);
|
|
};
|
|
}
|
|
|
|
module.exports = restParam;
|
|
|
|
},{}],26:[function(require,module,exports){
|
|
/**
|
|
* lodash 3.1.2 (Custom Build) <https://lodash.com/>
|
|
* Build: `lodash modern modularize exports="npm" -o ./`
|
|
* Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
|
|
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
|
|
* Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
|
* Available under MIT license <https://lodash.com/license>
|
|
*/
|
|
var getNative = require('lodash._getnative'),
|
|
isArguments = require('lodash.isarguments'),
|
|
isArray = require('lodash.isarray');
|
|
|
|
/** Used to detect unsigned integer values. */
|
|
var reIsUint = /^\d+$/;
|
|
|
|
/** Used for native method references. */
|
|
var objectProto = Object.prototype;
|
|
|
|
/** Used to check objects for own properties. */
|
|
var hasOwnProperty = objectProto.hasOwnProperty;
|
|
|
|
/* Native method references for those with the same name as other `lodash` methods. */
|
|
var nativeKeys = getNative(Object, 'keys');
|
|
|
|
/**
|
|
* Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer)
|
|
* of an array-like value.
|
|
*/
|
|
var MAX_SAFE_INTEGER = 9007199254740991;
|
|
|
|
/**
|
|
* The base implementation of `_.property` without support for deep paths.
|
|
*
|
|
* @private
|
|
* @param {string} key The key of the property to get.
|
|
* @returns {Function} Returns the new function.
|
|
*/
|
|
function baseProperty(key) {
|
|
return function(object) {
|
|
return object == null ? undefined : object[key];
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Gets the "length" property value of `object`.
|
|
*
|
|
* **Note:** This function is used to avoid a [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792)
|
|
* that affects Safari on at least iOS 8.1-8.3 ARM64.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to query.
|
|
* @returns {*} Returns the "length" value.
|
|
*/
|
|
var getLength = baseProperty('length');
|
|
|
|
/**
|
|
* Checks if `value` is array-like.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is array-like, else `false`.
|
|
*/
|
|
function isArrayLike(value) {
|
|
return value != null && isLength(getLength(value));
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is a valid array-like index.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to check.
|
|
* @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
|
|
* @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
|
|
*/
|
|
function isIndex(value, length) {
|
|
value = (typeof value == 'number' || reIsUint.test(value)) ? +value : -1;
|
|
length = length == null ? MAX_SAFE_INTEGER : length;
|
|
return value > -1 && value % 1 == 0 && value < length;
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is a valid array-like length.
|
|
*
|
|
* **Note:** This function is based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength).
|
|
*
|
|
* @private
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
|
|
*/
|
|
function isLength(value) {
|
|
return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
|
|
}
|
|
|
|
/**
|
|
* A fallback implementation of `Object.keys` which creates an array of the
|
|
* own enumerable property names of `object`.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to query.
|
|
* @returns {Array} Returns the array of property names.
|
|
*/
|
|
function shimKeys(object) {
|
|
var props = keysIn(object),
|
|
propsLength = props.length,
|
|
length = propsLength && object.length;
|
|
|
|
var allowIndexes = !!length && isLength(length) &&
|
|
(isArray(object) || isArguments(object));
|
|
|
|
var index = -1,
|
|
result = [];
|
|
|
|
while (++index < propsLength) {
|
|
var key = props[index];
|
|
if ((allowIndexes && isIndex(key, length)) || hasOwnProperty.call(object, key)) {
|
|
result.push(key);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`.
|
|
* (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is an object, else `false`.
|
|
* @example
|
|
*
|
|
* _.isObject({});
|
|
* // => true
|
|
*
|
|
* _.isObject([1, 2, 3]);
|
|
* // => true
|
|
*
|
|
* _.isObject(1);
|
|
* // => false
|
|
*/
|
|
function isObject(value) {
|
|
// Avoid a V8 JIT bug in Chrome 19-20.
|
|
// See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
|
|
var type = typeof value;
|
|
return !!value && (type == 'object' || type == 'function');
|
|
}
|
|
|
|
/**
|
|
* Creates an array of the own enumerable property names of `object`.
|
|
*
|
|
* **Note:** Non-object values are coerced to objects. See the
|
|
* [ES spec](http://ecma-international.org/ecma-262/6.0/#sec-object.keys)
|
|
* for more details.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @category Object
|
|
* @param {Object} object The object to query.
|
|
* @returns {Array} Returns the array of property names.
|
|
* @example
|
|
*
|
|
* function Foo() {
|
|
* this.a = 1;
|
|
* this.b = 2;
|
|
* }
|
|
*
|
|
* Foo.prototype.c = 3;
|
|
*
|
|
* _.keys(new Foo);
|
|
* // => ['a', 'b'] (iteration order is not guaranteed)
|
|
*
|
|
* _.keys('hi');
|
|
* // => ['0', '1']
|
|
*/
|
|
var keys = !nativeKeys ? shimKeys : function(object) {
|
|
var Ctor = object == null ? undefined : object.constructor;
|
|
if ((typeof Ctor == 'function' && Ctor.prototype === object) ||
|
|
(typeof object != 'function' && isArrayLike(object))) {
|
|
return shimKeys(object);
|
|
}
|
|
return isObject(object) ? nativeKeys(object) : [];
|
|
};
|
|
|
|
/**
|
|
* Creates an array of the own and inherited enumerable property names of `object`.
|
|
*
|
|
* **Note:** Non-object values are coerced to objects.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @category Object
|
|
* @param {Object} object The object to query.
|
|
* @returns {Array} Returns the array of property names.
|
|
* @example
|
|
*
|
|
* function Foo() {
|
|
* this.a = 1;
|
|
* this.b = 2;
|
|
* }
|
|
*
|
|
* Foo.prototype.c = 3;
|
|
*
|
|
* _.keysIn(new Foo);
|
|
* // => ['a', 'b', 'c'] (iteration order is not guaranteed)
|
|
*/
|
|
function keysIn(object) {
|
|
if (object == null) {
|
|
return [];
|
|
}
|
|
if (!isObject(object)) {
|
|
object = Object(object);
|
|
}
|
|
var length = object.length;
|
|
length = (length && isLength(length) &&
|
|
(isArray(object) || isArguments(object)) && length) || 0;
|
|
|
|
var Ctor = object.constructor,
|
|
index = -1,
|
|
isProto = typeof Ctor == 'function' && Ctor.prototype === object,
|
|
result = Array(length),
|
|
skipIndexes = length > 0;
|
|
|
|
while (++index < length) {
|
|
result[index] = (index + '');
|
|
}
|
|
for (var key in object) {
|
|
if (!(skipIndexes && isIndex(key, length)) &&
|
|
!(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) {
|
|
result.push(key);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
module.exports = keys;
|
|
|
|
},{"lodash._getnative":27,"lodash.isarguments":28,"lodash.isarray":29}],27:[function(require,module,exports){
|
|
/**
|
|
* lodash 3.9.1 (Custom Build) <https://lodash.com/>
|
|
* Build: `lodash modern modularize exports="npm" -o ./`
|
|
* Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
|
|
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
|
|
* Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
|
* Available under MIT license <https://lodash.com/license>
|
|
*/
|
|
|
|
/** `Object#toString` result references. */
|
|
var funcTag = '[object Function]';
|
|
|
|
/** Used to detect host constructors (Safari > 5). */
|
|
var reIsHostCtor = /^\[object .+?Constructor\]$/;
|
|
|
|
/**
|
|
* Checks if `value` is object-like.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is object-like, else `false`.
|
|
*/
|
|
function isObjectLike(value) {
|
|
return !!value && typeof value == 'object';
|
|
}
|
|
|
|
/** Used for native method references. */
|
|
var objectProto = Object.prototype;
|
|
|
|
/** Used to resolve the decompiled source of functions. */
|
|
var fnToString = Function.prototype.toString;
|
|
|
|
/** Used to check objects for own properties. */
|
|
var hasOwnProperty = objectProto.hasOwnProperty;
|
|
|
|
/**
|
|
* Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
|
|
* of values.
|
|
*/
|
|
var objToString = objectProto.toString;
|
|
|
|
/** Used to detect if a method is native. */
|
|
var reIsNative = RegExp('^' +
|
|
fnToString.call(hasOwnProperty).replace(/[\\^$.*+?()[\]{}|]/g, '\\$&')
|
|
.replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'
|
|
);
|
|
|
|
/**
|
|
* Gets the native function at `key` of `object`.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to query.
|
|
* @param {string} key The key of the method to get.
|
|
* @returns {*} Returns the function if it's native, else `undefined`.
|
|
*/
|
|
function getNative(object, key) {
|
|
var value = object == null ? undefined : object[key];
|
|
return isNative(value) ? value : undefined;
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is classified as a `Function` object.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
|
|
* @example
|
|
*
|
|
* _.isFunction(_);
|
|
* // => true
|
|
*
|
|
* _.isFunction(/abc/);
|
|
* // => false
|
|
*/
|
|
function isFunction(value) {
|
|
// The use of `Object#toString` avoids issues with the `typeof` operator
|
|
// in older versions of Chrome and Safari which return 'function' for regexes
|
|
// and Safari 8 equivalents which return 'object' for typed array constructors.
|
|
return isObject(value) && objToString.call(value) == funcTag;
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`.
|
|
* (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is an object, else `false`.
|
|
* @example
|
|
*
|
|
* _.isObject({});
|
|
* // => true
|
|
*
|
|
* _.isObject([1, 2, 3]);
|
|
* // => true
|
|
*
|
|
* _.isObject(1);
|
|
* // => false
|
|
*/
|
|
function isObject(value) {
|
|
// Avoid a V8 JIT bug in Chrome 19-20.
|
|
// See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
|
|
var type = typeof value;
|
|
return !!value && (type == 'object' || type == 'function');
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is a native function.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a native function, else `false`.
|
|
* @example
|
|
*
|
|
* _.isNative(Array.prototype.push);
|
|
* // => true
|
|
*
|
|
* _.isNative(_);
|
|
* // => false
|
|
*/
|
|
function isNative(value) {
|
|
if (value == null) {
|
|
return false;
|
|
}
|
|
if (isFunction(value)) {
|
|
return reIsNative.test(fnToString.call(value));
|
|
}
|
|
return isObjectLike(value) && reIsHostCtor.test(value);
|
|
}
|
|
|
|
module.exports = getNative;
|
|
|
|
},{}],28:[function(require,module,exports){
|
|
/**
|
|
* lodash 3.0.4 (Custom Build) <https://lodash.com/>
|
|
* Build: `lodash modern modularize exports="npm" -o ./`
|
|
* Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
|
|
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
|
|
* Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
|
* Available under MIT license <https://lodash.com/license>
|
|
*/
|
|
|
|
/**
|
|
* Checks if `value` is object-like.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is object-like, else `false`.
|
|
*/
|
|
function isObjectLike(value) {
|
|
return !!value && typeof value == 'object';
|
|
}
|
|
|
|
/** Used for native method references. */
|
|
var objectProto = Object.prototype;
|
|
|
|
/** Used to check objects for own properties. */
|
|
var hasOwnProperty = objectProto.hasOwnProperty;
|
|
|
|
/** Native method references. */
|
|
var propertyIsEnumerable = objectProto.propertyIsEnumerable;
|
|
|
|
/**
|
|
* Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer)
|
|
* of an array-like value.
|
|
*/
|
|
var MAX_SAFE_INTEGER = 9007199254740991;
|
|
|
|
/**
|
|
* The base implementation of `_.property` without support for deep paths.
|
|
*
|
|
* @private
|
|
* @param {string} key The key of the property to get.
|
|
* @returns {Function} Returns the new function.
|
|
*/
|
|
function baseProperty(key) {
|
|
return function(object) {
|
|
return object == null ? undefined : object[key];
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Gets the "length" property value of `object`.
|
|
*
|
|
* **Note:** This function is used to avoid a [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792)
|
|
* that affects Safari on at least iOS 8.1-8.3 ARM64.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to query.
|
|
* @returns {*} Returns the "length" value.
|
|
*/
|
|
var getLength = baseProperty('length');
|
|
|
|
/**
|
|
* Checks if `value` is array-like.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is array-like, else `false`.
|
|
*/
|
|
function isArrayLike(value) {
|
|
return value != null && isLength(getLength(value));
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is a valid array-like length.
|
|
*
|
|
* **Note:** This function is based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength).
|
|
*
|
|
* @private
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
|
|
*/
|
|
function isLength(value) {
|
|
return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is classified as an `arguments` object.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
|
|
* @example
|
|
*
|
|
* _.isArguments(function() { return arguments; }());
|
|
* // => true
|
|
*
|
|
* _.isArguments([1, 2, 3]);
|
|
* // => false
|
|
*/
|
|
function isArguments(value) {
|
|
return isObjectLike(value) && isArrayLike(value) &&
|
|
hasOwnProperty.call(value, 'callee') && !propertyIsEnumerable.call(value, 'callee');
|
|
}
|
|
|
|
module.exports = isArguments;
|
|
|
|
},{}],29:[function(require,module,exports){
|
|
/**
|
|
* lodash 3.0.4 (Custom Build) <https://lodash.com/>
|
|
* Build: `lodash modern modularize exports="npm" -o ./`
|
|
* Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
|
|
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
|
|
* Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
|
* Available under MIT license <https://lodash.com/license>
|
|
*/
|
|
|
|
/** `Object#toString` result references. */
|
|
var arrayTag = '[object Array]',
|
|
funcTag = '[object Function]';
|
|
|
|
/** Used to detect host constructors (Safari > 5). */
|
|
var reIsHostCtor = /^\[object .+?Constructor\]$/;
|
|
|
|
/**
|
|
* Checks if `value` is object-like.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is object-like, else `false`.
|
|
*/
|
|
function isObjectLike(value) {
|
|
return !!value && typeof value == 'object';
|
|
}
|
|
|
|
/** Used for native method references. */
|
|
var objectProto = Object.prototype;
|
|
|
|
/** Used to resolve the decompiled source of functions. */
|
|
var fnToString = Function.prototype.toString;
|
|
|
|
/** Used to check objects for own properties. */
|
|
var hasOwnProperty = objectProto.hasOwnProperty;
|
|
|
|
/**
|
|
* Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
|
|
* of values.
|
|
*/
|
|
var objToString = objectProto.toString;
|
|
|
|
/** Used to detect if a method is native. */
|
|
var reIsNative = RegExp('^' +
|
|
fnToString.call(hasOwnProperty).replace(/[\\^$.*+?()[\]{}|]/g, '\\$&')
|
|
.replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'
|
|
);
|
|
|
|
/* Native method references for those with the same name as other `lodash` methods. */
|
|
var nativeIsArray = getNative(Array, 'isArray');
|
|
|
|
/**
|
|
* Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer)
|
|
* of an array-like value.
|
|
*/
|
|
var MAX_SAFE_INTEGER = 9007199254740991;
|
|
|
|
/**
|
|
* Gets the native function at `key` of `object`.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to query.
|
|
* @param {string} key The key of the method to get.
|
|
* @returns {*} Returns the function if it's native, else `undefined`.
|
|
*/
|
|
function getNative(object, key) {
|
|
var value = object == null ? undefined : object[key];
|
|
return isNative(value) ? value : undefined;
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is a valid array-like length.
|
|
*
|
|
* **Note:** This function is based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength).
|
|
*
|
|
* @private
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
|
|
*/
|
|
function isLength(value) {
|
|
return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is classified as an `Array` object.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
|
|
* @example
|
|
*
|
|
* _.isArray([1, 2, 3]);
|
|
* // => true
|
|
*
|
|
* _.isArray(function() { return arguments; }());
|
|
* // => false
|
|
*/
|
|
var isArray = nativeIsArray || function(value) {
|
|
return isObjectLike(value) && isLength(value.length) && objToString.call(value) == arrayTag;
|
|
};
|
|
|
|
/**
|
|
* Checks if `value` is classified as a `Function` object.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
|
|
* @example
|
|
*
|
|
* _.isFunction(_);
|
|
* // => true
|
|
*
|
|
* _.isFunction(/abc/);
|
|
* // => false
|
|
*/
|
|
function isFunction(value) {
|
|
// The use of `Object#toString` avoids issues with the `typeof` operator
|
|
// in older versions of Chrome and Safari which return 'function' for regexes
|
|
// and Safari 8 equivalents which return 'object' for typed array constructors.
|
|
return isObject(value) && objToString.call(value) == funcTag;
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`.
|
|
* (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is an object, else `false`.
|
|
* @example
|
|
*
|
|
* _.isObject({});
|
|
* // => true
|
|
*
|
|
* _.isObject([1, 2, 3]);
|
|
* // => true
|
|
*
|
|
* _.isObject(1);
|
|
* // => false
|
|
*/
|
|
function isObject(value) {
|
|
// Avoid a V8 JIT bug in Chrome 19-20.
|
|
// See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
|
|
var type = typeof value;
|
|
return !!value && (type == 'object' || type == 'function');
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is a native function.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a native function, else `false`.
|
|
* @example
|
|
*
|
|
* _.isNative(Array.prototype.push);
|
|
* // => true
|
|
*
|
|
* _.isNative(_);
|
|
* // => false
|
|
*/
|
|
function isNative(value) {
|
|
if (value == null) {
|
|
return false;
|
|
}
|
|
if (isFunction(value)) {
|
|
return reIsNative.test(fnToString.call(value));
|
|
}
|
|
return isObjectLike(value) && reIsHostCtor.test(value);
|
|
}
|
|
|
|
module.exports = isArray;
|
|
|
|
},{}],30:[function(require,module,exports){
|
|
/**
|
|
* lodash 3.1.0 (Custom Build) <https://lodash.com/>
|
|
* Build: `lodash modern modularize exports="npm" -o ./`
|
|
* Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
|
|
* Based on Underscore.js 1.8.2 <http://underscorejs.org/LICENSE>
|
|
* Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
|
* Available under MIT license <https://lodash.com/license>
|
|
*/
|
|
var createWrapper = require('lodash._createwrapper'),
|
|
replaceHolders = require('lodash._replaceholders'),
|
|
restParam = require('lodash.restparam');
|
|
|
|
/** Used to compose bitmasks for wrapper metadata. */
|
|
var BIND_FLAG = 1,
|
|
PARTIAL_FLAG = 32;
|
|
|
|
/**
|
|
* Creates a function that invokes `func` with the `this` binding of `thisArg`
|
|
* and prepends any additional `_.bind` arguments to those provided to the
|
|
* bound function.
|
|
*
|
|
* The `_.bind.placeholder` value, which defaults to `_` in monolithic builds,
|
|
* may be used as a placeholder for partially applied arguments.
|
|
*
|
|
* **Note:** Unlike native `Function#bind` this method does not set the `length`
|
|
* property of bound functions.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @category Function
|
|
* @param {Function} func The function to bind.
|
|
* @param {*} thisArg The `this` binding of `func`.
|
|
* @param {...*} [partials] The arguments to be partially applied.
|
|
* @returns {Function} Returns the new bound function.
|
|
* @example
|
|
*
|
|
* var greet = function(greeting, punctuation) {
|
|
* return greeting + ' ' + this.user + punctuation;
|
|
* };
|
|
*
|
|
* var object = { 'user': 'fred' };
|
|
*
|
|
* var bound = _.bind(greet, object, 'hi');
|
|
* bound('!');
|
|
* // => 'hi fred!'
|
|
*
|
|
* // using placeholders
|
|
* var bound = _.bind(greet, object, _, '!');
|
|
* bound('hi');
|
|
* // => 'hi fred!'
|
|
*/
|
|
var bind = restParam(function(func, thisArg, partials) {
|
|
var bitmask = BIND_FLAG;
|
|
if (partials.length) {
|
|
var holders = replaceHolders(partials, bind.placeholder);
|
|
bitmask |= PARTIAL_FLAG;
|
|
}
|
|
return createWrapper(func, bitmask, thisArg, partials, holders);
|
|
});
|
|
|
|
// Assign default placeholders.
|
|
bind.placeholder = {};
|
|
|
|
module.exports = bind;
|
|
|
|
},{"lodash._createwrapper":31,"lodash._replaceholders":34,"lodash.restparam":35}],31:[function(require,module,exports){
|
|
(function (global){
|
|
/**
|
|
* lodash 3.0.7 (Custom Build) <https://lodash.com/>
|
|
* Build: `lodash modern modularize exports="npm" -o ./`
|
|
* Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
|
|
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
|
|
* Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
|
* Available under MIT license <https://lodash.com/license>
|
|
*/
|
|
var arrayCopy = require('lodash._arraycopy'),
|
|
baseCreate = require('lodash._basecreate'),
|
|
replaceHolders = require('lodash._replaceholders');
|
|
|
|
/** Used to compose bitmasks for wrapper metadata. */
|
|
var BIND_FLAG = 1,
|
|
BIND_KEY_FLAG = 2,
|
|
CURRY_BOUND_FLAG = 4,
|
|
CURRY_FLAG = 8,
|
|
CURRY_RIGHT_FLAG = 16,
|
|
PARTIAL_FLAG = 32,
|
|
PARTIAL_RIGHT_FLAG = 64,
|
|
ARY_FLAG = 128;
|
|
|
|
/** Used as the `TypeError` message for "Functions" methods. */
|
|
var FUNC_ERROR_TEXT = 'Expected a function';
|
|
|
|
/** Used to detect unsigned integer values. */
|
|
var reIsUint = /^\d+$/;
|
|
|
|
/* Native method references for those with the same name as other `lodash` methods. */
|
|
var nativeMax = Math.max,
|
|
nativeMin = Math.min;
|
|
|
|
/**
|
|
* Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer)
|
|
* of an array-like value.
|
|
*/
|
|
var MAX_SAFE_INTEGER = 9007199254740991;
|
|
|
|
/**
|
|
* Creates an array that is the composition of partially applied arguments,
|
|
* placeholders, and provided arguments into a single array of arguments.
|
|
*
|
|
* @private
|
|
* @param {Array|Object} args The provided arguments.
|
|
* @param {Array} partials The arguments to prepend to those provided.
|
|
* @param {Array} holders The `partials` placeholder indexes.
|
|
* @returns {Array} Returns the new array of composed arguments.
|
|
*/
|
|
function composeArgs(args, partials, holders) {
|
|
var holdersLength = holders.length,
|
|
argsIndex = -1,
|
|
argsLength = nativeMax(args.length - holdersLength, 0),
|
|
leftIndex = -1,
|
|
leftLength = partials.length,
|
|
result = Array(leftLength + argsLength);
|
|
|
|
while (++leftIndex < leftLength) {
|
|
result[leftIndex] = partials[leftIndex];
|
|
}
|
|
while (++argsIndex < holdersLength) {
|
|
result[holders[argsIndex]] = args[argsIndex];
|
|
}
|
|
while (argsLength--) {
|
|
result[leftIndex++] = args[argsIndex++];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* This function is like `composeArgs` except that the arguments composition
|
|
* is tailored for `_.partialRight`.
|
|
*
|
|
* @private
|
|
* @param {Array|Object} args The provided arguments.
|
|
* @param {Array} partials The arguments to append to those provided.
|
|
* @param {Array} holders The `partials` placeholder indexes.
|
|
* @returns {Array} Returns the new array of composed arguments.
|
|
*/
|
|
function composeArgsRight(args, partials, holders) {
|
|
var holdersIndex = -1,
|
|
holdersLength = holders.length,
|
|
argsIndex = -1,
|
|
argsLength = nativeMax(args.length - holdersLength, 0),
|
|
rightIndex = -1,
|
|
rightLength = partials.length,
|
|
result = Array(argsLength + rightLength);
|
|
|
|
while (++argsIndex < argsLength) {
|
|
result[argsIndex] = args[argsIndex];
|
|
}
|
|
var offset = argsIndex;
|
|
while (++rightIndex < rightLength) {
|
|
result[offset + rightIndex] = partials[rightIndex];
|
|
}
|
|
while (++holdersIndex < holdersLength) {
|
|
result[offset + holders[holdersIndex]] = args[argsIndex++];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Creates a function that wraps `func` and invokes it with the `this`
|
|
* binding of `thisArg`.
|
|
*
|
|
* @private
|
|
* @param {Function} func The function to bind.
|
|
* @param {*} [thisArg] The `this` binding of `func`.
|
|
* @returns {Function} Returns the new bound function.
|
|
*/
|
|
function createBindWrapper(func, thisArg) {
|
|
var Ctor = createCtorWrapper(func);
|
|
|
|
function wrapper() {
|
|
var fn = (this && this !== global && this instanceof wrapper) ? Ctor : func;
|
|
return fn.apply(thisArg, arguments);
|
|
}
|
|
return wrapper;
|
|
}
|
|
|
|
/**
|
|
* Creates a function that produces an instance of `Ctor` regardless of
|
|
* whether it was invoked as part of a `new` expression or by `call` or `apply`.
|
|
*
|
|
* @private
|
|
* @param {Function} Ctor The constructor to wrap.
|
|
* @returns {Function} Returns the new wrapped function.
|
|
*/
|
|
function createCtorWrapper(Ctor) {
|
|
return function() {
|
|
// Use a `switch` statement to work with class constructors.
|
|
// See http://ecma-international.org/ecma-262/6.0/#sec-ecmascript-function-objects-call-thisargument-argumentslist
|
|
// for more details.
|
|
var args = arguments;
|
|
switch (args.length) {
|
|
case 0: return new Ctor;
|
|
case 1: return new Ctor(args[0]);
|
|
case 2: return new Ctor(args[0], args[1]);
|
|
case 3: return new Ctor(args[0], args[1], args[2]);
|
|
case 4: return new Ctor(args[0], args[1], args[2], args[3]);
|
|
case 5: return new Ctor(args[0], args[1], args[2], args[3], args[4]);
|
|
case 6: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5]);
|
|
case 7: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
|
|
}
|
|
var thisBinding = baseCreate(Ctor.prototype),
|
|
result = Ctor.apply(thisBinding, args);
|
|
|
|
// Mimic the constructor's `return` behavior.
|
|
// See https://es5.github.io/#x13.2.2 for more details.
|
|
return isObject(result) ? result : thisBinding;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Creates a function that wraps `func` and invokes it with optional `this`
|
|
* binding of, partial application, and currying.
|
|
*
|
|
* @private
|
|
* @param {Function|string} func The function or method name to reference.
|
|
* @param {number} bitmask The bitmask of flags. See `createWrapper` for more details.
|
|
* @param {*} [thisArg] The `this` binding of `func`.
|
|
* @param {Array} [partials] The arguments to prepend to those provided to the new function.
|
|
* @param {Array} [holders] The `partials` placeholder indexes.
|
|
* @param {Array} [partialsRight] The arguments to append to those provided to the new function.
|
|
* @param {Array} [holdersRight] The `partialsRight` placeholder indexes.
|
|
* @param {Array} [argPos] The argument positions of the new function.
|
|
* @param {number} [ary] The arity cap of `func`.
|
|
* @param {number} [arity] The arity of `func`.
|
|
* @returns {Function} Returns the new wrapped function.
|
|
*/
|
|
function createHybridWrapper(func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity) {
|
|
var isAry = bitmask & ARY_FLAG,
|
|
isBind = bitmask & BIND_FLAG,
|
|
isBindKey = bitmask & BIND_KEY_FLAG,
|
|
isCurry = bitmask & CURRY_FLAG,
|
|
isCurryBound = bitmask & CURRY_BOUND_FLAG,
|
|
isCurryRight = bitmask & CURRY_RIGHT_FLAG,
|
|
Ctor = isBindKey ? undefined : createCtorWrapper(func);
|
|
|
|
function wrapper() {
|
|
// Avoid `arguments` object use disqualifying optimizations by
|
|
// converting it to an array before providing it to other functions.
|
|
var length = arguments.length,
|
|
index = length,
|
|
args = Array(length);
|
|
|
|
while (index--) {
|
|
args[index] = arguments[index];
|
|
}
|
|
if (partials) {
|
|
args = composeArgs(args, partials, holders);
|
|
}
|
|
if (partialsRight) {
|
|
args = composeArgsRight(args, partialsRight, holdersRight);
|
|
}
|
|
if (isCurry || isCurryRight) {
|
|
var placeholder = wrapper.placeholder,
|
|
argsHolders = replaceHolders(args, placeholder);
|
|
|
|
length -= argsHolders.length;
|
|
if (length < arity) {
|
|
var newArgPos = argPos ? arrayCopy(argPos) : undefined,
|
|
newArity = nativeMax(arity - length, 0),
|
|
newsHolders = isCurry ? argsHolders : undefined,
|
|
newHoldersRight = isCurry ? undefined : argsHolders,
|
|
newPartials = isCurry ? args : undefined,
|
|
newPartialsRight = isCurry ? undefined : args;
|
|
|
|
bitmask |= (isCurry ? PARTIAL_FLAG : PARTIAL_RIGHT_FLAG);
|
|
bitmask &= ~(isCurry ? PARTIAL_RIGHT_FLAG : PARTIAL_FLAG);
|
|
|
|
if (!isCurryBound) {
|
|
bitmask &= ~(BIND_FLAG | BIND_KEY_FLAG);
|
|
}
|
|
var result = createHybridWrapper(func, bitmask, thisArg, newPartials, newsHolders, newPartialsRight, newHoldersRight, newArgPos, ary, newArity);
|
|
|
|
result.placeholder = placeholder;
|
|
return result;
|
|
}
|
|
}
|
|
var thisBinding = isBind ? thisArg : this,
|
|
fn = isBindKey ? thisBinding[func] : func;
|
|
|
|
if (argPos) {
|
|
args = reorder(args, argPos);
|
|
}
|
|
if (isAry && ary < args.length) {
|
|
args.length = ary;
|
|
}
|
|
if (this && this !== global && this instanceof wrapper) {
|
|
fn = Ctor || createCtorWrapper(func);
|
|
}
|
|
return fn.apply(thisBinding, args);
|
|
}
|
|
return wrapper;
|
|
}
|
|
|
|
/**
|
|
* Creates a function that wraps `func` and invokes it with the optional `this`
|
|
* binding of `thisArg` and the `partials` prepended to those provided to
|
|
* the wrapper.
|
|
*
|
|
* @private
|
|
* @param {Function} func The function to partially apply arguments to.
|
|
* @param {number} bitmask The bitmask of flags. See `createWrapper` for more details.
|
|
* @param {*} thisArg The `this` binding of `func`.
|
|
* @param {Array} partials The arguments to prepend to those provided to the new function.
|
|
* @returns {Function} Returns the new bound function.
|
|
*/
|
|
function createPartialWrapper(func, bitmask, thisArg, partials) {
|
|
var isBind = bitmask & BIND_FLAG,
|
|
Ctor = createCtorWrapper(func);
|
|
|
|
function wrapper() {
|
|
// Avoid `arguments` object use disqualifying optimizations by
|
|
// converting it to an array before providing it `func`.
|
|
var argsIndex = -1,
|
|
argsLength = arguments.length,
|
|
leftIndex = -1,
|
|
leftLength = partials.length,
|
|
args = Array(leftLength + argsLength);
|
|
|
|
while (++leftIndex < leftLength) {
|
|
args[leftIndex] = partials[leftIndex];
|
|
}
|
|
while (argsLength--) {
|
|
args[leftIndex++] = arguments[++argsIndex];
|
|
}
|
|
var fn = (this && this !== global && this instanceof wrapper) ? Ctor : func;
|
|
return fn.apply(isBind ? thisArg : this, args);
|
|
}
|
|
return wrapper;
|
|
}
|
|
|
|
/**
|
|
* Creates a function that either curries or invokes `func` with optional
|
|
* `this` binding and partially applied arguments.
|
|
*
|
|
* @private
|
|
* @param {Function|string} func The function or method name to reference.
|
|
* @param {number} bitmask The bitmask of flags.
|
|
* The bitmask may be composed of the following flags:
|
|
* 1 - `_.bind`
|
|
* 2 - `_.bindKey`
|
|
* 4 - `_.curry` or `_.curryRight` of a bound function
|
|
* 8 - `_.curry`
|
|
* 16 - `_.curryRight`
|
|
* 32 - `_.partial`
|
|
* 64 - `_.partialRight`
|
|
* 128 - `_.rearg`
|
|
* 256 - `_.ary`
|
|
* @param {*} [thisArg] The `this` binding of `func`.
|
|
* @param {Array} [partials] The arguments to be partially applied.
|
|
* @param {Array} [holders] The `partials` placeholder indexes.
|
|
* @param {Array} [argPos] The argument positions of the new function.
|
|
* @param {number} [ary] The arity cap of `func`.
|
|
* @param {number} [arity] The arity of `func`.
|
|
* @returns {Function} Returns the new wrapped function.
|
|
*/
|
|
function createWrapper(func, bitmask, thisArg, partials, holders, argPos, ary, arity) {
|
|
var isBindKey = bitmask & BIND_KEY_FLAG;
|
|
if (!isBindKey && typeof func != 'function') {
|
|
throw new TypeError(FUNC_ERROR_TEXT);
|
|
}
|
|
var length = partials ? partials.length : 0;
|
|
if (!length) {
|
|
bitmask &= ~(PARTIAL_FLAG | PARTIAL_RIGHT_FLAG);
|
|
partials = holders = undefined;
|
|
}
|
|
length -= (holders ? holders.length : 0);
|
|
if (bitmask & PARTIAL_RIGHT_FLAG) {
|
|
var partialsRight = partials,
|
|
holdersRight = holders;
|
|
|
|
partials = holders = undefined;
|
|
}
|
|
var newData = [func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity];
|
|
|
|
newData[9] = arity == null
|
|
? (isBindKey ? 0 : func.length)
|
|
: (nativeMax(arity - length, 0) || 0);
|
|
|
|
if (bitmask == BIND_FLAG) {
|
|
var result = createBindWrapper(newData[0], newData[2]);
|
|
} else if ((bitmask == PARTIAL_FLAG || bitmask == (BIND_FLAG | PARTIAL_FLAG)) && !newData[4].length) {
|
|
result = createPartialWrapper.apply(undefined, newData);
|
|
} else {
|
|
result = createHybridWrapper.apply(undefined, newData);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is a valid array-like index.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to check.
|
|
* @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
|
|
* @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
|
|
*/
|
|
function isIndex(value, length) {
|
|
value = (typeof value == 'number' || reIsUint.test(value)) ? +value : -1;
|
|
length = length == null ? MAX_SAFE_INTEGER : length;
|
|
return value > -1 && value % 1 == 0 && value < length;
|
|
}
|
|
|
|
/**
|
|
* Reorder `array` according to the specified indexes where the element at
|
|
* the first index is assigned as the first element, the element at
|
|
* the second index is assigned as the second element, and so on.
|
|
*
|
|
* @private
|
|
* @param {Array} array The array to reorder.
|
|
* @param {Array} indexes The arranged array indexes.
|
|
* @returns {Array} Returns `array`.
|
|
*/
|
|
function reorder(array, indexes) {
|
|
var arrLength = array.length,
|
|
length = nativeMin(indexes.length, arrLength),
|
|
oldArray = arrayCopy(array);
|
|
|
|
while (length--) {
|
|
var index = indexes[length];
|
|
array[length] = isIndex(index, arrLength) ? oldArray[index] : undefined;
|
|
}
|
|
return array;
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`.
|
|
* (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is an object, else `false`.
|
|
* @example
|
|
*
|
|
* _.isObject({});
|
|
* // => true
|
|
*
|
|
* _.isObject([1, 2, 3]);
|
|
* // => true
|
|
*
|
|
* _.isObject(1);
|
|
* // => false
|
|
*/
|
|
function isObject(value) {
|
|
// Avoid a V8 JIT bug in Chrome 19-20.
|
|
// See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
|
|
var type = typeof value;
|
|
return !!value && (type == 'object' || type == 'function');
|
|
}
|
|
|
|
module.exports = createWrapper;
|
|
|
|
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
|
},{"lodash._arraycopy":32,"lodash._basecreate":33,"lodash._replaceholders":34}],32:[function(require,module,exports){
|
|
/**
|
|
* lodash 3.0.0 (Custom Build) <https://lodash.com/>
|
|
* Build: `lodash modern modularize exports="npm" -o ./`
|
|
* Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
|
|
* Based on Underscore.js 1.7.0 <http://underscorejs.org/LICENSE>
|
|
* Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
|
* Available under MIT license <https://lodash.com/license>
|
|
*/
|
|
|
|
/**
|
|
* Copies the values of `source` to `array`.
|
|
*
|
|
* @private
|
|
* @param {Array} source The array to copy values from.
|
|
* @param {Array} [array=[]] The array to copy values to.
|
|
* @returns {Array} Returns `array`.
|
|
*/
|
|
function arrayCopy(source, array) {
|
|
var index = -1,
|
|
length = source.length;
|
|
|
|
array || (array = Array(length));
|
|
while (++index < length) {
|
|
array[index] = source[index];
|
|
}
|
|
return array;
|
|
}
|
|
|
|
module.exports = arrayCopy;
|
|
|
|
},{}],33:[function(require,module,exports){
|
|
/**
|
|
* lodash 3.0.3 (Custom Build) <https://lodash.com/>
|
|
* Build: `lodash modern modularize exports="npm" -o ./`
|
|
* Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
|
|
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
|
|
* Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
|
* Available under MIT license <https://lodash.com/license>
|
|
*/
|
|
|
|
/**
|
|
* The base implementation of `_.create` without support for assigning
|
|
* properties to the created object.
|
|
*
|
|
* @private
|
|
* @param {Object} prototype The object to inherit from.
|
|
* @returns {Object} Returns the new object.
|
|
*/
|
|
var baseCreate = (function() {
|
|
function object() {}
|
|
return function(prototype) {
|
|
if (isObject(prototype)) {
|
|
object.prototype = prototype;
|
|
var result = new object;
|
|
object.prototype = undefined;
|
|
}
|
|
return result || {};
|
|
};
|
|
}());
|
|
|
|
/**
|
|
* Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`.
|
|
* (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is an object, else `false`.
|
|
* @example
|
|
*
|
|
* _.isObject({});
|
|
* // => true
|
|
*
|
|
* _.isObject([1, 2, 3]);
|
|
* // => true
|
|
*
|
|
* _.isObject(1);
|
|
* // => false
|
|
*/
|
|
function isObject(value) {
|
|
// Avoid a V8 JIT bug in Chrome 19-20.
|
|
// See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
|
|
var type = typeof value;
|
|
return !!value && (type == 'object' || type == 'function');
|
|
}
|
|
|
|
module.exports = baseCreate;
|
|
|
|
},{}],34:[function(require,module,exports){
|
|
/**
|
|
* lodash 3.0.0 (Custom Build) <https://lodash.com/>
|
|
* Build: `lodash modern modularize exports="npm" -o ./`
|
|
* Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
|
|
* Based on Underscore.js 1.7.0 <http://underscorejs.org/LICENSE>
|
|
* Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
|
* Available under MIT license <https://lodash.com/license>
|
|
*/
|
|
|
|
/** Used as the internal argument placeholder. */
|
|
var PLACEHOLDER = '__lodash_placeholder__';
|
|
|
|
/**
|
|
* Replaces all `placeholder` elements in `array` with an internal placeholder
|
|
* and returns an array of their indexes.
|
|
*
|
|
* @private
|
|
* @param {Array} array The array to modify.
|
|
* @param {*} placeholder The placeholder to replace.
|
|
* @returns {Array} Returns the new array of placeholder indexes.
|
|
*/
|
|
function replaceHolders(array, placeholder) {
|
|
var index = -1,
|
|
length = array.length,
|
|
resIndex = -1,
|
|
result = [];
|
|
|
|
while (++index < length) {
|
|
if (array[index] === placeholder) {
|
|
array[index] = PLACEHOLDER;
|
|
result[++resIndex] = index;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
module.exports = replaceHolders;
|
|
|
|
},{}],35:[function(require,module,exports){
|
|
arguments[4][25][0].apply(exports,arguments)
|
|
},{"dup":25}],36:[function(require,module,exports){
|
|
/**
|
|
* lodash 3.0.4 (Custom Build) <https://lodash.com/>
|
|
* Build: `lodash modern modularize exports="npm" -o ./`
|
|
* Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
|
|
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
|
|
* Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
|
* Available under MIT license <https://lodash.com/license>
|
|
*/
|
|
var isArguments = require('lodash.isarguments'),
|
|
isArray = require('lodash.isarray'),
|
|
isFunction = require('lodash.isfunction'),
|
|
isString = require('lodash.isstring'),
|
|
keys = require('lodash.keys');
|
|
|
|
/**
|
|
* Checks if `value` is object-like.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is object-like, else `false`.
|
|
*/
|
|
function isObjectLike(value) {
|
|
return !!value && typeof value == 'object';
|
|
}
|
|
|
|
/**
|
|
* Used as the [maximum length](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-number.max_safe_integer)
|
|
* of an array-like value.
|
|
*/
|
|
var MAX_SAFE_INTEGER = 9007199254740991;
|
|
|
|
/**
|
|
* The base implementation of `_.property` without support for deep paths.
|
|
*
|
|
* @private
|
|
* @param {string} key The key of the property to get.
|
|
* @returns {Function} Returns the new function.
|
|
*/
|
|
function baseProperty(key) {
|
|
return function(object) {
|
|
return object == null ? undefined : object[key];
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Gets the "length" property value of `object`.
|
|
*
|
|
* **Note:** This function is used to avoid a [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792)
|
|
* that affects Safari on at least iOS 8.1-8.3 ARM64.
|
|
*
|
|
* @private
|
|
* @param {Object} object The object to query.
|
|
* @returns {*} Returns the "length" value.
|
|
*/
|
|
var getLength = baseProperty('length');
|
|
|
|
/**
|
|
* Checks if `value` is array-like.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is array-like, else `false`.
|
|
*/
|
|
function isArrayLike(value) {
|
|
return value != null && isLength(getLength(value));
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is a valid array-like length.
|
|
*
|
|
* **Note:** This function is based on [`ToLength`](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength).
|
|
*
|
|
* @private
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
|
|
*/
|
|
function isLength(value) {
|
|
return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is empty. A value is considered empty unless it is an
|
|
* `arguments` object, array, string, or jQuery-like collection with a length
|
|
* greater than `0` or an object with own enumerable properties.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @category Lang
|
|
* @param {Array|Object|string} value The value to inspect.
|
|
* @returns {boolean} Returns `true` if `value` is empty, else `false`.
|
|
* @example
|
|
*
|
|
* _.isEmpty(null);
|
|
* // => true
|
|
*
|
|
* _.isEmpty(true);
|
|
* // => true
|
|
*
|
|
* _.isEmpty(1);
|
|
* // => true
|
|
*
|
|
* _.isEmpty([1, 2, 3]);
|
|
* // => false
|
|
*
|
|
* _.isEmpty({ 'a': 1 });
|
|
* // => false
|
|
*/
|
|
function isEmpty(value) {
|
|
if (value == null) {
|
|
return true;
|
|
}
|
|
if (isArrayLike(value) && (isArray(value) || isString(value) || isArguments(value) ||
|
|
(isObjectLike(value) && isFunction(value.splice)))) {
|
|
return !value.length;
|
|
}
|
|
return !keys(value).length;
|
|
}
|
|
|
|
module.exports = isEmpty;
|
|
|
|
},{"lodash.isarguments":37,"lodash.isarray":38,"lodash.isfunction":39,"lodash.isstring":40,"lodash.keys":41}],37:[function(require,module,exports){
|
|
arguments[4][28][0].apply(exports,arguments)
|
|
},{"dup":28}],38:[function(require,module,exports){
|
|
arguments[4][29][0].apply(exports,arguments)
|
|
},{"dup":29}],39:[function(require,module,exports){
|
|
/**
|
|
* lodash 3.0.6 (Custom Build) <https://lodash.com/>
|
|
* Build: `lodash modern modularize exports="npm" -o ./`
|
|
* Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
|
|
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
|
|
* Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
|
* Available under MIT license <https://lodash.com/license>
|
|
*/
|
|
|
|
/** `Object#toString` result references. */
|
|
var funcTag = '[object Function]';
|
|
|
|
/** Used for native method references. */
|
|
var objectProto = Object.prototype;
|
|
|
|
/**
|
|
* Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
|
|
* of values.
|
|
*/
|
|
var objToString = objectProto.toString;
|
|
|
|
/**
|
|
* Checks if `value` is classified as a `Function` object.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
|
|
* @example
|
|
*
|
|
* _.isFunction(_);
|
|
* // => true
|
|
*
|
|
* _.isFunction(/abc/);
|
|
* // => false
|
|
*/
|
|
function isFunction(value) {
|
|
// The use of `Object#toString` avoids issues with the `typeof` operator
|
|
// in older versions of Chrome and Safari which return 'function' for regexes
|
|
// and Safari 8 equivalents which return 'object' for typed array constructors.
|
|
return isObject(value) && objToString.call(value) == funcTag;
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`.
|
|
* (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is an object, else `false`.
|
|
* @example
|
|
*
|
|
* _.isObject({});
|
|
* // => true
|
|
*
|
|
* _.isObject([1, 2, 3]);
|
|
* // => true
|
|
*
|
|
* _.isObject(1);
|
|
* // => false
|
|
*/
|
|
function isObject(value) {
|
|
// Avoid a V8 JIT bug in Chrome 19-20.
|
|
// See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
|
|
var type = typeof value;
|
|
return !!value && (type == 'object' || type == 'function');
|
|
}
|
|
|
|
module.exports = isFunction;
|
|
|
|
},{}],40:[function(require,module,exports){
|
|
/**
|
|
* lodash 3.0.1 (Custom Build) <https://lodash.com/>
|
|
* Build: `lodash modern modularize exports="npm" -o ./`
|
|
* Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
|
|
* Based on Underscore.js 1.8.2 <http://underscorejs.org/LICENSE>
|
|
* Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
|
* Available under MIT license <https://lodash.com/license>
|
|
*/
|
|
|
|
/** `Object#toString` result references. */
|
|
var stringTag = '[object String]';
|
|
|
|
/**
|
|
* Checks if `value` is object-like.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is object-like, else `false`.
|
|
*/
|
|
function isObjectLike(value) {
|
|
return !!value && typeof value == 'object';
|
|
}
|
|
|
|
/** Used for native method references. */
|
|
var objectProto = Object.prototype;
|
|
|
|
/**
|
|
* Used to resolve the [`toStringTag`](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object.prototype.tostring)
|
|
* of values.
|
|
*/
|
|
var objToString = objectProto.toString;
|
|
|
|
/**
|
|
* Checks if `value` is classified as a `String` primitive or object.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
|
|
* @example
|
|
*
|
|
* _.isString('abc');
|
|
* // => true
|
|
*
|
|
* _.isString(1);
|
|
* // => false
|
|
*/
|
|
function isString(value) {
|
|
return typeof value == 'string' || (isObjectLike(value) && objToString.call(value) == stringTag);
|
|
}
|
|
|
|
module.exports = isString;
|
|
|
|
},{}],41:[function(require,module,exports){
|
|
arguments[4][26][0].apply(exports,arguments)
|
|
},{"dup":26,"lodash._getnative":42,"lodash.isarguments":37,"lodash.isarray":38}],42:[function(require,module,exports){
|
|
arguments[4][27][0].apply(exports,arguments)
|
|
},{"dup":27}],43:[function(require,module,exports){
|
|
/**
|
|
* A dumb in-memory data store. Do not use in production.
|
|
* Only for demo purposes.
|
|
* @param {Object} cache
|
|
*/
|
|
var InMemoryDataAdapter = function(cache){
|
|
// `stores` all data
|
|
this.cache = cache || {};
|
|
};
|
|
|
|
/**
|
|
* Get the data specified by the id
|
|
* @param {String/Number} id ID for the requested data
|
|
* @param {Function} cb
|
|
*/
|
|
InMemoryDataAdapter.prototype.getData = function(id, cb){
|
|
var data = this.cache[id];
|
|
if(!data){
|
|
this.cache[id] = {};
|
|
}
|
|
cb(null, this.cache[id]);
|
|
};
|
|
|
|
/**
|
|
* Stores `data` at `id`
|
|
*/
|
|
InMemoryDataAdapter.prototype.storeData = function(id, data, cb){
|
|
this.cache[id] = data;
|
|
if(cb){ cb(null); }
|
|
};
|
|
|
|
module.exports = InMemoryDataAdapter;
|
|
|
|
},{}],44:[function(require,module,exports){
|
|
var assign = require('lodash.assign'),
|
|
bind = require('lodash.bind'),
|
|
isEmpty = require('lodash.isempty'),
|
|
EventEmitter = require('events').EventEmitter,
|
|
jsondiffpatch = require('jsondiffpatch'),
|
|
|
|
COMMANDS = require('./commands'),
|
|
utils = require('./utils'),
|
|
Client;
|
|
|
|
Client = function(socket, room, diffOptions){
|
|
if(!socket){ throw new Error('No socket specified'); }
|
|
if(!room){ room = ''; }
|
|
if(!diffOptions){ diffOptions = {}; }
|
|
|
|
this.socket = socket;
|
|
this.room = room;
|
|
this.syncing = false;
|
|
this.initialized = false;
|
|
this.scheduled = false;
|
|
this.doc = {
|
|
localVersion: 0,
|
|
serverVersion: 0,
|
|
shadow: {},
|
|
localCopy: {},
|
|
edits: []
|
|
};
|
|
|
|
// set up the jsondiffpatch options
|
|
// see here for options: https://github.com/benjamine/jsondiffpatch#options
|
|
diffOptions = assign({
|
|
objectHash: function(obj) { return obj.id || obj._id || JSON.stringify(obj); }
|
|
}, diffOptions);
|
|
|
|
this.jsondiffpatch = jsondiffpatch.create(diffOptions);
|
|
|
|
// let client be an EventEmitter
|
|
EventEmitter.call(this);
|
|
|
|
// bind functions
|
|
var methodsToBind = ['_onConnected', 'syncWithServer', 'applyServerEdit', 'applyServerEdits', 'schedule', 'onRemoteUpdate'],
|
|
method;
|
|
|
|
for(var index in methodsToBind){
|
|
method = methodsToBind[index];
|
|
this[method] = bind(this[method], this);
|
|
}
|
|
};
|
|
|
|
// inherit from EventEmitter
|
|
Client.prototype = new EventEmitter();
|
|
|
|
/**
|
|
* Get the data
|
|
* @return {Object} [description]
|
|
*/
|
|
Client.prototype.getData = function(){
|
|
return this.doc.localCopy;
|
|
};
|
|
|
|
/**
|
|
* Initializes the sync session
|
|
*/
|
|
Client.prototype.initialize = function(){
|
|
// connect, join room and initialize
|
|
this.syncing = true;
|
|
this.socket.emit(COMMANDS.join, this.room, this._onConnected);
|
|
};
|
|
|
|
/**
|
|
* Sets up the local version and listens to server updates
|
|
* Will notify the `onConnected` callback.
|
|
* @param {Object} initialVersion The initial version from the server
|
|
*/
|
|
Client.prototype._onConnected = function(initialVersion){
|
|
// client is not syncing anymore and is initialized
|
|
this.syncing = false;
|
|
this.initialized = true;
|
|
|
|
// set up shadow doc, local doc and initial server version
|
|
// IMPORTANT: the shadow needs to be a deep copy of the initial version
|
|
// because otherwise changes to the local object will also result in changes
|
|
// to the shadow object because they are pointing to the same doc
|
|
this.doc.shadow = utils.deepCopy(initialVersion);
|
|
this.doc.localCopy = initialVersion;
|
|
this.doc.serverVersion = 0;
|
|
|
|
// listen to incoming updates from the server
|
|
this.socket.on(COMMANDS.remoteUpdateIncoming, this.onRemoteUpdate);
|
|
|
|
// notify about established connection
|
|
this.emit('connected');
|
|
};
|
|
|
|
/**
|
|
* Handler for remote updates
|
|
* @param {String} fromId id from the socket that initiated the update
|
|
*/
|
|
Client.prototype.onRemoteUpdate = function(fromId){
|
|
// only schedule if the update was not initiated by this client
|
|
if(this.socket.id !== fromId){
|
|
this.schedule();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Schedule a sync cycle. This method should be used from the outside to
|
|
* trigger syncs.
|
|
*/
|
|
Client.prototype.schedule = function(){
|
|
// do nothing if already scheduled
|
|
if(this.scheduled){ return; }
|
|
this.scheduled = true;
|
|
|
|
// try to sync now
|
|
this.syncWithServer();
|
|
};
|
|
|
|
/**
|
|
* Alias function for `sync`
|
|
*/
|
|
Client.prototype.sync = function(){
|
|
this.schedule();
|
|
};
|
|
|
|
/**
|
|
* Starts a sync cycle. Should not be called from third parties
|
|
*/
|
|
Client.prototype.syncWithServer = function(){
|
|
if(this.syncing || !this.initialized){ return false; }
|
|
if(this.scheduled){ this.scheduled = false; }
|
|
|
|
// initiate syncing cycle
|
|
this.syncing = true;
|
|
|
|
// 1) create a diff of local copy and shadow
|
|
var diff = this.createDiff(this.doc.shadow, this.doc.localCopy);
|
|
var basedOnLocalVersion = this.doc.localVersion;
|
|
|
|
// 2) add the difference to the local edits stack if the diff is not empty
|
|
if(!isEmpty(diff)){
|
|
this.doc.edits.push(this.createDiffMessage(diff, basedOnLocalVersion));
|
|
this.doc.localVersion++;
|
|
}
|
|
|
|
// 3) create an edit message with all relevant version numbers
|
|
var editMessage = this.createEditMessage(basedOnLocalVersion);
|
|
|
|
// 4) apply the patch to the local shadow
|
|
this.applyPatchTo(this.doc.shadow, utils.deepCopy(diff));
|
|
|
|
// 5) send the edits to the server
|
|
this.sendEdits(editMessage);
|
|
|
|
// yes, we're syncing
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Returns a diff of the passed documents
|
|
* @param {Object} docA
|
|
* @param {Object} docB
|
|
* @return {Diff} The diff of both documents
|
|
*/
|
|
Client.prototype.createDiff = function(docA, docB){
|
|
return this.jsondiffpatch.diff(docA, docB);
|
|
};
|
|
|
|
/**
|
|
* Applies the path to the specified object
|
|
* WARNING: The patch is applied in place!
|
|
* @param {Object} obj
|
|
* @param {Diff} patch
|
|
*/
|
|
Client.prototype.applyPatchTo = function(obj, patch){
|
|
this.jsondiffpatch.patch(obj, patch);
|
|
};
|
|
|
|
/**
|
|
* Creates a message for the specified diff
|
|
* @param {Diff} diff the diff that will be sent
|
|
* @param {Number} baseVersion the version of which the diff is based
|
|
* @return {Object} a diff message
|
|
*/
|
|
Client.prototype.createDiffMessage = function(diff, baseVersion){
|
|
return {
|
|
serverVersion: this.doc.serverVersion,
|
|
localVersion: baseVersion,
|
|
diff: diff
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Creates a message representing a set of edits
|
|
* An edit message contains all edits since the last sync has happened.
|
|
* @param {Number} baseVersion The version that these edits are based on
|
|
* @return {Object} An edit message
|
|
*/
|
|
Client.prototype.createEditMessage = function(baseVersion){
|
|
return {
|
|
room: this.room,
|
|
edits: this.doc.edits,
|
|
localVersion: baseVersion,
|
|
serverVersion: this.doc.serverVersion
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Send the the edits to the server and applies potential updates from the server
|
|
*/
|
|
Client.prototype.sendEdits = function(editMessage){
|
|
this.socket.emit(COMMANDS.syncWithServer, editMessage, this.applyServerEdits);
|
|
};
|
|
|
|
/**
|
|
* Applies all edits from the server and notfies about changes
|
|
* @param {Object} serverEdits The edits message
|
|
*/
|
|
Client.prototype.applyServerEdits = function(serverEdits){
|
|
if(serverEdits && serverEdits.localVersion === this.doc.localVersion){
|
|
// 0) delete all previous edits
|
|
this.doc.edits = [];
|
|
// 1) iterate over all edits
|
|
serverEdits.edits.forEach(this.applyServerEdit);
|
|
}else{
|
|
// Rejected patch because localVersions don't match
|
|
this.emit('error', 'REJECTED_PATCH');
|
|
}
|
|
|
|
// we are not syncing any more
|
|
this.syncing = false;
|
|
|
|
// notify about sync
|
|
this.emit('synced');
|
|
|
|
// if a sync has been scheduled, sync again
|
|
if(this.scheduled) {
|
|
this.syncWithServer();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Applies a single edit message to the local copy and the shadow
|
|
* @param {[type]} editMessage [description]
|
|
* @return {[type]} [description]
|
|
*/
|
|
Client.prototype.applyServerEdit = function(editMessage){
|
|
// 2) check the version numbers
|
|
if(editMessage.localVersion === this.doc.localVersion &&
|
|
editMessage.serverVersion === this.doc.serverVersion){
|
|
|
|
if(!isEmpty(editMessage.diff)){
|
|
// versions match
|
|
// 3) patch the shadow
|
|
this.applyPatchTo(this.doc.shadow, editMessage.diff);
|
|
|
|
// 4) increase the version number for the shadow if diff not empty
|
|
this.doc.serverVersion++;
|
|
// apply the patch to the local document
|
|
// IMPORTANT: Use a copy of the diff, or newly created objects will be copied by reference!
|
|
this.applyPatchTo(this.doc.localCopy, utils.deepCopy(editMessage.diff));
|
|
}
|
|
|
|
return true;
|
|
}else{
|
|
// TODO: check in the algo paper what should happen in the case of not matching version numbers
|
|
return false;
|
|
}
|
|
};
|
|
|
|
module.exports = Client;
|
|
|
|
},{"./commands":45,"./utils":47,"events":1,"jsondiffpatch":16,"lodash.assign":19,"lodash.bind":30,"lodash.isempty":36}],45:[function(require,module,exports){
|
|
module.exports = {
|
|
join: 'diffsync-join',
|
|
syncWithServer: 'diffsync-send-edit',
|
|
remoteUpdateIncoming: 'diffsync-updated-doc',
|
|
error: 'diffsync-error'
|
|
};
|
|
|
|
},{}],46:[function(require,module,exports){
|
|
var assign = require('lodash.assign'),
|
|
bind = require('lodash.bind'),
|
|
isEmpty = require('lodash.isempty'),
|
|
jsondiffpatch = require('jsondiffpatch'),
|
|
|
|
COMMANDS = require('./commands'),
|
|
utils = require('./utils'),
|
|
Server;
|
|
|
|
Server = function(adapter, transport, diffOptions){
|
|
if(!(adapter && transport)){ throw new Error('Need to specify an adapter and a transport'); }
|
|
if(!diffOptions){ diffOptions = {}; }
|
|
|
|
this.adapter = adapter;
|
|
this.transport = transport;
|
|
this.data = {};
|
|
this.requests = {};
|
|
this.saveRequests = {};
|
|
this.saveQueue = {};
|
|
|
|
// bind functions
|
|
this.trackConnection = bind(this.trackConnection, this);
|
|
|
|
// set up the jsondiffpatch options
|
|
// see here for options: https://github.com/benjamine/jsondiffpatch#options
|
|
diffOptions = assign({
|
|
objectHash: function(obj) { return obj.id || obj._id || JSON.stringify(obj); }
|
|
}, diffOptions);
|
|
|
|
this.jsondiffpatch = jsondiffpatch.create(diffOptions);
|
|
|
|
this.transport.on('connection', this.trackConnection);
|
|
};
|
|
|
|
/**
|
|
* Registers the correct event listeners
|
|
* @param {Connection} connection The connection that should get tracked
|
|
*/
|
|
Server.prototype.trackConnection = function(connection){
|
|
connection.on(COMMANDS.join, this.joinConnection.bind(this, connection));
|
|
connection.on(COMMANDS.syncWithServer, this.receiveEdit.bind(this, connection));
|
|
};
|
|
|
|
/**
|
|
* Joins a connection to a room and send the initial data
|
|
* @param {Connection} connection
|
|
* @param {String} room room identifier
|
|
* @param {Function} initializeClient Callback that is being used for initialization of the client
|
|
*/
|
|
Server.prototype.joinConnection = function(connection, room, initializeClient){
|
|
this.getData(room, function(error, data){
|
|
// connect to the room
|
|
connection.join(room);
|
|
|
|
// set up the client version for this socket
|
|
// each connection has a backup and a shadow
|
|
// and a set of edits
|
|
data.clientVersions[connection.id] = {
|
|
backup: {
|
|
doc: utils.deepCopy(data.serverCopy),
|
|
serverVersion: 0
|
|
},
|
|
shadow: {
|
|
doc: utils.deepCopy(data.serverCopy),
|
|
serverVersion: 0,
|
|
localVersion: 0
|
|
},
|
|
edits: []
|
|
};
|
|
|
|
// send the current server version
|
|
initializeClient(data.serverCopy);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Gets data for a room from the internal cache or from the adapter
|
|
* @param {String} room room identifier
|
|
* @param {Function} callback notifier-callback
|
|
*/
|
|
Server.prototype.getData = function(room, callback){
|
|
var cachedVersion = this.data[room],
|
|
cache = this.data,
|
|
requests = this.requests;
|
|
|
|
if(cachedVersion){
|
|
callback(null, cachedVersion);
|
|
}else{
|
|
// if there is no request for this room
|
|
// ask the adapter for the data
|
|
// do nothing in the else case because this operation
|
|
// should only happen once
|
|
if(!requests[room]){
|
|
requests[room] = true;
|
|
this.adapter.getData(room, function(error, data){
|
|
cache[room] = {
|
|
registeredSockets: [],
|
|
clientVersions: {},
|
|
serverCopy: data
|
|
};
|
|
|
|
requests[room] = false;
|
|
callback(null, cache[room]);
|
|
});
|
|
}else{
|
|
requests[room] = true;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Applies the sent edits to the shadow and the server copy, notifies all connected sockets and saves a snapshot
|
|
* @param {Object} connection The connection that sent the edits
|
|
* @param {Object} editMessage The message containing all edits
|
|
* @param {Function} sendToClient The callback that sends the server changes back to the client
|
|
*/
|
|
Server.prototype.receiveEdit = function(connection, editMessage, sendToClient){
|
|
// -1) The algorithm actually says we should use a checksum here, I don't think that's necessary
|
|
// 0) get the relevant doc
|
|
this.getData(editMessage.room, function(err, doc){
|
|
// 0.a) get the client versions
|
|
var clientDoc = doc.clientVersions[connection.id];
|
|
|
|
// no client doc could be found, client needs to re-auth
|
|
if(err || !clientDoc){
|
|
connection.emit(COMMANDS.error, 'Need to re-connect!');
|
|
return;
|
|
}
|
|
|
|
// when the versions match, remove old edits stack
|
|
if(editMessage.serverVersion === clientDoc.shadow.serverVersion){
|
|
clientDoc.edits = [];
|
|
}
|
|
|
|
// 1) iterate over all edits
|
|
editMessage.edits.forEach(function(edit){
|
|
// 2) check the version numbers
|
|
if(edit.serverVersion === clientDoc.shadow.serverVersion &&
|
|
edit.localVersion === clientDoc.shadow.localVersion){
|
|
// versions match
|
|
// backup! TODO: is this the right place to do that?
|
|
clientDoc.backup.doc = utils.deepCopy(clientDoc.shadow.doc);
|
|
|
|
// 3) patch the shadow
|
|
// var snapshot = utils.deepCopy(clientDoc.shadow.doc);
|
|
this.jsondiffpatch.patch(clientDoc.shadow.doc, utils.deepCopy(edit.diff));
|
|
// clientDoc.shadow.doc = snapshot;
|
|
|
|
// apply the patch to the server's document
|
|
// snapshot = utils.deepCopy(doc.serverCopy);
|
|
this.jsondiffpatch.patch(doc.serverCopy, utils.deepCopy(edit.diff));
|
|
// doc.serverCopy = snapshot;
|
|
|
|
// 3.a) increase the version number for the shadow if diff not empty
|
|
if(!isEmpty(edit.diff)){
|
|
clientDoc.shadow.localVersion++;
|
|
}
|
|
}else{
|
|
// TODO: implement backup workflow
|
|
// has a low priority since `packets are not lost` - but don't quote me on that :P
|
|
console.log('error', 'patch rejected!!', edit.serverVersion, '->', clientDoc.shadow.serverVersion, ':',
|
|
edit.localVersion, '->', clientDoc.shadow.localVersion);
|
|
}
|
|
}.bind(this));
|
|
|
|
// 4) save a snapshot of the document
|
|
this.saveSnapshot(editMessage.room);
|
|
|
|
// notify all sockets about the update, if not empty
|
|
if(editMessage.edits.length > 0){
|
|
this.transport.to(editMessage.room).emit(COMMANDS.remoteUpdateIncoming, connection.id);
|
|
}
|
|
|
|
this.sendServerChanges(doc, clientDoc, sendToClient);
|
|
}.bind(this));
|
|
};
|
|
|
|
Server.prototype.saveSnapshot = function(room){
|
|
var noRequestInProgress = !this.saveRequests[room],
|
|
checkQueueAndSaveAgain = function(){
|
|
// if another save request is in the queue, save again
|
|
var anotherRequestScheduled = this.saveQueue[room] === true;
|
|
this.saveRequests[room] = false;
|
|
if(anotherRequestScheduled){
|
|
this.saveQueue[room] = false;
|
|
this.saveSnapshot(room);
|
|
}
|
|
}.bind(this);
|
|
|
|
// only save if no save going on at the moment
|
|
if(noRequestInProgress){
|
|
this.saveRequests[room] = true;
|
|
// get data for saving
|
|
this.getData(room, function(err, data){
|
|
// store data
|
|
if(!err && data){
|
|
this.adapter.storeData(room, data.serverCopy, checkQueueAndSaveAgain);
|
|
}else{
|
|
checkQueueAndSaveAgain();
|
|
}
|
|
}.bind(this));
|
|
}else{
|
|
// schedule a new save request
|
|
this.saveQueue[room] = true;
|
|
}
|
|
};
|
|
|
|
Server.prototype.sendServerChanges = function(doc, clientDoc, send){
|
|
// create a diff from the current server version to the client's shadow
|
|
var diff = this.jsondiffpatch.diff(clientDoc.shadow.doc, doc.serverCopy);
|
|
var basedOnServerVersion = clientDoc.shadow.serverVersion;
|
|
|
|
// add the difference to the server's edit stack
|
|
if(!isEmpty(diff)){
|
|
clientDoc.edits.push({
|
|
serverVersion: basedOnServerVersion,
|
|
localVersion: clientDoc.shadow.localVersion,
|
|
diff: diff
|
|
});
|
|
// update the server version
|
|
clientDoc.shadow.serverVersion++;
|
|
|
|
// apply the patch to the server shadow
|
|
this.jsondiffpatch.patch(clientDoc.shadow.doc, utils.deepCopy(diff));
|
|
}
|
|
|
|
// we explicitly want empty diffs to get sent as well
|
|
send({
|
|
localVersion: clientDoc.shadow.localVersion,
|
|
serverVersion: basedOnServerVersion,
|
|
edits: clientDoc.edits
|
|
});
|
|
};
|
|
|
|
module.exports = Server;
|
|
|
|
},{"./commands":45,"./utils":47,"jsondiffpatch":16,"lodash.assign":19,"lodash.bind":30,"lodash.isempty":36}],47:[function(require,module,exports){
|
|
module.exports = {
|
|
deepCopy: function(obj){
|
|
if (obj === null || obj === undefined) {
|
|
return obj;
|
|
}
|
|
return JSON.parse(JSON.stringify(obj));
|
|
}
|
|
};
|
|
|
|
},{}]},{},[2])(2)
|
|
}); |