const Hipchat = require('./Hipchat');
const hipchat = new Hipchat( process.env.HIPCHAT_KEY || '' );

const STATUS = {
  Started: 'lozenge-current',
  Completed: 'lozenge-success',
  Error: 'lozenge-error',
}

const Colors = {
  Started: 'yellow',
  Completed: 'green',
  Error: 'red',
}

async function Notification( options ) {
  const status = options['status'];
  const notificationOptions = {
    room: options.room,
    args: {
      title: 'Lighthouse Report',
      message: 'Reporter message',
      color: Colors[status],
      card: {
        description: options.description,
        url: options.url,
        attributes: [{
          label: 'Status',
          value: {
            label: status,
            style: STATUS[status],
          }
        }]
      }
    }
  }
  return await hipchat.send( notificationOptions );
}

module.exports = Notification;

const Hipchat = require('./Hipchat');
const hipchat = new Hipchat( process.env.HIPCHAT_KEY || '' );

const STATUS = {
  Started: 'lozenge-moved',
  Completed: 'lozenge-success',
  Good: 'lozenge-success',
  Error: 'lozenge-error',
  Fail: 'lozenge-error',
  Avarage: 'lozenge-current',
}

const Colors = {
  Started: 'gray',
  Completed: 'green',
  Good: 'green',
  Error: 'red',
  Fail: 'red',
  Avarage: 'yellow',
}

async function Notification( options ) {
  const status = options['status'];
  const notificationOptions = {
    room: options.room,
    args: {
      title: 'Lighthouse Report',
      message: 'Reporter message',
      color: Colors[status],
      card: {
        description: {
          value: options.description,
          format: 'html',
        },
        url: options.url,
        attributes: [{
          label: 'Status',
          value: {
            label: status,
            style: STATUS[status],
          }
        }]
      }
    }
  }
  return await hipchat.send( notificationOptions );
}

module.exports = Notification;

Update format and colors of the notifications


"use strict";

var slicer = Array.prototype.slice

module.exports = compose
function compose() {
  /**
  Returns the composition of a list of functions, where each function
  consumes the return value of the function that follows. In math
  terms, composing the functions `f()`, `g()`, and `h()` produces
  `f(g(h()))`.
  Usage:
  var greet = function(name) { return 'hi: ' + name }
  var exclaim = function(statement) { return statement + '!' }
  var welcome = compose(exclaim, greet)
  welcome('moe')
  // => 'hi: moe!'
  **/

  var lambdas = slicer.call(arguments)
  return function composed() {
    var params = slicer.call(arguments)
    var index = lambdas.length
    var result = [lambdas[--index].apply(this, params)]
    while (0 <= --index) result[0] = lambdas[index].apply(this, result)
    return result[0]
  }
}

"use strict";

var slicer = Array.prototype.slice

module.exports = compose
function compose() {
  /**
  Returns the composition of a list of functions, where each function
  consumes the return value of the function that follows. In math
  terms, composing the functions `f()`, `g()`, and `h()` produces
  `f(g(h()))`.
  Usage:

  var square = function(x) { return x * x }
  var increment = function(x) { return x + 1 }

  var f1 = compose(increment, square)
  f1(5) // => 26

  var f2 = compose(square, increment)
  f2(5) // => 36
  **/

  var lambdas = slicer.call(arguments)
  return function composed() {
    var params = slicer.call(arguments)
    var index = lambdas.length
    var result = [lambdas[--index].apply(this, params)]
    while (0 <= --index) result[0] = lambdas[index].apply(this, result)
    return result[0]
  }
}

Improve examples to make composition order clear


define([
	"Ember",
	"hbs!templates/components/PanelItemComponent"
], function(
	Ember,
	layout
) {

	return Ember.Component.extend({
		layout: layout,
		tagName: "li",
		classNames: [ "panel-item-component" ],

		didInsertElement: function() {
			var self = this;

			this.$( "a" ).addClass( "external-link" ).click(function( e ) {
				e.preventDefault();
				e.stopImmediatePropagation();
				self.send( "openBrowser", this.href );
			});

			this._super.apply( this, arguments );
		},

		action: "openBrowser",

		actions: {
			"openBrowser": function( url ) {
				this.sendAction( "action", url );
			}
		}
	});

});

define([
	"Ember",
	"hbs!templates/components/PanelItemComponent"
], function(
	Ember,
	layout
) {

	return Ember.Component.extend({
		layout: layout,
		tagName: "li",
		classNames: [ "panel-item-component" ],

		didInsertElement: function() {
			var self = this;

			this.$( "a" )
				.addClass( "external-link" )
				.each(function() {
					this.setAttribute( "title", this.href );
				})
				.click(function( e ) {
					e.preventDefault();
					e.stopImmediatePropagation();
					self.send( "openBrowser", this.href );
				});

			this._super.apply( this, arguments );
		},

		action: "openBrowser",

		actions: {
			"openBrowser": function( url ) {
				this.sendAction( "action", url );
			}
		}
	});

});

Add title to anchors in panel descriptions


'use strict'
var net = require('net')
var helper = require(__dirname + '/../test-helper')
var Connection = require(__dirname + '/../../../lib/connection')
var connect = function (callback) {
  var username = helper.args.user
  var database = helper.args.database
  var con = new Connection({stream: new net.Stream()})
  con.on('error', function (error) {
    console.log(error)
    throw new Error('Connection error')
  })
  con.connect(helper.args.port || '5432', helper.args.host || 'localhost')
  con.once('connect', function () {
    con.startup({
      user: username,
      database: database
    })
    con.once('authenticationCleartextPassword', function () {
      con.password(helper.args.password)
    })
    con.once('authenticationMD5Password', function (msg) {
      // need js client even if native client is included
      var client = require(__dirname + '/../../../lib/client')
      var inner = client.md5(helper.args.password + helper.args.user)
      var outer = client.md5(inner + msg.salt.toString('binary'))
      con.password('md5' + outer)
    })
    con.once('readyForQuery', function () {
      con.query('create temp table ids(id integer)')
      con.once('readyForQuery', function () {
        con.query('insert into ids(id) values(1); insert into ids(id) values(2);')
        con.once('readyForQuery', function () {
          callback(con)
        })
      })
    })
  })
}

module.exports = {
  connect: connect
}

'use strict'
var net = require('net')
var helper = require(__dirname + '/../test-helper')
var Connection = require(__dirname + '/../../../lib/connection')
var utils = require(__dirname + '/../../../lib/utils')
var connect = function (callback) {
  var username = helper.args.user
  var database = helper.args.database
  var con = new Connection({stream: new net.Stream()})
  con.on('error', function (error) {
    console.log(error)
    throw new Error('Connection error')
  })
  con.connect(helper.args.port || '5432', helper.args.host || 'localhost')
  con.once('connect', function () {
    con.startup({
      user: username,
      database: database
    })
    con.once('authenticationCleartextPassword', function () {
      con.password(helper.args.password)
    })
    con.once('authenticationMD5Password', function (msg) {
      var inner = utils.md5(helper.args.password + helper.args.user)
      var outer = utils.md5(Buffer.concat([Buffer.from(inner), msg.salt]))
      con.password('md5' + outer)
    })
    con.once('readyForQuery', function () {
      con.query('create temp table ids(id integer)')
      con.once('readyForQuery', function () {
        con.query('insert into ids(id) values(1); insert into ids(id) values(2);')
        con.once('readyForQuery', function () {
          callback(con)
        })
      })
    })
  })
}

module.exports = {
  connect: connect
}

Fix reference to md5 helper in test


import debug from "debug";

const debugMask = process.env.DEBUG || "*";

if ("production" !== process.env.NODE_ENV) {
    debug.enable(debugMask);
} else {
    debug.disable();
}

const log = debug("bootstrap");

function domready(f) {
    if ("undefined" === typeof document) {
        throw new Error(require("path").basename(__filename) + " is browser only");
    }

    if (/interactive|complete/.test(document.readyState)) {
        f();
    } else {
        document.addEventListener("DOMContentLoaded", f);
    }
}

function start() {
    log("starting app");
    require("./app").start();
}

domready(start);

import debug from "debug";

const debugMask = process.env.DEBUG || "*";

if ("production" !== process.env.NODE_ENV) {
    debug.enable(debugMask);
} else {
    debug.disable();
}

const log = debug("bootstrap");

function domready(f) {
    if ("undefined" === typeof document) {
        throw new Error(require("path").basename(__filename) + " is browser only");
    }

    if (/interactive|complete/.test(document.readyState)) {
        f();
    } else {
        document.addEventListener("DOMContentLoaded", f);
    }
}

function start() {
    log("starting app, build version=%s", document.querySelector("html").dataset.buildVersion);
    require("./app").start();
}

domready(start);

Add version log on app start


'use strict';

// Holds the selected target. We need to fetch this from either contextmenu (obviously the best),
// or the mousedown event (filtered by button). The contextmenu event doesn't always provide us
// with the selected element, instead just giving us the HTML element, so we need to check that.
var target;

function getBackgroundImage(element) {
    while (element) {
        var bg = getComputedStyle(element).getPropertyValue('background-image');

        if (bg !== 'none') {
            return bg.match(/url\("?(.+?)"?\)/)[1];
        }

        element = element.parentElement;
    }
}

function setTarget(e) {
    var isActualTarget = e.target !== window.document.documentElement;
    var isMouseEvent = e.type === 'mousedown';
    var isRightClick = isMouseEvent && e.which === 3;

    if (!isActualTarget || (isMouseEvent && isRightClick)) {
        return;
    }

    target = e.target;
}

function addListeners(frame) {
    frame.addEventListener('mousedown', setTarget, false);
    frame.document.addEventListener('contextmenu', setTarget, false);
}

addListeners(window);

// Listen for "get background image" requests, perform lookup and return the result
chrome.runtime.onMessage.addListener(function onMessage(msg, sender, sendResponse) {
    sendResponse(getBackgroundImage(target));
});

'use strict';

// Holds the selected target. We need to fetch this from either contextmenu (obviously the best),
// or the mousedown event (filtered by button). The contextmenu event doesn't always provide us
// with the selected element, instead just giving us the HTML element, so we need to check that.
var target;

function getBackgroundImage(element) {
    while (element) {
        var bg = getComputedStyle(element).getPropertyValue('background-image');

        if (bg !== 'none') {
            return bg.match(/url\("?(.+?)"?\)/)[1];
        }

        element = element.parentElement;
    }
}

function setTarget(e) {
    var isDocument = e.target === window.document.documentElement;
    var isMouseEvent = e.type === 'mousedown';
    var isRightClick = isMouseEvent && e.which === 3;

    // The `contextmenu` event has a thing where it returns the document
    // element as the target instead of the one we actually want
    if (!isMouseEvent && isDocument) {
        return;
    }

    // Disregard left-clicks/middle-clicks
    if (isMouseEvent && !isRightClick) {
        return;
    }

    target = e.target;
}

function addListeners(frame) {
    frame.addEventListener('mousedown', setTarget, false);
    frame.document.addEventListener('contextmenu', setTarget, false);
}

addListeners(window);

// Listen for "get background image" requests, perform lookup and return the result
chrome.runtime.onMessage.addListener(function onMessage(msg, sender, sendResponse) {
    sendResponse(getBackgroundImage(target));
});

Fix bug where document element would never be allowed as valid element


var VERSION='2.0.0';
var scripts = document.getElementsByTagName('script');
var cordovaPath = scripts[scripts.length - 1].src.replace('cordova.js', 'cordova-'+VERSION+'.js');

document.write('<script type="text/javascript" charset="utf-8" src="' + cordovaPath + '"></script>');

