'use strict';

const Boom = require('boom');
const User = require('../models/user');
const Tweet = require('../models/tweet');

const utils = require('./utils.js');
const _ = require('lodash');

exports.deleteMultipleTweets = {
  auth: {
    strategy: 'jwt',
    scope: 'admin',
  },

  handler: function (request, reply) {

  },
};

exports.deleteMultipleUsers = {
  auth: {
    strategy: 'jwt',
    scope: 'admin',
  },

  handler: function (request, reply) {
    const usersToDelete = request.payload;
    let usersDeleted;

    User.remove({ _id: { $in: usersToDelete } }).then((users) => {
      usersDeleted = users.result.n;
      return Tweet.remove({ creator: { $in: usersToDelete } });
    }).then((tweets) => {
      const message = 'Deleted ' + usersDeleted + ' users and ' +
          tweets.result.n + ' related tweets.';

      reply({ success: true, message: message }).code(204);
    }).catch((error) => {
      reply(Boom.badImplementation('error deleting users'));
    });
  },
};

'use strict';

const Boom = require('boom');
const User = require('../models/user');
const Tweet = require('../models/tweet');

const utils = require('./utils.js');
const _ = require('lodash');

exports.deleteMultipleTweets = {
  auth: {
    strategy: 'jwt',
    scope: 'admin',
  },

  handler: function (request, reply) {
    const tweetsToDelete = request.payload;

    Tweet.remove({ _id: { $in: tweetsToDelete } }).then((tweets) => {
      const message = 'Deleted ' + tweets.result.n + ' tweets.';

      reply({ success: true, message: message }).code(204);
    }).catch((error) => {
      reply(Boom.badImplementation('error deleting tweets'));
    });
  },
};

exports.deleteMultipleUsers = {
  auth: {
    strategy: 'jwt',
    scope: 'admin',
  },

  handler: function (request, reply) {
    const usersToDelete = request.payload;
    let usersDeleted;

    User.remove({ _id: { $in: usersToDelete } }).then((users) => {
      usersDeleted = users.result.n;
      return Tweet.remove({ creator: { $in: usersToDelete } });
    }).then((tweets) => {
      const message = 'Deleted ' + usersDeleted + ' users and ' +
          tweets.result.n + ' related tweets.';

      reply({ success: true, message: message }).code(204);
    }).catch((error) => {
      reply(Boom.badImplementation('error deleting users'));
    });
  },
};

Implement admin bulk tweet deletion


// MulitpleProcess model
// An abstract class that allows us to group some atomic process related entities
// Like: registrations, requirements or processingSteps

'use strict';

var memoize  = require('memoizee/plain')
  , ensureDb = require('dbjs/valid-dbjs');

module.exports = memoize(function (db/*, options*/) {
	return ensureDb(db).Object.extend('MultipleProcess', {
		map: { type: db.Object, nested: true },
		applicable: { type: db.Object, multiple: true, value: function (_observe) {
			var result = [];
			this.map.forEach(function (entity) {
				var isApplicable = entity._get ? _observe(entity._isApplicable) : entity.isApplicable;
				if (isApplicable) result.push(entity);
			});
			return result;
		} }
	});
}, { normalizer: require('memoizee/normalizers/get-1')() });

// MulitpleProcess model
// An abstract class that allows us to group some atomic process related entities
// Like: registrations, requirements or processingSteps

'use strict';

var memoize  = require('memoizee/plain')
  , ensureDb = require('dbjs/valid-dbjs');

module.exports = memoize(function (db/*, options*/) {
	var MultipleProcess = ensureDb(db).Object.extend('MultipleProcess', {
		map: { type: db.Object, nested: true },
		applicable: { type: db.Object, multiple: true, value: function (_observe) {
			var result = [];
			this.map.forEach(function (entity) {
				var isApplicable = entity._get ? _observe(entity._isApplicable) : entity.isApplicable;
				if (isApplicable) result.push(entity);
			});
			return result;
		} }
	});
	MultipleProcess.prototype.map._descriptorPrototype_.type = db.Object;
	return MultipleProcess;
}, { normalizer: require('memoizee/normalizers/get-1')() });

Make it a map in MultipleProcess


/**
 * @author alteredq / http://alteredqualia.com/
 */

THREE.RenderPass = function ( scene, camera, overrideMaterial, clearColor, clearAlpha ) {

	THREE.Pass.call( this );

	this.scene = scene;
	this.camera = camera;

	this.overrideMaterial = overrideMaterial;

	this.clearColor = clearColor;
	this.clearAlpha = ( clearAlpha !== undefined ) ? clearAlpha : 0;

	this.clear = true;
	this.needsSwap = false;

};

THREE.RenderPass.prototype = Object.assign( Object.create( THREE.Pass.prototype ), {

	constructor: THREE.RenderPass,

	render: function ( renderer, writeBuffer, readBuffer, delta, maskActive ) {

		var oldAutoClear = renderer.autoClear;
		renderer.autoClear = false;

		this.scene.overrideMaterial = this.overrideMaterial;

		var oldClearColor, oldClearAlpha;

		if ( this.clearColor ) {

			oldClearColor = renderer.getClearColor().getHex();
			oldClearAlpha = renderer.getClearAlpha();

			renderer.setClearColor( this.clearColor, this.clearAlpha );

		}

		renderer.render( this.scene, this.camera, this.renderToScreen ? null : readBuffer, this.clear );

		if ( this.clearColor ) {

			renderer.setClearColor( oldClearColor, oldClearAlpha );

		}

		this.scene.overrideMaterial = null;
		renderer.autoClear = oldAutoClear;
	}

} );

/**
 * @author alteredq / http://alteredqualia.com/
 */

THREE.RenderPass = function ( scene, camera, overrideMaterial, clearColor, clearAlpha ) {

	THREE.Pass.call( this );

	this.scene = scene;
	this.camera = camera;

	this.overrideMaterial = overrideMaterial;

	this.clearColor = clearColor;
	this.clearAlpha = ( clearAlpha !== undefined ) ? clearAlpha : 0;

	this.clear = false;
	this.needsSwap = false;

};

THREE.RenderPass.prototype = Object.assign( Object.create( THREE.Pass.prototype ), {

	constructor: THREE.RenderPass,

	render: function ( renderer, writeBuffer, readBuffer, delta, maskActive ) {

		var oldAutoClear = renderer.autoClear;
		renderer.autoClear = false;

		this.scene.overrideMaterial = this.overrideMaterial;

		var oldClearColor, oldClearAlpha;

		if ( this.clearColor ) {

			oldClearColor = renderer.getClearColor().getHex();
			oldClearAlpha = renderer.getClearAlpha();

			renderer.setClearColor( this.clearColor, this.clearAlpha );

		}

		renderer.render( this.scene, this.camera, this.renderToScreen ? null : readBuffer, this.clear );

		if ( this.clearColor ) {

			renderer.setClearColor( oldClearColor, oldClearAlpha );

		}

		this.scene.overrideMaterial = null;
		renderer.autoClear = oldAutoClear;
	}

} );

Set clear flag on render pass to false


var spawn = require('child_process').spawn;

var BIN = './src/index.js';

describe('--config without 1', function() {

	it('should be 1 on a failed test without the failing feature', function(done) {
		this.timeout(30 * 1000);

		var config = {
			without: [1]
		};

		var subject = spawn(BIN, [ '--config', JSON.stringify(config), 'test/resources/FailingSuite' ]);

		subject.on('exit', function(code) {
			code.should.equal(0);
			done();
		});
	});

	it('should fail on a successful test without its only feature', function(done) {
		this.timeout(30 * 1000);

		var config = {
			without: [1]
		};

		var subject = spawn(BIN, [ '--config', JSON.stringify(config), 'test/resources/SucceedingSuite' ]);

		subject.on('exit', function(code) {
			code.should.not.equal(0);
			done();
		});
	});

});

var spawn = require('child_process').spawn;

var BIN = './src/index.js';

describe('--config without 1', function() {

	it('should be 1 on a failed test without the failing feature', function(done) {
		this.timeout(30 * 1000);

		var config = {
			without: [1]
		};

		var subject = spawn(BIN, [ '--config', JSON.stringify(config), 'test/resources/FailingSuite' ]);

		subject.on('exit', function(code) {
			code.should.equal(0);
			done();
		});
	});

	it('should fail on a successful test without its only feature', function(done) {
		this.timeout(30 * 1000);

		var config = {
			without: [1]
		};

		var subject = spawn(BIN, [ '--config', JSON.stringify(config), 'test/resources/SucceedingSuite' ]);

		subject.on('exit', function(code) {
			code.should.not.equal(0);
			done();
		});
	});

	describe('without a feature that does not exist', function() {
		var subject,
			message;

		before(function() {
			var config = {
				without: [5555]
			};

			subject = spawn(BIN, [ '--config', JSON.stringify(config), 'test/resources/SucceedingSuite' ]);

			subject.stderr.on('data', function(data) {
				message = data.toString();
			});
		});

		it('should fail', function(done) {
			this.timeout(10000);

			subject.on('exit', function(code) {
				code.should.not.equal(0);
				done();
			});
		});

		it('should give details', function() {
			message.should.match(/could not be found/);
		});
	});

});

Add integration test to assert that it fails ignoring "not a feature"


module.exports = {
  extends: ["google", "plugin:react/all", "./index.js", "./react.js"],
  plugins: ["react", "prettier"],
  env: {
    es6: true,
    node: true
  },
  rules: {
    "require-jsdoc": "off",
    "prettier/prettier": "error"
  }
};

"use strict";

module.exports = {
  extends: ["google", "plugin:react/all", "./index.js", "./react.js"],
  plugins: ["react", "prettier"],
  env: {
    es6: true,
    node: true
  },
  rules: {
    strict: "error",
    "require-jsdoc": "off",
    "prettier/prettier": "error"
  }
};

Add the "strict" ESLint rule


class EventEmitter {
  constructor() {
    this.observers = {};
  }

  on(events, listener) {
    events.split(' ').forEach(event => {
      this.observers[event] = this.observers[event] || [];
      this.observers[event].push(listener);
    });
    return this;
  }

  off(event, listener) {
    if (!this.observers[event]) {
      return;
    }

    const observers = this.observers[event];
    for (const i = observers.length - 1; i >= 0; i--) {
      const observer = observers[i];
      if (observer === listener) {
        observers.splice(i, 1);
      }
    }
  }

  emit(event, ...args) {
    if (this.observers[event]) {
      const cloned = [].concat(this.observers[event]);
      cloned.forEach(observer => {
        observer(...args);
      });
    }

    if (this.observers['*']) {
      const cloned = [].concat(this.observers['*']);
      cloned.forEach(observer => {
        observer.apply(observer, [event, ...args]);
      });
    }
  }
}

export default EventEmitter;

class EventEmitter {
  constructor() {
    this.observers = {};
  }

  on(events, listener) {
    events.split(' ').forEach(event => {
      this.observers[event] = this.observers[event] || [];
      this.observers[event].push(listener);
    });
    return this;
  }

  off(event, listener) {
    if (!this.observers[event]) {
      return;
    }

    const observers = this.observers[event];
    for (let i = observers.length - 1; i >= 0; i--) {
      const observer = observers[i];
      if (observer === listener) {
        observers.splice(i, 1);
      }
    }
  }

  emit(event, ...args) {
    if (this.observers[event]) {
      const cloned = [].concat(this.observers[event]);
      cloned.forEach(observer => {
        observer(...args);
      });
    }

    if (this.observers['*']) {
      const cloned = [].concat(this.observers['*']);
      cloned.forEach(observer => {
        observer.apply(observer, [event, ...args]);
      });
    }
  }
}

export default EventEmitter;

Replace incorrect const with let


// postcss.config.js
const path = require('path');
const purgecss = require('@fullhuman/postcss-purgecss')({

  // Specify the paths to all of the template files in your project
  content: [
    './src/index.php',
    './src/**/*.html',
    './src/**/*.vue',
    './src/**/*.js', /* Lots of classes are used within inline templates... */
  ],
  defaultExtractor: content => content.match(/[\w-/:]+(?<!:)/g) || []
})

