const
    nJwt = require('njwt'),
    config = require('config.js'),
    errors = require('feathers-errors');

function processJWTIfExists (req, res, next) {

  req.feathers = req.feathers || {};

  let token = req.headers['authorization'];

  if (token == null) {
    let cookies = req.cookies;
    token = cookies.idToken;
  }

  if (token == null) {
    next();
    return;
  }

  const verifiedJwt = nJwt.verify(token, config.jwtSigningKey);
  req.feathers.userId = verifiedJwt.body.sub;

  next();
}

module.exports = {
  processJWTIfExists
};

const
    nJwt = require('njwt'),
    config = require('config.js'),
    errors = require('feathers-errors');

function processJWTIfExists (req, res, next) {

  req.feathers = req.feathers || {};

  let token = req.headers['authorization'];

  if (token == null) {
    let cookies = req.cookies;
    token = cookies.idToken;
  }

  if (token == null) {
    next();
    return;
  }

  try {
    const verifiedJwt = nJwt.verify(token, config.jwtSigningKey);
    req.feathers.userId = verifiedJwt.body.sub;
  } catch(e) {
    next();
    return;
  }

  next();
}

module.exports = {
  processJWTIfExists
};

Use a catch with the JWT verify function


(function() {
  'use strict';
  angular.module('kuulemmaApp').directive('locationMap', function($window) {
    return {
      restrict: 'A',
      scope: {
        latitude: '@',
        longitude: '@',
        polygon: '@'
      },
      link: function(scope, element) {
        var L = $window.L;
        var map = L.map(element[0] ,{
          zoomControl:false,
          scrollWheelZoom: false,
          dragging:false,
          touchZoom:false,
          tap:false,
          doubleClickZoom:false,
        });
        var zoom = 14;
        var osmUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
        var osmAttribution = 'Map data © OpenStreetMap contributors';
        var osm = new L.TileLayer(osmUrl, {
            minzoom: 1,
            maxzoom: 15,
            attribution: osmAttribution
        });

        map.setView(new L.LatLng(scope.latitude, scope.longitude), zoom);
        map.addLayer(osm);
        if (scope.polygon) {
          var poly = L.geoJson(JSON.parse(scope.polygon));
          poly.addTo(map);
          // Center and zoom map
          map.fitBounds(poly.getBounds());
        }
      }
    };
  });
})();

(function() {
  'use strict';
  angular.module('kuulemmaApp').directive('locationMap', function($window) {
    return {
      restrict: 'A',
      scope: {
        latitude: '@',
        longitude: '@',
        polygon: '@'
      },
      link: function(scope, element) {
        var L = $window.L;
        var map = L.map(element[0] ,{
          zoomControl:false,
          scrollWheelZoom: false,
          dragging:false,
          touchZoom:false,
          tap:false,
          doubleClickZoom:false,
        });
        var zoom = 14;
        var osmUrl = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
        var osmAttribution = 'Map data © OpenStreetMap contributors';
        var osm = new L.TileLayer(osmUrl, {
            minzoom: 1,
            maxzoom: 15,
            attribution: osmAttribution
        });

        map.setView(new L.LatLng(scope.latitude, scope.longitude), zoom);
        map.addLayer(osm);
        if (scope.polygon) {
          var poly = L.geoJson(JSON.parse(scope.polygon));
          poly.addTo(map);
          // Center and zoom map
          map.fitBounds(poly.getBounds());
        }
      }
    };
  });
})();

Use https to load openstreetmap data


// Store the preference locale in cookies and (if available) session to use
// on next requests.

'use strict';

var _ = require('lodash');

var LOCALE_COOKIE_MAX_AGE = 0xFFFFFFFF; // Maximum 32-bit unsigned integer.

module.exports = function (N, apiPath) {
  N.validate(apiPath, {
    locale: { type: 'string' }
  });

  N.wire.on(apiPath, function set_language(env, callback) {
    var locale = env.params.locale;

    if (!_.contains(N.config.locales.enabled, env.params.locale)) {
      // User sent a non-existent or disabled locale - reply with the default.
      locale = N.config.locales['default'];
    }

    env.extras.setCookie('locale', locale, {
      path: '/'
    , maxAge: LOCALE_COOKIE_MAX_AGE
    });

    if (env.session) {
      env.session.locale = locale;
    }

    if (env.session && env.session.user_id) {
      N.models.users.User.findById(env.session.user_id, function (err, user) {
        if (err) {
          callback(err);
          return;
        }

        user.locale = locale;
        user.save(callback);
      });
    } else {
      callback();
    }
  });
};

// Store the preference locale in cookies and (if available) session to use
// on next requests.

'use strict';

var _ = require('lodash');

var LOCALE_COOKIE_MAX_AGE = 0xFFFFFFFF; // Maximum 32-bit unsigned integer.

module.exports = function (N, apiPath) {
  N.validate(apiPath, {
    locale: { type: 'string' }
  });

  N.wire.on(apiPath, function set_language(env, callback) {
    var locale = env.params.locale;

    if (!_.contains(N.config.locales.enabled, env.params.locale)) {
      // User sent a non-existent or disabled locale - reply with the default.
      locale = N.config.locales['default'];
    }

    env.extras.setCookie('locale', locale, {
      path: '/'
    , maxAge: LOCALE_COOKIE_MAX_AGE
    });

    if (env.session) {
      env.session.locale = locale;
    }

    if (env.session && env.session.user_id) {
      N.models.users.User.findByIdAndUpdate(env.session.user_id, { locale: locale }, callback);
    } else {
      callback();
    }
  });
};

Simplify updating of locale field in user account.


import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import ArticlePreview from '../ArticlePreview';

describe('ArticlePreview', () => {
    it('should render ArticlePreview component properly', () => {
        const wrapper = shallow(<ArticlePreview description="Article Description" title="Article title" />);
        expect(wrapper.render().text).to.contain('Article title');
    });
});

import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import ArticlePreview from '../ArticlePreview';

describe('ArticlePreview', () => {
    it('should render ArticlePreview component properly', () => {
        const wrapper = shallow(<ArticlePreview description="Article Description" title="Article title" />);
        expect(wrapper.render().text()).to.contain('Article title');
    });
});

Fix a bug, used .test instead of .test()


import ExtentInteraction from '../src/ol/interaction/Extent.js';
import GeoJSON from '../src/ol/format/GeoJSON.js';
import Map from '../src/ol/Map.js';
import View from '../src/ol/View.js';
import {OSM, Vector as VectorSource} from '../src/ol/source.js';
import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js';

const vectorSource = new VectorSource({
  url: 'data/geojson/countries.geojson',
  format: new GeoJSON(),
});

const map = new Map({
  layers: [
    new TileLayer({
      source: new OSM(),
    }),
    new VectorLayer({
      source: vectorSource,
    }),
  ],
  target: 'map',
  view: new View({
    center: [0, 0],
    zoom: 2,
  }),
});

const extent = new ExtentInteraction();
map.addInteraction(extent);
extent.setActive(false);

//Enable interaction by holding shift
window.addEventListener('keydown', function (event) {
  if (event.keyCode == 16) {
    extent.setActive(true);
  }
});
window.addEventListener('keyup', function (event) {
  if (event.keyCode == 16) {
    extent.setActive(false);
  }
});

import ExtentInteraction from '../src/ol/interaction/Extent.js';
import GeoJSON from '../src/ol/format/GeoJSON.js';
import Map from '../src/ol/Map.js';
import View from '../src/ol/View.js';
import {OSM, Vector as VectorSource} from '../src/ol/source.js';
import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js';
import {shiftKeyOnly} from '../src/ol/events/condition.js';

const vectorSource = new VectorSource({
  url: 'data/geojson/countries.geojson',
  format: new GeoJSON(),
});

const map = new Map({
  layers: [
    new TileLayer({
      source: new OSM(),
    }),
    new VectorLayer({
      source: vectorSource,
    }),
  ],
  target: 'map',
  view: new View({
    center: [0, 0],
    zoom: 2,
  }),
});

const extent = new ExtentInteraction({condition: shiftKeyOnly});
map.addInteraction(extent);

Use condition instead of setActive and listeners


function short_hash(data, type, row, meta)
{
    return data.substring(0,8);
}

function init_page()
{
    $('#tickets').dataTable({
        "ajax": '../api/v1/tickets?filter=sold eq 1 and year eq 2016',
        columns: [
            {'data': 'hash', 'render':short_hash},
            {'data': 'firstName'},
            {'data': 'lastName'},
            {'data': 'type'}
        ]
    });
}

$(init_page)

function short_hash(data, type, row, meta)
{
    return data.substring(0,8);
}

function init_page()
{
    $('#tickets').dataTable({
        "ajax": '../api/v1/tickets?filter=sold eq 1 and year eq 2016&fmt=data-table',
        columns: [
            {'data': 'hash', 'render':short_hash},
            {'data': 'firstName'},
            {'data': 'lastName'},
            {'data': 'type'}
        ]
    });
}

$(init_page)

Fix new API call for datatable consumer


$(document).ready(function() {
	$('select').chosen();
	$('input.date').datepicker({
		beforeShowDay: $.datepicker.noWeekends
	});

	$('#save-button').savebutton($('#post-status'), '');
	$('#cancel-button').cancelbutton();
});

$(document).ready(function() {
	$('select').chosen();
	$('input.date').datepicker({
		beforeShowDay: $.datepicker.noWeekends
	});

	$('#save-button').savebutton($('#post-status'), '');
	$('#cancel-button').cancelbutton('/');
});

Fix cancel button on new sprint page


export default function argsToFindOptions(args, target) {
  var result = {}
    , targetAttributes = Object.keys(target.rawAttributes);

  if (args) {
    Object.keys(args).forEach(function (key) {
      if (~targetAttributes.indexOf(key)) {
        result.where = result.where || {};
        result.where[key] = args[key];
      }

      if (key === 'limit' && args[key]) {
        result.limit = args[key];
      }

      if (key === 'offset' && args[key]) {
        result.offset = args[key];
      }

      if (key === 'order' && args[key]) {
        result.order = [
          [args[key]]
        ];
      }
    });
  }

  return result;
}

export default function argsToFindOptions(args, target) {
  var result = {}
    , targetAttributes = Object.keys(target.rawAttributes);

  if (args) {
    Object.keys(args).forEach(function (key) {
      if (~targetAttributes.indexOf(key)) {
        result.where = result.where || {};
        result.where[key] = args[key];
      }

      if (key === 'limit' && args[key]) {
        result.limit = args[key];
      }

      if (key === 'offset' && args[key]) {
        result.offset = args[key];
      }

      if (key === 'order' && args[key]) {
        var order;
        if (args[key].indexOf('reverse:') === 0) {
          order = [args[key].substring(8), 'DESC'];
        } else {
          order = [args[key], 'ASC'];
        }

        result.order = [
          order
        ];
      }
    });
  }

  return result;
}

Allow "reverse:fieldname" for a descending sort


'use strict';

const _ = require('lodash');

module.exports = function (Model, bootOptions = {}) {
  Model.defineProperty('deleted', {
    type: Boolean,
    required: true,
    default: false
  });

  /**
   * @return {Promise<Model>}
   */
  Model.prototype.softDelete = function () {
    return this.updateAttribute('deleted', true);
  }

  /**
   * @return {Promise<Model>}
   */
  Model.prototype.restore = function () {
    return this.updateAttribute('deleted', false);
  }

  Model.beforeRemote('find', (ctx, instance, next) => {
    const scope = _.get(ctx.args, 'filter.scope', 'active');
    _.unset(ctx.args, 'filter.scope');
    switch (scope) {
      case 'active':
        _.set(ctx.args, 'filter.where.deleted', false);
        break;
      case 'inactive':
        _.set(ctx.args, 'filter.where.deleted', true);
        break;
      default:
        _.unset(ctx.args, 'filter.where.deleted');
        break;
    }
    process.nextTick(next);
  });
}

