(function() {
    'use strict';

    window.widgets = {};

    $(function() {
        var defaults = {
            platformUrl: 'https://partner.voipgrid.nl/',
            c2d: 'true',
        };

        for(var key in defaults) {
            if(defaults.hasOwnProperty(key)) {
                if(storage.get(key) === null) {
                    storage.put(key, defaults[key]);
                }
            }
        }

        console.info('current storage:');
        for(var i = 0; i < localStorage.length; i++) {
            console.info(localStorage.key(i)+'='+localStorage.getItem(localStorage.key(i)));
        }

        // keep track of some notifications
        storage.put('notifications', {});
        panels.MainPanel(widgets);

        // widgets initialization
        for(var widget in widgets) {
            widgets[widget].init();
        }

        // continue last session if credentials are available
        if(storage.get('user') && storage.get('username') && storage.get('password')) {
            for(var widget in widgets) {
                widgets[widget].restore();
            }

            // look for phone numbers in tabs from now on
            page.init();
        }
    });

    window.logout = function() {
        console.info('logout');

        chrome.runtime.sendMessage('logout');

        panels.resetStorage();
        panels.resetWidgets();
        page.reset();
        storage.remove('user');
        storage.remove('username');
        storage.remove('password');
    };
})();

(function() {
    'use strict';

    window.widgets = {};

    $(function() {
        var defaults = {
            platformUrl: 'https://partner.voipgrid.nl/',
            c2d: 'true',
        };

        for(var key in defaults) {
            if(defaults.hasOwnProperty(key)) {
                if(storage.get(key) === null) {
                    storage.put(key, defaults[key]);
                }
            }
        }

        console.info('current storage:');
        for(var i = 0; i < localStorage.length; i++) {
            console.info(localStorage.key(i)+'='+localStorage.getItem(localStorage.key(i)));
        }

        // keep track of some notifications
        storage.put('notifications', {});
        panels.MainPanel(widgets);

        // widgets initialization
        for(var widget in widgets) {
            widgets[widget].init();
        }

        // continue last session if credentials are available
        if(storage.get('user') && storage.get('username') && storage.get('password')) {
            for(var widget in widgets) {
                // use 'load' instead of 'restore' to refresh the data on browser restart
                widgets[widget].load();
            }

            // look for phone numbers in tabs from now on
            page.init();
        }
    });

    window.logout = function() {
        console.info('logout');

        chrome.runtime.sendMessage('logout');

        panels.resetStorage();
        panels.resetWidgets();
        page.reset();
        storage.remove('user');
        storage.remove('username');
        storage.remove('password');
    };
})();

Refresh the widget data on browser restart.


import Vue        from 'vue'
import Router     from 'vue-router'
import menuModule from '../store/modules/menu'

Vue.use(Router)

const router =  new Router({
  mode: 'history',
  routes: [
    ...generateRoutesFromMenu(menuModule.state.items),
    {
      name: 'News',
      path: '/culture/news/:id',
      component: (resolve) => { require(['@/components/layout/Reader.vue'], resolve) }
    },
    {
      name: 'Error',
      path: '/error/:status',
      component: (resolve) => { require(['@/components/layout/Error.vue'], resolve) }
    }
  ]
})

function generateRoutesFromMenu (menu = [], routes = []) {
  for (let i = 0, l = menu.length; i < l; i++) {
    let item = menu[i]
    if (item.path) {
      routes.push(item)
    }
  }
  return routes
}

export default router

import Vue        from 'vue'
import Router     from 'vue-router'
import menuModule from '../store/modules/menu'

Vue.use(Router)

const router =  new Router({
  mode: 'history',
  routes: [
    ...generateRoutesFromMenu(menuModule.state.items),
    {
      name: 'News',
      path: '/culture/news/:id',
      component: (resolve) => { require(['@/components/layout/Reader.vue'], resolve) }
    },
    {
      name: 'Error',
      path: '/error/:status',
      component: (resolve) => { require(['@/components/layout/Error.vue'], resolve) }
    },
    {
      path: '*',
      redirect: '/error/404'
    }
  ]
})

function generateRoutesFromMenu (menu = [], routes = []) {
  for (let i = 0, l = menu.length; i < l; i++) {
    let item = menu[i]
    if (item.path) {
      routes.push(item)
    }
  }
  return routes
}

export default router

Add error event to router.


'use strict';

const semver = require('semver');
const utils = require('./utils');

const codemods = {
  'ember-modules-codemod': {
    version: '2.16.0-beta.1',
    projectTypes: ['app', 'addon'],
    commands: ['ember-modules-codemod']
  },
  'ember-qunit-codemod': {
    version: '3.0.0-beta.1',
    projectTypes: ['app', 'addon'],
    commands: ['jscodeshift -t https://rawgit.com/rwjblue/ember-qunit-codemod/master/ember-qunit-codemod.js ./tests/']
  }
};

module.exports = function runCodemods(options) {
  let projectType = options.projectType;
  let startVersion = options.startVersion;

  return Object.keys(codemods).filter(codemod => {
    return semver.gte(startVersion, codemods[codemod].version) &&
      codemods[codemod].projectTypes.indexOf(projectType) !== -1;
  }).reduce((promise, codemod) => {
    return codemods[codemod].commands.reduce((promise, command) => {
      return promise.then(() => utils.npx(command));
    }, promise);
  }, Promise.resolve()).then(() => {
    return utils.run('git add -A');
  });
};

'use strict';

const semver = require('semver');
const utils = require('./utils');

const codemods = {
  'ember-modules-codemod': {
    version: '2.16.0-beta.1',
    projectTypes: ['app', 'addon'],
    commands: ['ember-modules-codemod']
  },
  'ember-qunit-codemod': {
    version: '3.0.0-beta.1',
    projectTypes: ['app', 'addon'],
    commands: ['jscodeshift -t https://rawgit.com/rwjblue/ember-qunit-codemod/master/ember-qunit-codemod.js ./tests/']
  },
  'es5-getter-ember-codemod': {
    version: '3.1.0-beta.1',
    projectTypes: ['app', 'addon'],
    commands: ['jscodeshift -t https://rawgit.com/rondale-sc/es5-getter-ember-codemod/master/es5-getter-ember-codemod.js ./app']
  }
};

module.exports = function runCodemods(options) {
  let projectType = options.projectType;
  let startVersion = options.startVersion;

  return Object.keys(codemods).filter(codemod => {
    return semver.gte(startVersion, codemods[codemod].version) &&
      codemods[codemod].projectTypes.indexOf(projectType) !== -1;
  }).reduce((promise, codemod) => {
    return codemods[codemod].commands.reduce((promise, command) => {
      return promise.then(() => utils.npx(command));
    }, promise);
  }, Promise.resolve()).then(() => {
    return utils.run('git add -A');
  });
};

Add es5-getter-ember-codemod to list of codemods to run


import invariant from "invariant";

const propValidates = ([name, propValidationFn]) => (props, componentName) => !(propValidationFn(props, name, componentName) instanceof Error);

export const satisfyOneOf = (propSet) => {
    invariant(
        "object" === typeof propSet && null != propSet,
        "Expected propSet to be an object"
    );

    if (2 > Object.keys(propSet).length) {
        return propSet;
    }

    const propSetValidationFailed = `Expected one in [${Object.keys(propSet).join(", ")}] to pass validation`;

    const satisfyOne = (name, propValidationFn) => {
        const restOfPropSet = Object.entries(propSet).filter(([propName]) => propName !== name).map(propValidates);

        return (props, propName, componentName) => {
            const shouldValidateLocally = null != props[propName];

            if (!shouldValidateLocally) {
                return restOfPropSet.some((isValid) => isValid(props, componentName)) || new Error(propSetValidationFailed);
            }

            return propValidationFn(props, propName, componentName);
        };
    };

    return Object.entries(propSet).
        reduce((props, [name, propValidationFn]) => ({
            ...props,
            [name]: satisfyOne(name, propValidationFn)
        }), {});
};

import invariant from "invariant";

const entries = (obj) => Object.keys(obj).map((key) => [key, obj[key]]);

const propValidates = ([name, propValidationFn]) => (props, componentName) => !(propValidationFn(props, name, componentName) instanceof Error);

export const satisfyOneOf = (propSet) => {
    invariant(
        "object" === typeof propSet && null != propSet,
        "Expected propSet to be an object"
    );

    if (2 > Object.keys(propSet).length) {
        return propSet;
    }

    const propSetValidationFailed = `Expected one in [${Object.keys(propSet).join(", ")}] to pass validation`;

    const satisfyOne = (name, propValidationFn) => {
        const restOfPropSet = entries(propSet).filter(([propName]) => propName !== name).map(propValidates);

        return (props, propName, componentName) => {
            const shouldValidateLocally = null != props[propName];

            if (!shouldValidateLocally) {
                return restOfPropSet.some((isValid) => isValid(props, componentName)) || new Error(propSetValidationFailed);
            }

            return propValidationFn(props, propName, componentName);
        };
    };

    return entries(propSet).
        reduce((props, [name, propValidationFn]) => ({
            ...props,
            [name]: satisfyOne(name, propValidationFn)
        }), {});
};

Refactor implement object entries locally


'use strict';

var bluebird = require('bluebird');
var nano = require('nano');
var _ = require('lodash');

const blacklist = new Set(['use']);

/**
 * Promisifies the exposed functions on an object
 * Based on a similar function in `qano`
 *
 * @ref https://github.com/jclohmann/qano
 */
function deepPromisify(obj) {
  return _.transform(obj, function(promisifiedObj, value, key) {
    if (blacklist.has(key)) {
      promisifiedObj[key] = value;
      return;
    }

    if (typeof value === 'function') {
      promisifiedObj[key] = bluebird.promisify(value, obj);
    } else if (typeof value === 'object') {
      promisifiedObj[key] = deepPromisify(value);
    } else {
      promisifiedObj[key] = value;
    }
  });
}

module.exports = function nanoblue() {
  return deepPromisify(nano.apply(null, arguments));
};

'use strict';

var bluebird = require('bluebird');
var nano = require('nano');
var _ = require('lodash');

const blacklist = new Set(['use', 'scope']);

/**
 * Promisifies the exposed functions on an object
 * Based on a similar function in `qano`
 *
 * @ref https://github.com/jclohmann/qano
 */
function deepPromisify(obj) {
  return _.transform(obj, function(promisifiedObj, value, key) {
    if (blacklist.has(key)) {
      promisifiedObj[key] = value;
      return;
    }

    if (typeof value === 'function') {
      promisifiedObj[key] = bluebird.promisify(value, obj);
    } else if (typeof value === 'object') {
      promisifiedObj[key] = deepPromisify(value);
    } else {
      promisifiedObj[key] = value;
    }
  });
}

module.exports = function nanoblue() {
  var nanoP = deepPromisify(nano.apply(null, arguments));

  // replace nano's docModule calls with a promisified version
  var originalDocModule = nanoP.use;
  nanoP.use = nanoP.scope = nanoP.db.use = nanoP.db.scope = function() {
    return deepPromisify(originalDocModule());
  };

  return nanoP;
};

Enable promisification regardless of initial scope


/*
#
# img2musicXML.js - v0.1-Alpha
# Apache v2 License
#
*/

// Create img2musicXML (abbreviated as i2mx) namespace
var i2mx = im2x || { };

// Create JsCheckup class (@TODO: Move to inside namespace)
var JsCheckup = function() {
	this.divClassName = "i2mx-checkup";

	this.activate = function() {
		var divList = document.getElementsByClassName(this.divClassName);

		// @TODO: Implement multiple divs support
		divList[0].innerHTML = "No checlist for now, but img2musicXML loaded successfully!"; // @TODO: Multi-line
	}

}

var jsCheckup = new JsCheckup();

// @TODO: Check window.onload vs document.onload
// @TODO: Use event listener instead of onload
document.onload = function() {
	jsCheckup.activate();
}

Add newline at end of file


'use strict';

var noop = require('./noop');
var Records = require('./records');

module.exports = ResultSet;

function ResultSet(resultset) {
  this._resultset = resultset;
}

