import Vue from 'vue';
import notesApp from '../notes/components/notes_app.vue';
import discussionCounter from '../notes/components/discussion_counter.vue';
import store from '../notes/stores';

export default function initMrNotes() {
  // eslint-disable-next-line no-new
  new Vue({
    el: '#js-vue-mr-discussions',
    components: {
      notesApp,
    },
    data() {
      const notesDataset = document.getElementById('js-vue-mr-discussions')
        .dataset;
      return {
        noteableData: JSON.parse(notesDataset.noteableData),
        currentUserData: JSON.parse(notesDataset.currentUserData),
        notesData: JSON.parse(notesDataset.notesData),
      };
    },
    render(createElement) {
      return createElement('notes-app', {
        props: {
          noteableData: this.noteableData,
          notesData: this.notesData,
          userData: this.currentUserData,
        },
      });
    },
  });

  // eslint-disable-next-line no-new
  new Vue({
    el: '#js-vue-discussion-counter',
    components: {
      discussionCounter,
    },
    store,
    render(createElement) {
      return createElement('discussion-counter');
    },
  });
}

import Vue from 'vue';
import notesApp from '../notes/components/notes_app.vue';
import discussionCounter from '../notes/components/discussion_counter.vue';
import store from '../notes/stores';

export default function initMrNotes() {
  // eslint-disable-next-line no-new
  new Vue({
    el: '#js-vue-mr-discussions',
    components: {
      notesApp,
    },
    data() {
      const notesDataset = document.getElementById('js-vue-mr-discussions')
        .dataset;
      const noteableData = JSON.parse(notesDataset.noteableData);
      noteableData.noteableType = notesDataset.noteableType;

      return {
        noteableData,
        currentUserData: JSON.parse(notesDataset.currentUserData),
        notesData: JSON.parse(notesDataset.notesData),
      };
    },
    render(createElement) {
      return createElement('notes-app', {
        props: {
          noteableData: this.noteableData,
          notesData: this.notesData,
          userData: this.currentUserData,
        },
      });
    },
  });

  // eslint-disable-next-line no-new
  new Vue({
    el: '#js-vue-discussion-counter',
    components: {
      discussionCounter,
    },
    store,
    render(createElement) {
      return createElement('discussion-counter');
    },
  });
}

Set noteableType on noteableData as provided from DOM dataset


/*
* @Author: Mike Reich
* @Date:   2016-01-26 11:48:16
* @Last Modified 2016-01-26
*/

'use strict';

import MandrilService from './MandrilService'

export default class Mailer {

  constructor(app) {
    new MandrilService(app)

    this.app = app;
    this.mailer = app.get('mailer')

    this._services = [];
    this.mailer.gather('service', this._registerService.bind(this))
  }

  _registerService(service) {
    console.log('registering service')
    this.mailer.respond('send', service.sendMessage.bind(service))
  }
}

/*
* @Author: Mike Reich
* @Date:   2016-01-26 11:48:16
* @Last Modified 2016-01-26
*/

'use strict';

import MandrilService from './MandrilService'

export default class Mailer {

  constructor(app) {
    this.app = app;
    this.mailer = app.get('mailer')

    if(this.app.config.MANDRILL_APIKEY || app.config.mandrill.api_key) new MandrilService(app)

    this._services = [];
    this.mailer.gather('service', this._registerService.bind(this))
  }

  _registerService(service) {
    console.log('registering service')
    this.mailer.respond('send', service.sendMessage.bind(service))
  }
}

Make Mandrill install only if service APIKEYs exist


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

define([
  'underscore',
  'moment',
  'views/base',
  'stache!templates/visits/date_divider',
], function (_, moment, BaseView, VisitsDateDividerTemplate, UserPagePresenter) {
  'use strict';

  var VisitsDateDividerView = BaseView.extend({
    tagName: 'h3',
    className: 'visit-date-divider',
    template: VisitsDateDividerTemplate,

    initialize: function (date) {
      this.date = date;
    },

    getContext: function (date) {
      return {
        formattedDate: this._formatDate()
      };
    },

    _formatDate: function () {
      var formattedDate;

      var diff = this.date.diff(moment().startOf('day'), 'days');

      if (diff === 0) {
        formattedDate = 'Today';
      } else if (diff === -1) {
        formattedDate = 'Yesterday';
      } else {
        formattedDate = this.date.format('MMMM do');
      }

      return formattedDate;
    }
  });

  return VisitsDateDividerView;
});

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

define([
  'underscore',
  'moment',
  'views/base',
  'stache!templates/visits/date_divider',
], function (_, moment, BaseView, VisitsDateDividerTemplate, UserPagePresenter) {
  'use strict';

  var VisitsDateDividerView = BaseView.extend({
    tagName: 'h3',
    className: 'visit-date-divider',
    template: VisitsDateDividerTemplate,

    initialize: function (date) {
      this.date = date;
    },

    getContext: function (date) {
      return {
        formattedDate: this._formatDate()
      };
    },

    _formatDate: function () {
      var formattedDate;

      var diff = this.date.diff(moment().startOf('day'), 'days');

      if (diff === 0) {
        formattedDate = 'Today';
      } else if (diff === -1) {
        formattedDate = 'Yesterday';
      } else {
        formattedDate = this.date.format('MMMM Do');
      }

      return formattedDate;
    }
  });

  return VisitsDateDividerView;
});

Change date heading to use day of month with ordinal


module.exports = {
  extends: [
    './base.js'
  ],
  plugins: [
    'node'
  ],
  rules: {
    'node/no-unsupported-features': 'error',
    'node/no-deprecated-api': 'error',

    'generator-star-spacing': ['error', { before: true }],
    'template-curly-spacing': ['error', 'always'],
    'prefer-arrow-callback': 'error',
    'no-dupe-class-members': 'error',
    'no-this-before-super': 'error',
    'constructor-super': 'error',
    'arrow-body-style': 'error',
    'object-shorthand': 'error',
    'no-const-assign': 'error',
    'prefer-template': 'error',
    'no-new-symbol': 'error',
    'require-yield': 'error',
    'arrow-spacing': 'error',
    'arrow-parens': ['error', 'as-needed'],
    'prefer-const': 'error',
    'strict': ['error', 'global'],
    'no-var': 'error'
  },
  env: {
    es6: true
  }
}

module.exports = {
  extends: [
    './base.js'
  ],
  plugins: [
    'node'
  ],
  rules: {
    'node/no-unsupported-features': 'error',
    'node/no-deprecated-api': 'error',

    'generator-star-spacing': ['error', { before: true }],
    'template-curly-spacing': ['error', 'always'],
    'prefer-arrow-callback': 'error',
    'no-dupe-class-members': 'error',
    'no-this-before-super': 'error',
    'constructor-super': 'error',
    'arrow-body-style': 'error',
    'object-shorthand': 'error',
    'no-const-assign': 'error',
    'prefer-template': 'error',
    'no-new-symbol': 'error',
    'require-yield': 'error',
    'arrow-spacing': 'error',
    'arrow-parens': ['error', 'as-needed'],
    'prefer-const': 'error',
    'strict': ['error', 'global'],
    'no-var': 'error'
  },
  env: {
    es6: true
  },
  parserOptions: {
    sourceType: 'script'
  }
}

Fix sourceType in server config


// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

var localStrings = new LocalStrings();

/**
 * Window onload handler, sets up the page.
 */
function load() {
  chrome.send('getPrinters');
};

/**
 * Fill the printer list drop down.
 */
function setPrinters(printers) {
  if (printers.length > 0) {
    for (var i = 0; i < printers.length; ++i) {
      var option = document.createElement('option');
      option.textContent = printers[i];
      $('printer-list').add(option);
    }
  } else {
    var option = document.createElement('option');
    option.textContent = localStrings.getString('no-printer');
    $('printer-list').add(option);
    $('printer-list').disabled = true;
    $('print-button').disabled = true;
  }
}

window.addEventListener('DOMContentLoaded', load);

// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

var localStrings = new LocalStrings();

/**
 * Window onload handler, sets up the page.
 */
function load() {
  $('cancel-button').addEventListener('click', function(e) {
    window.close();
  });

  chrome.send('getPrinters');
};

/**
 * Fill the printer list drop down.
 */
function setPrinters(printers) {
  if (printers.length > 0) {
    for (var i = 0; i < printers.length; ++i) {
      var option = document.createElement('option');
      option.textContent = printers[i];
      $('printer-list').add(option);
    }
  } else {
    var option = document.createElement('option');
    option.textContent = localStrings.getString('no-printer');
    $('printer-list').add(option);
    $('printer-list').disabled = true;
    $('print-button').disabled = true;
  }
}

window.addEventListener('DOMContentLoaded', load);

Print Preview: Hook up the cancel button.


export default function getBaseUrl() {
  const inDevelopment = window.location.hostname === 'localhost';
  return inDevelopment ? 'http://localhost:3001/' : '/'
}

export default function getBaseUrl() {
  return getQueryStringParameterByName('useMockApi') ? 'http://localhost:3001/' : '/';
}

function getQueryStringParameterByName(name, url) {
  url = url || window.location.href;
  console.log(url.indexOf(name) > -1, url);
  return url.indexOf(name) > -1;
}

Check for mock api in path


Template.duplicates.onRendered(function() {

    var playlist_id = Router.current().params['_id']

    Meteor.call('getDuplicateTracksFromPlaylist', playlist_id, function(err, response) {
        console.log(response);

        Session.set('duplicateTracks', response);
        Session.set('loadedDuplicates', true);
    });
});

Template.duplicates.events({
    'click .remove-dup': function (e) {
        var track_uri = e.currentTarget.id;
        var track_id = e.currentTarget.id.split(':')[2];
        var playlist_id = e.currentTarget.baseURI.split('/')[4];

        console.log(track_uri);
        console.log(playlist_id);

        Meteor.call('removeDuplicates', playlist_id, track_uri, function(err, response) {

        });

        // Temporary hack, can't
        // remove more than 1 duplicate without
        // completely reloading
        window.location.reload()

        //$('#' + track_id).remove();
        //Session.set('duplicateTracks', $.grep(Session.get('duplicateTracks'), function(e) { return e.uri != track_uri }));
    },

    'click .home': function(e) {
        Session.set('loadedPlaylists', true);
        Router.go('/');
    }
});

Template.duplicates.helpers({
    duplicates: function() {
        return Session.get('duplicateTracks');
    },

    loadedDuplicates: function() {
        return Session.get('loadedDuplicates');
    }

});

Template.duplicates.onRendered(function() {

    Session.set('loadedDuplicates', false);

    var playlist_id = Router.current().params['_id']

    Meteor.call('getDuplicateTracksFromPlaylist', playlist_id, function(err, response) {
        console.log(response);

        Session.set('duplicateTracks', response);
        Session.set('loadedDuplicates', true);
    });
});

Template.duplicates.events({
    'click .remove-dup': function (e) {
        var track_uri = e.currentTarget.id;
        var track_id = e.currentTarget.id.split(':')[2];
        var playlist_id = e.currentTarget.baseURI.split('/')[4];

        console.log(track_uri);
        console.log(playlist_id);

        Meteor.call('removeDuplicates', playlist_id, track_uri, function(err, response) {

        });

        // Temporary hack, can't
        // remove more than 1 duplicate without
        // completely reloading
        window.location.reload()

        //$('#' + track_id).remove();
        //Session.set('duplicateTracks', $.grep(Session.get('duplicateTracks'), function(e) { return e.uri != track_uri }));
    },

    'click .home': function(e) {
        Session.set('loadedPlaylists', true);
        Router.go('/');
    }
});