'use strict';

const _ = require('lodash');

module.exports = function (Model, bootOptions = {}) {
  Model.defineProperty('deleted', {
    type: Boolean,
    required: true,
    default: false
  });

  /**
   * @return {Promise<Model>}
   */
  Model.prototype.softDelete = function () {
    return this.updateAttribute('deleted', true);
  }

  /**
   * @return {Promise<Model>}
   */
  Model.prototype.restore = function () {
    return this.updateAttribute('deleted', false);
  }

  Model.beforeRemote('find', (ctx, instance, next) => {
    const scope = _.get(ctx.args, 'filter.scope', 'active');
    _.unset(ctx.args, 'filter.scope');
    switch (scope) {
      case 'active':
        _.set(ctx.args, 'filter.where.deleted.neq', true);
        break;
      case 'inactive':
        _.set(ctx.args, 'filter.where.deleted', true);
        break;
      default:
        _.unset(ctx.args, 'filter.where.deleted');
        break;
    }
    process.nextTick(next);
  });
}

Use not equal filter instead.


// Tab registration by MediaControlled event
document.addEventListener("MediaControlled", function () {
    chrome.runtime.sendMessage({command: "registerTab"});
});

// Tab unregistration by MediaUncontrolled event
document.addEventListener("MediaUncontrolled", function () {
    chrome.runtime.sendMessage({command: "unregisterTab"});
});

// Media Events emmiter
chrome.runtime.onMessage.addListener(function(request) {
    switch (request.command) {
        case "play-pause":
            document.dispatchEvent(new Event("MediaPlayPause"));
            break;

        case "stop":
            document.dispatchEvent(new Event("MediaStop"));
            break;

        case "prev":
            document.dispatchEvent(new Event("MediaPrev"));
            break;

        case "next":
            document.dispatchEvent(new Event("MediaNext"));
            break;
    }
});

// Tell document that we are ready
console.log('Keysocket Media Control API initialized');
document.dispatchEvent(new Event("MediaControlApiInit"));

// Tab registration by meta tag
if (document.getElementsByName("media-controlled").length > 0) {
    chrome.runtime.sendMessage({command: "registerTab"});
}

// Unregister tab before move to another URI
window.onunload = function() {
    chrome.runtime.sendMessage({command: "unregisterTab"});
}

// Media Events emmiter
chrome.runtime.onMessage.addListener(function(request) {
    switch (request.command) {
        case "play-pause":
            document.dispatchEvent(new Event("MediaPlayPause"));
            break;

        case "stop":
            document.dispatchEvent(new Event("MediaStop"));
            break;

        case "prev":
            document.dispatchEvent(new Event("MediaPrev"));
            break;

        case "next":
            document.dispatchEvent(new Event("MediaNext"));
            break;
    }
});

// Tab registration/unregistration by MediaControlStateChanged event
document.addEventListener("MediaControlStateChanged", function () {
    registerOrUnregisterPage();
});

// Unregister tab before move to another URI
window.onunload = function() {
    chrome.runtime.sendMessage({command: "unregisterTab"});
}

// Initial tab registration by meta tag
registerOrUnregisterPage();

function isPageMediaControllable() {
    var tags = document.getElementsByName("media-controllable");
    if (tags.length > 0) {
        for (var i = 0; i < tags.length; i++) {
            if (tags[i].getAttribute("content") !== 'no') {
                return true;
            }
        }
    }
    return false;
}

function registerOrUnregisterPage() {
    chrome.runtime.sendMessage({command: isPageMediaControllable() ? "registerTab" : "unregisterTab"});
}

Support for Web Page Media Control API v0.4


var QuizzCollection = Backbone.Collection.extend({
  model: QuizzModel,
  url: './data/quizz.json'
});

var QuizzCollection = Backbone.Collection.extend({
  model: QuizzModel,
  url: 'http://dcamilleri.com/wsf/api'
});

Change JSON url to API url


function Smallstache(source) {
    this.template = source.match(/^[#|\.].*/g) ?
        document.querySelector(source) :  // source is selector
        source;                           // source is template
}

Smallstache.prototype.render = function(obj) {
    return this.template.replace(/{{\s*([\S|^}]+)\s*}}/g,
        function(substring, matched) { return obj[matched]; });
};

function Smallstache(source) {
    this.template = source.match(/^[#|\.].*/g) ?
        window.document.querySelector(source) :  // source is selector
        source;                           // source is template
}

Smallstache.prototype.render = function(obj) {
    return this.template.replace(/{{\s*([\S|^}]+)\s*}}/g,
        function(substring, matched) { return obj[matched] });
};

module.exports = Smallstache;

Fix error with module export


'use strict';

var Component = require('../../ui/Component');
var $$ = Component.$$;

function CrossLinkComponent() {
  Component.apply(this, arguments);
}

CrossLinkComponent.Prototype = function() {
  this.render = function() {
    var doc = this.context.doc;
    var nodeId = this.props.nodeId;
    var el;
    if (nodeId && doc.get(nodeId)) {
      el = $$('a').addClass('sc-cross-link')
        .attr({
          href: '#contextId=toc,nodeId='+nodeId,
          "data-type": 'cross-link',
          "data-node-id": nodeId
        });
    } else {
      el = $$('span');
    }
    if (this.props.children) {
      el.append(this.props.children);
    } else {
      el.append(nodeId);
    }
    return el;
  };
};

Component.extend(CrossLinkComponent);

module.exports = CrossLinkComponent;

'use strict';

var Component = require('../../ui/Component');
var $$ = Component.$$;

function CrossLinkComponent() {
  Component.apply(this, arguments);
}

CrossLinkComponent.Prototype = function() {
  this.render = function() {
    var doc = this.context.doc;
    var nodeId = this.props.nodeId;
    var el;
    if (nodeId && doc.get(nodeId)) {
      el = $$('a').addClass('sc-cross-link')
        .attr({
          href: '#contextId=toc,nodeId='+nodeId,
          "data-type": 'cross-link',
          "data-node-id": nodeId
        });
    } else {
      el = $$('span');
    }
    if (this.props.children.length > 0) {
      el.append(this.props.children);
    } else {
      el.append(nodeId);
    }
    return el;
  };
};

Component.extend(CrossLinkComponent);

module.exports = CrossLinkComponent;

Fix display of return type.


import { config } from 'config'
import invariant from 'invariant'
import isString from 'lodash/isString'

function isDataURL (s) {
  // Regex from https://gist.github.com/bgrins/6194623#gistcomment-1671744
  // eslint-disable-next-line max-len
  const regex = /^\s*data:([a-z]+\/[a-z0-9\-\+]+(;[a-z\-]+=[a-z0-9\-]+)?)?(;base64)?,[a-z0-9!\$&',\(\)\*\+,;=\-\._~:@\/\?%\s]*\s*$/i
  return !!s.match(regex)
}

// Function to add prefix to links.
const prefixLink = (_link) => {
  if (
      (typeof __PREFIX_LINKS__ !== 'undefined' && __PREFIX_LINKS__ !== null)
      && __PREFIX_LINKS__ && (config.linkPrefix !== null
  )) {
    const invariantMessage = `
    You're trying to build your site with links prefixed
    but you haven't set 'linkPrefix' in your config.toml.
    `
    invariant(isString(config.linkPrefix), invariantMessage)

    return isDataURL(_link) ? _link : config.linkPrefix + _link
  } else {
    return _link
  }
}

module.exports = {
  prefixLink,
}

import { config } from 'config'
import invariant from 'invariant'
import isString from 'lodash/isString'
import path from 'path'

function isDataURL (s) {
  // Regex from https://gist.github.com/bgrins/6194623#gistcomment-1671744
  // eslint-disable-next-line max-len
  const regex = /^\s*data:([a-z]+\/[a-z0-9\-\+]+(;[a-z\-]+=[a-z0-9\-]+)?)?(;base64)?,[a-z0-9!\$&',\(\)\*\+,;=\-\._~:@\/\?%\s]*\s*$/i
  return !!s.match(regex)
}

// Function to add prefix to links.
const prefixLink = (_link) => {
  if (
      (typeof __PREFIX_LINKS__ !== 'undefined' && __PREFIX_LINKS__ !== null)
      && __PREFIX_LINKS__ && (config.linkPrefix !== null
  )) {
    const invariantMessage = `
    You're trying to build your site with links prefixed
    but you haven't set 'linkPrefix' in your config.toml.
    `
    invariant(isString(config.linkPrefix), invariantMessage)

    return isDataURL(_link) ? _link : path.join(config.linkPrefix, _link)
  } else {
    return _link
  }
}

module.exports = {
  prefixLink,
}

Use path.join for link prefixing


"use strict";

angular.module('arethusa.core').service('dependencyLoader', [
  '$ocLazyLoad',
  '$q',
  'basePath',
  function($ocLazyLoad, $q, basePath) {
    function expand(p) {
      return basePath.path + '/' + p;
    }
    function expandPath(path) {
      var res = [];
      if (angular.isArray(path)) {
        return aU.map(path, expand);
      } else {
        if (angular.isString(path)) {
          return expand(path);
        } else {
          var files = aU.map(path.files, expand);
          path.files = files;
          return path;
        }
      }
    }
    this.load = function(args) {
      return $ocLazyLoad.load(expandPath(args));
    };

    this.loadInOrder = function(args) {
      var start = $q.defer();
      var promises = [start.promise];
      angular.forEach(args, function(el, i) {
        var deferred = $q.defer();
        promises.push(deferred.promise);
        promises[i].then(function() {
          $ocLazyLoad.load(expandPath(el))['finally'](aU.resolveFn(deferred));
        });
      });
      start.resolve();
      return aU.last(promises);
    };
  }
]);

"use strict";

angular.module('arethusa.core').service('dependencyLoader', [
  '$ocLazyLoad',
  '$q',
  'basePath',
  function($ocLazyLoad, $q, basePath) {
    function expand(p) {
      if (aU.isUrl(p)) {
        return p;
      } else {
        return basePath.path + '/' + p;
      }
    }
    function expandPath(path) {
      var res = [];
      if (angular.isArray(path)) {
        return aU.map(path, expand);
      } else {
        if (angular.isString(path)) {
          return expand(path);
        } else {
          var files = aU.map(path.files, expand);
          path.files = files;
          return path;
        }
      }
    }
    this.load = function(args) {
      return $ocLazyLoad.load(expandPath(args));
    };

    this.loadInOrder = function(args) {
      var start = $q.defer();
      var promises = [start.promise];
      angular.forEach(args, function(el, i) {
        var deferred = $q.defer();
        promises.push(deferred.promise);
        promises[i].then(function() {
          $ocLazyLoad.load(expandPath(el))['finally'](aU.resolveFn(deferred));
        });
      });
      start.resolve();
      return aU.last(promises);
    };
  }
]);

Fix path handling in dependencyLoader


import m from 'mithril';
import classNames from '../classnames.js';

class PlayerReactionComponent {

  oninit({ attrs: { session, player } }) {
    session.on('send-reaction', ({ reactingPlayer, reaction }) => {
      if (reactingPlayer.color === player.color) {
        // Immediately update the reaction if another reaction was sent before
        // the current reaction disappears
        clearTimeout(player.reaction ? player.reaction.timer : null);
        player.reaction = reaction;
        // The reaction should disappear after the specified duration
        player.reaction.timer = setTimeout(() => {
          player.reaction.timer = null;
          m.redraw();
        }, PlayerReactionComponent.reactionDuration);
        m.redraw();
      }
    });
  }

  view({ attrs: { player } }) {
    return m('div.player-reaction', m('div.player-reaction-symbol', {
      class: classNames({ 'show': player.reaction && player.reaction.timer })
    }, player.reaction ? player.reaction.symbol : null));
  }

}

// The duration a reaction is shown before disappearing
PlayerReactionComponent.reactionDuration = 1500;

export default PlayerReactionComponent;

import m from 'mithril';
import classNames from '../classnames.js';

class PlayerReactionComponent {

  oninit({ attrs: { session, player } }) {
    this.player = player;
    session.on('send-reaction', ({ reactingPlayer, reaction }) => {
      if (reactingPlayer.color === this.player.color) {
        // Immediately update the reaction if another reaction was sent before
        // the current reaction disappears
        clearTimeout(this.player.reaction ? this.player.reaction.timer : null);
        this.player.reaction = reaction;
        // The reaction should disappear after the specified duration
        this.player.reaction.timer = setTimeout(() => {
          this.player.reaction.timer = null;
          m.redraw();
        }, PlayerReactionComponent.reactionDuration);
        m.redraw();
      }
    });
  }

  // The oninit() call (and therefore the send-reaction listener) only runs once
  // for the lifetime of this component instance, however the player object may
  // change during that time, so we need to ensure the above listener has an
  // dynamic reference to the freshest instance of the player
  onupdate({ attrs: { player } }) {
    this.player = player;
  }

  view({ attrs: { player } }) {
    return m('div.player-reaction', m('div.player-reaction-symbol', {
      class: classNames({ 'show': player.reaction && player.reaction.timer })
    }, player.reaction ? player.reaction.symbol : null));
  }

}

// The duration a reaction is shown before disappearing
PlayerReactionComponent.reactionDuration = 1500;

export default PlayerReactionComponent;

Fix bug where reactions would break after starting new game


/*
Copyright 2016 Mozilla

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

import { PropTypes } from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import * as Model from './index';

export const Page = PropTypes.instanceOf(Model.Page);
export const Pages = ImmutablePropTypes.listOf(Page.isRequired);

export const Profile = PropTypes.instanceOf(Model.Profile);
export const Profiles = ImmutablePropTypes.listOf(Profile.isRequired);

Add shared immutable prop types for Profile models


/*global describe, beforeEach, it*/
'use strict';

var path    = require('path');
var helpers = require('yeoman-generator').test;

describe('ink generator', function () {
  beforeEach(function (done) {
    helpers.testDirectory(path.join(__dirname, 'temp'), function (err) {
      if (err) {
        return done(err);
      }

      this.app = helpers.createGenerator('ink:app', [
        '../../app'
      ]);
      done();
    }.bind(this));
  });

  it('creates expected files', function (done) {
    var expected = [
      // add files you expect to exist here.
      '.jshintrc',
      '.editorconfig'
    ];

    helpers.mockPrompt(this.app, {
      'someOption': true
    });
    this.app.options['skip-install'] = true;
    this.app.run({}, function () {
      helpers.assertFiles(expected);
      done();
    });
  });
});

/*global describe, beforeEach, it*/
'use strict';

var path    = require('path');
var helpers = require('yeoman-generator').test;

describe('ink generator', function () {
  beforeEach(function (done) {
    helpers.testDirectory(path.join(__dirname, 'temp'), function (err) {
      if (err) {
        return done(err);
      }

      this.app = helpers.createGenerator('ink:app', [
        '../../app'
      ]);
      done();
    }.bind(this));
  });

  it('creates expected files', function (done) {
    var expected = [
      // add files you expect to exist here.
      'package.json',
      'bower.json',
      'app',
      'app/index.html',
      '.jshintrc',
      '.editorconfig'
    ];

    helpers.mockPrompt(this.app, {
      'someOption': true
    });
    this.app.options['skip-install'] = true;
    this.app.run({}, function () {
      helpers.assertFiles(expected);
      done();
    });
  });
});

Update test to check all files created


"use strict";

Object.defineProperty(exports, "__esModule", { value: true });

const Lint = require("tslint");

class Rule extends Lint.Rules.AbstractRule {
	apply(sourceFile) {
		if (sourceFile.fileName.indexOf("src/debug/") === -1) {
			return this.applyWithWalker(new NoNonSharedCode(sourceFile, this.getOptions()));
		}
	}
}
Rule.DEBUG_FAILURE_STRING = "Do not import debugger code because it is expected to run in another process.";
Rule.EXTENSION_FAILURE_STRING = "Do not import extension code because the extension packing will mean duplicate definitions and state.";

class NoNonSharedCode extends Lint.RuleWalker {
	visitImportDeclaration(node) {
		// TODO: Remove first part of this condition when DAs are not running in process.
		// https://github.com/Dart-Code/Dart-Code/issues/1876
		if (this.sourceFile.fileName.indexOf("debug_config_provider") === -1
			&& node.moduleSpecifier.text.indexOf("../debug/") !== -1) {
			this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.DEBUG_FAILURE_STRING));
		}

		if (node.moduleSpecifier.text.indexOf("../extension/") !== -1) {
			this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.EXTENSION_FAILURE_STRING));
		}
	}
}

