"use strict";

angular.module('arethusa.relation').directive('nestedMenu', [
  '$compile',
  'relation',
  function($compile, relation) {
    return {
      restrict: 'A',
      scope: {
        relObj: '=',
        labelObj: '=',
        label: '=',
        property: '=',
        ancestors: '='
      },
      link: function(scope, element, attrs) {
        var html = '\
          <ul\
            nested-menu-collection\
            current="relObj"\
            property="property"\
            ancestors="ancestors"\
            all="labelObj.nested">\
          </ul>\
        ';

        if (scope.labelObj.nested) {
          element.append($compile(html)(scope));
        }

        scope.selectLabel = function() {
          scope.relObj[scope.property] = scope.label;
          relation.buildLabel(scope.relObj);
        };

        element.bind('click', function(event) {
          scope.$apply(function() {
            if (event.eventPhase === 2) { // at target, three would be bubbling!
              scope.selectLabel();
              if (scope.ancestors) {
                relation.resetAncestors(scope.relObj);
              }
            }
            if (scope.ancestors) {
              relation.addAncestor(scope.relObj, scope.label);
            }
          });
        });
      },
      template: '{{ label }}'
    };
  }
]);

"use strict";

angular.module('arethusa.relation').directive('nestedMenu', [
  '$compile',
  'relation',
  function($compile, relation) {
    return {
      restrict: 'A',
      scope: {
        relObj: '=',
        labelObj: '=',
        label: '=',
        property: '=',
        ancestors: '='
      },
      link: function(scope, element, attrs) {
        var html = '\
          <ul\
            nested-menu-collection\
            current="relObj"\
            property="property"\
            ancestors="ancestors"\
            all="labelObj.nested">\
          </ul>\
        ';

        if (scope.labelObj.nested) {
          element.append($compile(html)(scope));
          element.addClass('nested');
        }

        scope.selectLabel = function() {
          scope.relObj[scope.property] = scope.label;
          relation.buildLabel(scope.relObj);
        };

        element.bind('click', function(event) {
          scope.$apply(function() {
            if (event.eventPhase === 2) { // at target, three would be bubbling!
              scope.selectLabel();
              if (scope.ancestors) {
                relation.resetAncestors(scope.relObj);
              }
            }
            if (scope.ancestors) {
              relation.addAncestor(scope.relObj, scope.label);
            }
          });
        });
      },
      template: '{{ label }}'
    };
  }
]);

Add a nested class for nested menus


import Ember from 'ember';

export default Ember.Mixin.create({

  errors: [],
  showError: false,

  syncErrors: function () {
    if (this.get('isDestroyed')) return;
    this.set('errors', this.get('parentModel.errors.' + this.get('name')));
  },

  observeErrors: function () {
    if (!this.get('parentModel')) return;
    this.get('parentModel').addObserver('errors.' + this.get('name'), this, this.syncErrors);
  }.on('didInsertElement'),

  // Propagate showError changes to parentModel
  errorVisibilityForModel: function () {
    var parentModel = this.get('parentModel');
    if (this.get('showError')) {
      parentModel.trigger('shouldShowValidationError', this.get('name'));
    } else {
      parentModel.trigger('shouldDismissValidationError', this.get('name'));
    }
  }.observes('showError', 'name')

});

import Ember from 'ember';

export default Ember.Mixin.create({

  errors: [],
  showError: false,

  syncErrors: function () {
    if (this.get('isDestroyed')) return;
    this.set('errors', this.get('parentModel.errors.' + this.get('name')));
  },

  observeErrors: function () {
    if (!this.get('parentModel')) return;
    this.get('parentModel').addObserver('errors.' + this.get('name'), this, this.syncErrors);
  }.on('didInsertElement'),

  // Propagate showError changes to parentModel
  errorVisibilityForModel: function () {
    var parentModel = this.get('parentModel');
    if (!parentModel) return;

    if (this.get('showError')) {
      parentModel.trigger('shouldShowValidationError', this.get('name'));
    } else {
      parentModel.trigger('shouldDismissValidationError', this.get('name'));
    }
  }.observes('showError', 'name')

});

Return created PaymentMethod when user's 401's in /your-account/billing


'use strict';

angular.module('gisto.filter.dotToDash', []).filter('dotToDash', function () {
    return function (input) {
        return input.replace('.','-');
    };
});

'use strict';

angular.module('gisto.filter.dotToDash', []).filter('dotToDash', function () {
    return function (input) {
        var output = input;
        if(input.charAt(0) === '.') {
            output = input.substr(1);
        }
        return output.replace('.','-');
    };
});

FIX (dotfiles in gist): fix links of dotfiles


/* Copyright (c) 2015-present, salesforce.com, inc. All rights reserved */
/* Licensed under BSD 3-Clause - see LICENSE.txt or git.io/sfdc-license */

/* eslint-disable no-console, global-require */

import path from 'path';

const exec = ({
	command,
	isExecuted = true,
	message,
	dir = '.',
	rootPath,
	verbose = true
}, callback) => {
	if (isExecuted) {
		if (message) {
			console.log(message);
		}
		const child = require('child_process').exec(command, {
			cwd: path.resolve(rootPath, dir),
			maxBuffer: 1024 * 500
		}, (err) => {
			callback(err);
		});

		if (verbose === true) {
			child.stdout.on('data', (data) => process.stdout.write(data.toString()));
		}
	}
};

export { exec as default };

/* Copyright (c) 2015-present, salesforce.com, inc. All rights reserved */
/* Licensed under BSD 3-Clause - see LICENSE.txt or git.io/sfdc-license */

/* eslint-disable no-console, global-require */

import path from 'path';

const exec = ({
	command,
	message,
	dir = '.',
	rootPath,
	verbose = true
}, callback) => {
	if (message) {
		console.log(message);
	}
	const child = require('child_process').exec(command, {
		cwd: path.resolve(rootPath, dir),
		maxBuffer: 1024 * 500
	}, (err) => {
		callback(err);
	});

	if (verbose === true) {
		child.stdout.on('data', (data) => process.stdout.write(data.toString()));
	}
};

export { exec as default };

Remove isExecuted from CLI utils


function refresh () {
  $(".refresh-link").on("click", function(event) {
    event.preventDefault();
    var $target = $(event.target);
    var url = $target.attr("href");
    var id = url.match(/(\d+)(?!.*\d)/)[0].toString();
    var dateTimeIn24Hours = new Date();
    var $dateSpan = $(".data" + id);
    var $timeRemainingSpan = $(".time-remaining" + id);
    var longest = parseInt($("#longest" + id).text(), 10);
    var current = parseInt($("#current" + id).text(), 10);

    $.ajax({
      url: url,
      type: "get"
    }).done(function(response) {
      countdown();
    });
  });
}

function refresh () {
  $(".refresh-link").on("click", function(event) {
    event.preventDefault();
    var $target = $(event.target);
    var url = $target.attr("href");
    var id = url.match(/(\d+)(?!.*\d)/)[0].toString();
    var dateTimeIn24Hours = new Date();
    var $dateSpan = $(".data" + id);
    var $timeRemainingSpan = $(".time-remaining" + id);
    var longest = parseInt($("#longest" + id).text(), 10);
    var current = parseInt($("#current" + id).text(), 10);

    $.ajax({
      url: url,
      type: "get"
    }).done(function(response) {
      if ($timeRemainingSpan.text() === "Chain broken:") {
        $timeRemainingSpan.html("Time remaining: ");
        $dateSpan.attr("data-countdown", formatCurrentDateTime(dateTimeIn24Hours));
      } else {
        $dateSpan.data("countdown", formatCurrentDateTime(dateTimeIn24Hours));
      }
      incrementStreak(current, longest, id);
      countdown();
    });
  });
}

Add conditional to check if chain is currently broken or active


#!/bin/env node

module.exports = function()
{
	var scope = this;

	this.CronJob = require('cron').CronJob;
	this.NewReleases = require(__dirname + '/newreleases');
	this.User = require(__dirname + '/db/user');
	this.jobs = [];

	this.init = function()
	{
		// TODO: select distinct crontime and timezone from database users
		scope.jobs.push(
			new scope.CronJob(
			{
				cronTime: '10 * * * * *',
				onTick: scope.runCheck,
				start: true,
				timeZone: "Europe/Amsterdam"
			})
		);
	};

	this.runCheck = function()
	{
		console.log(this.cronTime.source);
		console.log(this.cronTime.zone);
		scope.User.find({active:true, crontime: this.cronTime.source, timezone: this.cronTime.zone}, function(err, users)
		{
			if(err) { console.log('Error getting users from the database: '+ err); }
			users.forEach(function(user)
			{
				var releases = new scope.NewReleases();
				releases.checkNewReleases(user);
			});
		});
	};
};

#!/bin/env node

module.exports = function()
{
	var scope = this;

	this.CronJob = require('cron').CronJob;
	this.NewReleases = require(__dirname + '/newreleases');
	this.User = require(__dirname + '/db/user');
	this.jobs = [];

	this.init = function()
	{
		// TODO: select distinct crontime and timezone from database users
		scope.jobs.push(
			new scope.CronJob(
			{
				cronTime: '* 00 11 * * *',
				onTick: scope.runCheck,
				start: true,
				timeZone: "Europe/Amsterdam"
			})
		);
	};

	this.runCheck = function()
	{
		console.log(this.cronTime.source);
		console.log(this.cronTime.zone);
		scope.User.find({active:true, crontime: this.cronTime.source, timezone: this.cronTime.zone}, function(err, users)
		{
			if(err) { console.log('Error getting users from the database: '+ err); }
			users.forEach(function(user)
			{
				var releases = new scope.NewReleases();
				releases.checkNewReleases(user);
			});
		});
	};
};

Set default to once a day for now


var path = require( 'path' );
var webpack = require( 'webpack' );

module.exports = {
	devtool: 'cheap-module-eval-source-map',
	entry: [
		'webpack-hot-middleware/client',
		'./index.jsx'
	],
	output: {
		path: path.join( __dirname, 'dist' ),
		filename: 'bundle.js',
		publicPath: '/static/'
	},
	plugins: [
		new webpack.optimize.OccurrenceOrderPlugin(),
		new webpack.HotModuleReplacementPlugin(),
		new webpack.NoErrorsPlugin()
	],
	module: {
		loaders: [ {
			test: /\.jsx?$/,
			loaders: [ 'babel' ],
			exclude: /node_modules/,
			include: __dirname
		} ]
	}
};

var path = require( 'path' );
var webpack = require( 'webpack' );

module.exports = {
	devtool: 'cheap-module-eval-source-map',
	entry: [
		'webpack-hot-middleware/client',
		'./index.jsx'
	],
	output: {
		path: path.join( __dirname, 'dist' ),
		filename: 'bundle.js',
		publicPath: '/static/'
	},
	plugins: [
		new webpack.optimize.OccurrenceOrderPlugin(),
		new webpack.HotModuleReplacementPlugin(),
		new webpack.NoErrorsPlugin()
	],
	module: {
		loaders: [ {
			test: /\.jsx?$/,
			loaders: [ 'babel' ],
			exclude: /node_modules/,
			include: __dirname
		} ]
	},
	resolve: {
		extensions: [ '', '.js', '.jsx' ],
		modulesDirectories: [ 'node_modules' ]
	}
};

Add .jsx to resolver for webpack.


/* eslint-disable space-before-function-paren, no-var, comma-dangle, no-return-assign, max-len */

import '~/behaviors/autosize';

(function() {
  describe('Autosize behavior', function() {
    var load;
    beforeEach(function() {
      return setFixtures('<textarea class="js-autosize" style="resize: vertical"></textarea>');
    });
    it('does not overwrite the resize property', function() {
      load();
      return expect($('textarea')).toHaveCss({
        resize: 'vertical'
      });
    });
    return load = function() {
      return $(document).trigger('load');
    };
  });
}).call(window);

import '~/behaviors/autosize';

function load() {
  $(document).trigger('load');
}