module.exports = ({env}) => (
  {
    syntax: 'postcss-scss',
    plugins: [
      require('postcss-import'),
      require('postcss-mixins')({
        mixinsDir: path.join(__dirname, 'src', 'css', 'mixins')
      }),
      // Using utilities for clearfix - ie8 style is closer to original one used
      require('postcss-utilities')({ ie8: true}),
      require('postcss-simple-vars'),
      require('postcss-strip-inline-comments'),
      require('postcss-color-function'),
      require('postcss-nested'),
      require('postcss-extend-rule'),
      require('tailwindcss'),
      require('autoprefixer'),
      ...process.env.NODE_ENV === 'production' ? [purgecss]: [],
    ]
  }
)

// postcss.config.js
const path = require('path');
// Disabling purgecss for now because it strips too many styles.
// TODO - figure out the correct extractor to parse backbone templates (inline and html)
// const purgecss = require('@fullhuman/postcss-purgecss')({

//   // Specify the paths to all of the template files in your project
//   content: [
//     './src/index.php',
//     './src/**/*.html',
//     './src/**/*.vue',
//     './src/**/*.js', /* Lots of classes are used within inline templates... */
//   ],
//   defaultExtractor: content => content.match(/[\w-/:]+(?<!:)/g) || []
// })

module.exports = ({env}) => (
  {
    syntax: 'postcss-scss',
    plugins: [
      require('postcss-import'),
      require('postcss-mixins')({
        mixinsDir: path.join(__dirname, 'src', 'css', 'mixins')
      }),
      // Using utilities for clearfix - ie8 style is closer to original one used
      require('postcss-utilities')({ ie8: true}),
      require('postcss-simple-vars'),
      require('postcss-strip-inline-comments'),
      require('postcss-color-function'),
      require('postcss-nested'),
      require('postcss-extend-rule'),
      require('tailwindcss'),
      require('autoprefixer'),
      // Disabling purgecss for now - strips out too many styles
      // ...process.env.NODE_ENV === 'production' ? [purgecss]: [],
    ]
  }
)

Disable purgecss for production builds


import React from 'react';
import { Header } from 'components/Header/Header';
import { shallow } from 'enzyme';

describe('(Component) Header', () => {
  let _wrapper;

  beforeEach(() => {
    _wrapper = shallow(
      <Header
        isFetching={false}
        message={{
          text: ''
        }}
        closeMessage={() => null}
      />
    );
  });

  it('Renders a nav bar with correct title', () => {
    const nav = _wrapper.find('nav');
    const title = nav.find('Link');
    expect(nav).to.exist;
    expect(title).to.exist;
    expect(title.children().at(0).text()).to.match(/Tiedonohjausjärjestelmä/);
  });
});

import React from 'react';
import { Header } from 'components/Header/Header';
import { shallow } from 'enzyme';

describe('(Component) Header', () => {
  let _wrapper;

  beforeEach(() => {
    _wrapper = shallow(
      <Header
        isFetching={false}
      />
    );
  });

  it('Renders a nav bar with correct title', () => {
    const nav = _wrapper.find('nav');
    const title = nav.find('Link');
    expect(nav).to.exist;
    expect(title).to.exist;
    expect(title.children().at(0).text()).to.match(/Tiedonohjausjärjestelmä/);
  });
});

Remove unnecessary code in test


var LeaderboardView, RatView

RatView = Marionette.ItemView.extend({
  tagName: 'tr',

  template: Handlebars.compile(
    '{{CMDRname}}	' +
    '{{successfulRescueCount}}	'
  )
})

LeaderboardView = Marionette.CompositeView.extend({
  childView: RatView,

  childViewContainer: 'tbody',

  className: 'card',

  initialize: function () {
    $(window).on('resize', this.render)
    this.listenTo(this.model, 'change', this.render)
  },

  onRender: function () {},

  tagName: 'div',

  template: Handlebars.compile(
    '
' +
      'Leaderboard' +
    '
' +
    '
' +
      '
' +
        '
' +
          'CMDR	' +
          'Rescues	' +
        '
' +
      '
' +
      '
' +
      '
' +
    '
'
  )
})

'use strict'

let RatView = Marionette.ItemView.extend({
  tagName: 'tr',

  template: Handlebars.compile(
    '{{CMDRname}}	' +
    '{{successfulRescueCount}}	'
  )
})

Marionette.CompositeView.extend({
  childView: RatView,

  childViewContainer: 'tbody',

  className: 'card',

  initialize: function () {
    $(window).on('resize', this.render)
    this.listenTo(this.model, 'change', this.render)
  },

  onRender: function () {},

  tagName: 'div',

  template: Handlebars.compile(
    '
' +
      'Leaderboard' +
    '
' +
    '
' +
      '
' +
        '
' +
          'CMDR	' +
          'Rescues	' +
        '
' +
      '
' +
      '
' +
      '
' +
    '
'
  )
})

Fix eslint errors in leaderboard.js


var app = require('./web-server/app');

app.listen(3000, () => {
  console.log(`Pong web-server!`);
  console.log(`Mode: ${app.get('env')}`);
  console.log(`Port: listening on port 3000`);
  console.log(`URL: http://localhost:3000`);
})

var app = require('./web-server/app');
const PORT = process.env.PORT || 3000;

app.listen(PORT, () => {
  console.log(`Pong web-server!`);
  console.log(`Mode: ${app.get('env')}`);
  console.log(`Port: listening on port 3000`);
  console.log(`URL: http://localhost:3000`);
})

Add port 80 support for heroku


#!/usr/bin/env node
'use strict';

var pkg = require('./package.json');
var statsministerietSpeeches = require('./');
var argv = process.argv.slice(2);

var fs = require('fs');

function help() {
  console.log([
    '',
      '  ' + pkg.description,
    '',
    '  Example',
    '    statsministeriet-speeches ',
    ''
  ].join('\n'));
}

if (argv.indexOf('--help') !== -1) {
  help();
  return;
}

if (argv.indexOf('--version') !== -1) {
  console.log(pkg.version);
  return;
}

statsministerietSpeeches(argv[0], function(err, data){
  if(!err){
    fs.writeFile('speech.md', data.markdown, function(){});

    console.log(JSON.stringify(data, null, 4));
  }
});

#!/usr/bin/env node
'use strict';

var pkg = require('./package.json');
var statsministerietSpeeches = require('./');
var argv = process.argv.slice(2);

var fs = require('fs');

function help() {
  console.log([
    '',
      '  ' + pkg.description,
    '',
    '  Example',
    '    statsministeriet-speeches ',
    ''
  ].join('\n'));
}

if (argv.indexOf('--help') !== -1) {
  help();
  return;
}

if (argv.indexOf('--version') !== -1) {
  console.log(pkg.version);
  return;
}

statsministerietSpeeches(argv[0], function(err, data){
  if(!err){
    fs.writeFile('index.md', data.markdown, function(){});

    console.log(JSON.stringify(data, null, 4));
  }
});

Change Markdown file name to index.md


// @flow

import * as React from 'react';
import { Helmet } from 'react-helmet';
import config from 'app/config';
import { type ComponentType, createElement } from 'react';

/**
 * A higher order component that wraps the given component in
 * LoadingIndicator while `props[loadingProp]` is being fetched.
 */

type PropertyGenerator = (
  props: Object,
  config?: Object
) => Array<{
  property?: string,
  content?: string,
  element?: string,
  children?: string,
  rel?: string,
  href?: string
}>;

export default function helmet<T>(propertyGenerator: ?PropertyGenerator) {
  return (Component: ComponentType<T>) => ({
    PropertyGenerator,
    ...props
  }: T & {
    propertyGenerator: PropertyGenerator
  }) => [
    <Helmet key="helmet">
      {!!propertyGenerator &&
        propertyGenerator(props, config).map(
          ({ element, children, ...props }, index) =>
            createElement(element || 'meta', { key: index, ...props }, children)
        )}
    </Helmet>,
    <Component key="component" {...props} />
  ];
}

// @flow

import * as React from 'react';
import { Helmet } from 'react-helmet';
import config from 'app/config';
import { type ComponentType, createElement } from 'react';

/**
 * A higher order component that wraps the given component in
 * LoadingIndicator while `props[loadingProp]` is being fetched.
 */
type Property = {
  property?: string,
  content?: string,
  element?: string,
  children?: string,
  rel?: string,
  href?: string
};
type PropertyGenerator = (props: Object, config?: Object) => ?Array<Property>;

export default function helmet<T>(propertyGenerator: ?PropertyGenerator) {
  return (Component: ComponentType<T>) => ({
    PropertyGenerator,
    ...props
  }: T & {
    propertyGenerator: ?PropertyGenerator
  }) => {
    const properties: ?Array<Property> =
      propertyGenerator && propertyGenerator(props, config);

    return (
      <>
        {properties && (
          <Helmet>
            {properties.map(({ element, children, ...props }, index) =>
              createElement(
                element || 'meta',
                { key: index, ...props },
                children
              )
            )}
          </Helmet>
        )}
        <Component {...props} />
      </>
    );
  };
}

Fix helment null ptr exception


var period = 1;

chrome.runtime.onInstalled.addListener(function() {
  chrome.alarms.create("update", {
    periodInMinutes: period
  });
});

chrome.runtime.onUpdateAvailable.addListener(function() {
  // Restart is for Kiosk apps, no-op in non-kiosk
  chrome.runtime.restart();

  // Reload is for non-kiosk apps.
  chrome.runtime.reload();
});

chrome.alarms.onAlarm.addListener(function(alarms) {
  chrome.runtime.requestUpdateCheck(function(u) {
    if ( u == 'throttled' ) {
      period *= 2;
      chrome.alarms.create("update", {
        periodInMinutes: period
      });
    } else {
      period = 1;
    }
  });
});

function launchWindow() {
  var windows = chrome.app.window.getAll();
  for ( var i = 0 ; i < windows.length ; i++ ) {
    windows[i].close();
  }

  chrome.app.window.create("main.html", {
    frame: 'none',
    innerBounds: {
      left: -3240,
      top: 0,
      width: 2700,
      height: 1920
    }
  }, function(w) {
  });
}

chrome.app.runtime.onLaunched.addListener(function() {
  launchWindow();
});

var period = 1;

chrome.runtime.onUpdateAvailable.addListener(function() {
  // Restart is for Kiosk apps, no-op in non-kiosk
  chrome.runtime.restart();

  // Reload is for non-kiosk apps.
  chrome.runtime.reload();
});

chrome.alarms.onAlarm.addListener(function(alarms) {
  chrome.runtime.requestUpdateCheck(function(u) {
    if ( u == 'throttled' ) {
      period *= 2;
      chrome.alarms.create("update", {
        periodInMinutes: period
      });
    } else {
      period = 1;
    }
  });
});

var launchWindow = (function() {
  var launching = false;

  return function() {
    var windows = chrome.app.window.getAll();
    for ( var i = 0 ; i < windows.length ; i++ ) {
      windows[i].close();
    }

    if ( ! launching ) {
      launching = true;
      chrome.app.window.create("main.html", {
        frame: 'none',
        innerBounds: {
          left: -3240,
          top: 0,
          width: 2700,
          height: 1920
        }
      }, function(w) {
        launching = false;
      });
    }
  };
})();

chrome.app.runtime.onLaunched.addListener(function() {
  launchWindow();
});

chrome.runtime.onInstalled.addListener(function() {
  launchWindow();
  chrome.alarms.create("update", {
    periodInMinutes: period
  });
});

chrome.runtime.onStartup.addListener(function() {
  launchWindow();
});

Update launcher to always launch a window onstartup/install/launch


var express = require('express');
var assign = require('lodash.assign');

function ensureAPIAuthenticated(req, res, next) {
  if (req.isAuthenticated()) {return next();}
  sendAPIError(res, 'authentication required', 401);
}

function sendAPISuccess(res, data) {
  res.json({data: data});
}

function sendAPIError(res, error, optErrorCode, optDetails) {
  res.status(optErrorCode || 400);
  res.json(assign({}, {error: error, details: optDetails}));
}

function APIRoutes(model) {
  var router = express.Router();

  router.post('/exp/:expId/events', ensureAPIAuthenticated, this.postEvents.bind(this));

  this.router = router;
  this.model = model;
}