function backHome() {
	if (window.device && device.platform && device.platform.toLowerCase() == 'android') {
            navigator.app.backHistory();
	}
	else {
	    window.history.go(-1);
	}
}

var VERSION='2.1.0';
var scripts = document.getElementsByTagName('script');
var cordovaPath = scripts[scripts.length - 1].src.replace('cordova.js', 'cordova-'+VERSION+'.js');

document.write('<script type="text/javascript" charset="utf-8" src="' + cordovaPath + '"></script>');

function backHome() {
	if (window.device && device.platform && device.platform.toLowerCase() == 'android') {
            navigator.app.backHistory();
	}
	else {
	    window.history.go(-1);
	}
}

Update the version reference to 2.1.0


// Copyright (c) 2016, Frappe Technologies and contributors
// For license information, please see license.txt

frappe.ui.form.on('Page', {
	refresh: function(frm) {
		if(!frappe.boot.developer_mode && user != 'Administrator') {
			// make the document read-only
			frm.set_read_only();
		}
		if (!frm.is_new() && !frm.doc.istable) {
			frm.add_custom_button(__('Go to {0} Page', [frm.doc.title || frm.doc.name]), () => {
				frappe.set_route(frm.doc.name);
			});
		}
	}
});

// Copyright (c) 2016, Frappe Technologies and contributors
// For license information, please see license.txt

frappe.ui.form.on('Page', {
	refresh: function(frm) {
		if (!frappe.boot.developer_mode && frappe.session.user != 'Administrator') {
			// make the document read-only
			frm.set_read_only();
		}
		if (!frm.is_new() && !frm.doc.istable) {
			frm.add_custom_button(__('Go to {0} Page', [frm.doc.title || frm.doc.name]), () => {
				frappe.set_route(frm.doc.name);
			});
		}
	}
});

Add a space and user frappe.session.user instead of just user


(function() {
  window.shared || (window.shared = {});
  var dom = window.shared.ReactHelpers.dom;
  var createEl = window.shared.ReactHelpers.createEl;
  var merge = window.shared.ReactHelpers.merge;

  var PropTypes = window.shared.PropTypes;

  /*
  Canonical display of an educator, showing their name as a link to email them.
  */
  var Educator = window.shared.Educator = React.createClass({
    displayName: 'Educator',

    propTypes: {
      educator: React.PropTypes.shape({
        full_name: PropTypes.nullable(React.PropTypes.string.isRequired),
        email: React.PropTypes.string.isRequired
      }).isRequired
    },

    render: function() {
      var educator = this.props.educator;
      var educatorName = (educator.full_name !== null)
        ? educator.full_name
        : educator.email.split('@')[0] + '@';
      return dom.a({
        className: 'Educator',
        href: 'mailto:' + educator.email
      }, educatorName);
    }
  });
})();

(function() {
  window.shared || (window.shared = {});
  var dom = window.shared.ReactHelpers.dom;
  var createEl = window.shared.ReactHelpers.createEl;
  var merge = window.shared.ReactHelpers.merge;

  var PropTypes = window.shared.PropTypes;

  /*
  Canonical display of an educator, showing their name as a link to email them.
  */
  var Educator = window.shared.Educator = React.createClass({
    displayName: 'Educator',

    propTypes: {
      educator: React.PropTypes.shape({
        full_name: PropTypes.nullable(React.PropTypes.string.isRequired),
        email: React.PropTypes.string.isRequired
      }).isRequired
    },

    // Turns SIS format (Curtatone, Joe) -> Joe Curtatone
    educatorName: function(educator) {
      if (educator.full_name === null) return educator.email.split('@')[0] + '@';
      var parts = educator.full_name.split(', ');
      return parts[1] + ' ' + parts[0];
    },

    render: function() {
      var educator = this.props.educator;
      var educatorName = this.educatorName(educator);
      return dom.a({
        className: 'Educator',
        href: 'mailto:' + educator.email
      }, educatorName);
    }
  });
})();

Swap name order so firstname is first in Educator


var exec = require('child_process').exec;

describe('the environment', function(){

  it('can find espeak', function(){
    exec('espeak -h', function(error, stdout, stderr){
      expect(error).toBe(null);
    });
  });

  it('can find mbrola', function(){
    exec('mbrola -h', function(error, stdout, stderr){
      expect(error).toBe(null);
    });
  });

  it('can find lame', function(){
    exec('lame -h', function(error, stdout, stderr){
      expect(error).toBe(null);
    });
  });

});

describe('the environment', function(){

  it('can find espeak', function(done){
    var exec = require('child_process').exec;
    exec('espeak -h', function(error, stdout, stderr){
      expect(stdout).toNotEqual('');
      done();
    });
  });

  it('can find mbrola', function(done){
    var exec = require('child_process').exec;
    exec('mbrola -h', function(error, stdout, stderr){
      expect(stdout).toNotEqual('');
      done();
    });
  });

  it('can find lame', function(done){
    var exec = require('child_process').exec;
    exec('lame -h', function(error, stdout, stderr){
      expect(stdout).toNotEqual('');
      done();
    });
  });

});

Correct environment test to deal with async.


import {findParentFolder, copyHookFiles, verifyHooksFolder} from './fs-utils.js';
import path from 'path';

if (process.env.TEAMCITY_VERSION) {
  console.log('TeamCity Detected not installing the commit hook');
  process.exit(0); // eslint-disable-line no-process-exit
}

let currentPath = process.cwd();

let gitPath;

console.log('Installing JIRA pre-commit hook....');

try {
  gitPath = findParentFolder(currentPath, '.git');
}
catch(error) {
  console.error('Your project needs a git repository to install the hook.');
}

console.log(`Found .git directory at: ${gitPath}`);

let hooksPath = path.join(gitPath, 'hooks');
verifyHooksFolder(hooksPath);

copyHookFiles(gitPath)
  .then(() => console.log('Copied commit hook.'))
  .catch(error => console.error(error));

if (process.env.TEAMCITY_VERSION) {
  console.log('TeamCity Detected not installing the commit hook');
  process.exit(0); // eslint-disable-line no-process-exit
}

import {findParentFolder, copyHookFiles, verifyHooksFolder} from './fs-utils.js';
import path from 'path';

let currentPath = process.cwd();

let gitPath;

console.log('Installing JIRA pre-commit hook....');

try {
  gitPath = findParentFolder(currentPath, '.git');
}
catch(error) {
  console.error('Your project needs a git repository to install the hook.');
}

console.log(`Found .git directory at: ${gitPath}`);

let hooksPath = path.join(gitPath, 'hooks');
verifyHooksFolder(hooksPath);

copyHookFiles(gitPath)
  .then(() => console.log('Copied commit hook.'))
  .catch(error => console.error(error));

Move TeamCity check above the imports


UI.once('init', function(){
	var UI = this
		, should_notify = false
		, notify_cycle = 1
		, interval_id = null
		, missed_messages = 0
		, standard_title = document.title;

	if(/*@cc_on!@*/false){ // check for Internet Explorer
		document.onfocusin = function() {should_notify = false;};
		document.onfocusout = function() {should_notify = true;};
	}else{
		window.onfocus = function() {should_notify = false;};
		window.onblur = function() {should_notify = true;};
	}

	UI.on('after_append', function(data){
		if(should_notify){
			missed_messages += $.isArray(data) ? data.length : 1;
			if(!interval_id){
				interval_id = setInterval(function(){
					if(should_notify){
						document.title = notify_cycle == 1 ? missed_messages + " Unread Messages" : standard_title;
						notify_cycle *= -1;
					}else{
						document.title = standard_title;
						notify_cycle = 1;
						missed_messages = 0;
						clearInterval(interval_id);
					}
				}, 1000);
			}
		}
	});
});

UI.once('init', function(){
	var UI = this
		, should_notify = false
		, notify_cycle = true
		, interval_id = null
		, missed_messages = 0
		, standard_title = document.title;

	if(/*@cc_on!@*/false){ // check for Internet Explorer
		document.onfocusin = function() {should_notify = false;};
		document.onfocusout = function() {should_notify = true;};
	}else{
		window.onfocus = function() {should_notify = false;};
		window.onblur = function() {should_notify = true;};
	}

	UI.on('after_append', function(data){
		if(should_notify){
			missed_messages += $.isArray(data) ? data.length : 1;
			if(!interval_id){
				interval_id = setInterval(function(){
					if(should_notify){
						document.title = notify_cycle ? missed_messages + " Unread Messages" : standard_title;
						notify_cycle = !notify_cycle;
					}else{
						document.title = standard_title;
						notify_cycle = false;
						missed_messages = 0;
						clearInterval(interval_id);
					}
				}, 1000);
			}
		}
	});
});

Change title message cycling variable to bool from int


// function updateCount()  {

//$('#characters').text(cs);
// }
$("#id_lead").on("change paste keyup keydown keypress", function(){
    console.log("funktion");
    var cs = $("#id_lead").val().length;
    document.getElementById("characters").value = cs+"/160";
});

//$('#id_lead').on('input', updateCount());

// function updateCount()  {

//$('#characters').text(cs);
// }

$(document).ready(function()
{
    $("#id_lead").attr('maxlength','160');
});

$("#id_lead").on("change paste keyup keydown keypress", function(){
    console.log("funktion");
    var cs = $("#id_lead").val().length;
    document.getElementById("characters").value = cs+"/160";
});

//$('#id_lead').on('input', updateCount());

Change the article_lead to only allow 160 characters in the input form.


import React from 'react';
import RegisterForm from './RegisterForm';
import Title from '../Title';
import ScrollAnimation from 'react-animate-on-scroll';

import '../../scss/_register.scss';

class Register extends React.Component {
	render() {
		return (
			<main>
				<Title/>
				<ScrollAnimation animateIn='zoomIn' offset={0}>
					<div className='registerform'>
						Register
						<RegisterForm/>
					</div>
				</ScrollAnimation>
			</main>
		);
	}
}

export default Register;

import React from 'react';
import RegisterForm from './RegisterForm';
import Title from '../Title';
import ScrollAnimation from 'react-animate-on-scroll';

import '../../scss/_register.scss';

class Register extends React.Component {
	render() {
		return (
			<main>
				<Title/>
				<ScrollAnimation animateOnce animateIn='zoomIn' offset={0}>
					<div className='registerform'>
						Register
						<RegisterForm/>
					</div>
				</ScrollAnimation>
			</main>
		);
	}
}

export default Register;

Fix issue with register form being animated on scroll


/**
 * Production environment settings
 *
 * This file can include shared settings for a production environment,
 * such as API keys or remote database passwords.  If you're using
 * a version control solution for your Sails app, this file will
 * be committed to your repository unless you add it to your .gitignore
 * file.  If your repository will be publicly viewable, don't add
 * any private information to this file!
 *
 */

module.exports = {

  /***************************************************************************
   * Set the default database connection for models in the production        *
   * environment (see config/connections.js and config/models.js )           *
   ***************************************************************************/

  models: {
     connection: 'productionMongodbServer'
  },

  /***************************************************************************
   * Set the port in the production environment to 80                        *
   ***************************************************************************/

  port: 80,

  /***************************************************************************
   * Set the log level in production environment to "silent"                 *
   ***************************************************************************/

  // log: {
  //   level: "silent"
  // }

};

/**
 * Production environment settings
 *
 * This file can include shared settings for a production environment,
 * such as API keys or remote database passwords.  If you're using
 * a version control solution for your Sails app, this file will
 * be committed to your repository unless you add it to your .gitignore
 * file.  If your repository will be publicly viewable, don't add
 * any private information to this file!
 *
 */