describe('Autosize behavior', () => {
  beforeEach(() => {
    setFixtures('<textarea class="js-autosize" style="resize: vertical"></textarea>');
  });

  it('does not overwrite the resize property', () => {
    load();
    expect($('textarea')).toHaveCss({
      resize: 'vertical',
    });
  });
});

Remove iife and eslint disable


'use strict'

const morgan = require('morgan')

// This adds a very simple log message for each request. Morgan is a package
// from express to do this, and we use tiny so we don't record any user
// information to logs in production.
//
// TODO: Add a different log format for dev & staging that provides more
// information for debugging.
module.exports = morgan('tiny')

'use strict'

const morgan = require('morgan')

// This adds a very simple log message for each request. Morgan is a package
// from express to do this, and we use tiny so we don't record any user
// information to logs in production.
//
// TODO: Add a different log format for dev & staging that provides more
// information for debugging.
module.exports = morgan(':date[iso] :method :url :status :res[content-length] - :response-time ms')

Add datetime to log format


import {getField} from "./getField";

/**
  * Creates a new object whose fields are either equal to obj[field] or defaultValue.
  * Useful for extracting a subset of fields from an object.
  *
  * @param obj the object to query the fields for.
  * @param fields an array of the target fields, in string form.
  * @param defaultValue the value to be used if obj[field] === undefined.
 */
export const getFields = (obj, fields, defaultValue) => {
  const result = { };
  for(const field of fields) result[field] = getField(obj, field, defaultValue);
  return result;
}

import {getField} from "./getField";

/**
  * Creates a new object whose fields are either equal to obj[field] or defaultValue.
  * Useful for extracting a subset of fields from an object.
  *
  * @param obj the object to query the fields for.
  * @param fields an array of the target fields, in string form.
  * @param defaultValue the value to be used if obj[field] === undefined.
 */
export const getFields = (obj, fields, defaultValue) => {
  const result = {};
  for(const field of fields) result[field] = getField(obj, field, defaultValue);
  return result;
}

Delete Space from empty Object


var _ = require('underscore');
var Mosaic = require('mosaic-commons');
var App = require('mosaic-core').App;
var Api = App.Api;
var Teleport = require('mosaic-teleport');

/** This module manages resource statistics. */
module.exports = Api.extend({

    /**
     * Initializes internal fields.
     */
    _initFields : function() {
        this._user = null;
    },

    /**
     * Loads information about selected/active entities.
     */
    start : function() {
    },

    /** Closes this module. */
    stop : function() {
    },

    _http : function(url, method) {
        method = method || 'GET';
        var client = Teleport.HttpClient.newInstance({
            baseUrl : url
        });
        return client.exec({
            path : '',
            method : method
        }).then(function(json) {
            try {
                return _.isObject(json) ? json : JSON.parse(json);
            } catch (err) {
                return;
            }
        });
    },

    logout : function() {
        return this._http(this.app.options.logoutApiUrl).then(function(user) {
            return user;
        });
    },

    getUserInfo : function() {
        return this._http(this.app.options.userInfoApiUrl).then(function(user) {
            if (!user || !user.displayName)
                return;
            return user;
        });
    },

});

var _ = require('underscore');
var Mosaic = require('mosaic-commons');
var App = require('mosaic-core').App;
var Api = App.Api;
var Teleport = require('mosaic-teleport');

/** This module manages resource statistics. */
module.exports = Api.extend({

    /**
     * Initializes internal fields.
     */
    _initFields : function() {
        this._user = null;
    },

    /**
     * Loads information about selected/active entities.
     */
    start : function() {
    },

    /** Closes this module. */
    stop : function() {
    },

    _http : function(url, method) {
        method = method || 'GET';
        var client = Teleport.HttpClient.newInstance({
            baseUrl : url
        });
        return client.exec({
            path : '',
            method : method
        }).then(function(json) {
            try {
                return _.isObject(json) ? json : JSON.parse(json);
            } catch (err) {
                return;
            }
        });
    },

    logout : function() {
        var that = this;
        return this._http(this.app.options.logoutApiUrl).then(function(user) {
            return that.notify();
        }).then(function() {
            return user;
        });
    },

    getUserInfo : function() {
        return this._http(this.app.options.userInfoApiUrl).then(function(user) {
            if (!user || !user.displayName)
                return;
            return user;
        });
    },

});

Add notifications for changes in the user module


import angular from 'angular';
import uiRouter from 'angular-ui-router';
import organizationComponent from './organization.component';
import organizationDetail from './organizationDetail/organizationDetail';

 require('angular-ui-grid/ui-grid.css');

//require('angular-datatables/dist/plugins/columnfilter/angular-datatables.columnfilter.js');
//require('angular-datatables/dist/plugins/bootstrap/angular-datatables.bootstrap.js');
//require('datatables.net-select');
//require('angular-datatables/dist/plugins/select/angular-datatables.select.min.js');

let organizationModule = angular.module('organization', [
  uiRouter,
  'navbar',
  organizationDetail,
   require('angular-ui-grid')
])

.config(($stateProvider) => {
  "ngInject";
  $stateProvider
    .state('organization', {
      url: '/organization',
      component: 'organization'
    });
})

.run(['Menu', function(Menu){
  Menu.addToMainMenu({ display: "Organization", url: "/organization", requireLogin: true })
}])

.component('organization', organizationComponent)

.name;

export default organizationModule;

import angular from 'angular';
import uiRouter from 'angular-ui-router';
import organizationComponent from './organization.component';
import organizationDetail from './organizationDetail/organizationDetail';

 require('angular-ui-grid/ui-grid.css');

//require('angular-datatables/dist/plugins/columnfilter/angular-datatables.columnfilter.js');
//require('angular-datatables/dist/plugins/bootstrap/angular-datatables.bootstrap.js');
//require('datatables.net-select');
//require('angular-datatables/dist/plugins/select/angular-datatables.select.min.js');

let organizationModule = angular.module('organization', [
  uiRouter,
  'navbar',
  organizationDetail,
   require('angular-ui-grid')
])

.config(($stateProvider) => {
  "ngInject";
  $stateProvider
    .state('organization', {
      url: '/organization',
      component: 'organization'
    });
})

.run(['Menu', function(Menu){
  Menu.addToMainMenu({
    display: "Organization",
    url: "/organization",
    requireLogin: true,
    requireDev: true })
}])

.component('organization', organizationComponent)

.name;

export default organizationModule;

Make org a dev function for now


import glob from 'glob';

import cssHook from 'css-modules-require-hook';
cssHook({generateScopedName: '[name]__[local]'});

glob.sync('**/*-test.js', {realpath: true, cwd: __dirname}).forEach(require);

require('css-modules-require-hook')({
  generateScopedName: '[name]__[local]'
});

require('glob')
  .sync('**/*-test.js', {
    realpath: true,
    cwd: require('path').resolve(process.cwd(), 'test')
  })
  .forEach(require);

Use ES5 for test endpoint so there is no need for babel transform


'use strict';

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

  function planCustomersService(baseServiceClass) {
    /*jshint validthis: true */
    var ServiceClass = baseServiceClass.extend({
      init:function() {
        this._super();
        this.endpoint = '/plan-customers/';
      }
    });
    return new ServiceClass();
  }

})();

Add new line to end of file (saas-265)


'use strict';

module.exports = function (t, a, d) {
	var invoked;
	a(t(function () {
		a(arguments.length, 0, "Arguments");
		invoked = true;
	}), undefined, "Return");
	a(invoked, undefined, "Is not run immediately");
	setTimeout(function () {
		a(invoked, true, "Run in next tick");
		d();
	}, 10);
};

'use strict';

module.exports = function (t, a, d) {
	var invoked;

	a(t(function () {
		a(arguments.length, 0, "Arguments");
		invoked = true;
	}), undefined, "Return");
	a(invoked, undefined, "Is not run immediately");
	setTimeout(function () {
		a(invoked, true, "Run in next tick");
		invoked = [];
		t(function () { invoked.push(0); });
		t(function () { invoked.push(1); });
		t(function () { invoked.push(2); });
		setTimeout(function () {
			a.deep(invoked, [0, 1, 2], "Serial");
			d();
		}, 10);
	}, 10);
};

Add tests for serial calls


var caps = require('../');
var test = require('tape');

test('works', function(t) {
  var TEST_ARRAY = [
    ['kitten', 0.0],
    ['Kitten', 1 / 6.0],
    ['KItten', 1 / 3.0],
    ['KITten', 0.5],
    ['KITTen', 2 / 3.0],
    ['KITTEn', 5 / 6.0],
    ['KITTEN', 1.0],
    ['kittens ARE COOL', 7 / 16.0]
  ];

  t.plan(TEST_ARRAY.length);

  TEST_ARRAY.forEach(function(value) {
    t.equal(caps(value[0]), value[1]);
  });
});

var caps = require('../');
var test = require('tape');

test('works', function(t) {
  var TEST_ARRAY = [
    ['kitten', 0.0],
    ['Kitten', 1 / 6.0],
    ['KItten', 1 / 3.0],
    ['KITten', 0.5],
    ['KITTen', 2 / 3.0],
    ['KITTEn', 5 / 6.0],
    ['KITTEN', 1.0],
    ['kittens ARE COOL', 7 / 16.0],
    ['kitteñ', 0.0],
    ['kitteÑ', 1 / 6.0],
    ['kittÉÑ', 1 / 3.0]
  ];

  t.plan(TEST_ARRAY.length);

  TEST_ARRAY.forEach(function(value) {
    t.equal(caps(value[0]), value[1]);
  });
});

Add tests for accented characters


var html = document.getElementById("html");
var render = document.getElementById("render");
var toggle = document.getElementById("toggle");
var error = document.getElementById("error");

var code = CodeMirror.fromTextArea(document.getElementById("code"), {
  mode: "javascript"
});

function update() {
  try {
    var element = new Function(
      "with(xmgen.svg) { with (xmgen.html) { return "
      + code.getValue()
      + "}}"
    )();

    error.innerText = "";
    if (toggle.classList.contains("down")) {
      html.innerText = element.toString(2);
      CodeMirror.colorize([html], "xml");
    } else {
      render.innerHTML = element.toString();
    }
  } catch (e) {
    error.innerText = e.toString();
  }
}

code.on("change", update);
toggle.addEventListener("click", function() {
  if (toggle.classList.contains("down")) {
    toggle.classList.remove("down");
    html.innerHTML = "";
  } else {
    toggle.classList.add("down");
    render.innerHTML = "";
  }
  update();
});
update();

var html = document.getElementById("html");
var render = document.getElementById("render");
var toggle = document.getElementById("toggle");
var error = document.getElementById("error");

var code = CodeMirror.fromTextArea(document.getElementById("code"), {
  mode: "javascript"
});

function update() {
  try {
    var element = new Function(
      "with(xmgen.svg) { with (xmgen.html) { return "
      + code.getValue()
      + "}}"
    )();

    error.textContent = "";
    if (toggle.classList.contains("down")) {
      html.textContent = element.toString(2);
      CodeMirror.colorize([html], "xml");
    } else {
      render.innerHTML = element.toString();
    }
  } catch (e) {
    error.textContent = e.toString();
  }
}

code.on("change", update);
toggle.addEventListener("click", function() {
  if (toggle.classList.contains("down")) {
    toggle.classList.remove("down");
    html.innerHTML = "";
  } else {
    toggle.classList.add("down");
    render.innerHTML = "";
  }
  update();
});
update();

Use textContent instead of innerText


/*global phantom*/
'use strict';

var system  = require('system');
var webpage = require('webpage');

var page    = webpage.create();
var errd    = false;
var timeout = null;
var signal  = '[E_PHANTOMIC] ';
var url     = 'http://localhost:' + system.env.PHANTOMIC_PORT;
var debug   = system.env.PHANTOMIC_DEBUG;
var lastLog = Date.now();
var done    = false;

if (debug) {
  system.stderr.write('PHANTOMIC_DEBUG');
  url += '?debug';
}