Template.duplicates.helpers({
    duplicates: function() {
        return Session.get('duplicateTracks');
    },

    loadedDuplicates: function() {
        return Session.get('loadedDuplicates');
    }

});

Make spinner reappear on subsequent visits


var Utils = {};

(function() {
  Utils.convertTemp = function(temp) {
    if(Ti.App.Properties.getString('units','c')==='f') {
        return Math.round(temp*1.8+32) +'\u00b0F'; // convert to Fahrenheit & append degree symbol-F
    } else {
        return temp +'\u00b0C';
    }
  };

  Utils.weatherIcon = function(iconName) {
    // Determine if this icon is already downloaded.
    var appDir = Ti.Filesystem.applicationDataDirectory;
    var file = Ti.Filesystem.getFile(appDir, 'weathericons/' + iconName);
    // If the file does not already exists, download and store it.
    if (file.exists()) {
      Ti.API.log('Icon ' + iconName + ' exists on device filesystem. Use local file.');
      return appDir + 'weathericons/' + iconName;
    }
    else {
      Ti.API.log('Icon ' + iconName + ' does not exists on device. Use external URL.');
      return 'http://www.worldweather.org/img_cartoon/' + iconName;
    }
  }
})();

var Utils = {};

(function() {
  Utils.convertTemp = function(temp) {
    if(Ti.App.Properties.getString('units','c')==='f') {
        return Math.round(temp*1.8+32) +'\u00b0F'; // convert to Fahrenheit & append degree symbol-F
    } else {
        return temp +'\u00b0C';
    }
  };

  Utils.weatherIcon = function(iconName) {
    // Determine if this icon is already downloaded.
    var appDir = Ti.Filesystem.applicationDataDirectory;
    var file = Ti.Filesystem.getFile(appDir, 'weathericons/' + iconName);

    // If the file already exists, display the file stored on the local filesystem.
    if (file.exists()) {
      Ti.API.log('Icon ' + iconName + ' exists on device filesystem. Use local file.');
      return appDir + 'weathericons/' + iconName;
    }
    // Otherwise, cache the image in the local filesystem, but still
    // return the path right away, as caching takes a few seconds.
    else {
      Ti.API.log('Icon ' + iconName + ' does not exists on device. Use external URL.');
      var imageView = Ti.UI.createImageView({
        image:'http://www.worldweather.org/img_cartoon/' + iconName
      });
      // Wait a few seconds because it'll take a little bit to download the image.
      setTimeout(function() {
        // Valid images will be 35 pixels wide. Only store those images
        // in the local filesystem.
        var imageBlob = imageView.toImage();
        Ti.API.log('Width of this icon is ' + imageBlob.width);
        var file = Ti.Filesystem.getFile(appDir, 'weathericons/' + iconName);
        file.write(imageBlob);
        Ti.API.log('Successfully created this weathericon file: ' + iconName);
      }, 5000);
      return 'http://www.worldweather.org/img_cartoon/' + iconName;
    }
  }
})();

Add caching mechanism for weather icons.


import Bit from '../components/Bit'
import { createElement } from 'react'

const COMMENT_TAG = '--'
const DEFAULT_TAG = Bit

// eslint-disable-next-line max-params
function parse (buffer, doc, options, key) {
  switch (doc.type) {
    case 'text':
      return [...buffer, doc.content]
    case 'tag': {
      if (doc.name.startsWith(COMMENT_TAG)) {
        return buffer
      }
      const tag = options.mappings[doc.name] || DEFAULT_TAG
      let children = reactify(doc.children, options, key)
      if (!children.length) {
        children = null
      }
      if (tag.fromMarkup) {
        doc.attrs = tag.fromMarkup(doc.attrs)
      }
      if (tag === DEFAULT_TAG && !doc.attrs.as) {
        doc.attrs.as = doc.name
      }
      if (doc.attrs.class) {
        // Rename class to className for React compatibility
        doc.attrs.className = doc.attrs.class
        delete doc.attrs.class
      }
      return [...buffer, createElement(tag, { key, ...doc.attrs }, children)]
    }
    default:
      return buffer
  }
}

export default function reactify (doc, options, keyPrefix = '$') {
  return doc.reduce(
    (collection, rootEl, key) => [
      ...collection,
      ...parse([], rootEl, options, `${keyPrefix}.${key}`)
    ],
    []
  )
}

import Bit from '../components/Bit'
import { createElement } from 'react'

const COMMENT_TAG = '--'
const DEFAULT_TAG = Bit

// eslint-disable-next-line max-params
function parse (buffer, doc, options, key) {
  switch (doc.type) {
    case 'text':
      return [...buffer, doc.content]
    case 'tag': {
      let children = reactify(doc.children, options, key)
      if (!children.length) {
        children = null
      }
      if (doc.name.startsWith(COMMENT_TAG)) {
        return [...buffer, ...children]
      }
      const tag = options.mappings[doc.name] || DEFAULT_TAG
      if (tag.fromMarkup) {
        doc.attrs = tag.fromMarkup(doc.attrs)
      }
      if (tag === DEFAULT_TAG && !doc.attrs.as) {
        doc.attrs.as = doc.name
      }
      if (doc.attrs.class) {
        // Rename class to className for React compatibility
        doc.attrs.className = doc.attrs.class
        delete doc.attrs.class
      }
      return [...buffer, createElement(tag, { key, ...doc.attrs }, children)]
    }
    default:
      return buffer
  }
}

export default function reactify (doc, options, keyPrefix = '$') {
  return doc.reduce(
    (collection, rootEl, key) => [
      ...collection,
      ...parse([], rootEl, options, `${keyPrefix}.${key}`)
    ],
    []
  )
}

Fix bug that caused HTML comments to halt all additional parsing


// Test object id find.

function testObjectIdFind( db ) {
    r = db.ed_db_find2_oif;
    r.drop();

    z = [ {}, {}, {} ];
    for( i = 0; i < z.length; ++i )
	r.save( z[ i ] );

    center = r.find( { _id: z[ 1 ]._id } );
    assert.eq( 1, center.count() );
    assert.eq( z[ 1 ]._id, center[ 0 ]._id );

    left = r.find( { _id: { $lt: z[ 1 ]._id } } );
    assert.eq( 1, left.count() );
    assert.eq( z[ 0 ]._id, left[ 0 ]._id );

    right = r.find( { _id: { $gt: z[ 1 ]._id } } );
    assert.eq( 1, right.count() );
    assert.eq( z[ 2 ]._id, right[ 0 ]._id );
}

db = connect( "test" );
testObjectIdFind( db );

// Test object id sorting.

function testObjectIdFind( db ) {
    r = db.ed_db_find2_oif;
    r.drop();

    for( i = 0; i < 3; ++i )
	r.save( {} );

    f = r.find().sort( { _id: 1 } );
    assert.eq( 3, f.count() );
    assert( f[ 0 ]._id < f[ 1 ]._id );
    assert( f[ 1 ]._id < f[ 2 ]._id );
}

db = connect( "test" );
testObjectIdFind( db );

Test shouldn't assume object ids are strictly increasing


var env = require('../lib/environment');
var persona = require('../lib/persona');
var util = require('util');

exports.login = function(req, res){
  if (req.method === 'GET')
    return res.render("login.html");
  var assertion = req.body.assertion;
  persona.verify(assertion, function (err, email) {
    if (err)
      return res.send(util.inspect(err));
    if (!userIsAuthorized(email))
      return res.send(403, 'not authorized')
    req.session.user = email;
    return res.redirect('/admin');
  });
};

exports.logout = function (req, res) {
  req.session.destroy(function () {
    return res.redirect('/login');
  });
};

var middleware = exports.middleware = {};
middleware.requireAuth = function requireAuth(options) {
  var whitelist = options.whitelist || [];
  return function (req, res, next) {
    var path = req.path;
    var user = req.session.user;
    if (whitelist.indexOf(path) > -1)
      return next();
    if (!user || !userIsAuthorized(user))
      return res.redirect(options.redirectTo);
    return next();
  };
};

function userIsAuthorized(email) {
  var admins = env.get('admins');
  return admins.indexOf(email) >= -1;
}

var env = require('../lib/environment');
var persona = require('../lib/persona');
var util = require('util');

exports.login = function(req, res){
  if (req.method === 'GET')
    return res.render("login.html");
  var assertion = req.body.assertion;
  persona.verify(assertion, function (err, email) {
    if (err)
      return res.send(util.inspect(err));
    if (!userIsAuthorized(email))
      return res.send(403, 'not authorized')
    req.session.user = email;
    return res.redirect('/admin');
  });
};

exports.logout = function (req, res) {
  req.session.destroy(function () {
    return res.redirect('/login');
  });
};

var middleware = exports.middleware = {};
middleware.requireAuth = function requireAuth(options) {
  var whitelist = options.whitelist || [];
  return function (req, res, next) {
    var path = req.path;
    var user = req.session.user;
    if (whitelist.indexOf(path) > -1)
      return next();
    if (!user || !userIsAuthorized(user))
      return res.redirect(options.redirectTo);
    return next();
  };
};

function userIsAuthorized(email) {
  var admins = env.get('admins');
  var authorized = false;
  admins.forEach(function (admin) {
    if (authorized) return;
    var adminRe = new RegExp(admin.replace('*', '.+?'));
    if (adminRe.test(email))
      return authorized = true;
  });
  return authorized;
}

Allow regexp for authorized array.


$(".hamburger").click(function () {
    $('.slider').slideToggle();
}); //TODO: Write own CSS animation or use animate.css or alternative

Add TODO to fix animation.


let Q = require("q");

module.exports = {
  // `pcall` takes a function that takes a set of arguments and
  // a callback (NON-Node.js style) and turns it into a promise
  // that gets resolved with the arguments to the callback.
  pcall: function(fn) {
    let deferred = Q.defer();
    let callback = function() {
      deferred.resolve(Array.prototype.slice.call(arguments)[0]);
    };
    let newArgs = Array.prototype.slice.call(arguments, 1);
    newArgs.push(callback);
    fn.apply(null, newArgs);
    return deferred.promise;
  }
};

let Q = require("q");

module.exports = {
  // `pcall` takes a function that takes a set of arguments and
  // a callback (NON-Node.js style) and turns it into a promise
  // that gets resolved with the arguments to the callback.
  pcall: function(fn) {
    let deferred = Q.defer();
    let callback = function() {
      deferred.resolve(Array.prototype.slice.call(arguments)[0]);
      let message = chrome.runtime.lastError.message;
      if (message != "") {
        console.log("pcall message: " + message);
      }
    };
    let newArgs = Array.prototype.slice.call(arguments, 1);
    newArgs.push(callback);
    fn.apply(null, newArgs);
    return deferred.promise;
  }
};

Add logging for pcall errors


'use strict';

var JsMockito = require('jsmockito').JsMockito;
var JsHamcrest = require('jsmockito/node_modules/jshamcrest').JsHamcrest;

var mochito = {
  installTo: function(target) {
    JsMockito.Integration.importTo(target);
    JsHamcrest.Integration.copyMembers(target);
  }
};
mochito.installTo(mochito);

module.exports = mochito;

'use strict';

var JsMockito = require('jsmockito').JsMockito;
// NOTE: the line below only works if you are using NPM 3, and even then it might fail!
var JsHamcrest = require('jshamcrest').JsHamcrest;

var mochito = {
  installTo: function(target) {
    JsMockito.Integration.importTo(target);
    JsHamcrest.Integration.copyMembers(target);
  }
};
mochito.installTo(mochito);

module.exports = mochito;

Update to work in NPM 3


/**
 * @module src/logger_test
 */
'use strict'

const test = require('unit.js')

const logger = require('./logger')

const sinon = test.sinon

