import expect from 'expect.js';

describe('<App />', () => {
  it('should render the application', () => {
     expect(1).to.be(1);
  });
});

import React from 'react';
import expect from 'expect.js';
import { shallow } from 'enzyme';

import App from '../../app/components/App';

describe('<App />', () => {
  it('should render the application', () => {
    const child =
Rendered from ./app/components/App.js!
;
    expect(shallow(<App />).contains(child)).to.be(true);
  });
});

Add basic test with shallow rendering


'use strict';

const tls = require('tls');
const Promise = require('bluebird');
const moment = require('moment');

const SITES = ['porybox.com', 'hq.porygon.co', 'modapps.porygon.co'];

module.exports = {
  period: 60 * 60 * 24,
  concurrent: true,
  task () {
    return Promise.reduce(SITES, (messages, host) => {
      return getSecureSocket(host).then(socket => {
        if (!socket.authorized) {
          return messages.concat(`Warning: The site ${host} has an invalid SSL certificate. SSL error: ${socket.authorizationError}`);
        }
        const expirationTime = moment.utc(socket.getPeerCertificate().valid_to, 'MMM D HH:mm:ss YYYY');
        if (expirationTime < moment().add({days: 14})) {
          return messages.concat(`Warning: The SSL certificate for ${host} expires ${expirationTime.fromNow()}`);
        }
        return messages;
      });
    }, []);
  }
};

function getSecureSocket(host) {
  return new Promise(resolve => {
    const socket = tls.connect({host, port: 443, rejectUnauthorized: false}, () => resolve(socket));
  });
}

'use strict';

const tls = require('tls');
const Promise = require('bluebird');
const moment = require('moment');

const SITES = ['porybox.com', 'hq.porygon.co', 'modapps.porygon.co'];

module.exports = {
  period: 60 * 60 * 24,
  concurrent: true,
  task () {
    return Promise.reduce(SITES, (messages, host) => {
      return getSecureSocket(host).then(socket => {
        if (!socket.authorized) {
          return messages.concat(`Warning: The site ${host} has an invalid SSL certificate. SSL error: ${socket.authorizationError}`);
        }
        const expirationTime = moment.utc(socket.getPeerCertificate().valid_to, 'MMM D HH:mm:ss YYYY');
        if (expirationTime < moment().add({days: 14})) {
          return messages.concat(`Warning: The SSL certificate for ${host} expires ${expirationTime.fromNow()}`);
        }
        return messages;
      });
    }, []);
  }
};

function getSecureSocket(host) {
  return new Promise(resolve => {
    const socket = tls.connect({host, servername: host, port: 443, rejectUnauthorized: false}, () => resolve(socket));
  });
}

Use SNI correctly for cert check


const number2ip = number => [
  (number & (0xff << 24)) >>> 24,
  (number & (0xff << 16)) >>> 16,
  (number & (0xff << 8)) >>> 8,
  number & 0xff
].join('.');

const isIP = (ip) => {
  const ipArray = (ip + '').split('.');
  return !(ipArray.length === 0 || ipArray.length > 4);
}

const ip2number = ip => {
  const ipArray = (ip + '').split('.');
  if (ipArray.length === 0 || ipArray.length > 4) {
    throw new Error('Invalid IP');
    return;
  } else {
    return ipArray
      .map((segment, i) => {
        if (isNaN(parseInt(segment, 10)) || segment < 0 || segment > 255) {
          throw new Error('One or more segments of IP-address is invalid');
          return;
        }
        return (segment || 0) << (8 * (3 - i));
      })
      .reduce((acc, cur) => acc | cur, 0) >>> 0;
  }
}

module.exports = {number2ip, ip2number, isIP};

const number2ip = number => [
  (number & (0xff << 24)) >>> 24,
  (number & (0xff << 16)) >>> 16,
  (number & (0xff << 8)) >>> 8,
  number & 0xff
].join('.');

const isIP = (ip) => {
  const ipArray = (ip + '').split('.');
  return !(ipArray.length === 0 || ipArray.length > 4);
}

const ip2number = ip => {
  const ipArray = (ip + '').split('.');
  if (ipArray.length === 0 || ipArray.length > 4) {
    throw new Error('Invalid IP');
    return;
  } else {
    return ipArray
      .map((segment, i) => {
        if (isNaN(+segment) || segment < 0 || segment > 255) {
          throw new Error('One or more segments of IP-address is invalid');
          return;
        }
        return (segment || 0) << (8 * (3 - i));
      })
      .reduce((acc, cur) => acc | cur, 0) >>> 0;
  }
}

module.exports = {number2ip, ip2number, isIP};

Fix bug with segment parsing


import React from 'react';
import { Link } from 'react-router';
import './style.css';

const MainLinks = () => (

Try Now

                npm i -g getstorybook

                cd my-react-app

                getstorybook

Read Docs

              <Link to="/docs/react-storybook/basics/introduction/">

              </Link>

);

export default MainLinks;

import React from 'react';
import { Link } from 'react-router';
import './style.css';

const MainLinks = () => (

Try Now

                npm i -g @storybook/cli

                cd my-react-app

                getstorybook

Read Docs

              <Link to="/docs/react-storybook/basics/introduction/">

              </Link>

);

export default MainLinks;

CHANGE install docs on homepage to new package name


module.exports = {
  entry: ['whatwg-fetch', './build/index.js'],
  output: {
    path: __dirname + '/build',
    filename: 'bundle.js',
    publicPath: './example/'
  },
  bail: true,
  devtool: 'cheap-source-map',
  mode: 'production',
  module: {
    rules: [
      { test: /\.css$/, use: ['style-loader', 'css-loader'] },
      { test: /\.html$/, use: 'file-loader' },
      { test: /\.md$/, use: 'raw-loader' },
      { test: /\.js.map$/, use: 'file-loader' },
      {
        test: /\.svg/,
        use: [
          { loader: 'svg-url-loader', options: {} },
          { loader: 'svgo-loader', options: { plugins: [] } }
        ]
      },
      {
        test: /\.(png|jpg|gif|ttf|woff|woff2|eot)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        use: [{ loader: 'url-loader', options: { limit: 10000 } }]
      }
    ]
  }
};

module.exports = {
  entry: ['whatwg-fetch', './build/index.js'],
  output: {
    path: __dirname + '/build',
    filename: 'bundle.js',
    publicPath: './example/'
  },
  bail: true,
  devtool: 'source-map',
  mode: 'production',
  module: {
    rules: [
      { test: /\.css$/, use: ['style-loader', 'css-loader'] },
      { test: /\.html$/, use: 'file-loader' },
      { test: /\.md$/, use: 'raw-loader' },
      { test: /\.js.map$/, use: 'file-loader' },
      {
        test: /\.svg/,
        use: [
          { loader: 'svg-url-loader', options: {} },
          { loader: 'svgo-loader', options: { plugins: [] } }
        ]
      },
      {
        test: /\.(png|jpg|gif|ttf|woff|woff2|eot)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        use: [{ loader: 'url-loader', options: { limit: 10000 } }]
      }
    ]
  }
};

Use better source map for filebrowser example.


var expect = require("expect.js");
var mocha = require("mocha");
var connectAssets = require("..");

describe("instance", function () {
  it("exposes the mincer environment", function () {
    var assets = connectAssets();

    expect(assets.environment).to.be.an("object");
  });

  it("allows you to call .bind()", function () {
    var assets = connectAssets();

    expect(assets.bind).to.be.a("function");
  });

  it("allows you to call .apply()", function () {
    var assets = connectAssets();

    expect(assets.apply).to.be.an("function");
  });
});

var expect = require("expect.js");
var mocha = require("mocha");
var connectAssets = require("..");

describe("instance", function () {
  it("exposes mincer to allow for registering engines", function () {
    expect(connectAssets.Mincer).to.be.an("object");
  });

  it("exposes the mincer environment", function () {
    var assets = connectAssets();

    expect(assets.environment).to.be.an("object");
  });

  it("allows you to call .bind()", function () {
    var assets = connectAssets();

    expect(assets.bind).to.be.a("function");
  });

  it("allows you to call .apply()", function () {
    var assets = connectAssets();

    expect(assets.apply).to.be.an("function");
  });
});

Add test to ensure mincer is exposed.


import {
  name as LIB_NAME,
  version as LIB_VERSION
} from '../package.json';

function pretty(arg) {
  let objs = [];
  let keys = [];

  return JSON.stringify(arg, (k, v) => {
    if (v instanceof Object) {
      let seen = objs.indexOf(v);

      if (seen === -1) {
        objs.push(v);
        keys.push(k || 'ROOT');

        return v;
      }

      return `${v} (ref to ${keys[seen]})`;
    }

    return v;
  }, 2);
}

function _output(arg) {
  console.log(String(arg));
}

const peek42 = {
  get [Symbol.toStringTag]() {
    return LIB_NAME;
  },
  version: LIB_VERSION,
  pretty,
  _output
};

export {
  pretty,
  _output
};
export default peek42;

import {
  name as LIB_NAME,
  version as LIB_VERSION
} from '../package.json';

function _output(arg) {
  console.log(String(arg));
}

function pretty(arg) {
  let objs = [];
  let keys = [];

  return JSON.stringify(arg, (k, v) => {
    if (v instanceof Object) {
      let seen = objs.indexOf(v);

      if (seen === -1) {
        objs.push(v);
        keys.push(k || 'ROOT');

        return v;
      }

      return `${v} (ref to ${keys[seen]})`;
    }

    return v;
  }, 2);
}

// TODO: Only works with apivis for now, develop general plugin model
function use(lib) {
  const {typeStr, descStr, membersStr, chainStr, apiStr} = lib;
  const {p} = peek42;

  p.type = (arg) => p(typeStr(arg));
  p.desc = (arg, k) => p(descStr(arg, k));
  p.members = (arg) => p(membersStr(arg));
  p.chain = (arg) => p(chainStr(arg));
  p.api = (arg) => p(apiStr(arg));

  return peek42;
}

const peek42 = {
  get [Symbol.toStringTag]() {
    return LIB_NAME;
  },
  version: LIB_VERSION,
  _output,
  pretty,
  use
};

export {
  _output,
  pretty,
  use
};
export default peek42;

Add function for creating plugins (only works with apivis for now)


import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {ROW, COL} from './constants';

const Style = (props) => {
  const ctx = props.ctx;
  const gutter = props.gutter / 2;
  const style = `
    .${ctx} .${ROW}:after, .${ctx} .${COL}:after{content:'';display:block;height:0;visibility:hidden;clear:both;}
    .${ctx} .${ROW}{margin-left:auto;margin-right:auto;width:100%;}
    .${ctx} .${COL}{box-sizing:border-box;-ms-box-sizing:border-box;-moz-box-sizing:border-box;width:100%;max-width:100%;float:left;min-height:1px;}
    .${ctx} .${COL}{padding-left:${gutter}px;padding-right:${gutter}px;}
    .${ctx} .${COL} .${ROW}{margin-left:-${gutter}px;margin-right:-${gutter}px;width:auto;}
  `;
  return <style dangerouslySetInnerHTML={{__html: style}}/>;
}

Style.propTypes = {
  ctx: PropTypes.string,
  gutter: PropTypes.number
}

export default Style;

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {ROW, COL} from './constants';

const Style = (props) => {
  const ctx = props.ctx;
  const gutter = props.gutter / 2;
  const style = `
    .${ctx} .${ROW}:after, .${ctx} .${COL}:after{content:'';display:block;height:0;visibility:hidden;clear:both;}
    .${ctx} .${ROW}{margin-left:auto;margin-right:auto;width:100%;}
    .${ctx} .${COL}{box-sizing:border-box;width:100%;max-width:100%;float:left;min-height:1px;}
    .${ctx} .${COL}{padding-left:${gutter}px;padding-right:${gutter}px;}
    .${ctx} .${COL} .${ROW}{margin-left:-${gutter}px;margin-right:-${gutter}px;width:auto;}
  `;
  return <style dangerouslySetInnerHTML={{__html: style}}/>;
}

