/*
---

name: Event.CSS3

description: Provides CSS3 events.

license: MIT-style license.

authors:
	- Jean-Philippe Dery (jeanphilippe.dery@gmail.com)

requires:
	- Custom-Event/Element.defineCustomEvent

provides:
	- Event.CSS3

...
*/

(function() {

// TODO: Property detect if a prefix-less version is available

var a = '';
var t = '';

if (Browser.safari || Browser.chrome || Browser.platform.ios) {
	a = 'webkitAnimationEnd';
	t = 'webkitTransitionEnd';
} else if (Browser.firefox) {
	a = 'animationend';
	t = 'transitionend';
} else if (Browser.opera) {
	a = 'oAnimationEnd';
	t = 'oTransitionEnd';
} else if (Browser.ie) {
	a = 'msAnimationEnd';
	t = 'msTransitionEnd';
}

Element.NativeEvents[a] = 2;
Element.NativeEvents[t] = 2;
Element.Events['transitionend'] = { base: t, onAdd: function(){}, onRemove: function(){} };
Element.Events['animationend'] = { base: a, onAdd: function(){}, onRemove: function(){} };

Element.defineCustomEvent('owntransitionend', {
	base: 'transitionend',
	condition: function(e) {
		e.stop();
		return e.target === this;
	}
});

Element.defineCustomEvent('ownanimationend', {
	base: 'animationend',
	condition: function(e) {
		e.stop();
		return e.target === this;
	}
})

})();

/*
---

name: Event.CSS3

description: Provides CSS3 events.

license: MIT-style license.

authors:
	- Jean-Philippe Dery (jeanphilippe.dery@gmail.com)

requires:
	- Custom-Event/Element.defineCustomEvent

provides:
	- Event.CSS3

...
*/

(function() {

// TODO: Property detect if a prefix-less version is available

var a = 'animationend';
var t = 'transitionend';

Element.NativeEvents[a] = 2;
Element.NativeEvents[t] = 2;
Element.Events['transitionend'] = { base: t, onAdd: function(){}, onRemove: function(){} };
Element.Events['animationend'] = { base: a, onAdd: function(){}, onRemove: function(){} };

Element.defineCustomEvent('owntransitionend', {
	base: 'transitionend',
	condition: function(e) {
		e.stop();
		return e.target === this;
	}
});

Element.defineCustomEvent('ownanimationend', {
	base: 'animationend',
	condition: function(e) {
		e.stop();
		return e.target === this;
	}
})

})();

Fix an issue with prefixing animationend and transitionend


define(["lib/reqwest", "lib/lodash", "lib/knockout", "githubEvent"],
  function (reqwest, _, ko, GithubEvent) {
    var githubApi = {};

    var rootUrl = "https://api.github.com";

    function getOrganisationMembers(orgName) {
        return reqwest({
            url: rootUrl + '/orgs/' + orgName + '/members',
            type: 'json'
        });
    }

    function getUserEvents(username) {
        return reqwest({
            url: rootUrl + '/users/' + username + '/events',
            type: 'json'
        }).then(function (response) {
            return _.map(response, function (response) {
                return new GithubEvent(response);
            });
        });
    }

    // Returns a KO array which is then updated as data arrives
    githubApi.getOrganisationEvents = function (orgName) {
        var events = ko.observableArray([]);

        var loading = 1;

        getOrganisationMembers(orgName).then(function (members) {
            loading = members.length;

            _.each(members, function (member) {
                getUserEvents(member.login).then(function (userEvents) {
                    events(events().concat(userEvents));
                    loading--;
                });
            });
        });

        events.loadingComplete = ko.computed(function () {
            return loading === 0;
        });

        return events;
    };

    return githubApi;
});

define(["lib/reqwest", "lib/lodash", "lib/knockout", "githubEvent"],
  function (reqwest, _, ko, GithubEvent) {
    var githubApi = {};

    var rootUrl = "https://api.github.com";

    function getOrganisationMembers(orgName) {
        return reqwest({
            url: 'https://api.github.com/orgs/' + orgName + '/members',
            type: 'json'
        });
    }

    function getUserEvents(username) {
        return reqwest({
            url: 'https://github.com/' + username + '.json?callback=?',
            method: 'get',
            crossOrigin: true,
            headers: { "Accept": "application/json" },
            type: 'jsonp'
        }).then(function (response) {
            return _.map(response, function (response) {
                return new GithubEvent(response);
            });
        });
    }

    // Returns a KO array which is then updated as data arrives
    githubApi.getOrganisationEvents = function (orgName) {
        var events = ko.observableArray([]);

        var loading = 1;

        getOrganisationMembers(orgName).then(function (members) {
            loading = members.length;

            _.each(members, function (member) {
                getUserEvents(member.login).then(function (userEvents) {
                    events(events().concat(userEvents));
                    loading--;
                });
            });
        });

        events.loadingComplete = ko.computed(function () {
            return loading === 0;
        });

        return events;
    };

    return githubApi;
});

Move event source to Github site feeds, rather than full (rate-limited) API


var http = require('http')
var moment = require('moment')
var url = require('url')

function stringifyTime(now) {
  var timeString = '{"hour":' + now.hour() +
                  ',"minute":' + now.minute() +
                  ',"second":' + now.second() + '}'
  return timeString;
}

function unixifyTime(now) {
  var timeString = '{"unixtime":' + now.valueOf() + '}';
  return timeString;
}

var server = http.createServer(function(req, resp) {
  if (req.method != 'GET') {
    resp.writeHead(405, {'Allow': 'GET'})
    resp.end('{"message": "bad method (method != GET)"}')
  }

  var hash = url.parse(req.url, true)
  var fcn = (function(now){ return null });

  switch(hash.pathname) {
    case '/api/parsetime':
      fcn = stringifyTime;
      break;
    case '/api/unixtime':
      fcn = unixifyTime;
      break;
  }

  var dateString = hash.query.iso
  var dateObj = moment(dateString)

  resp.writeHead(200, {'Content-Type': 'application/json'})
  resp.end(fcn(dateObj))
})

server.listen(8000)

var http = require('http')
var moment = require('moment')
var url = require('url')

function stringifyTime(time) {
  return {
    hour: time.hour(),
    minute: time.minute(),
    second: time.second()
  }
}

function unixifyTime(time) {
  return { unixtime: time.valueOf() }
}

var server = http.createServer(function(req, resp) {
  if (req.method != 'GET') {
    resp.writeHead(405, {'Allow': 'GET'})
    resp.end(JSON.stringify({message: 'bad method (method != GET)'}))
  }

  var parsed = url.parse(req.url, true)
  var result = null
  var date   = moment(parsed.query.iso)

  switch(parsed.pathname) {
    case '/api/parsetime':
      result = stringifyTime(date)
      break;
    case '/api/unixtime':
      result = unixifyTime(date)
      break;
  }

  resp.writeHead(200, {'Content-Type': 'application/json'})
  resp.end(JSON.stringify(result))
})

server.listen(8000)

Use JS Objects for generating JSON


/* eslint-disable prefer-arrow-callback */
/* eslint-env node, mocha, browser */

const assert = require('assert');
const test = require('../src/index').test;
const item = require('./harness/component-data');
const path = require('path');

describe('Vue test utils', () => {
  const pathToHarness = path.resolve(__dirname, './harness/vue-main');

  it('should render components with children', function () {
    return test(pathToHarness, 'parent').then((win) => {
      assert.equal(document.querySelector('#div1').textContent, 'div1');
      window.x = 1;
      win.cleanup();
    });
  });

  it('should render components with data', function () {
    return test(pathToHarness, 'parent').then((win) => {
      assert.equal(document.querySelector('#div2').textContent, item.x);
      window.x = 1;
      win.cleanup();
    });
  });

  it('clean up jsdom environment', function () {
    return test(pathToHarness, 'parent').then((win) => {
      assert.ok(!win.x);
      win.cleanup();
    });
  });

  it('update dom per Vue dom event click handlers', function () {
    return test(pathToHarness, 'parent', function (win) {
      win.$('#click-add').trigger('click');
    }).then((win) => {
      assert.equal(win.$('#msg').text(), '1');
      win.cleanup();
    });
  });
});

/* eslint-disable prefer-arrow-callback */
/* eslint-env node, mocha, browser */

const assert = require('assert');
const test = require('../src/index').test;
const item = require('./harness/component-data');
const path = require('path');

describe('Vue test utils', () => {
  const pathToHarness = path.resolve(__dirname, './harness/vue-main');

  it('Pass take callback with window, $, cleanup function', function () {
    return test(pathToHarness, 'parent').then((win) => {
      assert.ok(win);
      assert.ok(win.$);
      assert.ok(win.cleanup);
      win.cleanup();
    });
  });

  it('should render components with children', function () {
    return test(pathToHarness, 'parent').then((win) => {
      assert.equal(document.querySelector('#div1').textContent, 'div1');
      window.x = 1;
      win.cleanup();
    });
  });

  it('should render components with data', function () {
    return test(pathToHarness, 'parent').then((win) => {
      assert.equal(document.querySelector('#div2').textContent, item.x);
      window.x = 1;
      win.cleanup();
    });
  });

  it('clean up jsdom environment', function () {
    return test(pathToHarness, 'parent').then((win) => {
      assert.ok(!win.x);
      win.cleanup();
    });
  });

  it('update dom per Vue dom event click handlers', function () {
    return test(pathToHarness, 'parent', function (win) {
      win.$('#click-add').trigger('click');
    }).then((win) => {
      assert.equal(win.$('#msg').text(), '1');
      win.cleanup();
    });
  });
});

Add unit test for verifying window in then callback.


const utils = require('ethereumjs-util')
const BN = utils.BN

const bn128_module = require('rustbn.js')
const ecMul_precompile = bn128_module.cwrap('ec_mul', 'string', ['string'])

module.exports = function (opts) {
  let results = {}
  let data = opts.data

  let inputHexStr = data.toString('hex')

  try {
    let returnData = ecMul_precompile(inputHexStr)
    results.return = Buffer.from(returnData, 'hex')
    results.exception = 1
  } catch (e) {
    console.log('exception in ecMul precompile is expected. ignore previous panic...')
    // console.log(e)
    results.return = Buffer.alloc(0)
    results.exception = 0
  }

  // Temporary, replace with finalized gas cost from EIP spec (via ethereum-common)
  results.gasUsed = new BN(2000)
  return results
}

const utils = require('ethereumjs-util')
const BN = utils.BN

const bn128_module = require('rustbn.js')
const ecMul_precompile = bn128_module.cwrap('ec_mul', 'string', ['string'])

module.exports = function (opts) {
  let results = {}
  let data = opts.data

  let inputHexStr = data.toString('hex')

  try {
    let returnData = ecMul_precompile(inputHexStr)
    results.return = Buffer.from(returnData, 'hex')
    results.exception = 1
  } catch (e) {
    console.log('exception in ecMul precompile is expected. ignore previous panic...')
    // console.log(e)
    results.return = Buffer.alloc(0)
    results.exception = 0
  }

  // Temporary, replace with finalized gas cost from EIP spec (via ethereum-common)
  results.gasUsed = new BN(40000)
  return results
}

Update gas costs for EC mult precompile


import React from 'react'
import PropTypes from 'prop-types'
import { compose, withProps } from 'recompose'
import QRCode from 'qrcode.react'
import { withRouter } from 'next/router'

import { StaticLayout } from '../components/layouts'
import { withLogging } from '../lib'

const propTypes = {
  shortname: PropTypes.string.isRequired,
}

