var url = require("url");
var qs = require("qs");

/**
 * getNextLink
 * @function
 * @param {Object} response A response object returned from calling 'client.makeRequest'
 * @returns A parsed version of the link to the subsequent page, or null if no such page exists.
 */
var getNextLink = function (response) {
	if (response.headers.link) {
		var links = response.headers.link.split(",");
		for (var elem in links) {
			if (links[elem].indexOf("rel=\"next\"") !== -1) {
				var linkRegex = /<(.*)>/;
				var link = linkRegex.exec(links[elem])[1];
				var parsedUrl = url.parse(link);
				return qs.parse(parsedUrl.query);
			}
		}
	}
	return null;
};

module.exports.getNextLink = getNextLink;

var url = require("url");
var querystring = require("querystring");

/**
 * getNextLink
 * @function
 * @param {Object} response A response object returned from calling 'client.makeRequest'
 * @returns A parsed version of the link to the subsequent page, or null if no such page exists.
 */
var getNextLink = function (response) {
	if (response.headers.link) {
		var links = response.headers.link.split(",");
		for (var elem in links) {
			if (links[elem].indexOf("rel=\"next\"") !== -1) {
				var linkRegex = /<(.*)>/;
				var link = linkRegex.exec(links[elem])[1];
				var parsedUrl = url.parse(link);
				return querystring.parse(parsedUrl.query);
			}
		}
	}
	return null;
};

module.exports.getNextLink = getNextLink;

Switch from qs to querystring


import React from 'react';
import ReactDOM from 'react-dom';

const Layout = props => <h1>{props.name} がやっていく</h1>;

ReactDOM.render(
  <Layout name="yukiyan" />,
  document.getElementById('app')
);

import React from 'react';
import ReactDOM from 'react-dom';

const Layout = props => <h1>{props.name} がやっていく</h1>;

Layout.propTypes = {
  name: React.PropTypes.string.isRequired,
};

ReactDOM.render(
  <Layout name="yukiyan" />,
  document.getElementById('app')
);

Fix `Prevent missing props validation`


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

module.exports = Controller.create({
    index(req, res) {
        res.render("index");
    },

    socket(req, res) {
        res.render("socket");
    },

    _private(req, res) {
        res
            .type("text/html; charset=UTF-8")
            .end("Correctry called private method via routes.js");
    }
});

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

module.exports = Controller.create({
    index(req, res) {
        res.render("index");
    },

    "403"(req, res) {
        throw new maya.NotAllowedException();
    },

    error(req, res) {
        throw new Error("Handled correctry?");
    },

    socket(req, res) {
        res.render("socket");
    },

    json(req, res) {
        res.json({"its": "response json"});
    },

    plain(req, res) {
        res.type("text/plain; charset=UTF-8").write("plain");
    },

    _private(req, res) {
        res
            .type("text/html; charset=UTF-8")
            .end("Correctry called private method via routes.js");
    }
});

Add more example in devapp


import Ember from 'ember';
import layout from './template';

/**
 * Modal that provides examples and explanation of Lucene Search syntax
 *
 * ```handlebars
 * {{search-help-modal
 *      isOpen=isOpen
 * }}
 * ```
 * @class search-help-modal
 */
export default Ember.Component.extend({
    layout,
    isOpen: false,
    currentPath: Ember.computed(function() {
        return Ember.getOwner(this).lookup('controller:application').currentPath;
    }),
    actions: {
        close() {
            this.set('isOpen', false);
        },
        toggleHelpModal() {
            this.toggleProperty('isOpen');
        },
    }
});

import Ember from 'ember';
import layout from './template';

/**
 * Modal that provides examples and explanation of Lucene Search syntax
 *
 * ```handlebars
 * {{search-help-modal
 *      isOpen=isOpen
 * }}
 * ```
 * @class search-help-modal
 */
export default Ember.Component.extend({
    layout,
    isOpen: false,
    currentPath: Ember.computed(function() {
        return window.location.origin + window.location.pathname;
    }),
    actions: {
        close() {
            this.set('isOpen', false);
        },
        toggleHelpModal() {
            this.toggleProperty('isOpen');
        },
    }
});

Modify how the currentPath is retrieved.


import React from 'react'
import styled from 'styled-components'
import { NavLink as Link } from 'react-router-dom'

const Nav = styled.nav`
  display: flex;
  justify-content: center;
`

const NavLink = styled(Link)`
  margin: 0 12px;
  font-size: 1.8rem;
  text-decoration: none;
  color: var(--gray700);

  &:hover,
  &:focus {
    color: var(--gray900);
  }

  &.${props => props.activeClassName} {
    color: var(--gray900);
    text-decoration: line-through;
  }
`

export default () => (
  <Nav>
    <NavLink exact to="/" activeClassName="active">
      Home
    </NavLink>
    <NavLink to="/about" activeClassName="active">
      About
    </NavLink>
  </Nav>
)

import React from 'react'
import styled from 'styled-components'
import { NavLink as Link } from 'react-router-dom'

const Nav = styled.nav`
  display: flex;
  justify-content: center;
`

const NavLink = styled(Link)`
  margin: 0 12px;
  font-size: 1.8rem;
  text-decoration: none;
  color: var(--gray700);

  &:hover,
  &:focus {
    color: var(--gray900);
  }

  &[aria-current='true'] {
    color: var(--gray900);
    text-decoration: line-through;
  }
`

export default () => (
  <Nav>
    <NavLink exact to="/">
      Home
    </NavLink>
    <NavLink to="/about">About</NavLink>
  </Nav>
)

Use aria-current selector instead of className


/************************************
 * Redis key and channel namespaces *
 ************************************/

module.exports = {
	getItemPropertyKey: getItemPropertyKey,
	getKeyInfo: getKeyInfo,
	getPropertyChannel: getPropertyChannel,
	getFocusProperty: getFocusProperty,
	// The unique ID key is used to consistently increase item id's. Should we use guid's instead?
	uniqueIdKey: '__fin_unique_id'
}

// item properties are stored at 		I<item id>@<propName>	e.g. I20@books
// channel names for items are			#I<item id>				e.g. #I20
// channel names for properties are		#P<propName>			e.g. #Pbooks

// Data state keys
function getItemPropertyKey(itemId, propName) {
	if (!propName || typeof itemId == 'undefined') {
		throw new Error("itemId and propName are required for keys.getItemPropertyKey")
	}
	return 'I' + itemId + '@' + propName
}

function getKeyInfo(key) {
	var type = key[0],
		parts = key.substr(1).split('@')

	return { type: type, id: parseInt(parts[0]), property: parts[1] }
}

function getPropertyChannel(propName) {
	return '#P' + propName
}

// Misc
function getFocusProperty(propName) {
	return '_focus_' + propName
}

/************************************
 * Redis key and channel namespaces *
 ************************************/

module.exports = {
	getItemPropertyKey: getItemPropertyKey,
	getKeyInfo: getKeyInfo,
	getPropertyChannel: getPropertyChannel,
	getFocusProperty: getFocusProperty,
	// The unique ID key is used to consistently increase item id's. Should we use guid's instead?
	uniqueIdKey: '__fin_unique_id'
}

// item properties are stored at 		I<item id>@<propName>	e.g. I20@books
// channel names for items are			#I<item id>				e.g. #I20
// channel names for properties are		#P<propName>			e.g. #Pbooks

// Data state keys
function getItemPropertyKey(itemID, propName) {
	if (itemID === undefined || propName === undefined) {
		throw new Error("itemID and propName are required for keys.getItemPropertyKey")
	}
	return 'I' + itemID + '@' + propName
}

function getKeyInfo(key) {
	var type = key[0],
		parts = key.substr(1).split('@')

	return { type: type, id: parseInt(parts[0]), property: parts[1] }
}

function getPropertyChannel(propName) {
	return '#P' + propName
}

// Misc
function getFocusProperty(propName) {
	return '_focus_' + propName
}

Normalize variable names, and error check for undefined rather than for falsy


import restArguments from './restArguments.js';
import now from './now.js';

// When a sequence of calls of the returned function ends, the argument
// function is triggered. The end of a sequence is defined by the `wait`
// parameter. If `immediate` is passed, the argument function will be
// triggered at the beginning of the sequence instead of at the end.
export default function debounce(func, wait, immediate) {
  var timeout, previous, args, result, context;

  var later = function() {
    var passed = now() - previous;
    if (wait > passed) {
      timeout = setTimeout(later, wait - passed);
    } else {
      timeout = null;
      if (!immediate) result = func.apply(context, args);
      // This check is needed because the func can recursively invoke debounced
      if (!timeout) args = context = null;
    }
  };

  var debounced = restArguments(function(_args) {
    context = this;
    args = _args;
    previous = now();
    if (!timeout) {
      timeout = setTimeout(later, wait);
      if (immediate) result = func.apply(context, args);
    }
    return result;
  });

  debounced.cancel = function() {
    clearTimeout(timeout);
    timeout = args = context = null;
  };

  return debounced;
}

import restArguments from './restArguments.js';
import now from './now.js';

// When a sequence of calls of the returned function ends, the argument
// function is triggered. The end of a sequence is defined by the `wait`
// parameter. If `immediate` is passed, the argument function will be
// triggered at the beginning of the sequence instead of at the end.
export default function debounce(func, wait, immediate) {
  var timeout, previous, args, result, context;

  var later = function() {
    var passed = now() - previous;
    if (wait > passed) {
      timeout = setTimeout(later, wait - passed);
    } else {
      timeout = null;
      if (!immediate) result = func.apply(context, args);
      // This check is needed because `func` can recursively invoke `debounced`.
      if (!timeout) args = context = null;
    }
  };

  var debounced = restArguments(function(_args) {
    context = this;
    args = _args;
    previous = now();
    if (!timeout) {
      timeout = setTimeout(later, wait);
      if (immediate) result = func.apply(context, args);
    }
    return result;
  });

  debounced.cancel = function() {
    clearTimeout(timeout);
    timeout = args = context = null;
  };

  return debounced;
}

Make a comment render more nicely


import Ember from 'ember';

export default Ember.Handlebars.makeBoundHelper(function(value) {
  return moment(value).format('dddd, Do MMMM YYYY, HH:MM A');
});

import Ember from 'ember';

export default Ember.Handlebars.makeBoundHelper(function(value) {
  return window.moment(value).format('dddd, Do MMMM YYYY, HH:MM A');
});

Fix JSHint error with moment


describe("Tic Tac Toe Game", function() {
  it("makes sure that player One starts", function() {
    var game = new Game()
    var playerOne = "X"
    var playerTwo = "O"

    var validTurn = game.put(playerOne)
    expect(validTurn).toEqual(true)

  })

  it("makes sure that player Two does not start", function() {
    var game = new Game()
    var playerOne = "X"
    var playerTwo = "O"

    var validTurn = game.put(playerTwo)
    expect(validTurn).toEqual(false)

  })

  it("makes sure that player Two makes second turn", function() {
    var game = new Game()
    var playerOne = "X"
    var playerTwo = "O"

    game.put(playerOne)

    validTurn = game.put(playerTwo)
    expect(validTurn).toEqual(true)
  })

  it("is possible to put a mark only in empty cell", function() {
    var game = new Game()
    var playerOne = "X"
    var playerTwo = "O"
    var cell = [3, 3]

    var validTurn = game.put(playerOne, cell)
    expect(validTurn).toEqual(true)

  })

  it("is not possible to put mark in occupied cell", function() {
    var game = new Game()
    var playerOne = "X"
    var playerTwo = "O"
    var cell = [3, 3]

    game.put(playerOne, cell)

    validTurn = game.put(playerTwo, cell)
    expect(validTurn).toEqual(false)
  })
})

