'use strict';

module.exports = function () {
	var set, iterator, result;
	if (typeof Set !== 'function') return false;
	if (String(Set.prototype) !== '[object Set]') return false;
	set = new Set(['raz', 'dwa', 'trzy']);
	if (set.size !== 3) return false;
	if (typeof set.add !== 'function') return false;
	if (typeof set.clear !== 'function') return false;
	if (typeof set.delete !== 'function') return false;
	if (typeof set.entries !== 'function') return false;
	if (typeof set.forEach !== 'function') return false;
	if (typeof set.has !== 'function') return false;
	if (typeof set.keys !== 'function') return false;
	if (typeof set.values !== 'function') return false;

	iterator = set.values();
	result = iterator.next();
	if (result.done !== false) return false;
	if (result.value !== 'raz') return false;

	if (!set) return false;
	if (set.size !== 3) return false;

	return true;
};

'use strict';

module.exports = function () {
	var set, iterator, result;
	if (typeof Set !== 'function') return false;
	if (String(Set.prototype) !== '[object Set]') return false;
	set = new Set(['raz', 'dwa', 'trzy']);
	if (set.size !== 3) return false;
	if (typeof set.add !== 'function') return false;
	if (typeof set.clear !== 'function') return false;
	if (typeof set.delete !== 'function') return false;
	if (typeof set.entries !== 'function') return false;
	if (typeof set.forEach !== 'function') return false;
	if (typeof set.has !== 'function') return false;
	if (typeof set.keys !== 'function') return false;
	if (typeof set.values !== 'function') return false;

	iterator = set.values();
	result = iterator.next();
	if (result.done !== false) return false;
	if (result.value !== 'raz') return false;

	return true;
};

Remove leftovers after logic cleanup


define([
  'extensions/controllers/module',
  'common/views/visualisations/user-satisfaction',
  'common/collections/list'
],
function (ModuleController, UserSatisfactionView, ListCollection) {
  var UserSatisfactionModule = ModuleController.extend({
    className: function () {
      var classes = this.model.get('classes');

      return ['user_satisfaction'].concat(classes || []).join(' ');
    },
    visualisationClass: UserSatisfactionView,
    collectionClass: ListCollection,
    clientRenderOnInit: true,
    collectionOptions: function () {
      return {
        id: 'user_satisfaction',
        title: 'User satisfaction',
        sortBy: '_timestamp:descending',
        valueAttr: this.model.get("value-attribute")
      };
    }
  });

  return UserSatisfactionModule;
});

define([
  'extensions/controllers/module',
  'common/views/visualisations/user-satisfaction',
  'common/collections/list'
],
function (ModuleController, UserSatisfactionView, ListCollection) {
  var UserSatisfactionModule = ModuleController.extend({
    className: function () {
      var classes = this.model.get('classes');

      return ['user_satisfaction'].concat(classes || []).join(' ');
    },
    visualisationClass: UserSatisfactionView,
    collectionClass: ListCollection,
    clientRenderOnInit: true,
    collectionOptions: function () {
      return {
        id: 'user_satisfaction',
        title: 'User satisfaction',
        sortBy: '_timestamp:ascending',
        limit: 0,
        valueAttr: this.model.get("value-attribute")
      };
    }
  });

  return UserSatisfactionModule;
});

Use correct Backdrop options for user satisfaction.


import { ElasticSearchService } from "../../configs/nuage/elasticsearch/index"
import { VSDService } from "../../configs/nuage/vsd/index"

let config = {
    timingCache: 50000,
}

/*
    Stores all services.
*/
let services = {
    elasticsearch: ElasticSearchService,
    VSD: VSDService,
};

/*
    Registers a new service to a service name
*/
const register = function (service, serviceName) {
    services[serviceName] = service;
}

/*
    Get the service registered for the given name
*/
const getService = function (serviceName) {
    if (!(serviceName in services))
        throw new Error("No service named " + serviceName + " has been registered yet!" );

    return services[serviceName];
}

export const ServiceManager = {
    config: config,
    register: register,
    getService: getService,
}

import { ElasticSearchService } from "../../configs/nuage/elasticsearch/index"
import { VSDService } from "../../configs/nuage/vsd/index"

let config = {
    timingCache: 50000,
}

/*
    Stores all services.
*/
let services = {
    elasticsearch: ElasticSearchService,
    VSD: VSDService,
};

/*
    Registers a new service to a service name
*/
const register = function (service, serviceName) {
    services[serviceName] = service;
}

/*
    Get the service registered for the given name
*/
const getService = function (serviceName) {
    if (!(serviceName in services))
        throw new Error("No service named " + serviceName + " has been registered yet!" );

    return services[serviceName];
}

/*
    Get the service registered for the given name
*/
const getRequestID = function (query, serviceName) {
    const service = getService(serviceName)
    return service.getRequestID(query);
}

export const ServiceManager = {
    config: config,
    register: register,
    getService: getService,
    getRequestID: getRequestID,
}

Enable the serivce manager to call getRequestID on a specific service


import getFormElementType from './getFormElementType';

function selectValues(node) {

    let result = [];
    let index = node.selectedIndex;
    let option;
    let options = node.options;
	let length = options.length;
    let i = index < 0 ? length : 0;

    for (; i < length; i++) {

        option = options[i];
        // IMPORTANT! IE9 doesn't update selected after form reset
        if ((option.selected || i === index) &&
            // Don't return options that are disabled or in a disabled optgroup
            !option.disabled && (!option.parentNode.disabled || option.parentNode.nodeName !== 'OPTGROUP')) {
            result.push(option.value);
        }
    }
    return result.length === 0 ? null : result;
}

export default function getFormElementValues(node) {

    const name = getFormElementType(node);

    switch (name) {
        case 'checkbox':
        case 'radio':
            if (node.checked) {
                return true;
            }
            return false;
        case 'select-multiple':
            return selectValues(node);
        default:
            return node.value;
    }
}

import getFormElementType from './getFormElementType';

function selectValues(node) {

    let result = [];
    let index = node.selectedIndex;
    let option;
    let options = node.options;
	let length = options.length;
    let i = index < 0 ? length : 0;

    for (; i < length; i++) {

        option = options[i];
        // IMPORTANT! IE9 doesn't update selected after form reset
        if ((option.selected || i === index) &&
            // Don't return options that are disabled or in a disabled optgroup
            !option.disabled && (!option.parentNode.disabled || option.parentNode.nodeName !== 'OPTGROUP')) {
            result.push(option.value);
        }
    }
    return result;
}

export default function getFormElementValues(node) {

    const name = getFormElementType(node);

    switch (name) {
        case 'checkbox':
        case 'radio':
            if (node.checked) {
                return true;
            }
            return false;
        case 'select-multiple':
            return selectValues(node);
        default:
            return node.value;
    }
}

Return empty array instead of null here


export default (iconset, name, spinning) => {
  if (iconset === 'fa') {
    switch (name) {
      case 'save':
        name = 'download';
        break;
      case 'zoom-in':
        name = 'search-plus';
        break;
      case 'zoom-out':
        name = 'search-minus';
        break;
      case 'question-sign':
        name = 'question-circle';
        break;
      case 'remove-circle':
        name = 'times-circle-o';
        break;
      case 'new-window':
        name = 'window-restore';
        break;
      case 'move':
        name = 'arrows';
        break;
    }
  }

  return spinning ? `${iconset} ${iconset}-${name} ${iconset}-spin` : `${iconset} ${iconset}-${name}`;
};

export default (iconset, name, spinning) => {
  if (iconset === 'fa') {
    switch (name) {
      case 'save':
        name = 'download';
        break;
      case 'zoom-in':
        name = 'search-plus';
        break;
      case 'zoom-out':
        name = 'search-minus';
        break;
      case 'question-sign':
        name = 'question-circle';
        break;
      case 'remove-circle':
        name = 'times-circle-o';
        break;
      case 'new-window':
        name = 'window-restore';
        break;
      case 'move':
        name = 'arrows';
        break;
      case 'time':
        name = 'clock-o';
        break;
    }
  }

  return spinning ? `${iconset} ${iconset}-${name} ${iconset}-spin` : `${iconset} ${iconset}-${name}`;
};

Fix icon for time component


(function(){
  var window = this;

  // Utility object dom scripting normalization
  var DOM = {
    on: (function() {
      if(window.addEventListener) {
        return function(target, type, listener) {
          target.addEventListener(type, listener, false);
        };
      }
      return function(target, type, listener) {
        target.attachEvent('on' + type, listener);
      };
    })(),

    off: (function() {
      if(window.removeEventListener) {
        return function(target, type, listener) {
          target.removeEventListener(type, listener, false);
        };
      }
      return function(target, type, listener) {
        target.detachEvent('on' + type, listener);
      };
    })(),

    cancelEvent: function(event) {
      if (event.preventDefault) {
        event.preventDefault();
      } else {
        event.returnValue = false;
      }
    }
  };

  var STORAGE_TRANSPORT_EVENT_TYPE = 'message';

  // Storage transport
  var eventHub = StorageMessenger.EventHub.create();

  // Send message using storage transport
  var sendMessage = function(event) {
    var messageInput = document.getElementById('message');
    var message = messageInput.value;

    DOM.cancelEvent(event);

    messageInput.value = '';
    eventHub.trigger(STORAGE_TRANSPORT_EVENT_TYPE, message);
  };

  // Handler for storage transport message events
  var showReceivedMessage = function(params) {
    var value = params || '';
    document.getElementById('received-message').value = value;
  };

  eventHub.on(STORAGE_TRANSPORT_EVENT_TYPE, showReceivedMessage);
  DOM.on(document.getElementById('message-form'), 'submit', sendMessage);
}).call(this);

(function(){
  var window = this;

  // Utility object dom scripting normalization
  var DOM = {
    on: (function() {
      if(window.addEventListener) {
        return function(target, type, listener) {
          target.addEventListener(type, listener, false);
        };
      }
      return function(target, type, listener) {
        target.attachEvent('on' + type, listener);
      };
    })(),

    off: (function() {
      if(window.removeEventListener) {
        return function(target, type, listener) {
          target.removeEventListener(type, listener, false);
        };
      }
      return function(target, type, listener) {
        target.detachEvent('on' + type, listener);
      };
    })(),

    cancelEvent: function(event) {
      if (event.preventDefault) {
        event.preventDefault();
      } else {
        event.returnValue = false;
      }
    }
  };

  var STORAGE_TRANSPORT_EVENT_TYPE = 'message';

  // Storage transport
  var eventHub = StorageMessenger.create();

  // Send message using storage transport
  var sendMessage = function(event) {
    var messageInput = document.getElementById('message');
    var message = messageInput.value;

    DOM.cancelEvent(event);

    messageInput.value = '';
    eventHub.trigger(STORAGE_TRANSPORT_EVENT_TYPE, message);
  };

  // Handler for storage transport message events
  var showReceivedMessage = function(params) {
    var value = params || '';
    document.getElementById('received-message').value = value;
  };

  eventHub.on(STORAGE_TRANSPORT_EVENT_TYPE, showReceivedMessage);
  DOM.on(document.getElementById('message-form'), 'submit', sendMessage);
}).call(this);

Use new API in integration tests.


'use strict'

module.exports = {
  extends: 'stylelint-config-uploadcare',
  rules: {
    'declaration-empty-line-before': ['always', {
      except: [
        'first-nested',
      ],
      ignore: [
        'after-comment',
        'after-declaration',
        'inside-single-line-block',
      ],
    }],
    'number-leading-zero': 'never',
  },
}

'use strict'

module.exports = {
  extends: 'stylelint-config-uploadcare',
  rules: {
    'declaration-empty-line-before': ['always', {
      except: [
        'first-nested',
      ],
      ignore: [
        'after-comment',
        'after-declaration',
        'inside-single-line-block',
      ],
    }],
  },
}

Remove the `number-leading-zero` rule from stylelint config