APIRoutes.prototype = {
  postEvents: function(req, res) {
    // TODO: validate expId to make sure that it's a real experiment that's
    // been registered before we do anything else.

    this.model.putExpEvents(
      req.params.expId,
      req.user.id,
      req.body.events
    )
      .then(function(numPut) {sendAPISuccess(res, numPut)})
      .catch(function(err) {sendAPIError(res, 500, err)});
  }
};

module.exports = APIRoutes;

var express = require('express');
var assign = require('lodash.assign');

function ensureAPIAuthenticated(req, res, next) {
  if (req.isAuthenticated()) {return next();}
  sendAPIError(res, 'authentication required', 401);
}

function sendAPISuccess(res, data) {
  res.json({data: data});
}

function sendAPIError(res, error, optErrorCode, optDetails) {
  res.status(optErrorCode || 400);
  res.json(assign({}, {error: error, details: optDetails}));
}

function APIRoutes(model) {
  var router = express.Router();

  router.post('/exp/:expId/events', ensureAPIAuthenticated, this.postEvents.bind(this));

  this.router = router;
  this.model = model;
}

APIRoutes.prototype = {
  postEvents: function(req, res) {
    this.model.haveExpById(req.params.expId).then(function(haveExp) {
      if (!haveExp) {
        sendAPIError(res, 400, 'The experiment does not exist.');
        return;
      }

      this.model.putExpEvents(req.params.expId, req.body.events).then(
        function(numPut) {sendAPISuccess(res, numPut)},
        function(err) {sendAPIError(res, 500, err)}
      );
    });
  }
};

module.exports = APIRoutes;

Add exp id validation to apiroutes.


 // I worked on this challenge by myself.
 // This challenge took me 1 hours.

 // Pseudocode
 // 1.) Accept a number argument and set defaults for fib and next fib.
//  2.) Compare the number to the next fib variable. If it's the same, return true.
//  3.) otherwise, recursively call the function again using the next fib argument.
//  4.) Continue until the next fib is equal or greater than the number.
//  5.) If the number is equal to the fib, return true, otherwise return false.

// Initial Solution

// function isFibonacci(num){
//   if (calculateFibonacci(num) == num){
//     return true;
//   }
//   else {
//     return false
//   }
// }

// function calculateFibonacci(num, priorFib, fibNumber){
//   if (typeof(priorFib) === 'undefined') priorFib = 0;
//   if (typeof(fibNumber) === 'undefined') fibNumber = 1;
//   var nextFib = priorFib + fibNumber
//   if (nextFib <= num){
//     return calculateFibonacci(num, fibNumber, nextFib);
//   }
//   else{
//     return fibNumber;
//   }
// }

// Refactored Solution

function isFibonacci(num){
  return (calculateFibonacci(num) == num)
}

function calculateFibonacci(num, priorFib, fibNumber){
  if (typeof(priorFib) === 'undefined')  priorFib = 0;
  if (typeof(fibNumber) === 'undefined') fibNumber = 1;
  var nextFib = priorFib + fibNumber
  if (nextFib <= num) return calculateFibonacci(num, fibNumber, nextFib);
  else return fibNumber;
}

// Driver Code
console.log("Should return true");
console.log(isFibonacci(3));
console.log(isFibonacci(5));
console.log(isFibonacci(13));
console.log(isFibonacci(55));
console.log(isFibonacci(144));
console.log(isFibonacci(610));
console.log(isFibonacci(6765));
console.log(isFibonacci(10946));

console.log("Should return false");
console.log(isFibonacci(4));
console.log(isFibonacci(9));
console.log(isFibonacci(25));
console.log(isFibonacci(150));
console.log(isFibonacci(400));

// Reflection

Add initial and refactored solutions


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

const mutation = graphql`
  mutation LogTabMutation($input: LogTabInput!) {
    logTab(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('logTab')
        const user = rootField.getLinkedRecord('user')
        user.setValue(user.getValue('vcCurrent') + 1, 'vcCurrent')
      }
    }
  )
}

export default {commit}

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

const mutation = graphql`
  mutation LogTabMutation($input: LogTabInput!) {
    logTab(input: $input) {
      user {
        id
        heartsUntilNextLevel
        level
        tabs
        vcCurrent
      }
    }
  }