describe("Tic Tac Toe Game", function() {
  var playerOne = "X"
  var playerTwo = "O"

  var game

  beforeEach(function() {
    game = new Game()
  })

  it("makes sure that player One starts", function() {
    var validTurn = game.put(playerOne)
    expect(validTurn).toEqual(true)
  })

  it("makes sure that player Two does not start", function() {
    var validTurn = game.put(playerTwo)
    expect(validTurn).toEqual(false)
  })

  it("makes sure that player Two makes second turn", function() {
    game.put(playerOne)

    validTurn = game.put(playerTwo)
    expect(validTurn).toEqual(true)
  })

  it("is possible to put a mark only in empty cell", function() {
    var validTurn = game.put(playerOne, [3, 3])
    expect(validTurn).toEqual(true)
  })

  it("is not possible to put mark in occupied cell", function() {
    game.put(playerOne, [3, 3])

    validTurn = game.put(playerTwo, [3, 3])
    expect(validTurn).toEqual(false)
  })
})

Clean up test suite's common variables.


'use strict';

var express = require('express');
var router = express.Router();
var auth = require('../../passport');

var restUtils = require('../utils');

var os = require('os');

router.post('/mqtt', auth.requireAPIAuth(), function(req, res) {
  var userId = req.user.id;
  var topic = req.query.topic;
  var payload = req.body;

  if (typeof payload === 'object') {
  	payload = JSON.stringify(payload);
  }

  var routingKey = userId + '.' + topic.replace('/', '.');

  req.app.get('service').facet('amqp').then(function(amqp) {
  	amqp.publish('amq.topic', routingKey, new Buffer(payload));

  	restUtils.standardResponse(res, { "success": true }, {type: 'object'});
  });
});

module.exports = function(parent) {
  parent.use(router);
};

'use strict';

var debug = require('debug')('frontend:mqtt');
var express = require('express');
var router = express.Router();
var auth = require('../../passport');

var restUtils = require('../utils');

var os = require('os');

router.post('/mqtt', auth.requireAPIAuth(), function(req, res) {
  var userId = req.user.id;
  var topic = req.query.topic;
  var payload = req.body;

  if (typeof payload === 'object') {
  	payload = JSON.stringify(payload);
  }

  var routingKey = userId + '.' + topic.replace('/', '.');

  req.app.get('service').facet('amqp').then(function(amqp) {
  	amqp.publish('amq.topic', routingKey, new Buffer(payload));

  	debug('Publishing to routing key', routingKey, 'payload', payload);

  	restUtils.standardResponse(res, { "success": true }, {type: 'object'});
  });
});

module.exports = function(parent) {
  parent.use(router);
};

Add some debug output for convenience.


(function (angular) {
    'use strict';
    var InEngineApi = function ($resource, apiUrl) {
        this.$resource = $resource;
        this.apiUrl = apiUrl;
    };

    InEngineApi.prototype.queryResource = function (name) {
        return this.$resource(this.apiUrl + '/' + name).query().$promise;
    };

    InEngineApi.prototype.getCronTriggers = function () {
        return this.queryResource('CronTrigger');
    };

    InEngineApi.prototype.getSimpleTriggers = function () {
        return this.queryResource('SimpleTrigger');
    };

    InEngineApi.prototype.getJobTypes = function () {
        return this.queryResource('JobType');
    };

    angular.module('eehInEngine.api', ['ngResource'])
    .provider('eehInEngineApi', function () {
        var _apiUrl = 'http://localhost:9001/api';
        this.apiUrl = function (value) {
            if (angular.isUndefined(value)) {
                return _apiUrl;
            }
            _apiUrl = value;
        };
        this.$get = function ($resource) {
            return new InEngineApi($resource, _apiUrl);
        };
    });
}(angular));

(function (angular) {
    'use strict';
    var InEngineApi = function ($resource, apiUrl) {
        this.$resource = $resource;
        this.apiUrl = apiUrl;
    };

    InEngineApi.prototype.queryResource = function (name) {
        return this.$resource(this.apiUrl + '/' + name).query().$promise;
    };

    InEngineApi.prototype.getCronTriggers = function () {
        return this.queryResource('CronTrigger');
    };

    InEngineApi.prototype.getSimpleTriggers = function () {
        return this.queryResource('SimpleTrigger');
    };

    InEngineApi.prototype.getJobTypes = function () {
        return this.queryResource('JobType');
    };

    InEngineApi.prototype.getTimeZones = function () {
        return this.queryResource('TimeZone');
    };

    angular.module('eehInEngine.api', ['ngResource'])
    .provider('eehInEngineApi', function () {
        var _apiUrl = 'http://localhost:9001/api';
        this.apiUrl = function (value) {
            if (angular.isUndefined(value)) {
                return _apiUrl;
            }
            _apiUrl = value;
        };
        this.$get = function ($resource) {
            return new InEngineApi($resource, _apiUrl);
        };
    });
}(angular));

Allow TimeZone list to be retrieved


// https://docs.influxdata.com/influxdb/v1.5/write_protocols/line_protocol_tutorial/#special-characters-and-keywords

export function stringifyPoints(points) {
  return points.map(_stringifyPoint).join("\n");
}

export function _stringifyPoint({ measurement, values, tags, timestamp }) {
  const tagsString = Object.entries(tags)
    .map(([key, value]) => {
      return `${escapeTag(key)}=${escapeTag(value)}`;
    })
    .join(",");

  const valuesString = Object.entries(values)
    .map(([key, value]) => {
      return `${escapeTag(key)}=${_formatValue(value)}`;
    })
    .join(",");

  return `${measurement},${tagsString} ${valuesString} ${timestamp}`;
}

const _formatValue = value => {
  if (typeof value === "string") {
    return `"${value.replace('"', '\\"')}"`;
  }
  return value;
};

const escapeTag = value =>
  value
    .replace(",", "\\,")
    .replace(" ", "\\ ")
    .replace("=", "\\=");

// https://docs.influxdata.com/influxdb/v1.5/write_protocols/line_protocol_tutorial/#special-characters-and-keywords

export function stringifyPoints(points) {
  return points.map(_stringifyPoint).join("\n");
}

export function _stringifyPoint({ measurement, values, tags, timestamp }) {
  const tagsString = Object.entries(tags)
    .map(([key, value]) => {
      return `${escapeTag(key)}=${escapeTag(value)}`;
    })
    .join(",");

  const valuesString = Object.entries(values)
    .map(([key, value]) => {
      return `${escapeTag(key)}=${_formatValue(value)}`;
    })
    .join(",");

  return `${measurement},${tagsString} ${valuesString} ${timestamp}`;
}

const _formatValue = value => {
  if (typeof value === "string") {
    return `"${value.replace('"', '\\"')}"`;
  }
  return value;
};

const escapeTag = value => {
  if (typeof value === "string") {
    return value
      .replace(",", "\\,")
      .replace(" ", "\\ ")
      .replace("=", "\\=");
  }
  return value;
};

Fix escapeTag for non-string values


import Ember from 'ember';

export default Ember.Route.extend({
  blogData: Ember.inject.service(),

  model() {
    return this.get('blogData').posts;
  }
});

import Ember from 'ember';
import DS from 'ember-data';

export default Ember.Route.extend({
  blogData: Ember.inject.service(),

  model() {
    console.log('store service is an instanceof DS.Store', this.get('store') instanceof DS.Store);

    return this.get('blogData').posts;
  }
});

Add logging to show store injected into routes is a DS.Store


exports.func = (client, oldMember, newMember) => {
	if(oldMember.nickname != newMember.nickname) {
		oldFormatted = oldMember.nickname ? `\`${oldMember.nickname}\`` : "_(none)_";
		newFormatted = newMember.nickname ? `\`${newMember.nickname}\`` : "_(none)_";

		client.logToGuild(newMember.guild, `:name_badge: ${client.formatUser(newMember.user)} nickname was changed from ${oldFormatted} to ${newFormatted}`);
	}
	if(oldMember.roles != newMember.roles) {
		oldArray = [];
		newArray = [];

		oldMember.roles.reduce((snowflake, role) => {
			if(role.id == role.guild.id) return; // filters out @everyone
			oldArray.push(role.name);
		}, []);

		newMember.roles.reduce((snowflake, role) => {
			if(role.id == role.guild.id) return;
			newArray.push(role.name);
		}, []);

		oldRoles = oldArray.join(', ') || "_(none)_";
		newRoles = newArray.join(', ') || "_(none)_";

		client.logToGuild(newMember.guild, `:lock_with_ink_pen: ${client.formatUser(newMember.user)} roles were changed:\n:b: ${oldRoles}\n:a: ${newRoles}`);
	}
};

exports.func = (client, oldMember, newMember) => {
	if(oldMember.nickname != newMember.nickname) {
		oldFormatted = oldMember.nickname ? `\`${oldMember.nickname}\`` : "_(none)_";
		newFormatted = newMember.nickname ? `\`${newMember.nickname}\`` : "_(none)_";

		client.logToGuild(newMember.guild, `:name_badge: ${client.formatUser(newMember.user)} nickname was changed from ${oldFormatted} to ${newFormatted}`);
	}

	if(!oldMember.roles.equals(newMember.roles)) {
		oldArray = [];
		newArray = [];

		oldMember.roles.reduce((snowflake, role) => {
			if(role.id == role.guild.id) return; // filters out @everyone
			oldArray.push(role.name);
		}, []);

		newMember.roles.reduce((snowflake, role) => {
			if(role.id == role.guild.id) return;
			newArray.push(role.name);
		}, []);

		oldRoles = oldArray.join(', ') || "_(none)_";
		newRoles = newArray.join(', ') || "_(none)_";

		client.logToGuild(newMember.guild, `:lock_with_ink_pen: ${client.formatUser(newMember.user)} roles were changed:\n:b: ${oldRoles}\n:a: ${newRoles}`);
	}
};

Fix bug where role change logged on nickname change.


// This plugin checks for clients that talk before we sent a response

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

exports.register = function() {
    this.pause = config.get('early_talker.pause', 'value');
    this.register_hook('data', 'check_early_talker');
};

exports.check_early_talker = function(callback) {
    if (this.pause) {
        var connection = this.connection;
        setTimeout(function () { _check_early_talker(connection, callback) }, this.pause);
    }
    else {
        _check_early_talker(self, callback);
    }
};

var _check_early_talker = function (connection, callback) {
    if (connection.early_talker) {
        callback(constants.deny, "You talk too soon");
    }
    else {
        callback(constants.cont);
    }
};

// This plugin checks for clients that talk before we sent a response

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

exports.register = function() {
    this.pause = config.get('early_talker.pause', 'value');
    this.register_hook('data', 'check_early_talker');
};

exports.check_early_talker = function(callback) {
    if (this.pause) {
        var connection = this.connection;
        setTimeout(function () { _check_early_talker(connection, callback) }, this.pause);
    }
    else {
        _check_early_talker(self, callback);
    }
};

var _check_early_talker = function (connection, callback) {
    if (connection.early_talker) {
        callback(constants.denydisconnect, "You talk too soon");
    }
    else {
        callback(constants.cont);
    }
};

Switch to denydisconnect simply cos early talkers are RUDE!


var hoverZoomPlugins = hoverZoomPlugins || [];
hoverZoomPlugins.push({
    name:'Le bon coin',
    version:'0.1',
    prepareImgLinks:function (callback) {
        var res = [];
        hoverZoom.urlReplace(res,
            'img[src*="thumbs"], span.thumbs',
            'thumbs',
            'images'
        );
        callback($(res));
    }
});

var hoverZoomPlugins = hoverZoomPlugins || [];
hoverZoomPlugins.push({
    name:'Le bon coin',
    version:'0.2',
    prepareImgLinks:function (callback) {
        var res = [];

        hoverZoom.urlReplace(res,
            'img[src*="thumbs"], span.thumbs',
            'thumbs',
            'images'
        );

        //sample url : https://img3.leboncoin.fr/ad-thumb/cc15170487f75709b751a61ee8b52b4ebed2a062.jpg
        //          -> https://img3.leboncoin.fr/ad-large/cc15170487f75709b751a61ee8b52b4ebed2a062.jpg
        hoverZoom.urlReplace(res,
            'img[src]',
            /\/ad-.*?\//,
            '/ad-large/'
        );

        callback($(res));
    }
});

Fix for plug-in : LeBonCoin


'use strict';

/* Controllers */

var phonecatApp = angular.module('phonecatApp', []);

phonecatApp.controller('PhoneListCtrl', function($scope, $http) {
  $http.get('phone/list').success(function(data) {
    $scope.phones = data.phone;
  });

  $scope.orderProp = 'age';
});

