'use strict';

jest.disableAutomock();

import React, { View } from 'react-native';
import TestUtils from 'react-addons-test-utils';
import expect from 'expect';

import { IdeaCell } from '../../../components/IdeaCell';

function setup(Component, props) {
  let renderer = TestUtils.createRenderer();
  renderer.render(
      <Component {...props} />
    );
  let output = renderer.getRenderOutput();

  return {
    props,
    output,
    renderer
  };
}

describe('idea cell', () => {
  it('should render properly', () => {
    const props = {
      index: 0,
      rowIDDisplay: 1,
      idea: {
        title: 'My idea'
      },
      state: {
        ideas: [ { title: 'My idea' }]
      }
    };
    const { output } = setup(IdeaCell, props);

    expect(output.type).toEqual(View);
    expect(output.props.children.length).toEqual(3);

    expect(output.props.children[0].props.children.props.children).toEqual(1);
    expect(output.props.children[1].props.children).toEqual('My idea');
  });
});

'use strict';

jest.disableAutomock();

import React, { View } from 'react-native';
import TestUtils from 'react-addons-test-utils';
import expect from 'expect';

import { IdeaCell } from '../../../components/IdeaCell';

function setup(Component, props) {
  let renderer = TestUtils.createRenderer();
  renderer.render(
      <Component {...props} />
    );
  let output = renderer.getRenderOutput();

  return {
    props,
    output,
    renderer
  };
}

describe('idea cell', () => {
  it('should render properly', () => {
    const props = {
      index: 0,
      rowIDDisplay: 1,
      idea: {
        title: 'My idea'
      },
      state: {
        ideas: [ { title: 'My idea' }]
      }
    };
    const { output } = setup(IdeaCell, props);

    expect(output.type).toEqual(View);
    expect(output.props.children.length).toEqual(3);

    expect(output.props.children[0].props.children.props.children).toEqual(1);
    expect(output.props.children[1].props.children).toEqual('My idea');
  });

  it('should be able to press buttons for new idea', () => {
    const props = {
      index: 0,
      rowIDDisplay: 1,
      idea: {
        title: 'My idea'
      },
      state: {
        ideas: [ { title: 'My idea' }]
      },
      onSelect: expect.createSpy()
    };
    const { output } = setup(IdeaCell, props);

    const button = output.props.children[2];
    button.props.onPress();
    expect(props.onSelect.calls.length).toBe(1);
  });
});

Add tests for onPress methods on add idea cell


import { AsyncStorage } from 'react-native';

export { MobileAppSettings } from './MobileAppSettings';

export const SETTINGS_KEYS = {
  APP_VERSION: 'AppVersion',
  CURRENT_LANGUAGE: 'CurrentLanguage',
  MOST_RECENT_USERNAME: 'MostRecentUsername',
  SUPPLYING_STORE_ID: 'SupplyingStoreId',
  SUPPLYING_STORE_NAME_ID: 'SupplyingStoreNameId',
  LAST_POST_PROCESSING_FAILED: 'LastPostProcessingFailed',
  SYNC_IS_INITIALISED: 'SyncIsInitialised',
  SYNC_PRIOR_FAILED: 'SyncPriorFailed',
  SYNC_URL: 'SyncURL',
  SYNC_SITE_ID: 'SyncSiteId',
  SYNC_SERVER_ID: 'SyncServerId',
  SYNC_SITE_NAME: 'SyncSiteName',
  SYNC_SITE_PASSWORD_HASH: 'SyncSitePasswordHash',
  THIS_STORE_ID: 'ThisStoreId',
  THIS_STORE_NAME_ID: 'ThisStoreNameId',
  HARDWARE_UUID: 'Hardware_UUID',
};

export const getAppVersion = async () => {
  const appVersion = await AsyncStorage.getItem(SETTINGS_KEYS.APP_VERSION);
  return appVersion;
};

import { AsyncStorage } from 'react-native';

export { MobileAppSettings } from './MobileAppSettings';

export const SETTINGS_KEYS = {
  APP_VERSION: 'AppVersion',
  CURRENT_LANGUAGE: 'CurrentLanguage',
  MOST_RECENT_USERNAME: 'MostRecentUsername',
  SUPPLYING_STORE_ID: 'SupplyingStoreId',
  SUPPLYING_STORE_NAME_ID: 'SupplyingStoreNameId',
  LAST_POST_PROCESSING_FAILED: 'LastPostProcessingFailed',
  SYNC_IS_INITIALISED: 'SyncIsInitialised',
  SYNC_PRIOR_FAILED: 'SyncPriorFailed',
  SYNC_URL: 'SyncURL',
  SYNC_SITE_ID: 'SyncSiteId',
  SYNC_SERVER_ID: 'SyncServerId',
  SYNC_SITE_NAME: 'SyncSiteName',
  SYNC_SITE_PASSWORD_HASH: 'SyncSitePasswordHash',
  THIS_STORE_ID: 'ThisStoreId',
  THIS_STORE_NAME_ID: 'ThisStoreNameId',
  HARDWARE_UUID: 'Hardware_UUID',
  THIS_STORE_TAGS: 'ThisStoreTags',
};

export const getAppVersion = async () => {
  const appVersion = await AsyncStorage.getItem(SETTINGS_KEYS.APP_VERSION);
  return appVersion;
};

Add store tags to settings


import React from 'react';
import { Card, Image, Icon } from 'semantic-ui-react';
import ListingDeleteModal from './ListingDeleteModal';

const getIconString = str => (
  str === 'home improvement' ? 'home' : 'laptop'
);

const Listing = ({ userId, listingId, title, createdAt, body, type, handleDelete, pathname, cutBody }) =>
  <Card centered raised>
    <Card.Content>
      {
        pathname === '/dashboard' ?
          <ListingDeleteModal
            handleDelete={handleDelete}
            userId={userId}
            listingId={listingId}
          /> : null
      }
      <Image floated="right">
        <Icon name={getIconString(type)} size="big" />
      </Image>
      <Card.Header>
        { title }
      </Card.Header>
      <Card.Meta>
        { createdAt }
      </Card.Meta>
      <Card.Description>
        { cutBody(body) }
      </Card.Description>
    </Card.Content>
  </Card>;

Listing.propTypes = {
  title: React.PropTypes.string.isRequired,
  createdAt: React.PropTypes.string.isRequired,
  body: React.PropTypes.string.isRequired,
  type: React.PropTypes.string.isRequired,
  userId: React.PropTypes.number,
  listingId: React.PropTypes.number.isRequired,
  handleDelete: React.PropTypes.func,
  pathname: React.PropTypes.string.isRequired,
};

export default Listing;

import React from 'react';
import { Link } from 'react-router';
import { Card, Image, Icon } from 'semantic-ui-react';
import ListingDeleteModal from './ListingDeleteModal';

const getIconString = str => (
  str === 'home improvement' ? 'home' : 'laptop'
);

const Listing = ({ userId, listingId, title, createdAt, body, type, handleDelete, pathname, cutBody }) =>
  <Card centered raised>
    <Link to={`/listings/${listingId}`} >
      <Card centered raised>
        <Card.Content>
          {
            pathname === '/dashboard' ?
              <ListingDeleteModal
                handleDelete={handleDelete}
                userId={userId}
                listingId={listingId}
              /> : null
          }
          <Image floated="right">
            <Icon name={getIconString(type)} size="big" />
          </Image>
          <Card.Header>
            { title }
          </Card.Header>
          <Card.Meta>
            { createdAt }
          </Card.Meta>
          <Card.Description>
            { cutBody(body) }
          </Card.Description>
        </Card.Content>
      </Card>
    </Link>
  </Card>;

Listing.propTypes = {
  title: React.PropTypes.string.isRequired,
  createdAt: React.PropTypes.string.isRequired,
  body: React.PropTypes.string.isRequired,
  type: React.PropTypes.string.isRequired,
  userId: React.PropTypes.number,
  listingId: React.PropTypes.number.isRequired,
  handleDelete: React.PropTypes.func,
  pathname: React.PropTypes.string.isRequired,
};

export default Listing;

Add ability to click on listing cards


var fs = require ('fs');

var logger = {
	_logFile: undefined,
	initLog: function (logfile_, version_) {
		logger._logFile = logfile_;

		// Clear the log
		fs.writeFileSync (logger._logFile, "");

		// Write header data
		fs.appendFileSync (logger._logFile,
		                   'Created with ugly v' + version_);
		fs.appendFileSync (logger._logFile,
		                   'Start date: ' + (new Date ().toString ()));
		fs.appendFileSync (logger._logFile,
		                   '=================================================');
	},
	// Print an info (non-fatal) message to the console and the log file
	info: function (msg_) {
		var time = new Date ().getTime ();

		msg_ = '[' + time + '] INFO: ' + msg_;

		console.log (msg_);
		fs.appendFileSync (logger._logFile, msg_);
	},
	// Print an error (fatal) message to the consol and the log file and then
	// kill the program
	error: function (msg_) {
		var time = new Date ().getTime ();

		msg_ = '[' + time + '] ERROR: ' + msg_;

		console.error (msg_);
		fs.appendFileSync (logger._logFile, msg_);
		process.exit (1);
	}
};

module.exports = logger;

var fs = require ('fs');

var logger = {
	_logFile: undefined,
	initLog: function (logfile_, version_) {
		logger._logFile = logfile_;

		// Clear the log
		fs.writeFileSync (logger._logFile, "");

		// Write header data
		fs.appendFileSync (logger._logFile,
		                   'Created with ugly v' + version_);
		fs.appendFileSync (logger._logFile,
		                   'Start date: ' + (new Date ().toString ()));
		fs.appendFileSync (logger._logFile,
		                   '=================================================');
	},
	// Print an info (non-fatal) message to the console and the log file
	info: function (msg_) {
		var time = new Date ().getTime ();

		msg_ = '[' + time + '] INFO: ' + msg_;

		console.log (msg_);
		fs.appendFileSync (logger._logFile, msg_);
	},
	// Print an error (fatal) message to the consol and the log file and then
	// kill the program
	error: function (msg_) {
		var time = new Date ().getTime ();

		var logMsg = '[' + time + '] ERROR: ' + msg_;

		fs.appendFileSync (logger._logFile, logMsg);
		throw new Error (msg_);
	}
};

module.exports = logger;

Throw an error instead of killing the process


Template.homeReport.onCreated(function () {
  // get a reference to template instance
  const templateInstance = this;

  // set reactive variable to hold chart data (attached to template instance)
  const activityData = new ReactiveVar()

  // call method to retrieve aggregated activity data
  // Get Home ID
  templateInstance.homeId = Router.current().params.homeId;

  Meteor.call('getDailyAggregatedHomeResidentActivities', templateInstance.homeId, function (error, activities) {
    console.log(activities)
  })
  // set activity data in the reactive variable

});

Template.homeReport.onCreated(function () {
  // get a reference to template instance
  const templateInstance = this;

  // set reactive variable to hold chart data (attached to template instance)
  templateInstance.activityData = new ReactiveVar();

  // call method to retrieve aggregated activity data
  // Get Home ID
  templateInstance.homeId = Router.current().params.homeId;

  Meteor.call('getDailyAggregatedHomeResidentActivities', templateInstance.homeId, function (error, activities) {
    // set activity data in the reactive variable
    templateInstance.activityData.set(activities);
  })
});

Set reactive variable with chart data


import { Observable } from 'rxjs/Observable'
import { merge } from 'rxjs/observable/merge'
import { takeUntil } from 'rxjs/operator/takeUntil'
import { share } from 'rxjs/operator/share'

const makeVirtualEmission = (scheduler, value, delay) => {
  return new Observable(observer => {
    scheduler.schedule(() => {
      observer.next(value)
    }, delay, value)
  })
}

const makeVirtualStream = (scheduler, diagram) => {
  const { emissions, completion } = diagram

  const partials = emissions.map(({ x, d }) => (
    makeVirtualEmission(scheduler, d, x)
  ))

  const completion$ = makeVirtualEmission(scheduler, null, completion)

  const emission$ = merge(...partials)
    ::takeUntil(completion$)
    ::share()

  return emission$
}

export default makeVirtualStream