ResultSet.prototype = {
  set: function (key, val) {
    this._resultset.set(key, val);
    return this;
  },

  get: function (key) {
    return this._resultset.get(key);
  },

  get size() {
    return this._resultset.size();
  },

  getRecords: function (index, counts, cb) {
    cb || (cb = noop);
    this._resultset.getRecords(index, counts, function (err, records) {
      if (err) {
        cb(err);
        return;
      }
      cb(null, new Records(records));
    });
  }
};

'use strict';

var noop = require('./noop');
var Records = require('./records');

module.exports = ResultSet;

function ResultSet(resultset) {
  this._resultset = resultset;
}

ResultSet.prototype = {
  set: function (key, val) {
    this._resultset.setOption(key, val);
    return this;
  },

  get: function (key) {
    return this._resultset.getOption(key);
  },

  get size() {
    return this._resultset.size();
  },

  getRecords: function (index, counts, cb) {
    cb || (cb = noop);
    this._resultset.getRecords(index, counts, function (err, records) {
      if (err) {
        cb(err);
        return;
      }
      cb(null, new Records(records));
    });
  }
};

Fix resultSet set and get functions


import vk_sdk from 'vksdk'
import util from 'util'
import {logger} from './logger'

export const vk_api = new vk_sdk({
  appId: process.env.VK_GROUP_STATS_VK_APP_ID || 5878021,
  appSecret: process.env.VK_GROUP_STATS_VK_APP_SECRET,
  version: '5.62',
})
vk_api.on('http-error', error => {
  logger.error(`app can't request VK API: ${util.inspect(error)}`)
})
vk_api.on('parse-error', error => {
  logger.error(`VK API response is incorrect: ${util.inspect(error)}`)
})

import vk_api from 'node-vkapi'

export const vk_api_client = new vk_api({
  app: {
    id: process.env.VK_GROUP_STATS_VK_APP_ID || 5878021,
    secret: process.env.VK_GROUP_STATS_VK_APP_SECRET,
  },
  version: '5.62',
})

Correct an initialization of VK API


import React, { Component } from 'react'
import { Map, TileLayer, GeoJSON } from 'react-leaflet'

const MAP = {
  lat: 47,
  lon: 1,
  zoom: 4,
  osmUrl: 'https://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png',
  osmAttribution: '© Contributeurs OpenStreetMap',
}

class CenteredMap extends Component {

  componentDidMount() {
    const { vectors, map } = this.refs

    if (vectors && map) {
      const bounds = vectors.leafletElement.getBounds()
      map.leafletElement.fitBounds(bounds)
    }
  }

  render() {
    const zoom = this.props.zoom || MAP.zoom
    const lat = this.props.lat || MAP.lat
    const lon = this.props.lon || MAP.lon

    const { vectors, className, frozen } = this.props

    return (
      <Map ref="map" className={className} center={[lat, lon]} zoom={zoom} dragging={!frozen} scrollWheelZoom={false} zoomControl={!frozen}>
        <TileLayer attribution={MAP.osmAttribution} url={MAP.osmUrl} />
        <GeoJSON color='blue' fillOpacity={0.1} weight={2} ref="vectors" data={vectors} />
      </Map>
    )
  }
}

export default CenteredMap

import React, { Component } from 'react'
import { Map, TileLayer, GeoJSON } from 'react-leaflet'

const MAP = {
  lat: 47,
  lon: 1,
  zoom: 4,
  osmUrl: 'https://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png',
  osmAttribution: '© Contributeurs OpenStreetMap',
}

class CenteredMap extends Component {

  componentDidMount() {
    const { vectors, map } = this.refs

    if (vectors && map) {
      const bounds = vectors.leafletElement.getBounds()
      map.leafletElement.fitBounds(bounds)
    }
  }

  render() {
    const zoom = this.props.zoom || MAP.zoom
    const lat = this.props.lat || MAP.lat
    const lon = this.props.lon || MAP.lon

    const { vectors, className, frozen } = this.props

    return (
      <Map
        ref="map"
        className={className}
        center={[lat, lon]}
        zoom={zoom}
        dragging={!frozen}
        scrollWheelZoom={false}
        doubleClickZoom={false}
        zoomControl={!frozen}
      >
        <TileLayer attribution={MAP.osmAttribution} url={MAP.osmUrl} />
        <GeoJSON color='blue' fillOpacity={0.1} weight={2} ref="vectors" data={vectors} />
      </Map>
    )
  }
}

export default CenteredMap

Disable spatial extent map double click zoom


$(document).ready(function () {
    $("#id_type_1").click(function () {
        $("#id_inbound_date").prop('disabled', true);
        $("#id_inbound_date_preference").prop('disabled', true);
    });
    $("#id_type_0").click(function () {
        $("#id_inbound_date").prop('disabled', false);
        $("#id_inbound_date_preference").prop('disabled', false);
    });
});

$(document).ready(function () {
    $("#id_type_1").click(function () {
        $("#id_inbound_date").parents('.row').hide();
        $("#id_inbound_date_preference").parents('.row').hide();
    });
    $("#id_type_0").click(function () {
        $("#id_inbound_date").parents('.row').show();
        $("#id_inbound_date_preference").parents('.row').show();
    });
});

Hide or show inbound date according with type of trip selected


define(["underscore", "jquery", "app"], function(_, $, app) {
  return function () {

    var public = {};
    var private = {};

    private.createObject = function (averageCartype) {
      var queryObject = {
        c: [],
        ecol: [],
        sc: null,
        p: null,
        psz: 100,
        ll: null,
        dam: 0,
        ao: "PICTURES"
      };

      // Cartypes
      for (var carclass in averageCartype.get("carclass"))
        queryObject.c.push(carclass);

      // Colors
      for (var color in averageCartype.get("colors"))
        queryObject.ecol.push(color);

      // Seats
      var seatsRange = averageCartype.get("min_seats") + ":" + averageCartype.get("max_seats");
      queryObject.sc = seatsRange;

      // Price
      var priceRange = averageCartype.get("min_price") + ":" + averageCartype.get("max_price");
      queryObject.p = priceRange;

      // Location
      queryObject.ll = app.userProfile.get("point");

      return queryObject;
    };

    private.createURL = function (queryObject) {
      var url = "http://m.mobile.de/svc/s/?";
      var params = $.param(queryObject);
      url = url.concat(params);

      return url;
    };

    public.getResults = function (averageCartype) {
      var queryObject = private.createObject(averageCartype);
      var url = private.createURL(queryObject);
      return $.getJSON(url);
    };

    return public;
  };
});

define(["underscore", "jquery", "app"], function(_, $, app) {
  return function () {

    var public = {};
    var private = {};

    private.createObject = function (averageCartype) {
      var queryObject = {
        c: [],
        ecol: [],
        sc: null,
        p: null,
        psz: 100,
        ll: null,
        dam: 0,
        ao: "PICTURES",
        sb: "ml",
        rtd: 1
      };

      // Cartypes
      for (var carclass in averageCartype.get("carclass"))
        queryObject.c.push(carclass);

      // Colors
      for (var color in averageCartype.get("colors"))
        queryObject.ecol.push(color);

      // Seats
      var seatsRange = averageCartype.get("min_seats") + ":" + averageCartype.get("max_seats");
      queryObject.sc = seatsRange;

      // Price
      var priceRange = averageCartype.get("min_price") + ":" + averageCartype.get("max_price");
      queryObject.p = priceRange;

      // Location
      queryObject.ll = app.userProfile.get("point");

      return queryObject;
    };

    private.createURL = function (queryObject) {
      var url = "http://m.mobile.de/svc/s/?";
      var params = $.param(queryObject);
      url = url.concat(params);

      return url;
    };

    public.getResults = function (averageCartype) {
      var queryObject = private.createObject(averageCartype);
      var url = private.createURL(queryObject);
      return $.getJSON(url);
    };

    return public;
  };
});

Change sort of cars and add aditional params to query


var profile = (function () {
	var testResourceRe = /^core\/tests\//,

		copyOnly = function (filename, mid) {
			var list = {
				'core/package': 1,
				'core/package.json': 1,
				'core/tests': 1,
				'core/third_party': 1,
				'core/node_modules': 1
			};
			return (mid in list) ||
				/(png|jpg|jpeg|gif|tiff)$/.test(filename);
		};

	return {
		resourceTags: {
			test: function (filename, mid) {
				return testResourceRe.test(mid) || mid === 'core/tests';
			},

			copyOnly: function (filename, mid) {
				return copyOnly(filename, mid);
			},

			amd: function (filename, mid) {
				return !testResourceRe.test(mid) && !copyOnly(filename, mid) && /\.js$/.test(filename);
			}
		}
	};
})();

var profile = (function () {
	var testResourceRe = /^core\/tests\//,

		copyOnly = function (filename, mid) {
			var list = {
				'core/package': 1,
				'core/package.json': 1,
				'core/tests': 1,
				'core/third_party': 1,
				'core/node_modules': 1
			};
			return (mid in list) ||
				/^core\/tests\/\w+$/ ||
				/^core\/node_modules\/\w+$/ ||
				// /^core\/third_party\/\w+$/ ||
				/(png|jpg|jpeg|gif|tiff)$/.test(filename);
		};

	return {
		resourceTags: {
			test: function (filename, mid) {
				return testResourceRe.test(mid) || mid === 'core/tests';
			},

			copyOnly: function (filename, mid) {
				return copyOnly(filename, mid);
			},

			amd: function (filename, mid) {
				return !testResourceRe.test(mid) && !copyOnly(filename, mid) && /\.js$/.test(filename);
			}
		}
	};
})();

Update packaging for Dojo builder


Package.describe({
    summary: 'Minifiers for Meteor with PostCSS processing - use Autoprefixer and others with ease',
    version: '0.2.4',
    name: 'juliancwirko:postcss',
    git: 'https://github.com/juliancwirko/meteor-postcss.git'
});

Package.registerBuildPlugin({
    name: 'minifiers-postcss',
    use: [
        'ecmascript@0.1.6',
        'minifiers@1.1.7'
    ],
    npmDependencies: {
        'source-map': '0.5.3',
        'postcss': '5.0.12',
        'es6-promise': '3.0.2',
        'app-module-path': '1.0.5'
    },
    sources: [
        'plugin/minify-js.js',
        'plugin/minify-css.js'
    ]
});

Package.onUse(function(api) {
    api.use('meteorhacks:npm@1.5.0', ['client']);
    api.use('isobuild:minifier-plugin@1.0.0');
});

Package.onTest(function(api) {

});

Package.describe({
    summary: 'Minifiers for Meteor with PostCSS processing - use Autoprefixer and others with ease',
    version: '0.2.4',
    name: 'juliancwirko:postcss',
    git: 'https://github.com/juliancwirko/meteor-postcss.git'
});

Package.registerBuildPlugin({
    name: 'minifiers-postcss',
    use: [
        'ecmascript@0.1.6',
        'minifiers@1.1.7'
    ],
    npmDependencies: {
        'source-map': '0.5.3',
        'postcss': '5.0.12',
        'app-module-path': '1.0.5'
    },
    sources: [
        'plugin/minify-js.js',
        'plugin/minify-css.js'
    ]
});

Package.onUse(function(api) {
    api.use('meteorhacks:npm@1.5.0', ['client']);
    api.use('isobuild:minifier-plugin@1.0.0');
});

Package.onTest(function(api) {

});

Remove promise from NPM dependencies


Package.describe({
  summary: "Use Bower packages in your Meteor app",
  version: "0.1.7"
});

Package._transitional_registerBuildPlugin({
  name: "bower",
  use: [
    "meteor",
    "underscore",
  ],
  sources: [
    "plugin/bower.js",
    "plugin/handler.js",
  ],
  npmDependencies: {
    "bower": "1.3.2",
  }
});

// XXX Tests?

Package.describe({
  summary: "Use Bower packages in your Meteor app",
  version: "0.1.7"
});

