const assert = require('assert');
const parse = require('../app/shared/database/parse');

describe('database', () => {
  describe('parse.js', () => {
    const testQuery = 'Learn something! @3+2w !';
    it('should return correct task text', () => {
      assert(parse(testQuery).text === 'Learn something!');
    });
    it('should return correct starting point', () => {
      assert(parse(testQuery).start < Date.now() + (14 * 86400000) + 1000);
    });
    it('should return correct ending point', () => {
      assert(parse(testQuery).end < Date.now() + (17 * 86400000) + 1000);
    });
    it('should return correct importance', () => {
      assert(parse(testQuery).importance === 2);
    });
    it('should return correct status', () => {
      assert(parse(testQuery).status === 0);
    });
  });
});

const assert = require('assert');
const parse = require('../app/shared/database/parse');

describe('database', () => {
  describe('parse.js', () => {
    const testQuery = 'Learn something! @3+2w !';
    it('should return task text', () => {
      assert(parse(testQuery).text === 'Learn something!');
    });
    it('should return starting point', () => {
      assert(parse(testQuery).start < Date.now() + (14 * 86400000) + 1000);
    });
    it('should return ending point', () => {
      assert(parse(testQuery).end < Date.now() + (17 * 86400000) + 1000);
    });
    it('should return importance', () => {
      assert(parse(testQuery).importance === 2);
    });
    it('should return status', () => {
      assert(parse(testQuery).status === 0);
    });
  });
});

Remove "correct" word from test titles


var hoverZoomPlugins = hoverZoomPlugins || [];
hoverZoomPlugins.push({
    name:'Know Your Meme',
    version:'0.1',
    prepareImgLinks:function (callback) {
        var res = [];
        hoverZoom.urlReplace(res,
            'a img.small',
            '/small/',
            '/original/'
        );
        callback($(res));
    }
});

var hoverZoomPlugins = hoverZoomPlugins || [];
hoverZoomPlugins.push({
    name:'Know Your Meme',
    version:'0.2',
    prepareImgLinks:function (callback) {
        var res = [];

        hoverZoom.urlReplace(res,
            'a img.small',
            '/small/',
            '/original/'
        );

        hoverZoom.urlReplace(res,
            'img[src]',
            ['/list/', '/masonry/', '/medium/', '/newsfeed/', '/tiny/'],
            ['/original/', '/original/', '/original/', '/original/', '/original/']
        );

        callback($(res), this.name);
    }
});

Update for plug-in : KnowYourMeme


var document = require('global/document');
var value = require('observ');
var struct = require('observ-struct');
var Delegator = require('dom-delegator');
var mainLoop = require('main-loop');
var h = require('virtual-hyperscript');
var MultipleEvent = require('geval/multiple');
var changeEvent = require('value-event/change');

var courses = require('./catalog');

var state = struct({
  query: value(''),
  events: MultipleEvent(['change'])
});

state.events.change(function(data) {
  state.query.set(data.query);
});

Delegator();
var loop = mainLoop(state(), render);
document.body.appendChild(loop.target);
state(loop.update);

function render(state) {
  var results = courses.filter(function(course) {
    return course.name.toLowerCase().indexOf(state.query) >= 0
        || course.code.toLowerCase().indexOf(state.query) >= 0;
  });

  var ret = [];

  var inputField = h('input', {
    type: 'text',
    name: 'query',
    value: String(state.query),
    'ev-event': changeEvent(state.events.change),
    autofocus: true
  });

  results.forEach(function(course) {
    ret.push(h('li', [
      h('h3', [
        h('span.code', course.code),
        h('span', ' ' + course.name)
      ]),
      h('p', course.description)
    ]));
  });

  return h('div', [
    inputField,
    h('ul', ret)
  ]);
}

var document = require('global/document');
var value = require('observ');
var struct = require('observ-struct');
var Delegator = require('dom-delegator');
var mainLoop = require('main-loop');
var h = require('virtual-hyperscript');
var MultipleEvent = require('geval/multiple');
var changeEvent = require('value-event/change');

var courses = require('./catalog');

var state = struct({
  query: value(''),
  events: MultipleEvent(['change'])
});

state.events.change(function(data) {
  state.query.set(data.query);
});

Delegator();
var loop = mainLoop(state(), render);
document.body.appendChild(loop.target);
state(loop.update);

function render(state) {
  var results = courses.filter(function(course) {
    return course.name.toLowerCase().indexOf(state.query) >= 0
        || course.code.toLowerCase().indexOf(state.query) >= 0;
  });

  var ret = [];

  var inputField = h('input', {
    type: 'search',
    name: 'query',
    value: String(state.query),
    'ev-event': changeEvent(state.events.change),
    autofocus: true
  });

  results.forEach(function(course) {
    ret.push(h('li', [
      h('h3', [
        h('span.code', course.code),
        h('span', ' ' + course.name)
      ]),
      h('p', course.description)
    ]));
  });

  return h('div', [
    inputField,
    h('ul', ret)
  ]);
}

Change search field type to "search"


'use strict'
const fg = require('d3-fg')
const render = require('nanohtml')
const morphdom = require('morphdom')
const debounce = require('debounce')
const createActions = require('./actions')
const createState = require('./state')
const graph = require('./cmp/graph')(render)
const ui = require('./cmp/ui')(render)

module.exports = function (trees, opts) {
  opts = opts || {}
  const { kernelTracing } = opts
  const exclude = new Set(['cpp', 'regexp', 'v8', 'native', 'init'])

  const chart = graph()
  const tree = trees.unmerged // default view
  const categorizer = !kernelTracing && graph.v8cats
  const flamegraph = fg({
    categorizer,
    tree,
    exclude: Array.from(exclude),
    element: chart
  })
  const { colors } = flamegraph

  window.addEventListener('resize', debounce(() => {
    const width = document.body.clientWidth * 0.89
    flamegraph.width(width).update()
    chart.querySelector('svg').setAttribute('width', width)
  }, 150))

  const state = createState({colors, trees, exclude, kernelTracing, title: opts.title})

  const actions = createActions({flamegraph, state}, (state) => {
    morphdom(iface, ui({state, actions}))
  })
  const iface = ui({state, actions})

  document.body.appendChild(chart)
  document.body.appendChild(iface)
}

'use strict'
const fg = require('d3-fg')
const render = require('nanohtml')
const morphdom = require('morphdom')
const debounce = require('debounce')
const createActions = require('./actions')
const createState = require('./state')
const graph = require('./cmp/graph')(render)
const ui = require('./cmp/ui')(render)

module.exports = function (trees, opts) {
  opts = opts || {}
  const { kernelTracing } = opts
  const exclude = new Set(['cpp', 'regexp', 'v8', 'native', 'init'])

  const chart = graph()
  const tree = trees.unmerged // default view
  const categorizer = !kernelTracing && graph.v8cats
  const flamegraph = fg({
    categorizer,
    tree,
    exclude: Array.from(exclude),
    element: chart,
    topOffset: 55
  })
  const { colors } = flamegraph

  window.addEventListener('resize', debounce(() => {
    const width = document.body.clientWidth * 0.89
    flamegraph.width(width).update()
    chart.querySelector('svg').setAttribute('width', width)
  }, 150))

  const state = createState({colors, trees, exclude, kernelTracing, title: opts.title})

  const actions = createActions({flamegraph, state}, (state) => {
    morphdom(iface, ui({state, actions}))
  })
  const iface = ui({state, actions})

  document.body.appendChild(chart)
  document.body.appendChild(iface)
}

Add top offset to prevent top bar from cropping the top stack


import Ember from 'ember';

Ember.deprecate("ember-getowner-polyfill is now a true polyfill. Use Ember.getOwner directly instead of importing from ember-getowner-polyfill", false, {
  id: "ember-getowner-polyfill.import"
});

export default Ember.getOwner;

import Ember from 'ember';

Ember.deprecate("ember-getowner-polyfill is now a true polyfill. Use Ember.getOwner directly instead of importing from ember-getowner-polyfill", false, {
  id: "ember-getowner-polyfill.import",
  until: '3.0.0'
});

export default Ember.getOwner;

Add option.until to prevent extra deprecation


import React from 'react';
import classnames from 'classnames';

const Media = (props) => {
  const children = props.children;

  return (
    <div className={classnames('ui-media')}>
      {children}
    </div>
  );
};

Media.propTypes = {
  children: React.PropTypes.node,
};

export default Media;

import React from 'react';
import classnames from 'classnames';

const Media = ({ compact, children }) => {
  const className = classnames('ui-media', {
    'ui-media-compact': compact,
  });

  return (
    <div className={className}>
      {children}
    </div>
  );
};

Media.propTypes = {
  children: React.PropTypes.node,
  compact: React.PropTypes.bool,
};

Media.defaultProps = {
  compact: false,
  children: null,
};

export default Media;

Introduce compact prop for media component


'use strict';

const config = require('config');
const logger = require('../modules').logger;
const rollbarHelper = require('../modules').rollbarHelper;

const invalidator = require('./invalidator');
const opendata = require('./opendata');

function shutdown() {
  logger.info('[WORKER] Ending');
  setTimeout(process.exit, config.worker.exit_timeout);
}

if (!module.parent) {
  require('../modules').mongooseHelper.connect()
    .then(() => rollbarHelper.init())
    .then(() => {
      invalidator.start();
      opendata.start();

      logger.info('[WORKER] Started');
    })
    .catch(err => {
      logger.error(err, '[WORKER] Uncaught error');
      rollbarHelper.rollbar.handleError(err, '[WORKER] Uncaught exception');
      shutdown();
    });

  process.on('SIGTERM', shutdown);
}

module.exports = {
  opendata
};

'use strict';

const config = require('config');
const logger = require('../modules').logger;
const mongooseHelper = require('../modules').mongooseHelper;
const rollbarHelper = require('../modules').rollbarHelper;

const invalidator = require('./invalidator');
const opendata = require('./opendata');

function shutdown() {
  logger.info('[WORKER] Ending');
  setTimeout(process.exit, config.worker.exit_timeout);
}

if (!module.parent) {
  rollbarHelper.init()
    .then(() => mongooseHelper.connect())
    .then(() => {
      invalidator.start();
      opendata.start();

      logger.info('[WORKER] Started');
    })
    .catch(err => {
      logger.error(err, '[WORKER] Uncaught error');
      rollbarHelper.rollbar.handleError(err, '[WORKER] Uncaught exception');
      shutdown();
    });

  process.on('SIGTERM', shutdown);
}

module.exports = {
  opendata
};

Initialize rollbar before mongoose in worker


var path = require('path');
var _ = require('underscore')._;

module.exports = function(app, settings) {
    var providers = {};
    for (var p in settings.providers) {
        // TODO :express better
        providers[p] = require('./' + path.join(p, 'index'))(app, settings);
    }
    app.get('/provider', function(req, res) {
        res.send({
            status: true,
            provider: _.keys(settings.providers)
        });
    });
    app.get('/provider/:provider', function(req, res) {
        if (providers[req.params.provider]) {
            providers[req.params.provider].objects(function(objects) {
                res.send(objects);
            });
        }
        else {
            // @TODO: better error handling here.
            // Currently allows graceful downgrade of missing S3 information
            // but ideally client side JS would not request for the S3 provider
            // at all if it is not enabled.
            res.send([]);
        }
    });
};

var path = require('path');
var _ = require('underscore')._;

module.exports = function(app, settings) {
    var providers = {};
    for (var p in settings.providers) {
        // TODO :express better
        providers[p] = require('./' + path.join(p, 'index'))(app, settings);
    }
    app.get('/provider', function(req, res) {
        res.send({
            status: true,
            provider: _.keys(settings.providers)
        });
    });
    /**
     * This endpoint is a backbone.js collection compatible REST endpoint
     * providing datasource model objects. The models provided are read-only
     * and cannot be created, saved or destroyed back to the server.
     */
    app.get('/provider/:provider', function(req, res) {
        if (providers[req.params.provider]) {
            providers[req.params.provider].objects(function(objects) {
                res.send(objects);
            });
        }
        else {
            // @TODO: better error handling here.
            // Currently allows graceful downgrade of missing S3 information
            // but ideally client side JS would not request for the S3 provider
            // at all if it is not enabled.
            res.send([]);
        }
    });
};