module.exports = {

  /***************************************************************************
   * Set the default database connection for models in the production        *
   * environment (see config/connections.js and config/models.js )           *
   ***************************************************************************/

  models: {
     connection: 'productionMongodbServer'
  },

  /***************************************************************************
   * Set the port                        *
   ***************************************************************************/

  port: 80,

  /***************************************************************************
   * Set the log level                 *
   ***************************************************************************/

  log: {
    level: "warn"
  }

};

Set an explicit log level on live.


/*-----------------------------------------------------------------

Modular - JS Extension
Made by @esr360
http://github.com/esr360/Modular/

-----------------------------------------------------------------*/

//-----------------------------------------------------------------
// Convert CSS config to JS
//-----------------------------------------------------------------

// Get styles' configuration
var stylesConfigJSON = document.getElementById("stylesConfigJSON");

// Remove quotes from computed JSON
function removeQuotes(json) {
    json = json.replace(/^['"]+|\s+|\\|(;\s?})+|['"]$/g, '');
    return json;
}

// Convert computed JSON to camelCase
function camelCase(json) {
	json = json.replace(/-([a-z])/g, function (g) {
		return g[1].toUpperCase();
	});
	return json;
}

// Convert the config to JS
function getStylesConfig(camelCase) {
    var style = null;
    style = window.getComputedStyle(stylesConfigJSON, '::before');
    style = style.content;
	style = removeQuotes(style);
	if(camelCase) {
		style = camelCase(style);
	}
    return JSON.parse(style);
}

// Store configuartion data in a variable
var module = getStylesConfig();

// CamelCase the config
var moduleCC = getStylesConfig(camelCase);

// Create a global variable to select each main component in the DOM
var componentIndex = 0;
$.each(module, function(component) {
	var componentCC = moduleCC[Object.keys(moduleCC)[componentIndex]]['name'];
	window[componentCC] = '.' + component + ', [class*="' + component + '-"]';
	componentIndex++;
});


import DataLoader from 'dataloader';
import cache from '../cache';
import timer from '../timer';
import { throttled } from '../throttle';
import { error, verbose } from '../loggers';

export default api => {
  return new DataLoader(keys => Promise.all(keys.map(key => {
    const clock = timer(key);

    clock.start();

    return new Promise((resolve, reject) => {
      cache
        .get(key)
        .then(data => {
          resolve(data);

          verbose(`Cached: ${key}`);
          clock.end();
          throttled(key, () => {
            api(key)
              .then(({ body }) => {
                verbose(`Refreshing: ${key}`);
                cache.set(key, body);
              });
          });
        }, () => {
          api(key)
            .then(({ body }) => {
              resolve(body);

              verbose(`Requested (Uncached): ${key}`);
              clock.end();
              cache.set(key, body);
            })
            .catch(err => {
              reject(err);
              error(key, err);

              // If the error is a 404 then the object
              // may exist in cache and may have been unpublished
              if (err.statusCode === 404) {
                cache.delete(key);
              }
            });
        });
    });
  })), {
    batch: false,
    cache: true,
  });
};

import DataLoader from 'dataloader';
import cache from '../cache';
import timer from '../timer';
import { throttled } from '../throttle';
import { error, verbose } from '../loggers';

export default api => {
  return new DataLoader(keys => Promise.all(keys.map(key => {
    const clock = timer(key);

    clock.start();

    return new Promise((resolve, reject) => {
      cache
        .get(key)
        .then(data => {
          resolve(data);

          verbose(`Cached: ${key}`);
          clock.end();
          throttled(key, () => {
            api(key)
              .then(({ body }) => {
                verbose(`Refreshing: ${key}`);
                cache.set(key, body);
              })
              .catch(err => {
                if (err.statusCode === 404) { // Unpublished
                  cache.delete(key);
                }
              });
          });
        }, () => {
          api(key)
            .then(({ body }) => {
              resolve(body);

              verbose(`Requested (Uncached): ${key}`);
              clock.end();
              cache.set(key, body);
            })
            .catch(err => {
              reject(err);
              error(key, err);
            });
        });
    });
  })), {
    batch: false,
    cache: true,
  });
};

Move to the correct catch


'use strict';

/**
 * PhoneNumber
 *
 **/

var Helpers = require('./helpers');
var definitions = require('./definitions');

/**
 * PhoneNumber#phoneNumber(format = null) -> String
 * - format (string): input format for example '#####-####'
 *
 * Generate phone number by format. Each sharp symbol will be replaced by number.
 * If format is null, then randomly
 **/
exports.phoneNumber = function ( format ){
  if (!format) {
    format = Helpers.randomize(definitions.phone_formats());
  }
  return Helpers.replaceSymbolWithNumber(format);
};

'use strict';

/**
 * PhoneNumber
 *
 **/

var Helpers = require('./helpers');
var Charlatan = require('./charlatan');

/**
 * PhoneNumber#phoneNumber() -> String
 *
 * Generate phone number.
 **/
exports.phoneNumber = function() {
  return Charlatan.numerify(Charlatan.fetch('phone_number.formats'));
};

/**
 * PhoneNumber#phoneNumber() -> String
 *
 * Generate cell phone number. If locale hasn't `cell_phone` section then use `phone`.
 **/
exports.cellPhone = function() {
  try {
    Charlatan.numerify(Charlatan.fetch('phone_number.cell_phone'));
  }
  catch (e) {
    return exports.phoneNumber();
  }
};

Move PhoneNumber section to new api


var express = require('express'),
    mongoose = require('mongoose');

var db = mongoose.connect('mongodb://192.168.88.103:27017/bookApi');

var Book = require('./models/bookModel');
var app = express();

var port = process.env.PORT || 3000;

var bookRouter = express.Router();

bookRouter.route('/books')
    .get(function(req,res){
        Book.find(function(err,books){
            if(err)
                res.status(500).send(err);
            else
                res.json(books)
        });
});

app.use('/api', bookRouter);

app.get('/',function(req,res){
    res.send('Welcome to API');
});

app.listen(port, function(){
    console.log('Running on port' + port);
});

var express = require('express'),
    mongoose = require('mongoose');

var Schema = mongoose.Schema;
var db = mongoose.connect('mongodb://127.0.0.1:27017/bookApi');

var Book = require('./models/bookModel');
var app = express();

var port = process.env.PORT || 3000;

var bookRouter = express.Router();

bookRouter.route('/books')
    .get(function(req,res){
        Book.find({},function(err,books){
            console.log("aki");
            if(err)
                res.status(500).send(err);
            else
                res.json(books);
        });
});

bookRouter.route('/books')
    .post(function(req,res){
        Book.create(req.body,function(err,books){
            if(err)
                res.status(500).send(err);
            else
                res.json(books);
        });
});

app.use('/api', bookRouter);

app.get('/',function(req,res){
    res.send('Welcome to API');
});

app.listen(port, function(){
    console.log('Running on port' + port);
});

Update get e post for mongoose


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

function config(options){
  var configFile = path.join(options.project.root, 'config', 'ember-try.js');
  if(fs.existsSync(configFile)){
    return require(configFile);
  }
  else {
    return defaultConfig();
  }
}

module.exports = config;

function defaultConfig(){
  return {
    scenarios: [
      {
        name: 'ember-1.10',
        dependencies: {
          "ember": "1.10.0"
        }
      },
      {
        name: 'ember-1.11.0-beta.5',
        dependencies: {
          "ember": "1.11.0-beta.5"
        }
      }

    ]
  }
}

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

function config(options){
  var configFile = path.join(options.project.root, 'config', 'ember-try.js');
  if(fs.existsSync(configFile)){
    return require(configFile);
  }
  else {
    return defaultConfig();
  }
}

module.exports = config;

function defaultConfig(){
  return {
    scenarios: [
      {
        name: 'ember-1.10',
        dependencies: {
          "ember": "1.10.0"
        }
      },
      {
        name: 'ember-1.11.0',
        dependencies: {
          "ember": "1.11.0"
        }
      },
      {
        name: 'ember-1.12.0-beta.1',
        dependencies: {
          "ember": "1.12.0-beta.1"
        }
      }

    ]
  }
}

Update for 1.11.0 and 1.12.0-beta.1.


#!/usr/bin/env node

'use strict';

var wrap = require('wrap-stream');

var result = '', args = process.argv.slice(2);

//no args passed
if (!args.length) {
	process.stdout.write([
		'',
		'Wrap stdin',
		'',
		'Usage',
		'  echo <string> | wrap "before" "after"',
		'',
		'Example',
		'  cat index.js | wrap ";(function($){" "})(jQuery);"'
	].join('\n  '));
	process.exit(1);
}

process.stdin
.pipe(wrap(args[0], args[1]))
.pipe(process.stdout);

#!/usr/bin/env node

'use strict';

var wrap = require('wrap-stream');
var pkg = require('./package.json');

var result = '', args = process.argv.slice(2);

if (/-v|--version/.test(args[0])) {
	console.log(pkg.version);
	return;
}

//show help
if (!args.length || /-h|--help/.test(args[0]) || process.stdin.isTTY) {
	process.stdout.write([
		'',
		'Wrap stdin',
		'',
		'Usage',
		'  echo <string> | wrap "before" "after"',
		'',
		'Example',
		'  cat index.js | wrap ";(function($){" "})(jQuery);"'
	].join('\n  '));
	return;
}

process.stdin
.pipe(wrap(args[0], args[1]))
.pipe(process.stdout);

Add version and help option


Components.utils.import('resource://greasemonkey/util.js');

var GM_MenuCommander = {};

GM_MenuCommander.createMenuItem = function(command) {
  var menuItem = document.createElement("menuitem");
  menuItem.setAttribute("label", command.name);
  if ('function' == typeof command.commandFunc) {
    menuItem.addEventListener("command", command.commandFunc, true);
  }

  if (command.accessKey) {
      menuItem.setAttribute("accesskey", command.accessKey);
  }

  return menuItem;
};

GM_MenuCommander.onPopupShowing = function(aMenuPopup) {
  GM_util.emptyEl(aMenuPopup);

  // Add menu items for commands for the active window.
  var haveCommands = false;
  GM_BrowserUI.gmSvc.withAllMenuCommandsForWindowId(
      GM_util.windowId(gBrowser.contentWindow),
      function(index, command) {
        if (command.frozen) return;
        aMenuPopup.insertBefore(
            GM_MenuCommander.createMenuItem(command),
            aMenuPopup.firstChild);
        haveCommands = true;
      });
  aMenuPopup.parentNode.disabled = !haveCommands;
};

Components.utils.import('resource://greasemonkey/util.js');

var GM_MenuCommander = {};

GM_MenuCommander.createMenuItem = function(command) {
  var menuItem = document.createElement("menuitem");
  menuItem.setAttribute("label", command.name);
  if ('function' == typeof command.commandFunc) {
    menuItem.addEventListener("command", command.commandFunc, true);
  }

  if (command.accessKey) {
      menuItem.setAttribute("accesskey", command.accessKey);
  }

  return menuItem;
};

GM_MenuCommander.onPopupShowing = function(aMenuPopup) {
  GM_util.emptyEl(aMenuPopup);

  // Add menu items for commands for the active window.
  var haveCommands = false;
  var windowId = GM_util.windowId(gBrowser.contentWindow);

  if(windowId) {
    GM_BrowserUI.gmSvc.withAllMenuCommandsForWindowId(
        windowId,
        function(index, command) {
          if (command.frozen) return;
          aMenuPopup.insertBefore(
              GM_MenuCommander.createMenuItem(command),
              aMenuPopup.firstChild);
          haveCommands = true;
        });
  }
  aMenuPopup.parentNode.disabled = !haveCommands;
};

Add missing null check in GM_menuCommander.onPopupShowing.


document.addEventListener('DOMContentLoaded', () => {
  const contactForm = document.getElementById('contact-form');
  const formSubmitButton = document.getElementById('contact-form-submit');
  formSubmitButton.addEventListener('click', () => {
    fetch('https://arkisio.now.sh/contact', {
      method: 'POST',
      body: new FormData(contactForm),
    }).then(response => response.text()).then((body) => {
      if (body === 'true') {
        contactForm.innerHTML = 'Success :D';
      } else {
        contactForm.innerHTML = 'Failure :/';
      }
    });
  });
});

document.addEventListener('DOMContentLoaded', () => {
  const contactForm = document.getElementById('contact-form');
  const formSubmitButton = document.getElementById('contact-form-submit');
  formSubmitButton.addEventListener('click', () => {
    fetch(contactForm.getAttribute('action'), {
      method: contactForm.getAttribute('method'),
      body: new FormData(contactForm),
    }).then(response => response.text()).then((body) => {
      if (body === 'true') {
        contactForm.innerHTML = 'Success :D';
      } else {
        contactForm.innerHTML = 'Failure :/';
      }
    });
  });
});

Use the form's attributes for URL and method instead of hardcoding values


import sqlite3 from 'sqlite3';
import { createLogObject } from './csv.js';

const SQL = `
    INSERT INTO log (event_id, date, id, name, type, value, battery, dark, daylight)
    VALUES ($event_id, $date, $id, $name, $type, $value, $battery, $dark, $daylight)
`;

export default class SQLiteLogger {
    constructor(dbURL) {
        this.db = new sqlite3.Database(dbURL);
        //date,id,name,type,value,battery,dark,daylight
        this.db.run(`CREATE TABLE IF NOT EXISTS log (
            event_id TEXT PRIMARY KEY,
            date TEXT NOT NULL,
            id NUMERIC NOT NULL,
            name TEXT NOT NULL,
            type TEXT NOT NULL,
            value TEXT NOT NULL,
            battery NUMERIC,
            dark TEXT,
            daylight TEXT
        )`);
    }

    log = s => {
        const msg = createLogObject(s);
        if (msg.type) {
            const dbInsert = {};
            for (const property in msg) {
                dbInsert[`$${property}`] = msg[property];
            }
            try {
                this.db.run(SQL, dbInsert);
            } catch (err) {
                console.exception("Could not insert log line", dbInsert)
            }
        }
    };
}

import sqlite3 from 'sqlite3';
import { createLogObject } from './csv.js';

const SQL_PROPERTIES = [
    'event_id', 'date', 'id', 'name', 'type', 'value', 'battery', 'dark', 'daylight'
]
const SQL = `
    INSERT INTO log (${SQL_PROPERTIES.join(", ")})
    VALUES (${SQL_PROPERTIES.map(v => `\$${v}`).join(", ")})
`;

export default class SQLiteLogger {
    constructor(dbURL) {
        this.db = new sqlite3.Database(dbURL);
        //date,id,name,type,value,battery,dark,daylight
        this.db.run(`CREATE TABLE IF NOT EXISTS log (
            event_id TEXT PRIMARY KEY,
            date TEXT NOT NULL,
            id NUMERIC NOT NULL,
            name TEXT NOT NULL,
            type TEXT NOT NULL,
            value TEXT NOT NULL,
            battery NUMERIC,
            dark TEXT,
            daylight TEXT
        )`);
    }

    log = s => {
        const msg = createLogObject(s);
        if (msg.type) {
            const dbInsert = SQL_PROPERTIES.reduce((pv, cv) => {
                pv[`\$${cv}`] = '';
                return pv;
            }, {});
            for (const property in msg) {
                if (msg[property] != null) {
                    dbInsert[`$${property}`] = msg[property];
                }
            }
            this.db.run(SQL, dbInsert, (err) => {
                if (err) {
                    console.log("Error while executing", dbInsert)
                    return console.error(err)
                }
            });
        }
    };
}

Make sure all properties are set during insert and errors are logged properly


import nodemailer from 'nodemailer';
import config     from '../../config';
import templates  from './templates';

// library configuration ---------------------------------------

// setup email transporter
var transporter = nodemailer.createTransport({
	service: 'Gmail',
	auth: {
		user: config.email.user,
		pass: config.email.pass
	}
});

// public api --------------------------------------------------

export default {

	/**
	 * Send
	 *
	 * Takes an email address, a subject, a template name
	 * and a data object and immediately sends an email.
	 */
	send (email, callback) {

		// configure mail options
		var mailOptions = {
			from: config.email.user,
			to: email.to,
			subject: email.subject,
			html: templates[email.template](email.data)
		};

		// send email
		transporter.sendMail(mailOptions, callback);

	}

};

import nodemailer from 'nodemailer';
import config     from '../../config';
import templates  from './templates';

// library configuration ---------------------------------------

// setup email transporter
var transporter = nodemailer.createTransport({
	service: config.notifications.email.service,
	auth: {
		user: config.notifications.email.user,
		pass: config.notifications.email.pass
	}
});

// public api --------------------------------------------------

export default {

	/**
	 * Send
	 *
	 * Takes an email address, a subject, a template name
	 * and a data object and immediately sends an email.
	 */
	send (email, callback) {

		// configure mail options
		var mailOptions = {
			from: config.notifications.email.user,
			to: email.to,
			subject: email.subject,
			html: templates[email.template](email.data)
		};

		// send email
		transporter.sendMail(mailOptions, callback);

	}

};

Update configuration format and add configurable mail service


(function ($) {
    var _accountService = abp.services.app.account;

    var _$form = $('form[name=TenantChangeForm]');

    _$form.closest('div.modal-content').find(".save-button").click(function (e) {
        e.preventDefault();

        var tenancyName = _$form.find('input[name=TenancyName]').val();

        if (!tenancyName) {
            abp.multiTenancy.setTenantIdCookie(null);
            location.reload();
            return;
        }

        _accountService.isTenantAvailable({
            tenancyName: tenancyName
        }).done(function (result) {
            switch (result.state) {
                case 1: //Available
                    abp.multiTenancy.setTenantIdCookie(result.tenantId);
                    //_modalManager.close();
                    location.reload();
                    return;
                case 2: //InActive
                    abp.message.warn(abp.utils.formatString(abp.localization
                        .localize("TenantIsNotActive", "AbpProjectName"),
                        this.tenancyName));
                    break;
                case 3: //NotFound
                    abp.message.warn(abp.utils.formatString(abp.localization
                        .localize("ThereIsNoTenantDefinedWithName{0}", "AbpProjectName"),
                        this.tenancyName));
                    break;
            }
        });
    });
})(jQuery);

(function ($) {
    var _accountService = abp.services.app.account;

    var _$form = $('form[name=TenantChangeForm]');

    function switchToSelectedTenant () {

        var tenancyName = _$form.find('input[name=TenancyName]').val();

        if (!tenancyName) {
            abp.multiTenancy.setTenantIdCookie(null);
            location.reload();
            return;
        }

        _accountService.isTenantAvailable({
            tenancyName: tenancyName
        }).done(function (result) {
            switch (result.state) {
            case 1: //Available
                abp.multiTenancy.setTenantIdCookie(result.tenantId);
                //_modalManager.close();
                location.reload();
                return;
            case 2: //InActive
                abp.message.warn(abp.utils.formatString(abp.localization
                    .localize("TenantIsNotActive", "AbpProjectName"),
                    this.tenancyName));
                break;
            case 3: //NotFound
                abp.message.warn(abp.utils.formatString(abp.localization
                    .localize("ThereIsNoTenantDefinedWithName{0}", "AbpProjectName"),
                    this.tenancyName));
                break;
            }
        });
    }

    //Handle save button click
    _$form.closest('div.modal-content').find(".save-button").click(function(e) {
        e.preventDefault();
        switchToSelectedTenant();
    });

    //Handle enter key
    _$form.find('input').on('keypress', function (e) {
        if (e.which === 13) {
            e.preventDefault();
            switchToSelectedTenant();
        }
    });
})(jQuery);

Handle enter key on tenant switch.


import settings from './settings';

/*
  The findAll test helper uses `querySelectorAll` to search inside the test
  DOM (based on app configuration for the rootElement).

  Alternalively, a second argument may be passed which is an element as the
  DOM context to search within.

  @method findAll
  @param {String} CSS selector to find elements in the test DOM
  @param {HTMLElement} contextEl to query within, query from its contained DOM
  @return {NodeList} A (non-live) list of zero or more HTMLElement objects
  @public
*/
export function findAll(selector, contextEl) {
  let result;
  if (contextEl instanceof HTMLElement) {
    result = contextEl.querySelectorAll(selector);
  } else {
    result = document.querySelectorAll(`${settings.rootElement} ${selector}`);
  }
  return toArray(result);
}

function toArray(nodelist) {
  let array = new Array(nodelist.length);
  for (let i = 0; i < nodelist.length; i++) {
    array[i] = nodelist[i];
  }
  return array;
}

import settings from './settings';

/*
  The findAll test helper uses `querySelectorAll` to search inside the test
  DOM (based on app configuration for the rootElement).

  Alternalively, a second argument may be passed which is an element as the
  DOM context to search within.

  @method findAll
  @param {String} CSS selector to find elements in the test DOM
  @param {HTMLElement} contextEl to query within, query from its contained DOM
  @return {Array} An array of zero or more HTMLElement objects
  @public
*/
export function findAll(selector, contextEl) {
  let result;
  if (contextEl instanceof HTMLElement) {
    result = contextEl.querySelectorAll(selector);
  } else {
    result = document.querySelectorAll(`${settings.rootElement} ${selector}`);
  }
  return toArray(result);
}

function toArray(nodelist) {
  let array = new Array(nodelist.length);
  for (let i = 0; i < nodelist.length; i++) {
    array[i] = nodelist[i];
  }
  return array;
}

Update inline documentation for `NodeList` -> `Array` change.


import {Router} from 'express'
import models from '../models';
import tasks from './tasks'

export default () => {
	let app = Router({mergeParams: true})

	app.use('/:userId/tasks', tasks())

	app.get('/', (req, res) => {
		models.User.findAll({
			attributes: ['id', 'email']
		}).then((users) => {
			console.log(users)
			res.send(users)
		})
	})

	app.get('/new', (req, res) => {
		res.render('users/new')
	})

	app.get('/:id', (req, res) => {
		models.User.find({
			where: {
				id: req.params.id
			},
			include: [models.Task]
		}).then((user) => {
			res.render('users/show', {user})
		})
	})

	app.post('/', (req, res) => {
		models.User.create(req.body.user).then((user) => {
			req.session.userId = user.dataValues.id
			req.app.locals.userId = user.dataValues.id
			res.redirect('/');
		})
	})
	return app
}

import {Router} from 'express'
import models from '../models';
import tasks from './tasks'

export default () => {
	let app = Router({mergeParams: true})

	app.use('/:userId/tasks', tasks())

	app.get('/', (req, res) => {
		models.User.findAll({
			attributes: ['id', 'email']
		}).then((users) => {
			res.send(users)
		})
	})

	app.get('/new', (req, res) => {
		res.render('users/new')
	})

	app.get('/:id', (req, res) => {
		models.User.find({
			where: {
				id: req.params.id
			},
			include: [models.Task]
		}).then((user) => {
			res.render('users/show', {user})
		})
	})

	app.post('/', (req, res) => {
		models.User.create(req.body).then((user) => {
			req.session.userId = user.dataValues.id
			req.app.locals.userId = user.dataValues.id
			req.session.save()
			// add validations and errors
			res.send(user);
		})
	})
	return app
}

Update route to serve correct json object


var argv = require('yargs').argv;

module.exports = {
  plugins: {
    istanbul: {
      dir: './coverage',
      reporters: ['text-summary', 'lcov'],
      include: [
        '**/vaadin-date-picker/src/*.html'
      ],
      exclude: [],
      thresholds: {
        global: {
          statements: 100
        }
      }
    }
  },

  registerHooks: function(context) {
    var saucelabsPlatforms = [
      // 'macOS 10.12/iphone@10.3',
      // 'macOS 10.12/ipad@10.3',
      'Windows 10/microsoftedge@15',
      'Windows 10/internet explorer@11',
      'macOS 10.12/safari@11.0',
      'macOS 9.3.2/iphone@9.3'
    ];

    var cronPlatforms = [
      'Windows 10/chrome@59',
      'Windows 10/firefox@54'
    ];

    if (argv.env === 'saucelabs') {
      context.options.plugins.sauce.browsers = saucelabsPlatforms;

    } else if (argv.env === 'saucelabs-cron') {
      context.options.plugins.sauce.browsers = cronPlatforms;
    }
  }
};

var argv = require('yargs').argv;

module.exports = {
  plugins: {
    istanbul: {
      dir: './coverage',
      reporters: ['text-summary', 'lcov'],
      include: [
        '**/vaadin-date-picker/src/*.html'
      ],
      exclude: [],
      thresholds: {
        global: {
          statements: 91
        }
      }
    }
  },

  registerHooks: function(context) {
    var saucelabsPlatforms = [
      // 'macOS 10.12/iphone@10.3',
      // 'macOS 10.12/ipad@10.3',
      'Windows 10/microsoftedge@15',
      'Windows 10/internet explorer@11',
      'macOS 10.12/safari@11.0',
      'macOS 9.3.2/iphone@9.3'
    ];

    var cronPlatforms = [
      'Windows 10/chrome@59',
      'Windows 10/firefox@54'
    ];

    if (argv.env === 'saucelabs') {
      context.options.plugins.sauce.browsers = saucelabsPlatforms;

    } else if (argv.env === 'saucelabs-cron') {
      context.options.plugins.sauce.browsers = cronPlatforms;
    }
  }
};

Set code coverage threshold to 91


'use strict';

/*
 * Module for juggling call state.
 */

angular.module('call', [])
.factory('callFactory', function (apiFactory) {
    var callStates = {
        error: 'error',
        not_answered: 'not_answered',
        succeeded: 'succeeded'
    };

    var currentCallID = null;

    var initiateCall = function (recipient) {
        return new Promise(function (resolve, reject) {
            apiFactory.call.initiate(recipient)
                .then(function (newCall) {
                    currentCallID = newCall.CallId;
                    resolve(newCall);
                })
                .catch(function (callErr) {
                    reject(callErr);
                });
        });
    };

    var endCall = function (finalStatus) {
        return new Promise(function (resolve, reject) {
            if (!currentCallID) {
                return reject(new Error('Current call ID is null!'));
            }

            apiFactory.call.end(finalStatus, currentCallID)
                .then(function (endedCall) {
                    resolve(endedCall);
                })
                .catch(function (error) {
                    reject(error);
                })
                .finally(function () {
                    currentCallID = null;
                });
        });
    };

    return {
        initiateCall: initiateCall,
        callStates: callStates,
        callError: function () {
            return endCall(callStates.error);
        },
        callSucceeded: function () {
            return endCall(callStates.succeeded);
        },
        callNotAnswered: function () {
            return endCall(callStates.not_answered);
        }
    };
});

'use strict';

/*
 * Module for juggling call state.
 */

angular.module('call', [])
.factory('callFactory', function (apiFactory) {
    var callStates = {
        error: 'error',
        not_answered: 'not_answered',
        succeeded: 'succeeded'
    };

    var currentCallID = null;

    var initiateCall = function (recipient) {
        return new Promise(function (resolve, reject) {
            apiFactory.call.initiate(recipient)
                .then(function (newCall) {
                    console.log(newCall);
                    currentCallID = newCall.id;
                    resolve(newCall);
                })
                .catch(function (callErr) {
                    reject(callErr);
                });
        });
    };

    var endCall = function (finalStatus) {
        return new Promise(function (resolve, reject) {
            if (!currentCallID) {
                return reject(new Error('Current call ID is null!'));
            }

            apiFactory.call.end(finalStatus, currentCallID)
                .then(function (endedCall) {
                    resolve(endedCall);
                })
                .catch(function (error) {
                    reject(error);
                })
                .finally(function () {
                    currentCallID = null;
                });
        });
    };

    return {
        initiateCall: initiateCall,
        callStates: callStates,
        callError: function () {
            return endCall(callStates.error);
        },
        callSucceeded: function () {
            return endCall(callStates.succeeded);
        },
        callNotAnswered: function () {
            return endCall(callStates.not_answered);
        }
    };
});

Fix call id parameter name


import path from 'path'

const library = 'style-script'
const srcPath = path.resolve(__dirname, 'src')
const libPath = path.resolve(__dirname, 'lib')

export default {
  devtool: 'source-map',

  context: srcPath,

  entry: 'index.js',

  resolve: {
    root: srcPath,
  },

  output: {
    library,
    libraryTarget: 'commonjs2',
    filename: `${library}.js`,
    path: libPath,
  },

  module: {
    loaders: [
      {
        test: /\.js$/,
        include: srcPath,
        loader: 'babel',
      },
    ],
  },

  externals: {
    lodash: '_',
  },
}

import path from 'path'

const library = 'style-script'
const srcPath = path.resolve(__dirname, 'src')
const libPath = path.resolve(__dirname, 'lib')

export default {
  devtool: 'source-map',

  context: srcPath,

  entry: 'index.js',

  resolve: {
    root: srcPath,
  },

  output: {
    library,
    libraryTarget: 'commonjs2',
    filename: `${library}.js`,
    path: libPath,
  },

  module: {
    loaders: [
      {
        test: /\.js$/,
        include: srcPath,
        loader: 'babel',
      },
    ],
  },

  externals: {
    lodash: true,
  },
}

Fix `externals` option of webpack config


import Ember from 'ember';
import Base from 'simple-auth/authenticators/base';
import config from 'ttz/config/environment';

export default Base.extend({
  torii: null,

  restore(data) {
    return Ember.RSVP.resolve(data);
  },

  authenticate(state) {
    if (config.environment !== 'production') {
      return Ember.RSVP.resolve({
        accessToken: 'token',
        scope: 'identify,read'
      });
    }

    return new Ember.RSVP.Promise((resolve, reject) => {
      this.torii.open('slack-oauth2', state).then(data => {
        return Ember.$.ajax({
          type: 'POST',
          url: '/api/tokens',
          data,
          dataType: 'json'
        });
      }).then(data => {
        resolve(data);
      }).catch(error => {
        reject(error);
      });
    });
  }
});

import Ember from 'ember';
import Base from 'simple-auth/authenticators/base';
import config from 'ttz/config/environment';

export default Base.extend({
  torii: null,

  restore(data) {
    return Ember.RSVP.resolve(data);
  },

  authenticate(state) {
    if (config.environment !== 'production') {
      return Ember.RSVP.resolve({
        accessToken: 'token',
        scope: 'identify,users:read'
      });
    }

    return new Ember.RSVP.Promise((resolve, reject) => {
      this.torii.open('slack-oauth2', state).then(data => {
        return Ember.$.ajax({
          type: 'POST',
          url: '/api/tokens',
          data,
          dataType: 'json'
        });
      }).then(data => {
        resolve(data);
      }).catch(error => {
        reject(error);
      });
    });
  }
});

Update to new more restrictive Slack OAuth2 scopes


'use strict';
var Support = require('../support');

before(function() {
  var dialect = Support.getTestDialect();

  if (dialect !== 'postgres' && dialect !== 'postgres-native') {
    return;
  }
  return Support.sequelize.query('CREATE EXTENSION IF NOT EXISTS hstore', null, {raw: true});
});

beforeEach(function() {
  return Support.clearDatabase(this.sequelize);
});

module.exports = Support;

'use strict';
var Support = require('../support');

before(function() {
  var dialect = Support.getTestDialect();

  if (dialect !== 'postgres' && dialect !== 'postgres-native') {
    return;
  }
  return Support.sequelize.query('CREATE EXTENSION IF NOT EXISTS hstore', null, {raw: true});
});

before(function() {
  var dialect = Support.getTestDialect();

  if (dialect !== 'postgres' && dialect !== 'postgres-native') {
    return;
  }
  return Support.sequelize.query('CREATE EXTENSION IF NOT EXISTS btree_gist', null, {raw: true});
});

beforeEach(function() {
  return Support.clearDatabase(this.sequelize);
});

module.exports = Support;

Enable btree_gist extension before running tests


#!/bin/env node

var http = require('http');

var host = process.env.OPENSHIFT_NODEJS_IP;
var port = process.env.OPENSHIFT_NODEJS_PORT || 8080

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('paintr. See http://robmyers.org/paintr for details.\n');
}).listen(port, host);