const QR = ({ shortname }) => {
  const joinLink = `${process.env.APP_BASE_URL}/join/${shortname}`

  return (
    <StaticLayout pageTitle="QR">

{joinLink.replace(/^https?:\/\//,'')}

        <QRCode size={700} value={joinLink} />

    </StaticLayout>
  )
}
QR.propTypes = propTypes

export default compose(
  withRouter,
  withLogging(),
  withProps(({ router }) => ({
    shortname: router.query.shortname,
  }))
)(QR)

import React from 'react'
import PropTypes from 'prop-types'
import { compose, withProps } from 'recompose'
import QRCode from 'qrcode.react'
import { withRouter } from 'next/router'

import { StaticLayout } from '../components/layouts'
import { withLogging } from '../lib'

const propTypes = {
  shortname: PropTypes.string.isRequired,
}

const QR = ({ shortname }) => {
  const joinLink = `${process.env.APP_BASE_URL}/join/${shortname}`

  return (
    <StaticLayout pageTitle="QR">

{joinLink.replace(/^https?:\/\//, '')}

        <QRCode size={700} value={joinLink} />

    </StaticLayout>
  )
}
QR.propTypes = propTypes

export default compose(
  withRouter,
  withLogging(),
  withProps(({ router }) => ({
    shortname: router.query.shortname,
  }))
)(QR)

Format regex for QR page


module.exports = {
  getViewPortInfo: function getViewPort () {
    var e = document.documentElement
    var g = document.getElementsByTagName('body')[0]
    var x = window.innerWidth || e.clientWidth || g.clientWidth
    var y = window.innerHeight || e.clientHeight || g.clientHeight

    return {
      width: x,
      height: y
    }
  },

  mergeObject: function (o1, o2) {
    var a
    var o3 = {}

    for (a in o1) {
      o3[a] = o1[a]
    }

    for (a in o2) {
      o3[a] = o2[a]
    }

    return o3
  },

  isUndefined: function (obj) {
    return (typeof obj) === 'undefined'
  },

  getCurrentScript: function () {
    // Source http://www.2ality.com/2014/05/current-script.html
    return document.currentScript || (function () {
        var scripts = document.getElementsByTagName('script')
        return scripts[scripts.length - 1]
      })()
  }

}

module.exports = {
  getViewPortInfo: function getViewPort () {
    var e = document.documentElement
    var g = document.getElementsByTagName('body')[0]
    var x = window.innerWidth || e.clientWidth || g.clientWidth
    var y = window.innerHeight || e.clientHeight || g.clientHeight

    return {
      width: x,
      height: y
    }
  },

  mergeObject: function (o1, o2) {
    var a
    var o3 = {}

    for (a in o1) {
      o3[a] = o1[a]
    }

    for (a in o2) {
      o3[a] = o2[a]
    }

    return o3
  },

  isUndefined: function (obj) {
    return (typeof obj) === 'undefined'
  },

  getCurrentScript: function () {
    // Source http://www.2ality.com/2014/05/current-script.html
    return document.currentScript || (function () {
        var scripts = document.getElementsByTagName('script')
        return scripts[scripts.length - 1]
      })()
  },

  promiseAny: function(arrayOfPromises) {
    if(!arrayOfPromises || !(arrayOfPromises instanceof Array)) {
      throw new Error('Must pass Promise.any an array');
    }

    if(arrayOfPromises.length === 0) {
      return Promise.resolve([]);
    }

    // For each promise that resolves or rejects,
    // make them all resolve.
    // Record which ones did resolve or reject
    var resolvingPromises = arrayOfPromises.map(function(promise) {
      return promise.then(function(result) {
        return {
          resolve: true,
          result: result
        };
      }, function(error) {
        return {
          resolve: false,
          result: error
        };
      });
    });

    return Promise.all(resolvingPromises).then(function(results) {
      // Count how many passed/failed
      var passed = [], failed = [], allFailed = true;
      results.forEach(function(result) {
        if(result.resolve) {
          allFailed = false;
        }
        passed.push(result.resolve ? result.result : null);
        failed.push(result.resolve ? null : result.result);
      });

      return passed;

    });

  }

}

Add promiseAny until function to get Promise.any support without extending prototype


import path from 'path'

export default {
  name: 'eslint',
  configure ({ projectPath }) {
    return {
      eslint: {
        configFile: path.join(projectPath, '.eslintrc')
      },

      module: {
        preLoaders: [
          {
            test: /\.jsx?$/,
            loader: 'eslint',
            exclude: /node_modules/
          }
        ]
      }
    }
  }
}

import path from 'path'

export default {
  name: 'eslint',
  configure ({ projectPath, enableCoverage }) {
    if (!enableCoverage) {
      return {}
    }

    return {
      eslint: {
        configFile: path.join(projectPath, '.eslintrc')
      },

      module: {
        preLoaders: [
          {
            test: /\.jsx?$/,
            loader: 'eslint',
            exclude: /node_modules/
          }
        ]
      }
    }
  }
}

Disable linting when generating coverage


/* globals Stripe */
import Ember from 'ember';
var $ = Ember.$;

function registrationDataFromUrl(url) {
  var matches = url.match(/\?code=(\d+)/);

  if (matches && matches.length === 2) {
    return {registration_code: matches[1]};
  } else {
    return {};
  }
}

function initializeStripe(key) {
  Stripe.setPublishableKey(key);
}

function setupCsrfHeaders(token) {
  $.ajaxPrefilter(function(options, originalOptions, jqXHR) {
    return jqXHR.setRequestHeader('X-CSRF-Token', token);
  });
}

export default Ember.Service.extend({
  setup: function(url) {
    return $.get(
      '/training/api/sessions',
      registrationDataFromUrl(url)
    ).then(this.requestDidReturn);
  },

  requestDidReturn: function(response) {
    setupCsrfHeaders(response.authenticity_token);
    initializeStripe(response.stripe_public_key);
  }
});

/* globals Stripe */
import Ember from 'ember';
var $ = Ember.$;

function registrationDataFromUrl(url) {
  var matches = url.match(/\?code=([a-z0-9]+)/);

  if (matches && matches.length === 2) {
    return {registration_code: matches[1]};
  } else {
    return {};
  }
}

function initializeStripe(key) {
  Stripe.setPublishableKey(key);
}

function setupCsrfHeaders(token) {
  $.ajaxPrefilter(function(options, originalOptions, jqXHR) {
    return jqXHR.setRequestHeader('X-CSRF-Token', token);
  });
}

export default Ember.Service.extend({
  setup: function(url) {
    return $.get(
      '/training/api/sessions',
      registrationDataFromUrl(url)
    ).then(this.requestDidReturn);
  },

  requestDidReturn: function(response) {
    setupCsrfHeaders(response.authenticity_token);
    initializeStripe(response.stripe_public_key);
  }
});

Update regular expression for registration code


$(document).ready(function() {

	module("Canvas presentations");

	test("Check Canvas Presentations", function() {
		expect(1);
		ok(MITHGrid.Presentation.RaphSVG !== undefined, "RaphSVG Presentation exists");
		//ok(MITHGrid.Presentation.SVGRect !== undefined, "SVGRect Presentation exists");

	});

	test("Check Canvas availability", function() {
		expect(2);
		var container = $("#test-158603");

		var svg, presentation;
		presentation = MITHGrid.Presentation.RaphSVG.initPresentation(container, {
			dataView: MITHGrid.Data.initView({
				dataStore: MITHGrid.Data.initStore({})
			}),
			cWidth: 1,
			cHeight: 1,
			lenses: {}
		});

		ok(presentation !== undefined, "Presentation object exists");
		ok(presentation.canvas !== undefined && presentation.canvas.canvas !== undefined &&
			presentation.canvas.canvas.localName === "svg", "presentation canvas svg element is good");
	});
/*
	module("Canvas");

	test("Check namespace", function() {
		expect(2);
		ok(MITHGrid.Application.Canvas !== undefined, "MITHGrid.Application.Canvas exists");
        ok($.isFunction(MITHGrid.Application.Canvas.namespace), "MITHGrid.Application.Canvas.namespace is a function");
	});

	module("Canvas.initApp");

	test("Check initApp", function() {
		expect(1);

		ok(MITHGrid.Application.Canvas.initApp !== undefined, "Canvas.initApp defined and is a function");
	});
	*/
});

$(document).ready(function() {

	module("Canvas presentations");

	test("Check Canvas Presentations", function() {
		expect(1);
		ok(MITHGrid.Presentation.RaphSVG !== undefined, "RaphSVG Presentation exists");
	});

	test("Check Canvas availability", function() {
		var presentation;
		expect(3);

		try {
			presentation = MITHGrid.Presentation.RaphSVG.initPresentation($('test-158603'), {
				dataView: MITHGrid.Data.initView({
					dataStore: MITHGrid.Data.initStore({})
				}),
				cWidth: 1,
				cHeight: 1,
				lenses: {}
			});
			ok(true, "Presentation object created");
		}
		catch(e) {
			ok(false, "Presentation object not created: " + e);
		}

		ok(presentation !== undefined, "Presentation object exists");
		ok(presentation.canvas !== undefined && presentation.canvas.canvas !== undefined &&
			presentation.canvas.canvas.localName === "svg", "presentation canvas svg element is good");
	});
});

Fix test missing the container element


var $ = require('../')
  , assert = require('assert')

$.import('Foundation')
$.NSAutoreleasePool('alloc')('init')

// Subclass 'NSObject', creating a new class named 'NRTest'
var NRTest = $.NSObject.extend('NRTest')
  , counter = 0

// Add a new method to the NRTest class responding to the "description" selector
NRTest.addMethod('description', '@@:', function (self, _cmd) {
  counter++
  console.log(_cmd)
  return $.NSString('stringWithUTF8String', 'test')
})

// Finalize the class so the we can make instances of it
NRTest.register()

// Create an instance
var instance = NRTest('alloc')('init')

// call [instance description] in a variety of ways (via toString())
console.log(instance('description')+'')
console.log(instance.toString())
console.log(String(instance))
console.log(''+instance)
console.log(instance+'')
console.log(instance)

assert.equal(counter, 6)

var $ = require('../')
  , assert = require('assert')

$.import('Foundation')
$.NSAutoreleasePool('alloc')('init')

// Subclass 'NSObject', creating a new class named 'NRTest'
var NRTest = $.NSObject.extend('NRTest')
  , counter = 0

// Add a new method to the NRTest class responding to the "description" selector
NRTest.addMethod('description', '@@:', function (self, _cmd) {
  counter++
  console.log(_cmd)
  return $.NSString('stringWithUTF8String', 'test')
})

// Finalize the class so the we can make instances of it
NRTest.register()

// Create an instance
var instance = NRTest('alloc')('init')

// call [instance description] in a variety of ways (via toString())
console.log(instance('description')+'')
console.log(instance.toString())
//console.log(String(instance)) // now this one segfaults? WTF!?!?!
console.log(''+instance)
console.log(instance+'')
console.log(instance)

assert.equal(counter, 5)

Remove this test case for now... still segfaulting for some crazy reason...


/*jshint node:true*/
/* global require, module */
var EmberAddon = require('ember-cli/lib/broccoli/ember-addon');

module.exports = function(defaults) {
  var app = new EmberAddon(defaults, {
    // Add options here
  });

  /*
    This build file specifies the options for the dummy test app of this
    addon, located in `/tests/dummy`
    This build file does *not* influence how the addon or the app using it
    behave. You most likely want to be modifying `./index.js` or app's build file
  */

  return app.toTree();
};

/*jshint node:true*/
/* global require, module */
var EmberAddon = require('ember-cli/lib/broccoli/ember-addon');

module.exports = function(defaults) {
  var app = new EmberAddon(defaults, {
    snippetSearchPaths: ['app', 'node_modules/ui-base-theme/addon']
  });

  /*
    This build file specifies the options for the dummy test app of this
    addon, located in `/tests/dummy`
    This build file does *not* influence how the addon or the app using it
    behave. You most likely want to be modifying `./index.js` or app's build file
  */

  return app.toTree();
};

Fix code snippets in styleguide


// @flow
/* eslint-disable arrow-body-style */
import { applyMiddleware, createStore } from 'redux'
import { createEpicMiddleware } from 'redux-observable'
import thunk from 'redux-thunk'
import promiseMiddleware from 'redux-promise-middleware'
import { routerMiddleware } from 'react-router-redux'
import { composeWithDevTools } from 'redux-devtools-extension/logOnlyInProduction'
import rootEpic from './epics'
import rootReducer from './modules'

function configureStore (
  preloadedState: Object, history: Object,
): Object {

  const epicMiddleware = createEpicMiddleware(rootEpic)

  const middleware = [
    epicMiddleware,
    thunk,
    promiseMiddleware(),
    routerMiddleware(history),
  ]

  // only log redux actions in development
  if (process.env.NODE_ENV === 'development') {

    // logger needs to be last
    // uncomment if needed
    // middleware.push(require('redux-logger').createLogger())

  }

  // https://github.com/zalmoxisus/redux-devtools-extension
  // https://medium.com/@zalmoxis/using-redux-devtools-in-production-4c5b56c5600f

  const enhancer = composeWithDevTools(
    applyMiddleware(...middleware)
  )

  const store = createStore(rootReducer, preloadedState, enhancer)

  return store

}

export default configureStore

// @flow
/* eslint-disable arrow-body-style */
import { applyMiddleware, createStore, compose } from 'redux'
import { createEpicMiddleware } from 'redux-observable'
import thunk from 'redux-thunk'
import promiseMiddleware from 'redux-promise-middleware'
import { routerMiddleware } from 'react-router-redux'
import { devToolsEnhancer } from 'redux-devtools-extension/logOnlyInProduction'
import rootEpic from './epics'
import rootReducer from './modules'

function configureStore (
  preloadedState: Object, history: Object,
): Object {

  const epicMiddleware = createEpicMiddleware(rootEpic)

  const middleware = [
    epicMiddleware,
    thunk,
    promiseMiddleware(),
    routerMiddleware(history),
  ]

  // only log redux actions in development
  if (process.env.NODE_ENV === 'development') {

    // logger needs to be last
    // uncomment if needed
    // middleware.push(require('redux-logger').createLogger())

  }

  // https://github.com/zalmoxisus/redux-devtools-extension
  // https://medium.com/@zalmoxis/using-redux-devtools-in-production-4c5b56c5600f

  const enhancer = compose(
    applyMiddleware(...middleware),
    devToolsEnhancer()
  )

  const store = createStore(rootReducer, preloadedState, enhancer)

  return store

}

export default configureStore

Use redux compose with devToolsEnhancer


'use strict';

var articles = require('../controllers/articles');

// Article authorization helpers
var hasAuthorization = function(req, res, next) {
    if (req.article.user.id !== req.user.id) {
        return res.send(401, 'User is not authorized');
    }
    next();
};

module.exports = function(Articles, app, auth) {

    app.route('/articles')
        .get(articles.all)
        .post(auth.requiresLogin, articles.create);
    app.route('/articles/:articleId')
        .get(articles.show)
        .put(auth.requiresLogin, hasAuthorization, articles.update)
        .delete(auth.requiresLogin, hasAuthorization, articles.destroy);

    // Finish with setting up the articleId param
    app.param('articleId', articles.article);
};

'use strict';

var articles = require('../controllers/articles');

// Article authorization helpers
var hasAuthorization = function(req, res, next) {
    if (res.adminEnabled() || (req.article.user && (req.article.user_id === req.user._id))) {
        next();
    }
    else {
        return res.send(401, 'User is not authorized');
    }
};

module.exports = function(Articles, app, auth) {

    app.route('/articles')
        .get(articles.all)
        .post(auth.requiresLogin, articles.create);
    app.route('/articles/:articleId')
        .get(articles.show)
        .put(auth.requiresLogin, hasAuthorization, articles.update)
        .delete(auth.requiresLogin, hasAuthorization, articles.destroy);

    // Finish with setting up the articleId param
    app.param('articleId', articles.article);
};

Allow for Admin to Edit / Delete


var currentTip = null;
var currentTipElement = null;

function hideTip(evt, name, unique)
{
  var el = document.getElementById(name);
  el.style.display = "none";
  currentTip = null;
}

function findPos(obj)
{
  // no idea why, but it behaves differently in webbrowser component
  if (window.location.search == "?inapp")
    return [obj.offsetLeft + 10, obj.offsetTop + 30];

  var curleft = 0;
  var curtop = obj.offsetHeight;
  while (obj)
  {
    curleft += obj.offsetLeft;
    curtop += obj.offsetTop;
    obj = obj.offsetParent;
  };
  return [curleft, curtop];
}

function hideUsingEsc(e)
{
  if (!e) { e = event; }
  hideTip(e, currentTipElement, currentTip);
}

function showTip(evt, name, unique, owner)
{
  document.onkeydown = hideUsingEsc;
  if (currentTip == unique) return;
  currentTip = unique;
  currentTipElement = name;

  var pos = findPos(owner ? owner : (evt.srcElement ? evt.srcElement : evt.target));
  var posx = pos[0];
  var posy = pos[1];

  var el = document.getElementById(name);
  var parent = (document.documentElement == null) ? document.body : document.documentElement;
  el.style.position = "absolute";
  el.style.left = posx + "px";
  el.style.top = posy + "px";
  el.style.display = "block";
}

var currentTip = null;
var currentTipElement = null;

function hideTip(evt, name, unique)
{
  var el = document.getElementById(name);
  el.style.display = "none";
  currentTip = null;
}

function findPos(obj)
{
  var curleft = 0;
  var curtop = obj.offsetHeight;
  while (obj)
  {
    curleft += obj.offsetLeft;
    curtop += obj.offsetTop;
    obj = obj.offsetParent;
  };
  return [curleft, curtop];
}

function hideUsingEsc(e)
{
  if (!e) { e = event; }
  hideTip(e, currentTipElement, currentTip);
}

function showTip(evt, name, unique, owner)
{
  document.onkeydown = hideUsingEsc;
  if (currentTip == unique) return;
  currentTip = unique;
  currentTipElement = name;

  var pos = findPos(owner ? owner : (evt.srcElement ? evt.srcElement : evt.target));
  var posx = pos[0];
  var posy = pos[1];

  var el = document.getElementById(name);
  var parent = (document.documentElement == null) ? document.body : document.documentElement;
  parent.appendChild(el);
  el.style.position = "absolute";
  el.style.left = posx + "px";
  el.style.top = posy + "px";
  el.style.display = "block";
}

Fix tooltip positions in tutorial pages


import Mixin from '@ember/object/mixin';
import { inject } from '@ember/service';
import { get, computed } from '@ember/object';
import { isEmpty } from '@ember/utils';
import config from 'ember-get-config';
import DataAdapterMixin from 'ember-simple-auth/mixins/data-adapter-mixin';

/**
  Adapter Mixin that works with token-based authentication like JWT.

  @class TokenAdapter
  @module ember-simple-auth-token/mixins/token-adapter
  @extends Ember.Mixin
*/
export default Mixin.create(DataAdapterMixin, {
  session: inject('session'),

  /**
    @method init
  */
  init() {
    this._super(...arguments);
    const conf = config['ember-simple-auth-token'] || {};
    this.tokenPropertyName = conf.tokenPropertyName || 'token';
    this.authorizationHeaderName = conf.authorizationHeaderName || 'Authorization';
    this.authorizationPrefix = conf.authorizationPrefix === '' ? '' : conf.authorizationPrefix || 'Bearer ';
  },

  /*
    Adds the `token` property from the session to the `authorizationHeaderName`:
  */
  headers: computed('session.isAuthenticated', function() {
    const data = get(this, 'session.data.authenticated');
    const token = get(data, this.get('tokenPropertyName'));
    const prefix = this.get('authorizationPrefix');
    const header = this.get('authorizationHeaderName');

    if (this.get('session.isAuthenticated') && !isEmpty(token)) {
      return {
        [header]: `${prefix}${token}`
      };
    } else {
      return {};
    }
  })
});

import Mixin from '@ember/object/mixin';
import { inject } from '@ember/service';
import { get, computed } from '@ember/object';
import { isEmpty } from '@ember/utils';
import config from 'ember-get-config';
import DataAdapterMixin from 'ember-simple-auth/mixins/data-adapter-mixin';

/**
  Adapter Mixin that works with token-based authentication like JWT.

  @class TokenAdapter
  @module ember-simple-auth-token/mixins/token-adapter
  @extends Ember.Mixin
*/
export default Mixin.create(DataAdapterMixin, {
  session: inject('session'),

  /**
    @method init
  */
  init() {
    this._super(...arguments);
    const conf = config['ember-simple-auth-token'] || {};
    this.tokenPropertyName = conf.tokenPropertyName || 'token';
    this.authorizationHeaderName = conf.authorizationHeaderName || 'Authorization';
    this.authorizationPrefix = conf.authorizationPrefix === '' ? '' : conf.authorizationPrefix || 'Bearer ';
  },

  /*
    Adds the `token` property from the session to the `authorizationHeaderName`:
  */
  headers: computed('session.data.authenticated', function() {
    const data = this.get('session.data.authenticated');
    const token = get(data, this.get('tokenPropertyName'));
    const prefix = this.get('authorizationPrefix');
    const header = this.get('authorizationHeaderName');

    if (this.get('session.isAuthenticated') && !isEmpty(token)) {
      return {
        [header]: `${prefix}${token}`
      };
    } else {
      return {};
    }
  })
});

Fix computed header property issue


/**
 * util-lang.js - The minimal language enhancement
 */

function isType(type) {
  return function(obj) {
    return {}.toString.call(obj) == "[object " + type + "]"
  }
}

var isObject = isType("Object")
var isString = isType("String")
var isArray = Array.isArray || isType("Array")
var isFunction = isType("Function")

var _cid = 0
function cid() {
  return _cid++
}

/**
 * util-lang.js - The minimal language enhancement
 */

var _object = {}

function isType(type) {
  return function(obj) {
    return _object.toString.call(obj) == "[object " + type + "]"
  }
}

var isObject = isType("Object")
var isString = isType("String")
var isArray = Array.isArray || isType("Array")
var isFunction = isType("Function")

var _cid = 0
function cid() {
  return _cid++
}

Update share one object for check type.


'use strict';

describe('Controller: FormDropAreaCtrl', function () {

  // load the controller's module
  beforeEach(module('confRegistrationWebApp'));

  var FormDropAreaCtrl,
    scope;

  // Initialize the controller and a mock scope
  beforeEach(inject(function ($controller, $rootScope) {
    scope = $rootScope.$new();
    FormDropAreaCtrl = $controller('FormDropAreaCtrl', {
      $scope: scope
    });
  }));

//  it('should attach a list of awesomeThings to the scope', function () {
//    expect(scope.awesomeThings.length).toBe(3);
//  });
});

'use strict';

describe('Controller: FormDropAreaCtrl', function () {

  // load the controller's module
  beforeEach(module('confRegistrationWebApp'));

  var FormDropAreaCtrl,
    scope;

  // Initialize the controller and a mock scope
  beforeEach(inject(function ($controller, $rootScope) {
    scope = $rootScope.$new();

    scope.conference = {
      registrationPages: [
        {
          id: 'page1',
          blocks: [ { id: 'block1' } ]
        },
        {
          id: 'page2',
          blocks: [ { id: 'block2' } ]
        }
      ]
    };

    FormDropAreaCtrl = $controller('FormDropAreaCtrl', {
      $scope: scope
    });
  }));

  it('should have a function to move a block', function () {
    scope.moveBlock('block2', 'page1', 0);

    expect(scope.conference.registrationPages[0].blocks.length).toBe(2);
    expect(scope.conference.registrationPages[0].blocks[0].id).toBe('block2');
  });
});

Test the move block fn


import Ember from 'ember';

export default Ember.Component.extend({
  userSettings: Ember.inject.service(),

  init () {
    this._super();
    this.get('userSettings').timePeriods().then(response => {
      // Sort by latest on top
      this.set('timePeriods', response.sortBy('begins').reverse());

      // Create array of years
      this.set('years', response.uniqBy('year'));
    });
  }
});

import Ember from 'ember';

export default Ember.Component.extend({
  btnActive: false,
  didReceiveAttrs () {
    this.set('timePeriodChanged', this.get('currentDate'));
  },

  init () {
    this._super();
    this.get('userSettings').timePeriods().then(response => {
      // Sort by latest on top
      this.set('timePeriods', response.sortBy('begins').reverse());

      // Create array of years
      this.set('years', response.uniqBy('year'));
    });
  }
});

Set variable for time period base on value passed to component


import Ember from 'ember';

export default Ember.Route.extend({
  model: function() {
    var person_id = this.controllerFor('application').get('attrs.person.id');
    return this.apijax.GET('/identifier.json', { 'person_id': person_id });
  },
  setupController: function(controller, model) {
    this._super(controller, model);

    var person = this.controllerFor('application').get('attrs.person');
    person = model.objectAt(0);
    this.controllerFor('application').set('attrs.person', person);
  }
});

import Ember from 'ember';

export default Ember.Route.extend({
  model: function() {
    var person = this.controllerFor('application').get('attrs.person'),
        person_id = person.id,
        model;

    if (!Ember.isEmpty(person.full_name)) {
      // we have full passed in details for the person, no need for lookup
      model = [person];
    } else {
      model = this.apijax.GET('/identifier.json', { 'person_id': person_id });
    }

    return model;
  },
  setupController: function(controller, model) {
    this._super(controller, model);

    var person = this.controllerFor('application').get('attrs.person');
    person = model.objectAt(0);
    this.controllerFor('application').set('attrs.person', person);
  }
});

Add ability to pass in full details for person


// Install the configuration interface and set default values
require('configuration.js');

// Use DIM styling overrides and our own custom styling
require('beautification.js');

// Stores weapons pulled from our custom database
require('weaponDatabase.js');

// Pulls down weapon data and broadcasts updates
require('weaponDataRefresher.js');

// Removes DIM's native tagging elements
require('dimTagRemover.js');

// Store original details about the weapon in 'data-fate' attributes
require('weaponDecorator.js');

// Update weapon comments from our database
require('commentDecorator.js');

// Rejigger how weapons with legendary mods are displayed
require('modIndicator.js');

// Show higher/lower dupes
require('dupeIndicator.js');

/*
  The nicest change-refresh flow means loading the development version of
  the script from Tampermonkey while editing. This lets us skip kicking off
  the app when running under Karma.
*/
if (!window.navigator.userAgent.includes('HeadlessChrome')) {
  // const logger = require('logger');
  // const postal = require('postal');
  // logger.log('main.js: Initializing');
  // postal.publish({topic:'fate.init'});
  //
  // setInterval(function() {
  //   postal.publish({topic:'fate.refresh'});
  // }, 5000);
  //
  // setInterval(function() {
  //   postal.publish({topic:'fate.weaponDataStale'});
  // }, 30000);
}

// Install the configuration interface and set default values
require('configuration.js');

// Use DIM styling overrides and our own custom styling
require('beautification.js');

// Stores weapons pulled from our custom database
require('weaponDatabase.js');

// Pulls down weapon data and broadcasts updates
require('weaponDataRefresher.js');

// Removes DIM's native tagging elements
require('dimTagRemover.js');

// Store original details about the weapon in 'data-fate' attributes
require('weaponDecorator.js');

// Update weapon comments from our database
require('commentDecorator.js');

// Rejigger how weapons with legendary mods are displayed
require('modIndicator.js');

// Show higher/lower dupes
require('dupeIndicator.js');

/*
  The nicest change-refresh flow means loading the development version of
  the script from Tampermonkey while editing. This lets us skip kicking off
  the app when running under Karma.
*/
if (!window.navigator.userAgent.includes('HeadlessChrome')) {
  const logger = require('logger');
  logger.log('main.js: Initializing');
  fateBus.publish(module, 'fate.init');

  setInterval(function() {
    fateBus.publish(module, 'fate.refresh');
  }, 5000);

  setInterval(function() {
    fateBus.publish(module, 'fate.weaponDataStale');
  }, 30000);
}

Swap over to using fateBus.


'use strict';

(function(app) {

  app.directive('twitterBtn', [function() {
    var link = function() {
      !function(d,s,id){
        var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';
        if(!d.getElementById(id)){
          js=d.createElement(s);
          js.id=id;
          js.src=p+'://platform.twitter.com/widgets.js';
          fjs.parentNode.insertBefore(js,fjs);
        }
      }(document, 'script', 'twitter-wjs');
    };
    return {
      restrict: 'A',
      link: link,
      templateUrl: '/angular/views/twitter-button.html'
    };

  }]);

})(timer.app);

'use strict';

(function(app) {

  app.directive('twitterBtn', [function() {
    var link = function() {
      !function(d, s, id){
        var js, fjs = d.getElementsByTagName(s)[0], p = /^http:/.test(d.location)?'http':'https';
        if(!d.getElementById(id)){
          js = d.createElement(s);
          js.id = id;
          js.src = p+'://platform.twitter.com/widgets.js';
          fjs.parentNode.insertBefore(js,fjs);
        }
      }(document, 'script', 'twitter-wjs');
    };
    return {
      restrict: 'A',
      link: link,
      templateUrl: '/angular/views/twitter-button.html'
    };

  }]);

})(timer.app);

Add more spaces to twitter js code.


module.exports = {
	extends: [
		'nextcloud'
	],
	/**
	 * Allow jest syntax in the src folder
	 */
	env: {
		jest: true
	},
	/**
	 * Allow shallow import of @vue/test-utils in order to be able to use it in
	 * the src folder
	 */
	rules: {
		"node/no-unpublished-import": ["error", {
			"allowModules": ["@vue/test-utils"]
		}]
	}
}

module.exports = {
	extends: [
		'nextcloud'
	],
}

Remove eslint rules added to the eslint-config module


import Ember from 'ember';
import SortableItems from 'sortable-items/components/sortable-items';

export default SortableItems;

import Ember from 'ember';
import SortableItems from 'ember-cli-sortable/components/sortable-items';

export default SortableItems;

Update import for the app with the correct ember-addon name


'use strict';
var _ = require('lodash');
var convert = require('./convert.js');

var context = {
  getDefaults: getDefaults
};

function getDefaults(name, module) {
  var folderName = convert.moduleToFolder(module);
  return {
    module: module,
    camelName: _.camelCase(name),
    controller: _.upperFirst(_.camelCase(name)) + 'Controller',
    directive: _.camelCase(name),
    directiveUrl: folderName + _.kebabCase(name) + '.directive.html',
    kebabName: _.kebabCase(name),
    route: _.camelCase(name) + 'Route',
    service: _.camelCase(name) + 'Service',
    state: module.replace('app.modules.', ''),
    templateUrl: folderName + _.kebabCase(name) + '.html'
  };
}

module.exports = context;

'use strict';
var _ = require('lodash');
var convert = require('./convert.js');

var context = {
  getDefaults: getDefaults
};

function getDefaults(name, module) {
  var folderName = convert.moduleToFolder(module);
  return _.clone({
    module: module,
    camelName: _.camelCase(name),
    controller: _.upperFirst(_.camelCase(name)) + 'Controller',
    directive: _.camelCase(name),
    directiveUrl: folderName + _.kebabCase(name) + '.directive.html',
    kebabName: _.kebabCase(name),
    route: _.camelCase(name) + 'Route',
    service: _.camelCase(name) + 'Service',
    state: module.replace('app.modules.', ''),
    templateUrl: folderName + _.kebabCase(name) + '.html'
  });
}

module.exports = context;

Return new instance of object


// ==UserScript==
// @name           Rakuten Mail Magazine Unchecker
// @namespace      https://github.com/takamario/gm_script
// @description    Rakuten Mail Magazine Unchecker
// @include        https://order.step.rakuten.co.jp/rms/mall/basket/vc*
// @include        https://basket.step.rakuten.co.jp/rms/mall/bs/confirmorder/*
// @include        https://basket.step.rakuten.co.jp/rms/mall/bs/confirmorderquicknormalize/*
// @include        https://books.step.rakuten.co.jp/rms/mall/book/bs*
// ==/UserScript==

(function() {
  var i, ipt = document.getElementsByTagName('input');
  for (i in ipt) {
    if (ipt[i].type === 'checkbox') {
      ipt[i].checked = '';
    }
  }
})();

// ==UserScript==
// @name           Rakuten Mail Magazine Unchecker
// @namespace      https://github.com/takamario/gm_script
// @description    Rakuten Mail Magazine Unchecker
// @include        https://order.step.rakuten.co.jp/rms/mall/basket/vc*
// @include        https://basket.step.rakuten.co.jp/rms/mall/bs/confirmorder/*
// @include        https://basket.step.rakuten.co.jp/rms/mall/bs/confirmorderquicknormalize/*
// @include        https://books.step.rakuten.co.jp/rms/mall/book/bs*
// @exclude        https://books.step.rakuten.co.jp/rms/mall/book/bs/Cart*
// ==/UserScript==

(function() {
  var i, ipt = document.getElementsByTagName('input');
  for (i in ipt) {
    if (ipt[i].type === 'checkbox') {
      ipt[i].checked = '';
    }
  }
})();

Add exclude books cart page


import Ember from "ember";

var container = '<g transform="scale(%s)">%h</g>';

export default Ember.Mixin.create({
  exportToSvg: function(){
    var svg = this.$("svg").clone(),
        width = +svg.find("desc.width")[0].innerHTML,
        div = document.createElement("div"),
        svgContent,
        promise;
    // clean up a little

    svg.find("image.base").remove();
    svg.find("defs").remove();
    svg.find("desc").remove();

    div.appendChild(svg[0]);

    svgContent = div.innerHTML;

    svgContent = svgContent.replace(svgContent.match(/<(?:"[^"]*"['"]*|'[^']*'['"]*|[^'">])+>/)[0], "");
    svgContent = svgContent.replace("</svg>", "");

    promise = new Ember.RSVP.Promise(function(resolve, reject){
      resolve(container.replace("%s", 1/width )
                       .replace("%h", svgContent) );
    });

    return promise;
  }
});