`

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

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

export default {commit}

Fix faulty mutation that was not updating related fields.


import Ember from 'ember';
import _ from 'lodash';

export default Ember.Route.extend({
  /**
   * @param {params.mailbox_name} The name of the mailbox ('Inbox', 'Archives')
   */
  model(params) {
    var mailboxName = params.mailbox_name,
        mailboxes = this.modelFor('shell.mail');

    return new Ember.RSVP.Promise(function (resolve, reject) {
      var mailbox = _.find(mailboxes, _.matchesProperty('name', mailboxName));
      if (!mailbox) {
        console.error('No mailbox by that name found.');
        return reject();
      }

      // TODO: Need design input before we add ways to fetch more messages
      mailbox.getMessageList({
        sort: 'date desc',
        collapseThreads: true,
        fetchMessages: true,
        limit: 50
      }).then(function (response) {
        resolve(response[1]);
      }, reject);
    });
  }
});

import Ember from 'ember';
import _ from 'lodash';

export default Ember.Route.extend({
  /**
   * @param {params.mailbox_name} The name of the mailbox ('Inbox', 'Archives')
   */
  model(params) {
    var mailboxName = params.mailbox_name,
        mailboxes = this.modelFor('shell.mail');

    return new Ember.RSVP.Promise(function (resolve, reject) {
      var mailbox = _.find(mailboxes, _.matchesProperty('name', mailboxName));
      if (!mailbox) {
        console.error('No mailbox by that name found.');
        return reject();
      }

      // TODO: Need design input before we add ways to fetch more messages
      return mailbox.getMessageList({
        sort: 'date desc',
        collapseThreads: true,
        fetchMessages: true,
        limit: 50
      }).then(function (response) {
        let messages = response.length ? response[1] : [];
        resolve(messages);
      }, reject);
    });
  }
});

Check for empty response for getMessageList() calls


function Metronome(tempo, beatsPerMeasure){
  this.tempo = Number(tempo);
  this.beatsPerMeasure = Number(beatsPerMeasure);
  this.interval = null;
}

Metronome.prototype.start = function(){
  var millisecondsToWait = this.tempoToMilliseconds(this.tempo);
  this.interval = window.setInterval(this.updateCounterView, millisecondsToWait, this.beatsPerMeasure);
}

Metronome.prototype.tempoToMilliseconds = function(tempo){
  return (1000 * 60)/tempo;
}

Metronome.prototype.updateCounterView = function(beatsPerMeasure){
  var counter = document.getElementById("metronome-counter");
  var pastBeat = Number(counter.innerHTML);
  if (pastBeat < beatsPerMeasure){
    counter.innerHTML = pastBeat + 1;
  } else {
    counter.innerHTML = 1;
  }

  Metronome.prototype.stop = function(){
    window.clearInterval(this.interval);
    counter.innerHTML = "";
  }
}

function Metronome(tempo, beatsPerMeasure){
  this.tempo = Number(tempo);
  this.beatsPerMeasure = Number(beatsPerMeasure);
  this.interval = null;
}

Metronome.prototype.start = function(){
  var millisecondsToWait = this.tempoToMilliseconds(this.tempo);
  this.interval = window.setInterval(updateCounterView, millisecondsToWait, this);
}

Metronome.prototype.tempoToMilliseconds = function(tempo){
  return (1000 * 60)/tempo;
}

updateCounterView = function(metronome){
  var counter = document.getElementById("metronome-counter");
  var pastBeat = Number(counter.innerHTML);
  if (pastBeat < metronome.beatsPerMeasure){
    counter.innerHTML = pastBeat + 1;
  } else {
    counter.innerHTML = 1;
  }
}

Metronome.prototype.stop = function(){
  window.clearInterval(this.interval);
  counter.innerHTML = "";
}

Change updateCounterView to stand alone function


/*
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
Copyright (c) 2017 Mozilla Corporation

Contributors:
Jeff Bryner jbryner@mozilla.com
Anthony Verez averez@mozilla.com
Brandon Myers bmyers@mozilla.com
*/

//configuration settings

mozdef = {
  rootURL: "localhost",
  port: "80",
  rootAPI: "http://localhost:8081",
  kibanaURL: "http://localhost:9090/app/kibana#",
  enableBlockIP: false,
  enableClientAccountCreation: true
}

/*
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
Copyright (c) 2017 Mozilla Corporation

Contributors:
Jeff Bryner jbryner@mozilla.com
Anthony Verez averez@mozilla.com
Brandon Myers bmyers@mozilla.com
*/

//configuration settings

mozdef = {
  rootURL: "localhost",
  port: "80",
  rootAPI: "http://rest:8081",
  kibanaURL: "http://localhost:9090/app/kibana#",
  enableBlockIP: false,
  enableClientAccountCreation: true
}

Fix restapi in meteor docker container


#!/usr/bin/env node
'use strict'

function convert(win, htmlRoot, jsonRoot) {
	if (!jsonRoot) jsonRoot = []
	for (let i = 0; i < htmlRoot.childNodes.length; i++) {
		const node = htmlRoot.childNodes[i]
		if (node.nodeType === win.Node.ELEMENT_NODE) {
			const jsonElement = {
				element: node.tagName.toLowerCase(),
				contains: convert(win, node, null)
			}
			if (node.attributes.length > 0) {
				jsonElement.attributes = {}
				for (const attr of node.attributes) {
					jsonElement.attributes[attr.name] = attr.value
				}
			}
			jsonRoot.push(jsonElement)
		} else if (node.nodeType === win.Node.TEXT_NODE) {
			const content = node.nodeValue
			if (content.length > 0) {
				jsonRoot.push({ text: content })
			}
		}
	}
	return jsonRoot
}

if (require.main === module) {
	// TODO loaded from commandline
} else {
	module.exports = convert
}

#!/usr/bin/env node
'use strict'

function convert(win, htmlRoot, jsonRoot) {
	if (!jsonRoot) jsonRoot = []
	for (let i = 0; i < htmlRoot.childNodes.length; i++) {
		const node = htmlRoot.childNodes[i]
		if (node.nodeType === win.Node.ELEMENT_NODE) {
			const jsonElement = {
				element: node.tagName.toLowerCase(),
				contains: convert(win, node, null)
			}
			if (node.attributes.length > 0) {
				jsonElement.attributes = {}
				for (const attr of node.attributes) {
					jsonElement.attributes[attr.name] = attr.value
				}
			}
			jsonRoot.push(jsonElement)
		} else if (node.nodeType === win.Node.TEXT_NODE) {
			const content = node.nodeValue
			if (content.length > 0) {
				jsonRoot.push({ text: content })
			}
		}
	}
	return jsonRoot
}

if (require.main === module) {
	// TODO DRY wrt test runner
	const fs = require('fs')
	const jsdom = require('jsdom')
	const { JSDOM } = jsdom

	const html = fs.readFileSync(process.argv[2], 'utf-8')
		.replace(/\s*\n\s*/gm, '')

	const doc = new JSDOM(html).window.document
	const res = convert(
		doc.defaultView,
		doc.body,
		null)

	console.log(JSON.stringify(res, null, 2))
} else {
	module.exports = convert
}

Add command-line support to HTML-to-JSON script


function CurrentUser() {
	// 0 - guest, 1 - user, 2 - admin, 3 - root
	this._typeAccount = 0; // default 0
}

CurrentUser.prototype.getTypeAccount = function () {
	return this._typeAccount;
};

module.exports.CurrentUser = CurrentUser;

var when = require('when');
var nodefn = require('when/node');
var mongodb = require('mongodb');
var BSON = mongodb.BSONPure;

function CurrentUser() {
	// 0 - guest, 1 - user, 2 - admin, 3 - root
	this._typeAccount = 0; // default 0
}

CurrentUser.prototype.getTypeAccount = function () {
	return this._typeAccount;
};

CurrentUser.prototype.setTypeAccount = function (typeID) {
	this._typeAccount = typeID;
};

CurrentUser.prototype.checkCookie = function (mongo, redis) {
	var self = this;

	return function (req, res, next) {
		var sessionID = req.cookies.sessionID;

		redis.client.get('sessionID:' + sessionID, function (error, userID) {
			if (error) {
				throw Error('Error while getting key from redis.');
			}
			var usersCollection = mongo.collections.users;
			if (userID === null) {
				self.setTypeAccount(0);
			}
			var promisedFind = nodefn.call(usersCollection.find({
				_id: new BSON.ObjectID(userID)
			}).toArray);

			return promisedFind.then(function (userData) {
				if (userData.length === 0) {
					return when.reject('Invalid userID.');
				}
				self.setTypeAccount(userData[0].accountType || 0);
				return when.resolve();
			}).catch(function (content) {
				throw new Error(content);
			}).finally(function () {
				next();
			});
		});
	};
};

module.exports.CurrentUser = CurrentUser;

Create method for checking if user is logged. Method also set account type.


$(document).ready(function() {
  $('.add_child').click(function() {
    var assoc   = $(this).attr('data-association');
    var content = $('#' + assoc + '_fields_template').html();
    var regexp  = new RegExp('new_' + assoc, 'g')
    var new_id  = new Date().getTime();

    $(this).parent().before(content.replace(regexp, new_id));
  });

  $('.remove_child').live('click', function() {
    var hidden_field = $(this).prev('input[type=hidden]')[0];
    if(hidden_field) {
      hidden_field.value = '1';
    }
    $(this).parents('.fields').hide();
    return false;
  });
});

$(document).ready(function() {
  $('.add_child').click(function() {
    var assoc   = $(this).attr('data-association');
    var content = $('#' + assoc + '_fields_template').html();
    var regexp  = new RegExp('new_' + assoc, 'g')
    var new_id  = new Date().getTime();

    $(this).parent().before(content.replace(regexp, new_id));
    return false;
  });

  $('.remove_child').live('click', function() {
    var hidden_field = $(this).prev('input[type=hidden]')[0];
    if(hidden_field) {
      hidden_field.value = '1';
    }
    $(this).parents('.fields').hide();
    return false;
  });
});

Add a missing return false at the end of a jquery event


import Ember from 'ember';
import config from './config/environment';

var Router = Ember.Router.extend({
  location: config.locationType
});

Router.map(function() {
  this.resource('space', {path: '/:id'}, function(){
    this.route('channel', {path: '/:slug'}, function() {
    });
  });

  this.route('settings');
});

export default Router;

import Ember from 'ember';
import config from './config/environment';

var Router = Ember.Router.extend({
  location: config.locationType
});

Router.map(function() {
  this.resource('space', {path: '/:id'}, function(){
    this.route('channel', {path: '/:slug'});
  });

  this.route('settings');
});

export default Router;

Remove empty route nesting for channel


const $ = require('jquery');

function reload() {
  $('#stats').load(`${location.href}stats`, ''); // eslint-disable-line
  $('#glider-rider-detail').load(`${location.href}?refresh=true`, ''); // eslint-disable-line
}

setInterval(reload, 5000);

const $ = require('jquery');

function reload() {
  $('#stats').load(`${location.href}/stats`, ''); // eslint-disable-line
  $('#glider-rider-detail').load(`${location.href}?refresh=true`, ''); // eslint-disable-line
}

setInterval(reload, 5000);

Fix leaderboard page reload problem


var Templates = {};

Templates.ItemListingTemplate = _.template('\

\
		<img class="pull-left" src="<%= imageUrl %>" />\

\

<%= name %>
\

 <%= location %>
\

<%= price %>
\

<%= buyers.length %>
\

\

');

var Templates = {};

Templates.ItemListingTemplate = _.template('\

\
		<img class="pull-left" src="<%= imageUrl %>" />\

\

<%= name %>
\

<%= dateEnd.toDateString() %>
\

 <%= location %>
\

<%= price %>
\

<%= buyers.length %>
\

\

');

Update template to include expiration date.


// Karma configuration
// Generated on Fri Oct 17 2014 15:45:37 GMT-0400 (EDT)

module.exports = function(config) {
  config.set({
    autoWatch: false,
    basePath: './',
    browserNoActivityTimeout: 60000,
    browserify: {
      transform: [
        ['reactify', {es6: true}]
      ]
    },
    browsers: ['Chrome', 'PhantomJS'],
    colors: true,
    files: [
      'test/spec/support/*.js',
      'test/spec/javascripts/**/*_spec.js',
      'test/spec/javascripts/**/*_spec.jsx'
    ],
    frameworks: ['jasmine-jquery', 'jasmine', 'browserify'],
    logLevel: config.LOG_INFO,
    port: 9876,
    preprocessors: {
      'test/spec/javascripts/**/*_spec.js': ['browserify'],
      'test/spec/javascripts/**/*_spec.jsx': ['browserify']
    },
    reporters: ['progress']
  });
};

// Karma configuration
// Generated on Fri Oct 17 2014 15:45:37 GMT-0400 (EDT)

module.exports = function(config) {
  config.set({
    autoWatch: false,
    basePath: './',
    browserNoActivityTimeout: 60000,
    browserify: {
      transform: [
        ['reactify', {es6: true}]
      ],
      debug: true
    },
    browsers: ['Chrome', 'PhantomJS'],
    colors: true,
    files: [
      'test/spec/support/*.js',
      'test/spec/javascripts/**/*_spec.js',
      'test/spec/javascripts/**/*_spec.jsx'
    ],
    frameworks: ['jasmine-jquery', 'jasmine', 'browserify'],
    logLevel: config.LOG_INFO,
    port: 9876,
    preprocessors: {
      'test/spec/javascripts/**/*_spec.js': ['browserify'],
      'test/spec/javascripts/**/*_spec.jsx': ['browserify']
    },
    reporters: ['progress']
  });
};

Add source maps to browserify


const path = require('path');

var webpack = require('./webpack.config.js');
webpack.module.preLoaders = [
  {
    test: /\.js$/,
    include: path.resolve('app/lib/'),
    loader: 'babel-istanbul',
  },
];

module.exports = config => {
  config.set({
    browsers: [ 'PhantomJS' ],
    autoWatch: false,
    singleRun: true,
    failOnEmptyTestSuite: false,

    files: [
      'app/tests/unit/**/*.spec.js',
    ],

    frameworks: [
      'mocha',
      'chai',
      'sinon',
    ],

    reporters: [
      'mocha',
      'html',
      'coverage',
    ],

    preprocessors: {
      'app/lib/**/*.js': [ 'webpack', 'babel' ],
      'app/tests/**/*.js': [ 'webpack', 'babel' ],
    },

    webpack: webpack,

    htmlReporter: {
      outputFile: 'dist/unit-tests/index.html',
      groupSuites: true,
      useCompactStyle: true,
    },

    coverageReporter: {
      reporters: [
        { type: 'text' },
        { type: 'html', dir: 'dist', subdir: 'unit-coverage' },
      ],
    },

  });
}

const path = require('path');

var webpack = require('./webpack.config.js');
webpack.module.preLoaders = [
  {
    test: /\.js$/,
    include: path.resolve('app/lib/'),
    loader: 'babel-istanbul',
  },
];

module.exports = config => {
  config.set({
    browsers: [ 'PhantomJS' ],
    autoWatch: false,
    singleRun: true,
    failOnEmptyTestSuite: false,

    files: [
      'app/tests/unit/**/*.spec.js',
    ],

    frameworks: [
      'mocha',
      'chai',
      'sinon',
    ],

    reporters: [
      'mocha',
      'html',
      'coverage',
    ],

    preprocessors: {
      'app/lib/**/*.js': [ 'webpack', 'babel' ],
      'app/tests/**/*.js': [ 'webpack', 'babel' ],
    },

    webpack: webpack,

    htmlReporter: {
      outputFile: 'dist/unit-tests/index.html',
      groupSuites: true,
      useCompactStyle: true,
    },

    coverageReporter: {
      dir: 'dist',
      subdir: 'unit-coverage',
      reporters: [
        { type: 'text', file: 'coverage.txt' },
      ],
    },

  });
}

Remove html coverage reporter, caused test to hang


/*global chrome hashblot blotpw bloticon*/

chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
  bloticon.updateTabIcon(tab);
});

// Update all tabs on startup
chrome.runtime.onInstalled.addListener(function() {
  chrome.tabs.query({url: '*://*/*'},
    function (tabs) {
      for (var i = 0; i < tabs.length; ++i) {
        bloticon.updateTabIcon(tabs[i]);
      }
    });
});

Update all tab icons on install/extension reload


import React, { PropTypes } from 'react'
import Alert from 'react-s-alert';

const Layout = ({ children }) => (
  <div>
    {children}
    <Alert stack={{ limit: 3 }} position="top-left" timeout={5000} html effect="stackslide" />
  </div>
)

Layout.propTypes = {
  children: PropTypes.element.isRequired,
}

export default Layout

import React, { PropTypes } from 'react'
import Alert from 'react-s-alert';

const Layout = ({ children }) => (
  <div>
    {children}
    <Alert
      stack={{ limit: 3 }}
      position="top-left"
      timeout={5000}
      effect="stackslide"
      offset={80}
      html
    />
  </div>
)

Layout.propTypes = {
  children: PropTypes.element.isRequired,
}

export default Layout

Add offset for header bar.


(function() {
    if (!window.localStorage) {
        console.warn('Sorry, this example needs localStorage support');
        return;
    }

    // Get ProbabilityDrive and a localStorage data store
    var probabilitydrive = new window.probabilitydrive;
    var cache = window.localStorage;

    // Retrieve and set the historical data from the data store
    var previousData;
    try {
        previousData = JSON.parse(cache.getItem('pd-data'));
    } catch (e) {
        // swallow the error - don't do this in real life!
    }

    log('setData', previousData);
    probabilitydrive.setData(previousData);

    // Observe the current path
    var pathname = window.location.pathname;
    log('observe', pathname);
    probabilitydrive.observe(pathname);

    // Save the updated data back to the data store
    var updatedData = probabilitydrive.getData('data');
    log('getData', updatedData);
    cache.setItem('pd-data', JSON.stringify(updatedData));

    // Make a prediction
    var prediction = probabilitydrive.determine();
    var info = getInfoFromPrediction(prediction);
    log('prediction', info);

    function getInfoFromPrediction(prediction) {
        var predictionsText = [];
        Object.keys(prediction).forEach(function(key) {
            var urlData = prediction[key];
            predictionsText.push(
                urlData.url + ' - probability: ' + urlData.probability
            );
        });
        return predictionsText;
    }

    function log(method, data) {
        console.log('probabilitydrive.' + method, data);
    }
})();

(function() {
    if (!window.localStorage) {
        console.warn('Sorry, this example needs localStorage support');
        return;
    }

    // Get ProbabilityDrive and a localStorage data store
    var probabilitydrive = new window.probabilitydrive;
    var cache = window.localStorage;

    // Make it available globally just for playing around with in this example
    window.pdInstance = probabilitydrive;

    // Retrieve and set the historical data from the data store
    var previousData;
    try {
        previousData = JSON.parse(cache.getItem('pd-data'));
    } catch (e) {
        // swallow the error - don't do this in real life!
    }

    log('setData', previousData);
    probabilitydrive.setData(previousData);

    // Observe the current path
    var pathname = window.location.pathname;
    log('observe', pathname);
    probabilitydrive.observe(pathname);

    // Save the updated data back to the data store
    var updatedData = probabilitydrive.getData('data');
    log('getData', updatedData);
    cache.setItem('pd-data', JSON.stringify(updatedData));

    // Make a prediction
    var prediction = probabilitydrive.determine();
    log('prediction', prediction.join(', '));

    function log(method, data) {
        console.log('probabilitydrive.' + method, data);
    }
})();

Update persistent example following prediction return API changes


(function() {
	if(window.cordova || window.PhoneGap) {
		var v = window.useCordovaTVVersion||"3.0";
		var externalModules = [
			"/usr/palm/frameworks/cordova-tv/" + v + "/advertisement.js",
			"/usr/palm/frameworks/cordova-tv/" + v + "/billing.js",
			"/usr/palm/frameworks/cordova-tv/" + v + "/broadcast.js",
			"/usr/palm/frameworks/cordova-tv/" + v + "/camera.js",
			"/usr/palm/frameworks/cordova-tv/" + v + "/deviceinfo.js",
			"/usr/palm/frameworks/cordova-tv/" + v + "/drmagent.js",
			"/usr/palm/frameworks/cordova-tv/" + v + "/mrcu.js",
			"/usr/palm/frameworks/cordova-tv/" + v + "/push.js",
			"/usr/palm/frameworks/cordova-tv/" + v + "/upnp.js"
		];
		enyo.load.apply(enyo, externalModules);
	} else {
		enyo.warn("Cordova not found, ignoring tv modules");
	}
})();

(function() {
	if(window.cordova || window.PhoneGap) {
		var v = window.useCordovaTVVersion||"3.0";
		var externalModules = [
			"/usr/palm/frameworks/cordova-tv/" + v + "/advertisement.js",
			"/usr/palm/frameworks/cordova-tv/" + v + "/billing.js",
			"/usr/palm/frameworks/cordova-tv/" + v + "/broadcast.js",
			"/usr/palm/frameworks/cordova-tv/" + v + "/camera.js",
			"/usr/palm/frameworks/cordova-tv/" + v + "/deviceinfo.js",
			"/usr/palm/frameworks/cordova-tv/" + v + "/drmagent.js",
			"/usr/palm/frameworks/cordova-tv/" + v + "/mrcu.js",
			"/usr/palm/frameworks/cordova-tv/" + v + "/push.js",
			"/usr/palm/frameworks/cordova-tv/" + v + "/upnp.js"
		];
		enyo.load(externalModules);
	} else {
		enyo.warn("Cordova not found, ignoring tv modules");
	}
})();

Simplify enyo.load call since it handles arrays natively


module.exports = VirtDate;

function VirtDate(timeVirtualizer) {
    this._timeVirtualizer = timeVirtualizer;
    this._realDate = window.Date;
    this._realDateNow = window.Date.now;
    this._virtualized = false;
};

VirtDate.prototype.destroy = function() {
    this.unVirtualize();
    delete this._timeVirtualizer;
    delete this._realDate;
    delete this._realDateNow;
    delete this._virtualized;
};

VirtDate.prototype.realNow = function() {
    return this._realDateNow.call(this._realDate);
};

VirtDate.prototype.now = function() {
    if (this._virtualized) {
        return this._timeVirtualizer.getVirtTSMS();
    } else {
        return this._realDateNow.call(this._realDate);
    }
};

VirtDate.prototype.virtualize = function() {
    if (this._virtualized) { return; }
    this._virtualized = true;
    window.Date.now = this.now.bind(this);
};

VirtDate.prototype.unVirtualize = function() {
    if (!this._virtualized) { return; }
    this._virtualized = false;
    window.Date.now = this._realDateNow;
};

module.exports = VirtDate;

function VirtDate(timeVirtualizer) {
    this._timeVirtualizer = timeVirtualizer;
    this._realDate = window.Date;
    this._realDateNow = window.Date.now;
    this._virtualized = false;
};

VirtDate.prototype.destroy = function() {
    this.unVirtualize();
    delete this._timeVirtualizer;
    delete this._realDate;
    delete this._realDateNow;
    delete this._virtualized;
};

VirtDate.prototype.realNow = function() {
    return this._realDateNow.call(this._realDate);
};

VirtDate.prototype.now = function() {
    if (this._virtualized) {
        return this._timeVirtualizer.virtDateNow();
    } else {
        return this._realDateNow.call(this._realDate);
    }
};

VirtDate.prototype.virtualize = function() {
    if (this._virtualized) { return; }
    this._virtualized = true;
    window.Date.now = this.now.bind(this);
};

VirtDate.prototype.unVirtualize = function() {
    if (!this._virtualized) { return; }
    this._virtualized = false;
    window.Date.now = this._realDateNow;
};

Update method name from 'getVirtTSMS' to 'virtDateNow'


function msgReceived(info, tab) {
  chrome.tabs.create({'url': chrome.extension.getURL('newtab.html'), 'selected': true}, function(tab) {
    console.log('In create: ', tab);
    chrome.tabs.getSelected(null, function(tab) {
      chrome.tabs.sendMessage(tab.id, {message: 'imgData', url: info.srcUrl});
    });
  });
}

chrome.contextMenus.create({'title': 'Crop', 'contexts': ['image'], 'onclick': msgReceived});

function msgReceived(info, tab) {
  chrome.tabs.create({'url': chrome.extension.getURL('newtab.html'), 'selected': true}, function(tab) {
    setTimeout(()=>{
      chrome.tabs.sendMessage(tab.id, {message: 'imgData', url: info.srcUrl});
    }, 500);
  });
}

chrome.contextMenus.create({'title': 'Crop', 'contexts': ['image'], 'onclick': msgReceived});

Fix issue with message handling


/** @babel */

import { CompositeDisposable } from 'atom';
import TerminalView from './terminal-view';

export default {

  disposables: null,
  terminalViews: [],

  activate(state) {
    this.disposables = new CompositeDisposable();

    // Register Opener for the Terminal URI (`terminal://`)
    this.disposables.add(atom.workspace.addOpener((uri) => {
      if (uri.indexOf('atom-terminal-tab:') == 0) {
        const terminalView = new TerminalView();
        this.terminalViews.push(terminalView);
        return terminalView;
      }
    }));

    this.disposables.add(atom.commands.add('atom-workspace', {
      'terminal:open': this.open.bind(this),
      'terminal:copy': this.handleCopy.bind(this),
      'terminal:paste': this.handlePaste.bind(this),
      'terminal:clear': this.handleClear.bind(this)
    }));
  },

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

  open() {
    atom.workspace.open('atom-terminal-tab://');
  },

  handleCopy(event) {
    let activeTerminalView = atom.workspace.getActivePaneItem();
    activeTerminalView.copySelection();
  },

  handlePaste(event) {
    let activeTerminalView = atom.workspace.getActivePaneItem();
    activeTerminalView.pasteFromClipboard();
  },

  handleClear(event) {
    let activeTerminalView = atom.workspace.getActivePaneItem();
    activeTerminalView.clear();
  }

};