(function($){
	$.entwine('userswitcher', function($){

		$('form.userswitcher select').entwine({
			onchange : function(){
				this.parents('form:first').submit();
				this._super();
			}
		});

		$('form.userswitcher .Actions').entwine({
			onmatch : function(){
				this.hide();
				this._super();
			}
		});

		$('body').entwine({
			onmatch : function(){
				var base = $('base').prop('href'),
					isCMS = this.hasClass('cms') ? 1 : '';

				$.get(base + 'userswitcher/UserSwitcherFormHTML/', {userswitchercms: isCMS}).done(function(data){
					var body = $('body');

					if(body.hasClass('cms')){
						$('.cms-login-status').append(data);
					}else{
						$('body').append(data);
					}

				});
				this._super();
			}
		});

	});
})(jQuery);

(function($){
	function loadSwitcher() {
		var base = $('base').prop('href');
		var isCMS = $('body').hasClass('cms') ? 1 : 0;

		$.get(base + 'userswitcher/UserSwitcherFormHTML/', {userswitchercms: isCMS}).done(function(data){
			var $data = $(data);
			if (!$data.length) {
				return;
			}
			// Submit form on change
			$data.find('select').on('change', function() {
				$(this.form).submit();
			});
			// Hide submit button
			$data.find('.Actions').hide();

			if($('body').hasClass('cms')){
				$('.cms-login-status').append($data);
			}else{
				$('body').append($data);
			}
		});
	}

	function main() {
		var isCMS = $('body').hasClass('cms') ? 1 : 0;
		if (!$.entwine || !isCMS) {
			loadSwitcher();
		} else {
			$.entwine('userswitcher', function($){
				$('body').entwine({
					onmatch : function(){
						loadSwitcher();
						this._super();
					},
					onunmatch: function() {
						this._super();
					}
				});
			});
		}
	}
	main();
})(jQuery);

Stop most of the logic from using entwine


const React = require('react')
const propTypes = require('prop-types')

class SelectLanguage extends React.Component {
  render () {
    const languages = ['All', 'Javascript', 'Python', 'clojure', 'Haskell']
    return (

        {languages.map(function (lang) {
          return (

              {lang}

)
        }, this)}

    )
  }
}

SelectLanguage.propTypes = {
  selectedLanguage: propTypes.string.isRequired,
  onSelect: propTypes.func.isRequired
}
class Popular extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
      selectedLanguage: 'All'
    }
    this.updateLanguage = this.updateLanguage.bind(this)
  }
  updateLanguage (lang) {
    this.setState(function () {
      return {
        selectedLanguage: lang
      }
    })
  }

  render () {
    return (

        <SelectLanguage
          selectedLanguage={this.state.selectedLanguage}
          onSelect={this.updateLanguage} />

    )
  }
}

module.exports = Popular

const React = require('react')
const propTypes = require('prop-types')

function SelectLanguage (props) {
  const languages = ['All', 'Javascript', 'Python', 'clojure', 'Haskell']
  return (

      {languages.map(function (lang) {
        return (

            {lang}

)
      }, this)}

  )
}

SelectLanguage.propTypes = {
  selectedLanguage: propTypes.string.isRequired,
  onSelect: propTypes.func.isRequired
}

class Popular extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
      selectedLanguage: 'All'
    }
    this.updateLanguage = this.updateLanguage.bind(this)
  }
  updateLanguage (lang) {
    this.setState(function () {
      return {
        selectedLanguage: lang
      }
    })
  }

  render () {
    return (

        <SelectLanguage
          selectedLanguage={this.state.selectedLanguage}
          onSelect={this.updateLanguage} />

    )
  }
}

module.exports = Popular

Modify SelectLanguage to function declaration


var _ = require('lodash')
var fs = require('fs')
var gzip = require('gzip-size')
var filesize = require('filesize')
var cssstats = require('cssstats')

var module = require('tachyons-box-sizing/package.json')
var moduleCss = fs.readFileSync('node_modules/tachyons-box-sizing/tachyons-box-sizing.min.css', 'utf8')
var moduleObj = cssstats(moduleCss)
var moduleSize = filesize(moduleObj.gzipSize)

var srcCSS = fs.readFileSync('./src/_box-sizing.css', 'utf8')
var navDocs = fs.readFileSync('./templates/nav_docs.html', 'utf8')

var template = fs.readFileSync('./templates/docs/box-sizing/index.html', 'utf8')
var tpl = _.template(template)
var html = tpl({
  moduleVersion: module.version,
  moduleSize: moduleSize,
  moduleObj: moduleObj,
  srcCSS: srcCSS,
  navDocs: navDocs
})

fs.writeFileSync('./docs/layout/box-sizing/index.html', html)

var _ = require('lodash')
var fs = require('fs')
var gzip = require('gzip-size')
var filesize = require('filesize')
var cssstats = require('cssstats')

var module = require('tachyons-box-sizing/package.json')
var moduleCss = fs.readFileSync('node_modules/tachyons-box-sizing/tachyons-box-sizing.min.css', 'utf8')
var moduleObj = cssstats(moduleCss)
var moduleSize = filesize(moduleObj.gzipSize)

var srcCSS = fs.readFileSync('./src/_box-sizing.css', 'utf8')
var navDocs = fs.readFileSync('./templates/nav_docs.html', 'utf8')
var siteFooter = fs.readFileSync('./templates/footer.html', 'utf8')

var template = fs.readFileSync('./templates/docs/box-sizing/index.html', 'utf8')
var tpl = _.template(template)
var html = tpl({
  moduleVersion: module.version,
  moduleSize: moduleSize,
  moduleObj: moduleObj,
  srcCSS: srcCSS,
  navDocs: navDocs,
  siteFooter: siteFooter
})

fs.writeFileSync('./docs/layout/box-sizing/index.html', html)

Add site footer to each documentation generator


// ==========================================================================
//                            DG.MapModel
//
//  Author:   William Finzer
//
//  Copyright ©2014 Concord Consortium
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
// ==========================================================================

/** @class  DG.MapConstants

 */
DG.MapConstants =
    {
      kLatNames: ['latitude', 'lat', 'latitud', 'breitengrad', '緯度'],
      kLongNames: ['longitude', 'long', 'lng', 'lon', 'longitud', 'längengrad', '経度'],
      kPolygonNames: ['boundary', 'boundaries', 'polygon', 'polygons', 'grenze', '境界']
    };

// ==========================================================================
//                            DG.MapModel
//
//  Author:   William Finzer
//
//  Copyright ©2014 Concord Consortium
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
// ==========================================================================

/** @class  DG.MapConstants

 */
DG.MapConstants =
    {
      kLatNames: ['latitude', 'lat', 'latitud', 'breitengrad', '緯度','عرض جغرافیایی'],
      kLongNames: ['longitude', 'long', 'lng', 'lon', 'longitud', 'längengrad', '経度', 'طول جغرافیایی'],
      kPolygonNames: ['boundary', 'boundaries', 'polygon', 'polygons', 'grenze', '境界','مرز']
    };

Add Persian terms for 'latitude', 'longitude' and 'boundary'


function startModule() {
  // Get locale from phonegap
  var globalization = navigator.globalization;

  if (globalization) {
    navigator.globalization.getLocaleName(
        function (locale) {
          setLocale(locale.value);
        },
        function () {
          console.log("Failed to get locale from phonegap. Using default.");
          setLocale();
        }
    );
  }
  else {
    setLocale();
  }

  var appView = new AppView({ el:$('#page-map') });
  appView.render();

  $('#page-map').trigger("pagecreate");
  document.addEventListener("searchbutton", function () {
    this.openSearchPopup(null);
  }, false);
}

function setLocale(locale) {
  var options = {
    useCookie:false,
    fallbackLng:'en',
    resGetPath:'locales/__lng__.json',
    getAsync:false
  };

  if (locale) {
    options.locale = locale;
  }

  i18n.init(options);
}

function startModule() {
  // Get locale from phonegap
  var globalization = navigator.globalization;

  if (globalization) {
    globalization.getLocaleName(
        function (locale) {
          setLocale(locale.value);
        },
        function () {
          console.log("Failed to get locale from phonegap. Using default.");
          setLocale();
        }
    );
  }
  else {
    setLocale();
  }

  var appView = new AppView({ el: $('#page-map') });
  appView.render();

  $('#page-map').trigger("pagecreate");
  document.addEventListener("searchbutton", function () {
    this.openSearchPopup(null);
  }, false);
}

function setLocale(locale) {
  var options = {
    useCookie: false,
    fallbackLng: 'en',
    resGetPath: 'locales/__lng__.json',
    getAsync: false
  };

  if (locale) {
    options.locale = locale;
  }

  i18n.init(options);
}

Use the globalization object we already collected.


module.exports = {
  navigateFallback: '/index.html',
  stripPrefix: 'dist',
  root: 'dist/',
  staticFileGlobs: [
    'dist/index.html',
    'dist/**.js',
    'dist/assets/**.*',
    'dist/**.css'
  ],
  runtimeCaching: [{
    urlPattern: '*',
    handler: 'networkFirst'
  }]
};

// Cache external https://stackoverflow.com/questions/40587733/caching-external-resources-with-sw-precache
module.exports = {
  navigateFallback: '/index.html',
  stripPrefix: 'dist',
  root: 'dist/',
  staticFileGlobs: [
    'dist/index.html',
    'dist/**.js',
    'dist/assets/**.*',
    'dist/**.css'
  ],
  runtimeCaching: [{
    urlPattern: /^https:\/\/api\.wickeyappstore\.com\//,
    handler: 'networkFirst'
  }, {
    urlPattern: /^https:\/\/maxcdn\.bootstrapcdn\.com\//,
    handler: 'cacheFirst'
  }, {
    urlPattern: /^https:\/\/cdnjs\.cloudflare\.com\/ajax\/libs\/fastclick\/1\.0\.6\/fastclick\.min\.js/,
    handler: 'cacheFirst'
  }, {
    urlPattern: /^https:\/\/unpkg\.com\/wickeyappstore@0\.5\.2\/styles\.css/,
    handler: 'cacheFirst'
  }],
  navigateFallback: '/index.html',
};

Add correct runtimeCaching urls (external files like bootstrap)


export const GAUGE_COLORS = [
  {
    hex: '#BF3D5E',
    text: 'Ruby',
  },
  {
    hex: '#DC4E58',
    text: 'Fire',
  },
  {
    hex: '#DC4E58',
    text: 'Fire',
  },
  {
    hex: '#F95F53',
    text: 'Curacao',
  },
  {
    hex: '#F48D38',
    text: 'Tiger',
  },
  {
    hex: '#FFB94A',
    text: 'Pineapple',
  },
  {
    hex: '#FFD255',
    text: 'Thunder',
  },
  {
    hex: '#7CE490',
    text: 'Honeydew',
  },
  {
    hex: '#4ED8A0',
    text: 'Rainforest',
  },
  {
    hex: '#32B08C',
    text: 'Viridian',
  },
  {
    hex: '#4591ED',
    text: 'Ocean',
  },
  {
    hex: '#22ADF6',
    text: 'Pool',
  },
  {
    hex: '#00C9FF',
    text: 'Laser',
  },
  {
    hex: '#513CC6',
    text: 'Planet',
  },
  {
    hex: '#7A65F2',
    text: 'Star',
  },
  {
    hex: '#9394FF',
    text: 'Comet',
  },
]

export const GAUGE_COLORS = [
  {
    hex: '#BF3D5E',
    text: 'Ruby',
  },
  {
    hex: '#DC4E58',
    text: 'Fire',
  },
  {
    hex: '#F95F53',
    text: 'Curacao',
  },
  {
    hex: '#F48D38',
    text: 'Tiger',
  },
  {
    hex: '#FFB94A',
    text: 'Pineapple',
  },
  {
    hex: '#FFD255',
    text: 'Thunder',
  },
  {
    hex: '#7CE490',
    text: 'Honeydew',
  },
  {
    hex: '#4ED8A0',
    text: 'Rainforest',
  },
  {
    hex: '#32B08C',
    text: 'Viridian',
  },
  {
    hex: '#4591ED',
    text: 'Ocean',
  },
  {
    hex: '#22ADF6',
    text: 'Pool',
  },
  {
    hex: '#00C9FF',
    text: 'Laser',
  },
  {
    hex: '#513CC6',
    text: 'Planet',
  },
  {
    hex: '#7A65F2',
    text: 'Star',
  },
  {
    hex: '#9394FF',
    text: 'Comet',
  },
]

