const fs = require('fs')
const path = require('path')
const chalk = require('chalk')
const emojify = require('node-emoji').emojify
const program = require('commander')
const getConfig = require('lingui-conf').default
const plurals = require('make-plural')

const config = getConfig()

program.parse(process.argv)

function validateLocales (locales) {
  const unknown = locales.filter(locale => !(locale in plurals))
  if (unknown.length) {
    console.log(chalk.red(`Unknown locale(s): ${unknown.join(', ')}.`))
    process.exit(1)
  }
}

function addLocale (locales) {
  locales.forEach(locale => {
    const localeDir = path.join(config.localeDir, locale)

    if (fs.existsSync(localeDir)) {
      console.log(chalk.yellow(`Locale ${chalk.underline(locale)} already exists.`))
    } else {
      fs.mkdirSync(localeDir)
      console.log(chalk.green(`Added locale ${chalk.underline(locale)}.`))
    }
  })
}

validateLocales(program.args)

console.log(emojify(':white_check_mark:  Adding locales:'))
addLocale(program.args)
console.log()
console.log(`(use "${chalk.yellow('lingui extract')}" to extract messages)`)
console.log(emojify(':sparkles:  Done!'))

const fs = require('fs')
const path = require('path')
const chalk = require('chalk')
const emojify = require('node-emoji').emojify
const program = require('commander')
const getConfig = require('lingui-conf').default
const plurals = require('make-plural')

const config = getConfig()

program.parse(process.argv)

function validateLocales (locales) {
  const unknown = locales.filter(locale => !(locale in plurals))
  if (unknown.length) {
    console.log(chalk.red(`Unknown locale(s): ${unknown.join(', ')}.`))
    process.exit(1)
  }
}

function addLocale (locales) {
  if (fs.existsSync(config.localeDir)) {
    fs.mkdirSync(config.localeDir)
  }

  locales.forEach(locale => {
    const localeDir = path.join(config.localeDir, locale)

    if (fs.existsSync(localeDir)) {
      console.log(chalk.yellow(`Locale ${chalk.underline(locale)} already exists.`))
    } else {
      fs.mkdirSync(localeDir)
      console.log(chalk.green(`Added locale ${chalk.underline(locale)}.`))
    }
  })
}

validateLocales(program.args)

console.log(emojify(':white_check_mark:  Adding locales:'))
addLocale(program.args)
console.log()
console.log(`(use "${chalk.yellow('lingui extract')}" to extract messages)`)
console.log(emojify(':sparkles:  Done!'))

Create locale dir if doesn\' exist


Template.lottoAdd.created = function() {
  Session.set('lottoAddErrors', {});
};
Template.lottoAdd.rendered = function() {
  this.find('input[name=date]').valueAsDate = new Date();
};

Template.lottoAdd.helpers({
  errorMessage: function(field) {
    return Session.get('lottoAddErrors')[field];
  },
  errorClass: function (field) {
    return !!Session.get('lottoAddErrors')[field] ? 'has-error' : '';
  }
});

Template.lottoAdd.events({
  'submit form': function(e) {
    e.preventDefault();

    var lotto = {
      date: moment(e.target.date.value).toDate()
    };

    if (!lotto.date) {
      var errors = {};
      errors.date = "Please select a date";
      return Session.set('lottoAddErrors', errors);
    }

    Meteor.call('lottoInsert', lotto, function(error, result) {
      if(error) {
        return throwError(error.reason);
      }
      if(result.lottoExists) {
        throwError('Lotto for this date already exists');
      }
      Router.go('lottoPage', {_id: result._id});
    });
  }
});

Template.lottoAdd.created = function() {
  Session.set('lottoAddErrors', {});
};
Template.lottoAdd.rendered = function() {
  this.find('#date').valueAsDate = moment().day(5).toDate();
};

Template.lottoAdd.helpers({
  errorMessage: function(field) {
    return Session.get('lottoAddErrors')[field];
  },
  errorClass: function (field) {
    return !!Session.get('lottoAddErrors')[field] ? 'has-error' : '';
  }
});

Template.lottoAdd.events({
  'submit form': function(e) {
    e.preventDefault();

    var lotto = {
      date: moment(e.target.date.value).toDate()
    };

    if (!lotto.date) {
      var errors = {};
      errors.date = "Please select a date";
      return Session.set('lottoAddErrors', errors);
    }

    Meteor.call('lottoInsert', lotto, function(error, result) {
      if(error) {
        return throwError(error.reason);
      }
      if(result.lottoExists) {
        throwError('Lotto for this date already exists');
      }
      Router.go('lottoPage', {_id: result._id});
    });
  }
});

Set the date on the input box for new lotto to the next Saturday


$(function () {
    $('.tree li:has(ul)').addClass('parent_li').find(' > span').attr('title', 'Collapse this branch');
    $('.tree li.parent_li > span').on('click', function (e) {
        var children = $(this).parent('li.parent_li').find(' > ul > li');
        if (children.is(":visible")) {
            children.hide('fast');
            $(this).attr('title', 'Expand this branch').find(' > i').addClass('icon-plus-sign').removeClass('icon-minus-sign');
        } else {
            children.show('fast');
            $(this).attr('title', 'Collapse this branch').find(' > i').addClass('icon-minus-sign').removeClass('icon-plus-sign');
        }
        e.stopPropagation();
    });
});

$(function () {
    $('.tree li:has(ul)').addClass('parent_li').find(' > span').attr('title', 'Collapse this branch');
    $('.tree li.parent_li > span').on('click', function (e) {
        var children = $(this).parent('li.parent_li').find(' > ul > li');
        if (children.is(":visible")) {
            children.hide('fast');
            $(this).attr('title', 'Expand this branch').find(' > i').addClass('glyphicon-plus-sign').removeClass('glyphicon-minus-sign');
        } else {
            children.show('fast');
            $(this).attr('title', 'Collapse this branch').find(' > i').addClass('glyphicon-minus-sign').removeClass('glyphicon-plus-sign');
        }
        e.stopPropagation();
    });
});

Fix class names for icons in trees for Bootstrap 3


/* jshint es3: false, esnext: true */
/* global jasmine, beforeEach, describe, it, expect, module, inject */

(function () {

    var helpers = window._helpers = window._helpers || {};

    helpers.testModuleDependencies = function (moduleName) {

        'use strict';

        describe(moduleName + ' dependencies', function () {

            beforeEach(function () {

                module('user_management.' + moduleName);

                this.runModule = function () {
                    inject(function ($rootScope, $route) {
                        this.$rootScope = $rootScope;
                        this.$route = $route;
                    });
                };

            });

            it('should load the module without error', function () {
                expect(function () {
                    this.runModule();
                }.bind(this)).not.toThrow();
            });

            it('should have routes', function () {
                this.runModule();
                expect(Object.keys(this.$route.routes)).not.toEqual([]);
            });

        });

    };

}());

/* jshint es3: false, esnext: true */
/* global jasmine, beforeEach, describe, it, expect, module, inject */

(function () {

    var helpers = window._helpers = window._helpers || {};

    helpers.testModuleDependencies = function (moduleName) {

        'use strict';

        describe(moduleName + ' dependencies', function () {

            beforeEach(function () {

                module('user_management.' + moduleName);

                this.runModule = function () {
                    inject(function ($rootScope, $route) {
                        this.$rootScope = $rootScope;
                        this.$route = $route;
                    });
                };

            });

            it('should load the module without error', function () {
                expect(function () {
                    this.runModule();
                }.bind(this)).not.toThrow();
            });

        });

    };

}());

Remove "should have routes" common spec


/* @flow */
/* eslint-disable import/no-commonjs */

import 'core-js/es6/symbol';
import 'core-js/es6/array';

global.navigator.userAgent = 'react-native';

require('./client-base');

/* @flow */
/* eslint-disable import/no-commonjs */

import 'core-js/es6/symbol';
import 'core-js/es6/object';
import 'core-js/es6/array';
import 'core-js/es6/function';

global.navigator.userAgent = 'react-native';

require('./client-base');

Add more polyfills from core-js


'use strict';
const express = require('express');
const router = express.Router();
const bodyParser = require('body-parser');
const request = require('request');
const models = require('../../db/models');

module.exports.getAll = (req, res) => {
  models.User.where({ email: req.user.email}).fetch()
    .then((result) => {

      models.Stats.where({city: result.attributes.destination}).fetchAll()
      .then(data => {
        if (data.length === 0) {
          request
            .get(`https://api.teleport.org/api/urban_areas/slug:${result.attributes.destination}/scores/`,
              (error, response, stats) => {
                if (error) {
                  console.error(err);
                }
                models.Stats.forge({ city: result.attributes.destination, city_stats: stats })
                  .save()
                  .then(data => {
                    res.status(201).send(data.attributes.city_stats);
                  })
                  .catch(err => {
                    res.status(500).send(err);
                  });
              })
              .on('error', (err) => {
                console.log(err);
                res.status(500).send(err);
              });
        } else {
          res.status(200).send(data.models[0].attributes.city_stats);
        }

      })
      .catch(err => {
        res.status(503).send(err);
      });
    })
    .catch(err => {
      res.status(503).send(err);
    });

};

'use strict';
const express = require('express');
const router = express.Router();
const bodyParser = require('body-parser');
const request = require('request');
const models = require('../../db/models');

module.exports.getAll = (req, res) => {
  models.User.where({ email: req.user.email}).fetch()
    .then((result) => {

      models.Stats.where({city: result.attributes.destination}).fetchAll()
      .then(data => {
        if (data.length === 0) {
          request
            .get(`https://api.teleport.org/api/urban_areas/slug:${result.attributes.destination}/scores/`,
              (error, response, stats) => {
                if (error) {
                  console.error(err);
                }
                models.Stats.forge({ city: result.attributes.destination, city_stats: stats })
                  .save()
                  .then(data => {
                    res.status(201).send(data.attributes.city_stats);
                  })
                  .catch(err => {
                    res.status(500).send(err);
                  });
              })
              .on('error', (err) => {
                console.log(err);
                res.status(500).send(err);
              });
        } else {
          res.status(200).send(data.attributes.city_stats);
        }

      })
      .catch(err => {
        res.status(503).send(err);
      });
    })
    .catch(err => {
      res.status(503).send(err);
    });

};

Fix cityInfo tab not loading data after user completes form


import React from 'react';
import PureComponent from '../components/purecomponent.react';

import ProductImage from './product-image.react';

export default class ProductTile extends PureComponent {
  render() {
    const title = this.props.product.get('title');
    const productNumber = this.props.product.get('productNumber');
    const normalizedName = this.props.product.get('normalizedName');
    return (

{title}

<ProductImage productNumber={productNumber} normalizedName={normalizedName} alt={title}/>

    );
  }
}

ProductTile.propTypes = {
  product: React.PropTypes.shape({get: React.PropTypes.func.isRequired}).isRequired
};

Fix height of product title to avoid weird tile positioning


/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.u2.view',
  name: 'StrategizerChoiceView',
  extends: 'foam.u2.view.ChoiceView',

  documentation: 'A choice view that gets its choices array from Strategizer.',

  imports: [
    'strategizer'
  ],

  properties: [
    {
      class: 'String',
      name: 'desiredModelId',
      required: true
    },
    {
      class: 'String',
      name: 'target'
    }
  ],

  methods: [
    function init() {
      this.onDetach(this.desiredModelId$.sub(this.updateChoices));
      this.onDetach(this.target$.sub(this.updateChoices));
      this.updateChoices();
    }
  ],

  listeners: [
    {
      name: 'updateChoices',
      code: function() {
        var self = this;
        self.strategizer.query(null, self.desiredModelId, self.target).then((strategyReferences) => {
          self.choices = strategyReferences
            .reduce((arr, sr) => {
              if ( ! sr.strategy ) {
                console.warn('Invalid strategy reference: ' + sr.id);
                return arr;
              }

              return arr.concat([[sr.strategy.id, sr.strategy.name]]);
            }, [[null, 'Select...']])
            .filter(x => x);
        });
      }
    }
  ]
});