/** @babel */

import { CompositeDisposable } from 'atom';
import TerminalView from './terminal-view';

const TERMINAL_TAB_URI = 'atom-terminal-tab://'

export default {

  disposables: null,
  terminalViews: [],

  activate(state) {
    this.disposables = new CompositeDisposable();

    // Register Opener for the Terminal URI (`terminal://`)
    this.disposables.add(atom.workspace.addOpener((uri) => {
      if (uri === TERMINAL_TAB_URI) {
        const terminalView = new TerminalView();
        this.terminalViews.push(terminalView);
        return terminalView;
      }
    }));

    this.disposables.add(atom.commands.add('atom-workspace', {
      'terminal:open': this.open.bind(this),
      'terminal:copy': this.handleCopy.bind(this),
      'terminal:paste': this.handlePaste.bind(this),
      'terminal:clear': this.handleClear.bind(this)
    }));
  },

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

  open() {
    atom.workspace.open(TERMINAL_TAB_URI);
  },

  handleCopy(event) {
    let activeTerminalView = atom.workspace.getActivePaneItem();
    activeTerminalView.copySelection();
  },

  handlePaste(event) {
    let activeTerminalView = atom.workspace.getActivePaneItem();
    activeTerminalView.pasteFromClipboard();
  },

  handleClear(event) {
    let activeTerminalView = atom.workspace.getActivePaneItem();
    activeTerminalView.clear();
  }

};

Use exact matching instead of startsWith in uri


/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.cron',
  name: 'TimeHMS',

  documentation: `
    Models a a time or duration in 24-hour notation.
    Does not model a date.
  `,

  properties: [
    { name: 'hour',   class: 'Int' },
    { name: 'minute', class: 'Int' },
    { name: 'second', class: 'Int' }
  ]
});

/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.cron',
  name: 'TimeHMS',

  documentation: `
    Models a a time or duration in 24-hour notation.
    Does not model a date.
  `,

  properties: [
    {
      class: 'Int',
      name: 'hour',
      gridColumns: 4
    },
    {
      class: 'Int',
      name: 'minute',
      gridColumns: 4
    },
    {
      class: 'Int',
      name: 'second',
      gridColumns: 4
    }
  ]
});

Put TimeHSM inputs inline to save space


import filePaths from '../utils/file-paths';
import cp from 'child_process';
import path from 'path';
import app from 'app';

class SquirrelEvents {

  check(options) {
    if (options.squirrelInstall) {
      this.spawnSquirrel(['--createShortcut', app.getPath('exe')], app.exit);
      return true;
    }

    if (options.squirrelUpdated || options.squirrelObsolete) {
      app.exit(0);
      return true;
    }

    if (options.squirrelUninstall) {
      this.spawnSquirrel(['--removeShortcut', app.getPath('exe')], app.exit);
      return true;
    }

    return false;
  }

  spawnSquirrel(args, callback) {
    const squirrelExec = path.join(filePaths.getAppDir(), 'Update.exe');
    log('spawning', squirrelExec, args);

    const child = cp.spawn(squirrelExec, args, { detached: true });
    child.on('close', function(code) {
      if (code) {
        logError(squirrelExec, 'exited with code', code);
      }
      callback(code || 0);
    });
  }

}

export default new SquirrelEvents();

import filePaths from '../utils/file-paths';
import cp from 'child_process';
import path from 'path';
import app from 'app';

import AutoLauncher from './auto-launcher';

class SquirrelEvents {

  check(options) {
    if (options.squirrelInstall) {
      this.spawnSquirrel(['--createShortcut', app.getPath('exe')], app.exit);
      return true;
    }

    if (options.squirrelUpdated || options.squirrelObsolete) {
      app.exit(0);
      return true;
    }

    if (options.squirrelUninstall) {
      AutoLauncher.disable(function(err) {
        if (err) {
          logError(err);
        }
        this.spawnSquirrel(['--removeShortcut', app.getPath('exe')], app.exit);
      });
      return true;
    }

    return false;
  }

  spawnSquirrel(args, callback) {
    const squirrelExec = path.join(filePaths.getAppDir(), 'Update.exe');
    log('spawning', squirrelExec, args);

    const child = cp.spawn(squirrelExec, args, { detached: true });
    child.on('close', function(code) {
      if (code) {
        logError(squirrelExec, 'exited with code', code);
      }
      callback(code || 0);
    });
  }

}

export default new SquirrelEvents();

Remove app from registry on uninstall


// @flow
import React from 'react'
import PrintIcon from '@material-ui/icons/Print'
import { defineMessages, FormattedMessage } from 'react-intl'

import ToolbarButton from '../internal/ToolbarButton'

const messages = defineMessages({
  // Button label to print a report
  print: 'Print'
})

type Props = {
  disabled: boolean,
  url: string
}

class PrintButton extends React.Component<Props, State> {

  download () {
    var anchor = document.createElement('a')
    anchor.href = this.url
    anchor.download = 'report.pdf'
    anchor.click()
  }

  render () {
    const { disabled } = this.props

    return (
      <React.Fragment>
        <ToolbarButton onClick={this.download.bind(this)} disabled={disabled}>
          <PrintIcon />
          <FormattedMessage {...messages.print} />
        </ToolbarButton>
      </React.Fragment>
    )
  }
}

export default PrintButton

// @flow
import React from 'react'
import PrintIcon from '@material-ui/icons/Print'
import { defineMessages, FormattedMessage } from 'react-intl'

import ToolbarButton from '../internal/ToolbarButton'

const messages = defineMessages({
  // Button label to print a report
  print: 'Print'
})

type Props = {
  disabled: boolean,
  url: string
}

const PrintButton = ({ url, disabled }: Props) => (
  <React.Fragment>
    <ToolbarButton
      component='a'
      href={url}
      download='report.pdf'
      disabled={disabled}
    >
      <PrintIcon />
      <FormattedMessage {...messages.print} />
    </ToolbarButton>
  </React.Fragment>
)

export default PrintButton

Fix print button (still downloads, does not print)


import m from 'mithril';
import h from '../h';
import userVM from '../vms/user-vm';
import projectVM from '../vms/project-vm';
import youtubeLightbox from '../c/youtube-lightbox';