exports.Rule = Rule;

"use strict";

Object.defineProperty(exports, "__esModule", { value: true });

const Lint = require("tslint");

class Rule extends Lint.Rules.AbstractRule {
	apply(sourceFile) {
		return this.applyWithWalker(new NoNonSharedCode(sourceFile, this.getOptions()));
	}
}
Rule.DEBUG_FAILURE_STRING = "Do not import debugger code because it is expected to run in another process.";
Rule.EXTENSION_FAILURE_STRING = "Do not import extension code because the extension packing will mean duplicate definitions and state.";

class NoNonSharedCode extends Lint.RuleWalker {
	visitImportDeclaration(node) {
		// TODO: Remove first part of this condition when DAs are not running in process.
		// https://github.com/Dart-Code/Dart-Code/issues/1876
		if (this.sourceFile.fileName.indexOf("debug_config_provider") === -1
			&& node.moduleSpecifier.text.indexOf("../debug/") !== -1) {
			this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.DEBUG_FAILURE_STRING));
		}

		if (node.moduleSpecifier.text.indexOf("../extension/") !== -1) {
			this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.EXTENSION_FAILURE_STRING));
		}
	}
}

exports.Rule = Rule;

Enable more linting for shared imports


function setupDisplayFieldsEducationTab(TempObject, selectBox){
    // This code is needed to merge the multiselect field and the "other" input field per
    if (typeof TempObject[selectBox] != 'undefined') {
        if (TempObject[selectBox] != "") {
            var optionsToSelect = TempObject[selectBox];
            var select = document.getElementById( [selectBox] );
            for ( var i = 0, l = select.options.length, o; i < l; i++ ){
                o = select.options[i];
                var tempO = o;
                if ( optionsToSelect.toLowerCase().indexOf( tempO.text.toLowerCase() ) != -1 ) {
                    o.selected = true;
                    optionsToSelect = optionsToSelect.replace(o.text,"");
                    optionsToSelect = optionsToSelect.replace(/,,/g,",");
                    optionsToSelect = optionsToSelect.replace(/^,/g,"");
                }
                else document.getElementById( [selectBox]+'Other' ).value = optionsToSelect;
            }
        }
    }
}

// Take the comma separated list for {field} from {item}.{field} and populate the select#{field} box
// Any of the values in the comma separated list for {field} that doesn't have an option in select#{field}
// Get stuffed into {field}Other as it's own comma separated list.
function setupDisplayFieldsEducationTab(item, field) {
    if (item[field] != '' && $('select#'+field)[0] != undefined) {
        var itemOptions = item[field].split(',');
        $('select#'+field+' option').each(function() {
            if ($.inArray($(this).val(),itemOptions) !== -1) {
                $(this).attr('selected',true);
            }
        });
        var selectOptions = $('select#'+field+' option').map(function() { return this.value });
        var otherOptions = [];
        for (i in itemOptions) {
            if ($.inArray(itemOptions[i], selectOptions) === -1) {
                otherOptions.push(itemOptions[i]);
            }
        }
        if (otherOptions.length > 0) {
            $('#'+field+'Other').attr('value',otherOptions.join(','));
        }
    }
}

Refactor of comma separated list to selectbox & other inputs.


import React from 'react'

import EventBets from '../templates/event-bets'
import EventHeading from '../templates/event-heading'

import styles from './index.module.styl'

export default ({ data }) => (

    {
      data.allMongodbPlacardDevEvents.edges.map(({ node }, idx) => (

          <EventHeading event={node} />
          <EventBets betTypes={node.betTypes} />

      ))
    }

)

export const query = graphql`
  query EventsListQuery($sport: String, $country: String, $skip: Int = 0, $limit: Int = 20) {
    allMongodbPlacardDevEvents(
      filter: { sport: { eq: $sport }, country: { eq: $country } },
      sort: { fields: [date], order: ASC },
      skip: $skip,
      limit: $limit
    ) {
      edges {
        node {
          code
          home
          away
          date
          sport
          country
          competition
          betTypes {
            name
            options {
              name
              value
            }
          }
        }
      }
    }
  }
`

import React from 'react'

import EventBets from '../templates/event-bets'
import EventHeading from '../templates/event-heading'

import styles from './index.module.styl'

export default ({ data }) => (

    {
      data.allMongodbPlacardDevEvents.edges.map(({ node }, idx) => (

          <EventHeading event={node} />
          <EventBets betTypes={node.betTypes} />

      ))
    }

)

export const query = graphql`
  query EventsListQuery($sport: String, $country: String, $skip: Int = 0, $limit: Int = 10) {
    allMongodbPlacardDevEvents(
      filter: { sport: { eq: $sport }, country: { eq: $country } },
      sort: { fields: [date], order: ASC },
      skip: $skip,
      limit: $limit
    ) {
      edges {
        node {
          code
          home
          away
          date
          sport
          country
          competition
          betTypes {
            name
            options {
              name
              value
            }
          }
        }
      }
    }
  }
`

Set default page size to 10 events.


'use babel'

export const compiler = false
export const minifier = false
export function process(contents) {
  const chunks = contents.split(/\r?\n/)
  if (chunks[chunks.length - 1] !== '') {
    chunks.push('')
  }
  return chunks.join('\n')
}

'use babel'

import Path from 'path'
import {rollup} from 'rollup'
import {requireDependency} from './helpers'

export const preprocessor = true
export const compiler = false
export const minifier = false

export function process(contents, {rootDirectory, filePath, config, state}) {
  const plugins = []

  plugins.push(requireDependency('memory', rootDirectory)({
    contents: contents,
    path: filePath
  }))

  const rollupConfig = config.rollup || {}
  if (typeof config.rollup === 'object' && config.rollup) {
    if (rollupConfig.npm === true) {
      plugins.push(requireDependency('npm', rootDirectory)({
        jsnext: true,
        main: true,
        builtins: false,
        extensions: [ '.js', '.json' ]
      }))
    }
    if (rollupConfig.commonjs === true) {
      plugins.push(requireDependency('commonjs', rootDirectory)())
    }

    if (typeof rollupConfig.plugins === 'object' && rollupConfig.plugins) {
      for (const name in rollupConfig.plugins) {
        const value = rollupConfig[name]
        if (typeof value === 'object' && value) {
          plugins.push(requireDependency(name, rootDirectory)(value))
        }
      }
    }
  }

  return rollup({
    entry: '__file__',
    plugins: plugins
  }).then(function(results) {
    const generated = results.generate({
      format: rollupConfig.format || 'cjs',
      sourceMap: true
    })
    generated.map.sources = generated.map.sources.map(function(mapPath) {
      return Path.relative(rootDirectory, mapPath)
    })
    results.modules.forEach(function(module) {
      const modulePath = module.id
      if (modulePath !== filePath) {
        state.imports.push(modulePath)
      }
    })
    return {
      contents: generated.code,
      sourceMap: generated.map
    }
  })
}