console.log('Server running at http://' + host + ":" + port + "/");

#!/bin/env node

// Copyright 2013 Rob Myers <rob@robmyers.org>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

"use strict";

var http = require('http');

var host = process.env.OPENSHIFT_NODEJS_IP;
var port = process.env.OPENSHIFT_NODEJS_PORT || 8080

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('paintr. See http://robmyers.org/paintr for details.\n');
}).listen(port, host);

console.log('Server running at http://' + host + ":" + port + "/");

Add license header to web app.


import 'babel-polyfill';
import $ from 'jquery';
import Backbone from 'backbone';
import AppConfig from 'config';
import User from 'models/user';
import Storage from 'services/storage';

const SESSION_KEY = AppConfig.sessionKey;
const STORAGE_KEY = AppConfig.storageKey;
const EMAIL_KEY = 'email';
const currentUser = new User(Storage.get(STORAGE_KEY));

class Session {
  static currentUser() {
    return currentUser;
  }

  static create() {
    const deferred = $.Deferred();

    if (!this.isLoggedIn()) {
      this.currentUser().signIn().done(() => {
        this.currentUser().unsetPrivateFields();
        this.save();
        this.trigger('create');
        deferred.resolve();
      }).fail(deferred.reject);
    }

    return deferred.promise();
  }