/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.u2.view',
  name: 'StrategizerChoiceView',
  extends: 'foam.u2.view.ChoiceView',

  documentation: 'A choice view that gets its choices array from Strategizer.',

  imports: [
    'strategizer'
  ],

  properties: [
    {
      class: 'String',
      name: 'desiredModelId',
      required: true
    },
    {
      class: 'String',
      name: 'target'
    }
  ],

  methods: [
    function init() {
      this.SUPER();
      this.onDetach(this.desiredModelId$.sub(this.updateChoices));
      this.onDetach(this.target$.sub(this.updateChoices));
      this.updateChoices();
    }
  ],

  listeners: [
    {
      name: 'updateChoices',
      code: function() {
        var self = this;
        self.strategizer.query(null, self.desiredModelId, self.target).then((strategyReferences) => {
          self.choices = strategyReferences
            .reduce((arr, sr) => {
              if ( ! sr.strategy ) {
                console.warn('Invalid strategy reference: ' + sr.id);
                return arr;
              }

              return arr.concat([[sr.strategy, sr.strategy.name]]);
            }, [[null, 'Select...']])
            .filter(x => x);
        });
      }
    }
  ]
});

Fix strategizer choice view choice selection in update view


import React from 'react';
import { IconWarningBadgedMediumOutline } from '@teamleader/ui-icons';

import { Dialog, Box, TextBody } from '../..';

export default {
  component: Dialog,
  title: 'Dialog',
};

export const Main = () => (
  <Dialog
    headerIcon={<IconWarningBadgedMediumOutline />}
    active
    onCloseClick={() => {}}
    onEscKeyDown={() => {}}
    onOverlayClick={() => {}}
    primaryAction={{
      label: 'Confirm',
    }}
    secondaryAction={{
      label: 'Cancel',
    }}
    tertiaryAction={{
      children: 'Read more',
    }}
    title="Dialog title"
  >
    <Box padding={4}>
      <TextBody>Here you can add arbitrary content.</TextBody>
    </Box>
  </Dialog>
);

Main.parameters = {
  // add a delay to make sure the dialog animation is finished
  chromatic: { delay: 300 },
};

Add delay to the dialog story to make sure the open animation has finished


import buildFormatLongFn from '../../../_lib/buildFormatLongFn/index.js'

var dateFormats = {
  full: 'EEEE, d MMMM yyyy',
  long: 'd MMMM yyyy',
  medium: 'd MMM yyyy',
  short: 'dd/MM/yyyy'
}

var timeFormats = {
  full: 'HH:mm:ss zzzz',
  long: 'HH:mm:ss z',
  medium: 'HH:mm:ss',
  short: 'HH:mm'
}

var dateTimeFormats = {
  full: "{{date}} 'at' {{time}}",
  long: "{{date}} 'at' {{time}}",
  medium: '{{date}}, {{time}}',
  short: '{{date}}, {{time}}'
}

var formatLong = {
  date: buildFormatLongFn({
    formats: dateFormats,
    defaultWidth: 'full'
  }),

  time: buildFormatLongFn({
    formats: timeFormats,
    defaultWidth: 'full'
  }),

  dateTime: buildFormatLongFn({
    formats: dateTimeFormats,
    defaultWidth: 'full'
  })
}

export default formatLong

import buildFormatLongFn from '../../../_lib/buildFormatLongFn/index.js'

var dateFormats = {
  full: 'EEEE, d MMMM yyyy',
  long: 'd MMMM yyyy',
  medium: 'd MMM yyyy',
  short: 'dd/MM/yyyy'
}

var timeFormats = {
  full: 'h:mm:ss a zzzz',
  long: 'h:mm:ss a z',
  medium: 'h:mm:ss a',
  short: 'h:mm a'
}

var dateTimeFormats = {
  full: "{{date}} 'at' {{time}}",
  long: "{{date}} 'at' {{time}}",
  medium: '{{date}}, {{time}}',
  short: '{{date}}, {{time}}'
}

var formatLong = {
  date: buildFormatLongFn({
    formats: dateFormats,
    defaultWidth: 'full'
  }),

  time: buildFormatLongFn({
    formats: timeFormats,
    defaultWidth: 'full'
  }),

  dateTime: buildFormatLongFn({
    formats: dateTimeFormats,
    defaultWidth: 'full'
  })
}

export default formatLong

Set 12-hour-clock format in the en-AU locale


(function (angular) {

  'use strict';

  angular.module('birdyard.users')

  .factory('presenceService', ['$q', 'firebaseService',

  function ($q, firebaseService) {

    // Private

    var $userCountRef = firebaseService.getRef('presence/count');

    function init() {

      var $ref = firebaseService.getRef('.info/connected');

      $ref.on("value", function(snap) {
        if (snap.val() === true) {
          $userCountRef.push(true);
        }
      });

      $userCountRef.onDisconnect().remove();
    }

    init();

    // Public

    var _presenceService = {};

    function _getUserCount () {
      return $q(function (resolve, reject) {
        $userCountRef.on('value', function ($snap) {
          var foo = $snap.numChildren();
          resolve(foo);
        }, reject);
      });
    }

    _presenceService.getUserCount = _getUserCount;

    return _presenceService;
  }]);

})(angular);

(function (angular) {

  'use strict';

  angular.module('birdyard.users')

  .factory('presenceService', ['$q', 'firebaseService',

  function ($q, firebaseService) {

    // Private

    var $userCountRef = firebaseService.getRef('presence/count');

    function init() {

      var $ref = firebaseService.getRef('.info/connected');

      $ref.on("value", function(snap) {
        if (snap.val() === true) {
          $userCountRef.push(true);
        }
      });

      $userCountRef.on('value', function () {
        $userCountRef.onDisconnect().remove();
      });
    }

    init();

    // Public

    var _presenceService = {};

    function _getUserCount () {
      return $q(function (resolve, reject) {
        $userCountRef.on('value', function ($snap) {
          var foo = $snap.numChildren();
          resolve(foo);
        }, reject);
      });
    }

    _presenceService.getUserCount = _getUserCount;

    return _presenceService;
  }]);

})(angular);

Fix broken unit test caused by presence service .onDisconnect() bug


import Resolver from 'ember/resolver';
import config from '../../config/environment';

const resolver = Resolver.create();

resolver.namespace = {
  modulePrefix: config.modulePrefix,
  podModulePrefix: config.podModulePrefix
};

export default resolver;

Adjust indentation spacing to pass linting


'use strict';

const KMSProvider = require('../../providers/kms');

/**
 * AWS-SDK gives out weirdly formatted keys, so make them
 * consistent with formatting across the rest of Tokend
 *
 * @param {Object} obj
 * @returns {Object}
 */
const normalizeKeys = (obj) => Object.keys(obj).reduce((acc, key) => {
  acc[key.toLowerCase()] = obj[key];

  return acc;
}, {});

/**
 * Route handler for KMS secrets
 * @returns {function}
 */
function KMS() {
  return (req, res, next) => {
    (new KMSProvider(req.body)).initialize().then((result) => {
      // I don't know how we'd get a falsy `result` but we need a method for handling it
      if (!result) {
        return;
      }

      let data = {};

      // Handle a case where there's no
      if (result.hasOwnProperty('data')) {
        data = normalizeKeys(result.data);
      }

      // Check for both undefined and null. `!= null` handles both cases.
      if (data.hasOwnProperty('plaintext')) { // eslint-disable-line eqeqeq
        data.plaintext = Buffer.from(data.plaintext, 'base64').toString();
      }

      res.json(data);
    }).catch(next);
  };
}

module.exports = KMS;

'use strict';

const KMSProvider = require('../../providers/kms');

/**
 * AWS-SDK gives out weirdly formatted keys, so make them
 * consistent with formatting across the rest of Tokend
 *
 * @param {Object} obj
 * @returns {Object}
 */
const normalizeKeys = (obj) => Object.keys(obj).reduce((acc, key) => {
  acc[key.toLowerCase()] = obj[key];

  return acc;
}, {});

/**
 * Route handler for KMS secrets
 * @returns {function}
 */
function KMS() {
  return (req, res, next) => {
    (new KMSProvider(req.body)).initialize().then((result) => {
      // I don't know how we'd get a falsy `result` but we need a method for handling it
      if (!result) {
        return res.json(result);
      }

      let data = {};

      // Handle a case where there's no
      if (result.hasOwnProperty('data')) {
        data = normalizeKeys(result.data);
      }

      // Check for both undefined and null. `!= null` handles both cases.
      if (data.hasOwnProperty('plaintext')) { // eslint-disable-line eqeqeq
        data.plaintext = Buffer.from(data.plaintext, 'base64').toString();
      }

      res.json(data);
    }).catch(next);
  };
}

module.exports = KMS;

Maintain the previous behavior where if there was no result set we'd attempt to pass it to res.json and have express handle the exception and bubble it up.


'use strict';

(function() {
    angular.module('ncsaas')
        .service('invoicesService', ['baseServiceClass', '$http', 'ENV', '$state', invoicesService]);

    function invoicesService(baseServiceClass, $http, ENV, $state) {
        /*jshint validthis: true */
        var ServiceClass = baseServiceClass.extend({
            init:function() {
                this._super();
                this.endpoint = '/invoices/';
            },
            sendNotification: function(invoice_uuid) {
                var url = ENV.apiEndpoint + 'api' + this.endpoint + invoice_uuid + '/send_notification/';
                return $http.post(url, {link_template: this.getTemplateUrl()});
            },
            getTemplateUrl: function() {
                var path = $state.href('organization.invoiceDetails', {uuid: 'TEMPLATE'});
                return location.origin + path.replace('TEMPLATE', '{uuid}');
            }
        });
        return new ServiceClass();
    }

})();

'use strict';

(function() {
    angular.module('ncsaas')
        .service('invoicesService', ['baseServiceClass', '$http', 'ENV', '$state', invoicesService]);

    function invoicesService(baseServiceClass, $http, ENV, $state) {
        /*jshint validthis: true */
        var ServiceClass = baseServiceClass.extend({
            init:function() {
                this._super();
                this.endpoint = '/invoices/';
            },
            sendNotification: function(invoice_uuid) {
                var url = ENV.apiEndpoint + 'api' + this.endpoint + invoice_uuid + '/send_notification/';
                return $http.post(url, {link_template: this.getTemplateUrl()});
            },
            getTemplateUrl: function() {
                var path = $state.href('organization.invoiceDetails', {invoiceUUID: 'TEMPLATE'});
                return location.origin + path.replace('TEMPLATE', '{uuid}');
            }
        });
        return new ServiceClass();
    }

})();

Fix template URL generation (WAL-141)


'use strict';

(function() {
  angular.module('ncsaas')
    .service('paymentsService', ['baseServiceClass', paymentsService]);

  function paymentsService(baseServiceClass) {
    /*jshint validthis: true */
    var ServiceClass = baseServiceClass.extend({
      init:function() {
        this._super();
        this.endpoint = '/payments/';
        this.filterByCustomer = false;
      }
    });
    return new ServiceClass();
  }

})();

'use strict';

(function() {
  angular.module('ncsaas')
    .service('paymentsService', ['baseServiceClass', paymentsService]);

  function paymentsService(baseServiceClass) {
    /*jshint validthis: true */
    var ServiceClass = baseServiceClass.extend({
      init:function() {
        this._super();
        this.endpoint = '/paypal-payments/';
        this.filterByCustomer = false;
      }
    });
    return new ServiceClass();
  }

})();

Fix PayPal payments API path (SAAS-1070)


'use strict';

var mongoose = require('mongoose');

/**
 * Define a visit model:
 *	-
 */

var VisitSchema = mongoose.Schema({
    visitor_id: String,
    session_id: String,
	domain_id: String,
    host: String,
    referer: String,
    ip_address: String,
    user_agent: String,
    events: [
      {
        timestamp: Date,
        event_type: String,
        attributes: [{name: String}],
        inner_html: String,
        classes: [{name:String}]
      }
    ]
	});

module.exports = mongoose.model('Visit', VisitSchema);

'use strict';

var mongoose = require('mongoose');