Remove redundant color from gauge colors list


/**
 * Module dependencies.
 */
var _ = require('@sailshq/lodash');

/**
 * Sails.prototype.getActions()
 *
 * Return a shallow clone of the loaded actions dictionary.
 *
 * @returns {dictionary} A shallow clone of sails._actions
 *
 * @api public
 */
module.exports = function getActions() {

  return _.clone(this._actions);

};

/**
 * Module dependencies.
 */

var _ = require('@sailshq/lodash');

/**
 * Sails.prototype.getActions()
 *
 * Return a shallow clone of the loaded actions dictionary.
 *
 * @returns {Dictionary} A shallow clone of all actions, indexed by their unique identifier.
 *
 * @this {SailsApp}
 * ----------------------------------------------------------------------------------------
 *
 * Usage:
 *
 * ```
 * sails.getActions();
 * // =>
 * // {
 * //   'duck/quack': {...},
 * //   // ...
 * // }
 * ```
 */
module.exports = function getActions() {

  return _.clone(this._actions);

};

Add usage example for getActions()


var Model = require('ampersand-model');

module.exports = Model.extend({
  props: {
    id: 'any',
    name: ['string', true, ''],
    description: ['string', true, ''],
    latlng: ['array', true, undefined],
  },

  parse: function(resp) {
    return {
      name: resp['Project Name'],
      description: resp['Project Description'],
      latlng: [33.848481 + (Math.random() - 1) * 0.1, -84.388375 + (Math.random() - 1) * 0.1],
    };
  }
});

var Model = require('ampersand-model');

module.exports = Model.extend({
  props: {
    id: 'any',
    name: ['string', true, ''],
    description: ['string', true, ''],
    category: ['string', true, ''],
    npus: ['array', true, undefined],
    neighborhoods: ['array', true, undefined],
    latlng: ['array', true, undefined],
  },

  parse: function(resp) {
    var npus = [];
    var neighborhoods = [];

    for (var i = 1; i <= 5; i++) {
      if (resp['NPU ' + i]) npus.push(resp['NPU ' + i]);
      if (resp['Neighborhood ' + i]) neighborhoods.push(resp['Neighborhood ' + i]);
    }

    return {
      name: resp['Project Name'],
      description: resp['Project Description'],
      category: resp['Category'],
      npus: npus,
      neighborhoods: neighborhoods,
      latlng: [33.848481 + (Math.random() - 1) * 0.1, -84.388375 + (Math.random() - 1) * 0.1],
    };
  }
});

Add neighborhoods, NPUs, and categories.


// Define an amd module called "-jquery"
// which exposes jQuery bundeled with ember-cli-page-object.
//
// This mode is used when we deal with `jquery`-less apps.
(function() {
  // prevent jquery provided by ember-cli-page-object
  // to be set to `Ember.$` by Ember.
  var jq = self['$'].noConflict();
  delete self['jQuery'];

  function vendorModule() {
    'use strict';

    if (!jq) {
      throw new Error('Unable to find jQuery');
    }

    return { 'default': jq };
  }

  define('-jquery', [], vendorModule);
})();

// Define an amd module called "-jquery".
// It expects that we have already imported our own version of jQuery
// which is going to be used in ember-cli-page-oject internally.
//
// This mode is used when we deal with `jquery`-less apps.
(function() {
  // prevent jquery provided by ember-cli-page-object
  // to be set to `Ember.$` by Ember.
  var jq = self['$'].noConflict();
  delete self['jQuery'];

  function vendorModule() {
    'use strict';

    if (!jq) {
      throw new Error('Unable to find jQuery');
    }

    return { 'default': jq };
  }

  define('-jquery', [], vendorModule);
})();

Clarify relation between shim and bundled jquery


import $ from 'jquery';
import Modernizr from 'modernizr';

const utils = {};

let running = false;

(function wait () {
  if (window.requestAnimationFrame) return window.requestAnimationFrame;

  return function (cb) {
    window.setTimeout(cb, 100);
  };
})();

utils.replaceSVG = function () {
  // If SVG is not supported replace it with png version
  if (!Modernizr.svg) {
    $('img[src*="svg"]').attr('src', () => {
      return $(this).attr('src').replace('.svg', '.png');
    });
  }
};

utils.throttle = function (cb) {
  return () => {
    if (running) return;
    running = true;

    wait(() => {
      cb.apply();
      running = false;
    });
  };
}

export default utils;

import $ from 'jquery';
import Modernizr from 'modernizr';

const utils = {};

let running = false;

window.raf = (function () {
  if (window.requestAnimationFrame) return window.requestAnimationFrame;

  return function (cb) {
    window.setTimeout(cb, 100);
  };
})();

utils.replaceSVG = function () {
  // If SVG is not supported replace it with png version
  if (!Modernizr.svg) {
    $('img[src*="svg"]').attr('src', () => {
      return $(this).attr('src').replace('.svg', '.png');
    });
  }
};

utils.throttle = function (cb) {
  return () => {
    if (running) return;
    running = true;

    window.raf(() => {
      cb.apply();
      running = false;
    });
  };
};

export default utils;

Attach wait function to window


const _ = require('lodash');

const parseMultipartBody = require('../../utils/parse-multipart');
const uploadFiles = require('../../utils/upload-files');

module.exports = async (ctx, next) => {
  const { model } = ctx.params;

  const ct = strapi.contentTypes[model];

  const target =
    ct.plugin === 'admin' ? strapi.admin : strapi.plugins[ct.plugin];

  const actionPath = [
    'config',
    'layout',
    ct.modelName,
    'actions',
    ctx.request.route.action,
  ];

  if (_.has(target, actionPath)) {
    const [controller, action] = _.get(target, actionPath, []).split('.');

    if (controller && action) {
      if (ctx.is('multipart')) {
        const { data, files } = parseMultipartBody(ctx);
        ctx.request.body = data;
        ctx.request.files = {};

        await target.controllers[controller.toLowerCase()][action](ctx);
        const resBody = ctx.body;

        if (ctx.status >= 300) return;

        await uploadFiles(resBody, files, {
          model: ct.modelName,
          source: ct.plugin,
        });

        return ctx.send(resBody);
      }

      return await target.controllers[controller.toLowerCase()][action](ctx);
    }
  }

  await next();
};

const _ = require('lodash');

const parseMultipartBody = require('../../utils/parse-multipart');
const uploadFiles = require('../../utils/upload-files');

module.exports = async (ctx, next) => {
  const { model } = ctx.params;

  const ct = strapi.contentTypes[model];

  if (!ct) {
    return ctx.send({ error: 'contentType.notFound' }, 404);
  }

  const target =
    ct.plugin === 'admin' ? strapi.admin : strapi.plugins[ct.plugin];

  const actionPath = [
    'config',
    'layout',
    ct.modelName,
    'actions',
    ctx.request.route.action,
  ];

  if (_.has(target, actionPath)) {
    const [controller, action] = _.get(target, actionPath, []).split('.');

    if (controller && action) {
      if (ctx.is('multipart')) {
        const { data, files } = parseMultipartBody(ctx);
        ctx.request.body = data;
        ctx.request.files = {};

        await target.controllers[controller.toLowerCase()][action](ctx);
        const resBody = ctx.body;

        if (ctx.status >= 300) return;

        await uploadFiles(resBody, files, {
          model: ct.modelName,
          source: ct.plugin,
        });

        return ctx.send(resBody);
      }

      return await target.controllers[controller.toLowerCase()][action](ctx);
    }
  }

  await next();
};

Fix not found content type in content manager routes


'use strict'

let _ = require('underscore')
let mongoose = require('mongoose')
let winston = require('winston')

let Rat = require('../api/models/rat')
let Rescue = require('../api/models/rescue')

mongoose.connect('mongodb://localhost/fuelrats')

console.log('Loading rescues...')

Rescue.find({
  firstLimpet: {
    $exists: false
  },
  rats: {
    $exists: true
  }
})
.exec()
.then(function (rescues) {
  let saves = []

  console.log('Updating rescues...')

  rescues.forEach(function (rescue, index) {
    console.log(rescue)
    if (rescue.rats.length) {
      rescue.firstLimpet = rescue.rats.shift()

      saves.push(rescue.save())
    }
  })

  Promise.all(saves)
  .then(function (results) {
    console.log('rescues', rescues.length)
    console.log('results', results.length)
    console.log('Done')
//    mongoose.disconnect()
  })
  .catch(function (error) {
    console.error(error)
    mongoose.disconnect()
  })
})

'use strict'

let _ = require('underscore')
let mongoose = require('mongoose')
let winston = require('winston')

let Rat = require('../api/models/rat')
let Rescue = require('../api/models/rescue')

mongoose.connect('mongodb://localhost/fuelrats')

console.log('Loading rescues...')

Rescue.find({
  firstLimpet: {
    $exists: false
  },
  rats: {
    $exists: true
  }
})
.exec()
.then(function (rescues) {
  let saves = []

  console.log('Updating rescues...')

  rescues.forEach(function (rescue, index) {
    if (rescue.rats.length) {
      saves.push(Rescue.findByIdAndUpdate(rescue._id, {
        $set: {
          firstLimpet: rescue.rats.shift(),
          rats: rescue.rats
        }
      }))
    }
  })

  Promise.all(saves)
  .then(function (results) {
    console.log('Updated ' + results.length + ' rescues')
    mongoose.disconnect()
  })
  .catch(function (error) {
    console.error(error)
    mongoose.disconnect()
  })
})

Use updates instead of saves which magically makes the script more reliable


angular.module('eee-c.angularBindPolymer', []).
directive('bindPolymer', function() {
  return {
    restrict: 'A',
    link: function(scope, element, attrs) {
      var attrMap = {};
      for (var prop in attrs.$attr) {
        if (prop != 'bindPolymer') {
          var _attr = attrs.$attr[prop];
          var _match = element.attr(_attr).match(/\{\{\s*([\.\w]+)\s*\}\}/);
          if (_match) {
            attrMap[_attr] = _match[1];
          }
        }
      }

      // When Polymer sees a change to the bound variable,
      // $apply / $digest the changes here in Angular
      new MutationObserver(function() { scope.$apply(); }).
        observe(element[0], {attributes: true});

      for (var _attr in attrMap) { watch (_attr); }

      function watch(attr) {
        scope.$watch(
          function() { return element.attr(attr); },
          function(value) {
            var tokens = attrMap[attr].split(/\./);
            var parent = scope;
            for (var i=0; i<tokens.length-1; i++) {
              if (typeof(parent[tokens[i]]) == 'undefined') {
                parent[tokens[i]] = {};
              }
              parent = parent[tokens[i]];
            }
            parent[tokens[tokens.length - 1]] = value;
          }
        );
      }
    }
  };
});

angular.module('eee-c.angularBindPolymer', []).
directive('bindPolymer', function() {
  'use strict';
  return {
    restrict: 'A',
    link: function(scope, element, attrs) {
      var attrMap = {};
      for (var prop in attrs.$attr) {
        if (prop != 'bindPolymer') {
          var _attr = attrs.$attr[prop];
          var _match = element.attr(_attr).match(/\{\{\s*([\.\w]+)\s*\}\}/);
          if (_match) {
            attrMap[_attr] = _match[1];
          }
        }
      }

      // When Polymer sees a change to the bound variable,
      // $apply / $digest the changes here in Angular
      var observer = new MutationObserver(function(){ scope.$apply(); });
      observer.observe(element[0], {attributes: true});
      scope.$on('$destroy', function(){ observer.disconnect(); });

      for (var _attr in attrMap) { watch (_attr); }

      function watch(attr) {
        scope.$watch(
          function() { return element.attr(attr); },
          function(value) {
            var tokens = attrMap[attr].split(/\./);
            var parent = scope;
            for (var i=0; i<tokens.length-1; i++) {
              if (typeof(parent[tokens[i]]) == 'undefined') {
                parent[tokens[i]] = {};
              }
              parent = parent[tokens[i]];
            }
            parent[tokens[tokens.length - 1]] = value;
          }
        );
      }
    }
  };
});