'use strict';

/* Controllers */

var phonecatApp = angular.module('phonecatApp', []);

phonecatApp.controller('PhoneListCtrl', function($scope, $http) {
  $http.get('phone/list').success(function(data) {
    $scope.phones = data;
  });

  $scope.orderProp = 'age';
});

Fix missing phone list in WebApp.


//
// Tests!
//

'use strict';

var dom = require('./dom');
var assert = require('assert');

//
// Test 'element' and 'clone'.
//
(function() {
    var e = dom.element({name: 'p', contents: 'hello'});
    assert.equal(e.name, 'p');
    assert.equal(e.contents, 'hello');
})();

module.exports = 'passed!';

//
// Tests!
//

'use strict';

var dom = require('./dom');
var assert = require('assert');
var eq = assert.deepEqual;

//
// Test 'element' and 'render'.
//
(function() {
    var e = dom.element({name: 'br'});
    eq(e.name, 'br');
    eq(e.render().tagName, 'BR');

    // Pass element a string to create an element with the given tag name.
    eq(dom.element('br').name, 'br');

    // Pass createElement a string to create a DOM element with the given tag name.
    eq(dom.createElement('br').tagName, 'BR');
})();

//
// element with child nodes
//
(function() {
    var e = dom.element({name: 'p', contents: 'hello'});
    eq(e.contents, 'hello');

    // Pass contents a list to add multiple child nodes.
    eq(dom.element({name: 'p', contents: ['hello']}).render().childNodes[0].data, 'hello');

    // Child nodes can be vdom elements as well.
    eq(dom.element({name: 'p', contents: [dom.element('br')]}).render().childNodes[0].tagName, 'BR');
})();

//
// element with attributes
//
(function() {
    var e = dom.element({name: 'p', attributes: {id: 'foo'}, contents: 'bar'});
    eq(e.render().getAttribute('id'), 'foo');
})();

//
// element with style
//
(function() {
    var e = dom.element({name: 'p', style: {color: 'blue'}, contents: 'hello'});
    eq(e.render().style.color, 'blue');
})();

//
// element with an event handler
//
(function() {
    dom.element({name: 'p', handlers: {click: function() {}}}).render();
})();

//
// Test 'dom.render'.
//
(function() {
    var e = dom.element({name: 'p', contents: 'hello'});
    eq(dom.render(e).tagName, 'P');

    // Pass dom.createElement or dom.render a raw string to render a textNode.
    eq(dom.render('hello').data, 'hello');
})();

module.exports = 'passed!';

Add basic tests for dom.js


import React from "react";
import { connect } from "react-redux";
import styled from "styled-components";
import { Box, Button, ExternalLink } from "../../base";
import { checkAdminOrPermission } from "../../utils/utils";
import { remoteReference } from "../actions";

const StyledReferenceOfficial = styled(Box)`
    align-items: center;
    display: flex;

    h5 {
        font-size: 16px;
        font-weight: bold;
    }

    button {
        margin-left: auto;
    }
`;

export const ReferenceOfficial = ({ show, onRemote }) => {
    if (show) {
        return (

        );
    }

    return null;
};

const mapStateToProps = state => ({
    show: !state.references.official_installed && checkAdminOrPermission(state, "create_ref")
});

const mapDispatchToProps = dispatch => ({
    onRemote: () => {
        dispatch(remoteReference());
    }
});

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

Change official reference message wording


import {
  commitMutation,
  graphql
} from 'react-relay/compat'

const mutation = graphql`
  mutation UpdateVcMutation($input: UpdateVcInput!) {
    updateVc(input: $input) {
      user {
        id
      }
    }
  }
`

function commit (environment, user) {
  const userId = user.id

  return commitMutation(
    environment,
    {
      mutation,
      variables: {
        input: { userId }
      }
    }
  )
}

export default {commit}

import {
  commitMutation,
  graphql
} from 'react-relay/compat'

const mutation = graphql`
  mutation UpdateVcMutation($input: UpdateVcInput!) {
    updateVc(input: $input) {
      user {
        id
      }
    }
  }
`

function commit (environment, user) {
  const userId = user.id

  return commitMutation(
    environment,
    {
      mutation,
      variables: {
        input: { userId }
      },
      updater: (store) => {
        // Add 1 to the user's VC.
        const rootField = store.getRootField('updateVc')
        const user = rootField.getLinkedRecord('user')
        user.setValue(user.getValue('vcCurrent') + 1, 'vcCurrent')
      }
    }
  )
}

export default {commit}

Fix user VC incrementing on client side.


/**
 * Copyright 2015-present 650 Industries. All rights reserved.
 *
 * @providesModule AuthTokenActions
 */
'use strict';

import { action } from 'Flux';
import LocalStorage from '../Storage/LocalStorage';

let AuthTokenActions = {
  @action
  setAuthTokens(tokens) {
    LocalStorage.saveAuthTokensAsync(tokens);
    return tokens;
  },

  @action
  updateIdToken(idToken) {
    LocalStorage.updateIdTokenAsync(idToken);
    return { idToken };
  },

  @action
  signOut() {
    return null;
  },
};

export default AuthTokenActions;

/**
 * Copyright 2015-present 650 Industries. All rights reserved.
 *
 * @providesModule AuthTokenActions
 */
'use strict';

import { action } from 'Flux';
import LocalStorage from '../Storage/LocalStorage';

let AuthTokenActions = {
  @action
  setAuthTokens(tokens) {
    LocalStorage.saveAuthTokensAsync(tokens);
    return tokens;
  },

  @action
  updateIdToken(idToken) {
    LocalStorage.updateIdTokenAsync(idToken);
    return { idToken };
  },

  @action
  signOut() {
    LocalStorage.removeAuthTokensAsync();
    return null;
  },
};

export default AuthTokenActions;

Fix sign out to clear local storage


import Auth0Lock from 'auth0-lock'

export const lock = new Auth0Lock(process.env.AUTH0_CLIENT_ID, process.env.AUTH0_DOMAIN)

import Auth0Lock from 'auth0-lock'

export const isRequired = process.env.AUTH0_CLIENT_ID && process.env.AUTH0_DOMAIN
export const lock = new Auth0Lock(process.env.AUTH0_CLIENT_ID, process.env.AUTH0_DOMAIN)

Add a check to see if auth is necessary


const withCSS = require('@zeit/next-css');

const { ANALYZE } = process.env;

// Use next-compose-plugins if this gets weird
module.exports = withCSS({
  distDir: '../.next', // this is next's config, not next-css'
  webpack: (config, { isServer }) => {
    if (ANALYZE) {
      const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');

      config.plugins.push(
        new BundleAnalyzerPlugin({
          analyzerMode: 'server',
          analyzerPort: isServer ? 8888 : 8889,
          openAnalyzer: true,
        }),
      );
    }

    return config;
  },
});

const withCSS = require('@zeit/next-css');

const { ANALYZE } = process.env;

// Use next-compose-plugins if this gets weird
module.exports = withCSS({
  distDir: '../.next', // this is next's config, not next-css'
  webpack: (config, { isServer }) => {
    if (ANALYZE) {
      const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');

      config.plugins.push(
        new BundleAnalyzerPlugin({
          analyzerMode: 'server',
          analyzerPort: isServer ? 8888 : 8889,
          openAnalyzer: true,
        }),
      );
    }

    return config;
  },
  onDemandEntries: {
    // Period (in ms) where the server will keep pages in the buffer
    maxInactiveAge: 120 * 1e3, // default 25s
    // Number of pages that should be kept simultaneously without being disposed
    pagesBufferLength: 3, // default 2
  },
});

Set higher age and buffer length for Next.js pages in dev


var buildFile = require('./build-file')
var runGitCommand = require('./helpers/run-git-command')

var COMMITHASH_COMMAND = 'rev-parse HEAD'
var VERSION_COMMAND = 'describe --always'

function GitRevisionPlugin (options) {
  this.gitWorkTree = options && options.gitWorkTree
  this.lightweightTags = options && options.lightweightTags || false
  this.commithashCommand = options && options.commithashCommand || COMMITHASH_COMMAND
  this.versionCommand = options && options.versionCommand || VERSION_COMMAND
}

GitRevisionPlugin.prototype.apply = function (compiler) {
  buildFile(compiler,
    this.gitWorkTree,
    this.commithashCommand,
    /\[git-revision-hash\]/gi,
    'COMMITHASH'
  )

  buildFile(compiler,
    this.gitWorkTree,
    this.versionCommand + (this.lightweightTags ? ' --tags' : ''),
    /\[git-revision-version\]/gi,
    'VERSION'
  )
}

GitRevisionPlugin.prototype.commithash = function () {
  return runGitCommand(
    this.gitWorkTree,
    this.commithashCommand
  )
}

GitRevisionPlugin.prototype.version = function () {
  return runGitCommand(
    this.gitWorkTree,
    this.versionCommand + (this.lightweightTags ? ' --tags' : '')
  )
}

module.exports = GitRevisionPlugin

var buildFile = require('./build-file')
var runGitCommand = require('./helpers/run-git-command')

var COMMITHASH_COMMAND = 'rev-parse HEAD'
var VERSION_COMMAND = 'describe --always'

function GitRevisionPlugin (options) {
  options = options || {}

  this.gitWorkTree = options.gitWorkTree

  this.commithashCommand = options.commithashCommand ||
    COMMITHASH_COMMAND

  this.versionCommand = options.versionCommand ||
    VERSION_COMMAND + (options.lightweightTags ? ' --tags' : '')
}

GitRevisionPlugin.prototype.apply = function (compiler) {
  buildFile(compiler,
    this.gitWorkTree,
    this.commithashCommand,
    /\[git-revision-hash\]/gi,
    'COMMITHASH'
  )

  buildFile(compiler,
    this.gitWorkTree,
    this.versionCommand,
    /\[git-revision-version\]/gi,
    'VERSION'
  )
}

GitRevisionPlugin.prototype.commithash = function () {
  return runGitCommand(
    this.gitWorkTree,
    this.commithashCommand
  )
}

GitRevisionPlugin.prototype.version = function () {
  return runGitCommand(
    this.gitWorkTree,
    this.versionCommand
  )
}

module.exports = GitRevisionPlugin

Refactor lightweight tags to remove duplication 🐞


// Dependencies
var Config = require("../conf");

var Artworks = module.exports = {};

// TODO JSDoc
Artworks.fromArtist = function (user, callback) {

    var $ = null
      , $artworks = []
      , $cArtwork = null
      , artworks = []
      , i = 0
      ;

    function getSeq(page) {
        Request.get({
            url: "https://theartstack.com" + user.profile_url + "?page=" + page
          , headers: {
                "Cookie": Config.cookie
            }
        }, function (err, res, body) {
            if (err) { return callback(err); }
            $ = Cheerio.load(body);
            $artworks = $(".work-title [data-highres-pic]");
            if (!$artworks.length) {
                return callback(null, artworks);
            }
            for (i = 0; i < $artworks.length; ++i) {
                $cArtwork = $artworks.eq(i);
                artworks.push({
                    url: $cArtwork.attr("data-highres-pic")
                });
            }
            getSeq(page + 1);
        });
    }

    getSeq(1);
};

// Dependencies
var Config = require("../conf")
  , Util = require("../util")
  ;

var Artworks = module.exports = {};

// TODO JSDoc
Artworks.fromArtist = function (user, callback) {

    var $ = null
      , $artworks = []
      , $cArtwork = null
      , artworks = []
      , i = 0
      ;

    function getSeq(page) {
        Util.request(user.profile_url + "?page=" + page, function (err, $) {
            if (err) { return callback(err); }
            $artworks = $(".work-title [data-highres-pic]");
            if (!$artworks.length) {
                return callback(null, artworks);
            }
            for (i = 0; i < $artworks.length; ++i) {
                $cArtwork = $artworks.eq(i);
                artworks.push({
                    url: $cArtwork.attr("data-highres-pic")
                });
            }
            getSeq(page + 1);
        });
    }

    getSeq(1);
};

Use util.request to make authenticated requests


var gulp = require('gulp');
var tasks = [];

// Browserify
var browserify = require('browserify');
var vinylSourceStream = require('vinyl-source-stream');

