diff --git a/NzbDrone.Core/Datastore/ConnectionFactory.cs b/NzbDrone.Core/Datastore/ConnectionFactory.cs
index 39186f167..56eab6b39 100644
--- a/NzbDrone.Core/Datastore/ConnectionFactory.cs
+++ b/NzbDrone.Core/Datastore/ConnectionFactory.cs
@@ -137,7 +137,7 @@ private static void RepairDatabase(string connectionString)
return;
}
logger.WarnException(
- "Safe recovery failed. will attempts a more aggressive strategy. might case loss of data.",
+ "Safe recovery failed. will attempts a more aggressive strategy. might cause loss of data.",
e);
sqlEngine.Repair(connectionString, RepairOption.DeleteCorruptedRows);
logger.Warn("Database was recovered. some data might have been lost");
diff --git a/NzbDrone.Web/NzbDrone.Web.csproj b/NzbDrone.Web/NzbDrone.Web.csproj
index 0b8ad1f39..42c6a8cb4 100644
--- a/NzbDrone.Web/NzbDrone.Web.csproj
+++ b/NzbDrone.Web/NzbDrone.Web.csproj
@@ -271,6 +271,8 @@
+
+
@@ -388,6 +390,7 @@
+
diff --git a/NzbDrone.Web/_backboneApp/AddSeries/RootDir/RootDirItemTemplate.html b/NzbDrone.Web/_backboneApp/AddSeries/RootDir/RootDirItemTemplate.html
index 4eeaf664a..5b0c84735 100644
--- a/NzbDrone.Web/_backboneApp/AddSeries/RootDir/RootDirItemTemplate.html
+++ b/NzbDrone.Web/_backboneApp/AddSeries/RootDir/RootDirItemTemplate.html
@@ -1,5 +1,7 @@
|
- |
+
+
+ |
diff --git a/NzbDrone.Web/_backboneApp/AddSeries/RootDir/RootDirModel.js b/NzbDrone.Web/_backboneApp/AddSeries/RootDir/RootDirModel.js
index 80153966c..f7c3b8b70 100644
--- a/NzbDrone.Web/_backboneApp/AddSeries/RootDir/RootDirModel.js
+++ b/NzbDrone.Web/_backboneApp/AddSeries/RootDir/RootDirModel.js
@@ -1,4 +1,14 @@
-///
+///
NzbDrone.AddSeries.RootDirModel = Backbone.Model.extend({
idAttribute: 'Id',
+
+ mutators: {
+ FreeSpaceString: function () {
+ return this.get('FreeSpace').bytes(2) + " Free";
+ }
+ },
+
+ defaults: {
+ FreeSpace: 0,
+ }
});
diff --git a/NzbDrone.Web/_backboneApp/AddSeries/RootDir/RootDirTemplate.html b/NzbDrone.Web/_backboneApp/AddSeries/RootDir/RootDirTemplate.html
index ae2092199..2f4e33848 100644
--- a/NzbDrone.Web/_backboneApp/AddSeries/RootDir/RootDirTemplate.html
+++ b/NzbDrone.Web/_backboneApp/AddSeries/RootDir/RootDirTemplate.html
@@ -1,7 +1,7 @@
diff --git a/NzbDrone.Web/_backboneApp/AddSeries/RootDir/RootDirView.js b/NzbDrone.Web/_backboneApp/AddSeries/RootDir/RootDirView.js
index d83cce29c..454ce52d4 100644
--- a/NzbDrone.Web/_backboneApp/AddSeries/RootDir/RootDirView.js
+++ b/NzbDrone.Web/_backboneApp/AddSeries/RootDir/RootDirView.js
@@ -45,6 +45,10 @@ NzbDrone.AddSeries.RootDirView = Backbone.Marionette.Layout.extend({
'click #add-dir': 'addDir',
},
+ shortcuts: {
+ 'enter': 'addDir'
+ },
+
collection: new NzbDrone.AddSeries.RootDirCollection(),
diff --git a/NzbDrone.Web/_backboneApp/AddSeries/addSeriesLayoutTemplate.html b/NzbDrone.Web/_backboneApp/AddSeries/addSeriesLayoutTemplate.html
index 9412dcb06..2a35d3434 100644
--- a/NzbDrone.Web/_backboneApp/AddSeries/addSeriesLayoutTemplate.html
+++ b/NzbDrone.Web/_backboneApp/AddSeries/addSeriesLayoutTemplate.html
@@ -4,7 +4,7 @@
Add new series.
diff --git a/NzbDrone.Web/_backboneApp/JsLibraries/backbone.marionette.extend.js b/NzbDrone.Web/_backboneApp/JsLibraries/backbone.marionette.extend.js
index 31d9f4499..60fd10617 100644
--- a/NzbDrone.Web/_backboneApp/JsLibraries/backbone.marionette.extend.js
+++ b/NzbDrone.Web/_backboneApp/JsLibraries/backbone.marionette.extend.js
@@ -9,6 +9,7 @@
jQuery.ajax({
url: '_backboneApp//' + templateId + '.html',
+ cache:false,
async: false
}).done(function (data) {
diff --git a/NzbDrone.Web/_backboneApp/JsLibraries/backbone.mutators.js b/NzbDrone.Web/_backboneApp/JsLibraries/backbone.mutators.js
new file mode 100644
index 000000000..bb79f4d1b
--- /dev/null
+++ b/NzbDrone.Web/_backboneApp/JsLibraries/backbone.mutators.js
@@ -0,0 +1,174 @@
+(function (root, define, require, exports, module, factory, undef) {
+ 'use strict';
+
+ if (typeof exports === 'object') {
+ // Node. Does not work with strict CommonJS, but
+ // only CommonJS-like enviroments that support module.exports,
+ // like Node.
+ module.exports = factory(require('underscore'), require('backbone'));
+ } else if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module.
+ define(['underscore', 'backbone'], function (_, Backbone) {
+ // Check if we use the AMD branch of Back
+ _ = _ === undef ? root._ : _;
+ Backbone = Backbone === undef ? root.Backbone : Backbone;
+ return (root.returnExportsGlobal = factory(_, Backbone, root));
+ });
+ } else {
+ // Browser globals
+ root.returnExportsGlobal = factory(root._, root.Backbone);
+ }
+
+ // Usage:
+ //
+ // Note: This plugin is UMD compatible, you can use it in node, amd and vanilla js envs
+ //
+ // Vanilla JS:
+ //
+ //
+ //
+ //
+ // Node:
+ // var _ = require('underscore');
+ // var Backbone = require('backbone');
+ // var Mutators = require('backbone.mutators');
+ //
+ //
+ // AMD:
+ // define(['underscore', 'backbone', 'backbone.mutators'], function (_, Backbone, Mutators) {
+ // // insert sample from below
+ // return User;
+ // });
+ //
+ // var User = Backbone.Model.extend({
+ // mutators: {
+ // fullname: function () {
+ // return this.firstname + ' ' + this.lastname;
+ // }
+ // },
+ //
+ // defaults: {
+ // firstname: 'Sebastian',
+ // lastname: 'Golasch'
+ // }
+ // });
+ //
+ // var user = new User();
+ // user.get('fullname') // returns 'Sebastian Golasch'
+ // user.toJSON() // return '{firstname: 'Sebastian', lastname: 'Golasch', fullname: 'Sebastian Golasch'}'
+
+}(this, this.define, this.require, this.exports, this.module, function (_, Backbone, root, undef) {
+ 'use strict';
+
+ // check if we use the amd branch of backbone and underscore
+ Backbone = Backbone === undef ? root.Backbone : Backbone;
+ _ = _ === undef ? root._ : _;
+
+ // extend backbones model prototype with the mutator functionality
+ var Mutator = function () { },
+ oldGet = Backbone.Model.prototype.get,
+ oldSet = Backbone.Model.prototype.set,
+ oldToJson = Backbone.Model.prototype.toJSON;
+
+ // This is necessary to ensure that Models declared without the mutators object do not throw and error
+ Mutator.prototype.mutators = {};
+
+ // override get functionality to fetch the mutator props
+ Mutator.prototype.get = function (attr) {
+ var isMutator = this.mutators !== undef;
+
+ // check if we have a getter mutation
+ if (isMutator === true && _.isFunction(this.mutators[attr]) === true) {
+ return this.mutators[attr].call(this);
+ }
+
+ // check if we have a deeper nested getter mutation
+ if (isMutator === true && _.isObject(this.mutators[attr]) === true && _.isFunction(this.mutators[attr].get) === true) {
+ return this.mutators[attr].get.call(this);
+ }
+
+ return oldGet.call(this, attr);
+ };
+
+ // override set functionality to set the mutator props
+ Mutator.prototype.set = function (key, value, options) {
+ var isMutator = this.mutators !== undef,
+ ret = null,
+ attrs = null,
+ attr = null;
+
+ // seamleassly stolen from backbone core
+ // check if the setter action is triggered
+ // using key <-> value or object
+ if (_.isObject(key) || key === null) {
+ attrs = key;
+ options = value;
+ } else {
+ attrs = {};
+ attrs[key] = value;
+ }
+
+ // check if we have a deeper nested setter mutation
+ if (isMutator === true && _.isObject(this.mutators[key]) === true) {
+
+ // check if we need to set a single value
+ if (_.isFunction(this.mutators[key].set) === true) {
+ ret = this.mutators[key].set.call(this, key, attrs[key], options, _.bind(oldSet, this));
+ } else if (_.isFunction(this.mutators[key])) {
+ ret = this.mutators[key].call(this, key, attrs[key], options, _.bind(oldSet, this));
+ }
+ }
+
+ if (_.isObject(attrs)) {
+ _.each(attrs, _.bind(function (attr, attrKey) {
+ if (isMutator === true && _.isObject(this.mutators[attrKey]) === true) {
+ // check if we need to set a single value
+
+ var meth = this.mutators[attrKey];
+ if (_.isFunction(meth.set)) {
+ meth = meth.set;
+ }
+
+ if (_.isFunction(meth)) {
+ if (options === undef || (_.isObject(options) === true && options.silent !== true && (options.mutators !== undef && options.mutators.silent !== true))) {
+ this.trigger('mutators:set:' + attrKey);
+ }
+ ret = meth.call(this, attrKey, attr, options, _.bind(oldSet, this));
+ }
+
+ }
+ }, this));
+ }
+
+ //validation purposes
+ if (ret !== null) {
+ return ret;
+ }
+
+ return oldSet.call(this, key, value, options);
+ };
+
+ // override toJSON functionality to serialize mutator properties
+ Mutator.prototype.toJSON = function () {
+ // fetch ye olde values
+ var attr = oldToJson.call(this);
+ // iterate over all mutators (if there are some)
+ _.each(this.mutators, _.bind(function (mutator, name) {
+ // check if we have some getter mutations (nested or )
+ if (_.isObject(this.mutators[name]) === true && _.isFunction(this.mutators[name].get)) {
+ attr[name] = _.bind(this.mutators[name].get, this)();
+ } else {
+ attr[name] = _.bind(this.mutators[name], this)();
+ }
+ }, this));
+
+ return attr;
+ };
+
+ // extend the models prototype
+ _.extend(Backbone.Model.prototype, Mutator.prototype);
+
+ // make mutators globally available under the Backbone namespace
+ Backbone.Mutators = Mutator;
+ return Mutator;
+}));
\ No newline at end of file
diff --git a/NzbDrone.Web/_backboneApp/JsLibraries/backbone.shortcuts.js b/NzbDrone.Web/_backboneApp/JsLibraries/backbone.shortcuts.js
new file mode 100644
index 000000000..631112f77
--- /dev/null
+++ b/NzbDrone.Web/_backboneApp/JsLibraries/backbone.shortcuts.js
@@ -0,0 +1,35 @@
+(function() {
+ var Shortcuts;
+
+ Shortcuts = function(options) {
+ this.cid = _.uniqueId("backbone.shortcuts");
+ this.initialize.apply(this, arguments);
+ return this.delegateShortcuts();
+ };
+
+ _.extend(Shortcuts.prototype, Backbone.Events, {
+ initialize: function() {},
+ delegateShortcuts: function() {
+ var callback, match, method, scope, shortcut, shortcutKey, _ref, _results;
+ if (!this.shortcuts) return;
+ _ref = this.shortcuts;
+ _results = [];
+ for (shortcut in _ref) {
+ callback = _ref[shortcut];
+ if (!_.isFunction(callback)) method = this[callback];
+ if (!method) throw new Error("Method " + callback + " does not exist");
+ match = shortcut.match(/^(\S+)\s*(.*)$/);
+ shortcutKey = match[1];
+ scope = match[2] === "" ? "all" : match[2];
+ method = _.bind(method, this);
+ _results.push(key(shortcutKey, scope, method));
+ }
+ return _results;
+ }
+ });
+
+ Backbone.Shortcuts = Shortcuts;
+
+ Backbone.Shortcuts.extend = Backbone.View.extend;
+
+}).call(this);
diff --git a/NzbDrone.Web/_backboneApp/JsLibraries/marionette.viewswapper.js b/NzbDrone.Web/_backboneApp/JsLibraries/marionette.viewswapper.js
new file mode 100644
index 000000000..bd19cbb80
--- /dev/null
+++ b/NzbDrone.Web/_backboneApp/JsLibraries/marionette.viewswapper.js
@@ -0,0 +1,212 @@
+https://github.com/marionettejs/backbone.marionette/blob/viewswap/docs/marionette.viewswapper.md
+
+// View Swapper
+// ------------
+//
+// Switch out views based on events that are triggered
+// by the currently displayed view. Enables easy "edit in
+// place" features, "loading" screens, and more.
+
+ Marionette.ViewSwapper = Marionette.View.extend({
+ constructor: function (options) {
+ this._swapperViews = {};
+ this._swapperBindings = new Marionette.EventBinder();
+ this._currentViewBindings = new Marionette.EventBinder();
+
+ Marionette.View.prototype.constructor.apply(this, arguments);
+
+ this.views = Marionette.getOption(this, "views");
+ this.swapOn = Marionette.getOption(this, "swapOn");
+ this.initialView = Marionette.getOption(this, "initialView");
+
+ this._setupViewEvents("swapper", this, this._swapperBindings);
+ },
+
+ // Render the current view. If no current view is set, it
+ // will render the `initialView` that was configured.
+ render: function () {
+ // set up the initial view to display, on first render
+ if (!this.currentView) {
+ var initialViewName = Marionette.getOption(this, "initialView");
+ this._swapView(initialViewName);
+ }
+
+ // render and show the new view
+ this.currentView.render();
+ this.$el.append(this.currentView.$el);
+
+ // setup a callback for the showView call to recieve
+ var done = _.bind(function () {
+ // trigger show/onShow on the previous view
+ if (this.currentView) {
+ Marionette.triggerMethod.call(this.currentView, "show");
+ Marionette.triggerMethod.call(this, "swap:in", this.currentView);
+ }
+ }, this);
+
+ // show the view, passing it the done callback
+ this.showView(this.currentView, done);
+ },
+
+ // Show a view that is being swapped in. Override this method to
+ // set up your own custom fade in / show method
+ showView: function (view, done) {
+ view.$el.show();
+ done();
+ },
+
+ // Hide a view that is being swapped out. Override this method to
+ // set up your own custom fade out / hide method
+ hideView: function (view, done) {
+ view.$el.hide();
+ done();
+ },
+
+ // Ensure the views that were configured for this view swapper get closed
+ close: function () {
+
+ // Close all of the configured views that we are swapping between
+ _.each(this.views, function (view, name) {
+ view.close();
+ });
+
+ // unbind all the events, and clean up any decorator views
+ this._swapperViews = {};
+ this._currentViewBindings.unbindAll();
+ this._swapperBindings.unbindAll();
+
+ // Close the base view that we extended from
+ Marionette.View.prototype.close.apply(this, arguments);
+ },
+
+ // Get a view by name, throwing an exception if the view instance
+ // is not found.
+ _getView: function (viewName) {
+ var originalView, error, views;
+ var swapperView = this._swapperViews[viewName];
+
+ // Do not allow the name "swapper" to be used as a target view
+ // or initial view. This is reserved for the ViewSwapper instance,
+ // when configuring `swapOn` events
+ if (viewName === "swapper") {
+ error = new Error("Cannot display 'swapper' as a view.");
+ error.name = "InvalidViewName";
+ throw error;
+ }
+
+ // Do we have a view with the specified name?
+ if (!swapperView) {
+ originalView = this.views[viewName];
+
+ // No view, so throw an exception
+ if (!originalView) {
+ error = new Error("Cannot show view in ViewSwapper. View '" + viewName + "' not found.");
+ error.name = "ViewNotFoundError";
+ throw error;
+ }
+
+ // Found the view, so build a Decorator around it
+ swapperView = this._buildSwapperView(originalView, viewName);
+ this._swapperViews[viewName] = swapperView;
+ }
+
+ return swapperView;
+ },
+
+ // Decorate the configured view with information that the view swapper
+ // needs, to keep track of the view's current state.
+ _buildSwapperView: function (originalView, viewName) {
+ var swapperView = Marionette.createObject(originalView);
+ _.extend(swapperView, {
+
+ viewName: viewName,
+ originalView: originalView,
+
+ // Prevent the underlying view from being rendered more than once
+ render: function () {
+ var value;
+
+ if (this._hasBeenRendered) {
+ return this;
+ } else {
+
+ // prevent any more rendering
+ this._hasBeenRendered = true;
+
+ // do the render
+ value = originalView.render.apply(originalView, arguments);
+
+ // trigger render/onRender
+ Marionette.triggerMethod.call(this, "render");
+
+ // return whatever was sent back to us
+ return value;
+ }
+ }
+
+ });
+
+ return swapperView;
+ },
+
+ // Set up the event handlers for the individual views, so that the
+ // swapping can happen when a view event is triggered
+ _setupViewEvents: function (viewName, view, bindings) {
+ if (!this.swapOn || !this.swapOn[viewName]) { return; }
+ var that = this;
+
+ // default to current view bindings, unless otherwise specified
+ if (!bindings) {
+ bindings = this._currentViewBindings;
+ }
+
+ // close the previous event bindings
+ bindings.unbindAll();
+
+ // set up the new view's event bindings
+ _.each(this.swapOn[viewName], function (targetViewName, eventName) {
+
+ bindings.bindTo(view, eventName, function () {
+ that._swapView(targetViewName);
+ });
+
+ });
+ },
+
+ // Do the swapping of the views to the new view, by name
+ _swapView: function (viewName) {
+
+ // only swap views if the target view is not the same
+ // as the current view
+ var view = this._getView(viewName);
+ if (view === this.currentView) {
+ return;
+ }
+
+ // Provide a callback function that will switch over to
+ // the new view, when called
+ var done = _.bind(function () {
+
+ // trigger hide/onHide on the previous view
+ if (this.currentView) {
+ Marionette.triggerMethod.call(this.currentView, "hide");
+ Marionette.triggerMethod.call(this, "swap:out", this.currentView);
+ }
+
+ // get the next view, configure it's events and render it
+ this._setupViewEvents(viewName, view);
+ this.currentView = view;
+ this.render();
+
+ }, this);
+
+ if (this.currentView) {
+ // if we have a current view, hide it so that the new
+ // view can be show in it's place
+ this.hideView(this.currentView, done);
+ } else {
+ // no current view, so just switch to the new view
+ done();
+ }
+ }
+ });
\ No newline at end of file
diff --git a/NzbDrone.Web/_backboneApp/JsLibraries/sugar.js b/NzbDrone.Web/_backboneApp/JsLibraries/sugar.js
new file mode 100644
index 000000000..193b22384
--- /dev/null
+++ b/NzbDrone.Web/_backboneApp/JsLibraries/sugar.js
@@ -0,0 +1,8637 @@
+/*
+ * Sugar Library vedge
+ *
+ * Freely distributable and licensed under the MIT-style license.
+ * Copyright (c) 2013 Andrew Plummer
+ * http://sugarjs.com/
+ *
+ * ---------------------------- */
+(function(){
+ /***
+ * @package Core
+ * @description Internal utility and common methods.
+ ***/
+
+
+ // A few optimizations for Google Closure Compiler will save us a couple kb in the release script.
+ var object = Object, array = Array, regexp = RegExp, date = Date, string = String, number = Number, math = Math, Undefined;
+
+ // Internal toString
+ var internalToString = object.prototype.toString;
+
+ // The global context
+ var globalContext = typeof global !== 'undefined' ? global : this;
+
+ // Type check methods need a way to be accessed dynamically outside global context.
+ var typeChecks = {};
+
+ // defineProperty exists in IE8 but will error when trying to define a property on
+ // native objects. IE8 does not have defineProperies, however, so this check saves a try/catch block.
+ var definePropertySupport = object.defineProperty && object.defineProperties;
+
+
+ // Class initializers and class helpers
+
+ var ClassNames = 'Array,Boolean,Date,Function,Number,String,RegExp'.split(',');
+
+ var isArray = buildClassCheck(ClassNames[0]);
+ var isBoolean = buildClassCheck(ClassNames[1]);
+ var isDate = buildClassCheck(ClassNames[2]);
+ var isFunction = buildClassCheck(ClassNames[3]);
+ var isNumber = buildClassCheck(ClassNames[4]);
+ var isString = buildClassCheck(ClassNames[5]);
+ var isRegExp = buildClassCheck(ClassNames[6]);
+
+ function buildClassCheck(name) {
+ var type, fn;
+ if(/String|Number|Boolean/.test(name)) {
+ type = name.toLowerCase();
+ }
+ fn = (name === 'Array' && array.isArray) || function(obj) {
+ if(type && typeof obj === type) {
+ return true;
+ }
+ return className(obj) === '[object '+name+']';
+ }
+ typeChecks[name] = fn;
+ return fn;
+ }
+
+ function className(obj) {
+ return internalToString.call(obj);
+ }
+
+ function initializeClasses() {
+ initializeClass(object);
+ iterateOverObject(ClassNames, function(i,name) {
+ initializeClass(globalContext[name]);
+ });
+ }
+
+ function initializeClass(klass) {
+ if(klass['SugarMethods']) return;
+ defineProperty(klass, 'SugarMethods', {});
+ extend(klass, false, false, {
+ 'extend': function(methods, override, instance) {
+ extend(klass, instance !== false, override, methods);
+ },
+ 'sugarRestore': function() {
+ return batchMethodExecute(klass, arguments, function(target, name, m) {
+ defineProperty(target, name, m.method);
+ });
+ },
+ 'sugarRevert': function() {
+ return batchMethodExecute(klass, arguments, function(target, name, m) {
+ if(m.existed) {
+ defineProperty(target, name, m.original);
+ } else {
+ delete target[name];
+ }
+ });
+ }
+ });
+ }
+
+ // Class extending methods
+
+ function extend(klass, instance, override, methods) {
+ var extendee = instance ? klass.prototype : klass;
+ initializeClass(klass);
+ iterateOverObject(methods, function(name, method) {
+ var original = extendee[name];
+ var existed = hasOwnProperty(extendee, name);
+ if(typeof override === 'function') {
+ method = wrapNative(extendee[name], method, override);
+ }
+ if(override !== false || !extendee[name]) {
+ defineProperty(extendee, name, method);
+ }
+ // If the method is internal to Sugar, then store a reference so it can be restored later.
+ klass['SugarMethods'][name] = { instance: instance, method: method, original: original, existed: existed };
+ });
+ }
+
+ function extendSimilar(klass, instance, override, set, fn) {
+ var methods = {};
+ set = isString(set) ? set.split(',') : set;
+ set.forEach(function(name, i) {
+ fn(methods, name, i);
+ });
+ extend(klass, instance, override, methods);
+ }
+
+ function batchMethodExecute(klass, args, fn) {
+ var all = args.length === 0, methods = multiArgs(args), changed = false;
+ iterateOverObject(klass['SugarMethods'], function(name, m) {
+ if(all || methods.indexOf(name) > -1) {
+ changed = true;
+ fn(m.instance ? klass.prototype : klass, name, m);
+ }
+ });
+ return changed;
+ }
+
+ function wrapNative(nativeFn, extendedFn, condition) {
+ return function() {
+ var fn;
+ if(nativeFn && (condition === true || !condition.apply(this, arguments))) {
+ fn = nativeFn;
+ } else {
+ fn = extendedFn;
+ }
+ return fn.apply(this, arguments);
+ }
+ }
+
+ function defineProperty(target, name, method) {
+ if(definePropertySupport) {
+ object.defineProperty(target, name, { 'value': method, 'configurable': true, 'enumerable': false, 'writable': true });
+ } else {
+ target[name] = method;
+ }
+ }
+
+
+ // Argument helpers
+
+ function multiArgs(args, fn) {
+ var result = [], i, len;
+ for(i = 0, len = args.length; i < len; i++) {
+ result.push(args[i]);
+ if(fn) fn.call(args, args[i], i);
+ }
+ return result;
+ }
+
+ function flattenedArgs(obj, fn, from) {
+ multiArgs(array.prototype.concat.apply([], array.prototype.slice.call(obj, from || 0)), fn);
+ }
+
+ function checkCallback(fn) {
+ if(!fn || !fn.call) {
+ throw new TypeError('Callback is not callable');
+ }
+ }
+
+
+ // General helpers
+
+ function isDefined(o) {
+ return o !== Undefined;
+ }
+
+ function isUndefined(o) {
+ return o === Undefined;
+ }
+
+
+ // Object helpers
+
+ function isObjectPrimitive(obj) {
+ // Check for null
+ return obj && typeof obj === 'object';
+ }
+
+ function isObject(obj) {
+ // === on the constructor is not safe across iframes
+ // 'hasOwnProperty' ensures that the object also inherits
+ // from Object, which is false for DOMElements in IE.
+ return !!obj && className(obj) === '[object Object]' && 'hasOwnProperty' in obj;
+ }
+
+ function hasOwnProperty(obj, key) {
+ return object['hasOwnProperty'].call(obj, key);
+ }
+
+ function iterateOverObject(obj, fn) {
+ var key;
+ for(key in obj) {
+ if(!hasOwnProperty(obj, key)) continue;
+ if(fn.call(obj, key, obj[key], obj) === false) break;
+ }
+ }
+
+ function simpleMerge(target, source) {
+ iterateOverObject(source, function(key) {
+ target[key] = source[key];
+ });
+ return target;
+ }
+
+ // Hash definition
+
+ function Hash(obj) {
+ simpleMerge(this, obj);
+ };
+
+ Hash.prototype.constructor = object;
+
+ // Number helpers
+
+ function getRange(start, stop, fn, step) {
+ var arr = [], i = parseInt(start), down = step < 0;
+ while((!down && i <= stop) || (down && i >= stop)) {
+ arr.push(i);
+ if(fn) fn.call(this, i);
+ i += step || 1;
+ }
+ return arr;
+ }
+
+ function round(val, precision, method) {
+ var fn = math[method || 'round'];
+ var multiplier = math.pow(10, math.abs(precision || 0));
+ if(precision < 0) multiplier = 1 / multiplier;
+ return fn(val * multiplier) / multiplier;
+ }
+
+ function ceil(val, precision) {
+ return round(val, precision, 'ceil');
+ }
+
+ function floor(val, precision) {
+ return round(val, precision, 'floor');
+ }
+
+ function padNumber(num, place, sign, base) {
+ var str = math.abs(num).toString(base || 10);
+ str = repeatString(place - str.replace(/\.\d+/, '').length, '0') + str;
+ if(sign || num < 0) {
+ str = (num < 0 ? '-' : '+') + str;
+ }
+ return str;
+ }
+
+ function getOrdinalizedSuffix(num) {
+ if(num >= 11 && num <= 13) {
+ return 'th';
+ } else {
+ switch(num % 10) {
+ case 1: return 'st';
+ case 2: return 'nd';
+ case 3: return 'rd';
+ default: return 'th';
+ }
+ }
+ }
+
+
+ // String helpers
+
+ // WhiteSpace/LineTerminator as defined in ES5.1 plus Unicode characters in the Space, Separator category.
+ function getTrimmableCharacters() {
+ return '\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u2028\u2029\u3000\uFEFF';
+ }
+
+ function repeatString(times, str) {
+ return array(math.max(0, isDefined(times) ? times : 1) + 1).join(str || '');
+ }
+
+
+ // RegExp helpers
+
+ function getRegExpFlags(reg, add) {
+ var flags = reg.toString().match(/[^/]*$/)[0];
+ if(add) {
+ flags = (flags + add).split('').sort().join('').replace(/([gimy])\1+/g, '$1');
+ }
+ return flags;
+ }
+
+ function escapeRegExp(str) {
+ if(!isString(str)) str = string(str);
+ return str.replace(/([\\/'*+?|()\[\]{}.^$])/g,'\\$1');
+ }
+
+
+ // Specialized helpers
+
+
+ // Used by Array#unique and Object.equal
+
+ function stringify(thing, stack) {
+ var type = typeof thing,
+ thingIsObject,
+ thingIsArray,
+ klass, value,
+ arr, key, i, len;
+
+ // Return quickly if string to save cycles
+ if(type === 'string') return thing;
+
+ klass = internalToString.call(thing)
+ thingIsObject = isObject(thing);
+ thingIsArray = klass === '[object Array]';
+
+ if(thing != null && thingIsObject || thingIsArray) {
+ // This method for checking for cyclic structures was egregiously stolen from
+ // the ingenious method by @kitcambridge from the Underscore script:
+ // https://github.com/documentcloud/underscore/issues/240
+ if(!stack) stack = [];
+ // Allowing a step into the structure before triggering this
+ // script to save cycles on standard JSON structures and also to
+ // try as hard as possible to catch basic properties that may have
+ // been modified.
+ if(stack.length > 1) {
+ i = stack.length;
+ while (i--) {
+ if (stack[i] === thing) {
+ return 'CYC';
+ }
+ }
+ }
+ stack.push(thing);
+ value = string(thing.constructor);
+ arr = thingIsArray ? thing : object.keys(thing).sort();
+ for(i = 0, len = arr.length; i < len; i++) {
+ key = thingIsArray ? i : arr[i];
+ value += key + stringify(thing[key], stack);
+ }
+ stack.pop();
+ } else if(1 / thing === -Infinity) {
+ value = '-0';
+ } else {
+ value = string(thing && thing.valueOf ? thing.valueOf() : thing);
+ }
+ return type + klass + value;
+ }
+
+ function isEqual(a, b) {
+ if(objectIsMatchedByValue(a) && objectIsMatchedByValue(b)) {
+ return stringify(a) === stringify(b);
+ } else {
+ return a === b;
+ }
+ }
+
+ function objectIsMatchedByValue(obj) {
+ var klass = className(obj);
+ return /^\[object Date|Array|String|Number|RegExp|Boolean|Arguments\]$/.test(klass) ||
+ isObject(obj);
+ }
+
+
+ // Used by Array#at and String#at
+
+ function entryAtIndex(arr, args, str) {
+ var result = [], length = arr.length, loop = args[args.length - 1] !== false, r;
+ multiArgs(args, function(index) {
+ if(isBoolean(index)) return false;
+ if(loop) {
+ index = index % length;
+ if(index < 0) index = length + index;
+ }
+ r = str ? arr.charAt(index) || '' : arr[index];
+ result.push(r);
+ });
+ return result.length < 2 ? result[0] : result;
+ }
+
+
+ // Object class methods implemented as instance methods
+
+ function buildObjectInstanceMethods(set, target) {
+ extendSimilar(target, true, false, set, function(methods, name) {
+ methods[name + (name === 'equal' ? 's' : '')] = function() {
+ return object[name].apply(null, [this].concat(multiArgs(arguments)));
+ }
+ });
+ }
+
+ initializeClasses();
+
+
+
+ /***
+ * @package ES5
+ * @description Shim methods that provide ES5 compatible functionality. This package can be excluded if you do not require legacy browser support (IE8 and below).
+ *
+ ***/
+
+
+ /***
+ * Object module
+ *
+ ***/
+
+ extend(object, false, false, {
+
+ 'keys': function(obj) {
+ var keys = [];
+ if(!isObjectPrimitive(obj) && !isRegExp(obj) && !isFunction(obj)) {
+ throw new TypeError('Object required');
+ }
+ iterateOverObject(obj, function(key, value) {
+ keys.push(key);
+ });
+ return keys;
+ }
+
+ });
+
+
+ /***
+ * Array module
+ *
+ ***/
+
+ // ECMA5 methods
+
+ function arrayIndexOf(arr, search, fromIndex, increment) {
+ var length = arr.length,
+ fromRight = increment == -1,
+ start = fromRight ? length - 1 : 0,
+ index = toIntegerWithDefault(fromIndex, start);
+ if(index < 0) {
+ index = length + index;
+ }
+ if((!fromRight && index < 0) || (fromRight && index >= length)) {
+ index = start;
+ }
+ while((fromRight && index >= 0) || (!fromRight && index < length)) {
+ if(arr[index] === search) {
+ return index;
+ }
+ index += increment;
+ }
+ return -1;
+ }
+
+ function arrayReduce(arr, fn, initialValue, fromRight) {
+ var length = arr.length, count = 0, defined = isDefined(initialValue), result, index;
+ checkCallback(fn);
+ if(length == 0 && !defined) {
+ throw new TypeError('Reduce called on empty array with no initial value');
+ } else if(defined) {
+ result = initialValue;
+ } else {
+ result = arr[fromRight ? length - 1 : count];
+ count++;
+ }
+ while(count < length) {
+ index = fromRight ? length - count - 1 : count;
+ if(index in arr) {
+ result = fn(result, arr[index], index, arr);
+ }
+ count++;
+ }
+ return result;
+ }
+
+ function toIntegerWithDefault(i, d) {
+ if(isNaN(i)) {
+ return d;
+ } else {
+ return parseInt(i >> 0);
+ }
+ }
+
+ function checkFirstArgumentExists(args) {
+ if(args.length === 0) {
+ throw new TypeError('First argument must be defined');
+ }
+ }
+
+
+
+
+ extend(array, false, false, {
+
+ /***
+ *
+ * @method Array.isArray( )
+ * @returns Boolean
+ * @short Returns true if is an Array.
+ * @extra This method is provided for browsers that don't support it internally.
+ * @example
+ *
+ * Array.isArray(3) -> false
+ * Array.isArray(true) -> false
+ * Array.isArray('wasabi') -> false
+ * Array.isArray([1,2,3]) -> true
+ *
+ ***/
+ 'isArray': function(obj) {
+ return isArray(obj);
+ }
+
+ });
+
+
+ extend(array, true, false, {
+
+ /***
+ * @method every(, [scope])
+ * @returns Boolean
+ * @short Returns true if all elements in the array match .
+ * @extra [scope] is the %this% object. %all% is provided an alias. In addition to providing this method for browsers that don't support it natively, this method also implements @array_matching.
+ * @example
+ *
+ + ['a','a','a'].every(function(n) {
+ * return n == 'a';
+ * });
+ * ['a','a','a'].every('a') -> true
+ * [{a:2},{a:2}].every({a:2}) -> true
+ ***/
+ 'every': function(fn, scope) {
+ var length = this.length, index = 0;
+ checkFirstArgumentExists(arguments);
+ while(index < length) {
+ if(index in this && !fn.call(scope, this[index], index, this)) {
+ return false;
+ }
+ index++;
+ }
+ return true;
+ },
+
+ /***
+ * @method some(, [scope])
+ * @returns Boolean
+ * @short Returns true if any element in the array matches .
+ * @extra [scope] is the %this% object. %any% is provided as an alias. In addition to providing this method for browsers that don't support it natively, this method also implements @array_matching.
+ * @example
+ *
+ + ['a','b','c'].some(function(n) {
+ * return n == 'a';
+ * });
+ + ['a','b','c'].some(function(n) {
+ * return n == 'd';
+ * });
+ * ['a','b','c'].some('a') -> true
+ * [{a:2},{b:5}].some({a:2}) -> true
+ ***/
+ 'some': function(fn, scope) {
+ var length = this.length, index = 0;
+ checkFirstArgumentExists(arguments);
+ while(index < length) {
+ if(index in this && fn.call(scope, this[index], index, this)) {
+ return true;
+ }
+ index++;
+ }
+ return false;
+ },
+
+ /***
+ * @method map( |