Remove the observer on $destroy


import React from 'react';
import { TacoTable, DataType, SortDirection, Formatters } from 'react-taco-table';
import cellLinesData from '../data/cell_lines.json';

/**
 * An example demonstrating various formatters
 */

const columns = [
  {
    id: 'name',
    type: DataType.String,
    value: rowData => rowData.cellLine.label,
    header: 'String',
  },
  {
    id: 'value',
    type: DataType.Number,
    header: 'First sort descending',
    renderer: Formatters.decFormat(1),
    firstSortDirection: SortDirection.Descending,
  },
  {
    id: 'level-str',
    type: DataType.String,
    value: rowData => rowData.level,
    header: 'Number as string',
    firstSortDirection: SortDirection.Descending,
  },
  {
    id: 'level-num',
    type: DataType.Number,
    value: rowData => rowData.level,
    header: 'Number as number',
    firstSortDirection: SortDirection.Descending,
  },
  {
    id: 'name-length',
    type: DataType.String,
    sortType: DataType.Number,
    value: rowData => rowData.cellLine.label,
    sortValue: cellData => cellData.length,
    header: 'String as number',
  },
];

class ExampleFormatters extends React.Component {
  render() {
    return (
      <TacoTable
        columns={columns}
        data={cellLinesData}
        initialSortDirection={SortDirection.Ascending}
        initialSortColumnId={'name'}
      />
    );
  }
}

export default ExampleFormatters;

import React from 'react';
import { TacoTable, DataType, SortDirection, Formatters } from 'react-taco-table';
import cellLinesData from '../data/cell_lines.json';

/**
 * An example demonstrating sort configurations
 */

const columns = [
  {
    id: 'name',
    type: DataType.String,
    value: rowData => rowData.cellLine.label,
    header: 'String',
  },
  {
    id: 'value',
    type: DataType.Number,
    header: 'First sort descending',
    renderer: Formatters.decFormat(1),
    firstSortDirection: SortDirection.Descending,
  },
  {
    id: 'level-str',
    type: DataType.String,
    value: rowData => rowData.level,
    header: 'Number as string',
    firstSortDirection: SortDirection.Descending,
  },
  {
    id: 'level-num',
    type: DataType.Number,
    value: rowData => rowData.level,
    header: 'Number as number',
    firstSortDirection: SortDirection.Descending,
  },
  {
    id: 'name-length',
    type: DataType.String,
    sortType: DataType.Number,
    value: rowData => rowData.cellLine.label,
    sortValue: cellData => cellData.length,
    header: 'String as number',
  },
];

class ExampleFormatters extends React.Component {
  render() {
    return (
      <TacoTable
        columns={columns}
        data={cellLinesData}
        initialSortDirection={SortDirection.Ascending}
        initialSortColumnId={'name'}
      />
    );
  }
}

export default ExampleFormatters;

Fix comment in sort example


'use strict';

/**
 * @ngdoc function
 * @name cnodejs.filters:tabName
 * @description
 * # tabName
 * tab name filter of the cnodejs app
 */

angular.module('cnodejs.filters')
.filter('link', function($sce) {
  return function(content) {
    if (typeof content === 'string') {
      var userLinkRegex = /href="\/user\/([\S]+)"/g;
      var externalLinkRegex = /href="((?!#\/user\/)[\S]+)"/g;
      return $sce.trustAsHtml(
        content
        .replace(userLinkRegex, 'href="#/user/$1"')
        .replace(externalLinkRegex, "onClick=\"window.open('$1', '_blank', 'location=yes')\"")
      );
    }
    return content;
  };
})
.filter('tabName', function(Tabs) {
  return function(tab) {
    for (var i in Tabs) {
      if (Tabs[i].value === tab) {
        return Tabs[i].label;
      }
    }
  };
});

'use strict';

/**
 * @ngdoc function
 * @name cnodejs.filters:tabName
 * @description
 * # tabName
 * tab name filter of the cnodejs app
 */

angular.module('cnodejs.filters')
.filter('link', function($sce) {
  return function(content) {
    if (typeof content === 'string') {
      var userLinkRegex = /href="\/user\/([\S]+)"/gi;
      var noProtocolSrcRegex = /src="\/\/([\S]+)"/gi;
      var externalLinkRegex = /href="((?!#\/user\/)[\S]+)"/gi;
      return $sce.trustAsHtml(
        content
        .replace(userLinkRegex, 'href="#/user/$1"')
        .replace(noProtocolSrcRegex, 'src="https://$1"')
        .replace(externalLinkRegex, "onClick=\"window.open('$1', '_blank', 'location=yes')\"")
      );
    }
    return content;
  };
})
.filter('tabName', function(Tabs) {
  return function(tab) {
    for (var i in Tabs) {
      if (Tabs[i].value === tab) {
        return Tabs[i].label;
      }
    }
  };
});

Add protocol for src which has no protocol


import _ from 'lodash';
import * as d3Selection from 'd3-selection';
import DonutChart from 'britecharts/dist/umd/donut.min';

export default {
  template: '

',

  props: {
    initDataset: {
      type: String,
      required: true
    }
  },

  data() {
    return {
      dataset: _.sortBy(JSON.parse(this.initDataset), data => data.name_key)
    };
  },

  mounted() {
    const container = d3Selection.select('.c-episode-rating-state-chart');
    const containerWidth = container.node()
      ? container.node().getBoundingClientRect().width
      : false;

    const donutChart = new DonutChart();

    if (containerWidth) {
      const colors = ['#FFAB40', '#bdbdbd', '#69F0AE', '#40C4FF'];
      const removedColors = [];

      // Remove colors which corresponded to status if its quantity is zero.
      _.forEach(this.dataset, function(data, i) {
        if (data.quantity === 0) {
          return removedColors.push(colors[i]);
        }
      });

      donutChart
        .width(containerWidth)
        .height(containerWidth - 35)
        .externalRadius(containerWidth / 2.5)
        .internalRadius(containerWidth / 5)
        .colorSchema(_.difference(colors, removedColors));

      return container.datum(this.dataset).call(donutChart);
    }
  }
};

import _ from 'lodash'
import * as d3Selection from 'd3-selection'
import DonutChart from 'britecharts/dist/umd/donut.min'

export default {
  template: '

',

  props: {
    initDataset: {
      type: String,
      required: true,
    },
  },

  data() {
    return {
      dataset: _.sortBy(JSON.parse(this.initDataset), data => data.name_key),
    }
  },

  mounted() {
    const container = d3Selection.select('.c-episode-rating-state-chart')
    const containerWidth = container.node() ? container.node().getBoundingClientRect().width : false

    const donutChart = new DonutChart()

    if (containerWidth) {
      const colors = ['#FFAB40', '#bdbdbd', '#69F0AE', '#40C4FF']

      donutChart
        .width(containerWidth)
        .height(containerWidth - 35)
        .externalRadius(containerWidth / 2.5)
        .internalRadius(containerWidth / 5)
        .colorSchema(colors)

      return container.datum(this.dataset).call(donutChart)
    }
  },
}

Fix sort of state chart


'use strict';

// Development specific configuration
// ==================================
module.exports = {

  ip: '127.0.0.1',

  httpsPort: 4343,
  httpPort: 8085,

  url: 'https://localhost:4343',

  // CWL
  callbackUrl: 'https://localhost:4343/auth/cwl/login/callback',
  entryPoint: 'https://authentication.stg.id.ubc.ca/idp/profile/SAML2/Redirect/SSO',
  idpCert: path.join(__dirname, '/../../..', 'server/cert/idp_cert-staging.pem'),

  // MongoDB connection options
  mongo: {
    uri: 'mongodb://localhost/its110-dev'
  },

  seedDB: true
};

'use strict';

var path = require('path');

// Development specific configuration
// ==================================
module.exports = {

  ip: '127.0.0.1',

  httpsPort: 4343,
  httpPort: 8080,

  url: 'https://localhost:4343',

  // CWL
  callbackUrl: 'https://127.0.0.1:4343/auth/cwl/login/callback',
  entryPoint: 'https://authentication.stg.id.ubc.ca/idp/profile/SAML2/Redirect/SSO',
  idpCert: path.join(__dirname, '/../../..', 'server/cert/idp_cert-staging.pem'),

  // MongoDB connection options
  mongo: {
    uri: 'mongodb://localhost/its110-dev'
  },

  seedDB: true
};

Fix CWL staging callback url


var icalendar = require('icalendar');
var _ = require('underscore');

exports.generateIcal = function(currentUser, boards, params) {
    var ical = new icalendar.iCalendar();
    boards.each(function(board) {
        board.cards().each(function(card) {
            // no arm, no chocolate
            if (!card.get('badges').due) return;
            if (params &&
                params.only_me == 'true' &&
                !_(card.get('idMembers')).include(currentUser.id)) {
                return;
            }
            var event = new icalendar.VEvent(card.id);
            event.setSummary(card.get('name'));
            event.setDescription(card.get('desc'));
            event.setDate(card.get('badges').due, 60*60);
            event.addProperty('ATTACH', card.get('url'));
            event.addProperty('URL', card.get('url'));
            if (params && params.alarm != '-1') {
                comp = event.addComponent('VALARM');
                comp.addProperty('ACTION', 'DISPLAY');
                comp.addProperty('DESCRIPTION', 'Generated by trello calendar');
                comp.addProperty('TRIGGER', params.alarm);
            }
            ical.addComponent(event);
        });
    });
    return ical;
}

var icalendar = require('icalendar');
var _ = require('underscore');

exports.generateIcal = function(currentUser, boards, params) {
    var ical = new icalendar.iCalendar();
    boards.each(function(board) {
        board.cards().each(function(card) {
            // no arm, no chocolate
            if (!card.get('badges').due) return;
            if (params &&
                params.only_me == 'true' &&
                !_(card.get('idMembers')).include(currentUser.id)) {
                return;
            }
            var description = card.get('desc');
            description += "\n\n"+ card.get('url');
            var event = new icalendar.VEvent(card.id);
            event.setSummary(card.get('name'));
            event.setDescription(description);
            event.setDate(card.get('badges').due, 60*60);
            event.addProperty('ATTACH', card.get('url'));
            event.addProperty('URL', card.get('url'));
            if (params && params.alarm != '-1') {
                comp = event.addComponent('VALARM');
                comp.addProperty('ACTION', 'DISPLAY');
                comp.addProperty('DESCRIPTION', 'Generated by trello calendar');
                comp.addProperty('TRIGGER', params.alarm);
            }
            ical.addComponent(event);
        });
    });
    return ical;
}

Add url to the description field.


var mssql = require("mssql");

var database = {
  query: function(connection, query, callback) {
    mssql.connect(connection, function(connectionError) {
      if(connectionError) {
        mssql.close();
        callback(connectionError, []);
      } else {

        var request = new mssql.Request();

        // Attach the input params to the request
        var values = query.values || [];
        for(var i = 0; i !== values.length; i++) {
          request.input((i + 1).toString(), query.values[i]);
        }

        request.query(query.text, function(queryError, rows) {
          callback(queryError, rows || []);
        });
      }
    });

    mssql.on('error', callback);
  },
  execute: function(connection, name, parameters, callback) {
    mssql.connect(connection, function(connectionError) {
      if(connectionError) {
        mssql.close();
        callback(connectionError, []);
      } else {
        var request = new mssql.Request();

        // Attach input params to the request
        var params = parameters || {};
        var keys = Object.keys(params);
        for(var i = 0; i !== keys.length; i++) {
          var key = keys[i];
          request.input(key, params[key]);
        }

        request.execute(name, function(error, recordsets) {
          callback(error, recordsets || []);
        });
      }
    });

    mssql.on('error', callback);
  }
};