/**
 * Define a visit model:
 *	-
 */

var VisitSchema = mongoose.Schema({
    visitor_id: String,
    session_id: String,
	domain_id: String,
    host: String,
    referer: String,
    ip_address: String,
    user_agent: String,
    events: [
      {
        nodeName: String,
        page: String,
        timeStamp: Date,
        type: String,
        innerHTML: String,
        classes: [{name:String}]
      }
    ]
	});

module.exports = mongoose.model('Visit', VisitSchema);

Update the schema to match what we're passing in from the DOM


/* eslint-env node */
'use strict';

module.exports = {
  normalizeEntityName() {},

  afterInstall() {
    let pkg = require('../../package.json');

    return this.addPackageToProject('jquery-datetimepicker', pkg.devDependencies['jquery-datetimepicker']).then(() => {
      return this.addAddonToProject({ name: 'ember-cli-moment-shim', target: pkg.devDependencies['ember-cli-moment-shim'] });
    });
  }
};

/* eslint-env node */
'use strict';

const fs = require('fs');
const path = require('path');

module.exports = {
  normalizeEntityName() {},

  afterInstall() {
    let pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../../package.json'), 'utf8'));

    return this.addPackageToProject('jquery-datetimepicker', pkg.devDependencies['jquery-datetimepicker']).then(() => {
      return this.addAddonToProject({ name: 'ember-cli-moment-shim', target: pkg.devDependencies['ember-cli-moment-shim'] });
    });
  }
};

Revert "use require to read package.json"


import { Meteor } from 'meteor/meteor'
import { render } from 'react-dom'
import { createStore } from 'redux'

import { renderRoutes } from './routes.jsx'
import withProvider from '../both/withProvider'
import mainReducer from '../../api/redux/reducers'

Meteor.startup(() => {
  const store = createStore(mainReducer, window.__PRELOADED_STATE__)

  delete window.window.__PRELOADED_STATE__

  render(withProvider(store, renderRoutes()), document.getElementById('app'))
})

import { Meteor } from 'meteor/meteor'
import { render } from 'react-dom'
import { createStore } from 'redux'

import { renderRoutes } from './routes.jsx'
import withProvider from '../both/withProvider'
import mainReducer from '../../api/redux/reducers'

Meteor.startup(() => {
  const store = createStore(mainReducer, window.__PRELOADED_STATE__)

  delete window.__PRELOADED_STATE__

  render(withProvider(store, renderRoutes()), document.getElementById('app'))
})

Fix delete statement: remove duplicated word.


angular.module('mwUI.Form')
  .config(function(mwValidationMessagesProvider){
    mwValidationMessagesProvider.registerValidator(
      'customValidation',
      'mwErrorMessages.invalidInput'
    );
  })

  .directive('mwCustomErrorValidator', function (mwValidationMessages, i18n) {
    return {
      require: 'ngModel',
      scope: {
        isValid: '=mwIsValid',
        errorMsg: '@mwCustomErrorValidator'
      },
      link: function (scope, elm, attr, ngModel) {
        ngModel.$validators.customValidation = function () {
          var isValid = false;
          if(_.isUndefined(scope.isValid)){
            isValid = true;
          } else {
            isValid = scope.isValid;
          }
          return isValid;
        };

        scope.$watch('isValid', function(){
          mwValidationMessages.updateMessage(
            'customValidation',
            function(){
              if(scope.errorMsg && angular.isString(scope.errorMsg)){
                return scope.errorMsg;
              } else {
                return i18n.get('mwErrorMessages.invalidInput');
              }
            }
          );
          ngModel.$validate();
        });
      }
    };
  });

angular.module('mwUI.Form')
  .config(function (mwValidationMessagesProvider) {
    mwValidationMessagesProvider.registerValidator(
      'customValidation',
      'mwErrorMessages.invalidInput'
    );
  })

  .directive('mwCustomErrorValidator', function (mwValidationMessages, i18n) {
    return {
      require: 'ngModel',
      scope: {
        isValid: '=mwIsValid',
        errorMsg: '@mwCustomErrorValidator'
      },
      link: function (scope, elm, attr, ngModel) {
        ngModel.$validators.customValidation = function () {
          var isValid = false;
          if (_.isUndefined(scope.isValid)) {
            isValid = true;
          } else {
            isValid = scope.isValid;
          }
          return isValid;
        };

        scope.$watch('isValid', function () {
          mwValidationMessages.updateMessage(
            'customValidation',
            function () {
              if (scope.errorMsg && angular.isString(scope.errorMsg) && scope.errorMsg !== 'false') {
                return scope.errorMsg;
              } else if (angular.isUndefined(scope.errorMsg)) {
                return i18n.get('mwErrorMessages.invalidInput');
              } else {
                return '';
              }
            }
          );
          ngModel.$validate();
        });
      }
    };
  });

Make it possible to display no error message by setting attr errorMsg to false


const { isHaiku } = require('./validateHaiku');

class ChannelProcessor {
  constructor(channelID) {
    this.channelID = channelID;
    this.messages = [];
    this.onHaiku = () => {};
  }

  processMessage(newMessage) {
    this.messages.push(newMessage);
    while (this.messages.length > 3) {
      // remove old messages
      this.messages.shift();
    }

    if (this.messages.length === 3) {
      const lines = this.messages.map(message => message.content);
      if (isHaiku(lines)) {
        this.onHaiku(this.messages);
      }
    }
  }
}

exports.ChannelProcessor = ChannelProcessor;

const { isHaiku } = require('./validateHaiku');

class ChannelProcessor {
  constructor(channelID) {
    this.channelID = channelID;
    this.messages = [];
    this.onHaiku = () => {};
  }

  processMessage(newMessage) {
    this.messages.push(newMessage);
    while (this.messages.length > 3) {
      // remove old messages
      this.messages.shift();
    }

    if (this.messages.length === 3) {
      const lines = this.messages.map(message => message.content);
      if (isHaiku(lines)) {
        this.onHaiku(this.messages);
      }
    }
  }

  setOnHaikuFunction(func) {
    this.onHaiku = func;
  }
}

exports.ChannelProcessor = ChannelProcessor;

Add explicit function for changing in haiku functionality


export default {
  'circle': {
    'radius': 'r',
    'x': 'cx',
    'y': 'cy'
  }
};

/*
 This acts as both a way to normalize attribute names, but also acts
 as a white list for the supported properties.
 */
export default {
  'circle': {
    'radius': 'r',
    'x': 'cx',
    'y': 'cy'
  },
  'group': {
    'transform': 'transform'
  },
  'rect': {
    'x': 'x',
    'y': 'y',
    'height': 'height',
    'width': 'width',
    'fill': 'fill',
    'stroke': 'stroke',
    'stroke-width': 'stroke-width'
  },
  'line': {
    'x1': 'x1',
    'x2': 'x2',
    'y1': 'y1',
    'y2': 'y2',
    'stroke': 'stroke',
    'stroke-width': 'stroke-width'
  },
  'path': {
    'd': 'd',
    'stroke': 'stroke',
    'stroke-width': 'stroke-width',
    'fill': 'fill'
  },
  'text': {
    'x': 'x',
    'y': 'y'
  }
};

Make Attribute Map a white-list


module.exports = {

	showAlertDialog: function(options, callback) {
		setTimeout(function() {
			if (navigator.notification && navigator.notification.alert) {
				navigator.notification.alert(
					options.message,
					callback,
					options.title || 'Alert',
					options.buttonLabel || 'OK'
				);
			} else {
				var msg = options.title ? options.title + '\n\n' : '';
					msg += options.message;
				alert(msg);
				setTimeout(callback, 0);
			}
		}, 1);
	}

}

module.exports = {

	// ALERT
	// Options: (message, [title], [buttonName])
	showAlertDialog: function(options, alertCallback) {
		setTimeout(function() {
			if (navigator.notification && navigator.notification.alert) {
				navigator.notification.alert(
					options.message,
					alertCallback,
					options.title || 'Alert',
					options.buttonLabel || 'OK'
				);
			} else {
				var msg = options.title ? options.title + '\n\n' : '';
					msg += options.message;
				alert(msg);
				setTimeout(alertCallback, 0);
			}
		}, 1);
	},

	// CONFIRM
	// Options: (message, [title], [buttonLabels])
	// Callback: invoke with index of button pressed (1, 2, or 3) or when the dialog is dismissed without a button press (0)
	showConfirmDialog: function(options, confirmCallback) {
		setTimeout(function() {
			if (navigator.notification && navigator.notification.confirm) {
				navigator.notification.confirm(
					options.message,
					confirmCallback,
					options.title || 'Confirm',
					options.buttonLabels || ['OK', 'Cancel']
				);
			} else {
				var msg = options.title ? options.title + '\n\n' : '';
					msg += options.message;
				confirm(msg);
				setTimeout(confirmCallback, 0);
			}
		}, 1);
	}

}

Add confirm to dialogs mixin


Set classes on ef-list based on context


'use strict';

const BaseStep = require('./base-step');

module.exports = class WhitelistChoice extends BaseStep {
  start(state, install) {
    return new Promise((resolve, reject) => {
      this.subscriptions.add(install.on('did-whitelist', () => {
        resolve({whitelist: state.path});
      }));
      this.subscriptions.add(install.on('did-skip-whitelist', () =>
        resolve({whitelist: 'skipped'})));
    });
  }
};

'use strict';

const BaseStep = require('./base-step');

module.exports = class WhitelistChoice extends BaseStep {
  start(state, install) {
    return new Promise((resolve, reject) => {
      this.subscriptions.add(install.on('did-whitelist', () => {
        install.updateState({whitelist: state.path});
        resolve();
      }));
      this.subscriptions.add(install.on('did-skip-whitelist', () => {
        install.updateState({whitelist: 'skipped'});
        resolve();
      }));
    });
  }
};

Make sure whitelist choice is taken in account in state asap


Places.MapConfig = {
  APIKEY: process.env.MAP_API_KEY
}

Places.MapConfig = {
  APIKEY: process.env.REACT_APP_MAP_API_KEY
}

Make env variable compatible with create-react-app.


var keystone = require('keystone'),
	Types = keystone.Field.Types;

// Create model. Additional options allow menu name to be used what auto-generating URLs
var MailingList = new keystone.List('Mailing List', {
	autokey: { path: 'key', from: 'mailingList', unique: true },
	map: { name: 'mailingList' }
});

// Create fields
MailingList.add({
	mailingList: { type: Types.Text, label: 'mailing list', required: true, index: true, initial: true }
});

MailingList.relationship({ path: 'mailing-lists', ref: 'Prospective Parent or Family', refPath: 'prospectiveParentOrFamily' });

// Define default columns in the admin interface and register the model
MailingList.defaultColumns = 'mailingList';
MailingList.register();

var keystone = require('keystone'),
	Types = keystone.Field.Types;

// Create model. Additional options allow menu name to be used what auto-generating URLs
var MailingList = new keystone.List('Mailing List', {
	autokey: { path: 'key', from: 'mailingList', unique: true },
	map: { name: 'mailingList' }
});

// Create fields
MailingList.add({
	mailingList: { type: Types.Text, label: 'mailing list', required: true, index: true, initial: true },
	siteUserAttendees: { type: Types.Relationship, label: 'site users', ref: 'Site User', many: true, initial: true },
	socialWorkerAttendees: { type: Types.Relationship, label: 'social workers', ref: 'Social Worker', many: true, initial: true },
	prospectiveParentOrFamilyAttendees: { type: Types.Relationship, label: 'prospective parents or families', ref: 'Prospective Parent or Family', many: true, initial: true }
});

// Define default columns in the admin interface and register the model
MailingList.defaultColumns = 'mailingList';
MailingList.register();

Update the Mailing List model with relationship fields to select users that have signed up.


'use strict';

var util = require('util');
var openVeoAPI = require('@openveo/api');
var Database = openVeoAPI.Database;

function FakeSuccessDatabase() {
}

module.exports = FakeSuccessDatabase;
util.inherits(FakeSuccessDatabase, Database);

