<commit_before>define(function(require) {
    describe('Minionette.View', function() {
        beforeEach(function() {
            this.view = new Minionette.View;
        })
        afterEach(function() {
            this.view.remove();
        });

        it('Exists', function() {
            expect(Minionette.View).to.not.be.undefined;
        });

        describe("constructor", function() {
            it("calls Backbone.View's constructor", function() {
                var spy = this.sinon.spy(Backbone, 'View');

                new Minionette.View;

                expect(spy).to.have.been.called;
            });

            it("initializes _subViews", function() {
                expect(this.view._subViews).to.not.be.undefined;
            });

            xit("listens for model events", function() {});
            xit("listens for collection events", function() {});

        });

        describe("instances", function() {
            it("creates #template function", function() {
                expect(this.view.template).to.be.a.function;
            });

            describe("#delegateEvents", function() {
                it("calls Backbone.View's #delegateEvents", function() {
                    var spy = this.sinon.spy(Backbone.View.prototype, 'delegateEvents');

                    this.view.delegateEvents();

                    expect(spy).to.have.been.called;
                });
            });

            describe("#remove", function() {
                it("triggers remove:before event", function() {
                    var spy = this.sinon.spy();
                    this.view.on('remove:before', spy);

                    this.view.remove();

                    expect(spy).to.have.been.called;
                });

                it("triggers remove event", function() {
                    var spy = this.sinon.spy();
                    this.view.on('remove', spy);

                    this.view.remove();

                    expect(spy).to.have.been.called;
                });

                it("removes subViews", function() {
                    var v = new Backbone.View,
                        spy = this.sinon.spy(v, 'remove');
                    this.view._subViews[0] = v;

                    this.view.remove();

                    expect(spy).to.have.been.called;
                });
            });

            describe("#assign", function() {
                xit();
            });

            describe("#_jqueryRemove", function() {
                it("is called when jQuery removes $el", function() {
                    var spy = this.sinon.spy(this.view, '_jqueryRemove');

                    this.view.delegateEvents();
                    this.view.$el.remove();

                    expect(spy).to.have.been.called;
                });

                it("is not called with #undelegateEvents", function() {
                    var spy = this.sinon.spy(this.view, '_jqueryRemove');

                    this.view.delegateEvents();
                    this.view.undelegateEvents();

                    expect(spy).to.not.have.been.called;
                });
            });
        });
    });
});
<commit_msg>More Minionette.View testing.<commit_after>define(function(require) {
    describe('Minionette.View', function() {
        beforeEach(function() {
            this.view = new Minionette.View;
        })
        afterEach(function() {
            this.view.remove();
        });

        it('Exists', function() {
            expect(Minionette.View).to.not.be.undefined;
        });

        describe("constructor", function() {
            it("calls Backbone.View's constructor", function() {
                var spy = this.sinon.spy(Backbone, 'View');

                new Minionette.View;

                expect(spy).to.have.been.called;
            });

            it("initializes _subViews", function() {
                expect(this.view._subViews).to.not.be.undefined;
            });

            xit("listens for model events", function() {});
            xit("listens for collection events", function() {});

        });

        describe("instances", function() {
            it("creates #template function", function() {
                expect(this.view.template).to.be.a.function;
            });

            describe("#delegateEvents", function() {
                it("calls Backbone.View's #delegateEvents", function() {
                    var spy = this.sinon.spy(Backbone.View.prototype, 'delegateEvents');

                    this.view.delegateEvents();

                    expect(spy).to.have.been.called;
                });
            });

            describe("#remove", function() {
                it("triggers remove:before event", function() {
                    var spy = this.sinon.spy();
                    this.view.on('remove:before', spy);

                    this.view.remove();

                    expect(spy).to.have.been.called;
                });

                it("triggers remove event", function() {
                    var spy = this.sinon.spy();
                    this.view.on('remove', spy);

                    this.view.remove();

                    expect(spy).to.have.been.called;
                });

                it("removes subViews", function() {
                    var v = new Backbone.View,
                        spy = this.sinon.spy(v, 'remove');
                    this.view._subViews[0] = v;

                    this.view.remove();

                    expect(spy).to.have.been.called;
                });
            });

            describe("#assign", function() {
                beforeEach(function() {
                    this.subView = new Backbone.View({id: 'subView'});
                });

                it("takes a selector and a subView as arguments", function() {
                    var selector = '.selector',
                        spy = this.sinon.spy(this.subView, 'setElement');
                    this.view.$el.append('
');

                    this.view.assign(selector, this.subView);

                    expect(spy).to.have.been.calledWith(this.view.$(selector));
                });

                it("takes an object (with keys as selectors and values as subViews) as an argument", function() {
                    var subView2 = new Backbone.View,
                        subViews = {
                            '.selector': this.subView,
                            '.test': subView2
                        },
                        spy = this.sinon.spy(this.subView, 'setElement'),
                        spy2 = this.sinon.spy(subView2, 'setElement');
                    this.view.$el.append('
');
                    this.view.$el.append('
');

                    this.view.assign(subViews);

                    expect(spy).to.have.been.calledWith(this.view.$('.selector'));
                    expect(spy2).to.have.been.calledWith(this.view.$('.test'));
                });

                describe("replace param", function() {
                    it("can optionally take a replace param", function() {
                        var selector = '.selector',
                        spy = this.sinon.spy(this.subView, 'setElement');
                        this.view.$el.append('
');

                        this.view.assign(selector, this.subView, true);

                        expect(spy).to.not.have.been.called;
                        expect(this.view.$(this.subView.el).get(0)).to.not.be.undefined;
                    });

                    it("can optionally take a replace param with alternate syntax", function() {
                        var subView2 = new Backbone.View,
                        subViews = {
                            '.selector': this.subView,
                            '.test': subView2
                        },
                        spy = this.sinon.spy(this.subView, 'setElement'),
                        spy2 = this.sinon.spy(subView2, 'setElement');
                        this.view.$el.append('
');
                        this.view.$el.append('
');

                        this.view.assign(subViews, true);

                        expect(spy).to.not.have.been.calledWith(this.view.$('.selector'));
                        expect(spy2).to.not.have.been.calledWith(this.view.$('.test'));
                    });
                });
            });

            describe("#_jqueryRemove", function() {
                it("is called when jQuery removes $el", function() {
                    var spy = this.sinon.spy(this.view, '_jqueryRemove');

                    this.view.delegateEvents();
                    this.view.$el.remove();

                    expect(spy).to.have.been.called;
                });

                it("is not called with #undelegateEvents", function() {
                    // Why are we testing this?
                    // With the way jQuery defines special events,
                    // the "remove" event will actually fire whenever we
                    // remove the event handler from the DOM element, calling
                    // the callback. We don't want that.
                    var spy = this.sinon.spy(this.view, '_jqueryRemove');

                    this.view.delegateEvents();
                    this.view.undelegateEvents();

                    expect(spy).to.not.have.been.called;
                });
            });
        });
    });
});
<|endoftext|>


<commit_before>define([
    'bootstrap/core'
], function (Core) {
    'use strict';

    var core;

    beforeEach(function() {
        core = new Core();
    });

    describe('Artist Init', function() {
        it('it can create an Artist', function() {
            core.isRunning = true;
            expect(core.isRunning).toBe(true);
        });
    });
});<commit_msg>changed titles<commit_after>define([
    'bootstrap/core'
], function (Core) {
    'use strict';

    var core;

    beforeEach(function() {
        core = new Core();
    });

    describe('Core Init', function() {
        it('it can create a Core', function() {
            core.isRunning = true;
            expect(core.isRunning).toBe(true);
        });
    });
});<|endoftext|>


<commit_before>"use strict";

const express = require('express');
const app = express();
const record = require('node-record-lpcm16')
const snowboy = require('snowboy');
const Models = snowboy.Models;
const Detector = snowboy.Detector;
const http = require('http').Server(app);
const ApiAi = require('apiai-promise')
const config = require('./config.js');
const apiai = ApiAi(config.apiaitoken);
const sessionId = 'marcel';
const models = new Models();
const ip = require("ip");

models.add({
  file: 'resources/marcel.pmdl',
  sensitivity: '0.4',
  hotwords: 'marcel'
})

const detector = new Detector({
    resource: 'resources/common.res',
    audioGain: 2.0,
    models
})

app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Headers', 'X-Requested-With')
  next()
})

app.get('/', (req, res) => {
  res.sendfile("public/index.html");
});

/**
 * Server itself
 * @type {http.Server}
 */
const server = app.listen(8080, () => {
  //print few information about the server
  const host = server.address().address;
  const port = server.address().port;
  console.log("Server running and listening @ " + host + ":" + port);
});
const io = require('socket.io')(server);

io.on('connection', socket => {
  socket.on('speech', (speech) => {
    apiai.textRequest(speech.message, { sessionId })
         .then((response) => {
            console.log(response)
            if (response.result.metadata.intentName === 'YoutubeSearch') {
              io.sockets.emit('youtube', {type: "search", content: response.result.parameters.video});
            }

            if (response.result.metadata.intentName === 'YoutubePause') {
              io.sockets.emit('youtube', {type: "pause"});
            }

            if (response.result.metadata.intentName === 'YoutubePlay') {
              io.sockets.emit('youtube', {type: "play"});
            }

            if (response.result.metadata.intentName === "Planning") {
              io.sockets.emit('devfest', {type: "planning"});
            }

            if (response.result.metadata.intentName === 'CurrentTalk') {
              io.sockets.emit('devfest', {type: "current", location: response.result.parameters.location});
            }

            if (response.result.metadata.intentName === 'NextTalk') {
              io.sockets.emit('devfest', {type: "next", location: response.result.parameters.location});
            }

            if (response.result.metadata.intentName === 'CloseDisplayed') {
              io.sockets.emit('close');
            }

            if (response.result.metadata.intentName === 'Speaker') {
              io.sockets.emit('devfest', {type: "speaker", name: response.result.parameters.speaker});
            }

            if (response.result.metadata.intentName === 'Talk') {
              io.sockets.emit('devfest', {type: "talk", title: response.result.parameters.title});
            }
          })
          .catch(err => console.log(err));
  });

})

detector.on('hotword', (index, hotword) => {
  console.log('hotword detected');
  io.sockets.emit('hotword');
})

const mic = record.start(config.microphone);
mic.pipe(detector);<commit_msg>Fix missing ip module<commit_after>"use strict";

const express = require('express');
const app = express();
const record = require('node-record-lpcm16')
const snowboy = require('snowboy');
const Models = snowboy.Models;
const Detector = snowboy.Detector;
const http = require('http').Server(app);
const ApiAi = require('apiai-promise')
const config = require('./config.js');
const apiai = ApiAi(config.apiaitoken);
const sessionId = 'marcel';
const models = new Models();

models.add({
  file: 'resources/marcel.pmdl',
  sensitivity: '0.4',
  hotwords: 'marcel'
})

const detector = new Detector({
    resource: 'resources/common.res',
    audioGain: 2.0,
    models
})

app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Headers', 'X-Requested-With')
  next()
})

app.get('/', (req, res) => {
  res.sendfile("public/index.html");
});

/**
 * Server itself
 * @type {http.Server}
 */
const server = app.listen(8080, () => {
  //print few information about the server
  const host = server.address().address;
  const port = server.address().port;
  console.log("Server running and listening @ " + host + ":" + port);
});
const io = require('socket.io')(server);

io.on('connection', socket => {
  socket.on('speech', (speech) => {
    apiai.textRequest(speech.message, { sessionId })
         .then((response) => {
            console.log(response)
            if (response.result.metadata.intentName === 'YoutubeSearch') {
              io.sockets.emit('youtube', {type: "search", content: response.result.parameters.video});
            }

            if (response.result.metadata.intentName === 'YoutubePause') {
              io.sockets.emit('youtube', {type: "pause"});
            }

            if (response.result.metadata.intentName === 'YoutubePlay') {
              io.sockets.emit('youtube', {type: "play"});
            }

            if (response.result.metadata.intentName === "Planning") {
              io.sockets.emit('devfest', {type: "planning"});
            }

            if (response.result.metadata.intentName === 'CurrentTalk') {
              io.sockets.emit('devfest', {type: "current", location: response.result.parameters.location});
            }

            if (response.result.metadata.intentName === 'NextTalk') {
              io.sockets.emit('devfest', {type: "next", location: response.result.parameters.location});
            }

            if (response.result.metadata.intentName === 'CloseDisplayed') {
              io.sockets.emit('close');
            }

            if (response.result.metadata.intentName === 'Speaker') {
              io.sockets.emit('devfest', {type: "speaker", name: response.result.parameters.speaker});
            }

            if (response.result.metadata.intentName === 'Talk') {
              io.sockets.emit('devfest', {type: "talk", title: response.result.parameters.title});
            }
          })
          .catch(err => console.log(err));
  });

})

detector.on('hotword', (index, hotword) => {
  console.log('hotword detected');
  io.sockets.emit('hotword');
})

const mic = record.start(config.microphone);
mic.pipe(detector);
<|endoftext|>


<commit_before>    /*
    Copyright (C) 2015 Tony Mobily

    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 ( define ){

      "use strict";
      /*jslint node: true */

      define( function( require, exports, module ){

        function getObjectBase( o, fn ){

          var found = false, currentPoint = o;
          var k, i, l;

          while( currentPoint ){

            var objMethods = Object.getOwnPropertyNames( currentPoint );
            for( i = 0, l = objMethods.length; i < l; i ++ ){
              k = objMethods[ i ];

              if( currentPoint.hasOwnProperty( k ) && currentPoint[ k ] === fn ){
                found = true;
                break;
              }
            }
            // If found, break out of the cycle. Otherwise, keep looking in the next proto up
            if( found ) break;
            currentPoint = Object.getPrototypeOf( currentPoint );
          }

          // Return crrentPoint. Note that if the search was unsccessful,
          // currentPoint will be `null`
          return { base: currentPoint, key: k };
        }

        // This will become a method call, so `this` is the object
        var getInherited = function( fn ){

          // Get the object's base
          var objectBase = getObjectBase( this, fn );

          // If the function is not found anywhere in the prototype chain
          // there is a pretty big problem
          if( ! objectBase.base ) throw new Error( "inherited coun't find method in chain (getInherited)" );

          // At this point, I know the key. To look for the super method, I
          // only have to check if one of the parent __proto__ has a matching key `k`
          var p = Object.getPrototypeOf( objectBase.base );
          return p[ objectBase.key ];
        };

        var makeInheritedFunction = function( type ){

          // This will become a method call, so `this` is the object
          return function( fn, args, cb ){

            // Get the inherited function
            var fnUp = this.getInherited( fn );

            // No inherited function in the chain, just call the callback (async) or return nothing
            if( ! fnUp  ){
              if( type === 'async' ) return cb.call( this, null );
              if( type === 'sync' ) return;
            }

            // Call the function. It could be sync or async
            if( type == 'async' ){
              var argsMinusCallback = Array.prototype.slice.call(args, 0, -1 ).concat( cb );
              return fnUp.apply( this, argsMinusCallback );
            } else {
              return fnUp.apply( this, args );
            }
          };
        };

        // This will be added as a Constructor-wide method
        // of constructor created with simpleDeclare (only if needed)
        var extend = function(){

          // Normalise the arguments passed
          var r = workoutDeclareArguments( arguments );
          r.SuperCtorList.unshift( this );

          // Will run declare with the 2-parameter signature
          return declare( r.SuperCtorList, r.protoMixin );
        };

        // Look for Ctor.prototype anywhere in the __proto__ chain.
        // Unlike Javascript's plain instanceof, this method attempts
        // to compare
        var instanceOf = function( Ctor ){

          var searchedProto = Ctor.OriginalConstructor ? Ctor.OriginalConstructor.prototype : Ctor.prototype;
          var current = this;
          var compare;

          while( ( current = Object.getPrototypeOf( current ) ) ){

            // It will compare either with OriginalConstructor.prototype or plain prototype
            compare = current.constructor.OriginalConstructor ?
                      current.constructor.OriginalConstructor.prototype :
                      current.constructor.prototype;

            // Actually run the comparison
            if( compare === searchedProto ) return true;
          }
          return false;
        };

        var makeConstructor = function( FromCtor, protoMixin, SourceOfProto ){

          var ActualConstructor;

          var ReturnedCtor = function(){

            // The object's main constructor is being run. It will be responsible of
            // running all of the constructors in the prototype chain, starting from
            // the innermost and moving all the way out, except the last one
            // (which is itself)
            if( Object.getPrototypeOf( this ).constructor === ReturnedCtor ){

              // Goes through the prototype chain and execute every single constructor.
              // If the constructor has the ActualConstructor attribute, then it's a SimpleDeclare
              // constructor
              var l = [];
              var o = this;
              while( ( o = Object.getPrototypeOf( o ) ) ){
                l.push( o.constructor );
              }
              for( var i = l.length - 1; i >=1; i -- ){
                l[ i ].apply( this, arguments );
              }
            }

            // Itself. Since I *know* this is a SimpleDeclare constructor,
            // run ActualConstructor if available
            if( ActualConstructor ) ActualConstructor.apply( this, arguments );
          };

          // protoMixin MUST be a valid, not-null object
          if( typeof( protoMixin ) !== 'object' || protoMixin === null) protoMixin = {};

          // Create the new function's prototype. It's a new object, which happens to
          // have its own prototype (__proto__) set as the superclass' prototype and the
          // `constructor` attribute set as FromCtor (the one we are about to return)
          ReturnedCtor.prototype = Object.create(FromCtor.prototype, {
            constructor: {
              value: ReturnedCtor,
              enumerable: false,
              writable: true,
              configurable: true
            }
          });

          // Copy every element in protoMixin into the prototype.
          // Note that `constructor` is special: it's _not_ copied over.
          // Instead, it's placed in ReturnedCtor.ActualConstructor.
          // It can either come:
          //   * from protoMixin, in cases where SourceOfProto is not defined
          //     (which means that it's what the developer passed herself in `protoMixin` as `constructor`)
          //   * from the source of protoMixin, in cases where SourceOfProto is defined
          //     (which means that we are taking it from the SourceOfProto, since the goal
          //      is to mimic it completely creating a working copy of the original constructor)
          var ownProps = Object.getOwnPropertyNames( protoMixin );
          for( var i = 0, l = ownProps.length; i < l; i ++ ){
            var k = ownProps[ i ];

            if( k !== 'constructor' ) ReturnedCtor.prototype[ k ] = protoMixin[ k ];
          }

          // We are not cloning a constructor, but creating a brand new one (using protoMixin as
          // a source of methods that just got added to the prototype).
          // ActualConstructor will be set to the `constructor` property of protoMixin
          if( ! SourceOfProto ){
            if( protoMixin.hasOwnProperty( 'constructor' ) ) ActualConstructor = protoMixin.constructor;
          }

          // We are in the process of cloning an existing constructor.
          // When doing that:
          // * ReturnedCtor's ActualConstructor will be set to the Source's ActualConstructor.
          //   This will ensure that the stock constructor (that just invokes ActualConstructor) works.
          // * ReturnedCtor's OriginalConstructor will be set to the Source's ActualConstructor (or the souce itself).
          //   This will ensure that we have a path to the actual constructor we actually cloned,
          //   so that instanceOf() will work (by checking ActualConstructor whenever possible)
          if( SourceOfProto ){
            ActualConstructor = SourceOfProto;
            ReturnedCtor.OriginalConstructor = SourceOfProto.OriginalConstructor || SourceOfProto;
          }

          // That's it!
          return ReturnedCtor;
        };

        var copyClassMethods = function( Source, Dest ){

          // Source class needs to be a function
          if( typeof( Source) !== 'function' ) throw new Error("Error: source constructor must be a function");

          // Copy class methods over, EXCEPT if it's Object (the base class) which is ALWAYS
          // gracefully ignored
          if( Source !== Object ){

            var ownProps = Object.getOwnPropertyNames( Source );
            for( var i = 0, l = ownProps.length; i < l; i ++){
              var property = ownProps[ i ];

              // This statement is there so that strict mode still works -- even accessing these
              // automatically like this will cause an error!
              if( property !== 'arguments' && property !== 'callee' && property !== 'caller' ){

                // It's one of the attributes' in Function()'s prototype: skip
                if( Function.prototype[ property ] === Source[ property ] || property === 'prototype' ) continue;
                // It's one of the attributes managed by simpleDeclare: skip
                if( [ 'extend', 'OriginalConstructor' ].indexOf( property ) !== -1 ) continue;
                if( property !== 'name') Dest[ property ] = Source[ property ];
              }
            }
          }
        };

        // This method will be attached to `list` in `declare()`,
        // and it will be used to make sure that only fresh prototypes
        // are added
        var constructorAlreadyInList = function( Ctor, list ){

          var CtorConstructor = Ctor.OriginalConstructor ||  Ctor;
          var protoConstructor;

          var found = false;
          for( var i = 0, l = list.length; i < l; i ++ ){
            var proto = list[ i ];

            protoConstructor = proto.constructor.OriginalConstructor || proto.constructor;
            if( protoConstructor === CtorConstructor ){
              found = true;
              break;
            }
          }

          return found;
        };

        // Normalise arguments passed to declare(): the end result will always be
        // an object with two values:
        //  * SuperCtorList - an array of constructors
        //  * protoMixin - an object with prototype attributes
        // This implies that you can actually throw basically anything that makes sense
        // to declare: declare(), declare( A ), declare( )
        var workoutDeclareArguments = function( args ){

          var arg;

          // No arguments at all: inheriting straight from Object, nothing in the prototype
          if( args.length === 0 ) return { SuperCtorList: [], protoMixin: {} };

          // One argument: it could be:
          //   * An array of constructors
          //   * A protoMixin
          //   * A single constructor
          //  Act accordingly
          if( args.length === 1 ){

            arg = args[ 0 ];
            // It's an array of constructors
            if( Array.isArray( arg ) )
              return { SuperCtorList: arg, protoMixin: {} };
            // It's a protoMixin
            else if( typeof arg === 'object' && arg !== null )
              return { SuperCtorList: [], protoMixin: arg };
            // It's a constructor
            else if( typeof arg === 'function' )
              return { SuperCtorList: [ arg ], protoMixin: {} };
            else
              throw new Error( "Invalid lone argument to declare(), needs to be array, function or pure object" );
          }

          // Two arguments. Only check if the first one is an array. If it is, then it's the list of
          // constructors, with second argument POSSIBLY being protoMixin
          if( args.length === 2 ){
            var a = args[ 0 ];
            var b = args[ 1 ];

            // First parameter is an array: that's the list of ctors
            if( Array.isArray( a ) ){
              // Normalise b
              if( typeof b !== 'object' || b === null ) b = {};
              return { SuperCtorList: a, protoMixin: b };
            }
          }

          // CHECK POINT: at this point, there are 2 or more arguments, and the "array as first proto" is
          // out of the picture. So, will go through args and make up the array based on it
          var lastIndex = args.length - 1;
          var list = [];
          // This cycle will deal with all items except the last one, which might be either a
          // constructor or a protoMixin
          for( var i = 0; i < lastIndex; i ++ ){
            var item = args[ i ];

            // Check that it's the right type, allowing for the last one to be an exception
            if( typeof item !== 'function' )
              //console.err( "ITEM: ", item );
              throw new Error("Parameters to declare() must be constructor functions (except the last one which can be an object for the prototype)");
            list.push( item );
          }

          // The last one might be either a constructor function, or a protoMixin.
          // Deal with either case
          var lastOne = args[ lastIndex ];
          if( typeof lastOne === 'function' ){
            list.push( lastOne );
            return { SuperCtorList: list, protoMixin: {} };
          } else if( typeof lastOne === 'object' && lastOne !== null ) {
            return { SuperCtorList: list, protoMixin: lastOne };
          } else throw new Error("Last argument of declare() must be either a function or a mixin object");
        };

        // Parameters are very variable
        var declare = function(){

          // These will be worked out from `arguments`
          var SuperCtorList, protoMixin;

          var MixedClass, ResultClass;
          var list = [];
          var i, l, ii, ll;
          var proto;

          var r = workoutDeclareArguments( arguments );
          SuperCtorList = r.SuperCtorList;
          protoMixin = r.protoMixin;

          // No parameters: inheriting from Object directly, no multiple inheritance
          if( SuperCtorList.length === 0 ){
            MixedClass = Object;
          }
          // Only one parameter: straght single inheritance.
          else if( SuperCtorList.length === 1 ){
            MixedClass = SuperCtorList[ 0 ];

          // More than one parameter: multiple inheritance at work
          // MixedClass will end up being an artificially made constructor
          // where the prototype chain is the sum of _every_ prototype in
          // every element of SuperCtorList (taking out duplicates)
          } else {
            MixedClass = Object;

            // NOW:
            // Go through every __proto__ of every derivative class, and augment
            // MixedClass by inheriting from A COPY OF each one of them.

            list = [];
            for( i = 0, l = SuperCtorList.length; i < l; i ++ ){

              // Get the prototype list, in the right order
              // (the reversed discovery order)
              // The result will be placed in `subList`
              var subList = [];
              proto = SuperCtorList[ i ].prototype;
              while( proto ){
                if( proto.constructor !== Object ) subList.push( proto );
                proto = Object.getPrototypeOf( proto );
              }
              subList = subList.reverse();

              // Add each element of sublist as long as it's not already in the main `list`
              for( ii = 0, ll = subList.length; ii < ll; ii ++ ){
                if( ! constructorAlreadyInList( subList[ ii ].constructor, list ) ) list.push( subList[ ii ] );
              }
            }

            // For each element in the prototype list that isn't Object(),
            // augment MixedClass with a copy of the new prototype
            for( ii = 0, ll = list.length; ii < ll; ii ++ ){
              proto = list[ ii ];

              var M = MixedClass;

              if( proto.constructor !== Object ){

                MixedClass = makeConstructor( MixedClass, proto, proto.constructor );
                copyClassMethods( M, MixedClass ); // Methods previously inherited

                copyClassMethods( proto.constructor, MixedClass ); // Extra methods from the father constructor
              }
            }
          }

          // Finally, inherit from the MixedClass, and add
          // class methods over
          // MixedClass might be:
          // * Object (coming from no inheritance),
          // * SuperCtorList[0] (coming from single inheritance)
          // * A constructor with the appropriate prototype chain (multiple inheritance)
          ResultClass = makeConstructor( MixedClass, protoMixin );
          copyClassMethods( MixedClass, ResultClass );

          // Add getInherited, inherited() and inheritedAsync() to the prototype
          // (only if they are not already there)
          if( ! ResultClass.prototype.getInherited ) {
            ResultClass.prototype.getInherited = getInherited;
          }
          if( ! ResultClass.prototype.inherited ) {
            ResultClass.prototype.inherited = makeInheritedFunction( 'sync' );
          }
          if( ! ResultClass.prototype.inheritedAsync ) {
            ResultClass.prototype.inheritedAsync = makeInheritedFunction( 'async' );
          }

          // Add instanceOf
          if( ! ResultClass.prototype.instanceOf ) {
            ResultClass.prototype.instanceOf = instanceOf;
          }

          // Add class-wide method `extend`
          ResultClass.extend = function(){
            return extend.apply( this, arguments );
          };

          // That's it!
          return ResultClass;
        };

        // Returned extra: declarableObject
        declare.extendableObject = declare( Object );

        exports = module.exports = declare;

      });
    }(
        typeof define == 'function' && define.amd ?
            define :
            function ( factory ) { factory( require, exports, module); }
    ));<commit_msg>Comment taken out<commit_after>    /*
    Copyright (C) 2015 Tony Mobily

    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 ( define ){

      "use strict";
      /*jslint node: true */

      define( function( require, exports, module ){

        function getObjectBase( o, fn ){

          var found = false, currentPoint = o;
          var k, i, l;

          while( currentPoint ){

            var objMethods = Object.getOwnPropertyNames( currentPoint );
            for( i = 0, l = objMethods.length; i < l; i ++ ){
              k = objMethods[ i ];

              if( currentPoint.hasOwnProperty( k ) && currentPoint[ k ] === fn ){
                found = true;
                break;
              }
            }
            // If found, break out of the cycle. Otherwise, keep looking in the next proto up
            if( found ) break;
            currentPoint = Object.getPrototypeOf( currentPoint );
          }

          // Return crrentPoint. Note that if the search was unsccessful,
          // currentPoint will be `null`
          return { base: currentPoint, key: k };
        }

        // This will become a method call, so `this` is the object
        var getInherited = function( fn ){

          // Get the object's base
          var objectBase = getObjectBase( this, fn );

          // If the function is not found anywhere in the prototype chain
          // there is a pretty big problem
          if( ! objectBase.base ) throw new Error( "inherited coun't find method in chain (getInherited)" );

          // At this point, I know the key. To look for the super method, I
          // only have to check if one of the parent __proto__ has a matching key `k`
          var p = Object.getPrototypeOf( objectBase.base );
          return p[ objectBase.key ];
        };

        var makeInheritedFunction = function( type ){

          // This will become a method call, so `this` is the object
          return function( fn, args, cb ){

            // Get the inherited function
            var fnUp = this.getInherited( fn );

            // No inherited function in the chain, just call the callback (async) or return nothing
            if( ! fnUp  ){
              if( type === 'async' ) return cb.call( this, null );
              if( type === 'sync' ) return;
            }

            // Call the function. It could be sync or async
            if( type == 'async' ){
              var argsMinusCallback = Array.prototype.slice.call(args, 0, -1 ).concat( cb );
              return fnUp.apply( this, argsMinusCallback );
            } else {
              return fnUp.apply( this, args );
            }
          };
        };

        // This will be added as a Constructor-wide method
        // of constructor created with simpleDeclare (only if needed)
        var extend = function(){

          // Normalise the arguments passed
          var r = workoutDeclareArguments( arguments );
          r.SuperCtorList.unshift( this );

          // Will run declare with the 2-parameter signature
          return declare( r.SuperCtorList, r.protoMixin );
        };

        // Look for Ctor.prototype anywhere in the __proto__ chain.
        // Unlike Javascript's plain instanceof, this method attempts
        // to compare
        var instanceOf = function( Ctor ){

          var searchedProto = Ctor.OriginalConstructor ? Ctor.OriginalConstructor.prototype : Ctor.prototype;
          var current = this;
          var compare;

          while( ( current = Object.getPrototypeOf( current ) ) ){

            // It will compare either with OriginalConstructor.prototype or plain prototype
            compare = current.constructor.OriginalConstructor ?
                      current.constructor.OriginalConstructor.prototype :
                      current.constructor.prototype;

            // Actually run the comparison
            if( compare === searchedProto ) return true;
          }
          return false;
        };

        var makeConstructor = function( FromCtor, protoMixin, SourceOfProto ){

          var ActualConstructor;

          var ReturnedCtor = function(){

            // The object's main constructor is being run. It will be responsible of
            // running all of the constructors in the prototype chain, starting from
            // the innermost and moving all the way out, except the last one
            // (which is itself)
            if( Object.getPrototypeOf( this ).constructor === ReturnedCtor ){

              // Goes through the prototype chain and execute every single constructor.
              var l = [];
              var o = this;
              while( ( o = Object.getPrototypeOf( o ) ) ){
                l.push( o.constructor );
              }
              for( var i = l.length - 1; i >=1; i -- ){
                l[ i ].apply( this, arguments );
              }
            }

            // Itself. Since I *know* this is a SimpleDeclare constructor,
            // run ActualConstructor if available
            if( ActualConstructor ) ActualConstructor.apply( this, arguments );
          };

          // protoMixin MUST be a valid, not-null object
          if( typeof( protoMixin ) !== 'object' || protoMixin === null) protoMixin = {};

          // Create the new function's prototype. It's a new object, which happens to
          // have its own prototype (__proto__) set as the superclass' prototype and the
          // `constructor` attribute set as FromCtor (the one we are about to return)
          ReturnedCtor.prototype = Object.create(FromCtor.prototype, {
            constructor: {
              value: ReturnedCtor,
              enumerable: false,
              writable: true,
              configurable: true
            }
          });

          // Copy every element in protoMixin into the prototype.
          // Note that `constructor` is special: it's _not_ copied over.
          // Instead, it's placed in ReturnedCtor.ActualConstructor.
          // It can either come:
          //   * from protoMixin, in cases where SourceOfProto is not defined
          //     (which means that it's what the developer passed herself in `protoMixin` as `constructor`)
          //   * from the source of protoMixin, in cases where SourceOfProto is defined
          //     (which means that we are taking it from the SourceOfProto, since the goal
          //      is to mimic it completely creating a working copy of the original constructor)
          var ownProps = Object.getOwnPropertyNames( protoMixin );
          for( var i = 0, l = ownProps.length; i < l; i ++ ){
            var k = ownProps[ i ];

            if( k !== 'constructor' ) ReturnedCtor.prototype[ k ] = protoMixin[ k ];
          }

          // We are not cloning a constructor, but creating a brand new one (using protoMixin as
          // a source of methods that just got added to the prototype).
          // ActualConstructor will be set to the `constructor` property of protoMixin
          if( ! SourceOfProto ){
            if( protoMixin.hasOwnProperty( 'constructor' ) ) ActualConstructor = protoMixin.constructor;
          }

          // We are in the process of cloning an existing constructor.
          // When doing that:
          // * ReturnedCtor's ActualConstructor will be set to the Source's ActualConstructor.
          //   This will ensure that the stock constructor (that just invokes ActualConstructor) works.
          // * ReturnedCtor's OriginalConstructor will be set to the Source's ActualConstructor (or the souce itself).
          //   This will ensure that we have a path to the actual constructor we actually cloned,
          //   so that instanceOf() will work (by checking ActualConstructor whenever possible)
          if( SourceOfProto ){
            ActualConstructor = SourceOfProto;
            ReturnedCtor.OriginalConstructor = SourceOfProto.OriginalConstructor || SourceOfProto;
          }

          // That's it!
          return ReturnedCtor;
        };

        var copyClassMethods = function( Source, Dest ){

          // Source class needs to be a function
          if( typeof( Source) !== 'function' ) throw new Error("Error: source constructor must be a function");

          // Copy class methods over, EXCEPT if it's Object (the base class) which is ALWAYS
          // gracefully ignored
          if( Source !== Object ){

            var ownProps = Object.getOwnPropertyNames( Source );
            for( var i = 0, l = ownProps.length; i < l; i ++){
              var property = ownProps[ i ];

              // This statement is there so that strict mode still works -- even accessing these
              // automatically like this will cause an error!
              if( property !== 'arguments' && property !== 'callee' && property !== 'caller' ){

                // It's one of the attributes' in Function()'s prototype: skip
                if( Function.prototype[ property ] === Source[ property ] || property === 'prototype' ) continue;
                // It's one of the attributes managed by simpleDeclare: skip
                if( [ 'extend', 'OriginalConstructor' ].indexOf( property ) !== -1 ) continue;
                if( property !== 'name') Dest[ property ] = Source[ property ];
              }
            }
          }
        };

        // This method will be attached to `list` in `declare()`,
        // and it will be used to make sure that only fresh prototypes
        // are added
        var constructorAlreadyInList = function( Ctor, list ){

          var CtorConstructor = Ctor.OriginalConstructor ||  Ctor;
          var protoConstructor;

          var found = false;
          for( var i = 0, l = list.length; i < l; i ++ ){
            var proto = list[ i ];

            protoConstructor = proto.constructor.OriginalConstructor || proto.constructor;
            if( protoConstructor === CtorConstructor ){
              found = true;
              break;
            }
          }

          return found;
        };

        // Normalise arguments passed to declare(): the end result will always be
        // an object with two values:
        //  * SuperCtorList - an array of constructors
        //  * protoMixin - an object with prototype attributes
        // This implies that you can actually throw basically anything that makes sense
        // to declare: declare(), declare( A ), declare( )
        var workoutDeclareArguments = function( args ){

          var arg;

          // No arguments at all: inheriting straight from Object, nothing in the prototype
          if( args.length === 0 ) return { SuperCtorList: [], protoMixin: {} };

          // One argument: it could be:
          //   * An array of constructors
          //   * A protoMixin
          //   * A single constructor
          //  Act accordingly
          if( args.length === 1 ){

            arg = args[ 0 ];
            // It's an array of constructors
            if( Array.isArray( arg ) )
              return { SuperCtorList: arg, protoMixin: {} };
            // It's a protoMixin
            else if( typeof arg === 'object' && arg !== null )
              return { SuperCtorList: [], protoMixin: arg };
            // It's a constructor
            else if( typeof arg === 'function' )
              return { SuperCtorList: [ arg ], protoMixin: {} };
            else
              throw new Error( "Invalid lone argument to declare(), needs to be array, function or pure object" );
          }

          // Two arguments. Only check if the first one is an array. If it is, then it's the list of
          // constructors, with second argument POSSIBLY being protoMixin
          if( args.length === 2 ){
            var a = args[ 0 ];
            var b = args[ 1 ];

            // First parameter is an array: that's the list of ctors
            if( Array.isArray( a ) ){
              // Normalise b
              if( typeof b !== 'object' || b === null ) b = {};
              return { SuperCtorList: a, protoMixin: b };
            }
          }

          // CHECK POINT: at this point, there are 2 or more arguments, and the "array as first proto" is
          // out of the picture. So, will go through args and make up the array based on it
          var lastIndex = args.length - 1;
          var list = [];
          // This cycle will deal with all items except the last one, which might be either a
          // constructor or a protoMixin
          for( var i = 0; i < lastIndex; i ++ ){
            var item = args[ i ];

            // Check that it's the right type, allowing for the last one to be an exception
            if( typeof item !== 'function' )
              //console.err( "ITEM: ", item );
              throw new Error("Parameters to declare() must be constructor functions (except the last one which can be an object for the prototype)");
            list.push( item );
          }

          // The last one might be either a constructor function, or a protoMixin.
          // Deal with either case
          var lastOne = args[ lastIndex ];
          if( typeof lastOne === 'function' ){
            list.push( lastOne );
            return { SuperCtorList: list, protoMixin: {} };
          } else if( typeof lastOne === 'object' && lastOne !== null ) {
            return { SuperCtorList: list, protoMixin: lastOne };
          } else throw new Error("Last argument of declare() must be either a function or a mixin object");
        };

        // Parameters are very variable
        var declare = function(){

          // These will be worked out from `arguments`
          var SuperCtorList, protoMixin;

          var MixedClass, ResultClass;
          var list = [];
          var i, l, ii, ll;
          var proto;

          var r = workoutDeclareArguments( arguments );
          SuperCtorList = r.SuperCtorList;
          protoMixin = r.protoMixin;

          // No parameters: inheriting from Object directly, no multiple inheritance
          if( SuperCtorList.length === 0 ){
            MixedClass = Object;
          }
          // Only one parameter: straght single inheritance.
          else if( SuperCtorList.length === 1 ){
            MixedClass = SuperCtorList[ 0 ];

          // More than one parameter: multiple inheritance at work
          // MixedClass will end up being an artificially made constructor
          // where the prototype chain is the sum of _every_ prototype in
          // every element of SuperCtorList (taking out duplicates)
          } else {
            MixedClass = Object;

            // NOW:
            // Go through every __proto__ of every derivative class, and augment
            // MixedClass by inheriting from A COPY OF each one of them.

            list = [];
            for( i = 0, l = SuperCtorList.length; i < l; i ++ ){

              // Get the prototype list, in the right order
              // (the reversed discovery order)
              // The result will be placed in `subList`
              var subList = [];
              proto = SuperCtorList[ i ].prototype;
              while( proto ){
                if( proto.constructor !== Object ) subList.push( proto );
                proto = Object.getPrototypeOf( proto );
              }
              subList = subList.reverse();

              // Add each element of sublist as long as it's not already in the main `list`
              for( ii = 0, ll = subList.length; ii < ll; ii ++ ){
                if( ! constructorAlreadyInList( subList[ ii ].constructor, list ) ) list.push( subList[ ii ] );
              }
            }

            // For each element in the prototype list that isn't Object(),
            // augment MixedClass with a copy of the new prototype
            for( ii = 0, ll = list.length; ii < ll; ii ++ ){
              proto = list[ ii ];

              var M = MixedClass;

              if( proto.constructor !== Object ){

                MixedClass = makeConstructor( MixedClass, proto, proto.constructor );
                copyClassMethods( M, MixedClass ); // Methods previously inherited

                copyClassMethods( proto.constructor, MixedClass ); // Extra methods from the father constructor
              }
            }
          }

          // Finally, inherit from the MixedClass, and add
          // class methods over
          // MixedClass might be:
          // * Object (coming from no inheritance),
          // * SuperCtorList[0] (coming from single inheritance)
          // * A constructor with the appropriate prototype chain (multiple inheritance)
          ResultClass = makeConstructor( MixedClass, protoMixin );
          copyClassMethods( MixedClass, ResultClass );

          // Add getInherited, inherited() and inheritedAsync() to the prototype
          // (only if they are not already there)
          if( ! ResultClass.prototype.getInherited ) {
            ResultClass.prototype.getInherited = getInherited;
          }
          if( ! ResultClass.prototype.inherited ) {
            ResultClass.prototype.inherited = makeInheritedFunction( 'sync' );
          }
          if( ! ResultClass.prototype.inheritedAsync ) {
            ResultClass.prototype.inheritedAsync = makeInheritedFunction( 'async' );
          }

          // Add instanceOf
          if( ! ResultClass.prototype.instanceOf ) {
            ResultClass.prototype.instanceOf = instanceOf;
          }

          // Add class-wide method `extend`
          ResultClass.extend = function(){
            return extend.apply( this, arguments );
          };

          // That's it!
          return ResultClass;
        };

        // Returned extra: declarableObject
        declare.extendableObject = declare( Object );

        exports = module.exports = declare;

      });
    }(
        typeof define == 'function' && define.amd ?
            define :
            function ( factory ) { factory( require, exports, module); }
    ));<|endoftext|>