module.exports = database;

var mssql = require("mssql");

var database = {
  query: function(connectionString, query, callback) {
    var connection = new mssql.Connection(connectionString, function(connectionError) {
      if(connectionError) {
        mssql.close();
        callback(connectionError, []);
      } else {

        var request = new mssql.Request(connection);

        // Attach the input params to the request
        var values = query.values || [];
        for(var i = 0; i !== values.length; i++) {
          request.input((i + 1).toString(), query.values[i]);
        }

        request.query(query.text, function(queryError, rows) {
          callback(queryError, rows || []);
        });
      }
    });

    mssql.on('error', callback);
  },
  execute: function(connectionString, name, parameters, callback) {
    var connection = new mssql.Connection(connectionString, function(connectionError) {
      if(connectionError) {
        mssql.close();
        callback(connectionError, []);
      } else {
        var request = new mssql.Request(connection);

        // Attach input params to the request
        var params = parameters || {};
        var keys = Object.keys(params);
        for(var i = 0; i !== keys.length; i++) {
          var key = keys[i];
          request.input(key, params[key]);
        }

        request.execute(name, function(error, recordsets) {
          callback(error, recordsets || []);
        });
      }
    });

    mssql.on('error', callback);
  }
};

module.exports = database;

Make a new connection per request


var convert = require('convert');
var parse = require('color-parser');

exports.segments = {
  stroke: function(display, segment) {
    if (!segment.focused) return;

    switch (segment.type) {
      case 'CAR':
        return '#888';
      case 'WALK':
        return 'none';
      case 'TRANSIT':
        var route = segment.patterns[0].route;
        var id = route.route_id.split(':');
        return convert.routeToColor(route.route_type, id[0].toLowerCase(), id[1].toLowerCase(), route.route_color);
    }
  }
};

exports.places = {
  stroke: 1
};

exports.places_icon = {
  x: -8,
  y: -8,
  width: 16,
  height: 16,
  'xlink:href': function(display, data) {
    if (data.owner.getId() === 'from') return 'build/planner-app/transitive-view/start.svg';
    if (data.owner.getId() === 'to') return 'build/planner-app/transitive-view/end.svg';
  },
  stroke: 1,
  visibility: 'visible'
};

exports.multipoints_pattern = exports.multipoints_merged = exports.stops_pattern = exports.stops_merged = {
  r: function(display, data, index, utils) {
    return utils.pixels(display.zoom.scale(), 4, 6, 8);
  }
};

var config = require('config');
var convert = require('convert');
var parse = require('color-parser');

exports.segments = {
  stroke: function(display, segment) {
    if (!segment.focused) return;

    switch (segment.type) {
      case 'CAR':
        return '#888';
      case 'WALK':
        return 'none';
      case 'TRANSIT':
        var route = segment.patterns[0].route;
        var id = route.route_id.split(':');
        return convert.routeToColor(route.route_type, id[0].toLowerCase(), id[1].toLowerCase(), route.route_color);
    }
  }
};

exports.places = {
  stroke: 1
};

exports.places_icon = {
  x: -8,
  y: -8,
  width: 16,
  height: 16,
  'xlink:href': function(display, data) {
    if (data.owner.getId() === 'from') return config.s3_bucket() + '/build/planner-app/transitive-view/start.svg';
    if (data.owner.getId() === 'to') return config.s3_bucket() + '/build/planner-app/transitive-view/end.svg';
  },
  stroke: 1,
  visibility: 'visible'
};

exports.multipoints_pattern = exports.multipoints_merged = exports.stops_pattern = exports.stops_merged = {
  r: function(display, data, index, utils) {
    return utils.pixels(display.zoom.scale(), 4, 6, 8);
  }
};

Prepend SVG icons with S3 bucket


'use strict';

/* property.js
 * Object to represent CSS properties.
 */

const Value = require('./value');

module.exports = exports = Property;

function Property(sel, values) {
  if (!(this instanceof Property)) return new Property(sel, values);

  // Type checking
  if (typeof sel !== 'string')
    throw new TypeError('sel must be a string');
  if (Array.isArray(values))
    values.forEach((x) => {
      if (!(x instanceof Value))
        throw new TypeError('values must only contain Value objects');
    });
  else if (typeof values === 'undefined');
  else
    throw new TypeError('values must be an array of Value objects');

  // Set values
  this.sel = sel;
  this.values = Array.isArray(values) ? values : [];
}

Property.prototype.add = function(value){
  if (!(value instanceof Value))
    throw new TypeError('value must be a Value object');

  this.values.push(value);
};

Property.prototype.set = function(index, value){
  if (typeof index !== 'number')
    throw new TypeError('index must be a number');
  if (!(value instanceof Value))
    throw new TypeError('value must be a Value object');

  this.values[index] = value;
};

Property.prototype.remove = function(index){
  this.values.slice(index, index + 1);
};

'use strict';

/* property.js
 * Object to represent CSS properties.
 */

const Value = require('./value');

module.exports = exports = Property;

function Property(sel, values) {
  if (!(this instanceof Property)) return new Property(sel, values);

  // Type checking
  if (typeof sel !== 'string')
    throw new TypeError('sel must be a string');
  if (Array.isArray(values))
    values.forEach((x) => {
      if (!(x instanceof Value))
        throw new TypeError('values must only contain Value objects');
    });
  else if (typeof values === 'undefined');
  else
    throw new TypeError('values must be an array of Value objects');

  // Set values
  this.sel = sel;
  this.values = Array.isArray(values) ? values : [];
}

Property.prototype.add = function(value){
  if (!(value instanceof Value))
    throw new TypeError('value must be a Value object');

  this.values.push(value);
};

Property.prototype.set = function(index, value){
  if (typeof index !== 'number')
    throw new TypeError('index must be a number');
  if (!(value instanceof Value))
    throw new TypeError('value must be a Value object');

  this.values[index] = value;
};

Property.prototype.remove = function(index){
  this.values.splice(index, 1);
};

Fix .remove method not removing values.


// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// This file contains various hacks needed to inform JSCompiler of various
// WebKit-specific properties and methods. It is used only with JSCompiler
// to verify the type-correctness of our code.

/** @type Array.<HTMLElement> */
Document.prototype.all;

/** @type {function(string): void} */
Document.prototype.execCommand = function(command) {};

/** @return {void} Nothing. */
Document.prototype.webkitCancelFullScreen = function() {};

/** @type {boolean} */
Document.prototype.webkitIsFullScreen;

/** @type {number} */
Element.ALLOW_KEYBOARD_INPUT;

/** @param {number} flags
/** @return {void} Nothing. */
Element.prototype.webkitRequestFullScreen = function(flags) {};

/** @type {{getRandomValues: function(Uint16Array):void}} */
Window.prototype.crypto;

// This string is replaced with the actual value in build-webapp.py.
var OAUTH2_USE_OFFICIAL_CLIENT_ID = false;

Make JS compiler happy about OAUTH2_USE_OFFICEIAL_CLIENT_ID.


define(['mac/roman'], function(macintoshRoman) {

  'use strict';

  return function(resource) {
    var dv = new DataView(resource.data.buffer, resource.data.byteOffset, resource.data.byteLength);
    resource.dataObject = {
      id: dv.getUint16(0, false),
      definitionProcedureResourceID: dv.getUint16(6, false),
      enabledState: dv.getUint32(10, false),
      title: macintoshRoman(resource.data, 15, resource.data[14]),
    };
    var pos = 15 + resource.data[14];
    if (resource.dataObject.definitionProcedureResourceID === 0) {
      delete resource.dataObject.definitionProcedureResourceID;
      resource.dataObject.items = [];
      while (pos < resource.data.length && resource.data[pos] !== 0) {
        var text = macintoshRoman(resource.data, pos + 1, resource.data[pos]);
        pos += 1 + text.length;
        var item = {
          text: text,
          iconNumberOrScriptCode: resource.data[pos],
          keyboardEquivalent: resource.data[pos + 1],
          markingCharacterOrSubmenuID: resource.data[pos + 2],
          style: resource.data[pos + 3],
        };
        resource.dataObject.items.push(item);
        pos += 4;
      }
    }
    else {
      resource.dataObject.itemData = atob(String.fromCharCode.apply(null, resource.data.subarray(pos)));
    }
  };

});

define(['mac/roman'], function(macintoshRoman) {

  'use strict';

  return function(item) {
    return item.getBytes().then(function(bytes) {
      var dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
      var dataObject = {
        id: dv.getUint16(0, false),
        definitionProcedureResourceID: dv.getUint16(6, false),
        enabledState: dv.getUint32(10, false),
        title: macintoshRoman(bytes, 15, bytes[14]),
      };
      var pos = 15 + bytes[14];
      if (dataObject.definitionProcedureResourceID === 0) {
        delete dataObject.definitionProcedureResourceID;
        dataObject.items = [];
        while (pos < bytes.length && bytes[pos] !== 0) {
          var text = macintoshRoman(bytes, pos + 1, bytes[pos]);
          pos += 1 + text.length;
          var item = {
            text: text,
            iconNumberOrScriptCode: bytes[pos],
            keyboardEquivalent: bytes[pos + 1],
            markingCharacterOrSubmenuID: bytes[pos + 2],
            style: bytes[pos + 3],
          };
          dataObject.items.push(item);
          pos += 4;
        }
      }
      else {
        dataObject.itemData = atob(String.fromCharCode.apply(null, bytes.subarray(pos)));
      }
      item.setDataObject(dataObject);
    });
  };

});

Change MENU to new loader format


const DEFAULT_HEIGHT = 600,
    DEFAULT_WIDTH= 944,
    DEFAULT_MEDIA_QUERY = 'screen';

export function init() {
    $('.js-popup').each((index, elem) => {
        new FefPopup($(elem));
    });
}

export class FefPopup {

    /**
     * @param $element jQuery.element
     */
    constructor ($element) {
        this.$element = $element;
        this.target = this.$element.attr('href');
        this.width = this.$element.data('popup-width') || DEFAULT_WIDTH;
        this.height = this.$element.data('popup-height') || DEFAULT_HEIGHT;
        this.mediaQuery = this.$element.data('popup-media-query') || DEFAULT_MEDIA_QUERY;
        this.triggerEvent = this.$element.data('popup-trigger-event') || null;
        this.bindEvents();

    }

    /**
     * Bind click
     */
    bindEvents () {
        this.$element.on('click', (event) => {
            this.openPopup(event);
        });
    }

    openPopup(event) {
        if (matchMedia(this.mediaQuery).matches) {
            event.preventDefault();
            let parameters = [
                'width='+this.width,
                'height='+this.height,
                'toolbar=1',
                'scrollbars=1',
                'location=1',
                'status=0',
                'menubar=1',
                'resizable=1'
            ];
            if (this.triggerEvent) {
                $(window).trigger(this.triggerEvent);
            }
            window.open(this.target, '_blank', parameters.join(','));
        }
    }
}

const DEFAULT_HEIGHT = 600,
    DEFAULT_WIDTH= 944,
    DEFAULT_MEDIA_QUERY = 'screen';

export function init() {
    $(document).on('click', '.js-popup', (event) => {
        let popup = new FefPopup($(event.currentTarget));
        popup.openPopup(event);
    });
}

class FefPopup {

    /**
     * @param $element jQuery.element
     */
    constructor ($element) {
        this.$element = $element;
        this.target = this.$element.attr('href');
        this.width = this.$element.data('popup-width') || DEFAULT_WIDTH;
        this.height = this.$element.data('popup-height') || DEFAULT_HEIGHT;
        this.mediaQuery = this.$element.data('popup-media-query') || DEFAULT_MEDIA_QUERY;
        this.triggerEvent = this.$element.data('popup-trigger-event') || null;
    }

    openPopup(event) {
        if (matchMedia(this.mediaQuery).matches) {
            event.preventDefault();
            let parameters = [
                'width='+this.width,
                'height='+this.height,
                'toolbar=1',
                'scrollbars=1',
                'location=1',
                'status=0',
                'menubar=1',
                'resizable=1'
            ];
            if (this.triggerEvent) {
                $(window).trigger(this.triggerEvent);
            }
            window.open(this.target, '_blank', parameters.join(','));
        }
    }
}