Style.propTypes = {
  ctx: PropTypes.string,
  gutter: PropTypes.number
}

export default Style;

Remove browser prefixes for box-sizing


"use strict";

var defaultText = `# newtabnotes

Please enter *your* **Markdown** text;

- It is
- *easy*
- trust me!

-> Ligatures are supported as well!

1 != 2
`;

var editor;

(function() {
var editorId = "newtabnotes-editor";
editor = ace.edit(editorId);
editor.setTheme("ace/theme/github");
editor.getSession().setMode("ace/mode/markdown");
editor.setValue(defaultText);
editor.session.selection.clearSelection();
editor.getSession().setUseWrapMode(true);
editor.setShowPrintMargin(false);
})();

window.onresize = function() {
  editor.resize();
};

"use strict";

var defaultText = `# newtabnotes

Please enter *your* **Markdown** text;

- It is
- *easy*
- trust me!

-> Ligatures are supported as well!

1 != 2
`;

var editor;
var editorId = "newtabnotes-editor";
var editorData = {
  "content": "",
  "scheme": "github",
  "syntax": "markdown",
  "fontSize": "16"
};

(function() {
var editorId = "newtabnotes-editor";
editor = ace.edit(editorId);
editor.setTheme("ace/theme/github");
editor.getSession().setMode("ace/mode/markdown");
editor.setValue(defaultText);
editor.session.selection.clearSelection();
editor.getSession().setUseWrapMode(true);
editor.setShowPrintMargin(false);
})();

window.onresize = function() {
  editor.resize();
};

Add global editor id and default values


import moment from 'moment-timezone';
import AbstractFormat from './abstract';

export default class DateFormat extends AbstractFormat {
  format(dateValue, dateFormat, locale, timezone) {
    return this
      .moment(dateValue, locale, timezone)
      .format(dateFormat);
  }

  moment(dateValue, locale, timezone) {
    return moment(dateValue)
      .locale(locale || this._locale)
      .tz(timezone || this._timezone);
  }

  parse(dateValue, dateFormat, locale, timezone) {
    const result = moment.tz(
      dateValue,
      dateFormat,
      locale || this._locale,
      true,
      timezone || this._timezone
    );

    return result.isValid() === true ? result.toDate() : null;
  }
}

import moment from 'moment-timezone';
import AbstractFormat from './abstract';

export default class DateFormat extends AbstractFormat {
  format(dateValue, dateFormat, locale, timezone) {
    locale = locale || this._locale;
    timezone = timezone || this._timezone;

    return this
      .moment(dateValue, locale, timezone)
      .format(dateFormat);
  }

  moment(dateValue, locale, timezone) {
    locale = locale || this._locale;
    timezone = timezone || this._timezone;

    return moment(dateValue)
      .locale(locale)
      .tz(timezone);
  }

  parse(dateValue, dateFormat, locale, timezone) {
    locale = locale || this._locale;
    timezone = timezone || this._timezone;

    const result = moment.tz(
      dateValue,
      dateFormat,
      locale,
      true,
      timezone
    );

    return result.isValid() === true ? result.toDate() : null;
  }
}

Set locale/timezone correctly when in format


/*
---

script: Clone.js

description: Clones an element and inserts it back to parent again

license: Public domain (http://unlicense.org).

authors: Yaroslaff Fedin

requires:
  - LSD.Action

provides:
  - LSD.Action.Clone

...
*/

LSD.Action.Clone = LSD.Action.build({
  enable: function(target) {
    var widget = LSD.Module.DOM.find(target);
    if (widget == target) var element = target, parent = widget;
    else var element = widget.element, parent = widget.parentNode;
    var clone = this.document.layout.render(element, parent, 'clone');
    (clone.toElement ? clone.toElement() : clone).inject(target, 'after');
  }
});

/*
---

script: Clone.js

description: Clones an element and inserts it back to parent again

license: Public domain (http://unlicense.org).

authors: Yaroslaff Fedin

requires:
  - LSD.Action

provides:
  - LSD.Action.Clone

...
*/

LSD.Action.Clone = LSD.Action.build({
  enable: function(target) {
    var widget = LSD.Module.DOM.find(target);
    if (widget == target) var element = widget.element, parent = widget.parentNode;
    else var element = target, parent = widget;
    var clone = this.document.layout.render(element, parent, 'clone');
    (clone.toElement ? clone.toElement() : clone).inject(target, 'after');
  }
});

Fix a bug in clone action that switched the widgets with elements


/*
 * @license
 * Copyright (c) 2016 The IBM Research Emergent Solutions authors. All rights reserved.
 * This code may only be used under the MIT style license found at https://ibmresearch.github.io/LICENSE.txt
 * The complete set of authors may be found at https://ibmresearch.github.io/AUTHORS.txt
 * The complete set of contributors may be found at https://ibmresearch.github.io/CONTRIBUTORS.txt
 */

'use strict';

var path = require('path');
var assert = require('yeoman-assert');
var helpers = require('yeoman-test');

describe('generator-polymer-init-ibm-element:app', function() {
  before(function() {
    return helpers.run(path.join(__dirname, '../generators/app'))
      .withPrompts({someAnswer: true})
      .toPromise();
  });

  it('creates files', function() {
    assert.file([
      'dummyfile.txt'
    ]);
  });
});

/*
 * @license
 * Copyright (c) 2016 The IBM Research Emergent Solutions authors. All rights reserved.
 * This code may only be used under the MIT style license found at https://ibmresearch.github.io/LICENSE.txt
 * The complete set of authors may be found at https://ibmresearch.github.io/AUTHORS.txt
 * The complete set of contributors may be found at https://ibmresearch.github.io/CONTRIBUTORS.txt
 */

'use strict';

var path = require('path');

var assert = require('yeoman-assert');
var helpers = require('yeoman-test');

describe('generator-polymer-init-ibm-element:app', function() {
  before(function() {
    return helpers.run(path.join(__dirname, '../generators/app'))
      .withPrompts({
        name: 'ibm-element',
        description: 'IBM element description'
      })
      .toPromise();
  });

  it('creates files', function() {
    assert.file([
      'demo/index.html',
      'test/ibm-element.html',
      'test/index.html',
      '.gitignore',
      'ibm-element.html',
      'bower.json',
      'index.html',
      'LICENSE',
      'README.md'
    ]);
  });
});

Update the test with the expected files


/*jslint browser: true, undef: true *//*global Ext*/
Ext.define('Jarvus.ext.form.field.Search', {
    extend: 'Ext.form.field.Text',
    xtype: 'jarvus-searchfield',

    inputType: 'search',
    selectOnFocus: true,
    emptyText: 'Search…',

    initEvents: function() {
        var me = this;

        me.callParent();

        me.mon(me.inputEl, 'search', function(ev) {
            if (!me.getValue()) {
                me.fireEvent('clear', me, ev);
            }
        });
    }
});

/*jslint browser: true, undef: true *//*global Ext*/
Ext.define('Jarvus.ext.form.field.Search', {
    extend: 'Ext.form.field.Text',
    xtype: 'jarvus-searchfield',

    cls: 'jarvus-searchfield',

    inputType: 'search',
    selectOnFocus: true,
    emptyText: 'Search…',

    initEvents: function() {
        var me = this;

        me.callParent();

        me.mon(me.inputEl, 'search', function(ev) {
            if (!me.getValue()) {
                me.fireEvent('clear', me, ev);
            }
        });
    }
});

Add an html class to jarvus-ext-searchfield


'use strict';
/* eslint-env mocha */
const assert = require('assert');
const execFile = require('child_process').execFile;
const path = require('path');
const ejslint = path.resolve('cli.js');

suite('cli', () => {
  test('valid input', done => {
    execFile(ejslint, ['test/fixtures/valid.ejs'], (err, stdout, stderr) => {
      assert.ifError(err);
      assert.ifError(stderr);
      done();
    });
  });
  test('invalid input', done => {
    execFile(ejslint, ['test/fixtures/invalid.ejs'], (err, stdout, stderr) => {
      assert.equal(err.code, 1, 'expected exit code of 1');
      assert.equal(stderr.trim(), 'Unexpected token (3:4) in test/fixtures/invalid.ejs');
      done();
    });
  });
});

'use strict';
/* eslint-env mocha */
const assert = require('assert');
const execFile = require('child_process').execFile;
const path = require('path');
const ejslint = path.resolve('cli.js');

suite('cli', () => {
  test('valid input', done => {
    execFile(ejslint, ['test/fixtures/valid.ejs'], (err, stdout, stderr) => {
      assert.ifError(err);
      assert(!stderr);
      done();
    });
  });
  test('invalid input', done => {
    execFile(ejslint, ['test/fixtures/invalid.ejs'], (err, stdout, stderr) => {
      assert.equal(err.code, 1, 'expected exit code of 1');
      assert.equal(stderr.trim(), 'Unexpected token (3:4) in test/fixtures/invalid.ejs');
      done();
    });
  });
});

Fix tests on Node 10


