/**
 * Module dependencies.
 */

var Agent = require('superagent').agent
  , methods = require('methods')
  , http = require('http')
  , Test = require('./test');

/**
 * Expose `Agent`.
 */

module.exports = TestAgent;

/**
 * Initialize a new `TestAgent`.
 *
 * @param {Function|Server} app
 * @api public
 */

function TestAgent(app){
  if (!(this instanceof TestAgent)) return new TestAgent(app);
  if ('function' == typeof app) app = http.createServer(app);
  Agent.call(this);
  this.app = app;
}

/**
 * Inherits from `Agent.prototype`.
 */

TestAgent.prototype.__proto__ = Agent.prototype;

// override HTTP verb methods
methods.forEach(function(method){
  TestAgent.prototype[method] = function(url, fn){
    var req = new Test(this.app, method.toUpperCase(), url);

    req.on('response', this.saveCookies.bind(this));
    req.on('redirect', this.saveCookies.bind(this));
    req.on('redirect', this.attachCookies.bind(this, req));
    this.attachCookies(req);

    return req;
  };
});

TestAgent.prototype.del = TestAgent.prototype.delete;

/**
 * Module dependencies.
 */

var Agent = require('superagent').agent
  , methods = require('methods')
  , http = require('http')
  , Test = require('./test');

/**
 * Expose `Agent`.
 */

module.exports = TestAgent;

/**
 * Initialize a new `TestAgent`.
 *
 * @param {Function|Server} app
 * @param {Object} options
 * @api public
 */

function TestAgent(app, options){
  if (!(this instanceof TestAgent)) return new TestAgent(app, options);
  if ('function' == typeof app) app = http.createServer(app);
  if (options) this._ca = options.ca;
  Agent.call(this);
  this.app = app;
}

/**
 * Inherits from `Agent.prototype`.
 */

TestAgent.prototype.__proto__ = Agent.prototype;

// override HTTP verb methods
methods.forEach(function(method){
  TestAgent.prototype[method] = function(url, fn){
    var req = new Test(this.app, method.toUpperCase(), url);
    req.ca(this._ca);

    req.on('response', this.saveCookies.bind(this));
    req.on('redirect', this.saveCookies.bind(this));
    req.on('redirect', this.attachCookies.bind(this, req));
    this.attachCookies(req);

    return req;
  };
});

TestAgent.prototype.del = TestAgent.prototype.delete;

Allow TestAgent pass a CA to request


module.exports = (function() {
	var path = require('path');
	// var java_home = path.resolve(__dirname + '/../jre/Contents/Home');
	// var java_home = path.resolve(__dirname + '/../jre');

	var ReadLine = require('readline');
	var spawn = require('child_process').spawn;
	var events = require('events');
	var os = require('os');

	return {
		startNew: function() {
			var url;
			var hash;
			var eventEmitter = new events.EventEmitter();

			var osName = os.type();
			if (osName.startsWith('Windows')){
				backend = spawn(path.resolve(__dirname + '/../dist/beaker.command.bat'), ['--open-browser', 'false']);
			} else {
				backend = spawn(path.resolve(__dirname + '/../dist/beaker.command'), ['--open-browser', 'false']);
			}
		  // process.env['JAVA_HOME'] = java_home;
		  // backend = spawn(path.resolve(__dirname + '/../dist/beaker.command'), ['--open-browser', 'false']);

		  var rl = ReadLine.createInterface({
		    input: backend.stdout
		  });

		  rl.on('line', function(line) {
		    console.log(line); // Pipe backend's stdout to electron's stdout
		    if (line.startsWith('Beaker hash')){
		      backend.hash = line.split(' ')[2];
		    }
		    else if (line.startsWith('Beaker listening on')){
		      backend.url = line.split(' ')[3];
		      backend.local = true;
		      eventEmitter.emit('ready', backend);
		    }
		  });
		  return eventEmitter;
		}
	}
})();

module.exports = (function() {
	var path = require('path');

	var ReadLine = require('readline');
	var spawn = require('child_process').spawn;
	var events = require('events');
	var os = require('os');

	return {
		startNew: function() {
			var url;
			var hash;
			var eventEmitter = new events.EventEmitter();

			var osName = os.type();
			if (osName.startsWith('Windows')){
			  process.env['JAVA_HOME'] = path.resolve(__dirname + '/../jre');
				backend = spawn(path.resolve(__dirname + '/../dist/beaker.command.bat'), ['--open-browser', 'false']);
			} else  if (osName.startsWith('Darwin')) {
			  process.env['JAVA_HOME'] = path.resolve(__dirname + '/../jre/Contents/Home');
				backend = spawn(path.resolve(__dirname + '/../dist/beaker.command'), ['--open-browser', 'false']);
			}

		  var rl = ReadLine.createInterface({
		    input: backend.stdout
		  });

		  rl.on('line', function(line) {
		    console.log(line); // Pipe backend's stdout to electron's stdout
		    if (line.startsWith('Beaker hash')){
		      backend.hash = line.split(' ')[2];
		    }
		    else if (line.startsWith('Beaker listening on')){
		      backend.url = line.split(' ')[3];
		      backend.local = true;
		      eventEmitter.emit('ready', backend);
		    }
		  });
		  return eventEmitter;
		}
	}
})();

Set java_home correctly for each OS


var chp = require('child_process');