Refactor FefPopup to work with dynamic links


'use strict';

const config = {

  browserPort: 3000,
  UIPort: 3001,

  scripts: {
    src: './app/js/**/*.js',
    dest: './build/js/',
    test: './tests/**/*.js',
    gulp: './gulp/**/*.js'
  },

  images: {
    src: './app/images/**/*.{jpeg,jpg,png,gif}',
    dest: './build/images/'
  },

  styles: {
    src: './app/styles/**/*.scss',
    dest: './build/css/'
  },

  sourceDir: './app/',

  buildDir: './build/',

  testFiles: './tests/**/*.{js,jsx}',

  assetExtensions: [
    'js',
    'css',
    'png',
    'jpe?g',
    'gif',
    'svg',
    'eot',
    'otf',
    'ttc',
    'ttf',
    'woff2?'
  ]

};

export default config;

'use strict';

const config = {

  browserPort: 3000,
  UIPort: 3001,

  scripts: {
    src: './app/js/**/*.js',
    dest: './build/js/',
    test: './tests/**/*.js',
    gulp: './gulp/**/*.js'
  },

  images: {
    src: './app/images/**/*.{jpeg,jpg,png,gif}',
    dest: './build/images/'
  },

  styles: {
    src: './app/styles/**/*.scss',
    dest: './build/css/'
  },

  sourceDir: './app/',

  buildDir: './build/',

  testFiles: './tests/**/*.{js,jsx}',

  assetExtensions: [
    'js',
    'css',
    'map',
    'png',
    'jpe?g',
    'gif',
    'svg',
    'eot',
    'otf',
    'ttc',
    'ttf',
    'woff2?'
  ]

};

export default config;

Add 'map' to asset extensions


var CoreView = require('backbone/core-view');

module.exports = CoreView.extend({
  className: 'CDB-Box-modal Table-editor',

  initialize: function () {
    this.listenTo(this.model, 'change:show', this._onShowChange);
    this.listenTo(this.model, 'destroy', this._onDestroy);
  },

  render: function () {
    this.clearSubViews();

    var view = this.model.createContentView();
    this.addView(view);
    view.render();
    this.$el.append(view.el);
    this.$el.css(this.options.position);

    return this;
  },

  show: function () {
    this.model.show();
  },

  hide: function () {
    this.model.hide();
  },

  destroy: function () {
    this.model.destroy();
  },

  _onShowChange: function (m, show) {
    this.$el[show ? 'show' : 'hide']();
  },

  _onClose: function () {
    this.destroy();
  },

  _onDestroy: function () {
    this.hide();
    this.clean();
  }

});

var _ = require('underscore');
var CoreView = require('backbone/core-view');

module.exports = CoreView.extend({
  className: 'CDB-Box-modal Table-editor',

  initialize: function () {
    this.listenTo(this.model, 'change:show', this._onShowChange);
    this.listenTo(this.model, 'destroy', this._onDestroy);
  },

  render: function () {
    this.clearSubViews();

    var view = this.model.createContentView();
    this.addView(view);
    view.render();
    this.$el.append(view.el);
    this.$el.css(_.extend({
      zIndex: 100
    }, this.options.position));

    return this;
  },

  show: function () {
    this.model.show();
  },

  hide: function () {
    this.model.hide();
  },

  destroy: function () {
    this.model.destroy();
  },

  _onShowChange: function (m, show) {
    this.$el[show ? 'show' : 'hide']();
  },

  _onClose: function () {
    this.destroy();
  },

  _onDestroy: function () {
    this.hide();
    this.clean();
  }

});

Fix edit month in table cell


'use strict';

// Load modules

// Declare internals

const internals = {};

exports.method = function (value) {
    return true ? (value-1, value) : value+1;  //Should return value
};

'use strict';

// Load modules

// Declare internals

const internals = {};

exports.method = function (value) {
    return true ? (value - 1, value) : value + 1;  //Should return value
};

Fix spacing to meet guidelines


import { connect } from 'react-redux'

import Dashboard from '../Dashboard'

const mapStateToProps = (state) => {
  return {
    userId: state.user.get('userId'),
    loggedIn: state.user.get('loggedIn'),
    games: state.games.get('games') && state.games.get('games').toJS() || {},
    fetchingGames: state.games.get('fetchingGames'),
    teams: state.teams.get('teams') && state.teams.get('teams').toJS() || {},
    fetchingTeams: state.teams.get('fetchingTeams')
  }
}

const DashboardContainer = connect(mapStateToProps)(Dashboard)

export default DashboardContainer

import { connect } from 'react-redux'

import Dashboard from '../Dashboard'

const mapStateToProps = (state) => {
  return {
    userId: state.user.get('userId'),
    loggedIn: state.user.get('loggedIn'),
    games: state.games.get('games') && state.games.get('games').toJS() || {},
    fetchingGames: state.games.get('fetchingGames'),
    teams: state.teams.get('teams') && state.teams.get('teams').toJS() || {},
    fetchingTeams: state.teams.get('fetchingTeams'),
    players: state.players.get('players') && state.players.get('players').toJS() || {},
    fetchingPlayers: state.players.get('fetchingPlayers')
  }
}

const DashboardContainer = connect(mapStateToProps)(Dashboard)

export default DashboardContainer

Add players to dashboard container


'use strict';

/*
 * Application .run
 *
 * On a successful state change set the root scopes title property
 * used for the html <title> tag.
 *
 * Angular .run exicuted after the .config function.
 */

var app = angular.module('app.run', []);
app.run(function ($rootScope) {

    // Execute every time the state is successfully changed (after interceptors)
    $rootScope.$on('$stateChangeSuccess', function (event, current, previous) {
        // If the new state has property for 'title' (set in the state declaration)
        // set the root scope variable title to be used by the index.html template.
        $rootScope.title = (current.hasOwnProperty('title')) ? current.title : 'Rachel\'s New Project Seed';
    });

});

'use strict';

/*
 * Application .run
 *
 * On a successful state change set the root scopes title property
 * used for the html <title> tag.
 *
 * Angular .run exicuted after the .config function.
 */

var app = angular.module('app.run', []);
app.run(function ($rootScope) {

    // Execute every time the state is successfully changed (after interceptors)
    $rootScope.$on('$stateChangeSuccess', function (event, current, previous) {
        // If the new state has property for 'title' (set in the state declaration)
        // set the root scope variable title to be used by the index.html template.
        $rootScope.title = (current.hasOwnProperty('title')) ? current.title : 'Rachel\'s New Project Seed';
        // If the new state has property for 'bodyClass' (set in the state declaration)
        // set the root scope variable title to be used by the index.html template.
        $rootScope.bodyClass = (current.hasOwnProperty('bodyClass')) ? current.bodyClass : '';
    });

});

Add body class to page.


require('babel-jest');

module.exports = {
  roots: ['js'],
  moduleFileExtensions: ['js', 'jsx'],
  transform: {
    '\\.jsx?$': 'babel-jest',
  },
};

require('babel-jest');

module.exports = {
  testURL: 'http://localhost/',
  roots: ['js'],
  moduleFileExtensions: ['js', 'jsx'],
  transform: {
    '\\.jsx?$': 'babel-jest',
  },
};

Fix "localStorage is not available" error in test


import React from 'react';
import { render } from 'react-dom';
import { DevTools, DebugPanel, LogMonitor } from 'redux-devtools/lib/react';

/**
 * Puts Redux DevTools into a separate window.
 * Based on https://gist.github.com/tlrobinson/1e63d15d3e5f33410ef7#gistcomment-1560218.
 */
export default function createDevToolsWindow(store) {
  // Window name.
  const name = 'Redux DevTools';

  // Give it a name so it reuses the same window.
  const win = window.open(
    null,
    name,
    'menubar=no,location=no,resizable=yes,scrollbars=no,status=no'
  );

  // Reload in case it's reusing the same window with the old content.
  win.location.reload();

  // Set visible Window title.
  win.document.title = name;

  // Wait a little bit for it to reload, then render.
  setTimeout(() => render(
    <DebugPanel top right bottom left>
      <DevTools store={store} monitor={LogMonitor} />
    </DebugPanel>,
    win.document.body.appendChild(document.createElement('div'))
  ), 10);
}

import React from 'react';
import { render } from 'react-dom';
import { DevTools, DebugPanel, LogMonitor } from 'redux-devtools/lib/react';

/**
 * Puts Redux DevTools into a separate window.
 * Based on https://gist.github.com/tlrobinson/1e63d15d3e5f33410ef7#gistcomment-1560218.
 */
export default function createDevToolsWindow(store) {
  // Window name.
  const name = 'Redux DevTools';

  // Give it a name so it reuses the same window.
  const win = window.open(
    null,
    name,
    'menubar=no,location=no,resizable=yes,scrollbars=no,status=no'
  );

  // Pop-ups are blocked by default, so it may open async
  if (!win) {
    console.warn(
      'Couldn\'t open the dev Tools, probably the popup window ' +
      'was blocked, please enable the popup window for the current page.\n'
    );
    return;
  }

  // Reload in case it's reusing the same window with the old content.
  win.location.reload();

  // Set visible Window title.
  win.document.title = name;

  // Wait a little bit for it to reload, then render.
  setTimeout(() => render(
    <DebugPanel top right bottom left>
      <DevTools store={store} monitor={LogMonitor} />
    </DebugPanel>,
    win.document.body.appendChild(document.createElement('div'))
  ), 10);
}

Check if window.open succeed in DevToolsWindow


'use strict';

module.exports = {
  env: {
    es6: true,
    node: true,
    browser: true,
  },
  extends: 'eslint:recommended',
  parserOptions: {
    sourceType: 'module',
    ecmaVersion: 8,
  },
  rules: {
    indent:             [ 'error', 2 , {SwitchCase: 1}],
    'linebreak-style':  [ 'error', 'unix' ],
    quotes:             [ 'error', 'single', {
      avoidEscape: true,
      allowTemplateLiterals: true
    } ],
    semi:               [ 'error', 'always' ],
    'no-console':       'off',
    'no-unused-vars':   'warn',
  }
};

'use strict';

/* eslint-env node */
module.exports = {
  env: {
    es6: true,
    browser: true,
  },
  extends: 'eslint:recommended',
  parserOptions: {
    sourceType: 'module',
    ecmaVersion: 8,
  },
  rules: {
    indent:             [ 'error', 2 , {SwitchCase: 1}],
    'linebreak-style':  [ 'error', 'unix' ],
    quotes:             [ 'error', 'single', {
      avoidEscape: true,
      allowTemplateLiterals: true
    } ],
    semi:               [ 'error', 'always' ],
    'no-console':       'off',
    'no-unused-vars':   'warn',
  }
};

Remove Node.js from global environment


import { by, device, element, expect as detoxExpect, waitFor } from 'detox';

import { getTextAsync, sleepAsync } from './Utils';
import { expectResults } from './utils/report';

let TESTS = [
  'Basic',
  // 'Asset',
  // 'FileSystem',
  // 'Font',
  'Permissions',
  'Blur',
  'LinearGradient',
  'Constants',
  // 'Contacts',
  'Crypto',
  // 'GLView',
  'Haptics',
  'Localization',
  // 'SecureStore',
  // 'Segment',
  // 'SQLite',
  'Random',
  'Permissions',
  'KeepAwake',
  'FirebaseCore',
  'FirebaseAnalytics',
  // 'Audio',
  'HTML',
];

const MIN_TIME = 50000;

describe('test-suite', () => {
  TESTS.map(testName => {
    it(
      `passes ${testName}`,
      async () => {
        await device.launchApp({
          newInstance: true,
          url: `bareexpo://test-suite/select/${testName}`,
        });
        await sleepAsync(100);
        await detoxExpect(element(by.id('test_suite_container'))).toBeVisible();
        await waitFor(element(by.id('test_suite_text_results')))
          .toBeVisible()
          .withTimeout(MIN_TIME);

        const input = await getTextAsync('test_suite_final_results');

        expectResults({
          testName,
          input,
        });
      },
      MIN_TIME * 1.5
    );
  });
});