(function($){
  $(function(){

  	$('.tooltipped').tooltip({delay: 50});

  	// Smooth scroll for links on the current page
    $(document.body).on('click', 'a[href^="#"]', function (e) {
		e.preventDefault();
		if (location.pathname.replace(/^\//, '') == this.pathname.replace(/^\//, '') || location.hostname == this.hostname) {

			var target = $(this.hash);
			target = target.length ? target : $('[name=' + this.hash.slice(1) + ']');
			if (target.length) {
				var offset = 0;
				if (target[0].id.indexOf("feature-") == 0) {
					offset = 100;
				}
				$('html,body').animate({
					scrollTop: target.offset().top - offset
				}, 1000);
				return false;
			}
		}
	});

    // Allow toasts to be dismissed with a click
	$(document).on('click', '#toast-container .toast', function() {
	    $(this).fadeOut(function(){
	        $(this).remove();
	    });
	});

  });
})(jQuery);

(function($){
  $(function(){

  	$('.tooltipped').tooltip({delay: 50});

  	// Smooth scroll for links on the current page
    $(document.body).on('click', 'a[href^="#"]', function (e) {
		e.preventDefault();
		if (location.pathname.replace(/^\//, '') == this.pathname.replace(/^\//, '') || location.hostname == this.hostname) {

			var target = $(this.hash);
			target = target.length ? target : $('[name=' + this.hash.slice(1) + ']');
			if (target.length) {
				var offset = 0;
				if (target[0].id.indexOf("feature-") == 0) {
					offset = 100;
				}
				$('html,body').animate({
					scrollTop: target.offset().top - offset
				}, 1000);
				return false;
			}
		}
	});

    // Allow toasts to be dismissed with a click
	$(document).on('click', '#toast-container .toast', function() {
	    $(this).fadeOut(function(){
	        $(this).remove();
	    });
	});

	// Setup all ajax queries to use the CSRF token
	$.ajaxSetup({
	    headers: {
	        'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
	    }
	});
  });
})(jQuery);

Add our meta tag CSRF token to every ajax request


'use babel'

import Path from 'path'
import Less from 'less'

export const minifier = false
export const compiler = true
export function process(contents, {rootDirectory, filePath, config, state}) {
  return Less.render(contents, Object.assign({}, config.less, {
    filename: Path.relative(rootDirectory, filePath),
    sourceMap: true,
    paths: [Path.dirname(filePath)]
  })).then(function(output) {
    output.imports.forEach(function(item) {
      state.imports.push(Path.join(rootDirectory, item))
    })
    return {
      contents: output.css,
      sourceMap: JSON.parse(output.map)
    }
  })
}

'use babel'

import Path from 'path'
import Less from 'less'

export const minifier = false
export const compiler = true
export function process(contents, {rootDirectory, filePath, config, state}) {
  return Less.render(contents, Object.assign({}, config.less, {
    filename: Path.relative(rootDirectory, filePath),
    sourceMap: true,
    paths: [Path.dirname(filePath)]
  })).then(function(output) {
    if (!output.map) {
      return null
    }

    output.imports.forEach(function(item) {
      state.imports.push(Path.join(rootDirectory, item))
    })
    return {
      contents: output.css,
      sourceMap: JSON.parse(output.map)
    }
  })
}

Fix a bug when empty files would trigger an error


import childProcess from 'child_process'
import fs from 'fs'

import promisify from 'promisify-function'

const mkdir = promisify(fs.mkdir)
const readFile = promisify(fs.readFile)

function exec (command, options) {
  return new Promise(function (resolve, reject) {
    childProcess.exec(command, options, function (err, stdout, stderr) {
      if (err) {
        reject(err)
      } else {
        resolve({command, stdout, stderr})
      }
    })
  })
}

function exists (path) {
  return new Promise(function (resolve, reject) {
    fs.exists(path, function (exists) {
      if (exists) {
        resolve()
      } else {
        reject()
      }
    })
  })
}

export { exec, exists, mkdir, readFile }

import childProcess from 'child_process'
import fs from 'fs'

import promisify from 'promisify-function'

const mkdir = promisify(fs.mkdir)
const readFile = promisify(fs.readFile)

function exec (command, options) {
  return new Promise(function (resolve, reject) {
    childProcess.exec(command, options, function (err, stdout, stderr) {
      if (err) {
        reject(new Error(err))
      } else {
        resolve({command, stdout, stderr})
      }
    })
  })
}

function exists (path) {
  return new Promise(function (resolve, reject) {
    fs.exists(path, function (exists) {
      if (exists) {
        resolve()
      } else {
        reject(new Error())
      }
    })
  })
}

export { exec, exists, mkdir, readFile }

Use Error constructor in rejection callbacks


var eslint = require('eslint')
var path = require('path')
var pkg = require('./package.json')

module.exports = {
  bugs: pkg.bugs.url,
  cmd: 'standard',
  eslint: eslint,
  eslintConfig: {
    configFile: path.join(__dirname, 'eslintrc.json')
  },
  formatter: '`standard-format` is no longer included with `standard`. Try using --fix instead. Or, install `standard-format` separately: "npm install -g standard-format"',
  homepage: pkg.homepage,
  tagline: 'Use JavaScript Standard Style',
  version: pkg.version
}

var eslint = require('eslint')
var path = require('path')
var pkg = require('./package.json')

module.exports = {
  bugs: pkg.bugs.url,
  cmd: 'standard',
  eslint: eslint,
  eslintConfig: {
    configFile: path.join(__dirname, 'eslintrc.json')
  },
  homepage: pkg.homepage,
  tagline: 'Use JavaScript Standard Style',
  version: pkg.version
}

Remove deprecated 'formatter' option (for standard-engine@5)


var TwinklingStar = new Class({
    Extends: Star,
    initialize: function(options) {
        this.parent(options);
        if (this.brightness < 4) this.brightness = 4;
        this.color = "#eeeeee";
        this.stepSeed = Math.random() * Math.PI;
    },
    draw: function() {
        var x = cx(this.pos.x);
        var y = cy(this.pos.y);
        var brightness = this.brightness;
        brightness += Math.sin(FRAME * 0.3 + this.stepSeed);

        context.save();
        context.fillStyle = this.color;
        context.beginPath();
        context.moveTo(x - brightness, y);
        context.quadraticCurveTo(x, y, x, y - brightness);
        context.quadraticCurveTo(x, y, x + brightness, y);
        context.quadraticCurveTo(x, y, x, y + brightness);
        context.quadraticCurveTo(x, y, x - brightness, y);
        context.fill();
        context.closePath();
        context.restore();
        this.update();
    },
});

function makeTwinkles(locations) {
    var stars = [];
    var i = 0;

    stars = locations.map(function (coords) {
        return new TwinklingStar({
            x: coords[0],
            y: coords[1],
            brightness: Math.random() * 3 + 4,
        });
    });

    return {
        draw: function () {
            Array.each(stars, function(star) {
                star.draw();
            });
        },
        locations: locations,
    };
}

var TwinklingStar = new Class({
    Extends: Star,
    initialize: function(options) {
        this.parent(options);
        if (this.brightness < 4) this.brightness = 4;
        this.color = "#eeeeee";
        this.stepSeed = Math.random() * Math.PI;
        this.stepScale = Math.random() / 3;
    },
    draw: function() {
        var x = cx(this.pos.x);
        var y = cy(this.pos.y);
        var brightness = this.brightness;
        brightness += Math.sin(FRAME * this.stepScale + this.stepSeed);

        context.save();
        context.fillStyle = this.color;
        context.beginPath();
        context.moveTo(x - brightness, y);
        context.quadraticCurveTo(x, y, x, y - brightness);
        context.quadraticCurveTo(x, y, x + brightness, y);
        context.quadraticCurveTo(x, y, x, y + brightness);
        context.quadraticCurveTo(x, y, x - brightness, y);
        context.fill();
        context.closePath();
        context.restore();
        this.update();
    },
});

function makeTwinkles(locations) {
    var stars = [];
    var i = 0;

    stars = locations.map(function (coords) {
        return new TwinklingStar({
            x: coords[0],
            y: coords[1],
            brightness: Math.random() * 3 + 4,
        });
    });

    return {
        draw: function () {
            Array.each(stars, function(star) {
                star.draw();
            });
        },
        locations: locations,
    };
}

Put a bunch of stars together, and you can see that they twinkle at the same rate.


var d = new Discovery();
var controller;
$("button#discovery").bind("click", function(e){
  d.start(function(data) {
      $("output .progress").append(data.host).append("
");
      controller = new NetiaController(data.host, data.port);
      fillActions($("output .controller"), controller);
  });
})

$("button#custom_controller").bind("click", function(e){
    var host = $("input#host").val();
    var port = $("input#port").val();
    controller = new NetiaController(host, port);
    $("output .progress").append(host + ":" + port).append("
");
    fillActions($("output .controller"), controller);
})

var fillActions = function(div, controller) {
    var that = this;
    div.empty();
    document.getElementsByClassName("controller")[0].onclick = function(e) {
        that.controller[e.srcElement.getAttribute('id')]();
    };
    for (var action in controller) {
        if (action.indexOf("sendKey") == 0 ) {
            div.append('<button id="' + action + '">' + action + '</button>');
            //TODO: why this is not working ??? ask guru, hm ... grze do you know ?
            //div.find("button#"+action).bind("click", function(e) { controller[action](); })
        }
    }
}

var d = new Discovery();
var controller;
$("button#discovery").bind("click", function(e){
  d.start(function(data) {
      $("output .progress").append(data.host).append("
");
      controller = new NetiaController(data.host, data.port);
      fillActions($("output .controller"), controller);
  });
})

$("button#custom_controller").bind("click", function(e){
    var host = $("input#host").val();
    var port = $("input#port").val();
    controller = new NetiaController(host, port);
    $("output .progress").append(host + ":" + port).append("
");
    fillActions($("output .controller"), controller);
})

var fillActions = function(div, controller) {
    var that = this;
    div.empty();
    for (var action in controller) {
        if (action.indexOf("sendKey") == 0 ) {
            div.append('<button id="' + action + '">' + action + '</button>');
            div.find("button#"+action).bind("click", getActionClickHandler(action))
        }
    }
}

var getActionClickHandler = function (action) {
  return function () { controller[action]() }
}

Create a closure for click handlers in a loop


/**
 * Depending on if null & undefined are allowed they are returned about 10% of the time
 *
 * @param  {any}    value          The value we use if it's not null or undefined
 * @param  {bool} canBeNull        If the returned value can be null
 * @param  {bool} canBeUndefined   If the returned value can be undefined
 *
 * @return {any}    Either the initial value or, in 10% of the cases, null or undefined
 */
const valueOrNullOrUndefined = (value, canBeNull, canBeUndefined) => {
  const random = Math.random();
  if (canBeNull && random < 0.05) {
    return null;
  } else if (canBeUndefined && random > 0.95) {
    return undefined;
  }

  return value;
};

export default valueOrNullOrUndefined;

/**
 * Depending on if null & undefined are allowed they are returned about 10% of the time
 *
 * @param  {any}    value          The value we use if it's not null or undefined
 * @param  {bool} canBeNull        If the returned value can be null
 * @param  {bool} canBeUndefined   If the returned value can be undefined
 *
 * @return {any}    Either the initial value or, in 10% of the cases, null or undefined
 */
const valueOrNullOrUndefined = (value, canBeNull, canBeUndefined) => {
  const random = Math.random();
  if (canBeNull && random < 0.025) {
    return null;
  } else if (canBeUndefined && random > 0.975) {
    return undefined;
  }

  return value;
};

export default valueOrNullOrUndefined;

Reduce the amount of null and undefineds


import Ember from 'ember';

import countries from './countries';

export default Ember.Component.extend({
    i18n: Ember.inject.service(),
    countries: countries,
    languages: Ember.computed('countries', function() {
      var langs = [];
      this.get('countries').forEach((country) => {
        country.languages.forEach((lang) => {
          langs.push({
            countryCode: country.code,
            countryName: country.name,
            name: lang.name,
            code: lang.code
          });
        });
      });
      langs.sort(function(a, b) {
        if (a.countryName > b.countryName) {
          return 1;
        }
        if (a.countryName < b.countryName) {
          return -1;
        }
        return 0;
      });
      return langs;
    }),
    onPick: null,
    country: null,
    actions: {
      pickLanguage(language, code) {
        this.get('onPick')(language, code);
        this.set('i18n.locale', code);
      }
    }
});

import Ember from 'ember';
import config from 'ember-get-config';
import countries from './countries';

export default Ember.Component.extend({
    i18n: Ember.inject.service(),
    countries: countries,
    languages: Ember.computed('countries', 'i18n.locales', function () {
        const isProduction = config.environment === 'production';
        const locales = this.get('i18n.locales').toArray();
        const countries = this.get('countries');
        const languages = [];

        for (const locale of locales) {
            if (isProduction && locale === 'test') {
                continue;
            }

            const rgx = new RegExp(`^${locale}`);

            for (const country of countries) {
                for (const language of country.languages) {
                    if (rgx.test(language.code)) {
                        languages.push({
                            countryCode: country.code,
                            countryName: country.name,
                            name: language.name,
                            code: language.code
                        });

                        break;
                    }
                }
            }
        }

        languages.sort((a, b) => {
            if (a.countryName > b.countryName) {
                return 1;
            }

            if (a.countryName < b.countryName) {
                return -1;
            }

            return 0;
        });

        return languages;
    }),
    onPick: null,
    country: null,
    actions: {
        pickLanguage(language, code) {
            this.get('onPick')(language, code);
            this.set('i18n.locale', code);
        }
    }
});

Remove pseudo translation when in production, allow only languages with available translations in the language picker


function ProgressionController() {
    var counter;
    var request;
    var canvas;

    this.draw = function() {
      var ctx = canvas.getContext('2d');

      document.getElementById('counter').innerHTML = Math.floor(counter);

      ctx.clearRect(0, 0, canvas.width, canvas.height);

      ctx.beginPath();
      ctx.lineWidth = "1";
      ctx.strokeStyle = "black";
      ctx.moveTo(0,0);
      var y = 200;
      var w = (canvas.width / counter);
      for (var i = 1; i <= counter; i++) {
          ctx.lineTo(w * i, y);
          y = (y == 200 ? 0 : 200);
      }
      ctx.lineTo(canvas.width,0);
      ctx.stroke();

      counter += 0.5;
    };

    this.start = function(c) {
      canvas = c;
      counter = 1;
      this.draw();
      var $this = this;
      var animFrame = function(time) {
          $this.draw();
          request = requestAnimationFrame(animFrame);
      };
      request = requestAnimationFrame(animFrame);
    };
}

function ProgressionController() {
    var counter;
    var request;
    var canvas;

    this.draw = function(timeElapsed) {
      var ctx = canvas.getContext('2d');

      var counter_elem = document.getElementById('counter');
      if (counter_elem) {
          counter_elem.innerHTML = Math.floor(counter);
              // + "<br/>" + timeElapsed;
      }

      ctx.clearRect(0, 0, canvas.width, canvas.height);

      ctx.beginPath();
      ctx.lineWidth = "1";
      ctx.strokeStyle = "black";
      ctx.moveTo(0,0);
      var y = 200;
      var w = (canvas.width / counter);
      for (var i = 1; i <= counter; i++) {
          ctx.lineTo(w * i, y);
          y = (y == 200 ? 0 : 200);
      }
      ctx.lineTo(canvas.width,0);
      ctx.stroke();

      counter += timeElapsed / 45.0;
    };

    this.start = function(c) {
      canvas = c;
      counter = 1;
      var $this = this;
      var lastTime = null;
      var animFrame = function(time) {
          var timeElapsed = lastTime == null ? 0 : time - lastTime;
          lastTime = time;
          $this.draw(timeElapsed);
          requestAnimationFrame(animFrame);
      };
      requestAnimationFrame(animFrame);
    };
}

Use time parameter in requestAnimationFrame.


export async function up(knex) {
  await knex.raw(`CREATE EXTENSION IF NOT EXISTS pg_trgm WITH SCHEMA public`);
  await knex.raw(
    `create index posts_body_trgm_idx
      on posts
      using gist(body gist_trgm_ops);`
  );
  await knex.raw(
    `create index comments_body_trgm_idx
      on comments
      using gist(body gist_trgm_ops);`
  );
}

export async function down(knex) {
  await knex.raw(`drop index comments_body_trgm_idx;`);
  await knex.raw(`drop index posts_body_trgm_idx;`);
}

export async function up(knex) {
  await knex.raw(`create extension pg_trgm;`);
  await knex.raw(
    `create index posts_body_trgm_idx
      on posts
      using gist(body gist_trgm_ops);`
  );
  await knex.raw(
    `create index comments_body_trgm_idx
      on comments
      using gist(body gist_trgm_ops);`
  );
}

export async function down(knex) {
  await knex.raw(`drop index comments_body_trgm_idx;`);
  await knex.raw(`drop index posts_body_trgm_idx;`);
  await knex.raw(`drop extension pg_trgm;`);
}

Revert "allow for oob enabling of extension"


import cc from 'classcat'
import React from 'react'
import PropTypes from 'prop-types'
import Mute from 'lib/components/Mute'
import './style'

const valueClasses = value => cc([
  'Stats__value',
  { 'Stats__value--empty': !value }
])

const Stats = ({ items, placeholder }) => (

    {items.map(({ title, value }, index) => (

{title}

{value || placeholder}

    ))}

)

Stats.propTypes = {
  items: PropTypes.arrayOf(PropTypes.shape({
    title: PropTypes.string,
    value: PropTypes.any
  })),
  placeholder: PropTypes.oneOfType([PropTypes.string, PropTypes.node])
}

Stats.defaultProps = {
  items: [],
  placeholder: <Mute>–</Mute>
}

export default Stats

import cc from 'classcat'
import React from 'react'
import PropTypes from 'prop-types'
import Mute from 'lib/components/Mute'
import './style'

const valueClasses = value => cc([
  'Stats__value',
  { 'Stats__value--empty': !value }
])

const Stats = ({ className, items, placeholder }) => (

    {items.map(({ title, value }, index) => (

{title}

{value || placeholder}

    ))}

)

Stats.propTypes = {
  className: PropTypes.string,
  items: PropTypes.arrayOf(PropTypes.shape({
    title: PropTypes.string,
    value: PropTypes.any
  })),
  placeholder: PropTypes.oneOfType([PropTypes.string, PropTypes.node])
}

Stats.defaultProps = {
  className: undefined,
  items: [],
  placeholder: <Mute>–</Mute>
}

export default Stats

Add className prop to Stats


import { Alert, Icon } from 'react-components';
import { c } from 'ttag';
import React from 'react';
import calendarSvg from 'design-system/assets/img/pm-images/calendar.svg';

const CalendarReady = () => {
    const supportIcon = <Icon key="support-icon" name="support1" />;
    return (
        <>
            <Alert>{c('Info')
                .t`Your new calendar is now ready. All events in your calendar are encrypted and inaccessible to anybody other than you.`}</Alert>

            <Alert>{c('Info')
                .jt`If you encounter a problem, you can reach our support team by clicking the ${supportIcon} button.`}</Alert>
        </>
    );
};

export default CalendarReady;

import { Alert, Icon } from 'react-components';
import { c } from 'ttag';
import React from 'react';
import calendarSvg from 'design-system/assets/img/pm-images/calendar.svg';

const CalendarReady = () => {
    const supportIcon = <Icon key="support-icon" name="support1" className="alignsub" />;
    return (
        <>
            <Alert>{c('Info')
                .t`Your new calendar is now ready. All events in your calendar are encrypted and inaccessible to anybody other than you.`}</Alert>

            <Alert>{c('Info')
                .jt`If you encounter a problem, you can reach our support team by clicking the ${supportIcon} button.`}</Alert>
        </>
    );
};

export default CalendarReady;

Fix - icon alignment in onboarding modal (calendar ready)


(function(Perseus) {

var InputInteger = Perseus.Widget.extend({
    initialize: function() {
        this.$input = $("<input>");
    },

    render: function() {
        this.$el.empty();
        this.$el.append(this.$input);
        return $.when(this);
    },

    focus: function() {
        this.$input.focus();
    },

    toJSON: function() {
        return {
            value: this.$input.val()
        };
    },

    set: function(options) {
        this.$input.val(options.value);
    },

    simpleValidate: function(rubric) {
        return InputInteger.validate(this.toJSON(), rubric);
    }
});

_.extend(InputInteger, {
    validate: function(state, rubric) {
        // For now, rubric is just like {value: "17"}
        // TODO(alpert): Smarter checking

        if (state.value === "") {
            return {
                type: "invalid",
                message: null
            };
        } else if (state.value === rubric.value) {
            return {
                type: "points",
                earned: 1,
                total: 1,
                message: null
            };
        } else {
            return {
                type: "points",
                earned: 0,
                total: 1,
                message: "Consider writing in the right answer, silly."
            };
        }
    }
});

Perseus.Widgets.register("input-integer", InputInteger);

})(Perseus);

(function(Perseus) {

var InputInteger = Perseus.Widget.extend({
    initialize: function() {
        this.$input = $("<input type='number'>");
    },

    render: function() {
        this.$el.empty();
        this.$el.append(this.$input);
        return $.when(this);
    },

    focus: function() {
        this.$input.focus();
    },

    toJSON: function() {
        return {
            value: this.$input.val()
        };
    },

    set: function(options) {
        this.$input.val(options.value);
    },

    simpleValidate: function(rubric) {
        return InputInteger.validate(this.toJSON(), rubric);
    }
});

_.extend(InputInteger, {
    validate: function(state, rubric) {
        // For now, rubric is just like {value: "17"}
        // TODO(alpert): Smarter checking

        if (state.value === "") {
            return {
                type: "invalid",
                message: null
            };
        } else if (state.value === rubric.value) {
            return {
                type: "points",
                earned: 1,
                total: 1,
                message: null
            };
        } else {
            return {
                type: "points",
                earned: 0,
                total: 1,
                message: "Consider writing in the right answer, silly."
            };
        }
    }
});

Perseus.Widgets.register("input-integer", InputInteger);

})(Perseus);

Use 'number' input to use the width: 80px CSS rule


/**
 * Create Controller
 */

angular
    .module('RDash')
    .controller('CreateCtrl', ['$scope', '$http', CreateCtrl]);

function CreateCtrl($scope, $http) {
    $scope.sendPost = function() {
        var payload = {
            meta: {
                project: $scope.project,
                module: $scope.module,
                name: $scope.name
            },
            unikernel: $scope.unikernel,
            config: $scope.config,
            backend: $scope.backend
        };

        $http.post("http://localhost:5000/api/unikernel/create", payload).success(function(data, status) {
            alert(JSON.stringify(data));
        }).error(function(data, status) {
            alert(JSON.stringify(data));
        });
    };
}

/**
 * Create Controller
 */

angular
    .module('RDash')
    .controller('CreateCtrl', ['$scope', '$http', CreateCtrl]);

function CreateCtrl($scope, $http) {
    $scope.sendPost = function() {
        var payload = {
            meta: {
                project: $scope.project,
                module: $scope.module,
                name: $scope.name
            },
            unikernel: $scope.unikernel,
            config: $scope.config,
            backend: $scope.backend
        };

        $http.post("http://localhost:5000/api/unikernel/create", payload).success(function(data, status) {
            $scope.addAlert('success', '[code ' + data.code + '] [ID ' + data._id + '] ' + data.message);
        }).error(function(data, status) {
            $scope.addAlert('danger', '[code ' + data.code + '] ' + data.message);
        });
    };
}

Integrate AlertsCtrl with CreateCtrl controller


/*
 * grunt-flush-redis
 * https://github.com/yaymukund/grunt-flush-redis
 *
 * Copyright (c) 2012 Mukund Lakshman
 * Licensed under the MIT license.
 */

var redis = require('redis');

module.exports = function(grunt) {

  // Please see the grunt documentation for more information regarding task and
  // helper creation: https://github.com/cowboy/grunt/blob/master/docs/toc.md

  // ==========================================================================
  // TASKS
  // ==========================================================================

  grunt.registerTask('flushredis', 'Flush a redis database.', function() {
    grunt.log.write('Flushing the redis database');
    redis.createClient().flushdb();
  });
};

/*
 * grunt-flush-redis
 * https://github.com/yaymukund/grunt-flush-redis
 *
 * Copyright (c) 2012 Mukund Lakshman
 * Licensed under the MIT license.
 */

var redis = require('redis');

module.exports = function(grunt) {

  // Please see the grunt documentation for more information regarding task and
  // helper creation: https://github.com/cowboy/grunt/blob/master/docs/toc.md

  // ==========================================================================
  // TASKS
  // ==========================================================================

  grunt.registerInitTask('flushredis', 'Flush a redis database.', function() {
    grunt.log.write('Flushing the redis database');
    redis.createClient().flushdb();
  });
};

Make this an init task instead, since it doesn't require any arguments


const fs = require('fs')

const { FsUtils } = require('../../shared/utils')

class AlterationLoggerService {
  get dataPath() {
    return `${this.configService.configFolderPath}/alterations`
  }

  constructor(configService) {
    this.configService = configService

    if (!FsUtils.fileExist(this.dataPath)) {
      fs.mkdirSync(this.dataPath)
    }
  }
}

module.exports = exports = { AlterationLoggerService }

const fs = require('fs')

const { FsUtils } = require('../../shared/utils')

class AlterationLoggerService {
  get dataPath() {
    return `${this.configService.configFolderPath}/alterations`
  }

  constructor(configService) {
    this.configService = configService

    if (!FsUtils.fileExist(this.dataPath)) {
      fs.mkdirSync(this.dataPath)
    }
  }

  alterationFilePath(moduleName) {
    return `${this.dataPath}/${moduleName}.json`
  }

  resetAlterations(moduleName) {
    if (!FsUtils.fileExist(this.alterationFilePath(moduleName))) {
      return
    }

    fs.unlinkSync(this.alterationFilePath(moduleName))
  }

  addAlteration(moduleName, path, type) {
    fs.writeFileSync(this.alterationFilePath(moduleName), JSON.stringify({}, null, 4))
  }
}

module.exports = exports = { AlterationLoggerService }

Add file / remove file


Template.registerHelper("layoutHidden", function (type) {
  return (!!Session.get('fullApi')) ^ (type === 'full') ? 'hidden' : '';
});

Template.basicOrFullSelect.events({
  "change .basic-or-full": function (event) {
    // XXX might not work in IE9?
    navigate("#/" + event.target.value + "/");
  }
});

Template.basicOrFullSelect.helpers({
  isBasic: function () {
    return ! Session.get("fullApi");
  },
  isFull: function () {
    return Session.get("fullApi");
  }
});

Template.sidebar.helpers({
  topLink: function () {
    var docsType = Session.get("fullApi") ? "full" : "basic";
    return "#/" + docsType + "/";
  }
});

Template.registerHelper("layoutHidden", function (type) {
  return (!!Session.get('fullApi')) ^ (type === 'full') ? 'hidden' : '';
});

Template.basicOrFullSelect.events({
  "change .basic-or-full": function (event) {
    // XXX might not work in IE9?
    window.location.hash = "#/" + event.target.value + "/";
  }
});

Template.basicOrFullSelect.helpers({
  isBasic: function () {
    return ! Session.get("fullApi");
  },
  isFull: function () {
    return Session.get("fullApi");
  }
});

Template.sidebar.helpers({
  topLink: function () {
    var docsType = Session.get("fullApi") ? "full" : "basic";
    return "#/" + docsType + "/";
  }
});

Make back button work when switching from basic to full via dropdown


angular.module("realEstProperties", ["ngRoute","realEstProperties.controllers", "realEstProperties.services"])
.config(function($routeProvider) {
  $routeProvider
    .when("/", {
      templateUrl : "views/home.html",
      controller  : "mainCtrl"
    });
});

angular.module("realEstProperties", ["ngRoute","realEstProperties.controllers", "realEstProperties.services"])
.config(function($routeProvider) {
  $routeProvider
    .when("/", {
      templateUrl : "views/home.html",
      controller  : "mainCtrl"
    })
    .otherwise({
      redirectTo: "/"
    });
});

Add redirect to root route if unavailable route is entered


describe('traffic', function() {
  beforeEach(function() {
    window.matrix.settings = {
      profileId: ''
    };
    subject =  window.matrix.traffic;
  });
  it('has initial points', function() {
    expect(subject.points).to.eq(720);
  });
  it('has empty counts', function() {
    expect(subject.counts).to.eql([]);
  });
  it('has interval of 2 minutes', function() {
    expect(subject.interval).to.eq(120000);
  });
  describe('#endpoint', function() {
    it('returns the path to the servers realtime endpoint', function() {
      expect(subject.endpoint()).to.eql('/realtime?ids=ga:&metrics=rt:activeUsers&max-results=10');
    });
    context('with profileId', function() {
      beforeEach(function() {
        window.matrix.settings = {
          profileId: 'Test'
        };
      });
      it('returns correct profile Id in the endpoint path', function() {
        expect(subject.endpoint()).to.eql('/realtime?ids=ga:Test&metrics=rt:activeUsers&max-results=10');
      });
    });
  });
});

describe('traffic', function() {
  beforeEach(function() {
    window.matrix.settings = {
      profileId: ''
    };
    subject =  window.matrix.traffic;
  });
  it('has initial points', function() {
    expect(subject.points).to.eq(720);
  });
  it('has empty counts', function() {
    expect(subject.counts).to.eql([]);
  });
  it('has interval of 2 minutes', function() {
    expect(subject.interval).to.eq(120000);
  });
  describe('#endpoint', function() {
    it('returns the path to the servers realtime endpoint', function() {
      expect(subject.endpoint()).to.eql('/realtime?ids=ga:&metrics=rt:activeUsers&max-results=10');
    });
    context('with profileId', function() {
      beforeEach(function() {
        window.matrix.settings = {
          profileId: 'Test'
        };
      });
      it('returns correct profile Id in the endpoint path', function() {
        expect(subject.endpoint()).to.eql('/realtime?ids=ga:Test&metrics=rt:activeUsers&max-results=10');
      });
    });
  });
  describe('#historic', function() {
    it('returns the path to the servers historic endpoint', function() {
      expect(subject.historic()).to.eql('/historic?ids=ga:&dimensions=ga%3AnthMinute&metrics=ga%3Asessions&start-date=2015-08-03&end-date=2015-08-04&max-results=1000');
    });
  });
});

Add test for the historic endpoint


import { createStore, applyMiddleware, compose } from 'redux';
import { syncHistoryWithStore, routerMiddleware } from 'react-router-redux';
import { browserHistory } from 'react-router';
import promiseMiddleware from 'redux-promise-middleware';
import thunkMiddleware from 'redux-thunk';

import reducer from './reducer';
import featureFiltersStorage from './filters/featureFiltersStorage';
import statsDashboardFiltersStorage from './filters/statsDashboardFiltersStorage';
import { default as createStorageMiddleware } from './browserStorage/createMiddleware';

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

const middlewares = [
  createStorageMiddleware(featureFiltersStorage, state => state.featureFilters),
  createStorageMiddleware(statsDashboardFiltersStorage, state => state.statsDashboardFilters),
  routerMiddleware(browserHistory),
  thunkMiddleware,
  promiseMiddleware(),
];

const initialState = {};

const store = createStore(reducer, initialState, composeEnhancers(applyMiddleware(...middlewares)));

const history = syncHistoryWithStore(browserHistory, store);

export default store;
export { history };

import { createStore, applyMiddleware, compose } from 'redux';
import { syncHistoryWithStore, routerMiddleware } from 'react-router-redux';
import { browserHistory } from 'react-router';
import promiseMiddleware from 'redux-promise-middleware';
import thunkMiddleware from 'redux-thunk';

import reducer from './reducer';
import featureFiltersStorage from './filters/featureFiltersStorage';
import statsDashboardFiltersStorage from './filters/statsDashboardFiltersStorage';
import historyFiltersStorage from './filters/historyFiltersStorage';
import { default as createStorageMiddleware } from './browserStorage/createMiddleware';

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

const middlewares = [
  createStorageMiddleware(featureFiltersStorage, state => state.featureFilters),
  createStorageMiddleware(statsDashboardFiltersStorage, state => state.statsDashboardFilters),
  createStorageMiddleware(historyFiltersStorage, state => state.historyFilters),
  routerMiddleware(browserHistory),
  thunkMiddleware,
  promiseMiddleware(),
];

const initialState = {};

const store = createStore(reducer, initialState, composeEnhancers(applyMiddleware(...middlewares)));

const history = syncHistoryWithStore(browserHistory, store);

export default store;
export { history };

Update storage on history filter change


(function (module) {
    module.controller('ProjectHomeController', ProjectHomeController);
    ProjectHomeController.$inject = ["project", "mcmodal", "templates", "$state", "Restangular"];

    function ProjectHomeController(project, mcmodal, templates, $state, Restangular) {
        var ctrl = this;

        ctrl.project = project;
        ctrl.chooseTemplate = chooseTemplate;
        ctrl.chooseExistingProcess = chooseExistingProcess;
        ctrl.templates = templates;
        ctrl.hasFavorites = _.partial(_.any, ctrl.templates, _.matchesProperty('favorite', true));

        /////////////////////////

        function chooseTemplate() {
            mcmodal.chooseTemplate(ctrl.project, templates).then(function (processTemplateName) {
                $state.go('projects.project.processes.create', {process: processTemplateName, process_id: ''});
            });
        }

        function chooseExistingProcess() {
            Restangular.one('v2').one("projects", project.id).one("processes").getList().then(function(processes) {
                mcmodal.chooseExistingProcess(processes).then(function (existingProcess) {
                    var processName = existingProcess.process_name ? existingProcess.process_name : 'TEM';
                    $state.go('projects.project.processes.create', {process: processName, process_id: existingProcess.id});
                });
            });
        }
    }
}(angular.module('materialscommons')));

(function (module) {
    module.controller('ProjectHomeController', ProjectHomeController);
    ProjectHomeController.$inject = ["project", "mcmodal", "templates", "$state", "Restangular"];

    function ProjectHomeController(project, mcmodal, templates, $state, Restangular) {
        var ctrl = this;

        ctrl.project = project;
        ctrl.chooseTemplate = chooseTemplate;
        ctrl.chooseExistingProcess = chooseExistingProcess;
        ctrl.templates = templates;
        ctrl.hasFavorites = _.partial(_.any, ctrl.templates, _.matchesProperty('favorite', true));

        /////////////////////////

        function chooseTemplate() {
            mcmodal.chooseTemplate(ctrl.project, templates).then(function (processTemplateName) {
                $state.go('projects.project.processes.create', {process: processTemplateName, process_id: ''});
            });
        }

        function chooseExistingProcess() {
            Restangular.one('v2').one("projects", project.id).one("processes").getList().then(function(processes) {
                mcmodal.chooseExistingProcess(processes).then(function (existingProcess) {
                    $state.go('projects.project.processes.create',
                        {process: existingProcess.process_name, process_id: existingProcess.id});
                });
            });
        }
    }
}(angular.module('materialscommons')));

Remove the hard coded process name now that the process name has been set.


/* global ln */

import 'shelljs/global';

ln('-sf', './', './node_modules/eslint-config-exchange-solutions');

/* global ln */

import 'shelljs/global';

ln('-sf', process.cwd(), './node_modules/eslint-config-exchange-solutions');

Fix link to use the process cwd


(function() {
    var defaults = require('./defaults');

    chrome.storage.sync.get(defaults, function(settings) {
        var body = document.body;
        var bannerWrapper = document.createElement('div');
        var banner = document.createElement('div');
        var bannerText = document.createElement('p');
        var closeButton = document.createElement('a');
        bannerWrapper.id = 'productionWarningBanner-wrapper';
        banner.id = 'productionWarningBanner';
        banner.style.backgroundColor = settings.backgroundColor;
        banner.style.opacity = settings.opacity;
        bannerText.id = 'productionWarningBanner-text';
        bannerText.style.color = settings.textColor;
        if (settings.blinky) {
            bannerText.classList.add('productionWarningBanner-blinky');
        }
        bannerText.appendChild(document.createTextNode(settings.warningText));
        closeButton.id = "productionWarningBanner-close";
        closeButton.setAttribute("aria-label", "Close");
        closeButton.onclick = function() {
            body.removeChild(bannerWrapper);
        };
        banner.appendChild(bannerText);
        banner.appendChild(closeButton);
        bannerWrapper.appendChild(banner);
        body.insertBefore(bannerWrapper, body.childNodes[0]);
    });

})();

(function() {
    var defaults = require('./defaults');

    chrome.storage.sync.get(defaults, function(settings) {
        if(document.getElementById('productionWarningBanner-wrapper')) return;

        var body = document.body;
        var bannerWrapper = document.createElement('div');
        var banner = document.createElement('div');
        var bannerText = document.createElement('p');
        var closeButton = document.createElement('a');
        bannerWrapper.id = 'productionWarningBanner-wrapper';
        banner.id = 'productionWarningBanner';
        banner.style.backgroundColor = settings.backgroundColor;
        banner.style.opacity = settings.opacity;
        bannerText.id = 'productionWarningBanner-text';
        bannerText.style.color = settings.textColor;
        if (settings.blinky) {
            bannerText.classList.add('productionWarningBanner-blinky');
        }
        bannerText.appendChild(document.createTextNode(settings.warningText));
        closeButton.id = "productionWarningBanner-close";
        closeButton.setAttribute("aria-label", "Close");
        closeButton.onclick = function() {
            body.removeChild(bannerWrapper);
        };
        banner.appendChild(bannerText);
        banner.appendChild(closeButton);
        bannerWrapper.appendChild(banner);
        body.insertBefore(bannerWrapper, body.childNodes[0]);
    });

})();

Fix duplicated banners for single page application


module.exports = {
    Course: function Course(subject, number, credits) {
        this.subject = subject;
        this.number = number;
        if (credits !== undefined)
            this.credits = credits;

        this.stripCredits = function() {
            return new Course(subject, number);
        };
    },
    /**
     * Creates a new CourseEquivalency.
     *
     * @param keyCourse Course to perform lookups on. Created with only subject
     *                  and number, no credits
     * @param input Array of courses from NVCC
     * @param output Array of ocurses from `institution`
     */
    CourseEquivalency: function CourseEquivalency(input, output) {
        this.keyCourse = input[0].stripCredits();
        this.input = input;
        this.output = output;
    },
    EquivalencyContext: function EquivalencyContext(institution, equivalencies) {
        this.institution = institution;
        this.equivalencies = equivalencies;
    },
    Institution: function(acronym, fullName) {
        this.acronym = acronym;
        this.fullName = fullName;
    },
    // No information about credits given
    CREDITS_UNKNOWN: -1,
    // A student should check with the university for further clarificaiton
    CREDITS_UNCLEAR: -2
};

module.exports = {
    Course: function Course(subject, number, credits) {
        this.subject = subject;
        this.number = number;
        this.credits = credits;
    },
    /**
     * Creates a new CourseEquivalency.
     *
     * @param keyCourse Course to perform lookups on. Created with only subject
     *                  and number, no credits
     * @param input Array of courses from NVCC
     * @param output Array of ocurses from `institution`
     * @param type Equivalency type. Must be a value specified by models.TYPE_*
     */
    CourseEquivalency: function CourseEquivalency(input, output, type) {
        this.keyCourse = new module.exports.Course(input[0].subject, input[0].number);
        this.input = input;
        this.output = output
        this.type = type;
    },
    EquivalencyContext: function EquivalencyContext(institution, equivalencies) {
        this.institution = institution;
        this.equivalencies = equivalencies;
    },
    Institution: function(acronym, fullName) {
        this.acronym = acronym;
        this.fullName = fullName;
    },
    // No information about credits given
    CREDITS_UNKNOWN: -1,
    // A student should check with the university for further clarificaiton
    CREDITS_UNCLEAR: -2
};

Remove unnecessary stripCredits() function from Course


module.exports = {
    'extends': 'eslint:recommended',
    'rules': {
        'indent': ['error', 4, { 'SwitchCase': 1 }],
        'space-before-function-paren': ['error', 'never'],
        'no-console': ['error', { 'allow': ['warn'] }],
        'object-curly-spacing': ['error', 'always', { 'objectsInObjects': false }],
        'no-constant-condition': ['off'],
        'no-undef': ['error'],
        'no-unused-vars': ['error', { 'vars': 'local', 'args': 'none' }],
        'quotes': ['error', 'single'],
        'semi': ['error', 'always']
    },
    'env': {
        'browser': true,
        'node': true
    },
    'globals': {
       'Uint8Array': true
    },
    'parserOptions': {
        'ecmaVersion': 6,
        sourceType: 'module'
    }
};

module.exports = {
    'extends': 'eslint:recommended',
    'rules': {
        'indent': ['error', 4, { 'SwitchCase': 1 }],
        'space-before-function-paren': ['error', 'never'],
        'no-console': ['error', { 'allow': ['warn'] }],
        'object-curly-spacing': ['error', 'always', { 'objectsInObjects': false }],
        'no-constant-condition': ['off'],
        'no-undef': ['error'],
        'no-unused-vars': ['error', { 'vars': 'local', 'args': 'none' }],
        'quotes': ['error', 'single'],
        'semi': ['error', 'always']
    },
    'env': {
        'browser': true,
        'node': true
    },
    'globals': {
       'Uint8Array': true,
       'CDATASection': true
    },
    'parserOptions': {
        'ecmaVersion': 6,
        sourceType: 'module'
    }
};

Add CDATASection to eslint globals


// https://eslint.org/docs/user-guide/configuring

module.exports = {
  root: true,
  parserOptions: {
    parser: 'babel-eslint'
  },
  env: {
    browser: true,
  },
  extends: [
    // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
    // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
    'plugin:vue/essential',
    // https://github.com/standard/standard/blob/master/docs/RULES-en.md
    'standard'
  ],
  // required to lint *.vue files
  plugins: [
    'vue'
  ],
  // add your custom rules here
  rules: {
    // allow async-await
    'generator-star-spacing': 'off',
    // allow debugger during development
    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
  }
}

// https://eslint.org/docs/user-guide/configuring

module.exports = {
  root: true,
  parserOptions: {
    parser: 'babel-eslint'
  },
  env: {
    browser: true,
  },
  extends: [
    // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
    // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
    'plugin:vue/essential',
    // https://github.com/standard/standard/blob/master/docs/RULES-en.md
    'standard'
  ],
  // required to lint *.vue files
  plugins: [
    'eslint:recommended',
    'vue'
  ],
  // add your custom rules here
  rules: {
    // allow async-await
    'generator-star-spacing': 'off',
    // allow debugger during development
    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
  }
}

Include recommended on eslint config


//= require govuk/multivariate-test

$(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',
      customVarIndex: 14,
      cohorts: {
        control: { callback: function () { } },
        tax_disc_beta1: { callback: GOVUK.taxDiscBetaPrimary }
      }
    });
  }
});

//= require govuk/multivariate-test

$(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',
      customVarIndex: 20,
      cohorts: {
        control: { callback: function () { } },
        tax_disc_beta1: { callback: GOVUK.taxDiscBetaPrimary }
      }
    });
  }
});