import { Observable } from 'rxjs/Observable'
import { merge } from 'rxjs/observable/merge'
import { takeUntil } from 'rxjs/operator/takeUntil'
import { share } from 'rxjs/operator/share'

const makeVirtualEmission = (scheduler, value, delay) => {
  return new Observable(observer => {
    scheduler.schedule(() => {
      observer.next(value)
    }, delay, value)
  })
}

const makeVirtualStream = (scheduler, diagram) => {
  const { emissions, completion } = diagram

  const partials = emissions.map(({ x, d }) => (
    makeVirtualEmission(scheduler, d, x)
  ))

  const completion$ = makeVirtualEmission(scheduler, null, completion + .0001)

  const emission$ = merge(...partials)
    ::takeUntil(completion$)
    ::share()

  return emission$
}

export default makeVirtualStream

Fix last value disappearing when it coincides with completion


'use strict';
angular.module('arethusa.core').controller('NavigatorCtrl', [
  '$scope',
  'navigator',
  function ($scope, navigator) {
    $scope.next = function () {
      navigator.nextSentence();
    };
    $scope.prev = function () {
      navigator.prevSentence();
    };
    $scope.hasNext = function() {
      return navigator.hasNext();
    };
    $scope.hasPrev = function() {
      return navigator.hasPrev();
    };
    $scope.goToFirst = function() {
      navigator.goToFirst();
    };
    $scope.goToLast = function() {
      navigator.goToLast();
    };
    $scope.goTo = function(id) {
      navigator.goTo(id);
    };
    $scope.navStat = navigator.status;
  }
]);

'use strict';
angular.module('arethusa.core').controller('NavigatorCtrl', [
  '$scope',
  'navigator',
  function ($scope, navigator) {
    $scope.next = function () {
      navigator.nextSentence();
    };
    $scope.prev = function () {
      navigator.prevSentence();
    };
    $scope.goToFirst = function() {
      navigator.goToFirst();
    };
    $scope.goToLast = function() {
      navigator.goToLast();
    };
    $scope.goTo = function(id) {
      navigator.goTo(id);
    };
    $scope.navStat = navigator.status;
  }
]);

Remove obsolete functions from NavigatorCtrl


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

import Oauth2Bearer from 'torii/providers/oauth2-bearer';
import {configurable} from 'torii/configuration';