const I18nScope = _.partial(h.i18nScope, 'projects.dashboard_start');
const projectEditStart = {
    controller(args) {
    },

    view(ctrl, args) {
        return m('.dashboard-header.min-height-70.u-text-center',
            m('.w-container',
                m('.u-marginbottom-40.w-row',
                    [
                        m('.w-col.w-col-8.w-col-push-2',
                            [
                                m('.fontsize-larger.fontweight-semibold.lineheight-looser.u-marginbottom-10',
                                    I18n.t('title', I18nScope())
                                ),
                                m('.fontsize-small.lineheight-loose.u-marginbottom-40',
                                    I18n.t('description', I18nScope({ name: args.project().user.name || '' }))
                                ),
                                m('.card.card-terciary.u-radius',
                                    m(`iframe[allowfullscreen="true"][frameborder="0"][scrolling="no"][mozallowfullscreen="true"][webkitallowfullscreen="true"][src=${I18n.t('video_src', I18nScope())}]`)
                                )
                            ]
                        )
                    ]
                )
            )
        );
    }
};

export default projectEditStart;

import m from 'mithril';
import h from '../h';
import userVM from '../vms/user-vm';
import projectVM from '../vms/project-vm';
import youtubeLightbox from '../c/youtube-lightbox';

const I18nScope = _.partial(h.i18nScope, 'projects.dashboard_start');
const projectEditStart = {
    controller(args) {
    },

    view(ctrl, args) {
        return m('.dashboard-header.min-height-70.u-text-center',
            m('.w-container',
                m('.u-marginbottom-40.w-row',
                    [
                        m('.w-col.w-col-8.w-col-push-2',
                            [
                                m('.fontsize-larger.fontweight-semibold.lineheight-looser.u-marginbottom-10',
                                    I18n.t('title', I18nScope())
                                ),
                                m('.fontsize-small.lineheight-loose.u-marginbottom-40',
                                    I18n.t('description', I18nScope({ name: args.project().user.name || '' }))
                                ),
                                m('.card.card-terciary.u-radius',
                                    m(`iframe[allowfullscreen="true"][width="630"][height="383"][frameborder="0"][scrolling="no"][mozallowfullscreen="true"][webkitallowfullscreen="true"][src=${I18n.t('video_src', I18nScope())}]`)
                                )
                            ]
                        )
                    ]
                )
            )
        );
    }
};

export default projectEditStart;

Set height and width for Sub Ebook Embed iframe


// Adapted from: http://bl.ocks.org/cpbotha/5200394
export function dropShadow (el, name, dx, dy, blur) {
  let defs = el.select('defs');

  if (defs.empty()) {
    defs = el.append('defs');
  }

  // create filter with id #drop-shadow
  // height = 130% so that the shadow is not clipped
  const filter = defs.append('filter')
    .attr('id', 'drop-shadow' + (name ? `-${name}` : ''))
    .attr('height', '130%');

  // SourceAlpha refers to opacity of graphic that this filter will be applied to
  // convolve that with a Gaussian with standard deviation 3 and store result
  // in blur
  filter.append('feGaussianBlur')
    .attr('in', 'SourceAlpha')
    .attr('stdDeviation', blur);

  // translate output of Gaussian blur to the right and downwards with 2px
  // store result in offsetBlur
  filter.append('feOffset')
    .attr('dx', dx)
    .attr('dy', dy)
    .attr('result', 'offsetBlur');

  // overlay original SourceGraphic over translated blurred opacity by using
  // feMerge filter. Order of specifying inputs is important!
  const feMerge = filter.append('feMerge');

  feMerge.append('feMergeNode');
  feMerge.append('feMergeNode').attr('in', 'SourceGraphic');
}

// Adapted from: http://bl.ocks.org/cpbotha/5200394
export function dropShadow (el, name, dx, dy, blur, opacity) {
  let defs = el.select('defs');

  if (defs.empty()) {
    defs = el.append('defs');
  }

  // create filter with id #drop-shadow
  // height = 130% so that the shadow is not clipped
  const filter = defs.append('filter')
    .attr('id', 'drop-shadow' + (name ? `-${name}` : ''))
    .attr('height', '130%');

  // SourceAlpha refers to opacity of graphic that this filter will be applied to
  // convolve that with a Gaussian with standard deviation 3 and store result
  // in blur
  filter.append('feGaussianBlur')
    .attr('in', 'SourceAlpha')
    .attr('stdDeviation', blur);

  // translate output of Gaussian blur to the right and downwards with 2px
  // store result in offsetBlur
  filter.append('feOffset')
    .attr('dx', dx)
    .attr('dy', dy)
    .attr('result', 'offsetBlur');

  filter.append('feComponentTransfer').append('feFuncA')
    .attr('type', 'linear')
    .attr('slope', opacity || 1);

  // overlay original SourceGraphic over translated blurred opacity by using
  // feMerge filter. Order of specifying inputs is important!
  const feMerge = filter.append('feMerge');

  feMerge.append('feMergeNode');
  feMerge.append('feMergeNode').attr('in', 'SourceGraphic');
}

Add opacity to the drop shadow filter.


var express = require('express');
var app     = module.exports = express();

app.get('*', function(req, res) {
  console.log('404 ERROR:', req.url);
  res.redirect('/#' + req.url);
});

app.use(function(err, req, res, next) {
  console.log('500 ERROR:', req.url);
  res.redirect('/#/error' + req.url);
});

var express = require('express');
var app     = module.exports = express();

app.get('/fail', function(req, res, next) {
  console.log('attempting to trigger 500...');
  var a = b[d]; // breakage

  next();
});

app.get('*', function(req, res) {
  console.log('404 ERROR:', req.url);
  res.redirect('/#' + req.url);
});

app.use(function(err, req, res, next) {
  console.log('500 ERROR:', req.url);
  res.redirect('/#/error' + req.url);
});

Add "/fail" route to errors lib for 500 testing.


/* 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 no-console, global-require */

import path from 'path';

const exec = ({
	command,
	message,
	dir = '.',
	rootPath,
	verbose = true
}, callback) => {
	if (message) {
		console.log(message);
	}
	const child = require('child_process').exec(command, {
		cwd: path.resolve(rootPath, dir),
		maxBuffer: 1024 * 500
	}, (err) => {
		callback(err);
	});

	if (verbose === true) {
		child.stdout.on('data', (data) => process.stdout.write(data.toString()));
	}
};

export { exec as default };

/* 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 no-console, global-require */

import path from 'path';

const exec = ({
	command,
	message,
	dir = '.',
	rootPath,
	verbose = true
}, callback) => {
	if (message) {
		console.log(message);
	} else {
		console.log(command);
	}
	const child = require('child_process').exec(command, {
		cwd: path.resolve(rootPath, dir),
		maxBuffer: 1024 * 500
	}, (err) => {
		callback(err);
	});

	if (verbose === true) {
		child.stdout.on('data', (data) => process.stdout.write(data.toString()));
	}
};

export { exec as default };

Add console log of commands


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

module.exports.info = {
	name: 'Playlist',
	description: 'List the current sounds and music that is stored within the playlist.',
	category: 'Music',
	aliases: [
		'list',
		'playlist',
		'queue'
	],
	use: [
		{
			name: '',
			value: 'List the playlist'
		}
	]
};
module.exports.command = (message) => {
	utils.music.list(message, (playlist, repeat) => {
		if (playlist.length === 0) {
			message.channel.createMessage('The playlist is empty.');
		} else {
			let reply = `https://discord.mss.ovh/music/${message.channel.guild.id}\n\`\`\`\n`;
			reply += `Repeat mode is ${repeat ? 'enabled' : 'disabled'}\n`;
			playlist.forEach((element, index) => {
				reply += `[${index || 'Current'}] ${element.title}\n`;
			});
			reply += '```';

			message.channel.createMessage(reply);
		}
	});
};

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

module.exports.info = {
	name: 'Playlist',
	description: 'List the current sounds and music that is stored within the playlist.',
	category: 'Music',
	aliases: [
		'list',
		'playlist',
		'queue'
	],
	use: [
		{
			name: '',
			value: 'List the playlist'
		}
	]
};
module.exports.command = (message) => {
	utils.music.list(message, (playlist, repeat) => {
		if (playlist.length === 0) {
			message.channel.createMessage('The playlist is empty.');
		} else {
			let reply = `https://discord.mss.ovh/music/${message.channel.guild.id}\n\`\`\`\n`;
			reply += `Repeat mode is ${repeat ? 'enabled' : 'disabled'}\n`;
			playlist.forEach((element, index) => {
				reply += `[${index || 'Current'}] ${element.title}\n`;
			});
			reply += '```';

			if (reply && reply.length > 1900) {
				message.channel.createMessage(`https://discord.mss.ovh/music/${message.channel.guild.id}\nThe list is too long to display. Please view the playlist online.`);
			} else {
				message.channel.createMessage(reply);
			}
		}
	});
};

Remove entire playlist if too long


/*
* This file can be used to convey information to other eslint files inside
* Canvas.
*/

module.exports = {
  globals: {
    "ENV": true
  },
  plugins: [
    "promise"
  ],
  // 0 - off, 1 - warning, 2 - error
  rules: {
    "class-methods-use-this": [0],
    "comma-dangle": [2, "only-multiline"],
    "func-names": [0],
    "max-len": [1, {"code": 140}],
    "no-continue": [0],
    "no-plusplus": [0],
    "no-underscore-dangle": [0],
    "no-unused-vars": [2, { "argsIgnorePattern": "^_"}],
    "object-curly-spacing": [0],
    "semi": [0],
    "space-before-function-paren": [2, "always"],
    "import/no-amd": [0],

    // allows 'i18n!webzip_exports' and 'compiled/foo/bar'
    "import/no-extraneous-dependencies": [0],
    "import/named": [2],
    "import/no-unresolved": [0],
    "import/no-webpack-loader-syntax": [0],

    "import/extensions": [1, { "js": "never", "jsx": "never", "json": "always" }],
    "promise/avoid-new": [0],
  }
};

/*
* This file can be used to convey information to other eslint files inside
* Canvas.
*/

module.exports = {
  globals: {
    "ENV": true
  },
  plugins: [
    "promise"
  ],
  // 0 - off, 1 - warning, 2 - error
  rules: {
    "class-methods-use-this": [0],
    "comma-dangle": [2, "only-multiline"],
    "func-names": [0],
    "max-len": [1, {"code": 140}],
    "no-continue": [0],
    "no-plusplus": [0],
    "no-underscore-dangle": [0],
    "no-unused-vars": [2, { "argsIgnorePattern": "^_"}],
    "object-curly-spacing": [0],
    "semi": [0],
    "space-before-function-paren": [2, "always"],

    // allows 'i18n!webzip_exports' and 'compiled/foo/bar'
    "import/no-extraneous-dependencies": [0],
    "import/named": [2],
    "import/no-unresolved": [0],
    "import/no-webpack-loader-syntax": [0],

    "import/extensions": [1, { "js": "never", "jsx": "never", "json": "always" }],
    "promise/avoid-new": [0],
  }
};

Make using AMD imports a linting error


module.exports = function(r) {
    'use strict';

    return {
        allByProject: allByProject
    };

    function allByProject() {
        return r.table('access').run().then(function(allAccess) {
            let byProject = {};
            allAccess.forEach(function(a) {
                if (!(a.project_id in byProject)) {
                    byProject[a.project_id] = [];
                }
                byProject[a.project_id].push(a);
            });
            return byProject;
        });
    }
};

module.exports = function(r) {
    'use strict';

    return {
        allByProject: allByProject,
        adminUsers: adminUsers,
    };

    function allByProject() {
        return r.table('access').run().then(function(allAccess) {
            let byProject = {};
            allAccess.forEach(function(a) {
                if (!(a.project_id in byProject)) {
                    byProject[a.project_id] = [];
                }
                byProject[a.project_id].push(a);
            });
            return byProject;
        });
    }

    function adminUsers() {
        return r.table('users').filter({admin: true}).run();
    }
};

Add method to get admin users.


var deps = {
	DrawHandler: {
		src: ['Handler.Draw.js'],
		desc: 'The base class for drawing.',
	},

	Polys: {
		src: ['Polyline.Draw.js',
		      'Polygon.Draw.js'],
		desc: 'Polyline and Polygon drawing handlers.',
		deps: ['DrawHandler']
	},

	SimpleShapes: {
		src: ['SimpleShape.Draw.js',
		      'Circle.Draw.js',
		      'Rectangle.Draw.js'],
		desc: 'Simple Shape drawing handlers.',
		deps: ['DrawHandler']
	},

	Markers: {
		src: ['Marker.Draw.js'],
		desc: 'Marker drawing handlers.',
		deps: ['DrawHandler']
	},

	DrawingControl: {
		src: ['Control.Draw.js'],
		desc: 'Drawing control.',
		deps: ['Polys',
			   'SimpleShapes',
			   'Markers']
	}
};