Implement proper plugins, resolution and maps support


var lib = require("__buttercup/module.js"),
  Westley = lib.Westley;

module.exports = {

  setUp: function(cb) {
    this.westley = new Westley();
    (cb)();
  },

  _getCommandForName: {

    cachesCommandWhenCalledTwice: function(test) {
      var testCommandKey = 'tgr';

      var firstCall = this.westley._getCommandForKey(testCommandKey),
        secondCall = this.westley._getCommandForKey(testCommandKey);

      firstCall.randomvariable = 'bla';

      test.strictEqual(firstCall.randomvariable, secondCall.randomvariable, "Randomly assigned variable is the same for both instances");
      test.done();
    },

    cachesCommandWhenCalledThrice: function(test) {
      var testCommandKey = 'tgr';

      var firstCall = this.westley._getCommandForKey(testCommandKey),
        secondCall = this.westley._getCommandForKey(testCommandKey),
        thirdCall = this.westley._getCommandForKey(testCommandKey);

      firstCall.randomvariable = 'bla';

      test.strictEqual(firstCall.randomvariable, secondCall.randomvariable, "Randomly assigned variable is the same for first and second instance");
      test.strictEqual(firstCall.randomvariable, thirdCall.randomvariable, "Randomly assigned variable is the same for first and third instance");
      test.strictEqual(secondCall.randomvariable, thirdCall.randomvariable, "Randomly assigned variable is the same for second and third instance");
      test.done();
    }

  }

};

Correct indentation in westley tests


/**
 * isAuthenticated
 * @description :: Policy that inject user in `req` via JSON Web Token
 */

var passport = require('passport');

module.exports = function (req, res, next) {
  passport.authenticate('jwt', function (error, user, info) {
    if (error) return res.serverError(error);
    if (!user) return res.unauthorized(null, info && info.code, info && info.message);

    req.user = user;

    next();
  })(req, res);
};

/**
 * isAuthenticated
 * @description :: Policy that inject user in `req` via JSON Web Token
 */

var _ = require('lodash');
var passport = require('passport');

module.exports = function (req, res, next) {
  passport.authenticate('jwt', function (error, user, info) {
    if (error || !user) return res.negotiate(_.assign(error || {}, info));

    req.user = user;

    next();
  })(req, res);
};

Fix policy for new responses


'use strict'

var shimmer = require('shimmer')
var modules = require('./instrumentation/modules')

module.exports = function (client) {
  if (!client._ff_instrument) return

  // TODO: This will actual just use the logger of the last client parsed in.
  // In most use-cases this is a non-issue, but if someone tries to initiate
  // multiple clients with different loggers, this will get weird
  shimmer({ logger: client.logger.error })

  client.logger.trace('shimming function Module._load')
  var Module = require('module')
  shimmer.wrap(Module, '_load', function (orig) {
    return function (file) {
      return modules.patch(file, orig.apply(this, arguments), client)
    }
  })
}

'use strict'

var shimmer = require('shimmer')
var modules = require('./instrumentation/modules')

module.exports = function (client) {
  // TODO: This will actual just use the logger of the last client parsed in.
  // In most use-cases this is a non-issue, but if someone tries to initiate
  // multiple clients with different loggers, this will get weird
  shimmer({ logger: client.logger.error })

  if (client._ff_instrument) fullInstrumentation(client)
  else errorTracingOnly(client)
}

function fullInstrumentation (client) {
  client.logger.trace('shimming function Module._load')
  var Module = require('module')
  shimmer.wrap(Module, '_load', function (orig) {
    return function (file) {
      return modules.patch(file, orig.apply(this, arguments), client)
    }
  })
}

function errorTracingOnly (client) {
  client.logger.trace('shimming http.Server.prototype functions "on" and "addListener"')

  var http = require('http')
  var asyncState = require('./async-state')

  shimmer.massWrap(http.Server.prototype, ['on', 'addListener'], function (fn, name) {
    return function (event, listener) {
      if (event === 'request' && typeof listener === 'function') return fn.call(this, event, onRequest)
      else return fn.apply(this, arguments)

      function onRequest (req, res) {
        client.logger.trace('intercepted call to http.Server.prototype.%s', name)
        asyncState.req = req
        listener.apply(this, arguments)
      }
    }
  })
}

Allow simple shimming of http.Server if full instrumentation is disabled