FakeSuccessDatabase.prototype.get = function(collection, criteria, projection, limit, callback) {
  callback(null, [{
    state: 7,
    files: []
  }]);
};

FakeSuccessDatabase.prototype.insert = function(collection, data, callback) {
  callback(null, (Array.isArray(data) ? data.length : 1), (Array.isArray(data) ? data : [data]));
};

FakeSuccessDatabase.prototype.update = function(collection, criteria, data, callback) {
  callback(null);
};

FakeSuccessDatabase.prototype.remove = function(collection, filter, callback) {
  callback(null, 1);
};

'use strict';

var util = require('util');
var openVeoAPI = require('@openveo/api');
var Database = openVeoAPI.Database;

function FakeSuccessDatabase() {
}

module.exports = FakeSuccessDatabase;
util.inherits(FakeSuccessDatabase, Database);

FakeSuccessDatabase.prototype.get = function(collection, criteria, projection, limit, callback) {
  var id = 1;
  if (criteria && criteria.id) {
    if (criteria.id.$in) id = criteria.id.$in;
    else id = criteria.id;
  }
  callback(null, [{
    id: id
  }]);
};

FakeSuccessDatabase.prototype.insert = function(collection, data, callback) {
  callback(null, (Array.isArray(data) ? data.length : 1), (Array.isArray(data) ? data : [data]));
};

FakeSuccessDatabase.prototype.update = function(collection, criteria, data, callback) {
  callback(null);
};

FakeSuccessDatabase.prototype.remove = function(collection, filter, callback) {
  callback(null, 1);
};

Change get fonction to retreive criteria


import React from 'react';
import ReactDOM from 'react-dom';
import { Simulate } from 'react-addons-test-utils';
import Checkbox from '../../../../src/components/checkbox.jsx';
import toggleableTests from '../toggleable/base.spec.js';
import { render } from '../../../helpers/rendering.js';
import $ from 'jquery';

describe('Checkbox', function() {
  toggleableTests(
    props => render(<Checkbox {...props} />),
    $component => Simulate.change($component.find(':checkbox')[0])
  );

  let $checkbox;
  const onToggle = () => { };

  describe('checked', function() {
    beforeEach(function() {
      const component = render(<Checkbox onToggle={onToggle} checked />);

      $checkbox = $(':checkbox', ReactDOM.findDOMNode(component));
    });

    it('should be checked', function() {
      expect($checkbox.is(':checked')).to.be.true;
    });
  });

  describe('unchecked', function() {
    beforeEach(function() {
      const component = render(<Checkbox onToggle={onToggle} />);

      $checkbox = $(':checkbox', ReactDOM.findDOMNode(component));
    });

    it('should be unchecked', function() {
      expect($checkbox.is(':checked')).to.be.false;
    });
  });
});

import React from 'react';
import ReactDOM from 'react-dom';
import { Simulate } from 'react-addons-test-utils';
import Checkbox from '../../../../src/components/checkbox.jsx';
import toggleableTests from '../toggleable/base.spec.js';
import { render } from '../../../helpers/rendering.js';
import $ from 'jquery';

describe('Checkbox', function() {
  toggleableTests(
    props => render(<Checkbox {...props} />),
    $component => Simulate.change($component.find(':checkbox')[0])
  );

  function renderCheckbox(props) {
    const component = render(<Checkbox onToggle={() => { }} {...props} />);

    return $(':checkbox', ReactDOM.findDOMNode(component));
  }

  it('should mark the checkbox when checked', function() {
    const $checkbox = renderCheckbox({ checked: true });

    expect($checkbox.is(':checked')).to.be.true;
  });

  it('should not mark the checkbox when unchecked', function() {
    const $checkbox = renderCheckbox({ checked: false });

    expect($checkbox.is(':checked')).to.be.false;
  });
});

Refactor <Checkbox> tests to reduce duplication


'use strict';

var sidebar = require('npm-documentation-pagelet').sidebar;

//
// Add some different styling to the sidebar to make it a table of contents.
//
module.exports = sidebar.extend({
  css: [
    sidebar.prototype.css,
    __dirname + '/toc.styl'
  ]
});

'use strict';

var sidebar = require('npm-documentation-pagelet').sidebar;

//
// Add some different styling to the sidebar to make it a table of contents.
//
module.exports = sidebar.extend({
  css: sidebar.prototype.css.concat([
    __dirname + '/toc.styl'
  ])
});

Revert "[fix] create new array by referencing sidebar styling"


// Karma configuration
// http://karma-runner.github.io/0.10/config/configuration-file.html

module.exports = function(config) {
  config.set({
    // base path, that will be used to resolve files and exclude
    basePath: '',

    // testing framework to use (jasmine/mocha/qunit/...)
    frameworks: ['ng-scenario'],

    // list of files / patterns to load in the browser
    files: [
      'test/e2e/**/*.js'
    ],

    // list of files / patterns to exclude
    exclude: [],

    // web server port
    port: 8081,

    // level of logging
    // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
    logLevel: config.LOG_INFO,

    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: false,

    // Start these browsers, currently available:
    // - Chrome
    // - ChromeCanary
    // - Firefox
    // - Opera
    // - Safari (only Mac)
    // - PhantomJS
    // - IE (only Windows)
    browsers: ['Chrome'],

    // Continuous Integration mode
    // if true, it capture browsers, run tests and exit
    singleRun: true,

    // Uncomment the following lines if you are using grunt's server to run the tests
    proxies: {
      '/': 'http://localhost:9001/'
    },
    // URL root prevent conflicts with the site root
    urlRoot: '_karma_'
  });
};

// Karma configuration
// http://karma-runner.github.io/0.10/config/configuration-file.html

module.exports = function(config) {
  config.set({
    // base path, that will be used to resolve files and exclude
    basePath: '',

    // testing framework to use (jasmine/mocha/qunit/...)
    frameworks: ['ng-scenario'],

    // list of files / patterns to load in the browser
    files: [
      'test/e2e/**/*.js'
    ],

    // list of files / patterns to exclude
    exclude: [],

    // web server port
    port: 8081,

    // level of logging
    // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
    logLevel: config.LOG_INFO,

    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: false,

    // Start these browsers, currently available:
    // - Chrome
    // - ChromeCanary
    // - Firefox
    // - Opera
    // - Safari (only Mac)
    // - PhantomJS
    // - IE (only Windows)
    browsers: ['PhantomJS'],

    // Continuous Integration mode
    // if true, it capture browsers, run tests and exit
    singleRun: true,

    // Uncomment the following lines if you are using grunt's server to run the tests
    proxies: {
      '/': 'http://localhost:9001/'
    },
    // URL root prevent conflicts with the site root
    urlRoot: '_karma_'
  });
};

Use headless phantomjs for grunt started e2e tests


$(function() {
  initHeartAnimation();
  initSubscribeForm();
});

function scrollToAnchor(element) {
  $('html,body').animate({scrollTop: $(element).offset().top}, 'slow');
}

function toggleMobileMenu(element) {
  $('#mobile-nav').toggle(800);
  $('#menu-toggle').toggleClass("ischecked");
}

$(function() {
  initHeartAnimation();
  initSubscribeForm();
});

function scrollToAnchor(element) {
  $('html,body').animate({scrollTop: $(element).offset().top}, 'slow');
}

function toggleMobileMenu(element) {
  $('#mobile-nav').slideToggle(400);
  $('#menu-toggle').toggleClass("ischecked");
}

Switch mobile navigation menu to slide down.


var os = require('os');
var path = require('path');
var paths = require('./paths');

// Guess proxy defined in the env
/*jshint camelcase: false*/
var proxy = process.env.HTTP_PROXY
    || process.env.http_proxy
    || null;

var httpsProxy = process.env.HTTPS_PROXY
    || process.env.https_proxy
    || process.env.HTTP_PROXY
    || process.env.http_proxy
    || null;
/*jshint camelcase: true*/

var defaults = {
    'cwd': process.cwd(),
    'directory': 'bower_components',
    'registry': 'https://bower.herokuapp.com',
    'shorthand-resolver': 'git://github.com/{{owner}}/{{package}}.git',
    'tmp': os.tmpdir ? os.tmpdir() : os.tmpDir(),
    'proxy': proxy,
    'https-proxy': httpsProxy,
    'timeout': 60000,
    'ca': { search: [] },
    'strict-ssl': true,
    'user-agent': 'node/' + process.version + ' ' + process.platform + ' ' + process.arch,
    'color': true,
    'interactive': false,
    'storage': {
        packages: path.join(paths.cache, 'packages'),
        links: path.join(paths.data, 'links'),
        completion: path.join(paths.data, 'completion'),
        registry: path.join(paths.cache, 'registry'),
        git: path.join(paths.data, 'git')
    }
};

module.exports = defaults;

var os = require('os');
var path = require('path');
var paths = require('./paths');

// Guess proxy defined in the env
/*jshint camelcase: false*/
var proxy = process.env.HTTP_PROXY
    || process.env.http_proxy
    || null;

var httpsProxy = process.env.HTTPS_PROXY
    || process.env.https_proxy
    || process.env.HTTP_PROXY
    || process.env.http_proxy
    || null;
/*jshint camelcase: true*/

var defaults = {
    'cwd': process.cwd(),
    'directory': 'bower_components',
    'registry': 'https://bower.herokuapp.com',
    'shorthand-resolver': 'git://github.com/{{owner}}/{{package}}.git',
    'tmp': os.tmpdir ? os.tmpdir() : os.tmpDir(),
    'proxy': proxy,
    'https-proxy': httpsProxy,
    'timeout': 30000,
    'ca': { search: [] },
    'strict-ssl': true,
    'user-agent': 'node/' + process.version + ' ' + process.platform + ' ' + process.arch,
    'color': true,
    'interactive': false,
    'storage': {
        packages: path.join(paths.cache, 'packages'),
        links: path.join(paths.data, 'links'),
        completion: path.join(paths.data, 'completion'),
        registry: path.join(paths.cache, 'registry'),
        git: path.join(paths.data, 'git')
    }
};

module.exports = defaults;

Set timeout default value to 30sec.


import React, { PropTypes } from "react";
import cx from 'classNames';

/**
 * Simple Card component that wraps a div around content with card styling.
 */
const Card = ({ children, ...props }) => {
  return
{children}
;
};

Card.propTypes = {
  /**
   * Any HTML element or React Component.
   * @examples
Some Text.

   */
  "children": PropTypes.node.isRequired
};

Card.defaultProps = { "children":
Some card text.
 };

Card.styleguide = { "category": "Layout", "index": "4.1" };

export default Card;

import React, { PropTypes } from "react";
import cx from 'classNames';

/**
 * Simple Card component that wraps a div around content with card styling.
 */
const Card = ({ children, className, ...props }) => {
  return
{children}
;
};

Card.propTypes = {
  /**
   * Any HTML element or React Component.
   * @examples
Some Text.

   */
  "children": PropTypes.node.isRequired,

  /**
   * Custom classnames prop
   */
   "className": PropTypes.string
};

Card.defaultProps = { "children":
Some card text.
 };

Card.styleguide = { "category": "Layout", "index": "4.1" };

export default Card;

Set classNames and props on card component and add classNames to proptypes.


'use strict';

var Node = require('../node');
var debug = require('../../util/debug')('analysis:georeference-country');

var TYPE = 'georeference-country';
var PARAMS = {
    source: Node.PARAM.NODE(Node.GEOMETRY.ANY),
    country_column: Node.PARAM.STRING()
};

var GeoreferenceCountry = Node.create(TYPE, PARAMS, {
    cache: true
});

module.exports = GeoreferenceCountry;
module.exports.TYPE = TYPE;
module.exports.PARAMS = PARAMS;

GeoreferenceCountry.prototype.sql = function() {
    var sql = queryTemplate({
        source: this.source.getQuery(),
        columns: this.source.getColumns(true).join(', '),
        country: this.country_column
    });

    debug(sql);

    return sql;
};

var queryTemplate = Node.template([
    'SELECT',
    '  {{=it.columns}},',
    '  cdb_dataservices_client.cdb_geocode_admin0_polygon({{=it.country}}) AS the_geom',
    'FROM ({{=it.source}}) AS _camshaft_georeference_admin_region_analysis'
 ].join('\n'));