page.onConsoleMessage = function (msg) {
  if (msg.indexOf(signal) === 0) {
    errd = true;
    msg = msg.substring(signal.length);
  }
  if (msg === '[X_PHANTOMIC]') {
    if (!done && Date.now() - lastLog > 100) {
      done = true;
      phantom.exit(errd ? 1 : 0);
    }
  } else {
    lastLog = Date.now();
    console.log(msg);
  }
};

page.onError = function (msg, trace) {
  errd = true;
  console.log(msg);
  if (trace) {
    trace.forEach(function (t) {
      console.log('    at ' + url + '/js/bundle:' + t.line);
    });
  }
};

page.open(url);

/*global phantom*/
'use strict';

var system  = require('system');
var webpage = require('webpage');

var page    = webpage.create();
var errd    = false;
var timeout = null;
var signal  = '[E_PHANTOMIC] ';
var url     = 'http://localhost:' + system.env.PHANTOMIC_PORT;
var debug   = system.env.PHANTOMIC_DEBUG;
var lastLog = Date.now();
var done    = false;

if (debug) {
  system.stderr.write('PHANTOMIC_DEBUG');
  url += '?debug';
}

page.onConsoleMessage = function (msg) {
  if (msg.indexOf(signal) === 0) {
    errd = true;
    msg = msg.substring(signal.length);
  }
  if (msg === '[X_PHANTOMIC]') {
    if (!done && Date.now() - lastLog > 100) {
      done = true;
      var code = errd ? 1 : 0;
      if (debug) {
        phantom.debugExit(code);
      } else {
        phantom.exit(code);
      }
    }
  } else {
    lastLog = Date.now();
    console.log(msg);
  }
};

page.onError = function (msg, trace) {
  errd = true;
  console.log(msg);
  if (trace) {
    trace.forEach(function (t) {
      console.log('    at ' + url + '/js/bundle:' + t.line);
    });
  }
};

page.open(url);

Use phantom.debugExit(code) to exit in debug mode


import test from 'ava';
import removeTrailingSeparator from '..';

test('strip trailing separator:', t => {
	t.is(removeTrailingSeparator('foo/'), 'foo');
	t.is(removeTrailingSeparator('foo\\'), 'foo');
});

test('don\'t strip when it\'s the only char in the string', async t => {
	t.is(removeTrailingSeparator('/'), '/');
	t.is(removeTrailingSeparator('\\'), '\\');
});

test('strip only trailing separator:', t => {
	t.is(removeTrailingSeparator('/test/foo/bar/'), '/test/foo/bar');
	t.is(removeTrailingSeparator('\\test\\foo\\bar\\'), '\\test\\foo\\bar');
});

test('strip multiple trailing separators', t => {
	t.is(removeTrailingSeparator('/test//'), '/test');
	t.is(removeTrailingSeparator('\\test\\\\'), '\\test');
});

test('leave 1st separator in a string of only separators', t => {
	t.is(removeTrailingSeparator('//'), '/');
	t.is(removeTrailingSeparator('////'), '/');
	t.is(removeTrailingSeparator('\\\\'), '\\');
	t.is(removeTrailingSeparator('\\\\\\\\'), '\\');
});

test('return back empty string', t => {
	t.is(removeTrailingSeparator(''), '');
});

import test from 'ava';
import removeTrailingSeparator from '..';

test('strip trailing separator:', t => {
	t.is(removeTrailingSeparator('foo/'), 'foo');
	t.is(removeTrailingSeparator('foo\\'), 'foo');
});

test('don\'t strip when it\'s the only char in the string', t => {
	t.is(removeTrailingSeparator('/'), '/');
	t.is(removeTrailingSeparator('\\'), '\\');
});

test('strip only trailing separator:', t => {
	t.is(removeTrailingSeparator('/test/foo/bar/'), '/test/foo/bar');
	t.is(removeTrailingSeparator('\\test\\foo\\bar\\'), '\\test\\foo\\bar');
});

test('strip multiple trailing separators', t => {
	t.is(removeTrailingSeparator('/test//'), '/test');
	t.is(removeTrailingSeparator('\\test\\\\'), '\\test');
});

test('leave 1st separator in a string of only separators', t => {
	t.is(removeTrailingSeparator('//'), '/');
	t.is(removeTrailingSeparator('////'), '/');
	t.is(removeTrailingSeparator('\\\\'), '\\');
	t.is(removeTrailingSeparator('\\\\\\\\'), '\\');
});

test('return back empty string', t => {
	t.is(removeTrailingSeparator(''), '');
});

Use normal function for normal test


const express = require('express')
const fs = require('fs')
const http = require('http')
const https = require('https')
const path = require('path')

function startHttpServer(app, port) {
  const httpServer = http.Server(app)
  httpServer.listen(port, () => console.log(`Listening on HTTP port *:${port}`))
}

function startHttpsServer(app, port, sslKey, sslCert) {
  const privateKey = fs.readFileSync(sslKey, 'utf8')
  const certificate = fs.readFileSync(sslCert, 'utf8')
  const httpsServer = https.Server({key: privateKey, cert: certificate}, app)
  httpsServer.listen(port, () => console.log(`Listening on HTTPS port *:${port}`))
}

function bindRoutes(app, rootFolder) {
  const sendFileOptions = {root: path.resolve(rootFolder)}
  app.use((req, res, next) => {
    if (req.url.endsWith('gz')) {
      res.sendFile(req.url, sendFileOptions)
    } else {
      res.sendFile(`${req.url}/index.json`, sendFileOptions)
    }
  })
}

function init(options) {
  const app = express()
  bindRoutes(app, options.root)
  startHttpServer(app, options.port)
  if (options.https) {
    startHttpsServer(app, options.https.port, options.https.sslKey, options.https.sslCert)
  }
}

module.exports = init

const express = require('express')
const fs = require('fs')
const http = require('http')
const https = require('https')
const path = require('path')

function startHttpServer(app, port) {
  const httpServer = http.Server(app)
  httpServer.listen(port, () => console.log(`Listening on HTTP port *:${port}`))
}

function startHttpsServer(app, port, sslKey, sslCert) {
  const privateKey = fs.readFileSync(sslKey, 'utf8')
  const certificate = fs.readFileSync(sslCert, 'utf8')
  const httpsServer = https.Server({key: privateKey, cert: certificate}, app)
  httpsServer.listen(port, () => console.log(`Listening on HTTPS port *:${port}`))
}

function jsonError(res) {
  return function (err) {
    if (err) {
      res.status(err.status).json({})
    }
  }
}

function bindRoutes(app, rootFolder) {
  const sendFileOptions = {root: path.resolve(rootFolder)}
  app.use((req, res, next) => {
    if (req.url.endsWith('gz')) {
      res.sendFile(req.url, sendFileOptions, jsonError(res))
    } else {
      res.sendFile(`${req.url}/index.json`, sendFileOptions, jsonError(res))
    }
  })
}

function init(options) {
  const app = express()
  bindRoutes(app, options.root)
  startHttpServer(app, options.port)
  if (options.https) {
    startHttpsServer(app, options.https.port, options.https.sslKey, options.https.sslCert)
  }
}

module.exports = init

Send 404 errors as JSON to keep NPM happy


import React from 'react'

export function FlightsForm({ homeFloor, value, onChange }) {
  function onFlightsChanged(event) {
    const flights = parseFloat(event.target.value)
    onChange(flights)
  }

  function addFlights(flights) {
    onChange(value + flights)
  }

  function onAddDefaultFlights() {
    addFlights(homeFloor)
  }

  function addFlight() {
    addFlights(1)
  }

  function subtractFlight() {
    addFlights(-1)
  }

  return (

      <fieldset className="input-group">

          <button className="btn btn-secondary" onClick={subtractFlight} type="button">-</button>

        <input type="number" className="form-control" value={value} onChange={onFlightsChanged} />

          <button className="btn btn-secondary" onClick={addFlight} type="button">+</button>

      </fieldset>
      <button className="btn btn-block btn-primary" onClick={onAddDefaultFlights}>
        Add {homeFloor} flights
      </button>

{value * 13} ft

  )
}

FlightsForm.propTypes = {
  homeFloor: React.PropTypes.number,
  value:     React.PropTypes.number,
  onChange:  React.PropTypes.func.isRequired,
}

FlightsForm.defaultProps = {
  homeFloor: 1,
  value: 0,
  onChange() {},
}

import React from 'react'

export function FlightsForm({ homeFloor, value, onChange }) {
  function onFlightsChanged(event) {
    const flights = parseFloat(event.target.value)
    onChange(flights)
  }

  function addFlights(flights) {
    onChange(value + flights)
  }

  function onAddDefaultFlights() {
    addFlights(homeFloor)
  }

  function addFlight() {
    addFlights(1)
  }

  function subtractFlight() {
    addFlights(-1)
  }

  return (

          <button className="btn btn-secondary" onClick={subtractFlight} type="button">-</button>

        <input type="number" className="form-control" value={value} onChange={onFlightsChanged} />

          <button className="btn btn-secondary" onClick={addFlight} type="button">+</button>

      <button className="btn btn-block btn-primary" onClick={onAddDefaultFlights}>
        Add {homeFloor} flights
      </button>

{value * 13} ft

  )
}

FlightsForm.propTypes = {
  homeFloor: React.PropTypes.number,
  value:     React.PropTypes.number,
  onChange:  React.PropTypes.func.isRequired,
}

FlightsForm.defaultProps = {
  homeFloor: 1,
  value: 0,
  onChange() {},
}

Fix styling on flights form


'use strict';

/**
 * @ngdoc directive
 * @name vlui.directive:pasteDataset
 * @description
 * # pasteDataset
 */
angular.module('vlui')
  .directive('pasteDataset', function (Dataset, Alerts, Logger, Config, _, Papa) {
    return {
      templateUrl: 'dataset/pastedataset.html',
      restrict: 'E',
      replace: true,
      scope: false,  // use scope from datasetSelector
      link: function postLink(scope/*, element, attrs*/) {
        scope.datasetName = '';
        scope.data = '';

        // need to give this a unique name because we share the namespace
        scope.addPasted = function() {
          var data;

          var result = Papa.parse(scope.data, {
            dynamicTyping: true,
            header: true
          });

          if (result.errors.length === 0) {
            data = result.data;
          } else {
            _.each(result.errors, function(err) {
              Alerts.add(err.message, 2000);
            });
            return;
          }

          var dataset = {
            id: Date.now(),  // time as id
            name: scope.datasetName,
            values: data,
            group: 'pasted'
          };

          Dataset.dataset = Dataset.add(angular.copy(dataset));
          scope.datasetChanged();

          scope.datasetName = '';
          scope.data = '';

          scope.doneAdd();
        };
      }
    };
  });

'use strict';

/**
 * @ngdoc directive
 * @name vlui.directive:pasteDataset
 * @description
 * # pasteDataset
 */
angular.module('vlui')
  .directive('pasteDataset', function (Dataset, Alerts, Logger, Config, _, dl) {
    return {
      templateUrl: 'dataset/pastedataset.html',
      restrict: 'E',
      replace: true,
      scope: false,  // use scope from datasetSelector
      link: function postLink(scope/*, element, attrs*/) {
        scope.datasetName = '';
        scope.data = '';

        // need to give this a unique name because we share the namespace
        scope.addPasted = function() {
          var data = dl.read(response.data, {type: 'csv'});

          var dataset = {
            id: Date.now(),  // time as id
            name: scope.datasetName,
            values: data,
            group: 'pasted'
          };

          Dataset.dataset = Dataset.add(angular.copy(dataset));
          scope.datasetChanged();

          scope.datasetName = '';
          scope.data = '';

          scope.doneAdd();
        };
      }
    };
  });

Use details parser for reading pasted data


let config = require("./config");
let Token = require("../lib/token");

(async function() {

    let token = new Token(config.token);
    let value = await token.value;
    console.log(value);

})();