import {createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import createLogger from 'redux-logger';
import reducer from './reducer';

const loggerMiddleware = createLogger();

const createStoreWithMiddleware = applyMiddleware(
  thunkMiddleware,
  loggerMiddleware
)(createStore);

export default function makeStore(initialState) {
    return createStoreWithMiddleware(reducer, initialState);
};

import {createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import createLogger from 'redux-logger';
import reducer from './reducer';

const loggerMiddleware = createLogger();

const createStoreWithMiddleware = applyMiddleware(
  thunkMiddleware,
  loggerMiddleware //remove in production
)(createStore);

export default function makeStore(initialState) {
    return createStoreWithMiddleware(reducer, initialState);
};

Add comment to remove loggerMiddleware in production.


import Server from './server';
import Client from './client';

const debug = require('debug')('tinylr');

// Need to keep track of LR servers when notifying
const servers = [];

export default tinylr;

// Expose Server / Client objects
tinylr.Server = Server;
tinylr.Client = Client;

// and the middleware helpers
tinylr.middleware = middleware;
tinylr.changed = changed;

// Main entry point
function tinylr (opts) {
  const srv = new Server(opts);
  servers.push(srv);
  return srv;
}

// A facade to Server#handle
function middleware (opts) {
  const srv = new Server(opts);
  servers.push(srv);
  return function tinylr (req, res, next) {
    srv.handler(req, res, next);
  };
}

// Changed helper, helps with notifying the server of a file change
function changed (done) {
  const files = [].slice.call(arguments);
  if (files[files.length - 1] === 'function') done = files.pop();
  done = typeof done === 'function' ? done : () => {};
  debug('Notifying %d servers - Files: ', servers.length, files);
  servers.forEach(srv => {
    const params = { params: { files: files } };
    srv && srv.changed(params);
  });
  done();
}

import Server from './server';
import Client from './client';

const debug = require('debug')('tinylr');

// Need to keep track of LR servers when notifying
const servers = [];

export default tinylr;

// Expose Server / Client objects
tinylr.Server = Server;
tinylr.Client = Client;

// and the middleware helpers
tinylr.middleware = middleware;
tinylr.changed = changed;

// Main entry point
function tinylr (opts) {
  const srv = new Server(opts);
  servers.push(srv);
  return srv;
}

// A facade to Server#handle
function middleware (opts) {
  const srv = new Server(opts);
  servers.push(srv);
  return function tinylr (req, res, next) {
    srv.handler(req, res, next);
  };
}

// Changed helper, helps with notifying the server of a file change
function changed (done) {
  const files = [].slice.call(arguments);
  if (typeof files[files.length - 1] === 'function') done = files.pop();
  done = typeof done === 'function' ? done : () => {};
  debug('Notifying %d servers - Files: ', servers.length, files);
  servers.forEach(srv => {
    const params = { params: { files: files } };
    srv && srv.changed(params);
  });
  done();
}

Fix changed done callback, missing typeof.


 define([
     'base/class',
     'jquery',
     'underscore',
     'base/utils'
 ], function(Class, $, _, Util) {

     var LocalJSONReader = Class.extend({

         init: function(basepath) {
             this.data = [];
             this.basepath = basepath;
         },

         read: function(queries, language) {
             var _this = this,
                 defer = $.Deferred();

             var path = this.basepath.replace("{{LANGUAGE}}", language);

             _this.data = [];

             for (var i=0; i < queries.length; i++) {
                var fakeResponsePath = path.replace("response", "response_" + i);
                var promise = $.getJSON(fakeResponsePath, function(res) {
                    _this.data.push(res);
                })
                .error(function() {
                    console.log("Error Happened While Lading File: " + fakeResponsePath);
                });
             }

             promise.done(function() {
                 console.log(_this.data);
                 defer.resolve();
             });

             return defer;
         },

         getData: function() {
             return this.data;
         }

     });

    return LocalJSONReader;

 });

 define([
     'base/class',
     'jquery',
     'underscore',
     'base/utils'
 ], function(Class, $, _, Util) {

     var LocalJSONReader = Class.extend({

         init: function(basepath) {
             this.data = [];
             this.basepath = basepath;
         },

         read: function(queries, language) {
             var _this = this,
                 defer = $.Deferred(),
                 promises = [];

             var path = this.basepath.replace("{{LANGUAGE}}", language);

             _this.data = [];

             for (var i=0; i < queries.length; i++) {
                var fakeResponsePath = path.replace("response", "response_" + i);
                var promise = $.getJSON(fakeResponsePath, function(res) {
                    _this.data.push(res);
                })
                .error(function() {
                    console.log("Error Happened While Lading File: " + fakeResponsePath);
                });

                promises.push(promise);
             }

             $.when.apply(null, promises).done(function() {
                 defer.resolve();
             });

             return defer;
         },

         getData: function() {
             return this.data;
         }

     });

    return LocalJSONReader;

 });

Fix Load multiple JSON bug using array of promises


/* jshint -W117, -W030 */
describe('dataservice', function() {
  beforeEach(function() {
    bard.appModule('app.core');
    bard.inject('$http','$httpBackend','$q','dataservice','$rootScope');
  });

  it('exists' , function(){
    expect(dataservice).to.exist;
  });

  it('getMessageCount returns a value' , function() {
    dataservice.getMessageCount().then(function(data) {
      expect(data).to.exist;
    });
    $rootScope.$apply();
  });

  it('getPeople hits the right /api/people' , function() {
    $httpBackend.when('GET', '/api/people').respond(200,[{}]);
    dataservice.getPeople().then(function(data) {
      expect(data).to.exist;
    });
    $httpBackend.flush();
  });

  it('getPeople reports error if server fails' , function() {
    $httpBackend
      .when('GET', '/api/people')
      .respond(500,{data: {description: 'you fail'}});
    dataservice.getPeople().catch(function(error) {
      expect(true).to.be.false;
    });
    $httpBackend.flush();
  });

});

/* jshint -W117, -W030 */
describe('dataservice', function() {
  beforeEach(function() {
    bard.appModule('app.core');
    bard.inject('$http','$httpBackend','$q','dataservice','$rootScope');
  });

  it('exists' , function(){
    expect(dataservice).to.exist;
  });

  it('getMessageCount returns a value' , function() {
    dataservice.getMessageCount().then(function(data) {
      expect(data).to.exist;
    });
    $rootScope.$apply();
  });

  it('getPeople hits the right /api/people' , function() {
    $httpBackend.when('GET', '/api/people').respond(200,[{}]);
    dataservice.getPeople().then(function(data) {
      expect(data).to.exist;
    });
    $httpBackend.flush();
  });

  it('getPeople reports error if server fails' , function() {
    $httpBackend
      .when('GET', '/api/people')
      .respond(500,{description: 'you fail'});
    dataservice.getPeople().catch(function(error) {
      expect(error.data.description).to.match(/you fail/);
    });
    $httpBackend.flush();
  });

});

Fix test so that is uses regex to check the text returned.


import React, {Component} from 'react';

import Icon from './Icon';

class CollapsibleContainer extends Component {
  constructor() {
    super(...arguments);

    this.state = {
      toggle: false
    };
    this.toggleContainer = this.toggleContainer.bind(this);
  }

  toggleContainer() {
    this.setState({toggle: !this.state.toggle});
  }

  getChildren() {
    if (this.state.toggle) {
      return this.props.children;
    }
    return null;
  }

  render() {
    let icon = 'triangle-right';
    if (this.state.toggle) {
      icon = 'triangle-down';
    }

    return (

          <Icon
            id={icon}
            color="purple"
            family="mini"
            size="mini" />
          {this.props.label}

        {this.getChildren()}

    );
  }

}

CollapsibleContainer.defaultProps = {
  label: ''
};

CollapsibleContainer.propTypes = {
  label: React.PropTypes.string
};

module.exports = CollapsibleContainer;

import React, {Component} from 'react';

import Icon from './Icon';

class CollapsibleContainer extends Component {
  constructor() {
    super(...arguments);

    this.state = {
      toggle: false
    };
    this.toggleContainer = this.toggleContainer.bind(this);
  }

  toggleContainer() {
    this.setState({toggle: !this.state.toggle});
  }

  getChildren() {
    if (this.state.toggle) {
      return this.props.children;
    }
    return null;
  }

  render() {
    let icon = 'triangle-right';
    if (this.state.toggle) {
      icon = 'triangle-down';
    }

    return (

          <Icon
            id={icon}
            color="purple"
            family="mini"
            size="mini" />
          {this.props.label}

        {this.getChildren()}

    );
  }

}

CollapsibleContainer.defaultProps = {
  label: ''
};

CollapsibleContainer.propTypes = {
  label: React.PropTypes.string.isRequired
};

module.exports = CollapsibleContainer;

Add required flag to label prop


// @flow

import { minify as processUglify } from 'uglify-es'
import { minify as processTerser } from 'terser'
import { createChunkTransformer } from 'pundle-api'

import manifest from '../package.json'

// TODO: Fix source maps
const VALID_UGLIFIERS = new Set(['uglify', 'terser'])
function createComponent({ options = {}, uglifier = 'uglify' }: { options?: Object, uglifier: 'terser' | 'uglify' } = {}) {
  if (!VALID_UGLIFIERS.has(uglifier)) {
    throw new Error(`options.uglifier must be either 'uglify' or 'terser'. Got: ${uglifier}`)
  }

  return createChunkTransformer({
    name: 'pundle-chunk-transformer-js-uglify',
    version: manifest.version,
    async callback({ format, contents }) {
      if (format !== 'js') return null

      const uglify = uglifier === 'terser' ? processTerser : processUglify
      const { code, error } = uglify(typeof contents === 'string' ? contents : contents.toString(), {
        ...options,
      })
      if (error) {
        throw new Error(error)
      }

      return {
        contents: code,
        sourceMap: null,
      }
    },
  })
}

module.exports = createComponent

// @flow

import { minify as processUglify } from 'uglify-es'
import { minify as processTerser } from 'terser'
import { createChunkTransformer } from 'pundle-api'

import manifest from '../package.json'

// TODO: Fix source maps
const VALID_UGLIFIERS = new Set(['uglify', 'terser'])
function createComponent({ options = {}, uglifier = 'uglify' }: { options?: Object, uglifier: 'terser' | 'uglify' } = {}) {
  if (!VALID_UGLIFIERS.has(uglifier)) {
    throw new Error(`options.uglifier must be either 'uglify' or 'terser'. Got: ${uglifier}`)
  }

  return createChunkTransformer({
    name: 'pundle-chunk-transformer-js-uglify',
    version: manifest.version,
    async callback({ format, contents }) {
      if (format !== 'js') return null

      const uglify = uglifier === 'terser' ? processTerser : processUglify
      const { code, error } = uglify(typeof contents === 'string' ? contents : contents.toString(), {
        ...options,
          compress: {
            defaults: false,
            ...options.compress,
          },
      })
      if (error) {
        throw new Error(error)
      }

      return {
        contents: code,
        sourceMap: null,
      }
    },
  })
}

module.exports = createComponent

Tweak uglifyjs config to be faster


// Trigger resize event to relayout the React Layout plugin

export default function resizeLayout() {
  return next => action => {
    next(action)
    if (
      action.type === 'ENABLE_PRESENTATION_MODE' ||
      action.type === 'DISABLE_PRESENTATION_MODE'
    ) {
      // Uses longer event object creation method due to IE compatibility.
      const evt = document.createEvent('HTMLEvents')
      evt.initEvent('resize', false, true)
      window.dispatchEvent(evt)
    }
  }
}

// Trigger resize event to relayout the React Layout plugin
import {enablePresentationMode} from '../actions/app'

export default function resizeLayout() {
  return next => action => {
    next(action)
    if (
      action.type === 'ENABLE_PRESENTATION_MODE' ||
      action.type === 'DISABLE_PRESENTATION_MODE'
    ) {
      // Uses longer event object creation method due to IE compatibility.
      const evt = document.createEvent('HTMLEvents')
      evt.initEvent('resize', false, true)
      window.dispatchEvent(evt)
    }
    if (window.location.search.includes('present')) {
      next(enablePresentationMode())
      next(action)
    }
  }
}

Add 'present' url query/search param to presentation mode middleware.


import React, { Component } from "react"
import TopBarProgressIndicator from "react-topbar-progress-indicator"

import styles from "./index.css"

TopBarProgressIndicator.config({
  barColors: {
    "0": "#0085a1",
    "1.0": "#0085a1",
  },
  shadowBlur: 5,
})

export default class PageLoading extends Component {

  render() {
    return (

        <TopBarProgressIndicator />

    )
  }
}

import React, { Component } from "react"
import TopBarProgressIndicator from "react-topbar-progress-indicator"

import styles from "./index.css"

TopBarProgressIndicator.config({
  barColors: {
    "0": "#fff",
    "1.0": "#fff",
  },
  shadowBlur: 5,
})

export default class PageLoading extends Component {

  render() {
    return (

        <TopBarProgressIndicator />

    )
  }
}

Update color to match with header


var gulp = require('gulp');
var sass = require('gulp-sass');
var sasslint = require('gulp-sass-lint');
var sourcemaps = require('gulp-sourcemaps');
var rename = require('gulp-rename');
var header = require('gulp-header');

var pkg = require('./package.json');
var banner = '/*! <%= pkg.name %>: v.<%= pkg.version %>, last updated on: <%= new Date() %> */\n';

///////////////////////////////////
// Build Tasks
///////////////////////////////////

///////////////////////////////////
// Development Tasks
///////////////////////////////////

gulp.task('styles:dev', function() {
  return gulp.src('./scss/**/*.s+(a|c)ss')
    .pipe(sasslint({configFile: '.ubs-sass-lint.yml'}))
    .pipe(sasslint.format())
    .pipe(sasslint.failOnError(false))
    .pipe(sourcemaps.init())
    .pipe(sass({outputStyle: 'expanded'}))
    //.pipe(rename({extname: '.dev.css'}))
    .pipe(header(banner, {pkg : pkg}))
    .pipe(sourcemaps.write('.'))
    .pipe(gulp.dest('./css'))
    .pipe(notify(notifyMsg));
});

gulp.task('default', ['styles:dev']);

var gulp = require('gulp');
var sass = require('gulp-sass');
var sasslint = require('gulp-sass-lint');
var sourcemaps = require('gulp-sourcemaps');
var rename = require('gulp-rename');
var header = require('gulp-header');

var pkg = require('./package.json');
var banner = '/*! <%= pkg.name %>: v.<%= pkg.version %>, last updated on: <%= new Date() %> */\n';

///////////////////////////////////
// Build Tasks
///////////////////////////////////

///////////////////////////////////
// Development Tasks
///////////////////////////////////

gulp.task('styles:dev', function() {
  return gulp.src('./scss/**/*.s+(a|c)ss')
    .pipe(sasslint({configFile: '.ubs-sass-lint.yml'}))
    .pipe(sasslint.format())
    .pipe(sasslint.failOnError(false))
    .pipe(sourcemaps.init())
    .pipe(sass({outputStyle: 'expanded'}))
    //.pipe(rename({extname: '.dev.css'}))
    .pipe(header(banner, {pkg : pkg}))
    .pipe(sourcemaps.write('.'))
    .pipe(gulp.dest('./css'))
    //.pipe(notify(notifyMsg));
});

gulp.task('default', ['styles:dev']);

Comment out missing call to notify plugin


var glimpse = require('glimpse'),
    _requests = [];

(function() {
    function dataFound(payload) {
        // TODO: Really bad hack to get things going atm
        _requests = payload.allRequests;

        glimpse.emit('shell.request.summary.changed', _requests);
    }

    // External data coming in
    glimpse.on('data.request.summary.found', dataFound);
})();

/*
// TODO: Need to see if this is needed
module.exports = {
    getAllFor: function(sessionId) {
        console.log(sessionId);
        return _requests;
    }
};
*/

var glimpse = require('glimpse'),
    _requests = [],
    _filteredRequests = [],
    _filters = {};

function requestsChanged(targetRequests) {
    glimpse.emit('shell.request.summary.changed', targetRequests);
}

// TODO: This needs to be refactored to dynamically lookup records, etc
// TODO: Even at the moment this is going to need to be refactored
var filterRequests = (function() {
        function hasFilters(filters) {
            return filters.userId;
        }

        function checkMatch(request, filters) {
            return (request.user.id === filters.userId);
        }

        function applyFilters(sourceRequests, destinationRequests, filters) {
            var matchFound = false;

            for (var i = sourceRequests.length - 1; i >= 0; i--) {
                var sourceRequest = sourceRequests[i];

                if (checkMatch(sourceRequest, filters)) {
                    destinationRequests.unshift(sourceRequest);
                    matchFound = true;
                }
            }

            return matchFound;
        }

        return function(allRequests, newRequests, filterHasChanged) {
            if (hasFilters(_filters)) {
                var sourceRequests = newRequests;

                if (filterHasChanged) {
                    sourceRequests = allRequests;
                    _filteredRequests = [];
                }

                if (applyFilters(sourceRequests, _filteredRequests, _filters)) {
                    requestsChanged(_filteredRequests);
                }
            }
            else {
                requestsChanged(_requests);
            }
        };
    })();

(function() {
    function userSwitch(payload) {
        _filters.userId = payload.userId;

        filterRequests(_requests, null, true);
    }

    glimpse.on('shell.request.user.selected', userSwitch);
})();

(function() {
    function dataFound(payload) {
        // TODO: Really bad hack to get things going atm
        _requests = payload.allRequests;

        filterRequests(_requests, payload.newRequests, false);
    }

    // External data coming in
    glimpse.on('data.request.summary.found', dataFound);
})();

/*
// TODO: Need to see if this is needed
module.exports = {
    getAllFor: function(sessionId) {
        console.log(sessionId);
        return _requests;
    }
};
*/

Implement a basic version of filtering based on use selection


module.exports = {
  scenarios: [
    {
      name: 'default',
      dependencies: { }
    },
    {
      name: 'ember-release',
      dependencies: {
        'ember': 'components/ember#release',
        'ember-data': 'components/ember-data#1.13.4'
      },
      resolutions: {
        'ember': 'release',
        'ember-data': '1.13.4'
      }
    },
    {
      name: 'ember-beta',
      dependencies: {
        'ember': 'components/ember#beta',
        'ember-data': 'components/ember-data#canary'
      },
      resolutions: {
        'ember': 'beta',
        'ember-data': 'canary'
      }
    },
    {
      name: 'ember-canary',
      dependencies: {
        'ember': 'components/ember#canary',
        'ember-data': 'components/ember-data#canary'
      },
      resolutions: {
        'ember': 'canary',
        'ember-data': 'canary'
      }
    }
  ]
};

module.exports = {
  scenarios: [
    {
      name: 'default',
      dependencies: { }
    },
    {
      name: 'ember-release',
      dependencies: {
        'ember': 'components/ember#release',
        'ember-data': 'components/ember-data#release'
      },
      resolutions: {
        'ember': 'release',
        'ember-data': 'release'
      }
    },
    {
      name: 'ember-beta',
      dependencies: {
        'ember': 'components/ember#beta',
        'ember-data': 'components/ember-data#canary'
      },
      resolutions: {
        'ember': 'beta',
        'ember-data': 'canary'
      }
    },
    {
      name: 'ember-canary',
      dependencies: {
        'ember': 'components/ember#canary',
        'ember-data': 'components/ember-data#canary'
      },
      resolutions: {
        'ember': 'canary',
        'ember-data': 'canary'
      }
    }
  ]
};

Use release channel of ember-data


'use strict';

// MODULES //

var nativeClass = require( '@stdlib/utils/native-class' );
var RE = require( '@stdlib/regexp/function-name' );
var isBuffer = require( '@stdlib/assert/is-buffer' );

// MAIN //

/**
* Determines the name of a value's constructor.
*
* @param {*} v - input value
* @returns {string} name of a value's constructor
*
* @example
* var v = constructorName( 'a' );
* // returns 'String'
* @example
* var v = constructorName( 5 );
* // returns 'Number'
* @example
* var v = constructorName( null );
* // returns 'Null'
* @example
* var v = constructorName( undefined );
* // returns 'Undefined'
* @example
* var v = constructorName( function noop(){} );
* // returns 'Function'
*/
function constructorName( v ) {
	var name;
	var ctor;
	name = nativeClass( v ).slice( 8, -1 );
	if ( (name === 'Object' || name === 'Error') && v.constructor ) {
		ctor = v.constructor;
		if ( typeof ctor.name === 'string' ) {
			return ctor.name;
		}
		return RE.exec( ctor.toString() )[ 1 ];
	}
	if ( isBuffer( v ) ) {
		return 'Buffer';
	}
	return name;
} // end FUNCTION constructorName()

// EXPORTS //

module.exports = constructorName;

'use strict';

// MODULES //

var nativeClass = require( '@stdlib/utils/native-class' );
var RE = require( '@stdlib/regexp/function-name' );
var isBuffer = require( '@stdlib/assert/is-buffer' );

// MAIN //

/**
* Determines the name of a value's constructor.
*
* @param {*} v - input value
* @returns {string} name of a value's constructor
*
* @example
* var v = constructorName( 'a' );
* // returns 'String'
*
* @example
* var v = constructorName( 5 );
* // returns 'Number'
*
* @example
* var v = constructorName( null );
* // returns 'Null'
*
* @example
* var v = constructorName( undefined );
* // returns 'Undefined'
*
* @example
* var v = constructorName( function noop() {} );
* // returns 'Function'
*/
function constructorName( v ) {
	var name;
	var ctor;
	name = nativeClass( v ).slice( 8, -1 );
	if ( (name === 'Object' || name === 'Error') && v.constructor ) {
		ctor = v.constructor;
		if ( typeof ctor.name === 'string' ) {
			return ctor.name;
		}
		return RE.exec( ctor.toString() )[ 1 ];
	}
	if ( isBuffer( v ) ) {
		return 'Buffer';
	}
	return name;
} // end FUNCTION constructorName()

// EXPORTS //

module.exports = constructorName;

Fix lint error and update JSDoc examples


$(document).ready(function() {

 $("a.up-vote-answer").click(function(event) {
      event.preventDefault();
      var $target = $(event.target);
      console.log($target.data("url"))
      $.ajax({
        type: "post",
        url: $target.data("url"),
        data: $target.data(),
        success:
        function(response) {
          $('#' + $target.data('answer-id')).empty();
          $('#' + $target.data('answer-id')).append(response)
        }
      });
    }
  );

});

$(document).ready(function() {

 $("a.up-vote-answer").click(function(event) {
      event.preventDefault();
      var $target = $(event.target);
      $.ajax({
        type: "post",
        url: $target.data("url"),
        data: $target.data(),
        success: function(response) {
          $('#' + $target.data('answer-id')).empty();
          $('#' + $target.data('answer-id')).append(response)
        }
      });
    }
  );

 $("a.up-vote-question").click(function(event) {
      event.preventDefault();
      var $target = $(event.target);
      console.log($target.data("url"))
      $.ajax({
        type: "post",
        url: $target.data("url"),
        data: $target.data(),
        success: function(response) {
          console.log(response);
          $('#' + $target.data('question-id')).empty();
          $('#' + $target.data('question-id')).append(response)
        }
      });
    }
  );

});

Add ajax call for questions


import moment from 'moment';
import getPetitionEndDate from 'selectors/petitionEndDate';

export default ({ created, expires }, timeNow) => {
  const endDate = getPetitionEndDate({ created, expires });
  const nowDate = timeNow || moment().valueOf();
  const daysRemaining = moment.duration(moment(endDate).diff(nowDate)).asDays();
  return Math.max(Math.round(daysRemaining), 0);
};

import moment from 'moment';
import getPetitionEndDate from 'selectors/petitionEndDate';

export default ({ created, expires }) => {
  const endDate = getPetitionEndDate({ created, expires });
  const nowDate = moment().valueOf();
  const daysRemaining = moment.duration(endDate.diff(nowDate)).asDays();
  return Math.max(Math.round(daysRemaining), 0);
};

Remove unused param from daysRemaining function


'use strict';

class CustomerIoEvent {
  constructor(id, name, data) {
    this.id = id;
    this.name = name;
    this.data = data;
  }

  getId() {
    return this.id;
  }

  getName() {
    return this.name;
  }

  getData() {
    return this.data;
  }

  setVersion(version) {
    this.data.version = version;
  }
}

module.exports = CustomerIoEvent;

'use strict';

class CustomerIoEvent {
  constructor(id, name, data) {
    this.id = id;
    this.name = name;
    this.data = data;
  }

  getId() {
    return this.id;
  }

  getName() {
    return this.name;
  }

  getData() {
    return this.data;
  }

  // Schema version of event data.
  setVersion(version) {
    this.data.version = version;
  }
}

module.exports = CustomerIoEvent;

Add comment clarifying that cio event version is data schema version


"use strict";
const isValidTargetOrigin = require("../utils").isValidTargetOrigin;
const DOMException = require("../web-idl/DOMException");

module.exports = function (message, targetOrigin) {
  if (arguments.length < 2) {
    throw new TypeError("'postMessage' requires 2 arguments: 'message' and 'targetOrigin'");
  }

  targetOrigin = String(targetOrigin);

  if (!isValidTargetOrigin(targetOrigin)) {
    throw new DOMException(DOMException.SYNTAX_ERR, "Failed to execute 'postMessage' on 'Window': " +
      "Invalid target origin '" + targetOrigin + "' in a call to 'postMessage'.");
  }

  // TODO: targetOrigin === '/' - requires reference to source window
  // See https://github.com/tmpvar/jsdom/pull/1140#issuecomment-111587499
  if (targetOrigin !== "*" && targetOrigin !== this.origin) {
    return;
  }

  // TODO: event.source - requires reference to source window
  // TODO: event.origin - requires reference to source window
  // TODO: event.ports
  // TODO: event.data - structured clone message - requires cloning DOM nodes
  const event = new this.MessageEvent("message", {
    data: message
  });

  event.initEvent("message", false, false);

  setTimeout(() => {
    this.dispatchEvent(event);
  }, 0);
};

"use strict";
const isValidTargetOrigin = require("../utils").isValidTargetOrigin;
const DOMException = require("../web-idl/DOMException");

module.exports = function (message, targetOrigin) {
  if (arguments.length < 2) {
    throw new TypeError("'postMessage' requires 2 arguments: 'message' and 'targetOrigin'");
  }

  targetOrigin = String(targetOrigin);

  if (!isValidTargetOrigin(targetOrigin)) {
    throw new DOMException(DOMException.SYNTAX_ERR, "Failed to execute 'postMessage' on 'Window': " +
      "Invalid target origin '" + targetOrigin + "' in a call to 'postMessage'.");
  }

  // TODO: targetOrigin === '/' - requires reference to source window
  // See https://github.com/tmpvar/jsdom/pull/1140#issuecomment-111587499
  if (targetOrigin !== "*" && targetOrigin !== this.location.origin) {
    return;
  }

  // TODO: event.source - requires reference to source window
  // TODO: event.origin - requires reference to source window
  // TODO: event.ports
  // TODO: event.data - structured clone message - requires cloning DOM nodes
  const event = new this.MessageEvent("message", {
    data: message
  });

  event.initEvent("message", false, false);

  setTimeout(() => {
    this.dispatchEvent(event);
  }, 0);
};

Fix origin-checking logic in postMessage


"use strict";

const fs = require("fs");
const packageJsonPath = "./package.json";
const packageJson = require(packageJsonPath);
const configurationSchema = require("./node_modules/markdownlint/schema/markdownlint-config-schema.json");
const defaultConfig = require("./default-config.json");

// Update package.json
const configurationRoot = packageJson.contributes.configuration.properties["markdownlint.config"];
configurationRoot.default = defaultConfig;
configurationRoot.properties = configurationSchema.properties;
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, "\t"));