var Git = {
	stageFile: function(modulePath, file, callback) {
		if (file.status === 'deleted') {
			this._exec(modulePath, 'rm', [file.name], callback);
		} else {
			if (file.type === 'submodule') this.exec(modulePath, 'submodule add', [file.name], callback);
			else this._exec(modulePath, 'add', [file.name], callback);
		}
	},

	unstageFile: function(modulePath, file, callback) {
		this._exec(modulePath, 'reset HEAD', [file.name], callback);
	},

	commit: function(modulePath, message, callback) {
		this._exec(modulePath, 'commit -m', [message], callback);
	},

	push: function(modulePath, callback) {
		this._exec(modulePath, 'push', [], callback);
	},

	_exec: function(modulePath, cmd, args, callback) {
		var escapedArgs = args.map(function(arg) {
			return '"' + arg.replace(/["\\]/g, '\\$1') + '"';
		}).join(' ');
		cmd = 'git ' + cmd + ' ' + escapedArgs;
		chp.exec(cmd, {cwd: modulePath}, function(err, stdout, stderr) {
			if (err) console.error(err, stdout, stderr);
			callback(null, stdout);
		});
	}
};

module.exports = Git;

var chp = require('child_process');

var Git = {
	stageFile: function(modulePath, file, callback) {
		if (file.status === 'deleted') {
			this._exec(modulePath, 'rm', [file.name], callback);
		} else {
			if (file.type === 'submodule') this._exec(modulePath, 'submodule add', ['.' + require('path').sep +  file.name], callback);
			else this._exec(modulePath, 'add', [file.name], callback);
		}
	},

	unstageFile: function(modulePath, file, callback) {
		this._exec(modulePath, 'reset HEAD', [file.name], callback);
	},

	commit: function(modulePath, message, callback) {
		this._exec(modulePath, 'commit -m', [message], callback);
	},

	push: function(modulePath, callback) {
		this._exec(modulePath, 'push', [], callback);
	},

	_exec: function(modulePath, cmd, args, callback) {
		var escapedArgs = args.map(function(arg) {
			return '"' + arg.replace(/["\\]/g, '\\$1') + '"';
		}).join(' ');
		cmd = 'git ' + cmd + ' ' + escapedArgs;
		chp.exec(cmd, {cwd: modulePath}, function(err, stdout, stderr) {
			if (err) console.error(err, stdout, stderr);
			callback(null, stdout);
		});
	}
};

module.exports = Git;

Fix typo and file path


/* Ubivar API client factory
 * @token [String] required bearer-token from admin interface
 * @version [String] optional, default to 'latest'
 */
module.exports  = function(token, version, resources){
  var _         = require("lodash")

  var Ubivar    = require("./Ubivar")

  resources     = resources || [
      "me"
    , "accounts"
    , "addresses"
    , "login"
    , "logout"
    , "items"
    , "orders"
    , "transactions"
    , "routing"
    , "reviewqueues"
    , "reviewers"
    , "reviewerqueuebindings"
    , "labels"
  ]

  var ubivar    = new Ubivar(token, version, resources)

  _.each(resources, function(resource){
    ubivar.extend(resource)
  })

  return ubivar
}

/* Ubivar API client factory
 * @token [String] required bearer-token from admin interface
 * @version [String] optional, default to 'latest'
 */
module.exports  = function(token, version, resources){
  var _         = require("lodash")

  var Ubivar    = require("./Ubivar")

  resources     = resources || [
      "me"
    , "accounts"
    , "addresses"
    , "login"
    , "logout"
    , "items"
    , "orders"
    , "transactions"
    , "routing"
    , "labels"
  ]

  var ubivar    = new Ubivar(token, version, resources)

  _.each(resources, function(resource){
    ubivar.extend(resource)
  })

  return ubivar
}

Remove review* as resources from ubivar-node binding


var _ = require('lodash'),
    StylusIncludesPlugin = require('./plugins/stylus-includes');

module.exports.config = function(additions) {
  var options = additions.stylus || {},
      defines = options.defines || {};

  return _.defaults({
    module: _.defaults({
      loaders: loaders(defines, additions.module && additions.module.loaders)
    }, additions.module),

    plugins: plugins(options, additions.plugins)
  }, additions);
};

function loaders(stylusDefines, additions) {
  var base = [
    {
      test: /\.styl$/,
      loader: require.resolve('stylus-loader')
          + '?' + JSON.stringify({define: stylusDefines})
    }
  ];

  return additions ? base.concat(additions) : base;
}

function plugins(options, additions) {
  var base = [
    new StylusIncludesPlugin(options)
  ];

  return additions ? base.concat(additions) : base;
}

var _ = require('lodash'),
    StylusIncludesPlugin = require('./plugins/stylus-includes');

module.exports.config = function(additions) {
  var options = additions.stylus || {},
      defines = options.defines || {};

  return _.defaults({
    module: _.defaults({
      loaders: loaders(defines, additions.module && additions.module.loaders)
    }, additions.module),

    plugins: plugins(options, additions.plugins)
  }, additions);
};

function loaders(stylusDefines, additions) {
  var base = [
    {
      test: /\.styl$/,
      loader: require.resolve('stylus-loader')
          + '?' + JSON.stringify({define: stylusDefines, 'resolve url': true})
    },
  ];

  return additions ? base.concat(additions) : base;
}

function plugins(options, additions) {
  var base = [
    new StylusIncludesPlugin(options)
  ];

  return additions ? base.concat(additions) : base;
}

Resolve urls embedded in stylus sheets


import dom from 'virtual-element'
import {render, tree} from 'deku'
import settings from './settings'
import socket from './socket'
import routes from './routes'
import actions from './actions'
import board from './board'
import App from './app'

let app = tree(dom(App))

app.set('connected', false)
app.set('client', {})
app.set('stats', {
  games: [],
  players: []
})

app.use(settings)
app.use(board)
app.use(socket)
app.use(routes)
app.use(actions)

render(app, document.getElementById('app'))

Add newline at end of file.


$(function() {
  var CustomersView, CustomerRowView, CustomerModel, CustomerCollection;

  CustomerModel = Backbone.Model.extend({});

  CustomerCollection = Backbone.Collection.extend({
    model: CustomerModel,
    url: 'customers.json'
  });

  CustomersView = Backbone.View.extend({
    events: {
      'submit .new-customer': 'addCustomer',
      'click .delete': 'deleteCustomer'
    },

    el: $('#application'),

    render: function() {
      this.$('.customers').render(customers.toJSON(), {
        delete: {
          index: function(params) { return params.index; }
        }
      });

      return this;
    },

    initialize: function() {
      window.customers = this.customers = new CustomerCollection();
      this.customers.on('add', this.render, this);
      this.customers.on('reset', this.render, this);
      this.customers.on('destroy', this.render, this);
      this.customers.fetch();
    },

    addCustomer: function() {
      this.customers.create({
        suffix: this.$('.new-customer .suffixes').val(),
        name:   this.$('.new-customer .name').val(),
        email:  this.$('.new-customer .email').val(),
        phone:  this.$('.new-customer .phone').val()
      });

      return false;
    },

    deleteCustomer: function(event) {
      customers.at(event.target.getAttribute('index')).destroy();
      return false;
    }
  });

  //Kickstart
  var app = new CustomersView();
  app.render();
});

$(function() {
  var CustomersView, CustomerRowView, CustomerModel, CustomerCollection;

  CustomerModel = Backbone.Model.extend({});

  CustomerCollection = Backbone.Collection.extend({
    model: CustomerModel,
    url: 'customers.json'
  });

  CustomersView = Backbone.View.extend({
    events: {
      'submit .new-customer': 'addCustomer',
      'click .delete': 'deleteCustomer'
    },

    el: $('#application'),

    render: function() {
      this.$('.customers').render(customers.toJSON(), {
        delete: {
          index: function(params) { return params.index; }
        }
      });

      return this;
    },

    initialize: function() {
      this.customers = new CustomerCollection();
      this.customers.on('add', this.render, this);
      this.customers.on('reset', this.render, this);
      this.customers.on('destroy', this.render, this);
      this.customers.fetch();
    },

    addCustomer: function() {
      this.customers.create({
        suffix: this.$('.new-customer .suffixes').val(),
        name:   this.$('.new-customer .name').val(),
        email:  this.$('.new-customer .email').val(),
        phone:  this.$('.new-customer .phone').val()
      });

      return false;
    },

    deleteCustomer: function(event) {
      customers.at(event.target.getAttribute('index')).destroy();
      return false;
    }
  });

  //Kickstart
  var app = new CustomersView();
  app.render();
});

Remove global reference to customers.


var path = require('path'),
    glob = require('glob');

module.exports = function (url, file, done) {

    // if url starts with . or / then assume it's a local/relative path
    if (['.', '/'].indexOf(url.substr(0, 1)) > -1) {
        return done(null, true);
    }

    // otherwise construct a glob to match possible relative file paths
    var basedir = path.dirname(file);

    var bits = url.split('/');

    var filename = bits.pop(),
        filepath = bits.join('/');

    // support _ prefixing of filenames
    if (filename[0] !== '_') {
        filename = '?(_)' + filename;
    }

    // match .scss, .sass or .css if the extension is not defined
    if (filename.indexOf('.') === -1) {
        filename += '.@(sa|c|sc)ss';
    }

    var target = path.join(basedir, filepath, filename);

    // test the constructed glob against the file system for possible matches
    glob(target, function (err, list) {
        done(err, list.length);
    });

}

var path = require('path'),
    glob = require('glob');

module.exports = function (url, file, done) {

    // if url starts with . or / then assume it's a local/relative path
    if (['.', path.sep].indexOf(url.substr(0, 1)) > -1) {
        return done(null, true);
    }

    // otherwise construct a glob to match possible relative file paths
    var basedir = path.dirname(file);

    var bits = url.split(path.sep);

    var filename = bits.pop(),
        filepath = bits.join(path.sep);

    // support _ prefixing of filenames
    if (filename[0] !== '_') {
        filename = '?(_)' + filename;
    }

    // match .scss, .sass or .css if the extension is not defined
    if (filename.indexOf('.') === -1) {
        filename += '.@(sa|c|sc)ss';
    }

    var target = path.join(basedir, filepath, filename);

    // test the constructed glob against the file system for possible matches
    glob(target, function (err, list) {
        done(err, list.length);
    });

}

Replace references to '/' with `path.sep`


var Controller = function() {
  this.pac = new Actor({
    context: actorCanvas.context,
    startX: 113,
    startY: 212,
    name: "m",
    direction: "right"
  });
  this.blinky = new Ghost({
    context: actorCanvas.context,
    direction: "left",
    name: "b",
    startX: 112,
    startY: 116
  });
  this.inky = new Ghost({
    context: actorCanvas.context,
    direction: "up",
    name: "i",
    startX: 96,
    startY: 139
  });
  this.pinky = new Ghost({
    context: actorCanvas.context,
    direction: "right",
    name: "p",
    startX: 112,
    startY: 139
  });
  this.clyde = new Ghost({
    context: actorCanvas.context,
    direction: "down",
    name: "c",
    startX: 128,
    startY: 139
  });
}

var Controller = function(args) {
  this.actorCanvas = args.actorCanvas;
  this.pac = new Actor({
    context: this.actorCanvas.context,
    startX: 113,
    startY: 212,
    name: "m",
    direction: "right"
  });
  this.blinky = new Ghost({
    context: this.actorCanvas.context,
    direction: "left",
    name: "b",
    startX: 112,
    startY: 116
  });
  this.inky = new Ghost({
    context: this.actorCanvas.context,
    direction: "up",
    name: "i",
    startX: 96,
    startY: 139
  });
  this.pinky = new Ghost({
    context: this.actorCanvas.context,
    direction: "right",
    name: "p",
    startX: 112,
    startY: 139
  });
  this.clyde = new Ghost({
    context: this.actorCanvas.context,
    direction: "down",
    name: "c",
    startX: 128,
    startY: 139
  });
}

Update Controller to accept args


var assert = require('assert');
var subprocess = require('ringo/subprocess');
var fs = require('fs');

const RINGO_HOME = system.prefix;

function isWindows() /win/i.test(java.lang.System.getProperty('os.name'));

exports.testCwdSensibleness = function () {
    fs.changeWorkingDirectory(RINGO_HOME);

    var cmd = isWindows() ? 'cd' : '/bin/pwd';
    var out = subprocess.command(cmd).replace(/\n/, '').replace(/\\/g, '/');
    var pwd = /\/$/.test(out) ? out : out + '/';

    assert.strictEqual(fs.workingDirectory(), RINGO_HOME);
    assert.strictEqual(pwd, isWindows() ?
            RINGO_HOME.replace(/\\/g, '/') : RINGO_HOME);
};

var assert = require('assert');
var subprocess = require('ringo/subprocess');
var fs = require('fs');

const RINGO_HOME = system.prefix;

function isWindows() /win/i.test(java.lang.System.getProperty('os.name'));

exports.testCwdSensibleness = function () {
    fs.changeWorkingDirectory(RINGO_HOME);

    var cmd = isWindows() ? 'cmd /c cd' : '/bin/pwd';
    var out = subprocess.command(cmd)
            .replace(/\n/, '')
            .replace(/\\/g, '/');
    var pwd = /\/$/.test(out) ? out : out + '/';

    assert.strictEqual(fs.workingDirectory(), RINGO_HOME);
    assert.strictEqual(pwd, isWindows() ?
            RINGO_HOME.replace(/\\/g, '/') : RINGO_HOME);
};

Make `ringo/subprocess` test actually work & pass as expected on Windows.


var _ = require('lodash'),
    combine = require('stream-combiner'),
    loadGulpPlugins = require('gulp-load-plugins');

module.exports = function buildSVG(options) {
  var gulpPlugins = loadGulpPlugins();

  options = _.defaults({}, options, {
    doMinify: false,

    concatName: 'override_me.svg',

    svgStoreOptions: {},
    svgMinOptions: {
        plugins: [
            {
                cleanupIDs: false
              },
            {
                removeUnknownsAndDefaults: {
                    defaultAttrs: false
                  }
              }
          ]
      },
    minifyRenameOptions: {
        extname: '.min.svg'
      }
  });

  return combine(_.compact([
    // Processing pipeline
    gulpPlugins.svgstore(options.svgStoreOptions),
    gulpPlugins.rename(options.concatName),

    // Productionization pipeline
    options.doMinify && gulpPlugins.svgmin(options.svgMinOptions),
    options.doMinify && gulpPlugins.rename(options.minifyRenameOptions)
  ]));
};

var _ = require('lodash'),
    combine = require('stream-combiner'),
    loadGulpPlugins = require('gulp-load-plugins');

module.exports = function buildSVG(options) {
  var gulpPlugins = loadGulpPlugins();

  options = _.defaults({}, options, {
    doProcessing: true,
    doMinify: false,

    concatName: 'override_me.svg',

    svgStoreOptions: {},
    svgMinOptions: {
        plugins: [
            {
                cleanupIDs: false
              },
            {
                removeUnknownsAndDefaults: {
                    defaultAttrs: false
                  }
              }
          ]
      },
    minifyRenameOptions: {
        extname: '.min.svg'
      }
  });

  return combine(_.compact([
    // Processing pipeline (concat all files and store as symbols, for inclusion in HTML body)
    options.doProcessing && gulpPlugins.svgstore(options.svgStoreOptions),
    options.doProcessing && gulpPlugins.rename(options.concatName),

    // Productionization pipeline
    options.doMinify && gulpPlugins.svgmin(options.svgMinOptions),
    options.doMinify && gulpPlugins.rename(options.minifyRenameOptions)
  ]));
};

Make processing option in SVG builder


import buildmessage from "../utils/buildmessage.js";
import {
  pathJoin,
  statOrNull,
  writeFile,
} from "../fs/files.js";

const INSTALL_JOB_MESSAGE = "installing dependencies from package.json";

export function install(appDir) {
  const testAppPkgJsonPath = pathJoin(appDir, "package.json");

  if (! statOrNull(testAppPkgJsonPath)) {
    const { dependencies } = require("../static-assets/skel/package.json");

    // Write a minimial package.json with the same dependencies as the
    // default new-app package.json file.
    writeFile(
      testAppPkgJsonPath,
      JSON.stringify({ dependencies }, null, 2) + "\n",
      "utf8",
    );
  }

  return buildmessage.enterJob(INSTALL_JOB_MESSAGE, function () {
    const { runNpmCommand } = require("../isobuild/meteor-npm.js");

    const installResult = runNpmCommand(["install"], appDir);
    if (! installResult.success) {
      buildmessage.error(
        "Could not install npm dependencies for test-packages: " +
          installResult.error);

      return false;
    }

    return true;
  });
}

import buildmessage from "../utils/buildmessage.js";
import {
  pathJoin,
  statOrNull,
  writeFile,
  unlink,
} from "../fs/files.js";

const INSTALL_JOB_MESSAGE = "installing dependencies from package.json";

export function install(appDir) {
  const packageJsonPath = pathJoin(appDir, "package.json");
  const needTempPackageJson = ! statOrNull(packageJsonPath);

  if (needTempPackageJson) {
    const { dependencies } = require("../static-assets/skel/package.json");

    // Write a minimial package.json with the same dependencies as the
    // default new-app package.json file.
    writeFile(
      packageJsonPath,
      JSON.stringify({ dependencies }, null, 2) + "\n",
      "utf8",
    );
  }

  const ok = buildmessage.enterJob(INSTALL_JOB_MESSAGE, function () {
    const { runNpmCommand } = require("../isobuild/meteor-npm.js");

    const installResult = runNpmCommand(["install"], appDir);
    if (! installResult.success) {
      buildmessage.error(
        "Could not install npm dependencies for test-packages: " +
          installResult.error);

      return false;
    }

    return true;
  });

  if (needTempPackageJson) {
    // Clean up the temporary package.json file created above.
    unlink(packageJsonPath);
  }

  return ok;
}

Clean up temporary package.json files.


/*!
{
  "name": "CSS Shapes",
  "property": "shapes",
  "tags": ["css"],
  "notes": [{
    "name": "CSS Shapes W3C specification",
    "href": "http://www.w3.org/TR/css-shapes"
  },{
    "name": "Examples from Adobe",
    "href": "http://html.adobe.com/webplatform/layout/shapes"
  }, {
    "name": "Samples showcasing uses of Exclusions and Shapes",
    "href": "http://codepen.io/collection/qFesk"
  }]
}
!*/
define(['Modernizr', 'createElement', 'docElement', 'prefixed', 'testStyles'], function( Modernizr, createElement, docElement, prefixed, testStyles ) {
    // Separate test for CSS shapes as WebKit has just implemented this alone
    Modernizr.addTest('shapes', function () {
        var prefixedProperty = prefixed('shapeInside');

        if (!prefixedProperty)
            return false;

        var shapeInsideProperty = prefixedProperty.replace(/([A-Z])/g, function (str, m1) { return '-' + m1.toLowerCase(); }).replace(/^ms-/, '-ms-');

        return testStyles('#modernizr { ' + shapeInsideProperty + ':rectangle(0,0,0,0) }', function (elem) {
            // Check against computed value
            var styleObj = window.getComputedStyle ? getComputedStyle(elem, null) : elem.currentStyle;
            return styleObj[prefixed('shapeInside', docElement.style, false)] == 'rectangle(0px, 0px, 0px, 0px)';
        });
    });
});

/*!
{
  "name": "CSS Shapes",
  "property": "shapes",
  "tags": ["css"],
  "notes": [{
    "name": "CSS Shapes W3C specification",
    "href": "http://www.w3.org/TR/css-shapes"
  },{
    "name": "Examples from Adobe",
    "href": "http://html.adobe.com/webplatform/layout/shapes"
  }, {
    "name": "Samples showcasing uses of Shapes",
    "href": "http://codepen.io/collection/qFesk"
  }]
}
!*/
define(['Modernizr', 'createElement', 'docElement', 'prefixed', 'testStyles'], function( Modernizr, createElement, docElement, prefixed, testStyles ) {
    // Separate test for CSS shapes as WebKit has just implemented this alone
    Modernizr.addTest('shapes', function () {
        var prefixedProperty = prefixed('shapeInside');

        if (!prefixedProperty)
            return false;

        var shapeInsideProperty = prefixedProperty.replace(/([A-Z])/g, function (str, m1) { return '-' + m1.toLowerCase(); }).replace(/^ms-/, '-ms-');

        return testStyles('#modernizr { ' + shapeInsideProperty + ':rectangle(0,0,0,0) }', function (elem) {
            // Check against computed value
            var styleObj = window.getComputedStyle ? getComputedStyle(elem, null) : elem.currentStyle;
            return styleObj[prefixed('shapeInside', docElement.style, false)] == 'rectangle(0px, 0px, 0px, 0px)';
        });
    });
});

Modify CSS Exclusions to CSS Shapes, since we split up the specification. Update references.


Wee.fn.make('todo', {
	init: function() {
		$($('ref:template').text()).insertAfter('ref:nav');

		Wee.app.make('items', {
			view: 'ref:todo',
			model: {
				todo: [
					{
						label: 'Download and run Wee',
						done: true
					},
					{
						label: 'Explore the welcome module'
					},
					{
						label: 'Configure wee.json'
					},
					{
						label: 'Run node wee reset'
					}
				]
			}
		});

		$('ref:toggle').on('click', function() {
			var id = $(this).data('id'),
				done = Wee.items.$get('todo.' + id + '.done');

			Wee.items.$set('todo.' + id + '.done', done ? false : true);
		}, {
			delegate: 'ref:todo'
		});
	}
});

Wee.routes.map({
	'$root': 'todo'
}, true);

Wee.fn.make('todo', {
	init: function() {
		$('ref:nav').after($.first('ref:template').text);

		Wee.app.make('items', {
			view: 'ref:todo',
			model: {
				todo: [
					{
						label: 'Download and run Wee',
						done: true
					},
					{
						label: 'Explore the welcome module'
					},
					{
						label: 'Configure wee.json'
					},
					{
						label: 'Run node wee reset'
					}
				]
			}
		});

		$('ref:toggle').on('click', function() {
			var id = $(this).data('id'),
				done = Wee.items.$get('todo.' + id + '.done');

			Wee.items.$set('todo.' + id + '.done', done ? false : true);
		}, {
			delegate: 'ref:todo'
		});
	}
});

Wee.routes.map({
	'$root': 'todo'
}, true);

Simplify adding the template markup to the document


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

window.onload = function() {
  chrome.management.getAll(function(items) {
    for (var i in items) {
      var item = items[i];
      if (item.name == "packaged_app") {
        chrome.management.launchApp(item.id);
        break;
      }
      if (item.name == "simple_extension") {
        // Try launching a non-app extension, which should fail.
        var expected_error = "Extension " + item.id + " is not an App";
        chrome.management.launchApp(item.id, function() {
          if (chrome.extension.lastError &&
              chrome.extension.lastError.message == expected_error) {
            chrome.test.sendMessage("got_expected_error");
          }
        });
      }
    }
  });
};

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

window.onload = function() {
  chrome.management.getAll(function(items) {
    for (var i in items) {
      var item = items[i];
      if (item.name == "packaged_app") {
        chrome.management.launchApp(item.id);
      }
      if (item.name == "simple_extension") {
        // Try launching a non-app extension, which should fail.
        var expected_error = "Extension " + item.id + " is not an App";
        chrome.management.launchApp(item.id, function() {
          if (chrome.extension.lastError &&
              chrome.extension.lastError.message == expected_error) {
            chrome.test.sendMessage("got_expected_error");
          }
        });
      }
    }
  });
};

Fix ExtensionManagementApiBrowserTest.LaunchApp to no longer be order-dependent.


(function(Fontclod) {
	"use strict";

	function Undo(options) {
		this.index = options.index || 0;
		this.stack = options.stack || [];
		this._data = options.data  || {};
	}

	// Undo.step(1) Undo.step(-10)
	Undo.prototype.step = function(n) {};
	// Undo.seek(index)
	Undo.prototype.seek = function(i) {};

	Undo.prototype.push = function(data) {
		var diff = this.diff(data);

		if (Object.keys(diff).length)
			this.stack.push(diff);
	};

	Undo.prototype.diff = function(data) {
		var i, key,
		    diff = {},
		    keys = Object.keys(data);

		for (i=0, key=keys[0]; i<keys.length; key=keys[++i]) {
			if (this._data[key] != data[key])
				diff[key] = data[key];
		}

		this._data = data;

		return diff;
	};

	Fontclod.Undo = Undo;
})(Fontclod);

(function(Fontclod) {
	"use strict";

	function Undo(options) {
		options = options || {};
		this.stack  = options.stack || [];
		this._index = options.index || 0;
		this._data  = options.data  || {};
	}

	// Undo.step(1) Undo.step(-10)
	Undo.prototype.step = function(n) {
		this._index = Math.max(0, Math.min(this._index + n, this.data.length));
	};

	// Undo.seek(index)
	Undo.prototype.seek = function(i) {
		this._index = i;
	};

	Undo.prototype.push = function(data) {
		var diff = this.diff(data);

		if (Object.keys(diff).length)
			this.stack.push(diff);
	};

	Undo.prototype.diff = function(data) {
		var i, key,
		    diff = {},
		    keys = Object.keys(data);

		for (i=0, key=keys[0]; i<keys.length; key=keys[++i]) {
			if (this._data[key] != data[key])
				diff[key] = data[key];
		}

		this._data = data;

		return diff;
	};

	Fontclod.Undo = Undo;
})(Fontclod);

Fix undefined errors when constructing Undo() with no arguments


// Returns a jQuery deferred object, or pass a success and fail callbacks if you don't want to use jQuery
var getAppVersion = function (success, fail) {
  var dfr = null;
  if ((typeof success) === 'undefined' && jQuery) {
    dfr = jQuery.Deferred();
    success = dfr.resolve;
    fail = dfr.reject;
  }
  // 5th param is NOT optional. must be at least empty array
  cordova.exec(success, fail, "AppVersion", "getVersionNumber", []);
  return dfr;
};

module.exports = getAppVersion;

// Returns a jQuery deferred object, or pass a success and fail callbacks if you don't want to use jQuery
var getAppVersion = function (success, fail) {
  var dfr = null;
  if ((typeof success) === 'undefined' && window.jQuery) {
    dfr = jQuery.Deferred();
    success = dfr.resolve;
    fail = dfr.reject;
  }
  // 5th param is NOT optional. must be at least empty array
  cordova.exec(success, fail, "AppVersion", "getVersionNumber", []);
  return dfr;
};

module.exports = getAppVersion;

Test for window.jQuery, not just jQuery


var LiteAjax = (function () {
  var LiteAjax = {};

  LiteAjax.options = {
    method: 'GET',
    url: window.location.href,
    async: true,
  };

  LiteAjax.ajax = function (url, options) {
    if (typeof url == 'object') {
      options = url;
      url = undefined;
    }

    options = options || {};
    url = url || options.url || location.href || '';

    var xhr;

    xhr = new XMLHttpRequest();

    xhr.addEventListener('load', function () {
      var event = new CustomEvent('ajaxComplete', {detail: xhr});
      document.dispatchEvent(event);
    });

    if (typeof options.success == 'function')
      xhr.addEventListener('load', function (event) {
        if (xhr.status >= 200 && xhr.status < 300)
          options.success(xhr);
      });

    if (typeof options.error == 'function') {
      xhr.addEventListener('load', function (event) {
        if (xhr.status < 200 || xhr.status >= 300)
          options.error(xhr);
      });
      xhr.addEventListener('error', function (event) {
        options.error(xhr);
      });
    }

    xhr.open(options.method || 'GET', url, options.async);
    var beforeSend = new CustomEvent('ajax:before', {detail: xhr});
    document.dispatchEvent(beforeSend);
    xhr.send(options.data);

    return xhr;
  };

  return LiteAjax;
})();

var LiteAjax = (function () {
  var LiteAjax = {};

  LiteAjax.options = {
    method: 'GET',
    url: window.location.href,
    async: true,
  };

  LiteAjax.ajax = function (url, options) {
    if (typeof url == 'object') {
      options = url;
      url = undefined;
    }

    options = options || {};
    url = url || options.url || location.href || '';

    var xhr;

    xhr = new XMLHttpRequest();

    xhr.addEventListener('load', function () {
      responseType = xhr.getResponseHeader('content-type');
      if(responseType === 'text/javascript; charset=utf-8') {
        eval(xhr.response);
      }

      var event = new CustomEvent('ajaxComplete', {detail: xhr});
      document.dispatchEvent(event);
    });

    if (typeof options.success == 'function')
      xhr.addEventListener('load', function (event) {
        if (xhr.status >= 200 && xhr.status < 300)
          options.success(xhr);
      });

    if (typeof options.error == 'function') {
      xhr.addEventListener('load', function (event) {
        if (xhr.status < 200 || xhr.status >= 300)
          options.error(xhr);
      });
      xhr.addEventListener('error', function (event) {
        options.error(xhr);
      });
    }

    xhr.open(options.method || 'GET', url, options.async);
    var beforeSend = new CustomEvent('ajax:before', {detail: xhr});
    document.dispatchEvent(beforeSend);
    xhr.send(options.data);

    return xhr;
  };

  return LiteAjax;
})();

Call eval(), if server returns javascript


//= require govuk-frontend/govuk/vendor/polyfills/Function/prototype/bind

//= require govuk_publishing_components/lib
//= require govuk_publishing_components/components/accordion
//= require govuk_publishing_components/components/button
//= require govuk_publishing_components/components/details
//= require govuk_publishing_components/components/feedback
//= require govuk_publishing_components/components/govspeak
//= require govuk_publishing_components/components/intervention
//= require govuk_publishing_components/components/radio
//= require govuk_publishing_components/components/step-by-step-nav
//= require govuk_publishing_components/components/error-summary

//= require support
//= require browse-columns
//= require modules/organisation-list-filter
//= require modules/toggle-attribute
//= require modules/coronavirus-landing-page

//= require govuk-frontend/govuk/vendor/polyfills/Function/prototype/bind

//= require govuk_publishing_components/lib
//= require govuk_publishing_components/components/accordion
//= require govuk_publishing_components/components/button
//= require govuk_publishing_components/components/details
//= require govuk_publishing_components/components/feedback
//= require govuk_publishing_components/components/govspeak
//= require govuk_publishing_components/components/radio
//= require govuk_publishing_components/components/step-by-step-nav
//= require govuk_publishing_components/components/error-summary

//= require support
//= require browse-columns
//= require modules/organisation-list-filter
//= require modules/toggle-attribute
//= require modules/coronavirus-landing-page

Remove import of non-existent JS


/**
 * Creates a new Lexer for a specific language.
 */
exports.Lexer = function(lang) {
  return require('./gherkin/lexer/' + lang).Lexer;
};

/**
 * Creates a connect middleware for loading lexer sources (typically for browsers).
 */
exports.connect = function(path) {
  var gherkinFiles = require('connect').static(__dirname);

  return function(req, res, next) {
    if(req.url.indexOf(path) == 0) {
      req.url = req.url.slice(path.length);
      gherkinFiles(req, res, next);
    } else {
      next();
    }
  };
};

/**
 * Creates a new Lexer for a specific language.
 */
exports.Lexer = function(lang) {
  return require('./gherkin/lexer/' + lang);
};

/**
 * Creates a connect middleware for loading lexer sources (typically for browsers).
 */
exports.connect = function(path) {
  var gherkinFiles = require('connect').static(__dirname);

  return function(req, res, next) {
    if(req.url.indexOf(path) == 0) {
      req.url = req.url.slice(path.length);
      gherkinFiles(req, res, next);
    } else {
      next();
    }
  };
};

Fix lexer instantiation in JavaScript


#!/usr/bin/env node
var generateInvoice = require('./')
var JSONStream = require('JSONStream')

if (process.argv.length < 4) {
  console.error('Usage: <from> <to>');
  process.exit(1) }

var from = Date.parse(process.argv[2])
var through = Date.parse(process.argv[3])

var projects = []

process.stdin
  .pipe(JSONStream.parse())
  .on('data', function(chunk) {
    if (chunk) {
      projects.push(chunk) } })
  .on('end', function() {
    console.log(generateInvoice(from, through, projects)) })

#!/usr/bin/env node
var markdownBill = require('./')
var JSONStream = require('JSONStream')

if (process.argv.length < 4) {
  console.error('Usage: <from> <to>');
  process.exit(1) }

var from = Date.parse(process.argv[2])
var through = Date.parse(process.argv[3])

var projects = []

process.stdin
  .pipe(JSONStream.parse())
  .on('data', function(chunk) {
    if (chunk) {
      projects.push(chunk) } })
  .on('end', function() {
    console.log(markdownBill(from, through, projects)) })

Update function name in CLI


import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { TextBody, TextDisplay } from '../typography';
import theme from './theme.css';

export default class Label extends PureComponent {
  static propTypes = {
    children: PropTypes.oneOfType([PropTypes.element, PropTypes.string, PropTypes.array]),
    for: PropTypes.string,
    size: PropTypes.oneOf(['small', 'medium']),
  };

  static defaultProps = {
    size: 'medium',
  };

  render() {
    const { children, size } = this.props;

    const Element = size === 'medium' ? TextDisplay : TextBody;

    return (
      <Element element="label" htmlFor={this.props.for} marginBottom={3} className={theme['label']}>
        {children}
      </Element>
    );
  }
}

import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { TextBody, TextDisplay } from '../typography';
import theme from './theme.css';
import cx from "classnames";

export default class Label extends PureComponent {
  static propTypes = {
    children: PropTypes.oneOfType([PropTypes.element, PropTypes.string, PropTypes.array]),
    for: PropTypes.string,
    inverse: PropTypes.bool,
    size: PropTypes.oneOf(['small', 'medium']),
  };

  static defaultProps = {
    inverse: false,
    size: 'medium',
  };

  render() {
    const { children, inverse, size } = this.props;
    const classNames = cx(
      theme['label'],
      {
        [theme['is-inverse']]: inverse,
      },
    );
    const Element = size === 'medium' ? TextDisplay : TextBody;

    return (
      <Element color={inverse ? 'white' : 'teal'} element="label" htmlFor={this.props.for} marginBottom={3} className={classNames}>
        {children}
      </Element>
    );
  }
}

Implement 'inverse' prop and remove obsolete styling


/**
 * Created by sylva on 30/05/2017.
 */

$(document).ready(function () {
    $('#lastFMSearch').autocomplete({
        source: function( request, response ) {
            $.ajax({
                url: "https://ws.audioscrobbler.com/2.0/?method=artist.search&artist="+$('#lastFMSearch').val()+"&api_key=f6734ae2b9887488059fc9507f2c6c60&format=json&limit=10",
                dataType: "json",
                success: function(data) {
                    var results = [];
                    for(var cptArtist = 0; cptArtist < data.results.artistmatches.artist.length; cptArtist++)
                    {
                        results.push({
                            label: data.results.artistmatches.artist[cptArtist].name,
                            icon: data.results.artistmatches.artist[cptArtist].image[1]["#text"]
                        })
                    }
                    response(results);
                }
            });
        },
        minLength: 2
    } )
    .autocomplete( "instance" )._renderItem = function( ul, item ) {
        return $( "
" )
            .append( "
" + item.label + "
" )
            .appendTo( ul );
    };
});

/**
 * Created by sylva on 30/05/2017.
 */

$(document).ready(function () {
    $('#lastFMSearch').autocomplete({
        source: function( request, response ) {
            $.ajax({
                url: "https://ws.audioscrobbler.com/2.0/?method=artist.search&artist="+$('#lastFMSearch').val()+"&api_key=f6734ae2b9887488059fc9507f2c6c60&format=json&limit=10",
                dataType: "json",
                success: function(data) {
                    var results = [];
                    for(var cptArtist = 0; cptArtist < data.results.artistmatches.artist.length; cptArtist++)
                    {
                        results.push({
                            label: data.results.artistmatches.artist[cptArtist].name,
                            icon: data.results.artistmatches.artist[cptArtist].image[1]["#text"]
                        })
                    }
                    response(results);
                }
            });
        },
        select: function( event, ui ) {
            $("#lastFMSearch").parent().submit();
        },
        minLength: 2
    } )
    .autocomplete( "instance" )._renderItem = function( ul, item ) {
        return $( "
" )
            .append( "
" + item.label + "
" )
            .appendTo( ul );
    };
});

ADD auto submit after select search


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

const Media = require('../lib/models/media');
const decorate = require('../lib/decorate/index');
const render = require('../lib/push/render');
const tmdbApi = require('moviedb');

/* GET home page. */
router.get('/', (req, res, next) => {
  const opts = {
    baseUrl: process.env.BASE_URL,
    replyTo: process.env.MAIL_REPLY_TO,
    premailer: false,
    api: tmdbApi(process.env.TMBDP_API_KEY),
  };
  const period = 1; // day
  let added;
  let removed;
  Media.retrieveAdded(period)
    .then(docs => decorate(docs, opts))
    .then(docs => { added = docs; })
    .then(() => Media.retrieveRemoved(period))
    .then(docs => { removed = docs; })
    .then(() => render(added, removed, opts))
    .then(html => res.end(html))
    .catch(err => next(err));
});

module.exports = router;

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

const Media = require('../lib/models/media');
const decorate = require('../lib/decorate/index');
const render = require('../lib/push/render');
const tmdbApi = require('moviedb');

router.get('/', (req, res, next) => {
  const opts = {
    baseUrl: process.env.BASE_URL,
    replyTo: process.env.MAIL_REPLY_TO,
    premailer: false,
    api: tmdbApi(process.env.TMBDP_API_KEY),
  };

  let period = 1; // days
  if (req.query && req.query.period) {
    const int = parseInt(req.query.period, 10);
    if (int > 1 && int < 7) {
      period = req.query.period;
    }
  }

  let added;
  let removed;
  Media.retrieveAdded(period)
    .then(docs => decorate(docs, opts))
    .then(docs => { added = docs; })
    .then(() => Media.retrieveRemoved(period))
    .then(docs => { removed = docs; })
    .then(() => render(added, removed, opts))
    .then(html => res.end(html))
    .catch(err => next(err));
});

module.exports = router;

Add 'period' query param to news page


// Launch search daemon if needed, send config to it
//

'use strict';

const _       = require('lodash');
const cluster = require('cluster');
const path    = require('path');
const Server  = require('nodeca.search/lib/server');

module.exports = function (N) {

  let server;

  N.wire.after('init:models', function* search_daemon_init(N) {
    if (!cluster.isMaster) return;

    let endpoint = _.get(N.config, 'search.api_endpoint', 'http://localhost:9304/').replace(/\/$/, '');

    if (N.config.search.is_local) {
      server = new Server(path.resolve(N.mainApp.root, N.config.search.base));

      yield server.start(endpoint);
    }

    try {
      let status = yield N.search.status();

      if (!status.initialized) {
        yield N.search.config(N.config.search);
      }
    } catch (err) {
      N.logger.error("Can't initialize search: " + (err.message || err));
    }
  });

  N.wire.on([ 'exit.shutdown', 'exit.terminate' ], { ensure: true, parallel: true }, function close_search() {
    if (!server) return;

    return server.stop();
  });
};

// Launch search daemon if needed, send config to it
//

'use strict';

const _       = require('lodash');
const cluster = require('cluster');
const path    = require('path');
const Server  = require('nodeca.search/lib/server');

module.exports = function (N) {

  let server;

  N.wire.before('init:services', function* search_daemon_init(N) {
    if (!cluster.isMaster) return;

    let endpoint = _.get(N.config, 'search.api_endpoint', 'http://localhost:9304/').replace(/\/$/, '');

    if (N.config.search.is_local) {
      server = new Server(path.resolve(N.mainApp.root, N.config.search.base));

      yield server.start(endpoint);
    }

    try {
      let status = yield N.search.status();

      if (!status.initialized) {
        yield N.search.config(N.config.search);
      }
    } catch (err) {
      N.logger.error("Can't initialize search: " + (err.message || err));
    }
  });

  N.wire.on([ 'exit.shutdown', 'exit.terminate' ], { ensure: true, parallel: true }, function close_search() {
    if (!server) return;

    return server.stop();
  });
};

Change proxy init priority (avoid conflict with assets rebuild)


module.exports = {
    port: 8000,
    ga: 'UA-XXXXX-X',
    bugira: 'replacethis',
    cron: 'some expression',
    pingdom: {
        user: 'replacethis',
        pass: 'replacethis',
        appkey: 'replacethis'
    }
};

module.exports = {
    port: 8000,
    ga: 'UA-XXXXX-X',
    bugira: 'replacethis',
    cron: '',
    pingdom: {
        user: 'replacethis',
        pass: 'replacethis',
        appkey: 'replacethis'
    }
};

Use empty expression for cron by default


import React from 'react';
import { c } from 'ttag';
import { SubTitle, Label, Row, Field, Alert, useUser } from 'react-components';
import { Link } from 'react-router-dom';

const UsernameSection = () => {
    const [{ Name }] = useUser();
    const link = <Link key="linkIdentity" to="/settings/identity">{c('Link').t`identity settings`}</Link>;

    return (
        <>
            <SubTitle>{c('Title').t`Username`}</SubTitle>
            <Row>
                {c('Label').t`Name`}
                <Field>
                    {Name}
                </Field>
            </Row>
            <Alert>{c('Info').jt`To manage your display name and signature, go to ${link}.`}</Alert>
        </>
    );
};

export default UsernameSection;

import React from 'react';
import { c } from 'ttag';
import { SubTitle, Label, Row, Field, useUser } from 'react-components';

const UsernameSection = () => {
    const [{ Name }] = useUser();

    return (
        <>
            <SubTitle>{c('Title').t`Username`}</SubTitle>
            <Row>
                {c('Label').t`Name`}
                <Field>
                    {Name}
                </Field>
            </Row>
        </>
    );
};

export default UsernameSection;

Remove Alert since this section is used in ProtonVPN also


import React from 'react';
import { c } from 'ttag';
import { SubTitle, Label, Row, Field, useUser } from 'react-components';

const UsernameSection = () => {
    const [{ Name }] = useUser();

    return (
        <>
            <SubTitle>{c('Title').t`Username`}</SubTitle>
            <Row>
                {c('Label').t`Name`}
                <Field className="pt0-5">
                    {Name}
                </Field>
            </Row>
        </>
    );
};

export default UsernameSection;

import React from 'react';
import { c } from 'ttag';
import { SubTitle, Label, Row, Field, Href, Loader, useUser, useAddresses, useConfig } from 'react-components';
import { CLIENT_TYPES } from 'proton-shared/lib/constants';

const { VPN } = CLIENT_TYPES;

const UsernameSection = () => {
    const [{ Name }] = useUser();
    const [addresses, loading] = useAddresses();
    const [{ Email } = {}] = addresses || [];
    const { CLIENT_TYPE } = useConfig();

    return (
        <>
            <SubTitle>{c('Title').t`Username`}</SubTitle>
            <Row>
                {c('Label').t`Name`}
                <Field className="pt0-5">
                    {Name}
                </Field>
            </Row>
            {CLIENT_TYPE === VPN ? (
                loading ? (
                    <Loader />
                ) : (
                    <Row>
                        {c('Label').t`ProtonMail address`}
                        <Field className="pt0-5">
                            {addresses.length ? (
                                {Email}
                            ) : (
                                <Href
                                    url="https://mail.protonmail.com/login"
                                    title={c('Info').t`Log in to ProtonMail to activate your address`}
                                >{c('Link').t`Not activated`}</Href>
                            )}
                        </Field>
                    </Row>
                )
            ) : null}
        </>
    );
};

export default UsernameSection;

Allow user to activate his ProtonMail address


'use strict';

/**
 * Provides a custom marked renderer.
 * @param {Marked} marked - The marked application.
 * @returns {Marked.Renderer} The customized marked renderer.
 */
function markedRenderer( marked ) {

  // Get the original renderer.
  var renderer = new marked.Renderer();

  /**
   * Overwrite link rendering to provide target properties.
   * The title property can contain the target value separated by a vertical bar.
   * Examples:
   *    * "title" - title contains the title only
   *    * "title | _blank" - title contains both title and target
   *    * "|_blank" - title contains the target only
   *    * "|" - default target is "_blank"
   * @param {string} href
   * @param {string} title
   * @param {string} text
   * @returns {string} The HTML text of the link.
   */
  renderer.link = function (href, title, text) {
    var target = null;

    var pos = (title || '').indexOf( '|' );
    if (pos >= 0) {
      target = title.substring( pos + 1).trim() || '_blank';
      title = title.substring( 0, pos).trim();
    }

    return  '' + text + '';
  };

  // Returns the customized marked renderer.
  return renderer;
}

module.exports = markedRenderer;

'use strict';

/**
 * Provides a custom marked renderer.
 * @param {Marked} marked - The marked application.
 * @returns {Marked.Renderer} The customized marked renderer.
 */
function markedRenderer( marked ) {

  // Get the original renderer.
  var renderer = new marked.Renderer();

  /**
   * Overwrite link rendering to provide target properties.
   * The title property can contain the target value separated by a vertical bar.
   * Examples:
   *    * "title" - title contains the title only
   *    * "title | _blank" - title contains both title and target
   *    * "|_blank" - title contains the target only
   *    * "|" - default target is "_blank"
   * @param {string} href
   * @param {string} title
   * @param {string} text
   * @returns {string} The HTML text of the link.
   */
  renderer.link = function (href, title, text) {
    var target = null;

    title = title || '';
    var pos = title.indexOf( '|' );
    if (pos > -1) {
      target = title.substring( pos + 1).trim() || '_blank';
      title = title.substring( 0, pos).trim();
    }

    return  '' + text + '';
  };

  // Returns the customized marked renderer.
  return renderer;
}

module.exports = markedRenderer;

Fix a bug in renderer.


module.exports = [
    {
        test: /\.svg$/,
        loader: 'svg-sprite',
        exclude: /fonts/,
    },{
        test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        loader: 'url-loader?limit=60000&mimetype=application/font-woff',
    },{
        test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        loader: 'url-loader?limit=60000',
        include: /fonts/,
    },{
        test: /\.(png|jpg)$/,
        loader: 'url-loader?limit=8192',
    },{
        test: /\.css$/,
        include: /node_modules/,
        loader: 'style!css!postcss',
    },{
        test: /\.mcss$/,
        loader: 'style!css?modules&importLoaders=1&localIdentName=[name]_[local]_[hash:base64:5]!postcss',
    },{
        test: /\.c$/i,
        loader: "shader",
    },{
        test: /\.json$/,
        loader: 'json-loader',
    },{
        test: /\.html$/,
        loader: 'html-loader',
    },{
        test: /\.js$/,
        include: /node_modules\/paraviewweb\//,
        loader: 'babel?presets[]=es2015,presets[]=react',
    },{
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel?presets[]=es2015,presets[]=react',
    },
]

module.exports = [
    {
        test: /\.svg$/,
        loader: 'svg-sprite',
        exclude: /fonts/,
    },{
        test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        loader: 'url-loader?limit=60000&mimetype=application/font-woff',
    },{
        test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        loader: 'url-loader?limit=60000',
        include: /fonts/,
    },{
        test: /\.(png|jpg)$/,
        loader: 'url-loader?limit=8192',
    },{
        test: /\.css$/,
        loader: 'style!css!postcss',
    },{
        test: /\.mcss$/,
        loader: 'style!css?modules&importLoaders=1&localIdentName=[name]_[local]_[hash:base64:5]!postcss',
    },{
        test: /\.c$/i,
        loader: "shader",
    },{
        test: /\.json$/,
        loader: 'json-loader',
    },{
        test: /\.html$/,
        loader: 'html-loader',
    },{
        test: /\.js$/,
        include: /node_modules\/paraviewweb\//,
        loader: 'babel?presets[]=es2015,presets[]=react',
    },{
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel?presets[]=es2015,presets[]=react',
    },
]

Make css loader rule work on *all* css files.


'use strict';

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

class TestRunner {

  constructor(dir, router) {

    this.dir = dir;
    this.router = router;

  }

  tests() {

    let tests = [];
    let filter;

    if (process.argv.length > 3) {
      filter = process.argv[3];
      filter = filter.endsWith('.js') ? filter : `${filter}.js`;
    }

    let addTest = dir => {

      return filename => {

        if (!path.extname(filename) && filename[0] !== '.') {

          let nextDir = path.resolve(dir, filename);
          return fs.readdirSync(nextDir).forEach(addCommand(nextDir));

        }

        if (filter && filename !== filter) {
          return;
        }

        let Test = require(path.resolve(dir, filename));
        tests.push(new Test(this));

      }

    };

    let testDir = path.resolve(process.cwd(), this.dir || '/');
    fs.readdirSync(testDir).forEach(addTest(testDir));

    return tests;

  }

  start(verb) {

    this.tests().forEach(t => {

      t.__test__(verb);

    });

  }

}

module.exports = TestRunner;

'use strict';

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

class TestRunner {

  constructor(dir, router) {

    this.dir = dir;
    this.router = router;

  }

  tests() {

    let tests = [];
    // Strip mocha and node flags out before looking for filter
    let filter = process.argv.filter(str => !str.startsWith('--')).slice(3)[0]

    if (filter) {
      filter = filter.endsWith('.js') ? filter : `${filter}.js`;
    }

    let addTest = dir => {

      return filename => {

        if (!path.extname(filename) && filename[0] !== '.') {

          let nextDir = path.resolve(dir, filename);
          return fs.readdirSync(nextDir).forEach(addCommand(nextDir));

        }

        if (filter && filename !== filter) {
          return;
        }

        let Test = require(path.resolve(dir, filename));
        tests.push(new Test(this));

      }

    };

    let testDir = path.resolve(process.cwd(), this.dir || '/');
    fs.readdirSync(testDir).forEach(addTest(testDir));

    return tests;

  }

  start(verb) {

    this.tests().forEach(t => {

      t.__test__(verb);

    });

  }

}

module.exports = TestRunner;

Add support for passing flags to Mocha


function Land()
{
  this.width              = 100;
  this.height             = 100;
  this.fillStyle          = "#00d23e"; // green
  this.x                  = 200;
  this.y                  = 100;
  this.drawWaves          = true;
  this.waves              = [];
  this.waveCycle          = 0;
}

Land.prototype.draw = function(ctx)
{
  ctx.fillStyle = this.fillStyle;
  ctx.fillRect(this.x,this.y,this.width,this.height);
  if(this.drawWaves)
  {
    var i = 0;
    var len = this.waves.length;
    this.waveCycle++;
    if(this.waveCycle % 60 == 0){
      this.waves.push(new Wave(this));
      len + 1;
    }

    for(;i<len;i++)
    {
      if(this.waves[i].age < 100){
        this.waves[i].draw(ctx);
      }
      else {
        this.waves.slice(i,1);
      }
    }

  }
}

function Land()
{
  this.width              = 100;
  this.height             = 100;
  this.fillStyle          = "#00d23e"; // green
  this.x                  = 200;
  this.y                  = 100;
  this.drawWaves          = true;
  this.waves              = [];
  this.waveCycle          = 0;
}

Land.prototype.draw = function(ctx)
{
  ctx.fillStyle = this.fillStyle;
  ctx.fillRect(this.x,this.y,this.width,this.height);
  if(this.drawWaves)
  {
    var i = 0;
    var len = this.waves.length;
    this.waveCycle++;
    if(this.waveCycle % 60 == 0){
      this.waves.push(new Wave(this));
      len + 1;
    }

    for(;i<len;i++)
    {
      if(this.waves[i].age < 1/this.waves[i].opacityStep){
        this.waves[i].draw(ctx);
      }
      else {
        this.waves.slice(i,1);
      }
    }

  }
}

Stop drawing wave at inverse of opacityStep


"use strict";
/**
 * Get count of database-wide values.
 **/
define([
    "underscore",
    "underscore.string",
    "backbone"
], function(_, _s, Backbone) {

    var GlobalCountModel = Backbone.Model.extend({
        url: function() {
            return "_view/global?group=true";
        },

        /**
         * Parse the *grouped* values.
         **/
        parse: function(response, options){
            var results = {};

            if (_.has(response, "rows") && _.isArray(response.rows)) {
                _.each(response.rows, function(row) {
                    results[row.key] = row.value;
                });
            }

            return results;
        },

        toJSON: function() {
            var result = this.attributes;
            // Customize the strings.
            _.each(_.pairs(this.attributes), function(pair) {
                result[pair[0] + "_fmt"] = _s.numberFormat(pair[1], 0);
            });
            return result;
        }

    });

    return GlobalCountModel;
});

"use strict";
/**
 * Get count of database-wide values.
 **/
define([
    "underscore",
    "underscore.string",
    "backbone"
], function(_, _s, Backbone) {

    var GlobalCountModel = Backbone.Model.extend({
        url: function() {
            return "_view/global?group=true";
        },

        /**
         * Parse the *grouped* values.
         **/
        parse: function(response, options){
            var results = {};

            if (_.has(response, "rows") && _.isArray(response.rows)) {
                _.each(response.rows, function(row) {
                    results[row.key] = row.value;
                });
            }

            return results;
        },

        toJSON: function() {
            var result = this.attributes;
            // Customize the strings.
            _.each(_.pairs(this.attributes), function(pair) {
                result[pair[0] + "_fmt"] = _s.numberFormat(pair[1], 0);
            });
            return result;
        },

        /* Custom functions. */
        /**
         * Format a field simply (commas after thousands).
         */
        formatSimply: function(fieldName) {
            return _s.numberFormat(this.get(fieldName), 0);
        }

    });

    return GlobalCountModel;
});

Add helper function to format numbers.


import {
  moduleForComponent,
  test
} from 'ember-qunit';

import Ember from 'ember';

moduleForComponent('markdown-to-html', 'MarkdownToHtmlComponent', {
  // specify the other units that are required for this test
  needs: ['component:markdown-to-html']
});

test('it renders', function() {
  expect(2);

  // creates the component instance
  var component = this.subject();
  equal(component._state, 'preRender');

  // appends the component to the page
  this.append();
  equal(component._state, 'inDOM');
});

test('it produces markdown', function() {
  expect(2);

  var component = this.subject();
  this.append();

  Ember.run(function() {
    component.set('markdown', '##Hello, [world](#)');
  });

  var expectedHtml = '
Hello, world
';

  equal(component.get('html').toString(), expectedHtml);
  equal(component.$().html().toString().trim(), expectedHtml);
});

test('it inserts
 tag', function() {
  expect(1);

  var component = this.subject();
  this.append();

  Ember.run(function() {
    component.set('markdown', 'foo  \nbar');
  });

  var expectedHtml = '
foo
\nbar
';

  equal(component.get('html').toString(), expectedHtml);
});

Add test to verify it inserts <br> tag on new lines.


/**
 * Serve automatic timeline updates.
 */
define([], function() {

  'use strict';

  return function(socket) {

    var redis = null;

    /**
     * Client seeds the update stream with the last-seen ID/time.
     *
     * @param options {Object} Last-seen attributes.
     * - newestEventId {String}
     * - newestEventTime {Number}
     */
    socket.on('StartTimelineUpdate', function (options) {
      if (!redis) {
        redis = mainevent.requireModule('redis').createInstance();

        redis.connect();
        redis.client.on('ready', function() {
          redis.client.subscribe('InsertLog');
          redis.client.on('message', function(channel, message) {
            if ('InsertLog' == channel) {
              var docs = JSON.parse(message),
              parsers = mainevent.requireModule('parsers');

              parsers.buildPreviewTemplateContext(docs, function(docs) {
                socket.emit('TimelineUpdate', docs);
              });
            }
          });
        });
      }
    });

    socket.on('disconnect', function () {
      redis.client.unsubscribe();
      redis.end();
      redis = null;
    });
  };
});

/**
 * Serve automatic timeline updates.
 */
define([], function() {

  'use strict';

  return function(socket) {

    var redis = null;

    /**
     * Client seeds the update stream with the last-seen ID/time.
     *
     * @param options {Object} Last-seen attributes.
     * - newestEventId {String}
     * - newestEventTime {Number}
     */
    socket.on('StartTimelineUpdate', function (options) {
      if (!redis) {
        redis = mainevent.requireModule('redis').createInstance();

        redis.connect();
        redis.client.on('ready', function() {
          redis.client.subscribe('InsertLog');
          redis.client.on('message', function(channel, message) {
            if ('InsertLog' == channel) {
              var docs = JSON.parse(message),
              parsers = mainevent.requireModule('parsers');

              parsers.buildPreviewTemplateContext(docs, function(docs) {
                socket.emit('TimelineUpdate', docs);
              });
            }
          });
        });
      }
    });

    socket.on('disconnect', function () {
      if (!redis) {
        return;
      }
      redis.client.unsubscribe();
      redis.end();
      redis = null;
    });
  };
});

Fix socket.io disconnect handler TypeError.


define([
    "jquery",
    "underscore",
    "parse",
    "backbone"
], function( $, _, Parse, Backbone ) {

    var userChannel = Backbone.Wreqr.radio.channel('user');

    Backbone.Wreqr.radio.reqres.setHandler("user", "testing", function(id) {
        return "deez" + id;
    });

    console.log(userChannel.reqres.request('testing', 'anid'));

    return userChannel;
} );

define([
    "jquery",
    "underscore",
    "parse",
    "backbone",
    "marionette"
], function( $, _, Parse, Backbone, Marionette ) {

    var userChannel = Backbone.Wreqr.radio.channel('user');

    Backbone.Wreqr.radio.reqres.setHandler("user", "testing", function(id) {
        return "deez" + id;
    });

    console.log(userChannel.reqres.request('testing', 'anid'));

    return userChannel;
} );

Fix radio load order problems


AutoForm.addHooks(['newActivityForm'], {
  'onSuccess': function () {
    // Hide the modal dialogue
    Modal.hide('newActivity');

    // Alert user that activity was added
    FlashMessages.sendSuccess('<i class="fa fa-check"></i> Activity added!');
  }
});

AutoForm.addHooks(['newActivityForm'], {
  'onSuccess': function () {
    // Hide the modal dialogue
    Modal.hide('newActivity');

    const successMessage = TAPi18n.__("newActivity-success");

    const successMessageWithIcon = '<i class="fa fa-check"></i> ' + successMessage

    // Alert user that activity was added
    FlashMessages.sendSuccess(successMessageWithIcon);
  }
});

Add i18n string to success message


/*
Copyright 2016 Mozilla

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

/* eslint-disable no-console */

import { put } from 'redux-saga/effects';
import { logger } from '../../../shared/logging';

export const failed = type => ({ type: `${type}_FAILED` });

export const wrapped = function(type, fn) {
  const generator = function*(...args) {
    try {
      yield* fn(...args);
    } catch (e) {
      yield put(failed(type));
      logger.error(e);
    }
  };
  Object.defineProperty(generator, 'name', { value: type });
  return [type, generator];
};

/*
Copyright 2016 Mozilla

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

import { logger } from '../../../shared/logging';

export const wrapped = function(pattern, fn, name) {
  if (typeof pattern === 'string') {
    name = name || fn.name || pattern;
  } else if (pattern instanceof Function) {
    name = name || fn.name || 'PREDICATE';
  } else if (pattern instanceof Array) {
    name = name || fn.name || `ARRAY:[${pattern}]`;
  } else if (pattern instanceof RegExp) {
    return wrapped(action => action.type.match(pattern), fn);
  } else {
    throw new Error(`Unsupported redux-saga pattern: ${pattern}`);
  }

  const generator = function*(...args) {
    try {
      yield* fn(...args);
    } catch (e) {
      logger.error(e);
    }
  };

  Object.defineProperty(generator, 'name', { value: name });
  return [pattern, generator];
};

Make the `wrapped` sagas utility function properly handle a more varied set of patterns


import { REHYDRATE } from 'redux-persist/constants'
import {
  refreshAuthenticationToken,
  scheduleAuthRefresh,
} from '../actions/authentication'
import { AUTHENTICATION } from '../constants/action_types'

const toMilliseconds = seconds => seconds * 1000

// Get a timeout value about 100ms before a given date,
// or at least not in the past
const futureTimeout = time => {
  let msFromNow = time - new Date()

  // Establish a lead time of 100ms before expiration date
  msFromNow = msFromNow - 100

  // Let's not set a timeout for in the past
  return Math.max(msFromNow, 0)
}

export const authentication = store => next => action => {
  const { payload, type } = action

  switch (type) {
    case REHYDRATE:
      if (action.key === 'authentication') {
        const { createdAt, expiresIn, refreshToken } = payload

        if (!refreshToken) break

        const now = new Date()
        const expirationDate = new Date(toMilliseconds(createdAt + expiresIn))

        if (expirationDate < now) {
          store.dispatch(refreshAuthenticationToken(refreshToken))
        } else {
          const newTimeout = futureTimeout(expirationDate)

          store.dispatch(scheduleAuthRefresh(refreshToken, newTimeout))
        }
      }

      break
    case AUTHENTICATION.REFRESH_SUCCESS:
    case AUTHENTICATION.USER_SUCCESS:
      store.dispatch(scheduleAuthRefresh(
        payload.response.refreshToken,
        toMilliseconds(7100)
      ))
      break
    default:
      break
  }

  next(action)
}

import { REHYDRATE } from 'redux-persist/constants'
import {
  refreshAuthenticationToken,
  scheduleAuthRefresh,
} from '../actions/authentication'
import { AUTHENTICATION } from '../constants/action_types'

const toMilliseconds = seconds => seconds * 1000

// Get a timeout value about 100ms before a given date,
// or at least not in the past
const futureTimeout = time => {
  let msFromNow = time - new Date()

  // Establish a lead time of 100ms before expiration date
  msFromNow = msFromNow - 100

  // Let's not set a timeout for in the past
  return Math.max(msFromNow, 0)
}

export const authentication = store => next => action => {
  const { payload, type } = action

  switch (type) {
    case REHYDRATE:
      if (action.key === 'authentication') {
        const { createdAt, expiresIn, refreshToken } = payload

        if (!refreshToken) break

        const now = new Date()
        const expirationDate = new Date(toMilliseconds(createdAt + expiresIn))

        if (expirationDate < now) {
          store.dispatch(refreshAuthenticationToken(refreshToken))
        } else {
          const newTimeout = futureTimeout(expirationDate)

          store.dispatch(scheduleAuthRefresh(refreshToken, newTimeout))
        }
      }

      break
    case AUTHENTICATION.REFRESH_SUCCESS:
    case AUTHENTICATION.USER_SUCCESS:
      store.dispatch(scheduleAuthRefresh(
        payload.response.refreshToken,
        toMilliseconds(7100)
      ))
      break
    default:
      break
  }

  return next(action)
}

Make sure the auth middleware returns


import webpack from 'webpack'
import getBaseConfig from './webpack.config'

export default config => {
  const baseConfig = getBaseConfig(config)

  return Object.assign({}, baseConfig, {
    devtool: 'cheap-module-eval-source-map',
    entry: [
      'eventsource-polyfill',
      'webpack-hot-middleware/client'
    ].concat(baseConfig.entry),
    plugins: (baseConfig.plugins || []).concat([
      new webpack.HotModuleReplacementPlugin(),
      new webpack.NoErrorsPlugin()
    ])
  })
}

import webpack from 'webpack'
import getBaseConfig from './webpack.config'

export default config => {
  const baseConfig = getBaseConfig(config)

  return Object.assign({}, baseConfig, {
    devtool: 'cheap-module-eval-source-map',
    entry: [
      require.resolve('eventsource-polyfill'),
      require.resolve('webpack-hot-middleware/client')
    ].concat(baseConfig.entry),
    plugins: (baseConfig.plugins || []).concat([
      new webpack.HotModuleReplacementPlugin(),
      new webpack.NoErrorsPlugin()
    ])
  })
}

Use full paths to webpack entries


'use strict';

var Component = require('../../ui/Component');
var $$ = Component.$$;
var ListHtmlConverter = require('./ListHTMLConverter');
var ListItemComponent = require('./ListItemComponent');

var ListComponent = Component.extend({

  displayName: "ListComponent",

  initialize: function() {
    this.doc = this.props.doc;
    this.doc.getEventProxy('path').connect(this, [this.props.node.id, 'items'], this.onItemsChanged);
    this.doc.getEventProxy('path').connect(this, [this.props.node.id, 'ordered'], this.onOrderChanged);
  },

  dispose: function() {
    this.doc.getEventProxy('path').disconnect(this);
    this.doc = null;
  },

  render: function() {
    var elem = ListHtmlConverter.render(this.props.node, {
      createListElement: function(list) {
        var tagName = list.ordered ? 'ol' : 'ul';
        return $$(tagName)
          .attr('data-id', list.id)
          .addClass('sc-list');
      },
      renderListItem: function(item) {
        return $$(ListItemComponent, {node: item});
      }
    });
    return $$('div').append(elem);
  },

  onItemsChanged: function() {
    this.rerender();
  },

  onOrderChanged: function() {
    this.rerender();
  }

});

module.exports = ListComponent;

'use strict';

var Component = require('../../ui/Component');
var $$ = Component.$$;
var ListHtmlConverter = require('./ListHTMLConverter');
var ListItemComponent = require('./ListItemComponent');

var ListComponent = Component.extend({

  displayName: "ListComponent",

  initialize: function() {
    this.doc = this.props.doc;
    this.doc.getEventProxy('path').connect(this, [this.props.node.id, 'items'], this.onItemsChanged);
    this.doc.getEventProxy('path').connect(this, [this.props.node.id, 'ordered'], this.onOrderChanged);
  },

  dispose: function() {
    this.doc.getEventProxy('path').disconnect(this);
    this.doc = null;
  },

  render: function() {
    var elem = ListHtmlConverter.render(this.props.node, {
      createListElement: function(list) {
        var tagName = list.ordered ? 'ol' : 'ul';
        return $$(tagName)
          .attr('data-id', list.id);
      },
      renderListItem: function(item) {
        return $$(ListItemComponent, {node: item});
      }
    });
    return $$('div').addClass('sc-list').append(elem);
  },

  onItemsChanged: function() {
    this.rerender();
  },

  onOrderChanged: function() {
    this.rerender();
  }

});

module.exports = ListComponent;

Move the list class to the div


const mongoose = require('mongoose');
mongoose.Promise = global.Promise;
const slug = require('slugs');

const storeSchema = new mongoose.Schema({
  name: {
    type: String,
    trim: true,
    required: 'Please enter a store name'
  },
  slug: String,
  description: {
    type: String,
    trime: true
  },
  tags: [String]
});

storeSchema.pre('save', function(next) {
  if (!this.isModified('name')) {
    next(); // skip it
    return; // stop this function from running
  }
  this.slug = slug(this.name);
  next();
  // TODO make more resilient so slugs are unique
});

module.exports = mongoose.model('Store', storeSchema);

const mongoose = require('mongoose');
mongoose.Promise = global.Promise;
const slug = require('slugs');

const storeSchema = new mongoose.Schema({
  name: {
    type: String,
    trim: true,
    required: 'Please enter a store name'
  },
  slug: String,
  description: {
    type: String,
    trim: true
  },
  tags: [String]
});

storeSchema.pre('save', function(next) {
  if (!this.isModified('name')) {
    next(); // skip it
    return; // stop this function from running
  }
  this.slug = slug(this.name);
  next();
  // TODO make more resilient so slugs are unique
});

module.exports = mongoose.model('Store', storeSchema);

Fix typo in store schema


/* @flow */
import {
  Either,
} from './utils/'

import {
  curry,
  compose,
  map,
  prop,
  mergeWithKey,
  sequence,
} from 'ramda'

const { Right, Left } = Either

const identity = r => r

const makePredicate = ([predFn, e]) => (a, all) => predFn(a, all) ? Right(a) : Left(e)
const runPredicates = ([input, validations, all]) =>
  map(predFn => predFn(input, all), map(makePredicate, validations))

const validator = transform => map(compose(transform, sequence(Either.of), runPredicates))
const makeValidationObject = (input, validations) => mergeWithKey((k, l, r) => [l, r, input], input, validations)
const createValidation = (transform: Function = identity) => compose(validator(transform), makeValidationObject)

export default createValidation

/* @flow */
import {
  Either,
} from './utils/'

import {
  curry,
  compose,
  map,
  prop,
  merge,
  mergeWithKey,
  sequence,
} from 'ramda'

const { Right, Left } = Either

const identity = r => r
// create a new object containing an empty array
// createDefaultValidation({'name': '1', 'random': '2'})
// outputs: {name: [], random: []}
const createDefaultValidation = map(i => [])

// merge default validation rules with actual validation rules
// createBaseValidation({name: [], random: []}, {name: [[x => x > 1, 'some msg']]})
// outputs: {name: [[x => x > 1, 'some msg']], random: []})
const createBaseValidation = (input, validations) => merge(createDefaultValidation(input), validations)

const makePredicate = ([predFn, e]) => (a, all) => predFn(a, all) ? Right(a) : Left(e)
const runPredicates = ([input, validations, all]) =>
  map(predFn => predFn(input, all), map(makePredicate, validations))

const validator = transform => map(compose(transform, sequence(Either.of), runPredicates))
const makeValidationObject = (input, validations) => mergeWithKey((k, l, r) => [l, r, input], input, createBaseValidation(input, validations))
const createValidation = (transform: Function = identity) => compose(validator(transform), makeValidationObject)

export default createValidation

Update validation handling to work with missing data


/* Git JS gateway */
/* Tim Harper (tim.harper at leadmediapartners.org) */
function e_sh(str) {
  return '"' + (str.toString().gsub('"', '\\"')) + '"';
}

function exec(command, params) {
  params = params.map(function(a) { return e_sh(a) }).join(" ")

  return TextMate.system(command + " " + params, null)
}

function ENV(var_name) {
  return TextMate.system("echo $" + var_name, null).outputString.strip();
}

function gateway_command(command, params) {
  // var cmd = arguments.shift
  // var params = arguments
  try {
    command = "ruby " + e_sh(TM_BUNDLE_PATH) + "/Support/gateway/" + command
    return exec(command, params).outputString
  }
  catch(err) {
    return "ERROR!" + err;
  }
}

function dispatch(params) {
  try {
    params = $H(params).map(function(pair) { return(pair.key + "=" + pair.value.toString())})
    command = "ruby " + e_sh(TM_BUNDLE_PATH) + "/Support/dispatch.rb";
    // return params.map(function(a) { return e_sh(a) }).join(" ")
    return exec(command, params).outputString
  }
  catch(err) {
    return "ERROR!" + err;
  }
}
TM_BUNDLE_PATH = ENV('TM_BUNDLE_PATH')

/* Git JS gateway */
/* Tim Harper (tim.harper at leadmediapartners.org) */
function e_sh(str) {
  return '"' + (str.toString().gsub('"', '\\"')) + '"';
}

function exec(command, params) {
  params = params.map(function(a) { return e_sh(a) }).join(" ")

  return TextMate.system(command + " " + params, null)
}

function ENV(var_name) {
  return TextMate.system("echo $" + var_name, null).outputString.strip();
}

function gateway_command(command, params) {
  // var cmd = arguments.shift
  // var params = arguments
  try {
    command = "ruby " + e_sh(TM_BUNDLE_SUPPORT) + "/gateway/" + command
    return exec(command, params).outputString
  }
  catch(err) {
    return "ERROR!" + err;
  }
}

function dispatch(params) {
  try {
    params = $H(params).map(function(pair) { return(pair.key + "=" + pair.value.toString())})
    command = "ruby " + e_sh(TM_BUNDLE_SUPPORT) + "/dispatch.rb";
    // return params.map(function(a) { return e_sh(a) }).join(" ")
    return exec(command, params).outputString
  }
  catch(err) {
    return "ERROR!" + err;
  }
}
TM_BUNDLE_SUPPORT = ENV('TM_BUNDLE_SUPPORT')

Use TM_BUNDLE_SUPPORT over the deprecated TM_BUNDLE_PATH variable.


import EventEmitter from 'eventemitter3'
import Mousetrap from 'mousetrap'
import dragDrop from 'drag-drop'

class Commands extends EventEmitter {

	constructor() {
		super()

		this._initKeybind()
		this._initDragDrop()
	}

	_initKeybind() {
		Mousetrap.bind('esc', () => {
			this.emit('reset-canvas')
			return false
		})

		Mousetrap.bind('command+s', () => {
			this.emit('save-canvas')
			return false
		})

		Mousetrap.bind('command+1', () => {
			this.emit('reload-effects')
			return false
		})

	}

	_initDragDrop() {

		dragDrop('body', (files) => this._validateImageAndEmit('load-source', files))
	}

	_validateImageAndEmit(eventName, files) {
		if (files.length == 1 && files[0].name.match(/\.(jpg|jpeg|png|gif)$/)) {
			this.emit(eventName, files[0])
		} else {
			console.log('failed')
		}
	}

	// public
	loadImage(eventName) {
		$('#image-loader')
			.on('change', (e) => this._validateImageAndEmit(eventName, e.target.files))
			.trigger('click')
	}

	execute() {
		this.emit(...arguments)
	}
}

window.Commands = new Commands()

import EventEmitter from 'eventemitter3'
import Mousetrap from 'mousetrap'
import dragDrop from 'drag-drop'

class Commands extends EventEmitter {

	constructor() {
		super()

		this._initKeybind()
		this._initDragDrop()
	}

	_initKeybind() {
		Mousetrap.bind('esc', () => {
			this.emit('reset-canvas')
			return false
		})

		Mousetrap.bind('command+s', () => {
			this.emit('save-canvas')
			return false
		})

		Mousetrap.bind('command+1', () => {
			this.emit('reload-effects')
			return false
		})

		Mousetrap.bind('f', () => {
			this.emit('step-forward')
		})

	}

	_initDragDrop() {

		dragDrop('body', (files) => this._validateImageAndEmit('load-source', files))
	}

	_validateImageAndEmit(eventName, files) {
		if (files.length == 1 && files[0].name.match(/\.(jpg|jpeg|png|gif)$/i)) {
			this.emit(eventName, files[0])
		} else {
			console.log('failed')
		}
	}

	// public
	loadImage(eventName) {
		$('#image-loader')
			.on('change', (e) => this._validateImageAndEmit(eventName, e.target.files))
			.trigger('click')
	}

	execute() {
		this.emit(...arguments)
	}
}

window.Commands = new Commands()

Update regex of image source


#!/usr/bin/env node
// adapted based on rackt/history (MIT)
var execSync = require('child_process').execSync;
var stat = require('fs').stat;

function exec(command) {
  execSync(command, {
    stdio: [0, 1, 2]
  });
}

stat('dist-modules', function(error, stat) {
  if (error || !stat.isDirectory()) {
    exec('npm run dist-modules');
  }
});

#!/usr/bin/env node
// adapted based on rackt/history (MIT)
var execSync = require('child_process').execSync;
var stat = require('fs').stat;

function exec(command) {
  execSync(command, {
    stdio: [0, 1, 2]
  });
}

stat('dist-modules', function(error, stat) {
  if (error || !stat.isDirectory()) {
    exec('npm i babel');
    exec('npm run dist-modules');
  }
});

Install Babel before trying to generate `dist-modules`


var config = require('config');
var fmt = require('fmt');
var otp = require('otp');
var Plan = require('plan');
var view = require('view');

var Feedback = module.exports = view(require('./feedback.html'));

Feedback.prototype.link = function() {
  return fmt('%s/planner?%s', config.base_url(), decodeURIComponent(this.model.plan.generateQueryString()));
};

Feedback.prototype.summary = function() {
  return this.model.results ? this.model.results.summary : '';
};

Feedback.prototype.name = function() {
  var commuter = this.model._commuter || {};
  return commuter.name || commuter.email || 'Anonymous';
};

Feedback.prototype.resultsLink = function() {
  var plan = new Plan(this.model.plan);
  return otp.url(plan.generateQuery());
};

var config = require('config');
var fmt = require('fmt');
var otp = require('otp');
var Plan = require('plan');
var view = require('view');

var Feedback = module.exports = view(require('./feedback.html'));

Feedback.prototype.link = function() {
  var plan = new Plan(this.model.plan);
  return fmt('%s/planner?%s', config.base_url(), decodeURIComponent(plan.generateQueryString()));
};

Feedback.prototype.summary = function() {
  return this.model.results ? this.model.results.summary : '';
};

Feedback.prototype.name = function() {
  var commuter = this.model._commuter || {};
  return commuter.name || commuter.email || 'Anonymous';
};

Feedback.prototype.resultsLink = function() {
  var plan = new Plan(this.model.plan);
  return otp.url(plan.generateQuery());
};

Fix function call error on feeback page


const Image = require('@11ty/eleventy-img');

module.exports = async (src, alt, className, outputFormat = 'jpeg') => {
  const stats = await Image(src, {
    cacheDuration: '12w',
    formats: [outputFormat],
    outputDir: '_site/assets/avatar/',
    widths: [96],
  });

  const classAttr = className ? `class="${className}"` : '';

  const props = stats[outputFormat].pop();

  if (alt === undefined) {
    throw new Error(`Missing ALT attribute from: ${src}`);
  }

  return `<img ${classAttr} src="${props.outputPath.replace('_site', '')}" alt="${alt}" loading="lazy">`;
};

const Image = require('@11ty/eleventy-img');

module.exports = async (src, alt, className, outputFormat = 'jpeg') => {
  if (alt === undefined) {
    throw new Error(`Missing ALT attribute from: ${src}`);
  }

  const classAttr = className ? `class="${className}"` : '';

  try {
    const stats = await Image(src, {
      cacheDuration: '12w',
      formats: [outputFormat],
      outputDir: '_site/assets/avatar/',
      widths: [96],
    });

    const props = stats[outputFormat].pop();

    return `<img ${classAttr} src="${props.outputPath.replace('_site', '')}" alt="${alt}" loading="lazy">`;
  } catch (err) {
    // eslint-disable-next-line no-console
    console.error(err);

    return '';
  }
};

Fix eleventy-img fetch 404 issue


export default function interact() {
  const game = this.game.state.states.Game;

  const player = game.player;

  player.inventory.water++;

  // this should be its own function somewhere
  game.hud.water.setText('Water: ' + player.inventory.water);
}

export default function interact() {
  const game = this.game.state.states.Game;

  const player = game.player;

  player.inventory.water++;
}

Handle updating water count elsewhere


export default (props) => (
  <img src={`https://chalees-min.imgix.net${props.imagePath}?w=726&fit=clip&auto=format,compress`} />
)

export default (props) => (
  <img className="pure-img" src={`https://chalees-min.imgix.net${props.imagePath}?w=726&fit=clip&auto=format,compress`} />
)

Fix missing auto-resizing behavior on image learning items


'use strict';

var path = require('path');
var async = require('async');
var bowerHash = require('./bower-hash');

function updateCache(context, bowerDirectory, done) {
  var cachier = context.cachier('modules');
  var dataDir = context.dataDir;

  bowerHash(dataDir, function (err, hash) {
    if (err) {
      return done();
    }

    var dest = path.join(dataDir, bowerDirectory);

    context.comment('saved ' + bowerDirectory + ' to cache');

    async.series([
      cachier.update.bind(null, hash, dest),
      cachier.update.bind(null, context.branch, dest),
    ], done);
  });
}

module.exports = updateCache;

'use strict';

var path = require('path');
var async = require('async');
var bowerHash = require('./bower-hash');

function updateCache(context, bowerDirectory, done) {
  var cachier = context.cachier('modules');
  var dataDir = context.dataDir;

  bowerHash(dataDir, function (err, hash) {
    if (err) {
      return done(err);
    }

    var dest = path.join(dataDir, bowerDirectory);

    context.comment('saved ' + bowerDirectory + ' to cache');

    async.series([
      cachier.update.bind(null, hash, dest),
      cachier.update.bind(null, context.branch, dest),
    ], done);
  });
}

module.exports = updateCache;

Send bowerHash error via done


//= require govuk/multivariate-test

/*jslint browser: true,
 *  indent: 2,
 *  white: true
 */
/*global $, GOVUK */
$(function () {

  GOVUK.taxDiscBetaPrimary = function () {
    $('.primary-apply').html($('#beta-primary').html());
    $('.secondary-apply').html($('#dvla-secondary').html());
  };

  if(window.location.href.indexOf("/tax-disc") > -1) {
    new GOVUK.MultivariateTest({
      name: 'tax-disc-beta',
      customVarIndex: 20,
      cohorts: {
        tax_disc_beta_control: { weight: 50, callback: function () { } }, //50%
        tax_disc_beta: { weight: 50, callback: GOVUK.taxDiscBetaPrimary } //50%
      }
    });
  }
});

//= require govuk/multivariate-test

/*jslint browser: true,
 *  indent: 2,
 *  white: true
 */
/*global $, GOVUK */
$(function () {

  GOVUK.taxDiscBetaPrimary = function () {
    $('.primary-apply').html($('#beta-primary').html());
    $('.secondary-apply').html($('#dvla-secondary').html());
  };

  if(window.location.href.indexOf("/tax-disc") > -1) {
    new GOVUK.MultivariateTest({
      name: 'tax-disc-beta',
      customVarIndex: 20,
      cohorts: {
        tax_disc_beta_control: { weight: 1, callback: function () { } }, //~100%
        tax_disc_beta: { weight: 0, callback: GOVUK.taxDiscBetaPrimary } //~0%
      }
    });
  }
});

Increase weight of Tax Disc Beta AB to 100%


import Component from '@ember/component';
import { computed } from '@ember-decorators/object';

export default class BlogPostMenu extends Component {
  @computed('page', 'posts.[]')
  get paginatedPosts() {
    const page = this.get('page');
    const posts = this.get('posts');

    const start = (page - 1) * 10;
    const end = start + 10;

    return posts.slice(start, end);
  }
}

import Component from '@ember/component';
import { computed } from '@ember-decorators/object';

export default class BlogPostMenu extends Component {
  @computed('page', 'posts.[]')
  get paginatedPosts() {
    const page = this.get('page');
    const posts = this.get('posts');

    if (posts) {
      const start = (page - 1) * 10;
      const end = start + 10;

      return posts.slice(start, end);
    }

    return [];
  }
}

Return empty array, if no posts


window.EventSourcePolyfill = require('./eventsource')
window.EventSource = window.EventSource || window.EventSourcePolyfill

var EventSource = require('./eventsource')
if (typeof window === 'object') {
  window.EventSourcePolyfill = EventSource
  if (!window.EventSource) window.EventSource = EventSource
}

Check window object exists before polyfilling


import express from 'express';
import * as validate from '../../middleware/validate';
import * as recipeController from '../../controllers/recipes';
import * as reviewController from '../../controllers/reviews';
import * as voteController from '../../controllers/votes';
import * as auth from '../../middleware/auth';

const user = express.Router();

user.use('*', auth.verify);

user.post('/', recipeController.createRecipe);
user.get('/', recipeController.getAllRecipes);

user.use('/:recipeId', validate.validateRecipeId);
user.get('/:recipeId', recipeController.getRecipe);
user.put('/:recipeId', recipeController.modifyRecipe);
user.delete('/:recipeId', recipeController.deleteRecipe);

user.post('/:recipeId/reviews', reviewController.postReview);
user.get('/:recipeId/reviews', reviewController.getReviews);

user.post('/:recipeId/upvotes', voteController.upvoteRecipe);
user.post('/:recipeId/downvotes', voteController.downvoteRecipe);
user.get('/:recipeId/upvotes', voteController.getUserUpvotes);
user.get('/:recipeId/downvotes', voteController.getUserDownvotes);

export default user;

import express from 'express';
import * as validate from '../../middleware/validate';
import * as recipeController from '../../controllers/recipes';
import * as reviewController from '../../controllers/reviews';
import * as voteController from '../../controllers/votes';
import * as auth from '../../middleware/auth';

const user = express.Router();

user.use('*', auth.verify);

user.post('/', recipeController.createRecipe);
user.get('/', recipeController.getAllRecipes);

user.use('/:recipeId', validate.validateUserId, validate.validateRecipeId);
user.get('/:recipeId', recipeController.getRecipe);
user.put('/:recipeId', recipeController.modifyRecipe);
user.delete('/:recipeId', recipeController.deleteRecipe);

user.post('/:recipeId/reviews', reviewController.postReview);
user.get('/:recipeId/reviews', reviewController.getReviews);

user.post('/:recipeId/upvotes', voteController.upvoteRecipe);
user.post('/:recipeId/downvotes', voteController.downvoteRecipe);
user.get('/:recipeId/upvotes', voteController.getUserUpvotes);
user.get('/:recipeId/downvotes', voteController.getUserDownvotes);

export default user;

Add validate User id middleware to protected routes


var Q = require('q');

module.exports = function(url) {
    var deferred = Q.defer();
    require('request')(url, function(err, response, body) {
        if(!err) {
            return deferred.resolve(body);
        } else {
            return deferred.reject(new Error(error));
        }
    });
    return deferred.promise;
};

var Q = require('q');

module.exports = function(url) {
    var deferred = Q.defer();
    require('request')(url, function(err, response, body) {
        if(!err) {
            return deferred.resolve(body);
        } else {
            return deferred.reject(new Error(err));
        }
    });
    return deferred.promise;
};

Fix error handling on promised requests


var Assert            = require('assert');
var CacheAllTheThings = require('../');

describe('CacheAllTheThings', function() {

  it('When asked to boot a Redis instance, it should do so', function() {
    var inst = new CacheAllTheThings('redis');
    Assert(inst.name, 'RedisCache');
  });

  describe('Redis', function() {

    it('Should set a key', function(done) {
      var redisInst = new CacheAllTheThings('redis');

      redisInst
        .set('lol', 'hai')
        .then(function(e) {
          Assert.equal(e, null);
          done();
        });
    });

    it('Should get a key that exists', function(done) {
      var redisInst = new CacheAllTheThings('redis');

      redisInst
        .get('lol')
        .then(function(val) {
          Assert(val, 'hai');
          done();
        }, function(e) {
          Assert.notEqual(e, null);
          done();
        });
    });

    it('Should get a key that does not exist', function(done) {
      var redisInst = new CacheAllTheThings('redis');

      redisInst
        .get('lols')
        .then(function(val) {
          Assert(true);
          done();
        }, function(e) {
          Assert.notEqual(e, null);
          done();
        });
    });

  });

});

var Assert            = require('assert');
var CacheAllTheThings = require('../');

describe('CacheAllTheThings', function() {

  it('When asked to boot a Redis instance, it should do so', function() {
    var inst = new CacheAllTheThings('redis');
    Assert(inst.name, 'RedisCache');
  });

  describe('Redis', function() {

    it('Should set a key', function(done) {
      var redisInst = new CacheAllTheThings('redis');

      redisInst
        .set('lol', 'hai')
        .then(function(e) {
          Assert.equal(e, null);
          done();
        });
    });

    it('Should get a key that exists', function(done) {
      var redisInst = new CacheAllTheThings('redis');

      redisInst
        .get('lol')
        .then(function(val) {
          Assert(val, 'hai');
          done();
        }, function(e) {
          Assert.notEqual(e, null);
          done();
        });
    });

    it('Should get a key that does not exist', function(done) {
      var redisInst = new CacheAllTheThings('redis');

      redisInst
        .get('lols')
        .then(function(val) {
          Assert(true);
          done();
        }, function(e) {
          Assert.notEqual(e, null);
          done();
        });
    });

    it('Should set an object, and get back an object', function(done) {
      var redisInst = new CacheAllTheThings('redis');

      redisInst
        .set('obj', {
          testing : true
        })
        .then(function() {

          redisInst
            .get('obj')
            .then(function(val) {
              Assert(val, {
                testing : true
              });
              done();
            }, function(e) {
              done();
            });

        });
    });

  });

});

Add test to get an object out back as an object