<commit_before>//<nowiki>
( function ( $, mw ) {
    mw.loader.load( "jquery.chosen" );
    mw.loader.load( "mediawiki.ui.input", "text/css" );

    var delsortCategories = {
        "Geographic": ["United States of America", "New York", "California", "United Kingdom", "England", "India", "Australia"],
        "Arts": ["Film", "Fictional elements", "Music", "Television", "Albums and songs"],
        "People": ["People", "Academics and educators", "Actors and filmmakers", "Authors", "Bands and musicians", "Businesspeople", "Politicians", "Sportspeople"],
        "Technology": ["Software", "Internet", "Websites"],
        "Miscellaneous": ["Organizations", "Football", "Martial arts", "Christianity", "Business"]
    };
    var ADVERTISEMENT = " ([[User:APerson/delsort|delsort.js]])";

    if (wgPageName.indexOf('Wikipedia:Articles_for_deletion/') != -1 &&
        wgPageName.indexOf('Wikipedia:Articles_for_deletion/Log/201') == -1) {
        var portletLink = mw.util.addPortletLink('p-cactions', '#', 'Delsort', 'pt-delsort', 'Perform deletion sorting');
        $( portletLink ).click( function ( e ) {
            e.preventDefault();

            // Define a function to add a new custom field, used below
            var addCustomField = function ( e ) {
                $( "
" )
                    .insertBefore( "#delsort #sort-button" )
                    .css( "width", "40%" )
                    .css( "margin", "0.25em auto" )
                    .append( $( "<input>" )
                             .attr( "type", "text" )
                             .addClass( "mw-ui-input mw-ui-input-inline custom-delsort-field" ) )
                    .append( $( "<button>" )
                             .addClass( "mw-ui-button mw-ui-destructive mw-ui-quiet" )
                             .text( "Remove" )
                             .click( function ( e ) {
                                 $( this ).parent().remove();
                             } ) );
            }

            $( "#jump-to-nav" ).after( $( "
" )
                    .attr( "id", "delsort" )
                    .css( "border", "thin solid #c5c5c5" )
                    .css( "box-shadow", "0 3px 8px rgba(0, 0, 0, .25)" )
                    .css( "border-radius", "3px" )
                    .css( "padding", "5px" )
                    .css( "position", "relative" )
                    .append( $( "
" )
                        .text( "Select a deletion sorting category" )
                        .css( "font-size", "larger" )
                        .css( "font-weight", "bold" )
                        .css( "text-align", "center" )
                        .attr( "id", "delsort-title") )
                    .append( $( "
" ) // this is disgusting but it works somehow
                        .css( "width", "100%" )
                        .append( $( "
" ) // yes, that is yet another div
                            .css( "text-align", "center" )
                            .css( "margin", "0.5em 0" )
                            .append( $( "<select>" )
                                .attr( "data-placeholder",
                                       "Select a deletion sorting category..." )
                                .attr( "multiple", "true" ) )
                            .append( $( "<button>" )
                                .addClass( "mw-ui-button mw-ui-progressive mw-ui-quiet" )
                                .text( "Add custom" )
                                .click( addCustomField ) ) ) )
                    .append( $( "<button>" )
                        .addClass( "mw-ui-button mw-ui-destructive mw-ui-quiet" )
                        .css( "position", "absolute" )
                        .css( "top", "5px" )
                        .css( "right", "5px" )
                        .text( "Close" )
                        .click( function ( e ) {
                                $( "#delsort" ).remove();
                        } ) ) );
                $.each( delsortCategories, function ( groupName, categories ) {
                        var group = $( "<optgroup>" )
                            .appendTo( "#delsort select" )
                            .attr( "label", groupName );
                        $.each( categories, function ( index, category ) {
                                group.append( $( "<option>" )
                                    .val( category )
                                    .text( category ) );
                        } );
                } );

            // Set up the chosen one (some code stolen from http://stackoverflow.com/a/27445788)
            $( "#delsort select" ).chosen();
            $( "#delsort .chzn-container" ).css( "text-align", "left" );

            // Add the button that triggers sorting
            $( "#delsort" ).append( $( "
" )
                .css( "text-align", "center" )
                .append( $( "<button> ")
                    .addClass( "mw-ui-button" )
                    .addClass( "mw-ui-progressive" )
                    .attr( "id", "sort-button" )
                    .text( "Sort deletion discussion" )
                    .click( function ( e ) {

                        // Make a status list
                        $( "#delsort" ).append( $( "
 ")
                                                .attr( "id", "status" ) );

                        // Build a list of categories
                        var categories = $( "#delsort select" ).val() || [];
                        $( ".custom-delsort-field" ).each( function ( index, element ) {
                            categories.push( $( element ).val() );
                        } );
                        categories = categories.filter( Boolean ); // remove empty strings

                        // Actually do the delsort
                        delsortAll( categories );
                    } ) ) );
        } );
    }

    function delsortAll( cats ) {

        // Indicate to the user that we're doing some deletion sorting
        $( "#delsort .chzn-container" ).parent().remove();
        $( ".custom-delsort-field" ).parent().remove();
        $( "#delsort #sort-button" )
            .text( "Sorting discussion..." )
            .prop( "disabled", true )
            .fadeOut( 400, function () {
                $( this ).remove();
            } );
        var titleCategory = ( cats.length === 1 ) ? ( "the \"" + cats[0] + "\" category" ) : ( cats.length + " categories" );
        $( "#delsort-title" )
            .html( "Sorting discussion into " + titleCategory + "" );

        // Start the animation, using super-advanced techniques
        var animationInterval = setInterval( function () {
            $( "#delsort-dots" ).text( $( "#delsort-dots" ).text() + "." );
            if( $( "#delsort-dots" ).text().length > 3 ) {
                $( "#delsort-dots" ).text( "" );
            }
        }, 600 );

        // Place (a) notification(s) on the discussion
        var notificationDeferred = postDelsortNotices( cats );

        // List the discussion at the DELSORT pages
        var deferreds = cats.map( listAtDelsort );

        // We still have to wait for notifications to be placed
        deferreds.push( notificationDeferred );

        // When everything's done, say something
        $.when.apply( $, deferreds ).then( function () {

            // We're done!
            $( "#delsort-title" )
                .text( "Done sorting discussion into " + titleCategory + "." );
            showStatus( "Done! Discussion sorted into " + titleCategory + ". (" )
                .append( $( "" )
                         .text( "reload" )
                         .attr( "href", "#" )
                         .click( function () { document.location.reload( true ); } ) )
                .append( ")" );
            clearInterval( animationInterval );
        } );
    }

    function showStatus( newStatus ) {
        return $( "
" )
            .appendTo( "#delsort ul#status" )
            .html( newStatus );
    }

    /*
     * Adds some notices to the discussion page that this discussion was sorted.
     */
    function postDelsortNotices( cats ) {
        var deferred = $.Deferred();

        // Build a notification string
        var appendText = "";
        cats.forEach( function ( cat ) {
            appendText += "\n\{\{subst:Delsort|" + cat + "|\~\~\~\~\}\}";
        } );

        // Post the notice to the discussion
        var catPlural = ( cats.length === 1 ) ? "" : "s";
        $.ajax( {
            url: mw.util.wikiScript( 'api' ),
            type: 'POST',
            dataType: 'json',
            data: {
                format: 'json',
                action: 'edit',
                title: mw.config.get( 'wgPageName' ),
                summary: "Placing notification for listing at [[WP:DELSORT]]" + ADVERTISEMENT,
                token: mw.user.tokens.get( 'editToken' ),
                appendtext: appendText
            }
        } ).done ( function ( data ) {
            if ( data && data.edit && data.edit.result && data.edit.result == 'Success' ) {
                showStatus( cats.length + " notice" + catPlural + " placed on the discussion!" );
                deferred.resolve();
            } else {
                showStatus( "While placing " + cats.length + " notification" + catPlural + ", the edit query returned an error. =(" );
                deferred.reject();
            }
        } ).fail ( function() {
            showStatus( "While placing " + cats.length + " notification" + catPlural + ", the AJAX request failed." );
            deferred.reject();
        } );
        return deferred;
    }

    /*
     * Adds a listing at the DELSORT page for the category.
     */
    function listAtDelsort( cat ) {
        var deferred = $.Deferred();

        // Make a status element just for this category
        var statusElement = showStatus( "Listing this discussion at DELSORT/" +
                                        cat + "..." );

        // First, get the current wikitext for the DELSORT page
        var wikitext;
        $.getJSON(
            mw.util.wikiScript('api'),
            {
                format: 'json',
                action: 'query',
                prop: 'revisions',
                rvprop: 'content',
                rvlimit: 1,
                titles: "Wikipedia:WikiProject Deletion sorting/" + cat
            }
        ).done( function ( data ) {
            try {
                var pageId = Object.keys(data.query.pages)[0];
                wikitext = data.query.pages[pageId].revisions[0]['*'];

                statusElement.html( "Got the DELSORT/" + cat + " listing wikitext, processing..." );

                // Actually edit the content to include the new listing
                var newDelsortContent = wikitext.replace('directly below this line -->', 'directly below this line -->\n\{\{' + mw.config.get('wgPageName') + '\}\}');

                // Then, replace the DELSORT listing with the new content
                var listTitle = 'Wikipedia:WikiProject Deletion sorting/' + cat;
                $.ajax( {
                    url: mw.util.wikiScript( 'api' ),
                    type: 'POST',
                    dataType: 'json',
                    data: {
                        format: 'json',
                        action: 'edit',
                        title: listTitle,
                        summary: "Listing [[" + mw.config.get('wgPageName') + "]]" + ADVERTISEMENT,
                        token: mw.user.tokens.get( 'editToken' ),
                        text: newDelsortContent
                    }
                } ).done ( function ( data ) {
                    if ( data && data.edit && data.edit.result && data.edit.result == 'Success' ) {
                        statusElement.html( "Listed page at the " + cat + " deletion sorting list!" );
                        deferred.resolve();
                    } else {
                        statusElement.html( "While listing at DELSORT/" + cat + ", the edit query returned an error. =(" );
                        deferred.reject();
                    }
                } ).fail ( function() {
                    statusElement.html( "While listing at DELSORT/" + cat + ", the ajax request failed." );
                    deferred.reject();
                } );
            } catch ( e ) {
                statusElement.html( "While getting the DELSORT/" + cat + " content, there was an error." );
                console.log( "DELSORT content request error: " + e.message );
                console.log( "DELSORT content request response: " + JSON.stringify( data ) );
                deferred.reject();
            }
        } ).fail( function () {
            statusElement.html( "While getting the DELSORT/" + cat + " content, there was an AJAX error." );
            deferred.reject();
        } );
        return deferred;
    }
}( jQuery, mediaWiki ) );
//</nowiki>
<commit_msg>Added validation for custom cats<commit_after>//<nowiki>
( function ( $, mw ) {
        mw.loader.load( "jquery.chosen" );
        mw.loader.load( "mediawiki.ui.input", "text/css" );

        var delsortCategories = {
                "Geographic": ["United States of America", "New York", "California", "United Kingdom", "England", "India", "Australia"],
                "Arts": ["Film", "Fictional elements", "Music", "Television", "Albums and songs"],
                "People": ["People", "Academics and educators", "Actors and filmmakers", "Authors", "Bands and musicians", "Businesspeople", "Politicians", "Sportspeople"],
                "Technology": ["Software", "Internet", "Websites"],
                "Miscellaneous": ["Organizations", "Football", "Martial arts", "Christianity", "Business"]
        };
    var ADVERTISEMENT = " ([[User:APerson/delsort|delsort.js]])";

    if (wgPageName.indexOf('Wikipedia:Articles_for_deletion/') != -1 &&
        wgPageName.indexOf('Wikipedia:Articles_for_deletion/Log/201') == -1) {
        var portletLink = mw.util.addPortletLink('p-cactions', '#', 'Delsort', 'pt-delsort', 'Perform deletion sorting');
        $( portletLink ).click( function ( e ) {
		e.preventDefault();

                // Validation for new custom fields
                var validateCustomCat = function ( container ) {
		    var categoryName = container.children( "input" ).first().val();
		    $.getJSON(
                        mw.util.wikiScript('api'),
                        {
                            format: 'json',
                            action: 'query',
                            prop: 'pageprops',
                            titles: "Wikipedia:WikiProject Deletion sorting/" + categoryName
                        }
                    ).done( function ( data ) {
                        var setStatus = function ( status ) {
			    var text = "Not sure";
                            var imageSrc = "https://upload.wikimedia.org/wikipedia/commons/a/ad/Question_mark_grey.png";
                            switch( status ) {
			    case "d":
			        text = "Doesn't exist";
			        imageSrc = "https://upload.wikimedia.org/wikipedia/commons/5/5f/Red_X.svg";
			        break;
			    case "e":
			        text = "Exists";
			        imageSrc = "https://upload.wikimedia.org/wikipedia/commons/1/16/Allowed.svg";
			        break;
                            }
                            container.children( ".category-status" ).empty()
			        .append( $( "", { "src": imageSrc,
					    "style": "padding: 0 5px; width: 20px; height: 20px" } ) )
                                .append( text );
                        }
                        if( data && data.query && data.query.pages ) {
                            if( data.query.pages.hasOwnProperty( "-1" ) ) {
                                console.log( categoryName + " DOES NOT EXIST" );
                                setStatus( "d" );
                            } else {
                                console.log( categoryName + " EXISTS" );
                                setStatus( "e" );
                            }
                        } else {
                            setStatus( "n" );
                        }
                    } );
                }

                // Define a function to add a new custom field, used below
                var addCustomField = function ( e ) {
                        $( "
" )
                            .insertBefore( "#delsort #sort-button" )
                            .css( "width", "40%" )
                            .css( "margin", "0.25em auto" )
                        .append( $( "<input>" )
                            .attr( "type", "text" )
                            .addClass( "mw-ui-input mw-ui-input-inline custom-delsort-field" )
                            .change( function ( e ) {
                                validateCustomCat( $( this ).parent() );
                            } ) )
                        .append( $( "" ).addClass( "category-status" ) )
                        .append( " (" )
                        .append( $( "", { "src": "https://upload.wikimedia.org/wikipedia/commons/a/a2/Crystal_128_reload.svg",
                                               "style": "width: 15px; height: 15px; cursor: pointer" } )
                            .click( function ( e ) {
                                validateCustomCat( $( this ).parent() );
                            } ) )
                        .append( ")" )
                        .append( $( "<button>" )
                            .addClass( "mw-ui-button mw-ui-destructive mw-ui-quiet" )
                            .text( "Remove" )
                            .click( function ( e ) {
                                $( this ).parent().remove();
                            } ) );
                };

                $( "#jump-to-nav" ).after( $( "
" )
                    .attr( "id", "delsort" )
                    .css( "border", "thin solid #c5c5c5" )
                    .css( "box-shadow", "0 3px 8px rgba(0, 0, 0, .25)" )
                    .css( "border-radius", "3px" )
                    .css( "padding", "5px" )
                    .css( "position", "relative" )
                    .append( $( "
" )
                        .text( "Select a deletion sorting category" )
                        .css( "font-size", "larger" )
                        .css( "font-weight", "bold" )
                        .css( "text-align", "center" )
                        .attr( "id", "delsort-title") )
                    .append( $( "
" ) // this is disgusting but it works somehow
                        .css( "width", "100%" )
                        .append( $( "
" ) // yes, that is yet another div
                            .css( "text-align", "center" )
                            .css( "margin", "0.5em 0" )
                            .append( $( "<select>" )
                                .attr( "data-placeholder",
                                       "Select a deletion sorting category..." )
                                .attr( "multiple", "true" ) )
                            .append( $( "<button>" )
                                .addClass( "mw-ui-button mw-ui-progressive mw-ui-quiet" )
                                .text( "Add custom" )
                                .click( addCustomField ) ) ) )
                    .append( $( "<button>" )
                        .addClass( "mw-ui-button mw-ui-destructive mw-ui-quiet" )
                        .css( "position", "absolute" )
                        .css( "top", "5px" )
                        .css( "right", "5px" )
                        .text( "Close" )
                        .click( function ( e ) {
                                $( "#delsort" ).remove();
                        } ) ) );
                $.each( delsortCategories, function ( groupName, categories ) {
                        var group = $( "<optgroup>" )
                            .appendTo( "#delsort select" )
                            .attr( "label", groupName );
                        $.each( categories, function ( index, category ) {
                                group.append( $( "<option>" )
                                    .val( category )
                                    .text( category ) );
                        } );
                } );

            // Set up the chosen one (some code stolen from http://stackoverflow.com/a/27445788)
            $( "#delsort select" ).chosen();
            $( "#delsort .chzn-container" ).css( "text-align", "left" );

                // Add the button that triggers sorting
                $( "#delsort" ).append( $( "
" )
                    .css( "text-align", "center" )
                    .append( $( "<button> ")
                        .addClass( "mw-ui-button" )
                        .addClass( "mw-ui-progressive" )
                        .attr( "id", "sort-button" )
                        .text( "Sort deletion discussion" )
                        .click( function ( e ) {

                                // Make a status list
                                $( "#delsort" ).append( $( "
 ")
                            .attr( "id", "status" ) );

                                // Build a list of categories
                                var categories = $( "#delsort select" ).val() || [];
                                console.log("before each: " + categories);
                                $( ".custom-delsort-field" ).each( function ( index, element ) {
                                        categories.push( $( element ).val() );
                                } );
                                console.log("before filter: " + categories);
                                categories = categories.filter( Boolean ); // remove empty strings
								console.log("after filter: " + categories);
                                // Actually do the delsort
                            delsortAll( categories );
                        } ) ) );
        } );
    }

    function delsortAll( cats ) {

        // Indicate to the user that we're doing some deletion sorting
        $( "#delsort .chzn-container" ).parent().remove();
        $( ".custom-delsort-field" ).parent().remove();
        $( "#delsort #sort-button" )
            .text( "Sorting discussion..." )
            .prop( "disabled", true )
            .fadeOut( 400, function () {
                $( this ).remove();
            } );
        var titleCategory = ( cats.length === 1 ) ? ( "the \"" + cats[0] + "\" category" ) : ( cats.length + " categories" );
        $( "#delsort-title" )
            .html( "Sorting discussion into " + titleCategory + "" );

        // Start the animation, using super-advanced techniques
        var animationInterval = setInterval( function () {
                $( "#delsort-dots" ).text( $( "#delsort-dots" ).text() + "." );
                if( $( "#delsort-dots" ).text().length > 3 ) {
                        $( "#delsort-dots" ).text( "" );
                }
        }, 600 );

        // Place (a) notification(s) on the discussion
        var notificationDeferred = postDelsortNotices( cats );

        // List the discussion at the DELSORT pages
        var deferreds = cats.map( listAtDelsort );

        // We still have to wait for notifications to be placed
        deferreds.push( notificationDeferred );

        // When everything's done, say something
        $.when.apply( $, deferreds ).then( function () {

                // We're done!
                $( "#delsort-title" )
                    .text( "Done sorting discussion into " + titleCategory + "." );
                showStatus( "Done! Discussion sorted into " + titleCategory + ". (" )
                    .append( $( "" )
                        .text( "reload" )
                        .attr( "href", "#" )
                        .click( function () { document.location.reload( true ); } ) )
                    .append( ")" );
                clearInterval( animationInterval );
        } );
    }

    function showStatus( newStatus ) {
        return $( "
" )
             .appendTo( "#delsort ul#status" )
             .html( newStatus );
    }

    /*
     * Adds some notices to the discussion page that this discussion was sorted.
     */
    function postDelsortNotices( cats ) {
        var deferred = $.Deferred();

        // Build a notification string
        var appendText = "";
        cats.forEach( function ( cat ) {
                appendText += "\n\{\{subst:Delsort|" + cat + "|\~\~\~\~\}\}";
        } );

        // Post the notice to the discussion
        var catPlural = ( cats.length === 1 ) ? "" : "s";
        $.ajax( {
            url: mw.util.wikiScript( 'api' ),
            type: 'POST',
            dataType: 'json',
            data: {
                format: 'json',
                action: 'edit',
                title: mw.config.get( 'wgPageName' ),
                summary: "Placing notification for listing at [[WP:DELSORT]]" + ADVERTISEMENT,
                token: mw.user.tokens.get( 'editToken' ),
                appendtext: appendText
            }
        } ).done ( function ( data ) {
            if ( data && data.edit && data.edit.result && data.edit.result == 'Success' ) {
                showStatus( cats.length + " notice" + catPlural + " placed on the discussion!" );
                deferred.resolve();
            } else {
                showStatus( "While placing " + cats.length + " notification" + catPlural + ", the edit query returned an error. =(" );
                deferred.reject();
            }
        } ).fail ( function() {
            showStatus( "While placing " + cats.length + " notification" + catPlural + ", the AJAX request failed." );
            deferred.reject();
        } );
        return deferred;
    }

    /*
     * Adds a listing at the DELSORT page for the category.
     */
    function listAtDelsort( cat ) {
        var deferred = $.Deferred();

        // Make a status element just for this category
        var statusElement = showStatus( "Listing this discussion at DELSORT/" +
                                        cat + "..." );

        // First, get the current wikitext for the DELSORT page
        var wikitext;
        $.getJSON(
            mw.util.wikiScript('api'),
            {
                format: 'json',
                action: 'query',
                prop: 'revisions',
                rvprop: 'content',
                rvlimit: 1,
                titles: "Wikipedia:WikiProject Deletion sorting/" + cat
            }
        ).done( function ( data ) {
            try {
                var pageId = Object.keys(data.query.pages)[0];
                wikitext = data.query.pages[pageId].revisions[0]['*'];

                statusElement.html( "Got the DELSORT/" + cat + " listing wikitext, processing..." );

                // Actually edit the content to include the new listing
                var newDelsortContent = wikitext.replace('directly below this line -->', 'directly below this line -->\n\{\{' + mw.config.get('wgPageName') + '\}\}');

                // Then, replace the DELSORT listing with the new content
                var listTitle = 'Wikipedia:WikiProject Deletion sorting/' + cat;
                $.ajax( {
                    url: mw.util.wikiScript( 'api' ),
                    type: 'POST',
                    dataType: 'json',
                    data: {
                        format: 'json',
                        action: 'edit',
                        title: listTitle,
                        summary: "Listing [[" + mw.config.get('wgPageName') + "]]" + ADVERTISEMENT,
                        token: mw.user.tokens.get( 'editToken' ),
                        text: newDelsortContent
                    }
                } ).done ( function ( data ) {
                    if ( data && data.edit && data.edit.result && data.edit.result == 'Success' ) {
                        statusElement.html( "Listed page at the " + cat + " deletion sorting list!" );
                        deferred.resolve();
                    } else {
                        statusElement.html( "While listing at DELSORT/" + cat + ", the edit query returned an error. =(" );
                        deferred.reject();
                    }
                } ).fail ( function() {
                    statusElement.html( "While listing at DELSORT/" + cat + ", the ajax request failed." );
                    deferred.reject();
                } );
            } catch ( e ) {
                statusElement.html( "While getting the DELSORT/" + cat + " content, there was an error." );
                console.log( "DELSORT content request error: " + e.message );
                console.log( "DELSORT content request response: " + JSON.stringify( data ) );
                deferred.reject();
            }
        } ).fail( function () {
            statusElement.html( "While getting the DELSORT/" + cat + " content, there was an AJAX error." );
            deferred.reject();
        } );
        return deferred;
    }
}( jQuery, mediaWiki ) );
//</nowiki>
<|endoftext|>