<commit_before>(function(){
/**
@namespace mi2JS(comp)
*/

/** Base class for all components. Goes beyond simple {@link NodeWrapper} to add parent/child/children
  relationship. Also adding other functionalities needed for component based composition of an application.
@class Base
@memberof mi2JS(comp)
@extends NodeWrapper
*/
	var mi2 = mi2JS;
	var mi2Proto = mi2.prototype;

	function Base(){}

	mi2.comp.def.Base = Base;
	mi2.comp.tpl.Base = '';
	Base.compName = 'Base';
	// extend mi2JS to get addClass and other html utility functions (html.js must be included in the bundle to have them)
	mi2.extend(Base, mi2);

	var proto = Base.prototype; //

	/* called when the component is initially constructed */
	proto.construct = function(el, parent){
		this.children = [];
		this.el = el;
	};

	proto.__init = function(){
		if(!this.__initialized){
			this.initTemplate();
			this.parseChildren();
			this.initChildren();
			this.fireEvent('init');
		}
		this.__initialized = true;
	};

	proto.initTemplate = function(){
		if(this.__template){
			this.el.innerHTML = this.__template;
		}
		// if(this.__template || this.el.getAttribute('template') == 'inline'){
		// 	this.parseChildren();
		// 	this.initChildren();
		// }
		delete this.__template;
	};

	proto.parseChildren = function(){
		mi2.parseChildren(this.el,this);
	};

	proto.initChildren = function(){
		var c;
		for(var i=0; i<this.children.length; i++){
			c = this.children[i];
			if(!c.lazyInit) c.__init();
		}
	};

	proto.on_init = function(evt){ };

	proto.getCompName = function(){
		if(this.el && this.el.getAttribute){
			return this.el.getAttribute('as');
		}
	};

	/** listener shortcut that by default binds callback function to current object/component
	so you can use this in callback without extra bloat otherwise needed
	*/
	proto.listen = function(el,evt,listener){
		listener = listener || this['on_'+evt];

		if(el.addListener){
			el.addListener( evt, listener, this);
		}else //dom node
			mi2.listen( el, evt, listener, this);
	};

	//setTimeout and setInterval shortcut that by default binds callback function to current object
	//so you can use this in callback without extra bloat otherwise needed
	proto.setTimeout = function(fc,delay){
		return setTimeout( fc.bind(this), delay );
	};

	proto.setInterval = function(fc,delay){
		return setInterval( fc.bind(this), delay );
	};

	proto.addListener = function(evtName,callback, scope){
		if(!this.__listeners) this.__listeners = {};
		var l = this.__listeners[evtName];
		if(!l) l = this.__listeners[evtName] = [];

		// bind a scope to the callback function
		if(scope) callback = mi2.bind( scope, callback );

		l.push({callback:callback, scope:scope });
	};

	proto.isTransitive = function(){ return false; };

/** Event object used for firing events inside components
  @typedef EventObject
  @type {object}
  @property {string} name - event name
  @property {string} fireTo - direction of the event
  @property {Object} src - source component where the the event originated
  @property {Event} domEvent - (optional) should be passed whan DOM Event was th cause
 */

/** Fire event in desired direction in component tree (the component itself, to children, to parent)

	@method fireEvent
	@memberof mi2JS(comp).Base

	@param {String|EventObject} evt event or just event name

	@example
// version when there is no additional data with event
this.someComp.fireEvent('reload');

// tell child component to reload
this.someComp.fireEvent({name:'reload'});

// tell all children components to reload
this.fireEvent({name:'reload', fireTo:'children'});

// tell parent you want to submit (inputs can fire this)
this.fireEvent({name:'submit', fireTo:'parent'});

// if the event is caused by domEvent, pass it along as domEvent property
this.fireEvent({name:'submit', fireTo:'parent', domEvent:evt});

	*/
	proto.fireEvent = function(evt){
		if(typeof(evt) == 'string'){
			evt = {name:evt};
		}
		var evtName = evt.name;

		if(evtName == 'show'){
			// if not initialized yet, fire init event first
			this.__init();
		}

		if(!evt) evt = {};
		evt.name = evtName;
		evt.target = this;

		var initialFire = !evt.__src;
		if(initialFire) evt.src = evt.__src = this;

		if(evt.fireTo == 'parent'){
			if((this.isTransitive && this.isTransitive()) || initialFire){
				if(this.parent) this.parent.fireEvent(evtName, evt);
				return;
			}
		}

		if(typeof(this['on_'+evtName]) == 'function'){
			try{
				this['on_'+evtName](evt);
			}catch(e){
				console.log('Error calling event handler function ','on_'+evtName, evt, 'component', this , 'error', e);
				console.error(e.message);
			}

		}

		var l;
		if(this.__listeners) l=this.__listeners[evtName];
		if(l) for(var i=0; i<l.length; i++){
			try{
				l[i].callback(evt);
			}catch(e){
				console.log('Error firing event ',evtName, evt, 'listener', l[i].scope, 'error', e);
				console.error(e.message);
			}
		}

		var child;
		if(evt.fireTo == 'children' && this.children){
			for(var i=0; i<this.children.length; i++){
				child = this.children[i];
				// if hidden no need for hide/show event to propagate
				if(!child.isVisible() && (evtName == 'hide' || evtName == 'show' )) continue;

				child.fireEvent(evt);
			}
		}
	};

	proto.addRef = function(r){
		if(!this.__refs) this.__refs = [];
		this.__refs.push(r);
	};

	proto.addChild = function(c){
		if(!this.el) {
			var msg = 'Component not propery initialized. Must call Base constructor before parseChildren';
			console.log(msg, this, this.el);
			throw new Error(msg);
		}
		var cnt = this.children.length >>> 0;
		for(var i =0; i<cnt; i++) if(c == this.children[i]) return;
		this.children.push(c);
	};

	proto.removeChild = function(){
		var found = -1, cnt = this.children.length >>> 0;
		for(var i =0; i<cnt; i++) if(c == this.children[i]) return;
		if(found != -1) this.children.splice(found,1);
	};

	proto.setParent = function(p){
		var old = this.parent;
		if(old && old.removeChild)
			old.removeChild(this);
		this.parent = p;
		if(p  && p.addChild) p.addChild(this);
	};

	/* find component that is holding the DOM node */
	proto.findRef = function(toFind, from){
		var testFunc = function(comp){
			return comp.el == toFind;
		}
		if(typeof toFind == 'string'){
			if('+' == toFind)
				toFind = from.el.nextElementSibling;
			else if('-' == toFind)
				toFind = from.el.previousElementSibling;
			else{
				testFunc = function(comp){
					return comp.attr('p') == toFind;
				}
			}
		}
		var i,cd = this.children;
		if(cd) for(i=0; i<cd.length; i++){
			if(testFunc(cd[i])) return cd[i];
		}
		cd = this.__refs;
		if(cd) for(i=0; i<cd.length; i++){
			if(testFunc(cd[i])) return cd[i];
		}
		// if it is a domNode and we have no reference to it, just return a NodeWrapper
		if(toFind.tagName)
			return new mi2(toFind);
	}

	proto.find    = function(search){ return mi2.find   (this.el, search); }
	proto.findAll = function(search){ return mi2.findAll(this.el, search); }

	/** Use this when the tag on which the component is defined*/
	proto.replaceTag = function(el,tag){
		var ret = document.createElement(tag);
		var arr = el.attributes;
		for(var i=0; i<arr.length; i++){
			ret.setAttribute(arr[i].name, arr[i].value);
		}
		el.parentNode.replaceChild(ret,el);
		return ret;
	};

	proto.isVisibleTruly = function(){
		var c = this;
		while(c){
			if(!c.isVisible()) return false;
			c = c.parent;
		}
		return true;
	};

	proto.setVisible = function(visible){
		if(visible == this.isVisible()) return;

		mi2Proto.setVisible.call(this, visible);

		// when hidden by parent, there is no point in firing the event
		// correct event will be fired when parent becomes visible
		if(!this.parent || this.parent.isVisibleTruly())
			this.fireEvent({name:visible ? 'show':'hide',fireTo:'children'});
	};

}(mi2JS));
<commit_msg>bugfix for fireTo:parent<commit_after>(function(){
/**
@namespace mi2JS(comp)
*/

/** Base class for all components. Goes beyond simple {@link NodeWrapper} to add parent/child/children
  relationship. Also adding other functionalities needed for component based composition of an application.
@class Base
@memberof mi2JS(comp)
@extends NodeWrapper
*/
	var mi2 = mi2JS;
	var mi2Proto = mi2.prototype;

	function Base(){}

	mi2.comp.def.Base = Base;
	mi2.comp.tpl.Base = '';
	Base.compName = 'Base';
	// extend mi2JS to get addClass and other html utility functions (html.js must be included in the bundle to have them)
	mi2.extend(Base, mi2);

	var proto = Base.prototype; //

	/* called when the component is initially constructed */
	proto.construct = function(el, parent){
		this.children = [];
		this.el = el;
	};

	proto.__init = function(){
		if(!this.__initialized){
			this.initTemplate();
			this.parseChildren();
			this.initChildren();
			this.fireEvent('init');
		}
		this.__initialized = true;
	};

	proto.initTemplate = function(){
		if(this.__template){
			this.el.innerHTML = this.__template;
		}
		// if(this.__template || this.el.getAttribute('template') == 'inline'){
		// 	this.parseChildren();
		// 	this.initChildren();
		// }
		delete this.__template;
	};

	proto.parseChildren = function(){
		mi2.parseChildren(this.el,this);
	};

	proto.initChildren = function(){
		var c;
		for(var i=0; i<this.children.length; i++){
			c = this.children[i];
			if(!c.lazyInit) c.__init();
		}
	};

	proto.on_init = function(evt){ };

	proto.getCompName = function(){
		if(this.el && this.el.getAttribute){
			return this.el.getAttribute('as');
		}
	};

	/** listener shortcut that by default binds callback function to current object/component
	so you can use this in callback without extra bloat otherwise needed
	*/
	proto.listen = function(el,evt,listener){
		listener = listener || this['on_'+evt];

		if(el.addListener){
			el.addListener( evt, listener, this);
		}else //dom node
			mi2.listen( el, evt, listener, this);
	};

	//setTimeout and setInterval shortcut that by default binds callback function to current object
	//so you can use this in callback without extra bloat otherwise needed
	proto.setTimeout = function(fc,delay){
		return setTimeout( fc.bind(this), delay );
	};

	proto.setInterval = function(fc,delay){
		return setInterval( fc.bind(this), delay );
	};

	proto.addListener = function(evtName,callback, scope){
		if(!this.__listeners) this.__listeners = {};
		var l = this.__listeners[evtName];
		if(!l) l = this.__listeners[evtName] = [];

		// bind a scope to the callback function
		if(scope) callback = mi2.bind( scope, callback );

		l.push({callback:callback, scope:scope });
	};

	proto.isTransitive = function(){ return false; };

/** Event object used for firing events inside components
  @typedef EventObject
  @type {object}
  @property {string} name - event name
  @property {string} fireTo - direction of the event
  @property {Object} src - source component where the the event originated
  @property {Event} domEvent - (optional) should be passed whan DOM Event was th cause
 */

/** Fire event in desired direction in component tree (the component itself, to children, to parent)

	@method fireEvent
	@memberof mi2JS(comp).Base

	@param {String|EventObject} evt event or just event name

	@example
// version when there is no additional data with event
this.someComp.fireEvent('reload');

// tell child component to reload
this.someComp.fireEvent({name:'reload'});

// tell all children components to reload
this.fireEvent({name:'reload', fireTo:'children'});

// tell parent you want to submit (inputs can fire this)
this.fireEvent({name:'submit', fireTo:'parent'});

// if the event is caused by domEvent, pass it along as domEvent property
this.fireEvent({name:'submit', fireTo:'parent', domEvent:evt});

	*/
	proto.fireEvent = function(evt){
		if(typeof(evt) == 'string'){
			evt = {name:evt};
		}
		var evtName = evt.name;

		if(evtName == 'show'){
			// if not initialized yet, fire init event first
			this.__init();
		}

		if(!evt) evt = {};
		evt.target = this;

		var initialFire = !evt.__src;
		if(initialFire) evt.src = evt.__src = this;

		if(evt.fireTo == 'parent'){
			if((this.isTransitive && this.isTransitive()) || initialFire){
				if(this.parent) this.parent.fireEvent(evt);
				return;
			}
		}

		if(typeof(this['on_'+evtName]) == 'function'){
			try{
				this['on_'+evtName](evt);
			}catch(e){
				console.log('Error calling event handler function ','on_'+evtName, evt, 'component', this , 'error', e);
				console.error(e.message);
			}

		}

		var l;
		if(this.__listeners) l=this.__listeners[evtName];
		if(l) for(var i=0; i<l.length; i++){
			try{
				l[i].callback(evt);
			}catch(e){
				console.log('Error firing event ',evtName, evt, 'listener', l[i].scope, 'error', e);
				console.error(e.message);
			}
		}

		var child;
		if(evt.fireTo == 'children' && this.children){
			for(var i=0; i<this.children.length; i++){
				child = this.children[i];
				// if hidden no need for hide/show event to propagate
				if(!child.isVisible() && (evtName == 'hide' || evtName == 'show' )) continue;

				child.fireEvent(evt);
			}
		}
	};

	proto.addRef = function(r){
		if(!this.__refs) this.__refs = [];
		this.__refs.push(r);
	};

	proto.addChild = function(c){
		if(!this.el) {
			var msg = 'Component not propery initialized. Must call Base constructor before parseChildren';
			console.log(msg, this, this.el);
			throw new Error(msg);
		}
		var cnt = this.children.length >>> 0;
		for(var i =0; i<cnt; i++) if(c == this.children[i]) return;
		this.children.push(c);
	};

	proto.removeChild = function(){
		var found = -1, cnt = this.children.length >>> 0;
		for(var i =0; i<cnt; i++) if(c == this.children[i]) return;
		if(found != -1) this.children.splice(found,1);
	};

	proto.setParent = function(p){
		var old = this.parent;
		if(old && old.removeChild)
			old.removeChild(this);
		this.parent = p;
		if(p  && p.addChild) p.addChild(this);
	};

	/* find component that is holding the DOM node */
	proto.findRef = function(toFind, from){
		var testFunc = function(comp){
			return comp.el == toFind;
		}
		if(typeof toFind == 'string'){
			if('+' == toFind)
				toFind = from.el.nextElementSibling;
			else if('-' == toFind)
				toFind = from.el.previousElementSibling;
			else{
				testFunc = function(comp){
					return comp.attr('p') == toFind;
				}
			}
		}
		var i,cd = this.children;
		if(cd) for(i=0; i<cd.length; i++){
			if(testFunc(cd[i])) return cd[i];
		}
		cd = this.__refs;
		if(cd) for(i=0; i<cd.length; i++){
			if(testFunc(cd[i])) return cd[i];
		}
		// if it is a domNode and we have no reference to it, just return a NodeWrapper
		if(toFind.tagName)
			return new mi2(toFind);
	}

	proto.find    = function(search){ return mi2.find   (this.el, search); }
	proto.findAll = function(search){ return mi2.findAll(this.el, search); }

	/** Use this when the tag on which the component is defined*/
	proto.replaceTag = function(el,tag){
		var ret = document.createElement(tag);
		var arr = el.attributes;
		for(var i=0; i<arr.length; i++){
			ret.setAttribute(arr[i].name, arr[i].value);
		}
		el.parentNode.replaceChild(ret,el);
		return ret;
	};

	proto.isVisibleTruly = function(){
		var c = this;
		while(c){
			if(!c.isVisible()) return false;
			c = c.parent;
		}
		return true;
	};

	proto.setVisible = function(visible){
		if(visible == this.isVisible()) return;

		mi2Proto.setVisible.call(this, visible);

		// when hidden by parent, there is no point in firing the event
		// correct event will be fired when parent becomes visible
		if(!this.parent || this.parent.isVisibleTruly())
			this.fireEvent({name:visible ? 'show':'hide',fireTo:'children'});
	};

}(mi2JS));
<|endoftext|>