  static destroy() {
    Storage.remove(STORAGE_KEY);
    this.currentUser().clear();
    this.trigger('destroy');
  }

  static save() {
    Storage.set(STORAGE_KEY, this.currentUser().toJSON());
  }

  static isLoggedIn() {
    return !this.currentUser().isNew();
  }

  static get token() {
    return this.currentUser().get(SESSION_KEY);
  }

  static get email() {
    return this.currentUser().get(EMAIL_KEY);
  }
}

Object.assign(Session, Backbone.Events);

export default Session;

import 'babel-polyfill';
import $ from 'jquery';
import Backbone from 'backbone';
import AppConfig from 'config';
import User from 'models/user';
import Storage from 'services/storage';

const SESSION_KEY = AppConfig.sessionKey;
const STORAGE_KEY = AppConfig.storageKey;
const EMAIL_KEY = 'email';
let currentUser = null;

class Session {
  static currentUser() {
    return currentUser || (currentUser = new User(Storage.get(STORAGE_KEY)));
  }

  static create() {
    const deferred = $.Deferred();

    if (!this.isLoggedIn()) {
      this.currentUser().signIn().done(() => {
        this.currentUser().unsetPrivateFields();
        this.save();
        this.trigger('create');
        deferred.resolve();
      }).fail(deferred.reject);
    }

    return deferred.promise();
  }

  static destroy() {
    Storage.remove(STORAGE_KEY);
    this.currentUser().clear();
    this.trigger('destroy');
  }

  static save() {
    Storage.set(STORAGE_KEY, this.currentUser().toJSON());
  }

  static isLoggedIn() {
    return !this.currentUser().isNew();
  }