var makeBrowserify = function(source, destination, output) {
  gulp.task(output+"-browserify", function() {
    bundler = browserify(source);
    bundler.transform('brfs');
    bundle = function() {
      bundler.bundle()
      .pipe(vinylSourceStream(output+".js"))
      .pipe(gulp.dest(destination));
    };
    bundle();
  });

  tasks.push(output+"-browserify");
};

makeBrowserify("./api/index.js", "./public", "api");

// Watch
gulp.task("watch", function() {
  gulp.watch(["./api/*"], ["api-browserify"]);
});

// All
gulp.task('default', tasks);

var gulp = require("gulp");
var tasks = [];

// Browserify
var browserify = require("browserify");
var vinylSourceStream = require("vinyl-source-stream");

var makeBrowserify = function(source, destination, output) {
  gulp.task(output+"-browserify", function() {
    bundler = browserify(source);
    bundler.transform("brfs");
    bundle = function() {
      bundler.bundle()
      .pipe(vinylSourceStream(output+".js"))
      .pipe(gulp.dest(destination));
    };
    bundle();
  });

  tasks.push(output+"-browserify");
};

makeBrowserify("./api/index.js", "./public", "api");

// Watch
gulp.task("watch", function() {
  gulp.watch(["./api/*"], ["api-browserify"]);
});

// All
gulp.task("default", tasks);

Use double quotes instead of single ones.


App.info({
  id: 'org.erikdemaine.coauthor',
  name: 'Coauthor',
  version: '1.0.0',
  author: 'Erik Demaine',
  email: 'edemaine+coauthor@mit.edu'
});

App.accessRule('*');
App.accessRule('blob:*');

Allow blob handling on Cordova


Router.onBeforeAction(function () {
  if (!Meteor.loggingIn() && !Meteor.userId() && Session.get('resetPassword')) {
    this.redirect('forgotPassword');
  }
  this.next();
}, { except: ['landing', 'login', 'invite', 'forgotPassword'] });

Router.onBeforeAction(function () {
  if (Meteor.userId()) {
    this.redirect('home');
  }
  this.next();
}, { only: ['landing', 'login', 'invite'] });

Router.onBeforeAction(function () {
  if (!this.data()) {
    this.layout('mainLayout');
  } else {
    this.layout('pageLayout');
  }
  this.next();
}, { only: ['topic', 'comment', 'profile'] });

Router.plugin('dataNotFound', { notFoundTemplate: 'notFound' });

Router.onBeforeAction(function () {
  if (!Meteor.loggingIn() && !Meteor.userId() && Session.get('resetPassword')) {
    this.redirect('forgotPassword');
  }
  this.next();
}, { except: ['landing', 'login', 'invite', 'forgotPassword'] });

Router.onBeforeAction(function () {
  if (Meteor.userId()) {
    this.redirect('home');
  }
  this.next();
}, { only: ['login', 'invite'] });

Router.onBeforeAction(function () {
  if (!this.data()) {
    this.layout('mainLayout');
  } else {
    this.layout('pageLayout');
  }
  this.next();
}, { only: ['topic', 'comment', 'profile'] });

Router.plugin('dataNotFound', { notFoundTemplate: 'notFound' });

Allow visiting landing page even when logged in


// @flow
import isPlainObject from 'lodash/isPlainObject';
import zip from 'lodash/zip';
import zipObject from 'lodash/zipObject';

export default async function mux(promises: any): Promise<*> {
  if (promises == null) {
    return promises;
  }

  if (typeof promises.then === 'function') {
    let value = await promises;
    return mux(value);
  }

  if (Array.isArray(promises)) {
    return Promise.all(promises.map(mux));
  }

  if (promises instanceof Map) {
    let keys = [...promises.keys()];
    let values = await Promise.all([...promises.values()].map(mux));
    return new Map(zip(keys, values));
  }

  if (promises instanceof Set) {
    let values = await Promise.all([...promises.values()].map(mux));
    return new Set(values);
  }

  if (isPlainObject(promises)) {
    let keys = Object.keys(promises);
    let values = [];
    for (let key of keys) {
      values.push(promises[key]);
    }
    values = await Promise.all(values.map(mux));
    return zipObject(keys, values);
  }

  return promises;
}

// @flow
import isPlainObject from 'lodash/isPlainObject';
import zip from 'lodash/zip';
import zipObject from 'lodash/zipObject';

export default async function mux(promises: any): Promise<any> {
  if (promises == null) {
    return promises;
  }

  if (typeof promises.then === 'function') {
    let value = await promises;
    return mux(value);
  }

  if (Array.isArray(promises)) {
    return Promise.all(promises.map(mux));
  }

  if (promises instanceof Map) {
    let keys = [...promises.keys()];
    let values = await Promise.all([...promises.values()].map(mux));
    return new Map(zip(keys, values));
  }

  if (promises instanceof Set) {
    let values = await Promise.all([...promises.values()].map(mux));
    return new Set(values);
  }

  if (isPlainObject(promises)) {
    let keys = Object.keys(promises);
    let values = [];
    for (let key of keys) {
      values.push(promises[key]);
    }
    values = await Promise.all(values.map(mux));
    return zipObject(keys, values);
  }

  return promises;
}

Use "Promise<any>" instead of "Promise<*>" to avoid inference


'use strict';

// Evaluate a Javascript expression.

const babel = require('babel-core');

/* eslint no-eval: 1 */
/* jshint evil: true */

// Setup debug log
const debug = require('abacus-debug')('abacus-eval');

// Evaluate the given Javascript expression
const xeval = (s) => {
  debug('Evaluating expression %s', s);
  return eval(babel.transform('(' + s + ')', { optional: ['runtime'] }).code);
};

// Export our public functions
module.exports = xeval;

'use strict';

// Evaluate a Javascript expression.

const babel = require('babel-core');
const vm = require('vm');

/* eslint no-eval: 1 */
/* jshint evil: true */

// Setup debug log
const debug = require('abacus-debug')('abacus-eval');

// Evaluate the given Javascript expression
const xeval = (s, c) => {
  const context = c || {};
  debug('Evaluating expression %s with context %c', s, context);
  vm.createContext(context);
  return vm.runInContext(
    babel.transform('(' + s + ')', { optional: ['runtime'] }).code, context);
};

// Export our public functions
module.exports = xeval;

Allow a context to be passed into eval


Add svc to sample ctrl


importScripts('/importme.mjs');

const CACHE_NAME = 'ROOT_CACHE';

async function cacheAllThings() {
    const cache = await caches.open(CACHE_NAME);
    cache.addAll([
        './index.html',
        './app.js',
        './app.css',
    ]);
}

self.addEventListener('install', async (event) => {
    consoleTheLogs('ROOT DONE', event);
    event.waitUntil(cacheAllThings());
});

self.addEventListener('fetch', (event) => {

    if ('storage' in navigator && 'estimate' in navigator.storage) {
        navigator.storage.estimate()
             .then(function(estimate){
                consoleTheLogs(`Using ${estimate.usage} out of ${estimate.quota} bytes.`);
             });
    }

    event.respondWith(
        fetch(event.request)
            .then(response => {
                consoleTheLogs('sw-root fetch interception');
                return response;
            })
    )
});

Update again to cause purge hopefully


/** @jsx React.DOM */

define([
  "React"
], function(React) {
  return React.createClass({
    destroy: function(event) {
      if (event.target.className === "modal" ||
          event.target.className === "modal-dialog") {
        var domNode = this.getDOMNode();
        React.unmountComponentAtNode(domNode);

        $(domNode).remove();
      }
    },
    render: function() {
      return (

                  {this.props.children}

      );
    }
  });
});

/** @jsx React.DOM */

define([
  "jquery",
  "React"
], function($, React) {
  return React.createClass({
    destroy: function(event) {
      if (event.target.className === "modal" ||
          event.target.className === "modal-dialog") {
        var domNode = this.getDOMNode();

        // TODO(ssorallen): Why does this need to unmount from the parentNode?
        // If it is unmounted from `domNode`, the second render throws an
        // invariant exception.
        React.unmountComponentAtNode(domNode.parentNode);

        $(domNode).remove();
      }
    },
    render: function() {
      return (

                {this.props.children}

      );
    }
  });
});

Clean up destroyed modal properly


'use strict';

module.exports = {
  meta: {
    docs: {
      description: 'Forbid methods added in ES6'
    },
    schema: [{
      type: 'object',
      properties: {
        exceptMethods: {
          type: 'array',
          items: {
            type: 'string'
          }
        }
      }
    }]
  },
  create(context) {
    const options = Object.assign({ exceptMethods: [] }, context.options[0]);
    const exceptMethods = new Set(options.exceptMethods);

    const es6StaticFunctions = [
      'Array.from',
      'Array.of',
      'Math.acosh',
      'Math.hypot',
      'Math.trunc',
      'Math.imul',
      'Math.sign',
      'Number.isNaN',
      'Number.isFinite',
      'Number.isSafeInteger',
      'Object.assign',
    ];
    const staticFunctions = es6StaticFunctions.filter((name) => !exceptMethods.has(name));

    return {
      CallExpression(node) {
        if(node.callee && node.callee.property && node.callee.object) {
          const functionName = node.callee.object.name + '.' + node.callee.property.name;
          if(staticFunctions.includes(functionName)) {
            context.report({
              node: node.callee.property,
              message: 'ES6 static methods not allowed: ' + functionName
            });
          }
        }
      }
    };
  }
};

'use strict';

module.exports = {
  meta: {
    docs: {
      description: 'Forbid methods added in ES6'
    },
    schema: [{
      type: 'object',
      properties: {
        exceptMethods: {
          type: 'array',
          items: {
            type: 'string'
          }
        }
      }
    }]
  },
  create(context) {
    const options = Object.assign({ exceptMethods: [] }, context.options[0]);
    const exceptMethods = new Set(options.exceptMethods);

    const es6StaticFunctions = [
      'Array.from',
      'Array.of',
      'Math.acosh',
      'Math.hypot',
      'Math.trunc',
      'Math.imul',
      'Math.sign',
      'Number.isNaN',
      'Number.isFinite',
      'Number.isInteger',
      'Number.isSafeInteger',
      'Number.parseFloat',
      'Number.parseInt',
      'Object.assign',
      'Object.is',
      'Object.getOwnPropertySymbols',
      'Object.setPrototypeOf',
    ];
    const staticFunctions = es6StaticFunctions.filter((name) => !exceptMethods.has(name));

    return {
      CallExpression(node) {
        if(node.callee && node.callee.property && node.callee.object) {
          const functionName = node.callee.object.name + '.' + node.callee.property.name;
          if(staticFunctions.includes(functionName)) {
            context.report({
              node: node.callee.property,
              message: 'ES6 static methods not allowed: ' + functionName
            });
          }
        }
      }
    };
  }
};

Add missing number and object static methods


import React from 'react';
import { default as Base, PopoverProps } from '@material-ui/core/Popover';
import styled from 'styled-components';
import { Box } from 'rebass/styled-components';

import { borders, radii, shadows, space } from '../../themes/baseTheme';

const StyledPopover = styled(Base)`
  .MuiPopover-paper {
    padding: ${space[3]}px;
    margin-top: ${space[2]}px;
    margin-bottom: ${space[2]}px;
    border: ${borders.modal};
    box-shadow: ${shadows.large};
    border-radius: ${radii.default}px;
  }
`;

const PopoverContentWrapper = React.forwardRef((props, ref) => (
  <Box width={['100%', '50%', '33.3%', '25%']} {...props} ref={ref} />
));

const Popover = props => <StyledPopover {...props} />;

Popover.propTypes = PopoverProps;

Popover.defaultProps = {
  PaperProps: {
    component: PopoverContentWrapper,
  },
  anchorOrigin: {
    vertical: 'bottom',
    horizontal: 'left',
  },
  transformOrigin: {
    vertical: 'top',
    horizontal: 'left',
  },
};

export default Popover;

import React from 'react';
import PropTypes from 'prop-types';
import { default as Base, PopoverProps } from '@material-ui/core/Popover';
import styled from 'styled-components';
import { Box, BoxProps } from 'rebass/styled-components';