var GoogleOauth2Bearer = Oauth2Bearer.extend({

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

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

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

  responseParams: ['access_token'],

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

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

export default GoogleOauth2Bearer;

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

import Oauth2Bearer from 'torii/providers/oauth2-bearer';
import {configurable} from 'torii/configuration';

var GoogleOauth2Bearer = Oauth2Bearer.extend({

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

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

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

  responseParams: ['access_token'],

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

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

export default GoogleOauth2Bearer;

Make Google implicit grant redirect to the local Ember app


'use strict';

module.exports = function(app) {
	var users = require('../../app/controllers/users.server.controller');
	var mal = require('../../app/controllers/mal.server.controller');

	// MAL Routes
	app.route('/malSearch/:type/:searchString')
		.get(mal.search);
};

'use strict';

module.exports = function(app) {
	var users = require('../../app/controllers/users.server.controller');
	var mal = require('../../app/controllers/mal.server.controller');

	// MAL Routes
	app.route('/malSearch/:type')
		.get(mal.search);
};

Change to reflect client service.


var path = require('path');
var jade = require('jade');
var normalize = require('./normalize');

/**
 * A Metalsmith plugin to create HTTP redirections.
 *
 * @param {Object} files
 * @param {Metalsmith} metalsmith
 * @param {Function} done
 */
module.exports = function plugin(options) {
  options = options || {};

  return function(files, metalsmith, done) {
    var source;
    var destination;
    var rendered;

    for (var key in options) {
      if (options.hasOwnProperty(key)) {
        source = key;
        destination = options[key];

        // Normalize the source and the destination
        source = normalize(source)
          .appendHTMLIndexIfNeeded()
          .ensureHTML()
          .relativeTo('/')
          .get();
        destination = normalize(destination)
          .relativeTo(path.dirname(source))
          .get();

        // Render the view
        rendered = jade.renderFile(path.join(__dirname, 'template.jade'), {
          pretty: true, // pretty HTML
          destination: destination
        });

        // Add the new file to the stream
        files[source] = {
          contents: rendered
        };
      }
    }

    return done();
  };
}

var path = require('path');
var jade = require('jade');
var normalize = require('./normalize');

/**
 * A Metalsmith plugin to create HTTP redirections.
 *
 * @param {Object} files
 * @param {Metalsmith} metalsmith
 * @param {Function} done
 */
module.exports = function plugin(options) {
  options = options || {};

  return function(files, metalsmith, done) {
    Object.keys(options).forEach(function (source) {
      var destination = options[source];

      // Normalize the source and the destination
      source = normalize(source)
        .appendHTMLIndexIfNeeded()
        .ensureHTML()
        .relativeTo('/')
        .get();
      destination = normalize(destination)
        .relativeTo(path.dirname(source))
        .get();

      // Render the view
      var rendered = jade.renderFile(path.join(__dirname, 'template.jade'), {
        pretty: true, // pretty HTML
        destination: destination
      });

      // Add the new file to the stream
      files[source.substr(1)] = {
        contents: rendered
      };
    });

    return done();
  };
};

Remove absolute path of source locations


(function(Pebble, window) {
  var settings = {};

  Pebble.addEventListener("ready", function(e) {
    settings = window.localStorage.getItem("settings");
    if(settings !== '') {
      var options = JSON.parse(settings);
      Pebble.sendAppMessage(options);
    }
  });

  Pebble.addEventListener("showConfiguration", function() {
    settings = window.localStorage.getItem("settings");
    if(!settings) {
      settings = {};
    }
    Pebble.openURL("http://rexmac.com/projects/pebble/chronocode/settings.html");
  });

  Pebble.addEventListener("webviewclosed", function(e) {
    var options = JSON.parse(decodeURIComponent(e.response));
    if(Object.keys(options).length > 0) {
      window.localStorage.setItem("settings", JSON.stringify(options));
      Pebble.sendAppMessage(options);
    }
  })
})(Pebble, window);

(function(Pebble, window) {
  var settings = {};

  Pebble.addEventListener("ready", function(e) {
    settings = window.localStorage.getItem("settings");
    if(settings !== '') {
      var options = JSON.parse(settings);
      Pebble.sendAppMessage(options);
    }
  });

  Pebble.addEventListener("showConfiguration", function() {
    settings = window.localStorage.getItem("settings");
    if(!settings) {
      settings = {};
    }
    Pebble.openURL("http://rexmac.com/projects/pebble/chronocode/settings.html#" + encodeURIComponent(JSON.stringify(settings)));
  });

  Pebble.addEventListener("webviewclosed", function(e) {
    var options = JSON.parse(decodeURIComponent(e.response));
    if(Object.keys(options).length > 0) {
      window.localStorage.setItem("settings", JSON.stringify(options));
      Pebble.sendAppMessage(options);
    }
  })
})(Pebble, window);

Append settings hash to URL


'use strict'

import mongoose from 'mongoose'
import uriFormat from 'mongodb-uri'

import { config } from './'

config.mongo = config.get('mongo')

mongoose.set('useNewUrlParser', true)

export const connectionAgenda = mongoose.createConnection(encodeMongoURI(config.mongo.url))
export const connectionAPI = mongoose.createConnection(encodeMongoURI(config.mongo.url), getMongoOptions())
export const connectionATNA = mongoose.createConnection(encodeMongoURI(config.mongo.atnaUrl))
export const connectionDefault = mongoose.createConnection(encodeMongoURI(config.mongo.url))

function encodeMongoURI (urlString) {
  if (urlString) {
    let parsed = uriFormat.parse(urlString)
    urlString = uriFormat.format(parsed);
  }
  return urlString;
}

function getMongoOptions () {
  return {
    readPreference: config.mongo.openHIMApiReadPreference,
    readConcern: {level: config.mongo.openHIMApiReadConcern},
    w: config.mongo.openHIMApiWriteConcern
  }
}

if (process.env.NODE_ENV === 'test') {
  exports.encodeMongoURI = encodeMongoURI
}

'use strict'

import mongoose from 'mongoose'
import uriFormat from 'mongodb-uri'

import { config } from './'

config.mongo = config.get('mongo')

mongoose.set('useNewUrlParser', true)
mongoose.set('useUnifiedTopology', true)
mongoose.set('useFindAndModify', false)

export const connectionAgenda = mongoose.createConnection(encodeMongoURI(config.mongo.url))
export const connectionAPI = mongoose.createConnection(encodeMongoURI(config.mongo.url), getMongoOptions())
export const connectionATNA = mongoose.createConnection(encodeMongoURI(config.mongo.atnaUrl))
export const connectionDefault = mongoose.createConnection(encodeMongoURI(config.mongo.url))

function encodeMongoURI (urlString) {
  if (urlString) {
    let parsed = uriFormat.parse(urlString)
    urlString = uriFormat.format(parsed);
  }
  return urlString;
}

function getMongoOptions () {
  return {
    readPreference: config.mongo.openHIMApiReadPreference,
    readConcern: {level: config.mongo.openHIMApiReadConcern},
    w: config.mongo.openHIMApiWriteConcern
  }
}

if (process.env.NODE_ENV === 'test') {
  exports.encodeMongoURI = encodeMongoURI
}

Add mongoose settings for mongodb


// --------------------
// Sequelize hierarchy
// Utility functions
// --------------------

// exports

module.exports = function(Sequelize) {
	var Utils = Sequelize.Utils,
		_ = Utils._;

	// return functions
	return {
		replaceIdentifiers: function(sql, identifiers) {
			_.forIn(identifiers, function(replacement, identifier) {
				sql = sql.replace(new RegExp('\\*' + identifier + '(?![a-zA-Z0-9])', 'g'), escapeIdentifier(replacement));
			});
			return sql.replace(/[ \t\r\n]+/g, ' ');
		}
	};

	function escapeIdentifier(identifier) {
		return '`' + identifier.replace(/`/g, '``') + '`';
	}
};

// --------------------
// Sequelize hierarchy
// Utility functions
// --------------------

// modules
var _ = require('lodash');

// exports

module.exports = function(Sequelize) {
	// return functions
	return {
		replaceIdentifiers: function(sql, identifiers) {
			_.forIn(identifiers, function(replacement, identifier) {
				sql = sql.replace(new RegExp('\\*' + identifier + '(?![a-zA-Z0-9])', 'g'), escapeIdentifier(replacement));
			});
			return sql.replace(/[ \t\r\n]+/g, ' ');
		}
	};

	function escapeIdentifier(identifier) {
		return '`' + identifier.replace(/`/g, '``') + '`';
	}
};

Use lodash dependency in utility functions


var game = new Phaser.Game(800, 600, Phaser.AUTO, '', { preload: preload, create: create, update: update });
var player;

function preload() {
	game.load.image('star', 'assets/star.png');
	game.load.image('diamond', 'assets/diamond.png');
}

function create() {
	game.physics.startSystem(Phaser.Physics.ARCADE);

	player = game.add.sprite(0, 400, 'star');
	game.add.sprite(200, 200, 'diamond');

	game.physics.arcade.enable(player);
	player.body.velocity.x = 60;
}

function update() {

}

var game = new Phaser.Game(800, 600, Phaser.AUTO, '', { preload: preload, create: create, update: update });
var player;
var cursors;

function preload() {
	game.load.image('star', 'assets/star.png');
	game.load.image('diamond', 'assets/diamond.png');
}

function create() {
	game.physics.startSystem(Phaser.Physics.ARCADE);

	player = game.add.sprite(0, 400, 'star');
	game.add.sprite(200, 200, 'diamond');

	game.physics.arcade.enable(player);
	player.body.velocity.x = 60;

	cursors = game.input.keyboard.createCursorKeys();
}

function update() {
	var rotSpeed = 0.05;
	if (cursors.left.isDown) {
		player.body.velocity.rotate(0, 0, -rotSpeed);
	} else if (cursors.right.isDown) {
		player.body.velocity.rotate(0, 0, rotSpeed);
	}
}

Make the star (player) move and be steerable


// Download the helper library https://www.twilio.com/docs/libraries/node#install

// Your Account Sid and Auth Token from twilio.com/console
const accountSid = 'ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
const authToken = 'your_auth_token';
const client = require('twilio')(accountSid, authToken);

// Replace 'UAXXX...' with your Assistant's unique SID https://www.twilio.com/console/autopilot/list
query = client.preview.understand.assistants('UAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX')
                                 .queries
                                 .create({
                                   language: 'en-US',
                                   query: 'Tell me a joke',
                                 })
                                 .then(query => console.log(query.results.task))
                                 .done();

// Download the helper library https://www.twilio.com/docs/libraries/node#install

// Your Account Sid and Auth Token from twilio.com/console
const accountSid = 'ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
const authToken = 'your_auth_token';
const client = require('twilio')(accountSid, authToken);

// Replace 'UAXXX...' with your Assistant's unique SID https://www.twilio.com/console/autopilot/list
query = client.autopilot.assistants('UAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX')
                                 .queries
                                 .create({
                                   language: 'en-US',
                                   query: 'Tell me a joke',
                                 })
                                 .then(query => console.log(query.results.task))
                                 .done();

Update to use autopilot instead of understand


/**
 * Searches the first loaded style sheet for an @keyframes animation by name.
 * FYI: If you use "to" or "from" they'll get converted to percentage values, so use them for manipulating rules.
 * Source (Optimized for flapper): http://jsfiddle.net/russelluresti/RHhBz/3/.
 **/
function findKeyframesRule(name) {
  var stylesheet = document.styleSheets[0];
  for (var i = 0; i < stylesheet.cssRules.length; i++) {
    var cssRule = stylesheet.cssRules[i];
    if (cssRule.name == name) return cssRule;
  }
  return null;
}

/**
 * Searches the first loaded style sheet for a CSS rule by name.
 **/
 function findCSSRule(name) {
   var stylesheet = document.styleSheets[0];
   for (var i = 0; i < stylesheet.cssRules.length; i++) {
     var cssRule = stylesheet.cssRules[i];
     if (cssRule.selectorText == name) return cssRule;
   }
   return null;
 }

/**
 * Searches the first loaded style sheet for an @keyframes animation by name.
 * FYI: If you use "to" or "from" they'll get converted to percentage values, so use them for manipulating rules.
 * Source (Optimized for flapper): http://jsfiddle.net/russelluresti/RHhBz/3/.
 **/
function findKeyframesRule(name) {
  var stylesheet = document.styleSheets[0];
  for (var i = 0; i < stylesheet.cssRules.length; i++) {
    var cssRule = stylesheet.cssRules[i];
    if (cssRule.name == name) return cssRule;
  }
  return null;
}

/**
 * Searches the first loaded style sheet for a CSS rule by name.
 **/
 function findCSSRule(name) {
   var stylesheet = document.styleSheets[0];
   console.log("Stylesheet: " + stylesheet);
   for (var i = 0; i < stylesheet.cssRules.length; i++) {
     var cssRule = stylesheet.cssRules[i];
     console.log(`cssRule(${i}): ${cssRule}`)
     if (cssRule.selectorText == name) {
       console.log("Found it");
       return cssRule;
     }
   }
   return null;
 }

Add css rule debugging, cause Chrome's being a weirdo


import React, { PropTypes } from 'react'
import { connect } from 'react-redux'

import ContainerFluid from '../ContainerFluid'
import VmUserMessages from '../VmUserMessages'
import UserMenu from './UserMenu'

import { getAllVms } from '../../actions/vm'

/**
 * Main application header on top of the page
 */
const VmsPageHeader = ({ title, onRefresh }) => {
  const titleStyle = { padding: '0px 0 5px' }

  return (

      <ContainerFluid>

          {title}

               Refresh

          <UserMenu />
          <VmUserMessages />

      </ContainerFluid>

  )
}
VmsPageHeader.propTypes = {
  title: PropTypes.string.isRequired,
  onRefresh: PropTypes.func.isRequired,
}

export default connect(
  (state) => ({ }),
  (dispatch) => ({
    onRefresh: () => dispatch(getAllVms({ shallowFetch: false })),
  })
)(VmsPageHeader)

Fix refresh button highlighted after click


import React from "react";
import {
  Text
} from "react-native";
import {connect} from "react-redux";

import PageFrame from "../components/PageFrame";
import EventListView from "../components/EventListView";
import store from "../store";

class EventsScreen extends React.Component {
  render() {
    return (
      <PageFrame title="Upcoming events" backButton={false} searchButton={true}>
        <EventListView events={this.props.events} />
      </PageFrame>
    );
  }
}

const mapStoreToProps = function(store) {
  return {
    events: store.events
  };
};

export default connect(mapStoreToProps)(EventsScreen);

import React from "react";
import {
  Text,
  StyleSheet
} from "react-native";
import {connect} from "react-redux";

import PageFrame from "../components/PageFrame";
import EventListView from "../components/EventListView";
import store from "../store";

class EventsScreen extends React.Component {
  render() {
    var list = <EventListView events={this.props.events} />;
    var emptyMessage = <Text style={styles.emptyMessage}>You have no upcoming events.</Text>;

    return (
      <PageFrame title="Upcoming events" backButton={false} searchButton={true}>
        {this.props.events.length > 0 ? list : emptyMessage}
      </PageFrame>
    );
  }
}

const mapStoreToProps = function(store) {
  return {
    events: store.events
  };
};

const styles = StyleSheet.create({
  emptyMessage: {
    flex: 1,
    textAlign: "center",
    textAlignVertical: "center"
  }
});

export default connect(mapStoreToProps)(EventsScreen);

Add empty message when no upcoming events


var nearestPackageRoot = require('../../lib/resolver/nearest-package-root');
var assert = require('assert');
var path = require('path');

var fixturesPath = path.resolve.bind(null,
  __dirname,
  '..',
  'fixtures',
  'nearest-package-root-test'
)

describe('nearestPackageRoot', function () {
  it('finds the nearest module folder based on the import origin', function (done) {
    var sourcePath = fixturesPath('index.scss');
    nearestPackageRoot('test-module', sourcePath).then(function (result) {
      assert.equal(result, fixturesPath('node_modules', 'test-module'));
      done();
    });
  });

  it('works with nested dependencies', function (done) {
    var sourcePath = fixturesPath('node_modules', 'test-module', 'index.scss');
    nearestPackageRoot('nested-module', sourcePath).then(function (result) {
      assert.equal(result, fixturesPath('node_modules', 'test-module', 'node_modules', 'nested-module'));
      done();
    });
  });
});

var nearestPackageRoot = require('../../lib/resolver/nearest-package-root');
var assert = require('assert');
var path = require('path');

var fixturesPath = path.resolve.bind(null,
  __dirname,
  '..',
  'fixtures',
  'nearest-package-root-test'
)

describe('nearestPackageRoot', function () {
  it('finds the nearest module folder based on the import origin', function (done) {
    var sourcePath = fixturesPath('index.scss');
    nearestPackageRoot('test-module', sourcePath).then(function (result) {
      assert.equal(result, fixturesPath('node_modules', 'test-module'));
    })
    .then(done)
    .catch(done);
  });

  it('works with nested dependencies', function (done) {
    var sourcePath = fixturesPath('node_modules', 'test-module', 'index.scss');
    nearestPackageRoot('nested-module', sourcePath).then(function (result) {
      assert.equal(result, fixturesPath('node_modules', 'test-module', 'node_modules', 'nested-module'));
    })
    .then(done)
    .catch(done);
  });
});

Handle exception in nearestPackageRoot test


"use strict";

function $(id) {
  return document.getElementById(id);
}

// This is here because setTimeout() doesn't seem to call member functions correctly
function Hide(popupManager)
{
    popupManager.HidePopup();
}

var popUpDuration = 10000;

define( [
        ],
       function (
                 ) {

  var PopUpManager = function (services) {
    this.services = services;

      this.overlay = $("overlay");
      this.line1 = $("overlay-line1");
      this.line2 = $("overlay-line2");

      this.HidePopup();
  };

  PopUpManager.prototype.CreatePopUp = function(line1, line2)
  {
    this.line1.childNodes[0].data = line1;
    this.line2.childNodes[0].data = line2;
    this.ShowPopUp();
    setTimeout( Hide, popUpDuration, this );
  }

  PopUpManager.prototype.ShowPopUp = function()
  {
    this.overlay.style.visibility = 'visible';
  }

  PopUpManager.prototype.HidePopup = function()
  {
    this.overlay.style.visibility = 'hidden';
  }

  return PopUpManager;
});

"use strict";

function $(id) {
  return document.getElementById(id);
}

// This is here because setTimeout() doesn't seem to call member functions correctly
function Hide(popupManager)
{
    popupManager.HidePopup();
}

var popUpDuration = 3000;

define( [
        ],
       function (
                 ) {

  var PopUpManager = function (services) {
    this.services = services;
      this.hideTimeoutID = 0;

      this.overlay = $("overlay");
      this.line1 = $("overlay-line1");
      this.line2 = $("overlay-line2");

      this.HidePopup();
  };

  PopUpManager.prototype.CreatePopUp = function(line1, line2)
  {
    this.line1.childNodes[0].data = line1;
    this.line2.childNodes[0].data = line2;
    this.ShowPopUp();
    clearTimeout( this.hideTimeoutID );
    this.hideTimeoutID = setTimeout( Hide, popUpDuration, this );
  }

  PopUpManager.prototype.ShowPopUp = function()
  {
    this.overlay.style.visibility = 'visible';
  }

  PopUpManager.prototype.HidePopup = function()
  {
    this.overlay.style.visibility = 'hidden';
  }

  return PopUpManager;
});

Add management of the popup hide callback.


/**
 * @license
 * Copyright (c) 2016 The GDG Spain Authors. All rights reserved.
 * This code may only be used under the MIT style license found at http://gdg.es/LICENSE.txt
 */

module.exports = {
  staticFileGlobs: [
    '/index.html',
    '/manifest.json',
    '/bower_components/webcomponentsjs/webcomponents-lite.min.js'
  ],
  navigateFallback: '/index.html'
};

/**
 * @license
 * Copyright (c) 2016 The GDG Spain Authors. All rights reserved.
 * This code may only be used under the MIT style license found at http://gdg.es/LICENSE.txt
 */

module.exports = {
  staticFileGlobs: [
    '/index.html',
    '/manifest.json',
    '/bower_components/webcomponentsjs/webcomponents-lite.min.js',
    '/images/*'
  ],
  navigateFallback: '/index.html',
  navigateFallbackWhitelist: [/^(?!.*\.html$|\/data\/).*/],
  runtimeCaching: [
    {
      urlPattern: /\/data\/images\/.*/,
      handler: 'cacheFirst',
      options: {
        cache: {
          maxEntries: 50,
          name: 'items-cache'
        }
      }
    }
  ]
};

Update Service Worker precache config


describe('Filter: ivhPluckAndJoin', function() {
  'use strict';

  beforeEach(module('ivh.pluckAndJoin'));

  var ivhPluckAndJoinFilter, items;

  beforeEach(inject(function(_ivhPluckAndJoinFilter_) {
    ivhPluckAndJoinFilter = _ivhPluckAndJoinFilter_;
    items = [{name: 'Justin'}, {name: 'Bella'}];
  }));

  it('should return the empty string for empty collections', function() {
    var actual = ivhPluckAndJoinFilter([], 'name');
    expect(actual).toBe('');
  });

  it('should join items by ", " by default', function() {
    var actual = ivhPluckAndJoinFilter(items, 'name');
    expect(actual).toBe('Justin, Bella');
  });

  it('should accept a custom delimiter', function() {
    var actual = ivhPluckAndJoinFilter(items, 'name', ' - ');
    expect(actual).toBe('Justin - Bella');
  });
});

describe('Filter: ivhPluckAndJoin', function() {
  'use strict';

  beforeEach(module('ivh.pluckAndJoin'));

  var ivhPluckAndJoinFilter, items;

  beforeEach(inject(function(_ivhPluckAndJoinFilter_) {
    ivhPluckAndJoinFilter = _ivhPluckAndJoinFilter_;
    items = [{name: 'Justin'}, {name: 'Bella'}];
  }));

  it('should return the empty string for empty collections', function() {
    var actual = ivhPluckAndJoinFilter([], 'name');
    expect(actual).toBe('');
  });

  it('should join items by ", " by default', function() {
    var actual = ivhPluckAndJoinFilter(items, 'name');
    expect(actual).toBe('Justin, Bella');
  });

  it('should accept a custom delimiter', function() {
    var actual = ivhPluckAndJoinFilter(items, 'name', ' - ');
    expect(actual).toBe('Justin - Bella');
  });

  it('should return an empty string when given null', function() {
    var actual = ivhPluckAndJoinFilter(null, 'name');
    expect(actual).toEqual('');
  });

  it('should return the empty string when given undefined', function() {
    var actual = ivhPluckAndJoinFilter(undefined, 'name');
    expect(actual).toBe('');
  });
});

Add test to allow null/undefined


const fs = require('fs');
const buildConstants = require('../buildConstants');

const cleanTask = (resolve, reject) => {
    fs.promises.rmdir(buildConstants.outputDirectory, { recursive: true })
        .then(() => {
            fs.promises.mkdir(buildConstants.outputDirectory)
                .then(resolve)
                .catch((mkdirError) => reject(mkdirError));
        })
        .catch((rmdirError) => reject(rmdirError));
};

module.exports = cleanTask;

const fs = require('fs');
const buildConstants = require('../buildConstants');

const cleanTask = (resolve, reject) => {
    const recreatedOutputDirectory = (res, rej) => {
        fs.promises.mkdir(buildConstants.outputDirectory)
            .then(res)
            .catch((mkdirError) => rej(mkdirError));
    };

    fs.promises.stat(buildConstants.outputDirectory)
        .then(() => {
            fs.promises.rmdir(buildConstants.outputDirectory, { recursive: true })
                .then(() => recreatedOutputDirectory(resolve, reject))
                .catch((rmdirError) => reject(rmdirError));
        })
        .catch(() => {
            recreatedOutputDirectory(resolve, reject);
        });
};

module.exports = cleanTask;

Make the clean task continue on if the output folder does not exist.


import stream from 'stream';
import csvWriter from 'csv-write-stream';

class json extends stream.Transform {
  constructor() {
    super({ objectMode: true });
  }

  _transform(data, encoding, cb) {
    data = JSON.stringify(data);
    this.push(data);
    cb();
  }
}

export default {
  bson: collection => Promise.resolve(collection),
  json: collection => {
    const parserJson = new json();
    parserJson.filename = collection.filename;
    return Promise.resolve(collection.pipe(parserJson));
  },
  csv: collection => {
    const writer = csvWriter();
    writer.filename = collection.filename;
    return Promise.resolve(collection.pipe(writer));
  }
};

import stream from 'stream';
import csvWriter from 'csv-write-stream';

class json extends stream.Transform {
  constructor() {
    super({ objectMode: true });
  }

  _transform(data, encoding, cb) {
    data = `${JSON.stringify(data)}\r\n`;
    this.push(data);
    cb();
  }
}

export default {
  bson: collection => Promise.resolve(collection),
  json: collection => {
    const parserJson = new json();
    parserJson.filename = collection.filename;
    return Promise.resolve(collection.pipe(parserJson));
  },
  csv: collection => {
    const writer = csvWriter();
    writer.filename = collection.filename;
    return Promise.resolve(collection.pipe(writer));
  }
};

Fix JSON string formatting to allow mongoimport


var galv = require('galvatron');
var assign = require('lodash/object/assign');
var test = require('./test');

module.exports = function () {
  galv.watch('test/**', function (opts) {
    test(assign(opts || {}, {
      action: 'watch'
    }));
  });
};

var galv = require('galvatron');
var assign = require('lodash/object/assign');
var test = require('./test');

module.exports = function () {
  galv.watch('test/**', function (opts) {
    test(assign(opts || {}, {
      action: 'watch'
    })).on('error', function(){});
  });
};

Add empty error handler in watch mode so the pipe doesn't break. :


/**
 * App() object
 *
 */
var App = {
	Views: {}
};

App.Router = Backbone.Router.extend({

	routes:{
		"":"main",
		"checkinout":"check_in_out",
		"events":"events",
		"event/:id":"event"
	},

	main: function()
	{
		App.mainview.render();
	},
	check_in_out: function()
	{
		App.checkinout.render();
	},
	events: function()
	{
		App.mainview.render();
	},
	event: function(id)
	{
		App.mainview.render();
	},
	checkLogin: function()
	{
		if(!App.model.get('loggedin'))
		{
			App.loginview.render();
		}

		return App.model.loggedin;
	}
});

/**
 * App() object
 *
 */
var App = {
	Views: {}
};

App.Router = Backbone.Router.extend({

	routes:{
		"":"main",
		"checkinout":"check_in_out",
		"events":"events",
		"event/:id":"event"
	},

	main: function()
	{
		App.mainview.render();
	},
	check_in_out: function()
	{
		App.checkinout.render();
	},
	events: function()
	{
		App.mainview.render();
	},
	event: function(id)
	{
		App.mainview.render();
	}
});

Remove unused verifyLogin view code


var requestUrl = window.location.href;
var autoRefresh = requestUrl.indexOf("autoRefresh=") != -1;

function doAutoRefresh() {
  var refreshUrl = window.location.href;
  refreshUrl = refreshUrl.replace(window.location.hash, "");
  if (refreshUrl.indexOf("?") == -1) {
    refreshUrl = refreshUrl + "?";
  }
  refreshUrl = refreshUrl.replace(/(.+)&?autoRefresh=([^&]+)(.+)?/, "$1$3");
  refreshUrl = refreshUrl + "&autoRefresh=1";
  refreshUrl = refreshUrl.replace("?&", "?");
  window.location.replace(refreshUrl);
}

var autoRefreshTimer;
function toggleAutoRefresh() {
  if (autoRefresh) {
      autoRefresh = false;
      window.clearTimeout(autoRefreshTimer);
      $("#autorefresh-link").text("Enable Auto-Refresh");
  } else {
      autoRefresh = true;
      autoRefreshTimer = window.setTimeout(doAutoRefresh, 15000);
      $("#autorefresh-link").text("Disable Auto-Refresh");
  }
}
if (autoRefresh) {
  $("#autorefresh-link").text("Disable Auto-Refresh");
  autoRefreshTimer = window.setTimeout(doAutoRefresh, 15000);
} else {
  $("#autorefresh-link").text("Enable Auto-Refresh");
}

Format indent to 2 space.


Package.describe({
  name: "practicalmeteor:loglevel",
  summary: "Simple logger with app and per-package log levels and line number preserving output.",
  version: "1.1.0_2",
  git: "https://github.com/practicalmeteor/meteor-loglevel.git"
});

Package.onUse(function (api) {
  api.versionsFrom('0.9.3');

  api.use(['meteor', 'coffeescript']);

  api.use(['spacejamio:chai']);

  api.addFiles('loglevel.js');
  api.addFiles('LoggerFactory.coffee');

  api.export('loglevel');
});

Package.onTest(function(api) {
  api.use(['coffeescript', 'practicalmeteor:loglevel', 'spacejamio:munit']);
  api.addFiles('tests/LoggerFactoryTest.coffee');
});

Package.describe({
  name: "practicalmeteor:loglevel",
  summary: "Simple logger with app and per-package log levels and line number preserving output.",
  version: "1.1.0_2",
  git: "https://github.com/practicalmeteor/meteor-loglevel.git"
});

Package.onUse(function (api) {
  api.versionsFrom('0.9.3');

  api.use(['meteor', 'coffeescript']);

  api.use(['spacejamio:chai@1.9.2_2']);

  api.addFiles('loglevel.js');
  api.addFiles('LoggerFactory.coffee');

  api.export('loglevel');
});

Package.onTest(function(api) {
  api.use(['coffeescript', 'practicalmeteor:loglevel', 'spacejamio:munit@2.1.1']);
  api.addFiles('tests/LoggerFactoryTest.coffee');
});

Update min required chai and munit versions


Package.describe({
  name: 'svelte:tracker',
  version: '0.0.1',
  summary: 'Tracker integration for Svelte',
  git: 'https://github.com/meteor-svelte/svelte-tracker.git'
});

Package.onUse(function (api) {
  api.versionsFrom('1.4.2.3');

  api.use('tracker');

  // Compile ECMAScript and Svelte in this package
  api.use('ecmascript');
  api.use('svelte:compiler');

  api.mainModule('with-tracker.js');
});

Package.describe({
  name: 'svelte:tracker',
  version: '1.0.0',
  summary: 'Tracker integration for Svelte',
  git: 'https://github.com/meteor-svelte/svelte-tracker.git'
});

Package.onUse(function (api) {
  api.versionsFrom('1.4.2.3');

  api.use('tracker');

  // Compile ECMAScript and Svelte in this package
  api.use('ecmascript');
  api.use('svelte:compiler@1.0.0');

  api.mainModule('with-tracker.js');
});

Bump version to 1.0.0 for first release


import { CREATE_LABEL, LIST_LABELS, REMOVE_LABEL, UPDATE_LABEL } from "../app/actionTypes";
import { insert, remove, update } from "../utils/reducers";

export const initialState = {
    documents: []
};

export default function labelsReducer(state = initialState, action) {
    switch (action.type) {
        case LIST_LABELS.SUCCEEDED:
            return { ...state, documents: action.data };

        case CREATE_LABEL.SUCCEEDED:
            return insert(state, action);

        case UPDATE_LABEL.SUCCEEDED:
            return update(state, action);

        case REMOVE_LABEL.SUCCEEDED:
            return remove(state, action);

        default:
            return state;
    }
}

import { CREATE_LABEL, LIST_LABELS, REMOVE_LABEL, UPDATE_LABEL } from "../app/actionTypes";
import { insert, remove, update } from "../utils/reducers";

export const initialState = {
    documents: null
};

export default function labelsReducer(state = initialState, action) {
    switch (action.type) {
        case LIST_LABELS.SUCCEEDED:
            return { ...state, documents: action.data };

        case CREATE_LABEL.SUCCEEDED:
            return insert(state, action);

        case UPDATE_LABEL.SUCCEEDED:
            return update(state, action);

        case REMOVE_LABEL.SUCCEEDED:
            return remove(state, action);

        default:
            return state;
    }
}

Use `null` as label documents initial state value


angular.module('GLBrowserCrypto', [])
.factory('glbcProofOfWork', ['$q', function($q) {
  // proofOfWork return the answer to the proof of work
  // { [challenge string] -> [ answer index] }
  var str2Uint8Array = function(str) {
    var result = new Uint8Array(str.length);
    for (var i = 0; i < str.length; i++) {
      result[i] = str.charCodeAt(i);
    }
    return result;
  };

  var getWebCrypto = function() {
    if (typeof window !== 'undefined') {
      if (window.crypto) {
        return window.crypto.subtle || window.crypto.webkitSubtle;
      }
      if (window.msCrypto) {
        return window.msCrypto.subtle;
      }
    }
  };

  return {
    proofOfWork: function(str) {
      var deferred = $q.defer();

      var i;

      var xxx = function (hash) {
        hash = new Uint8Array(hash);
        if (hash[31] === 0) {
          deferred.resolve(i);
        } else {
          i += 1;
          work();
        }
      }

      var work = function() {
        var hashme = str2Uint8Array(str + i);
        var damnIE = getWebCrypto().digest({name: "SHA-256"}, hashme);

        if (damnIE.then !== undefined) {
          damnIE.then(xxx);
        } else {
          damnIE.oncomplete = function(r) { xxx(r.target.result); };
        }
      }

      work();

      return deferred.promise;
    }
  };
}]);

angular.module('GLBrowserCrypto', [])
.factory('glbcProofOfWork', ['$q', function($q) {
  // proofOfWork return the answer to the proof of work
  // { [challenge string] -> [ answer index] }
  var str2Uint8Array = function(str) {
    var result = new Uint8Array(str.length);
    for (var i = 0; i < str.length; i++) {
      result[i] = str.charCodeAt(i);
    }
    return result;
  };

  var getWebCrypto = function() {
    if (typeof window !== 'undefined') {
      if (window.crypto) {
        return window.crypto.subtle || window.crypto.webkitSubtle;
      }
      if (window.msCrypto) {
        return window.msCrypto.subtle;
      }
    }
  };

  return {
    proofOfWork: function(str) {
      var deferred = $q.defer();

      var i = 0;

      var xxx = function (hash) {
        hash = new Uint8Array(hash);
        if (hash[31] === 0) {
          deferred.resolve(i);
        } else {
          i += 1;
          work();
        }
      }

      var work = function() {
        var hashme = str2Uint8Array(str + i);
        var damnIE = getWebCrypto().digest({name: "SHA-256"}, hashme);

        if (damnIE.then !== undefined) {
          damnIE.then(xxx);
        } else {
          damnIE.oncomplete = function(r) { xxx(r.target.result); };
        }
      }

      work();

      return deferred.promise;
    }
  };
}]);

Fix initialization of proof of worker index


$(function() {
	yourCollection = new Models.ItemListings();
	friendCollection = new Models.ItemListings();
	featuredCollection = new Models.ItemListings();
	publicCollection = new Models.ItemListings();

	yourView = new Views.ListingView({
		collection: yourCollection,
		el: $('#you-listing')[0]
	});

	friendView = new Views.ListingView({
		collection: friendCollection,
		el: $('#friend-listing')[0]
	});

	featuredView = new Views.ListingView({
		collection: featuredCollection,
		el: $('#featured-listing')[0]
	});

	publicView = new Views.ListingView({
		collection: publicCollection,
		el: $('#public-listing')[0]
	});

	yourCollection.add({
		id: 0,
		name: "Test Item",
		price: "(USD) $5",
		location: "Singapore",
		buyers: [0],
		owner: 0,
		imageUrl: 'http://placehold.it/96x96'
	});
	friendCollection.add({
		id: 1,
		name: "Another Item",
		price: "(USD) $25",
		location: "Singapore",
		buyers: [0, 1, 2],
		owner:  1,
		imageUrl: 'http://placehold.it/96x96'
	});
});

$(function() {
	yourCollection = new Models.ItemListings();
	friendCollection = new Models.ItemListings();
	featuredCollection = new Models.ItemListings();
	publicCollection = new Models.ItemListings();

	yourView = new Views.ListingView({
		collection: yourCollection,
		el: $('#you-listing')[0]
	});

	friendView = new Views.ListingView({
		collection: friendCollection,
		el: $('#friend-listing')[0]
	});

	featuredView = new Views.ListingView({
		collection: featuredCollection,
		el: $('#featured-listing')[0]
	});

	publicView = new Views.ListingView({
		collection: publicCollection,
		el: $('#public-listing')[0]
	});

	yourCollection.add({
		id: 0,
		name: "Test Item",
		price: "5.00 (USD)",
		location: "Singapore",
		buyers: [0],
		owner: 0,
		imageUrl: 'http://placehold.it/96x96'
	});
	friendCollection.add({
		id: 1,
		name: "Another Item",
		price: "25.00 (USD)",
		location: "Singapore",
		buyers: [0, 1, 2],
		owner:  1,
		imageUrl: 'http://placehold.it/96x96'
	});
});

Adjust dummy data to better fit new template.


'use strict';

/* eslint-env node */

let TEST_SELECTOR_PREFIX = /data-test-.*/;

function isTestSelector(attribute) {
  return TEST_SELECTOR_PREFIX.test(attribute);
}

function stripTestSelectors(node) {
  node.params = node.params.filter(function(param) {
    return !isTestSelector(param.original);
  });

  node.hash.pairs = node.hash.pairs.filter(function(pair) {
    return !isTestSelector(pair.key);
  });
}

function transform() {
  return {
    name: 'strip-test-selectors',

    visitor: {
      ElementNode(node) {
        node.attributes = node.attributes.filter(function(attribute) {
          return !isTestSelector(attribute.name);
        });
      },

      MustacheStatement(node) {
        stripTestSelectors(node);
      },

      BlockStatement(node) {
        stripTestSelectors(node);
      },
    }
  };
}

module.exports = transform;

'use strict';

/* eslint-env node */

let TEST_SELECTOR_PREFIX = /data-test-.*/;

function isTestSelector(attribute) {
  return TEST_SELECTOR_PREFIX.test(attribute);
}

function stripTestSelectors(node) {
  node.params = node.params.filter(param => !isTestSelector(param.original));
  node.hash.pairs = node.hash.pairs.filter(pair => !isTestSelector(pair.key));
}

function transform() {
  return {
    name: 'strip-test-selectors',

    visitor: {
      ElementNode(node) {
        node.attributes = node.attributes.filter(attribute => !isTestSelector(attribute.name));
      },

      MustacheStatement(node) {
        stripTestSelectors(node);
      },

      BlockStatement(node) {
        stripTestSelectors(node);
      },
    }
  };
}

module.exports = transform;

Convert callbacks to arrow functions


'use strict';

/* eslint-env node */

let TEST_SELECTOR_PREFIX = /data-test-.*/;

function isTestSelector(attribute) {
  return TEST_SELECTOR_PREFIX.test(attribute);
}

function stripTestSelectors(node) {
  node.params = node.params.filter(function(param) {
    return !isTestSelector(param.original);
  });

  node.hash.pairs = node.hash.pairs.filter(function(pair) {
    return !isTestSelector(pair.key);
  });
}

function StripTestSelectorsTransform() {
  this.syntax = null;
}

StripTestSelectorsTransform.prototype.transform = function(ast) {
  let { traverse } = this.syntax;

  traverse(ast, {
    ElementNode(node) {
      node.attributes = node.attributes.filter(function(attribute) {
        return !isTestSelector(attribute.name);
      });
    },

    MustacheStatement(node) {
      stripTestSelectors(node);
    },

    BlockStatement(node) {
      stripTestSelectors(node);
    },
  });

  return ast;
};

module.exports = StripTestSelectorsTransform;

'use strict';

/* eslint-env node */

let TEST_SELECTOR_PREFIX = /data-test-.*/;

function isTestSelector(attribute) {
  return TEST_SELECTOR_PREFIX.test(attribute);
}

function stripTestSelectors(node) {
  node.params = node.params.filter(function(param) {
    return !isTestSelector(param.original);
  });

  node.hash.pairs = node.hash.pairs.filter(function(pair) {
    return !isTestSelector(pair.key);
  });
}

function transform() {
  return {
    name: 'strip-test-selectors',

    visitor: {
      ElementNode(node) {
        node.attributes = node.attributes.filter(function(attribute) {
          return !isTestSelector(attribute.name);
        });
      },

      MustacheStatement(node) {
        stripTestSelectors(node);
      },

      BlockStatement(node) {
        stripTestSelectors(node);
      },
    }
  };
}

module.exports = transform;

Convert class based template compilation plugin to functional style


import { getters as authGetters } from '../auth';
import { getters as streamGetters } from '../stream';
import { getters as navigationGetters } from '../navigation';

const storage = 'localStorage' in window ? localStorage : {};

const observe = {
  authToken: {
    getter: [
      authGetters.currentAuthToken,
      authGetters.rememberAuth,
      (authToken, rememberAuth) => rememberAuth ? authToken : null,
    ],
    defaultValue: null,
  },
  useStreaming: {
    getter: streamGetters.useStreaming,
    defaultValue: true,
  },
  showSidebar: {
    getter: navigationGetters.showSidebar,
    defaultValue: false,
  },
};

const preferences = {};

Object.keys(observe).forEach(prop => {
  if (!(prop in storage)) {
    storage[prop] = observe[prop].defaultValue;
  }

  Object.defineProperty(preferences, prop, {
    get: () => {
      try {
        return JSON.parse(storage[prop]);
      } catch (err) {
        return observe[prop].defaultValue;
      }
    },
  });
});

preferences.startSync = function startSync(reactor) {
  Object.keys(observe).forEach(prop => {
    const { getter } = observe[prop];
    const valueChanged = function valueChanged(value) {
      storage[prop] = JSON.stringify(value);
    };
    reactor.observe(getter, valueChanged);
    valueChanged(reactor.evaluate(getter));
  });
};

export default preferences;

import { getters as authGetters } from '../auth';
import { getters as streamGetters } from '../stream';
import { getters as navigationGetters } from '../navigation';

function getLocalStorage() {
  if (!('localStorage' in window)) {
    return {};
  }

  const storage = window.localStorage;
  const testKey = '___test';

  try {
    // Safari throws exception in private mode
    storage.setItem(testKey, testKey);
    storage.removeItem(testKey);
    return storage;
  } catch (err) {
    return {};
  }
}

const storage = getLocalStorage();

const observe = {
  authToken: {
    getter: [
      authGetters.currentAuthToken,
      authGetters.rememberAuth,
      (authToken, rememberAuth) => rememberAuth ? authToken : null,
    ],
    defaultValue: null,
  },
  useStreaming: {
    getter: streamGetters.useStreaming,
    defaultValue: true,
  },
  showSidebar: {
    getter: navigationGetters.showSidebar,
    defaultValue: false,
  },
};

const preferences = {};

Object.keys(observe).forEach(prop => {
  if (!(prop in storage)) {
    storage[prop] = observe[prop].defaultValue;
  }

  Object.defineProperty(preferences, prop, {
    get: () => {
      try {
        return JSON.parse(storage[prop]);
      } catch (err) {
        return observe[prop].defaultValue;
      }
    },
  });
});

preferences.startSync = function startSync(reactor) {
  Object.keys(observe).forEach(prop => {
    const { getter } = observe[prop];
    const valueChanged = function valueChanged(value) {
      storage[prop] = JSON.stringify(value);
    };
    reactor.observe(getter, valueChanged);
    valueChanged(reactor.evaluate(getter));
  });
};

export default preferences;

Fix local storage problems with private mode Safari


describe('Game Manager', function() {

    it('is an object', function() {
        expect(Flown.GameManager).toBeDefined();
        expect(typeof Flown.GameManager).toBe('object');
    });

    describe('creating a game manager', function() {

        it('has a create function', function() {
            expect(Flown.GameManager.create).toBeDefined();
            expect(typeof Flown.GameManager.create).toBe('function');
        });

        it('creates a new game manager object', function() {
            var gameManager = Flown.GameManager.create();

            expect(gameManager).toBeDefined();
            expect(gameManager._levelManager).toBeDefined();
            expect(gameManager._gameBoard).toBeDefined();
        });

    });

    describe('initialising a game manager', function() {

        var gameManager;

        beforeEach(function() {
            gameManager = Flown.GameManager.create();
        });

        it('has an init function', function() {
            expect(Flown.GameManager.init).toBeDefined();
            expect(typeof Flown.GameManager.init).toBe('function');
        });

        it('sets the initial values of the game manager object', function() {
            var gameManager = Flown.GameManager.create();

            expect(gameManager._levelManager).toBeDefined();
            expect(gameManager._gameBoard).toBeDefined();
        });

    });

});

describe('Game Manager', function() {

    it('is an object', function() {
        expect(Flown.GameManager).toBeDefined();
        expect(typeof Flown.GameManager).toBe('object');
    });

    describe('creating a game manager', function() {

        it('has a create function', function() {
            expect(Flown.GameManager.create).toBeDefined();
            expect(typeof Flown.GameManager.create).toBe('function');
        });

        it('creates a new game manager object', function() {
            var gameManager = Flown.GameManager.create();

            expect(gameManager).toBeDefined();
            expect(gameManager._levelManager).toBeDefined();
            expect(gameManager._gameBoard).toBeDefined();
        });

    });

    describe('initialising a game manager', function() {

        var gameManager;

        beforeEach(function() {
            Flown.DefaultLevels = exampleLevelPacks1;
            window.localStorage.setItem('flown.extrapacks', JSON.stringify(exampleLevelPacks2));
            gameManager = Flown.GameManager.create();
        });

        afterEach(function() {
            window.localStorage.clear('flown.extrapacks');
        });

        it('has an init function', function() {
            expect(Flown.GameManager.init).toBeDefined();
            expect(typeof Flown.GameManager.init).toBe('function');
        });

        it('sets the initial values of the game manager object', function() {
            expect(gameManager._levelManager).toBeDefined();
            expect(gameManager._gameBoard).toBeDefined();
            expect(gameManager._levelManager._packs.length).toBe(exampleLevelPacks1.length + exampleLevelPacks2.length);
        });

    });

});

Add tests for adding default and installed level packs


Package.describe({
  git: 'https://github.com/zimme/meteor-collection-softremovable',
  name: 'zimme:collection-softremovable',
  summary: 'Add soft remove to collections',
  version: '1.0.3-rc.1'
});

Package.onUse(function(api) {
  api.versionsFrom('1.0');

  api.use([
    'coffeescript',
    'underscore'
  ]);

  api.use([
    'matb33:collection-hooks@0.7.6',
    'zimme:collection-behaviours@1.0.2'
  ]);

  api.use([
    'aldeed:autoform@4.0.0',
    'aldeed:collection2@2.0.0',
    'aldeed:simple-schema@1.0.3'
  ], ['client', 'server'], {weak: true});

  api.imply('zimme:collection-behaviours');

  api.addFiles('softremovable.coffee');
});

Package.describe({
  git: 'https://github.com/zimme/meteor-collection-softremovable.git',
  name: 'zimme:collection-softremovable',
  summary: 'Add soft remove to collections',
  version: '1.0.3-rc.1'
});

Package.onUse(function(api) {
  api.versionsFrom('1.0');

  api.use([
    'coffeescript',
    'underscore'
  ]);

  api.use([
    'matb33:collection-hooks@0.7.6',
    'zimme:collection-behaviours@1.0.2'
  ]);

  api.use([
    'aldeed:autoform@4.0.0',
    'aldeed:collection2@2.0.0',
    'aldeed:simple-schema@1.0.3'
  ], ['client', 'server'], {weak: true});

  api.imply('zimme:collection-behaviours');

  api.addFiles('softremovable.coffee');
});

Add .git to git url


'use strict';

var PerfTestFramework = require('../../../TestServer/PerfTestFramework.js');
var TestDevice = require('../../../TestServer/TestDevice.js');
var tape = require('../lib/thali-tape');

var test = tape({
  setup: function(t) {
    t.end();
  },
  teardown: function(t) {
    t.end();
  }
});

test('#should be able to add devices to the framework', function (t) {
  var testConfig = JSON.parse('{"devices":{"ios":2}, "honorCount":true}');
  var perfTestFramework = new PerfTestFramework(testConfig);
  var testDevice = new TestDevice(null, "a device", "ios", "perftest", [], null);
  perfTestFramework.addDevice(testDevice);
  console.log(perfTestFramework);
  t.equal(perfTestFramework.devices["ios"].length, 1);
  t.end();
});

'use strict';

var PerfTestFramework = require('../../../TestServer/PerfTestFramework.js');
var TestDevice = require('../../../TestServer/TestDevice.js');
var tape = require('../lib/thali-tape');

var test = tape({
  setup: function(t) {
    t.end();
  },
  teardown: function(t) {
    t.end();
  }
});

test('should be able to add devices to the framework', function (t) {
  var testConfig = JSON.parse('{"devices":{"ios":2}, "honorCount":true}');
  var perfTestFramework = new PerfTestFramework(testConfig);
  var testDevice = new TestDevice(null, 'Some name', 'uuid', 'ios', 'perftest', [], null);
  perfTestFramework.addDevice(testDevice);
  t.equal(perfTestFramework.devices['ios'].length, 1);
  t.end();
});

Fix add device meta test.


import 'core-js/es6/promise';
import 'core-js/es6/symbol';
import 'moment-timezone/moment-timezone';
import 'moment/locale/ru';

import moment from 'moment';

import timezones from '../data/timezones.json';
import App from './core/app';

moment.tz.load(timezones);

document.addEventListener('DOMContentLoaded', (event) => {
  const app = new App();
  app.run();
});

import 'core-js/es5';
import 'core-js/es6/promise';
import 'core-js/es6/symbol';
import 'core-js/es6/array';

import 'moment-timezone/moment-timezone';
import 'moment/locale/ru';

import moment from 'moment';

import timezones from '../data/timezones.json';
import App from './core/app';

moment.tz.load(timezones);

document.addEventListener('DOMContentLoaded', (event) => {
  const app = new App();
  app.run();
});

Add additional polyfills required to make this work on original iPad


// ==UserScript==
// @name	pmd-rp-beta
// @namespace	coaster3000@pmd-roleplay.forumotion.com
// @description	Provides the necessary userscripts to test out features for the site. These are scripts that will be placed on the site.
// @include	http://pmd-roleplay.forumotion.org/*
// @include	https://pmd-roleplay.forumotion.org/*
// @match	https://pmd-roleplay.forumotion.org/*
// @match	http://pmd-roleplay.foruomtion.org/*
// @version	@VERSION@
// @grant	none
// ==/UserScript==

// ==UserScript==
// @name	pmd-rp-beta
// @namespace	coaster3000@pmd-roleplay.forumotion.com
// @description	A userscript that provides all the custom new site features that are being tested. Once fully tested, these features are removed from this script and added to the site for global use.
// @include	http://pmd-roleplay.forumotion.org/*
// @include	https://pmd-roleplay.forumotion.org/*
// @match	https://pmd-roleplay.forumotion.org/*
// @match	http://pmd-roleplay.foruomtion.org/*
// @version	@VERSION@
// @updateURL https://github.com/coaster3000/pmd-roleplay-beta/raw/releases/dist/pmd-roleplay-beta.meta.js
// @downloadURL https://github.com/coaster3000/pmd-roleplay-beta/raw/releases/dist/pmd-roleplay-beta.user.js
// @grant	none
// ==/UserScript==

Update script header, implementing auto updater support.


const webpack = require('webpack')
const { execSync } = require('child_process')
const path = require('path')

let hash = execSync('git rev-parse --short HEAD').toString().trim()
let lang = process.env.ESP_LANG || 'en'

let plugins = []
let devtool = 'source-map'

if (process.env.ESP_PROD) {
  // ignore demo
  plugins.push(new webpack.IgnorePlugin(/\.\/demo(?:\.js)?$/))

  // no source maps
  devtool = ''
}

plugins.push(new webpack.optimize.UglifyJsPlugin({
  sourceMap: devtool === 'source-map'
}))

// replace "locale-data" with path to locale data
let locale = process.env.ESP_LANG || 'en'
plugins.push(new webpack.NormalModuleReplacementPlugin(
  /^locale-data$/,
  path.resolve(`lang/${locale}.php`)
))

module.exports = {
  entry: './js',
  output: {
    path: path.resolve(__dirname, 'out', 'js'),
    filename: `app.${hash}-${lang}.js`
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: [
          path.resolve(__dirname, 'node_modules')
        ],
        loader: 'babel-loader'
      },
      {
        test: /lang\/.+?\.php$/,
        loader: './lang/_js-lang-loader.js'
      }
    ]
  },
  devtool,
  plugins
}

const webpack = require('webpack')
const { execSync } = require('child_process')
const path = require('path')

let hash = execSync('git rev-parse --short HEAD').toString().trim()
let lang = process.env.ESP_LANG || 'en'

let plugins = []
let devtool = 'source-map'

if (process.env.ESP_PROD) {
  // ignore demo
  plugins.push(new webpack.IgnorePlugin(/(term|\.)\/demo(?:\.js)?$/))

  // no source maps
  devtool = ''
}

plugins.push(new webpack.optimize.UglifyJsPlugin({
  sourceMap: devtool === 'source-map'
}))

// replace "locale-data" with path to locale data
let locale = process.env.ESP_LANG || 'en'
plugins.push(new webpack.NormalModuleReplacementPlugin(
  /^locale-data$/,
  path.resolve(`lang/${locale}.php`)
))

module.exports = {
  entry: './js',
  output: {
    path: path.resolve(__dirname, 'out', 'js'),
    filename: `app.${hash}-${lang}.js`
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: [
          path.resolve(__dirname, 'node_modules')
        ],
        loader: 'babel-loader'
      },
      {
        test: /lang\/.+?\.php$/,
        loader: './lang/_js-lang-loader.js'
      }
    ]
  },
  devtool,
  plugins
}

Fix demo being loaded regardless of ESP_PROD


var HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {

	devServer: {
		inline: true,
		context: 'build',
		port: 9999,
		proxy: {
			'/api': {
				target: 'http://localhost:8080'
			}
		}
	},

	entry: './main.js',
	output: {
		path: 'build/',
		filename: 'app.js'
	},
	sassLoader: {
		includePaths: ['./node_modules/foundation-sites/scss']
	},
	module: {
		loaders: [
			{
				test: /\.scss$/,
				loader: 'style!css!sass'
			},
			{
				test: /\.jsx?$/,
				loader: 'babel-loader',
				exclude: /node_modules/,
				cacheDirectory: true
			}
		]
	},
	plugins: [
		new HtmlWebpackPlugin({
			template: './index.html'
		}),
	]
}

// Webpack Plugins
var HtmlWebpackPlugin = require('html-webpack-plugin')
var ExtractTextPlugin = require('extract-text-webpack-plugin')

// Post-css plugins
var autoprefixer = require('autoprefixer')

module.exports = {

	devServer: {
		inline: true,
		context: 'build',
		port: 9999,
		proxy: {
			'/api': {
				target: 'http://localhost:8080'
			}
		}
	},
	entry: './main.js',
	output: {
		path: 'build/',
		filename: 'app.js'
	},
	sassLoader: {
		includePaths: ['./node_modules/foundation-sites/scss']
	},
	postcss: [autoprefixer({browsers: ['last 2 versions']})],
	module: {
		loaders: [
			{
				test: /\.scss$/,
				loader: ExtractTextPlugin.extract(
					'style-loader',
					'css-loader!postcss-loader!sass-loader'
				)
			},
			{
				test: /\.jsx?$/,
				loader: 'babel-loader',
				exclude: /node_modules/,
				cacheDirectory: true
			}
		]
	},
	plugins: [
		new HtmlWebpackPlugin({
			template: './index.html'
		}),
		new ExtractTextPlugin('styles.[contenthash].css', {allChunks: true}),
	]
}

Build css to file, apply postcss autoprefixer


(function (undefined) {
'use strict';

var mock = function ($httpBackend, fxCampuses) {
  $httpBackend.when('GET', '/api/campuses').respond(fxCampuses);
  $httpBackend.when('GET', '/api/campuses/1').respond(fxCampuses.objects[0]);
};

if (module !== undefined && module.exports) {
  // for e2e testing
  var fxCampuses = require('./campuses.json');
  module.exports = function ($httpBackend) {
    return mock.call(this, $httpBackend, fxCampuses);
  };
} else {
  // for unit testing
  angular.module('dashApp.mock.campuses', ['dashApp.mock.common'])
    .factory('fxCampuses', function () {
      return getJSONFixture('campuses.json');
    })
    .run(mock);
}
})();

(function (undefined) {
'use strict';

var mock = function ($httpBackend, fxCampuses) {
  $httpBackend.when('GET', '/api/campuses').respond(fxCampuses);
  fxCampuses.objects.forEach(function (campus) {
    $httpBackend.when('GET', '/api/campuses/' + campus.id).respond(campus);
  });
};

if (module !== undefined && module.exports) {
  // for e2e testing
  var fxCampuses = require('./campuses.json');
  module.exports = function ($httpBackend) {
    return mock.call(this, $httpBackend, fxCampuses);
  };
} else {
  // for unit testing
  angular.module('dashApp.mock.campuses', ['dashApp.mock.common'])
    .factory('fxCampuses', function () {
      return getJSONFixture('campuses.json');
    })
    .run(mock);
}
})();

Update campus API endpoint mock to serve all


var getCleanCSSProcessor = require("./clean-css-processor"),
    usage = require("./usage"),
    parseOptions = require("./parse-options");

module.exports = {
    install: function(less, pluginManager) {
        var CleanCSSProcessor = getCleanCSSProcessor(less);
        pluginManager.addPostProcessor(new CleanCSSProcessor(this.options));
    },
    printUsage: function () {
        usage.printUsage();
    },
    setOptions: function(options) {
        this.options = parseOptions(options);
    },
    minVersion: [2, 0, 0]
};

var getCleanCSSProcessor = require("./clean-css-processor"),
    usage = require("./usage"),
    parseOptions = require("./parse-options");

function LessPluginCleanCSS(options) {
    this.options = options;
};

LessPluginCleanCSS.prototype = {
    install: function(less, pluginManager) {
        var CleanCSSProcessor = getCleanCSSProcessor(less);
        pluginManager.addPostProcessor(new CleanCSSProcessor(this.options));
    },
    printUsage: function () {
        usage.printUsage();
    },
    setOptions: function(options) {
        this.options = parseOptions(options);
    },
    minVersion: [2, 0, 0]
};

module.exports = LessPluginCleanCSS;

Fix usage to allow creating as an object with an options argument


module.exports = function (sails) {
  return {};
};

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

module.exports = function (sails) {
  return {
    defaults: {},

    initialize: function (cb) {
      var config = sails.config.cron;
      var tasks = Object.keys(config);

      tasks.forEach(function (time) {
        return createJob({
          cronTime: time,
          onTick: config[time] instanceof Function ? config[time] : config[time].onTick,
          onComplete: config[time].onComplete,
          timezone: config[time].timezone
        });
      });

      cb();
    }
  };
};

Implement basic cron tasks loading


(function (module) {
    module.component('mcProjectProcesses', {
        templateUrl: 'components/project/processes/mc-project-processes.html',
        controller: 'MCProjectProcessesComponentController'
    });

    module.controller('MCProjectProcessesComponentController', MCProjectProcessesComponentController);
    MCProjectProcessesComponentController.$inject = ["project", "$stateParams"];
    function MCProjectProcessesComponentController(project, $stateParams) {
        var ctrl = this;

        ctrl.processes = project.get().processes;

        ///////////////////////////
    }
}(angular.module('materialscommons')));

(function (module) {
    module.component('mcProjectProcesses', {
        templateUrl: 'components/project/processes/mc-project-processes.html',
        controller: 'MCProjectProcessesComponentController'
    });

    module.controller('MCProjectProcessesComponentController', MCProjectProcessesComponentController);
    MCProjectProcessesComponentController.$inject = ['project', '$stateParams', '$state', '$filter'];
    function MCProjectProcessesComponentController(project, $stateParams, $state, $filter) {
        var ctrl = this;

        ctrl.processes = project.get().processes;

        if (!$stateParams.process_id && ctrl.processes.length) {
            ctrl.processes = $filter('orderBy')(ctrl.processes, 'name');
            var firstProcess = ctrl.processes[0];
            $state.go('project.processes.process', {process_id: firstProcess.id});
        }

        ///////////////////////////
    }
}(angular.module('materialscommons')));

Sort processes and if there is no process id go to the first one.


'use strict';
const Chalk = require('chalk').constructor;

let ctx = null;
exports.get = () => {
	if (!ctx) {
		throw new Error('Chalk has not yet been configured');
	}
	return ctx;
};
exports.set = options => {
	if (ctx) {
		throw new Error('Chalk has already been configured');
	}
	ctx = new Chalk(options);
};

'use strict';
const Chalk = require('chalk').constructor;

let ctx = null;
exports.get = () => {
	if (!ctx) {
		throw new Error('Chalk has not yet been configured');
	}
	return ctx;
};
exports.set = options => {
	if (ctx) {
		throw new Error('Chalk has already been configured');
	}
	ctx = new Chalk(options);
	return ctx;
};

Return Chalk instance from set() call


'use strict';
module.exports =

/*@ngInject*/
function index ($scope, RuleQuestionService) {
  RuleQuestionService._getAllRuleQuestionsWithInstructions().then(function(questions) {
    var i = 39;
    $scope.showNextQuestion = false;
    $scope.showPreviousQuestion = false;
    $scope.currentRuleQuestion = questions[i];
    $scope.nextQuestion = function() {
      $scope.currentRuleQuestion = questions[++i];
    };
    $scope.previousQuestion = function() {
      $scope.currentRuleQuestion = questions[--i];
    };

    $scope.$watch('currentRuleQuestion', function() {
      if (!$scope.currentRuleQuestion) {
        $scope.finish();
      }
      $scope.showNextQuestion =
        i < questions.length &&
        ($scope.currentRuleQuestion && $scope.currentRuleQuestion.correct);

      $scope.showPreviousQuestion = i > 0;
    }, true);

    $scope.finish = function() {
      console.log(i);
      console.log('Thanks for playing');
      $scope.showNextQuestion = false;
      $scope.showPreviousQuestion = false;
    };
  });
};

'use strict';
module.exports =

/*@ngInject*/
function index ($scope, RuleQuestionService) {
  RuleQuestionService._getAllRuleQuestionsWithInstructions().then(function(questions) {
    var i = 0;
    $scope.showNextQuestion = false;
    $scope.showPreviousQuestion = false;
    $scope.currentRuleQuestion = questions[i];
    $scope.nextQuestion = function() {
      $scope.currentRuleQuestion = questions[++i];
    };
    $scope.previousQuestion = function() {
      $scope.currentRuleQuestion = questions[--i];
    };

    $scope.$watch('currentRuleQuestion', function() {
      if (!$scope.currentRuleQuestion) {
        $scope.finish();
      }
      $scope.showNextQuestion =
        i < questions.length &&
        ($scope.currentRuleQuestion && $scope.currentRuleQuestion.correct);

      $scope.showPreviousQuestion = i > 0;
    }, true);

    $scope.finish = function() {
      console.log(i);
      console.log('Thanks for playing');
      $scope.showNextQuestion = false;
      $scope.showPreviousQuestion = false;
    };
  });
};

Reset rqs index to 0


var winston = require('winston'),
    path    = require('path');

var logLevel = 'warn';

var sharedLogger = function(filename) {
  return new winston.Logger({
    transports: [
      new winston.transports.Console({
        prettyPrint: true,
        timestamp: true,
        level: logLevel,
        label: path.basename(filename)
      })
    ]
  });
};

sharedLogger.setLevel = function (val) {
  logLevel = val;
}

function failedPromiseHandler(logger) {
  logger = logger || sharedLogger;

  return function(err) {
    logger.error(err.stack);
  };
}

function uuid() {
	return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
    var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
    return v.toString(16);
  });
}