<commit_before>/**
 * Directive for using Dropzone.js in an AngularJS project.
 *
 * Based on angular-dropzone https://github.com/sandbochs/angular-dropzone/blob/master/lib/angular-dropzone.js
 * But implemented again, because source are not maintained
 */
angular.module('pcApp.datasets.directives.ckanImport', []).directive('ckanImport', [
    '$http', 'ngProgress', 'API_CONF', function ($http, ngProgress, API_CONF) {
        return {
            restrict: 'A',
            templateUrl: function (el, attrs) {
                return 'modules/datasets/partials/ckan-import.html';
            },
            scope: {
                'loadData': '='
            },
            link: function (scope, element, attrs, ctrls) {
                scope.itemsPerPage = 9;
                scope.ckanStart = 0;
                scope.byNumResourcesGtZero = function (result) {
                    if(result){
                        return result.num_resources > 0;
                    }
                    else if(scope.ckan){
                        return scope.ckan.num_resources > 0;
                    }

                };

                scope.byResourceTypeIn = function (types) {
                    return function (resource) {
                        for (var i in types) {
                            var type = types[i];
                            if (resource.format.toLowerCase() == type)
                                return true;
                        }
                        return false;
                    }
                };

                scope.onPageChange = function () {
                    var start = (scope.currentPage - 1) * scope.itemsPerPage;
                    scope.ckanStart = start;
                    handlePageResults(scope.ckan);
                };

                var handlePageResults = function(result){
                    scope.ckanSearchResults = [];
                    var length = scope.ckanStart + scope.itemsPerPage;

                    if(length > result.results.length){
                        length = result.results.length;
                    }

                    for(var i = scope.ckanStart; i<length; i++){
                        scope.ckanSearchResults[i-scope.ckanStart] = result.results[i];
                    }
                }

                scope.loadResource = function (dataset, resource) {
                    ngProgress.start();
                    $http({
                        url: API_CONF.DATASETS_MANAGER_URL + '/ckan/download',
                        params: {
                            api: attrs.apiBase,
                            id: resource.id,
                            convert: true
                        }
                    }).then(function (response) {
                        scope.loadData(dataset, resource, response.data);
                        ngProgress.complete();
                    });
                };

                scope.search = function (term, start) {
                    ngProgress.start();
                    scope.lastTerm = term;
                    scope.eurostatStart = 0;
                    $http({
                        url: API_CONF.DATASETS_MANAGER_URL + '/ckan/search',
                        params: {
                            api: attrs.apiBase,
                            q: term,
                            start: start
                        }
                    }).then(function (response) {
                        ngProgress.complete();
                        scope.ckan = response.data.result;
                        handlePageResults(response.data.result);
                    });
                };
            }
        }
    }
]);
<commit_msg>improved datasets EDP import<commit_after>/**
 * Directive for using Dropzone.js in an AngularJS project.
 *
 * Based on angular-dropzone https://github.com/sandbochs/angular-dropzone/blob/master/lib/angular-dropzone.js
 * But implemented again, because source are not maintained
 */