describe('src/logger', () => {

  it('load', () => {
    test.function(logger)
  })

  describe('logger function', () => {
    it('should return a child bunyan logger', () => {
      const log = logger(__filename)
      test.object(log).match({
        info: sinon.match.func
      })
    })
  })
})

Add unit tests for 100% coverage logger module


const string = require('../../src/util/string.js');

describe('string', () => {
  it('exports string', () => {
    expect(string).toBeDefined();
  });

  describe('mapAscii', () => {
    it('should return same result for no-ops', () => {
      const before = string.mapAscii('', '');
      const after = string.mapAscii('', '');
      expect(before).toEqual(after);
    });

    it('should return different results given input', () => {
      const before = string.mapAscii('b', 'a');
      const after = string.mapAscii('a', 'b');
      expect(before).not.toEqual(after);
    });
  });

  describe('translate', () => {
    const NO_OP = string.mapAscii('', '');
    const LEET = string.mapAscii('let', '137');

    it('should be a no-op for empty strings', () => {
      expect(string.translate('', '', '')).toEqual('');
    });

    it('should be a no-op for empty translations', () => {
      expect(string.translate('example', NO_OP)).toEqual('example');
    });

    it('should translate characters', () => {
      expect(string.translate('leet', LEET)).toEqual('1337');
    });
  });
});

const string = require('../../src/util/string.js');

describe('string', () => {
  it('exports string', () => {
    expect(string).toBeDefined();
  });

  describe('mapAscii', () => {
    it('should return same result for no-ops', () => {
      const before = string.mapAscii('', '');
      const after = string.mapAscii('', '');
      expect(before).toEqual(after);
    });

    it('should return different results given input', () => {
      const before = string.mapAscii('b', 'a');
      const after = string.mapAscii('a', 'b');
      expect(before).not.toEqual(after);
    });
  });

  describe('translate', () => {
    const NO_OP = string.mapAscii('', '');
    const LEET = string.mapAscii('let', '137');

    it('should be a no-op for empty strings', () => {
      expect(string.translate('', NO_OP)).toEqual('');
    });

    it('should be a no-op for empty translations', () => {
      expect(string.translate('example', NO_OP)).toEqual('example');
    });

    it('should translate characters', () => {
      expect(string.translate('leet', LEET)).toEqual('1337');
    });
  });
});

Fix no-op test for string library.


/* eslint-disable no-console */

import AccessTokenRetriever from './access-token-retriever';
import ConfigurationProvider from './configuration-provider';
import WebServer from './web-server';
import WindowsGraphUsersRetriever from './windows-graph-users-retriever';

export default class Main {
  constructor() {
    this.configuration = new ConfigurationProvider().getConfiguration();

    this.directoryItems = [];
    this.isRefreshing = false;
  }

  start() {
    return this.refreshData()
      .then(() => new WebServer(this.directoryItems, () => this.refreshData()).start())
      .catch(err => {
        console.error(err);
        process.exit(-1); // eslint-disable-line no-process-exit, no-magic-numbers
      });
  }

  refreshData() {
    if (this.isRefreshing) {
      return Promise.resolve(true);
    }

    this.isRefreshing = true;

    return new AccessTokenRetriever()
      .getAccessToken(
        this.configuration.endpointId,
        this.configuration.clientId,
        this.configuration.clientSecret)
      .then(accessToken => new WindowsGraphUsersRetriever()
        .getUsers(this.configuration.endpointId, accessToken))
      .then(users => {
        this.directoryItems = users;
        this.isRefreshing = false;
      })
      .catch(x => {
        this.isRefreshing = false;
        return x;
      });
  }
}

/* eslint-disable no-console */

import AccessTokenRetriever from './access-token-retriever';
import ConfigurationProvider from './configuration-provider';
import WebServer from './web-server';
import WindowsGraphUsersRetriever from './windows-graph-users-retriever';
import RandomUsersRetriever from './random-users-retriever';

export default class Main {
  constructor() {
    this.configuration = new ConfigurationProvider().getConfiguration();

    this.directoryItems = [];
    this.isRefreshing = false;
  }

  start() {
    return this.refreshData()
      .then(() => new WebServer(this.directoryItems, () => this.refreshData()).start())
      .catch(err => {
        console.error(err);
        process.exit(-1); // eslint-disable-line no-process-exit, no-magic-numbers
      });
  }

  refreshData() {
    if (!this.configuration.endpointId) {
      console.log('process.env.ENDPOINT_ID is not set, using RandomUsersRetriever ...');

      return new RandomUsersRetriever()
        .getUsers(100, 4) // eslint-disable-line no-magic-numbers
        .then(users => {
          this.directoryItems = users;
        });
    }

    if (this.isRefreshing) {
      return Promise.resolve(true);
    }

    this.isRefreshing = true;

    return new AccessTokenRetriever()
      .getAccessToken(
        this.configuration.endpointId,
        this.configuration.clientId,
        this.configuration.clientSecret)
      .then(accessToken => new WindowsGraphUsersRetriever()
        .getUsers(this.configuration.endpointId, accessToken))
      .then(users => {
        this.directoryItems = users;
        this.isRefreshing = false;
      })
      .catch(x => {
        this.isRefreshing = false;
        return x;
      });
  }
}

Use RandomUsersRetriever when ENDPOINT_ID is not set


import React, { PropTypes } from 'react';
import cx from 'classnames';
import Background from './background';
import Icon from './icon';
import Mask from './mask';
import { keyFor } from './networks';
import { socialIcon, socialContainer, socialSvg } from './styles';

function getNetworkKey(props) {
  return props.network || keyFor(props.url);
}

function SocialIcon(props) {
  const { url, network, color, className, ...rest } = props;
  const networkKey = getNetworkKey({ url, network });

  return (
    <a {...rest}
       href={url}
       target="_blank"
       rel="noopener"
       className={cx('social-icon', className)}
       style={{ ...socialIcon, ...props.style }}>
      <div className="social-container" style={socialContainer} >
        <svg className="social-svg" style={socialSvg} viewBox="0 0 64 64">
          <Background />
          <Icon networkKey={networkKey} />
          <Mask networkKey={networkKey} color={color} />
        </svg>
      </div>
    </a>
  );
}

SocialIcon.propTypes = {
  className: PropTypes.string,
  color: PropTypes.string,
  network: PropTypes.string,
  url: PropTypes.string,
};

export default SocialIcon;

import React, { PropTypes } from 'react';
import cx from 'classnames';
import Background from './background';
import Icon from './icon';
import Mask from './mask';
import { keyFor } from './networks';
import { socialIcon, socialContainer, socialSvg } from './styles';

function getNetworkKey(props) {
  return props.network || keyFor(props.url);
}

function SocialIcon(props) {
  const { url, network, color, className, label, ...rest } = props;
  const networkKey = getNetworkKey({ url, network });

  return (
    <a {...rest}
       href={url}
       target="_blank"
       rel="noopener"
       className={cx('social-icon', className)}
       style={{ ...socialIcon, ...props.style }}
       aria-label={label}>
      <div className="social-container" style={socialContainer} >
        <svg className="social-svg" style={socialSvg} viewBox="0 0 64 64">
          <Background />
          <Icon networkKey={networkKey} />
          <Mask networkKey={networkKey} color={color} />
        </svg>
      </div>
    </a>
  );
}

SocialIcon.propTypes = {
  className: PropTypes.string,
  color: PropTypes.string,
  label: PropTypes.string,
  network: PropTypes.string,
  url: PropTypes.string,
};

export default SocialIcon;

Add support for label prop


import { createStore, applyMiddleware, compose } from 'redux';
import createHistory from 'history/createHashHistory';
import { routerMiddleware } from 'connected-react-router';
import createRootReducer from 'reducers/root';
import gitHubAPIMiddleware from 'middlewares/gitHubAPI';

export const history = createHistory();

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const middlewares = [routerMiddleware(history), gitHubAPIMiddleware];

if (process.env.NODE_ENV === 'development') {
  const freeze = require('redux-freeze');

  // const { createLogger } = require('redux-logger');
  // const logger = createLogger({ collapsed: true });

  const middlewaresToAdd = [
    freeze
    // logger
  ];

  middlewares.push(...middlewaresToAdd);
}

const store = createStore(
  createRootReducer(history),
  composeEnhancers(applyMiddleware(...middlewares))
);

export default store;

import { createStore, applyMiddleware, compose } from 'redux';
import { createHashHistory } from 'history';
import { routerMiddleware } from 'connected-react-router';
import createRootReducer from 'reducers/root';
import gitHubAPIMiddleware from 'middlewares/gitHubAPI';

export const history = createHashHistory();

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const middlewares = [routerMiddleware(history), gitHubAPIMiddleware];

if (process.env.NODE_ENV === 'development') {
  const freeze = require('redux-freeze');

  // const { createLogger } = require('redux-logger');
  // const logger = createLogger({ collapsed: true });

  const middlewaresToAdd = [
    freeze
    // logger
  ];

  middlewares.push(...middlewaresToAdd);
}

const store = createStore(
  createRootReducer(history),
  composeEnhancers(applyMiddleware(...middlewares))
);

export default store;

FIX (router): use proper import instead of depricated


const config = require('../../../knexfile').web
const knex = require('knex')(config)
module.exports = function (id, type) {
  var table = "team_caseload_overview"
  var whereObject = {}
  if (id !== undefined) {
    whereObject.id = id
  }

  return knex(table)
    .where(whereObject)
    .select('name',
            'gradeCode',
            'untiered',
            'd2',
            'd1',
            'c2',
            'c1',
            'b2',
            'b1',
            'a',
            'totalCases')
    .then(function (results) {
      return results
    })
}

const config = require('../../../knexfile').web
const knex = require('knex')(config)
module.exports = function (id, type) {
  var table = "team_caseload_overview"
  var whereObject = {}
  if (id !== undefined) {
    whereObject.id = id
  }

  return knex(table)
    .where(whereObject)
    .select('name',
            'grade_code as gradeCode',
            'untiered',
            'd2',
            'd1',
            'c2',
            'c1',
            'b2',
            'b1',
            'a',
            'total_cases as totalCases')
    .then(function (results) {
      return results
    })
}

Change the column name to follow the standard naming convention.


var markup = null;
$(function() {
    var markupTools = MarkupTools('.button-bar > div');
    markup = Markup('#pdf-markup', markupTools);

    $(markup).on('update add', function() {
       $('#result').text(JSON.stringify(markup.Serialize()));
    });

    $(markupTools).on('browse', function(e, settings) {
        new Browser(settings);
    });

    $('.button-bar > ul').find('a').each(function() {
        var pluginName = $(this).data('plugin');
        var plugin = window[pluginName];

        markup.AddPlugin(plugin);
        $(this).on('click', function(){ window[$(this).data('plugin')].New(markup); });
    });
});

var markup = null;
$(function() {
    var markupTools = MarkupTools('.button-bar > div');
    markup = Markup('#pdf-markup', markupTools);

    $(markup).on('update add', function() {
       $('#result').text(JSON.stringify(markup.Serialize()));
    });

    $(markupTools).on('browse', function(e, settings) {
        new Browser(settings);
    });

    $('.button-bar > ul').find('a').each(function() {
        var pluginName = $(this).data('plugin');
        var plugin = window[pluginName];

        markup.AddPlugin(plugin);
        $(this).on('click', function(){ window[$(this).data('plugin')].New(markup); });
    });

    markup.Load([{"type":"text","text":"Invoice","x":94,"y":2,"color":"#00000","size":"20","bold":"1"},{"type":"image","source":"example.png","x":47,"y":50,"width":"129"}]);
});

Add example of using the Load method on the Markup object