module.exports = {
  logger: sharedLogger,
  failedPromiseHandler: failedPromiseHandler,
  uuid: uuid
}

var winston = require('winston'),
    path    = require('path');

var logLevel = 'warn';

var sharedLogger = function(filename) {
  return new winston.Logger({
    transports: [
      new winston.transports.Console({
        prettyPrint: true,
        timestamp: true,
        level: logLevel,
        label: path.basename(filename, '.js')
      })
    ]
  });
};

sharedLogger.setLevel = function (val) {
  logLevel = val;
}

function failedPromiseHandler(logger) {
  logger = logger || sharedLogger;

  return function(err) {
    logger.error(err.stack);
  };
}

function uuid() {
	return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
    var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
    return v.toString(16);
  });
}

module.exports = {
  logger: sharedLogger,
  failedPromiseHandler: failedPromiseHandler,
  uuid: uuid
}

Remove js extension from log labels


Object.values = function(obj) {
    var vals = [];
    for( var key in obj ) {
        if ( obj.hasOwnProperty(key) ) {
            vals.push(obj[key]);
        }
    }
    return vals;
}

Object.values = function(obj) {
    var vals = [];
    for( var key in obj ) {
        if ( obj.hasOwnProperty(key) ) {
            if(typeof(obj[key]) === 'object') {
              vals.push(JSON.stringify(obj[key]));
            } else {
              vals.push(obj[key])
            }
        }
    }
    return vals;
}