let config = require("./config");
let Token = require("../lib/token");

(async function() {

    let token = new Token(config.fmeToken);
    let value = await token.value;
    console.log(value);

})();

Fix up node name of config


var express = require('express');
var redis = require('ioredis');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var routes = require('./routes/endpoints');
var users = require('./routes/users');

var api = express();
// var redis = new Redis('/tmp/redis.sock');

api.get('/', function(req, res){
  res.send('Hello World');
});
api.use(function(err, req, res, next){
  console.error(err.stack);
  res.send(500, 'Something broke!');
});

api.set('port', process.env.PORT || 3000);

var server = api.listen(api.get('port'), function() {
  console.log('Listening on port %d', server.address().port);
});

var express = require('express');
var redis = require('ioredis');
var path = require('path');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var endpoints = require('./routes/endpoints');
var users = require('./routes/users');

var api = express();
// var redis = new Redis('/tmp/redis.sock');

// view engine setup
api.set('views', path.join(__dirname, 'views'));
api.set('view engine', 'jade');

api.use(bodyParser.json());
api.use(bodyParser.urlencoded());
api.use(cookieParser());

api.get('/', function(req, res){
  res.send('Hello World');
});
api.use(function(err, req, res, next){
  console.error(err.stack);
  res.send(500, 'Something broke!');
});

api.set('port', process.env.PORT || 3000);
api.set('env', 'development');

var server = api.listen(api.get('port'), function() {
  console.log('Listening on port %d', server.address().port);
});

api.use(express.static(path.join(__dirname, 'public')));

api.use('/', endpoints);
api.use('/users', users);

/// error handlers

/// catch 404 and forward to error handler
api.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// development error handler
// will print stacktrace
if (api.get('env') === 'development') {
  api.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
      message: err.message,
      error: err
    });
  });
}

// production error handler
// no stacktraces leaked to user
api.use(function(err, req, res, next) {
  res.status(err.status || 500);
  res.render('error', {
    message: err.message,
    error: {}
  });
});

Add error handling, endpoints, and view engine w/ views


'use strict';

// Dependencies
var express = require('express');
var http = require('http');
var bodyparser = require('body-parser');

// Config
var app = express();
app.use(bodyparser.json());
app.use(express.static(__dirname + (process.env.STATIC_DIR || '/build')));

// Routing
require('./expressRoutes')(app);

// Init
var server = app.listen(process.env.PORT || 3000, function() {
    console.log('Lookin legit on port: %d', server.address().port);
});

'use strict';

// Dependencies
var express = require('express');
var http = require('http');
var bodyparser = require('body-parser');

// Config
var app = express();
app.use(bodyparser.json());
app.use(express.static(__dirname + (process.env.STATIC_DIR || '/build')));

// Routing
require('./expressRoutes')(app);

// Init
var port = process.env.PORT || 3000;
var server = app.listen(port, function() {
    console.log('Lookin legit on port: %d', server.address().port);
});

exports.port = port;

Add port as an export, for testing


var Sockets = {}

Sockets.Socket = function Socket (signal) {
    if(!('WebSocket' in window)) throw new Error("Websockets not supported in this browser")

    this.connection = io.connect('http://localhost:3001')
    this.signal = signal

    this.connection.onopen = function () {
        //hmmm...
    }

    this.connection.onclose = function () {
        //hmmm...
    }

    this.connection.onerror = function(e) {
        console.error(e)
    }

    this.send = function (value) {
        //stringify?
        this.connection.send(value)
    }

    this.connection.on('data', function(value) {
        this.signal.push(value)
    })
}

Sockets.createSocketedSignal = function() {
    var signal = new Signal(), socket = new Sockets.Socket(signal)
    return signal
}

var Sockets = {}

Sockets.Socket = function Socket (signal) {
    if(!('WebSocket' in window)) throw new Error("Websockets not supported in this browser")

    this.connection = io ? io.connect('http://localhost:3001') : new WebSocket("ws://localhost:3001")
    this.signal = signal

    this.send = this.connection.emit ? function (value) {
        this.connection.emit('data', value)
    } : function (value) {
        //stringify?
        this.connection.send(value)
    }

    if(this.connection.on)
    this.connection.on('data', function(value) {
        this.signal.push(value)
    })
    else this.connection.onmessage = function(value) {
        this.signal.push(value)
    }

}

Sockets.createSocketedSignal = function() {
    var signal = new Signal(), socket = new Sockets.Socket(signal)
    return signal
}

Make it less tightly coupled with socket.io


const router = require('express').Router()

const { ENTITIES } = require('../../../search/constants')

const QUERY_FIELDS = require('../../constants')

const { getCollection, exportCollection } = require('../../../../modules/search/middleware/collection')
const { setDefaultQuery } = require('../../../middleware')
const { getRequestBody } = require('../../../../middleware/collection')

const { renderList } = require('./controllers')
const { setRequestBody } = require('./middleware')
const { transformOrderToListItem } = require('../../transformers')

const DEFAULT_QUERY = {
  sortby: 'created_on:desc',
}

router.get('/',
  setDefaultQuery(DEFAULT_QUERY),
  setRequestBody,
  getCollection('order', ENTITIES, transformOrderToListItem),
  renderList,
)

router.get('/export',
  setDefaultQuery(DEFAULT_QUERY),
  getRequestBody(QUERY_FIELDS),
  exportCollection('order'),
)

module.exports = router

const router = require('express').Router()

const { ENTITIES } = require('../../../search/constants')

const { QUERY_FIELDS } = require('../../constants')

const { getCollection, exportCollection } = require('../../../../modules/search/middleware/collection')
const { setDefaultQuery } = require('../../../middleware')
const { getRequestBody } = require('../../../../middleware/collection')

const { renderList } = require('./controllers')
const { setRequestBody } = require('./middleware')
const { transformOrderToListItem } = require('../../transformers')

const DEFAULT_QUERY = {
  sortby: 'created_on:desc',
}

router.get('/',
  setDefaultQuery(DEFAULT_QUERY),
  setRequestBody,
  getCollection('order', ENTITIES, transformOrderToListItem),
  renderList,
)

router.get('/export',
  setDefaultQuery(DEFAULT_QUERY),
  getRequestBody(QUERY_FIELDS),
  exportCollection('order'),
)

module.exports = router

Update QUERY_FIELDS const to only deconstruct the query fields object


if (!cbus.hasOwnProperty("server")) { cbus.server = {} }

(function() {
  const request = require("request");
  const x2j = require("xml2js");
  const path = require("path");

  cbus.server.getPodcastInfo = function(podcastUrl, callback) {
    var podcastData = {};

    request({
      url: podcastUrl,
      headers: REQUEST_HEADERS
    }, function(err, result, body) {
      x2j.parseString(body, function(err, result) {
        if (err) {
          console.log("error parsing xml", err);
          callback(null)
        } else {
          var channel = result.rss.channel[0];
          // console.log(channel);

          // title
          if (existsRecursive(channel, ["title", 0])) {
            podcastData.title = channel.title[0];
          }
          // publisher
          if (existsRecursive(channel, ["itunes:author", 0])) {
            podcastData.publisher = channel["itunes:author"][0];
          }
          // description
          if (channel.description && channel.description[0]) {
            podcastData.description = channel.description[0];
          }
          // image
          if (existsRecursive(channel, ["image", 0, "url", 0])) {
            podcastData.image = channel["image"][0]["url"][0];
          } else if (existsRecursive(channel, ["itunes:image", 0])) {
            podcastData.image = channel["itunes:image"][0].$.href;
          }

          callback(podcastData);
        }
      });
    });
  }
}());

if (!cbus.hasOwnProperty("server")) { cbus.server = {} }

(function() {
  const request = require("request");
  const x2j = require("xml2js");
  const path = require("path");

  cbus.server.getPodcastInfo = function(podcastUrl, callback) {
    var podcastData = {};

    request({
      url: podcastUrl,
      headers: REQUEST_HEADERS
    }, function(err, result, body) {
      x2j.parseString(body, function(err, result) {
        if (err) {
          console.log("error parsing xml", err);
          callback(null)
        } else {
          var channel = result.rss.channel[0];
          // console.log(channel);

          // title
          if (existsRecursive(channel, ["title", 0])) {
            podcastData.title = channel.title[0].trim();
          }
          // publisher
          if (existsRecursive(channel, ["itunes:author", 0])) {
            podcastData.publisher = channel["itunes:author"][0];
          }
          // description
          if (channel.description && channel.description[0]) {
            podcastData.description = channel.description[0];
          }
          // image
          if (existsRecursive(channel, ["image", 0, "url", 0])) {
            podcastData.image = channel["image"][0]["url"][0];
          } else if (existsRecursive(channel, ["itunes:image", 0])) {
            podcastData.image = channel["itunes:image"][0].$.href;
          }

          callback(podcastData);
        }
      });
    });
  }
}());

Trim whitespace from podcast titles


/*global module */
module.exports = function validAuthType(type) {
	'use strict';
	var authTypes = ['AWS_IAM', 'NONE', 'CUSTOM'];
	return (authTypes.indexOf(type) >= 0);
};

/*global module */
module.exports = function validAuthType(type) {
	'use strict';
	var authTypes = ['AWS_IAM', 'NONE', 'CUSTOM', 'COGNITO_USER_POOLS'];
	return (authTypes.indexOf(type) >= 0);
};

Allow cognito user pool authorization


"use strict";

const Handler = require('./handler');

class PermissionHandler extends Handler {
  constructor(data={}) {
    super(data);
    ['permissions'].map(val => this[val] = data[val] ? data[val] : (this[val] ? this[val] : this.constructor[val]));
  }

  async allowAccess(context) {
    if (!this.Account) {
      // Cache the location to the Account plugin, because this portion of code
      // will be run many, many times!
      this.Account = context.engine.pluginRegistry.get('Account');
    }
    for (let permission of this.permissions) {
      if (await this.Account.accountHasPermission(context.account, permission)) {
        return true;
      }
    }
    // Give the user an Access Denied code.
    context.httpResponse = 403;
    return false;
  }
}

PermissionHandler.permissions = [];

module.exports = PermissionHandler;

"use strict";

const Handler = require('./handler');

class PermissionHandler extends Handler {
  constructor(data={}) {
    super(data);
    ['permissions'].map(val => this[val] = data[val] ? data[val] : (this[val] ? this[val] : this.constructor[val]));
  }

  async allowAccess(context) {
    if (!this.Account) {
      // Cache the location to the Account plugin, because this portion of code
      // will be run many, many times!
      this.Account = context.engine.pluginRegistry.get('Account');
    }
    for (let permission of (this.permissions || [])) {
      if (await this.Account.accountHasPermission(context.account, permission)) {
        return true;
      }
    }
    // Give the user an Access Denied code.
    context.httpResponse = 403;
    return false;
  }
}

PermissionHandler.permissions = [];

module.exports = PermissionHandler;

Make PermissionHandler more robust to missing permission directive


const { search, exportSearch } = require('../services')
const { transformApiResponseToSearchCollection } = require('../transformers')

function getCollection (searchEntity, entityDetails, ...itemTransformers) {
  return async function (req, res, next) {
    try {
      res.locals.results = await search({
        searchEntity,
        requestBody: req.body,
        token: req.session.token,
        page: req.query.page,
        isAggregation: false,
      }).then(transformApiResponseToSearchCollection({ query: req.query }, entityDetails, ...itemTransformers))

      next()
    } catch (error) {
      next(error)
    }
  }
}

function exportCollection (searchEntity) {
  return async function (req, res, next) {
    try {
      exportSearch({
        searchEntity,
        requestBody: req.body,
        token: req.session.token,
      }).then(apiReq => {
        return apiReq.pipe(res)
      }).catch(error => {
        return next(error)
      })
    } catch (error) {
      next(error)
    }
  }
}

module.exports = {
  exportCollection,
  getCollection,
}

const { search, exportSearch } = require('../services')
const { transformApiResponseToSearchCollection } = require('../transformers')