import React from 'react'
import { applyMiddleware, createStore, compose } from 'redux'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { applyRouterMiddleware, browserHistory, Router } from 'react-router'
import { ReduxAsyncConnect } from 'redux-connect'
import { persistState } from 'redux-devtools'
import { routerMiddleware, syncHistoryWithStore } from 'react-router-redux'
import useScroll from 'react-router-scroll'

import PromiseMiddleware from './middlewares/PromiseMiddleware'
import reducers from './reducers'
import routes from './routes'

const initialState = decodeURIComponent(window.__INITIAL_STATE__)
const store = createStore(
  reducers,
  initialState,
  compose(
    applyMiddleware(
      PromiseMiddleware,
      routerMiddleware(browserHistory)
    ),
    window.devToolsExtension && window.devToolsExtension(),
    persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/))
  ),
)

const history = syncHistoryWithStore(browserHistory, store)

render(
  <Provider store={ store }>
    <Router render={ (renderProps) => {
      return (
        <ReduxAsyncConnect { ...renderProps }
          render={ applyRouterMiddleware(useScroll()) } />
      )
    } } history={ history }>
      { routes(store) }
    </Router>
  </Provider>,
  document.getElementById('root')
)

import React from 'react'
import { applyMiddleware, createStore, compose } from 'redux'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { applyRouterMiddleware, browserHistory, Router } from 'react-router'
import { ReduxAsyncConnect } from 'redux-connect'
import { persistState } from 'redux-devtools'
import { routerMiddleware, syncHistoryWithStore } from 'react-router-redux'
import useScroll from 'react-router-scroll'

import PromiseMiddleware from './middlewares/PromiseMiddleware'
import reducers from './reducers'
import routes from './routes'

const initialState = decodeURIComponent(window.__INITIAL_STATE__)
const store = createStore(
  reducers,
  initialState,
  // compose(
    applyMiddleware(
      PromiseMiddleware,
      routerMiddleware(browserHistory)
    ),
    // (window.devToolsExtension
    //   ? window.devToolsExtension()
    //   : null),
  //   persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/))
  // ),
)

const history = syncHistoryWithStore(browserHistory, store)

render(
  <Provider store={ store }>
    <Router render={ (renderProps) => {
      return (
        <ReduxAsyncConnect { ...renderProps }
          render={ applyRouterMiddleware(useScroll()) } />
      )
    } } history={ history }>
      { routes(store) }
    </Router>
  </Provider>,
  document.getElementById('root')
)

Disable redux devtools for fixing invalid object on mobile browser


var hits = 0;

function setup() {
    hits = 0;
}

function hit() {
    hits++;
    console.log(hits);
}

function showAll() {
    $('.hole').flip(true);
}

function hideAll() {
    $('.hole').flip({
        trigger: 'manual'
    });
}

function randomTimeout() {
    return Math.random() * 5000 + 2500;
}

function won() {
    console.log('You won!');
}

function lost() {
    console.log('You lost!');
}

var hits = 0;

function setup() {
    hits = 0;
}

function hit() {
    hits++;
}

function showAll() {
    $('.hole').flip(true);
}

function hideAll() {
    $('.hole').flip({
        trigger: 'manual'
    });
}

function randomTimeout() {
    return Math.random() * 5000 + 2500;
}

function won() {
    alert('You won!');
}

function lost() {
    alert('You lost!');
}

Remove debugging statements from example.


(function (define) { 'use strict';
define(function (require) {

  var _ = require('lodash');

  function DSL() {
    this.matches = [];
  }

  _.extend(DSL.prototype, {
    route: function (name, options, callback) {
      if (arguments.length === 2 && typeof options === 'function') {
        callback = options;
        options = {};
      }

      if (arguments.length === 1) {
        options = {};
      }

      if (typeof options.path !== 'string') {
        var parts = name.split(".");
        options.path = parts[parts.length - 1];
      }

      if (callback) {
        var routes = DSL.map(callback);
        this.push(options.path, name, routes);
      } else {
        this.push(options.path, name, null);
      }
    },

    push: function (url, name, routes) {
      this.matches.push({
        path: url,
        name: name,
        routes: routes
      });
    },

    generate: function () {
      return this.matches;
    }
  });

  DSL.map = function (callback) {
    var dsl = new DSL();
    callback.call(dsl);
    return dsl.generate();
  };

  return DSL;

});
})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); });

(function (define) { 'use strict';
define(function (require) {

  var _ = require('lodash');

  function DSL(ancestors) {
    this.ancestors = ancestors;
    this.matches = [];
  }

  _.extend(DSL.prototype, {
    route: function (name, options, callback) {
      if (arguments.length === 2 && typeof options === 'function') {
        callback = options;
        options = {};
      }

      if (arguments.length === 1) {
        options = {};
      }

      if (typeof options.path !== 'string') {
        var parts = name.split(".");
        options.path = parts[parts.length - 1];
      }

      if (callback) {
        var routes = DSL.map(callback, this.ancestors.concat(name));
        this.push(options.path, name, routes);
      } else {
        this.push(options.path, name, null);
      }
    },

    push: function (url, name, routes) {
      this.matches.push({
        path: url,
        name: name,
        routes: routes,
        ancestors: this.ancestors
      });
    },

    generate: function () {
      return this.matches;
    }
  });

  DSL.map = function (callback, ancestors) {
    var dsl = new DSL(ancestors || []);
    callback.call(dsl);
    return dsl.generate();
  };

  return DSL;

});
})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); });

Add information about the ancestors to each route descriptor


var config = module.exports;

config["specs"] = {
  rootPath: "../",
  environment: "browser",
  sources: [
    "lib/*.js"
  ],
  tests: [
    "spec/*.spec.js"
  ],
}

/*global module: true */
var config = module.exports;

config.specs = {
    rootPath: "../",
    environment: "browser",
    sources: [
        // lib/helpers.js needs to be loaded first
        "lib/helpers.js",
        "lib/EnglishParser.js",
        "lib/interpreters.js"
    ],
    specs: [
        "spec/*.spec.js"
    ]
};

Fix loading error of helpers.js


import React from "react/addons";

import Resolver from "./Resolver";

class ResolverContext extends React.Component {
  getChildContext() {
    const resolver = this.props.resolver || this.context.resolver;

    return { resolver };
  }

  render() {
    return React.addons.cloneWithProps(this.props.element, this.props);
  }
}

ResolverContext.childContextTypes = {
  resolver: React.PropTypes.any,
};

ResolverContext.contextTypes = {
  resolver: React.PropTypes.any,
};

ResolverContext.propTypes = {
  resolver: React.PropTypes.any,
};

export default ResolverContext;

import React from "react";

import Resolver from "./Resolver";

class ResolverContext extends React.Component {
  getChildContext() {
    const resolver = this.props.resolver || this.context.resolver;

    return { resolver };
  }

  render() {
    return React.cloneElement(this.props.element);
  }
}

ResolverContext.childContextTypes = {
  resolver: React.PropTypes.any,
};

ResolverContext.contextTypes = {
  resolver: React.PropTypes.any,
};

ResolverContext.propTypes = {
  resolver: React.PropTypes.any,
};

export default ResolverContext;

Switch from React.addons.cloneWithProps to React.cloneElement


/* globals requirejs, require */
(function() {
define("ember-cli/test-loader",
  [],
  function() {
    "use strict";

    var TestLoader = function() {
    };

    TestLoader.prototype = {
      shouldLoadModule: function(moduleName) {
        return (moduleName.match(/[-_]test$/));
      },

      loadModules: function() {
        var moduleName;

        for (moduleName in requirejs.entries) {
          if (this.shouldLoadModule(moduleName)) {
            this.require(moduleName);
          }
        }
      }
    };

    TestLoader.prototype.require = function(moduleName) {
      try {
        require(moduleName);
      } catch(e) {
        this.moduleLoadFailure(moduleName, e);
      }
    };

    TestLoader.prototype.moduleLoadFailure = function(moduleName, error) {
      console.error('Error loading: ' + moduleName, error.stack);
    };

    TestLoader.load = function() {
      new TestLoader().loadModules();
    };

    return {
      'default': TestLoader
    }
  }
);
})();

/* globals requirejs, require */
(function() {
define("ember-cli/test-loader",
  [],
  function() {
    "use strict";

    var TestLoader = function() {
    };

    TestLoader.prototype = {
      shouldLoadModule: function(moduleName) {
        return moduleName.match(/\/.*[-_]test$/);
      },

      loadModules: function() {
        var moduleName;

        for (moduleName in requirejs.entries) {
          if (this.shouldLoadModule(moduleName)) {
            this.require(moduleName);
          }
        }
      }
    };

    TestLoader.prototype.require = function(moduleName) {
      try {
        require(moduleName);
      } catch(e) {
        this.moduleLoadFailure(moduleName, e);
      }
    };

    TestLoader.prototype.moduleLoadFailure = function(moduleName, error) {
      console.error('Error loading: ' + moduleName, error.stack);
    };

    TestLoader.load = function() {
      new TestLoader().loadModules();
    };

    return {
      'default': TestLoader
    }
  }
);
})();

Exclude the project name when requiring files


'use strict';

var utils = require('../../').utils;
var assert = require('chai').assert;

suite('Utils', () => {
  test('create a random hex string', () => {
    const test1 = utils.getRandomHexString();
    assert.isString(test1);
    assert.equal(test1, test1.match(/^[0-9A-F]{8}$/)[0]);

    const test2 = utils.getRandomHexString(16);
    assert.isString(test2);
    assert.equal(test2, test2.match(/^[0-9A-F]{16}$/)[0]);
  });
});

'use strict';

var utils = require('../../').utils;
var assert = require('chai').assert;

suite('Utils', () => {
  test('create a random hex string', () => {
    const test1 = utils.getRandomHexString();
    assert.isString(test1);
    assert.equal(test1, test1.match(/^[0-9A-F]{8}$/)[0]);

    const test2 = utils.getRandomHexString(16);
    assert.isString(test2);
    assert.equal(test2, test2.match(/^[0-9A-F]{16}$/)[0]);
  });

  test('getting host ips', () => {
    const hostIPs = utils.getHostIPs();
    assert.isArray(hostIPs);
    hostIPs.forEach((ip) => {
      assert.isString(ip, 'IPs are given as');
      assert.isTrue(ip.indexOf('.') >= 0 || ip.indexOf(':') >= 0, 'IP format');
    });
  });
});

Enhance test coverage of util helpers


'use strict';

angular
    .module('lmServices')
    .constant('Admin', (/((; )|^)admin=true(;|$)/).test(document.cookie));

'use strict';

// This "admin" cookie is not intended to be secure. All it does is display a few convenience
// buttons for quick access to the admin interface. Access to the admin interface is unrestricted;
// the admin interface merely displays the same data as the normal web interface, but in an
// editable manner. Authentication happens server-side when the user attempts to save changes.
angular
    .module('lmServices')
    .constant('Admin', (/((; )|^)admin=true(;|$)/).test(document.cookie));

Add comments explaining how the admin interface works


describe("$.create", function() {
	it("creates an element and sets properties on it", function() {
		var element = $.create("article", {
			className: "wide"
		});

		expect(element).to.be.an.instanceOf(Element);
		expect(element.className).to.be.equal("wide");
		expect(element.tagName).to.be.equal("ARTICLE");
	});

	it("returns a text node when called with only a string", function() {
		var text = $.create("Lorem Ipsom dolor sit amet");

		expect(text).to.be.an.instanceOf(Text);
	});

	describe("passed only an object", function() {
		it("creates an element and sets the properties on it", function() {
			var element = $.create({ tag: "span" });

			expect(element).to.be.an.instanceOf(Element);
			expect(element.tagName).to.be.equal("SPAN");
		});

		it("creates a div by default", function() {
			var element = $.create({});

			expect(element.tagName).to.be.equal("DIV");
		});
	});
});