Use the correct analytics custom var


/*jshint browser:true */
/*global define:false */

define(["underscore", "jquery"], function (_, $) {
    "use strict";

    // Return the mp3-loader module
    return {

        // Load mp3 of given URL
        load: function (url, onLoaded) {
            onLoaded || (onLoaded = function () {});

            // Load mp3
            var request = new window.XMLHttpRequest();
            request.responseType = "arraybuffer";
            request.open("GET", url, true);

            request.onload = function (event) {

                if (request.readyState !== 4 || request.status !== 200) {
                    return onLoaded(request.status + ", " + request.statusText);
                }

                onLoaded(null, new DataView(request.response));
            };

            request.send(null);
        }
    };
});

/*jshint browser:true */
/*global define:false */

define(["underscore", "jquery"], function (_, $) {
    "use strict";

    var buildErrorMsg = function (status, statusText) {
            var msg = "Status: " + status;
            if (statusText) { msg += ", " + statusText; }
            return msg;
        };

    // Return the mp3-loader module
    return {

        // Load mp3 of given URL
        load: function (url, onLoaded) {
            onLoaded || (onLoaded = function () {});

            // Load mp3
            var request = new window.XMLHttpRequest();
            request.responseType = "arraybuffer";
            request.open("GET", url, true);

            request.onerror = function (event) {
                onLoaded(buildErrorMsg(request.status, request.statusText));
            };

            request.onload = function (event) {

                if (request.readyState !== 4 || request.status !== 200) {
                    return onLoaded(buildErrorMsg(request.status, request.statusText));
                }

                onLoaded(null, new DataView(request.response));
            };

            request.send(null);
        }
    };
});