import { by, device, element, expect as detoxExpect, waitFor } from 'detox';

import { getTextAsync, sleepAsync } from './Utils';
import { expectResults } from './utils/report';

let TESTS = [
  'Basic',
  // 'Asset',
  // 'FileSystem',
  // 'Font',
  'Permissions',
  'Blur',
  'LinearGradient',
  'Constants',
  // 'Contacts',
  'Crypto',
  // 'GLView',
  'Haptics',
  'Localization',
  // 'SecureStore',
  // 'Segment',
  // 'SQLite',
  'Random',
  'Permissions',
  'KeepAwake',
  'FirebaseCore',
  'FirebaseAnalytics',
  // 'Audio',
  'HTML',
];

const MIN_TIME = 50000;

describe('test-suite', () => {
  TESTS.map(testName => {
    it(
      `passes ${testName}`,
      async () => {
        await device.launchApp({
          newInstance: true,
          url: `bareexpo://test-suite/select/${testName}`,
        });
        await sleepAsync(100);
        await detoxExpect(element(by.id('test_suite_container'))).toExist();
        await waitFor(element(by.id('test_suite_text_results')))
          .toExist()
          .withTimeout(MIN_TIME);

        const input = await getTextAsync('test_suite_final_results');

        expectResults({
          testName,
          input,
        });
      },
      MIN_TIME * 1.5
    );
  });
});

Fix ios detox test hang


var RSVP = require('rsvp'),
    utils = require('./lib'),
    GoogleSpreadsheet = require("google-spreadsheet"),
    config = require('./config.js'),
    doc = new GoogleSpreadsheet(config['google_spreadsheet_key']),
    process = require('process');

RSVP.hash({
        currentItems: utils.getWishlistFromAmazon(config['amazon_wishlist_id']),
        previousItems: utils.getPreviousWishlist(),
        spreadsheetAuthenticated: utils.authenticateServiceAccount(doc, {
            private_key: config['google_private_key'],
            client_email: config['google_client_email']
        })
    })
    .then(function(wishlist){
        var itemsAdded = utils.getDifference(wishlist.previousItems, wishlist.currentItems);

        console.log(`Found ${itemsAdded.length} new items.`);
        console.log(itemsAdded);

        rowAddPromises = rowValues.map(function(rowObj){
            return utils.addRowsToDriveSpreadsheet(doc, 1, {
                Image: '=IMAGE("' + rowObj.picture + '")',
                Title: '=HYPERLINK("' + rowObj.link + '", "' + rowObj.name + '")'
            });
        });

        rowAddPromises.push(utils.savePreviousWishlist(wishlist.currentItems));

        return RSVP.all(rowAddPromises);
    }, function(err){
        console.log(err);
        process.exit();
    })
    .then(function(){
        console.log('Success adding rows to spreadsheet!');
    }, function(err){
        console.log(err.stack || err.message || err);
        process.exit();
    });

var RSVP = require('rsvp'),
    utils = require('./lib'),
    GoogleSpreadsheet = require("google-spreadsheet"),
    config = require('./config.js'),
    doc = new GoogleSpreadsheet(config['google_spreadsheet_key']),
    process = require('process');

RSVP.hash({
        currentItems: utils.getWishlistFromAmazon(config['amazon_wishlist_id']),
        previousItems: utils.getPreviousWishlist(),
        spreadsheetAuthenticated: utils.authenticateServiceAccount(doc, {
            private_key: config['google_private_key'],
            client_email: config['google_client_email']
        })
    })
    .then(function(wishlist){
        var itemsAdded = utils.getDifference(wishlist.previousItems, wishlist.currentItems);

        console.log(`Found ${itemsAdded.length} new items.`);
        console.log(itemsAdded.map(function(item) { image: item.image, title: item.title, link: item.link } ));

        rowAddPromises = itemsAdded.map(function(rowObj){
            return utils.addRowsToDriveSpreadsheet(doc, 1, {
                Image: '=IMAGE("' + rowObj.picture + '")',
                Title: '=HYPERLINK("' + rowObj.link + '", "' + rowObj.name + '")'
            });
        });

        rowAddPromises.push(utils.savePreviousWishlist(wishlist.currentItems));

        return RSVP.all(rowAddPromises);
    }, function(err){
        console.log(err);
        process.exit();
    })
    .then(function(){
        console.log('Success adding rows to spreadsheet!');
    }, function(err){
        console.log(err.stack || err.message || err);
        process.exit();
    });

Fix syntax error, filter row log


var Chunk = require('./lib/chunk')
  , Decoder = require('./lib/decode')
  , Encoder = require('./lib/encode')

module.exports = {
  decode: function() { return new Decoder }
, encode: function() { return new Encoder }
, chunk: make_chunk
, text: text
}

function make_chunk(type, data) {
  return new Chunk(new Buffer(type).readUInt32BE(0), new Buffer(data))
}

function text(key, data) {
  var keylen = Math.min(79, Buffer.byteLength(key))
    , buffer = new Buffer(keylen + 1 + Buffer.byteLength(data))

  buffer.write(key, 0, keylen)
  buffer[keylen + 1] = 0
  buffer.write(data, keylen + 1)

  return make_chunk('tEXt', buffer)
}

var Chunk = require('./lib/chunk')
  , Decoder = require('./lib/decode')
  , Encoder = require('./lib/encode')

module.exports = {
  decode: function() { return new Decoder }
, encode: function() { return new Encoder }
, chunk: make_chunk
, text: text
}

function make_chunk(type, data) {
  return new Chunk(new Buffer(type).readUInt32BE(0), new Buffer(data))
}

function text(key, data) {
  var keylen = Math.min(79, Buffer.byteLength(key))
    , buffer = new Buffer(keylen + 1 + Buffer.byteLength(data))

  buffer.write(key, 0, keylen)
  buffer[keylen] = 0
  buffer.write(data, keylen + 1)

  return make_chunk('tEXt', buffer)
}

Fix issue with rice.text not placing the null byte in the appropriate location


require('autoreleasepool');
module.exports = require('./build/default/bindings.node');

var Application = module.exports.Application
  , Item = module.exports.Item

Application.prototype.inspect = Application.prototype.toString;
Item.prototype.inspect = Item.prototype.toString;

require('autoreleasepool');
module.exports = require('./build/default/bindings.node');

var Application = module.exports.Application
  , Item = module.exports.Item
  , inspect = require('util').inspect

Application.prototype.inspect = function () {
  return inspect(Application.prototype.toString.call(this));
}
Item.prototype.inspect = function () {
  return inspect(Item.prototype.toString.call(this));
}

Use 'inspect' on the custom inspect functions.


module.exports = function(value) {
  var length = value.length;

  // If `value` is an empty string, then it will have 0 caps and a length of 0,
  // which, when divided, will yield `NaN`. A string with a length of 0 will
  // obviously have no caps.
  if (!length) {
    return 0;
  }

  var caps = 0;
  var index = -1;
  while (++index < length) {
    var chr = value.charAt(index);
    if (chr.toLowerCase() !== chr && chr.toUpperCase() === chr) {
      caps++;
    }
  }

  return caps / length;
};

module.exports = function(value) {
  var length = value.length;

  // A string with a length of 0 will obviously have no caps.
  if (!length) {
    return 0;
  }

  var caps = 0;
  var index = -1;
  while (++index < length) {
    var chr = value.charAt(index);
    if (chr.toLowerCase() !== chr && chr.toUpperCase() === chr) {
      caps++;
    }
  }

  return caps / length;
};

Simplify comment on 0 length strings


(function(fs, path){
  'use strict';

  // Resources
  global.__objectCache = {};

  // Module
  module.exports = exports = {
    __objectCache: global.__objectCache,

    read: function(fileName){
      fileName = path.resolve(fileName);
      var fileDataRaw = null, fileData = null;

      // Try searching cache:
      if (typeof this.__objectCache[fileName] !== 'undefined') {
        return this.__objectCache[fileName];
      }

      // Try reading file:
      try {
        fileDataRaw = fs.readFileSync(fileName);
      } catch (e) {
        throw {'type':'read', 'original':e};
      }

      // Try parsing file data:
      try {
        fileData = JSON.parse(fileDataRaw);
        this.__objectCache[fileName] = fileData;
      } catch (e) {
        throw {'type':'parse', 'original':e};
      }

      return fileData;
    },

    purge: function(fileName){
      fileName = path.resolve(fileName);

      if (typeof fileName !== 'undefined') {
        // Purge specific cache:
        this.__objectCache[fileName] = undefined;
      } else {
        // Purge all cache:
        for (var id in this.__objectCache) {
          this.__objectCache[id] = undefined;
        }
      }

      return true;
    }
  };
})(require('fs'),
   require('path'));

(function(fs, path){
  'use strict';

  // Resources
  global.__objectCache = {};

  // Module
  module.exports = exports = {
    __objectCache: global.__objectCache,

    read: function(fileName){
      fileName = path.resolve(fileName);
      var fileDataRaw = null, fileData = null;

      // Try searching cache:
      if (typeof this.__objectCache[fileName] !== 'undefined') {
        return JSON.parse(JSON.stringify(this.__objectCache[fileName]));
      }

      // Try reading file:
      try {
        fileDataRaw = fs.readFileSync(fileName);
      } catch (e) {
        throw {'type':'read', 'original':e};
      }

      // Try parsing file data:
      try {
        fileData = JSON.parse(fileDataRaw);
        this.__objectCache[fileName] = fileData;
      } catch (e) {
        throw {'type':'parse', 'original':e};
      }

      return fileData;
    },

    purge: function(fileName){
      fileName = path.resolve(fileName);

      if (typeof fileName !== 'undefined') {
        // Purge specific cache:
        this.__objectCache[fileName] = undefined;
      } else {
        // Purge all cache:
        for (var id in this.__objectCache) {
          this.__objectCache[id] = undefined;
        }
      }

      return true;
    }
  };
})(require('fs'),
   require('path'));

Create a clone. This allows multiple values to be set, and it doesn't conflict with the global object.


var fsExtra = require('fs-extra'),
    SnapshotCleaner = require('./lib/cleaner');

fsExtra.readJSON('./config/config.json', function (error, config) {
    if (error) {
        throw error;
    }
    var sc = new SnapshotCleaner(config);
    sc.start();
});

var fsExtra = require('fs-extra'),
    SnapshotCleaner = require('./lib/cleaner');

fsExtra.readJSON('./config/_config.json', function (error, config) {
    if (error) {
        throw error;
    }
    var sc = new SnapshotCleaner(config);
    sc.start();
});

Fix path to configuration file


var path = require('path');
var notifier = require('node-notifier');

var WebpackNotifierPlugin = module.exports = function(options) {
    this.options = options || {};
    this.lastBuildSucceeded = false;
};

WebpackNotifierPlugin.prototype.compileMessage = function(stats) {
    var error;
    if (stats.hasWarnings()) {
        error = stats.compilation.warnings[0];
    }
    if (stats.hasErrors()) {
        error = stats.compilation.errors[0];
    }

    if (error) {
        try {
            this.lastBuildSucceeded = false;
            return error.module.rawRequest + '\n' + error.error.toString();
        } catch (e) {
            return "Unknown error or warning";
        }
    }

    if (!this.lastBuildSucceeded) {
        this.lastBuildSucceeded = true;
        return 'Build successful';
    }
};

WebpackNotifierPlugin.prototype.compilationDone = function(stats) {
    var msg = this.compileMessage(stats);
    if (msg) {
        notifier.notify({
            title: this.options.title || 'Webpack',
            message: msg,
            contentImage: this.options.contentImage || path.join(__dirname, 'logo.png')
        });
    }
};

WebpackNotifierPlugin.prototype.apply = function(compiler) {
    compiler.plugin('done', this.compilationDone.bind(this));
};