describe("$.create", function() {
	it("creates an element and sets properties on it", function() {
		var element = $.create("article", {
			className: "wide"
		});

		expect(element).to.be.an.instanceOf(Element);
		expect(element.className).to.be.equal("wide");
		expect(element.tagName).to.be.equal("ARTICLE");
	});

	it("returns a text node when called with only a string", function() {
		var text = $.create("Lorem Ipsom dolor sit amet");

		expect(text).to.be.an.instanceOf(Text);
	});

	describe("passed only an object", function() {
		it("creates an element and sets the properties on it", function() {
			var element = $.create({ tag: "span" });

			expect(element).to.be.an.instanceOf(Element);
			expect(element.tagName).to.be.equal("SPAN");
		});

		it("creates a div by default", function() {
			var element = $.create({});

			expect(element.tagName).to.be.equal("DIV");
		});
	});

	it("creates a div when there are no arguments", function() {
		var element = $.create();

		expect(element.tagName).to.be.equal("DIV");
	});
});

Test that $.create makes a div by default


const React = require('react');
const classNames = require('classnames');

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

const Spinner = React.createClass({
    propTypes: {
        centered: React.PropTypes.bool,
        size:     React.PropTypes.oneOf(SIZES)
    },

    getDefaultProps: function() {
        return {
            centered: true,
            size: 'md'
        };
    },

    render: function() {
        let className = classNames('gb-spinner', 'spinner-' + this.props.size, {
            'spinner-inverse':  this.props.inverse,
            'spinner-centered': this.props.centered
        });

        return

;
    }
});

/**
 * Block div representing a loading area
 */
const SpinnerSlate = React.createClass({
    render: function() {
        return

            <Spinner {...this.props} />

;
    }
});

module.exports = Spinner;
module.exports.Slate = SpinnerSlate;

const React = require('react');
const classNames = require('classnames');

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

const Spinner = React.createClass({
    propTypes: {
        centered: React.PropTypes.bool,
        inverse:  React.PropTypes.bool,
        size:     React.PropTypes.oneOf(SIZES)
    },

    getDefaultProps: function() {
        return {
            centered: true,
            size: 'md'
        };
    },

    render: function() {
        let className = classNames('gb-spinner', 'spinner-' + this.props.size, {
            'spinner-inverse':  this.props.inverse,
            'spinner-centered': this.props.centered
        });

        return

;
    }
});

/**
 * Block div representing a loading area
 */
const SpinnerSlate = React.createClass({
    render: function() {
        return

            <Spinner {...this.props} />

;
    }
});

module.exports = Spinner;
module.exports.Slate = SpinnerSlate;

Add prop inverse for spinner


import Command from '../../ui/Command'
import insertInlineNode from '../../model/transform/insertInlineNode'

class InsertInlineNodeCommand extends Command {
  constructor(...args) {
    super(...args)

    if (!this.params.nodeType) {
      throw new Error('Every AnnotationCommand must have a nodeType')
    }
  }

  getCommandState(props, context) {
    let sel = context.documentSession.getSelection()
    let newState = {
      disabled: !sel.isPropertySelection(),
      active: false
    }
    return newState
  }

  execute(props, context) {
    let state = this.getCommandState(props, context)
    if (state.disabled) return
    let surface = context.surface || context.surfaceManager.getFocusedSurface()
    if (surface) {
      surface.transaction(function(tx, args) {
        return this.insertInlineNode(tx, args)
      }.bind(this))
    }
    return true
  }

  insertInlineNode(tx, args) {
    args.node = this.createNodeData(tx, args)
    return insertInlineNode(tx, args)
  }

  createNodeData(tx, args) { // eslint-disable-line
    return {
      type: this.constructor.type
    }
  }

  _getAnnotationsForSelection(props) {
    return props.selectionState.getAnnotationsForType(this.params.nodeType)
  }

}

export default InsertInlineNodeCommand

import Command from '../../ui/Command'
import insertInlineNode from '../../model/transform/insertInlineNode'

class InsertInlineNodeCommand extends Command {
  constructor(...args) {
    super(...args)

    if (!this.params.nodeType) {
      throw new Error('Every AnnotationCommand must have a nodeType')
    }
  }

  getCommandState(props, context) {
    let sel = context.documentSession.getSelection()
    let newState = {
      disabled: !sel.isPropertySelection(),
      active: false
    }
    return newState
  }

  execute(props, context) {
    let state = this.getCommandState(props, context)
    if (state.disabled) return
    let surface = context.surface || context.surfaceManager.getFocusedSurface()
    if (surface) {
      surface.transaction(function(tx, args) {
        return this.insertInlineNode(tx, args)
      }.bind(this))
    }
    return true
  }

  insertInlineNode(tx, args) {
    args.node = this.createNodeData(tx, args)
    return insertInlineNode(tx, args)
  }

  createNodeData(tx, args) { // eslint-disable-line
    return {
      type: this.params.nodeType
    }
  }

  _getAnnotationsForSelection(props) {
    return props.selectionState.getAnnotationsForType(this.params.nodeType)
  }

}

export default InsertInlineNodeCommand

Use nodeType from params instead of static prop.


var fs = require('fs')
var assert = require('assert')
var rmrf = require('rimraf')
var Writer = require('../src/writer')

describe('Writer', function() {

  this.timeout(5000)

  var tempPath = __dirname + '/../tmp'
  var filePath = tempPath + '/../tmp/test.txt'

  beforeEach(function() {
    rmrf.sync(tempPath)
    fs.mkdirSync(tempPath)
  })

  it('always writes data from the latest call', function(done) {
    var writer = new Writer(filePath)

    // 1M characters
    var data = ''
    for (var i = 0; i <= 1000 * 1000; i++) {
      data += 'x'
    }

    for (var i = 0; i <= 1000 * 1000; i++) {
      writer.write(data + i)
    }

    setTimeout(function() {
      assert.equal(fs.readFileSync(filePath, 'utf-8'), data + (i - 1))
      done()
    }, 1000)
  })

})

var fs = require('fs')
var assert = require('assert')
var rmrf = require('rimraf')
var Writer = require('../src/writer')

describe('Writer', function() {

  this.timeout(10000)

  var tempPath = __dirname + '/../tmp'
  var filePath = tempPath + '/../tmp/test.txt'

  beforeEach(function() {
    rmrf.sync(tempPath)
    fs.mkdirSync(tempPath)
  })

  it('always writes data from the latest call', function(done) {
    var writer = new Writer(filePath)

    // 1M characters
    var data = ''
    for (var i = 0; i <= 1000 * 1000; i++) {
      data += 'x'
    }

    for (var i = 0; i <= 1000 * 1000; i++) {
      writer.write(data + i)
    }

    setTimeout(function() {
      assert.equal(fs.readFileSync(filePath, 'utf-8'), data + (i - 1))
      done()
    }, 1000)
  })

})

Update test (trying to fix Travis error)


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

import plugin from '../src';

import assert from 'assert';
import fs from 'fs';
import path from 'path';

const rules = fs.readdirSync(path.resolve(__dirname, '../src/rules/'))
  .map(f => path.basename(f, '.js'));

describe('all rule files should be exported by the plugin', () => {
  rules.forEach(ruleName => {
    it(`should export ${ruleName}`, () => {
      assert.equal(
        plugin.rules[ruleName],
        require(path.join('../src/rules', ruleName))
      );
    });
  });
});

describe('configurations', function() {
  it('should export a \'recommended\' configuration', function() {
    assert(plugin.configs.recommended);
  });
});

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

import plugin from '../src';

import assert from 'assert';
import fs from 'fs';
import path from 'path';

const rules = fs.readdirSync(path.resolve(__dirname, '../src/rules/'))
  .map(f => path.basename(f, '.js'));

describe('all rule files should be exported by the plugin', () => {
  rules.forEach(ruleName => {
    it(`should export ${ruleName}`, () => {
      assert.equal(
        plugin.rules[ruleName],
        require(path.join('../src/rules', ruleName))
      );
    });
  });
});

describe('configurations', () => {
  it('should export a \'recommended\' configuration', () => {
    assert(plugin.configs.recommended);
  });
});

Fix test to pass lint.


// karma.conf.js
module.exports = function(config) {
  config.set({
    basePath : '../',
    frameworks: ['jasmine'],
    files : [
      'public/bower_components/jquery/dist/jquery.js',
      'public/bower_components/angular/angular.js',
      'public/bower_components/angular-cookies/angular-cookies.js',
      'public/bower_components/angular-route/angular-route.js',
      'public/bower_components/angular-mocks/angular-mocks.js',
      'public/bower_components/angular-socket.io-mock/angular-socket.io-mock.js',
      'public/bower_components/toastr/toastr.min.js',
      'public/js/**/*.js',
      'test/unit/**/*.js'
    ],
    reporters: ['junit', 'coverage'],
    coverageReporter: {
      type: 'html',
      dir: 'build/coverage/'
    },
    preprocessors: {
      'public/js/**/*.js': ['coverage']
    },
    junitReporter: {
      outputFile: 'build/unit/test-results.xml'
    },
    port: 8876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['PhantomJS'],
    captureTimeout: 60000,
    singleRun: true
  });
};

// karma.conf.js
module.exports = function(config) {
  config.set({
    basePath : '../',
    frameworks: ['jasmine'],
    files : [
      'public/bower_components/jquery/dist/jquery.js',
      'public/bower_components/angular/angular.js',
      'public/bower_components/angular-cookies/angular-cookies.js',
      'public/bower_components/angular-route/angular-route.js',
      'public/bower_components/angular-mocks/angular-mocks.js',
      'public/bower_components/angular-socket.io-mock/angular-socket.io-mock.js',
      'public/bower_components/toastr/toastr.min.js',
      'public/js/**/*.js',
      'test/unit/**/*.js'
    ],
    reporters: ['junit', 'coverage', 'dots'],
    coverageReporter: {
      type: 'html',
      dir: 'build/coverage/'
    },
    preprocessors: {
      'public/js/**/*.js': ['coverage']
    },
    junitReporter: {
      outputFile: 'build/unit/test-results.xml'
    },
    port: 8876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['PhantomJS'],
    captureTimeout: 60000,
    singleRun: true
  });
};

Add dots reporting to karma


const { expect } = require('chai');
const cheerio = require('cheerio');
const request = require('supertest');
const app = require('../app');

describe('Pages', function() {
  let req;

  describe('/', function() {
    beforeEach(function() {
      req = request(app).get('/')
    });

    it('returns 200 OK', function(done) {
      req.expect(200, done);
    });

    it('renders template', function (done) {
      req.expect('Content-Type', /text\/html/, done);
    });

    it('has valid title', function (done) {
      req
        .expect((res) => {
          const content = res.text;
          const $ = cheerio.load(content);
          expect($('title').text()).to.eq('Slava Pavlutin');
        })
        .end(done);
    });
  });

  describe('/contact', function() {
    beforeEach(function() {
      req = request(app).get('/contact');
    });

    it('returns 200 OK', function (done) {
      req.expect(200, done);
    });

    it('renders template', function (done) {
      req.expect('Content-Type', /text\/html/, done);
    });

    it('has valid title', function (done) {
      req
        .expect((res) => {
          const content = res.text;
          const $ = cheerio.load(content);
          expect($('title').text()).to.eq('Slava Pavlutin | Contact');
        })
        .end(done);
    });
  });
});

const { expect } = require('chai');
const cheerio = require('cheerio');
const request = require('supertest');
const app = require('../app');