import Ember from "ember";

var container = '<g transform="scale(%s)">%h</g>';

export default Ember.Mixin.create({
  exportToSvg: function(){
    var svg = this.$("svg").clone(),
        width = +svg.find("desc.width")[0].innerHTML,
        div = document.createElement("div"),
        svgContent,
        promise;
    // clean up a little

    svg.find("image.base").remove();
    svg.find("defs").remove();
    svg.find("desc").remove();
    svg.find("#base-svg").remove();

    div.appendChild(svg[0]);

    svgContent = div.innerHTML;

    svgContent = svgContent.replace(svgContent.match(/<(?:"[^"]*"['"]*|'[^']*'['"]*|[^'">])+>/)[0], "");
    svgContent = svgContent.replace("</svg>", "");

    promise = new Ember.RSVP.Promise(function(resolve, reject){
      resolve(container.replace("%s", 1/width )
                       .replace("%h", svgContent) );
    });

    return promise;
  }
});

Remove base svg from exported svg


import Ember from 'ember';
// import $ from 'jquery';

export default Ember.Component.extend({
  expense: {
    sum: '',
    category: '',
    name: ''
  },
  currency: '£',
  expenseCategories: [
    'Charity',
    'Clothing',
    'Education',
    'Events',
    'Food',
    'Gifts',
    'Healthcare',
    'Household',
    'Leisure',
    'Hobbies',
    'Trasportation',
    'Utilities',
    'Vacation'
  ],

  errorMessagesSum: {
    emRequired: 'This field can\'t be blank',
    emPattern: 'Must be a number!'
  },

  errorMessagesCategory: {
    emRequired: 'Must select a category'
  },

  init () {
    this._super();
    Ember.TextSupport.reopen({
      attributeBindings: ['em-required', 'em-min', 'em-max', 'em-pattern']
    });
  },

  didInsertElement () {
    componentHandler.upgradeAllRegistered();
  },

  actions: {
    clearInputs () {
      this.set('expense', {
        sum: '',
        category: '',
        name: ''
      });
      this.$('.mdl-textfield').removeClass('is-dirty is-invalid is-touched');
    },
    addExpense () {
      this.sendAction('action', this.get('expense'));
      this.send('clearInputs');
    }
  }
});