Add comment which notes that the provider endpoint is a backbone.js collection endpoint as well.


import { Component } from './component';
import { createElement } from './create-element';

// having a "custom class" here saves 50bytes gzipped
export function s(props) {}
s.prototype = new Component();
s.prototype._childDidSuspend = function(e) {
	this.setState({ _loading: true });
	const cb = () => { this.setState({ _loading: false }); };

	// Suspense ignores errors thrown in Promises as this should be handled by user land code
	e.then(cb, cb);
};
s.prototype.render = function(props, state) {
	return state._loading ? props.fallback : props.children;
};

// exporting s as Suspense instead of naming the class iself Suspense saves 4 bytes gzipped
// TODO: does this add the need of a displayName?
export const Suspense = s;

export function lazy(loader) {
	let prom;
	let component;
	let error;
	return function L(props) {
		if (!prom) {
			prom = loader();
			prom.then(
				({ default: c }) => { component = c; },
				e => error = e,
			);
		}

		if (error) {
			throw error;
		}

		if (!component) {
			throw prom;
		}

		return createElement(component, props);
	};
}

import { Component } from './component';
import { createElement } from './create-element';

// having a "custom class" here saves 50bytes gzipped
export function s(props) {}
s.prototype = new Component();

/**
 * @param {Promise} promise The thrown promise
 */
s.prototype._childDidSuspend = function(promise) {
	this.setState({ _loading: true });
	const cb = () => { this.setState({ _loading: false }); };

	// Suspense ignores errors thrown in Promises as this should be handled by user land code
	promise.then(cb, cb);
};

s.prototype.render = function(props, state) {
	return state._loading ? props.fallback : props.children;
};

// exporting s as Suspense instead of naming the class iself Suspense saves 4 bytes gzipped
// TODO: does this add the need of a displayName?
export const Suspense = s;

export function lazy(loader) {
	let prom;
	let component;
	let error;
	return function L(props) {
		if (!prom) {
			prom = loader();
			prom.then(
				({ default: c }) => { component = c; },
				e => error = e,
			);
		}

		if (error) {
			throw error;
		}

		if (!component) {
			throw prom;
		}

		return createElement(component, props);
	};
}

Add some jsdoc to _childDidSuspend


import { createSelector } from 'reselect';
// import omit from 'lodash/omit';
// import qs from 'query-string';

const getSections = routeData => routeData.route.sections || null;
// const getSearch = routeData => routeData.location.search || null;
// const getHash = routeData => routeData.hash || null;
// const getRoutes = routeData => routeData.route.routes || null;

export const getAnchorLinks = createSelector([getSections], sections =>
  sections.filter(route => route.anchor).map(route => ({
    label: route.label,
    path: route.path,
    hash: route.hash,
    component: route.component
  }))
);

// export const getRouteLinks = createSelector(
//   [getRoutes, getHash, getSearch],
//   (routes, hash, search) =>
//     routes &&
//     routes.filter(r => r.anchor).map(route => ({
//       label: route.label,
//       path: route.path,
//       search: qs.stringify(omit(qs.parse(search), 'search')), // we want to reset the search on tabs change
//       hash
//     }))
// );

export default {
  getAnchorLinks
  // getRouteLinks
};

import { createSelector } from 'reselect';

const getSections = routeData => routeData.route.sections || null;

export const getAnchorLinks = createSelector([getSections], sections =>
  sections.filter(route => route.anchor).map(route => ({
    label: route.label,
    path: route.path,
    hash: route.hash,
    component: route.component
  }))
);

export default {
  getAnchorLinks
};

Remove commented code from agriculture selectors


function Promise_all(promises) {
    return new Promise((resolve, reject) => {
        let ctr = promises.length;
        let resArray = [];
        if (ctr === 0) resolve(resArray);
        for (let idx = 0; idx < promises.length; ++idx) {
            promises[idx].then(result => {
                resArray[idx] = result;
                --ctr;
                if (ctr === 0) resolve(resArray);
            }, reject);
        }
    });
}

function Promise_all(promises) {
	return new Promise((resolve, reject) => {
		let result = [];
		let unresolved = promises.length;
		if (unresolved == 0) resolve(result);

		for (let i = 0; i < promises.length; ++i) {
			promises[i]
				.then(value => {
					result[i] = value;
					--unresolved;
					if (unresolved == 0) resolve(result);
				})
				.catch(error => reject(error));
		}
	});
}

Add chapter 11, exercise 2


$(function() {

	$("form :button").each(function(){

		var button = $(this);

		if ($(button).attr('id') == 'cancel') {
			$(button).click(function(e){
				e.preventDefault();
				history.back();
			});
		}
		else {
			$(button).click(function(e){

				var buttonId = $(button).attr('id');

				if (typeof buttonId !== 'undefined') {

					e.preventDefault();
					var form = $(this).closest('form');
					var originalActionUrl = $(form).attr('action');

					if ($(form).valid()) {

						if (buttonId == "plan" || buttonId == "apply") {
							$('#myModal').modal('toggle');
						}

						var submit = true;

						if (buttonId.startsWith('delete')) {
							submit = confirm('Do you really want to delete this item?');
							if (submit) {
								$('#myModal').modal('toggle');
							}
						}

						if (submit) {
							$(form).attr('action', originalActionUrl + "/" + buttonId);
							$(form).submit();
							$(form).attr('action', originalActionUrl);
						}
					}
				}
			});
		}
	});

	$('#datatable').DataTable({
        responsive: true,
        order: [[ 0, 'desc' ]]
    });
});

$(function() {

	$("form :button").each(function(){

		var button = $(this);

		if ($(button).attr('id') == 'cancel') {
			$(button).click(function(e){
				e.preventDefault();
				history.back();
			});
		}
		else {
			$(button).click(function(e){

				var buttonId = $(button).attr('id');

				if (typeof buttonId !== 'undefined') {

					e.preventDefault();
					var form = $(this).closest('form');
					var originalActionUrl = $(form).attr('action');

					if ($(form).valid()) {

						if (buttonId == "plan" || buttonId == "apply") {
							$('#myModal').modal('toggle');
						}

						var submit = true;

						if (buttonId.startsWith('delete')) {
							submit = confirm('Do you really want to delete this item?');
							if (submit) {
								$('#myModal').modal('toggle');
							}
						}

						if (submit) {
							$(form).attr('action', originalActionUrl + "/" + buttonId);
							$(form).submit();
							$(form).attr('action', originalActionUrl);
						}
					}
				}
			});
		}
	});

	$('#datatable').DataTable({
        responsive: true,
        order: [[ 0, 'asc' ]]
    });
});

Fix for vm list ordering


var fs = require('fs');
var path = require('path');
var md = require('cli-md');