describe('Pages', function() {
  let req;

  describe('/', function() {
    beforeEach(function() {
      req = request(app).get('/');
    });

    it('returns 200 OK', function(done) {
      req.expect(200, done);
    });

    it('renders template', function (done) {
      req.expect('Content-Type', /text\/html/, done);
    });

    it('has valid title', function (done) {
      req
        .expect((res) => {
          const content = res.text;
          const $ = cheerio.load(content);
          expect($('title').text()).to.eq('Slava Pavlutin');
        })
        .end(done);
    });
  });

  describe('/contact', function() {
    beforeEach(function() {
      req = request(app).get('/contact');
    });

    it('returns 200 OK', function (done) {
      req.expect(200, done);
    });

    it('renders template', function (done) {
      req.expect('Content-Type', /text\/html/, done);
    });

    it('has valid title', function (done) {
      req
        .expect((res) => {
          const content = res.text;
          const $ = cheerio.load(content);
          expect($('title').text()).to.eq('Slava Pavlutin | Contact');
        })
        .end(done);
    });
  });
});

Add semicolon in test file


import React from 'react';
import { Route, IndexRoute } from 'react-router';

/* containers */
import { App } from 'components/App';
import { Home } from 'components/Home';
import { DataDisplay } from 'components/DataDisplay';
// import { List } from 'containers/List';

export default (
  <Route path="/" component={App}>
    <IndexRoute component={Home} />
    <Route path="/chat" component={DataDisplay} />
    <Route status={404} path="*" component={Home} />
  </Route>
);

import React from 'react';
import { Route, IndexRoute } from 'react-router';

/* containers */
import { App } from 'components/App';
import { Home } from 'components/Home';
import { About } from 'components/About';
import { DataDisplay } from 'components/DataDisplay';
// import { List } from 'containers/List';

export default (
  <Route path="/" component={App}>
    <IndexRoute component={Home} />
    <Route path="/chat" component={DataDisplay} />
    <Route path="/about" component={About} />
    <Route status={404} path="*" component={Home} />
  </Route>
);

Add route path for About page


import bodyParser from "body-parser"
import cors from "cors"
import express from "express"
import graphqlHTTP from "express-graphql"
import { graphqlExpress, graphiqlExpress } from "graphql-server-express"
import models from "./db"
import schema from "./graphql"

// Constants
const CONFIG = require("./settings.yaml")

const app = express()
app.use(cors())
app.use(
  "/graphql",
  bodyParser.json({
    limit: CONFIG.max_api_call_size
  }),
  graphqlExpress(req => {
    return {
      schema,
      context: {
        loaders: {},
        models
      }
    }
  })
)

app.use(
  "/graphiql",
  graphiqlExpress({
    endpointURL: "/graphql"
  })
)

export { app, CONFIG }

import bodyParser from "body-parser"
import cors from "cors"
import express from "express"
import graphqlHTTP from "express-graphql"
import { graphqlExpress, graphiqlExpress } from "graphql-server-express"
import models, { sequelize } from "./db"
import schema from "./graphql"

// Constants
const CONFIG = require("./settings.yaml")

const app = express()
app.use(cors())
app.use(
  "/graphql",
  bodyParser.json({
    limit: CONFIG.max_api_call_size
  }),
  graphqlExpress(req => {
    return {
      schema,
      context: {
        loaders: {},
        models,
        sequelize
      }
    }
  })
)

app.use(
  "/graphiql",
  graphiqlExpress({
    endpointURL: "/graphql"
  })
)

export { app, CONFIG }

Add sequelize to context of GraphQL queries


import test from 'ava';
import plugin from '../postcss-rem-phi-units'
import postcss from 'postcss';
import { readFileSync } from 'fs';

test('Settingless units', async t => {
	postcss(plugin)
		.process(readFileSync('in.css'))
		.then(result =>
			t.same(result.css, readFileSync('expected.css', 'utf8'))
		);
});

import test from 'ava';
import plugin from '../postcss-rem-phi-units';
import postcss from 'postcss';
import { readFileSync } from 'fs';

test('Settingless units', async t => {
	postcss(plugin)
		.process(readFileSync('in.css'))
		.then(result =>
			t.same(result.css, readFileSync('expected.css', 'utf8'));
		);
});

Convert test js to happiness


Package.describe({
  summary: 'Web tracing framework wrapper for Meteor',
  version: "0.0.2",
  git: 'git@github.com:eluck/meteor-web-tracing-framework.git'
});

Npm.depends({
  "tracing-framework": "2014.8.18-1"
});

Package.on_use(function (api) {
  api.add_files('import.js', ['server']);
  api.add_files('web-tracing-framework-shim.js', ['client', 'server']);
  api.add_files('export.js', ['client', 'server']);
  api.export('WTF', ['client', 'server']);
});

Package.describe({
  summary: 'Web tracing framework wrapper for Meteor',
  version: "0.0.2",
  git: 'https://github.com/eluck/meteor-web-tracing-framework.git'
});

Npm.depends({
  "tracing-framework": "2014.8.18-1"
});

Package.on_use(function (api) {
  api.add_files('import.js', ['server']);
  api.add_files('web-tracing-framework-shim.js', ['client', 'server']);
  api.add_files('export.js', ['client', 'server']);
  api.export('WTF', ['client', 'server']);
});

Update path to the repository


var nconf = require('nconf');

// Specifying an env delimiter allows you to override below config when shipping to production server
// by e.g. defining piping__ignore or version variables.
nconf.env('__');

var config = {
  appLocales: ['en', 'fr'],
  defaultLocale: 'en',
  googleAnalyticsId: 'UA-XXXXXXX-X',
  isProduction: process.env.NODE_ENV === 'production',
  piping: {
    // Ignore webpack custom loaders on server. TODO: Reuse index.js config.
    ignore: /(\/\.|~$|\.(css|less|sass|scss|styl))/,
    // Hook ensures server restart on all required deps, even client side.
    // Server restarting invalidates require cache, no more stale html.
    hook: true
  },
  port: process.env.PORT || 8000,
  version: require('../../package').version,
  webpackStylesExtensions: ['css', 'less', 'sass', 'scss', 'styl']
};

// Use above config as a default one
// Multiple other providers are available like loading config from json and more
// Check out nconf docs for fancier examples
nconf.defaults(config);

module.exports = nconf.get();

var nconf = require('nconf');

// Specifying an env delimiter allows you to override below config when shipping to production server
// by e.g. defining piping__ignore or version variables.
nconf.env('__');

var config = {
  appLocales: ['en', 'fr'],
  defaultLocale: 'en',
  googleAnalyticsId: 'UA-XXXXXXX-X',
  isProduction: process.env.NODE_ENV === 'production',
  piping: {
    // Ignore webpack custom loaders on server. TODO: Reuse index.js config.
    ignore: /(\/\.|~$|\.(css|less|sass|scss|styl))/,
    // Hook ensures always fresh server response even for client file change.
    hook: true
  },
  port: process.env.PORT || 8000,
  version: require('../../package').version,
  webpackStylesExtensions: ['css', 'less', 'sass', 'scss', 'styl']
};

// Use above config as a default one
// Multiple other providers are available like loading config from json and more
// Check out nconf docs for fancier examples
nconf.defaults(config);

module.exports = nconf.get();

Update comment about piping hook.


import React from 'react'
import { Segment, Message } from 'semantic-ui-react'
import JSONTree from 'react-json-tree'

type Props = {
  loading: boolean,
  expanded: boolean,
  response: any,
  error: ?Error,
}

const Response = ({ loading, expanded, response, error }: Props) => {
  if (loading) {
    return (
      <Segment loading className='result' />
    )
  } else if (error != null) {
    return (
      <Segment className='result'>
        <Message negative>
          <Message.Header>{error.message}</Message.Header>
          <pre>{error.stack}</pre>
        </Message>
      </Segment>
    )
  } else if (response != null) {
    return (
      <Segment className='result'>
        <JSONTree
          hideRoot
          shouldExpandNode={() => expanded}
          data={response}
        />
      </Segment>
    )
  }

  return null
}

export default Response

import React from 'react'
import { Segment, Tab, Message } from 'semantic-ui-react'
import JSONTree from 'react-json-tree'

type Props = {
  loading: boolean,
  expanded: boolean,
  response: any,
  error: ?Error,
}

const Response = ({ loading, expanded, response, error }: Props) => {
  if (loading) {
    return (
      <Segment loading className='result' />
    )
  } else if (error != null) {
    return (
      <Segment className='result'>
        <Message negative>
          <Message.Header>{error.message}</Message.Header>

{error.stack}

        </Message>
      </Segment>
    )
  } else if (response != null) {
    return (
      <Tab
        className='result'
        panes={[
          {
            menuItem: 'Tree view',
            // eslint-disable-next-line react/display-name
            render: () => (
              <Tab.Pane>
                <JSONTree
                  hideRoot
                  shouldExpandNode={() => expanded}
                  data={response}
                />
              </Tab.Pane>
            )
          },
          {
            menuItem: 'Raw JSON',
            // eslint-disable-next-line react/display-name
            render: () => (
              <Tab.Pane>

{JSON.stringify(response, null, 2)}

              </Tab.Pane>
            )
          },
        ]}
      />
    )
  }

  return null
}

export default Response

Add `Raw JSON` pane to response


var webpack = require('webpack');

module.exports = {
  devtool: 'eval',
  entry: [
    'webpack-dev-server/client?http://localhost:3000',
    'webpack/hot/only-dev-server',
    './scripts/index'
  ],
  output: {
    path: __dirname + '/scripts/',
    filename: 'bundle.js',
    publicPath: '/scripts/'
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoErrorsPlugin()
  ],
  resolve: {
    extensions: ['', '.js']
  },
  module: {
    loaders: [
      { test: /\.js$/, loaders: ['react-hot', 'jsx?harmony'], exclude: /node_modules/ },
    ]
  }
};

var webpack = require('webpack');

module.exports = {
  devtool: 'eval',
  entry: [
    'webpack-dev-server/client?http://localhost:3000',
    'webpack/hot/only-dev-server',
    './scripts/index'
  ],
  output: {
    path: __dirname + '/scripts/',
    filename: 'bundle.js',
    publicPath: '/scripts/'
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoErrorsPlugin()
  ],
  resolve: {
    extensions: ['', '.js', '.jsx']
  },
  module: {
    loaders: [
      { test: /\.jsx?$/, loaders: ['react-hot', 'jsx?harmony'], exclude: /node_modules/ }
    ]
  }
};

Allow jsx extension as some people prefer it


Fix missing id of project in discussion of activities


const Block = require('./block');
const typeToConfig = require('./type-to-config');

class Board {
	constructor() {
		this.height = 3;
		this.width = 3;
		this.blocks = [
			[null, null, null],
			[null, null, null],
			[null, null, null],
		];
	}

	getBlock(x, y) {
		return this.blocks[y][x];
	}

	placeBlock(x, y, type) {
		const config = typeToConfig[type];
		config.x = x;
		config.y = y;
		const block = new Block(config);
		this.blocks[y][x] = block;
	}
}

module.exports = Board;

const Block = require('./block');
const typeToConfig = require('./type-to-config');

class Board {
	constructor() {
		this.height = 3;
		this.width = 3;
		this.blocks = [
			[null, null, null],
			[null, null, null],
			[null, null, null],
		];
		this.inputBlockX = 1;
		this.inputBlockY = 0;
		this.outputBlockX = 1;
		this.outputBlockY = 2;
	}

	get inputBlock() {
		return this.blocks[this.inputBlockY][this.inputBlockX];
	}

	get outputBlock() {
		return this.blocks[this.outputBlockY][this.outputBlockX];
	}

	getBlock(x, y) {
		return this.blocks[y][x];
	}