angular.module('pcApp.datasets.directives.ckanImport', []).directive('ckanImport', [
    '$http', 'ngProgress', 'API_CONF', 'dialogs', function ($http, ngProgress, API_CONF, dialogs) {
        return {
            restrict: 'A',
            templateUrl: function (el, attrs) {
                return 'modules/datasets/partials/ckan-import.html';
            },
            scope: {
                'loadData': '='
            },
            link: function (scope, element, attrs, ctrls) {
                scope.itemsPerPage = 9;
                scope.ckanStart = 0;
                scope.byNumResourcesGtZero = function (result) {
                    if(result){
                        return result.num_resources > 0;
                    }
                    else if(scope.ckan){
                        return scope.ckan.num_resources > 0;
                    }

                };

                scope.byResourceTypeIn = function (types) {
                    return function (resource) {
                        for (var i in types) {
                            var type = types[i];
                            if (resource.format.toLowerCase() == type)
                                return true;
                        }
                        return false;
                    }
                };

                scope.onPageChange = function () {
                    var start = (scope.currentPage - 1) * scope.itemsPerPage;
                    scope.ckanStart = start;
                    handlePageResults(scope.ckan);
                };

                var handlePageResults = function(result){
                    scope.ckanSearchResults = [];
                    var length = scope.ckanStart + scope.itemsPerPage;

                    if(length > result.results.length){
                        length = result.results.length;
                    }

                    for(var i = scope.ckanStart; i<length; i++){
                        scope.ckanSearchResults[i-scope.ckanStart] = result.results[i];
                    }
                }

                scope.loadResource = function (dataset, resource) {
                    ngProgress.start();
                    $http({
                        url: API_CONF.DATASETS_MANAGER_URL + '/ckan/download',
                        params: {
                            api: attrs.apiBase,
                            id: resource.id,
                            convert: true
                        }
                    }).then(function (response) {
                        if(response.data.result == 500){
                            ngProgress.complete();
                            dialogs.notify('Selected dataset cannot be displayed', 'Please choose another one.');
                        }
                        else{
                            scope.loadData(dataset, resource, response.data);
                            ngProgress.complete();
                        }
                    });
                };

                scope.search = function (term, start) {
                    ngProgress.start();
                    scope.lastTerm = term;
                    scope.eurostatStart = 0;
                    $http({
                        url: API_CONF.DATASETS_MANAGER_URL + '/ckan/search',
                        params: {
                            api: attrs.apiBase,
                            q: term,
                            start: start
                        }
                    }).then(function (response) {
                        ngProgress.complete();
                        scope.ckan = response.data.result;
                        handlePageResults(response.data.result);
                    });
                };
            }
        }
    }
]);
<|endoftext|>


<commit_before>'use strict';

var oauth = require('../lib/github-oauth.js');
var _ = require('lodash');

// Mock the GitHub API.
var nock = require('nock');
// nock.recorder.rec();
var api = nock('https://api.github.com').log(console.log);
// api.get('/').reply(200);
// api.get('/users/github').reply(200, {}, {
//     'X-Ratelimit-Remaining': '0'
// });

function breakpoint (name) {
    process.env.GITHUB_OAUTH_TESTING = name;
}

function clearBreakpoint() {
    breakpoint(true);
}

function run (options, callback) {
    if (_.isUndefined(options)) {
        options = {};
    }
    if (_.isFunction(options)) {
        callback = options;
    }
    options = _.extend({name: 'my-token'}, options);
    if (_.isUndefined(callback)) {
        callback = function () {};
    }
    var thisPrompt = oauth(options, callback);
    thisPrompt.rl.output.mute();
    return thisPrompt;
}

var assert = require('assert');
/*
    ======== A Handy Little Mocha Reference ========
    https://github.com/visionmedia/mocha/

    Test assertions:
        assert.fail(actual, expected, message, operator)
        assert(value, message), assert.ok(value, [message])
        assert.equal(actual, expected, [message])
        assert.notEqual(actual, expected, [message])
        assert.deepEqual(actual, expected, [message])
        assert.notDeepEqual(actual, expected, [message])
        assert.strictEqual(actual, expected, [message])
        assert.notStrictEqual(actual, expected, [message])
        assert.throws(block, [error], [message])
        assert.doesNotThrow(block, [message])
        assert.ifError(value)

        Apart from assert, Mocha allows you to use any of the following assertion libraries:
        - should.js
        - chai
        - expect.js
        - better-assert
*/

describe('Oauth', function () {

    beforeEach(function () {
        nock.cleanAll();
    });

    // Init option.
    describe('Option: name', function () {

        it('should error if nothing is passed', function () {
            assert.throws(
                function () {
                    oauth();
                },
                /Option name is required/
            );
        });

        it('should not error if required field "name" is given as a string of length', function () {
            assert.doesNotThrow(function () {
                oauth({name: 'my-token'});
            });
        });

        // Falsey.
        _.each(
            {
                'undefined': void 0,
                'null': null,
                'false': false,
                '0': 0,
                'NaN': NaN,
                'empty string': ''
            },
            function (wrongType, identifier) {
                it('should error if required field "name" is given as: ' + identifier, function () {
                    assert.throws(
                        function () {
                            oauth({name: wrongType});
                        },
                        /Option name is required/
                    );
                });
            }
        );

        // Truthy.
        _.each(
            {
                'true': true,
                '1': 1,
                'Infinity': Infinity,
                'array': [],
                'object': {}
            },
            function (wrongType, identifier) {
                it('should error if required field "name" is given as: ' + identifier, function () {
                    assert.throws(
                        function () {
                            oauth({name: wrongType});
                        },
                        /Option name must be a string/
                    );
                });
            }
        );

    });

    // Init option.
    describe('Option: scopes', function () {

        // Optional: defined or passed.
        _.each(
            {
                'null': null,
                'false': false,
                'true': true,
                'number': 1,
                'NaN': NaN,
                'empty string': '',
                'string of length': 'string',
                'object': {}
            },
            function (wrongType, identifier) {
                it('should error if optional field "scopes" is given as: ' + identifier, function () {
                    assert.throws(
                        function () {
                            oauth({
                                name: 'my-token',
                                scopes: wrongType
                            });
                        },
                        /Option scopes must be an array/
                    );
                });
            }
        );

        // Optional: undefined or not passed.
        _.each(
            {
                'undefined': void 0,
                'array': [],
                'array of length': ['scopes'],
            },
            function (rightType, identifier) {
                it('should not error if optional field "scopes" is given as: ' + identifier, function () {
                    assert.doesNotThrow(function () {
                        oauth({
                            name: 'my-token',
                            scopes: rightType
                        });
                    });
                });
            }
        );

    });

    // Init option.
    describe('Option: url', function () {

        // Optional: defined or passed.
        _.each(
            {
                'null': null,
                'false': false,
                'true': true,
                'number': 1,
                'NaN': NaN,
                'array': [],
                'array of length': ['url'],
                'object': {}
            },
            function (wrongType, identifier) {
                it('should error if optional field "url" is given as: ' + identifier, function () {
                    assert.throws(
                        function () {
                            oauth({
                                name: 'my-token',
                                url: wrongType
                            });
                        },
                        /Option url must be a string/
                    );
                });
            }
        );

        // Optional: undefined or not passed.
        _.each(
            {
                'undefined': void 0,
                'empty string': '',
                'string of length': 'string'
            },
            function (rightType, identifier) {
                it('should not error if optional field "url" is given as: ' + identifier, function () {
                    assert.doesNotThrow(function () {
                        oauth({
                            name: 'my-token',
                            url: rightType
                        });
                    });
                });
            }
        );

    });

    // Normal use: asks for details.
    describe('prompt', function () {

        var prompt;

        beforeEach(function () {
            clearBreakpoint();
        });

        afterEach(function () {
            if (prompt && prompt.rl) {
                prompt.close();
            }
        });

        describe('username', function () {
            it('should show message on empty username input', function (done) {
                breakpoint('VALIDATE_USERNAME');
                prompt = run(function (answer, message) {
                    assert.equal(answer.length, 0);
                    assert.equal(message, 'username is required');
                    done();
                });
                prompt.rl.write('\n');
            });
            it('should not error on username input', function (done) {
                assert.doesNotThrow(function () {
                    prompt = run();
                    prompt.rl.write('username\n');
                    done();
                });
            });
            it('should have a username after successful input', function (done) {
                prompt = run();
                prompt.rl.write('username\n');
                assert.ok(_.has(prompt.answers, 'username'));
                assert.equal(prompt.answers.username, 'username');
                done();
            });
        });

        describe('password', function () {
            it('should show message on empty password input', function (done) {
                breakpoint('VALIDATE_PASSWORD');
                prompt = run(function (answer, message) {
                    assert.equal(answer.length, 0);
                    assert.equal(message, 'password is required');
                    done();
                });
                prompt.rl.write('username\n');
                prompt.rl.write('\n');
            });
            it('should not error on password input', function (done) {
                assert.doesNotThrow(function () {
                    prompt = run();
                    prompt.rl.write('username\n');
                    prompt.rl.write('password\n');
                    done();
                });
            });
            it('should have a username and password after successful input', function (done) {
                prompt = run();
                prompt.rl.write('username\n');
                prompt.rl.write('password\n');
                assert.ok(_.has(prompt.answers, 'username'));
                assert.ok(_.has(prompt.answers, 'password'));
                assert.equal(prompt.answers.username, 'username');
                assert.equal(prompt.answers.password, 'password');
                done();
            });
        });

        describe('2FA', function () {
            it('should continue after password input when no 2FA code required');
            it('should ask for a 2FA code when required', function (done) {
                api.get('/authorizations').reply(
                    401,
                    {
                        "message":"Must specify two-factor authentication OTP code.",
                        "documentation_url":"https://developer.github.com/v3/auth#working-with-two-factor-authentication"
                    },
                    {
                        server: 'GitHub.com',
                        date: 'Mon, 30 Jun 2014 19:06:17 GMT',
                        'content-type': 'application/json; charset=utf-8',
                        status: '401 Unauthorized',
                        'x-github-otp': 'required; app',
                        'x-github-media-type': 'github.beta; format=json',
                        'x-ratelimit-limit': '60',
                        'x-ratelimit-remaining': '58',
                        'x-ratelimit-reset': '1404158751',
                        'x-xss-protection': '1; mode=block',
                        'x-frame-options': 'deny',
                        'content-security-policy': 'default-src \'none\'',
                        'content-length': '160',
                        'access-control-allow-credentials': 'true',
                        'access-control-expose-headers': 'ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval',
                        'access-control-allow-origin': '*',
                        'x-github-request-id': 'D57BAE20:1CCC:5FFCD44:53B1B529',
                        'strict-transport-security': 'max-age=31536000',
                        'x-content-type-options': 'nosniff'
                    }
                );
                breakpoint('CREATE_HEADERS');
                prompt = run(function (has2FA) {
                    assert(has2FA);
                    done();
                });
                prompt.rl.write('username\n');
                prompt.rl.write('password\n');
            });
            it('should not error on 2FA code input');
            it('should error and ask for input again on no 2FA code input');
        });

    });

    // Static use: accepts details.
    it('should error when username is not passed to static call');
    it('should error when password is not passed to static call');
    it('should return a 2FA code request from a static call if required');
    it('should error when 2FA code is required and not given for a static call');

    // Authentication test.
    describe('authentication', function () {

        it.skip('should error an authentication test with bad credentials when 2FA not required', function (done) {
            api.get('/authorizations').reply(
                '401',
                {
                    "message":"Bad credentials",
                    "documentation_url":"https://developer.github.com/v3"
                },
                {
                    server: 'GitHub.com',
                    date: 'Mon, 30 Jun 2014 17:51:23 GMT',
                    'content-type': 'application/json; charset=utf-8',
                    status: '401 Unauthorized',
                    'x-github-media-type': 'github.beta; format=json',
                    'x-ratelimit-limit': '60',
                    'x-ratelimit-remaining': '56',
                    'x-ratelimit-reset': '1404153929',
                    'x-xss-protection': '1; mode=block',
                    'x-frame-options': 'deny',
                    'content-security-policy': 'default-src \'none\'',
                    'content-length': '83',
                    'access-control-allow-credentials': 'true',
                    'access-control-expose-headers': 'ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval',
                    'access-control-allow-origin': '*',
                    'x-github-request-id': 'D57BAE20:5E0A:105DAD3:53B1A39B',
                    'strict-transport-security': 'max-age=31536000',
                    'x-content-type-options': 'nosniff'
                }
            );
            breakpoint('CHECK_2FA');
            var prompt = run(function (err, res) {
                assert.throws(function () {
                    assert.ifError(err);
                });
                done();
            });
            prompt.rl.output.mute();
            prompt.rl.write('username\n');
            prompt.rl.write('password\n');
        });
        it('should error an authentication test with bad credentials when 2FA required');
        it('should succeed an authentication test with good basic credentials');
        it('should succeed an authentication test with good 2FA credentials');
    });

    // Token creation.
    it('should error when it cannot connect to the api');
    it('should explain when rate limit remaining is 0');
    it('should create and return a new token');
    it('should get and return an existing token by name');

});
<commit_msg>Explain code. Default breakpoint. Remove duplicated assertions.<commit_after>'use strict';