function getCollection (searchEntity, entityDetails, ...itemTransformers) {
  return async function (req, res, next) {
    try {
      res.locals.results = await search({
        searchEntity,
        requestBody: req.body,
        token: req.session.token,
        page: req.query.page,
        isAggregation: false,
      }).then(transformApiResponseToSearchCollection({ query: req.query }, entityDetails, ...itemTransformers))

      next()
    } catch (error) {
      next(error)
    }
  }
}

function exportCollection (searchEntity) {
  return async function (req, res, next) {
    return exportSearch({
      searchEntity,
      requestBody: req.body,
      token: req.session.token,
    }).then(apiReq => {
      return apiReq.pipe(res)
    }).catch(error => {
      return next(error)
    })
  }
}

module.exports = {
  exportCollection,
  getCollection,
}

Remove redundant exception handling in exportCollection


/**
 * @require {core.ext.Object}
 * @require {core.ext.String}
 * @require {core.ext.Function}
 */
core.Class("lowland.Object", {
  include : [core.property.MGeneric, lowland.base.UserData, lowland.base.Events],

  construct : function() {
    lowland.base.UserData.call(this);
    lowland.base.Events.call(this);
  }
});

/**
 * @require {core.ext.Object}
 * @require {core.ext.String}
 * @require {core.ext.Function}
 */
core.Class("lowland.Object", {
  include : [core.property.MGeneric, lowland.base.UserData, lowland.base.Events],

  construct : function() {
    lowland.base.UserData.call(this);
    lowland.base.Events.call(this);
  },

  members : {
    error : function() {
      console.error(this.constructor || this, arguments);
    },

    warn : function() {
      console.warn(this.constructor || this, arguments);
    }
  }
});

Add warn and error to generic object