"use strict";

const fs = require("fs");
const packageJsonPath = "./package.json";
const packageJson = require(packageJsonPath);
const configurationSchema = require("./node_modules/markdownlint/schema/markdownlint-config-schema.json");
const defaultConfig = require("./default-config.json");

// Update package.json
const configurationRoot = packageJson.contributes.configuration.properties["markdownlint.config"];
configurationRoot.default = defaultConfig;
configurationRoot.properties = configurationSchema.properties;
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, "\t") + "\n");

Add trailing newline charater when generating config schema for package.json.


exports.assertion = function () {

    this.message = 'Page loaded with no errors.';

    this.expected = [];

    this.pass = function (result) {

        return result && result.length === 0;
    };

    this.failure = function (result) {

        var failed = result.value.length > 0;
        if (failed) {
            this.message = result.value;
        }

        return failed;
    };

    this.value = function (result) {
        return result.value;
    };

    this.command = function (callback) {

        this.api.getJsErrors(callback);
        return this;
    };

};

exports.assertion = function () {

    this.message = 'Page loaded with no errors.';

    this.expected = [];

    this.pass = function (result) {

        return !result.value && result.length === 0;
    };

    this.failure = function (result) {

        var failed = result.value && result.value.length > 0;
        if (failed) {
            this.message = result.value;
        }

        return failed;
    };

    this.value = function (result) {
        return result ? result.value : [];
    };

    this.command = function (callback) {

        this.api.getJsErrors(callback);
        return this;
    };

};

Improve client side error detection


var angularMeteorUser = angular.module('angular-meteor.user', ['angular-meteor.utils']);

angularMeteorUser.run(['$rootScope', '$meteorUtils', function($rootScope, $meteorUtils){
  var currentUserDefer;

  $meteorUtils.autorun($rootScope, function(){
    if (Meteor.user) {
      $rootScope.currentUser = Meteor.user();
      $rootScope.loggingIn = Meteor.loggingIn();
    }

    // if there is no currentUserDefer (on first autorun)
    // or it is already resolved, but the Meteor.user() is changing
    if (!currentUserDefer || Meteor.loggingIn() ) {
      currentUserDefer = $q.defer();
      $rootScope.currentUserPromise = currentUserDefer.promise;
    }
    if ( !Meteor.loggingIn() ) {
      currentUserDefer.resolve( Meteor.user() );
    }

  });
}]);

var angularMeteorUser = angular.module('angular-meteor.user', ['angular-meteor.utils']);