var oauth = require('../lib/github-oauth.js');
var _ = require('lodash');

// Mock the GitHub API.
var nock = require('nock');
// nock.recorder.rec();
var api = nock('https://api.github.com').log(console.log);
// api.get('/').reply(200);
// api.get('/users/github').reply(200, {}, {
//     'X-Ratelimit-Remaining': '0'
// });

// Allow a pseudo-breakpoint to be passed through to the main code.
function breakpoint (name) {
    process.env.GITHUB_OAUTH_TESTING = name;
}
function clearBreakpoint() {
    // Default the testing environment variable to the string of 'true'.
    breakpoint(true);
}
// Init the testing env.
clearBreakpoint();

// Dry the calling of oauth().
function run (options, callback) {
    if (_.isUndefined(options)) {
        options = {};
    }
    if (_.isFunction(options)) {
        callback = options;
    }
    options = _.extend({name: 'my-token'}, options);
    if (_.isUndefined(callback)) {
        callback = function () {};
    }
    var thisPrompt = oauth(options, callback);
    thisPrompt.rl.output.mute();
    return thisPrompt;
}

var assert = require('assert');
/*
    ======== A Handy Little Mocha Reference ========
    https://github.com/visionmedia/mocha/

    Test assertions:
        assert.fail(actual, expected, message, operator)
        assert(value, message), assert.ok(value, [message])
        assert.equal(actual, expected, [message])
        assert.notEqual(actual, expected, [message])
        assert.deepEqual(actual, expected, [message])
        assert.notDeepEqual(actual, expected, [message])
        assert.strictEqual(actual, expected, [message])
        assert.notStrictEqual(actual, expected, [message])
        assert.throws(block, [error], [message])
        assert.doesNotThrow(block, [message])
        assert.ifError(value)

        Apart from assert, Mocha allows you to use any of the following assertion libraries:
        - should.js
        - chai
        - expect.js
        - better-assert
*/

describe('Oauth', function () {

    beforeEach(function () {
        nock.cleanAll();
    });

    // Init option.
    describe('Option: name', function () {

        it('should error if nothing is passed', function () {
            assert.throws(
                function () {
                    oauth();
                },
                /Option name is required/
            );
        });

        it('should not error if required field "name" is given as a string of length', function () {
            assert.doesNotThrow(function () {
                oauth({name: 'my-token'});
            });
        });

        // Falsey.
        _.each(
            {
                'undefined': void 0,
                'null': null,
                'false': false,
                '0': 0,
                'NaN': NaN,
                'empty string': ''
            },
            function (wrongType, identifier) {
                it('should error if required field "name" is given as: ' + identifier, function () {
                    assert.throws(
                        function () {
                            oauth({name: wrongType});
                        },
                        /Option name is required/
                    );
                });
            }
        );

        // Truthy.
        _.each(
            {
                'true': true,
                '1': 1,
                'Infinity': Infinity,
                'array': [],
                'object': {}
            },
            function (wrongType, identifier) {
                it('should error if required field "name" is given as: ' + identifier, function () {
                    assert.throws(
                        function () {
                            oauth({name: wrongType});
                        },
                        /Option name must be a string/
                    );
                });
            }
        );

    });

    // Init option.
    describe('Option: scopes', function () {

        // Optional: defined or passed.
        _.each(
            {
                'null': null,
                'false': false,
                'true': true,
                'number': 1,
                'NaN': NaN,
                'empty string': '',
                'string of length': 'string',
                'object': {}
            },
            function (wrongType, identifier) {
                it('should error if optional field "scopes" is given as: ' + identifier, function () {
                    assert.throws(
                        function () {
                            oauth({
                                name: 'my-token',
                                scopes: wrongType
                            });
                        },
                        /Option scopes must be an array/
                    );
                });
            }
        );

        // Optional: undefined or not passed.
        _.each(
            {
                'undefined': void 0,
                'array': [],
                'array of length': ['scopes'],
            },
            function (rightType, identifier) {
                it('should not error if optional field "scopes" is given as: ' + identifier, function () {
                    assert.doesNotThrow(function () {
                        oauth({
                            name: 'my-token',
                            scopes: rightType
                        });
                    });
                });
            }
        );

    });

    // Init option.
    describe('Option: url', function () {

        // Optional: defined or passed.
        _.each(
            {
                'null': null,
                'false': false,
                'true': true,
                'number': 1,
                'NaN': NaN,
                'array': [],
                'array of length': ['url'],
                'object': {}
            },
            function (wrongType, identifier) {
                it('should error if optional field "url" is given as: ' + identifier, function () {
                    assert.throws(
                        function () {
                            oauth({
                                name: 'my-token',
                                url: wrongType
                            });
                        },
                        /Option url must be a string/
                    );
                });
            }
        );

        // Optional: undefined or not passed.
        _.each(
            {
                'undefined': void 0,
                'empty string': '',
                'string of length': 'string'
            },
            function (rightType, identifier) {
                it('should not error if optional field "url" is given as: ' + identifier, function () {
                    assert.doesNotThrow(function () {
                        oauth({
                            name: 'my-token',
                            url: rightType
                        });
                    });
                });
            }
        );

    });

    // Normal use: asks for details.
    describe('prompt', function () {

        var prompt;

        beforeEach(function () {
            clearBreakpoint();
        });

        afterEach(function () {
            if (prompt && prompt.rl) {
                prompt.close();
            }
        });

        describe('username', function () {
            it('should show message on empty username input', function (done) {
                breakpoint('VALIDATE_USERNAME');
                prompt = run(function (answer, message) {
                    assert.equal(answer.length, 0);
                    assert.equal(message, 'username is required');
                    done();
                });
                prompt.rl.write('\n');
            });
            it('should not error on username input', function (done) {
                assert.doesNotThrow(function () {
                    prompt = run();
                    prompt.rl.write('username\n');
                    done();
                });
            });
            it('should have a username after successful input', function (done) {
                prompt = run();
                prompt.rl.write('username\n');
                assert.ok(_.has(prompt.answers, 'username'));
                assert.equal(prompt.answers.username, 'username');
                done();
            });
        });

        describe('password', function () {
            it('should show message on empty password input', function (done) {
                breakpoint('VALIDATE_PASSWORD');
                prompt = run(function (answer, message) {
                    assert.equal(answer.length, 0);
                    assert.equal(message, 'password is required');
                    done();
                });
                prompt.rl.write('username\n');
                prompt.rl.write('\n');
            });
            it('should not error on password input', function (done) {
                assert.doesNotThrow(function () {
                    prompt = run();
                    prompt.rl.write('username\n');
                    prompt.rl.write('password\n');
                    done();
                });
            });
            it('should have a username and password after successful input', function (done) {
                prompt = run();
                prompt.rl.write('username\n');
                prompt.rl.write('password\n');
                assert.ok(_.has(prompt.answers, 'password'));
                assert.equal(prompt.answers.password, 'password');
                done();
            });
        });

        describe('2FA', function () {
            it('should continue after password input when no 2FA code required');
            it('should ask for a 2FA code when required', function (done) {
                api.get('/authorizations').reply(
                    401,
                    {
                        "message":"Must specify two-factor authentication OTP code.",
                        "documentation_url":"https://developer.github.com/v3/auth#working-with-two-factor-authentication"
                    },
                    {
                        server: 'GitHub.com',
                        date: 'Mon, 30 Jun 2014 19:06:17 GMT',
                        'content-type': 'application/json; charset=utf-8',
                        status: '401 Unauthorized',
                        'x-github-otp': 'required; app',
                        'x-github-media-type': 'github.beta; format=json',
                        'x-ratelimit-limit': '60',
                        'x-ratelimit-remaining': '58',
                        'x-ratelimit-reset': '1404158751',
                        'x-xss-protection': '1; mode=block',
                        'x-frame-options': 'deny',
                        'content-security-policy': 'default-src \'none\'',
                        'content-length': '160',
                        'access-control-allow-credentials': 'true',
                        'access-control-expose-headers': 'ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval',
                        'access-control-allow-origin': '*',
                        'x-github-request-id': 'D57BAE20:1CCC:5FFCD44:53B1B529',
                        'strict-transport-security': 'max-age=31536000',
                        'x-content-type-options': 'nosniff'
                    }
                );
                breakpoint('CREATE_HEADERS');
                prompt = run(function (has2FA) {
                    assert(has2FA);
                    done();
                });
                prompt.rl.write('username\n');
                prompt.rl.write('password\n');
            });
            it('should not error on 2FA code input');
            it('should error and ask for input again on no 2FA code input');
        });

    });

    // Static use: accepts details.
    it('should error when username is not passed to static call');
    it('should error when password is not passed to static call');
    it('should return a 2FA code request from a static call if required');
    it('should error when 2FA code is required and not given for a static call');

    // Authentication test.
    describe('authentication', function () {

        it.skip('should error an authentication test with bad credentials when 2FA not required', function (done) {
            api.get('/authorizations').reply(
                '401',
                {
                    "message":"Bad credentials",
                    "documentation_url":"https://developer.github.com/v3"
                },
                {
                    server: 'GitHub.com',
                    date: 'Mon, 30 Jun 2014 17:51:23 GMT',
                    'content-type': 'application/json; charset=utf-8',
                    status: '401 Unauthorized',
                    'x-github-media-type': 'github.beta; format=json',
                    'x-ratelimit-limit': '60',
                    'x-ratelimit-remaining': '56',
                    'x-ratelimit-reset': '1404153929',
                    'x-xss-protection': '1; mode=block',
                    'x-frame-options': 'deny',
                    'content-security-policy': 'default-src \'none\'',
                    'content-length': '83',
                    'access-control-allow-credentials': 'true',
                    'access-control-expose-headers': 'ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval',
                    'access-control-allow-origin': '*',
                    'x-github-request-id': 'D57BAE20:5E0A:105DAD3:53B1A39B',
                    'strict-transport-security': 'max-age=31536000',
                    'x-content-type-options': 'nosniff'
                }
            );
            breakpoint('CHECK_2FA');
            var prompt = run(function (err, res) {
                assert.throws(function () {
                    assert.ifError(err);
                });
                done();
            });
            prompt.rl.output.mute();
            prompt.rl.write('username\n');
            prompt.rl.write('password\n');
        });
        it('should error an authentication test with bad credentials when 2FA required');
        it('should succeed an authentication test with good basic credentials');
        it('should succeed an authentication test with good 2FA credentials');
    });

    // Token creation.
    it('should error when it cannot connect to the api');
    it('should explain when rate limit remaining is 0');
    it('should create and return a new token');
    it('should get and return an existing token by name');

});
<|endoftext|>