Package._transitional_registerBuildPlugin({
  name: "bower",
  use: [
    "meteor",
    "underscore",
  ],
  sources: [
    "plugin/bower.js",
    "plugin/handler.js",
  ],
  npmDependencies: {
    "bower": "1.3.3",
  }
});

// XXX Tests?

Upgrade bower from 1.3.2 to 1.3.3


'use strict';

var _ = require('lodash');
var XpItem = require('../models/xpItem');

module.exports = {
    set: function (exportString) {
        document.location.hash = exportString;
    },
    get: function () {
        var hash = document.location.hash;
        var exportString = hash.replace('#', '');
        return exportString;
    },
    clear: function () {
        document.location.hash = '';
    },
    generateExportString: function (build) {
        var itemExports = _.map(build.xpHistory, function (xpItem) {
            return xpItem.exportString();
        });
        // add callsign and playername as first items
        itemExports.unshift(build.callsign);
        itemExports.unshift(build.playerName);
        var exportString = '/v1/' + itemExports.join(',');
        return exportString;
    },
    parseExportStringToHistory: function (exportString) {
        var splitParts = exportString.split('/');
        var items = splitParts[2];

        var splitItems = items.split(',');

        var playerName = splitItems.shift();
        var callsign = splitItems.shift();

        var xpHistory = _.map(splitItems, function (stringItem) {
            var xpItem = new XpItem();
            xpItem.parseExportString(stringItem);
            return xpItem;
        });

        return {
            xpHistory: xpHistory,
            callsign: callsign,
            playerName: playerName
        };
    }
};

'use strict';

var _ = require('lodash');
var XpItem = require('../models/xpItem');

module.exports = {
    set: function (exportString) {
        document.location.hash = exportString;
    },
    get: function () {
        var hash = document.location.hash;
        var exportString = hash.replace('#', '');
        return exportString;
    },
    clear: function () {
        document.location.hash = '';
    },
    generateExportString: function (build) {
        var itemExports = _.map(build.xpHistory, function (xpItem) {
            return xpItem.exportString();
        });
        // add callsign and playername as first items
        itemExports.unshift(window.encodeURIComponent(build.callsign));
        itemExports.unshift(window.encodeURIComponent(build.playerName));
        var exportString = '/v1/' + itemExports.join(',');
        return exportString;
    },
    parseExportStringToHistory: function (exportString) {
        var splitParts = exportString.split('/');
        var items = splitParts[2];

        var splitItems = items.split(',');

        var playerName = window.decodeURIComponent(splitItems.shift());
        var callsign = window.decodeURIComponent(splitItems.shift());

        var xpHistory = _.map(splitItems, function (stringItem) {
            var xpItem = new XpItem();
            xpItem.parseExportString(stringItem);
            return xpItem;
        });

        return {
            xpHistory: xpHistory,
            callsign: callsign,
            playerName: playerName
        };
    }
};

Fix encoding of callsign and player name in URL


window.Slapdash = window.Slapdash || {};
Slapdash.Util = {

    _urlTemplates: {
        height: '%height%',
        width: '%width%'
    },

    debug: function(msg){
        if (console && console.log) console.log('Slapdash [DEBUG]: ' + msg);
    },

    showError: function(err){
        alert(err);
    },

    getSize: function(x){
        return (Slapdash.Config.spanSize * x) +
               (Slapdash.Config.spanMargin * (x - 1));
    },

    getUrl: function(graph,layoutElement){
        var url = graph.baseUrl + graph.url,
            width = Slapdash.Util.getSize(layoutElement[0]),
            height = Slapdash.Util.getSize(layoutElement[1]);
            params = _.extend({},graph.parameters,{width:width,height:height});
        url += (url.indexOf("?") >= 0) ? "&" : "?";
        url += $.param(params);

        if (graph.templateUrl){
            url = url.replace(Slapdash.Util._urlTemplates.height, height);
            url = url.replace(Slapdash.Util._urlTemplates.width, width);
        }

        return url;
    }
}

window.Slapdash = window.Slapdash || {};
Slapdash.Util = {

    _urlTemplates: {
        height: '%height%',
        width: '%width%'
    },

    debug: function(msg){
        if (console && console.log) console.log('Slapdash [DEBUG]: ' + msg);
    },

    showError: function(err){
        alert(err);
    },

    getSize: function(x){
        return (Slapdash.Config.spanSize * x) +
               (Slapdash.Config.spanMargin * (x - 1));
    },

    getUrl: function(graph,layoutElement){
        var url = graph.baseUrl + graph.url,
            width = Slapdash.Util.getSize(layoutElement[0]),
            height = Slapdash.Util.getSize(layoutElement[1]);
            params = graph.parameters;

        if (graph.templateUrl){
            url = url.replace(Slapdash.Util._urlTemplates.height, height);
            url = url.replace(Slapdash.Util._urlTemplates.width, width);
        }
        else {
            _.extend(params, {width:width, height:height});
        }

        url += (url.indexOf("?") >= 0) ? "&" : "?";
        url += $.param(params);

        return url;
    }
}

Add height/width URL parameters only if not templated


(function () {

    'use strict';

    var path = require('path'),
        loadConfig = require(path.join(__dirname, 'grunt/load')),
        config = {},
        scaffold = {};

    module.exports = function (grunt) {
        scaffold = {
            dev: {
                path: 'dev',
                assets: 'dev/assets',
                partials: 'dev/partials',
                templates: 'dev/templates'
            },
            staging: {
                path: 'staging',
                assets: 'staging/assets'
            },
            build: {
                path: 'build',
                assets: 'build/assets'
            },
            grunt: 'grunt',
            tmp: {
                path: '.tmp',
                assets: '.tmp/assets'
            }
        };

        config = {
            pkg: grunt.file.readJSON('package.json'),
            scaffold: scaffold,
            timestamp: '<%= new Date().getTime() %>',
            banner: '/*! <%= pkg.projectName %> - v<%= pkg.version %> - by <%= pkg.developers %> - <%= grunt.template.today("dd/mm/yyyy") %> */\n',
        };

        grunt.util._.extend(config, loadConfig(path.join(__dirname, 'grunt/options/')));

        grunt.initConfig(config);

        require('load-grunt-tasks')(grunt);
        require('time-grunt')(grunt);

        grunt.loadNpmTasks('assemble');

        grunt.loadTasks('grunt/tasks/');
    };

})();

(function () {

    'use strict';

    var path = require('path'),
        loadConfig = require(path.join(__dirname, 'grunt/load')),
        config = {};

    module.exports = function (grunt) {
        config = {
            pkg: grunt.file.readJSON('package.json'),
            scaffold:  {
                dev: {
                    path: 'dev',
                    assets: 'dev/assets',
                    partials: 'dev/partials',
                    templates: 'dev/templates'
                },
                staging: {
                    path: 'staging',
                    assets: 'staging/assets'
                },
                build: {
                    path: 'build',
                    assets: 'build/assets'
                },
                grunt: 'grunt',
                tmp: {
                    path: '.tmp',
                    assets: '.tmp/assets'
                }
            },
            timestamp: '<%= new Date().getTime() %>',
            banner: '/*! <%= pkg.projectName %> - v<%= pkg.version %> - by <%= pkg.developers %> - <%= grunt.template.today("dd/mm/yyyy") %> */\n',
        };

        grunt.util._.extend(config, loadConfig(path.join(__dirname, 'grunt/options/')));

        grunt.initConfig(config);

        require('load-grunt-tasks')(grunt);
        require('time-grunt')(grunt);

        grunt.loadNpmTasks('assemble');

        grunt.loadTasks('grunt/tasks/');
    };

})();

Remove extra variable by simplifying config


import React from 'react'
// just get render function from react-dom. means we don't have to get all of ReactDOM, and use ReactDOM.render
import { render } from 'react-dom'
import { BrowserRouter, Match } from 'react-router'
import Landing from './Landing'
import Search from './Search'
import Details from './Details'
import '../public/normalize.css'
import '../public/style.css'

const App = React.createClass({
  render () {
    return (
      <BrowserRouter>
        <div className='app'>
          <Match exactly pattern='/' component={Landing} />
          <Match pattern='/search' component={Search} />
          <Match pattern='/details/:id' component={Details} />
        </div>
      </BrowserRouter>
    )
  }
})

// need to call render at bottom
render(<App />, document.getElementById('app'))

import React from 'react'
// just get render function from react-dom. means we don't have to get all of ReactDOM, and use ReactDOM.render
import { render } from 'react-dom'
import { BrowserRouter, Match } from 'react-router'
import Landing from './Landing'
import Search from './Search'
import Details from './Details'
import preload from '../public/data.json'
import '../public/normalize.css'
import '../public/style.css'

const App = React.createClass({
  render () {
    return (
      <BrowserRouter>
        <div className='app'>
          <Match exactly pattern='/' component={Landing} />
          <Match
            pattern='/search'
            component={(props) => <Search shows={preload.shows} {...props} />}
          />
          <Match pattern='/details/:id' component={Details} />
        </div>
      </BrowserRouter>
    )
  }
})

// need to call render at bottom
render(<App />, document.getElementById('app'))

Refactor preloaded json data to ancestor so search and details can both access it; refactor how Search component is rendered so we can pass params to it in BrowserRouter.


window.angular  = {};
window._ngTrace = false;

Object.observe(window.angular, function(changes){
  if (!window._ngTrace && enabled()) {
    window._ngTrace = true;
    inject();
  }
});

function enabled() {
  return window.sessionStorage.getItem('ng-trace') == 'true';
}

function inject() {
  angular.module('ng').config(['$provide', function($provide) {
    $provide.decorator('$q', ['$delegate', function($delegate) {
      $delegate.when = decorate($delegate);
      return $delegate;
    }]);
  }]);
}

function decorate(q) {
  var _when = q.when;
  return function(v) {
    if (v && v.url && enabled() && !/\.html$/.test(v.url)) {
      console.groupCollapsed(v.method, v.url);
      console.trace('ng-trace');
      console.groupEnd();
    }
    return _when.apply(this, arguments);
  }
}

window.angular  = window.angular || {};
window._ngTrace = false;

Object.observe(window.angular, function(changes){
  if (!window._ngTrace && enabled()) {
    window._ngTrace = true;
    inject();
  }
});

function enabled() {
  return window.sessionStorage.getItem('ng-trace') == 'true';
}

function inject() {
  angular.module('ng').config(['$provide', function($provide) {
    $provide.decorator('$q', ['$delegate', function($delegate) {
      $delegate.when = decorate($delegate);
      return $delegate;
    }]);
  }]);
}

function decorate(q) {
  var _when = q.when;
  return function(v) {
    if (v && v.url && enabled() && !/\.html$/.test(v.url)) {
      console.groupCollapsed(v.method, v.url);
      console.trace('ng-trace');
      console.groupEnd();
    }
    return _when.apply(this, arguments);
  }
}

Create angular only when undefined


var gateway = require(__dirname+'/../../../../');

module.exports = function(req, res){

  gateway.config.set('DOMAIN', req.body.domain);
  res.send({ 'DOMAIN': gateway.config.get('DOMAIN') });

};

var gateway = require(__dirname+'/../../../../');

module.exports = function(req, res){

  gateway.config.set('DOMAIN', req.body.domain);
  gateway.config.save(function(){
    res.send({ 'DOMAIN': gateway.config.get('DOMAIN') });
  });

};

Save domain after updating via http.


const queryString = require('query-string')
const XHR = require('../lib/xhr')