import Ember from 'ember';
// import $ from 'jquery';

export default Ember.Component.extend({
  expense: {
    sum: '',
    category: '',
    name: ''
  },
  currency: '£',
  expenseCategories: [
    'Charity',
    'Clothing',
    'Education',
    'Events',
    'Food',
    'Gifts',
    'Healthcare',
    'Household',
    'Leisure',
    'Hobbies',
    'Trasportation',
    'Utilities',
    'Vacation'
  ],

  errorMessagesSum: {
    emRequired: 'This field can\'t be blank',
    emPattern: 'Must be a number!'
  },

  errorMessagesCategory: {
    emRequired: 'Must select a category'
  },

  init () {
    this._super();
    Ember.TextSupport.reopen({
      attributeBindings: ['em-required', 'em-min', 'em-max', 'em-pattern']
    });
  },

  didInsertElement () {
    componentHandler.upgradeAllRegistered();
  },

  actions: {
    clearInputs () {
      this.set('expense', {
        sum: '',
        category: '',
        name: ''
      });
      this.$('.mdl-textfield').removeClass('is-dirty is-invalid is-touched');
    },
    addExpense () {
      Ember.run.later(() => {
        if (!this.$().find('.is-invalid').length) {
          this.sendAction('action', this.get('expense'));
          this.send('clearInputs');
        }
      }, 200);
    }
  }
});