'use strict';

var Node = require('../node');
var debug = require('../../util/debug')('analysis:georeference-country');

var TYPE = 'georeference-country';
var PARAMS = {
    source: Node.PARAM.NODE(Node.GEOMETRY.ANY),
    country_column: Node.PARAM.STRING()
};

var GeoreferenceCountry = Node.create(TYPE, PARAMS, {
    cache: true
});

module.exports = GeoreferenceCountry;
module.exports.TYPE = TYPE;
module.exports.PARAMS = PARAMS;

GeoreferenceCountry.prototype.sql = function() {
    var sql = queryTemplate({
        source: this.source.getQuery(),
        columns: this.source.getColumns(true).join(', '),
        country: this.country_column
    });

    debug(sql);

    return sql;
};

var queryTemplate = Node.template([
    'SELECT',
    '  {{=it.columns}},',
    '  cdb_dataservices_client.cdb_geocode_admin0_polygon({{=it.country}}) AS the_geom',
    'FROM ({{=it.source}}) AS _camshaft_georeference_city_analysis'
 ].join('\n'));

Fix alias in query template of georeference-city analysis


import '../../public/dist/styles/main.css'
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import store from './store'
import Root from './containers/Root'
import { addLocaleData } from 'react-intl'
import en from 'react-intl/locale-data/en'
import no from 'react-intl/locale-data/no'

const areIntlLocalesSupported = require('intl-locales-supported')

const localesMyAppSupports = [
  'en', 'nb'
]

function applyPolyfill () {
  const IntlPollyfill = require('intl')
  Intl.NumberFormat = IntlPollyfill.NumberFormat
  Intl.DateTimeFormat = IntlPollyfill.DateTimeFormat
  require('intl/locale-data/jsonp/en.js')
  require('intl/locale-data/jsonp/nb.js')
  return IntlPollyfill
}

if (global.Intl) {
  if (!areIntlLocalesSupported(localesMyAppSupports)) {
    applyPolyfill()
  }
} else {
  global.Intl = applyPolyfill()
}

addLocaleData(en)
addLocaleData(no)

ReactDOM.render(<Provider store={store}><Root /></Provider>, document.getElementById('app'))

import '../../public/dist/styles/main.css'
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import store from './store'
import Root from './containers/Root'
import { addLocaleData } from 'react-intl'
import en from 'react-intl/locale-data/en'
import no from 'react-intl/locale-data/no'

const areIntlLocalesSupported = require('intl-locales-supported')

const localesMyAppSupports = [
  'en', 'nb'
]

function applyPolyfill () {
  const IntlPolyfill = require('intl')
  Intl.NumberFormat = IntlPolyfill.NumberFormat
  Intl.DateTimeFormat = IntlPolyfill.DateTimeFormat
  require('intl/locale-data/jsonp/en.js')
  require('intl/locale-data/jsonp/nb.js')
  return IntlPolyfill
}

if (global.Intl) {
  if (!areIntlLocalesSupported(localesMyAppSupports)) {
    applyPolyfill()
  }
} else {
  global.Intl = applyPolyfill()
}

addLocaleData(en)
addLocaleData(no)

ReactDOM.render(<Provider store={store}><Root /></Provider>, document.getElementById('app'))

Fix typo in variable name


#!/usr/bin/env node
var N3 = require('../N3');
var fs = require('fs'),
    assert = require('assert');

if (process.argv.length !== 3)
  return console.error('Usage: N3Parser-perf.js filename');

var filename = process.argv[2];

var TEST = '- Parsing file ' + filename;
console.time(TEST);

var count = 0;
new N3.Parser().parse(fs.createReadStream(filename), function (error, triple) {
  assert(!error, error);
  if (triple) {
    count++;
  }
  else {
    console.timeEnd(TEST);
    console.log('* Triples parsed: ' + count);
    console.log('* Memory usage: ' + Math.round(process.memoryUsage().rss / 1024 / 1024) + 'MB');
  }
});

#!/usr/bin/env node
var N3 = require('../N3');
var fs = require('fs'),
    path = require('path'),
    assert = require('assert');

if (process.argv.length !== 3)
  return console.error('Usage: N3Parser-perf.js filename');

var filename = path.resolve(process.cwd(), process.argv[2]),
    base = 'file://' + filename;

var TEST = '- Parsing file ' + filename;
console.time(TEST);

var count = 0;
new N3.Parser({ documentIRI: base }).parse(fs.createReadStream(filename), function (error, triple) {
  assert(!error, error);
  if (triple)
    count++;
  else {
    console.timeEnd(TEST);
    console.log('* Triples parsed: ' + count);
    console.log('* Memory usage: ' + Math.round(process.memoryUsage().rss / 1024 / 1024) + 'MB');
  }
});

Use base in perf tests.


/*jshint esversion:6, node:true*/
'use strict';

const extend = require('gextend');

var DEFAULTS = {
    middleware: require('./middleware')
};

module.exports = function(options){

    return {
        init: function(context, config){

            config = extend({}, options, config);
            const express = require('express');
            var app = express();

            /*
             * Enable middleware to use context,
             * i.e. to publish an event system wide.
             * context.emit('web.request.createUser', user);
             */
            config.context = context;

            /*
             * Create a logger for our express app.
             * This can be trickled down to middleware
             * as well.
             */
            config.logger = context.getLogger(config.moduleid);

            //TODO: This should be pulled out of here and implmented by
            //configurator.
            setup(app, config);

            return app;
        }
    };
};

function setup(app, config){

    config = extend({}, DEFAULTS, config);

    //TODO: Should we have policies & middleware?

    var middleware = config.middleware;

    var use = config.middleware.use;

    use.map((id)=>{
        if(!middleware.hasOwnProperty(id)){
            missingMiddleware(config, id);
        }
        middleware[id](app, config);
    });
}

function missingMiddleware(config, middleware){
    let message = 'Sub app "' + config.moduleid + '" has no middleware "' + middleware + '"';
    throw new Error(message);
}

/*jshint esversion:6, node:true*/
'use strict';

const extend = require('gextend');

var DEFAULTS = {
    middleware: require('./middleware')
};

module.exports = function(options) {

    return {
        init: function(context, config) {

            config = extend({}, options, config);
            const express = require('express');
            var app = express();

            /*
             * Enable middleware to use context,
             * i.e. to publish an event system wide.
             * context.emit('web.request.createUser', user);
             */
            config.context = context;

            /*
             * Create a logger for our express app.
             * This can be trickled down to middleware
             * as well.
             */
            config.logger = context.getLogger(config.moduleid);

            //TODO: This should be pulled out of here and implmented by
            //configurator.
            setup(app, config);

            return app;
        }
    };
};

function setup(app, config) {

    config = extend({}, DEFAULTS, config);

    //TODO: Should we have policies & middleware?

    var middleware = config.middleware;

    var use = config.middleware.use;

    use.map((id) => {
        if (!middleware.hasOwnProperty(id)) {
            missingMiddleware(config, id);
        }
        middleware[id](app, config);
    });

    /*
     * Make sub app aware of it's own
     * module id.
     */
    app.appId = config.moduleid;
}

function missingMiddleware(config, middleware) {
    let message = 'Sub app "' + config.moduleid + '" has no middleware "' + middleware + '"';
    throw new Error(message);
}

Add appId asap, however we should figure out a better flow


var log = require("../utils/log")

module.exports = function exec (cmd, args, env, pipe, cb) {
  if (!cb) cb = pipe, pipe = false
  if (!cb) cb = env, env = process.env
  log(cmd+" "+args.map(JSON.stringify).join(" "), "exec")
  var stdio = process.binding("stdio")
    , fds = [ stdio.stdinFD || 0
            , stdio.stdoutFD || 1
            , stdio.stderrFD || 2
            ]
    , cp = require("child_process").spawn( cmd
                                         , args
                                         , env
                                         , pipe ? null : fds
                                         )
    , stdout = ""
    , stderr = ""
  cp.stdout && cp.stdout.on("data", function (chunk) {
    if (chunk) stdout += chunk
  })
  cp.stderr && cp.stderr.on("data", function (chunk) {
    if (chunk) stderr += chunk
  })
  cp.on("exit", function (code) {
    if (code) cb(new Error("`"+cmd+"` failed with "+code))
    else cb(null, code, stdout, stderr)
  })
  return cp
}

var log = require("../utils/log")

module.exports = function exec (cmd, args, env, pipe, cb) {
  if (!cb) cb = pipe, pipe = false
  if (!cb) cb = env, env = process.env
  log(cmd+" "+args.map(JSON.stringify).join(" "), "exec")
  var stdio = process.binding("stdio")
    , fds = [ stdio.stdinFD || 0
            , stdio.stdoutFD || 1
            , stdio.stderrFD || 2
            ]
    , cp = require("child_process").spawn( cmd
                                         , args
                                         , { env : env
                                           , customFDs : pipe ? null : fds
                                           }
                                         )
    , stdout = ""
    , stderr = ""
  cp.stdout && cp.stdout.on("data", function (chunk) {
    if (chunk) stdout += chunk
  })
  cp.stderr && cp.stderr.on("data", function (chunk) {
    if (chunk) stderr += chunk
  })
  cp.on("exit", function (code) {
    if (code) cb(new Error("`"+cmd+"` failed with "+code))
    else cb(null, code, stdout, stderr)
  })
  return cp
}

Use the new child_process.spawn API


goog.provide('urlbuilder');

goog.require('urlbuilder.Controller');

urlbuilder.init = function()
{
  var controller = new urlbuilder.Controller();
  controller.init();
};
goog.exportSymbol("boot", urlbuilder.init);

Add entry point for any build mode


import React from 'react';
import PropTypes from 'prop-types';
import shortid from 'shortid';
import styled, { keyframes } from 'styled-components';

const stagger = keyframes`
  to {
    opacity: 1;
    transform: translateY(0%);
  }
`;

const Segments = styled.div`
  font-family: 'Playfair Display', serif;
  font-size: 38px;
  overflow-y: hidden;
  line-height: 1.25;
`;

const Segment = styled.span`
  animation-duration: ${props => `${props.animationDuration}ms`};
  animation-name: ${stagger};
  animation-fill-mode: forwards;
  display: inline-block;
  opacity: 0;
  transform: translateY(100%);
  white-space: pre-wrap;
`;

const MotionTypography = props => (
  <Segments>
    {[...props.title].map((segment, index) => (
      <Segment
        key={shortid.generate()}
        animationDuration={props.animationDuration}
        style={{
          animationDelay: `${props.animationDelay * index}s`,
        }}
      >
        {segment}
      </Segment>
    ))}
  </Segments>
);

MotionTypography.propTypes = {
  animationDelay: PropTypes.number,
  animationDuration: PropTypes.number,
  title: PropTypes.string,
};

MotionTypography.defaultProps = {
  animationDelay: 0.025,
  animationDuration: 100,
  title: '',
};

export default MotionTypography;

import React from 'react';
import PropTypes from 'prop-types';
import shortid from 'shortid';
import styled, { keyframes } from 'styled-components';

const stagger = keyframes`
  to {
    opacity: 1;
    transform: translateY(0%);
  }
`;

const Segments = styled.div`
  font-family: 'Playfair Display', serif;
  font-size: 38px;
  line-height: 1.25;
`;

const Segment = styled.span`
  display: inline-block;
  overflow-y: hidden;

  span {
    animation-name: ${stagger};
    animation-fill-mode: forwards;
    display: inline-block;
    opacity: 0;
    transform: translateY(100%);
    white-space: pre-wrap;
  }
`;

const MotionTypography = props => {
  const styles = index => ({
    animationDuration: `${props.animationDuration}ms`,
    animationDelay: `${props.animationDelay * index}s`,
  });

  return (
    <Segments>
      {[...props.title].map((segment, index) => (
        <Segment key={shortid.generate()} style={styles(index)}>
          {segment}
        </Segment>
      ))}
    </Segments>
  );
};