module.exports = function (filepath) {
  return md(fs.readFileSync(filepath, 'utf8'))
    .replace(/'/g, "'")
    .replace(/"/g, '"')
    .replace(/</g, '<')
    .replace(/>/g, '<');
};

Fix greate/less than HTML entity in problem description


var socket = io.connect('/');
var waitMilliSec = 1000;
var oldJavaCode = '';
var oldClassName = '';

socket.on('byte_code', function(data) {
	$('#output_bc').val(data.code);
});

socket.on('wrong', function(data) {
	$('#output_bc').val(data.err);
});

$(function() {
	var inputJavaCM = CodeMirror.fromTextArea(document.getElementById('input_java'), {
		mode: 'clike',
		lineNumbers: true
	});

	var idleIntervalId = setInterval(function() {
		newJavaCode = inputJavaCM.getValue();
		newClassName = $('#class_name').val();
		checkDiff(newJavaCode, newClassName);
	} , waitMilliSec);

	function checkDiff(newJavaCode, newClassName) {
		if((newJavaCode != oldJavaCode) || (newClassName != oldClassName)) {
			socket.emit('code_sent', {
				code: newJavaCode,
				className: newClassName
			});
			oldJavaCode = newJavaCode;
			oldClassName = newClassName;
		}
	}
});

var socket = io.connect('/');
var waitMilliSec = 1000;
var oldJavaCode = '';
var oldClassName = '';
var outputBcCM;

socket.on('byte_code', function(data) {
	outputBcCM.setValue(data.code);
});

socket.on('wrong', function(data) {
	outputBcCM.setValue(data.err);
});

$(function() {
	var inputJavaCM = CodeMirror.fromTextArea(document.getElementById('input_java'), {
		mode: 'clike',
		lineNumbers: true
	});
	outputBcCM = CodeMirror.fromTextArea(document.getElementById('output_bc'), {
		mode: 'clike',
		lineNumbers: true
	});

	var idleIntervalId = setInterval(function() {
		newJavaCode = inputJavaCM.getValue();
		newClassName = $('#class_name').val();
		checkDiff(newJavaCode, newClassName);
	} , waitMilliSec);

	function checkDiff(newJavaCode, newClassName) {
		if((newJavaCode != oldJavaCode) || (newClassName != oldClassName)) {
			socket.emit('code_sent', {
				code: newJavaCode,
				className: newClassName
			});
			oldJavaCode = newJavaCode;
			oldClassName = newClassName;
		}
	}
});

Apply CodeMirror to output textarea.


export function SET_CLASS_LESSONS(state, lessons) {
  state.pageState.lessons = lessons;
}

export function SET_CURRENT_LESSON(state, lesson) {
  state.pageState.currentLesson = lesson;
}

export function SET_LEARNER_GROUPS(state, learnerGroups) {
  state.pageState.learnerGroups = learnerGroups;
}

export function SET_CLASS_LESSONS(state, lessons) {
  state.pageState.lessons = lessons;
}

export function SET_CURRENT_LESSON(state, lesson) {
  state.pageState.currentLesson = { ...lesson };
}

export function SET_LEARNER_GROUPS(state, learnerGroups) {
  state.pageState.learnerGroups = learnerGroups;
}

Make copy before setting current lesson


var Log = require("log4js");
var Utils = require("./Utils");

Log.configure({
    "replaceConsole": true,
    "appenders": process.env.DEBUG ? [{"type": "console"}] :
        [
            {
                "type": "console"
            },
            {
                "type": "logLevelFilter",
                "level": "ERROR",
                "appender": {
                    "type": "smtp",
                    "recipients": process.env.EMAIL,
                    "sender": "info@ideacolorthemes.org",
                    "sendInterval": process.env.LOG_EMAIL_INTERVAL || 30,
                    "transport": "SMTP",
                    "SMTP": Utils.getEmailConfig()
                }
            }
        ]
});

module.exports = Log.getLogger();

var Log = require("log4js");
var Utils = require("./Utils");

Log.configure({
    "replaceConsole": true,
    "appenders": process.env.DEBUG ? [{"type": "console"}] :
        [
            {
                "type": "console"
            },
            {
                "type": "logLevelFilter",
                "level": "ERROR",
                "appender": {
                    "type": "smtp",
                    "recipients": process.env.EMAIL,
                    "sender": process.env.EMAIL,
                    "sendInterval": process.env.LOG_EMAIL_INTERVAL || 30,
                    "transport": "SMTP",
                    "SMTP": Utils.getEmailConfig()
                }
            }
        ]
});

module.exports = Log.getLogger();

Fix for error reporting email


var gulp = require('gulp');
var nodemon = require('gulp-nodemon');

gulp.task('mock-server', function() {
    nodemon({
        script: 'server.js'
        , ext: 'js json'
        , env: { 'NODE_ENV': 'development' }
    })
});

var gulp = require('gulp');
var nodemon = require('gulp-nodemon');

gulp.task('default', function() {
    nodemon({
        script: 'server.js'
        , ext: 'js json'
        , env: { 'NODE_ENV': 'development' }
    })
});

Set gulp task as default


var elixir = require('laravel-elixir');

/*
 |--------------------------------------------------------------------------
 | Elixir Asset Management
 |--------------------------------------------------------------------------
 |
 | Elixir provides a clean, fluent API for defining some basic Gulp tasks
 | for your Laravel application. By default, we are compiling the Sass
 | file for our application, as well as publishing vendor resources.
 |
 */

elixir(function(mix) {
    mix.sass('app.scss');
    //mix.less('');

    mix.copy(
    	'./public/js/libs/semantic/dist/themes',
    	'public/css/themes'
    );

    mix.styles([
    	'./public/js/libs/semantic/dist/semantic.min.css'
    ],
    'public/css/nestor.css');

 //    mix.scripts([
 //    	'./resources/assets/bower/jquery/dist/jquery.js',
 //    	'./resources/assets/bower/bootstrap-sass-official/assets/javascripts/bootstrap.min.js'
	// ],
	// 'public/js/nestor.js');
});

var elixir = require('laravel-elixir');

/*
 |--------------------------------------------------------------------------
 | Elixir Asset Management
 |--------------------------------------------------------------------------
 |
 | Elixir provides a clean, fluent API for defining some basic Gulp tasks
 | for your Laravel application. By default, we are compiling the Sass
 | file for our application, as well as publishing vendor resources.
 |
 */

elixir(function(mix) {
    mix.sass('app.scss');
    //mix.less('');

    mix.copy(
    	'./public/js/libs/semantic/dist/themes',
    	'./public/css/themes'
    );

    mix.styles([
    	'./public/js/libs/semantic/dist/semantic.min.css'
    ],
    'public/css/nestor.css');

 //    mix.scripts([
 //    	'./resources/assets/bower/jquery/dist/jquery.js',
 //    	'./resources/assets/bower/bootstrap-sass-official/assets/javascripts/bootstrap.min.js'
	// ],
	// 'public/js/nestor.js');
});

Use same syntax for all assets


var gulp = require('gulp');
var uglify = require('gulp-uglify');

gulp.task('default', function() {
  gulp.src('lib/fs.js')
  	.pipe(uglify())
  	.pipe(gulp.dest('build'));
});

var gulp = require('gulp');
var uglify = require('gulp-uglify');
var babel = require('gulp-babel');
var sourcemaps = require('gulp-sourcemaps');
var rename = require('gulp-rename');

gulp.task('default', function() {
  gulp.src('src/fs.js')
  	.pipe(sourcemaps.init())
  	.pipe(babel())
  	.pipe(uglify())
  	.pipe(rename('fs.min.js'))
  	.pipe(sourcemaps.write('.'))
  	.pipe(gulp.dest('dist'));
});

Update default task for compile prod code


'use strict';

var gulp = require('gulp')
  , jshint = require('gulp-jshint')
  , nodemon = require('gulp-nodemon')
  , paths
  ;

paths = {
  projectScripts: ['./bin/www', '**/*.js', '!node_modules/**', '!public/**']
};

gulp.task('default', ['develop'], function () {
});

gulp.task('develop', function () {
  nodemon({ script: 'bin/www', ext: 'js' })
    .on('change', ['lint'])
    .on('restart', function () {
      console.log('Restared!');
    });
});

gulp.task('lint', function () {
  gulp.src(paths.projectScripts)
    .pipe(jshint())
    .pipe(jshint.reporter('jshint-stylish'));
});

'use strict';

var gulp = require('gulp')
  , jshint = require('gulp-jshint')
  , nodemon = require('gulp-nodemon')
  , paths
  ;

paths = {
  projectScripts: ['./bin/www', '**/*.js', '!node_modules/**', '!public/**']
};

gulp.task('default', ['develop'], function () {
});

gulp.task('develop', function () {
  nodemon({ script: 'bin/www', ext: 'js' })
    .on('change', ['hint'])
    .on('restart', function () {
      console.log('Restared!');
    });
});

gulp.task('hint', function () {
  gulp.src(paths.projectScripts)
    .pipe(jshint())
    .pipe(jshint.reporter('jshint-stylish'));
});

Rename lint task to hint


import addons from '@storybook/addons';
import { withNotes } from '..';

jest.mock('@storybook/addons');

describe('Storybook Addon Notes', () => {
  it('should inject info', () => {
    const channel = { emit: jest.fn() };
    addons.getChannel.mockReturnValue(channel);

    const getStory = jest.fn();
    const context = {};

    const decoratedStory = withNotes('hello')(getStory);
    decoratedStory(context);
    expect(channel.emit).toHaveBeenCalledWith('storybook/notes/add_notes', 'hello');
    expect(getStory).toHaveBeenCalledWith(context);
  });
});

import addons from '@storybook/addons';
import { withNotes } from '..';

jest.mock('@storybook/addons');

describe('Storybook Addon Notes', () => {
  it('should inject text from `notes` parameter', () => {
    const channel = { emit: jest.fn() };
    addons.getChannel.mockReturnValue(channel);

    const getStory = jest.fn();
    const context = { parameters: { notes: 'hello' } };

    withNotes(getStory, context);
    expect(channel.emit).toHaveBeenCalledWith('storybook/notes/add_notes', 'hello');
    expect(getStory).toHaveBeenCalledWith(context);
  });

  it('should inject markdown from `notes.markdown` parameter', () => {
    const channel = { emit: jest.fn() };
    addons.getChannel.mockReturnValue(channel);

    const getStory = jest.fn();
    const context = { parameters: { notes: { markdown: '# hello' } } };

    withNotes(getStory, context);
    expect(channel.emit).toHaveBeenCalledWith(
      'storybook/notes/add_notes',
      expect.stringContaining('
hello
')
    );
    expect(getStory).toHaveBeenCalledWith(context);
  });

  it('should inject info (deprecated API)', () => {
    const channel = { emit: jest.fn() };
    addons.getChannel.mockReturnValue(channel);

    const getStory = jest.fn();
    const context = { parameters: {} };

    const decoratedStory = withNotes('hello')(getStory);
    decoratedStory(context);
    expect(channel.emit).toHaveBeenCalledWith('storybook/notes/add_notes', 'hello');
    expect(getStory).toHaveBeenCalledWith(context);
  });
});

Update tests with new API


import { treeGrow as growChance } from '../../../game-data/chances';
import { tryChance } from '../../../utils';
import { fastMap } from '../../../world';
import objectPool from '../../../object-pool';

import Phaser from 'phaser';
import Tree from '../Tree';

export default function place() {
  this.placed = true;

  this.changeType('planted-pine-cone');

  this.game.time.events.add(Phaser.Timer.SECOND * 5, function() {
    if (this.destroyed) return;

    const playerTile = this.game.state.states.Game.player.tile;
    const thisTile = this.tile;

    if (
      (playerTile.x !== thisTile.x || playerTile.y !== thisTile.y) &&
      fastMap[thisTile.x + ',' + thisTile.y].length === 1 && // this pine cone should be only thing on tile
      tryChance(growChance)
    ) {
      objectPool.new('tree', Tree, {
        game: this.game,
        x: this.x,
        y: this.y,
      });

      this.destroy();
    } else {
      place.call(this);
    }
  }, this);
}

import { treeGrow as growChance } from '../../../game-data/chances';
import { tryChance } from '../../../utils';
import { fastMap } from '../../../world';
import objectPool from '../../../object-pool';

import Phaser from 'phaser';
import Tree from '../Tree';

export default function place() {
  this.placed = true;

  this.changeType('planted-pine-cone');

  this.game.time.events.add(Phaser.Timer.SECOND * 5, function() {
    if (this.destroyed) return;

    const playerTile = this.game.state.states.Game.player.tile;
    const thisTile = this.tile;

    if (
      (playerTile.x !== thisTile.x || playerTile.y !== thisTile.y) &&
      fastMap[thisTile.y][thisTile.x].length === 1 && // this pine cone should be only thing on tile
      tryChance(growChance)
    ) {
      objectPool.new('tree', Tree, {
        game: this.game,
        x: this.x,
        y: this.y,
      });

      this.destroy();
    } else {
      place.call(this);
    }
  }, this);
}

Fix using old map format


var SecretsUtil = {
  /**
   * This function checks if the environment value is a reference to a secret
   * and if yes, it cross-references the secret with the `secrets` property
   * in `allFields` and it returns the secret reference name.
   *
   * @param {Object|String} value - The environment variable value
   * @param {Object} allFields - The full object definition or all form fields
   * @returns {String} Returns the string for the environment value
   */
  getSecretReferenceOfEnvValue: function (value, allFields) {
    if (typeof value !== "object") {
      return value;
    }

    let placeholder;
    const {secret} = value;
    const secretSource = allFields[`secrets.${secret}.source`] ||
      (allFields.secrets && allFields.secrets[secret] &&
        allFields.secrets[secret].source);

    if (!secretSource) {
      placeholder = 'Invalid Secret Reference';
      if (!secret) {
        placeholder = 'Invalid Value';
      }
    } else {
      placeholder = `Secret "${secretSource}"`;
    }

    return placeholder;
  }
};

export default SecretsUtil;

var SecretsUtil = {
  /**
   * This function checks if the environment value is a reference to a secret
   * and if yes, it cross-references the secret with the `secrets` property
   * in `allFields` and it returns the secret reference name.
   *
   * @param {Object|String} value - The environment variable value
   * @param {Object} allFields - The full object definition or all form fields
   * @returns {String} Returns the string for the environment value
   */
  getSecretReferenceOfEnvValue: function (value, allFields) {
    if (typeof value !== "object") {
      return value;
    }

    let placeholder;
    const {secret} = value;
    const secretSource = allFields[`secrets.${secret}.source`] ||
      (allFields.secrets && allFields.secrets[secret] &&
        allFields.secrets[secret].source);

    if (!secretSource) {
      placeholder = "Invalid Secret Reference";
      if (!secret) {
        placeholder = "Invalid Value";
      }
    } else {
      placeholder = `Secret "${secretSource}"`;
    }

    return placeholder;
  }
};

export default SecretsUtil;

Replace single with double quotes


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

'use strict';

describe('Filter: ISODateToMiddleEndian', function () {

  // load the filter's module
  beforeEach(module('ocwUiApp'));

  // initialize a new instance of the filter before each test
  var ISODateToMiddleEndian;
  beforeEach(inject(function ($filter) {
    ISODateToMiddleEndian = $filter('ISODateToMiddleEndian');
  }));

  it('should return the input prefixed with "ISODateToMiddleEndian filter:"', function () {
    var text = 'angularjs';
    expect(ISODateToMiddleEndian(text)).toBe('ISODateToMiddleEndian filter: ' + text);
  });

});

Add ASF license headers to filter tests


function TouchInput(inputController) {
    this.inputController = inputController;
}

TouchInput.prototype.listen = function() {
    document.addEventListener('touchstart', this.startTouch.bind(this));
    document.addEventListener('touchmove', this.detectSwipe.bind(this));
};

TouchInput.prototype.startTouch = function(event) {
    this.xStart = event.touches[0].clientX;
    this.yStart = event.touches[0].clientY;
};

TouchInput.prototype.detectSwipe = function(event) {
    if( this.xStart === null || this.yStart === null) {
        return;
    }

    var xEnd = event.touches[0].clientX;
    var yEnd = event.touches[0].clientY;

    var deltaX = xEnd - this.xStart;
    var deltaY = yEnd - this.yStart;

    var isHorizontalSwipe = Math.abs(deltaX) > Math.abs(deltaY);

    if(isHorizontalSwipe) {
        deltaX < 0 ? this.inputController.left(): this.inputController.right();
    } else if(deltaY > 0) {
        this.inputController.down();
    }

    this.xStart = null;
    this.yStart = null;
};

function TouchInput(inputController, element) {
    this.inputController = inputController;
    this.element = typeof element !== 'undefined' ? element : document;
}

TouchInput.prototype.listen = function() {
    this.element.addEventListener('touchstart', this.startTouch.bind(this));
    this.element.addEventListener('touchmove', this.detectSwipe.bind(this));
};

TouchInput.prototype.startTouch = function(event) {
    this.xStart = event.touches[0].clientX;
    this.yStart = event.touches[0].clientY;
};

TouchInput.prototype.detectSwipe = function(event) {
    if( this.xStart === null || this.yStart === null) {
        return;
    }

    var xEnd = event.touches[0].clientX;
    var yEnd = event.touches[0].clientY;

    var deltaX = xEnd - this.xStart;
    var deltaY = yEnd - this.yStart;

    var isHorizontalSwipe = Math.abs(deltaX) > Math.abs(deltaY);

    if(isHorizontalSwipe) {
        deltaX < 0 ? this.inputController.left(): this.inputController.right();
    } else if(deltaY > 0) {
        this.inputController.down();
    }

    this.xStart = null;
    this.yStart = null;
};

Add element binding to touch input


if (!!document.querySelector('#app-player')) { // Old Player
  controller = new BasicController({
    supports: {
    playpause: true,
    next: true,
    previous: true
    },
    useLazyObserving: true,
    frameSelector: '#app-player',
    playStateSelector: '#play-pause',
    playStateClass: 'playing',
    playPauseSelector: '#play-pause',
    nextSelector: '#next',
    previousSelector: '#previous',
    titleSelector: '#track-name',
    artistSelector: '#track-artist'
  });

  controller.override('getAlbumArt', function() {
    return document.querySelector(this.frameSelector).contentDocument.querySelector('#cover-art .sp-image-img').style.backgroundImage.slice(4, -1);
  });
} else { // New Player
  controller = new BasicController({
    supports: {
    playpause: true,
    next: true,
    previous: true
    },
    useLazyObserving: true,
    frameSelector: '#main',
    playStateSelector: '#play',
    playStateClass: 'playing',
    playPauseSelector: '#play',
    nextSelector: '#next',
    previousSelector: '#previous',
    titleSelector: '.caption .track',
    artistSelector: '.caption .artist'
  });

  controller.override('getAlbumArt', function() {
    return document.querySelector(this.frameSelector).contentDocument.querySelector('#large-cover-image').style.backgroundImage.slice(4, -1);
  });
}

var config = {
  supports: {
    playpause: true,
    next: true,
    previous: true
  },
  useLazyObserving: true,
  playStateClass: 'playing',
  nextSelector: '#next',
  previousSelector: '#previous'
}

if (document.querySelector('#app-player')) { // Old Player
  config.artworkImageSelector = '#cover-art .sp-image-img';
  config.frameSelector = '#app-player';
  config.playStateSelector = '#play-pause';
  config.playPauseSelector = '#play-pause';
  config.titleSelector = '#track-name';
  config.artistSelector = '#track-artist';
} else { // New Player
  config.artworkImageSelector = '#large-cover-image';
  config.frameSelector = '#main';
  config.playStateSelector = '#play';
  config.playPauseSelector = '#pause';
  config.titleSelector = '.caption .track';
  config.artistSelector = '.caption .artist';
}

controller = new BasicController(config);
controller.override('getAlbumArt', function() {
  var img = this.doc().querySelector(this.artworkImageSelector);
  return img && img.style.backgroundImage.slice(5, -2);
});

Fix album art retrieved from Spotify


import React, {PropTypes} from 'react'
import ToggleButton from 'part:@sanity/components/toggles/button'

import LinkIcon from 'part:@sanity/base/sanity-logo-icon'
import styles from './styles/LinkButton.css'

export default class LinkButton extends React.Component {

  static propTypes = {
    onClick: PropTypes.func,
    activeLink: PropTypes.bool
  }

  handleToggleButtonClick = () => {
    this.props.onClick(this.props.activeLink)
  }

  render() {
    return (

        <ToggleButton
          onClick={this.handleToggleButtonClick}
          title={'Link'}
          className={styles.button}
        >

            <LinkIcon />

        </ToggleButton>

    )
  }
}

import React, {PropTypes} from 'react'
import ToggleButton from 'part:@sanity/components/toggles/button'

import LinkIcon from 'part:@sanity/base/link-icon'
import styles from './styles/LinkButton.css'

export default class LinkButton extends React.Component {

  static propTypes = {
    onClick: PropTypes.func,
    activeLink: PropTypes.bool
  }

  handleToggleButtonClick = () => {
    this.props.onClick(this.props.activeLink)
  }

  render() {
    const {activeLink} = this.props
    return (

        <ToggleButton
          onClick={this.handleToggleButtonClick}
          title={'Link'}
          selected={activeLink}
          className={styles.button}
        >

            <LinkIcon />

        </ToggleButton>

    )
  }
}

Use link icon for link button


module.exports = {
  "extends": "justinlocsei/configurations/es6",
  "ecmaFeatures": {
    "jsx": true
  },
  "env": {
    "browser": true
  },
  "plugins": [
    "react"
  ],
  "rules": {
    "jsx-quotes": [2, "prefer-double"],
    "react/jsx-boolean-value": [2, "always"],
    "react/jsx-curly-spacing": [2, "never"],
    "react/jsx-handler-names": 2,
    "react/jsx-no-bind": 2,
    "react/jsx-no-duplicate-props": [2, {"ignoreCase": true}],
    "react/jsx-no-undef": 2,
    "react/jsx-pascal-case": 2,
    "react/jsx-uses-vars": 2,
    "react/no-did-mount-set-state": 2,
    "react/no-did-update-set-state": 2,
    "react/no-direct-mutation-state": 2,
    "react/no-multi-comp": 2,
    "react/no-unknown-property": 2,
    "react/prop-types": 2,
    "react/self-closing-comp": 2,
    "react/wrap-multilines": 2
  }
};

module.exports = {
  "extends": "justinlocsei/configurations/es6",
  "ecmaFeatures": {
    "jsx": true
  },
  "env": {
    "browser": true
  },
  "plugins": [
    "react"
  ],
  "rules": {
    "jsx-quotes": [2, "prefer-double"],
    "react/jsx-boolean-value": [2, "always"],
    "react/jsx-curly-spacing": [2, "never"],
    "react/jsx-handler-names": 2,
    "react/jsx-no-bind": 2,
    "react/jsx-no-duplicate-props": [2, {"ignoreCase": true}],
    "react/jsx-no-undef": 2,
    "react/jsx-pascal-case": 2,
    "react/jsx-uses-vars": 2,
    "react/jsx-wrap-multilines": 2,
    "react/no-did-mount-set-state": 2,
    "react/no-did-update-set-state": 2,
    "react/no-direct-mutation-state": 2,
    "react/no-multi-comp": 2,
    "react/no-unknown-property": 2,
    "react/prop-types": 2,
    "react/self-closing-comp": 2
  }
};

Update the named of a renamed React check


module.exports = function($scope, $state, $location, $http, GlobalService,
                           DocumentService, DocumentApiService, MathJaxService, QueryParser) {
        $scope.doSearch = function(){

            var apiServer = GlobalService.apiServer()
            var query = QueryParser.parse($scope.searchText)
            console.log('query = ' + query)

            $http.get('http://' + apiServer + '/v1/documents' + '?' + query  )
            .then(function(response){
              console.log(response.data['status'])
              console.log('Number of documents: ' + response.data['document_count'])
              var jsonData = response.data
              var documents = jsonData['documents']
              DocumentService.setDocumentList(documents)
              var id = documents[0]['id']
              console.log('SearchController, id: ' + id)
              DocumentApiService.getDocument(id)
              .then(function(response) {

                console.log('CURRENT STATE: ' + $state.current)
                $state.go('documents')
                $state.reload()

                $scope.$watch(function(scope) {
                    return $scope.renderedText },
                    MathJaxService.reload('SearchController')
                );

              })
            });

      };
    }

module.exports = function($scope, $state, $location, $http, GlobalService,
                           DocumentService, DocumentApiService, MathJaxService, QueryParser) {
        $scope.doSearch = function(){

            var apiServer = GlobalService.apiServer()
            var query = QueryParser.parse($scope.searchText)
            console.log('query = ' + query)

            $http.get('http://' + apiServer + '/v1/documents' + '?' + query  )
            .then(function(response){
              console.log(response.data['status'])
              console.log('Number of documents: ' + response.data['document_count'])
              var jsonData = response.data
              var documents = jsonData['documents']
              DocumentService.setDocumentList(documents)
              var id = documents[0]['id']
              console.log('SearchController, id: ' + id)
              DocumentApiService.getDocument(id)
              .then(function(response) {

                console.log('Document " + id + ' retrieved')
                console.log('CURRENT STATE: ' + $state.current)
                $state.go('documents')
                $state.reload()

                $scope.$watch(function(scope) {
                    return $scope.renderedText },
                    MathJaxService.reload('SearchController')
                );

              })
            });

      };
    }

Fix search problem (but how??)


'use strict';

const request = require('request-promise-native');
const querystring = require('querystring');
const hooks = require('./hooks');

class Service {
	constructor(options) {
		this.options = options || {};
	}

	find(params) {
		const options = {};
		if(params.query.$limit) options["page[limit]"] = params.query.$limit;
		if(params.query.$skip) options["page[offset]"] = params.query.$skip;
		if(params.query.query) options.query = params.query.query;

		const contentServerUrl = `https://schul-cloud.org:8090/contents?${querystring.encode(options)}`;
		return request(contentServerUrl).then(string => {
			return JSON.parse(string);
		});
	}

	/*get(id, params) {
	 return Promise.resolve({
	 id, text: `A new message with ID: ${id}!`
	 });
	 }*/
}

module.exports = function () {
	const app = this;

	// Initialize our service with any options it requires
	app.use('/contents', new Service());

	// Get our initialize service to that we can bind hooks
	const contentService = app.service('/contents');

	// Set up our before hooks
	contentService.before(hooks.before);

	// Set up our after hooks
	contentService.after(hooks.after);
};

module.exports.Service = Service;

'use strict';

const request = require('request-promise-native');
const querystring = require('querystring');
const hooks = require('./hooks');

class Service {
	constructor(options) {
		this.options = options || {};
	}

	find(params) {
		const options = {};
		if(params.query.$limit) options["page[limit]"] = params.query.$limit;
		if(params.query.$skip) options["page[offset]"] = params.query.$skip;
		if(params.query.query) options.query = params.query.query;

		const contentServerUrl = `https://schul-cloud.org:8090/contents?${querystring.encode(options)}`;
		return request(contentServerUrl).then(string => {
			let result = JSON.parse(string);
			result.total = result.meta.page.total;
			result.limit = result.meta.page.limit;
			result.skip = result.meta.page.offset;
			return result;
		});
	}

	/*get(id, params) {
	 return Promise.resolve({
	 id, text: `A new message with ID: ${id}!`
	 });
	 }*/
}

module.exports = function () {
	const app = this;

	// Initialize our service with any options it requires
	app.use('/contents', new Service());

	// Get our initialize service to that we can bind hooks
	const contentService = app.service('/contents');

	// Set up our before hooks
	contentService.before(hooks.before);

	// Set up our after hooks
	contentService.after(hooks.after);
};

module.exports.Service = Service;

Add result page information in feathers format


var config = module.exports = exports = {}

config.items =
	{ tmpDir:
		{ def: 'tmp' }
	, outDir:
		{ def: 'out' }
	, originalsPath:
		{ def: 'originals' }
	, maxCols:
		{ def: 10 }
	, maxTries:
		{ def: 6 }
	, invalidRequestMessage:
		{ def: "Invalid request. Make sure you are respecting the sprite maker api (https://github.com/vigour-io/vigour-img/blob/master/README.md#user-content-api) and that the requested data exists." }
	, port:
		{ def: 8000 }
	, maxWidth:
		{ def: 10000 }
	, maxHeight:
		{ def: 10000 }
	, minFreeSpace:
		{ def: 0.01 }
	}

config.files =
	{ def: null
	, env: "IMG_CONFIG"
	}

var config = module.exports = exports = {}

config.items =
	{ tmpDir:
		{ def: 'tmp' }
	, outDir:
		{ def: 'out' }
	, originalsPath:
		{ def: 'originals' }
	, maxCols:
		{ def: 10 }
	, maxTries:
		{ def: 6 }
	, invalidRequestMessage:
		{ def: "Invalid request. Make sure you are respecting the sprite maker api (https://github.com/vigour-io/vigour-img/blob/master/README.md#user-content-api) and that the requested data exists." }
	, port:
		{ def: 8000
		, cli: "-p, --port <nb>"
		, desc: "Port on which the server should listen"
		}
	, maxWidth:
		{ def: 10000 }
	, maxHeight:
		{ def: 10000 }
	, minFreeSpace:
		{ def: 0.01 }
	}

config.files =
	{ def: null
	, env: "IMG_CONFIG"
	}

Add command-line option to change port


'use strict';

var glob = require('glob'),
    _ = require('lodash'),
    async = require('async'),
    fs = require('fs'),
    lex = require('./lex');

function isFile(path) {
    var stat = fs.statSync(path);
    return stat.isFile();
}

function getStrings(path, done) {
    glob(path, function(er, files) {
        var tokens = [];

        files = files.filter(isFile);

        async.reduce(files, tokens, function(tokens, file, next) {
            fs.readFile(file, function(err, src) {
                if (err) return next();
                if (src.length === 0) return next();
                tokens.push(lex(src.toString()));
                return next(null, tokens);

            });
        }, done);
    });
}

function scan(path, done) {
    getStrings(path, function(err, results) {
        var strings = [];

        if (err) return done(err, strings);

        strings = results.reduce(function(strings, result) {
            return strings.concat(result);
        }, []);

        return done(null, _.uniq(strings).sort());
    });
}

module.exports = scan;

'use strict';

var glob = require('glob'),
    _ = require('lodash'),
    async = require('async'),
    fs = require('fs'),
    lex = require('./lex');

function isFile(path) {
    var stat = fs.statSync(path);
    return stat.isFile();
}

function getStrings(path, done) {
    glob(path, function(er, files) {
        var tokens = [];

        files = files.filter(isFile);

        async.reduce(files, tokens, function(tokens, file, next) {
            fs.readFile(file, function(err, src) {
                if (err) return next();
                if (src.length === 0) return next(null, tokens);
                tokens.push(lex(src.toString()));
                return next(null, tokens);

            });
        }, done);
    });
}

function scan(path, done) {
    getStrings(path, function(err, results) {
        var strings = [];

        if (err) return done(err, strings);

        strings = results.reduce(function(strings, result) {
            return strings.concat(result);
        }, []);

        return done(null, _.uniq(strings).sort());
    });
}

module.exports = scan;

Fix to ensure that tokens is not undefined on the next iteration when the previous file was empty


/**
 * General functions
 */
var general = {};

//General 404 error
general.send404 = function(res){
	res.status(404);
	res.jsonp({error: 'Not found'});
	res.end();
};

//General 500 error
general.send500 = function(res, msg){
	res.status(500);
	res.jsonp({error:'Server error ' + msg});
	res.end();
};

general.getOptions = function(res){
	res.json({"Get Patients": "/patients",
			"Get One Patient": "/patient"`});
	res.end();
};

module.exports = general;

/**
 * General functions
 */
var general = {};

//General 404 error
general.send404 = function(res){
	res.status(404);
	res.jsonp({error: 'Not found'});
	res.end();
};

//General 500 error
general.send500 = function(res, msg){
	res.status(500);
	res.jsonp({error:'Server error ' + msg});
	res.end();
};

general.getOptions = function(res){
	res.json({"Get Patients": "/patients",
			"Get One Patient": "/patient"});
	res.end();
};

module.exports = general;

Fix typo on getOptions response


define([
  'app'
], function(app) {
  'use strict';

  return {
    parent: 'admin_layout',
    url: '',
    templateUrl: 'partials/admin/home.html',
    controller: function($scope, $http, flash) {
      $scope.loading = true;

      $http.get('/api/0/systemstats/').success(function(data){
        $scope.statusCounts = data.statusCounts;
        $scope.resultCounts = data.resultCounts;
      }).error(function(){
        flash('error', 'There was an error loading system statistics.');
      }).finally(function(){
        $scope.loading = false;
      });
    }
  };
});

define([
  'app'
], function(app) {
  'use strict';

  return {
    parent: 'admin_layout',
    url: '',
    templateUrl: 'partials/admin/home.html',
    controller: function($scope, $http, flash) {
      var timeoutId;

      $scope.loading = true;

      $scope.$on('$destory', function(){
        if (timeoutId) {
          window.cancelTimeout(timeoutId);
        }
      });

      function tick() {
        $http.get('/api/0/systemstats/').success(function(data){
          $scope.statusCounts = data.statusCounts;
          $scope.resultCounts = data.resultCounts;
        }).error(function(){
          flash('error', 'There was an error loading system statistics.');
        }).finally(function(){
          $scope.loading = false;
        });

        timeoutId = window.setTimeout(tick, 5000);
      }

      tick();
    }
  };
});

Add polling for system stats


'use strict';
const app = require('./app');
const db = require('../db');
const PORT = process.env.PORT || 3000;
const path = require('path');

app.listen(PORT, () => {
  console.log('Example app listening on port 3000!');
});

'use strict';
const app = require('./app');
const db = require('../db');
const PORT = process.env.PORT || 3000;
const path = require('path');

app.listen(PORT, () => {
  console.log('Example app listening on port ' + PORT);
});

Change the console log to show proper port


// Todo Implement and export schema using mongoose
// Reference angular sprint
var mongoose = require('mongoose');
//var User = require('../users/UserModel.js');

var Schema = mongoose.Schema;

var PastTripSchema = new Schema({
	creator: {
		id:{
			type: Schema.Types.ObjectId,
			ref: 'User'
		},
		username: {type: String}
	},
	participants: [{
		id:{
			type: Schema.Types.ObjectId,
			ref: 'User'
		},
		username: {type: String}
	}],
	name: {
		type: String,
		required: true
	},
	code: {
		type: String,
		required: true
	},
	expenses: [
		{
			name: String,
			amount: Number,
			date: Date,
			location: Schema.Types.Mixed,
			payer: {
				id:{
					type: Schema.Types.ObjectId,
					ref: 'User'
				},
				username: {type: String}
			},
			stakeholders: [{
				id:{
					type: Schema.Types.ObjectId,
					ref: 'User'
				},
				username: {type: String}
			}],
		}
	],
	summary: []
});

module.exports = mongoose.model('pasttrips', PastTripSchema);

// Todo Implement and export schema using mongoose
// Reference angular sprint
var mongoose = require('mongoose');
//var User = require('../users/UserModel.js');

var Schema = mongoose.Schema;

var PastTripSchema = new Schema({
	creator: {
		id:{
			type: Schema.Types.ObjectId,
			ref: 'User'
		},
		username: {type: String}
	},
	participants: [{
		id:{
			type: Schema.Types.ObjectId,
			ref: 'User'
		},
		username: {type: String}
	}],
	name: {
		type: String,
		required: true
	},
	code: {
		type: String,
		required: true
	},
	expenses: [
		{
			name: String,
			amount: Number,
			date: Date,
			locationString: String,
			expenseString: String,
			location: Schema.Types.Mixed,
			payer: {
				id:{
					type: Schema.Types.ObjectId,
					ref: 'User'
				},
				username: {type: String}
			},
			stakeholders: [{
				id:{
					type: Schema.Types.ObjectId,
					ref: 'User'
				},
				username: {type: String}
			}],
		}
	],
	summary: []
});

module.exports = mongoose.model('pasttrips', PastTripSchema);

Add location and expense strings to pastTrip model


"use strict";

var TravisFilter = function(name) {
  console.dir(process.env)
  name = name || "ON_TRAVIS";
  // Get environmental variables that are known
  this.filter = function(test) {
    if(test.metadata == null) return false;
    if(test.metadata.ignore == null) return false;
    if(test.metadata.ignore.travis == null) return false;
    if(process.env[name] && test.metadata.ignore.travis == true) return true;
    return false;
  }
}

module.exports = TravisFilter;

"use strict";

var TravisFilter = function(name) {
  // console.dir(process.env)
  name = name || "TRAVIS_JOB_ID";
  // Get environmental variables that are known
  this.filter = function(test) {
    if(test.metadata == null) return false;
    if(test.metadata.ignore == null) return false;
    if(test.metadata.ignore.travis == null) return false;
    if(process.env[name] != null && test.metadata.ignore.travis == true) return true;
    return false;
  }
}

module.exports = TravisFilter;

Fix ignore tests for travis


#!/usr/bin/env node

var colors = require('colors'),
	exec = require('child_process').exec,
	pkg = require('../package.json'),
	preamble = '/*!\n' +
		' *  RadioRadio ' + pkg.version + '\n' +
		' *\n' +
		' *  ' + pkg.description + '\n' +
		' *\n' +
		' *  Source code available at: ' + pkg.homepage + '\n' +
		' *\n' +
		' *  (c) 2015-present ' + pkg.author.name + ' (' + pkg.author.url + ')\n' +
		' *\n' +
		' *  RadioRadio may be freely distributed under the ' + pkg.license + ' license.\n' +
		' */\n';

exec('uglifyjs src/radioradio.js --beautify "indent-level=2" --preamble "' + preamble + '" --output dist/radioradio.js');
exec('uglifyjs src/radioradio.js --compress --mangle --preamble "' + preamble + '" --output dist/radioradio.min.js');

console.log(colors.green('RadioRadio %s built successfully!'), pkg.version);

#!/usr/bin/env node

var colors = require('colors'),
	exec = require('child_process').exec,
	pkg = require('../package.json'),
	preamble = '/*!\n' +
		' *  RadioRadio ' + pkg.version + '\n' +
		' *\n' +
		' *  ' + pkg.description + '\n' +
		' *\n' +
		' *  Source code available at: ' + pkg.homepage + '\n' +
		' *\n' +
		' *  (c) 2015-present ' + pkg.author.name + ' (' + pkg.author.url + ')\n' +
		' *\n' +
		' *  RadioRadio may be freely distributed under the ' + pkg.license + ' license.\n' +
		' */\n';

exec('$(npm bin)/uglifyjs src/radioradio.js --beautify "indent-level=2" --preamble "' + preamble + '" --output dist/radioradio.js');
exec('$(npm bin)/uglifyjs src/radioradio.js --compress --mangle --preamble "' + preamble + '" --output dist/radioradio.min.js');

console.log(colors.green('RadioRadio %s built successfully!'), pkg.version);

Add 'npm bin' to uglifyjs command.


const Signal = require('signals')

let MaterialID = 0

function Material (opts) {
  this.type = 'Material'
  this.id = 'Material_' + MaterialID++
  this.changed = new Signal()

  this._uniforms = {}

  const ctx = opts.ctx

  this.baseColor = [0.95, 0.95, 0.95, 1]
  this.emissiveColor = [0, 0, 0, 1]
  this.metallic = 0.01
  this.roughness = 0.5
  this.displacement = 0
  this.depthTest = true
  this.depthWrite = true
  this.depthFunc = opts.ctx.DepthFunc.LessEqual
  this.blendEnabled = false
  this.blendSrcRGBFactor = ctx.BlendFactor.ONE
  this.blendSrcAlphaFactor = ctx.BlendFactor.ONE
  this.blendDstRGBFactor = ctx.BlendFactor.ONE
  this.blendDstAlphaFactor = ctx.BlendFactor.ONE
  this.castShadows = false
  this.receiveShadows = false

  this.set(opts)
}

Material.prototype.init = function (entity) {
  this.entity = entity
}

Material.prototype.set = function (opts) {
  Object.assign(this, opts)
  Object.keys(opts).forEach((prop) => this.changed.dispatch(prop))
}

module.exports = function (opts) {
  return new Material(opts)
}

const Signal = require('signals')

let MaterialID = 0

function Material (opts) {
  this.type = 'Material'
  this.id = 'Material_' + MaterialID++
  this.changed = new Signal()

  this._uniforms = {}

  const ctx = opts.ctx

  this.baseColor = [0.95, 0.95, 0.95, 1]
  this.emissiveColor = [0, 0, 0, 1]
  this.metallic = 0.01
  this.roughness = 0.5
  this.displacement = 0
  this.depthTest = true
  this.depthWrite = true
  this.depthFunc = opts.ctx.DepthFunc.LessEqual
  this.blendEnabled = false
  this.blendSrcRGBFactor = ctx.BlendFactor.ONE
  this.blendSrcAlphaFactor = ctx.BlendFactor.ONE
  this.blendDstRGBFactor = ctx.BlendFactor.ONE
  this.blendDstAlphaFactor = ctx.BlendFactor.ONE
  this.castShadows = false
  this.receiveShadows = false
  this.cullFaceEnabled = true

  this.set(opts)
}

Material.prototype.init = function (entity) {
  this.entity = entity
}

Material.prototype.set = function (opts) {
  Object.assign(this, opts)
  Object.keys(opts).forEach((prop) => this.changed.dispatch(prop))
}

module.exports = function (opts) {
  return new Material(opts)
}

Enable face culling by default


var util = require('util'),

    _ = require('lodash'),
    vow = require('vow'),
    nm = require('nodemailer'),
    transport = require('nodemailer-smtp-transport'),

    errors = require('../errors').Mailer,
    logger = require('./../logger'),
    mailer;

module.exports = {
    /**
     * Initialize mailer module
     */
    init: function (options) {
        logger.info('Initialize e-mail sending module', module);

        if (!options) {
            errors.createError(errors.CODES.MAILER_NOT_CONFIGURED).log('warn');
            return vow.resolve();
        }

        mailer = new nm.createTransport(transport({
            host: options.host,
            port: options.port
        }));
        return vow.resolve(mailer);
    },

    /**
     * Email sending
     * @param {Object} options - e-mail options object
     * @returns {*}
     */
    send: function (options) {
        var base = { encoding: 'utf-8' };

        logger.info(util.format('send email //subject: %s  //body: %s', options.subject, options.text), module);

        if (!mailer) {
            errors.createError(errors.CODES.NOT_INITIALIZED).log();
            return vow.resolve();
        }

        var def = vow.defer();
        mailer.sendMail(_.extend({}, base, options), function (err) {
            errors.createError(errors.CODES.COMMON, { err: err }).log();
            err ? def.reject(err) : def.resolve();
        });

        return def.promise();
    }
};

var util = require('util'),

    _ = require('lodash'),
    vow = require('vow'),
    nm = require('nodemailer'),
    transport = require('nodemailer-smtp-transport'),

    errors = require('../errors').Mailer,
    logger = require('./../logger'),
    mailer;

module.exports = {
    /**
     * Initialize mailer module
     */
    init: function (options) {
        logger.info('Initialize e-mail sending module', module);

        if (!options) {
            errors.createError(errors.CODES.MAILER_NOT_CONFIGURED).log('warn');
            return vow.resolve();
        }

        mailer = new nm.createTransport(transport({
            host: options.host,
            port: options.port
        }));
        return vow.resolve(mailer);
    },

    /**
     * Email sending
     * @param {Object} options - e-mail options object
     * @returns {*}
     */
    send: function (options) {
        var base = { encoding: 'utf-8' };

        logger.info(util.format('send email //subject: %s  //body: %s', options.subject, options.text), module);

        if (!mailer) {
            errors.createError(errors.CODES.NOT_INITIALIZED).log();
            return vow.resolve();
        }

        var def = vow.defer();
        mailer.sendMail(_.extend({}, base, options), function (err) {
            if (err) {
                errors.createError(errors.CODES.COMMON, { err: err }).log();
                def.reject(err);
            } else {
                def.resolve();
            }
        });

        return def.promise();
    }
};

Fix error with invalid error message while sending e-mail


describe('Operations', function () {
    var isNode = typeof module !== 'undefined' && module.exports;
    var Parallel = isNode ? require('../../lib/parallel.js') : self.Parallel;

    it('should require(), map() and reduce correctly (check console errors)', function () {
    	var p = new Parallel([0, 1, 2, 3, 4, 5, 6, 7, 8], { evalPath: isNode ? undefined : 'lib/eval.js' });

        function add(d) { return d[0] + d[1]; }
        function factorial(n) { return n < 2 ? 1 : n * factorial(n - 1); }

        p.require(factorial);

        var done = false;
        runs(function () {
            p.map(function (n) { return Math.pow(10, n); }).reduce(add).then(function() {
                done = true;
            });
        });

        waitsFor(function () {
            return done;
        }, "it should finish", 500);
    });
});

describe('Operations', function () {
    var isNode = typeof module !== 'undefined' && module.exports;
    var Parallel = isNode ? require('../../lib/parallel.js') : self.Parallel;

    it('should require(), map() and reduce correctly (check console errors)', function () {
    	var p = new Parallel([0, 1, 2, 3, 4, 5, 6, 7, 8], { evalPath: isNode ? undefined : 'lib/eval.js' });

        function add(d) { return d[0] + d[1]; }
        function factorial(n) { return n < 2 ? 1 : n * factorial(n - 1); }

        p.require(factorial);

        var done = false;
        runs(function () {
            p.map(function (n) { return Math.pow(10, n); }).reduce(add).then(function() {
                done = true;
            });
        });

        waitsFor(function () {
            return done;
        }, "it should finish", 1000);
    });
});

Increase timeout slightly to avoid failing


var express    = require('express');

module.exports = function setup(options, imports, register) {
    var backend = imports.backend.app,
        frontend = imports.frontend.app;

    var plugin = {
        app: {},
        documents: {}
    };

    backend.get('/api/Documents', function (req, res, next) {
        res.json(module.documents);
    });

    register(null, {
        document: plugin
    });
};

var express    = require('express');

module.exports = function setup(options, imports, register) {
    var backend = imports.backend.app,
        frontend = imports.frontend.app;

    var plugin = {
        app: {},
        documents: {},
        addType: function (name, config) {
            this.documents[name] = config;
        }
    };

    backend.get('/api/Documents', function (req, res, next) {
        res.json(module.documents);
    });

    register(null, {
        document: plugin
    });
};

Add addType method to content


const express = require('express');

const app = express();

app.use(express.static('dist'));

app.listen(8080, function() {
    console.log('App started on port 8080!');
});

const express = require('express');

const app = express();

app.use(express.static('dist'));

app.listen(3000, function() {
    console.log('Listening on port 3000!');
});

Use port 3000 for testing


/*
** Module dependencies
*/
var config = require('../../config/config');
var http = require('http');
var mapping = require('./mapping');
var url = require('url');

var openfiscaURL = url.parse(config.openfiscaApi);

function sendOpenfiscaRequest(simulation, callback) {
    var postData = JSON.stringify(simulation);
    var options = {
        hostname: openfiscaURL.hostname,
        port: openfiscaURL.port,
        path: 'calculate',
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Content-Length': Buffer.byteLength(postData)
        }
    };

    var request = http.request(options, function(response) {
        response.setEncoding('utf8');
        var rawData = '';
        response.on('data', function(chunk) { rawData += chunk; });
        response.on('end', function() {
            try {
                var content = JSON.parse(rawData);
                if (response.statusCode == 200) {
                    callback(null, content);
                } else {
                    callback(content.error);
                }
            } catch (e) {
                callback({
                    code: 500,
                    errors: 'Can\'t parse: ' + rawData,
                    message: e.message,
                    stack: e.stack,
                });
            }
        });
    });
    request.write(postData);

    request.on('error', callback);
    request.end();
}

var buildOpenFiscaRequest = exports.buildOpenFiscaRequest = mapping.buildOpenFiscaRequest;

function calculate(situation, callback) {
    var request;
    try {
        request = buildOpenFiscaRequest(situation);
    } catch(e) {
        return callback({
            message: e.message,
            name: e.name,
            stack: e.stack
        });
    }
    sendOpenfiscaRequest(request, callback);
}

exports.calculate = calculate;

var config = require('../../config/config');
var mapping = require('./mapping');
var rp = require('request-promise');

var buildOpenFiscaRequest = exports.buildOpenFiscaRequest = mapping.buildOpenFiscaRequest;
function sendToOpenfisca(endpoint) {
    return function(situation, callback) {
        var request;
        try {
            request = buildOpenFiscaRequest(situation);
        } catch(e) {
            return callback({
                message: e.message,
                name: e.name,
                stack: e.stack
            });
        }
        rp({
            uri: config.openfiscaApi + '/' + endpoint,
            method: 'POST',
            body: request,
            json: true,
        })
        .then(function(result) {
            callback(null, result);
        }).catch(callback);
    };
}

exports.calculate = sendToOpenfisca('calculate');
exports.trace = sendToOpenfisca('trace');

Use request-promise to manage OpenFisca calls


(function() {
  'use strict';
  var canvas = document.getElementById('game');
  var width = window.innerWidth;
  var height = window.innerHeight;
  canvas.width = width;
  canvas.height = height;
  var ctx = canvas.getContext('2d');
  var FPS = 1000 / 60;

  var clear = function() {
    ctx.clearRect(0, 0, width, height);
  };

  var update = function() {

  };

  var render = function () {

  };

  var step = function() {
    update();
    render();
    requestAnimationFrame(FPS);
  }

  requestAnimationFrame(step());
})();

(function() {
  'use strict';
  var canvas = document.getElementById('game');
  var WIDTH = window.innerWidth;
  var HEIGHT = window.innerHeight;
  canvas.width = WIDTH;
  canvas.height = HEIGHT;
  var ctx = canvas.getContext('2d');
  var FPS = 1000 / 60;

  var Paddle = function(x, y, width, height) {
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
    this.xSpeed = 0;
    this.ySpeed = 0;
  };

  Paddle.prototype.render = function() {
    ctx.fillStyle = "#FFFFFF";
    ctx.fillRect(this.x, this.y, this.width, this.height);
  }

  var clear = function() {
    ctx.clearRect(0, 0, WIDTH, HEIGHT);
  };

  var update = function() {

  };

  var render = function () {
    ctx.fillStyle = '#000000';
    ctx.fillRect(0, 0, WIDTH, HEIGHT);
  };

  var step = function() {
    update();
    render();
    requestAnimationFrame(FPS);
  }

  requestAnimationFrame(step());
})();

Define paddle object and a basic render function. Also capitalise width and height constants to easily distinguish them from object properties or parameters


(function() {
    var clientid;

    jive.tile.onOpen(function(config, options, other, container ) {
        osapi.jive.corev3.systemExtProps.get({}).execute(function (resp) {
            if( resp && resp.content ) {
                clientid = resp.content.clientid;
            }
        });
        gadgets.window.adjustHeight();

        if ( typeof config === "string" ) {
            config = JSON.parse(config);
        }

        var json = config || { };

        json.project = json.project || container.name;

        // prepopulate the sequence input dialog
        $("#project").val( json.project);

        $("#btn_submit").click( function() {
            config.project = $("#project").val();
            config.clientid = clientid;
            jive.tile.close(config, {} );
            gadgets.window.adjustHeight(300);
        });

    });

})();

(function() {
    var clientid;

    jive.tile.onOpen(function(config, options, other, container ) {
        osapi.jive.corev3.systemExtProps.get({}).execute(function (resp) {
            if( resp && resp.content ) {
                clientid = resp.content.clientid;
            }
        });
        gadgets.window.adjustHeight();

        if ( typeof config === "string" ) {
            config = JSON.parse(config);
        }

        var json = config || { };

        json.project = json.project || (container && container.name) || "";

        // prepopulate the sequence input dialog
        $("#project").val( json.project);

        $("#btn_submit").click( function() {
            config.project = $("#project").val();
            config.clientid = clientid;
            jive.tile.close(config, {} );
            gadgets.window.adjustHeight(300);
        });

    });

})();

Handle when container is undefined.


import React, { Component } from 'react';
import Item from './Item';
import sortByScore from './sorter';

var TopItems = React.createClass({
  sortByScore: function() {
    this.setState({items: sortByScore(this.props.items)});
  },

  render: function() {
    var rank = 0;
    var all_items = this.props.items.map(function(item) {
      rank++;
      return (
        <Item key={item.id} rank={rank} item={item} />
      );
    });

    // Retired, sort button
    // <SortButton items={this.props.items} sortByScore={this.sortByScore}/>
    return (

          {all_items}

    );
  }
});

var SortButton = React.createClass({
  render: function() {
    return (

        Sort!

    );
  }
});

export default TopItems;

import React, { Component } from 'react';
import Item from './Item';

var TopItems = React.createClass({
  render: function() {
    var rank = 0;
    var all_items = this.props.items.map(function(item) {
      rank++;
      return (
        <Item key={item.id} rank={rank} item={item} />
      );
    });

    return (

          {all_items}

    );
  }
});

export default TopItems;

Remove sort button, as we sort by default now


/**
 * Copyright (c) ActiveState 2013 - ALL RIGHTS RESERVED.
 */

define([
    './collection'],
    function (Collection) {

        var apps = function (api) {
            this.api = api;
            this.collection = 'apps';
        };

        apps.prototype = Object.create(new Collection());

        apps.prototype.getAppInstances = function (guid, done) {

            this.api.get('/v2/apps/' + guid + '/instances', {status_code: 200}, function (err, res) {
                if (err) {
                    return done(err);
                }
                done(null, res.body);
            });
        };

        apps.prototype.getFiles = function (guid, instance_index, path, done) {
            this.api.get(this.getCollectionUrl() + '/' + guid + '/instances/' + instance_index + '/files/' + path, {status_code: 200}, done);
        };

        // TODO: Pull out in to Stackato specific extension
        apps.prototype.getLogTail = function (guid, line_count, done) {
            this.api.get(this.getCollectionUrl() + '/' + guid + '/stackato_logs', {query: '?num=' + (line_count || 25) + '&monolith=1', status_code: 200}, done)
        };

        return apps;
    }
);

/**
 * Copyright (c) ActiveState 2013 - ALL RIGHTS RESERVED.
 */

define([
    './collection'],
    function (Collection) {

        var apps = function (api) {
            this.api = api;
            this.collection = 'apps';
        };

        apps.prototype = Object.create(new Collection());

        apps.prototype.getAppInstances = function (guid, done) {

            this.api.get('/v2/apps/' + guid + '/instances', {status_code: 200}, function (err, res) {
                if (err) {
                    return done(err);
                }
                done(null, res.body);
            });
        };

        apps.prototype.getFiles = function (guid, instance_index, path, done) {
            this.api.get(this.getCollectionUrl() + '/' + guid + '/instances/' + instance_index + '/files/' + path, {query: '?allow_redirect=false', status_code: 200}, done);
        };

        // TODO: Pull out in to Stackato specific extension
        apps.prototype.getLogTail = function (guid, line_count, done) {
            this.api.get(this.getCollectionUrl() + '/' + guid + '/stackato_logs', {query: '?num=' + (line_count || 25) + '&monolith=1', status_code: 200}, done)
        };

        return apps;
    }
);

Disable redirect when browsing files from browser


"use babel";
"use strict";

import fs from "fs";
import path from "path";
import {CompositeDisposable} from "atom";
import {name as packageName} from "../package.json";

export default {

	subscriptions: null,

	activate(state) {
		this.subscriptions = new CompositeDisposable();
		this.subscriptions.add(atom.themes.onDidChangeActiveThemes(() => {
			this.subscriptions.add(atom.config.observe(`${packageName}.subtheme`, value => {
				this.apply(value);
			}));
		}));
	},

	deactivate() {
		this.subscriptions.dispose();
	},

	apply(value) {
		let newData = `@import "colors/${value}";\n`,
		    file = path.join(__dirname, "..", "styles", "syntax-variables.less"),
		    oldData = fs.readFileSync(file, "utf8");

		if (newData !== oldData) {
			fs.writeFileSync(file, newData);
			this.reloadAllStylesheets();
		}
	},

	reloadAllStylesheets() {
		atom.themes.deactivateThemes();
		atom.themes.refreshLessCache();
		let promises = [],
		    ref = atom.themes.getEnabledThemeNames();

		for (let i = 0, len = ref.length; i < len; i++) {
			let themeName = ref[i];
			promises.push(atom.packages.activatePackage(themeName));
		}

		Promise.all(promises).then(() => {
			atom.themes.addActiveThemeClasses();
			atom.themes.refreshLessCache();
			atom.themes.loadUserStylesheet();
			atom.themes.reloadBaseStylesheets();
		});
	}

};

"use babel";
"use strict";

import fs from "fs";
import path from "path";
import {CompositeDisposable} from "atom";
import {name as packageName} from "../package.json";

export default {

	subscriptions: null,

	activate(state) {
		this.subscriptions = new CompositeDisposable();
		this.subscriptions.add(atom.themes.onDidChangeActiveThemes(() => {
			this.subscriptions.add(atom.config.observe(`${packageName}.subtheme`, value => {
				this.apply(value);
			}));
		}));
	},

	deactivate() {
		this.subscriptions.dispose();
	},

	apply(value) {
		let newData = `@import "colors/${value}";\n`,
		    file = path.join(__dirname, "..", "styles", "syntax-variables.less"),
		    oldData = fs.readFileSync(file, "utf8");

		if (newData !== oldData) {
			fs.writeFileSync(file, newData);
			this.reloadAllStylesheets();
		}
	},

	reloadAllStylesheets() {
		atom.themes.loadUserStylesheet();
		atom.themes.reloadBaseStylesheets();
		let ref = atom.packages.getActivePackages();

		for (let i = 0, len = ref.length; i < len; i++) {
			let pack = ref[i];
			pack.reloadStylesheets();
		}
	}

};

Revert ":bug: Fix stylesheets issue when changing subtheme"


'use babel';

let lineEndingTile = null;

export function activate() {
  atom.workspace.observeActivePaneItem((item) => {
    let ending = getLineEnding(item);
    if (lineEndingTile) lineEndingTile.firstChild.textContent = ending;
  });
}

export function consumeStatusBar(statusBar) {
  lineEndingTile = document.createElement('div');
  lineEndingTile.style.display = "inline-block";
  lineEndingTile.className = "line-ending-tile";
  lineEndingTile.innerHTML = "";
  statusBar.addRightTile({item: lineEndingTile, priority: 200});
}

function getLineEnding(item) {
  if (!item) return "";
  let hasLF = false;
  let hasCRLF = false;
  if (item && item.scan) {
    item.scan(/\n|\r\n/g, ({matchText}) => {
      if (matchText === "\n") hasLF = true;
      if (matchText === "\r\n") hasCRLF = true;
    });
  }
  if (hasLF && hasCRLF) return "Mixed";
  if (hasLF) return "LF";
  if (hasCRLF) return "CRLF";
  if (process.platform === 'win32') return "CRLF";
  return "LF";
}

'use babel';

let lineEndingTile = null;

export function activate() {
  atom.workspace.observeActivePaneItem((item) => {
    let ending = getLineEnding(item);
    if (lineEndingTile) lineEndingTile.textContent = ending;
  });
}

export function consumeStatusBar(statusBar) {
  lineEndingTile = document.createElement('a');
  lineEndingTile.className = "line-ending-tile";
  statusBar.addRightTile({item: lineEndingTile, priority: 200});
}

function getLineEnding(item) {
  if (!item) return "";
  let hasLF = false;
  let hasCRLF = false;
  if (item && item.scan) {
    item.scan(/\n|\r\n/g, ({matchText}) => {
      if (matchText === "\n") hasLF = true;
      if (matchText === "\r\n") hasCRLF = true;
    });
  }
  if (hasLF && hasCRLF) return "Mixed";
  if (hasLF) return "LF";
  if (hasCRLF) return "CRLF";
  if (process.platform === 'win32') return "CRLF";
  return "LF";
}

Remove unneeded div around tile element


import UserStore from 'new-dashboard/store/user';
import { testAction } from '../helpers';

jest.mock('carto-node');

const mutations = UserStore.mutations;
const actions = UserStore.actions;

describe('UserStore', () => {
  describe('mutations', () => {
    it('setUserData', () => {
      const state = {
        email: 'example@example.org',
        username: 'example',
        website: 'https://carto.com'
      };

      const userData = {
        email: 'example@carto.com',
        username: 'carto'
      };
      mutations.setUserData(state, userData);

      expect(state).toEqual({
        email: 'example@carto.com',
        username: 'carto',
        website: 'https://carto.com'
      });
    });
  });

  describe('actions', () => {
    describe('updateData', () => {
      let state;
      beforeEach(() => {
        state = {
          email: 'example@example.org',
          username: 'example',
          website: 'https://carto.com'
        };
      });

      it('success', done => {
        const newConfigData = {
          email: 'example@carto.com',
          username: 'carto'
        };

        testAction(actions.updateData, null, state, [
          { type: 'setUserData', payload: newConfigData }
        ], [], done);
      });
    });
  });
});

import UserStore from 'new-dashboard/store/user';
import { testAction2 } from '../helpers';

jest.mock('carto-node');

const mutations = UserStore.mutations;
const actions = UserStore.actions;

describe('UserStore', () => {
  describe('mutations', () => {
    it('setUserData', () => {
      const state = {
        email: 'example@example.org',
        username: 'example',
        website: 'https://carto.com'
      };

      const userData = {
        email: 'example@carto.com',
        username: 'carto'
      };
      mutations.setUserData(state, userData);

      expect(state).toEqual({
        email: 'example@carto.com',
        username: 'carto',
        website: 'https://carto.com'
      });
    });
  });

  describe('actions', () => {
    describe('updateData', () => {
      let state;
      beforeEach(() => {
        state = {
          email: 'example@example.org',
          username: 'example',
          website: 'https://carto.com'
        };
      });

      it('success', done => {
        const newConfigData = {
          email: 'example@carto.com',
          username: 'carto'
        };
        const expectedMutations = [
          { type: 'setUserData', payload: newConfigData }
        ];
        testAction2({ action: actions.updateData, state, expectedMutations, done });
      });
    });
  });
});

Use new test action function


/* @flow */
import type { State } from '../../common/types';
import React from 'react';
import AddrListByRoom from './AddressListByRoom';
import AddrList from './AddressList';
import { connect } from 'react-redux';
import linksMessages from '../../common/app/linksMessages';
import { isEmpty, pathOr, reject } from 'ramda';

import {
  Block,
  Title,
  View,
} from '../components';

import { Box } from 'reflexbox';

type HomePageProps = {
  smartHomeState: Object,
  location: Object,
};

const HomePage = ({ smartHomeState, location }: HomePageProps) => {
  const { livestate, prefs } = smartHomeState;

  /* Built address-list, remove some address-types which should not be displayed */
  const addresses = reject(addr => addr.type === 'fb', livestate);

  if (isEmpty(addresses)) {
    return (
      <Block>

Waiting for SmartHome-State...

      </Block>
    );
  }

  const viewType = pathOr('changes', ['pathname'], location);
  const addrList = viewType === 'changes'
                 ? <AddrList addresses={addresses} />
                 : <AddrListByRoom addresses={addresses} prefs={prefs} />;

  return (
    <View>
      <Title message={linksMessages.home} />
      <Box py={2}>
        {addrList}
      </Box>
    </View>
  );
};

export default connect(
  (state: State) => ({
    smartHomeState: state.smartHome,
  }),
)(HomePage);

/* @flow */
import type { State } from '../../common/types';
import React from 'react';
import AddrListByRoom from './AddressListByRoom';
import AddrList from './AddressList';
import { connect } from 'react-redux';
import linksMessages from '../../common/app/linksMessages';
import { isEmpty, pathOr, reject, test } from 'ramda';

import { Block, Title, View } from '../components';

import { Box } from 'reflexbox';

type HomePageProps = {
  smartHomeState: Object,
  location: Object,
};

const HomePage = ({ smartHomeState, location }: HomePageProps) => {
  const { livestate, prefs } = smartHomeState;

  /* Built address-list, remove some address-types which should not be displayed */
  const addresses = reject(addr => addr.type === 'fb', livestate);

  if (isEmpty(addresses)) {
    return (
      <Block>

Waiting for SmartHome-State...

      </Block>
    );
  }

  const viewType = pathOr('changes', ['pathname'], location);
  const addrList = test(/rooms$/, viewType)
    ? <AddrListByRoom addresses={addresses} prefs={prefs} />
    : <AddrList addresses={addresses} />;

  return (
    <View>
      <Title message={linksMessages.home} />
      <Box py={2}>
        {addrList}
      </Box>
    </View>
  );
};

export default connect((state: State) => ({
  smartHomeState: state.smartHome,
}))(HomePage);

Improve tolerance when matching url-paths for router-navigation.


import PlayerController from './PlayerController'
import { connect } from 'react-redux'
import { ensureState } from 'redux-optimistic-ui'
import getOrderedQueue from 'routes/Queue/selectors/getOrderedQueue'
import {
  playerLeave,
  playerError,
  playerLoad,
  playerPlay,
  playerStatus,
} from '../../modules/player'
import { playerVisualizerError } from '../../modules/playerVisualizer'

const mapActionCreators = {
  playerLeave,
  playerError,
  playerLoad,
  playerPlay,
  playerStatus,
  playerVisualizerError,
}

const mapStateToProps = (state) => {
  const { player, playerVisualizer, prefs } = state
  const queue = ensureState(state.queue)

  return {
    cdgAlpha: player.cdgAlpha,
    cdgSize: player.cdgSize,
    historyJSON: state.status.historyJSON, // @TODO use state.player instead?
    isAtQueueEnd: player.isAtQueueEnd,
    isErrored: player.isErrored,
    isPlaying: player.isPlaying,
    isPlayingNext: player.isPlayingNext,
    isQueueEmpty: queue.result.length === 0,
    isReplayGainEnabled: prefs.isReplayGainEnabled,
    isWebGLSupported: player.isWebGLSupported,
    mp4Alpha: player.mp4Alpha,
    queue: getOrderedQueue(state),
    queueId: player.queueId,
    volume: player.volume,
    visualizer: playerVisualizer,
  }
}

export default connect(mapStateToProps, mapActionCreators)(PlayerController)

import PlayerController from './PlayerController'
import { connect } from 'react-redux'
import { ensureState } from 'redux-optimistic-ui'
import getOrderedQueue from 'routes/Queue/selectors/getOrderedQueue'
import {
  playerLeave,
  playerError,
  playerLoad,
  playerPlay,
  playerStatus,
} from '../../modules/player'
import { playerVisualizerError } from '../../modules/playerVisualizer'

const mapActionCreators = {
  playerLeave,
  playerError,
  playerLoad,
  playerPlay,
  playerStatus,
  playerVisualizerError,
}

const mapStateToProps = (state) => {
  const { player, playerVisualizer, prefs } = state
  const queue = ensureState(state.queue)

  return {
    cdgAlpha: player.cdgAlpha,
    cdgSize: player.cdgSize,
    historyJSON: player.historyJSON,
    isAtQueueEnd: player.isAtQueueEnd,
    isErrored: player.isErrored,
    isPlaying: player.isPlaying,
    isPlayingNext: player.isPlayingNext,
    isQueueEmpty: queue.result.length === 0,
    isReplayGainEnabled: prefs.isReplayGainEnabled,
    isWebGLSupported: player.isWebGLSupported,
    mp4Alpha: player.mp4Alpha,
    queue: getOrderedQueue(state),
    queueId: player.queueId,
    volume: player.volume,
    visualizer: playerVisualizer,
  }
}

export default connect(mapStateToProps, mapActionCreators)(PlayerController)

Use internal player history instead of emitted status


exports = module.exports = require('util');

exports.formatDate = function(date){
  if (typeof date === 'string') return date;
  date = new Date(date);

  var month = date.getMonth() + 1,
    day = date.getDate(),
    str = date.getFullYear();

  str += '-';
  if (month < 10) str += '0';
  str += month;

  str += '-';
  if (day < 10) str += '0';
  str += day;

  return str;
};

exports.formatError = function(data){
  return new Error(data.message + ' (Code: ' + data.code + ')');
};

exports = module.exports = require('util');

exports.formatDate = function(date){
  if (typeof date === 'string') return date;
  date = new Date(date);

  var month = date.getMonth() + 1,
    day = date.getDate(),
    str = date.getFullYear();

  str += '-';
  if (month < 10) str += '0';
  str += month;

  str += '-';
  if (day < 10) str += '0';
  str += day;

  return str;
};

exports.formatError = function(data){
  var err = new Error(data.message + ' (Code: ' + data.code + ')');
  err.code = data.code;

  return err;
};

Add code to error object


import React from 'react';
import {FormattedMessage} from "react-intl";
import queryString from "query-string";

const OAuthLogin = ({providers, onLogin}) => {
  const location = window.location;
  const targetUrl = `${location.protocol}//${location.host}/login`;
  const loginUrlQueryString = `?next=${encodeURIComponent(targetUrl)}`;

  return

    {providers.map(provider =>

      <a href={`${provider.login}${loginUrlQueryString}`} className="pt-button pt-intent-primary pt-icon-log-in">
        <FormattedMessage id="login.provider" defaultMessage="Sign in with {label}"
                          values={{label: provider.label}}/>

)}

};

export const handleOAuthCallback = (onLoginFn) => {
  const parsedHash = queryString.parse(location.hash);
  if (parsedHash.token) {
    onLoginFn(parsedHash.token);
    location.hash = '';
  }
};

export default OAuthLogin;

import React from 'react';
import {FormattedMessage} from "react-intl";
import {AnchorButton, Intent} from '@blueprintjs/core';
import queryString from "query-string";

const OAuthLogin = ({providers, onLogin}) => {
  const location = window.location;
  const targetUrl = `${location.protocol}//${location.host}/login`;
  const loginUrlQueryString = `?next=${encodeURIComponent(targetUrl)}`;

  return

    {providers.map(provider =>

      <AnchorButton href={`${provider.login}${loginUrlQueryString}`} intent={Intent.PRIMARY}>
        <FormattedMessage id="login.provider" defaultMessage="Sign in with {label}"
                          values={{label: provider.label}}/>
      </AnchorButton>

)}

};

export const handleOAuthCallback = (onLoginFn) => {
  const parsedHash = queryString.parse(location.hash);
  if (parsedHash.token) {
    onLoginFn(parsedHash.token);
    location.hash = '';
  }
};

export default OAuthLogin;

Use AnchorButton for anchor button


Ext.define('Jarvus.aggregrid.Aggregrid', {
    extend: 'Ext.Component',

    html: 'Hello World'
});

Ext.define('Jarvus.aggregrid.Aggregrid', {
    extend: 'Ext.Component',

    config: {
        columnsStore: null,
        rowsStore: null
    },

    html: 'Hello World',

    // config handlers
    applyColumnsStore: function(store) {
        return Ext.StoreMgr.lookup(store);
    },

    applyRowsStore: function(store) {
        return Ext.StoreMgr.lookup(store);
    }
});

Add columnsStore and rowsStore configs


export const BLOCK_TYPE = {
  // This is used to represent a normal text block (paragraph).
  UNSTYLED: 'unstyled',
  HEADER_ONE: 'header-one',
  HEADER_TWO: 'header-two',
  HEADER_THREE: 'header-three',
  HEADER_FOUR: 'header-four',
  HEADER_FIVE: 'header-five',
  HEADER_SIX: 'header-six',
  UNORDERED_LIST_ITEM: 'unordered-list-item',
  ORDERED_LIST_ITEM: 'ordered-list-item',
  BLOCKQUOTE: 'blockquote',
  PULLQUOTE: 'pullquote',
  CODE: 'code-block',
  ATOMIC: 'atomic',
};

export const ENTITY_TYPE = {
  LINK: 'LINK',
};

export const INLINE_STYLE = {
  BOLD: 'BOLD',
  CODE: 'CODE',
  ITALIC: 'ITALIC',
  STRIKETHROUGH: 'STRIKETHROUGH',
  UNDERLINE: 'UNDERLINE',
};

export default {
  BLOCK_TYPE,
  ENTITY_TYPE,
  INLINE_STYLE,
};

export const BLOCK_TYPE = {
  // This is used to represent a normal text block (paragraph).
  UNSTYLED: 'unstyled',
  HEADER_ONE: 'header-one',
  HEADER_TWO: 'header-two',
  HEADER_THREE: 'header-three',
  HEADER_FOUR: 'header-four',
  HEADER_FIVE: 'header-five',
  HEADER_SIX: 'header-six',
  UNORDERED_LIST_ITEM: 'unordered-list-item',
  ORDERED_LIST_ITEM: 'ordered-list-item',
  BLOCKQUOTE: 'blockquote',
  PULLQUOTE: 'pullquote',
  CODE: 'code-block',
  ATOMIC: 'atomic',
};

export const ENTITY_TYPE = {
  LINK: 'LINK',
  IMAGE: 'IMAGE'
};

export const INLINE_STYLE = {
  BOLD: 'BOLD',
  CODE: 'CODE',
  ITALIC: 'ITALIC',
  STRIKETHROUGH: 'STRIKETHROUGH',
  UNDERLINE: 'UNDERLINE',
};

export default {
  BLOCK_TYPE,
  ENTITY_TYPE,
  INLINE_STYLE,
};

Add Image to ENTITY_TYPE Enum


import { StyleSheet } from 'react-native';

const paymentInfoStyle = StyleSheet.create({
  addCardButton: {
    padding: 20,
  },
});

module.exports = paymentInfoStyle;

import { StyleSheet } from 'react-native';

const paymentInfoStyle = StyleSheet.create({
  addCardButton: {
    padding: 10,
  },
});

module.exports = paymentInfoStyle;

Reduce button padding in PaymentInfoScreen


import { takeLatest, call, put } from 'redux-saga/effects';
import axios from 'axios';
import {
    MODIFY_PAYMENTTYPE_REQUEST,
    MODIFY_PAYMENTTYPE_RECEIVE,
    MODIFY_PAYMENTTYPE_FAILURE,
} from '../actiontypes';

function doModifyPaymenttype(paymenttype) {
    return axios.post('/ukelonn/api/admin/paymenttype/modify', paymenttype);
}

function* requestReceiveModifyPaymenttypeSaga(action) {
    try {
        const response = yield call(doModifyPaymenttype, action.payload);
        const paymenttypes = (response.headers['content-type'] == 'application/json') ? response.data : [];
        yield put(MODIFY_PAYMENTTYPE_RECEIVE(paymenttypes));
    } catch (error) {
        yield put(MODIFY_PAYMENTTYPE_FAILURE(error));
    }
}

export default function* modifyPaymenttypeSaga() {
    yield takeLatest(MODIFY_PAYMENTTYPE_REQUEST, requestReceiveModifyPaymenttypeSaga);
}

import { takeLatest, call, put } from 'redux-saga/effects';
import axios from 'axios';
import {
    MODIFY_PAYMENTTYPE_REQUEST,
    MODIFY_PAYMENTTYPE_RECEIVE,
    MODIFY_PAYMENTTYPE_FAILURE,
    CLEAR_PAYMENT_TYPE_FORM,
} from '../actiontypes';

function doModifyPaymenttype(paymenttype) {
    return axios.post('/ukelonn/api/admin/paymenttype/modify', paymenttype);
}

function* requestReceiveModifyPaymenttypeSaga(action) {
    try {
        const response = yield call(doModifyPaymenttype, action.payload);
        const paymenttypes = (response.headers['content-type'] == 'application/json') ? response.data : [];
        yield put(MODIFY_PAYMENTTYPE_RECEIVE(paymenttypes));
    } catch (error) {
        yield put(MODIFY_PAYMENTTYPE_FAILURE(error));
    }
}

function* clearPaymenttypeForm() {
    yield put(CLEAR_PAYMENT_TYPE_FORM());
}

export default function* modifyPaymenttypeSaga() {
    yield takeLatest(MODIFY_PAYMENTTYPE_REQUEST, requestReceiveModifyPaymenttypeSaga);
    yield takeLatest(MODIFY_PAYMENTTYPE_RECEIVE, clearPaymenttypeForm);
}

Clear payment type form data after succesfully saving a modified payment type