import { borders, radii, shadows, space } from '../../themes/baseTheme';

const StyledPopover = styled(Base)`
  .MuiPopover-paper {
    margin-top: ${space[2]}px;
    margin-bottom: ${space[2]}px;
    border: ${borders.modal};
    box-shadow: ${shadows.large};
    border-radius: ${radii.default}px;
    width: ${({ width }) => width};
    max-width: calc(100% - ${space[5]}px);
  }
`;

const PopoverContentWrapper = React.forwardRef((props, ref) => (
  <Box {...props} ref={ref} />
));

const Popover = props => {
  const {
    themeProps,
    PaperProps,
    ...popoverProps
  } = props;

  return (
    <StyledPopover
      PaperProps={{ component: PopoverContentWrapper }}
      {...popoverProps}
    />
  );
};

Popover.propTypes = {
  ...PopoverProps,
  themeProps: PropTypes.shape(BoxProps),
};

Popover.defaultProps = {
  width: 'auto',
  anchorOrigin: {
    vertical: 'bottom',
    horizontal: 'left',
  },
  transformOrigin: {
    vertical: 'top',
    horizontal: 'left',
  },
};

export default Popover;

Rework popover to allow width to auto size by default, and be overriden by prop.


import { FlowRouter } from 'meteor/kadira:flow-router';
import { BlazeLayout } from 'meteor/kadira:blaze-layout';
// import { AccountsTemplates } from 'meteor/useraccounts:core';

// Import to load these templates
import '../../ui/layouts/layout.js';
import '../../ui/pages/backlog.js';
import '../../ui/pages/dashboard.js';
import '../../ui/pages/history.js';

// Import to override accounts templates
// import '../../ui/accounts/accounts-templates.js'; // TODO: set up user accounts

// Below here are the route definitions
FlowRouter.route('/dashboard', {
  action: function(params, queryParams) {
    BlazeLayout.render('layout', { page: 'dashboard' });
  }
});

FlowRouter.route('/history', {
  action: function(params, queryParams) {
    BlazeLayout.render('layout', { page: 'history' });
  }
});

FlowRouter.route('/', {
  triggersEnter: [function(context, redirect) {
    redirect('/dashboard');
  }]
});

import { FlowRouter } from 'meteor/kadira:flow-router';
import { BlazeLayout } from 'meteor/kadira:blaze-layout';
// import { AccountsTemplates } from 'meteor/useraccounts:core';

// Import to load these templates
import '../../ui/layouts/layout.js';
import '../../ui/pages/dashboard.js';
import '../../ui/pages/history.js';

// Import to override accounts templates
// import '../../ui/accounts/accounts-templates.js'; // TODO: set up user accounts

// Below here are the route definitions
FlowRouter.route('/dashboard', {
  action: function(params, queryParams) {
    BlazeLayout.render('layout', { page: 'dashboard' });
  }
});

FlowRouter.route('/history', {
  action: function(params, queryParams) {
    BlazeLayout.render('layout', { page: 'history' });
  }
});

FlowRouter.route('/', {
  triggersEnter: [function(context, redirect) {
    redirect('/dashboard');
  }]
});

Remove lingering backlog import that broke things


/*global describe, it */
var Nut    = require('../lib'),
    pfs    = require('../lib/pfs'),
    path   = require('path'),
    expect = require('chai').expect;

function testFile(basename) {
  return path.resolve(__dirname, '..', 'tmp', 'data', basename);
}

describe('main', function () {
  it('should modify the index file', function(done) {
    // Takes about 15 seconds to optimize.
    this.timeout(30000);

    var out = testFile('out');
    Nut.main({
      argv: [
        '--silent',
        '--input', path.relative(path.join(__dirname, '..'), testFile('doc')),
        '--output', out,
      ],
    }).then(function () {
      pfs.readFile(path.resolve(out, 'index.html'), {encoding: 'utf8'}).then(
        function (data) {
          expect( data ).to.match(/<script src="main.opt.js"/);
          done();
        }
      ).catch(done);
    }).catch(done);
  });
});

/*global describe, it */
var Nut    = require('../lib'),
    pfs    = require('../lib/pfs'),
    path   = require('path'),
    expect = require('chai').expect;

function testFile(basename) {
  return path.resolve(__dirname, '..', 'tmp', 'data', basename);
}

describe('main', function () {
  it('should build docs and modify index file', function(done) {
    // Takes about 15 seconds to optimize.
    this.timeout(30000);

    var out = testFile('out');
    Nut.main({
      argv: [
        '--silent',
        '--input', path.relative(path.join(__dirname, '..'), testFile('doc')),
        '--output', out,
      ],
    }).then(function () {
      pfs.readFile(path.resolve(out, 'index.html'), {encoding: 'utf8'}).then(
        function (data) {
          expect( data ).to.match(/<script src="main.opt.js"/);
          done();
        }
      ).catch(done);
    }).catch(done);
  });
});

Make test name more descriptive


$(() => {
  $('.js-character-count').each((i, el) => {
    const $el = $(el);
    const $target = $el.siblings($el.attr('data-target'));
    const max = $el.attr('data-max');

    $target.on('keyup', (ev) => {
      const $tgt = $(ev.target);
      const count = $tgt.val().length;
      const text = `${count} / ${max}`;
      if (count > max) {
        $el.removeClass('has-color-yellow-700').addClass('has-color-red-500');
        const $button = $el.parents('form').find('input[type="submit"]');
        if ($button) {
          $button.attr('disabled', true).addClass('is-muted');
        }
      }
      else if (count > 0.75 * max) {
        $el.removeClass('has-color-red-500').addClass('has-color-yellow-700');
        const $button = $el.parents('form').find('input[type="submit"]');
        if ($button) {
          $button.attr('disabled', false).removeClass('is-muted');
        }
      }
      else {
        $el.removeClass('has-color-red-500 has-color-yellow-700');
        const $button = $el.parents('form').find('input[type="submit"]');
        if ($button) {
          $button.attr('disabled', false).removeClass('is-muted');
        }
      }
      $el.text(text);
    });
  });
});

$(() => {
  $('.js-character-count').each((i, el) => {
    const $el = $(el);
    const $target = $el.siblings($el.attr('data-target'));
    const max = $el.attr('data-max');

    $target.on('keyup cc-reset', (ev) => {
      const $tgt = $(ev.target);
      const count = $tgt.val().length;
      const text = `${count} / ${max}`;
      if (count > max) {
        $el.removeClass('has-color-yellow-700').addClass('has-color-red-500');
        const $button = $el.parents('form').find('input[type="submit"]');
        if ($button) {
          $button.attr('disabled', true).addClass('is-muted');
        }
      }
      else if (count > 0.75 * max) {
        $el.removeClass('has-color-red-500').addClass('has-color-yellow-700');
        const $button = $el.parents('form').find('input[type="submit"]');
        if ($button) {
          $button.attr('disabled', false).removeClass('is-muted');
        }
      }
      else {
        $el.removeClass('has-color-red-500 has-color-yellow-700');
        const $button = $el.parents('form').find('input[type="submit"]');
        if ($button) {
          $button.attr('disabled', false).removeClass('is-muted');
        }
      }
      $el.text(text);
    });

    $target.parents('form').on('ajax:success', ev => {
      $target.val('').trigger('cc-reset');
    });
  });
});

Reset char count on post


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

function createCompilationFile(body) {
  var require = "var assert = require('assert');";
  fs.appendFile('test.js', require+'\n'+'\n'+body.test, function (err) {
    });
}
exports.createCompilationFile = createCompilationFile;

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

function createCompilationFile(body) {
  var require = "var assert = require('assert');";
  fs.appendFile('test.js', require+'\n'+body.content+'\n'+body.test, function (err) {
    });
}
exports.createCompilationFile = createCompilationFile;

Add content submission for file


'use strict';

var mongoose = require('mongoose');

var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;

var runSchema = new Schema({
    time: Date,
    participants: [ String ],
    results: [{
        season: Number,
        fishStart: Number,
        fishEnd: Number,
        groupRestraint: Number,
        groupEfficiency: Number,
        fishers: [{
            name: String,
            type: {type: String},
            fishTaken: Number,
            profit: Number,
            greed: Number,
            individualRestraint: Number,
            individualEfficiency: Number
        }]
    }],
    log: [ String ],
    microworld: {}
});

exports.Run = mongoose.model('Run', runSchema);

'use strict';

var mongoose = require('mongoose');

var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;

var runSchema = new Schema({
    time: Date,
    participants: [ String ],
    results: [{
        season: Number,
        fishStart: Number,
        fishEnd: Number,
        groupRestraint: Number,
        groupEfficiency: Number,
        fishers: [{
            name: String,
            type: {type: String},
            fishTaken: Number,
            profit: Number,
            greed: Number,
            greedSpread: Number,
            individualRestraint: Number,
            individualEfficiency: Number
        }]
    }],
    log: [ String ],
    microworld: {}
});

exports.Run = mongoose.model('Run', runSchema);

Add greedSpread to Run Model


// Definitions we want Flow to assume.
//
// For docs on the format, see:
//   https://flow.org/en/docs/libdefs/creation/

declare var __TEST__: true | void;

Add a quick reference-comment in that new libdef file.


/* Copyright (c) 2015-present, salesforce.com, inc. All rights reserved */
/* Licensed under BSD 3-Clause - see LICENSE.txt or git.io/sfdc-license */

/* eslint-disable import/no-mutable-exports */

// This function will deliver an error message to the browser console about the removal of a property.
import warning from 'warning';

let deprecated = function () {};

if (process.env.NODE_ENV !== 'production') {
	const hasWarned = {};

	deprecated = function (control, propValue, oldProp, newProp, comment) {
		const additionalComment = comment ? ` ${comment}` : '';
		const newProperty = newProp ? `Use \`${newProp}\`` : '';
		if (!hasWarned[control + oldProp]) {
			/* eslint-disable max-len */
			warning(propValue === undefined, `[Design System React] \`${oldProp}\` will be removed in the next major version of ${control}. ${newProperty} instead.${additionalComment}`);
			/* eslint-enable max-len */
			hasWarned[control + oldProp] = propValue !== undefined;
		}
	};
}

export default deprecated;

/* Copyright (c) 2015-present, salesforce.com, inc. All rights reserved */
/* Licensed under BSD 3-Clause - see LICENSE.txt or git.io/sfdc-license */

/* eslint-disable import/no-mutable-exports */

// This function will deliver an error message to the browser console about the removal of a property.
import warning from 'warning';

let deprecated = function () {};

if (process.env.NODE_ENV !== 'production') {
	const hasWarned = {};

	deprecated = function (control, propValue, oldProp, newProp, comment) {
		const additionalComment = comment ? ` ${comment}` : '';
		const newProperty = newProp ? `Use \`${newProp}\`` : '';
		const newPropertySentence = newProp ? ` ${newProperty} instead.` : '';
		if (!hasWarned[control + oldProp]) {
			/* eslint-disable max-len */
			warning(propValue === undefined, `[Design System React] \`${oldProp}\` will be removed in the next major version of ${control}.${newPropertySentence}${additionalComment}`);
			/* eslint-enable max-len */
			hasWarned[control + oldProp] = propValue !== undefined;
		}
	};
}

export default deprecated;

Allow deprecated warning to have one prop


'use strict';

//TODO: Remove dependency on toastr
/** Check to see if dependent modules exist */
try {
    angular.module('toastr');
}
catch (e) {
    /** Toastr wasn't found so redirect all toastr requests to $log */
    angular.module('toastr', [])
        .factory('toastr', function ($log) {
            return {
                error: $log.error,
                info: $log.info,
                success: $log.info,
                warning: $log.warn
            };
        });
}

angular.module('angularPoint', [
    'toastr',
    'ngMock'
]);

'use strict';

//TODO: Remove dependency on toastr
/** Check to see if dependent modules exist */
try {
    angular.module('toastr');
}
catch (e) {
    /** Toastr wasn't found so redirect all toastr requests to $log */
    angular.module('toastr', [])
        .factory('toastr', function ($log) {
            return {
                error: $log.error,
                info: $log.info,
                success: $log.info,
                warning: $log.warn
            };
        });
}