<commit_before>import _ from 'underscore';
import moment from 'moment';
import {Model, Collection} from 'backbone';
import Config from '../../../Config';
import {props} from '../../decorators'

/**
 * @class PlaylistItemModel
 */
class PlaylistItem extends Model {

    defaults() {
        return {
            id: 0,
            etag: null,
            kind: null,
            videoId: null,
            playlistId: null,
            channelId: null,
            description: '',
            publishedAt: null,
            thumbnails: null,
            title: ''
        }
    }

    /** @param {{kind: string, etag: string, id: string, snippet: {publishedAt: string, channelId: string, title: string, description: string, thumbnails: {default: {url: string, width: number, height: number}, medium: {url: string, width: number, height: number}, high: {url: string, width: number, height: number}, standard: {url: string, width: number, height: number}, maxres: {url: string, width: number, height: number}}, channelTitle: string, playlistId: string, position: number, resourceId: {kind: string, videoId: string}}, contentDetails: {videoId: string}}} response */
    parse(response) {
        return {
            id: response.id,
            etag: response.etag,
            kind: response.kind,
            videoId: response.snippet.resourceId.videoId,
            playlistId: response.snippet.playlistId,
            channelId: response.snippet.channelId,
            description: response.snippet.description,
            publishedAt: moment(response.snippet.publishedAt),
            thumbnails: response.snippet.thumbnails,
            title: response.snippet.title
        }
    }
}

class PlaylistItems extends Collection {
    @props({
        model: PlaylistItem,

        url: function () {
            return Config.endpoints.playlistItems +'?playlistId='+ this._playlistId;
        }
    })

    set playlistId(val) {
        this._playlistId = val;
    }

    /** @returns {PlaylistItem} */
    getNextPlaylistItem(videoId) {
        const model = this.getCurrentPlaylistItem(videoId);
        const index = this.indexOf(model) + 1;

        return this.at(index);
    }

    /** @returns {PlaylistItem} */
    getCurrentPlaylistItem(videoId) {
        return this.findWhere({ videoId });
    }

    parse(response) {
        if(this._playlistId === 'PLsksxTH4pR3I6-7OYZ0GigNnc7KI5S0OK') {
            // Special case: Almost Daily -> reverse playlist
            response.items.reverse();
        }

        return response.items;
    }

    search({ search, date }) {
        if(!this._allModels) {
            this._allModels = this.models;
        }

        this.reset(
            _.filter(this._allModels, (model) => {
                const title = model.get('title');

                if (date) {
                    const publishedAt = model.get('publishedAt');

                    // Match date
                    if (date.date() !== publishedAt.date() || date.month() !== publishedAt.month() || date.year() !== publishedAt.year()) {
                        return false;
                    }
                }

                return title.toLowerCase().indexOf(search.toLowerCase()) !== -1;
            })
        )
    }
}

export {PlaylistItem, PlaylistItems}
export default PlaylistItems<commit_msg>Removed special case for almost daily<commit_after>import _ from 'underscore';
import moment from 'moment';
import {Model, Collection} from 'backbone';
import Config from '../../../Config';
import {props} from '../../decorators'

/**
 * @class PlaylistItemModel
 */
class PlaylistItem extends Model {

    defaults() {
        return {
            id: 0,
            etag: null,
            kind: null,
            videoId: null,
            playlistId: null,
            channelId: null,
            description: '',
            publishedAt: null,
            thumbnails: null,
            title: ''
        }
    }

    /** @param {{kind: string, etag: string, id: string, snippet: {publishedAt: string, channelId: string, title: string, description: string, thumbnails: {default: {url: string, width: number, height: number}, medium: {url: string, width: number, height: number}, high: {url: string, width: number, height: number}, standard: {url: string, width: number, height: number}, maxres: {url: string, width: number, height: number}}, channelTitle: string, playlistId: string, position: number, resourceId: {kind: string, videoId: string}}, contentDetails: {videoId: string}}} response */
    parse(response) {
        return {
            id: response.id,
            etag: response.etag,
            kind: response.kind,
            videoId: response.snippet.resourceId.videoId,
            playlistId: response.snippet.playlistId,
            channelId: response.snippet.channelId,
            description: response.snippet.description,
            publishedAt: moment(response.snippet.publishedAt),
            thumbnails: response.snippet.thumbnails,
            title: response.snippet.title
        }
    }
}

class PlaylistItems extends Collection {
    @props({
        model: PlaylistItem,

        url: function () {
            return Config.endpoints.playlistItems +'?playlistId='+ this._playlistId;
        }
    })

    set playlistId(val) {
        this._playlistId = val;
    }

    /** @returns {PlaylistItem} */
    getNextPlaylistItem(videoId) {
        const model = this.getCurrentPlaylistItem(videoId);
        const index = this.indexOf(model) + 1;

        return this.at(index);
    }

    /** @returns {PlaylistItem} */
    getCurrentPlaylistItem(videoId) {
        return this.findWhere({ videoId });
    }

    parse(response) {
        return response.items;
    }

    search({ search, date }) {
        if(!this._allModels) {
            this._allModels = this.models;
        }

        this.reset(
            _.filter(this._allModels, (model) => {
                const title = model.get('title');

                if (date) {
                    const publishedAt = model.get('publishedAt');

                    // Match date
                    if (date.date() !== publishedAt.date() || date.month() !== publishedAt.month() || date.year() !== publishedAt.year()) {
                        return false;
                    }
                }

                return title.toLowerCase().indexOf(search.toLowerCase()) !== -1;
            })
        )
    }
}

export {PlaylistItem, PlaylistItems}
export default PlaylistItems<|endoftext|>