angularMeteorUser.run(['$rootScope', '$meteorUtils', '$q', function($rootScope, $meteorUtils, $q){
  var currentUserDefer;

  $meteorUtils.autorun($rootScope, function(){
    if (Meteor.user) {
      $rootScope.currentUser = Meteor.user();
      $rootScope.loggingIn = Meteor.loggingIn();
    }

    // if there is no currentUserDefer (on first autorun)
    // or it is already resolved, but the Meteor.user() is changing
    if (!currentUserDefer || Meteor.loggingIn() ) {
      currentUserDefer = $q.defer();
      $rootScope.currentUserPromise = currentUserDefer.promise;
    }
    if ( !Meteor.loggingIn() ) {
      currentUserDefer.resolve( Meteor.user() );
    }

  });
}]);

Add missing $q service to User


//@flow
import React from 'react'
import { Route, IndexRoute } from 'react-router'
import App from './App'

if (process.env.NODE_ENV === 'development' || typeof require.ensure === 'undefined') require.ensure = (undefined, fc) => fc(require)

export default () => ( // eslint-disable-line
  <Route component={App}>
    <Route path="/">
      <IndexRoute
        getComponent={
          (location, callback) => {
            require.ensure([], (require) => {
              callback(null, require('./Posts').default)
            })
          }
        }
      />
      <Route path="addPost"
        getComponent={
          (location, callback) => {
            require.ensure([], (require) => {
              callback(null, require('./AddPost').default)
            })
          }
        }
      />
    </Route>
  </Route>
)

//@flow
import React from 'react'
import { Route, IndexRoute } from 'react-router'
import App from './App'
import Posts from './Posts'
import AddPost from './AddPost'

export default () => ( // eslint-disable-line
  <Route component={App}>
    <Route path="/">
      <IndexRoute component={Posts}/>
      <Route path="addPost" component={AddPost}/>
    </Route>
  </Route>
)

Revert "Disable code-split in development mode and for server side"


var NotificationComponent = Ember.Component.extend({
    classNames: ['js-bb-notification'],

    typeClass: function () {
        var classes = '',
            message = this.get('message'),
            type,
            dismissible;

        // Check to see if we're working with a DS.Model or a plain JS object
        if (typeof message.toJSON === 'function') {
            type = message.get('type');
            dismissible = message.get('dismissible');
        }
        else {
            type = message.type;
            dismissible = message.dismissible;
        }

        classes += 'notification-' + type;

        if (type === 'success' && dismissible !== false) {
            classes += ' notification-passive';
        }

        return classes;
    }.property(),

    didInsertElement: function () {
        var self = this;

        self.$().on('animationend webkitAnimationEnd oanimationend MSAnimationEnd', function (event) {
            /* jshint unused: false */
            self.notifications.removeObject(self.get('message'));
        });
    },

    actions: {
        closeNotification: function () {
            var self = this;
            self.notifications.closeNotification(self.get('message'));
        }
    }
});

export default NotificationComponent;

var NotificationComponent = Ember.Component.extend({
    classNames: ['js-bb-notification'],

    typeClass: function () {
        var classes = '',
            message = this.get('message'),
            type,
            dismissible;

        // Check to see if we're working with a DS.Model or a plain JS object
        if (typeof message.toJSON === 'function') {
            type = message.get('type');
            dismissible = message.get('dismissible');
        }
        else {
            type = message.type;
            dismissible = message.dismissible;
        }

        classes += 'notification-' + type;

        if (type === 'success' && dismissible !== false) {
            classes += ' notification-passive';
        }

        return classes;
    }.property(),

    didInsertElement: function () {
        var self = this;

        self.$().on('animationend webkitAnimationEnd oanimationend MSAnimationEnd', function (event) {
            /* jshint unused: false */
            if (event.originalEvent.animationName === 'fade-out') {
                self.notifications.removeObject(self.get('message'));
            }
        });
    },

    actions: {
        closeNotification: function () {
            var self = this;
            self.notifications.closeNotification(self.get('message'));
        }
    }
});

export default NotificationComponent;

Check the end of notification fade-out animation


import ModalDialog from 'ghost/components/gh-modal-dialog';
import upload from 'ghost/assets/lib/uploader';

var UploadModal = ModalDialog.extend({
    layoutName: 'components/gh-modal-dialog',

    didInsertElement: function () {
        this._super();
        upload.call(this.$('.js-drop-zone'), {fileStorage: this.get('config.fileStorage')});
    },
    confirm: {
        reject: {
            func: function () { // The function called on rejection
                return true;
            },
            buttonClass: true,
            text: 'Cancel' // The reject button text
        },
        accept: {
            buttonClass: 'btn btn-blue right',
            text: 'Save', // The accept button texttext: 'Save'
            func: function () {
                var imageType = 'model.' + this.get('imageType');

                if (this.$('.js-upload-url').val()) {
                    this.set(imageType, this.$('.js-upload-url').val());
                } else {
                    this.set(imageType, this.$('.js-upload-target').attr('src'));
                }
                return true;
            }
        }
    },

    actions: {
        closeModal: function () {
            this.sendAction();
        },
        confirm: function (type) {
            var func = this.get('confirm.' + type + '.func');
            if (typeof func === 'function') {
                func.apply(this);
            }
            this.sendAction();
            this.sendAction('confirm' + type);
        }
    }
});

export default UploadModal;

import ModalDialog from 'ghost/components/gh-modal-dialog';
import upload from 'ghost/assets/lib/uploader';

var UploadModal = ModalDialog.extend({
    layoutName: 'components/gh-modal-dialog',

    didInsertElement: function () {
        this._super();
        upload.call(this.$('.js-drop-zone'), {fileStorage: this.get('config.fileStorage')});
    },
    confirm: {
        reject: {
            func: function () { // The function called on rejection
                return true;
            },
            buttonClass: 'btn btn-default',
            text: 'Cancel' // The reject button text
        },
        accept: {
            buttonClass: 'btn btn-blue right',
            text: 'Save', // The accept button texttext: 'Save'
            func: function () {
                var imageType = 'model.' + this.get('imageType');

                if (this.$('.js-upload-url').val()) {
                    this.set(imageType, this.$('.js-upload-url').val());
                } else {
                    this.set(imageType, this.$('.js-upload-target').attr('src'));
                }
                return true;
            }
        }
    },

    actions: {
        closeModal: function () {
            this.sendAction();
        },
        confirm: function (type) {
            var func = this.get('confirm.' + type + '.func');
            if (typeof func === 'function') {
                func.apply(this);
            }
            this.sendAction();
            this.sendAction('confirm' + type);
        }
    }
});

export default UploadModal;

Fix button class on upload modal


var tape = require("tape"),
    jsdom = require("jsdom"),
    d3 = Object.assign(
      require("../"),
      require("d3-selection")
    );

// An example component.
function Paragraph(){
  return function (context, d){ context.text(d); };
}
Paragraph.tagName = "p";