MotionTypography.propTypes = {
  animationDelay: PropTypes.number,
  animationDuration: PropTypes.number,
  title: PropTypes.string,
};

MotionTypography.defaultProps = {
  animationDelay: 0.025,
  animationDuration: 100,
  title: '',
};

export default MotionTypography;

Update css and markup styles to suit new animation


'use strict';

var bytes = require('bytes');
var tpb = require('thepiratebay');
tpb.setUrl('http://thepiratebay.la');

module.exports = function (program, query, done) {
  program.log.debug('tpb: searching for %s', query);

  tpb.search(query, {
    category: 205,
    orderBy: 7
  }).then(function (results) {
    var torrents = results || [];
    program.log.debug('tpb: found %s torrents for %s', torrents.length, query);

    done(null, torrents.map(function (torrent) {
      return {
        title: torrent.name,
        size: Number(bytes(torrent.size.replace(/[i\s]/g, ''))),
        torrentLink: torrent.magnetLink,
        seeders: Number(torrent.seeders),
        leechers: Number(torrent.leechers),
        source: 'tpb'
      };
    }));
  }, function (e) {
    program.log.error('tpb', e);
    done(null, []);
  });
};

'use strict';

var bytes = require('bytes');
var tpb = require('thepiratebay');
tpb.setUrl('http://thepiratebay.se');

module.exports = function (program, query, done) {
  program.log.debug('tpb: searching for %s', query);

  tpb.search(query, {
    category: 205,
    orderBy: 7
  }).then(function (results) {
    var torrents = results || [];
    program.log.debug('tpb: found %s torrents for %s', torrents.length, query);

    done(null, torrents.map(function (torrent) {
      return {
        title: torrent.name,
        size: Number(bytes(torrent.size.replace(/[i\s]/g, ''))),
        torrentLink: torrent.magnetLink,
        seeders: Number(torrent.seeders),
        leechers: Number(torrent.leechers),
        source: 'tpb'
      };
    }));
  }, function (e) {
    program.log.error('tpb', e);
    done(null, []);
  });
};

Use currently responding TPB domain


var funcs = require('./funcs.js')

module.exports = function (dist, release) {
  var contents = funcs.replace(header, {dist: dist, release: release}) + '\n\n' +
    funcs.replace(pkgs, {pkgs: funcs.generatePkgStr(commonPkgs)})

  return contents
}

var header = 'FROM {{DIST}}:{{RELEASE}}\n' +
  'MAINTAINER William Blankenship <wblankenship@nodesource.com>'

var pkgs = 'RUN apt-get update \\\n' +
  ' && apt-get install -y --force-yes --no-install-recommends\\\n' +
  '{{PKGS}}' +
  ' && rm -rf /var/lib/apt/lists/*;'

var commonPkgs = [ 'apt-transport-https',
  'ssh-client',
  'build-essential',
  'curl',
  'ca-certificates',
  'git',
  'libicu-dev',
  '\'libicu5-*\'',
  'lsb-release',
  'python-all',
  'rlwrap']

var funcs = require('./funcs.js')

module.exports = function (dist, release) {
  var contents = funcs.replace(header, {dist: dist, release: release}) + '\n\n' +
    funcs.replace(pkgs, {pkgs: funcs.generatePkgStr(commonPkgs)})

  return contents
}

var header = 'FROM {{DIST}}:{{RELEASE}}\n' +
  'MAINTAINER William Blankenship <wblankenship@nodesource.com>'

var pkgs = 'RUN apt-get update \\\n' +
  ' && apt-get install -y --force-yes --no-install-recommends\\\n' +
  '{{PKGS}}' +
  ' && rm -rf /var/lib/apt/lists/*;'

var commonPkgs = [ 'apt-transport-https',
  'ssh-client',
  'build-essential',
  'curl',
  'ca-certificates',
  'git',
  'libicu-dev',
  '\'libicu[0-9][0-9].*\'',
  'lsb-release',
  'python-all',
  'rlwrap']

Support 4X releases of libicu


import webpack from 'webpack';
import path from 'path';
import CopyWebpackPlugin from 'copy-webpack-plugin';
import autoprefixer from 'autoprefixer';

import loaders from './webpack.loaders';
import config from './config';

const HOST = config.server.host
const PORT = config.server.port

export default {
	entry: [
        'webpack-hot-middleware/client',
        'webpack/hot/dev-server',
		`./src/client/index.jsx`
	],
	devtool: process.env.WEBPACK_DEVTOOL || 'source-map',
	output: {
        path: "/",
        publicPath: "http://" +HOST+ ":" +PORT+ "/",
		filename: 'app.bundle.js'
	},
	resolve: {
		extensions: ['', '.js', '.jsx', '.css', '.scss', '.sass']
	},
	module: {
		loaders
	},
	devServer: {
		contentBase: "./dist",
			noInfo: false, //  --no-info option
			hot: true,
			inline: true,
			port: PORT,
			host: HOST
		},
	plugins: [
		new webpack.NoErrorsPlugin(),
		new webpack.HotModuleReplacementPlugin(),
		new webpack.ProvidePlugin({
			$: "jquery",
			jQuery: "jquery",
			"window.jQuery": "jquery"
		})
	],
	postcss: function () {
        return [autoprefixer];
    }
};

import webpack from 'webpack';
import path from 'path';
import CopyWebpackPlugin from 'copy-webpack-plugin';
import autoprefixer from 'autoprefixer';

import loaders from './webpack.loaders';
import config from './config';

const HOST = config.server.host
const PORT = config.server.port

export default {
	entry: [
        'webpack-hot-middleware/client',
        'webpack/hot/dev-server',
		`./src/client/index.jsx`
	],
	devtool: process.env.WEBPACK_DEVTOOL || '#eval-source-map',
	output: {
        path: "/",
        publicPath: "http://" +HOST+ ":" +PORT+ "/",
		filename: 'app.bundle.js'
	},
	resolve: {
		extensions: ['', '.js', '.jsx', '.css', '.scss', '.sass']
	},
	module: {
		loaders
	},
	devServer: {
		contentBase: "./dist",
			noInfo: false, //  --no-info option
			hot: true,
			inline: true,
			port: PORT,
			host: HOST
		},
	plugins: [
		new webpack.NoErrorsPlugin(),
		new webpack.HotModuleReplacementPlugin(),
		new webpack.ProvidePlugin({
			$: "jquery",
			jQuery: "jquery",
			"window.jQuery": "jquery"
		})
	],
	postcss: function () {
        return [autoprefixer];
    }
};

Add eval sourcemap for increased recompile speed


// 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.

/**
 * TestFixture for browser options WebUI testing.
 * @extends {testing.Test}
 * @constructor
 **/
function BrowserOptionsWebUITest() {}

BrowserOptionsWebUITest.prototype = {
  __proto__: testing.Test.prototype,

  /**
   * Browse to browser options.
   **/
  browsePreload: 'chrome://chrome/settings/',
};

// Test opening the browser options has correct location.
TEST_F('BrowserOptionsWebUITest', 'testOpenBrowserOptions', function() {
  assertEquals(this.browsePreload, document.location.href);
});

// 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.

/**
 * TestFixture for browser options WebUI testing.
 * @extends {testing.Test}
 * @constructor
 **/
function BrowserOptionsWebUITest() {}

BrowserOptionsWebUITest.prototype = {
  __proto__: testing.Test.prototype,

  /**
   * Browse to browser options.
   **/
  browsePreload: 'chrome://chrome/settings/',
};

// Test opening the browser options has correct location.
// Times out on Mac debug only. See http://crbug.com/121030
GEN('#if defined(OS_MACOSX) && !defined(NDEBUG)');
GEN('#define MAYBE_testOpenBrowserOptions ' +
    'DISABLED_testOpenBrowserOptions');
GEN('#else');
GEN('#define MAYBE_testOpenBrowserOptions testOpenBrowserOptions');
GEN('#endif  // defined(OS_MACOSX)');
TEST_F('BrowserOptionsWebUITest', 'MAYBE_testOpenBrowserOptions', function() {
  assertEquals(this.browsePreload, document.location.href);
});

Disable BrowserOptionsWebUITest.testOpenBrowserOptions on Mac debug bots.


$('#can_embed_form').load(function(){
  console.log('Form loaded.');
  var phoneField = $('#phone');
  var zipField = $('#form-zip_code');
});

$(document).on('can_embed_loaded', function() {
  console.log('Form loaded.');
  phoneField = $('#phone');
  zipField = $('#form-zip_code');
});

Fix field var declarations & onload functionality


export default function(appModule) {
  appModule.config(function($stateProvider, $locationProvider) {
    'ngInject';

    $stateProvider
      .state({
        name: 'stopwatch',
        url: '/stopwatch',
        component: 'atStopwatch'
      })
      .state({
        name: 'timer',
        url: '/timer',
        component: 'atTimer'
      })
      .state({
        name: 'about',
        url: '/about',
        component: 'atAbout'
      });

    $locationProvider.html5Mode(true);
  });
}

export default function(appModule) {
  appModule.config(function(
        $stateProvider, $locationProvider, $urlRouterProvider
      ) {
    'ngInject';

    $stateProvider
      .state({
        name: 'stopwatch',
        url: '/stopwatch',
        component: 'atStopwatch'
      })
      .state({
        name: 'timer',
        url: '/timer',
        component: 'atTimer'
      })
      .state({
        name: 'about',
        url: '/about',
        component: 'atAbout'
      });

    $urlRouterProvider.otherwise('/stopwatch');
    $locationProvider.html5Mode(true);
  });
}

Make stopwatch the default route


browser.on("init", function () {
    "use strict";

    // Show the refresh button
    this.showRefresh = () => {
        this.stopButton.classList.remove("stopButton");
        this.stopButton.classList.add("refreshButton");
        this.stopButton.title = "Refresh the page";
    };

    // Show the stop button
    this.showStop = () => {
        this.stopButton.classList.add("stopButton");
        this.stopButton.classList.remove("refreshButton");
        this.stopButton.title = "Stop loading";
    };

    // Listen for the stop/refresh button to stop navigation/refresh the page
    this.stopButton.addEventListener("click", () => {
        if (this.loading) {
            this.webview.stop();
            this.showProgressRing(false);
            this.showRefresh();
        }
        else {
            this.webview.refresh();
        }
    });

    // Update the navigation state
    this.updateNavState = () => {
        this.backButton.disabled = !this.webview.canGoBack;
        this.forwardButton.disabled = !this.webview.canGoForward;
    };

    // Listen for the back button to navigate backwards
    this.backButton.addEventListener("click", () => {
        if (this.webview.canGoBack) {
            this.webview.goBack();
        }
    });

    // Listen for the forward button to navigate forwards
    this.forwardButton.addEventListener("click", () => {
        if (this.webview.canGoForward) {
            this.webview.goForward();
        }
    });
});

browser.on("init", function () {
    "use strict";

    // Show the refresh button
    this.showRefresh = () => {
        this.stopButton.classList.remove("stopButton");
        this.stopButton.classList.add("refreshButton");
        this.stopButton.title = "Refresh the page";
    };

    // Show the stop button
    this.showStop = () => {
        this.stopButton.classList.add("stopButton");
        this.stopButton.classList.remove("refreshButton");
        this.stopButton.title = "Stop loading";
    };

    // Listen for the stop/refresh button to stop navigation/refresh the page
    this.stopButton.addEventListener("click", () => {
        if (this.loading) {
            this.webview.stop();
            this.showProgressRing(false);
            this.showRefresh();
        }
        else {
            this.webview.refresh();
        }
    });

    // Update the navigation state
    this.updateNavState = () => {
        this.backButton.disabled = !this.webview.canGoBack;
        this.forwardButton.disabled = !this.webview.canGoForward;
    };

    // Listen for the back button to navigate backwards
    this.backButton.addEventListener("click", () => this.webview.goBack());

    // Listen for the forward button to navigate forwards
    this.forwardButton.addEventListener("click", () => this.webview.goForward());
});

Remove unneeded nav checks for forward/back buttons.


import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import Search from './search'
import Results from './results'