Improve error checking in browser example


/*
 *
 * ActivityMapsPage reducer
 *
 */

import {
  DEFAULT_ACTION,
  CLICK_DOT,
} from './constants';

const initialState = {
  selectedDot: {},
};

function activityMapsPageReducer(state = initialState, action) {
  switch (action.type) {
    case CLICK_DOT:
      return {
        ...state,
        selectedDot: action.payload,
      };
    case DEFAULT_ACTION:
      return state;
    default:
      return state;
  }
}

export default activityMapsPageReducer;

/*
 *
 * ActivityMapsPage reducer
 *
 */

import {
  DEFAULT_ACTION,
  CLICK_DOT,
} from './constants';

const initialState = {
  selectedDot: {
    text: 'Ti',
  },
};

function activityMapsPageReducer(state = initialState, action) {
  switch (action.type) {
    case CLICK_DOT:
      return {
        ...state,
        selectedDot: action.payload,
      };
    case DEFAULT_ACTION:
      return state;
    default:
      return state;
  }
}

export default activityMapsPageReducer;

Set default selected doping to Ti


/*!
 * jQuery special event "tap" using tap.js
 * Released under MIT license
 */
(function ($) {
    'use strict';

    $.event.special.tap = (function () {

        // Fallback to click events in old IE
        if (!document.addEventListener) return { bindType: 'click', delegateType: 'click' };

        var Tap = window.Tap;
        var dataKey = 'tap.js';

        return {
            setup: function () {
                $.data(this, dataKey, new Tap(this));
            },
            teardown: function () {
                var tap = $.data(this, dataKey);
                if (tap && tap.destroy) {
                    tap.destroy();
                    $.removeData(this, dataKey);
                }
            },
            add: function (handleObj) {
                this.addEventListener('tap', handleObj.handler, false);
            },
            remove: function (handleObj) {
                this.removeEventListener('tap', handleObj.handler, false);
            }
        };
    }());
}(window.jQuery));