	placeBlock(x, y, type) {
		const config = typeToConfig[type];
		config.x = x;
		config.y = y;
		const block = new Block(config);
		this.blocks[y][x] = block;
	}
}

module.exports = Board;

Define input and output block information


/* global require, module */

var EmberApp = require('ember-cli/lib/broccoli/ember-app');

var app = new EmberApp({
  'ember-cli-jquery-ui': {
    'theme': 'redmond'
  },
  vendorFiles: {
    'handlebars.js': {
      production:  'bower_components/handlebars/handlebars.js'
    }
  }
});

// Use `app.import` to add additional libraries to the generated
// output files.
//
// If you need to use different assets in different
// environments, specify an object as the first parameter. That
// object's keys should be the environment name and the values
// should be the asset to use in that environment.
//
// If the library that you are including contains AMD or ES6
// modules that you would like to import into your application
// please specify an object with the list of modules as keys
// along with the exports of each module as its value.

app.import('vendor/js/jquery-knob/jquery.knob.js');

module.exports = app.toTree();

/* global require, module */

var EmberApp = require('ember-cli/lib/broccoli/ember-app');

var app = new EmberApp({
  'ember-cli-jquery-ui': {
    'theme': 'redmond'
  },
  'ember-cli-bootstrap-sass': {
    'importBootstrapJS': true
  },
  vendorFiles: {
    'handlebars.js': {
      production:  'bower_components/handlebars/handlebars.js'
    }
  }
});

// Use `app.import` to add additional libraries to the generated
// output files.
//
// If you need to use different assets in different
// environments, specify an object as the first parameter. That
// object's keys should be the environment name and the values
// should be the asset to use in that environment.
//
// If the library that you are including contains AMD or ES6
// modules that you would like to import into your application
// please specify an object with the list of modules as keys
// along with the exports of each module as its value.

app.import('vendor/js/jquery-knob/jquery.knob.js');

module.exports = app.toTree();

Add Bootstrap JavaScript to build


const DrawCard = require('../../drawcard.js');
const EventRegistrar = require('../../eventregistrar.js');
const { CardTypes, Players } = require('../../Constants');

class MagnificentTriumph extends DrawCard {
    setupCardAbilities(ability) {
        this.duelWinnersThisConflict = [];
        this.eventRegistrar = new EventRegistrar(this.game, this);
        this.eventRegistrar.register(['onConflictFinished', 'afterDuel']);
        this.action({
            title: 'Give a character +2/+2',
            condition: () => this.game.isDuringConflict(),
            target: {
                cardType: CardTypes.Character,
                controller: Players.Any,
                cardCondition: card => this.duelWinnersThisConflict.includes(card),
                gameAction: ability.actions.cardLastingEffect({
                    effect: [
                        ability.effects.modifyBothSkills(2),
                        ability.effects.cardCannot({
                            cannot: 'target',
                            restricts: 'opponentsCardEffects'
                        })
                    ]
                })
            },
            effect: 'give {0} +2{1}, +2{2}, and prevent them from being targeted by opponent\'s abilities',
            effectArgs: () => ['military', 'political']
        });
    }

    onConflictFinished() {
        this.duelWinnersThisConflict = [];
    }

    afterDuel(event) {
        if(event.duel.winner) {
            this.duelWinnersThisConflict.push(event.duel.winner);
        }
    }
}

MagnificentTriumph.id = 'magnificent-triumph';

module.exports = MagnificentTriumph;

const DrawCard = require('../../drawcard.js');
const EventRegistrar = require('../../eventregistrar.js');
const { CardTypes, Players } = require('../../Constants');

class MagnificentTriumph extends DrawCard {
    setupCardAbilities(ability) {
        this.duelWinnersThisConflict = [];
        this.eventRegistrar = new EventRegistrar(this.game, this);
        this.eventRegistrar.register(['onConflictFinished', 'afterDuel']);
        this.action({
            title: 'Give a character +2/+2',
            condition: () => this.game.isDuringConflict(),
            target: {
                cardType: CardTypes.Character,
                controller: Players.Any,
                cardCondition: card => this.duelWinnersThisConflict.includes(card),
                gameAction: ability.actions.cardLastingEffect({
                    effect: [
                        ability.effects.modifyBothSkills(2),
                        ability.effects.cardCannot({
                            cannot: 'target',
                            restricts: 'opponentsEvents'
                        })
                    ]
                })
            },
            effect: 'give {0} +2{1}, +2{2}, and prevent them from being targeted by opponent\'s events',
            effectArgs: () => ['military', 'political']
        });
    }

    onConflictFinished() {
        this.duelWinnersThisConflict = [];
    }

    afterDuel(event) {
        if(event.duel.winner) {
            this.duelWinnersThisConflict.push(event.duel.winner);
        }
    }
}

MagnificentTriumph.id = 'magnificent-triumph';

module.exports = MagnificentTriumph;

Fix to target events (not all card effects)


// RPC reply serializer, that should heppen in the end,
// because we pass errors in serialized form too
//

'use strict';

////////////////////////////////////////////////////////////////////////////////

module.exports = function (N) {

  N.wire.after('responder:rpc', { priority: 90 }, function rpc_serialize(env) {

    //
    // Set Content-Type and charset (override existing)
    //

    env.headers['Content-Type'] = 'application/json; charset=UTF-8';

    //
    // Status is always ok - errors are embedded into payload
    //

    env.status = N.io.OK;

    //
    // HEAD requested - no need for real rendering
    //

    if ('HEAD' === env.origin.req.method) {
      env.body = null;
      return;
    }

    //
    // Replace body with serialized payload
    //

    env.body = JSON.stringify({
      version:  N.runtime.version,
      error:    env.err,
      response: env.err ? null : env.response
    });
  });
};

// RPC reply serializer, that should heppen in the end,
// because we pass errors in serialized form too
//

'use strict';

////////////////////////////////////////////////////////////////////////////////

module.exports = function (N) {

  N.wire.after('responder:rpc', { priority: 90 }, function rpc_serialize(env) {

    // Set Content-Type and charset (override existing)
    env.headers['Content-Type'] = 'application/json; charset=UTF-8';

    // Status is always ok - errors are embedded into payload
    env.status = N.io.OK;

    // Expose API path of the executed server method, e.g. 'forum.index'
    env.response.data.head.apiPath = env.method;

    // HEAD requested - no need for real rendering
    if ('HEAD' === env.origin.req.method) {
      env.body = null;
      return;
    }

    // Replace body with serialized payload
    env.body = JSON.stringify({
      version:  N.runtime.version,
      error:    env.err,
      response: env.err ? null : env.response
    });
  });
};

Fix pagination links on the RPC page navigation.


// if you checked "fancy-settings" in extensionizr.com, uncomment this lines

// var settings = new Store("settings", {
//     "sample_setting": "This is how you use Store.js to remember values"
// });

function generateUUID() {
  let id = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
    let r = Math.random()*16|0, v = c === 'x' ? r : (r&0x3|0x8);
    return v.toString(16);
  });
  return id;
}

const defaults = {
  id: generateUUID(),
  username: "test-user",
};

class StorageService {
  constructor() {
    this.storage = chrome.storage.local;
    this.storage.get(defaults, (items) => {
      this.storage.set(items);
    })
  }

  reset() {
    this.storage.clear(() => {
      defaults.id = generateUUID();
      this.set(defaults);
    });
  }

  get(key) {
    return new Promise((resolve) => {
      this.storage.get(key, (item) => {
        resolve(item[key]);
      });
    });
  }

  set(key, value) {
    let data = {};
    data[key] = value;
    this.storage.set(data);
  }

  remove(key) {
    this.storage.remove(key);
  }
}

window.BackgroundStorageService = new StorageService();

// if you checked "fancy-settings" in extensionizr.com, uncomment this lines

// var settings = new Store("settings", {
//     "sample_setting": "This is how you use Store.js to remember values"
// });

function generateUUID() {
  let id = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
    let r = Math.random()*16|0, v = c === 'x' ? r : (r&0x3|0x8);
    return v.toString(16);
  });
  return id;
}

const defaults = {
  id: generateUUID(),
  username: "test-user",
};

class StorageService {
  constructor() {
    this.storage = chrome.storage.sync;
    this.storage.get(defaults, (items) => {
      this.storage.set(items);
    })
  }

  reset() {
    this.storage.clear(() => {
      defaults.id = generateUUID();
      this.set(defaults);
    });
  }

  get(key) {
    return new Promise((resolve) => {
      this.storage.get(key, (item) => {
        resolve(item[key]);
      });
    });
  }

  set(key, value) {
    let data = {};
    data[key] = value;
    this.storage.set(data);
  }

  remove(key) {
    this.storage.remove(key);
  }
}

window.BackgroundStorageService = new StorageService();

Use sync storage instead of local storage


/*global contract, it, embark, assert, before*/
const SimpleStorage = embark.require('Embark/contracts/SimpleStorage');

contract("SimpleStorage Deploy", function () {
  let SimpleStorageInstance;

  before(async function() {
    SimpleStorageInstance = await SimpleStorage.deploy({arguments: [150]}).send();
  });

  it("should set constructor value", async function () {
    let result = await SimpleStorageInstance.methods.storedData().call();
    assert.strictEqual(parseInt(result, 10), 150);
  });

  it("set storage value", async function () {
    await SimpleStorageInstance.methods.set(150).send();
    let result = await SimpleStorageInstance.methods.get().call();
    assert.strictEqual(parseInt(result, 10), 150);
  });

});

/*global contract, it, embark, assert, before, web3*/
const SimpleStorage = embark.require('Embark/contracts/SimpleStorage');
const Utils = require('embarkjs').Utils;

contract("SimpleStorage Deploy", function () {
  let simpleStorageInstance;
  before(function(done) {
    Utils.secureSend(web3, SimpleStorage.deploy({arguments: [150]}), {}, true, function(err, receipt) {
      if(err) {
        return done(err);
      }
      simpleStorageInstance = SimpleStorage;
      simpleStorageInstance.options.address = receipt.contractAddress;
      done();
    });
  });

  it("should set constructor value", async function () {
    let result = await simpleStorageInstance.methods.storedData().call();
    assert.strictEqual(parseInt(result, 10), 150);
  });

  it("set storage value", function (done) {
    Utils.secureSend(web3, simpleStorageInstance.methods.set(200), {}, false, async function(err) {
      if (err) {
        return done(err);
      }
      let result = await simpleStorageInstance.methods.get().call();
      assert.strictEqual(parseInt(result, 10), 200);
      done();
    });
  });

});

Fix embark test using node option


var allTestFiles = [];
var TEST_REGEXP = /(spec|test)\.js$/i;