<commit_before>/*
Auto-runs grunt in $GIRDER_PATH after the webpack build finishes
(only happens with webpack --watch)

make sure to export GIRDER_PATH=path/to/your/girder/installation
before webpack --watch
*/

var exec = require('child_process').exec;

module.exports = {
  apply: function (compiler) {
    compiler.plugin('emit', (compilation, callback) => {
      if (compilation.options.watch === true) {
        exec('cd $GIRDER_PATH && grunt --force', function (error, stdout, stderr) {
          if (error) {
            console.log('Couldn\'t run grunt. Did you forget to export GIRDER_PATH ?');
            process.exit(1);
          }
          console.log(stdout);
        });
      }
      callback();
    });
  }
};
<commit_msg>Remove grunt --force option<commit_after>/*
Auto-runs grunt in $GIRDER_PATH after the webpack build finishes
(only happens with webpack --watch)

make sure to export GIRDER_PATH=path/to/your/girder/installation
before webpack --watch
*/

var exec = require('child_process').exec;

module.exports = {
  apply: function (compiler) {
    compiler.plugin('emit', (compilation, callback) => {
      if (compilation.options.watch === true) {
        exec('cd $GIRDER_PATH && grunt', function (error, stdout, stderr) {
          if (error) {
            console.log('Couldn\'t run grunt. Did you forget to export GIRDER_PATH ?');
            process.exit(1);
          }
          console.log(stdout);
        });
      }
      callback();
    });
  }
};
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import CurrentWeather from './CurrentWeather';
import Hourly from './Hourly';
import Daily from './Daily';
import $ from 'jquery';

export default class Weather extends Component {
  constructor() {
    super();
    this.state = {
      location: '',
      weather: [],
    };
  }

  componentDidMount() {
    if (localStorage.getItem('location')) {
      const value = localStorage.getItem('location');
      this.apiCall(value);
    }
  }

  apiCall(value) {
    $.get(`http://api.wunderground.com/api/1443dd5d9e0f4a45/forecast10day/conditions/hourly/q/${value}.json`).then((data) => {
      this.setState({
        location: '',
        weather: [data],
      });
    });
  }

  handleSubmit() {
    localStorage.setItem('location', this.state.location);
    this.apiCall(localStorage.getItem('location'));
  }

  render() {
    let hourly;
    let daily;
    let current;
    if (this.state.weather.length) {
      current = <CurrentWeather weather={this.state.weather}/>;
      hourly = <Hourly weather={this.state.weather}/>;
      daily = <Daily weather={this.state.weather}/>;
    }

    return (

          <fieldset>
            <input
                type="text"
                className="search"
                placeholder="Enter City, State or Zipcode"
                value={this.state.location}
                onChange={(e) => this.setState({
                  location: e.target.value,
                })} autoFocus/>
            <input type="submit" className="go" value="Go" onClick={() => this.handleSubmit()}/>
          </fieldset>

        { current }
        { hourly }
        { daily }

    );
  }
}
<commit_msg>Add welcome component<commit_after>import React, { Component } from 'react';
import CurrentWeather from './CurrentWeather';
import Hourly from './Hourly';
import Daily from './Daily';
import Welcome from './Welcome';
import $ from 'jquery';

export default class Weather extends Component {
  constructor() {
    super();
    this.state = {
      location: '',
      weather: [],
    };
  }

  componentDidMount() {
    if (localStorage.getItem('location')) {
      const value = localStorage.getItem('location');
      this.apiCall(value);
    }
  }

  apiCall(value) {
    $.get(`http://api.wunderground.com/api/1443dd5d9e0f4a45/forecast10day/conditions/hourly/q/${value}.json`).then((data) => {
      this.setState({
        location: '',
        weather: [data],
      });
    });
  }

  handleSubmit() {
    localStorage.setItem('location', this.state.location);
    this.apiCall(localStorage.getItem('location'));
  }

  render() {
    let current;
    let daily;
    let hourly;
    let welcome;
    if (this.state.weather.length) {
      current = <CurrentWeather weather={this.state.weather}/>;
      daily = <Daily weather={this.state.weather}/>;
      hourly = <Hourly weather={this.state.weather}/>;
    } else {
      welcome = <Welcome/>;
    }

    return (

          <fieldset>
            <input
                type="text"
                className="search"
                placeholder="Enter City, State or Zipcode"
                value={this.state.location}
                onChange={(e) => this.setState({
                  location: e.target.value,
                })} autoFocus/>
            <input type="submit" className="go" value="Go" onClick={() => this.handleSubmit()}/>
          </fieldset>

        { welcome }
        { current }
        { hourly }
        { daily }

    );
  }
}
<|endoftext|>


<commit_before>// This is heavily based on https://github.com/sindresorhus/electron-context-menu
// ❤ @sindresorhus

import {
  clipboard, remote, ipcRenderer, shell,
} from 'electron';

import { isDevMode, isMac } from '../environment';
import { SPELLCHECKER_LOCALES } from '../i18n/languages';

const debug = require('debug')('Franz:contextMenu');

const { Menu } = remote;

// const win = remote.getCurrentWindow();
const webContents = remote.getCurrentWebContents();

function delUnusedElements(menuTpl) {
  let notDeletedPrevEl;
  return menuTpl.filter(el => el.visible !== false).filter((el, i, array) => {
    const toDelete = el.type === 'separator' && (!notDeletedPrevEl || i === array.length - 1 || array[i + 1].type === 'separator');
    notDeletedPrevEl = toDelete ? notDeletedPrevEl : el;
    return !toDelete;
  });
}

const buildMenuTpl = (props, suggestions, isSpellcheckEnabled, defaultSpellcheckerLanguage, spellcheckerLanguage) => {
  const { editFlags } = props;
  const textSelection = props.selectionText.trim();
  const hasText = textSelection.length > 0;
  const can = type => editFlags[`can${type}`] && hasText;

  const canGoBack = webContents.canGoBack();
  const canGoForward = webContents.canGoForward();

  // @adlk: we can't use roles here due to a bug with electron where electron.remote.webContents.getFocusedWebContents() returns the first webview in DOM instead of the focused one
  // Github issue creation is pending
  let menuTpl = [
    {
      type: 'separator',
    }, {
      id: 'createTodo',
      label: `Create todo: "${textSelection.length > 15 ? `${textSelection.slice(0, 15)}...` : textSelection}"`,
      visible: hasText,
      click() {
        debug('Create todo from selected text', textSelection);
        ipcRenderer.sendToHost('feature:todos', {
          action: 'todos:create',
          data: {
            title: textSelection,
            url: window.location.href,
          },
        });
      },
    },
    {
      type: 'separator',
    }, {
      id: 'lookup',
      label: `Look Up "${textSelection.length > 15 ? `${textSelection.slice(0, 15)}...` : textSelection}"`,
      visible: isMac && props.mediaType === 'none' && hasText,
      click() {
        debug('Show definition for selection', textSelection);
        webContents.showDefinitionForSelection();
      },
    }, {
      type: 'separator',
    }, {
      id: 'cut',
      label: 'Cut',
      click() {
        if (can('Cut')) {
          webContents.cut();
        }
      },
      enabled: can('Cut'),
      visible: hasText && props.isEditable,
    }, {
      id: 'copy',
      label: 'Copy',
      click() {
        if (can('Copy')) {
          webContents.copy();
        }
      },
      enabled: can('Copy'),
      visible: props.isEditable || hasText,
    }, {
      id: 'paste',
      label: 'Paste',
      click() {
        if (editFlags.canPaste) {
          webContents.paste();
        }
      },
      enabled: editFlags.canPaste,
      visible: props.isEditable,
    }, {
      type: 'separator',
      visible: props.isEditable && hasText,
    }, {
      id: 'searchTextSelection',
      label: `Search Google for "${textSelection.length > 15 ? `${textSelection.slice(0, 15)}...` : textSelection}"`,
      visible: hasText,
      click() {
        const url = `https://www.google.com/search?q=${textSelection}`;
        debug('Search on Google', url);
        shell.openExternal(url);
      },
    }, {
      type: 'separator',
    },
  ];

  if (props.linkURL && props.mediaType === 'none') {
    menuTpl = [{
      type: 'separator',
    }, {
      id: 'openLink',
      label: 'Open Link in Browser',
      click() {
        debug('Open link in Browser', props.linkURL);
        shell.openExternal(props.linkURL);
      },
    }, {
      id: 'copyLink',
      label: 'Copy Link',
      click() {
        clipboard.write({
          bookmark: props.linkText,
          text: props.linkURL,
        });
      },
    }, {
      type: 'separator',
    }];
  }

  if (props.mediaType === 'image') {
    menuTpl.push({
      type: 'separator',
    }, {
      id: 'openImage',
      label: 'Open Image in Browser',
      click() {
        debug('Open image in Browser', props.srcURL);
        shell.openExternal(props.srcURL);
      },
    }, {
      id: 'copyImageAddress',
      label: 'Copy Image Address',
      click() {
        clipboard.write({
          bookmark: props.srcURL,
          text: props.srcURL,
        });
      },
    }, {
      type: 'separator',
    });
  }

  if (props.mediaType === 'image') {
    menuTpl.push({
      id: 'saveImageAs',
      label: 'Save Image As…',
      async click() {
        if (props.srcURL.startsWith('blob:')) {
          const url = new window.URL(props.srcURL.substr(5));
          const fileName = url.pathname.substr(1);
          const resp = await window.fetch(props.srcURL);
          const blob = await resp.blob();
          const reader = new window.FileReader();
          reader.readAsDataURL(blob);
          reader.onloadend = () => {
            const base64data = reader.result;

            ipcRenderer.send('download-file', {
              content: base64data,
              fileOptions: {
                name: fileName,
                mime: blob.type,
              },
            });
          };
          debug('binary string', blob);
        } else {
          ipcRenderer.send('download-file', { url: props.srcURL });
        }
      },
    }, {
      type: 'separator',
    });
  }

  if (suggestions.length > 0) {
    suggestions.reverse().map(suggestion => menuTpl.unshift({
      id: `suggestion-${suggestion}`,
      label: suggestion,
      click() {
        webContents.replaceMisspelling(suggestion);
      },
    }));
  }

  if (canGoBack || canGoForward) {
    menuTpl.push({
      type: 'separator',
    }, {
      id: 'goBack',
      label: 'Go Back',
      enabled: canGoBack,
      click() {
        webContents.goBack();
      },
    }, {
      id: 'goForward',
      label: 'Go Forward',
      enabled: canGoForward,
      click() {
        webContents.goForward();
      },
    }, {
      type: 'separator',
    });
  }

  const spellcheckingLanguages = [];
  Object.keys(SPELLCHECKER_LOCALES).sort(Intl.Collator().compare).forEach((key) => {
    spellcheckingLanguages.push({
      id: `lang-${key}`,
      label: SPELLCHECKER_LOCALES[key],
      type: 'radio',
      checked: spellcheckerLanguage === key,
      click() {
        debug('Setting service spellchecker to', key);
        ipcRenderer.sendToHost('set-service-spellchecker-language', key);
      },
    });
  });

  menuTpl.push({
    type: 'separator',
  }, {
    id: 'spellchecker',
    label: 'Spell Checking',
    visible: isSpellcheckEnabled,
    submenu: [
      {
        id: 'spellchecker',
        label: 'Available Languages',
        enabled: false,
      }, {
        type: 'separator',
      },
      {
        id: 'resetToDefault',
        label: `Reset to system default (${defaultSpellcheckerLanguage === 'automatic' ? 'Automatic' : SPELLCHECKER_LOCALES[defaultSpellcheckerLanguage]})`,
        type: 'radio',
        visible: defaultSpellcheckerLanguage !== spellcheckerLanguage || (defaultSpellcheckerLanguage !== 'automatic' && spellcheckerLanguage === 'automatic'),
        click() {
          debug('Resetting service spellchecker to system default');
          ipcRenderer.sendToHost('set-service-spellchecker-language', 'reset');
        },
      },
      {
        id: 'automaticDetection',
        label: 'Automatic language detection',
        type: 'radio',
        checked: spellcheckerLanguage === 'automatic',
        click() {
          debug('Detect language automatically');
          ipcRenderer.sendToHost('set-service-spellchecker-language', 'automatic');
        },
      },
      {
        type: 'separator',
        visible: defaultSpellcheckerLanguage !== spellcheckerLanguage,
      },
      ...spellcheckingLanguages],
  });

  if (isDevMode) {
    menuTpl.push({
      type: 'separator',
    }, {
      id: 'inspect',
      label: 'Inspect Element',
      click() {
        webContents.inspectElement(props.x, props.y);
      },
    });
  }

  return delUnusedElements(menuTpl);
};