Check if form is valid and then send save action to parent


'use strict';

module.exports = function(grunt) {
    // Configuration.
    grunt.initConfig({
        jscs: {
            options: {
                config: '.jscsrc'
            },
            grunt: 'Gruntfile.js',
            tasks: 'tasks/**/*.js'
        },
        jshint: {
            options: {
                jshintrc: true
            },
            grunt: 'Gruntfile.js',
            tasks: 'tasks/**/*.js'
        },
        jsonlint: {
            jshint: ['.jshintrc'],
            npm: ['package.json']
        },
        nodeunit: {
            all: 'test/**/*.js'
        }
    });

    // Load tasks.
    grunt.loadTasks('./tasks');
    grunt.loadNpmTasks('grunt-contrib-jshint');
    grunt.loadNpmTasks('grunt-contrib-nodeunit');
    grunt.loadNpmTasks('grunt-jscs-checker');
    grunt.loadNpmTasks('grunt-jsonlint');
    grunt.loadNpmTasks('grunt-release');

    // Register tasks.
    grunt.renameTask('release', 'bump');
    grunt.registerTask('lint', ['jsonlint', 'jshint', 'jscs']);
    grunt.registerTask('release', function() {
        grunt.task.run('lint', Array.prototype.concat.apply('bump', arguments).join(':'));
    });
    grunt.registerTask('test', ['nodeunit']);
    grunt.registerTask('travis', ['lint', 'validate']);
    grunt.registerTask('validate', ['npm-validate']);
    grunt.registerTask('default', ['lint', 'validate']);
};

'use strict';

module.exports = function(grunt) {
    // Configuration.
    grunt.initConfig({
        jscs: {
            options: {
                config: '.jscsrc'
            },
            grunt: 'Gruntfile.js',
            tasks: 'tasks/**/*.js'
        },
        jshint: {
            options: {
                jshintrc: true
            },
            grunt: 'Gruntfile.js',
            tasks: 'tasks/**/*.js'
        },
        jsonlint: {
            jshint: ['.jshintrc'],
            npm: ['package.json']
        },
        nodeunit: {
            all: 'test/**/*.js'
        }
    });

    // Load tasks.
    grunt.loadTasks('./tasks');
    grunt.loadNpmTasks('grunt-contrib-jshint');
    grunt.loadNpmTasks('grunt-contrib-nodeunit');
    grunt.loadNpmTasks('grunt-jscs-checker');
    grunt.loadNpmTasks('grunt-jsonlint');
    grunt.loadNpmTasks('grunt-release');

    // Register tasks.
    grunt.renameTask('release', 'bump');
    grunt.registerTask('lint', ['jsonlint', 'jshint', 'jscs']);
    grunt.registerTask('release', function() {
        grunt.task.run('lint', Array.prototype.concat.apply('bump', arguments).join(':'));
    });
    grunt.registerTask('test', ['nodeunit']);
    grunt.registerTask('travis', ['lint', 'validate']);
    grunt.registerTask('validate', ['npm-validate']);
    grunt.registerTask('default', ['lint', 'test', 'validate']);
};

Include testing workflow in default task.


var path = require('path');

module.exports = function(grunt) {
  grunt.initConfig({
    'pkg': grunt.file.readJSON('package.json'),
    'dtsGenerator': {
      options: {
        baseDir: './',
        name: 'core-components',
        out: './index.d.ts',
        excludes: [
          'typings/**',
          '!typings/lib.ext.d.ts',
          'bower_components/**'
        ]
      },
      default: {
        src: [
          'debug-configuration/debug-configuration.ts'
        ]
      }
    },
    'tsc': {
      options: {
        tscPath: path.resolve('node_modules', 'typescript', 'bin', 'tsc')
      },
      default: {}
    },
    'tsd': {
      lib: {
        options: {
          command: 'reinstall',
          latest: true,
          config: 'conf/tsd-lib.json',
          opts: {
            // props from tsd.Options
          }
        }
      }
    },
    'vulcanize': {
      default: {
        options: {
          csp: 'dependencies_bundle.js'
        },
        files: {
          'dependencies_bundle.html': 'dependencies.html'
        }
      }
    }
  });

  grunt.loadNpmTasks('grunt-tsc');
  grunt.loadNpmTasks('grunt-tsd');
  grunt.loadNpmTasks('dts-generator');
  grunt.loadNpmTasks('grunt-vulcanize');

  grunt.registerTask('default', ['vulcanize']);
};

var path = require('path');

module.exports = function(grunt) {
  grunt.initConfig({
    'pkg': grunt.file.readJSON('package.json'),
    'dtsGenerator': {
      options: {
        baseDir: './',
        name: 'core-components',
        out: './index.d.ts',
        excludes: [
          'typings/**',
          '!typings/lib.ext.d.ts',
          'bower_components/**'
        ]
      },
      default: {
        src: [
          'debug-configuration/debug-configuration.ts'
        ]
      }
    },
    'tsc': {
      options: {
        tscPath: path.resolve('node_modules', 'typescript', 'bin', 'tsc')
      },
      default: {}
    },
    'tsd': {
      lib: {
        options: {
          command: 'reinstall',
          latest: true,
          config: 'conf/tsd-lib.json',
          opts: {
            // props from tsd.Options
          }
        }
      }
    },
    'vulcanize': {
      default: {
        options: {
          // extract all inline JavaScript into a separate file to work around Atom's
          // Content Security Policy
          csp: 'dependencies_bundle.js'
        },
        files: {
          // output: input
          'dependencies_bundle.html': 'dependencies.html'
        }
      }
    }
  });

  grunt.loadNpmTasks('grunt-tsc');
  grunt.loadNpmTasks('grunt-tsd');
  grunt.loadNpmTasks('dts-generator');
  grunt.loadNpmTasks('grunt-vulcanize');

  grunt.registerTask('default', ['vulcanize']);
};

Add some comments for the vulcanize grunt task


'use strict';

module.exports = function (grunt) {
    require('time-grunt')(grunt);

    grunt.initConfig({
        eslint: {
            all: {
                src: [
                    '*.js',
                    'bin/grunth',
                    'lib/*.js',
                ],
            },
        },

        jscs: {
            all: {
                src: '<%= eslint.all.src %>',
                options: {
                    config: '.jscsrc',
                },
            },
        },
    });

    // Load all grunt tasks matching the `grunt-*` pattern.
    require('load-grunt-tasks')(grunt);

    grunt.registerTask('asserts', function () {
        try {
            /* eslint-disable no-eval */
            eval('function* f() {yield 2;} var g = f(); g.next();');
            /* eslint-ensable no-eval */
        } catch (e) {
            throw new Error('Generators not recognized!');
        }

        try {
            /* eslint-disable no-eval */
            eval('function f() {"use strict"; const x = 2; return x;} f();');
            /* eslint-ensable no-eval */
        } catch (e) {
            throw new Error('Generators not recognized!');
        }
    });

    grunt.registerTask('lint', [
        'eslint',
        'jscs',
    ]);

    grunt.registerTask('test', [
        'lint',
        'asserts',
    ]);

    grunt.registerTask('default', ['test']);
};

'use strict';

module.exports = function (grunt) {
    require('time-grunt')(grunt);

    grunt.initConfig({
        eslint: {
            all: {
                src: [
                    '*.js',
                    'bin/grunth',
                    'lib/*.js',
                ],
            },
        },

        jscs: {
            all: {
                src: '<%= eslint.all.src %>',
                options: {
                    config: '.jscsrc',
                },
            },
        },
    });

    // Load all grunt tasks matching the `grunt-*` pattern.
    require('load-grunt-tasks')(grunt);

    grunt.registerTask('asserts', function () {
        try {
            /* eslint-disable no-eval */
            eval('function* f() {yield 2;} var g = f(); g.next();');
            /* eslint-ensable no-eval */
        } catch (e) {
            throw new Error('Generators not recognized!');
        }
        grunt.log.writeln('Generators recognized.');

        try {
            /* eslint-disable no-eval */
            eval('function f() {"use strict"; const x = 2; let y = 3; return [x, y];} f();');
            /* eslint-ensable no-eval */
        } catch (e) {
            throw new Error('Block scoping (const/let) not recognized!');
        }
        grunt.log.writeln('Block scoping (const/let) recognized.');
    });

    grunt.registerTask('lint', [
        'eslint',
        'jscs',
    ]);

    grunt.registerTask('test', [
        'lint',
        'asserts',
    ]);

    grunt.registerTask('default', ['test']);
};

Correct messages printed during tests


module.exports = function(grunt) {
	grunt.loadNpmTasks('grunt-git');
	grunt.loadNpmTasks('grunt-shell');

	grunt.initConfig( {
		gitclone: {
			mastercointools: {
				options: {
					repository: 'https://github.com/curtislacy/mastercoin-tools.git',
                			branch: 'Specify-Directory',
                			directory: 'node_modules/mastercoin-tools'
				},
			}
		},
		shell: {
			html: {
				command: "./gen_www.sh"
			}
		}
	} );

	grunt.registerTask( 'default', function( file ) {
		if( !grunt.file.exists( 'node_modules/mastercoin-tools' ))
			grunt.task.run( 'gitclone:mastercointools' );
		grunt.task.run( 'shell:html' );
	} );

	grunt.registerTask( 'build', 'shell:html' );
};

module.exports = function(grunt) {
	grunt.loadNpmTasks('grunt-git');
	grunt.loadNpmTasks('grunt-shell');

	grunt.initConfig( {
		gitclone: {
			mastercointools: {
				options: {
					repository: 'https://github.com/curtislacy/mastercoin-tools.git',
                			branch: 'master',
                			directory: 'node_modules/mastercoin-tools'
				},
			}
		},
		shell: {
			html: {
				command: "./gen_www.sh"
			}
		}
	} );

	grunt.registerTask( 'default', function( file ) {
		if( !grunt.file.exists( 'node_modules/mastercoin-tools' ))
			grunt.task.run( 'gitclone:mastercointools' );
		grunt.task.run( 'shell:html' );
	} );

	grunt.registerTask( 'build', 'shell:html' );
};

Switch to using the master branch of my mastercoin-tools fork.


'use strict';