  static get token() {
    return this.currentUser().get(SESSION_KEY);
  }

  static get email() {
    return this.currentUser().get(EMAIL_KEY);
  }
}

Object.assign(Session, Backbone.Events);

export default Session;

Refactor current user method inside Session service


import React, { PropTypes } from 'react';
import { push } from 'react-router-redux';
import { connect } from 'react-redux';
import Project from './../components/Project'
import Button from './../components/Button';

const ProjectPage = ({project, editProjectLink}) => {
    return (
        <div>
            <Project project={project} />
            <Button onClick={editProjectLink} label="Add locale" />
        </div>
    );
};

ProjectPage.propTypes = {
    project: PropTypes.object.isRequired,
    editProjectLink: PropTypes.func.isRequired
};

const mockProject = {
    id: 1,
    locales: [
        {ident: "en_US", language: "English", country: "USA"},
        {ident: "de_DE", language: "German", country: "Germany"}
    ]
};

const mapStateToProps = (state) => {
    return {
        project: mockProject
    };
};

const mapDispatchToProps = (dispatch, ownProps) => {
    return {
        editProjectLink: () => {
            dispatch(push(`/projects/${ownProps.params.projectId}/locales/new`))
        }
    };
};

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

import React, { PropTypes } from 'react';
import { push } from 'react-router-redux';
import { connect } from 'react-redux';
import { projectActions } from './../../core/projects'
import Project from './../components/Project'
import Button from './../components/Button';

class ProjectPage extends React.Component {
    componentDidMount() {
        this.props.fetchProject();
    }

    static propTypes = {
        project: PropTypes.object.isRequired,
        editProjectLink: PropTypes.func.isRequired
    };

    render () {
        if (!this.props.project) {
            return (
Empty
);
        }
        return (

                <Project project={this.props.project} />
                <Button onClick={this.props.editProjectLink} label="Add locale" />

        );
    }
}

const mapStateToProps = (state, ownProps) => {
    return {
        project: state.projects.projects.find((element) => {
            return element.id === ownProps.params.projectId;
        })
    };
};

const mapDispatchToProps = (dispatch, ownProps) => {
    return {
        editProjectLink: () => {
            dispatch(push(`/projects/${ownProps.params.projectId}/locales/new`))
        },
        fetchProject: () => {
            dispatch(projectActions.fetchProject(ownProps.params.projectId));
        }
    };
};

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

Add fetch project on mount


var port = process.env.port || 5000;
var io = require('socket.io').listen(port);
var redis = require('redis');
var url = require('url');
var redisURL = url.parse(process.env.REDISCLOUD_URL);

var sub = redis.createClient(redisURL.port, redisURL.hostname, {no_ready_check: true});
sub.auth(redisURL.auth.split(":")[1]);

sub.subscribe('games');

io.sockets.on('connection', function (socket) {

    socket.on('room', function(room){
        socket.join(room);
    })
    sub.on('message', function(channel, message){
        console.log(message);
        io.sockets.in(message).emit('reload', 'hello');
    });
});

Make the port binding work?


var locastyle = locastyle || {};

locastyle.toggleText = (function() {
  'use strict';

  var config = {
    trigger: '[data-ls-module=toggleText]',
    triggerChange: 'toggleText:change'
  };

  function eventHandler(el, target, text) {
    el.trigger(config.triggerChange, [target, text]);
  }

  function bindToggle(el) {
    var $target      = el.data('target-text') ? $(el.data('target-text')) : el;
    var textChange   = el.data('toggle-text');
    var textOriginal = $target.text();

    el.data('toggle-text', textOriginal);
    $target.text(textChange);

    eventHandler(el, $target, textChange);
  }

  function bindEventOnClick() {
    $(config.trigger).on('click.ls', function(event) {
      event.preventDefault();
      bindToggle($(this));
    });
  }

  function unbind() {
    $(config.trigger).off('click.ls');
  }

  function init() {
    unbind();
    bindEventOnClick();
  }

  return {
    init: init
  };

}());

var locastyle = locastyle || {};

locastyle.toggleText = (function() {
  'use strict';

  var config = {
    trigger: '[data-ls-module="toggleText"]',
    triggerChange: 'toggleText:change'
  };

  function eventHandler(el, target, text) {
    el.trigger(config.triggerChange, [target, text]);
  }

  function bindToggle(el) {
    var $target      = el.data('target-text') ? $(el.data('target-text')) : el;
    var textChange   = el.data('toggle-text');
    var textOriginal = $target.text();

    el.data('toggle-text', textOriginal);
    $target.text(textChange);

    eventHandler(el, $target, textChange);
  }

  function bindEventOnClick() {
    $(config.trigger).on('click.ls', function(event) {
      event.preventDefault();
      bindToggle($(this));
    });
  }

  function unbind() {
    $(config.trigger).off('click.ls');
  }

  function init() {
    unbind();
    bindEventOnClick();
  }

  return {
    init: init
  };

}());

Improve the toggle text trigger selector


module.exports = {
  root: true,
  extends: ['eslint:recommended', 'prettier'],
  plugins: ['prettier'],
  parserOptions: {
    ecmaVersion: 2017,
    sourceType: 'module',
  },
  env: {
    browser: true,
  },
  rules: {
    'prettier/prettier': ['error', {
      singleQuote: true,
      trailingComma: 'es5',
      printWidth: 100,
    }],
  },
  overrides: [
    {
      files: ['index.js'],
      excludedFiles: ['addon-test-support/**', 'tests/**'],
      parserOptions: {
        sourceType: 'script',
      },
      env: {
        browser: false,
        node: true,
      },
      plugins: ['node'],
      rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, {
        // add your custom rules and overrides for node files here
      }),
    },
    {
      files: ['tests/**/*.js'],
      env: {
        qunit: true
      }
    },
    {
      files: ['index.js', 'addon-test-support/**/*.js', 'config/**/*.js'],
      plugins: [
        'disable-features',
      ],
      rules: {
        'disable-features/disable-async-await': 'error',
        'disable-features/disable-generator-functions': 'error',
      }
    },
    {
      files: ['addon-test-support/**/*.js'],
      rules: {
        'valid-jsdoc': 'warn',
      }
    },
  ]
};

module.exports = {
  root: true,
  extends: ['eslint:recommended', 'prettier'],
  plugins: ['prettier'],
  parserOptions: {
    ecmaVersion: 2017,
    sourceType: 'module',
  },
  env: {
    browser: true,
  },
  rules: {
    'prettier/prettier': ['error', {
      singleQuote: true,
      trailingComma: 'es5',
      printWidth: 100,
    }],
  },
  overrides: [
    {
      files: ['index.js'],
      excludedFiles: ['addon-test-support/**', 'tests/**'],
      parserOptions: {
        sourceType: 'script',
      },
      env: {
        browser: false,
        node: true,
      },
      plugins: ['node'],
      rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, {
        // add your custom rules and overrides for node files here
      }),
    },
    {
      files: ['tests/**/*.js'],
      env: {
        qunit: true
      }
    },
    {
      files: ['index.js', 'addon-test-support/**/*.js', 'config/**/*.js'],
      plugins: [
        'disable-features',
      ],
      rules: {
        'disable-features/disable-async-await': 'error',
        'disable-features/disable-generator-functions': 'error',
      }
    },
    {
      files: ['addon-test-support/**/*.js'],
      rules: {
        'valid-jsdoc': 'error',
      }
    },
  ]
};

Make valid-jsdoc an error (instead of warning)


module.exports = (ctx) => ({
  map: ctx.file.dirname.startsWith('docs') ? false : {
    inline: false,
    annotation: true,
    sourcesContent: true
  },
  plugins: {
    autoprefixer: {
      browsers: [
        //
        // Official browser support policy:
        // https://v4-alpha.getbootstrap.com/getting-started/browsers-devices/#supported-browsers
        //
        'Chrome >= 45', // Exact version number here is kinda arbitrary
        'Firefox ESR',
        // Note: Edge versions in Autoprefixer & Can I Use refer to the EdgeHTML rendering engine version,
        // NOT the Edge app version shown in Edge's "About" screen.
        // For example, at the time of writing, Edge 20 on an up-to-date system uses EdgeHTML 12.
        // See also https://github.com/Fyrd/caniuse/issues/1928
        'Edge >= 12',
        'Explorer >= 10',
        // Out of leniency, we prefix these 1 version further back than the official policy.
        'iOS >= 9',
        'Safari >= 9',
        // The following remain NOT officially supported, but we're lenient and include their prefixes to avoid severely breaking in them.
        'Android >= 4.4',
        'Opera >= 30'
      ]
    }
  }
})

module.exports = (ctx) => ({
  map: ctx.file.dirname.startsWith('docs') ? false : {
    inline: false,
    annotation: true,
    sourcesContent: true
  },
  plugins: {
    autoprefixer: {
      browsers: [
        //
        // Official browser support policy:
        // https://getbootstrap.com/docs/4.0/getting-started/browsers-devices/#supported-browsers
        //
        'Chrome >= 45', // Exact version number here is kinda arbitrary
        'Firefox ESR',
        // Note: Edge versions in Autoprefixer & Can I Use refer to the EdgeHTML rendering engine version,
        // NOT the Edge app version shown in Edge's "About" screen.
        // For example, at the time of writing, Edge 20 on an up-to-date system uses EdgeHTML 12.
        // See also https://github.com/Fyrd/caniuse/issues/1928
        'Edge >= 12',
        'Explorer >= 10',
        // Out of leniency, we prefix these 1 version further back than the official policy.
        'iOS >= 9',
        'Safari >= 9',
        // The following remain NOT officially supported, but we're lenient and include their prefixes to avoid severely breaking in them.
        'Android >= 4.4',
        'Opera >= 30'
      ]
    }
  }
})

Update URL for supported browsers


module.exports = {
  root: true,
  parser: "@typescript-eslint/parser",
  parserOptions: {
    project: './tsconfig.json'
  },
  ignorePatterns: ["*.txt"],
  plugins: ["@typescript-eslint"],
  overrides: [
    {
      files: ["*.js", "*.mjs"],
      rules: {
        "@typescript-eslint/explicit-function-return-type": "off",
      },
    },
  ],
  extends: [
    "eslint:recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:@typescript-eslint/recommended-requiring-type-checking",
  ],
  rules: {
    "@typescript-eslint/camelcase": "off",
    "@typescript-eslint/class-name-casing": "off",
    "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
  },
};

module.exports = {
  root: true,
  parser: "@typescript-eslint/parser",
  parserOptions: {
    project: "./tsconfig.json",
  },
  ignorePatterns: ["*.txt", "/dist"],
  plugins: ["@typescript-eslint"],
  overrides: [
    {
      files: ["*.js", "*.mjs"],
      rules: {
        "@typescript-eslint/explicit-function-return-type": "off",
      },
    },
  ],
  extends: [
    "eslint:recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:@typescript-eslint/recommended-requiring-type-checking",
  ],
  rules: {
    "@typescript-eslint/camelcase": "off",
    "@typescript-eslint/class-name-casing": "off",
    "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
  },
};

Add /dist to prettier ignore


'use strict';

angular.module('fivefifteenApp')
  .controller('MainCtrl', function ($scope, $routeParams, Data, Steps) {

    // Simple Data service to persist form values.
    $scope.data = Data;

    $scope.path = $routeParams.stepName;

    $scope.steps = Steps.data;

    Steps.promise.$on('loaded', function(values) {
      // If we get values, store it both in the scope and Steps.
      if (typeof values !== "undefined") {
        angular.extend(Steps.data, values);
      }
    });
  })

  // scope data is not persistent across views so we use a simple service.
  .factory('Data', function () {
    // This variable holds all of the text used on the site.
    return {};
  })

  .factory('Steps', function($firebase) {
    var url = new Firebase("https://fivefifteen.firebaseio.com/steps"),
        ref = $firebase(url);

    return {
      "promise": ref,
      "data": {}
    };
  });