export default function contextMenu(spellcheckProvider, isSpellcheckEnabled, getDefaultSpellcheckerLanguage, getSpellcheckerLanguage) {
  webContents.on('context-menu', async (e, props) => {
    e.preventDefault();

    let suggestions = [];
    if (spellcheckProvider && props.misspelledWord) {
      debug('Mispelled word', props.misspelledWord);
      suggestions = await spellcheckProvider.getSuggestion(props.misspelledWord);

      debug('Suggestions', suggestions);
    }

    const menu = Menu.buildFromTemplate(
      buildMenuTpl(
        props,
        suggestions.slice(0, 5),
        isSpellcheckEnabled(),
        getDefaultSpellcheckerLanguage(),
        getSpellcheckerLanguage(),
      ),
    );

    menu.popup();
  });
}
<commit_msg>feat(Context Menu): Add option to copy image<commit_after>// This is heavily based on https://github.com/sindresorhus/electron-context-menu
// ❤ @sindresorhus

import {
  clipboard, remote, ipcRenderer, shell, nativeImage,
} from 'electron';

import { isDevMode, isMac } from '../environment';
import { SPELLCHECKER_LOCALES } from '../i18n/languages';

const debug = require('debug')('Franz:contextMenu');

const { Menu } = remote;

// const win = remote.getCurrentWindow();
const webContents = remote.getCurrentWebContents();

function delUnusedElements(menuTpl) {
  let notDeletedPrevEl;
  return menuTpl.filter(el => el.visible !== false).filter((el, i, array) => {
    const toDelete = el.type === 'separator' && (!notDeletedPrevEl || i === array.length - 1 || array[i + 1].type === 'separator');
    notDeletedPrevEl = toDelete ? notDeletedPrevEl : el;
    return !toDelete;
  });
}

const buildMenuTpl = (props, suggestions, isSpellcheckEnabled, defaultSpellcheckerLanguage, spellcheckerLanguage) => {
  const { editFlags } = props;
  const textSelection = props.selectionText.trim();
  const hasText = textSelection.length > 0;
  const can = type => editFlags[`can${type}`] && hasText;

  const canGoBack = webContents.canGoBack();
  const canGoForward = webContents.canGoForward();

  // @adlk: we can't use roles here due to a bug with electron where electron.remote.webContents.getFocusedWebContents() returns the first webview in DOM instead of the focused one
  // Github issue creation is pending
  let menuTpl = [
    {
      type: 'separator',
    }, {
      id: 'createTodo',
      label: `Create todo: "${textSelection.length > 15 ? `${textSelection.slice(0, 15)}...` : textSelection}"`,
      visible: hasText,
      click() {
        debug('Create todo from selected text', textSelection);
        ipcRenderer.sendToHost('feature:todos', {
          action: 'todos:create',
          data: {
            title: textSelection,
            url: window.location.href,
          },
        });
      },
    },
    {
      type: 'separator',
    }, {
      id: 'lookup',
      label: `Look Up "${textSelection.length > 15 ? `${textSelection.slice(0, 15)}...` : textSelection}"`,
      visible: isMac && props.mediaType === 'none' && hasText,
      click() {
        debug('Show definition for selection', textSelection);
        webContents.showDefinitionForSelection();
      },
    }, {
      type: 'separator',
    }, {
      id: 'cut',
      label: 'Cut',
      click() {
        if (can('Cut')) {
          webContents.cut();
        }
      },
      enabled: can('Cut'),
      visible: hasText && props.isEditable,
    }, {
      id: 'copy',
      label: 'Copy',
      click() {
        if (can('Copy')) {
          webContents.copy();
        }
      },
      enabled: can('Copy'),
      visible: props.isEditable || hasText,
    }, {
      id: 'paste',
      label: 'Paste',
      click() {
        if (editFlags.canPaste) {
          webContents.paste();
        }
      },
      enabled: editFlags.canPaste,
      visible: props.isEditable,
    }, {
      type: 'separator',
      visible: props.isEditable && hasText,
    }, {
      id: 'searchTextSelection',
      label: `Search Google for "${textSelection.length > 15 ? `${textSelection.slice(0, 15)}...` : textSelection}"`,
      visible: hasText,
      click() {
        const url = `https://www.google.com/search?q=${textSelection}`;
        debug('Search on Google', url);
        shell.openExternal(url);
      },
    }, {
      type: 'separator',
    },
  ];

  if (props.linkURL && props.mediaType === 'none') {
    menuTpl = [{
      type: 'separator',
    }, {
      id: 'openLink',
      label: 'Open Link in Browser',
      click() {
        debug('Open link in Browser', props.linkURL);
        shell.openExternal(props.linkURL);
      },
    }, {
      id: 'copyLink',
      label: 'Copy Link',
      click() {
        clipboard.write({
          bookmark: props.linkText,
          text: props.linkURL,
        });
      },
    }, {
      type: 'separator',
    }];
  }

  if (props.mediaType === 'image') {
    menuTpl.push({
      type: 'separator',
    }, {
      id: 'openImage',
      label: 'Open Image in Browser',
      click() {
        debug('Open image in Browser', props.srcURL);
        shell.openExternal(props.srcURL);
      },
    }, {
      id: 'copyImage',
      label: 'Copy Image',
      click() {
        const img = document.createElement('img');
        img.src = props.srcURL;

        const canvas = document.createElement('canvas');
        canvas.width = img.width;
        canvas.height = img.height;

        const ctx = canvas.getContext('2d');
        ctx.drawImage(img, 0, 0);

        const dataURL = canvas.toDataURL('image/png');
        const image = nativeImage.createFromDataURL(dataURL);

        try {
          clipboard.write({
            image,
          });
        } catch (err) {
          console.warn(err);
        }
      },
    }, {
      id: 'copyImageAddress',
      label: 'Copy Image Address',
      click() {
        clipboard.write({
          bookmark: props.srcURL,
          text: props.srcURL,
        });
      },
    }, {
      type: 'separator',
    });
  }

  if (props.mediaType === 'image') {
    menuTpl.push({
      id: 'saveImageAs',
      label: 'Save Image As…',
      async click() {
        if (props.srcURL.startsWith('blob:')) {
          const url = new window.URL(props.srcURL.substr(5));
          const fileName = url.pathname.substr(1);
          const resp = await window.fetch(props.srcURL);
          const blob = await resp.blob();
          const reader = new window.FileReader();
          reader.readAsDataURL(blob);
          reader.onloadend = () => {
            const base64data = reader.result;

            ipcRenderer.send('download-file', {
              content: base64data,
              fileOptions: {
                name: fileName,
                mime: blob.type,
              },
            });
          };
          debug('binary string', blob);
        } else {
          ipcRenderer.send('download-file', { url: props.srcURL });
        }
      },
    }, {
      type: 'separator',
    });
  }

  if (suggestions.length > 0) {
    suggestions.reverse().map(suggestion => menuTpl.unshift({
      id: `suggestion-${suggestion}`,
      label: suggestion,
      click() {
        webContents.replaceMisspelling(suggestion);
      },
    }));
  }

  if (canGoBack || canGoForward) {
    menuTpl.push({
      type: 'separator',
    }, {
      id: 'goBack',
      label: 'Go Back',
      enabled: canGoBack,
      click() {
        webContents.goBack();
      },
    }, {
      id: 'goForward',
      label: 'Go Forward',
      enabled: canGoForward,
      click() {
        webContents.goForward();
      },
    }, {
      type: 'separator',
    });
  }

  const spellcheckingLanguages = [];
  Object.keys(SPELLCHECKER_LOCALES).sort(Intl.Collator().compare).forEach((key) => {
    spellcheckingLanguages.push({
      id: `lang-${key}`,
      label: SPELLCHECKER_LOCALES[key],
      type: 'radio',
      checked: spellcheckerLanguage === key,
      click() {
        debug('Setting service spellchecker to', key);
        ipcRenderer.sendToHost('set-service-spellchecker-language', key);
      },
    });
  });

  menuTpl.push({
    type: 'separator',
  }, {
    id: 'spellchecker',
    label: 'Spell Checking',
    visible: isSpellcheckEnabled,
    submenu: [
      {
        id: 'spellchecker',
        label: 'Available Languages',
        enabled: false,
      }, {
        type: 'separator',
      },
      {
        id: 'resetToDefault',
        label: `Reset to system default (${defaultSpellcheckerLanguage === 'automatic' ? 'Automatic' : SPELLCHECKER_LOCALES[defaultSpellcheckerLanguage]})`,
        type: 'radio',
        visible: defaultSpellcheckerLanguage !== spellcheckerLanguage || (defaultSpellcheckerLanguage !== 'automatic' && spellcheckerLanguage === 'automatic'),
        click() {
          debug('Resetting service spellchecker to system default');
          ipcRenderer.sendToHost('set-service-spellchecker-language', 'reset');
        },
      },
      {
        id: 'automaticDetection',
        label: 'Automatic language detection',
        type: 'radio',
        checked: spellcheckerLanguage === 'automatic',
        click() {
          debug('Detect language automatically');
          ipcRenderer.sendToHost('set-service-spellchecker-language', 'automatic');
        },
      },
      {
        type: 'separator',
        visible: defaultSpellcheckerLanguage !== spellcheckerLanguage,
      },
      ...spellcheckingLanguages],
  });

  if (isDevMode) {
    menuTpl.push({
      type: 'separator',
    }, {
      id: 'inspect',
      label: 'Inspect Element',
      click() {
        webContents.inspectElement(props.x, props.y);
      },
    });
  }

  return delUnusedElements(menuTpl);
};