Add support for json columns


// ==UserScript==
// @name        Wanikani for Vimperator
// @namespace   rr-@sakuya.pl
// @description Improves Wanikani navigation on Vimperator
// @include     https://www.wanikani.com/review/session
// @version     1
// @grant       none
// ==/UserScript==

function convertLinksInReviews()
{
    $('#additional-content li').each(function(i, $li)
    {
        var eventHandler = $._data($li, 'events')[0];
        var $a = $('<a/>').click(eventHandler);
        $('span', $li).wrapInner($a);
    });
}

$(function()
{
    convertLinksInReviews();
});

// ==UserScript==
// @name        Wanikani for Vimperator
// @namespace   rr-@sakuya.pl
// @description Improves Wanikani navigation on Vimperator
// @include     https://www.wanikani.com/review/session
// @include     https://www.wanikani.com/lesson/session
// @version     1
// @grant       none
// ==/UserScript==

function convertLinksInReviews()
{
    // buttons under the main input
    $('#additional-content li').each(function(i, li)
    {
        var $li = $(li);
        $('span', $li).wrapInner($('<a/>').click(function() { $li.click(); }));
    });
}

function convertLinksInLessons()
{
    // top tabs
    $('#supplement-nav').bind('DOMSubtreeModified', function()
    {
        $('#supplement-nav li').each(function(i, li)
        {
            var $li = $(li);
            // link already added?
            if ($li.find('a').length > 0)
                return;
            $li.wrapInner($('<a/>').click(function() { $li.click(); }));
        });
    });
}