angular.module('angularPoint', [
    'toastr',
    'ngMock'
]).constant('mockUser', {lookupId: 100, lookupValue: 'Joe User'});

Allow a default mock user to be identified when running in offline mode.


import assert from "assert";
import { join } from "path";
import {
  readdirSync,
  readFileSync,
  statSync,
} from "fs";
import { compile, transform } from "../lib/compiler.js";
import { prettyPrint } from "recast";

var jsFiles = Object.create(null);

function walk(dir) {
  readdirSync(dir).forEach(function (item) {
    var absPath = join(dir, item);
    var stat = statSync(absPath);
    if (stat.isDirectory()) {
      walk(absPath);
    } else if (item.endsWith(".js") &&
               stat.isFile()) {
      jsFiles[absPath] = readFileSync(absPath, "utf8");
    }
  });
}

walk(__dirname);

describe("compiler.transform", function () {
  function check(options) {
    Object.keys(jsFiles).forEach(function (absPath) {
      var code = jsFiles[absPath];
      assert.strictEqual(
        prettyPrint(compile(code, options).ast).code,
        prettyPrint(transform(options.parse(code), options)).code,
        "Transform mismatch for " + absPath
      );
    });
  }

  it("gives the same results as compile with babylon", function () {
    check({
      ast: true,
      parse: require("../lib/parsers/babylon.js").parse
    });
  });

  it("gives the same results as compile with acorn", function () {
    check({
      ast: true,
      parse: require("../lib/parsers/acorn.js").parse
    });
  });
});

import assert from "assert";
import { join } from "path";
import {
  readdirSync,
  readFileSync,
  statSync,
} from "fs";
import { compile, transform } from "../lib/compiler.js";
import { prettyPrint } from "recast";

var jsFiles = Object.create(null);

function walk(dir) {
  readdirSync(dir).forEach(function (item) {
    var absPath = join(dir, item);
    var stat = statSync(absPath);
    if (stat.isDirectory()) {
      walk(absPath);
    } else if (item.endsWith(".js") &&
               stat.isFile()) {
      jsFiles[absPath] = readFileSync(absPath, "utf8");
    }
  });
}

walk(__dirname);

describe("compiler.transform", function () {
  function check(options) {
    Object.keys(jsFiles).forEach(function (absPath) {
      var code = jsFiles[absPath];
      assert.strictEqual(
        prettyPrint(compile(code, options).ast).code,
        prettyPrint(transform(options.parse(code), options)).code,
        "Transform mismatch for " + absPath
      );
    });
  }

  it("gives the same results as compile with babylon", function () {
    check({
      ast: true,
      parse: require("../lib/parsers/babylon.js").parse
    });
  }).timeout(5000);

  it("gives the same results as compile with acorn", function () {
    check({
      ast: true,
      parse: require("../lib/parsers/acorn.js").parse
    });
  }).timeout(5000);
});

Fix expensive transform tests by increasing timeouts to 5000ms.


const Request = require('request');

module.exports = (options, cb) => {
  const payload = { 'assignees': [ options.userName ] };

  Request.post({
    url: options.assignUrl,
    headers: {
      'User-Agent': 'GitPom',
      'Authorization': `token ${options.access_token}`
    },
    json: payload
  }, cb);
};

const Request = require('request');

module.exports = (options, cb) => {
  const headers = {
    'User-Agent': 'GitPom',
    'Authorization': `token ${options.access_token}`
  };
  const assignUrl = options.issueUrl + '/asignees';
  const labelUrl = options.issueUrl + '/labels';
  const assignPayload = { 'assignees': [ options.userName ] };
  const labelPayload = [ 'In Progress' ];

  Request.post({
    url: assignUrl,
    headers: headers,
    json: assignPayload
  }, (err, res, body) => {
    if (err) cb(err);
    else {
      Request.post({
        url: labelUrl,
        headers: headers,
        json: labelPayload
      }, cb);
    }
  });
};

Add additional POST request to create 'In Progress' label on issue


import _ from 'underscore';

const DATE_FILENAME_MATCHER = /^(?:.+\/)*(\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1]))-(.*)(\.[^.]+)$/;
const FILENAME_MATCHER = /^(.*)(\.[^.]+)$/;

/**
 * Returns error messages if the given values does not pass the provided validations.
 * @param {Object} values
 * @param {Object} validations
 * @param {Object} messages
 * @return {Array} errorMessages
 */
export const validator = (values, validations, messages) => {
  let errorMessages = [];
  _.each(validations, (validationStr, field, list) => {
    const validationArr = validationStr.split('|');
    _.each(validationArr, single => {
      if (!validated(values[field], single)) {
        errorMessages.push(messages[`${field}.${single}`]);
      }
    });
  });
  return errorMessages;
};

const validated = (field, single) => {
  switch (single) {
    case 'required':
      return !!field;
    case 'date':
      return DATE_FILENAME_MATCHER.test(field);
    case 'filename':
      return FILENAME_MATCHER.test(field);
    default:
      return false;
  }
};

import _ from 'underscore';

const DATE_FILENAME_MATCHER = /^(?:.+\/)*(\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1]))-(.*)(\.[^.]+)$/;
const FILENAME_MATCHER = /^(.*)(\.[^.]+)$/;

const validated = (field, single) => {
  switch (single) {
    case 'required':
      return !!field;
    case 'date':
      return DATE_FILENAME_MATCHER.test(field);
    case 'filename':
      return FILENAME_MATCHER.test(field);
    default:
      return false;
  }
};

/**
 * Returns error messages if the given values does not pass the provided validations.
 * @param {Object} values
 * @param {Object} validations
 * @param {Object} messages
 * @return {Array} errorMessages
 */
export const validator = (values, validations, messages) => {
  let errorMessages = [];
  _.each(validations, (validationStr, field, list) => {
    const validationArr = validationStr.split('|');
    _.each(validationArr, single => {
      if (!validated(values[field], single)) {
        errorMessages.push(messages[`${field}.${single}`]);
      }
    });
  });
  return errorMessages;
};

Move exported function to end of file


module.exports = {
  'extends': [
    'eslint:recommended'
  ],
  'rules': {
    'indent': [2, 2, {
      'SwitchCase': 1
    }],
    'arrow-spacing': [2, {
      'before': true, 'after': true
    }],
    'quotes': [2, 'single'],
    'semi': [2, 'always'],
    'no-console': [0],
    'no-var': 1,
    'prefer-template': 2,
    'no-useless-concat': 2,
    'space-before-function-paren': [2, 'never'],
    'object-curly-spacing': [2, 'always'],
    'array-bracket-spacing': [2, 'never'],
    'comma-spacing': [2, {
      'before': false,
      'after': true
    }],
    'space-infix-ops': 2,
    'space-unary-ops': [2, {
      'words': true,
      'nonwords': false
    }],
    'space-in-parens': [2, 'never'],
    'keyword-spacing': 2,
    'no-unused-vars': [1, {
      'args': 'none',
      'vars': 'all',
      'varsIgnorePattern': 'React'
    }],
    'default-case': 2
  },
  'ecmaFeatures': {
    'modules': true
  },
  'env': {
    'es6': true,
    'node': true,
    'browser': true
  }
};

module.exports = {
  'extends': [
    'eslint:recommended'
  ],
  'rules': {
    'indent': [2, 2, {
      'SwitchCase': 1
    }],
    'arrow-spacing': [2, {
      'before': true, 'after': true
    }],
    'quotes': [2, 'single'],
    'semi': [2, 'always'],
    'no-console': [0],
    'no-var': 2,
    'prefer-template': 2,
    'no-useless-concat': 2,
    'space-before-function-paren': [2, 'never'],
    'object-curly-spacing': [2, 'always'],
    'array-bracket-spacing': [2, 'never'],
    'comma-spacing': [2, {
      'before': false,
      'after': true
    }],
    'space-infix-ops': 2,
    'space-unary-ops': [2, {
      'words': true,
      'nonwords': false
    }],
    'space-in-parens': [2, 'never'],
    'keyword-spacing': 2,
    'no-unused-vars': [1, {
      'args': 'none',
      'vars': 'all',
      'varsIgnorePattern': 'React'
    }],
    'default-case': 2
  },
  'ecmaFeatures': {
    'modules': true
  },
  'env': {
    'es6': true,
    'node': true,
    'browser': true
  }
};

Throw an error on var declaration


ECMAScript = {
  compileForShell(command, cacheOptions) {
    const babelOptions = Babel.getDefaultOptions();
    delete babelOptions.sourceMap;
    delete babelOptions.sourceMaps;
    babelOptions.ast = false;
    return Babel.compile(command, babelOptions, cacheOptions).code;
  }
};

ECMAScript = {
  compileForShell(command, cacheOptions) {
    const babelOptions = Babel.getDefaultOptions({
      nodeMajorVersion: parseInt(process.versions.node, 10)
    });
    delete babelOptions.sourceMap;
    delete babelOptions.sourceMaps;
    babelOptions.ast = false;
    return Babel.compile(command, babelOptions, cacheOptions).code;
  }
};

Make sure `meteor shell` commands are compiled for Node 8.


$(document).ready(function() {

  // put all your jQuery code here

});

$(document).ready(function() {

  // Add class to all inputs (input's type attribute value)
  $('input').each(function() {$(this).addClass(this.type)});

  // put all your jQuery code here
});

Add class to all inputs


/*! @license ©2013 Ruben Verborgh - Multimedia Lab / iMinds / Ghent University */
/** Browser replacement for the request module using jQuery. */

var EventEmitter = require('events').EventEmitter,
    SingleIterator = require('../../lib/iterators/Iterator').SingleIterator;

require('setimmediate');

// Requests the given resource as an iterator
function createRequest(settings) {
  var request = new EventEmitter();

  // Delegate the request to jQuery's AJAX module
  var jqXHR = jQuery.ajax({
    url: settings.url,
    timeout: settings.timeout,
    type: settings.method,
    headers: { accept: 'text/turtle' },
  })
  // Emit the result as a readable response iterator
  .always(function () {
    if (jqXHR.failure) return;
    var response = new SingleIterator(jqXHR.responseText || '');
    response.statusCode = jqXHR.status;
    response.headers = { 'content-type': jqXHR.getResponseHeader('content-type') };
    request.emit('response', response);
  });

  return request;
}

module.exports = createRequest;

/*! @license ©2013 Ruben Verborgh - Multimedia Lab / iMinds / Ghent University */
/** Browser replacement for the request module using jQuery. */

var EventEmitter = require('events').EventEmitter,
    SingleIterator = require('../../lib/iterators/Iterator').SingleIterator;

require('setimmediate');

// Requests the given resource as an iterator
function createRequest(settings) {
  var request = new EventEmitter();

  // Delegate the request to jQuery's AJAX module
  var jqXHR = jQuery.ajax({
    url: settings.url,
    timeout: settings.timeout,
    type: settings.method,
    headers: { accept: settings.headers && settings.headers.accept || '*/*' },
  })
  // Emit the result as a readable response iterator
  .always(function () {
    if (jqXHR.failure) return;
    var response = new SingleIterator(jqXHR.responseText || '');
    response.statusCode = jqXHR.status;
    response.headers = { 'content-type': jqXHR.getResponseHeader('content-type') };
    request.emit('response', response);
  });

  return request;
}

module.exports = createRequest;