'use strict';

angular.module('fivefifteenApp')
  .controller('MainCtrl', function ($scope, $routeParams, Data, Steps) {

    // Simple Data service to persist form values.
    $scope.data = Data;

    $scope.path = $routeParams.stepName;

    $scope.steps = Steps.data;
  })

  // scope data is not persistent across views so we use a simple service.
  .factory('Data', function () {
    // This variable holds all of the text used on the site.
    return {};
  })

  .factory('Steps', function($firebase) {
    var url = new Firebase("https://fivefifteen.firebaseio.com/steps"),
        ref = $firebase(url),
        factory = { "data": {} };

    ref.$on('loaded', function(values) {
      // If we get values, store it both in the scope and Steps.
      if (typeof values !== "undefined") {
        angular.extend(factory.data, values);
      }
    });

    return factory;
  });

Move promise handler into the factory.


module.exports = function(grunt) {
    "use strict";
    var exec = require("child_process").exec, fs = require("fs");
    grunt.initConfig({
        compress : {
            main : {
                options : {
                    archive : "./build/digexp-toolkit.zip"
                },
                files : [{
                    expand : true,
                    cwd : ".",
                    src : ["install.cmd", "install.sh", "readme.md", "wcm-design.tar.gz", "dashboard.tar.gz", "sp-server.tar.gz", "LICENSE", "NOTICE"],
                    dest : "/",
                    filter : "isFile"
                }]
            }
        }
    });
    grunt.loadNpmTasks("grunt-contrib-compress");
    grunt.registerTask("default", []);
    grunt.registerTask("build", ["compress:main", "_build"]);
    /**
     * Builds the app
     */
    grunt.registerTask("_build", function() {
    });
};

module.exports = function(grunt) {
    "use strict";
    var exec = require("child_process").exec, fs = require("fs");
    grunt.initConfig({
        compress : {
            main : {
                options : {
                    archive : "./build/digexp-toolkit.zip"
                },
                files : [{
                    expand : true,
                    cwd : ".",
                    src : ["install.cmd", "install.sh", "uninstall.cmd", "uninstall.sh", "readme.md", "wcm-design.tar.gz", "dashboard.tar.gz", "sp-server.tar.gz", "LICENSE", "NOTICE"],
                    dest : "/",
                    filter : "isFile"
                }]
            }
        }
    });
    grunt.loadNpmTasks("grunt-contrib-compress");
    grunt.registerTask("default", []);
    grunt.registerTask("build", ["compress:main", "_build"]);
    /**
     * Builds the app
     */
    grunt.registerTask("_build", function() {
    });
};

Add uninstall into the build


/**
 * Course Model
 * @file app/models/CourseModel.js
 */

"use strict";

module.exports = function(mongoose) {
  var Schema = mongoose.Schema;

  var CourseSchema = new Schema({
    name: {
      type: String,
      required: true
    },
    description: String,
    type: {
      type: Number,
      min: 4,
      max: 6,
      required: true
    }
  });

  var CourseModel = mongoose.model("Course", CourseSchema);

  return CourseModel;
};

/**
 * Course Model
 * @file app/models/CourseModel.js
 */

"use strict";

module.exports = function(mongoose) {
  var Schema = mongoose.Schema;

  var CourseSchema = new Schema({
    name: {
      type: String,
      required: true
    },
    description: String,
    type: {
      type: Number,
      min: 3,
      max: 6,
      required: true
    }
  });

  var CourseModel = mongoose.model("Course", CourseSchema);

  return CourseModel;
};

Fix : Le niveau peut aller de 6 à 3 (plutot que 4)


(function(){
    'use strict';
    angular.module('mtTimeValidator', [])
            .constant('TIMEREGEXP', /^(0?[1-9]|1[012])(:[0-5]\d)[APap][mM]$/)
            .directive('time', time);

    function time (TIMEREGEXP) {
      return {
        require: 'ngModel',
        limit: 'A',
        link: function(scope, elm, attrs, ctrl) {
          ctrl.$validators.time = function(modelValue, viewValue) {
            if (ctrl.$isEmpty(modelValue)) {
              // consider empty models to be valid
              return true;
            }

            if (TIMEREGEXP.test(viewValue)) {
              // it is valid
              return true;
            }

            // it is invalid
            return false;
          };
        }
      };
    }
})();

(function(){
    'use strict';
    angular.module('mtTimeValidator', [])
            .constant('TIMEREGEXP', /^(0?[1-9]|1[012])(:[0-5]\d)[APap][mM]$/)
            .directive('time', time);

    function time (TIMEREGEXP) {
      return {
        require: 'ngModel',
        restrict: 'A',
        link: function(scope, elm, attrs, ctrl) {
          ctrl.$validators.time = function(modelValue, viewValue) {
            if (ctrl.$isEmpty(modelValue)) {
              // consider empty models to be valid
              return true;
            }

            if (TIMEREGEXP.test(viewValue)) {
              // it is valid
              return true;
            }

            // it is invalid
            return false;
          };
        }
      };
    }
})();

Fix Restrict on mtTimeValidator Directive


(function($) {
  $.fn.extend({
    hotwire: function(delegate) {
      var target = this,
          args   = $.makeArray(arguments),
          events = args.slice(1, args.length);

      $(events).each(function(i, eventName) {
        var words     = $(eventName.split(/[_:]/)),
            titleized = words.map(function(i, word) {
              if (i == 0) { return word; }

              var firstChar = word.charAt(0).toUpperCase(),
                  rest      = word.slice(1, word.length);

              return firstChar + rest;
            }).toArray().join(""),

            handler = delegate[titleized];

        target.bind(eventName, handler);
      });

      return this;
    }
  });
})(jQuery);

(function($) {
  $.fn.extend({
    hotwire: function(delegate) {
      var target = this,
          args   = $.makeArray(arguments),
          events = args.slice(1, args.length);

      $(events).each(function(i, eventName) {
        var words     = $(eventName.split(/[_:]/)),
            titleized = words.map(function(i, word) {
              if (i === 0) { return word; }

              var firstChar = word.charAt(0).toUpperCase(),
                  rest      = word.slice(1, word.length);

              return firstChar + rest;
            }).toArray().join(""),

            handler = delegate[titleized];

        target.bind(eventName, handler);
      });

      return this;
    }
  });
})(jQuery);

Replace == with === to test for equality


qx.Class.define("vcms.client.widgets.WidgetList",
{
    extend : qx.ui.tabview.TabView,

    construct : function()
    {
        this.base(arguments);

        for (i = 0, n = 3; i < n; i++)
        {
            var page = new qx.ui.tabview.Page("Page #" + i);
            page.setLayout(new qx.ui.layout.VBox().set({ spacing: 2 }));
            page.add(new qx.ui.form.List());
            page.add(new qx.ui.form.Button("Remove widget from page"));
            this.add(page);
        }
    }
});

qx.Class.define("vcms.client.widgets.WidgetList",
{
    extend : qx.ui.tabview.TabView,

    construct : function()
    {
        this.base(arguments);

        for (i = 0, n = 3; i < n; i++)
        {
            var page = new qx.ui.tabview.Page("Page #" + i);
            page.setLayout(new qx.ui.layout.VBox().set({ spacing: 2 }));
            page.add(new qx.ui.form.List());
            var buttons_container = new qx.ui.container.Composite(new qx.ui.layout.HBox());
            page.add(buttons_container);
            buttons_container.add(new qx.ui.form.Button("Remove widget from page"), {flex: 0});
            buttons_container.add(new qx.ui.core.Spacer(), {flex: 1});
            this.add(page);
        }
    }
});

Set the button to be at the minimum width possible.


import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { DropdownButton, MenuItem } from 'react-bootstrap'

import Icon from '../narrative/icon'

import { MainPanelContent, setMainPanelContent } from '../../actions/ui'

// TODO: make menu items configurable via props/config

class AppMenu extends Component {
  static propTypes = {
    setMainPanelContent: PropTypes.func
  }

  render () {
    return (
      <div className='app-menu'>
        <DropdownButton title={(<Icon type='bars' />)} noCaret className='app-menu-button' id='app-menu'>
          <MenuItem onClick={() => {
            window.location.href = '/'
          }}><Icon type='undo' /> Start Over</MenuItem>
          <MenuItem onClick={() => {
            this.props.setMainPanelContent(MainPanelContent.ROUTE_VIEWER)
          }}><Icon type='bus' /> Route Viewer</MenuItem>
        </DropdownButton>
      </div>
    )
  }
}

// connect to the redux store

const mapStateToProps = (state, ownProps) => {
  return {
  }
}

const mapDispatchToProps = {
  setMainPanelContent
}

export default connect(mapStateToProps, mapDispatchToProps)(AppMenu)

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { DropdownButton, MenuItem } from 'react-bootstrap'

import Icon from '../narrative/icon'

import { MainPanelContent, setMainPanelContent } from '../../actions/ui'

// TODO: make menu items configurable via props/config

class AppMenu extends Component {
  static propTypes = {
    setMainPanelContent: PropTypes.func
  }

  render () {
    const { reactRouterConfig } = this.props

    let href = '/'
    if (reactRouterConfig && reactRouterConfig.basename) {
      href += reactRouterConfig.basename
    }

    return (
      <div className='app-menu'>
        <DropdownButton title={(<Icon type='bars' />)} noCaret className='app-menu-button' id='app-menu'>
          <MenuItem onClick={() => {
            window.location.href = href
          }}><Icon type='undo' /> Start Over</MenuItem>
          <MenuItem onClick={() => {
            this.props.setMainPanelContent(MainPanelContent.ROUTE_VIEWER)
          }}><Icon type='bus' /> Route Viewer</MenuItem>
        </DropdownButton>
      </div>
    )
  }
}

// connect to the redux store

const mapStateToProps = (state, ownProps) => {
  return {
    reactRouterConfig: state.otp.config.reactRouter
  }
}

const mapDispatchToProps = {
  setMainPanelContent
}

export default connect(mapStateToProps, mapDispatchToProps)(AppMenu)

Fix "Start Over" menu link to work w/ react-router basename


import { expect } from 'chai';

import { launchNewNotebook } from '../../build/main/launch';
describe('launch', () => {
  describe('launchNewNotebook', () => {
    it('launches a window', () => {
      return launchNewNotebook().then(win => {
        expect(win).to.not.be.undefined;
      });
    });
  });
});

import { expect } from 'chai';

import { launchNewNotebook } from '../../build/main/launch';

var ipc = require('ipc');

describe('launch', () => {
  describe('launchNewNotebook', () => {
    it('launches a window', function() {
      // The render window may take a while to display on slow machines.
      // Set the timeout to 5s
      this.timeout(5000);

      return launchNewNotebook().then(win => {
        expect(win).to.not.be.undefined;
        return new Promise(resolve => {

          // Query selector all and make sure the length for .cells is
          // greater than 0.  Use IPC to return the results from the
          // render window.
          ipc.on('queryLength', function (event, value) {
            expect(value).to.be.greaterThan(0);
            resolve();
          });
          win.webContents.executeJavaScript(`
            var ipc = require('ipc');
            ipc.send('queryLength', document.querySelectorAll('div.cell').length);
          `);
        });
      });
    });
  });
});

Make sure a cell exists in the round trip test


Template.postsList.helpers({
    posts: function () {
        return Posts.find();
    }
});

Template.postsList.helpers({
    posts: function () {
        return Posts.find({}, {sort: {submitted: -1}});
    }
});

Sort the post list by submitted.


export default {
  title: 'Core|Errors',
};