export default function contextMenu(spellcheckProvider, isSpellcheckEnabled, getDefaultSpellcheckerLanguage, getSpellcheckerLanguage) {
  webContents.on('context-menu', async (e, props) => {
    e.preventDefault();

    let suggestions = [];
    if (spellcheckProvider && props.misspelledWord) {
      debug('Mispelled word', props.misspelledWord);
      suggestions = await spellcheckProvider.getSuggestion(props.misspelledWord);

      debug('Suggestions', suggestions);
    }

    const menu = Menu.buildFromTemplate(
      buildMenuTpl(
        props,
        suggestions.slice(0, 5),
        isSpellcheckEnabled(),
        getDefaultSpellcheckerLanguage(),
        getSpellcheckerLanguage(),
      ),
    );

    menu.popup();
  });
}
<|endoftext|>


<commit_before>'use strict';

const plugabilly = require('plugabilly');
const merge = require('deepmerge');
const uuid = require('uuid');

/**
 * Retrieve Input Plugins
 *
 * {Object} - [Plugabilly](https://github.com/Snugug/plugabilly) object
 */
const plugins = plugabilly().name().containsSync('input-plugin-');

const squish = (types) => {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(types)) {
      reject(new Error('Content types must be an array'));
    }
    const configured = types.map(type => {
      const mergedType = type;
      const ids = [];

      const attrs = mergedType.attributes.map(attribute => {
        let plugin = Object.keys(plugins).indexOf(`input-plugin-${attribute.type}`);

        // Reject if input plugin isn't available
        if (plugin === -1) {
          reject(new Error(`Input ${attribute.type} not found`));
        }

        // Reject if plugin instance doesn't have a name
        if (!attribute.hasOwnProperty('name')) {
          let identifier = attribute.type;

          if (attribute.hasOwnProperty('id')) {
            identifier = attribute.id;
          }
          reject(new Error(`Input '${identifier}' in content type '${type.name}' needs a name`));
        }

        // Reject if plugin instance doesn't have an ID
        if (!attribute.hasOwnProperty('id')) {
          reject(new Error(`Input '${attribute.name}' in content type '${type.name}' needs an ID`));
        }

        // Reject if plugin instance ID is duplicated
        if (ids.indexOf(attribute.id) !== -1) {
          reject(new Error(`Input ID '${attribute.id}' in content type '${type.name}'' cannot be duplicated`));
        }
        ids.push(attribute.id);

        plugin = plugins[`input-plugin-${attribute.type}`];

        if (attribute.name) {
          plugin.name = attribute.name;
        }
        if (attribute.description) {
          plugin.description = attribute.description;
        }
        plugin.id = attribute.id;
        plugin.type = attribute.type;

        // Set Default Label from Plugin Name
        if (Object.keys(plugin.inputs).length === 1) {
          let input = Object.keys(plugin.inputs)[0];

          plugin.inputs[input].label = plugin.name;
        }

        // Merge Content Type settings with default configuration for each input
        if (attribute.inputs) {
          Object.keys(attribute.inputs).forEach(attr => {
            if (Object.keys(plugin.inputs).indexOf(attr) > -1) {
              // Merge attribute's options with default
              const merged = merge(plugin.inputs[attr], attribute.inputs[attr]);

              // Override any overriding with defaults
              merged.validation = plugin.inputs[attr].validation;
              merged.type = plugin.inputs[attr].type;

              plugin.inputs[attr] = merged;
            }
          });
        }

        // Add Unique ID to each input
        Object.keys(plugin.inputs).forEach(input => {
          plugin.inputs[input].id = uuid.v4();
          plugin.inputs[input].name = `${plugin.id}--${input}`;
        });

        console.log(plugin);
        console.log('---');

        return plugin;
      });

      console.log('=========')
      console.log(attrs);

      mergedType.attributes = attrs;

      return mergedType;
    });

    resolve(configured);
  });
};

module.exports = squish;
<commit_msg>:bug: Fix object reference bug<commit_after>'use strict';

const plugabilly = require('plugabilly');
const merge = require('deepmerge');
const uuid = require('uuid');
const _ = require('lodash');

/**
 * Retrieve Input Plugins
 *
 * {Object} - [Plugabilly](https://github.com/Snugug/plugabilly) object
 */
const plugins = plugabilly().name().containsSync('input-plugin-');

const squish = (types) => {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(types)) {
      reject(new Error('Content types must be an array'));
    }

    const configured = types.map(type => {
      const mergedType = type;
      const ids = [];

      const attrs = mergedType.attributes.map(attribute => {
        let plugin = Object.keys(plugins).indexOf(`input-plugin-${attribute.type}`);

        // Reject if input plugin isn't available
        if (plugin === -1) {
          reject(new Error(`Input ${attribute.type} not found`));
        }

        // Reject if plugin instance doesn't have a name
        if (!attribute.hasOwnProperty('name')) {
          let identifier = attribute.type;

          if (attribute.hasOwnProperty('id')) {
            identifier = attribute.id;
          }
          reject(new Error(`Input '${identifier}' in content type '${type.name}' needs a name`));
        }

        // Reject if plugin instance doesn't have an ID
        if (!attribute.hasOwnProperty('id')) {
          reject(new Error(`Input '${attribute.name}' in content type '${type.name}' needs an ID`));
        }

        // Reject if plugin instance ID is duplicated
        if (ids.indexOf(attribute.id) !== -1) {
          reject(new Error(`Input ID '${attribute.id}' in content type '${type.name}'' cannot be duplicated`));
        }
        ids.push(attribute.id);

        plugin = _.cloneDeep(plugins[`input-plugin-${attribute.type}`]);

        if (attribute.name) {
          plugin.name = attribute.name;
        }
        if (attribute.description) {
          plugin.description = attribute.description;
        }
        plugin.id = attribute.id;
        plugin.type = attribute.type;

        // Set Default Label from Plugin Name
        if (Object.keys(plugin.inputs).length === 1) {
          const input = Object.keys(plugin.inputs)[0];

          plugin.inputs[input].label = plugin.name;
        }

        // Merge Content Type settings with default configuration for each input
        if (attribute.inputs) {
          Object.keys(attribute.inputs).forEach(attr => {
            if (Object.keys(plugin.inputs).indexOf(attr) > -1) {
              // Merge attribute's options with default
              const merged = merge(plugin.inputs[attr], attribute.inputs[attr]);

              // Override any overriding with defaults
              merged.validation = plugin.inputs[attr].validation;
              merged.type = plugin.inputs[attr].type;

              plugin.inputs[attr] = merged;
            }
          });
        }

        // Add Unique ID to each input
        Object.keys(plugin.inputs).forEach(input => {
          plugin.inputs[input].id = uuid.v4();
          plugin.inputs[input].name = `${plugin.id}--${input}`;
        });

        return plugin;
      });

      mergedType.attributes = attrs;

      return mergedType;
    });

    resolve(configured);
  });
};

module.exports = squish;
<|endoftext|>