var path = require('path');
var notifier = require('node-notifier');

var DEFAULT_LOGO = path.join(__dirname, 'logo.png');

var WebpackNotifierPlugin = module.exports = function(options) {
    this.options = options || {};
    this.lastBuildSucceeded = false;
};

WebpackNotifierPlugin.prototype.compileMessage = function(stats) {
    var error;
    if (stats.hasWarnings()) {
        error = stats.compilation.warnings[0];
    }
    if (stats.hasErrors()) {
        error = stats.compilation.errors[0];
    }

    if (error) {
        try {
            this.lastBuildSucceeded = false;
            return error.module.rawRequest + '\n' + error.error.toString();
        } catch (e) {
            return "Unknown error or warning";
        }
    }

    if (!this.lastBuildSucceeded) {
        this.lastBuildSucceeded = true;
        return 'Build successful';
    }
};

WebpackNotifierPlugin.prototype.compilationDone = function(stats) {
    var msg = this.compileMessage(stats);
    if (msg) {
        var contentImage = ('contentImage' in this.options) ?
            this.options.contentImage : DEFAULT_LOGO;

        notifier.notify({
            title: this.options.title || 'Webpack',
            message: msg,
            contentImage: contentImage,
        });
    }
};

WebpackNotifierPlugin.prototype.apply = function(compiler) {
    compiler.plugin('done', this.compilationDone.bind(this));
};

Allow overriding contentImage with `undefined`


const Telegraf = require('telegraf');

let userManager = require('./user-manager');

// Enter bot API Token on here or add as environment varialbe
const BOT_API_TOKEN = process.env.API_TOKEN || '';

const bot = new Telegraf(BOT_API_TOKEN);

bot.startPolling();

console.log('Bot start polling....');

const Telegraf = require('telegraf');
const { memorySession } = require('telegraf');

let userManager = require('./user-manager');

// Enter bot API Token on here or add as environment varialbe
const BOT_API_TOKEN = process.env.API_TOKEN || '';

const bot = new Telegraf(BOT_API_TOKEN);

bot.use(memorySession);

bot.startPolling();

console.log('Bot start polling....');

Add memory session to bot


const rp = require("request-promise");
const fs = require("fs");

exports.run = async(client, msg, args) => {
    if(args.length < 1) return;

    try {
        fs.appendFileSync("bug_reports.log",
                          `Username: ${msg.author.username}#${msg.author.discriminator}\n` +
                          `Id: ${msg.author.id}\n` +
                          `Message: ${args.join(" ")}\n` +
                          `-----------------------------------------------------\n`);
        msg.channel.send("Bug successfully reported. @Wolfe#2282 should be checking the logs somtime soon.");
    } catch (err) {
        msg.channel.send("There was an error, oh no! :dizzy_face:");
    }
};

exports.help = {
    name: "reportbug",
    permission: 5,
    description: "Used to let the admins know of a bug.",
    usage: "reportbug [detailed description of bug]"
};

const rp = require("request-promise");
const fs = require("fs");

exports.run = async(client, msg, args) => {
    if(args.length < 1) return;

    try {
        fs.appendFileSync("bug_reports.log",
                          `Username: ${msg.author.username}#${msg.author.discriminator}\n` +
                          `Id: ${msg.author.id}\n` +
                          `Message: ${args.join(" ")}\n` +
                          `-----------------------------------------------------\n`);
        msg.channel.send("Bug successfully reported. @Executive should be checking the logs somtime soon.");
    } catch (err) {
        msg.channel.send("There was an error, oh no! :dizzy_face:");
    }
};

exports.help = {
    name: "reportbug",
    permission: 5,
    description: "Used to let the admins know of a bug.",
    usage: "reportbug [detailed description of bug]"
};

Tag all of exec now


const prompt = require('prompt');
const { randomBytes } = require('crypto')
const ethutil = require('ethereumjs-util');
const secp256k1 = require('secp256k1');

prompt.start();

prompt.get(['publicAddress', 'privateKey'], function (err, result) {
	const privateKeyBuffer = new Buffer(result.privateKey, 'hex');
	const publicKeyBuffer = ethutil.privateToPublic(privateKeyBuffer);

	// verify private key
	console.log('Private key legal: ' + ethutil.isValidPrivate(privateKeyBuffer));

	// verify public address
	console.log('Wallet address matches public address derived from private key: ' + (ethutil.bufferToHex(ethutil.pubToAddress(publicKeyBuffer)) === result.publicAddress));

	// generate message to sign
	const msg = randomBytes(32);

	// sign the message
	const sigObj = secp256k1.sign(msg, privateKeyBuffer);

	// verify the signature
	console.log('Signature verified: ' + secp256k1.verify(msg, sigObj.signature, Buffer.concat([ Buffer.from([4]), publicKeyBuffer ])));
});

const prompt = require('prompt');
const { randomBytes } = require('crypto')
const ethutil = require('ethereumjs-util');
const secp256k1 = require('secp256k1');

prompt.start();

prompt.get(['publicAddress', 'privateKey'], function (err, result) {
	const privateKeyBuffer = new Buffer(result.privateKey, 'hex');
	const publicKeyBuffer = ethutil.privateToPublic(privateKeyBuffer);

	// verify private key
	console.log('Private key legal: ' + ethutil.isValidPrivate(privateKeyBuffer));

	// verify public address
	console.log('Wallet address matches public address derived from private key: ' + (ethutil.bufferToHex(ethutil.pubToAddress(publicKeyBuffer)).toUpperCase() === result.publicAddress.toUpperCase()));

	// generate message to sign
	const msg = randomBytes(32);

	// sign the message
	const sigObj = secp256k1.sign(msg, privateKeyBuffer);

	// verify the signature
	console.log('Signature verified: ' + secp256k1.verify(msg, sigObj.signature, Buffer.concat([ Buffer.from([4]), publicKeyBuffer ])));
});

Fix case sensitivity in public key comparison


'use strict';
var path = require('path');
var gutil = require('gulp-util');
var through2 = require('through2');
var Mocha = require('mocha');

module.exports = function (options) {
	var mocha = new Mocha(options);
	return through2.obj(function (file, enc, cb) {
		delete require.cache[require.resolve(path.resolve(file.path))];
		mocha.addFile(file.path);
		this.push(file);
		cb();
	}, function (cb) {
		try {
			mocha.run(function (errCount) {
				if (errCount > 0) {
					this.emit('error', new gutil.PluginError('gulp-mocha', errCount + ' ' + (errCount === 1 ? 'test' : 'tests') + ' failed.'));
				}

				cb();
			}.bind(this));
		} catch (err) {
			this.emit('error', new gutil.PluginError('gulp-mocha', err));
			cb();
		}
	});
};

'use strict';
var path = require('path');
var gutil = require('gulp-util');
var through2 = require('through2');
var Mocha = require('mocha');

module.exports = function (options) {
	var mocha = new Mocha(options);
	var cache = {};

	for (var key in require.cache)
		cache[key] = true;

	return through2.obj(function (file, enc, cb) {
		mocha.addFile(file.path);
		this.push(file);
		cb();
	}, function (cb) {
		try {
			mocha.run(function (errCount) {
				if (errCount > 0) {
					this.emit('error', new gutil.PluginError('gulp-mocha', errCount + ' ' + (errCount === 1 ? 'test' : 'tests') + ' failed.'));
				}

				for (var key in require.cache)
					if (!cache[key])
						delete require.cache[key];

				cb();
			}.bind(this));
		} catch (err) {
			this.emit('error', new gutil.PluginError('gulp-mocha', err));
			cb();
		}
	});
};

Reset require cache back it original state on each run to avoid caching.


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

var loaderUtils = require("loader-utils");

function StripBlockLoader(content) {
    var query = (this.query === '' ? {} : loaderUtils.parseQuery(this.query));
    var startComment = query.start || 'develblock:start';
    var endComment = query.end || 'develblock:end';

    var regexPattern = new RegExp("[\\t ]*\\/\\* ?" + startComment + " ?\\*\\/[\\s\\S]*?\\/\\* ?" + endComment + " ?\\*\\/[\\t ]*\\n?", "g");

    content = content.replace(regexPattern, '');

    if (this.cacheable) {
        this.cacheable(true);
    }

    return content;
}

module.exports = StripBlockLoader;

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

var loaderUtils = require("loader-utils");

function StripBlockLoader(content) {
    var query = this.query && typeof this.query === 'string' ? loaderUtils.parseQuery(this.query) : {};
    var startComment = query.start || 'develblock:start';
    var endComment = query.end || 'develblock:end';

    var regexPattern = new RegExp("[\\t ]*\\/\\* ?" + startComment + " ?\\*\\/[\\s\\S]*?\\/\\* ?" + endComment + " ?\\*\\/[\\t ]*\\n?", "g");

    content = content.replace(regexPattern, '');

    if (this.cacheable) {
        this.cacheable(true);
    }

    return content;
}

module.exports = StripBlockLoader;

Test for non empty string before assigning default


$( document ).ready(function() {
    Dropzone.autoDiscover = false;
    var myDropzone = new Dropzone(".dropzone", {
        url: '/api/file',
        autoProcessQueue: true,
        maxFilesize: maxFilesize,
        acceptedFiles : acceptedFiles
    });
    myDropzone.on("queuecomplete", function(file, http) {
        window.setTimeout(function(){
            location.reload();
        }, 1000);
    });
    myDropzone.on("sending", function(file, fromData) {
        if ($('.alert-danger').length > 0) {
            $('.alert-danger').remove();
        }
    });
    myDropzone.on("error", function(file, errorMessage) {
        var html = '
' + errorMessage + '
';
        $('.col-md-12').first().prepend(html);
        setTimeout(function() {
            myDropzone.removeFile(file);
        }, 2000);
    });
});

$( document ).ready(function() {
    Dropzone.autoDiscover = false;
    var myDropzone = new Dropzone(".dropzone", {
        url: Asgard.dropzonePostUrl,
        autoProcessQueue: true,
        maxFilesize: maxFilesize,
        acceptedFiles : acceptedFiles
    });
    myDropzone.on("queuecomplete", function(file, http) {
        window.setTimeout(function(){
            location.reload();
        }, 1000);
    });
    myDropzone.on("sending", function(file, fromData) {
        if ($('.alert-danger').length > 0) {
            $('.alert-danger').remove();
        }
    });
    myDropzone.on("error", function(file, errorMessage) {
        var html = '
' + errorMessage + '
';
        $('.col-md-12').first().prepend(html);
        setTimeout(function() {
            myDropzone.removeFile(file);
        }, 2000);
    });
});

Read the dropzone post url from the Asgard global object


function(doc) {
   if(doc && doc.active && doc.active == "true") {
    emit(doc.infos.group.abbreviation, {
        code: doc.infos.group.abbreviation,
        name: doc.infos.group.name,
        count: 1
    });
   }
}

function(doc) {
   if(doc && doc.active) {
    emit(doc.infos.group.abbreviation, {
        code: doc.infos.group.abbreviation,
        name: doc.infos.group.name,
        count: 1
    });
   }
}

Revert "[fix] don't count unactive meps for the display of the groups list"


import Bishop from './bishop';
import Gold from './gold';
import King from './king';
import Knight from './knight';
import Lance from './lance';
import NullPiece from './nullPiece';
import Pawn from './pawn';
import Rook from './rook';
import Silver from './silver';

export { Bishop, Gold, King, Knight, Lance, NullPiece, Pawn, Rook, Silver }

import Bishop from './bishop';
import Gold from './gold';
import King from './king';
import Knight from './knight';
import Lance from './lance';
import NullPiece from './nullPiece';
import Pawn from './pawn';
import Rook from './rook';
import Silver from './silver';
import _ from 'lodash';

const classes = {
  b: Bishop,
  g: Gold,
  k: King,
  n: Knight,
  l: Lance,
  p: Pawn,
  r: Rook,
  s: Silver
};

export function getClass(type) {
  return classes[_.lowerCase(type)] || NullPiece;
}

Add getClass function. Return constructor from type.