$(function()
{
    convertLinksInReviews();
    convertLinksInLessons();
});

Add links to top tabs in lessons


$(function() {
  $('a[href*=#]:not([href=#])').click(function() {
    if (location.pathname.replace(/^\//,'') == this.pathname.replace(/^\//,'') && location.hostname == this.hostname) {
      var target = $(this.hash);
      target = target.length ? target : $('[name=' + this.hash.slice(1) +']');
      if (target.length) {
        $('html,body').animate({
          scrollTop: target.offset().top
        }, 1000);
        return false;
      }
    }
  });
});

$(function() {
  $('a[href*=\\#]:not([href=\\#])').click(function() {
    if (location.pathname.replace(/^\//,'') == this.pathname.replace(/^\//,'') && location.hostname == this.hostname) {
      var target = $(this.hash);
      target = target.length ? target : $('[name=' + this.hash.slice(1) +']');
      if (target.length) {
        $('html,body').animate({
          scrollTop: target.offset().top
        }, 1000);
        return false;
      }
    }
  });
});

Fix smooth scroll, thanks @frmendes


var DigitalOcean = require('do-wrapper')
var Promise = require('bluebird')

module.exports = function initializeModule (options) {
  var api = Promise.promisifyAll(new DigitalOcean(options.DOApiKey, 10))

  function testAccountKey () {
    api.account(function (err, res, body) {
      if (err) { console.log(err) }
      console.log(body)
    })
  }

  // TODO Set domainName and domainRecordID in env.
  function setDNS (domainName, domainRecordId, hash) {
    api.domainRecordsUpdate(domainName, domainRecordId, {
      type: 'TXT',
      name: 'h',
      data: hash
    }, function (err, data) {
      if (err) {
        console.log(err)
      } else {
        console.log('Success:', data)
      }
    })
  }

  return {
    testAccountKey: testAccountKey,
    setDNS: setDNS
  }
}

var DigitalOcean = require('do-wrapper')
var Promise = require('bluebird')

module.exports = function initializeModule (options) {
  var api = Promise.promisifyAll(new DigitalOcean(options.DOApiKey, 10))

  function testAccountKey () {
    api.account(function (err, res, body) {
      if (err) { console.log(err) }
      console.log(body)
    })
  }

  // TODO Set domainName and domainRecordID in env.
  function setDNS (domainName, domainRecordId, domainRecordName, path) {
    api.domainRecordsUpdate(domainName, domainRecordId, {
      type: 'TXT',
      name: domainRecordName,
      data: 'dnslink='+path
    }, function (err, data) {
      if (err) {
        console.log(err)
      } else {
        console.log('Success:', data)
      }
    })
  }

  return {
    testAccountKey: testAccountKey,
    setDNS: setDNS
  }
}

Build proper dnslink, pass recordName as parameter


#!/usr/bin/env node

process.title = 'timeline_serve';

var _ = require('underscore')._;
var express = require('express');
var socket = require('socket.io');
var fs = require('fs');

var app = express();
app.configure(function () {
    app.use(express.static(__dirname + '/../public'));
});
var server = app.listen(8888);

function readData () {
    var fileNames = fs.readdirSync('./');
    return _.reduce(fileNames, function (memo, filename) {
        var stat = fs.statSync(filename);
        if (stat.isFile()) {
            memo[filename] = fs.readFileSync(filename, 'utf8');
        }
        return memo;
    }, {});
}

var io = socket.listen(server);
io.configure(function () {
    io.set("log level", 1);
});
io.sockets.on('connection', function (socket) {
    socket.emit('data', readData());
});

var watch = require('node-watch');
watch(__dirname + '/../public', function () {
    io.sockets.emit('reload');
});
watch('./', function () {
    io.sockets.emit('data', readData());
});

console.log('Timeline server started.');
console.log('Reading timeline files from ' + fs.realpathSync('./'));
console.log('Point your browser to http://localhost:8888/');

#!/usr/bin/env node

process.title = 'timeline_serve';

var _ = require('underscore')._;
var express = require('express');
var socket = require('socket.io');
var fs = require('fs');

var port = process.env.PORT;
var app = express();
app.configure(function () {
    app.use(express.static(__dirname + '/../public'));
});
var server = app.listen(port);

function readData () {
    var fileNames = fs.readdirSync('./');
    return _.reduce(fileNames, function (memo, filename) {
        var stat = fs.statSync(filename);
        if (stat.isFile()) {
            memo[filename] = fs.readFileSync(filename, 'utf8');
        }
        return memo;
    }, {});
}

var io = socket.listen(server);
io.configure(function () {
    io.set("log level", 1);
});
io.sockets.on('connection', function (socket) {
    socket.emit('data', readData());
});

var watch = require('node-watch');
watch(__dirname + '/../public', function () {
    io.sockets.emit('reload');
});
watch('./', function () {
    io.sockets.emit('data', readData());
});

console.log('Timeline server started.');
console.log('Reading timeline files from ' + fs.realpathSync('./'));
console.log('Point your browser to http://localhost:' + port + '/');

Add option to set PORT via env variable


var sprintf = require('sprintf-js').sprintf;

module.exports = {
  gamedayUrlRoot: gamedayUrlRoot,
  ordinal: ordinal,
  ensureArray: ensureArray,
};

function gamedayUrlRoot() {
  var today = new Date();
  return sprintf(
      'http://mlb.mlb.com/gdcross/components/game/mlb/year_%d/month_%02d/day_%02d/',
      today.getFullYear(), today.getMonth() + 1, today.getDate());
}

function ordinal(num) {
  var rem = num % 10;

  if (rem === 1 && num !== 11) {
    return num + 'st';
  }

  if (rem === 2 && num !== 12) {
    return num + 'nd';
  }

  if (rem === 3 && num !== 13) {
    return num + 'rd';
  }

  return num + 'th';
}

function ensureArray(object, field) {
  if (!(object[field] instanceof Array)) {
    object[field] = [object[field]];
  }
}

var sprintf = require('sprintf-js').sprintf;

module.exports = {
  ordinal: ordinal,
  ensureArray: ensureArray,
};

function ordinal(num) {
  var rem = num % 10;

  if (rem === 1 && num !== 11) {
    return num + 'st';
  }

  if (rem === 2 && num !== 12) {
    return num + 'nd';
  }

  if (rem === 3 && num !== 13) {
    return num + 'rd';
  }

  return num + 'th';
}

function ensureArray(object, field) {
  if (!(object[field] instanceof Array)) {
    object[field] = [object[field]];
  }
}

Remove gameday root url util method


const svgo = require('svgo');
const { omit, concat, uniqBy } = require('lodash');
const { merge } = require('webpack-merge');

module.exports = (options, pre = [], post = []) => {
    try {
        // The preset-default plugin is only available since SVGO 2.4.0
        svgo.optimize('', {
            plugins: [{
                name: 'preset-default'
            }]
        });

        return merge({}, omit(options, ['plugins']), {
            plugins: [{
                name: 'preset-default',
                params: {
                    overrides: uniqBy(concat(pre, options.plugins, post).reverse(), 'name').reduce((overrides, plugin) => ({
                        ...overrides,
                        [plugin.name]: plugin.active !== false
                            ? plugin.params
                            : false
                    }), {})
                }
            }, ...options.plugins]
        });
    } catch (error) {
        // Fall back to extendDefaultPlugins which is deprecated since 2.4.0
        return merge({}, omit(options, ['plugins']), {
            plugins: uniqBy(concat(pre, svgo.extendDefaultPlugins(options.plugins), post).reverse(), 'name')
        });
    }
}

const svgo = require('svgo');
const { omit, concat, uniqBy } = require('lodash');
const { merge } = require('webpack-merge');

module.exports = (options, pre = [], post = []) => {
    try {
        // The preset-default plugin is only available since SVGO 2.4.0
        svgo.optimize('', {
            plugins: [{
                name: 'preset-default'
            }]
        });

        const names = concat(pre, post).map((plugin) => plugin.name);
        return merge({}, omit(options, ['plugins']), {
            plugins: [{
                name: 'preset-default',
                params: {
                    overrides: uniqBy(concat(pre, options.plugins, post).reverse(), 'name').reduce((overrides, plugin) => ({
                        ...overrides,
                        [plugin.name]: plugin.active !== false
                            ? plugin.params
                            : false
                    }), {})
                }
            }, ...options.plugins.filter((plugin) => {
                return !names.includes(plugin.name);
            })]
        });
    } catch (error) {
        // Fall back to extendDefaultPlugins which is deprecated since 2.4.0
        return merge({}, omit(options, ['plugins']), {
            plugins: uniqBy(concat(pre, svgo.extendDefaultPlugins(options.plugins), post).reverse(), 'name')
        });
    }
}

Fix pre/post plugins for generateSVGOConfig helper not being overwritten properly


import React, { Component } from 'react'

import globalConfig from '../../config'

import { fetchLocation, fetchOrganization } from '../../core/firebaseRestAPI'

import Layout from '../../components/Layout'
import Loading from '../../components/Loading'
import Location from '../../components/Location'

export default class LocationPage extends Component {
  constructor(props) {
    super(props)
    this.state = {
      location: null,
      organization: null,
    }
  }

  componentDidMount() {
    const match = this.props.route.pattern.exec(window.location.pathname)
    const locationId = match[1]

    fetchLocation(locationId)
      .then(location => {
        this.setState({ location })
      })
    fetchOrganization(location.organizationId)
      .then(organization => {
        this.setState({ organization })
      })
  }

  componentDidUpdate() {
    const { location } = this.state
    document.title = location ? location.name : globalConfig.title
  }

  render() {
    const { location, organization } = this.state

    return (
      <Layout>
        {(location && organization) ?
          <Location location={location} organization={organization} /> :
          <Loading />
        }
      </Layout>
    )
  }
}

import React, { Component } from 'react'

import globalConfig from '../../config'

import { fetchLocation, fetchOrganization } from '../../core/firebaseRestAPI'

import Layout from '../../components/Layout'
import Loading from '../../components/Loading'
import Location from '../../components/Location'

export default class LocationPage extends Component {
  constructor(props) {
    super(props)
    this.state = {
      location: null,
      organization: null,
    }
  }

  componentDidMount() {
    const match = this.props.route.pattern.exec(window.location.pathname)
    const locationId = match[1]

    fetchLocation(locationId)
      .then(location => {
        this.setState({ location })
        fetchOrganization(location.organizationId)
          .then(organization => {
            this.setState({ organization })
          })
      })
  }

  componentDidUpdate() {
    const { location } = this.state
    document.title = location ? location.name : globalConfig.title
  }

  render() {
    const { location, organization } = this.state

    return (
      <Layout>
        {(location && organization) ?
          <Location location={location} organization={organization} /> :
          <Loading />
        }
      </Layout>
    )
  }
}

Load orgs inside location promise


import nodemailer from 'nodemailer';
import smtpTransport from 'nodemailer-smtp-transport';

function sendEmail(emailParams) {
  const mailOptions = {
    from: emailParams.recepientAddress,
    to: emailParams.senderAddress,
    subject: emailParams.subject,
    html: emailParams.emailBody
  };

  const transporter = nodemailer.createTransport(smtpTransport({
    service: 'gmail',
    port: 465,
    auth: {
      user: 'mazi.mary.o@gmail.com',
      pass: process.env.EMAIL_PASSWORD
    }
  }));

  transporter.sendMail(mailOptions, (error, info) => {
    if (error) {
      console.log(error);
    } else {
      console.log(`Email sent:${info.response}`);
    }
  });
}

export default sendEmail;

import nodemailer from 'nodemailer';
import smtpTransport from 'nodemailer-smtp-transport';

function sendEmail(emailParams) {
  const mailOptions = {
    from: emailParams.recepientAddress,
    to: emailParams.senderAddress,
    subject: emailParams.subject,
    html: emailParams.emailBody
  };

  const transporter = nodemailer.createTransport(smtpTransport({
    service: 'gmail',
    port: 465,
    auth: {
      user: 'noreply.swiftpost@gmail.com',
      pass: process.env.EMAIL_PASSWORD
    }
  }));

  transporter.sendMail(mailOptions, (error, info) => {
    if (error) {
      console.log(error);
    } else {
      console.log(`Email sent:${info.response}`);
    }
  });
}

export default sendEmail;

Change email address for sending email notifications


import React from 'react'
import { ImageBackground, ActivityIndicator, View } from 'react-native'
import css from '../../styles/css'

class SafeImage extends React.Component {
	constructor(props) {
		super(props)
		this.state = { validImage: true, loading: true }
	}

	_handleError = (event) => {
		console.log(`Error loading image ${this.props.source.uri}:`, event.nativeEvent.error)
		this.setState({ validImage: false })
	}

	_handleOnLoadEnd = () => {
		this.setState({ loading: false })
	}

	render() {
		let selectedResizeMode = 'contain'
		if (this.props.resizeMode) {
			selectedResizeMode = this.props.resizeMode
		}
		if (this.props.source && this.props.source.uri && this.state.validImage) {
			return (
				<View>
					<ImageBackground
						{...this.props}
						onError={this._handleError}
						resizeMode={selectedResizeMode}
						onLoadEnd={this._handleOnLoadEnd}
					>
						{this.state.loading ? <ActivityIndicator size="small" style={css.safe_image_loading_activity_indicator} /> : null}
					</ImageBackground>
				</View>
			)
		} else if (this.props.onFailure) {
			return this.props.onFailure
		} else {
			return null
		}
	}
}

export default SafeImage

import React from 'react'
import { ImageBackground, ActivityIndicator, View } from 'react-native'
import css from '../../styles/css'

class SafeImage extends React.Component {
	constructor(props) {
		super(props)
		this.state = { validImage: true, loading: true }
	}

	_handleError = (event) => {
		console.log(`Error loading image ${this.props.source.uri}:`, event.nativeEvent.error)
		this.setState({ validImage: false })
	}

	_handleOnLoadEnd = () => {
		this.setState({ loading: false })
	}

	render() {
		let selectedResizeMode = 'contain'
		if (this.props.resizeMode) {
			selectedResizeMode = this.props.resizeMode
		}
		if (this.props.source && this.props.source.uri && this.state.validImage) {
			return (
				<ImageBackground
					{...this.props}
					onError={this._handleError}
					resizeMode={selectedResizeMode}
					onLoadEnd={this._handleOnLoadEnd}
				>
					{this.state.loading ? <ActivityIndicator size="small" style={css.safe_image_loading_activity_indicator} /> : null}
				</ImageBackground>
			)
		} else if (this.props.onFailure) {
			return this.props.onFailure
		} else {
			return null
		}
	}
}

export default SafeImage

Remove extra view from safeImage component


//= require jquery.ui.nestedSortable
//= require codemirror
//= require codemirror/modes/xml
//= require codemirror/modes/javascript
//= require codemirror/modes/css
//= require codemirror/modes/htmlmixed
//= require showdown
//= require jquery.markedit
//= require bootstrap-wysihtml5-all
//= require_tree ./simple-pages

//= require jquery.ui.nestedSortable
//= require codemirror
//= require codemirror/modes/xml
//= require codemirror/modes/javascript
//= require codemirror/modes/css
//= require codemirror/modes/htmlmixed
//= require showdown
//= require jquery.markedit
//= require bootstrap-wysihtml5
//= require_tree ./simple-pages

Update the javascript for the editor