// Simple file for handling text.
var Text = (function() {

	var emailRegex = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}/ig;
	var emailReplacement = '$&';
	var urlRegex = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/ig
	var urlReplacement = '$&';
	var lineRegex = /\n/g;
	var lineReplacement = '
';

	return {
		/**
		 * Escape text so we can output it in a HTML page.
		 * It attempts to escape HTML characters and marks up emails and links.
		 * It currently has a bug in that a & in URL will get escaped twice.
		 * @param source The source text.
		 * @returns The escape/marked up version.
		 */
		"toHtml": function(source) {
			var dest = source.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/, """);
			dest = dest.replace(urlRegex, urlReplacement);
			dest = dest.replace(emailRegex, emailReplacement);
			dest = dest.replace(lineRegex, lineReplacement);
			return dest;
		}
	};
})();

Make links open up in new windows.


// https://en.bitcoin.it/wiki/List_of_address_prefixes
module.exports = {
  bitcoin: {
    bip32: {
      pub: 0x0488b21e,
      priv: 0x0488ade4
    },
    pubKeyHash: 0x00,
    scriptHash: 0x05,
    wif: 0x80
  },
  dogecoin: {
    pubKeyHash: 0x30,
    scriptHash: 0x20,
    wif: 0x9e
  },
  litecoin: {
    scriptHash: 0x30,
  },
  testnet: {
    bip32: {
      pub: 0x043587cf,
      priv: 0x04358394
    },
    pubKeyHash: 0x6f,
    scriptHash: 0xc4,
    wif: 0xef
  }
}

// https://en.bitcoin.it/wiki/List_of_address_prefixes
// Dogecoin BIP32 is a proposed standard: https://bitcointalk.org/index.php?topic=409731
module.exports = {
  bitcoin: {
    bip32: {
      pub: 0x0488b21e,
      priv: 0x0488ade4
    },
    pubKeyHash: 0x00,
    scriptHash: 0x05,
    wif: 0x80
  },
  dogecoin: {
    bip32: {
      pub: 0x02facafd,
      priv: 0x02fac398
    },
    pubKeyHash: 0x1e,
    scriptHash: 0x16,
    wif: 0x9e
  },
  litecoin: {
    bip32: {
      pub: 0x019da462,
      priv: 0x019d9cfe
    },
    pubKeyHash: 0x30,
    scriptHash: 0x05,
    wif: 0xb0
  },
  testnet: {
    bip32: {
      pub: 0x043587cf,
      priv: 0x04358394
    },
    pubKeyHash: 0x6f,
    scriptHash: 0xc4,
    wif: 0xef
  }
}

Fix address prefixes, add dogecoin/litecoin BIP32 versions


var components = require('./components');
var javascript = require('../tasks/javascript');
var extend = require('extend');

/**
 * Helper function for rendering a page
 * Abstracted out here to reduce some duplication in the main server
 */
var renderPage = function(hbs, data) {

  return new Promise(function(resolve, reject) {
    var pageData = {
      pageTitle: data.title + ' - Land Registry pattern library',
      assetPath: '/',
      homepageUrl: '/',
      content: data.content,
      scripts: [],
      propositionName: 'Land Registry pattern library'
    };

    // If any of the demos define pageData we need to pop this up onto the page context
    extend(pageData, data.pageData);

    // Grab all our components, sort them into bundles and then output the
    // necessary script tags to load them
    components
      .getComponents()
      .then(javascript.sort)
      .then(function(bundles) {
        Object.keys(bundles).forEach(function(bundle) {
          pageData.scripts.push('<script async defer src="/javascripts/' + bundle + '.js"></script>')
        });
      })
      .then(function() {
        pageData.scripts = pageData.scripts.join('\n');
      })
      .then(function() {
        resolve(hbs.compile(hbs.partials['layout/govuk_template'])(pageData));
      })
      .catch(function(err) {
        reject(err);
      });

  })

}

module.exports = renderPage;

var components = require('./components');
var javascript = require('../tasks/javascript');
var extend = require('extend');

/**
 * Helper function for rendering a page
 * Abstracted out here to reduce some duplication in the main server
 */
var renderPage = function(hbs, data) {

  return new Promise(function(resolve, reject) {
    var pageData = {
      pageTitle: data.title + ' - Land Registry pattern library',
      assetPath: '/',
      homepageUrl: '/',
      content: data.content,
      scripts: [],
      propositionName: 'Land Registry pattern library'
    };

    // If any of the demos define pageData we need to pop this up onto the page context
    extend(pageData, data.pageData);

    // Grab all our components, sort them into bundles and then output the
    // necessary script tags to load them
    components
      .getComponents()
      .then(javascript.sort)
      .then(function(bundles) {
        Object.keys(bundles).forEach(function(bundle) {
          pageData.scripts.push('<script defer src="/javascripts/' + bundle + '.js"></script>')
        });
      })
      .then(function() {
        pageData.scripts = pageData.scripts.join('\n');
      })
      .then(function() {
        resolve(hbs.compile(hbs.partials['layout/govuk_template'])(pageData));
      })
      .catch(function(err) {
        reject(err);
      });

  })

}

module.exports = renderPage;

Remove async again - not compatibile with leafletjs


import { createAction } from 'redux-actions';
import { createThunkAction } from 'utils/redux';

const fetchStoriesInit = createAction('fetchStoriesInit');
const fetchStoriesReady = createAction('fetchStoriesReady');
const fetchStoriesFail = createAction('fetchStoriesFail');

const fetchStories = createThunkAction('fetchStories', () => dispatch => {
  dispatch(fetchStoriesInit());
  fetch('/api/v1/stories')
    .then(response => {
      if (response.ok) return response.json();
      throw Error(response.statusText);
    })
    .then(data => {
      dispatch(fetchStoriesReady(data));
    })
    .catch(error => {
      console.info(error);
      dispatch(fetchStoriesFail());
    });
});

export default {
  fetchStories,
  fetchStoriesInit,
  fetchStoriesReady,
  fetchStoriesFail
};

import { createAction } from 'redux-actions';
import { createThunkAction } from 'utils/redux';

const fetchStoriesInit = createAction('fetchStoriesInit');
const fetchStoriesReady = createAction('fetchStoriesReady');
const fetchStoriesFail = createAction('fetchStoriesFail');

const TAGS = ['NDC', 'ndcsdg', 'esp', 'climate watch'];

const fetchStories = createThunkAction('fetchStories', () => dispatch => {
  dispatch(fetchStoriesInit());
  fetch(`/api/v1/stories?tags=${TAGS}`)
    .then(response => {
      if (response.ok) return response.json();
      throw Error(response.statusText);
    })
    .then(data => {
      dispatch(fetchStoriesReady(data));
    })
    .catch(error => {
      console.info(error);
      dispatch(fetchStoriesFail());
    });
});

export default {
  fetchStories,
  fetchStoriesInit,
  fetchStoriesReady,
  fetchStoriesFail
};

Add filter tags to stories actions on js app


/**
 * Cache data.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { trackEvent } from './';

export async function trackAPIError( method, type, identifier, datapoint, error ) {
	// Exclude certain errors from tracking based on error code.
	const excludedErrorCodes = [
		'fetch_error', // Client failed to fetch from WordPress.
	];
	if ( excludedErrorCodes.indexOf( error.code ) >= 0 ) {
		return;
	}

	await trackEvent(
		'api_error',
		`${ method }:${ type }/${ identifier }/data/${ datapoint }`,
		`${ error.message } (code: ${ error.code }${ error.data?.reason ? ', reason: ' + error.data.reason : '' } ])`,
		error.data?.status || error.code
	);
}

/**
 * Track API errors.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { trackEvent } from './';

export async function trackAPIError( method, type, identifier, datapoint, error ) {
	// Exclude certain errors from tracking based on error code.
	const excludedErrorCodes = [
		'fetch_error', // Client failed to fetch from WordPress.
	];
	if ( excludedErrorCodes.indexOf( error.code ) >= 0 ) {
		return;
	}

	await trackEvent(
		'api_error',
		`${ method }:${ type }/${ identifier }/data/${ datapoint }`,
		`${ error.message } (code: ${ error.code }${ error.data?.reason ? ', reason: ' + error.data.reason : '' } ])`,
		error.data?.status || error.code
	);
}

Improve track API file header.


// Server startup script
// =====================
// Responsible for configuring the server,
// inserting middleware, and starting the
// app up.
//
// Note: this was written on my iPhone, please excuse typos

'use strict'

let express = require('express'),
    exphbs  = require('express-handlebars'),
    app     = express(),
    _       = require('lodash'),
    config  = _.merge(require(__dirname + '/config/app').common, require(__dirname + '/config/app')[process.env.NODE_ENV]);

// App configuration
// -----------------
// Configure views and other settings
app.engine('hbs', exphbs(config.handlebars));
app.set('view engine', 'hbs');
app.set('views', __dirname + '/views');

// Middleware
// ----------
// Insert, configure, update middleware

// Routes
// ------
// Initialize user facing routes
require('./controllers')(app);

// Start server
// ------------
// Start the server
let server = app.listen(process.env.PORT, () => {
  console.log('Started app on localhost:' + server.address().port);
});

// Server startup script
// =====================
// Responsible for configuring the server,
// inserting middleware, and starting the
// app up.
//
// Note: this was written on my iPhone, please excuse typos

'use strict'

let express = require('express'),
    exphbs  = require('express-handlebars'),
    app     = express(),
    _       = require('lodash'),
    config  = _.merge(require(__dirname + '/config/app').common, require(__dirname + '/config/app')[process.env.NODE_ENV]);

// App configuration
// -----------------
// Configure views and other settings
app.engine('hbs', exphbs(config.handlebars));
app.set('view engine', 'hbs');
app.set('views', __dirname + '/views');

// Middleware
// ----------
// Insert, configure, update middleware
if (_.includes(['development', 'test'], process.env.NODE_ENV)) {
  // DEVELOPMENT/TEST MIDDLEWARE
  app.use(express.static(__dirname + '/public'));
}

// Routes
// ------
// Initialize user facing routes
require('./controllers')(app);

// Start server
// ------------
// Start the server
let server = app.listen(process.env.PORT, () => {
  console.log('Started app on localhost:' + server.address().port);
});

Add static server middleware that is active in non-production environments only.


const Command = require('../Command')

module.exports =
class PingCommand extends Command {
  constructor (client) {
    super(client, {
      name: 'ping',
      properName: 'Ping',
      description: 'Ping the bot to see API latency'
    })
  }
  async fn (msg) {
    msg.channel.send('Pinging...').then(message => {
      message.edit(`:ping_pong: Pong! Took **${message.createdTimestamp - msg.createdTimestamp}ms**`)
    })
  }
}

const Command = require('../Command')

module.exports =
class PingCommand extends Command {
  constructor (client) {
    super(client, {
      name: 'ping',
      properName: 'Ping',
      description: 'Ping the bot to see API latency',
      userPermissions: []
    })
  }
  async fn (msg) {
    msg.channel.send('Pinging...').then(message => {
      message.edit(`:ping_pong: Pong! Took **${message.createdTimestamp - msg.createdTimestamp}ms**`)
    })
  }
}

Allow ping command to be used by anyone


(function() {
  'use strict';

  define(['sockjs'], function (sockjs) {
    var sockjs_opts = {
      sockjs_url: 'http://cdn.sockjs.org/sockjs-0.3.min.js'
    };

    return sockjs.createServer(sockjs_opts);
  });
}());

(function() {
  'use strict';

  define(['sockjs'], function (sockjs) {
    return sockjs.createServer();
  });
}());

Remove unused SockJS server-side config options


import { ADD_TEAM_SUCCESS } from './actionCreators'

let initialState = [
  { id: 2, name: 'gym', colour: "#49078d", balance: 105463 },
  { id: 1, name: 'fred', colour: "#23fe4d", balance: 105463 },
  { id: 3, name: 'suup', colour: "#1fffe4", balance: 105463 }
]

export default (state=initialState, action) => {
  switch (action.type) {
    case ADD_TEAM_SUCCESS:
      return Object.keys(action.response.teams).map(key => action.response.teams[key])

    default:
      return state
  }
}

import { ADD_TEAM_SUCCESS } from './actionCreators'

const teams_in = (hash) => Object.keys(hash).map(key => hash[key])

export default (state=[], action) => {
  switch (action.type) {
    case ADD_TEAM_SUCCESS:
      return teams_in(action.response.teams)

    default:
      return state
  }
}

Delete the hard-coded fake teams


'use strict';

var trackStar = (function(){
  function TrackStar() {
    var integrations = {};

    if(!(this instanceof TrackStar)){
      return new TrackStar();
    }

    this.getIntegrations = function() {
      return integrations;
    };

    this.integrate = function(integrationsObj) {
      if (Object.prototype.toString.call(integrationsObj) !== '[object Object]') {
        throw new Error('trackStar requires an Object of integrations');
      }

      for(var key in integrationsObj){
        var val = integrationsObj[key];
        if (integrations.hasOwnProperty(key)) {
          integrations[key] = integrations[key].concat(val);
        } else {
          integrations[key] = [].concat(val);
        }
      }

      return this;
    };

    // Start testing API

    this.wipeClean = TrackStar;

    // End testing API

    return this;
  }

  function sendFunction (context, functionName, opts) {
    var integrations = context.getIntegrations();

    for (var key in integrations){
      _integrationsMasterList[key][functionName](opts);
    };
  }

  TrackStar.prototype.trackPageView = function() {
    sendFunction(this, 'trackPageView');
  };

  TrackStar.prototype.trackConversion = function(){
    sendFunction(this, 'trackConversion');
  };

  TrackStar.prototype.trackAction = function(opts){
    sendFunction(this, 'trackAction', opts);
  };

  return window.trackStar = TrackStar();
}());

'use strict';

var trackStar = (function(){
  function TrackStar() {
    var integrations = {};

    if(!(this instanceof TrackStar)){
      return new TrackStar();
    }

    this.getIntegrations = function() {
      return integrations;
    };

    this.integrate = function(integrationsObj) {
      if (Object.prototype.toString.call(integrationsObj) !== '[object Object]') {
        throw new Error('trackStar requires an Object of integrations');
      }

      for(var key in integrationsObj){
        if (integrationsObj.hasOwnProperty[key]){
          var val = integrationsObj[key];
          if (integrations.hasOwnProperty(key)) {
            integrations[key] = integrations[key].concat(val);
          } else {
            integrations[key] = [].concat(val);
          }
        }
      }

      return this;
    };

    // Start testing API

    this.wipeClean = TrackStar;

    // End testing API

    return this;
  }

  function sendFunction (context, functionName, opts) {
    var integrations = context.getIntegrations();

    for (var key in integrations){
      if(integrations.hasOwnProperty(key)){
        _integrationsMasterList[key][functionName](opts);
      }
    }
  }

  TrackStar.prototype.trackPageView = function() {
    sendFunction(this, 'trackPageView');
  };

  TrackStar.prototype.trackConversion = function(){
    sendFunction(this, 'trackConversion');
  };

  TrackStar.prototype.trackAction = function(opts){
    sendFunction(this, 'trackAction', opts);
  };

  return window.trackStar = TrackStar();
}());

Make sure we're not grabbing any prototype props


/** Copyright (c) 2013 Jan Nicklas Released under MIT license */

/* global define: false, jQuery: true */
/* jshint sub:true */

// RequireJS amd factory
// http://stackoverflow.com/questions/10918063/how-to-make-a-jquery-plugin-loadable-with-requirejs#answer-11890239
(function (factory) {
  'use strict';
  if (typeof define === 'function' && define.amd) {
    // AMD. Register as an anonymous module.
    define(['jquery'], factory);
  } else {
    // Run without AMD
    factory(jQuery);
  }
}(function ($) {
  'use strict';
  // @@ code @@ //
}));

/** Copyright (c) 2013 Jan Nicklas Released under MIT license */

/* global define: false, jQuery: true */
/* jshint sub:true */

// RequireJS amd factory
// http://stackoverflow.com/questions/10918063/how-to-make-a-jquery-plugin-loadable-with-requirejs#answer-11890239
(function (factory) {
  'use strict';
  if (typeof define === 'function' && define.amd) {
    // AMD. Register as an anonymous module.
    define(['jquery'], factory);
  } else {
    // Run without AMD
    factory(jQuery);
  }
}(function ($) {
  'use strict';
  // @@ include ariaMenu.js @@ //
}));

Add the name of the included script


import React, { useState } from 'react';
import classnames from 'classnames';
import { search } from './api';
import SearchByPostcode from './SearchByPostcode';
import EmailComposer from './EmailComposer';
import ComponentWrapper from '../../components/ComponentWrapper';
import { redirect } from '../../util/redirector';

export const init = options => {
  if (!options.config || !options.config.active) return;
  if (options.el) {
    render(
      <EmailParliament config={options.config} onSend={options.onSend} />,
      options.el
    );
  }
};

const EmailParliament = props => {
  const [target, setTarget] = useState(null);
  const searchClassname = classnames({
    'hidden-irrelevant': target !== null,
  });
  return (
    <div className="EmailParliament">
      <ComponentWrapper locale={props.config.locale}>
        <SearchByPostcode className={searchClassname} onChange={setTarget} />
        <EmailComposer
          title={props.config.title}
          postcode={''}
          target={target}
          subject={props.config.subject}
          template={props.config.template}
          onSend={props.onSend || redirect}
        />
      </ComponentWrapper>
    </div>
  );
};

export default EmailParliament;

import React, { useState } from 'react';
import { render } from 'react-dom';
import classnames from 'classnames';
import { search } from './api';
import SearchByPostcode from './SearchByPostcode';
import EmailComposer from './EmailComposer';
import ComponentWrapper from '../../components/ComponentWrapper';
import { redirect } from '../../util/redirector';

export const init = options => {
  if (!options.config || !options.config.active) return;
  if (options.el) {
    render(
      <EmailParliament config={options.config} onSend={options.onSend} />,
      options.el
    );
  }
};

const EmailParliament = props => {
  const [target, setTarget] = useState(null);
  const searchClassname = classnames({
    'hidden-irrelevant': target !== null,
  });
  return (
    <div className="EmailParliament">
      <ComponentWrapper locale={props.config.locale}>
        <SearchByPostcode className={searchClassname} onChange={setTarget} />
        <EmailComposer
          title={props.config.title}
          postcode={''}
          target={target}
          subject={props.config.subject}
          template={props.config.template}
          onSend={props.onSend || redirect}
        />
      </ComponentWrapper>
    </div>
  );
};

export default EmailParliament;

Fix `render is undefined` in EmailParliament


'use strict';
import React from 'react';

var CommunityCard = React.createClass({
  displayName: 'CommunityCard',

  propTypes: {
    title: React.PropTypes.string,
    linkTitle: React.PropTypes.string,
    url: React.PropTypes.string,
    imageNode: React.PropTypes.node,
    children: React.PropTypes.object
  },

  render: function () {
    return (

              {this.props.imageNode}

{this.props.title}

              {this.props.children}
              Learn More

    );
  }
});

module.exports = CommunityCard;

'use strict';
import React from 'react';
import c from 'classnames';

var CommunityCard = React.createClass({
  displayName: 'CommunityCard',

  propTypes: {
    title: React.PropTypes.string,
    linkTitle: React.PropTypes.string,
    url: React.PropTypes.string,
    imageNode: React.PropTypes.node,
    horizontal: React.PropTypes.bool,
    children: React.PropTypes.object
  },

  getDefaultProps: function () {
    return {
      horizontal: false
    };
  },

  render: function () {
    return (

              {this.props.imageNode}

{this.props.title}

              {this.props.children}
              Learn More

    );
  }
});

module.exports = CommunityCard;

Make community card horizontal by default


import Metalsmith from 'metalsmith';
import common from '@rollup/plugin-commonjs';
import inPlace from '@metalsmith/in-place';
import layouts from '@metalsmith/layouts';
import markdown from '@metalsmith/markdown';
import {dirname, resolve} from 'node:path';
import {env} from 'node:process';
import {fileURLToPath} from 'node:url';
import {nodeResolve} from '@rollup/plugin-node-resolve';
import {rollup} from 'rollup';
import {terser} from 'rollup-plugin-terser';

const baseDir = dirname(fileURLToPath(import.meta.url));

const builder = Metalsmith(baseDir)
  .source('./src')
  .destination('./build')
  .clean(true)
  .metadata({
    version: env.OL_VERSION || 'dev',
  })
  .use(inPlace())
  .use(markdown())
  .use(layouts());

builder.build(async (err) => {
  if (err) {
    throw err;
  }
  await bundleMain();
});

async function bundleMain() {
  const inputOptions = {
    plugins: [
      common(),
      nodeResolve({
        moduleDirectories: [
          resolve(baseDir, '../src'),
          resolve(baseDir, '../node_modules'),
        ],
      }),
      terser(),
    ],
    input: resolve(baseDir, './build/main.js'),
  };

  const outputOptions = {
    dir: resolve(baseDir, './build'),
    format: 'iife',
  };

  const bundle = await rollup(inputOptions);
  await bundle.write(outputOptions);
  bundle.close();
}

import Metalsmith from 'metalsmith';
import common from '@rollup/plugin-commonjs';
import inPlace from '@metalsmith/in-place';
import layouts from '@metalsmith/layouts';
import markdown from '@metalsmith/markdown';
import {dirname, resolve} from 'node:path';
import {env} from 'node:process';
import {fileURLToPath} from 'node:url';
import {nodeResolve} from '@rollup/plugin-node-resolve';
import {rollup} from 'rollup';
import {terser} from 'rollup-plugin-terser';

const baseDir = dirname(fileURLToPath(import.meta.url));

const builder = Metalsmith(baseDir)
  .source('./src')
  .destination('./build')
  .clean(true)
  .metadata({
    version: env.OL_VERSION || 'dev',
  })
  .use(inPlace())
  .use(markdown())
  .use(layouts());

builder.build(async (err) => {
  if (err) {
    throw err;
  }
  await bundleMain();
});

async function bundleMain() {
  const inputOptions = {
    plugins: [
      common(),
      nodeResolve({
        modulePaths: [
          resolve(baseDir, '../src'),
          resolve(baseDir, '../node_modules'),
        ],
      }),
      terser(),
    ],
    input: resolve(baseDir, './build/main.js'),
  };

  const outputOptions = {
    dir: resolve(baseDir, './build'),
    format: 'iife',
  };

  const bundle = await rollup(inputOptions);
  await bundle.write(outputOptions);
  bundle.close();
}

Use modulePaths instead of moduleDirectories


import React from 'react';
import { changeApplicationFieldValue } from '../../actions/application';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';

const ApplicationTextField = ({changeApplicationFieldValue, field, applicationForm, disabled, isLoading, styles, options}) => (
    <select
      value={applicationForm[field] || ""}
      onChange={
        (e) => changeApplicationFieldValue(field,e.target.value)
      }
      disabled={(disabled || false) || isLoading}
      className="dropdown"
    >
        { Object.keys(options).map((t,i) => <option key={i} value={t}>{t}</option>) }
        {/* <option value="grapefruit">Grapefruit</option>
        <option value="lime">Lime</option>
        <option value="coconut">Coconut</option>
        <option value="mango">Mango</option> */}
    </select>
);

function mapStateToProps (state) {
    return {
        applicationForm: state.application.applicationForm,
        isLoading: state.application.isLoading
    };
}

const mapDispatchToProps = (dispatch) => {
    return bindActionCreators({changeApplicationFieldValue}, dispatch)
};

export default connect(mapStateToProps, mapDispatchToProps)(ApplicationTextField);

import React from 'react';
import { changeApplicationFieldValue } from '../../actions/application';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';

const ApplicationDropdown = ({changeApplicationFieldValue, field, applicationForm, disabled, isLoading, styles, options}) => (
    <select
      value={applicationForm[field] || ""}
      onChange={
        (e) => changeApplicationFieldValue(field,e.target.value)
      }
      disabled={(disabled || false) || isLoading}
      className="dropdown"
    >
        { Object.keys(options).map((t,i) => <option key={i} value={t}>{t}</option>) }
        {/* <option value="grapefruit">Grapefruit</option>
        <option value="lime">Lime</option>
        <option value="coconut">Coconut</option>
        <option value="mango">Mango</option> */}
    </select>
);

function mapStateToProps (state) {
    return {
        applicationForm: state.application.applicationForm,
        isLoading: state.application.isLoading
    };
}

const mapDispatchToProps = (dispatch) => {
    return bindActionCreators({changeApplicationFieldValue}, dispatch)
};

export default connect(mapStateToProps, mapDispatchToProps)(ApplicationDropdown);

Update dropdown with properr var name


import Kata from './kata.js';

export default class KataGroup {

  static withKatas(groupName, rawKataItems) {
    var group = new KataGroup();
    group.name = groupName;
    group.createKatas(rawKataItems);
    group.sortByName();
    return group;
  }

  createKatas(rawKataItems) {
    this.katas = rawKataItems.map(item => Kata.fromRawItem(item));
  }

  sortByName() {
    this.katas.sort((kata1, kata2) => toInt(kata1.id) < toInt(kata2.id) ? -1 : 1);
  }

  get highestId() {
    if (this.katas.length === 0) {
      return 0;
    }
    return this.katas[this.katas.length - 1].id;
  }
}

const toInt = (string) => Number.parseInt(string, 10);

import Kata from './kata.js';

export default class KataGroup {

  static withKatas(groupName, rawKataItems) {
    var group = new KataGroup();
    group.name = groupName;
    group.createKatas(rawKataItems);
    group.sortByName();
    return group;
  }

  createKatas(rawKataItems) {
    this.katas = rawKataItems.map(item => Kata.fromRawItem(item));
  }

  sortByName() {
    this.katas.sort((kata1, kata2) => kata1.id < kata2.id ? -1 : 1);
  }

  get highestId() {
    if (this.katas.length === 0) {
      return 0;
    }
    return this.katas[this.katas.length - 1].id;
  }
}

Remove unnecessary conversion, that is done now at kata creation time.


import { isArray } from 'lodash';
import { AppNavigator } from 'DailyScrum/src/Scenes';

export default (state, action) => {
  const { type } = action;
  if (type === 'Navigation/NAVIGATE' && isRouteSameAsLastRouteFromNavigationStateSelector(state, action)) {
    console.warn(
      'You pressed the navigation button two times, pushing two times to the same route.\n\n' +
        'The last dispatch was canceled. \n\n' +
        'If the call was intended, you can add an exception in redux routing.',
      action
    );
    return state || {};
  }

  const newState = AppNavigator.router.getStateForAction(action, state);
  return newState || state;
};

const isRouteSameAsLastRouteFromNavigationStateSelector = (state, action) => {
  const lastRoute = routeFromNavigationStateSelector(state);

  if (!lastRoute) {
    return false;
  }

  // FUTURE add exceptions here (params in lastRoute.params, action.params)

  return lastRoute.routeName === action.routeName;
};

export const routeFromNavigationStateSelector = state => {
  const currentRootRoute = state.routes[state.index];
  let route;
  if (isArray(currentRootRoute.routes)) {
    route = currentRootRoute.routes[currentRootRoute.index];
  } else {
    route = currentRootRoute;
  }
  return route;
};

import { isArray, isEqual } from 'lodash';
import { AppNavigator } from 'DailyScrum/src/Scenes';

export default (state, action) => {
  const { type } = action;
  if (type === 'Navigation/NAVIGATE' && isRouteSameAsLastRouteFromNavigationStateSelector(state, action)) {
    console.warn(
      'You pressed the navigation button two times, pushing two times to the same route.\n\n' +
        'The last dispatch was canceled. \n\n' +
        'If the call was intended, you can add an exception in redux routing.',
      action
    );
    return state || {};
  }

  const newState = AppNavigator.router.getStateForAction(action, state);
  return newState || state;
};

const isRouteSameAsLastRouteFromNavigationStateSelector = (state, action) => {
  const lastRoute = routeFromNavigationStateSelector(state);

  if (!lastRoute) {
    return false;
  }

  // FUTURE add exceptions here (params in lastRoute.params, action.params)
  if (lastRoute.routeName !== action.routeName) {
    return false;
  }

  return isEqual(lastRoute.params, action.params);
};

export const routeFromNavigationStateSelector = state => {
  const currentRootRoute = state.routes[state.index];
  let route;
  if (isArray(currentRootRoute.routes)) {
    route = currentRootRoute.routes[currentRootRoute.index];
  } else {
    route = currentRootRoute;
  }
  return route;
};

Fix navigating twice with params


import express from 'express';
import UserController from '../controllers/UserController';

const userRouter = express.Router();
userRouter.post('/api/user/signup', UserController.createUser());
userRouter.post('/api/user/signin', [
  UserController.validateRequest(),
  UserController.authenticateUser()
]);
userRouter.use('/api', [
  UserController.getClientAuthToken(),
  UserController.authorizeUser()
]);
userRouter.get('/api/user/groups', UserController.getUserGroups());

export default userRouter;

import express from 'express';
import UserController from '../controllers/UserController';

const userRouter = express.Router();
userRouter.post('/api/user/signup', UserController.createUser());
userRouter.post('/api/user/signin', [
  UserController.validateRequest(),
  UserController.authenticateUser()
]);
userRouter.use('/api', [
  UserController.getClientAuthToken(),
  UserController.authorizeUser()
]);
userRouter.put('/api/user', UserController.updateUser());
userRouter.delete('/api/user', UserController.deleteUser());
userRouter.get('/api/user/groups', UserController.getUserGroups());

export default userRouter;

Add endpoints to update and delete user


$(document).ready(function (){
    var width = $(window).width(),
        costumizedWith = width - 30,
        image = $('.artis-img');
        headline = $('.headline-img');
        ads = $('.ads');
    image.css('width', costumizedWith);
    headline.css('width', costumizedWith);
    ads.css('width', costumizedWith);
});

$(document).ready(function (){
    var width = $(window).width(),
        costumizedWith = width - 30,
        image = $('.artis-img'),
        headline = $('.headline-img'),
        ads = $('.ads');
    image.css('width', costumizedWith);
    headline.css('width', costumizedWith);
    ads.css('width', costumizedWith);
});

Use , rather than ;


// @flow
import fs from 'fs';
import path from 'path';
import { appFolderPath } from '../config';
import { getNumberOfEpochsConsolidatedChannel } from '../ipc/getNumberOfEpochsConsolidated.ipc';
import type { GetNumberOfEpochsConsolidatedChannelResponse } from '../../common/ipc/api';

export const getNumberOfEpochsConsolidated = () => {
  getNumberOfEpochsConsolidatedChannel
    .onRequest((): Promise<GetNumberOfEpochsConsolidatedChannelResponse> => {
      const epochsPath = path.join(appFolderPath, 'DB-1.0', 'epochs');
      let latestConsolidatedEpoch = 0;
      if (fs.existsSync(epochsPath)) {
        const epochfiles = fs
          .readdirSync(epochsPath)
          .filter(file => file.indexOf('.epoch') > -1)
          .map(file => parseInt(file.split('.').shift(), 10));
        if (epochfiles.length) latestConsolidatedEpoch = Math.max(...epochfiles);
      }
      return Promise.resolve(latestConsolidatedEpoch);
    });
};

// @flow
import fs from 'fs';
import path from 'path';
import { appFolderPath } from '../config';
import { getNumberOfEpochsConsolidatedChannel } from '../ipc/getNumberOfEpochsConsolidated.ipc';
import type { GetNumberOfEpochsConsolidatedChannelResponse } from '../../common/ipc/api';
import { environment } from '../environment';

const { isLinux } = environment;

export const getNumberOfEpochsConsolidated = () => {
  getNumberOfEpochsConsolidatedChannel
    .onRequest((): Promise<GetNumberOfEpochsConsolidatedChannelResponse> => {
      const epochsPath = isLinux
        ? path.join(appFolderPath, 'DB', 'epochs')
        : path.join(appFolderPath, 'DB-1.0', 'epochs');
      let latestConsolidatedEpoch = 0;
      if (fs.existsSync(epochsPath)) {
        const epochfiles = fs
          .readdirSync(epochsPath)
          .filter(file => file.indexOf('.epoch') > -1)
          .map(file => parseInt(file.split('.').shift(), 10));
        if (epochfiles.length) latestConsolidatedEpoch = Math.max(...epochfiles);
      }
      return Promise.resolve(latestConsolidatedEpoch);
    });
};

Correct path for EPOCH files in Linux


'use strict';
var fs = require('fs');
var Download = require('download');
var CSV = require('comma-separated-values');
var arrayUniq = require('array-uniq');

new Download({extract: true})
	.get('http://www.mapcode.com/kader/isotables.zip')
	.run(function (err, files) {
		var data = files[0].contents.toString();
		var items = new CSV(data, {header: true}).parse();
		var ret = [];

		items.forEach(function (el) {
			if (el.Territory !== '') {
				ret.push(el.Territory);
			}

			if (el['Local code'] !== '') {
				ret.push(el['Local code']);
			}

			if (el['Full code'] !== '') {
				ret.push(el['Full code']);
			}
		});

		var out = '\'use strict\';\nmodule.exports = function () {\n\treturn /(?:(' + arrayUniq(ret).sort().join('|') + ') )?[A-Z0-9]{2,}\.[A-Z0-9]{2,}/g;\n};';

		fs.writeFileSync('index.js', out);
	});

'use strict';
var fs = require('fs');
var Download = require('download');
var CSV = require('comma-separated-values');
var arrayUniq = require('array-uniq');

new Download({extract: true})
	.get('http://www.mapcode.com/kader/isotables.zip')
	.run(function (err, files) {
		var data = files[0].contents.toString();
		var items = new CSV(data, {header: true}).parse();
		var ret = [];

		items.forEach(function (el) {
			if (el.Territory !== '') {
				ret.push(el.Territory);
			}

			if (el['Local code'] !== '') {
				ret.push(el['Local code']);
			}

			if (el['Full code'] !== '') {
				ret.push(el['Full code']);
			}
		});

		var out = '\'use strict\';\nmodule.exports = function () {\n\treturn /(?:(' + arrayUniq(ret).sort().join('|') + ') )?[A-Z0-9]{2,5}\.[A-Z0-9]{2,4}/g;\n};';

		fs.writeFileSync('index.js', out);
	});

Set max length on mapcode pieces


define([
  'jquery', // src/jquery.js,
  'defaults'
], function ($, defaults) {

  function Crayon(element, options) {
    console.error('construct');
    this.element = element;
    this.options = $.extend({}, defaults, options);
    this.init();
  }

  // Define plugin
  Crayon.prototype = {

    elems: null,

    init: function () {
      console.error('init');
      this.elems = this.query();
      this.load(this.elems);
    },

    query: function () {
      return $(this.options.selector, this.element);
    },

    load: function (elems) {
      var me = this;
      elems.each(function (i, pre) {
        var atts = $(pre).attr(me.options.attrSelector);
        var parsedAtts = me.options.attrParser(atts);
        pre.crayon = {
          atts: parsedAtts
        };
        console.log('atts', parsedAtts);
        var output = me.parse(me.options.getValue(pre), parsedAtts);
        if (output && output.length) {
          me.options.setValue(pre, output);
        }
        console.log('output', output);
      });
    },

    parse: function (value, atts) {
      // TODO Load language, cache
      // TODO Apply regex to code
      // TODO Return output
      console.log('value', value);
      return value;
    }

  };

  return Crayon;

});

define([
  'jquery', // src/jquery.js,
  'defaults'
], function ($, defaults) {

  function Crayon(element, options) {
    console.error('construct');
    this.element = element;
    this.options = $.extend({}, defaults, options);
    this.init();
  }

  // Define plugin
  Crayon.prototype = {

    nodes: null,

    init: function () {
      console.error('init');
      this.nodes = this.query();
      this.load(this.nodes);
    },

    query: function () {
      return $(this.options.selector, this.element);
    },

    load: function (nodes) {
      var me = this;
      nodes.each(function (i, node) {
        var atts = $(node).attr(me.options.attrSelector);
        var parsedAtts = me.options.attrParser(atts);
        node.crayon = {
          atts: parsedAtts
        };
        console.log('atts', parsedAtts);
        var output = me.parse(me.options.getValue(node), parsedAtts);
        if (output && output.length) {
          me.options.setValue(node, output);
        }
        console.log('output', output);
      });
    },

    parse: function (value, atts) {
      // TODO Load language, cache
      // TODO Apply regex to code
      // TODO Return output
      console.log('value', value);
      return value;
    }

  };

  return Crayon;

});

Rename of pre and elems to generic nodes


'use strict';

var path = require('path');

var expect = require('thehelp-test').expect;
var supertest = require('supertest');
var util = require('./util');

describe('top-level crash in worker', function() {
  var agent, child;

  before(function(done) {
    agent = supertest.agent('http://localhost:3000');

    child = util.startProcess(path.join(__dirname, '../../scenarios/crash_worker.js'));
    setTimeout(done, 1000);
  });

  it('logs out top-level exception, calls last-ditch, graceful shutdown', function(done) {
    this.timeout(10000);

    child.on('close', function() {
      expect(child).to.have.property('result');

      expect(child.result).to.match(/LastDitch: crash/);
      expect(child.result).to.match(/Worker #1 top-level domain error/);
      expect(child.result).to.match(/Worker #2 top-level domain error/);

      expect(child.result).to.match(/died after less than spin timeout/);
      expect(child.result).to.match(/No workers currently running!/);

      expect(child.result).to.match(/All workers gone./);

      done();
    });

    setTimeout(function() {
      child.kill();
    }, 2000);
  });
});

'use strict';

var path = require('path');

var expect = require('thehelp-test').expect;
var supertest = require('supertest');
var util = require('./util');

describe('top-level crash in worker', function() {
  var agent, child;

  before(function(done) {
    agent = supertest.agent('http://localhost:3000');

    child = util.startProcess(path.join(__dirname, '../../scenarios/crash_worker.js'));
    setTimeout(done, 1000);
  });

  it('logs out top-level exception, calls last-ditch, graceful shutdown', function(done) {
    this.timeout(10000);

    child.on('close', function() {
      expect(child).to.have.property('result');

      expect(child.result).to.match(/LastDitch: crash/);
      expect(child.result).to.match(/Worker #1 top-level domain error/);
      expect(child.result).to.match(/Worker #2 top-level domain error/);

      expect(child.result).to.match(/died after less than spin timeout/);
      expect(child.result).to.match(/No workers currently running!/);

      expect(child.result).to.match(/All workers gone./);

      done();
    });

    setTimeout(function() {
      child.kill();
    }, 4000);
  });
});

Increase timeout in crash/worker test for travis


import React from 'react';
import './scss/footer.scss';

const Footer = () => {
  return (
    <footer>
      <p>© 2016 Michael Kohler - JavaScript frontend and backend developer
        with a passion for Web Standards</p>
    </footer>
  );
};

export default Footer;

import React from 'react';
import './scss/footer.scss';

const Footer = () => {
  return (
    <footer>
      <p>© 2016 Michael Kohler - Data accurate from 2013 onwards, before some are missing..</p>
    </footer>
  );
};

export default Footer;

Add data accuracy note in footer


'use strict';

let blessed = require('blessed');
let _ = require('lodash');
let config = require('../../config/config');

module.exports = function () {
    let hostDetailsPanel = blessed.list({
        top: 5,
        bottom: 5,
        left: '30%',
        tags: true,
        border: {type: 'line'},
        keys: true,
        scrollable: true,
        style: config.styles.main
    });

    hostDetailsPanel.updateItems = function (host) {
        let items = [];
        _.forEach(host, function (val, key) {
            if (_.isArray(val)) val = val.join(', ');
            let content = '{bold}' + key + '{/bold} : ' + val + '\n';
            items.push(content);
        });
        this.setItems(items);
    };

    return hostDetailsPanel;
};

'use strict';

let blessed = require('blessed');
let _ = require('lodash');
let config = require('../../config/config');

module.exports = function () {
    let hostDetailsPanel = blessed.list({
        top: 5,
        bottom: 5,
        left: '30%',
        tags: true,
        border: {type: 'line'},
        keys: true,
        scrollable: true,
        style: config.styles.main
    });

    hostDetailsPanel.updateItems = function (host) {
        let items = [];
        _.forEach(host, function (val, key) {
            if (_.isArray(val)) val = val.join(', ');
            key = key[0].toUpperCase() + key.slice(1);
            let content = '{bold}' + key + '{/bold} : ' + val + '\n';
            items.push(content);
        });
        this.setItems(items);
    };

    return hostDetailsPanel;
};

Fix keys case into details panel


angular.module('nnConsumerUi')
  .config(function($translateProvider) {
    $translateProvider.translations('fi', {
      'REQUIRED_FIELD': 'Pakollinen kenttä',
      'CHARACTERS_LEFT': 'Merkkiä jäljellä',
      'CHOOSE_FILE': 'Valitse tiedosto',
      'PRINT_BUTTON': 'Tulosta',
      'SEND_MAIL_BUTTON': 'Lähetä sähköpostiin',
      'MAIL_SENT_TEXT': 'Sähköposti lähetetty'
    });

    $translateProvider.translations('sv', {
      'REQUIRED_FIELD': 'Obligatoriskt fält',
      'CHARACTERS_LEFT': 'Tecken kvar',
      'CHOOSE_FILE': 'Välj fil',
      'PRINT_BUTTON': 'Skriv ut',
      'SEND_MAIL_BUTTON': 'Skicka till e-post',
      'MAIL_SENT_TEXT': 'E-post skickad'
    });

    $translateProvider.translations('en', {
      'REQUIRED_FIELD': 'Required field',
      'CHARACTERS_LEFT': 'Characters left',
      'CHOOSE_FILE': 'Choose file',
      'PRINT_BUTTON': 'Print',
      'SEND_MAIL_BUTTON': 'Send to e-mail',
      'MAIL_SENT_TEXT': 'E-mail sent'
    });

    $translateProvider.preferredLanguage('fi');
    $translateProvider.useSanitizeValueStrategy(null);
  });

angular.module('nnConsumerUi')
  .config(function($translateProvider) {
    $translateProvider.translations('fi', {
      'REQUIRED_FIELD': 'Pakollinen kenttä',
      'CHARACTERS_LEFT': 'Merkkiä jäljellä',
      'CHOOSE_FILE': 'Valitse tiedosto',
      'PRINT_BUTTON': 'Tulosta',
      'SEND_MAIL_BUTTON': 'Lähetä sähköpostiin',
      'MAIL_SENT_TEXT': 'Sähköposti lähetetty',
      'NEXT_TEXT': 'Seuraava'
    });

    $translateProvider.translations('sv', {
      'REQUIRED_FIELD': 'Obligatoriskt fält',
      'CHARACTERS_LEFT': 'Tecken kvar',
      'CHOOSE_FILE': 'Välj fil',
      'PRINT_BUTTON': 'Skriv ut',
      'SEND_MAIL_BUTTON': 'Skicka till e-post',
      'MAIL_SENT_TEXT': 'E-post skickad',
      'NEXT_TEXT': 'Nästa'
    });

    $translateProvider.translations('en', {
      'REQUIRED_FIELD': 'Required field',
      'CHARACTERS_LEFT': 'Characters left',
      'CHOOSE_FILE': 'Choose file',
      'PRINT_BUTTON': 'Print',
      'SEND_MAIL_BUTTON': 'Send to e-mail',
      'MAIL_SENT_TEXT': 'E-mail sent',
      'NEXT_TEXT': 'Next'
    });

    $translateProvider.preferredLanguage('fi');
    $translateProvider.useSanitizeValueStrategy(null);
  });

Add missing translation to consumer-ui


"use strict";

var Vow = require( "vow" );

module.exports = function( grunt ) {

    var filter = Array.prototype.filter,
        JSCS = require( "./lib/jscs" ).init( grunt );

    grunt.registerMultiTask( "jscs", "JavaScript Code Style checker", function() {
        var done = this.async(),
            options = this.options({
                config: null
            }),

            jscs = new JSCS( options ),
            checks = this.filesSrc.map(function( path ) {
                return jscs.check( path );
            });

        Vow.allResolved( checks ).spread(function() {

            // Filter unsuccessful promises
            var results = filter.call( arguments, function( promise ) {
                return promise.isFulfilled();

            // Make array of errors
            }).map(function( promise ) {
                return promise.valueOf()[ 0 ];
            });

            jscs.setErrors( results ).report().notify();

            done( options.force || !jscs.count() );
        });
    });
};

"use strict";

var Vow = require( "vow" );

module.exports = function( grunt ) {

    var filter = Array.prototype.filter,
        JSCS = require( "./lib/jscs" ).init( grunt );

    grunt.registerMultiTask( "jscs", "JavaScript Code Style checker", function() {
        var done = this.async(),
            options = this.options({

                // null is a default value, but its equivalent to `true`,
                // with this way it's easy to distinguish specified value
                config: null
            }),

            jscs = new JSCS( options ),
            checks = this.filesSrc.map(function( path ) {
                return jscs.check( path );
            });

        Vow.allResolved( checks ).spread(function() {

            // Filter unsuccessful promises
            var results = filter.call( arguments, function( promise ) {
                return promise.isFulfilled();

            // Make array of errors
            }).map(function( promise ) {
                return promise.valueOf()[ 0 ];
            });

            jscs.setErrors( results ).report().notify();

            done( options.force || !jscs.count() );
        });
    });
};

Add comment for null as default value