const XhrLink = {
  selector: '.js-xhr',

  init (isGlobal = false) {
    this.isGlobal = isGlobal
    document.addEventListener('click', this.handleClick.bind(this))
  },

  handleClick (evt) {
    const target = evt.target || evt.srcElement
    const url = target.getAttribute('href')

    if (!url || url.match(/^http:\/\//)) { return }
    const path = url.match(/^(.*)\?/)[1]

    const hasXhrClass = target.classList.contains(this.selector.substring(1))
    const shouldXhr = target.tagName === 'A' && this.isGlobal ? true : hasXhrClass
    const params = queryString.parse(url)

    if (!shouldXhr) { return }

    evt.preventDefault()

    XHR.request(path, params)
  },
}

module.exports = XhrLink

const queryString = require('query-string')
const XHR = require('../lib/xhr')

const XhrLink = {
  selector: '.js-xhr',

  init (isGlobal = false) {
    this.isGlobal = isGlobal
    document.addEventListener('click', this.handleClick.bind(this))
  },

  handleClick (evt) {
    const target = evt.target || evt.srcElement
    const url = target.getAttribute('href')
    const hasXhrClass = target.classList.contains(this.selector.substring(1))
    const shouldXhr = target.tagName === 'A' && this.isGlobal ? true : hasXhrClass

    if (!url || url.match(/^http:\/\//)) { return }
    const path = url.replace(/(\?.*)/, '')

    const params = queryString.parse(url)

    if (!shouldXhr) { return }

    evt.preventDefault()

    XHR.request(path, params)
  },
}

module.exports = XhrLink

Fix JS error in XHR link module


Package.describe({
    summary: "Adds basic support for Cordova/Phonegap\n"+
         "\u001b[32mv0.0.8\n"+
  		   "\u001b[33m-----------------------------------------\n"+
  		   "\u001b[0m Adds basic support for Cordova/Phonegap  \n"+
  		   "\u001b[0m shell communication in iframe            \n"+
  		   "\u001b[33m-------------------------------------RaiX\n"
});

Package.on_use(function (api) {
  api.use('ejson', 'client');

  api.add_files('cordova.client.js', 'client');
  api.add_files('cordova.client.notification.js', 'client');

  api.export && api.export('Cordova', 'client');

});

Package.on_test(function(api) {
  api.use('cordova', ['client']);
  api.use('test-helpers', 'client');
  api.use(['tinytest', 'underscore', 'ejson', 'ordered-dict',
           'random', 'deps']);

  api.add_files([
    'plugin/meteor.cordova.js',
    'meteor.cordova.tests.js',
  ], 'client');
});

Package.describe({
    summary: "Adds basic support for Cordova/Phonegap\n"+
         "\u001b[32mv0.0.8\n"+
  		   "\u001b[33m-----------------------------------------\n"+
  		   "\u001b[0m Adds basic support for Cordova/Phonegap  \n"+
  		   "\u001b[0m shell communication in iframe            \n"+
  		   "\u001b[33m-------------------------------------RaiX\n"
});

Package.on_use(function (api) {
  api.use('deps','client');
  api.use('ejson', 'client');

  api.add_files('cordova.client.js', 'client');
  api.add_files('cordova.client.notification.js', 'client');

  api.export && api.export('Cordova', 'client');

});

Package.on_test(function(api) {
  api.use('cordova', ['client']);
  api.use('test-helpers', 'client');
  api.use(['tinytest', 'underscore', 'ejson', 'ordered-dict',
           'random', 'deps']);

  api.add_files([
    'plugin/meteor.cordova.js',
    'meteor.cordova.tests.js',
  ], 'client');
});

Fix startup error in Meteor 0.8.3


"use strict";

var _          = require("underscore"),
    cheerio    = require("cheerio"),
    check      = require("check-types"),
    multimatch = require("multimatch");

function plugin(options) {
  options = normalize(options);

  return function (files, metalsmith, done) {

    var filesTbSanitized = multimatch(Object.keys(files), options.src);

    _.each(filesTbSanitized, function(fileName) {
      var data = files[fileName];
      if (check.string(data.excerpt)) { return; }

      var $ = cheerio.load(data.contents.toString());

      var firstElement = $("p").first();
      data.excerpt = firstElement.html().trim();
    });

    done();
  };
}

function normalize(options) {
  var defaults = {
    src: ["**/*.html"]
  };
  options = _.extend({}, defaults, options);

  return options;
}

module.exports = plugin;

"use strict";

var _          = require("underscore"),
    cheerio    = require("cheerio"),
    check      = require("check-types"),
    multimatch = require("multimatch");

function plugin(options) {
  options = normalize(options);

  return function (files, metalsmith, done) {

    var filesTbSanitized = multimatch(Object.keys(files), options.src);

    _.each(filesTbSanitized, function(fileName) {
      var data = files[fileName];
      if (check.string(data.excerpt)) { return; }

      var $ = cheerio.load(data.contents.toString());

      var firstElement = $("p").first();
      var html = firstElement.html();
      if (html !== null) {
        data.excerpt = html.trim();
      } else {
        data.excerpt = "";
      }
    });

    done();
  };
}

function normalize(options) {
  var defaults = {
    src: ["**/*.html"]
  };
  options = _.extend({}, defaults, options);

  return options;
}

module.exports = plugin;

Check if a paragraph exists in post before getting excerpt.


// Gulp.js configuration
var gulp = require('gulp'),
    watch = require('gulp-watch'),
    postcss = require('gulp-postcss');

// apply PostCSS plugins
gulp.task('css', function() {
  return gulp.src('src/style.min.css')
    .pipe(postcss([
      require('autoprefixer')({}),
      require('cssnano'),
      require('lost'),
      require('postcss-font-magician')({protocol: 'https:'}),
      require('postcss-simple-vars')
    ]))
    .pipe(gulp.dest('dest'));
});

gulp.task('default', ['css']);

gulp.task('watch', function() {
    gulp.watch('src/main.css', ['css'])
});

// Gulp.js configuration
var gulp = require('gulp'),
    watch = require('gulp-watch'),
    postcss = require('gulp-postcss');

// apply PostCSS plugins
gulp.task('css', function() {
  return gulp.src('src/style.min.css')
    .pipe(postcss([
      require('autoprefixer')({}),
      require('cssnano'),
      require('lost'),
      require('postcss-font-magician')({protocol: 'https:'}),
      require('postcss-simple-vars')
    ]))
    .pipe(gulp.dest('dest'));
});

gulp.task('default', ['css', 'watch']);

gulp.task('watch', function() {
    gulp.watch('src/main.css', ['css'])
});

Tweak default gulp action to watch and run css task.


'use strict'

import FB from 'fb'
import { facebookKey } from '../etc/secret.json'

/**
 * Отправить сообщение в Фейсбук
 *
 * @param { string } message - Текст сообщения, которое будет отправлено на
 *                             страницу в Фейсбуке
 */
export default function facebookIt (message) {
    FB.setAccessToken(facebookKey)

    FB.api('me/feed', 'post', { message: message }, response => {
        if (!response || response.error) {
            console.log(!response ? 'Ошибка отправки сообщения в FaceBook'
                : response.error)
        }

        console.log('ID опубликованного сообщения: ' + response.id)
    })
}

'use strict'

import FB from 'fb'
import { facebookKey } from '../etc/secret.json'

/**
 * Отправить сообщение в Фейсбук
 *
 * @param { string } message - Текст сообщения, которое будет отправлено на
 *                             страницу в Фейсбуке
 */
export default async function facebookIt (message, callback) {
    FB.setAccessToken(facebookKey)

    await FB.api('me/feed', 'post', { message: message }, response => {
        if (!response || response.error) {
            console.log(!response ? 'Ошибка отправки сообщения в FaceBook'
                : response.error)
        }

        callback(response.id)
    })
}

Add callback for FaceBook function


'use strict'
var ip = require('iterator-protocol'),
    iterator = ip.iterator

module.exports = iterable
function iterable(nextGen, iter) {
  return new LazyIterable(nextGen, iter)
}

function LazyIterable(gen, iter){
  this.gen = gen
  this.iter = iter
}
LazyIterable.prototype[ip.symbol] = function(){
  var iter = iterator(this.iter),
      values = [],
      result = {},
      nextGen
  nextGen = this.gen(values)
  nextGen.next();
  return {next: function(){
    while(!result.done && !values.length){
      result = nextGen.next(iter.next())
    }
    return !values.length ? result : {done: false, value: values.shift()}
  }};
}

'use strict'
var ip = require('iterator-protocol'),
    iterator = ip.iterator

module.exports = iterable
function iterable(nextGen, iter) {
  return new LazyIterable(nextGen, iter)
}

function LazyIterable(gen, iter){
  this.gen = gen
  this.iter = iter
}
LazyIterable.prototype[ip.symbol] = function(){
  var iter = iterator(this.iter),
      values, result, nextGen
  nextGen = this.gen(values)
  result = nextGen.next();
  return {next: function(){
    while(!result.value.length && !result.done){
      result = nextGen.next(iter.next())
    }
    return !result.value.length ? result : {done: false, value: result.value.shift()}
  }};
}

Simplify iteration with intermediate array result value


/*
 * Developed by Radu Puspana
 * Date August 2017
 * Version 1.0
 */

console.log("welcome to myExpenses!");

/*
 * Developed by Radu Puspana
 * Date August 2017
 * Version 1.0
 */

// module that handles the budget data using the module pattern
// IFEE creates a new scope. IFEE creates a closure for the vars/functions/objects
var budgetController = (function() {

    // some code

})();

// UI module manipulates the UI of the app
var uiController = (function() {

    // some code

})();

// App controller for communicating between the budgetController and the UIconroller
var controller = (function(budgetCtrl, UIctrl) {

    document.querySelector(".add__btn").addEventListener("click", function() {
        console.log("button with .add__btn was clicked");

        // get the field input data(expense or income)
        // add the item to the budget controller
        // add the new item to the UI
        // update the budget taking into account the expense/budget just entered
        // display the newly calculated budget on the UI
    }, false);

    // click event happens on the page, not on a specific elem. Used only for the ENTER key
    document.addEventListener("keypress", function(event) {

    }, false);

})(budgetController, uiController);

Add budgetController , uiController , controller modules and keypress event on the global object


'use strict';

var express = require('express');
var supertest = require('supertest');
var logger = require('log4js').getLogger('Unit-Test');
var nconf = require('nconf');

var request = supertest(app);

exports['Check view health'] = function(test){

	request.get('/sendmessage').end(function(err,res)){

  test.equal(err,null,'It should not had any error!');

	test.equal(res,200,'It should return 200!');

	test.done();

	}
}

exports['Test send message'] = {

	'Test send message sucess':function(test){

		request.post('/sendmessage'),
		.send({'receiver':''})
		.end(function(err,res)){

		  test.equal(err,null,'It should not had any error!')

			test.equal(res,200,'It should return 200!');

		}
	}
}

'use strict';

var express = require('express');
var supertest = require('supertest');
var logger = require('log4js').getLogger('Unit-Test');
var nconf = require('nconf');

var request = supertest(app);

exports['Check view health'] = function(test){

	request.get('/sendmessage').end(function(err,res)){

  test.equal(err,null,'It should not had any error!');

	test.equal(res,200,'It should return 200!');

	test.done();

	}
}

exports['Test send message'] = {

	'Test send message success':function(test){

		request.post('/sendmessage'),
		.send({'receiver':''})
		.end(function(err,res)){

		  test.equal(err,null,'It should not had any error!')

			test.equal(res,201,'It should return 201!');

		}
	}
}

Change success code to 201


import { Readable } from 'stream';

export default class ServerRequestAdapter extends Readable {
  constructor(connection, mpq, headers, body) {
    super({
      objectMode: true
    });

    const [method, url] = mpq.split(' ');
    const socket = connection.socket();

    if (socket.upgradeReq) {
      headers = Object.assign({}, socket.upgradeReq.headers, headers);
    }

    this.connection = connection;
    this.method = method;
    this.url = url;
    this.headers = headers;
    this.body = body;
  }

  _read() {
    this.push(this.body);
    this.body = null;
  }
}

import { Readable } from 'stream';

export default class ServerRequestAdapter extends Readable {
  constructor(connection, mpq, headers, body) {
    super({
      objectMode: true
    });

    const [method, url] = mpq.split(' ');
    const socket = connection.socket();

    if (socket.upgradeReq) {
      headers = Object.assign({}, socket.upgradeReq.headers, headers);
      delete headers.accept;
    }

    this.connection = connection;
    this.method = method;
    this.url = url;
    this.headers = headers;
    this.body = body;
  }

  _read() {
    this.push(this.body);
    this.body = null;
  }
}

Remove Accept header for websockets


App.Controllers.controller("usersController", ["$scope", "Restangular", usersController]);
function usersController($scope, Restangular) {
    Restangular.one("api").all("users").getList().then(function(users) {
        $scope.users = users;
    });
}

App.Controllers.controller("addUserController", ["$scope", "Restangular", addUserController]);
function addUserController($scope, Restangular) {
    $scope.addUser = function() {
        Restangular.one("api").all("users").post({
            fullname: $scope.username,
            email: $scope.email
        }).then(function(user) {
            $scope.users.push(user);
        });
        $scope.username = "";
        $scope.email = "";
    };
}

App.Controllers.controller("usersController",
                           ["$scope", "Restangular", "$timeout", usersController]);
function usersController($scope, Restangular, $timeout) {
    $scope.users = [];
    Restangular.one("api").all("users").getList().then(function(users) {
        users.forEach(function(user) {
            $scope.users.push({email: user.email, fullname: user.fullname});
        });
    });

    $scope.$on("newuser", function(event, user) {
        $timeout(function() {
            $scope.users.push(user);
        });
    });
}

App.Controllers.controller("addUserController",
                           ["$scope", "Restangular", "ws", "$rootScope",
                            addUserController]);
function addUserController($scope, Restangular, ws, $rootScope) {
    var s = ws.get();

    $scope.addUser = function() {
        Restangular.one("api").all("users").post({
            fullname: $scope.username,
            email: $scope.email
        }).then(function(user) {
            s.$emit("adduser", {email: user.email, fullname: user.fullname});
        });
        $scope.username = "";
        $scope.email = "";
    };

    s.$on("adduser", function(user) {
        $rootScope.$broadcast("newuser", user);
    });
}

Switch to getting added user from the websocket broadcast.


import {createSelector} from 'reselect'

import selectActiveRegionalAnalysis from './active-regional-analysis'
import selectRegionalAnalyses from './regional-analyses'

export default createSelector(
  selectActiveRegionalAnalysis,
  selectRegionalAnalyses,
  (analysis = {}, regionalAnalyses) =>
    regionalAnalyses
      // don't compare incomparable analyses
      .filter(
        ({zoom, width, height, north, west}) =>
          zoom === analysis.zoom &&
          width === analysis.width &&
          height === analysis.height &&
          north === analysis.north &&
          west === analysis.west
      )
)

import get from 'lodash/get'
import {createSelector} from 'reselect'

import selectActiveRegionalAnalysis from './active-regional-analysis'
import selectRegionalAnalyses from './regional-analyses'

export default createSelector(
  (state) => get(state, 'regionalAnalyses.activeJobs'),
  selectActiveRegionalAnalysis,
  selectRegionalAnalyses,
  (activeJobs = [], analysis = {}, regionalAnalyses) =>
    regionalAnalyses
      // don't compare incomparable analyses
      .filter(
        ({_id, zoom, width, height, north, west}) =>
          activeJobs.findIndex((job) => job.jobId === _id) === -1 &&
          zoom === analysis.zoom &&
          width === analysis.width &&
          height === analysis.height &&
          north === analysis.north &&
          west === analysis.west
      )
)

HIde analyses from comparison select if there are active jobs for that analysis


Handlebars.registerHelper('color', function(color) {
    var nameText = '';
    var hex = color.hex;
    var colorPickerLink = 'https://duckduckgo.com/?q=color+picker+' +
        encodeURIComponent(hex);
    if (color.name !== '') {
        nameText = ' (' + color.name + ')';
    }
    var href = 'href="' + colorPickerLink + '"';
    var hexBody = '' + hex +
        nameText + '';
    var boxBody = '&#9632';
    var body = '' + hexBody + ' ' + boxBody + '';
    return body;
});

Handlebars.registerHelper('color', function(color) {
    var nameText = '';
    var hex = color.hex;
    var colorPickerLink = 'https://duckduckgo.com/?q=color+picker+' +
        encodeURIComponent(hex);
    if (color.name !== '') {
        nameText = ' (' + color.name + ')';
    }
    var amountText = '';
    if (color.amount !== undefined) {
        var pct = Number(sprintf('%.2f', color.amount * 100)).
            toLocaleString();
        amountText = '(' + pct + '%) ';
    }
    var href = 'href="' + colorPickerLink + '"';
    var hexBody = '' + hex +
        nameText + '';
    var boxBody = '&#9632';
    var body = '' + amountText + hexBody + ' ' + boxBody + '';
    return body;
});

Add mixing percentage to display


/**
 * This class implements authentication against google
 * using the OAuth2 authorization flow in a popup window.
 */

import Oauth2 from 'torii/providers/oauth2-code';
import {configurable} from 'torii/configuration';

var GoogleOauth2 = Oauth2.extend({

  name:       'google-oauth2',
  baseUrl:    'https://accounts.google.com/o/oauth2/auth',

  // additional params that this provider requires
  requiredUrlParams:   ['state'],

  scope:        configurable('scope', 'email'),

  state: 'STATE',

  redirectUri: configurable('redirectUri',
                            'http://localhost:8000/oauth2callback')
});

export default GoogleOauth2;

/**
 * This class implements authentication against google
 * using the OAuth2 authorization flow in a popup window.
 */

import Oauth2 from 'torii/providers/oauth2-code';
import {configurable} from 'torii/configuration';

var GoogleOauth2 = Oauth2.extend({

  name:    'google-oauth2',
  baseUrl: 'https://accounts.google.com/o/oauth2/auth',

  // additional params that this provider requires
  requiredUrlParams: ['state'],
  optionalUrlParams: ['scope', 'request_visible_actions'],

  request_visible_actions: configurable('requestVisibleActions', ''),

  scope: configurable('scope', 'email'),

  state: 'STATE',

  redirectUri: configurable('redirectUri',
                            'http://localhost:8000/oauth2callback')
});

export default GoogleOauth2;

Allow additional parameter `request_visible_actions` for Google OAuth2 provider


/*
 * Grunt Remove Logging
 * https://github.com/ehynds/grunt-remove-logging
 *
 * Copyright (c) 2013 Eric Hynds
 * Licensed under the MIT license.
 */

module.exports = function(grunt) {
  "use strict";

  var task = require("./lib/removelogging").init(grunt);

  grunt.registerMultiTask("removelogging", "Remove console logging", function() {
    var opts = this.options();

    this.files.forEach(function(f) {
      var ret = f.src.map(function(srcFile) {
        if (grunt.file.isFile(srcFile)){
          var result = task(grunt.file.read(srcFile), opts);
          grunt.log.writeln("Removed " + result.count + " logging statements from " + srcFile);
          return result.src;
        }else{
          grunt.log.error("File not found " + srcFile);
        }
      }).join("");

      if(ret){
        grunt.file.write(f.dest, ret);
      }
    });
  });
};

/*
 * Grunt Remove Logging
 * https://github.com/ehynds/grunt-remove-logging
 *
 * Copyright (c) 2013 Eric Hynds
 * Licensed under the MIT license.
 */

module.exports = function(grunt) {
  "use strict";

  var task = require("./lib/removelogging").init(grunt);

  grunt.registerMultiTask("removelogging", "Remove console logging", function() {
    var opts = this.options();

    this.files.forEach(function(f) {
      if (typeof f.dest === "undefined") {
        f.src.forEach(function (srcFile) {
          var result = task(grunt.file.read(srcFile), opts);
          grunt.log.writeln("Removed " + result.count + " logging statements from " + srcFile + ". (File Overwritten)");
          grunt.file.write(srcFile, ret);
        });
      } else {
        var ret = f.src.map(function(srcFile) {
          if (grunt.file.isFile(srcFile)){
            var result = task(grunt.file.read(srcFile), opts);
            grunt.log.writeln("Removed " + result.count + " logging statements from " + srcFile);
            return result.src;
          }else{
            grunt.log.error("File not found " + srcFile);
          }
        }).join("");

        if(ret){
          grunt.file.write(f.dest, ret);
        }
      }
    });
  });
};

Change to allow file overwrites


import React from 'react';

import SaveTripModal from './save-trip-modal.js'

/**
 * Component corresponding to add trips modal.
 *
 * @param {Object} props These are the props for this component:
 * - db: Firestore database instance.
 * - show: Boolean that determines if the add trips modal should be displayed.
 * - handleClose: The function that handles closing the add trips modal.
 * - refreshTripsContainer: Function that handles refreshing the TripsContainer
 *        component upon trip creation (Remove when fix Issue #62).
 * - key: Special React attribute that ensures a new AddTripModal instance is
 *        created whenever this key is updated
 *
 * @extends React.Component
 */
const AddTripModal = (props) => {
  const placeholderObj =
  {
    name:               'Enter Trip Name',
    description:        'Enter Trip Description',
    destination:        'Enter Trip Destination',
    startDate:         '',
    endDate:           '',
    collaborators:      'person@email.xyz'
  };

  return (
    <SaveTripModal
      db={props.db}
      show={props.show}
      handleClose={props.handleClose}
      refreshTripsContainer={props.refreshTripsContainer}
      key={props.key}
      placeholderObj={placeholderObj}
    />
  );
};

export default AddTripModal;

import React from 'react';

import SaveTripModal from './save-trip-modal.js'

/**
 * Component corresponding to add trips modal.
 *
 * @param {Object} props These are the props for this component:
 * - db: Firestore database instance.
 * - show: Boolean that determines if the add trips modal should be displayed.
 * - handleClose: The function that handles closing the add trips modal.
 * - refreshTripsContainer: Function that handles refreshing the TripsContainer
 *        component upon trip creation (Remove when fix Issue #62).
 * - key: Special React attribute that ensures a new AddTripModal instance is
 *        created whenever this key is updated
 *
 * @extends React.Component
 */
const AddTripModal = (props) => {
  const placeholderObj =
  {
    name:               'Enter Trip Name',
    description:        'Enter Trip Description',
    destination:        'Enter Trip Destination',
    startDate:         '',
    endDate:           '',
    collaborators:      ['person@email.xyz']
  };

  return (
    <SaveTripModal
      db={props.db}
      show={props.show}
      handleClose={props.handleClose}
      refreshTripsContainer={props.refreshTripsContainer}
      key={props.key}
      placeholderObj={placeholderObj}
    />
  );
};

export default AddTripModal;

Fix collaborators field of placeholderObj to be an array.


var MethodProxy = function(object, queue) {
  this.init = function(object, queue) {
    this.object = object;

    for (var i = 0; i < queue.length; ++i) {
      this.forward(queue[i]);
    }
  };

  // payload : ['methodName', arguments*]
  this.push = this.forward = function(payload) {
    var methodName = payload.shift().split('.'),
        object     = this.object,
        method;
    while (methodName.length) {
      // dig into the object as many levels as needed (e.g. `FB.XFBML.parse`)
      if (method) {
        object = object[method];
      }
      method = methodName.shift();
    }
    return object[method].apply(object, payload);
  };

  this.init(object, queue);
};

var MethodProxy = function(object, queue) {
  this.init = function(object, queue) {
    this.object = object;

    for (var i = 0, len = queue.length; i < len; ++i) {
      this.forward(queue[i]);
    }
  };

  // payload : ['methodName', arguments*]
  this.push = this.forward = function(payload) {
    var methodName = payload.shift().split('.'),
        object     = this.object,
        method;
    while (methodName.length) {
      // dig into the object as many levels as needed (e.g. `FB.XFBML.parse`)
      if (method) {
        object = object[method];
      }
      method = methodName.shift();
    }
    return object[method].apply(object, payload);
  };

  this.init(object, queue);
};

Improve efficiency of queue loop


module.exports = function(config) {
    var commonConfig = (require("./karma-common.conf"))(config);
    var customLaunchers = {
        sl_chrome: {
            base: "SauceLabs",
            browserName: "chrome",
            platform: "Windows 8.1"
        },
        sl_firefox: {
            base: "SauceLabs",
            browserName: "firefox",
            platform: "Windows 8.1",
            version: "beta"
        },
        sl_safari: {
            base: "SauceLabs",
            browserName: "safari",
            platform: "OS X 10.10"
        },
        sl_ie_11: {
            base: "SauceLabs",
            browserName: "internet explorer",
            platform: "Windows 8.1"
        }
    };

    var options = {
        reporters: ["verbose", "saucelabs"],
        sauceLabs: {
            testName: "Vivliostyle.js",
            recordScreenshots: false,
            startConnect: false, // Sauce Connect is started by Travis CI
            tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER
        },
        captureTimeout: 120000,
        customLaunchers: customLaunchers,
        browsers: Object.keys(customLaunchers),
        singleRun: true
    };
    for (var key in commonConfig) {
        if (commonConfig.hasOwnProperty(key)) {
            options[key] = commonConfig[key];
        }
    }

    config.set(options);
};

module.exports = function(config) {
    var commonConfig = (require("./karma-common.conf"))(config);
    var customLaunchers = {
        sl_chrome: {
            base: "SauceLabs",
            browserName: "chrome",
            platform: "Windows 8.1"
        },
        sl_firefox: {
            base: "SauceLabs",
            browserName: "firefox",
            platform: "Windows 8.1",
            version: "dev"
        },
        sl_safari: {
            base: "SauceLabs",
            browserName: "safari",
            platform: "OS X 10.10"
        },
        sl_ie_11: {
            base: "SauceLabs",
            browserName: "internet explorer",
            platform: "Windows 8.1"
        }
    };

    var options = {
        reporters: ["verbose", "saucelabs"],
        sauceLabs: {
            testName: "Vivliostyle.js",
            recordScreenshots: false,
            startConnect: false, // Sauce Connect is started by Travis CI
            tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER
        },
        captureTimeout: 120000,
        customLaunchers: customLaunchers,
        browsers: Object.keys(customLaunchers),
        singleRun: true
    };
    for (var key in commonConfig) {
        if (commonConfig.hasOwnProperty(key)) {
            options[key] = commonConfig[key];
        }
    }

    config.set(options);
};

Use dev version of Firefox (supports vertical writing-mode) in tests on Sauce Labs


  var _paq = window._paq = window._paq || [];
  /* tracker methods like "setCustomDimension" should be called before "trackPageView" */
  _paq.push(["setDomains", ["*.ksi.fi.muni.cz"]]);
  _paq.push(["setDoNotTrack", true]);
  _paq.push(["disableCookies"]);
  _paq.push(['trackPageView']);
  _paq.push(['enableLinkTracking']);
  (function() {
    var u="https://fadmin.fi.muni.cz/piwik/";
    _paq.push(['setTrackerUrl', u+'matomo.php']);
    _paq.push(['setSiteId', '46']);
    var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
    g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
  })();

//window.onpopstate = function(event) { // this didn't work reliably
function trackNewPage(){
    _paq.push(['setReferrerUrl', location.href]);
    _paq.push(['setCustomUrl', window.location.pathname]);
    _paq.push(['setDocumentTitle', document.title]);

    // remove all previously assigned custom variables, requires Matomo (formerly Piwik) 3.0.2
    _paq.push(['deleteCustomVariables', 'page']);
    _paq.push(['trackPageView']);

    // make Matomo aware of newly added content
    var content = document.getElementById('content');
    // _paq.push(['MediaAnalytics::scanForMedia', content]);
    // _paq.push(['FormAnalytics::scanForForms', content]);
    _paq.push(['trackContentImpressionsWithinNode', content]);
    _paq.push(['enableLinkTracking']);
};

// modified https://stackoverflow.com/a/4585031
// this works, but document.title is not yet updated at this point

// modified https://stackoverflow.com/a/29540461
new MutationObserver(function(mutations) {
    // console.log(document.title);
    trackNewPage();
}).observe(
    document.querySelector('title'),
    { subtree: true, characterData: true, childList: true }
);

Improve tracking code to track movement across the site


'use strict'

const { waterfall } = require('async')
const { defaults } = require('lodash')

const checkHostsAvailability = require('../util/check-hosts-availability')
const checkRequiredParams = require('../util/check-required-params')
const createWorkerFromProvider = require('../provider')
const CONST = require('./constants')
const DEFAULT = require('./defaults')

function createWorker (opts) {
  opts = defaults(opts, DEFAULT)
  checkRequiredParams(opts, CONST.REQUIRED_PARAMS)

  const { provider, checkHosts, log } = opts
  const worker = createWorkerFromProvider[provider](opts)

  const tasks = [
    function ping (next) {
      if (!checkHosts) return next()
      const hosts = CONST.CHECK_HOSTS[provider]
      log.info('hosts reachability')
      return checkHostsAvailability(hosts, next)
    },
    function insert (next) {
      return worker(next)
    }
  ]

  function execWorker (cb) {
    waterfall(tasks, function (err, stats) {
      if (!err) {
        log.info('finished', stats)
        return cb(err)
      }
    })
  }

  execWorker.log = log

  return execWorker
}

module.exports = createWorker

'use strict'

const { waterfall } = require('async')
const { defaults } = require('lodash')

const checkHostsAvailability = require('../util/check-hosts-availability')
const checkRequiredParams = require('../util/check-required-params')
const createWorkerFromProvider = require('../provider')
const CONST = require('./constants')
const DEFAULT = require('./defaults')

function createWorker (opts) {
  opts = defaults(opts, DEFAULT)
  checkRequiredParams(opts, CONST.REQUIRED_PARAMS)

  const { provider, checkHosts, log } = opts
  const worker = createWorkerFromProvider[provider](opts)

  const tasks = [
    function ping (next) {
      if (!checkHosts) return next()
      const hosts = CONST.CHECK_HOSTS[provider]
      log.info('hosts reachability')
      return checkHostsAvailability(hosts, next)
    },
    function insert (next) {
      return worker(next)
    }
  ]

  function execWorker (cb) {
    waterfall(tasks, function (err, stats) {
      if (!err) log.info('finished', stats)
      return cb(err)
    })
  }

  execWorker.log = log

  return execWorker
}

module.exports = createWorker

Fix pass callback on error


/* global describe, it, beforeEach */

const Brave = require('../lib/brave')
const {urlInput, homepageInput} = require('../lib/selectors')

const prefsUrl = 'about:preferences'

function * setup (client) {
  yield client
    .waitForUrl(Brave.newTabUrl)
    .waitForBrowserWindow()
    .waitForVisible(urlInput)
}

describe('General Panel', function () {
  describe('homepage', function () {
    Brave.beforeEach(this)
    beforeEach(function * () {
      yield setup(this.app.client)
    })

    it('homepage displays punycode', function * () {
      yield this.app.client
        .tabByIndex(0)
        .loadUrl(prefsUrl)
        .waitForVisible(homepageInput)
        .click(homepageInput)
        .keys('а')
        .waitUntil(function () {
          return this.getValue(homepageInput).then((val) => {
            return val === 'https://www.brave.xn--com-7cd'
          })
        })
    })
  })
})

/* global describe, it, beforeEach */

const Brave = require('../lib/brave')
const {urlInput, homepageInput} = require('../lib/selectors')

const prefsUrl = 'about:preferences'

function * setup (client) {
  yield client
    .waitForUrl(Brave.newTabUrl)
    .waitForBrowserWindow()
    .waitForVisible(urlInput)
}

describe('General Panel', function () {
  describe('homepage', function () {
    Brave.beforeEach(this)
    beforeEach(function * () {
      yield setup(this.app.client)
    })

    it.skip('homepage displays punycode', function * () {
      yield this.app.client
        .tabByIndex(0)
        .loadUrl(prefsUrl)
        .waitForVisible(homepageInput)
        .click(homepageInput)
        .keys('а')
        .waitUntil(function () {
          return this.getValue(homepageInput).then((val) => {
            return val === 'https://www.brave.xn--com-7cd'
          })
        })
    })
  })
})

Disable punycode until it will work on Travis


/* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
ABOUT THIS NODE.JS EXAMPLE: This example works with AWS SDK for JavaScript version 3 (v3),
which is available at https://github.com/aws/aws-sdk-js-v3.

Purpose:
codeBuildClient.js is a helper function that creates the Amazon CodeBuild service clients.

Inputs (replace in code):
- REGION

*/
// snippet-start:[code-build.JavaScript.codeBuildClient]
import { CodeBuildClient } from "@aws-sdk/client-codebuild";

const REGION = "eu-west-1";

// Create an AWS CodeBuild service client object.
const codeBuildClient = new CodeBuildClient({region: REGION});

export { codeBuildClient };
// snippet-end:[code-build.JavaScript.codeBuildClient]

/* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
ABOUT THIS NODE.JS EXAMPLE: This example works with AWS SDK for JavaScript version 3 (v3),
which is available at https://github.com/aws/aws-sdk-js-v3.

Purpose:
codeBuildClient.js is a helper function that creates the Amazon CodeBuild service clients.

Inputs (replace in code):
- REGION

*/
// snippet-start:[code-build.JavaScript.codeBuildClient]
import { CodeBuildClient } from "@aws-sdk/client-codebuild";

const REGION = "REGION";

// Create an AWS CodeBuild service client object.
const codeBuildClient = new CodeBuildClient({region: REGION});

export { codeBuildClient };
// snippet-end:[code-build.JavaScript.codeBuildClient]

Add AWS CodeBuild examples (AWS SDK for JS v3)


/**
 * Created by Liam Vovk on 2017-05-14.
 */

module.exports.users = [
  {
    name: 'Liam TestDB',
    email: 'liam_is_swag@test.com',
    phone_number: 1234567,
    age: 21,
    account_type: 'contractor'
  },
  {
    name: 'Liam TestDB',
    email: 'liam_is_swag@test.com',
    phone_number: 1234567,
    age: 21,
    account_type: 'contractor'
  },
  {
    name: 'Liam TestDB',
    email: 'liam_is_swag@test.com',
    phone_number: 1234567,
    age: 21,
    account_type: 'contractor'
  }
]

module.exports.invalidUsers = [
  {
    noName: {
      name: 'Liam TestDB',
      email: 'liam_is_swag@test.com',
      phone_number: 1234567,
      age: 21,
      account_type: 'contractor'
    },
    noEmail: {
      name: 'Liam TestDB',
      email: 'liam_is_swag@test.com',
      phone_number: 1234567,
      age: 21,
      account_type: 'contractor'
    },
    noAccountType: {
      name: 'Liam TestDB',
      email: 'liam_is_swag@test.com',
      phone_number: 1234567,
      age: 21,
      account_type: 'contractor'
    },
    invalidAccountType: {
      name: 'Liam TestDB',
      email: 'liam_is_swag@test.com',
      phone_number: 1234567,
      age: 21,
      account_type: 'invalid'
    }
  }
]

Create invalid user objects in fixtures


'use strict';
const plugins = require('../collections-online/plugins');

module.exports.register = () => {
  plugins.register(require('./motif-tagging'));
  plugins.register(require('./geo-tagging'));
  plugins.register(require('./users'));
  plugins.register(require('./stats-pre-renderer'));
  require('../collections-online-cumulus').registerPlugins();
  require('../collections-online').registerPlugins();
};

'use strict';
const plugins = require('../collections-online/plugins');

module.exports.register = () => {
  plugins.register(require('./users'));
  require('../collections-online').registerPlugins();
  plugins.register(require('./motif-tagging'));
  plugins.register(require('./geo-tagging'));
  plugins.register(require('./stats-pre-renderer'));
  require('../collections-online-cumulus').registerPlugins();
};

Move plugin load order around to ensure required plugins exist at the right time


function makeData() {
  "use strict";

  return makeRandomData(50);
}

function run(div, data, Plottable) {
  "use strict";

  var svg = div.append("svg").attr("height", 500);

  var xScale = new Plottable.Scale.Linear().tickGenerator(Plottable.TickGenerators.intervalTickGenerator(0.2));
  var yScale = new Plottable.Scale.Linear();
  var xAxis = new Plottable.Axis.Numeric(xScale, "bottom");
  var yAxis = new Plottable.Axis.Numeric(yScale, "left");
  var titleLabel = new Plottable.Component.TitleLabel("Ticks in Increments of 0.2");

  var plot = new Plottable.Plot.Line(xScale, yScale);
    plot.addDataset(data);

  var chart = new Plottable.Component.Table([
                                            [null, titleLabel],
                                            [yAxis, plot],
                                            [null, xAxis]
                                            ]);
  chart.renderTo(svg);
}

function makeData() {
  "use strict";

  return makeRandomData(50);
}

function run(div, data, Plottable) {
  "use strict";

  var svg = div.append("svg").attr("height", 500);

  var xScale = new Plottable.Scale.Linear().tickGenerator(Plottable.Scale.TickGenerators.intervalTickGenerator(0.2));
  var yScale = new Plottable.Scale.Linear();
  var xAxis = new Plottable.Axis.Numeric(xScale, "bottom");
  var yAxis = new Plottable.Axis.Numeric(yScale, "left");
  var titleLabel = new Plottable.Component.TitleLabel("Ticks in Increments of 0.2");

  var plot = new Plottable.Plot.Line(xScale, yScale);
    plot.addDataset(data);

  var chart = new Plottable.Component.Table([
                                            [null, titleLabel],
                                            [yAxis, plot],
                                            [null, xAxis]
                                            ]);
  chart.renderTo(svg);
}

Fix namespace in tick generator quick test.


/* eslint-env mocha */
'use strict'

const _ = require('lodash')
const expect = require('chai').expect
const LodashWrapper = require('../client/wrappers/Lodash')
const TEST_DATA = require('./fixtures')

describe('wrapper: Lodash ()', function () {
  it('SHOULD return expected lodash transform', function () {
    const wrapper = new LodashWrapper(_)

    const result = wrapper.lodash(TEST_DATA)
    .map(city => wrapper.lodash.assign(
      {},
      city,
      {avg: wrapper.lodash.sum(city.temperatures) / wrapper.lodash.size(city.temperatures)}
    ))
    .reject(city => city.avg < 12.5)
    .sortBy()
    .map('city')
    .first()

    expect(result).to.be.equal('Rybnik')
  })
})

/* eslint-env mocha */
'use strict'

const _ = require('lodash')
const expect = require('chai').expect
const LodashWrapper = require('../client/wrappers/Lodash')
const TEST_DATA = require('./fixtures')

describe('wrapper: Lodash ()', function () {
  it('SHOULD return single step for _.map', function () {
  it('SHOULD return single step stats for _.map', function () {
  it('SHOULD record single step stats for _.map invocation', function () {
    const wrapper = new LodashWrapper(_)
    const data = [{city: 'Rybnik'}]

    wrapper.lodash.map(data, 'city')

    expect(wrapper.stats)
      .to.be.eql([{
        step: 1,
        funcName: 'map',
        isChained: false,
        result: ['Rybnik'],
        args: [[{city: 'Rybnik'}], 'city']
      }])
  })

  it('SHOULD return expected lodash transform', function () {
    const wrapper = new LodashWrapper(_)

    const result = wrapper.lodash(TEST_DATA)
    .map(city => wrapper.lodash.assign(
      {},
      city,
      {avg: wrapper.lodash.sum(city.temperatures) / wrapper.lodash.size(city.temperatures)}
    ))
    .reject(city => city.avg < 12.5)
    .sortBy()
    .map('city')
    .first()

    expect(result).to.be.equal('Rybnik')
  })
})

Cover single method invocation stats


'use strict';

const PORT = 23526;
const restify = require('restify');
const shortid = require('shortid');
const uuid = require('uuid');
const RetroSocket = require('./retroSocket');
// const messages = require('./messages');

const server = restify.createServer({
  name: 'Restrospectives'
});
const io = require('socket.io')(server.server);

io.on('connection', socket => {
  socket.on('create retro', msg => {
    const channel = shortid.generate();
    const secret = uuid.v4();
    const retro = new RetroSocket(channel, secret);

    retro.createChannel(io);
    socket.emit('join channel', { channel, secret });

    console.log(msg);
  });
});

server.get('/.*', restify.serveStatic({
  directory: 'web/bin',
  default: 'index.html'
}));

server.listen(PORT, () => {
  console.log(`Listening on port ${PORT}`);
});

'use strict';

const PORT = process.env.PORT || 23526;
const restify = require('restify');
const shortid = require('shortid');
const uuid = require('uuid');
const RetroSocket = require('./retroSocket');
// const messages = require('./messages');

const server = restify.createServer({
  name: 'Restrospectives'
});
const io = require('socket.io')(server.server);

io.on('connection', socket => {
  socket.on('create retro', msg => {
    const channel = shortid.generate();
    const secret = uuid.v4();
    const retro = new RetroSocket(channel, secret);

    retro.createChannel(io);
    socket.emit('join channel', { channel, secret });

    console.log(msg);
  });
});

server.get('/.*', restify.serveStatic({
  directory: 'web/bin',
  default: 'index.html'
}));

server.listen(PORT, () => {
  console.log(`Listening on port ${PORT}`);
});

Use environment-configured port if available


export default class MainView {
  mount() {
    //  This will be executed when the document loads...
    document.getElementsByClassName("logout-link")[0].onclick = () => {
      let xhttp = new XMLHttpRequest();
      xhttp.open("DELETE","/logout",true);
      xhttp.send();
    };
  }
}

export default class MainView {
  mount() {
    //  This will be executed when the document loads...
    let logout_link = document.getElementsByClassName("logout-link")
    if (logout_link.length > 0) {
      logout_link[0].onclick = () => {
        let xhttp = new XMLHttpRequest();
        xhttp.open("DELETE","/logout",false);
        xhttp.send();
        window.location.href = '/'
      };
    };
  }
}

Fix JS errors on pages without logout link also redirect after logging out


import {
  LOGIN_SUCCESS,
  LOGIN_FAILURE,
  LOGOUT_SUCCESS,
  LOGOUT_FAILURE
} from '../constants/actions/Auth';

const fakeUser = {
  username: 'demo',
  token: 'ojn2jr3wrefj'
};

export function login(username, password) {
  return async (dispatch) => {
    try {

      if (username !== 'demo' && password !== 'demo') {
        throw new Error('Bad credentials');
      }

      dispatch({
        type: LOGIN_SUCCESS,
        result: fakeUser
      });
    } catch (error) {
      dispatch({
        type: LOGIN_FAILURE,
        error: error.message
      });
    }
  };
}

export function logout() {
  return async (dispatch) => {
    try {
      dispatch({
        type: LOGIN_SUCCESS
      });
    } catch (error) {
      dispatch({
        type: LOGOUT_FAILURE,
        error: error.message
      });
    }
  };
}

import {
  LOGIN_SUCCESS,
  LOGIN_FAILURE,
  LOGOUT_SUCCESS,
  LOGOUT_FAILURE
} from '../constants/actions/Auth';

const fakeUser = {
  username: 'demo',
  token: 'ojn2jr3wrefj'
};

export function login(username, password) {
  return async (dispatch) => {
    try {

      if (username !== 'demo' && password !== 'demo') {
        throw new Error('Bad credentials');
      }

      dispatch({
        type: LOGIN_SUCCESS,
        result: fakeUser
      });
    } catch (error) {
      dispatch({
        type: LOGIN_FAILURE,
        error: error.message
      });
    }
  };
}

export function logout() {
  return async (dispatch) => {
    try {
      dispatch({
        type: LOGOUT_SUCCESS
      });
    } catch (error) {
      dispatch({
        type: LOGOUT_FAILURE,
        error: error.message
      });
    }
  };
}

Fix logout state name in actions


  describe('slider tests', function () {

    it('Should have MaterialSlider globally available', function () {
      expect(MaterialSlider).to.be.a('function');
    });

    it('Should be upgraded to a MaterialSlider successfully', function () {
      var el = document.createElement('input'),
        parent = document.createElement('div');
      el.type = 'range';
      parent.appendChild(el);

      componentHandler.upgradeElement(el, 'MaterialSlider');
      var upgraded = el.getAttribute('data-upgraded');
      expect(upgraded).to.contain('MaterialSlider');
    });
  });

  describe('slider tests', function () {

    it('Should have MaterialSlider globally available', function () {
      expect(MaterialSlider).to.be.a('function');
    });

    it('Should be upgraded to a MaterialSlider successfully', function () {
      var el = document.createElement('input'),
        parent = document.createElement('div');
      el.type = 'range';
      parent.appendChild(el);

      componentHandler.upgradeElement(el, 'MaterialSlider');
      expect($(el)).to.have.data('upgraded', ',MaterialSlider');
    });
  });

Update MaterialSlider unit test with chai-jquery


var FFI     = require("./ffi"),
    util    = require("util");

// CIF proves a JS interface for the libffi "callback info" (CIF) structure
var CIF = module.exports = function(rtype, types) {
    this._returnType    = rtype;
    this._types         = types;

    if (!FFI.isValidReturnType(this._returnType)) {
        throw new Error("Invalid Return Type");
    }

    this._argtypesptr   = new FFI.Pointer(types.length * FFI.Bindings.FFI_TYPE_SIZE);
    this._rtypeptr      = FFI.Bindings.FFI_TYPES[this._returnType];

    var tptr = this._argtypesptr.seek(0);

    for (var i = 0, len = types.length; i < len; i++) {
        var typeName = types[i];

        if (FFI.isValidParamType(typeName)) {
            tptr.putPointer(FFI.ffiTypeFor(typeName), true);
        }
        else {
            throw new Error("Invalid Type: " + types[i]);
        }
    }

    this._cifptr = FFI.Bindings.prepCif(types.length, this._rtypeptr, this._argtypesptr);
};

CIF.prototype.getArgTypesPointer    = function() { return this._argtypesptr; }
CIF.prototype.getReturnTypePointer  = function() { return this._rtypeptr; }
CIF.prototype.getPointer            = function() { return this._cifptr; }

var FFI     = require("./ffi"),
    util    = require("util");

// CIF proves a JS interface for the libffi "callback info" (CIF) structure
var CIF = module.exports = function(rtype, types) {
    this._returnType    = rtype;
    this._types         = types;

    if (!FFI.isValidReturnType(this._returnType)) {
        throw new Error("Invalid Return Type");
    }

    this._argtypesptr   = new FFI.Pointer(types.length * FFI.Bindings.FFI_TYPE_SIZE);
    this._rtypeptr      = FFI.ffiTypeFor(this._returnType);

    var tptr = this._argtypesptr.seek(0);

    for (var i = 0, len = types.length; i < len; i++) {
        var typeName = types[i];

        if (FFI.isValidParamType(typeName)) {
            tptr.putPointer(FFI.ffiTypeFor(typeName), true);
        }
        else {
            throw new Error("Invalid Type: " + types[i]);
        }
    }

    this._cifptr = FFI.Bindings.prepCif(types.length, this._rtypeptr, this._argtypesptr);
};

CIF.prototype.getArgTypesPointer    = function() { return this._argtypesptr; }
CIF.prototype.getReturnTypePointer  = function() { return this._rtypeptr; }
CIF.prototype.getPointer            = function() { return this._cifptr; }

Make it so that Struct Pointer return values work.


'use strict';

var path        = require('path');
var merge       = require('lodash.merge');
var configUtils = require('./utils/config-utils');
var Config      = require('./config');

var getCurrentPath       = configUtils.getCurrentPath;
var getUserHomeDirectory = configUtils.getUserHomeDirectory;

function Yam(id, options) {
  if (!id) { throw new Error('You must specify an id.'); }

  var projectPath  = getCurrentPath();
  var homePath     = getUserHomeDirectory();
  var localOptions = {};

  var localHomePath   = homePath + '/.' + id;
  var localProjectPath = projectPath + '/.' + id;

  if (options && options.primary) {
    localProjectPath = options.primary + '/.' + id;
  }

  if (options && options.secondary) {
    localHomePath = options.secondary + '/.' + id;
  }

  localOptions = {
    primary:   path.normalize(localProjectPath),
    secondary: path.normalize(localHomePath)
  };

  this.id = id;

  this.options = merge(
    new Config(localOptions.secondary),
    new Config(localOptions.primary)
  );

  return this;
}

Yam.prototype.getAll = function getAll() {
  return this.options;
};

Yam.prototype.get = function get(key) {
  var value = this.options[key];

  return value ? value : undefined;
};

module.exports = Yam;

'use strict';

function Yam(id, options) {
  if (!id) { throw new Error('You must specify an id.'); }

  var path = require('path');
  var merge = require('lodash.merge');
  var Config = require('./config');
  var configUtils = require('./utils/config-utils');
  var projectPath  = configUtils.getCurrentPath();
  var homePath     = configUtils.getUserHomeDirectory();
  var localOptions = {};

  var localHomePath   = homePath + '/.' + id;
  var localProjectPath = projectPath + '/.' + id;

  if (options && options.primary) {
    localProjectPath = options.primary + '/.' + id;
  }

  if (options && options.secondary) {
    localHomePath = options.secondary + '/.' + id;
  }

  localOptions = {
    primary:   path.normalize(localProjectPath),
    secondary: path.normalize(localHomePath)
  };

  this.id = id;

  this.options = merge(
    new Config(localOptions.secondary),
    new Config(localOptions.primary)
  );

  return this;
}

Yam.prototype.getAll = function getAll() {
  return this.options;
};

Yam.prototype.get = function get(key) {
  var value = this.options[key];

  return value ? value : undefined;
};

module.exports = Yam;

Make sure dependency are loaded on demand


var connect = require('connect'),
  port = process.env.PORT || 3000;

connect()
  .use(connect.static('app'))
  .listen(port);

console.log('Application listening on port ' + port);

var connect = require('connect'),
  fs = require('fs'),
  port = process.env.PORT || 3000;

connect()
  .use(connect.static('app'))
  .use(function(req, res) {
    var stream = fs.createReadStream(__dirname + '/app/index.html');
    stream.pipe(res);
  })
  .listen(port);

console.log('Application listening on port ' + port);

Send root page for all non-static files (let Backbone handle routing)


console.log("Hello!");

let div = document.createElement('div');
let h1 = document.createElement('h1');
h1.textContent = "Helloooo!";

div.appendChild(h1);
document.body.appendChild(div);

Add some elements to the DOM.


var shell = require('shelljs');

if (exec('git status --porcelain').output != '') {
  console.error('Git working directory not clean.');
  process.exit(2);
}

var versionIncrement = process.argv[process.argv.length -1];

if (versionIncrement != 'major' && versionIncrement != 'minor' && versionIncrement != 'patch') {
  console.error('Usage: node release.js major|minor|patch');
  process.exit(1);
}

exec('npm version ' + versionIncrement);

exec('npm test');
exec('git push');
exec('git push --tags');
exec('npm publish');

function exec(cmd) {
  var ret = shell.exec(cmd, { silent : true });

  if (ret.code != 0) {
    console.error(ret.output);
    process.exit(1);
  }

  return ret;
}

var shell = require('shelljs');

if (exec('git status --porcelain').output) {
  console.error('Git working directory not clean.');
  process.exit(2);
}

var versionIncrement = process.argv[process.argv.length -1];

if (versionIncrement != 'major' && versionIncrement != 'minor' && versionIncrement != 'patch') {
  console.error('Usage: node release.js major|minor|patch');
  process.exit(1);
}

exec('npm version ' + versionIncrement);

exec('npm test');
exec('git push');
exec('git push --tags');
exec('npm publish');

function exec(cmd) {
  var ret = shell.exec(cmd, { silent : true });

  if (ret.code != 0) {
    console.error(ret.output);
    process.exit(1);
  }

  return ret;
}

Fix output handling in shelljs 0.7


function generateTree(dataArr, root) {
    let subTree, directChildren, subDataArr;
    let { name, id, articlesCount } = root;
    if (dataArr.length === 0) {
        return {
            id,
            name,
            articlesCount,
            subCategories: []
        };
    }
    directChildren = dataArr.filter((node) => { return node.parentId === root.id; });
    subDataArr = dataArr.filter((node) => { return node.parentId !== root.id; });
    subTree = directChildren.map((node) => {
        return generateTree(subDataArr, node);
    });
    return {
        id,
        name,
        articlesCount,
        subCategories: subTree
    };
}

function transformToTree(dataArr) {
    let root = dataArr.filter((node) => { return !node.parentId; })[0],
        rest = dataArr.filter((node) => { return node.parentId; });
    return generateTree(rest, root);
}

export { transformToTree };

function generateTree(dataArr, root) {
    let subTree, directChildren, subDataArr;
    let { name, id, articlesCount } = root;
    if (Object.keys(root).length === 0) {
        return {};
    }
    if (dataArr.length === 0) {
        return {
            id,
            name,
            articlesCount,
            subCategories: []
        };
    }
    directChildren = dataArr.filter((node) => { return node.parentId === root.id; });
    subDataArr = dataArr.filter((node) => { return node.parentId !== root.id; });
    subTree = directChildren.map((node) => {
        return generateTree(subDataArr, node);
    });
    return {
        id,
        name,
        articlesCount,
        subCategories: subTree
    };
}

function transformToTree(dataArr) {
    let root = dataArr.filter((node) => { return !node.parentId; })[0],
        rest = dataArr.filter((node) => { return node.parentId; });
    return generateTree(rest, root);
}

export { transformToTree };

Enhance generateTree function by adding edge condition for root


const html = require('choo/html');
const map = require('./map');
const { loader } = require('../icons');
const { __ } = require('../../locale');

module.exports = function (state, emit) {
  let center;
  const { cooperatives, user, geoip } = state;

  if (user.isAuthenticated) {
    const home = cooperatives.find(item => item._id === user.cooperative);
    center = home && { longitude: home.lng, latitude: home.lat };
  }

  if ((!center || geoip.precission === 'exact') && geoip.longitude) {
    center = {
      longitude: geoip.longitude,
      latitude: geoip.latitude,
      isLoading: geoip.isLoading,
      precission: geoip.precission
    };
  }

  if (typeof window !== 'undefined' && !center) {
    emit('geoip:fetch');
  }

  return html`

      ${ center ? map(state.cooperatives.slice(), center) : html`

          ${ loader() }

      ` }

        <button class="Button Button--round u-textS" onclick=${ () => emit('geoip:getPosition') } disabled=${ !!geoip.isLoading }>
          ${ __('Find me') }
        </button>

  `;
};

const html = require('choo/html');
const map = require('./map');
const { loader } = require('../icons');
const { __ } = require('../../locale');

module.exports = function (state, emit) {
  let center;
  const { cooperatives, user, geoip } = state;

  if (user.isAuthenticated) {
    const home = cooperatives.find(item => item._id === user.cooperative);
    center = home && { longitude: home.lng, latitude: home.lat };
  }

  if ((!center || geoip.precission === 'exact') && geoip.longitude) {
    center = {
      longitude: geoip.longitude,
      latitude: geoip.latitude,
      isLoading: geoip.isLoading,
      precission: geoip.precission
    };
  }

  if (typeof window !== 'undefined' && !center) {
    emit('geoip:fetch');
  }

  return html`

      ${ center ? map(state.cooperatives.slice(), center) : html`

          ${ loader() }

      ` }

  `;
};

Remove geolocation in wait for https


'use strict';

Darwinator.Boot = function() {};

Darwinator.Boot.prototype = {

  preload: function () {
    this.load.image('preloader', 'assets/preloader.gif');
  },

  create: function () {
    this.game.input.maxPointers = 1;

    // Toggle pause with space
    var key = this.game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);
    key.onDown.add(this.togglePause, this);

    // this.game.stage.disableVisibilityChange = true;

    if (this.game.device.desktop) {
      this.game.stage.scale.pageAlignHorizontally = true;
    } else {
      this.game.stage.scaleMode = Phaser.StageScaleMode.SHOW_ALL;
      this.game.stage.scale.minWidth = 480;
      this.game.stage.scale.minHeight = 260;
      this.game.stage.scale.maxWidth = 640;
      this.game.stage.scale.maxHeight = 480;
      this.game.stage.scale.forceLandscape = true;
      this.game.stage.scale.pageAlignHorizontally = true;
      this.game.stage.scale.setScreenSize(true);
    }
    this.game.state.start('preloader');
  },

  togglePause: function() {
    this.game.paused = !this.game.paused;
  }

};

'use strict';

Darwinator.Boot = function() {};

Darwinator.Boot.prototype = {

  preload: function () {
    this.load.image('preloader', 'assets/preloader.gif');
  },

  create: function () {
    this.game.input.maxPointers = 1;

    // Toggle pause with space
    var key = this.game.input.keyboard.addKey(Phaser.Keyboard.P);
    key.onDown.add(this.togglePause, this);

    // this.game.stage.disableVisibilityChange = true;

    if (this.game.device.desktop) {
      this.game.stage.scale.pageAlignHorizontally = true;
    } else {
      this.game.stage.scaleMode = Phaser.StageScaleMode.SHOW_ALL;
      this.game.stage.scale.minWidth = 480;
      this.game.stage.scale.minHeight = 260;
      this.game.stage.scale.maxWidth = 640;
      this.game.stage.scale.maxHeight = 480;
      this.game.stage.scale.forceLandscape = true;
      this.game.stage.scale.pageAlignHorizontally = true;
      this.game.stage.scale.setScreenSize(true);
    }
    this.game.state.start('preloader');
  },

  togglePause: function() {
    this.game.paused = !this.game.paused;
  }

};

Change pause hotkey to P


Package.describe({
  name: 'hubaaa:endpoint-puller',
  version: '0.1.0',
  // Brief, one-line summary of the package.
  summary: 'Pulls API endpoints, checking for new data and passing it through json pipes.',
  // URL to the Git repository containing the source code for this package.
  git: 'https://github.com/hubaaa/meteor-endpoint-puller',
  // By default, Meteor will default to using README.md for documentation.
  // To avoid submitting documentation, set this field to null.
  documentation: 'README.md'
});

Package.onUse(function(api) {
  api.versionsFrom('1.1.0.2');
  api.use([
    'meteor',
    'underscore',
    'coffeescript',
    'http',
    'practicalmeteor:loglevel@1.2.0_2',
    'practicalmeteor:chai@2.1.0_1',
    'hubaaa:easy-meteor-settings',
    'hubaaa:json-pipes@0.1.1'
  ], 'server');

  api.addFiles('EndpointPuller.coffee', 'server');
});

Package.onTest(function(api) {
  api.use([
    'underscore',
    'coffeescript',
    'practicalmeteor:mocha@2.1.0_3'
  ], 'server');

  api.use('hubaaa:endpoint-puller@0.1.0', 'server');

  api.addFiles('EndpointPullerTest.coffee', 'server');
});

Package.describe({
  name: 'hubaaa:endpoint-puller',
  version: '0.1.0',
  // Brief, one-line summary of the package.
  summary: 'Pulls API endpoints, checking for new data and passing it through json pipes.',
  // URL to the Git repository containing the source code for this package.
  git: 'https://github.com/hubaaa/meteor-endpoint-puller',
  // By default, Meteor will default to using README.md for documentation.
  // To avoid submitting documentation, set this field to null.
  documentation: 'README.md'
});

Package.onUse(function(api) {
  api.versionsFrom('1.1.0.2');
  api.use([
    'meteor',
    'underscore',
    'coffeescript',
    'http',
    'practicalmeteor:loglevel@1.2.0_2',
    'practicalmeteor:chai@2.1.0_1',
    'hubaaa:easy-meteor-settings@0.1.1',
    'hubaaa:json-pipes@0.1.1'
  ], 'server');

  api.addFiles('EndpointPuller.coffee', 'server');
});

Package.onTest(function(api) {
  api.use([
    'underscore',
    'coffeescript',
    'practicalmeteor:mocha@2.1.0_3'
  ], 'server');

  api.use('hubaaa:endpoint-puller@0.1.0', 'server');

  api.addFiles('EndpointPullerTest.coffee', 'server');
});

Add version constraint for easy-meteor-settings