const Container = styled.div`
  line-height: 120%;
  margin: 10px 20px 0 20px;
  padding: 20px;

  @media (max-width: 330px) {
    margin: 10px 10px 0 10px;
    padding: 10px;
  }
`

const App = props => {
  return (
    <Container>
      <Search
        langs={props.allLangs}
        onLangSelectionChange={props.onLangSelectionChange}
        onSearch={props.onSearch} />
      <Results langLinks={props.langLinks} loading={props.loading} />
    </Container>
  )
}

App.propTypes = {
  allLangs: PropTypes.array.isRequired,
  langLinks: PropTypes.array.isRequired,
  loading: PropTypes.bool.isRequired,
  onLangSelectionChange: PropTypes.func.isRequired,
  onSearch: PropTypes.func.isRequired
}

export default App

import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import Search from './search'
import Results from './results'

const Container = styled.div`
  line-height: 150%;
  margin: 10px 20px 0 20px;
  padding: 20px;

  @media (max-width: 330px) {
    margin: 10px 10px 0 10px;
    padding: 10px;
  }
`

const App = props => {
  return (
    <Container>
      <Search
        langs={props.allLangs}
        onLangSelectionChange={props.onLangSelectionChange}
        onSearch={props.onSearch} />
      <Results langLinks={props.langLinks} loading={props.loading} />
    </Container>
  )
}

App.propTypes = {
  allLangs: PropTypes.array.isRequired,
  langLinks: PropTypes.array.isRequired,
  loading: PropTypes.bool.isRequired,
  onLangSelectionChange: PropTypes.func.isRequired,
  onSearch: PropTypes.func.isRequired
}

export default App

Update base line-height to 150%


import initialState from './initialState';
import * as types from '../constants/actionTypes';
import { setDotsFromStartingDots } from '../utils/categoryStarter';

export default (state = initialState.character.backgrounds, action) => {
  switch (action.type) {
    case types.SET_STARTING_DOTS:
      const { category, trait, startingDots } = action.payload;

      if (category !== 'backgrounds') {
        return state;
      }

      return setDotsFromStartingDots(state, trait, startingDots);
    default:
      return state;
  }
};

import initialState from './initialState';
import * as types from '../constants/actionTypes';
import { setDotsFromStartingDots } from '../utils/categoryStarter';
import { addPurchasedDot } from '../utils/categoryPurchaser';

export default (state = initialState.character.backgrounds, action) => {
  let category, trait, startingDots;

  switch (action.type) {
    case types.SET_STARTING_DOTS:
      ({ category, trait, startingDots } = action.payload);

      if (category !== 'backgrounds') {
        return state;
      }

      return setDotsFromStartingDots(state, trait, startingDots);

    case types.PURCHASE_DOT:
      ({ category, trait } = action.payload);

      if (category !== 'backgrounds') {
        return state;
      }

      return addPurchasedDot(state, trait);
    default:
      return state;
  }
};

Update backgrounds reducer to purchase dots


var compression = require("compression");
var express     = require("express");

//  Initialize Express
var app = express();

//  Treat "/foo" and "/Foo" as different URLs
app.set("case sensitive routing", true);

//  Treat "/foo" and "/foo/" as different URLs
app.set("strict routing", true);

//  Default to port 3000
app.set("port", process.env.PORT || 3000);

//  Compress all requests
app.use(compression());

//  Handle 404 errors
app.use(function(req, res) {
    res.type("text/plain");
    res.status(404);
    res.send("404 - Not Found");
});

//  Handle 500 errors
app.use(function(err, req, res, next) {
    console.error(err.stack);
    res.type("text/plain");
    res.status(500);
    res.send("500 - Server Error");
});

app.listen(app.get("port"), function() {
    console.log("Express started on http://localhost:" + app.get("port") + "; press Ctrl-C to terminate.");
});

var compression = require("compression");
var express     = require("express");
var handlebars  = require("express-handlebars");

//	Initialize Express
var app = express();

//  Treat "/foo" and "/Foo" as different URLs
app.set("case sensitive routing", true);

//  Treat "/foo" and "/foo/" as different URLs
app.set("strict routing", true);

//  Default to port 3000
app.set("port", process.env.PORT || 3000);

//  Compress all requests
app.use(compression());

//  Set Handlebars as the default template language
app.engine("handlebars", handlebars({defaultLayout: "main"}));
app.set("view engine", "handlebars");

//  Handle 404 errors
app.use(function(req, res) {
    res.type("text/plain");
    res.status(404);
    res.send("404 - Not Found");
});

//  Handle 500 errors
app.use(function(err, req, res, next) {
    console.error(err.stack);
    res.type("text/plain");
    res.status(500);
    res.send("500 - Server Error");
});

app.listen(app.get("port"), function() {
    console.log("Express started on http://localhost:" + app.get("port") + "; press Ctrl-C to terminate.");
});

Set Handlebars as the default template language in the main app file.


const puppeteer = require("puppeteer-core");

async function main () {
  const browser = await puppeteer.launch({
    headless: false,
    executablePath: "/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome",
    args: [ '--proxy-server=http://127.0.0.1:8080' ],
  });
  const page = await browser.newPage();
  await page.goto('https://streetvoice.com/megafeihong');
  page.once("close", async () => {
     await browser.close();
     console.log("\nDone!");
  });
}

main();

const puppeteer = require("puppeteer-core");

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function main () {
  // Wait a second to give mitmproxy time to start up
  await sleep(1000);

  const browser = await puppeteer.launch({
    headless: false,
    executablePath: "/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome",
    args: [ '--proxy-server=http://127.0.0.1:8080' ],
  });
  const page = await browser.newPage();
  await page.goto('https://streetvoice.com/megafeihong');
  page.once("close", async () => {
     await browser.close();
     console.log("\nDone!");
  });
}

main();

Add a small delay to startup of puppeteer


// / <reference types="Cypress" />

describe('The Locations Hub', () => {
  before(() => {
    cy.visit('/#/locations');
  });

  it('successfully loads', () => {
    cy.get('header');
    cy.get('main');
    cy.get('footer');
    cy.get('h1');
  });

  it('has a header with title and description', () => {
    cy.get('h1').should('contain', 'Locations');
    cy.get('.inpage__introduction').should(
      'contain',
      'Lorem ipsum dolor sit amet'
    );
  });

  it('has a results section with a list of location cards', () => {
    cy.get('.results-summary')
      .invoke('text')
      .should('match', /A total of \d+ locations were found/);

    cy.get('.card').should('have.length', 15);
    cy.get('.pagination').should('exist');
  });

  it('has some filters with dropdown menus', () => {
    cy.get('.filters').should('exist');

    cy.get('[title="type__filter"]').click();
    cy.get('.drop__menu-item').first().click();

    cy.get('.button--filter-pill').should('exist');

    cy.get('button').contains('Clear Filters').should('exist');
  });
});

// / <reference types="Cypress" />

describe('The Locations Hub', () => {
  before(() => {
    cy.visit('/#/locations');
  });

  it('successfully loads', () => {
    cy.get('header');
    cy.get('main');
    cy.get('footer');
    cy.get('h1');
  });

  it('has a header with title and description', () => {
    cy.get('h1').should('contain', 'Locations');
    cy.get('.inpage__introduction').should(
      'contain',
      'Lorem ipsum dolor sit amet'
    );
  });

  it('has a results section with a list of location cards', () => {
    cy.get('.results-summary')
      .invoke('text')
      .should('match', /A total of \d+ locations were found/);

    cy.get('.card').should('have.length', 15);
    cy.get('.pagination').should('exist');
  });

  it('has some filters with dropdown menus', () => {
    cy.get('.filters').should('exist');

    cy.get('[title="type__filter"]').click();
    cy.get('.drop__menu-item').first().click();

    cy.get('.button--filter-pill').should('exist');
    cy.get('button').contains('Clear Filters').should('exist');

    cy.get('.button--filter-pill').click();
    cy.get('.button--filter-pill').should('not.exist');
  });
});

Add test for filter removal


'use strict';

var fs = require('fs'),
    words = require('../data/spache.json');

fs.writeFileSync('Supported-words.md',
    'Supported Words\n' +
    '=================\n' +
    '\n' +

    words.map(function (word) {
        return '* “' + word + '”';
    }).join(';\n') +

    '.\n'
);

Add strict mode to support generation


'use strict'

module.exports = {
  up: function (queryInterface, Sequelize) {
    return queryInterface.createTable('users', {
      // it will add the id column automatically
      name: Sequelize.STRING,
      createdAt: Sequelize.DATE,
      updatedAt: Sequelize.DATE
    })
  },

  down: function (queryInterface, Sequelize) {
    return queryInterface.dropTable('users')
  }
}

'use strict'

module.exports = {
  up: function (queryInterface, Sequelize) {
    return queryInterface.createTable('users', {
      id: {
        type: Sequelize.INTEGER,
        primaryKey: true,
        autoIncrement: true
      },
      name: Sequelize.STRING,
      createdAt: Sequelize.DATE,
      updatedAt: Sequelize.DATE
    })
  },

  down: function (queryInterface, Sequelize) {
    return queryInterface.dropTable('users')
  }
}

Add id column with auto increment


;(function(Agent){

  var waitForMarionetteLoad = function(object, callback) {
    Agent.onObjectAndPropertiesSetted(
      object,
      'Marionette', ['Application', 'Module'],
      callback
    );
  }

  var waitForBackboneLoad = function(callback) {
    Agent.onObjectAndPropertiesSetted(
      window,
      'Backbone', ['View', 'Model', 'Router', 'Collection', '$'],
      callback
    );
  }

  Agent.patchWindow = function(patchBackbone, patchMarionette) {

    var loadedBackbone;

    var onBackboneLoaded = function() {
      debug.log('Backbone loaded');

      loadedBackbone = window.Backbone;
      Agent.patchBackbone(loadedBackbone);
      waitForMarionetteLoad(loadedBackbone, onMarionetteLoaded);
    };

    var onMarionetteLoaded = function() {
      debug.log('marionette loaded');

      var Marionette = window.Marionette || loadedBackbone.Marionette;
      if (loadedBackbone) {
        Agent.patchMarionette(loadedBackbone, Marionette);
      }
    };

    waitForBackboneLoad(onBackboneLoaded);
    waitForMarionetteLoad(window, onMarionetteLoaded);
  };

}(Agent));

;(function(Agent){

  var waitForMarionetteLoad = function(object, callback) {
    Agent.onObjectAndPropertiesSetted(
      object,
      'Marionette', ['VERSION'],
      function (Marionette) {
        var mnVersion = Marionette.VERSION && Marionette.VERSION[0];
        if (mnVersion === '2') {
          Agent.onceSetted(Marionette, 'Module', callback);
        } else {
          callback();
        }
      }
    );
  }

  var waitForBackboneLoad = function(callback) {
    Agent.onObjectAndPropertiesSetted(
      window,
      'Backbone', ['View', 'Model', 'Router', 'Collection', '$'],
      callback
    );
  }

  Agent.patchWindow = function(patchBackbone, patchMarionette) {

    var loadedBackbone;

    var onBackboneLoaded = function() {
      debug.log('Backbone loaded');

      loadedBackbone = window.Backbone;
      Agent.patchBackbone(loadedBackbone);
      waitForMarionetteLoad(loadedBackbone, onMarionetteLoaded);
    };

    var onMarionetteLoaded = function() {
      debug.log('marionette loaded');

      var Marionette = window.Marionette || loadedBackbone.Marionette;
      if (loadedBackbone) {
        Agent.patchMarionette(loadedBackbone, Marionette);
      }
    };

    waitForBackboneLoad(onBackboneLoaded);
    waitForMarionetteLoad(window, onMarionetteLoaded);
  };

}(Agent));

Rework how Marionette is detected when using script tag


const puppeteer = require('puppeteer')
const path = require('path')

const main = async () => {
  const browser = await puppeteer.launch()
  const page = await browser.newPage()
  await page.setViewport({ width: 1500, height: 500 })
  try {
    await page.goto('http://localhost:8080/twitter.html', {
      waitUntil: 'networkidle'
    })
    await page.screenshot({ path: 'public/twitterHeader.png' })
  } catch (e) {
    console.error(e)
  }

  await browser.close()
}

