diff --git a/NzbDrone.Backbone/JsLibraries/underscore.js b/NzbDrone.Backbone/JsLibraries/underscore.js index f98d56c47..a12f0d96c 100644 --- a/NzbDrone.Backbone/JsLibraries/underscore.js +++ b/NzbDrone.Backbone/JsLibraries/underscore.js @@ -1,10 +1,7 @@ -// Underscore.js 1.3.3 -// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. -// Underscore is freely distributable under the MIT license. -// Portions of Underscore are inspired or borrowed from Prototype, -// Oliver Steele's Functional, and John Resig's Micro-Templating. -// For all details and documentation: -// http://documentcloud.github.com/underscore +// Underscore.js 1.4.4 +// http://underscorejs.org +// (c) 2009-2013 Jeremy Ashkenas, DocumentCloud Inc. +// Underscore may be freely distributed under the MIT license. (function() { @@ -26,7 +23,7 @@ // Create quick reference variables for speed access to core prototypes. var push = ArrayProto.push, slice = ArrayProto.slice, - unshift = ArrayProto.unshift, + concat = ArrayProto.concat, toString = ObjProto.toString, hasOwnProperty = ObjProto.hasOwnProperty; @@ -47,7 +44,11 @@ nativeBind = FuncProto.bind; // Create a safe reference to the Underscore object for use below. - var _ = function(obj) { return new wrapper(obj); }; + var _ = function(obj) { + if (obj instanceof _) return obj; + if (!(this instanceof _)) return new _(obj); + this._wrapped = obj; + }; // Export the Underscore object for **Node.js**, with // backwards-compatibility for the old `require()` API. If we're in @@ -59,11 +60,11 @@ } exports._ = _; } else { - root['_'] = _; + root._ = _; } // Current version. - _.VERSION = '1.3.3'; + _.VERSION = '1.4.4'; // Collection Functions // -------------------- @@ -77,7 +78,7 @@ obj.forEach(iterator, context); } else if (obj.length === +obj.length) { for (var i = 0, l = obj.length; i < l; i++) { - if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return; + if (iterator.call(context, obj[i], i, obj) === breaker) return; } } else { for (var key in obj) { @@ -97,10 +98,11 @@ each(obj, function(value, index, list) { results[results.length] = iterator.call(context, value, index, list); }); - if (obj.length === +obj.length) results.length = obj.length; return results; }; + var reduceError = 'Reduce of empty array with no initial value'; + // **Reduce** builds up a single result from a list of values, aka `inject`, // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { @@ -118,7 +120,7 @@ memo = iterator.call(context, memo, value, index, list); } }); - if (!initial) throw new TypeError('Reduce of empty array with no initial value'); + if (!initial) throw new TypeError(reduceError); return memo; }; @@ -131,9 +133,22 @@ if (context) iterator = _.bind(iterator, context); return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); } - var reversed = _.toArray(obj).reverse(); - if (context && !initial) iterator = _.bind(iterator, context); - return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator); + var length = obj.length; + if (length !== +length) { + var keys = _.keys(obj); + length = keys.length; + } + each(obj, function(value, index, list) { + index = keys ? keys[--length] : --length; + if (!initial) { + memo = obj[index]; + initial = true; + } else { + memo = iterator.call(context, memo, obj[index], index, list); + } + }); + if (!initial) throw new TypeError(reduceError); + return memo; }; // Return the first value which passes a truth test. Aliased as `detect`. @@ -163,18 +178,16 @@ // Return all the elements for which a truth test fails. _.reject = function(obj, iterator, context) { - var results = []; - if (obj == null) return results; - each(obj, function(value, index, list) { - if (!iterator.call(context, value, index, list)) results[results.length] = value; - }); - return results; + return _.filter(obj, function(value, index, list) { + return !iterator.call(context, value, index, list); + }, context); }; // Determine whether all of the elements match a truth test. // Delegates to **ECMAScript 5**'s native `every` if available. // Aliased as `all`. _.every = _.all = function(obj, iterator, context) { + iterator || (iterator = _.identity); var result = true; if (obj == null) return result; if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); @@ -198,23 +211,22 @@ return !!result; }; - // Determine if a given value is included in the array or object using `===`. - // Aliased as `contains`. - _.include = _.contains = function(obj, target) { - var found = false; - if (obj == null) return found; + // Determine if the array or object contains a given value (using `===`). + // Aliased as `include`. + _.contains = _.include = function(obj, target) { + if (obj == null) return false; if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; - found = any(obj, function(value) { + return any(obj, function(value) { return value === target; }); - return found; }; // Invoke a method (with arguments) on every item in a collection. _.invoke = function(obj, method) { var args = slice.call(arguments, 2); + var isFunc = _.isFunction(method); return _.map(obj, function(value) { - return (_.isFunction(method) ? method || value : value[method]).apply(value, args); + return (isFunc ? method : value[method]).apply(value, args); }); }; @@ -223,11 +235,33 @@ return _.map(obj, function(value){ return value[key]; }); }; + // Convenience version of a common use case of `filter`: selecting only objects + // containing specific `key:value` pairs. + _.where = function(obj, attrs, first) { + if (_.isEmpty(attrs)) return first ? null : []; + return _[first ? 'find' : 'filter'](obj, function(value) { + for (var key in attrs) { + if (attrs[key] !== value[key]) return false; + } + return true; + }); + }; + + // Convenience version of a common use case of `find`: getting the first object + // containing specific `key:value` pairs. + _.findWhere = function(obj, attrs) { + return _.where(obj, attrs, true); + }; + // Return the maximum element or (element-based computation). + // Can't optimize arrays of integers longer than 65,535 elements. + // See: https://bugs.webkit.org/show_bug.cgi?id=80797 _.max = function(obj, iterator, context) { - if (!iterator && _.isArray(obj) && obj[0] === +obj[0]) return Math.max.apply(Math, obj); + if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { + return Math.max.apply(Math, obj); + } if (!iterator && _.isEmpty(obj)) return -Infinity; - var result = {computed : -Infinity}; + var result = {computed : -Infinity, value: -Infinity}; each(obj, function(value, index, list) { var computed = iterator ? iterator.call(context, value, index, list) : value; computed >= result.computed && (result = {value : value, computed : computed}); @@ -237,9 +271,11 @@ // Return the minimum element (or element-based computation). _.min = function(obj, iterator, context) { - if (!iterator && _.isArray(obj) && obj[0] === +obj[0]) return Math.min.apply(Math, obj); + if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { + return Math.min.apply(Math, obj); + } if (!iterator && _.isEmpty(obj)) return Infinity; - var result = {computed : Infinity}; + var result = {computed : Infinity, value: Infinity}; each(obj, function(value, index, list) { var computed = iterator ? iterator.call(context, value, index, list) : value; computed < result.computed && (result = {value : value, computed : computed}); @@ -249,68 +285,96 @@ // Shuffle an array. _.shuffle = function(obj) { - var shuffled = [], rand; - each(obj, function(value, index, list) { - rand = Math.floor(Math.random() * (index + 1)); - shuffled[index] = shuffled[rand]; + var rand; + var index = 0; + var shuffled = []; + each(obj, function(value) { + rand = _.random(index++); + shuffled[index - 1] = shuffled[rand]; shuffled[rand] = value; }); return shuffled; }; + // An internal function to generate lookup iterators. + var lookupIterator = function(value) { + return _.isFunction(value) ? value : function(obj){ return obj[value]; }; + }; + // Sort the object's values by a criterion produced by an iterator. - _.sortBy = function(obj, val, context) { - var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; }; + _.sortBy = function(obj, value, context) { + var iterator = lookupIterator(value); return _.pluck(_.map(obj, function(value, index, list) { return { value : value, + index : index, criteria : iterator.call(context, value, index, list) }; }).sort(function(left, right) { - var a = left.criteria, b = right.criteria; - if (a === void 0) return 1; - if (b === void 0) return -1; - return a < b ? -1 : a > b ? 1 : 0; + var a = left.criteria; + var b = right.criteria; + if (a !== b) { + if (a > b || a === void 0) return 1; + if (a < b || b === void 0) return -1; + } + return left.index < right.index ? -1 : 1; }), 'value'); }; - // Groups the object's values by a criterion. Pass either a string attribute - // to group by, or a function that returns the criterion. - _.groupBy = function(obj, val) { + // An internal function used for aggregate "group by" operations. + var group = function(obj, value, context, behavior) { var result = {}; - var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; }; + var iterator = lookupIterator(value || _.identity); each(obj, function(value, index) { - var key = iterator(value, index); - (result[key] || (result[key] = [])).push(value); + var key = iterator.call(context, value, index, obj); + behavior(result, key, value); }); return result; }; + // Groups the object's values by a criterion. Pass either a string attribute + // to group by, or a function that returns the criterion. + _.groupBy = function(obj, value, context) { + return group(obj, value, context, function(result, key, value) { + (_.has(result, key) ? result[key] : (result[key] = [])).push(value); + }); + }; + + // Counts instances of an object that group by a certain criterion. Pass + // either a string attribute to count by, or a function that returns the + // criterion. + _.countBy = function(obj, value, context) { + return group(obj, value, context, function(result, key) { + if (!_.has(result, key)) result[key] = 0; + result[key]++; + }); + }; + // Use a comparator function to figure out the smallest index at which // an object should be inserted so as to maintain order. Uses binary search. - _.sortedIndex = function(array, obj, iterator) { - iterator || (iterator = _.identity); - var value = iterator(obj); + _.sortedIndex = function(array, obj, iterator, context) { + iterator = iterator == null ? _.identity : lookupIterator(iterator); + var value = iterator.call(context, obj); var low = 0, high = array.length; while (low < high) { - var mid = (low + high) >> 1; - iterator(array[mid]) < value ? low = mid + 1 : high = mid; + var mid = (low + high) >>> 1; + iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid; } return low; }; // Safely convert anything iterable into a real, live array. _.toArray = function(obj) { - if (!obj) return []; - if (_.isArray(obj)) return slice.call(obj); - if (_.isArguments(obj)) return slice.call(obj); - if (obj.toArray && _.isFunction(obj.toArray)) return obj.toArray(); + if (!obj) return []; + if (_.isArray(obj)) return slice.call(obj); + if (obj.length === +obj.length) return _.map(obj, _.identity); return _.values(obj); }; // Return the number of elements in an object. _.size = function(obj) { - return _.isArray(obj) ? obj.length : _.keys(obj).length; + if (obj == null) return 0; + return (obj.length === +obj.length) ? obj.length : _.keys(obj).length; }; // Array Functions @@ -320,6 +384,7 @@ // values in the array. Aliased as `head` and `take`. The **guard** check // allows it to work with `_.map`. _.first = _.head = _.take = function(array, n, guard) { + if (array == null) return void 0; return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; }; @@ -334,6 +399,7 @@ // Get the last element of an array. Passing **n** will return the last N // values in the array. The **guard** check allows it to work with `_.map`. _.last = function(array, n, guard) { + if (array == null) return void 0; if ((n != null) && !guard) { return slice.call(array, Math.max(array.length - n, 0)); } else { @@ -341,31 +407,34 @@ } }; - // Returns everything but the first entry of the array. Aliased as `tail`. - // Especially useful on the arguments object. Passing an **index** will return - // the rest of the values in the array from that index onward. The **guard** + // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. + // Especially useful on the arguments object. Passing an **n** will return + // the rest N values in the array. The **guard** // check allows it to work with `_.map`. - _.rest = _.tail = function(array, index, guard) { - return slice.call(array, (index == null) || guard ? 1 : index); + _.rest = _.tail = _.drop = function(array, n, guard) { + return slice.call(array, (n == null) || guard ? 1 : n); }; // Trim out all falsy values from an array. _.compact = function(array) { - return _.filter(array, function(value){ return !!value; }); + return _.filter(array, _.identity); + }; + + // Internal implementation of a recursive `flatten` function. + var flatten = function(input, shallow, output) { + each(input, function(value) { + if (_.isArray(value)) { + shallow ? push.apply(output, value) : flatten(value, shallow, output); + } else { + output.push(value); + } + }); + return output; }; // Return a completely flattened version of an array. _.flatten = function(array, shallow) { - return (function flatten(input, output) { - each(input, function(value) { - if (_.isArray(value)) { - shallow ? push.apply(output, value) : flatten(value, output); - } else { - output.push(value); - } - }); - return output; - })(array, []); + return flatten(array, shallow, []); }; // Return a version of the array that does not contain the specified value(s). @@ -376,30 +445,33 @@ // Produce a duplicate-free version of the array. If the array has already // been sorted, you have the option of using a faster algorithm. // Aliased as `unique`. - _.uniq = _.unique = function(array, isSorted, iterator) { - var initial = iterator ? _.map(array, iterator) : array; + _.uniq = _.unique = function(array, isSorted, iterator, context) { + if (_.isFunction(isSorted)) { + context = iterator; + iterator = isSorted; + isSorted = false; + } + var initial = iterator ? _.map(array, iterator, context) : array; var results = []; - // The `isSorted` flag is irrelevant if the array only contains two elements. - if (array.length < 3) isSorted = true; - _.reduce(initial, function(memo, value, index) { - if (isSorted ? _.last(memo) !== value || !memo.length : !_.include(memo, value)) { - memo.push(value); + var seen = []; + each(initial, function(value, index) { + if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) { + seen.push(value); results.push(array[index]); } - return memo; - }, []); + }); return results; }; // Produce an array that contains the union: each distinct element from all of // the passed-in arrays. _.union = function() { - return _.uniq(_.flatten(arguments, true)); + return _.uniq(concat.apply(ArrayProto, arguments)); }; // Produce an array that contains every item shared between all the - // passed-in arrays. (Aliased as "intersect" for back-compat.) - _.intersection = _.intersect = function(array) { + // passed-in arrays. + _.intersection = function(array) { var rest = slice.call(arguments, 1); return _.filter(_.uniq(array), function(item) { return _.every(rest, function(other) { @@ -411,8 +483,8 @@ // Take the difference between one array and a number of other arrays. // Only the elements present in just the first array will remain. _.difference = function(array) { - var rest = _.flatten(slice.call(arguments, 1), true); - return _.filter(array, function(value){ return !_.include(rest, value); }); + var rest = concat.apply(ArrayProto, slice.call(arguments, 1)); + return _.filter(array, function(value){ return !_.contains(rest, value); }); }; // Zip together multiple lists into a single array -- elements that share @@ -421,10 +493,28 @@ var args = slice.call(arguments); var length = _.max(_.pluck(args, 'length')); var results = new Array(length); - for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i); + for (var i = 0; i < length; i++) { + results[i] = _.pluck(args, "" + i); + } return results; }; + // Converts lists into objects. Pass either a single array of `[key, value]` + // pairs, or two parallel arrays of the same length -- one of keys, and one of + // the corresponding values. + _.object = function(list, values) { + if (list == null) return {}; + var result = {}; + for (var i = 0, l = list.length; i < l; i++) { + if (values) { + result[list[i]] = values[i]; + } else { + result[list[i][0]] = list[i][1]; + } + } + return result; + }; + // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), // we need this function. Return the position of the first occurrence of an // item in an array, or -1 if the item is not included in the array. @@ -433,22 +523,29 @@ // for **isSorted** to use binary search. _.indexOf = function(array, item, isSorted) { if (array == null) return -1; - var i, l; + var i = 0, l = array.length; if (isSorted) { - i = _.sortedIndex(array, item); - return array[i] === item ? i : -1; + if (typeof isSorted == 'number') { + i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted); + } else { + i = _.sortedIndex(array, item); + return array[i] === item ? i : -1; + } } - if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item); - for (i = 0, l = array.length; i < l; i++) if (i in array && array[i] === item) return i; + if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted); + for (; i < l; i++) if (array[i] === item) return i; return -1; }; // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. - _.lastIndexOf = function(array, item) { + _.lastIndexOf = function(array, item, from) { if (array == null) return -1; - if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item); - var i = array.length; - while (i--) if (i in array && array[i] === item) return i; + var hasIndex = from != null; + if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) { + return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item); + } + var i = (hasIndex ? from : array.length); + while (i--) if (array[i] === item) return i; return -1; }; @@ -477,25 +574,23 @@ // Function (ahem) Functions // ------------------ - // Reusable constructor function for prototype setting. - var ctor = function(){}; - // Create a function bound to a given object (assigning `this`, and arguments, - // optionally). Binding with arguments is also known as `curry`. - // Delegates to **ECMAScript 5**'s native `Function.bind` if available. - // We check for `func.bind` first, to fail fast when `func` is undefined. - _.bind = function bind(func, context) { - var bound, args; + // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if + // available. + _.bind = function(func, context) { if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); - if (!_.isFunction(func)) throw new TypeError; - args = slice.call(arguments, 2); - return bound = function() { - if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); - ctor.prototype = func.prototype; - var self = new ctor; - var result = func.apply(self, args.concat(slice.call(arguments))); - if (Object(result) === result) return result; - return self; + var args = slice.call(arguments, 2); + return function() { + return func.apply(context, args.concat(slice.call(arguments))); + }; + }; + + // Partially apply a function by creating a version that has had some of its + // arguments pre-filled, without changing its dynamic `this` context. + _.partial = function(func) { + var args = slice.call(arguments, 1); + return function() { + return func.apply(this, args.concat(slice.call(arguments))); }; }; @@ -503,7 +598,7 @@ // all callbacks defined on an object belong to it. _.bindAll = function(obj) { var funcs = slice.call(arguments, 1); - if (funcs.length == 0) funcs = _.functions(obj); + if (funcs.length === 0) funcs = _.functions(obj); each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); return obj; }; @@ -534,23 +629,26 @@ // Returns a function, that, when invoked, will only be triggered at most once // during a given window of time. _.throttle = function(func, wait) { - var context, args, timeout, throttling, more, result; - var whenDone = _.debounce(function(){ more = throttling = false; }, wait); + var context, args, timeout, result; + var previous = 0; + var later = function() { + previous = new Date; + timeout = null; + result = func.apply(context, args); + }; return function() { - context = this; args = arguments; - var later = function() { + var now = new Date; + var remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0) { + clearTimeout(timeout); timeout = null; - if (more) func.apply(context, args); - whenDone(); - }; - if (!timeout) timeout = setTimeout(later, wait); - if (throttling) { - more = true; - } else { - throttling = true; + previous = now; result = func.apply(context, args); + } else if (!timeout) { + timeout = setTimeout(later, remaining); } - whenDone(); return result; }; }; @@ -560,17 +658,18 @@ // N milliseconds. If `immediate` is passed, trigger the function on the // leading edge, instead of the trailing. _.debounce = function(func, wait, immediate) { - var timeout; + var timeout, result; return function() { var context = this, args = arguments; var later = function() { timeout = null; - if (!immediate) func.apply(context, args); + if (!immediate) result = func.apply(context, args); }; var callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); - if (callNow) func.apply(context, args); + if (callNow) result = func.apply(context, args); + return result; }; }; @@ -581,7 +680,9 @@ return function() { if (ran) return memo; ran = true; - return memo = func.apply(this, arguments); + memo = func.apply(this, arguments); + func = null; + return memo; }; }; @@ -590,7 +691,8 @@ // conditionally execute the original function. _.wrap = function(func, wrapper) { return function() { - var args = [func].concat(slice.call(arguments, 0)); + var args = [func]; + push.apply(args, arguments); return wrapper.apply(this, args); }; }; @@ -612,7 +714,9 @@ _.after = function(times, func) { if (times <= 0) return func(); return function() { - if (--times < 1) { return func.apply(this, arguments); } + if (--times < 1) { + return func.apply(this, arguments); + } }; }; @@ -630,7 +734,23 @@ // Retrieve the values of an object's properties. _.values = function(obj) { - return _.map(obj, _.identity); + var values = []; + for (var key in obj) if (_.has(obj, key)) values.push(obj[key]); + return values; + }; + + // Convert an object into a list of `[key, value]` pairs. + _.pairs = function(obj) { + var pairs = []; + for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]); + return pairs; + }; + + // Invert the keys and values of an object. The values must be serializable. + _.invert = function(obj) { + var result = {}; + for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key; + return result; }; // Return a sorted list of the function names available on the object. @@ -646,8 +766,10 @@ // Extend a given object with all the properties in passed-in object(s). _.extend = function(obj) { each(slice.call(arguments, 1), function(source) { - for (var prop in source) { - obj[prop] = source[prop]; + if (source) { + for (var prop in source) { + obj[prop] = source[prop]; + } } }); return obj; @@ -655,18 +777,31 @@ // Return a copy of the object only containing the whitelisted properties. _.pick = function(obj) { - var result = {}; - each(_.flatten(slice.call(arguments, 1)), function(key) { - if (key in obj) result[key] = obj[key]; + var copy = {}; + var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); + each(keys, function(key) { + if (key in obj) copy[key] = obj[key]; }); - return result; + return copy; + }; + + // Return a copy of the object without the blacklisted properties. + _.omit = function(obj) { + var copy = {}; + var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); + for (var key in obj) { + if (!_.contains(keys, key)) copy[key] = obj[key]; + } + return copy; }; // Fill in a given object with default properties. _.defaults = function(obj) { each(slice.call(arguments, 1), function(source) { - for (var prop in source) { - if (obj[prop] == null) obj[prop] = source[prop]; + if (source) { + for (var prop in source) { + if (obj[prop] == null) obj[prop] = source[prop]; + } } }); return obj; @@ -687,18 +822,15 @@ }; // Internal recursive comparison function for `isEqual`. - function eq(a, b, stack) { + var eq = function(a, b, aStack, bStack) { // Identical objects are equal. `0 === -0`, but they aren't identical. // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal. if (a === b) return a !== 0 || 1 / a == 1 / b; // A strict comparison is necessary because `null == undefined`. if (a == null || b == null) return a === b; // Unwrap any wrapped objects. - if (a._chain) a = a._wrapped; - if (b._chain) b = b._wrapped; - // Invoke a custom `isEqual` method if one is provided. - if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b); - if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a); + if (a instanceof _) a = a._wrapped; + if (b instanceof _) b = b._wrapped; // Compare `[[Class]]` names. var className = toString.call(a); if (className != toString.call(b)) return false; @@ -728,14 +860,15 @@ if (typeof a != 'object' || typeof b != 'object') return false; // Assume equality for cyclic structures. The algorithm for detecting cyclic // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. - var length = stack.length; + var length = aStack.length; while (length--) { // Linear search. Performance is inversely proportional to the number of // unique nested structures. - if (stack[length] == a) return true; + if (aStack[length] == a) return bStack[length] == b; } // Add the first object to the stack of traversed objects. - stack.push(a); + aStack.push(a); + bStack.push(b); var size = 0, result = true; // Recursively compare objects and arrays. if (className == '[object Array]') { @@ -745,20 +878,24 @@ if (result) { // Deep compare the contents, ignoring non-numeric properties. while (size--) { - // Ensure commutative equality for sparse arrays. - if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break; + if (!(result = eq(a[size], b[size], aStack, bStack))) break; } } } else { - // Objects with different constructors are not equivalent. - if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false; + // Objects with different constructors are not equivalent, but `Object`s + // from different frames are. + var aCtor = a.constructor, bCtor = b.constructor; + if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) && + _.isFunction(bCtor) && (bCtor instanceof bCtor))) { + return false; + } // Deep compare objects. for (var key in a) { if (_.has(a, key)) { // Count the expected number of properties. size++; // Deep compare each member. - if (!(result = _.has(b, key) && eq(a[key], b[key], stack))) break; + if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break; } } // Ensure that both objects contain the same number of properties. @@ -770,13 +907,14 @@ } } // Remove the first object from the stack of traversed objects. - stack.pop(); + aStack.pop(); + bStack.pop(); return result; - } + }; // Perform a deep comparison to check if two objects are equal. _.isEqual = function(a, b) { - return eq(a, b, []); + return eq(a, b, [], []); }; // Is a given array, string, or object empty? @@ -790,7 +928,7 @@ // Is a given value a DOM element? _.isElement = function(obj) { - return !!(obj && obj.nodeType == 1); + return !!(obj && obj.nodeType === 1); }; // Is a given value an array? @@ -804,42 +942,36 @@ return obj === Object(obj); }; - // Is a given variable an arguments object? + // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp. + each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) { + _['is' + name] = function(obj) { + return toString.call(obj) == '[object ' + name + ']'; + }; + }); + // Define a fallback version of the method in browsers (ahem, IE), where // there isn't any inspectable "Arguments" type. - _.isArguments = function(obj) { - return toString.call(obj) == '[object Arguments]'; - }; if (!_.isArguments(arguments)) { _.isArguments = function(obj) { return !!(obj && _.has(obj, 'callee')); }; } - // Is a given value a function? - _.isFunction = function(obj) { - return toString.call(obj) == '[object Function]'; - }; - - // Is a given value a string? - _.isString = function(obj) { - return toString.call(obj) == '[object String]'; - }; - - // Is a given value a number? - _.isNumber = function(obj) { - return toString.call(obj) == '[object Number]'; - }; + // Optimize `isFunction` if appropriate. + if (typeof (/./) !== 'function') { + _.isFunction = function(obj) { + return typeof obj === 'function'; + }; + } // Is a given object a finite number? _.isFinite = function(obj) { - return _.isNumber(obj) && isFinite(obj); + return isFinite(obj) && !isNaN(parseFloat(obj)); }; - // Is the given value `NaN`? + // Is the given value `NaN`? (NaN is the only number which does not equal itself). _.isNaN = function(obj) { - // `NaN` is the only value for which `===` is not reflexive. - return obj !== obj; + return _.isNumber(obj) && obj != +obj; }; // Is a given value a boolean? @@ -847,16 +979,6 @@ return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; }; - // Is a given value a date? - _.isDate = function(obj) { - return toString.call(obj) == '[object Date]'; - }; - - // Is the given value a regular expression? - _.isRegExp = function(obj) { - return toString.call(obj) == '[object RegExp]'; - }; - // Is a given value equal to null? _.isNull = function(obj) { return obj === null; @@ -867,7 +989,8 @@ return obj === void 0; }; - // Does an object have the given "own" property? + // Shortcut function for checking if an object has a given property directly + // on itself (in other words, not on a prototype). _.has = function(obj, key) { return hasOwnProperty.call(obj, key); }; @@ -889,20 +1012,49 @@ // Run a function **n** times. _.times = function(n, iterator, context) { - for (var i = 0; i < n; i++) iterator.call(context, i); + var accum = Array(n); + for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i); + return accum; }; - // Escape a string for HTML interpolation. - _.escape = function(string) { - return (''+string) - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, ''') - .replace(/\//g,'/'); + // Return a random integer between min and max (inclusive). + _.random = function(min, max) { + if (max == null) { + max = min; + min = 0; + } + return min + Math.floor(Math.random() * (max - min + 1)); + }; + + // List of HTML entities for escaping. + var entityMap = { + escape: { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/' + } + }; + entityMap.unescape = _.invert(entityMap.escape); + + // Regexes containing the keys and values listed immediately above. + var entityRegexes = { + escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'), + unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g') }; + // Functions for escaping and unescaping strings to/from HTML interpolation. + _.each(['escape', 'unescape'], function(method) { + _[method] = function(string) { + if (string == null) return ''; + return ('' + string).replace(entityRegexes[method], function(match) { + return entityMap[method][match]; + }); + }; + }); + // If the value of the named property is a function then invoke it; // otherwise, return it. _.result = function(object, property) { @@ -911,11 +1063,15 @@ return _.isFunction(value) ? value.call(object) : value; }; - // Add your own custom functions to the Underscore object, ensuring that - // they're correctly added to the OOP wrapper as well. + // Add your own custom functions to the Underscore object. _.mixin = function(obj) { each(_.functions(obj), function(name){ - addToWrapper(name, _[name] = obj[name]); + var func = _[name] = obj[name]; + _.prototype[name] = function() { + var args = [this._wrapped]; + push.apply(args, arguments); + return result.call(this, func.apply(_, args)); + }; }); }; @@ -923,7 +1079,7 @@ // Useful for temporary DOM ids. var idCounter = 0; _.uniqueId = function(prefix) { - var id = idCounter++; + var id = ++idCounter + ''; return prefix ? prefix + id : id; }; @@ -938,63 +1094,71 @@ // When customizing `templateSettings`, if you don't want to define an // interpolation, evaluation or escaping regex, we need one that is // guaranteed not to match. - var noMatch = /.^/; + var noMatch = /(.)^/; // Certain characters need to be escaped so that they can be put into a // string literal. var escapes = { - '\\': '\\', - "'": "'", - 'r': '\r', - 'n': '\n', - 't': '\t', - 'u2028': '\u2028', - 'u2029': '\u2029' + "'": "'", + '\\': '\\', + '\r': 'r', + '\n': 'n', + '\t': 't', + '\u2028': 'u2028', + '\u2029': 'u2029' }; - for (var p in escapes) escapes[escapes[p]] = p; var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; - var unescaper = /\\(\\|'|r|n|t|u2028|u2029)/g; - - // Within an interpolation, evaluation, or escaping, remove HTML escaping - // that had been previously added. - var unescape = function(code) { - return code.replace(unescaper, function(match, escape) { - return escapes[escape]; - }); - }; // JavaScript micro-templating, similar to John Resig's implementation. // Underscore templating handles arbitrary delimiters, preserves whitespace, // and correctly escapes quotes within interpolated code. _.template = function(text, data, settings) { - settings = _.defaults(settings || {}, _.templateSettings); - - // Compile the template source, taking care to escape characters that - // cannot be included in a string literal and then unescape them in code - // blocks. - var source = "__p+='" + text - .replace(escaper, function(match) { - return '\\' + escapes[match]; - }) - .replace(settings.escape || noMatch, function(match, code) { - return "'+\n((__t=(" + unescape(code) + "))==null?'':_.escape(__t))+\n'"; - }) - .replace(settings.interpolate || noMatch, function(match, code) { - return "'+\n((__t=(" + unescape(code) + "))==null?'':__t)+\n'"; - }) - .replace(settings.evaluate || noMatch, function(match, code) { - return "';\n" + unescape(code) + "\n;__p+='"; - }) + "';\n"; + var render; + settings = _.defaults({}, settings, _.templateSettings); + + // Combine delimiters into one regular expression via alternation. + var matcher = new RegExp([ + (settings.escape || noMatch).source, + (settings.interpolate || noMatch).source, + (settings.evaluate || noMatch).source + ].join('|') + '|$', 'g'); + + // Compile the template source, escaping string literals appropriately. + var index = 0; + var source = "__p+='"; + text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { + source += text.slice(index, offset) + .replace(escaper, function(match) { return '\\' + escapes[match]; }); + + if (escape) { + source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; + } + if (interpolate) { + source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; + } + if (evaluate) { + source += "';\n" + evaluate + "\n__p+='"; + } + index = offset + match.length; + return match; + }); + source += "';\n"; // If a variable is not specified, place data values in local scope. if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; source = "var __t,__p='',__j=Array.prototype.join," + - "print=function(){__p+=__j.call(arguments,'')};\n" + + "print=function(){__p+=__j.call(arguments,'');};\n" + source + "return __p;\n"; - var render = new Function(settings.variable || 'obj', '_', source); + try { + render = new Function(settings.variable || 'obj', '_', source); + } catch (e) { + e.source = source; + throw e; + } + if (data) return render(data, _); var template = function(data) { return render.call(this, data, _); @@ -1011,29 +1175,15 @@ return _(obj).chain(); }; - // The OOP Wrapper + // OOP // --------------- - // If Underscore is called as a function, it returns a wrapped object that // can be used OO-style. This wrapper holds altered versions of all the // underscore functions. Wrapped objects may be chained. - var wrapper = function(obj) { this._wrapped = obj; }; - - // Expose `wrapper.prototype` as `_.prototype` - _.prototype = wrapper.prototype; // Helper function to continue chaining intermediate results. - var result = function(obj, chain) { - return chain ? _(obj).chain() : obj; - }; - - // A method to easily add functions to the OOP wrapper. - var addToWrapper = function(name, func) { - wrapper.prototype[name] = function() { - var args = slice.call(arguments); - unshift.call(args, this._wrapped); - return result(func.apply(_, args), this._chain); - }; + var result = function(obj) { + return this._chain ? _(obj).chain() : obj; }; // Add all of the Underscore functions to the wrapper object. @@ -1042,31 +1192,35 @@ // Add all mutator Array functions to the wrapper. each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { var method = ArrayProto[name]; - wrapper.prototype[name] = function() { + _.prototype[name] = function() { var obj = this._wrapped; method.apply(obj, arguments); if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0]; - return result(obj, this._chain); + return result.call(this, obj); }; }); // Add all accessor Array functions to the wrapper. each(['concat', 'join', 'slice'], function(name) { var method = ArrayProto[name]; - wrapper.prototype[name] = function() { - return result(method.apply(this._wrapped, arguments), this._chain); + _.prototype[name] = function() { + return result.call(this, method.apply(this._wrapped, arguments)); }; }); - // Start chaining a wrapped Underscore object. - wrapper.prototype.chain = function() { - this._chain = true; - return this; - }; + _.extend(_.prototype, { - // Extracts the result from a wrapped and chained object. - wrapper.prototype.value = function() { - return this._wrapped; - }; + // Start chaining a wrapped Underscore object. + chain: function() { + this._chain = true; + return this; + }, + + // Extracts the result from a wrapped and chained object. + value: function() { + return this._wrapped; + } + + }); }).call(this); diff --git a/NzbDrone.Backbone/Quality/qualityProfileModel.js b/NzbDrone.Backbone/Quality/qualityProfileModel.js index 464f63f54..6781fe306 100644 --- a/NzbDrone.Backbone/Quality/qualityProfileModel.js +++ b/NzbDrone.Backbone/Quality/qualityProfileModel.js @@ -1,47 +1,21 @@ define(['app'], function () { NzbDrone.Quality.QualityProfileModel = Backbone.Model.extend({ - initialize: function () { - this.validators = {}; - this.validators.name = function (value) { - return value.length > 0 ? { isValid: true } : { isValid: false, message: 'You must enter a name' }; - }; + mutators: { + allowed: function() { + return _.where(this.get('qualities'), { allowed: true }); + }, - //this.validators.allowed = function (value) { - // return value.length > 0 ? { isValid: true } : { isValid: false, message: 'You must have allowed qualities' }; - //}; - //Todo: Cutoff should be something that is allowed (double check) - this.validators.cutoff = function (value) { - return value !== null ? { isValid: true } : { isValid: false, message: 'You must have a valid cutoff' }; - }; - }, - - validateItem: function (key) { - return (this.validators[key]) ? this.validators[key](this.get(key)) : { isValid: true }; - }, - - // TODO: Implement Backbone's standard validate() method instead. - validateAll: function () { - - var messages = {}; - - for (var key in this.validators) { - if (this.validators.hasOwnProperty(key)) { - var check = this.validators[key](this.get(key)); - if (check.isValid === false) { - messages[key] = check.message; - } - } + cutoffName: function() { + return _.findWhere(this.get('qualities'), { id: this.get('cutoff') }).name; } - - return _.size(messages) > 0 ? { isValid: false, messages: messages } : { isValid: true }; }, defaults: { - Id: null, - Name: '', + id: null, + name: '', //allowed: {}, - Cutoff: null + cutoff: null } }); }); diff --git a/NzbDrone.Backbone/Settings/Quality/QualityLayout.js b/NzbDrone.Backbone/Settings/Quality/QualityLayout.js index 1f1eca63e..c8dc7c5db 100644 --- a/NzbDrone.Backbone/Settings/Quality/QualityLayout.js +++ b/NzbDrone.Backbone/Settings/Quality/QualityLayout.js @@ -33,7 +33,7 @@ onRender: function () { this.qualityStandard.show(new NzbDrone.Settings.Quality.QualityView({model: this.settings, qualityProfiles: qualityProfileCollection})); - this.qualityProfile.show(new NzbDrone.Settings.Quality.QualityProfileCollectionView({collection: this.qualityProfileCollection})); + this.qualityProfile.show(new NzbDrone.Settings.Quality.QualityProfileCollectionView({collection: qualityProfileCollection})); this.qualitySize.show(new NzbDrone.Settings.Quality.QualitySizeCollectionView({collection: this.qualitySizeCollection})); } }); diff --git a/NzbDrone.Backbone/Settings/Quality/QualityProfileCollectionTemplate.html b/NzbDrone.Backbone/Settings/Quality/QualityProfileCollectionTemplate.html index 666e8e3d2..e4f946cee 100644 --- a/NzbDrone.Backbone/Settings/Quality/QualityProfileCollectionTemplate.html +++ b/NzbDrone.Backbone/Settings/Quality/QualityProfileCollectionTemplate.html @@ -1,5 +1,14 @@
\ No newline at end of file diff --git a/NzbDrone.Backbone/Settings/Quality/QualityProfileCollectionView.js b/NzbDrone.Backbone/Settings/Quality/QualityProfileCollectionView.js index 1201279aa..f98b3f926 100644 --- a/NzbDrone.Backbone/Settings/Quality/QualityProfileCollectionView.js +++ b/NzbDrone.Backbone/Settings/Quality/QualityProfileCollectionView.js @@ -3,11 +3,10 @@ define(['app', 'Settings/Quality/QualityProfileView'], function (app) { NzbDrone.Settings.Quality.QualityProfileCollectionView = Backbone.Marionette.CompositeView.extend({ itemView: NzbDrone.Settings.Quality.QualityProfileView, - itemViewContainer: '#quality-profiles-container', + itemViewContainer: 'tbody', template: 'Settings/Quality/QualityProfileCollectionTemplate', initialize: function (options) { - }, ui:{ diff --git a/NzbDrone.Backbone/Settings/Quality/QualityProfileTemplate.html b/NzbDrone.Backbone/Settings/Quality/QualityProfileTemplate.html new file mode 100644 index 000000000..f2b0d8a6f --- /dev/null +++ b/NzbDrone.Backbone/Settings/Quality/QualityProfileTemplate.html @@ -0,0 +1,8 @@ +