Use the same accept headers in browsers.


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

    it('should require(), map() and reduce correctly (check console errors)', function () {
        var p = new Parallel([0, 1, 2, 3, 4, 5, 6, 7, 8]);

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

        p.require(factorial);

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

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

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

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

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

        p.require(factorial);

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

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

Make test in IE pass


Package.describe({
  name: 'svelte:compiler',
  version: '2.13.5_1',
  summary: 'Svelte compiler',
  git: 'https://github.com/meteor-svelte/meteor-svelte.git',
  documentation: 'README.md'
});

Package.registerBuildPlugin({
  name: 'svelte-compiler',
  use: [
    'babel-compiler@6.24.7||7.0.0',
    'caching-compiler@1.1.11',
    'ecmascript@0.9.0'
  ],
  sources: [
    'SvelteCompiler.js',
    'plugin.js'
  ],
  npmDependencies: {
    'find-up': '2.1.0',
    htmlparser2: '3.9.2',
    'source-map': '0.5.6',
    svelte: '2.13.5'
  }
});

Package.onUse(function (api) {
  api.versionsFrom('1.6');
  api.use('isobuild:compiler-plugin@1.0.0');

  // Dependencies for compiled Svelte components (taken from `ecmascript`).
  api.imply([
    'modules',
    'ecmascript-runtime',
    'babel-runtime',
    'promise'
  ]);
});

Package.describe({
  name: 'svelte:compiler',
  version: '2.13.5_2',
  summary: 'Svelte compiler',
  git: 'https://github.com/meteor-svelte/meteor-svelte.git',
  documentation: 'README.md'
});

Package.registerBuildPlugin({
  name: 'svelte-compiler',
  use: [
    'babel-compiler@6.24.7||7.0.0',
    'caching-compiler@1.1.11',
    'ecmascript@0.9.0'
  ],
  sources: [
    'SvelteCompiler.js',
    'plugin.js'
  ],
  npmDependencies: {
    '@babel/runtime': '7.1.2',
    'find-up': '2.1.0',
    htmlparser2: '3.9.2',
    'source-map': '0.5.6',
    svelte: '2.13.5'
  }
});

Package.onUse(function (api) {
  api.versionsFrom('1.6');
  api.use('isobuild:compiler-plugin@1.0.0');

  // Dependencies for compiled Svelte components (taken from `ecmascript`).
  api.imply([
    'modules',
    'ecmascript-runtime',
    'babel-runtime',
    'promise'
  ]);
});

Add `@babel/runtime` to npm dependencies


export function sendActivitySessionInteractionLog(activitySessionUID, data = {}) {
  if (activitySessionUID === 'null' || activitySessionUID == null) {
    return null;
  }
  const url = `${process.env.EMPIRICAL_BASE_URL}/api/v1/activity_sessions/${activitySessionUID}/activity_session_interaction_logs`;
  fetch(url, {
    method: 'POST',
    mode: 'cors',
    credentials: 'include',
    body: JSON.stringify({ meta: data, }),
    headers: {
      'Content-Type': 'application/json',
      'Accept': 'application/json',
    },
  });
}

export function sendActivitySessionInteractionLog(activitySessionUID, data = {}) {
  return null;
  // if (activitySessionUID === 'null' || activitySessionUID == null) {
  //   return null;
  // }
  // const url = `${process.env.EMPIRICAL_BASE_URL}/api/v1/activity_sessions/${activitySessionUID}/activity_session_interaction_logs`;
  // fetch(url, {
  //   method: 'POST',
  //   mode: 'cors',
  //   credentials: 'include',
  //   body: JSON.stringify({ meta: data, }),
  //   headers: {
  //     'Content-Type': 'application/json',
  //     'Accept': 'application/json',
  //   },
  // });
}

Comment out actvitiy session logging for now


/**
 * An implementation of selectionSort
 *
 * @name selectionSort
 * @function
 * @param {Array} xs the list to sort
 */
export function selectionSort(xs) {
  for (let i = 0; i < xs.length; i++) {
    let min = i;

    for (let j = i + 1; j < xs.length; j++) {
      if (xs[j] < xs[i]) {
        min = j;
      }
    }

    if (i !== min) {
      xs.splice(i, 0, xs.splice(min, 1)[0]);
    }
  }
}

/**
 * An implementation of selectionSort
 *
 * @name selectionSort
 * @function
 * @param {Array} xs the list to sort
 *
 * 1. Set the first item in the loop as the minimum.
 * 2. Check each item in the list to see if its smaller than the minimum.
 * 3. If any item is smaller than the initial minimum set it as the new minimum.
 * 4. If any smaller item was found move it to the space before the initial minimum.
 * 5. Repeat 1 - 4 for every item in the list.
 */
export function selectionSort(xs) {
  for (let i = 0; i < xs.length; i++) {
    // 1.
    let min = i;

    for (let j = i + 1; j < xs.length; j++) {
      // 2.
      if (xs[j] < xs[i]) {
        // 3.
        min = j;
      }
    }

    // 4.
    if (i !== min) {
      xs.splice(i, 0, xs.splice(min, 1)[0]);
    }
  }
}

Add documentation for each step in the selectionSort process


/*eslint-env amd */
var gulp = require('gulp');
var es6ify = require('es6ify');
var eslint = require('gulp-eslint');
var browserify = require('browserify');
var source = require('vinyl-source-stream');
var browserSync = require('browser-sync');

var paths = {
  src: ["src/**/*.js"]
}

gulp.task('eslint', function() {
  return gulp.src(paths.src)
    .pipe(eslint())
    .pipe(eslint.format())
    .pipe(eslint.failOnError());
})

gulp.task('browser-sync', function() {
  browserSync({
    server: {
      baseDir: "."
    },
    port: 3004
  })
})

gulp.task('default', ['eslint'], function () {
  return browserify('./src/cors-upload.js')
    .transform(es6ify)
    .bundle()
    .pipe(source('cors-upload-bundle.js'))
    .pipe(gulp.dest('./dist/'));
});

/*eslint-env amd */
var gulp = require('gulp');
var es6ify = require('es6ify');
var eslint = require('gulp-eslint');
var browserify = require('browserify');
var source = require('vinyl-source-stream');
var browserSync = require('browser-sync');

var paths = {
  src: ["src/**/*.js"]
}

gulp.task('eslint', function() {
  return gulp.src(paths.src)
    .pipe(eslint())
    .pipe(eslint.format())
    .pipe(eslint.failOnError());
})

gulp.task('browser-sync', function() {
  browserSync({
    server: {
      baseDir: "."
    },
    port: 3004
  });
})

gulp.task('browserify', ['eslint'], function () {
  return browserify('./src/cors-upload.js')
    .transform(es6ify)
    .bundle()
    .pipe(source('cors-upload-bundle.js'))
    .pipe(gulp.dest('./dist/'));
});

gulp.task('reload', function(){
  browserSync.reload();
})

gulp.task('watch', ['browserify', 'browser-sync'], function () {
  gulp.watch(paths.src, ['eslint', 'reload']);
})

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

Add watches on es6 sources


// @flow
import commentMarker from 'mdast-comment-marker'
import visit from 'unist-util-visit'

import type { Parent } from 'unist'
import type { HTML } from 'mdast'
import type { Marker } from 'mdast-comment-marker'

export interface DirectiveCommentVisitor<Options> {
  static directiveName: string,
  static reverse: boolean,
  constructor(options: Options): DirectiveCommentVisitor<Options>,
  beforeVisiting(parent: Parent): void,
  afterVisiting(parent: Parent): void,
  visit(marker: Marker, index: number, parent: ?Parent): ?boolean,
}

function isComment ({ value }: HTML): boolean {
  return value.startsWith('<!--') && value.endsWith('-->')
}

export default function createPlugin<O: Object> (Visitor: Class<DirectiveCommentVisitor<O>>) {
  return plugin

  function plugin (options: O) {
    let visitorInstance

    return transformer

    function transformer (tree: Parent) {
      visitorInstance = new Visitor(options)
      visitorInstance.beforeVisiting(tree)
      visit(tree, 'html', visitor, Visitor.reverse || false)
      visitorInstance.afterVisiting(tree)
    }

    function visitor (node: HTML, index: number, parent: ?Parent): ?boolean {
      if (!isComment(node)) {
        return
      }

      const marker = commentMarker(node)

      if (marker.name === Visitor.directiveName) {
        return visitorInstance.visit(marker, index, parent)
      }
    }
  }
}

// @flow
import commentMarker from 'mdast-comment-marker'
import visit from 'unist-util-visit'

import type { Parent } from 'unist'
import type { HTML } from 'mdast'
import type { Marker } from 'mdast-comment-marker'

export interface DirectiveCommentVisitor<Options> {
  static directiveName: string,
  static reverse: boolean,
  constructor(options: Options): DirectiveCommentVisitor<Options>,
  beforeVisiting(parent: Parent): void,
  afterVisiting(parent: Parent): void,
  visit(marker: Marker, index: number, parent: ?Parent): ?boolean,
}

function isComment ({ value }: HTML): boolean {
  return value.startsWith('<!--') && value.endsWith('-->')
}

export default function createPlugin<O: Object> (Visitor: Class<DirectiveCommentVisitor<O>>) {
  return plugin

  function plugin (options: O) {
    let visitorInstance

    return transformer

    function transformer (tree: Parent) {
      visitorInstance = new Visitor(options)
      visitorInstance.beforeVisiting(tree)
      visit(tree, 'html', visitor, Visitor.reverse || false)
      visitorInstance.afterVisiting(tree)
    }

    function visitor (node: HTML, index: number, parent: ?Parent): ?boolean {
      if (!isComment(node)) {
        return
      }

      const marker = commentMarker(node)

      if (marker != null && marker.name === Visitor.directiveName) {
        return visitorInstance.visit(marker, index, parent)
      }
    }
  }
}

Add null guard to mdast-directive-comment util


import babel from 'rollup-plugin-babel';
import { readFileSync } from 'fs';

const babelConfig = JSON.parse(readFileSync('.babelrc', { encoding: 'utf8' }));
babelConfig.presets = babelConfig.presets.map(preset => preset === 'es2015' ? 'es2015-rollup' : preset);
babelConfig.babelrc = false;

export default {
  plugins: [babel(babelConfig)],
  external: ['coffee-script']
};

import babel from 'rollup-plugin-babel';
import { readFileSync } from 'fs';

var babelConfig = JSON.parse(readFileSync('.babelrc', { encoding: 'utf8' }));
babelConfig.presets = babelConfig.presets.map(function(preset) {
  return preset === 'es2015' ? 'es2015-rollup' : preset;
});
babelConfig.babelrc = false;

export default {
  plugins: [babel(babelConfig)],
  external: ['coffee-script']
};

Use node v0.12-compatible features to allow building with that version.


import google from 'googleapis';
import nconf from '../config';
import User from '../models/user';

const OAuth2 = google.auth.OAuth2;

export default class GoogleProvider {

  constructor(token, dce) {
    this.dce = dce;
    this.authLevel = nconf.get('permissions:levels:high');
    this.token = token;
    this.client = new OAuth2(nconf.get('auth:google:id'), nconf.get('auth:google:secret'));
  }

  verify() {
    return new Promise((resolve, reject) => {
      this.client.verifyIdToken(this.token, this.client.clientId, (err, ticket) => {
        if (err) {
          return reject(err);
        }
        this.payload = ticket.getPayload();
        if (this.payload.hd === 'g.rit.edu') {
          return resolve();
        }
        return reject({ message: 'Must login with a g.rit.edu account' });
      });
    });
  }

  findOrCreateUser() {
    return User
      .findOrCreate({ where: { dce: this.dce } })
      .spread((user, created) => {
        user.firstName = this.payload.given_name;
        user.lastName = this.payload.family_name;
        return Promise.all([user.save(), created]);
      });
  }
}

import google from 'googleapis';
import nconf from '../config';
import User from '../models/user';

const OAuth2 = google.auth.OAuth2;

export default class GoogleProvider {

  constructor(token, dce) {
    this.dce = dce;
    this.authLevel = nconf.get('permissions:levels:high');
    this.token = token;
    this.client = new OAuth2(nconf.get('auth:google:id'), nconf.get('auth:google:secret'));
  }

  verify() {
    return new Promise((resolve, reject) => {
      // TODO: 'clientId' is 'undefined'; should be 'clientId_'
      // This may change when upgrading 'googleapis'
      this.client.verifyIdToken(this.token, this.client.clientId, (err, ticket) => {
        if (err) {
          return reject(err);
        }
        this.payload = ticket.getPayload();
        if (this.payload.hd === 'g.rit.edu') {
          return resolve();
        }
        return reject({ message: 'Must login with a g.rit.edu account' });
      });
    });
  }

  findOrCreateUser() {
    return User
      .findOrCreate({ where: { dce: this.dce } })
      .spread((user, created) => {
        user.firstName = this.payload.given_name;
        user.lastName = this.payload.family_name;
        return Promise.all([user.save(), created]);
      });
  }
}

Add TODO in Google Provider


var https = require('https');

var BASE_URL = 'http://isithackday.com/arrpi.php';

exports.getYoloPirates = function (msg, callback) {
	http.get(encodeURI(base+'?text='+msg), function (res) {
		var pageData = "";
		res.on('data', function (chunk) {
			pageData += chunk;
		});

		res.on('end', function() {
			callback(pageData);
		});

	}).on('error', function (e) {
		console.log("Got error: " + e.message);
	});
}

Call ArrPI and translate that yolo pirate talk


/**
 * @ngdoc service
 * @name ngCordovaMocks.cordovaDatePicker
 *
 * @description
 * A service for testing datepicker features
 * in an app build with ngCordova.
 */
ngCordovaMocks.factory('$cordovaDatePicker', function ($q) {
  return {
    show: function (options) {
      var q = $q.defer();
      options = options || {date: new Date(), mode: 'date'};
      q.resolve(options.date);
      return q.promise;
    }
  };
});

/**
 * @ngdoc service
 * @name ngCordovaMocks.cordovaDatePicker
 *
 * @description
 * A service for testing datepicker features
 * in an app build with ngCordova.
 */
ngCordovaMocks.factory('$cordovaDatePicker', ['$q', function ($q) {
  return {
    show: function (options) {
      var q = $q.defer();
      options = options || {date: new Date(), mode: 'date'};
      q.resolve(options.date);
      return q.promise;
    }
  };
}]);

Fix [$injector:unpr] by using inline array annotation.


var webpack = require('webpack')

module.exports = {
  entry: './js/app.js',
  debug: true,
  devtool: 'source-map',
  output: {
    filename: 'bundle.js'
  },
  plugins: [
    new webpack.ProvidePlugin({
      riot: 'riot'
    })
  ],
  module: {
    preLoaders: [
      { test: /\.tag$/, exclude: /node_modules/, loader: 'riotjs-loader', query: { type: 'none' } }
    ],
    loaders: [
      { test: /\.js|\.tag$/, exclude: /node_modules/, loader: 'babel-loader' }
    ]
  }
}

var webpack = require('webpack')

module.exports = {
  entry: './js/app.js',
  debug: true,
  devtool: 'eval-cheap-module-source-map',
  output: {
    filename: 'bundle.js'
  },
  plugins: [
    new webpack.ProvidePlugin({
      riot: 'riot'
    })
  ],
  module: {
    preLoaders: [
      { test: /\.tag$/, exclude: /node_modules/, loader: 'riotjs-loader', query: { type: 'none' } }
    ],
    loaders: [
      { test: /\.js|\.tag$/, exclude: /node_modules/, loader: 'babel-loader' }
    ]
  }
}

Speed up webpack a bit


const path = require('path');

module.exports = {
    entry: {
        app: ['./src/index.js']
    },
    output: {
        path: path.resolve(__dirname, 'build'),
        publicPath: '/assets/', //virtual
        filename: 'bundle.js'
    },
    devServer: {
        contentBase: path.resolve(__dirname, 'build'),
        publicPath: '/assets/', //virtual
        inline: true,
        port: 8080
    },
    module: {
        loaders: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                loader: 'babel-loader'
            }
        ]
    }
};