if (typeof exports !== 'undefined') {
	exports.deps = deps;
}

var deps = {
	LeafletExtensions: {
		src: [
			'ext/LineUtil.js',
			'ext/Polyline.js',
			'ext/Polygon.js'
		],
		desc: 'Extensions to Leaflet to add intersection detection.'
	},

	DrawHandler: {
		src: ['draw/Handler.Draw.js'],
		desc: 'The base class for drawing.',
		deps: ['LeafletExtensions']
	},

	Polys: {
		src: [
			'draw/shapes/Polyline.Draw.js',
			'draw/shapes/Polygon.Draw.js'
		],
		desc: 'Polyline and Polygon drawing handlers.',
		deps: ['DrawHandler']
	},

	SimpleShapes: {
		src: [
			'draw/shapes/SimpleShape.Draw.js',
			'draw/shapes/Circle.Draw.js',
			'draw/shapes/Rectangle.Draw.js'
		],
		desc: 'Simple Shape drawing handlers.',
		deps: ['DrawHandler']
	},

	Markers: {
		src: ['draw/shapes/Marker.Draw.js'],
		desc: 'Marker drawing handlers.',
		deps: ['DrawHandler']
	},

	DrawingControl: {
		src: ['draw/Control.Draw.js'],
		desc: 'Drawing control.',
		deps: [
			'Polys',
			'SimpleShapes',
			'Markers'
		]
	}
};

if (typeof exports !== 'undefined') {
	exports.deps = deps;
}

Add leaflet extensions to build script.


/*jshint node: true */
'use strict';

var browserify = require('browserify'),
    es6ify     = require('es6ify'),
    glob       = require('glob');

es6ify.traceurOverrides = { blockBinding: true };

var pipeline = browserify()
    .add(es6ify.runtime)
    .transform(es6ify);

// once we switch to a modern version of browserify, add takes an
// array as an argument
glob.sync('./test/*.js').forEach(function (file) {
    pipeline.add(file);
});

pipeline.bundle({ debug: true })
    .on('error', function (err) { console.error(err); console.log(err.stack); })
    .pipe(process.stdout);

/*jshint node: true */
'use strict';

var browserify = require('browserify'),
    es6ify     = require('es6ify'),
    glob       = require('glob');

es6ify.traceurOverrides = { blockBinding: true };

var pipeline = browserify()
    .add(es6ify.runtime)
    .transform(es6ify.configure(/^(?!.*node_modules)+.+\.js$/));

// once we switch to a modern version of browserify, add takes an
// array as an argument
glob.sync('./test/*.js').forEach(function (file) {
    pipeline.add(file);
});

pipeline.bundle({ debug: true })
    .on('error', function (err) { console.error(err); console.log(err.stack); })
    .pipe(process.stdout);

Exclude the node modules from es6ify


import { default as articles } from './articles';
import { default as replyrequests } from './replyrequests';
import { default as articlereplyfeedbacks } from './articlereplyfeedbacks';
import { default as replies } from './replies';
import { default as tags } from './tags';
import { default as users } from './users';

export { articles, replyrequests, articlereplyfeedbacks, replies, tags, users };

import { default as articles } from './articles';
import { default as replyrequests } from './replyrequests';
import { default as articlereplyfeedbacks } from './articlereplyfeedbacks';
import { default as replies } from './replies';
import { default as tags } from './tags';
import { default as users } from './users';
import { default as urls } from './urls';

export {
  articles,
  replyrequests,
  articlereplyfeedbacks,
  replies,
  tags,
  users,
  urls,
};

Add omitted urls to schema


/**
 * @module app.backgroundlayer.BlankLayer
 */
/**
 * @fileoverview This file defines the Blank Layer service. This service
 * creates an empty layer and the corresponding spec.
 */

import appModule from '../module.js';
import olLayerTile from 'ol/layer/Tile.js';

/**
 * @constructor
 * @param {angularGettext.Catalog} gettextCatalog Gettext service.
 * @ngInject
 */
const exports = function(gettextCatalog) {

  /**
   * @typedef {ol.layer.Tile}
   * @private
   */
  this.blankLayer_ = new olLayerTile();
  var blankLabel = gettextCatalog.getString('blank');
  this.blankLayer_.set('label', blankLabel);
};

/**
 * Get the blank layer.
 * @return {ol.layer.Tile} The blank tile.
 */
exports.prototype.getLayer = function() {
  return this.blankLayer_;
};

appModule.service('appBlankLayer', exports);

export default exports;

/**
 * @module app.backgroundlayer.BlankLayer
 */
/**
 * @fileoverview This file defines the Blank Layer service. This service
 * creates an empty layer and the corresponding spec.
 */

import appModule from '../module.js';
import olVectorLayer from 'ol/layer/Vector';
import olVectorSource from 'ol/source/Vector';

/**
 * @constructor
 * @param {angularGettext.Catalog} gettextCatalog Gettext service.
 * @ngInject
 */
const exports = function(gettextCatalog) {

  /**
   * @typedef {ol.layer.Tile}
   * @private
   */
  this.blankLayer_ = new olVectorLayer({
    source: new olVectorSource({features: []})
  });
  var blankLabel = gettextCatalog.getString('blank');
  this.blankLayer_.set('label', blankLabel);
};

/**
 * Get the blank layer.
 * @return {ol.layer.Tile} The blank tile.
 */
exports.prototype.getLayer = function() {
  return this.blankLayer_;
};

appModule.service('appBlankLayer', exports);

export default exports;

Create blank layer with VectorLayer instead of TileLayer


var TestHelper = require("../helpers/testHelper");

describe('app', function() {

	it('should register a user', function() {
		driver.get("/",{id:'viewEntries'});
		TestHelper.clickMenuItem('logout');
		expect(driver.getCurrentUrl()).toBe('http://minutedock.local.com:9443/login');
		clearUsers();
		persistAuthToken();
		driver.get("/",{id:'apiKey'});
		resetSessionCookie();
		$('#apiKey').sendKeys("valid_api_key");
		$('#register').click();
		driver.wait({id:'viewEntries'});
		$('#addEntryPanel').click();
		TestHelper.selectProject('project1');
		expect($('#contact').getAttribute("value")).toEqual('contact1');
	});
});

var TestHelper = require("../helpers/testHelper");

describe('app', function() {

	it('should register a user', function() {
		driver.get("/",{id:'viewEntries'});
		TestHelper.clickMenuItem('logout');
		expect(driver.getCurrentUrl()).toBe('http://minutedock.local.com:9443/login');
		clearUsers();
		persistAuthToken();
		driver.get("/",{id:'apiKey'});
		resetSessionCookie();
		$('#apiKey').sendKeys("valid_api_key");
		$('#register').click();
		driver.wait({id:'viewEntries'});
		$('#addEntryPanel').click();
		TestHelper.selectProject('project1');
		expect($('#contact').getAttribute("value")).toEqual('contact1');
	});

	it("should change a user's API key", function() {
		driver.get("/",{id:'viewEntries'});
		TestHelper.clickMenuItem('logout');
		expect(driver.getCurrentUrl()).toBe('http://minutedock.local.com:9443/login');
		clearUsers();
		persistAuthToken();
		driver.get("/",{id:'apiKey'});
		resetSessionCookie();
		$('#apiKey').sendKeys("valid_api_key");
		$('#register').click();
		driver.wait({id:'viewEntries'});
		$('#addEntryPanel').click();
		TestHelper.selectProject('project1');

		TestHelper.clickMenuItem('reset-api-key');
		expect(driver.getCurrentUrl()).toBe('http://minutedock.local.com:9443/#/register');
		$('#apiKey').sendKeys("valid_api_key");
		$('#register').click();
		driver.wait({id:'viewEntries'});
		$('#addEntryPanel').click();
		TestHelper.selectProject('project1');
		expect($('#contact').getAttribute("value")).toEqual('contact1');
	});
});

Add test to check update api key


/* AIDA Source Code */
/* Contributors located at: github.com/2nd47/CSC309-A4 */

// main app

// server modules
var bcrypt = require('bcryptjs');
var express = require('express');
var mongoose = require('mongoose');
var session = require('express-session');
var validator = require('validator');
var qs = require('querystring');

// testing modules
var testCase = require('mocha').describe;
var pre = require('mocha').before;
var assertions = require('mocha').it;
var assert = require('chai').assert;

// module init
var app = express();
// router import keeps main file clean
var router = require('./router');
var db = require('../db/db.js');

// app init
const APP_PORT = process.env.port || 3000;
const saltRounds = 10;

function init() {
  return;
}

function main() {
  init();
  app.use('/', router);
  app.listen(APP_PORT);
  console.log('Server listening on port ' + APP_PORT);
}

main();

/* AIDA Source Code */
/* Contributors located at: github.com/2nd47/CSC309-A4 */

// main app

// server modules
var bcrypt = require('bcryptjs');
var express = require('express');
var mongoose = require('mongoose');
var session = require('express-session');
var validator = require('validator');
var qs = require('querystring');

// testing modules
var testCase = require('mocha').describe;
var pre = require('mocha').before;
var assertions = require('mocha').it;
var assert = require('chai').assert;

// module init
var app = express();
// router import keeps main file clean
var router = require('./router');
var db = require('../db/db.js');

// app init
const APP_PORT = 3000;
const saltRounds = 10;

function init() {
  return;
}

function main() {
  init();
  app.use('/', router);
  app.listen(APP_PORT);
  console.log('Server listening on port ' + APP_PORT);
}

main();

Revert "use environment variable for port"


// listen for page navigations, detect if they're YT & include our disable
chrome.webNavigation.onBeforeNavigate.addListener((details) => {
  if(detectYTURL(details.url) && details.url.indexOf("disable_polymer=true") === -1){
    // append the disabling field
    if(details.url.indexOf("?") !== -1){
      chrome.tabs.update(details.tabId, {
        url: details.url + "&disable_polymer=true"
      });
    }else{
      chrome.tabs.update(details.tabId, {
        url: details.url + "?disable_polymer=true"
      });
    }
  }
});

// helper to keep things clean 👍
function detectYTURL(url){
  return ((url.indexOf("www.youtube.com") !== -1 || url.indexOf("https://youtube.com") !== -1)
          && url.indexOf("/js") === -1) && url.indexOf("google") === -1 &&
          url.indexOf("googleads") === -1 && url.indexOf(".g.") === -1;
}

// listen for page navigations, detect if they're YT & include our disable
chrome.webNavigation.onBeforeNavigate.addListener((details) => {
  if(detectYTURL(details.url) && details.url.indexOf("disable_polymer=true") === -1){
    // append the disabling field
    if(details.url.indexOf("?") !== -1){
      chrome.tabs.update(details.tabId, {
        url: details.url + "&disable_polymer=true"
      });
    }else{
      chrome.tabs.update(details.tabId, {
        url: details.url + "?disable_polymer=true"
      });
    }
  }
});

// helper to keep things clean 👍
function detectYTURL(url){
  return ((url.indexOf("www.youtube.com") !== -1 || url.indexOf("https://youtube.com") !== -1)
          && url.indexOf("/js") === -1) && url.indexOf("google") === -1 &&
          url.indexOf("googleads") === -1 && url.indexOf(".g.") === -1 &&
          url.indexOf("www.youtube.com/ad_companion") === -1;
}

Fix bug with yt ad companion crashing during preroll ads


import NBA from 'nba'

module.exports = (playerName, dispatch) => {
  const playerToAdd = NBA.findPlayer(playerName)
  if(playerToAdd) {
    dispatch({type: "TOGGLE_PLAYER_LOADING"})
    dispatch({type: 'HANDLE_FORM_NAME_CHANGE', payload: ""})
    dispatch({type: 'CLEAR_SUGGESTIONS'})
    NBA.stats.playerInfo({ PlayerID: playerToAdd.playerId})
      .then(function (response) {
        dispatch({type: 'RECEIVE_PLAYER_INFO', payload: response.playerHeadlineStats[0]})
        dispatch({type: "TOGGLE_PLAYER_LOADING"})
      })
      .catch(function (error) {
        console.log('Error:', error)
      })
  }
}