/*!
 * jQuery special event "tap" using tap.js
 * Released under MIT license
 */
(function ($) {
    'use strict';

    $.event.special.tap = (function () {

        // Fallback to click events in old IE
        if (!document.addEventListener) return { bindType: 'click', delegateType: 'click' };

        var Tap = window.Tap;
        var dataKey = 'tap.js';

        return {
            setup: function () {
                $.data(this, dataKey, new Tap(this));

                return false;
            },
            teardown: function () {
                var tap = $.data(this, dataKey);
                if (tap && tap.destroy) {
                    tap.destroy();
                    $.removeData(this, dataKey);
                }

                return false;
            }
    }());
}(window.jQuery));

Allow for event delegation to happen.


module.exports = {
  env: {
    es6: true,
    node: true,
  },
  extends: ['eslint:recommended', 'airbnb-base', 'plugin:prettier/recommended'],
  parserOptions: {
    sourceType: 'module',
  },
  rules: {
    indent: ['error', 2],
    'linebreak-style': ['error', 'unix'],
    'prettier/prettier': ['warn', { singleQuote: true, trailingComma: 'es5' }],
  },
};

module.exports = {
  env: {
    es6: true,
    node: true,
  },
  extends: ['eslint:recommended', 'airbnb-base', 'plugin:prettier/recommended'],
  parserOptions: {
    sourceType: 'module',
  },
  rules: {
    indent: ['error', 2],
    'no-use-before-define': ['error', { functions: false, classes: true }],
    'linebreak-style': ['error', 'unix'],
    'prettier/prettier': ['warn', { singleQuote: true, trailingComma: 'es5' }],
  },
};

Allow use-before-define for functions in eslint


import React from "react";
import _ from "lodash";

class Notifications extends React.Component {
  render() {
    var { kills } = this.props;
    var date = new Date();

    _.remove(kills, k => date - k.date > 3000);
    kills = _.sortBy(kills, k => k.date.valueOf);
    return (

        {_.map(kills, (k, idx) => {
          return
{k.text}
;
        })}

    );
  }
}

export default Notifications;

import React from "react";

function Notifications(props) {
  const { notifications } = props;
  const dateTime = new Date().getTime();

  const filteredKeys = notifications
    .filter((el) => el && el.text)
    .filter((el) => el.date > dateTime - 5000)
    .sort((a, b) => b.date - a.date)
    .slice(0, 5);

  return (

      {filteredKeys.map((k, idx) => {
        return (
          <div className={`notifications-element ${k.id % 2 === 0 ? "-alt" : ""}`} key={idx}>
            {k.text}

        );
      })}

  );
}

export default Notifications;

Refactor Notification to use hooks


'use strict'

module.exports = {
  extends: [
    './default.js'
  ]
}

'use strict'

module.exports = {
  extends: [
    './unused.js',
    './default.js',
  ],
}

Include unused rules as well, just in case


module.exports = {
    builtInStore: require('./built-in-store')
};

let dispatcher = require('../dispatcher');
module.exports = {
    builtInStore: require('./built-in-store'),
    /*
    * Dispatch the change of the query value, with the callerId information.
    */
    changeQuery(queryValue, callerId){
      dispatcher.handleViewAction({data: {query: queryValue}, type: 'update', callerId: callerId});
    },
    /*
    * Dispatch the change of the scope value, with the callerId information.
    */
    changeScope(scopeValue, callerId){
      dispatcher.handleViewAction({data: {scope: scopeValue}, type: 'update', callerId: callerId});
    }
};

Add a method to dispatch the new value of query and scope.


$(document).ready(function(){
    $('.popup__toggle').on('click', popup);
    function popup() {
        $('#popup').toggleClass("open");
    }
    function fileHandler(event){
        var file = event.target.files[0]; //event.target references the object that dispatched the event (so here it is the input element)
        var reader = new FileReader();
        reader.onload = function(event){
            var data = event.target.result; //again event.target references the object that dispatched the event (here it is reader).
            $("#preview-img").attr('src', data);
        };
        reader.readAsDataURL(file);
    }

    function addImgToInfo() {
        $("#preview-img").clone().attr('id', '').appendTo('#image-wrapper');
    }

    $('#file').on("change", fileHandler);
    $('#submit-img').on('click', addImgToInfo);
});
var map;
function initMap() {
    map = new google.maps.Map(document.getElementById('map'), {
        center: {lat: -34.397, lng: 150.644},
        zoom: 8
    });
    $("html, body, #popup").css({
        height: $(window).height()
    });
}

$(document).ready(function(){
    $('.popup__toggle').on('click', popup);
    function popup() {
        $('#popup').toggleClass("open");
    }
    function fileHandler(event){
        var file = event.target.files[0]; //event.target references the object that dispatched the event (so here it is the input element)
        var reader = new FileReader();
        reader.onload = function(event){
            var data = event.target.result; //again event.target references the object that dispatched the event (here it is reader).
            $("#preview-img").attr('src', data);
        };
        reader.readAsDataURL(file);
    }

    function addImgToInfo() {
        $("#preview-img").clone().attr('id', '').appendTo('#image-wrapper');
    }

    $('#file').on("change", fileHandler);
    $('#submit-img').on('click', addImgToInfo);
});
var map;
function initMap() {
    map = new google.maps.Map(document.getElementById('map'), {
        center: {lat: -34.397, lng: 150.644},
        zoom: 8
    });
}

Remove height setting from js


const express = require('express');
const app = express();
const PORT = process.env.PORT || 5000;

app.use(express.static(__dirname + '/../react-ui/build'));

app.get('/api', function (req, res) {
  res.set('Content-Type', 'application/json');
  res.send('{"message":"Hello from the custom server!"}');
});

app.listen(PORT, function () {
  console.log(`Listening on port ${PORT}`);
});

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

const app = express();
const PORT = process.env.PORT || 5000;

// Priority serve any static files.
app.use(express.static(path.resolve(__dirname, '../react-ui/build')));

// Answer API requests.
app.get('/api', function (req, res) {
  res.set('Content-Type', 'application/json');
  res.send('{"message":"Hello from the custom server!"}');
});

// All remaining requests return the React app, so it can handle routing.
app.get('*', function(request, response) {
  response.sendFile(path.resolve(__dirname, '../react-ui/build', 'index.html'));
});

app.listen(PORT, function () {
  console.log(`Listening on port ${PORT}`);
});

Support server-side URL routing to React app.


const { execSync } = require('child_process');
const { readFileSync } = require('fs');
const { resolve } = require('path');

function getGitCommit() {
  return execSync('git show -s --format=%h')
    .toString()
    .trim();
}

function getGitHubURL() {
  // TODO potentially replac this with an fb.me URL (if it can forward the query params)
  return execSync('git remote get-url origin')
    .toString()
    .trim()
    .replace(':', '/')
    .replace('git@', 'https://')
    .replace('.git', '');
}

function getVersionString() {
  const packageVersion = JSON.parse(
    readFileSync(resolve(__dirname, '../package.json'))
  ).version;

  const commit = getGitCommit();

  return `${packageVersion}-${commit}`;
}

module.exports = { getGitCommit, getGitHubURL, getVersionString };

const { execSync } = require('child_process');
const { readFileSync } = require('fs');
const { resolve } = require('path');

function getGitCommit() {
  return execSync('git show -s --format=%h')
    .toString()
    .trim();
}

function getGitHubURL() {
  // TODO potentially replace this with an fb.me URL (assuming it can forward the query params)
  const url = execSync('git remote get-url origin')
    .toString()
    .trim();

  if (url.startsWith('https://')) {
    return url.replace('.git', '');
  } else {
    return url
      .replace(':', '/')
      .replace('git@', 'https://')
      .replace('.git', '');
  }
}

function getVersionString() {
  const packageVersion = JSON.parse(
    readFileSync(resolve(__dirname, '../package.json'))
  ).version;

  const commit = getGitCommit();

  return `${packageVersion}-${commit}`;
}

module.exports = { getGitCommit, getGitHubURL, getVersionString };

Handle HTTPS Git clone URLs


(function(root, factory) {
  if (typeof exports === 'object') {
    // CommonJS
    factory(exports, module);
  } else if (typeof define === 'function' && define.amd) {
    // AMD
    define(['exports', 'module'], factory);
  }
}(this, function(exports, module) {

  /**
   * PLAIN `Mechanism` constructor.
   *
   * This class implements the PLAIN SASL mechanism.
   *
   * The PLAIN SASL mechanism provides support for exchanging a clear-text
   * username and password.  This mechanism should not be used without adequate
   * security provided by an underlying transport layer.
   *
   * References:
   *  - [RFC 4616](http://tools.ietf.org/html/rfc4616)
   *
   * @api public
   */
  function Mechanism() {
  }

  Mechanism.prototype.name = 'PLAIN';

  Mechanism.prototype.response = function(cred) {
    var str = '';
    str += cred.authzid || '';
    str += '\0';
    str += cred.username;
    str += '\0';
    str += cred.password;
    return str;
  };

  Mechanism.prototype.challenge = function(chal) {
  };

  exports = module.exports = Mechanism;

}));

(function(root, factory) {
  if (typeof exports === 'object') {
    // CommonJS
    factory(exports, module);
  } else if (typeof define === 'function' && define.amd) {
    // AMD
    define(['exports', 'module'], factory);
  }
}(this, function(exports, module) {

  /**
   * PLAIN `Mechanism` constructor.
   *
   * This class implements the PLAIN SASL mechanism.
   *
   * The PLAIN SASL mechanism provides support for exchanging a clear-text
   * username and password.  This mechanism should not be used without adequate
   * security provided by an underlying transport layer.
   *
   * References:
   *  - [RFC 4616](http://tools.ietf.org/html/rfc4616)
   *
   * @api public
   */
  function Mechanism() {
  }

  Mechanism.prototype.name = 'PLAIN';

  /**
   * Encode a response using given credential.
   *
   * Options:
   *  - `username`
   *  - `password`
   *  - `authzid`   authorization identity (optional)
   *
   * @param {Object} cred
   * @api public
   */
  Mechanism.prototype.response = function(cred) {
    var str = '';
    str += cred.authzid || '';
    str += '\0';
    str += cred.username;
    str += '\0';
    str += cred.password;
    return str;
  };

  /**
   * Decode a challenge issued by the server.
   *
   * @param {String} chal
   * @api public
   */
  Mechanism.prototype.challenge = function(chal) {
  };

  exports = module.exports = Mechanism;

}));

Document response and challenge functions.


var path = require( 'path' )
  , textract = require( './index' );

module.exports = function( filePath, flags ) {
  filePath = path.resolve( process.cwd(), filePath );

  for (var flag in flags) {
    if (flags[flag] !== undefined) {
      flags[flag] = ( flags[flag] === "true" );
    }
  }

  if ( flags.preserveLineBreaks === undefined ) {
    flags.preserveLineBreaks = true;
  }

  textract.fromFileWithPath( filePath, flags, function( error, text ) {
    if ( error ) {
      console.error( error );
    } else {
      console.log( text );
    }
  });
};

var path = require( 'path' )
  , textract = require( './index' );

module.exports = function( filePath, flags ) {
  filePath = path.resolve( process.cwd(), filePath );

  if ( flags.preserveLineBreaks === undefined ) {
    flags.preserveLineBreaks = true;
  }

  textract.fromFileWithPath( filePath, flags, function( error, text ) {
    if ( error ) {
      console.error( error );
    } else {
      console.log( text );
    }
  });
};

Fix all CLI options other than boolean true being converted to false


/*
 * pat
 * https://github.com/mikko/pat
 *
 * Copyright (c) 2013 Mikko Koski
 * Licensed under the MIT license.
 */

"use strict";

var _ = require("lodash");

function last(arr) {
  return arr.slice(-1)[0];
}

function initial(arr) {
  return arr.slice(0, -1);
}

function toArray(args) {
  return Array.prototype.slice.call(args);
}

function match(patterns, args) {
  var matches = patterns.filter(function(pattern) {
    return pattern.args.length === args.length && _.every(_.zip(pattern.args, args), function(zipped) {
      var patt = zipped[0], arg = zipped[1];
      return _.isFunction(patt) ? patt(arg) : _.isEqual(patt, arg);
    })
  });
  return matches[0];
}

function pat(f) {
  var patterns = [];

  if(!f) {
    f = function() {};
  }

  var wrapper = function() {
    var matchedPattern = match(patterns, toArray(arguments));

    if(matchedPattern) {
      return matchedPattern.clbk.apply(this, arguments);
    } else {
      return f.apply(this, arguments);
    }
  }

  wrapper.caseof = function() {
    var args = toArray(arguments);
    var clbk = last(args);
    args = initial(args);
    patterns.push(Object.freeze({args: args, clbk: clbk}));
    return wrapper;
  };

  return wrapper;
}

module.exports = pat;

/*
 * pat
 * https://github.com/mikko/pat
 *
 * Copyright (c) 2013 Mikko Koski
 * Licensed under the MIT license.
 */

"use strict";

var _ = require("lodash");

function match(patterns, args) {
  var matches = patterns.filter(function(pattern) {
    return pattern.args.length === args.length && _.every(_.zip(pattern.args, args), function(zipped) {
      var patt = zipped[0], arg = zipped[1];
      return _.isFunction(patt) ? patt(arg) : _.isEqual(patt, arg);
    })
  });
  return matches[0];
}

function pat(f) {
  var patterns = [];

  if(!f) {
    f = function() {};
  }

  var wrapper = function() {
    var matchedPattern = match(patterns, _.toArray(arguments));

    if(matchedPattern) {
      return matchedPattern.clbk.apply(this, arguments);
    } else {
      return f.apply(this, arguments);
    }
  }

  wrapper.caseof = function() {
    var clbk = _.last(arguments);
    var args = _.initial(arguments);
    patterns.push(Object.freeze({args: args, clbk: clbk}));
    return wrapper;
  };

  return wrapper;
}

module.exports = pat;

Remove unused helpers, use lodash


import { ReactiveVar } from 'meteor/reactive-var';

export class Result {
    constructor({
        observer,
        defaultValue = {}
    } = {}) {
        this.observer = observer;

        this._isReady = new ReactiveVar(false);
        this._errors = new ReactiveVar();
        this._var = new ReactiveVar(defaultValue);

        this.subscribe();
    }

    isReady() {
        return this._isReady.get();
    }

    get() {
        return this._var.get();
    }

    getErrors() {
        return this._errors.get();
    }

    unsubscribe() {
        return this._subscription && this._subscription.unsubscribe();
    }

    subscribe() {
        if (this._subscription) {
            this.unsubscribe();
        }

        this._subscription = this.observer.subscribe({
            next: ({ errors, data }) => {
                if (errors) {
                    this._errors.set(errors);
                } else {
                    this._errors.set(null);
                    this._var.set(data);
                }
                this._isReady.set(true);
            }
        });
    }
}

import { ReactiveVar } from 'meteor/reactive-var';

export class Result {
    constructor({
        observer,
        defaultValue = {}
    } = {}) {
        this.observer = observer;

        this._isReady = new ReactiveVar(false);
        this._errors = new ReactiveVar();
        this._var = new ReactiveVar(defaultValue);

        this.subscribe();
    }

    isReady() {
        return this._isReady.get();
    }

    get() {
        return this._var.get();
    }

    getErrors() {
        return this._errors.get();
    }

    unsubscribe() {
        return this._subscription && this._subscription.unsubscribe();
    }

    subscribe() {
        if (this._subscription) {
            this.unsubscribe();
        }

        this._subscription = this.observer.subscribe({
            next: ({ errors, data }) => {
                if (errors) {
                    this._errors.set(errors);
                } else {
                    this._errors.set(null);
                    this._var.set(data);
                }
                this._isReady.set(true);
            },
            error: (error) => {
                this._errors.set([].concat(error));
                this._isReady.set(true);
            }
        });
    }
}

Add some basic error handling


require('./board-test.js');
require('./game-test.js');
require('./light-test.js');
require('./levels-test.js');
require('./score-test.js');
require('./messages-test.js');
require('./next-move-test.js');
require('./end-pattern-test.js');
require('./neighbor-coords-test.js');
require('./relative-coords-test.js');

Add new test files for relativeCoords and neighborCoords


function liveReloadMiddleware(compiler) {
  let watchers = [];

  compiler.plugin('done', () => {
    const headers = {
      'Content-Type': 'application/json; charset=UTF-8',
    };

    watchers.forEach(watcher => {
      watcher.res.writeHead(205, headers);
      watcher.res.end(JSON.stringify({ changed: true }));
    });

    watchers = [];
  });

  return (req, res, next) => {
    if (req.path === '/onchange') {
      const watcher = { req, res };

      watchers.push(watcher);

      req.on('close', () => {
        for (let i = 0; i < watchers.length; i++) {
          if (watchers[i] && watchers[i].req === req) {
            watchers.splice(i, 1);
            break;
          }
        }
      });
    } else {
      next();
    }
  };
}

module.exports = liveReloadMiddleware;

const safeWay = true;

function notifyWatcher(watcher) {
  const headers = {
    'Content-Type': 'application/json; charset=UTF-8',
  };

  watcher.res.writeHead(205, headers);
  watcher.res.end(JSON.stringify({ changed: true }));
}

function liveReloadMiddleware(compiler) {
  let watchers = [];
  let notify = false;

  compiler.plugin('done', () => {
    if (safeWay) {
      notify = true;
    } else {
      watchers.forEach(watcher => {
        notifyWatcher(watcher);
      });

      watchers = [];
    }
  });

  return (req, res, next) => {
    if (req.path === '/onchange') {
      const watcher = { req, res };

      if (safeWay) {
        if (notify) {
          notifyWatcher(watcher);
          notify = false;
        }
      } else {
        watchers.push(watcher);

        req.on('close', () => {
          for (let i = 0; i < watchers.length; i++) {
            if (watchers[i] && watchers[i].req === req) {
              watchers.splice(i, 1);
              break;
            }
          }
        });
      }
    } else {
      next();
    }
  };
}

module.exports = liveReloadMiddleware;

Use slower but safe Live Reload RN impl for now


"use strict";

var _ZOOM = {
    NEIGHBORHOOD: 16,
    MIN: 11,
    MAX: 19
};

module.exports = {
    ZOOM: Object.freeze ? Object.freeze(_ZOOM) : _ZOOM,

    setCenterAndZoomLL: function(map, zoom, location) {
        // Never zoom out, or try to zoom farther than allowed.
        var zoomToApply = Math.max(
            map.getZoom(),
            Math.min(zoom, map.getMaxZoom()));

        map.setView(location, zoomToApply);
    }
};

"use strict";

var _ZOOM = {
    NEIGHBORHOOD: 16,
    MIN: 0,
    MAX: 19
};

module.exports = {
    ZOOM: Object.freeze ? Object.freeze(_ZOOM) : _ZOOM,

    setCenterAndZoomLL: function(map, zoom, location) {
        // Never zoom out, or try to zoom farther than allowed.
        var zoomToApply = Math.max(
            map.getZoom(),
            Math.min(zoom, map.getMaxZoom()));

        map.setView(location, zoomToApply);
    }
};

Revert 6914a00d4 because it broke 'add event' map


process.on('unhandledRejection', err => {
  throw err;
});

const express = require('express');
const next = require('next');
const { createRouter } = require('soya-next/server/router');
const { soya } = require('soya-next/server/config');
// @remove-on-eject-begin
const conf = require('../next.config');
// @remove-on-eject-end

const host = soya.config.host || '0.0.0.0';
const port = soya.config.port || 3000;
const dev = soya.config.dev || process.env.NODE_ENV !== 'production';
const app = next({
  dev,
  // @remove-on-eject-begin
  conf,
  // @remove-on-eject-end
});

app.prepare()
  .then(() => {
    const server = express();
    server.use(createRouter(app, soya.config));
    server.listen(port, host, err => {
      if (err) {
        throw err;
      }
      // eslint-disable-next-line no-console
      console.log(`> Ready on ${host}:${port}`);
    });
  })
  .catch(ex => {
    // eslint-disable-next-line no-console
    console.error(ex.stack);
    process.exit(1);
  });

process.on('unhandledRejection', err => {
  throw err;
});

const express = require('express');
const next = require('next');
const { createRouter } = require('soya-next/server/router');
const { soya } = require('soya-next/server/config');
// @remove-on-eject-begin
const conf = require('../next.config');
// @remove-on-eject-end

const host = soya.config.host || '0.0.0.0';
const port = soya.config.port || 3000;
const dev = soya.config.dev ? soya.config.dev : process.env.NODE_ENV !== 'production';
const app = next({
  dev,
  // @remove-on-eject-begin
  conf,
  // @remove-on-eject-end
});

app.prepare()
  .then(() => {
    const server = express();
    server.use(createRouter(app, soya.config));
    server.listen(port, host, err => {
      if (err) {
        throw err;
      }
      // eslint-disable-next-line no-console
      console.log(`> Ready on ${host}:${port}`);
    });
  })
  .catch(ex => {
    // eslint-disable-next-line no-console
    console.error(ex.stack);
    process.exit(1);
  });

Fix wrong dev config logic


var path = require('path');
var webpack = require('webpack');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  devtool: 'source-map',
  entry: {
    app: './lib/index.js'
  },
  output: {
    filename: '[name].min.js',
    path: path.join(__dirname, 'dist'),
    publicPath: ''
  },
  plugins: [
    new webpack.optimize.OccurenceOrderPlugin(),
    new webpack.NoErrorsPlugin(),
    new webpack.DefinePlugin({
      'process.env': {
        'NODE_ENV': JSON.stringify('production')
      },
      '__DEVTOOLS__': false
    }),
    new webpack.optimize.UglifyJsPlugin({
      compressor: {
        warnings: false
      }
    }),
    new ExtractTextPlugin('app.css', { allChunks: true }),
    new HtmlWebpackPlugin({
      title: 'Fresh Food Connect',
      filename: 'index.html',
      template: 'index.template.html',
      favicon: path.join(__dirname, 'assets/images/favicon.ico')
    })
  ],
  module: {
    loaders: [
      { test: /\.css$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader!cssnext-loader') },
      { test: /\.js$/, loaders: ['babel'], exclude: /node_modules/ }
    ]
  },
  cssnext: {
    browsers: 'last 2 versions'
  }
};

var path = require('path');
var webpack = require('webpack');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  devtool: 'source-map',
  entry: {
    app: './lib/index.js'
  },
  output: {
    filename: '[name].min.js',
    path: path.join(__dirname, 'dist'),
    publicPath: ''
  },
  plugins: [
    new webpack.optimize.OccurenceOrderPlugin(),
    new webpack.NoErrorsPlugin(),
    new webpack.DefinePlugin({
      'process.env': {
        'NODE_ENV': JSON.stringify('production')
      },
      '__DEVTOOLS__': false
    }),
    new webpack.optimize.UglifyJsPlugin({
      compressor: {
        warnings: false
      }
    }),
    new ExtractTextPlugin('app.css', { allChunks: true }),
    new HtmlWebpackPlugin({
      title: 'Fresh Food Connect',
      filename: 'index.html',
      template: 'index.template.html',
      favicon: path.join(__dirname, 'assets/images/favicon.ico')
    })
  ],
  module: {
    loaders: [
      {
        test: /\.scss$/,
        loader: 'style!css!sass'
      },
      {test: /\.png$/, loader: "url-loader?mimetype=image/png"},
      { test: /\.css$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader!cssnext-loader') },
      { test: /\.js$/, loaders: ['babel'], exclude: /node_modules/ }
    ]
  },
  cssnext: {
    browsers: 'last 2 versions'
  }
};

Fix ability to build prod static files


pageflow.EditWidgetView = Backbone.Marionette.Layout.extend({
  template: 'templates/edit_widget',
  tagName: 'li',
  className: 'edit_widget',

  regions: {
    widgetTypeContainer: '.widget_type'
  },

  ui: {
    role: 'h2'
  },

  onRender: function() {
    var widgetTypes = this.options.widgetTypes[this.model.role()];

    this.widgetTypeContainer.show(new pageflow.SelectInputView({
      model: this.model,
      propertyName: 'type_name',
      label: I18n.t('pageflow.widgets.roles.' + this.model.role()),
      collection: widgetTypes,
      valueProperty: 'name',
      translationKeyProperty: 'translationKey',
      includeBlank: true
    }));

    this.$el.toggle(widgetTypes.length > 1);
  }
});

pageflow.EditWidgetView = Backbone.Marionette.Layout.extend({
  template: 'templates/edit_widget',
  tagName: 'li',
  className: 'edit_widget',

  regions: {
    widgetTypeContainer: '.widget_type'
  },

  ui: {
    role: 'h2'
  },

  onRender: function() {
    var widgetTypes = this.options.widgetTypes[this.model.role()] || [];

    this.widgetTypeContainer.show(new pageflow.SelectInputView({
      model: this.model,
      propertyName: 'type_name',
      label: I18n.t('pageflow.widgets.roles.' + this.model.role()),
      collection: widgetTypes,
      valueProperty: 'name',
      translationKeyProperty: 'translationKey',
      includeBlank: true
    }));

    this.$el.toggle(widgetTypes.length > 1);
  }
});

Make EditWidgetView handle widgets with no available widget types