main()

const puppeteer = require('puppeteer')
const path = require('path')

const URL =
  process.env.TRAVIS == true
    ? 'http://localhost:8080/twitter.html'
    : 'https://jsonnull.com/twitter.html'

const main = async () => {
  const browser = await puppeteer.launch({
    args: ['--no-sandbox', '--disable-setuid-sandbox']
  })
  const page = await browser.newPage()
  await page.setViewport({ width: 1500, height: 500 })
  try {
    await page.goto(URL, {
      waitUntil: 'networkidle'
    })
    await page.screenshot({ path: 'public/twitterHeader.png' })
  } catch (e) {
    console.error(e)
  }

  await browser.close()
}

main()

Update server script to work on CI and production environments.


class Controller {
  constructor () {
    this.listeners = []
    this.key = null

    window.addEventListener('keydown', e => {
      if (e.keyCode === this.key)
        return
      this.key = e.keyCode
      this.listeners.forEach(listener => {
        listener && listener(Controller.KEYS[this.key])
      })
    })
    window.addEventListener('keyup', e => {
      this.key = null
    })
  }

  onKey (listener) {
    var listenerIndex = this.listeners.length
    this.listeners.push(listener)

    return function () {
      this.listeners[listenerIndex] = null
    }
  }
}

Controller.KEYS = {
  37: {move: 'left'},
  38: {move: 'up'},
  39: {move: 'right'},
  40: {move: 'down'},
  65: {rotate: 'left'},
  83: {rotate: 'right'},
  32: {pause: true}
}

class Controller {
  constructor () {
    this.listeners = []
    this.key = null

    window.addEventListener('keydown', e => {
      if (e.keyCode === this.key)
        return
      this.key = e.keyCode
      this.listeners.forEach(listener => {
        listener && Controller.KEYS[this.key] && listener(Controller.KEYS[this.key])
      })
    })
    window.addEventListener('keyup', e => {
      this.key = null
    })
  }

  onKey (listener) {
    var listenerIndex = this.listeners.length
    this.listeners.push(listener)

    return function () {
      this.listeners[listenerIndex] = null
    }
  }
}

Controller.KEYS = {
  37: {move: 'left'},
  38: {move: 'up'},
  39: {move: 'right'},
  40: {move: 'down'},
  65: {rotate: 'left'},
  83: {rotate: 'right'},
  32: {pause: true}
}

Fix controller on invalid key press


import React from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router';
import Loader from '../Loader';
import './Header.scss';

export const Header = ({ isFetching }) => (
  <div>
    <nav className='navbar navbar-inverse container-fluid'>
      <Link to='/' className='brand-title navbar-brand'>Tiedonohjausjärjestelmä Alpha v0.1.4</Link>
    </nav>
    {isFetching &&
    <Loader />
    }
  </div>
);

Header.propTypes = {
  isFetching: React.PropTypes.bool.isRequired
};

const mapStateToProps = (state) => {
  return {
    isFetching: state.home.isFetching || state.navigation.isFetching || state.selectedTOS.isFetching
  };
};

export default connect(mapStateToProps)(Header);

import React from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router';
import Loader from '../Loader';
import './Header.scss';

export const Header = ({ isFetching }) => (
  <div>
    <nav className='navbar navbar-inverse container-fluid'>
      <Link to='/' className='brand-title navbar-brand'>Tiedonohjausjärjestelmä Alpha v0.1.4</Link>
    </nav>
    {isFetching &&
    <Loader />
    }
  </div>
);

Header.propTypes = {
  isFetching: React.PropTypes.bool
};

const mapStateToProps = (state) => {
  return {
    isFetching: state.home.isFetching || state.navigation.isFetching || state.selectedTOS.isFetching
  };
};

export default connect(mapStateToProps)(Header);

Remove isRequired from header propTypes


import { PolymerElement } from '@polymer/polymer/polymer-element';

class BnbAnalytics extends PolymerElement {
  static get is() { return 'bnb-analytics'; }

  static get properties() {
    return {
      key: {
        type: String,
        observer: '_keyChanged',
      },
    };
  }

  sendPath(pathname) {
    ga('send', 'pageview', pathname);
  }

  _keyChanged(key) {
    if (key) {
      ga('create', key, 'auto', {
        appName: 'Botnbot',
      });
    }
  }
}
window.customElements.define(BnbAnalytics.is, BnbAnalytics);

/* eslint-disable */
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');

import { PolymerElement } from '@polymer/polymer/polymer-element';

class BnbAnalytics extends PolymerElement {
  static get properties() {
    return {
      key: {
        type: String,
        observer: '_keyChanged',
      },
    };
  }

  sendPath(pathname) {
    ga('send', 'pageview', pathname);
  }

  _keyChanged(key) {
    if (key) {
      ga('create', key, 'auto', {
        appName: 'Botnbot',
      });
    }
  }
}
window.customElements.define('bnb-analytics', BnbAnalytics);

/* eslint-disable */
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');

Refactor : remove get is function


"use strict";

import placeToEat from "application/domain/placeToEat";

export default function inMemoryPlacesRepo() {
    let places = [
        placeToEat({ id: 100, name: "Thai House", cuisine: "Thai" }),
        placeToEat({ id: 101, name: "Pizza Plus", cuisine: "American" }),
        placeToEat({ id: 102, name: "Mura Sushi", cuisine: "Japanese" })
    ];

    let fetchAll = () => places;

    let findById = (id) => {
        let _place;
        places.some((place) => {
            if (place.id === id) {
                _place = place;
                return true;
            }
        });

        return _place;
    };

    let fetchRandom = () => {
        let min = 0;
        let max = places.length;

        let idx = Math.floor(Math.random() * (max - min) + min);
        return places[idx];
    };

    return { fetchAll, findById, fetchRandom };
}

"use strict";

import placeToEat from "application/domain/placeToEat";

export default function inMemoryPlacesRepo() {
    let places = [
        placeToEat({ id: 100, name: "Thai House", cuisine: "Thai" }),
        placeToEat({ id: 101, name: "Pizza Plus", cuisine: "American" }),
        placeToEat({ id: 102, name: "Mura Sushi", cuisine: "Japanese" })
    ];

    let fetchAll = () => places;

    let findById = (id) => {
        let _place;
        places.some((place) => {
            if (place.id === id) {
                _place = place;
                return true;
            }
        });

        return _place;
    };

    let fetchRandom = () => {
        let min = 0;
        let max = places.length;

        let idx = Math.floor(Math.random() * (max - min) + min);
        return places[idx];
    };

    let add = (placeToEat) => {
        places.push(placeToEat);
    };

    return { add, fetchAll, findById, fetchRandom };
}

Add an `add` method to places repo.


/*
 * Appcelerator Titanium Mobile
 * Copyright (c) 2011-2012 by Appcelerator, Inc. All Rights Reserved.
 * Licensed under the terms of the Apache Public License
 * Please see the LICENSE included with this distribution for details.
 */

module.exports = new function() {
	var finish;
	var valueOf;
	this.init = function(testUtils) {
		finish = testUtils.finish;
		valueOf = testUtils.valueOf;
	}

	this.name = "jss";
	this.tests = [
		{name: "platform_jss_dirs"}
	]

	this.platform_jss_dirs = function(testRun) {
		var test = Ti.UI.createView({ id: "test" });
		valueOf(testRun, test).shouldNotBeNull();

		if (Ti.Platform.name == "android") {
			valueOf(testRun, test.backgroundColor).shouldBe("red");
		} else if (Ti.Platform.name == "tizen") {
			// In Tizen, test.backgroundColor is undefined by default.
			valueOf(testRun, test.backgroundColor).shouldBeUndefined();
		} else {
			valueOf(testRun, test.backgroundColor).shouldBe("blue");
		}

		finish(testRun);
	}
}

/*
 * Appcelerator Titanium Mobile
 * Copyright (c) 2011-2012 by Appcelerator, Inc. All Rights Reserved.
 * Licensed under the terms of the Apache Public License
 * Please see the LICENSE included with this distribution for details.
 */

module.exports = new function() {
	var finish;
	var valueOf;
	this.init = function(testUtils) {
		finish = testUtils.finish;
		valueOf = testUtils.valueOf;
	}

	this.name = "jss";
	this.tests = [
		{name: "platform_jss_dirs"}
	]

	this.platform_jss_dirs = function(testRun) {
		var test = Ti.UI.createView({ id: "test" });
		valueOf(testRun, test).shouldNotBeNull();

		if (Ti.Platform.name == "android") {
			valueOf(testRun, test.backgroundColor).shouldBe("red");
		} else if (Ti.Platform.osname == "tizen") {
			// In Tizen, test.backgroundColor is undefined by default.
			valueOf(testRun, test.backgroundColor).shouldBeUndefined();
		} else {
			valueOf(testRun, test.backgroundColor).shouldBe("blue");
		}

		finish(testRun);
	}
}

Change Ti.Platform.name to Ti.Platform.osname in Anvil for Tizen calls


module.exports = [{name:"LineChart",groupBy:true,chooseAxis:false,timeRadios:["allTime","pastYear","3Months"]},
			{name:"PieChart",groupBy:false,chooseAxis:false,timeRadios:["current"]},
			{name:"ChoroplethMap",groupBy:false,chooseAxis:false,timeRadios:["current"]},
			{name:"ColumnChart",groupBy:true,chooseAxis:false,timeRadios:["pastYear","3Months","current"]},
			{name:"ColumnChart",groupBy:true,chooseAxis:false,timeRadios:["allTime","pastYear","3Months","current"]},
			{name:"ScatterChart",groupBy:false,chooseAxis:true,timeRadios:["current"]},
			{name:"BarChart",groupBy:true,chooseAxis:false,timeRadios:["current"]}];

module.exports = [
  {name:"LineChart",groupBy:true,chooseAxis:false,timeRadios:["allTime","pastYear","3Months"]},
	{name:"PieChart",groupBy:false,chooseAxis:false,timeRadios:["current"]},
	{name:"ChoroplethMap",groupBy:false,chooseAxis:false,timeRadios:["current"]},
	{name:"ColumnChart",groupBy:true,chooseAxis:false,timeRadios:["pastYear","3Months","current"]},
	{name:"ScatterChart",groupBy:false,chooseAxis:true,timeRadios:["current"]},
	{name:"BarChart",groupBy:true,chooseAxis:false,timeRadios:["current"]}
];

Clean up the chart definitions


/**
 * Loads an array of urls using the loadFunction given by the user.
 * Returns a promise.all of all the urls.
 */
function loadUrls(urls, loadFunction) {
  let promises = [];
  for (url of urls) {
    let promise = loadFunction(url);
    promises.push(promise);
  }
  return Promise.all(promises);
}

/**
 * Loads the template from the url.
 * Returns a promise of the template.
 */
function loadTemplate(url) {
  const templatePromise = fetch(url).then(promiseResponse => promiseResponse.text());
  return templatePromise;
}

/**
 * Loads the object from the url.
 * Returns a promise of the object.
 */
function loadObject(url) {
  const objPromise = fetch(url).then(promiseResponse => promiseResponse.json());
  return objPromise;
}

/**
 * Loads an array of urls using the loadFunction given by the user.
 * Returns a promise.all of all the urls.
 */
function loadUrls(urls, loadFunction) {
  let promises = [];
  for (url of urls) {
    let promise = loadFunction(url);
    promises.push(promise);
  }
  return Promise.all(promises);
}

/**
 * Loads the template from the url.
 * Returns a promise of the template.
 */
function loadTemplate(url) {
  const templatePromise = fetch(url).then(promiseResponse => promiseResponse.text()).catch(error => console.log(error));
  return templatePromise;
}

/**
 * Loads the object from the url.
 * Returns a promise of the object.
 */
function loadObject(url) {
  const objPromise = fetch(url).then(promiseResponse => promiseResponse.json()).catch(error => console.log(error));
  return objPromise;
}

Add catch to fetch statements