import NBA from 'nba'
import request from 'superagent'

// http://stats.nba.com/stats/commonplayerinfo?PlayerID=203077&SeasonType=Regular%20Season&LeagueID=00

module.exports = (playerName, dispatch) => {
  const playerToAdd = NBA.findPlayer(playerName)
  if(playerToAdd) {
    dispatch({type: "TOGGLE_PLAYER_LOADING"})
    dispatch({type: 'HANDLE_FORM_NAME_CHANGE', payload: ""})
    dispatch({type: 'CLEAR_SUGGESTIONS'})
    NBA.stats.playerInfo({ PlayerID: playerToAdd.playerId})
      .then(function (response) {
        dispatch({type: 'RECEIVE_PLAYER_INFO', payload: response.playerHeadlineStats[0]})
        dispatch({type: "TOGGLE_PLAYER_LOADING"})
      })
      .catch(function (error) {
        console.log('Error:', error)
      })
  }
}

Change getplayer back to original with NBA module


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

if (config.isProduction || require('piping')(config.piping)) {
  if (!process.env.NODE_ENV)
    throw new Error('Environment variable NODE_ENV must be set.');

  // Load and use polyfill for ECMA-402.
  if (!global.Intl)
    global.Intl = require('intl');

  require('babel/register')({optional: ['es7']});

  // To ignore webpack custom loaders on server.
  config.webpackStylesExtensions.forEach(function(ext) {
    require.extensions['.' + ext] = function() {};
  });

  require('./main');
}

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

if (config.isProduction || require('piping')(config.piping)) {
  if (!process.env.NODE_ENV)
    throw new Error('Environment variable NODE_ENV isn\'t set. Remember it\'s up your production enviroment to set NODE_ENV and maybe other variables. To run app locally in production mode, use gulp -p command instead.');

  // Load and use polyfill for ECMA-402.
  if (!global.Intl)
    global.Intl = require('intl');

  require('babel/register')({optional: ['es7']});

  // To ignore webpack custom loaders on server.
  config.webpackStylesExtensions.forEach(function(ext) {
    require.extensions['.' + ext] = function() {};
  });

  require('./main');
}

Improve error message for npm start without NODE_ENV.


function printError(e) {
    chrome.alarms.clearAll();
    webkitNotifications.createNotification('',
            e, '').show();
}

function checkMail() {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
      if (xhr.readyState != 4)  {
          return;
      }
      if (xhr.responseText) {
          var count = xhr.responseText.match(/Message: Unread/g).length;
          if (count > 0) {
              chrome.alarms.clearAll();
              var notification = webkitNotifications.createNotification(
                  '', 'New mail message', 'You have ' + count + ' new message(s)'
              );
              notification.onclose = function() {
                  chrome.alarms.create({'delayInMinutes': 1});
              };
              notification.show();
          } else {
              printError('Could not get message count from URL');
          }
      } else {
          printError('Fetching failed');
      }
    };
    xhr.open("GET", localStorage["url"], true);
    xhr.send(null);
}
if (localStorage["url"] != ""){
    chrome.alarms.onAlarm.addListener(checkMail);
    chrome.alarms.create({'delayInMinutes': 1});
    checkMail();
} else {
    printError('owa-notification-chrome not configured');
}

function printError(e) {
    chrome.alarms.clearAll();
    webkitNotifications.createNotification('',
            e, '').show();
}

function setCheckAlarm() {
    chrome.alarms.create({'periodInMinutes': 1, 'delayInMinutes': 1});
}

function checkMail() {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
      if (xhr.readyState != 4)  {
          return;
      }
      if (xhr.responseText) {
          var matches = xhr.responseText.match(/Message: Unread/g);
          if (matches != null) {
              var count = matches.length;
              if (count > 0) {
                  chrome.alarms.clearAll();
                  var notification = webkitNotifications.createNotification(
                      '', 'New mail message', 'You have ' + count + ' new message(s)'
                  );
                  notification.onclose = setCheckAlarm;
                  notification.show();
              } else {
                  printError('Could not get message count from URL');
              }
          }
      } else {
          printError('Fetching failed');
      }
    };
    xhr.open("GET", localStorage["url"], true);
    xhr.send(null);
}
if (localStorage["url"] != ""){
    chrome.alarms.onAlarm.addListener(checkMail);
    setCheckAlarm();
    checkMail();
} else {
    printError('owa-notification-chrome not configured');
}

Fix to check match object for null and to set a recurring alarm.


const utilitiesMixin = {
    methods: {
        getStatus: function(job) {
            if (!job.pid && !job.start && !job.end && !job.message)
              return "pending";
            else if (job.pid && job.start && !job.end && !job.message)
              return "running";
            else if (job.pid && job.start && job.end && !job.message)
              return "completed";
            else if (job.message)
              return "failed";
        },
    },
    filters: {
        date: function(value) {
          var options = {
              year: "numeric",
              month: "2-digit",
              day: "numeric"
          };
          return value ? new Date(value).toLocaleString('en-US', options) : '';
        },
        iso_date: function(value) {
            let iso_date = new Date(new Date(value).toString().split('GMT')[0]+' UTC')
                                    .toISOString().split('.')[0]+'Z';
            return value ? iso_date : '';
        }
    },
}

export default utilitiesMixin;

const utilitiesMixin = {
    methods: {
        getStatus: function(job) {
            if (!job.pid && !job.start && !job.end && !job.message)
              return "pending";
            else if (job.pid && job.start && !job.end && !job.message)
              return "running";
            else if (job.pid && job.start && job.end && !job.message)
              return "completed";
            else if (job.message)
              return "failed";
        },
    },
    filters: {
        date: function(value) {
          var options = {
              year: "numeric",
              month: "2-digit",
              day: "numeric"
          };
          return value ? new Date(value).toLocaleString('en-US', options) : '';
        },
        iso_date: function(value) {
            let iso_date = '';
            if(value) {
              iso_date = new Date(new Date(value).toString().split('GMT')[0]+' UTC')
                                  .toISOString().split('.')[0]+'Z';
            }
            return iso_date;
        }
    },
}

export default utilitiesMixin;

Fix bug where reseting jobs would cause vue iso_date filter to break.


export default {
  resizers: ['lt', 't', 'rt', 'l', 'r', 'lb', 'b', 'rb'],
  axis: [{
      value: 'lt',
      name: '左上对齐'
    },
    {
      value: 'rt',
      name: '右上对齐'
    },
    {
      value: 'lb',
      name: '左下对齐'
    },
    {
      value: 'rb',
      name: '右下对齐'
    }
  ]
}

export default {
  resizers: ['lt', 't', 'rt', 'l', 'r', 'lb', 'b', 'rb'],
  axis: [{
      value: 'lt',
      name: '左上对齐'
    },
    {
      value: 'rt',
      name: '右上对齐'
    },
    {
      value: 'lb',
      name: '左下对齐'
    },
    {
      value: 'rb',
      name: '右下对齐'
    }
  ],
  unit: [
    {
      value: 'px',
      name: '像素'
    },
    {
      value: '%',
      name: '百分比'
    }
  ]
}

Change quick set panel settings


jest.unmock('../../src/actions/counter')

import {
  INCREMENT_COUNTER,
  DECREMENT_COUNTER,
  incrementCounter,
  decrementCounter,
  incrementCounterAsync
} from '../../src/actions/counter'

describe('Counter Actions', () => {
  it('exports counter action constants', () => {
    expect(INCREMENT_COUNTER).toEqual('INCREMENT_COUNTER')
    expect(DECREMENT_COUNTER).toEqual('DECREMENT_COUNTER')
  })

  it('exports action creators', () => {
    expect(incrementCounter()).toEqual({
      type: 'INCREMENT_COUNTER'
    })

    expect(decrementCounter()).toEqual({
      type: 'DECREMENT_COUNTER'
    })
  })
})

jest.unmock('../../src/actions/counter')

import {
  INCREMENT_COUNTER,
  DECREMENT_COUNTER,
  incrementCounter,
  decrementCounter,
  incrementCounterAsync
} from '../../src/actions/counter'

describe('Counter Actions', () => {
  it('exports counter action constants', () => {
    expect(INCREMENT_COUNTER).toEqual('INCREMENT_COUNTER')
    expect(DECREMENT_COUNTER).toEqual('DECREMENT_COUNTER')
  })

  it('exports action creators', () => {
    expect(incrementCounter()).toEqual({
      type: 'INCREMENT_COUNTER'
    })

    expect(decrementCounter()).toEqual({
      type: 'DECREMENT_COUNTER'
    })
  })

  it('exports async action creators', () => {
    const dispatch = jest.fn()
    incrementCounterAsync()(dispatch)
    window.setTimeout.mock.calls[0][0]()
    expect(dispatch.mock.calls[0][0]).toEqual({type: 'INCREMENT_COUNTER'})
  })
})

Add test for async action creators


//>>includeStart('strict', pragmas.strict);
define(['./isImmutable'], function (isImmutable) {

    'use strict';

    /**
     * Extract meta data from a property.
     *
     * @param {Mixed} prop The property
     * @param {String} name The name of the property
     *
     * @return {Object} An object containg the metadata
     */
    function propertyMeta(prop, name) {
        var ret = {};

        // Is it undefined?
        if (prop === undefined) {
            return null;
        }

        // Analyze visibility
        if (name) {
            if (name.charAt(0) === '_') {
                if (name.charAt(1) === '_') {
                    ret.isPrivate = true;
                } else {
                    ret.isProtected = true;
                }
            } else {
                ret.isPublic = true;
            }
        }

        ret.isImmutable = isImmutable(prop);
        ret.value = prop;

        return ret;
    }

    return propertyMeta;
});
//>>includeEnd('strict');

Remove unused code in loose.


import React, { Component } from 'react';
import { Link } from 'react-router';

import NavBar from './NavBar';
import Footer from './Footer';

export default class MainLayout extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <div id='main-layout'>
        <NavBar />
        <main id='main-content'>
          {this.props.children}
        </main>
        <Footer />
      </div>
    );
  }
};

'use strict';
import React, { Component } from 'react';
import { Link } from 'react-router';

import NavBar from './NavBar';
import Footer from './Footer';

export default class MainLayout extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <div className="main-layout">
        <NavBar />
        <main className="main-content">
          {this.props.children}
        </main>
        <Footer />
      </div>
    );
  }
};

Replace id attributes with className


/**
 * Start app
 */

'use strict';

var app = new (require('modules/app'))();
app.init();

/**
 * Start app
 */

'use strict';

var app = new (require('./modules/app'))();
app.init();

Use relative path for modules/app


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

myApp.config(function ($routeProvider) {
  $routeProvider.when('/',{
    controller: 'WomenController',
    templateUrl: 'views/women.html'
  })
  .when('/women',{
    controller: 'WomenController',
    templateUrl: 'views/women.html'
  })

  .when('/women/details/:id',{
    controller: 'WomenController',
    templateUrl: 'views/woman_details.html'
  })

  .when('/women/add',{
    controller: 'WomenController',
    templateUrl: 'views/add_woman.html'
  })

  .when('/women/edit/:id',{
    controller: 'WomenController',
    templateUrl: 'views/edit_woman.html'
  })

  .otherwise({
    redirectTo: '/'
  });

});

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

myApp.config(function ($routeProvider) {
  $routeProvider.when('/',{
    controller: 'WomenController',
    templateUrl: 'views/women.html'
  })
  .when('/women',{
    controller: 'WomenController',
    templateUrl: 'views/women.html'
  })

  .when('/women/details/:id',{
    controller: 'WomenController',
    templateUrl: 'views/woman_details.html'
  })

  .when('/women/add',{
    controller: 'WomenController',
    templateUrl: 'views/add_woman.html'
  })

  .when('/women/edit/:id',{
    controller: 'WomenController',
    templateUrl: 'views/edit_woman.html'
  })

  .when('/test',{
    controller: 'WomenController',
    templateUrl: 'views/template.html'
  })

  .otherwise({
    redirectTo: '/'
  });

});

Add controller for route /test. Template displaying.