const path = require('path');

module.exports = {
    entry: {
        app: ['./src/index.js']
    },
    output: {
        path: path.resolve(__dirname, 'build'),
        publicPath: '/assets/', //virtual
        filename: 'bundle.js'
    },
    devServer: {
        contentBase: path.resolve(__dirname, 'build'),
        publicPath: '/assets/', //virtual
        inline: true,
        port: 8080
    },
    module: {
        loaders: [
            {
                test: /\.(js|jsx)$/,
                exclude: /node_modules/,
                loader: 'babel-loader'
            }
        ]
    }
};

Add support for jsx in webpack


var path = require('path');
var ExtractTextPlugin = require("extract-text-webpack-plugin")

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js',
        publicPath: '/dist'
    },
    module: {
        loaders: [
            {
                test: /\.vue$/,
                loader: 'vue-loader',
                options: {
                    extractCss: true,
                    loaders: {
                        js: 'babel-loader'
                    }
                }
            }
        ]
    },
    plugins: [
        new ExtractTextPlugin("style.css")
    ],
    resolve: {
        alias: {
            vue: 'vue/dist/vue.js'
        }
    }
};

var path = require('path');
var ExtractTextPlugin = require("extract-text-webpack-plugin")

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js',
        publicPath: '/dist'
    },
    module: {
        loaders: [
            {
                exclude: [/(node_modules)/],
                test: /\.vue$/,
                loader: 'vue-loader',
                options: {
                    extractCss: true,
                    loaders: {
                        js: 'babel-loader'
                    }
                }
            }
        ]
    },
    plugins: [
        new ExtractTextPlugin("style.css")
    ],
    resolve: {
        alias: {
            vue: 'vue/dist/vue.js'
        }
    }
};

Exclude node_modules from vue loader


function documentClick(event) {
    var target = event.target;

    // Open non-javascript links clicked in background tabs.
    var targetClickedIsAnchor = target.nodeName === 'A';
    var anchorUrl = target.getAttribute('href');
    if (targetClickedIsAnchor && anchorUrl !== 'javascript:void(0)') {
        openBackgroundTab(anchorUrl);
        event.preventDefault();
        return;
    }

    // Avoid collapsing comment when there is a selection.
    if (window.getSelection().toString() !== '') {
        return;
    }

    // Toggle comment collapse when comment is clicked.
    var targetParents = getElementParents(target, '.comment');
    if (target.classList.contains('comment') || targetParents.length ) {
        var comment = getElementParents(target, '.athing')[0];
        var commentToggle = comment.querySelector('.togg');
        commentToggle.click();
    }
}

document.addEventListener('click', documentClick);

function documentClick(event) {
    var target = event.target;

    // Open non-javascript links clicked in background tabs.
    var targetClickedIsAnchor = target.nodeName === 'A';
    if (targetClickedIsAnchor && target.href && target.href !== 'javascript:void(0)') {
        openBackgroundTab(target.href);
        event.preventDefault();
        return;
    }

    // Avoid collapsing comment when there is a selection.
    if (window.getSelection().toString() !== '') {
        return;
    }

    // Toggle comment collapse when comment is clicked.
    var targetParents = getElementParents(target, '.comment');
    if (target.classList.contains('comment') || targetParents.length ) {
        var comment = getElementParents(target, '.athing')[0];
        var commentToggle = comment.querySelector('.togg');
        commentToggle.click();
    }
}

document.addEventListener('click', documentClick);

Fix link opened in background tab


var path = require("path");

module.exports = {
  entry: {
    forms:       "./forms.js",
    headings:    "./headings.js",
    images:      "./images.js",
    interactive: "./interactive.js",
    landmarks:   "./landmarks.js",
    lists:       "./lists.js"
  },
  output: {
    path: path.join(__dirname, "build"),
    filename: "[name].js"
  },
  module: {
    preLoaders: [
      { test: /\.js$/, exclude: /node_modules/, loader: "eslint-loader" }
    ],
    loaders: [
      { test: /\.js$/, exclude: /node_modules/, loader: "babel" }
    ]
  },
  eslint: {
    quiet: true
  }
};

var webpack = require("webpack");
var path = require("path");

module.exports = {
  entry: {
    forms:       "./forms.js",
    headings:    "./headings.js",
    images:      "./images.js",
    interactive: "./interactive.js",
    landmarks:   "./landmarks.js",
    lists:       "./lists.js"
  },
  output: {
    path: path.join(__dirname, "build"),
    filename: "[name].js"
  },
  module: {
    preLoaders: [
      { test: /\.js$/, exclude: /node_modules/, loader: "eslint-loader" }
    ],
    loaders: [
      { test: /\.js$/, exclude: /node_modules/, loader: "babel" },
      { test: /\.css$/, loader: "style!css" },
      { test: /\.(jpe?g|png|gif)$/i, loader: "file" }
    ]
  },
  plugins: [
    new webpack.ProvidePlugin({
      "$": "jquery",
      "jQuery": "jquery",
      "window.jQuery": "jquery"
    })
  ],
  resolve: {
    alias: {
      // jquery-ui dependencies
      "core": "jquery-ui/core.js",
      "widget": "jquery-ui/widget.js",
      "position": "jquery-ui/position.js",
      "tooltip": "jquery-ui/tooltip.js",
      // bind to modules;
      modules: path.join(__dirname, "node_modules")
    }
  },
  eslint: {
    quiet: true
  }
};

Add loaders, plugin and aliases for jquery, jquery-ui


var width = 570,
    height = 550;

var svg = d3.select("svg")
  .attr("width", width)
  .attr("height", height);

// Set up SVG for D3.
var width = 570,
    height = 550;

var svg = d3.select("svg")
    .attr("width", width)
    .attr("height", height)

var SEED_RADIUS = 5;

// Let F_n be the nth Fibonacci number. We can prove by
// induction that \sum_{i=1}^{n}{F_i} = F_{n+2} - 1. Then
// we choose 376 = F_14 - 1 = \sum{i=1}^{12}{F_i}, because
// the outer rim of a sunflower usually has F_12 = 144 seeds.
// See: http://www.proofwiki.org/wiki/Sum_of_Sequence_of_Fibonacci_Numbers
var NUM_SEEDS = 376;

// See: http://en.wikipedia.org/wiki/Golden_angle
var GOLDEN_ANGLE = Math.PI * (3 - Math.sqrt(5));

// Returns { r: radius, phi: angle } using Vogel's formulas.
// See: http://algorithmicbotany.org/papers/abop/abop-ch4.pdf
function place (n) {
  var   C = 7.5,
        r = C * Math.sqrt(n),
      phi = n * GOLDEN_ANGLE;

  return { r: r * Math.cos(phi), phi: r * Math.sin(phi) };
}

// d3.range(start, end) returns the range from start to end - 1.
// We add one back to get 376 elements as intended.
var seeds = d3.range(1, NUM_SEEDS + 1).map(place);

svg.selectAll("circle")
    .data(seeds)
  .enter().append("circle")
    .attr("cx", function(d) { return width / 2 + d.r; })
    .attr("cy", function(d) { return height / 2 + d.phi; })
    .attr("r", SEED_RADIUS);

Add code that generated the sunflower.


window.ImgLoader = function () {
    throw new Error();
}

window.ImgLoader._count = 0;

window.ImgLoader.count = function(inc){
    switch (inc) {
        case '+': window.ImgLoader._count++;
            break;
        case '-': window.ImgLoader._count--;
            break;
        default:
            throw new Error();
    }
}

window.ImgLoader.getCount = function(){
    return window.ImgLoader._count;
}

window.ImgLoader.isLoaded = function () {
    return window.ImgLoader.getCount() === 0 ? true : false;
}

window.ImgLoader.load = function (url){
    window.ImgLoader.count('+');
    var img = document.createElement('img');
    img.src = url;
    img.style.display = 'none';
    document.body.appendChild(img);
    img.onload = function () {
        document.body.removeChild(img);
        window.ImgLoader.count('-');
    }
}

window.ImgLoader = function(){
    throw new Error();
}

window.ImgLoader.init = function(){
    window.ImgLoader._count = 0;
    window.ImgLoader.container = document.createElement('load');
    window.ImgLoader.container.style.display = 'none';
    document.head.appendChild(window.ImgLoader.container);
}

window.ImgLoader.count = function(inc){
    switch (inc) {
        case '+': window.ImgLoader._count++;
            break;
        case '-': window.ImgLoader._count--;
            break;
        default:
            throw new Error();
    }
}

window.ImgLoader.getCount = function(){
    return window.ImgLoader._count;
}

window.ImgLoader.isLoaded = function(){
    return window.ImgLoader.getCount() === 0 ? true : false;
}

window.ImgLoader.load = function(_url){
    window.ImgLoader.count('+');
    var img = document.createElement('img');
    img.src = _url;
    window.ImgLoader.container.appendChild(img);
    img.onload = function () {
        window.ImgLoader.container.removeChild(img);
        window.ImgLoader.count('-');
    }
    return _url;
}

window.ImgLoader.init();

Return the image url and create load tag