module.exports = function (grunt) {
  grunt.initConfig({
    jshint: {
      options: {
        jshintrc: '.jshintrc'
      },
      gruntfile: {
        src: 'Gruntfile.js'
      },
      lib: {
        src: ['./*.js', 'routes/*.js', 'models/*.js']
      }
    },
    watch: {
      gruntfile: {
        files: '<%= jshint.gruntfile.src %>',
        tasks: ['jshint:gruntfile']
      },
      lib: {
        files: '<%= jshint.lib.src %>',
        tasks: ['jshint:lib', 'express:dev'],
        options: {
          spawn: false
        }
      },
    },
    express: {
      dev: {
        options: {
          script: './bin/www'
        }
      },
      prod: {
        options: {
          script: './bin/www',
          node_env: 'production'
        }
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-express-server');
  grunt.registerTask('default', ['jshint']);
  grunt.registerTask('server', [ 'express:dev', 'watch' ]);

};

'use strict';

module.exports = function (grunt) {
  grunt.initConfig({
    jshint: {
      options: {
        jshintrc: '.jshintrc'
      },
      gruntfile: {
        src: 'Gruntfile.js'
      },
      lib: {
        src: ['./*.js', 'routes/*.js', 'models/*.js']
      }
    },
    watch: {
      gruntfile: {
        files: '<%= jshint.gruntfile.src %>',
        tasks: ['jshint:gruntfile']
      },
      lib: {
        files: ['<%= jshint.lib.src %>', 'config.json'],
        tasks: ['jshint:lib', 'express:dev'],
        options: {
          spawn: false
        }
      },
    },
    express: {
      dev: {
        options: {
          script: './bin/www'
        }
      },
      prod: {
        options: {
          script: './bin/www',
          node_env: 'production'
        }
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-express-server');
  grunt.registerTask('default', ['jshint']);
  grunt.registerTask('server', [ 'express:dev', 'watch' ]);

};

Include watching config.json for changes


$(function handleSimpleIntegration() {

    cookieChoices.showCookieBar({
        linkHref: '#',
        cookieText: 'spine.io uses cookies to help operate the site and gather analytics data. You can read more about it in our',
        linkText: 'Privacy Statement',
        dismissText: 'I agree',
        dismissTextAlt: 'I agree',
        styles: 'position: fixed;'
    });

});

$(function handleSimpleIntegration() {

    cookieChoices.showCookieBar({
        linkHref: '#',
        cookieText: 'spine.io uses cookies to help operate the site and gather analytics data.',
        // Unlink this 2 lines to add link to Privacy Statements
        // cookieText: 'spine.io uses cookies to help operate the site and gather analytics data. You can read more about it in our',
        // linkText: 'Privacy Statement',
        dismissText: 'I agree',
        dismissTextAlt: 'I agree',
        styles: 'position: fixed;'
    });

});

Hide link to the Privacy Statement in the cookie notification


import crypto from 'crypto';

import { accountsKeyedbyAccessKey } from './vault.json';

export function hashSignature(stringToSign, secretKey, algorithm) {
    const hmacObject = crypto.createHmac(algorithm, secretKey);
    return hmacObject.update(stringToSign).digest('base64');
}

const vault = {
    authenticateV2Request: (accessKey, signatureFromRequest,
            stringToSign, callback) => {
        const account = accountsKeyedbyAccessKey[accessKey];
        if (!account) {
            return callback('InvalidAccessKeyId');
        }
        const secretKey = account.secretKey;
        const reconstructedSignature =
            hashSignature(stringToSign, secretKey, 'sha1');
        if (reconstructedSignature !== signatureFromRequest) {
            return callback('SignatureDoesNotMatch');
        }
        const userInfoToSend = {
            accountDisplayName: account.displayName,
            canonicalID: account.canonicalID,
            arn: account.arn,
            IAMdisplayName: account.IAMdisplayName,
        };

        // For now, I am just sending back the canonicalID.
        // TODO: Refactor so that the accessKey information
        // passed to the API is the full accountInfo Object
        // rather than just the canonicalID string.
        // This is GH Issue#75
        return callback(null, userInfoToSend.canonicalID);
    }

};

export default vault;

import crypto from 'crypto';

import { accountsKeyedbyAccessKey } from './vault.json';

export function hashSignature(stringToSign, secretKey, algorithm) {
    const hmacObject = crypto.createHmac(algorithm, secretKey);
    return hmacObject.update(stringToSign).digest('base64');
}

const vault = {
    authenticateV2Request: (accessKey, signatureFromRequest,
            stringToSign, callback) => {
        const account = accountsKeyedbyAccessKey[accessKey];
        if (!account) {
            return callback('InvalidAccessKeyId');
        }
        const secretKey = account.secretKey;
        // If the signature sent is 43 characters,
        // this means that sha256 was used:
        // 43 characters in base64
        const algo = signatureFromRequest.length === 43 ?
            'sha256' : 'sha1';
        const reconstructedSig =
            hashSignature(stringToSign, secretKey, algo);
        if (signatureFromRequest !== reconstructedSig) {
            return callback('SignatureDoesNotMatch');
        }
        const userInfoToSend = {
            accountDisplayName: account.displayName,
            canonicalID: account.canonicalID,
            arn: account.arn,
            IAMdisplayName: account.IAMdisplayName,
        };

        // For now, I am just sending back the canonicalID.
        // TODO: Refactor so that the accessKey information
        // passed to the API is the full accountInfo Object
        // rather than just the canonicalID string.
        // This is GH Issue#75
        return callback(null, userInfoToSend.canonicalID);
    }

};

export default vault;

Add check for sha256 hashed signature


var TiVoRemote = require('./TiVoRemote');
var readline = require('readline');

var config = {
  ip: '192.168.1.100',
  port: 31339
}
var remote = new TiVoRemote(config);

var rl = readline.createInterface({ input: process.stdin, output: process.stdout });
rl.setPrompt('');

remote.on('channel', function(channel) { console.log('Channel is now: ' + channel); });
remote.on('on', function(on) { console.log('On state is now: ' + on); });
remote.on('connecting', function() { console.log('Connecting'); });
remote.on('connect', function() { console.log('Connected'); rl.prompt(); });
remote.on('disconnect', function() { console.log('Disconnected'); });

rl.on('line', function(cmd) {
  remote.sendCommand(cmd);
});

var TiVoRemote = require('./TiVoRemote');
var readline = require('readline');

if (process.argv.length < 3) {
  console.log('Usage: <script> <ip>');
  process.exit(1);
}

var config = {
  ip: process.argv[2],
  port: 31339
}
var remote = new TiVoRemote(config);

var rl = readline.createInterface({ input: process.stdin, output: process.stdout });
rl.setPrompt('');

remote.on('channel', function(channel) { console.log('Channel is now: ' + channel); });
remote.on('on', function(on) { console.log('On state is now: ' + on); });
remote.on('connecting', function() { console.log('Connecting'); });
remote.on('connect', function() { console.log('Connected'); rl.prompt(); });
remote.on('disconnect', function() { console.log('Disconnected'); });

rl.on('line', function(cmd) {
  remote.sendCommand(cmd);
});

Allow setting IP from command line argument


import {ArrayExtensions} from './arrayextensions';
import {Letter} from './letter';
import {moveBefore, DIRECTION} from 'aurelia-dragula';

export class Welcome {

  constructor() {
    this.dragDirection = DIRECTION.HORIZONTAL;
    this.reset();
  }

  get unjumbledName() {
    return this.nameLetters.reduce((previous, current) => previous + current.char, '');
  }

  submit() {
    this.nameLetters = [];
    for(let i = 0; i < this.firstName.length; i++) {
      this.nameLetters.push(new Letter(this.firstName.charAt(i)));
    }

    ArrayExtensions.shuffle(this.nameLetters);

    //If, by chance, we shuffle it to the same as the original name, keep shuffling until it no longer is
    while(this.unjumbledName === this.firstName)
      ArrayExtensions.shuffle(this.nameLetters);

    this.haveName = true;
  }

  drop(item, target, source, sibling) {
    let itemId = item.dataset.id;
    let siblingId = sibling ? sibling.dataset.id : null;
    moveBefore(this.nameLetters, (letter) => letter.id === itemId, (letter) => letter.id === siblingId);
    if (this.unjumbledName === this.firstName)
      this.winner = true;
  }

  reset() {
    this.firstName = '';
    this.haveName = false;
    this.winner = false;
  }
}

import {ArrayExtensions} from './arrayextensions';
import {Letter} from './letter';
import {moveBefore, DIRECTION} from 'aurelia-dragula';

export class Welcome {

  constructor() {
    this.dragDirection = DIRECTION.HORIZONTAL;
    this.reset();
  }

  get unjumbledName() {
    return this.nameLetters.reduce((previous, current) => previous + current.char, '');
  }

  submit() {
    this.nameLetters = [];
    for(let i = 0; i < this.firstName.length; i++) {
      this.nameLetters.push(new Letter(this.firstName.charAt(i)));
    }

    do {
      ArrayExtensions.shuffle(this.nameLetters);
    }
    //If, by chance, we shuffle it to the same as the original name, keep shuffling until it no longer is
    while(this.unjumbledName === this.firstName)

    this.haveName = true;
  }

  drop(item, target, source, sibling) {
    let itemId = item.dataset.id;
    let siblingId = sibling ? sibling.dataset.id : null;
    moveBefore(this.nameLetters, (letter) => letter.id === itemId, (letter) => letter.id === siblingId);
    if (this.unjumbledName === this.firstName)
      this.winner = true;
  }

  reset() {
    this.firstName = '';
    this.haveName = false;
    this.winner = false;
  }
}

Make the code slightly clearer


define(['backbone', 'cilantro', 'text!/mock/fields.json'], function (Backbone, c, fieldsJSON) {
    var fields = new Backbone.Collection(JSON.parse(fieldsJSON));

    describe('FieldControl', function() {
        var control;

        describe('Base', function() {
            beforeEach(function() {
                control = new c.ui.FieldControl({
                    model: fields.models[0]
                });
                control.render();
            });

            it('should have an field by default', function() {
                expect(control.get()).toEqual({field: 30});
            });

            it('should never clear the field attr', function() {
                control.clear();
                expect(control.get()).toEqual({field: 30});
            });
        });

        describe('w/ Number', function() {
            beforeEach(function() {
                control = new c.ui.FieldControl({
                    model: fields.models[0]
                });
                control.render();
            });

            it('should coerce the value to a number', function() {
                control.set('value', '30');
                expect(control.get('value')).toEqual(30);
            });
        });

    });
});

define(['backbone', 'cilantro', 'text!/mock/fields.json'], function (Backbone, c, fieldsJSON) {
    var fields = new Backbone.Collection(JSON.parse(fieldsJSON));

    describe('FieldControl', function() {
        var control;

        describe('Base', function() {
            beforeEach(function() {
                control = new c.ui.FieldControl({
                    model: fields.models[0]
                });
                control.render();
            });

            it('should have an field by default', function() {
                expect(control.get()).toEqual({
                    field: 30,
                    value: null,
                    operator: null
                });
            });

            it('should never clear the field attr', function() {
                control.clear();
                expect(control.get()).toEqual({
                    field: 30,
                    value: null,
                    operator: null
                });
            });
        });

        describe('w/ Number', function() {
            beforeEach(function() {
                control = new c.ui.FieldControl({
                    model: fields.models[0]
                });
                control.render();
            });

            it('should coerce the value to a number', function() {
                control.set('value', '30');
                expect(control.get('value')).toEqual(30);
            });
        });

    });
});

Update spec to take in account null fields in context model


'use strict';

//supress log messages
process.env.LOG_LEVEL = 'fatal';

var chai = require('chai'),
    cap  = require('chai-as-promised');

global.sinon        = require('sinon');
global.fs           = require('fs');
global.utils        = require('radiodan-client').utils;
global.EventEmitter = require('events').EventEmitter;
global.assert       = chai.assert;
global.libDir       = __dirname + '/../lib/';

chai.use(cap);

'use strict';

//supress log messages
process.env.LOG_LEVEL = 'fatal';

var chai = require('chai'),
    cap  = require('chai-as-promised');

global.sinon        = require('sinon');
global.fs           = require('fs');
global.utils        = require('radiodan-client').utils;
global.EventEmitter = require('events').EventEmitter;
global.assert       = chai.assert;
global.fakeRadiodan = fakeRadiodan;
global.libDir       = __dirname + '/../lib/';

chai.use(cap);

function fakeRadiodan(objType) {
  switch(objType) {
    case 'player':
      return fakeRadiodanPlayer();
    case 'ui':
      return fakeRadiodanUI();
    default:
      throw new Error('Unknown radiodan component ' + objType);
  }
}

function fakeRadiodanPlayer() {
  var actions = ['add', 'clear', 'next', 'pause', 'play', 'previous', 'random', 'remove', 'repeat', 'search', 'status', 'stop', 'updateDatabase', 'volume'],
      players = ['main', 'announcer', 'avoider'],
      output = {};

  players.forEach(function(p) {
    output[p] = stubbedPromisesForActions(actions);
  });

  return output;
}

function fakeRadiodanUI() {
  var actions = ['emit'],
      obj     = { 'RGBLEDs': ['power'] },
      colours = { 'green': 'green'},
      output  = { colours: colours };

  Object.keys(obj).forEach(function(k) {
    output[k] = {};

    obj[k].forEach(function(p) {
      output[k][p] = stubbedPromisesForActions(actions);
    });
  });

  return output;
}

function stubbedPromisesForActions(actions) {
  var instance = {};

  actions.forEach(function(a) {
    instance[a] = sinon.stub().returns(utils.promise.resolve());
  });

  return instance;
}

Add fake Radiodan client objects


import DocumentConfigurator from './DocumentConfigurator'
import DocumentModel from './DocumentModel'
import DocumentJSONConverter from './DocumentJSONConverter'
import DocumentHTMLImporter from './DocumentHTMLImporter'
import DocumentHTMLExporter from './DocumentHTMLExporter'

function importJSON (content) {
  let doc = new DocumentModel()
  let jsonConverter = new DocumentJSONConverter()
  jsonConverter.importDocument(doc, content)
  return doc
}

function exportJSON (doc) {
  let jsonConverter = new DocumentJSONConverter()
  return jsonConverter.exportDocument(doc)
}

function importHTML (content) {
  let htmlImporter = new DocumentHTMLImporter({
    configurator: new DocumentConfigurator()
  })
  return htmlImporter.importDocument(content)
}

function exportHTML (doc) {
  let htmlExporter = new DocumentHTMLExporter({
    configurator: new DocumentConfigurator()
  })
  return htmlExporter.exportDocument(doc)
}

export {
  importJSON,
  exportJSON,
  importHTML,
  exportHTML
}

import DocumentConfigurator from './DocumentConfigurator'
import DocumentModel from './DocumentModel'
import DocumentJSONConverter from './DocumentJSONConverter'
import DocumentHTMLImporter from './DocumentHTMLImporter'
import DocumentHTMLExporter from './DocumentHTMLExporter'

function importJSON (content) {
  let doc = new DocumentModel()
  let jsonConverter = new DocumentJSONConverter()
  jsonConverter.importDocument(doc, content)
  return doc
}

function exportJSON (doc) {
  let jsonConverter = new DocumentJSONConverter()
  return jsonConverter.exportDocument(doc)
}

function importHTML (content) {
  let htmlImporter = new DocumentHTMLImporter({
    configurator: new DocumentConfigurator()
  })
  return htmlImporter.importDocument(content)
}

function exportHTML (doc) {
  let htmlExporter = new DocumentHTMLExporter({
    configurator: new DocumentConfigurator()
  })
  let html = htmlExporter.exportDocument(doc)
  html = html.replace(/ data-id=".+?"/g, '')
  return html
}

export {
  importJSON,
  exportJSON,
  importHTML,
  exportHTML
}

Remove the data-id attribute on HTML export


import Realm from 'realm';

export class InsuranceProvider extends Realm.Object {}

InsuranceProvider.schema = {
  name: 'InsuranceProvider',
  primaryKey: 'id',
  properties: {
    id: 'string',
    name: 'string',
    comment: 'string?',
    validityDays: 'int',
    isActive: 'bool',
    policies: {
      type: 'linkingObjects',
      objectType: 'InsurancePolicy',
      property: 'insuranceProvider',
    },
  },
};

export default InsuranceProvider;

import Realm from 'realm';

export class InsuranceProvider extends Realm.Object {}

InsuranceProvider.schema = {
  name: 'InsuranceProvider',
  primaryKey: 'id',
  properties: {
    id: 'string',
    name: { type: 'string', optional: true },
    comment: { type: 'string', optional: true },
    validityDays: { type: 'int', optional: true },
    isActive: { type: 'bool', optional: true },
    policies: {
      type: 'linkingObjects',
      objectType: 'InsurancePolicy',
      property: 'insuranceProvider',
    },
  },
};

export default InsuranceProvider;

Add update to provider properties


var path = require("path");
var stealTools = require("steal-tools");

var promise = stealTools.bundle({
  config: path.join(__dirname, "package.json!npm")
}, {
  filter: [ "**/*", "package.json" ],
  minify: true
});

var path = require("path");
var stealTools = require("steal-tools");

stealTools.bundle({
  config: path.join(__dirname, "package.json!npm")
}, {
  filter: [ "**/*", "package.json" ],
  minify: true
}).catch(function(error) {
  console.error(error);
  process.exit(1);
});

Exit with an error when the deps-bundle script fails


window.addEventListener("load", function() {
	const EXTRA_IMAGES = document.getElementsByClassName("alt-logo-img");
	EXTRA_IMAGES[0].classList.toggle("active");
	EXTRA_IMAGES[EXTRA_IMAGES.length - 1].classList.toggle("active");

	var just_switched = false;
	Array.from(EXTRA_IMAGES).forEach(function(img, idx) {
		var next_idx = idx + 1;
		if(next_idx == EXTRA_IMAGES.length)
			next_idx = 0;

		// Only triggered on active alt images
		img.addEventListener("mouseover", function() {
			if(!just_switched) {
				img.classList.toggle("active");
				EXTRA_IMAGES[next_idx].classList.toggle("active");
				just_switched = true;
			} else
				just_switched = false;
		});
	});
});

window.addEventListener("load", function() {
	const EXTRA_IMAGES = document.getElementsByClassName("alt-logo-img");
	EXTRA_IMAGES[0].classList.toggle("active");
	EXTRA_IMAGES[EXTRA_IMAGES.length - 1].classList.toggle("active");

	var just_switched = false;
	Array.from(EXTRA_IMAGES).forEach(function(img, idx) {
		var next_idx = (idx + 1) % EXTRA_IMAGES.length;

		// Only triggered on active alt images
		img.addEventListener("mouseover", function() {
			if(!just_switched) {
				img.classList.toggle("active");
				EXTRA_IMAGES[next_idx].classList.toggle("active");
				just_switched = true;
			} else
				just_switched = false;
		});
	});
});

Reduce if to a modulo for changing logos


console.log("=================================");
console.log("Creating search index...");
console.log("=================================");

// Scan through all content/ directories
//   For each markdown file
//     CreateIndex(title,content,path)

var lunr = require('lunr');

var idx = lunr(function () {
  this.field('title')
  this.field('body')

  this.add({
    "title": "Twelfth-Night",
    "body": "If music be the food of love, play on: Give me excess of it…",
    "author": "William Shakespeare",
    "id": "/william/shakespeare/"
  })
	this.add({
		"title": "Batman",
		"body": "Batman loves throwing batarangs.",
		"author": "DC Comics",
		"id": "/dc/batman/"
	})
})

console.log(idx.search("love"));
console.log("=================================");
console.log(JSON.stringify(idx));

console.log("=================================");
console.log("Creating search index...");
console.log("=================================");

// Scan through all content/ directories
//   For each markdown file
//     CreateIndex(title,content,path)

var lunr = require('lunr');

var idx = lunr(function () {
  this.field('title')
  this.field('body')
	this.field('author')
	this.ref('id')

  this.add({
    "title": "Wolverine",
    "body": "A animal-human hybrid with a indestructible metal skeleton",
    "author": "Marvel Comics",
    "id": "/xmen/wolverine/"
  })
	this.add({
		"title": "Batman",
		"body": "A cloaked hero with ninja like skills and dark personality",
		"author": "DC Comics",
		"id": "/justiceleague/batman/"
	})
	this.add({
		"title": "Superman",
		"body": "A humanoid alien that grew up on earth with super-human powers",
		"author": "DC Comics",
		"id": "/justiceleague/superman/"
	})
})

const searchTerm = "metal";

console.log(JSON.stringify(idx.search(searchTerm),null,2));
console.log("=================================");

// Build the search index
//console.log(JSON.stringify(idx));

Add super hero search data. Format the search output.


'use strict';

const router = require('express').Router();
const brewery = require('./breweryController');

router.route('/')
  .get(brewery.get)
  .post(brewery.post);

module.exports = router;

'use strict';

const router = require('express').Router();
const brewery = require('./breweryController');

router.route('/:location')
  .get(brewery.get)
  .post(brewery.post);

module.exports = router;

Add route param for location


module.exports = function (root, cb) {
  root.addEventListener('click', (ev) => {

    if (ev.altKey || ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.defaultPrevented) {
      return true
    }

    var anchor = null
    for (var n = ev.target; n.parentNode; n = n.parentNode) {
      if (n.nodeName === 'A') {
        anchor = n
        break
      }
    }
    if (!anchor) return true

    var href = anchor.getAttribute('href')

    if (href) {
      var isUrl

      try {
        var url = new URL(href)
        isUrl = true
      } catch (e) {
        isUrl = false
      }

      if (isUrl && url.host || isUrl && url.protocol === 'magnet:') {
        cb(href, true)
      } else if (href !== '#') {
        cb(href, false, anchor.anchor)
      }
    }

    ev.preventDefault()
    ev.stopPropagation()
  })
}

module.exports = function (root, cb) {
  root.addEventListener('click', (ev) => {

    if (ev.altKey || ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.defaultPrevented) {
      return true
    }

    var anchor = null
    for (var n = ev.target; n.parentNode; n = n.parentNode) {
      if (n.nodeName === 'A') {
        anchor = n
        break
      }
    }
    if (!anchor) return true

    var href = anchor.getAttribute('href')

    if (href) {
      var isUrl

      try {
        var url = new URL(href)
        isUrl = true
      } catch (e) {
        isUrl = false
      }

      if (isUrl && (url.host || url.protocol === 'magnet:')) {
        cb(href, true)
      } else if (href !== '#') {
        cb(href, false, anchor.anchor)
      }
    }

    ev.preventDefault()
    ev.stopPropagation()
  })
}

Refactor magnet URI link catch logic


var r = require('rethinkdb');

// Number of seconds after which Reddit will archive an article
// (180 days): https://github.com/reddit/reddit/commit/b7b24d2e9fa06ba37ea78e0275dce86d95158e64
var archiveLimit = 180 * 24 * 60 * 60;

exports.mostUrgentSubject = r.table('subjects')
  .between(r.now().sub(archiveLimit),r.now(),{index:'article_created'})
  .orderBy(r.desc(function(subject){
    var staleness = subject('last_checked').sub(r.now());
    var age = subject('article_created').sub(r.now());
    return staleness.div(age);
  })).limit(1).merge(function(subject){ return {
    forfeits: r.table('forfeits').getAll(
      subject('name'),{index: 'subject'})
      .filter(function(forfeit){
        return r.not(forfeit('withdrawn').default(false))})
      .merge(function(forfeit){ return {
        reply_klaxons: r.table('klaxons').getAll(
          forfeit('id'),{index:'forfeits'})
          .map(function(klaxon){return klaxon('parent')('id')})
          .coerceTo('array')
        };
      }).coerceTo('array')
    };
  })(0).default(null);

var r = require('rethinkdb');

// Number of seconds after which Reddit will archive an article
// (180 days): https://github.com/reddit/reddit/commit/b7b24d2e9fa06ba37ea78e0275dce86d95158e64
var archiveLimit = 180 * 24 * 60 * 60;

exports.mostUrgentSubject = r.table('subjects')
  .between(r.now().sub(archiveLimit),r.now(),{index:'article_created'})
  .orderBy(r.desc(function(subject){
    var staleness = subject('last_checked').sub(r.now());
    var age = subject('article_created').sub(r.now());
    return staleness.div(age);
  }))
  .limit(1)(0)
  .merge(function(subject){ return {
    forfeits: r.table('forfeits').getAll(
      subject('name'),{index: 'subject'})
      .filter(function(forfeit){
        return r.not(forfeit('withdrawn').default(false))})
      .merge(function(forfeit){ return {
        reply_klaxons: r.table('klaxons').getAll(
          forfeit('id'),{index:'forfeits'})
          .map(function(klaxon){return klaxon('parent')('id')})
          .coerceTo('array')
        };
      }).coerceTo('array')
    };
  })
  .default(null);

Put sub-index immediately after limit on query


var PromiseConstructor = typeof Promise === 'undefined' ? require('promise') : Promise;

module.exports = function(environment, ParseTree, ImportManager) {
    var render = function (input, options, callback) {
        var parse = require('./parse')(environment, ParseTree, ImportManager);

        if (typeof(options) === 'function') {
            callback = options;
            options = {};
        }

        if (!callback) {
            var self = this;
            return new PromiseConstructor(function (resolve, reject) {
                render.call(self, input, options, function(err, output) {
                    if (err) {
                        reject(err);
                    } else {
                        resolve(output);
                    }
                });
            });
        } else {
            parse(input, options, function(err, root, imports, options) {
                if (err) { return callback(err); }

                var result;
                try {
                    var parseTree = new ParseTree(root, imports);
                    result = parseTree.toCSS(options);
                }
                catch (err) { return callback(err); }

                callback(null, result);
            });
        }
    };

    return render;
};

var PromiseConstructor = typeof Promise === 'undefined' ? require('promise') : Promise;

module.exports = function(environment, ParseTree, ImportManager) {
    var render = function (input, options, callback) {
        if (typeof(options) === 'function') {
            callback = options;
            options = {};
        }

        if (!callback) {
            var self = this;
            return new PromiseConstructor(function (resolve, reject) {
                render.call(self, input, options, function(err, output) {
                    if (err) {
                        reject(err);
                    } else {
                        resolve(output);
                    }
                });
            });
        } else {
            this.parse(input, options, function(err, root, imports, options) {
                if (err) { return callback(err); }

                var result;
                try {
                    var parseTree = new ParseTree(root, imports);
                    result = parseTree.toCSS(options);
                }
                catch (err) { return callback(err); }

                callback(null, result);
            });
        }
    };

    return render;
};

Fix parse refactor - this passed to the plugin manager must be less


var util = require('util');

var sax = require('sax');

var TreeBuilder = require('./../treebuilder').TreeBuilder;

function XMLParser(target) {
  var self = this;
  this.parser = sax.parser(true);

  this.target = (target) ? target : new TreeBuilder();

  /* TODO: would be nice to move these out */
  this.parser.onopentag = function(tag) {
    self.target.start(tag.name, tag.attributes);
  };

  this.parser.ontext = function(text) {
    self.target.data(text);
  };

  this.parser.oncdata = function(text) {
    self.target.data(text);
  };

  this.parser.ondoctype = function(text) {

  };

  this.parser.oncomment = function(comment) {
    /* TODO: parse comment? */
  };

  this.parser.onclosetag = function(tag) {
    self.target.end(tag);
  };

  this.parser.onerror = function(error) {
    throw error;
  };
}

XMLParser.prototype.feed = function(chunk) {
  this.parser.write(chunk);
};

XMLParser.prototype.close = function() {
  this.parser.close();
  return this.target.close();
};

exports.XMLParser = XMLParser;

var util = require('util');

var sax = require('sax');

var TreeBuilder = require('./../treebuilder').TreeBuilder;

function XMLParser(target) {
  this.parser = sax.parser(true);

  this.target = (target) ? target : new TreeBuilder();

  this.parser.onopentag = this._handleOpenTag.bind(this);
  this.parser.ontext = this._handleText.bind(this);
  this.parser.oncdata = this._handleCdata.bind(this);
  this.parser.ondoctype = this._handleDoctype.bind(this);
  this.parser.oncomment = this._handleComment.bind(this);
  this.parser.onclosetag = this._handleCloseTag.bind(this);
  this.parser.onerror = this._handleError.bind(this);
}

XMLParser.prototype._handleOpenTag = function(tag) {
  this.target.start(tag.name, tag.attributes);
};

XMLParser.prototype._handleText = function(text) {
  this.target.data(text);
};

XMLParser.prototype._handleCdata = function(text) {
  this.target.data(text);
};

XMLParser.prototype._handleDoctype = function(text) {
};

XMLParser.prototype._handleComment = function(comment) {
};

XMLParser.prototype._handleCloseTag = function(tag) {
  this.target.end(tag);
};

XMLParser.prototype._handleError = function(err) {
  throw err;
};

XMLParser.prototype.feed = function(chunk) {
  this.parser.write(chunk);
};

XMLParser.prototype.close = function() {
  this.parser.close();
  return this.target.close();
};

exports.XMLParser = XMLParser;

Move methods out of the constructor.


import { describe } from 'riteway';

import dsm from '../dsm';

const SIGNED_OUT = 'signed_out';
const AUTHENTICATING = 'authenticating';

const actionStates = [
  ['initialize', SIGNED_OUT,
    ['sign in', AUTHENTICATING
      // ['report error', 'error',
      //   ['handle error', 'signed out']
      // ],
      // ['sign-in success', 'signed in']
    ]
  ]
];

const { reducer, actionCreators: { initialize, signIn } } = dsm({
  component: 'user-authentication',
  description: 'authenticate user',
  actionStates
});

const createState = ({
  status = SIGNED_OUT,
  payload = { type: 'empty' }
} = {}) => ({ status, payload });

describe('userAuthenticationReducer', async should => {
  {
    const {assert} = should('use "signed out" as initialized state');

    assert({
      given: '["initialize", "signed out", /*...*/',
      actual: reducer(),
      expected: createState()
    });
  }

  {
    const {assert} = should('transition into authenticating state');
    const initialState = reducer(undefined, initialize());

    assert({
      given: 'signed out initial state & signIn action',
      actual: reducer(initialState, signIn()),
      expected: createState({ status: 'authenticating' })
    });
  }
});

import { describe } from 'riteway';

import dsm from '../dsm';

const SIGNED_OUT = 'signed_out';
const AUTHENTICATING = 'authenticating';

const actionStates = [
  ['initialize', SIGNED_OUT,
    ['sign in', AUTHENTICATING
      // ['report error', 'error',
      //   ['handle error', 'signed out']
      // ],
      // ['sign-in success', 'signed in']
    ]
  ]
];

const { reducer, actionCreators: { initialize, signIn } } = dsm({
  component: 'user-authentication',
  description: 'authenticate user',
  actionStates
});

describe('userAuthenticationReducer', async should => {
  {
    const {assert} = should('use "signed out" as initialized state');

    assert({
      given: '["initialize", "signed out", /*...*/',
      actual: reducer().status,
      expected: SIGNED_OUT
    });
  }

  {
    const {assert} = should('transition into authenticating state');
    const initialState = reducer(undefined, initialize());

    assert({
      given: 'signed out initial state & signIn action',
      actual: reducer(initialState, signIn()).status,
      expected: AUTHENTICATING
    });
  }
});

Fix test to use main unit test files standards


var path = require('path')
var loaderUtils = require('loader-utils')
var HtmlbarsCompiler = require('ember-cli-htmlbars')

var DEFAULT_TEMPLATE_COMPILER = 'components-ember/ember-template-compiler.js'
var templateTree
var appPath

/*
 * options:
 *  - appPath: default assuming webpack.config.js in root folder + ./app
 *  - templateCompiler: default 'components-ember/ember-template-compiler.js'
 */
module.exports = function(source) {
	this.cacheable && this.cacheable()
	var options = loaderUtils.getOptions(this);
  var appPath = (options.appPath || 'app').replace(/\/$/, '')
  var templatesFolder = path.join(appPath, 'templates')

  templateTree = templateTree || new HtmlbarsCompiler(templatesFolder, {
    isHTMLBars: true,
    templateCompiler: require(options.templateCompiler || DEFAULT_TEMPLATE_COMPILER)
  });

  var resourcePathMatcher = new RegExp(templatesFolder + '/(.*)\.hbs$')
  var templateMatch = this.resourcePath.match(resourcePathMatcher)
  var templatePath = templateMatch.pop()

  var fullTemplate = templateTree.processString(source, templatePath)
  var templateString = fullTemplate.replace(/^export default Ember\./, 'Ember.')
  return 'Ember.TEMPLATES["' + templatePath + '"] = ' + templateString
}

Add indentation fix on htmlbar loader.


var div = React.DOM.div;
var h1 = React.DOM.h1;

var MyTitle = React.createClass({
  render: function() {
    return (
      div(null, h1(null, this.props.title))
    )
  }
})

var MyTitleFactory = React.createFactory(MyTitle)

var MyFirstComponent = React.createClass({
      render: function() {
        return (
          div(null, [
            MyTitleFactory({ title: 'props are the best' }),
            MyTitleFactory({ title: 'jk' }),
            MyTitleFactory({ title: 'whatever'}),
            MyTitleFactory({ title: 'best workshop' })
            ]
          )
        )
      }
    })

ReactDOM.render(React.createElement(MyFirstComponent), document.getElementById('app'))

var div = React.DOM.div;
var h1 = React.DOM.h1;

var MyTitle = React.createClass({
  render: function() {
    return (
      div(null, h1({ style: {color: this.props.color} }, this.props.title))
    )
  }
})

var MyTitleFactory = React.createFactory(MyTitle)

var MyFirstComponent = React.createClass({
      render: function() {
        return (
          div(null, [
            MyTitleFactory({title: 'Props are great!', color: 'rebeccapurple'}),
            MyTitleFactory({title: 'Props are great!', color: 'dodgerblue'}),
            MyTitleFactory({title: 'Use props everywhere!', color: 'mediumaquamarine'}),
            MyTitleFactory({title: 'Props are the best!', color: 'peru'})
            ]
          )
        )
      }
    })

ReactDOM.render(React.createElement(MyFirstComponent), document.getElementById('app'))

Add props to the components


'use strict';

var path = require('path');

var config = require('../config');

module.exports = function (req, res, next) {
    var app      = req.app,
        locales  = app.get('locales'),
        locale   = req.acceptsLanguage(locales) || app.get('default locale'),
        messages = require(path.join(config.dirs.i18n, locale));

    req.locale = locale;

    res.locals.intl || (res.locals.intl = {});
    res.locals.intl.locale   = locale;
    res.locals.intl.messages = messages;

    // TODO: Handle/merge and Expose the common formats.
    res.expose(res.locals.intl, 'APP.intl')

    next();
};

'use strict';

var path = require('path');

var config = require('../config');

module.exports = function (req, res, next) {
    var app      = req.app,
        locales  = app.get('locales'),
        locale   = req.acceptsLanguage(locales) || app.get('default locale'),
        messages = require(path.join(config.dirs.i18n, locale));

    req.locale = locale;

    res.locals.intl || (res.locals.intl = {});
    res.locals.intl.locale   = locale;
    res.locals.intl.messages = messages;

    // TODO: Handle/merge and Expose the common formats.
    res.expose(res.locals.intl, 'intl')

    next();
};

Remove APP prefix from middleware


var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var analysisSchema = new Schema({
  user_id:     {type: Schema.Types.ObjectId, ref: 'User', required: true},
  analysis_name: {type: String, required: true},
  analysis:    {type: String, required: true},
  corpora_ids: [{type: String, required: true}],
  result:      Schema.Types.Mixed
});

module.exports = mongoose.model('Analysis', analysisSchema);

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var analysisSchema = new Schema({
  user_id:     {type: Schema.Types.ObjectId, ref: 'User', required: true},
  analysis_name: {type: String, required: true},
  eta: {type: Number, required: true},
  time_created: {type: Date, required: true},
  analysis:    {type: String, required: true},
  corpora_ids: [{type: String, required: true}],
  result:      Schema.Types.Mixed
});

module.exports = mongoose.model('Analysis', analysisSchema);

Add eta and time created to db schema