var pathToModule = function(path) {
  return path.replace(/^\/base\//, '').replace(/\.js$/, '');
};

Object.keys(window.__karma__.files).forEach(function(file) {
  if (TEST_REGEXP.test(file)) {
    allTestFiles.push(pathToModule(file));
  }
});

require.config({
  baseUrl: "/base",

  paths: {
    // Libraries
    "jquery": "libraries/jquery/jquery",
    // /Libraries

    // Application
    "app": "app/scripts/app",
    // /Application

    // Fixtures
    "app-fixture": "tests/fixtures/app-fixture"
    // /Fixtures
  },

  shim: {
  },

  deps: allTestFiles,

  callback: window.__karma__.start
});

var allTestFiles = [];
var TEST_REGEXP = /(spec|test)\.js$/i;

var pathToModule = function(path) {
  return path.replace(/^\/base\//, '').replace(/\.js$/, '');
};

Object.keys(window.__karma__.files).forEach(function(file) {
  if (TEST_REGEXP.test(file)) {
    allTestFiles.push(pathToModule(file));
  }
});

require.config({
  baseUrl: "/base",

  paths: {
    // Libraries
    "jquery": "libraries/jquery/jquery",
    // /Libraries

    // Application
    "app": "app/scripts/app",
    "components/example": "app/scripts/components/example",
    // /Application

    // Fixtures
    "app-fixture": "tests/fixtures/app-fixture"
    // /Fixtures
  },

  shim: {
  },

  deps: allTestFiles,

  callback: window.__karma__.start
});

Define component path in the test RequireJS config.


ChatConfig = {
    join: ["uidev"],
    leave: [],
    hideDeathSpam: false,
    autoexec: [
        "/closeui perfhud",
        "jumpdelay 0",
        "/sleep 1000",
        "/openui mehuge-lb",
        "/openui mehuge-heatmap",
        "/openui mehuge-perf",
        "/openui mehuge-pop",
        "/openui mehuge-bct",
        "/openui mehuge-announcer",
        "/openui mehuge-deathspam",
        "/openui mehuge-loc",
        "/openui mehuge-group",
        "/openui mehuge-combatlog",
        "/openui castbar",
        "/openui ortu-compass"
]
};

ChatConfig = {
    join: ["uidev"],
    leave: [],
    hideDeathSpam: false,
    autoexec: [
        "/closeui perfhud",
        "jumpdelay 0",
        "/sleep 1000",
        "/openui mehuge-lb",
        "/openui mehuge-heatmap",
        "/openui mehuge-perf",
        "/openui mehuge-pop",
        "/openui mehuge-bct",
        "/openui mehuge-announcer",
        "/openui mehuge-deathspam",
        "/openui mehuge-loc",
        "/openui mehuge-group",
        "/openui mehuge-combatlog",
        "/openui castbar",
        "/openui ortu-compass",
        "/join _modsquad",
]
};

Join _modsquad channel (my personal config)


var LevelOne = function(){
  this.bricks = [new Brick(), new Brick(140), new Brick(230), new Brick(320), new Brick(410), new Brick(500), new Brick(590), new Brick(680), new Brick(50, 80),new Brick(140, 80), new Brick(230, 80), new Brick(320, 80), new Brick(410, 80), new Brick(500, 80), new Brick(590, 80), new Brick(680, 80)]
}

var LevelOne = function(){
  this.bricks = [
    new Brick(),
    new Brick(140),
    new Brick(230),
    new Brick(320),
    new Brick(410),
    new Brick(500),
    new Brick(590),
    new Brick(680),
    new Brick(50, 80),
    new Brick(140, 80),
    new Brick(230, 80),
    new Brick(320, 80),
    new Brick(410, 80),
    new Brick(500, 80),
    new Brick(590, 80),
    new Brick(680, 80)
  ]
}

Tidy up ogranisation of Brick objects in a level


'use strict'

module.exports = (ctx) => ({
  map: ctx.file.dirname.includes('examples') ? false : {
    inline: false,
    annotation: true,
    sourcesContent: true
  },
  plugins: {
    autoprefixer: {}
  }
})

'use strict'

module.exports = (ctx) => ({
  map: ctx.file.dirname.includes('examples') ? false : {
    inline: false,
    annotation: true,
    sourcesContent: true
  },
  plugins: {
    autoprefixer: { cascade: false }
  }
})

Set autoprefixer's cascade option to false.


const test = require('tape');
const User = require('../../../models/User');
const { connect, add, remove } = require('../../database');

test('connection to database', async (assert) => {
  const res = await connect();
  assert.equal(res, 0, 'It should connect to the database without errors');
  assert.end();
});

test('adding to database', async (assert) => {
  const res = await add(User, 'sample', 'secret');
  // try {
    await remove(User, 'sample');
  // } catch (e) { assert.fail(e); }
  assert.equal(res, 0, 'It should add documents to the database without errors');
  assert.end();
});

test('adding duplicates to database', async (assert) => {
  await add(User, 'sample', 'secret');
  const res = await add(User, 'sample', 'secret');
  // try {
    await remove(User, 'sample');
  // } catch (e) {}
  assert.equal(res, 2, 'It should not add duplicate documents to the database');
  assert.end();
});

const test = require('tape');
const User = require('../../../models/User');
const { connect, add, remove } = require('../../database');

test('connection to database', async (assert) => {
  const actual = await connect();
  const expected = 0;
  assert.equal(actual, expected, 'It should connect to the database without errors');
  assert.end();
});

test('adding to database', async (assert) => {
  const actual = await add(User, 'sample', 'secret');
  const expected = 0;
  await remove(User, 'sample');
  assert.equal(actual, expected, 'It should add documents to the database without errors');
  assert.end();
});

test('adding duplicates to database', async (assert) => {
  await add(User, 'sample', 'secret');
  const actual = await add(User, 'sample', 'secret');
  const expected = 2;
  await remove(User, 'sample');
  assert.equal(actual, expected, 'It should not add duplicate documents to the database');
  assert.end();
});

Update test file variable names


module.exports = {
  rules: {
    'quote-props': [2, 'as-needed'],
    'array-bracket-spacing': [2, 'never'],
    'vars-on-top': 0,
    'no-param-reassign': 0,
    'max-len': [
      1,
      110,
      2,
      {
        "ignoreComments": true,
        "ignoreUrls": true
      }
    ],
    'comma-dangle': [2, 'never'],
    'no-use-before-define': [2, 'nofunc'],
    'no-lonely-if': 2,
    'handle-callback-err': 2,
    'no-extra-parens': 2,
    'no-path-concat': 1,
    'func-style': [
      1,
      'declaration'
    ],
    'space-unary-ops': 2,
    'no-mixed-requires': [
      2,
      true
    ],
    'space-in-parens': [
      2,
      'never'
    ],
    'no-var': 0,
    'prefer-const': 0,
    strict: [2, 'global'],
    'object-curly-spacing': [2, 'never'],
    'computed-property-spacing': [2, 'never'],
    'func-names': 0,
    'valid-jsdoc': [2, {requireReturnDescription: false}],
    'id-length': 0
  }
};

module.exports = {
  rules: {
    'quote-props': [2, 'as-needed', {keywords: true}],
    'array-bracket-spacing': [2, 'never'],
    'vars-on-top': 0,
    'no-param-reassign': 0,
    'max-len': [
      1,
      110,
      2,
      {
        "ignoreComments": true,
        "ignoreUrls": true
      }
    ],
    'comma-dangle': [2, 'never'],
    'no-use-before-define': [2, 'nofunc'],
    'no-lonely-if': 2,
    'handle-callback-err': 2,
    'no-extra-parens': 2,
    'no-path-concat': 1,
    'func-style': [
      1,
      'declaration'
    ],
    'space-unary-ops': 2,
    'no-mixed-requires': [
      2,
      true
    ],
    'space-in-parens': [
      2,
      'never'
    ],
    'no-var': 0,
    'prefer-const': 0,
    strict: [2, 'global'],
    'object-curly-spacing': [2, 'never'],
    'computed-property-spacing': [2, 'never'],
    'func-names': 0,
    'valid-jsdoc': [2, {requireReturnDescription: false}],
    'id-length': 0
  }
};

Add the `keywords: true` option


import React, { Component, PropTypes } from 'react';

function can(permissionsData, permission) {
  const permissions = JSON.parse(permissionsData);
  return permissions[permission];
}

class Can extends Component {
  render() {
    if (can(this.props.permissions, this.props.permission)) {
      return (this.props.children);
    }
    else {
      return null;
    }
  }
}

export { Can as default, can };

import React, { Component, PropTypes } from 'react';

function can(permissionsData, permission) {
  try {
    const permissions = JSON.parse(permissionsData);
    return permissions[permission];
  } catch (e) {
    throw `Error parsing permissions data: ${permissionsData}`
  }
}

class Can extends Component {
  render() {
    if (can(this.props.permissions, this.props.permission)) {
      return (this.props.children);
    }
    else {
      return null;
    }
  }
}

export { Can as default, can };

Throw more descriptive permissions parse errors


// We have to remove node_modules/react to avoid having multiple copies loaded.
// eslint-disable-next-line import/no-unresolved
import React, { PropTypes } from 'react';
import { modules } from 'stripes-loader'; // eslint-disable-line

const Pluggable = props => {
  const plugins = modules.plugin || [];
  let best;

  for (name of Object.keys(plugins)) {
    const m = plugins[name];
    if (m.pluginType === props.type) {
      best = m;
      if (m.module === props.stripes.plugins[props.type]) break;
    }
  }

  if (best) {
    const Child = props.stripes.connect(best.getModule());
    return <Child {...props} />
  }

  return props.children;
};

Pluggable.propTypes = {
  type: PropTypes.string.isRequired,
};

export default Pluggable;

// We have to remove node_modules/react to avoid having multiple copies loaded.
// eslint-disable-next-line import/no-unresolved
import React, { PropTypes } from 'react';
import { modules } from 'stripes-loader'; // eslint-disable-line

const Pluggable = (props, context) => {
  const plugins = modules.plugin || [];
  let best;

  for (name of Object.keys(plugins)) {
    const m = plugins[name];
    if (m.pluginType === props.type) {
      best = m;
      if (m.module === context.stripes.plugins[props.type]) break;
    }
  }

  if (best) {
    const Child = context.stripes.connect(best.getModule());
    return <Child {...props} />
  }

  return props.children;
};

Pluggable.contextTypes = {
  stripes: PropTypes.shape({
    hasPerm: PropTypes.func.isRequired,
  }).isRequired,
};

Pluggable.propTypes = {
  type: PropTypes.string.isRequired,
};

export default Pluggable;

Use Stripes object from context, not from prop


'use strict';

const HTTP_OK = 200;
const HTTP_METHOD_NOT_ALLOWED = 405;

/**
 * Conqueso compatible API
 *
 * @param {Express.App} app
 */
function Conqueso(app) {
  app.get('/v1/conqueso*', (req, res) => {
    res.set('Content-Type', 'text/plain')
    res.status(HTTP_OK);
    res.end();
  });

  app.post('/v1/conqueso*', (req, res) => {
    res.status(HTTP_OK).end();
  });

  app.put('/v1/conqueso*', (req, res) => {
    res.status(HTTP_OK).end();
  });

  app.options('/v1/conqueso*', (req, res) => {
    res.set('Allow', 'POST,PUT,OPTIONS');
    res.status(HTTP_OK);
    res.end();
  });

  app.all('/v1/conqueso*', (req, res) => {
    res.set('Allow', 'POST,PUT,OPTIONS');
    res.status(HTTP_METHOD_NOT_ALLOWED);
    res.end();
  });
}

exports.attach = Conqueso;

'use strict';

const HTTP_OK = 200;
const HTTP_METHOD_NOT_ALLOWED = 405;

/**
 * Conqueso compatible API
 *
 * @param {Express.App} app
 */
function Conqueso(app) {
  function methodNotAllowed(req, res) {
    res.set('Allow', 'POST,PUT,OPTIONS');
    res.status(HTTP_METHOD_NOT_ALLOWED);
    res.end();
  }

  const route = app.route('/v1/conqueso*');

  route.get((req, res) => {
    res.set('Content-Type', 'text/plain')
    res.status(HTTP_OK);
    res.end();
  });

  route.post((req, res) => {
    res.status(HTTP_OK).end();
  });

  route.put((req, res) => {
    res.status(HTTP_OK).end();
  });

  route.options((req, res) => {
    res.set('Allow', 'POST,PUT,OPTIONS');
    res.status(HTTP_OK);
    res.end();
  });

  route.head(methodNotAllowed);
  route.all(methodNotAllowed);
}

exports.attach = Conqueso;

Refactor to use a route instead of pretty handlers.