// Comment this story out until Chromatic supports skipping stories via parameters (soon I promise!)
// export const exception = () => {
//   throw new Error('error');
// };
// exception.title = 'story throws exception';
// exception.parameters = {
//   storyshots: { disable: true },
// };

export const errors = () => null;
errors.title = 'story errors';
errors.parameters = {
  notes: 'Story does not return something react can render',
  storyshots: { disable: true },
};

export default {
  title: 'Core|Errors',
};

export const exception = () => {
  throw new Error('error');
};
exception.title = 'story throws exception';
exception.parameters = {
  storyshots: { disable: true },
  chromatic: { disable: true },
};

export const errors = () => null;
errors.title = 'story errors';
errors.parameters = {
  notes: 'Story does not return something react can render',
  storyshots: { disable: true },
};

FIX disable exception throwing story in Chromatic


(function(){
  'use strict';

  angular.module('app.states')
    .run(appRun);

  /** @ngInject */
  function appRun(routerHelper) {
    routerHelper.configureStates(getStates());
  }

  function getStates() {
    return {
      'orders.details': {
        url: '/:orderId',
        templateUrl: 'app/states/orders/details/details.html',
        controller: StateController,
        controllerAs: 'vm',
        title: 'Order Details',
        resolve: {
          order: resolveOrder
        }
      }
    };
  }

  /** @ngInject */
  function resolveOrder($stateParams, Order){
    return Order.get({
      id: $stateParams.orderId,
      'includes[]': ['project']
    }).$promise;
  }

  function StateController($state, lodash, order) {
    var vm = this;
    vm.order = order;

    vm.activate = activate;

    function activate() {
    }
  }
})();

(function(){
  'use strict';

  angular.module('app.states')
    .run(appRun);

  /** @ngInject */
  function appRun(routerHelper) {
    routerHelper.configureStates(getStates());
  }

  function getStates() {
    return {
      'orders.details': {
        url: '/details/:orderId',
        templateUrl: 'app/states/orders/details/details.html',
        controller: StateController,
        controllerAs: 'vm',
        title: 'Order Details',
        resolve: {
          order: resolveOrder
        }
      }
    };
  }

  /** @ngInject */
  function resolveOrder($stateParams, Order){
    return Order.get({
      id: $stateParams.orderId,
      'includes[]': ['product', 'project', 'service']
    }).$promise;
  }

  /** @ngInject */
  function StateController(order) {
    var vm = this;

    vm.order = order;

    vm.activate = activate;

    function activate() {

    }
  }
})();

Change order-details path and Order includes


/**
 * In case of an unsupported browser this will add a body className and register a click handler.
 * Upon clicking the button a cookie will be set to stop showing the information on subsequent loads.
 *
 * @param {boolean} support indicates whether the feature is supported
 */
const bodyClassNameFallback = support => {
  const COOKIE_NAME = 'browser-fallback-raised'
  const CLASS_NAME = '-browser-not-supported'
  const DISMISS_ID = 'dismiss-browser-not-supported'
  if (!support) {
    const alreadyDetected = document.cookie.indexOf(COOKIE_NAME) !== -1
    if (!alreadyDetected) {
      document.body.className += ' ' + CLASS_NAME
      const $element = document.getElementById(DISMISS_ID)
      if ($element !== null) {
        $element.onclick = function() {
          document.cookie = COOKIE_NAME + '=true'
          const reg = new RegExp('(\\s|^)' + CLASS_NAME + '(\\s|$)')
          document.body.className = document.body.className.replace(reg, ' ')
        }
      }
    }
  }
}

export default bodyClassNameFallback

Add quick documentation to fallback strategy


'use strict';

// FIXME
return;

var matrix = require( 'dstructs-matrix' );
var isMatrix = require( './../lib' );

var mat = matrix( [10,10] );

console.log( isMatrix( mat ) );
// => true

console.log( isMatrix( [] ) );
// => false

console.log( isMatrix( {} ) );
// => false

console.log( isMatrix({
	'data': new Int8Array( 10 ),
	'shape': [5,2],
	'strides': [2,1],
	'dtype': 'int8',
	'length': 10
}));
// => false

console.log( isMatrix( null ) );
// => false

'use strict';

// FIXME
// var matrix = require( 'dstructs-matrix' );
// var isMatrix = require( './../lib' );

// var mat = matrix( [10,10] );

// console.log( isMatrix( mat ) );
// // => true

// console.log( isMatrix( [] ) );
// // => false

// console.log( isMatrix( {} ) );
// // => false

// console.log( isMatrix({
// 	'data': new Int8Array( 10 ),
// 	'shape': [5,2],
// 	'strides': [2,1],
// 	'dtype': 'int8',
// 	'length': 10
// }));
// // => false

// console.log( isMatrix( null ) );
// // => false

Comment out until matrices are supported


#!/usr/bin/env node

var filename = '{test.wasm}';
var ret = read(filename, "binary");

function instantiate(bytes, imports) {
  return WebAssembly.compile(bytes).then(
    m => new WebAssembly.Instance(m, imports), reason => console.log(reason) );
}

var dependencies = {
    "global": {},
    "env": {}
};
dependencies["global.Math"] = Math;

instantiate( ret, dependencies ).then(
  instance => console.log( instance.exports[scriptArgs[0]]( ...scriptArgs.slice(1) ) ),
  reason => console.log(reason)
);

#!/usr/bin/env node

var filename = '{test.wasm}';
var ret = read(filename, "binary");

function instantiate(bytes, imports) {
  return WebAssembly.compile(bytes).then(
    m => new WebAssembly.Instance(m, imports), reason => console.log(reason) );
}

var dependencies = {
    "global": {},
    "env": {}
};
dependencies["global.Math"] = Math;

function callExport(instance) {
    try{
        console.log( instance.exports[scriptArgs[0]]( ...scriptArgs.slice(1) ) );
    }catch(err){
        console.log(err)
    }
}

instantiate( ret, dependencies ).then(
  instance => callExport(instance),
  reason => console.log(reason)
);

Add try catch to execution errors


// Common configuration for webpacker loaded from config/webpack/paths.yml

const { join, resolve } = require('path');
const { env } = require('process');
const { safeLoad } = require('js-yaml');
const { readFileSync } = require('fs');

const configPath = resolve('config', 'webpack');
const loadersDir = join(__dirname, 'loaders');
const paths = safeLoad(readFileSync(join(configPath, 'paths.yml'), 'utf8'))[env.NODE_ENV || 'development'];
const devServer = safeLoad(readFileSync(join(configPath, 'development.server.yml'), 'utf8'))[env.NODE_ENV || 'development'];

// Compute public path based on environment and CDN_HOST in production
const ifHasCDN = env.CDN_HOST !== undefined && env.NODE_ENV === 'production';
const devServerUrl = `http://${env.LOCAL_DOMAIN || devServer.host}:${devServer.port}/${paths.entry}/`;
const publicUrl = ifHasCDN ? `${env.CDN_HOST}/${paths.entry}/` : `/${paths.entry}/`;
const publicPath = env.NODE_ENV !== 'production' ? devServerUrl : publicUrl;

module.exports = {
  devServer,
  env,
  paths,
  loadersDir,
  publicUrl,
  publicPath,
};

// Common configuration for webpacker loaded from config/webpack/paths.yml

const { join, resolve } = require('path');
const { env } = require('process');
const { safeLoad } = require('js-yaml');
const { readFileSync } = require('fs');

const configPath = resolve('config', 'webpack');
const loadersDir = join(__dirname, 'loaders');
const paths = safeLoad(readFileSync(join(configPath, 'paths.yml'), 'utf8'))[env.NODE_ENV || 'development'];
const devServer = safeLoad(readFileSync(join(configPath, 'development.server.yml'), 'utf8'))[env.NODE_ENV || 'development'];

// Compute public path based on environment and CDN_HOST in production
const ifHasCDN = env.CDN_HOST !== undefined && env.NODE_ENV === 'production';
const devServerUrl = `http://${(env.LOCAL_DOMAIN || devServer.host).split(':')[0]}:${devServer.port}/${paths.entry}/`;
const publicUrl = ifHasCDN ? `${env.CDN_HOST}/${paths.entry}/` : `/${paths.entry}/`;
const publicPath = env.NODE_ENV !== 'production' ? devServerUrl : publicUrl;

module.exports = {
  devServer,
  env,
  paths,
  loadersDir,
  publicUrl,
  publicPath,
};

Fix a bug that urls which webpack-dev-server exports are broken


var pg = null

try {
	pg = require('pg')
	pg = pg.native
} catch (__e) {
	if (!pg) {
		exports.create = exports.createQuery = function () {
			throw new Exception("pg driver failed to load, please `npm install pg`")
		}
		return
	}
}

var Transaction = require('../transaction')
var helpers = require('../helpers')

exports.createConnection = function (url, callback) {
	var opts = helpers.prepareUrl(url)
	  , conn = new pg.Client(opts)

	if (callback) {
		conn.connect(function (err) {
			if (err) callback(err)
			else callback(null, conn)
		})
	} else {
		conn.connect()
	}
	conn.begin = Transaction.createBeginMethod(exports.createQuery)
	//conn.query = wrapQueryMethod(conn.query)
	return conn
}

// Create a Query object that conforms to the Any-DB interface
exports.createQuery = function (stmt, params, callback) {
	return new pg.Query(stmt, params, callback)
}

var pg = null
  , native = null

try {
	pg = require('pg')
	native = pg.native
} catch (__e) {
	if (!pg) {
		exports.create = exports.createQuery = function () {
			throw new Exception("pg driver failed to load, please `npm install pg`")
		}
		return
	}
}

var Transaction = require('../transaction')
var helpers = require('../helpers')

exports.forceJS = false

exports.createConnection = function (url, callback) {
	var opts = helpers.prepareUrl(url)
		, scheme = url.protocol
		, backend = (exports.forceJS || !native) ? pg : native
		, conn = backend.Client(opts)

	if (callback) {
		conn.connect(function (err) {
			if (err) callback(err)
			else callback(null, conn)
		})
	} else {
		conn.connect()
	}

	conn.begin = Transaction.createBeginMethod(exports.createQuery)
	//conn.query = wrapQueryMethod(conn.query)
	return conn
}

// Create a Query object that conforms to the Any-DB interface
exports.createQuery = function (stmt, params, callback) {
	var backend = (exports.forceJS || !native) ? pg : native
	return new backend.Query(stmt, params, callback)
}

Allow disabling of native PG driver


#!/usr/bin/env node

var express = require('express');
var app = express();

var server = require('http').Server(app);
var io = require('socket.io')(server);

var monitor = require('./monitor');
monitor.on('data', function(data) {
  io.emit('data', data);
});

app.use(express.static(__dirname + '/public'));

server.listen(parseInt(process.argv[2]) || 3000);

var sockets = {};

io.on('connection', function (socket) {
  sockets[socket.id] = socket;

  var history = monitor.getHistory();
  socket.emit('history', history);

  socket.on('disconnect', function() {
    delete sockets[socket.id];
  });
});

#!/usr/bin/env node

var express = require('express');
var app = express();

var server = require('http').Server(app);
var io = require('socket.io')(server);

var monitor = require('./monitor');
monitor.on('data', function(data) {
  io.emit('data', data);
});

app.use(express.static(__dirname + '/public'));

var port = parseInt(process.argv[2]) || 3000;
server.listen(port);
console.log("Mintr is listening on port " + port);

var sockets = {};

io.on('connection', function (socket) {
  sockets[socket.id] = socket;

  var history = monitor.getHistory();
  socket.emit('history', history);

  socket.on('disconnect', function() {
    delete sockets[socket.id];
  });
});

Add a "listening on port..." message