tape("component()", function(test) {
  var div = d3.select(jsdom.jsdom().body).append("div");
  var paragraph = d3.component(Paragraph);
  div.call(paragraph, ["foo", "bar"]);
  test.equal(div.html(), "
foo

bar
");
  test.end();
});

var tape = require("tape"),
    jsdom = require("jsdom"),
    d3 = Object.assign(
      require("../"),
      require("d3-selection")
    );

// An example component.
function Paragraph(){
  return function (context, d){
    context.text(d);
  };
}
Paragraph.tagName = "p";

tape("A component should render multiple instances.", function(test) {
  var div = d3.select(jsdom.jsdom().body).append("div");
  var paragraph = d3.component(Paragraph);
  div.call(paragraph, ["foo", "bar"]);
  test.equal(div.html(), "
foo

bar
");
  test.end();
});

tape("A component should render a single instance.", function(test) {
  var div = d3.select(jsdom.jsdom().body).append("div");
  var paragraph = d3.component(Paragraph);
  div.call(paragraph, "foo");
  test.equal(div.html(), "
foo
");
  test.end();
});

Add test for single instance


// Reverse Array In Place

/*RULES:
  Take array as parameter
    Reverse array
    Return reversed array

  NOTES:
  Can't create new empty array to push in
  Must modify given array
  Can't use array.reverse();
*/

/*PSEUDOCODE

  1) Find array length
  2) Create variable "stopper" for last element in array
  2) (array.length - 1) times do:
    2a) Push element previous to "stopper" to the end of the array
    2b) Splice that same element out from in front of 'stopper'
  3) When done, return reversed array
*/

function reverseArray(arr){
  var count = arr.length;

}

// Reverse Array In Place

/*RULES:
  Take array as parameter
    Reverse array
    Return reversed array

  NOTES:
  Can't create new empty array to push in
  Must modify given array
  Can't use array.reverse();
*/

/*PSEUDOCODE

  1) Find array length
  2) Create variable "stopper" for last element in array
  2) (array.length - 1) times do:
    2a) Push element previous to "stopper" to the end of the array
    2b) Splice that same element out from in front of 'stopper'
  3) When done, return reversed array
*/

function reverseArray(arr){
  var count = arr.length;
  var stopper = arr[count - 1];

  for (var i = 0; i < count; i++){
    arr.push(arr[arr.indexOf(stopper) - 1]);
    arr.splice((arr.indexOf(stopper) - 1), 1);
  }

  return arr;
}

Complete working algorithm, follows all rules and works perfectly


module.exports = {
  "extends": "react-app",
  "rules": {
    "comma-dangle": ["error", "always-multiline"],
    // disallow unnecessary semicolons
    'no-extra-semi': 'error',
  },
};

module.exports = {
  "extends": "react-app",
  "rules": {
    "comma-dangle": ["warn", "always-multiline"],
    // disallow unnecessary semicolons
    'no-extra-semi': 'warn',
  },
};

Change code style issues to warnings


// This dispatcher modifies some of the ideas in Facebook's template:
// https://github.com/facebook/flux/blob/master/src/Dispatcher.js

var FluxDispatcher = require('flux').Dispatcher;

class AppDispatcher extends FluxDispatcher {
  dispatch(payload) {
    if (!payload.action && !payload.type) {
      console.error('Cannot dispatch null action. Make sure action type is in constants.js');
      return;
    }

    super.dispatch(payload);
  }
};

module.exports = window.Dispatcher = new AppDispatcher();

Remove unnecessary comment, since the origins of the Dispatcher are now apparent


var assert = require('assert');
var encoders = require('../lib/encoders');
var fixtures = require('./fixture/server');

describe('encoders', function () {
  describe('thumbprint', function () {
    it('should return the thumbprint in all caps', function () {
      var certThumbprint = encoders.thumbprint(fixtures.credentials.cert);
      assert.equal(certThumbprint, '499FDF1C2218A99C8595AAC2FD95CE36F0A6D59D');
    });
  });
});

'use strict';

const assert = require('assert');
const encoders = require('../lib/encoders');
const fixtures = require('./fixture/server');

describe('encoders', function () {
  describe('thumbprint', function () {
    it('should return the thumbprint in all caps', function () {
      const certThumbprint = encoders.thumbprint(fixtures.credentials.cert);
      assert.equal(certThumbprint, '499FDF1C2218A99C8595AAC2FD95CE36F0A6D59D');
    });
  });
});

Revert "Make compatible with Node 0.10 🤢"


define(['underscore', 'backbone', 'jquery'], function(_, Backbone, $) {
	'use strict';

	var $navigatorBtn = $('.caption .controls button.navigator');
	var $navigatorOverlay = $('.navigator-overlay');

	return Backbone.View.extend({
		initialize: function() {
			$navigatorBtn.click(_.bind(this.toggleNavigator, this));
			$navigatorOverlay.on('click mousedown touchdown', _.bind(this.closeNavigator, this));
		},

		openNavigator: function() {
			$('body').addClass('navigator');
		},

		closeNavigator: function() {
			$('body').removeClass('navigator');
		},

		isNavigatorOpen: function() {
			return $('body').hasClass('navigator');
		},

		toggleNavigator: function() {
			if (!this.isNavigatorOpen()) {
				this.openNavigator();
			} else {
				this.closeNavigator();
			}
			return false;
		}
	});

});

define(['underscore', 'backbone', 'jquery'], function(_, Backbone, $) {
	'use strict';

	var $navigatorBtn = $('.caption .controls button.navigator');
	var $navigatorOverlay = $('.navigator-overlay');

	return Backbone.View.extend({
		initialize: function() {
			$navigatorBtn.click(_.bind(this.toggleNavigator, this));
			$navigatorOverlay.on('click mousedown touchdown', _.bind(this.closeNavigator, this));
		},

		toggleNavigator: function() {
			$('body').toggleClass('navigator');
		},

		closeNavigator: function() {
			$('body').removeClass('navigator');
		}
	});

});

Remove unused methods from navigator control


var utils = require('../utils')
  , selfClosing = require('void-elements')

module.exports = function () {}

module.exports.prototype =
  { name: 'validateSelfClosingTags'

  , configure: function (options) {

      utils.validateTrueOptions(this.name, options)

    }

  , lint: function (file, errors) {

      var isXml

      file.iterateTokensByType('doctype', function (token) {
        isXml = token.val === 'xml'
      })

      if (!isXml) {
        file.iterateTokensByType('tag', function (token) {
          if (token.selfClosing && selfClosing[token.val]) {
            errors.add('Unnecessary self closing tag', token.line)
          }
        })
      }

    }
  }

var utils = require('../utils')
  , selfClosing = require('void-elements')

module.exports = function () {}

module.exports.prototype =
  { name: 'validateSelfClosingTags'

  , configure: function (options) {

      utils.validateTrueOptions(this.name, options)

    }

  , lint: function (file, errors) {

      var isXml

      file.iterateTokensByType('doctype', function (token) {
        isXml = token.val === 'xml'
      })

      if (!isXml) {
        file.iterateTokensByType('tag', function (token) {
          var nextToken = file.getToken(token._index + 1)

          if (nextToken.type === 'slash' && selfClosing[token.val]) {
            errors.add('Unnecessary self closing tag', token.line)
          }
        })
      }

    }
  }

Refactor `validateSelfClosingTags` to handle new token structure


'use strict';

const CommonModel = require('../common/model.js');

class ServerModel extends CommonModel {
  constructor(validator, messenger, dbStorage, config, jwt) {
    super(validator, messenger);

    this.db = dbStorage;
    this.config = config;
    this.jwt = jwt;
  }

  signinPassword(event) {
    this.db
      .getUser(this.getUsername(), this.getPassword())
      .then(this.handleUser.bind(this, event))
      .catch(this.messenger.handleError.bind(this.messenger, event));
  }

  signinToken(event) {
    this.reply(event, this.jwt.verify(
      this.getToken(),
      this.config.get('@scola.auth.privateKey')
    ));
  }

  handleUser(event, user) {
    if (!user) {
      throw new Error('@scola.auth.error.user-not-found');
    }

    const token = this.jwt.sign({
      userId: user.user_id
    }, this.config.get('@scola.auth.privateKey'), {
      expiresIn: '7 days'
    });

    this.reply(event, {
      token,
      userId: user.user_id
    });
  }
}

module.exports = ServerModel;

'use strict';

const CommonModel = require('../common/model.js');

class ServerModel extends CommonModel {
  constructor(validator, messenger, dbStorage, config, jwt) {
    super(validator, messenger);

    this.db = dbStorage;
    this.config = config;
    this.jwt = jwt;
  }

  signinPassword(event) {
    this.db
      .getUser(this.getUsername(), this.getPassword())
      .then(this.handleUser.bind(this, event))
      .catch(this.messenger.handleError.bind(this.messenger, event));
  }

  signinToken(event) {
    this.messenger.reply(event, this.jwt.verify(
      this.getToken(),
      this.config.get('@scola.auth.privateKey')
    ));
  }

  handleUser(event, user) {
    if (!user) {
      throw new Error('@scola.auth.error.user-not-found');
    }

    const token = this.jwt.sign({
      userId: user.user_id
    }, this.config.get('@scola.auth.privateKey'), {
      expiresIn: '7 days'
    });

    this.messenger.reply(event, {
      token,
      userId: user.user_id
    });
  }
}

module.exports = ServerModel;

Fix bug: call reply method on messenger


import rp from 'request-promise';

import {Microservices} from '../../configs/microservices';
import {isEmpty, assignToAllById} from '../../common';

export default {

    // fetches the tags data
    fetchTagInfo(tags) {
        if (isEmpty(tags)) return Promise.resolve([]);

        return rp.get({
            uri: `${Microservices.tag.uri}/tags`,
            qs: {
                tagName: tags.map((t) => t.tagName),
                paging: false, // this allows for unpaged results
            },
            useQuerystring: true,
            json: true,
        });
    },

};

import rp from 'request-promise';

import {Microservices} from '../../configs/microservices';
import {isEmpty, assignToAllById} from '../../common';

export default {

    // fetches the tags data
    fetchTagInfo(tags) {
        if (isEmpty(tags)) return Promise.resolve([]);

        return rp.get({
            uri: `${Microservices.tag.uri}/tags`,
            qs: {
                tagType: 'all',
                tagName: tags.map((t) => t.tagName),
                paging: false, // this allows for unpaged results
            },
            useQuerystring: true,
            json: true,
        });
    },

};

Fix no topics shown when loading a deck


import * as selectors from '../src/selectors'

describe('selectors', () => {
  it('includes selector to get session data', () => {
    const state = {
      session: {
        data: { token: 'abcde' }
      }
    }

    const result = selectors.getSessionData(state)

    expect(result).toEqual({ token: 'abcde' })
  })

  it('includes selector to get isAuthenticated', () => {
    const state = {
      session: {
        isAuthenticated: true
      }
    }

    const result = selectors.getIsAuthenticated(state)

    expect(result).toEqual(true)
  })

  it('includes selector to get authenticator', () => {
    const state = {
      session: {
        authenticator: 'credentials'
      }
    }

    const result = selectors.getAuthenticator(state)

    expect(result).toEqual('credentials')
  })

  it('includes selector to get isRestored', () => {
    const state = {
      session: {
        isRestored: false
      }
    }

    const result = selectors.getIsRestored(state)

    expect(result).toEqual(false)
  })

  it('includes selector to get lastError', () => {
    const state = {
      session: {
        lastError: 'You shall not pass'
      }
    }

    const result = selectors.getLastError(state)

    expect(result).toEqual('You shall not pass')
  })
})

import * as selectors from '../src/selectors'

describe('selectors', () => {
  it('includes selector to get session data', () => {
    const state = {
      session: {
        data: { token: 'abcde' }
      }
    }

    const result = selectors.getSessionData(state)

    expect(result).toEqual({ token: 'abcde' })
  })

  it('includes selector to get isAuthenticated', () => {
    const state = {
      session: {
        isAuthenticated: true
      }
    }

    const result = selectors.getIsAuthenticated(state)

    expect(result).toEqual(true)
  })

  it('includes selector to get authenticator', () => {
    const state = {
      session: {
        authenticator: 'credentials'
      }
    }

    const result = selectors.getAuthenticator(state)

    expect(result).toEqual('credentials')
  })

  it('includes selector to get isRestored', () => {
    const state = {
      session: {
        isRestored: false
      }
    }

    const result = selectors.getIsRestored(state)

    expect(result).toEqual(false)
  })

  it('includes selector to get lastError', () => {
    const state = {
      session: {
        lastError: 'You shall not pass'
      }
    }

    const result = selectors.getLastError(state)

    expect(result).toEqual('You shall not pass')
  })

  it('includes selector to get hasFailedAuth', () => {
    const state = {
      session: {
        hasFailedAuth: true
      }
    }

    const result = selectors.getHasFailedAuth(state)

    expect(result).toEqual(true)
  })
})

Add test for getHasFailedAuth selector


import React, { PropTypes } from 'react';
import ImmutableTypes from 'react-immutable-proptypes';
import { Grid, Cell, Button } from 'react-mdl';

const LawCollectionChooser = ({ collections, selected, onSelect }) => (
  <Grid noSpacing>
    {collections.map(title => (
      <Cell key={title} col={3} tablet={4} phone={1}>
        <Button
          ripple raised
          accent={selected === title}
          onClick={() => onSelect(selected !== title ? title : '')}
        >
          {title}
        </Button>
      </Cell>
    ))}
  </Grid>
);

LawCollectionChooser.propTypes = {
  collections: ImmutableTypes.listOf(PropTypes.string).isRequired,
  onSelect: PropTypes.func.isRequired,
  selected: PropTypes.string,
};

export default LawCollectionChooser;

import React, { PropTypes } from 'react';
import { List } from 'immutable';
import { range } from 'lodash';
import ImmutableTypes from 'react-immutable-proptypes';
import { Grid, Cell, Button } from 'react-mdl';

const shell = List(range(4).map(() => ''));

const LawCollectionChooser = ({ collections, selected, onSelect }) => (
  <Grid>
    {(collections.size ? collections : shell).map((title, idx) => (
      <Cell
        key={title || `shell-${idx}`}
        col={3}
        tablet={4}
        phone={4}
      >
        <Button
          ripple raised
          disabled={title === ''}
          accent={selected === title}
          onClick={() => onSelect(selected !== title ? title : '')}
          style={{ width: '100%' }}
        >
          {title}
        </Button>
      </Cell>
    ))}
  </Grid>
);

LawCollectionChooser.propTypes = {
  collections: ImmutableTypes.listOf(PropTypes.string).isRequired,
  onSelect: PropTypes.func.isRequired,
  selected: PropTypes.string,
};

export default LawCollectionChooser;

Implement shells for law index collection chooser.


var _ = require("lodash");
var Q = require("q");
var fs = require("./fs");
var shellescape = require('shell-escape');
var spawn = require('spawn-cmd').spawn;

var links = require("./links");

// Convert a svg file
var convertSVG = function(source, dest, options) {
	if (!fs.existsSync(source)) return Q.reject(new Error("File doesn't exist: "+source));
	var d = Q.defer();

	options = _.defaults(options || {}, {

	});

	//var command = shellescape(['svgexport', source, dest]);
    var child = spawn('svgexport', [source, dest]);

    child.on("error", function(error) {
        if (error.code == "ENOENT") error = new Error("Need to install 'svgexport' using 'npm install svgexport -g'");
        return d.reject(error);
    });

    child.on("close", function(code) {
        if (code == 0 && fs.existsSync(dest)) {
            d.resolve();
        } else {
            d.reject(new Error("Error converting "+source+" into "+dest));
        }
    });

	return d.promise;
};

module.exports = {
	convertSVG: convertSVG,
	INVALID: [".svg"]
};

var _ = require("lodash");
var Q = require("q");
var fs = require("./fs");
var spawn = require('spawn-cmd').spawn;

var links = require("./links");

// Convert a svg file
var convertSVG = function(source, dest, options) {
	if (!fs.existsSync(source)) return Q.reject(new Error("File doesn't exist: "+source));
	var d = Q.defer();

	options = _.defaults(options || {}, {

	});

	//var command = shellescape(['svgexport', source, dest]);
    var child = spawn('svgexport', [source, dest]);

    child.on("error", function(error) {
        if (error.code == "ENOENT") error = new Error("Need to install 'svgexport' using 'npm install svgexport -g'");
        return d.reject(error);
    });

    child.on("close", function(code) {
        if (code == 0 && fs.existsSync(dest)) {
            d.resolve();
        } else {
            d.reject(new Error("Error converting "+source+" into "+dest));
        }
    });

	return d.promise;
};

module.exports = {
	convertSVG: convertSVG,
	INVALID: [".svg"]
};

Remove require of old dependency "shell-escape"