var assert = require('assert');
var connect = require('connect');
var csrf = require('csrf-lite');
var extend = require('obj-extend');

module.exports = function (options) {
  options = extend({
    tokenKey: 'csrfToken',
    dataKey: 'body'
  }, options);

  return function connectCsrf (req, res, next) {
    // Grab the token off the request object or create one
    var token = req[options.tokenKey] = req[options.tokenKey] || csrf();

    // Create a helper function to print a csrf form input
    req.csrfInput = res.locals.csrfInput = function () {
      return csrf.html(token);
    };

    // CSRF validation
    // https://github.com/senchalabs/connect/blob/2.12.0/lib/middleware/csrf.js#L76
    if (!req.method.match(/^(GET|HEAD|OPTIONS)$/)) {
      var data = req[options.dataKey] || {};
      var valid = csrf.validate(data, token);
      if (!valid) {
        return next(connect.utils.error(403));
      }
    }

    next();
  };
};

var assert = require('assert');
var connect = require('connect');
var csrf = require('csrf-lite');
var extend = require('obj-extend');

module.exports = function (options) {
  options = extend({
    tokenKey: 'csrfToken',
    dataKey: 'body'
  }, options);

  return function connectCsrf (req, res, next) {
    // Grab the token off the request object or create one
    var token = req[options.tokenKey];
    if (token === undefined) {
      token = csrf();
      req[options.tokenKey] = token;
    }

    // Create a helper function to print a csrf form input
    req.csrfInput = res.locals.csrfInput = function () {
      return csrf.html(token);
    };

    // CSRF validation
    // https://github.com/senchalabs/connect/blob/2.12.0/lib/middleware/csrf.js#L76
    if (!req.method.match(/^(GET|HEAD|OPTIONS)$/)) {
      var data = req[options.dataKey] || {};
      var valid = csrf.validate(data, token);
      if (!valid) {
        return next(connect.utils.error(403));
      }
    }

    next();
  };
};

Make token creation a little easier to read


angular.module('questionServices', ['ngResource']).
    factory('Question', function($resource) {
        return $resource('questions/:id/:action', {id:"@id"}, {
            update: { method: 'PUT' },
            copy: {method: "POST", params: {action: "copy"}}
        });
    });

angular.module('formServices', ['ngResource']).
    factory('Form', function($resource) {
        var Form = $resource('forms/:id', {id:"@id"}, {
            update: { method: 'PUT' }
        });

        // set up default values
        Form.prototype.id = undefined;
        Form.prototype.name = "Untitled Form";
        Form.prototype.header = "
Default Header
";
        Form.prototype.footer = "
Default Footer
";
        Form.prototype.questions = [];

        return Form;
    });

angular.module('questionServices', ['ngResource']).
    factory('Question', function($resource) {
        return $resource('api/questions/:id/:action', {id:"@id"}, {
            update: { method: 'PUT' },
            copy: {method: "POST", params: {action: "copy"}}
        });
    });

angular.module('formServices', ['ngResource']).
    factory('Form', function($resource) {
        var Form = $resource('api/forms/:id', {id:"@id"}, {
            update: { method: 'PUT' }
        });

        // set up default values
        Form.prototype.id = undefined;
        Form.prototype.name = "Untitled Form";
        Form.prototype.header = "
Default Header
";
        Form.prototype.footer = "
Default Footer
";
        Form.prototype.questions = [];

        return Form;
    });

Fix API URL to /api/*


Hummingbird.ChatRoute = Ember.Route.extend({
  pingInterval: null,

  model: function() {
    return [];
  },

  afterModel: function() {
    Hummingbird.TitleManager.setTitle("Chat");
  },

  ping: function() {
    var self = this;
    return ic.ajax({
      url: "/chat/ping",
      type: 'POST'
    }).then(function(pingResponse) {
      self.set('controller.onlineUsers', pingResponse.online_users);
      return pingResponse;
    });
  },

  activate: function() {
    //Notification.requestPermission();
    var self = this;
    self.ping().then(function(pingResponse) {
      var lastId = pingResponse.last_message_id - 20;
      if (lastId < 0) lastId = 0;
      MessageBus.subscribe("/chat/lobby", function(message) {
        self.get('controller').send("recvMessage", message);
      }, lastId);
    });
    self.set('pingInterval', setInterval(self.ping.bind(self), 20000));
  },

  deactivate: function() {
    MessageBus.unsubscribe("/chat");
    if (this.get('pingInterval')) {
      clearInterval(this.get('pingInterval'));
      this.set('pingInterval', null);
    }
  }
});

Hummingbird.ChatRoute = Ember.Route.extend({
  pingInterval: null,

  model: function() {
    return [];
  },

  afterModel: function() {
    Hummingbird.TitleManager.setTitle("Chat");
  },

  ping: function() {
    var self = this;
    return ic.ajax({
      url: "/chat/ping",
      type: 'POST'
    }).then(function(pingResponse) {
      self.set('controller.onlineUsers', pingResponse.online_users);
      return pingResponse;
    });
  },

  activate: function() {
    //Notification.requestPermission();
    var self = this;
    self.ping().then(function(pingResponse) {
      var lastId = pingResponse.last_message_id - 40;
      if (lastId < 0) lastId = 0;
      MessageBus.subscribe("/chat/lobby", function(message) {
        self.get('controller').send("recvMessage", message);
      }, lastId);
    });
    self.set('pingInterval', setInterval(self.ping.bind(self), 20000));
  },

  deactivate: function() {
    MessageBus.unsubscribe("/chat");
    if (this.get('pingInterval')) {
      clearInterval(this.get('pingInterval'));
      this.set('pingInterval', null);
    }
  }
});

Increase initial scrollback to 40 messages.


//@ts-check
var assert = require('assert')
    , test = require('selenium-webdriver/testing')
    , webdriver = require('selenium-webdriver')
    , server = require('../server')
    ;

var port = process.env.PORT || 9999;

test.describe('Google search', () => {
    var driver /*= (new webdriver.Builder()).
        withCapabilities(webdriver.Capabilities.chrome()).
        build()*/;

    var s;

    test.before(function () {
        this.timeout(10000);
        driver = (new webdriver.Builder()).
            withCapabilities(webdriver.Capabilities.chrome()).
            build();
        return new Promise((resolve, reject) => {
            s = server.createAndListen(port, resolve);
        });
    });

    test.after(() => {
        driver.quit();
        s.close();
    });

    test.it('Should work', function () {
        driver.get(`http://localhost:${port}/`);
        return driver.findElement(webdriver.By.tagName('p'))
            .then(el => el.getText())
            .then(text => assert.equal(text, "Hi everybody!"))
            ;
    });
});

//@ts-check
var assert = require('assert')
    , test = require('selenium-webdriver/testing')
    , webdriver = require('selenium-webdriver')
    , server = require('../server')
    ;

var port = process.env.PORT || 9999;

test.describe('Google search', () => {
    var driver /*= (new webdriver.Builder()).
        withCapabilities(webdriver.Capabilities.chrome()).
        build()*/;

    var s;

    test.before(function () {
        this.timeout(10000);

        // If we are using Travis, let's connect to Sauce Labs remote web drivers
        if (process.env.TRAVIS || false) {
            var username = process.env.SAUCE_USERNAME
                , accessKey = process.env.SAUCE_ACCESS_KEY
                ;
            process.env.SELENIUM_REMOTE_URL = `https://${username}:${accessKey}@localhost:4445`;
        }

        driver = (new webdriver.Builder()).
            withCapabilities(webdriver.Capabilities.chrome()).
            build();

        return new Promise((resolve, reject) => {
            s = server.createAndListen(port, resolve);
        });
    });

    test.after(() => {
        driver.quit();
        s.close();
    });

    test.it('Should work', function () {
        driver.get(`http://localhost:${port}/`);
        return driver.findElement(webdriver.By.tagName('p'))
            .then(el => el.getText())
            .then(text => assert.equal(text, "Hi everybody!"))
            ;
    });
});

Set travis / sauce labs selenium remote web driver.


var extend = require('extend')

var Crocodoc,
  createViewer

module.exports.mock = function (mocks) {
  Crocodoc = window.Crocodoc
  createViewer = Crocodoc.createViewer

  Crocodoc.addPlugin('expose-config', function (scope) {
    return {
      init: function () {
        scope.getConfig().api.getConfig = function () {
          return scope.getConfig()
        }
      }
    }
  })

  Crocodoc.createViewer = function (el, conf) {
    conf = extend(true, {}, conf, {
      plugins: { 'expose-config': true }
    })
    var viewer = createViewer(el, conf)
    Object.keys(mocks).forEach(function (method) {
      var orig = viewer[method]
      viewer[method] = mocks[method]
      viewer['_' + method] = orig
    })
    return viewer
  }
}

module.exports.restore = function () {
  if (Crocodoc) {
    Crocodoc.createViewer = createViewer
  }
}

var extend = require('extend')

var Crocodoc,
  createViewer

module.exports.mock = function (mocks) {
  mocks = mocks || {}
  Crocodoc = window.Crocodoc
  createViewer = Crocodoc.createViewer

  Crocodoc.addPlugin('expose-config', function (scope) {
    return {
      init: function () {
        scope.getConfig().api.getConfig = function () {
          return scope.getConfig()
        }
      }
    }
  })

  Crocodoc.createViewer = function (el, conf) {
    conf = extend(true, {}, conf, {
      plugins: { 'expose-config': true }
    })
    var viewer = createViewer(el, conf)
    Object.keys(mocks).forEach(function (method) {
      var orig = viewer[method]
      viewer[method] = mocks[method]
      viewer['_' + method] = orig
    })
    return viewer
  }
}

module.exports.restore = function () {
  if (Crocodoc) {
    Crocodoc.createViewer = createViewer
  }
}

Fix bug where empty viewer mock breaks stuff


#!/usr/bin/env node

// accepts conf through .env file
// suitable for configuring this in the docker env
var configuration = require('ms-amqp-conf');
var bunyan = require('bunyan');

var dir;
if (process.env.NODE_ENV === 'production') {
  dir = '../lib';
} else {
  dir = '../src';
  require('../test/babelhook.js');
}

var streams = [{
  level: 'trace',
  type: 'raw',
  stream: new bunyan.RingBuffer({ limit: 100 }),
}];
if (configuration.logger) {
  streams.push({
    stream: process.stdout,
    level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
  });
}

var Service = require(dir);
var service = new Service(configuration);
var logger = bunyan.createLogger({
  name: service._config.name || 'ms-mailer',
  streams: streams,
});
service.on('log', function writeLogs(namespace, message) {
  if (message) {
    logger.info({ namespace }, message);
  } else {
    logger.info(namespace);
  }
});

service.connect()
  .then(function serviceStarted() {
    logger.info('service started');
  })
  .catch(function serviceCrashed(err) {
    logger.fatal('service crashed', err);
    setImmediate(function escapeTryCatch() {
      throw err;
    });
  });

#!/usr/bin/env node

// accepts conf through .env file
// suitable for configuring this in the docker env
var configuration = require('ms-amqp-conf');

var dir;
if (process.env.NODE_ENV === 'production') {
  dir = '../lib';
} else {
  dir = '../src';
  require('../test/babelhook.js');
}

var Service = require(dir);
var service = new Service(configuration);
service.connect()
  .then(function serviceStarted() {
    service.log.info('service started');
  })
  .catch(function serviceCrashed(err) {
    service.log.fatal('service crashed', err);
    setImmediate(function escapeTryCatch() {
      throw err;
    });
  });

Remove extra logger creation from binary


exports.config = {
    capabilities: {
        'browserName': 'firefox'
    },
    specs: ['spec/**.spec.js'],
    onPrepare: function () {
        require("babel-core/register");
        require('./spec/support/setup-sinon-chai.js');
    }
};

// Choosing firefox on CI, Chrome at home
var os = require('os');
var browser = os.platform() === 'linux' ? 'firefox' : 'chrome';

exports.config = {
    capabilities: {
        'browserName': browser,
    },
    specs: ['spec/**.spec.js'],
    onPrepare: function () {
        require("babel-core/register");
        require('./spec/support/setup-sinon-chai.js');
    }
};

Use firefox on CI, chrome at home


import { createStore, compose } from 'redux';
import DevTools from '../components/devTools';
import Shogi from '../reducers';

let enhancer = compose(
  DevTools.instrument()
);

let store = createStore(Shogi, undefined, enhancer);

export default store;

import { createStore, compose } from 'redux';
import DevTools from '../components/devTools';
import Shogi from '../reducers';

const enhancer = compose(
  DevTools.instrument()
);

const store = createStore(Shogi, undefined, enhancer);

export default store;

Use const instead of let.


import { expect } from 'chai';
import { findModules, compileModules } from '../src/module';

describe.only('module.js', () => {
    describe('Module compilation', () => {
        it('should list all JS and JSON files', () => {
            return findModules()
                .then(files => {
                    expect(files).to.be.an('array');
                    expect(files).to.have.lengthOf(1);
                });
        });

        it('should correctly compile a JSON module', () => {
            return compileModules(['package.json'])
                .then(compiledFiles => {
                    expect(compiledFiles).to.be.an('object');
                    expect(compiledFiles).to.have.property('package.json');
                    expect(() => JSON.parse(compiledFiles['package.json'])).to.not.throw(Error);
                });
        });

        it('should correctly compile a JS module');
    });
});

import { expect } from 'chai';
import { findModules, compileModules } from '../src/module';

describe.only('module.js', () => {
    describe('Module compilation', () => {
        it('should list all JS and JSON files', () => {
            return findModules()
                .then(files => {
                    expect(files).to.be.an('array');
                    expect(files).to.have.lengthOf(1);
                });
        });

        it('should correctly compile a JSON module', () => {
            return compileModules(['package.json'])
                .then(compiledFiles => {
                    expect(compiledFiles).to.be.an('object');
                    expect(compiledFiles).to.have.property('package.json');
                    expect(() => JSON.parse(compiledFiles['package.json'])).to.not.throw(Error);
                });
        });

        it('should correctly compile a JS module', () => {
            return compileModules(['./test/module.spec.js', './test/build.spec.js'])
                .then(compiledFiles => {
                    expect(compiledFiles).to.be.an('object');
                });
        });
    });
});

Implement test for JS compilation


/**
 * Create the store with asynchronously loaded reducers
 */

import { createStore, applyMiddleware, compose } from 'redux';
import { fromJS } from 'immutable';
import { routerMiddleware } from 'react-router-redux';
import createSagaMiddleware from 'redux-saga';
import createReducer from './reducers';

const sagaMiddleware = createSagaMiddleware();
const devtools = window.devToolsExtension || (() => noop => noop);

export default function configureStore(initialState = {}, history) {
  // Create the store with two middlewares
  // 1. sagaMiddleware: Makes redux-sagas work
  // 2. routerMiddleware: Syncs the location/URL path to the state
  const middlewares = [
    sagaMiddleware,
    routerMiddleware(history),
  ];

  const enhancers = [
    applyMiddleware(...middlewares),
    devtools(),
  ];

  const store = createStore(
    createReducer(),
    fromJS(initialState),
    compose(...enhancers)
  );

  // Extensions
  store.runSaga = sagaMiddleware.run;
  store.asyncReducers = {}; // Async reducer registry

  // Make reducers hot reloadable, see http://mxs.is/googmo
  /* istanbul ignore next */
  if (module.hot) {
    module.hot.accept('./reducers', () => {
      const nextRootReducer = require('./reducers').default; // eslint-disable-line global-require
      store.replaceReducer(nextRootReducer);
    });
  }

  return store;
}

/**
 * Create the store with asynchronously loaded reducers
 */

import { createStore, applyMiddleware, compose } from 'redux';
import { fromJS } from 'immutable';
import { routerMiddleware } from 'react-router-redux';
import createSagaMiddleware from 'redux-saga';
import createReducer from './reducers';

const sagaMiddleware = createSagaMiddleware();
const devtools = window.devToolsExtension || (() => noop => noop);

export default function configureStore(initialState = {}, history) {
  // Create the store with two middlewares
  // 1. sagaMiddleware: Makes redux-sagas work
  // 2. routerMiddleware: Syncs the location/URL path to the state
  const middlewares = [
    sagaMiddleware,
    routerMiddleware(history),
  ];

  const enhancers = [
    applyMiddleware(...middlewares),
    devtools(),
  ];

  const store = createStore(
    createReducer(),
    fromJS(initialState),
    compose(...enhancers)
  );

  // Extensions
  store.runSaga = sagaMiddleware.run;
  store.asyncReducers = {}; // Async reducer registry

  // Make reducers hot reloadable, see http://mxs.is/googmo
  /* istanbul ignore next */
  if (module.hot) {
    module.hot.accept('./reducers', () => {
      System.import('./reducers').then((reducerModule) => {
        const createReducers = reducerModule.default;
        const nextReducers = createReducers(store.asyncReducers);

        store.replaceReducer(nextReducers);
      });
    });
  }

  return store;
}

Fix HMR for sync reducers


/**
 * SettingsModels.js
 */
(function (spiderOakApp, window, undefined) {
  "use strict";
  var console = window.console || {};
  console.log = console.log || function(){};
  var Backbone    = window.Backbone,
      _           = window._,
      $           = window.$;

  spiderOakApp.SettingModel = spiderOakApp.FolderModel.extend({
    initialize: function() {
      this.on("change", function () {
        this.collection.trigger("change");
      });
    },
    which: "SettingModel"
  });

})(window.spiderOakApp = window.spiderOakApp || {}, window);

/**
 * SettingsModels.js
 */
(function (spiderOakApp, window, undefined) {
  "use strict";
  var console = window.console || {};
  console.log = console.log || function(){};
  var Backbone    = window.Backbone,
      _           = window._,
      $           = window.$;

  spiderOakApp.SettingModel = spiderOakApp.FolderModel.extend({
    which: "SettingModel"
  });

})(window.spiderOakApp = window.spiderOakApp || {}, window);

Remove explicit propagation of change event from SettingsModel to SettingsCollectaion - it already happens implicitly. This eliminates a duplicate save.


import {normalizeActionName} from '../common/helpers.js';

export const MAP_URL = 'http://tiles.hel.ninja/styles/hel-osm-light/{z}/{x}/{y}.png';
export const MAP_RETINA_URL = MAP_URL.replace('.png','@2x.png');

export const DEFAULT_ZOOM = 12;
export const MIN_ZOOM = 8;
export const MAX_ZOOM = 15;
export const BOUNDARIES = [[59.4, 23.8], [61.5, 25.8]];

export const mapActions = {
  SET_LOCATION: normalizeActionName('map/SET_LOCATION'),
  RECEIVE_ADDRESS: normalizeActionName('map/RECEIVE_ADDRESS'),
};

import {normalizeActionName} from '../common/helpers.js';

export const MAP_URL = 'https://tiles.hel.ninja/styles/hel-osm-light/{z}/{x}/{y}.png';
export const MAP_RETINA_URL = MAP_URL.replace('.png','@2x.png');

export const DEFAULT_ZOOM = 12;
export const MIN_ZOOM = 8;
export const MAX_ZOOM = 15;
export const BOUNDARIES = [[59.4, 23.8], [61.5, 25.8]];

export const mapActions = {
  SET_LOCATION: normalizeActionName('map/SET_LOCATION'),
  RECEIVE_ADDRESS: normalizeActionName('map/RECEIVE_ADDRESS'),
};

Use https for loading the tiles


const noResult = () => (`

      No gene information available

`);

export default noResult;

import State from '../../state';

const noResult = () => (`
  <aside id="backdrop">
    <div
      class="theme_${State.settings.theme}"
      id="tooltip"
    >
      <header>No gene information available</header>
    </div>
  </aside>
`);

export default noResult;

Add theme to no result tooltip


import { moduleForComponent, test, todo } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';

moduleForComponent('bs-datetimepicker', 'Integration | Component | bs datetimepicker', {
  integration: true
});

todo('it renders iconClasses and iconText', function(assert) {
  assert.expect(2);

  this.render(
    hbs`{{bs-datetimepicker date='2016-01-01' iconClasses='material-icons' iconText='date-range'}}`
  );

  assert.equal(this.$('.input-group-addon i').attr('class'), 'material-icons');
  assert.equal(
    this.$('.input-group-addon i')
      .text()
      .trim(),
    'date-range'
  );
});

test('it renders with default icon classes', function(assert) {
  assert.expect(1);

  this.render(hbs`{{bs-datetimepicker date='2016-01-01'}}`);

  assert.equal(this.$('.input-group-addon i').attr('class'), 'glyphicon glyphicon-calendar');
});

import { module, test, todo } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';

module('Integration | Component | bs datetimepicker', function(hooks) {
  setupRenderingTest(hooks);

  todo('it renders iconClasses and iconText', async function(assert) {
    assert.expect(2);

    await render(hbs`{{bs-datetimepicker date='2016-01-01' iconClasses='material-icons' iconText='date-range'}}`);

    assert.dom('.input-group-addon i').hasAttribute('class', 'material-icons');
    assert.dom('.input-group-addon i').hasText('date-range');
  });

  test('it renders with default icon classes', async function(assert) {
    assert.expect(1);

    await render(hbs`{{bs-datetimepicker date='2016-01-01'}}`);

    assert.dom('.input-group-addon i').hasAttribute('class', 'glyphicon glyphicon-calendar');
  });
});

Apply ember codemods on tests


'use strict';

var fraction = require('../').fraction;
var tape = require('tape');
var Entities = require('html-entities').Html5Entities;
var Handlebars = require('handlebars');

Handlebars.registerHelper(fraction.name, fraction);

tape('fraction', function (test) {
  var entities = new Entities();
  var template = Handlebars.compile('{{{fraction number}}}');
  var expected = '1¼';
  var actual = entities.decode(template({
    number: 1.25
  }));
  test.plan(1);
  test.equal(actual, expected, 'Works');
});

'use strict';

var fraction = require('../').fraction;
var tape = require('tape');
var Entities = require('html-entities').Html5Entities;
var Handlebars = require('handlebars');

Handlebars.registerHelper(fraction.name, fraction);

tape('fraction', function (test) {
  var entities = new Entities();
  var actual;
  var expected;
  var template;

  test.plan(2);

  template = Handlebars.compile('{{{fraction number}}}');
  expected = '1¼';
  actual = entities.decode(template({
    number: 1.25
  }));
  test.equal(actual, expected, 'Works');

  template = Handlebars.compile('{{{fraction number}}}');
  expected = '1';
  actual = entities.decode(template({
    number: 1
  }));
  test.equal(actual, expected, 'Ignores non-fractions');
});

Add another assertion for the fraction helper test


/* globals module */

var EOL = require('os').EOL;

module.exports = {
    description: 'Register test helpers',

    afterInstall: function( options ) {
        // Import statement
        var firstFile = 'tests/helpers/start-app.js';
        var firstText = "import slRegisterTestHelpers from './sl/register-test-helpers';";
        var firstLocationText = "import Ember from 'ember';" + EOL;

        // Execution of registration function
        var secondFile = 'tests/helpers/start-app.js';
        var secondText = "    slRegisterTestHelpers();";
        var secondLocationText = "application.setupForTesting();" + EOL;

        // .jshintrc file
        var thirdFile = 'tests/.jshintrc';
        var thirdText = '    "contains",' + EOL + '    "requires",';
        var thirdLocationText = '"predef": [' + EOL;

        return this.insertIntoFile( firstFile, firstText, { after: firstLocationText } )
            // Execution of registration function
            .then( () => {
                return this.insertIntoFile( secondFile, secondText, { after: secondLocationText } );
            })
            .then( () => {
                return this.insertIntoFile( thirdFile, thirdText, { after: thirdLocationText } );
            })
            .then( () => {
                return this.addAddonToProject( 'ember-sinon' );
            });
    },

    normalizeEntityName: function() {}
};

/* globals module */

var EOL = require('os').EOL;

module.exports = {
    description: 'Register test helpers',

    afterInstall: function( options ) {
        // Import statement
        var firstFile = 'tests/helpers/start-app.js';
        var firstText = "import slRegisterTestHelpers from './sl/register-test-helpers';";
        var firstLocationText = "import Ember from 'ember';" + EOL;

        // Execution of registration function
        var secondFile = 'tests/helpers/start-app.js';
        var secondText = "    slRegisterTestHelpers();";
        var secondLocationText = "application.setupForTesting();" + EOL;

        // .jshintrc file
        var thirdFile = 'tests/.jshintrc';
        var thirdText = '    "contains",' + EOL + '    "requires",';
        var thirdLocationText = '"predef": [' + EOL;

        return this.insertIntoFile( firstFile, firstText, { after: firstLocationText } )
            // Execution of registration function
            .then( function() {
                return this.insertIntoFile( secondFile, secondText, { after: secondLocationText } );
            }.bind( this ))
            .then( function() {
                return this.insertIntoFile( thirdFile, thirdText, { after: thirdLocationText } );
            }.bind( this ))
            .then( function() {
                return this.addAddonToProject( 'ember-sinon' );
            }.bind( this ));
    },

    normalizeEntityName: function() {}
};

Use anonymous functions instead of arrow functions for backward compatibility with node < v4.0


var pingPong = function(i) {
  if ((i % 5 === 0) || (i % 3 === 0)) {
    return true;
  } else {
    return false;
  }
};

$(function() {
  $("#game").submit(function(event) {
    $('#outputList').empty();

    var number = parseInt($("#userNumber").val());

    for (var i = 1; i <= number; i += 1) {
      if (i % 15 === 0) {
        $('#outputList').append("
ping pong
");
      } else if (i % 3 === 0) {
        $('#outputList').append("
ping
");
      } else if (i % 5 === 0) {
        $('#outputList').append("
pong
");
      } else {
        $('#outputList').append("
" + i + "
");
      }
    }
    event.preventDefault();
  });
});

var pingPong = function(i) {
  if (isPingPong) {
    if (i % 15 === 0) {
      return "pingpong";
    } else if (i % 3 === 0) {
      return "ping";
    } else if (i % 5 === 0) {
      return "pong";
    }
  } else {
    return false;
  }
}

var isPingPong = function(i) {
  if ((i % 5 === 0) || (i % 3 === 0)){
    return true;
  } else {
    return false;
  }
}

$(function() {
  $("#game").submit(function(event) {
    $('#outputList').empty();

    var number = parseInt($("#userNumber").val());

    for (var i = 1; i <= number; i += 1) {
      if (winner) {
        $('#outputList').append("
" + winner +"
");
      } else if (!winner){
        $('#outputList').append("
" + i +"
");
      }
    }
    event.preventDefault();
  });
});

Refactor spec test into two parts, isPingPong and pingPong


// Like Promise.all, but it does not mind if any of the promises reject.
export default function whenAllSettled(promises, {
    onRejection = err => {},
} = {}) {
    return Promise.all(
        promises.map(p => p.catch(onRejection))
    )
}

// Like Promise.all, but it does not mind if any of the promises reject.
export default function whenAllSettled(promises, {
    onRejection = err => {},
} = {}) {
    return Promise.all(
        promises.map(p => Promise.resolve(p).catch(onRejection))
    )
}

Fix whenAllSettled: tolerate non-promise elements.


require.paths.unshift("lib");

var fishback = require("fishback.js");

var server = fishback.createServer();

server.addResFilter(function (res) {
    // If origin doesn't specify, make everything cachable for an hour
    if (!("cache-control" in res.headers)) {
        res.headers["cache-control"] = "max-age=3600";
    }
});

server.listen();

require.paths.unshift("lib");

var fishback = require("fishback.js");

var server = fishback.createServer();

server.addResFilter(function (res) {
    // If origin doesn't return a cache-control header, make everything cacheable
    // for 0 seconds.  This seems useless, but it means that if the the client
    // explicitly passes max-stale, it will get a cached response.
    if (!("cache-control" in res.headers)) {
        res.headers["cache-control"] = "max-age=0";
    }
});

server.listen();

Make the automatic header modification a bit safer.


var webpack = require('webpack')

module.exports = function(config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine'],
    files: [
      'test/**/*.js'
    ],

    preprocessors: {
      // add webpack as preprocessor
      'src/**/*.js': ['webpack', 'sourcemap'],
      'test/**/*.js': ['webpack', 'sourcemap']
    },

    webpack: { //kind of a copy of your webpack config
      devtool: 'inline-source-map', //just do inline source maps instead of the default
      module: {
        loaders: [
          {
            test: /\.js$/,
            loader: 'babel',
            query: {
              presets: ['airbnb']
            }
          },
          {
            test: /\.json$/,
            loader: 'json',
          },
        ]
      },
      externals: {
        'react/lib/ExecutionEnvironment': true,
        'react/lib/ReactContext': true
      }
    },

    webpackServer: {
      noInfo: true //please don't spam the console when running in karma!
    },

    plugins: [
      'karma-webpack',
      'karma-jasmine',
      'karma-sourcemap-loader',
      'karma-chrome-launcher',
      'karma-phantomjs-launcher'
    ],

    babelPreprocessor: {
      options: {
        presets: ['airbnb']
      }
    },
    reporters: ['progress'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['Chrome'],
    singleRun: false,
  })
};

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

module.exports = function(config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine'],
    files: [
      'test/**/*.js'
    ],

    preprocessors: {
      // add webpack as preprocessor
      'src/**/*.js': ['webpack', 'sourcemap'],
      'test/**/*.js': ['webpack', 'sourcemap']
    },

    webpack: { //kind of a copy of your webpack config
      devtool: 'inline-source-map', //just do inline source maps instead of the default
      module: {
        loaders: [
          {
            test: /\.js$/,
            loader: 'babel',
            exclude: path.resolve(__dirname, 'node_modules'),
            query: {
              presets: ['airbnb']
            }
          },
          {
            test: /\.json$/,
            loader: 'json',
          },
        ]
      },
      externals: {
        'react/lib/ExecutionEnvironment': true,
        'react/lib/ReactContext': true
      }
    },

    webpackServer: {
      noInfo: true //please don't spam the console when running in karma!
    },

    plugins: [
      'karma-webpack',
      'karma-jasmine',
      'karma-sourcemap-loader',
      'karma-chrome-launcher',
      'karma-phantomjs-launcher'
    ],

    babelPreprocessor: {
      options: {
        presets: ['airbnb']
      }
    },
    reporters: ['progress'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['Chrome'],
    singleRun: false,
  })
};

Exclude node modules from webpack using dir name


define([
    'underscore',
    'app',
    'backbone.marionette',
    'localforage',
    '../collections/TimetableModuleCollection',
    '../collections/LessonCollection',
    'common/config'
  ],
  function (_, App, Marionette, localforage, TimetableModuleCollection,
            LessonCollection, config) {
    'use strict';

    return Marionette.Controller.extend({
      initialize: function () {
        this.timetable = new LessonCollection();
        this.selectedModules = new TimetableModuleCollection([], {
          timetable: this.timetable
        });
        this.listenTo(this.selectedModules, 'add remove', this.modulesChanged);
        this.listenTo(this.timetable, 'change', this.modulesChanged);
      },

      modulesChanged: function () {
        localforage.setItem('selectedModules', this.selectedModules.toJSON());
      }
    });
  });

define([
    'underscore',
    'app',
    'backbone.marionette',
    'localforage',
    '../collections/TimetableModuleCollection',
    '../collections/LessonCollection',
    'common/config'
  ],
  function (_, App, Marionette, localforage, TimetableModuleCollection,
            LessonCollection, config) {
    'use strict';

    return Marionette.Controller.extend({
      initialize: function () {
        this.timetable = new LessonCollection();
        this.selectedModules = new TimetableModuleCollection([], {
          timetable: this.timetable
        });
        this.listenTo(this.selectedModules, 'add remove', this.modulesChanged);
        this.listenTo(this.timetable, 'change', this.modulesChanged);
      },

      modulesChanged: function () {
        localforage.setItem(config.semTimetableFragment +
          ':selectedModulesQueryString', this.selectedModules.toQueryString());
      }
    });
  });

Save query string instead of json representation, namespaced to timetable/:academicYear/sem:semester - same as url


angular.module('billett.admin').config(function ($stateProvider) {
    $stateProvider
        .state('admin-order', {
            url: '/a/order/:id',
            templateUrl: 'assets/views/admin/order/order.html',
            controller: 'AdminOrderController as ctrl',
            resolve: {auth: 'AuthRequireResolver'}
        })
        .state('admin-orders', {
            url: '/a/orders',
            templateUrl: 'assets/views/admin/order/index.html',
            controller: 'AdminOrderListController',
            resolve: {auth: 'AuthRequireResolver'},
            reloadOnSearch: false
        });
});

angular.module('billett.admin').config(function ($stateProvider) {
    $stateProvider
        .state('admin-order', {
            url: '/a/order/:id',
            templateUrl: 'assets/views/admin/order/order.html',
            controller: 'AdminOrderController as ctrl',
            resolve: {auth: 'AuthRequireResolver'}
        })
        .state('admin-orders', {
            url: '/a/orders?eventgroup_id',
            templateUrl: 'assets/views/admin/order/index.html',
            controller: 'AdminOrderListController',
            resolve: {auth: 'AuthRequireResolver'},
            reloadOnSearch: false
        });
});

Add eventgroup_id as param option to orders list


module.exports = function(config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine'],
    files: [
      { pattern: 'test/**/*.js', watched: false }
    ],
    exclude: [],
    preprocessors: {
      'test/**/*.js': ['webpack']
    },
    webpack: {
      module: {
        loaders: [
          { test: /\.js$/, loader: 'babel' },
          { test: /\.html$/, loader: 'html' }
        ]
      },
      devtool: "#inline-source-map"
    },
    webpackMiddleware: {
      noInfo: true
    },
    reporters: ['progress'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['Chrome'],
    singleRun: false
  });
};

module.exports = function(config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine'],
    files: [
      'node_modules/core-js/client/core.js',
      { pattern: 'test/**/*.js', watched: false }
    ],
    exclude: [],
    preprocessors: {
      'test/**/*.js': ['webpack']
    },
    webpack: {
      module: {
        loaders: [
          { test: /\.js$/, loader: 'babel' },
          { test: /\.html$/, loader: 'html' }
        ]
      },
      devtool: "#inline-source-map"
    },
    webpackMiddleware: {
      noInfo: true
    },
    reporters: ['progress'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['Chrome'],
    singleRun: false
  });
};

TEST added core-js to loading libraries for test suite


// Karma configuration
// Generated on Tue Jul 28 2015 16:29:51 GMT+0200 (CEST)
var webpack = require("webpack");

var CONTINUOUS_INTEGRATION = process.env.CONTINUOUS_INTEGRATION === "true";

module.exports = function(config) {
  config.set({
    frameworks: ["mocha", "sinon", "chai"],

    browsers: [CONTINUOUS_INTEGRATION ? "Firefox" : "Chrome"],

    singleRun: CONTINUOUS_INTEGRATION,

    files: [
      "test/index.js"
    ],

    preprocessors: {
      "test/index.js": ["webpack", "sourcemap"]
    },

    reporters: ["dots"],

    webpack: {
      devtool: "inline-source-map",
      module: {
        loaders: [
          {
            test: /\.jsx?$/,
            exclude: /(node_modules|bower_components)/,
            loader: "babel"
          }
        ]
      },
      plugins: [
        new webpack.DefinePlugin({
          "process.env.NODE_ENV": JSON.stringify("test")
        })
      ]
    },

    webpackServer: {
      noInfo: true
    }
  });
};

// Karma configuration
// Generated on Tue Jul 28 2015 16:29:51 GMT+0200 (CEST)
var webpack = require("webpack");

var CONTINUOUS_INTEGRATION = process.env.CONTINUOUS_INTEGRATION === "true";

module.exports = function(config) {
  config.set({
    frameworks: ["mocha", "sinon", "chai"],

    browsers: [CONTINUOUS_INTEGRATION ? "Firefox" : "Chrome"],

    singleRun: CONTINUOUS_INTEGRATION,

    files: [
      "test/index.js"
    ],

    preprocessors: {
      "test/index.js": ["webpack", "sourcemap"]
    },

    reporters: ["dots"],

    webpack: {
      devtool: "inline-source-map",
      module: {
        loaders: [
          {
            test: /\.jsx?$/,
            exclude: /(node_modules|bower_components)/,
            loader: "babel"
          }
        ]
      },
      plugins: [
        new webpack.DefinePlugin({
          "process.env.NODE_ENV": JSON.stringify("test")
        })
      ],
      resolve: {
        extensions: ["", ".jsx", ".js"]
      }
    },

    webpackServer: {
      noInfo: true
    }
  });
};

Resolve .jsx extensions from test build


const webpackConfig = require('./webpack.config.babel')({env: 'test'});
const fileGlob = 'src/**/*.test.js';

module.exports = config => {
  config.set({
    basePath: '',
    frameworks: ['mocha', 'chai'],
    files: [ fileGlob ],
    preprocessors: {
      [fileGlob]: ['webpack']
    },
    webpack: webpackConfig,
    reporters: ['progress', 'coverage'],
    coverageReporter: {
      reporters: [
        {type: 'lcov', dir: 'coverage/', subdir: '.'},
        {type: 'json', dir: 'coverage/', subdir: '.'},
        {type: 'text-summary'}
      ]
    },
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: false,
    browsers: ['Chrome'],
    singleRun: true,
    concurrency: Infinity
  });
};

const webpackConfig = require('./webpack.config.babel')({env: 'test'});
const testGlob = 'src/**/*.test.js';
const srcGlob = 'src/**/!(*.test|*.stub).js';

module.exports = config => {
  config.set({
    basePath: '',
    frameworks: ['mocha', 'chai'],
    files: [ testGlob, srcGlob ],
    exclude: ['src/bootstrap.js'],
    preprocessors: {
      [testGlob]: ['webpack'],
      [srcGlob]: ['webpack']
    },
    webpack: webpackConfig,
    webpackMiddleware: {noInfo: true},
    reporters: ['progress', 'coverage'],
    coverageReporter: {
      check: {
        global: {
          statements: 11,
          branches: 0,
          functions: 0,
          lines: 11
        }
      },
      reporters: [
        {type: 'lcov', dir: 'coverage/', subdir: '.'},
        {type: 'json', dir: 'coverage/', subdir: '.'},
        {type: 'text-summary'}
      ]
    },
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: false,
    browsers: ['Chrome'],
    singleRun: true,
    concurrency: Infinity
  });
};

Include all of the files in test coverage report


'use strict';

module.exports = function(config) {
  config.set({
    'basePath': '',
    'frameworks': ['jasmine'],
    'files': [
      'bower_components/jquery/dist/jquery.js',
      'bower_components/ScrollToFixed/jquery-scrolltofixed.js',
      'bower_components/angular/angular.js',
      'bower_components/angular-mocks/angular-mocks.js',
      'src/scrollToFixed.js',
      'test/**/*_test.js'
    ],
    'exclude': [],
    'preprocessors': {},
    'reporters': ['progress'],
    'port': 9876,
    'colors': true,
    'logLevel': config.LOG_INFO,
    'autoWatch': false,
    'browsers': ['PhantomJS'],
    'singleRun': true,
  });
};

'use strict';

module.exports = function(config) {
  config.set({
    'basePath': '',
    'frameworks': ['jasmine'],
    'files': [
      'bower_components/jquery/dist/jquery.js',
      'bower_components/ScrollToFixed/jquery-scrolltofixed.js',
      'bower_components/angular/angular.js',
      'bower_components/angular-mocks/angular-mocks.js',
      'src/*.js',
      'test/**/*_test.js'
    ],
    'exclude': [],
    'preprocessors': {},
    'reporters': ['progress'],
    'port': 9876,
    'colors': true,
    'logLevel': config.LOG_INFO,
    'autoWatch': false,
    'browsers': ['PhantomJS'],
    'singleRun': true,
  });
};

Load all source files in Karma


'use strict';

var {pluck} = require('./underscore_ext');
var parser = require('./models/geneSignatureParser').parse;
// Parse a simple list of genes and weights. More complex signatures would
// be possible, but will require some work writing the xena query.
//

function parse(str) {
	if (str[0] !== '=') {
		return;
	}
	try {
		var list = parser(str.slice(1));
		return {
			weights: pluck(list, 0),
				genes: pluck(list, 1)
		};
	} catch (e) {
		console.log('parsing error', e);
	}
	return;
}

module.exports = parse;

'use strict';

var {pluck} = require('./underscore_ext');
var parser = require('./models/geneSignatureParser').parse;
// Parse a simple list of genes and weights. More complex signatures would
// be possible, but will require some work writing the xena query.
//

function parse(str) {
	if (str[0] !== '=') {
		return;
	}
	try {
		var list = parser(str.slice(1).trim());
		return {
			weights: pluck(list, 0),
				genes: pluck(list, 1)
		};
	} catch (e) {
		console.log('parsing error', e);
	}
	return;
}

module.exports = parse;

Handle leading whitespace in signature.


// A catch-all error
function genericError() {
  return {
    title: "Server Error",
    detail: "There was an error while processing your request."
  };
}

// Generates an error for a 404 resource
function notFoundError() {
  return {
    title: "Not Found",
    detail: "Resource not found."
  };
}

// When a field is missing
function missingAttribute(fieldName) {
  return {
    title: "Missing Attribute",
    detail: `The attribute "${fieldName}" is required.`
  };
}

module.exports = {
  notFoundError,
  genericError,
  missingAttribute
};

// A catch-all error
function genericError() {
  return {
    title: "Server Error",
    detail: "There was an error while processing your request."
  };
}

// Generates an error for a 404 resource
function notFoundError() {
  return {
    title: "Not Found",
    detail: "Resource not found."
  };
}

module.exports = {
  notFoundError,
  genericError
};

Remove defunct missing attribute error generator


var config = require('../../app/config')
  , models = require('../../app/models')
  , logger = require('../../app/logger');

var mongoose = require('mongoose'),
    fs = require('fs');

/**
 * Setup database
 */

var dbSettings = config.dbSettings;
var dbTestSettings = config.dbTestSettings;

var db = mongoose.createConnection(dbTestSettings.host
                                , dbTestSettings.database
                                , dbTestSettings.port);

db.on('error', function(err) {
    logger.error("DB error", err);
});

db.on('connected', function() {
    logger.info("Connected to mongodb://%s/%s:%s", db.host
                                                 , db.name
                                                 , db.port);
});

db.on('disconnected', function() {
    logger.warn("DB connection has been lost!")
});

db.on('reconnected', function() {
    logger.info("DB connection has been restarted!");
});

db.on('close', function() {
    logger.warn("DB connection has been closed!");
});

// Register models
models.register(db);

var postsRaw = JSON.parse(fs.readFileSync('MOCK_JOB_POSTINGS.json', 'utf8'));

var JobPosting = models.jobPosting();

var savePost = function(postRaw) {
    var post = new JobPosting(postRaw);
    post.save();
};

postsRaw.forEach(savePost);

logger.info("DB successfully populated!");
db.close();

var config = require('../../app/config')
  , models = require('../../app/models')
  , logger = require('../../app/logger');

var mongoose = require('mongoose'),
    fs = require('fs');

/**
 * Setup database
 */

var dbSettings = config.dbSettings;

var db = mongoose.createConnection(dbSettings.host
                                , dbSettings.database
                                , dbSettings.port);

db.on('error', function(err) {
    logger.error("DB error", err);
});

db.on('connected', function() {
    logger.info("Connected to mongodb://%s/%s:%s", db.host
                                                 , db.name
                                                 , db.port);
});

db.on('disconnected', function() {
    logger.warn("DB connection has been lost!")
});

db.on('reconnected', function() {
    logger.info("DB connection has been restarted!");
});

db.on('close', function() {
    logger.warn("DB connection has been closed!");
});

// Register models
models.register(db);

var postsRaw = JSON.parse(fs.readFileSync('MOCK_JOB_POSTINGS.json', 'utf8'));

var JobPosting = models.jobPosting();

var savePost = function(postRaw) {
    var post = new JobPosting(postRaw);
    post.save();
};

postsRaw.forEach(savePost);

logger.info("DB successfully populated!");
db.close();

Use correct db settings in populate job postings


const webpack = require('webpack');
const webpackMerge = require('webpack-merge');
const commonConfig = require('./webpack.config.common.js');
const AppCachePlugin = require('appcache-webpack-plugin');

const doUglify = true;

module.exports = function(options) {
    const ENV = options.ENV || 'production';

    const plugins = [];
    plugins.push(new webpack.DefinePlugin({
        'ENV': JSON.stringify(ENV)
    }));
    plugins.push(new webpack.NoEmitOnErrorsPlugin());

    plugins.push(new AppCachePlugin({
        exclude: ['app.js', 'styles.css'],
        output: 'app.appcache'
    }));

    return webpackMerge(commonConfig, {
        plugins,
        optimization: {
            minimize: doUglify
        },
        mode: ENV
    });
};

const webpack = require('webpack');
const webpackMerge = require('webpack-merge');
const commonConfig = require('./webpack.config.common.js');
const AppCachePlugin = require('appcache-webpack-plugin');

const doUglify = true;

module.exports = function(options) {
    const ENV = options.ENV || 'production';

    console.log('ENV: ' + ENV);

    const plugins = [];
    plugins.push(new webpack.DefinePlugin({
        'ENV': JSON.stringify(ENV)
    }));
    plugins.push(new webpack.NoEmitOnErrorsPlugin());

    plugins.push(new AppCachePlugin({
        exclude: ['app.js', 'styles.css'],
        output: 'app.appcache'
    }));

    return webpackMerge(commonConfig, {
        plugins,
        optimization: {
            minimize: doUglify
        },
        mode: ENV
    });
};

ADD show env-value on build


'use strict';

let cli       = require('heroku-cli-util');
let columnify = require('columnify');

module.exports = {
  topic: 'builds',
  needsAuth: true,
  needsApp: true,
  description: 'list builds',
  help: 'List builds for a Heroku app',
  run: cli.command(function (context, heroku) {
    return heroku.request({
       path: `/apps/${context.app}/builds`,
       method: 'GET',
       headers: {
        'Range': 'created_at ..; order=desc;'
      },
      parseJSON: true
    }).then(function (builds) {
      var columnData = builds.slice(0, 10).map(function(build) {
        return { created_at: build.created_at,
          status: build.status,
          id: build.id,
          user: build.user.email
        };
      });

      // TODO: use `max` directive in query to avoid the slice nonsense
      // heroku-client currently breaks if one tries to do this
      var columns = columnify(columnData, {
        showHeaders: false
      });
      console.log(columns);
    });
  })
};

'use strict';

let cli       = require('heroku-cli-util');
let columnify = require('columnify');

module.exports = {
  topic: 'builds',
  needsAuth: true,
  needsApp: true,
  description: 'list builds',
  help: 'List builds for a Heroku app',
  run: cli.command(function (context, heroku) {
    return heroku.request({
       path: `/apps/${context.app}/builds`,
       method: 'GET',
       headers: {
        'Range': 'created_at ..; order=desc;'
      },
      parseJSON: true
    }).then(function (builds) {
      var columnData = builds.slice(0, 10).map(function(build) {
        return { created_at: build.created_at,
          status: build.status,
          id: build.id,
          version: build.source_blob.version,
          user: build.user.email
        };
      });

      // TODO: use `max` directive in query to avoid the slice nonsense
      // heroku-client currently breaks if one tries to do this
      var columns = columnify(columnData, {
        showHeaders: false
      });
      console.log(columns);
    });
  })
};

Add both build id / git commit sha via @hone


jQuery(function($) {
  kamo.Stream.fromEventHandlerFunction(
    $('#input'),
    'keyup'
  ).debounce(1000).map(function(event) {
    return event.target.message;
  }).filter(function(message) {
    return message.length >= 3;
  }).scan([], function(result, message) {
    return [result[1], message];
  }).filter(function(message) {
    return message[0] != message[1];
  }).map(function(message) {
    return message[1];
  }).flatMapLatest(function(message) {
    return kamo.Stream.fromEventHandlerFunction(
      $.ajax({
        url: 'http://en.wikipedia.org/w/api.php',
        dataType: 'jsonp',
        data: {
          action: 'opensearch',
          format: 'json',
          search: window.encodeURI(message)
        }
      }),
      'done'
    );
  }).subscribe(
    (function($candidates) {
      return function(data) {
        $candidates.empty();
        $.each(data[1], function(_, message) {
          $('<li>').append(document.createTextNode(message)).appendTo($candidates);
        });
      };
    })($('#candidates'))
  );
});

jQuery(function($) {
  kamo.Stream.fromEventHandlerFunction(
    $('#input'),
    'keyup'
  ).debounce(1000).map(function(event) {
    return event.target.value;
  }).filter(function(message) {
    return message.length >= 3;
  }).scan([], function(result, message) {
    return [result[1], message];
  }).filter(function(message) {
    return message[0] != message[1];
  }).map(function(message) {
    return message[1];
  }).flatMapLatest(function(message) {
    return kamo.Stream.fromEventHandlerFunction(
      $.ajax({
        url: 'http://en.wikipedia.org/w/api.php',
        dataType: 'jsonp',
        data: {
          action: 'opensearch',
          format: 'json',
          search: window.encodeURI(message)
        }
      }),
      'done'
    );
  }).subscribe(
    (function($candidates) {
      return function(data) {
        $candidates.empty();
        $.each(data[1], function(_, message) {
          $('<li>').append(document.createTextNode(message)).appendTo($candidates);
        });
      };
    })($('#candidates'))
  );
});

Fix bug generated when the change of value -> message


import { Component } from 'substance'

export default class BodyComponent extends Component {

  render($$) {
    const body = this.props.node
    let el = $$('div')
      .addClass('sc-body')
      .attr('data-id', body.id)

    // There can be multiple abstracts. We just take the first
    const content = body.findChild('body-content')
    let contentEl
    if (content) {
      contentEl = $$(this.getComponent('container'), {
        name: 'bodyEditor',
        node: content,
        disabled: this.props.disabled
      })
    } else {
      // TODO: ability to add an abstract
    }
    el.append(contentEl)

    // optional sig-block
    let sigBlock = body.findChild('sig-block')
    if (sigBlock) {
      el.append(
        $$(this.getComponent('sig-block'), { node: sigBlock })
      )
    } else {
      // TODO: means to add a signature
    }

    return el
  }

}

import { Component } from 'substance'

export default class BodyComponent extends Component {

  render($$) {
    const body = this.props.node
    let el = $$('div')
      .addClass('sc-body')
      .attr('data-id', body.id)

    // There can be multiple abstracts. We just take the first
    const content = body.findChild('body-content')
    let contentEl
    if (content) {
      contentEl = $$(this.getComponent('container'), {
        placeholder: 'Enter Text',
        name: 'bodyEditor',
        node: content,
        disabled: this.props.disabled
      })
    } else {
      // TODO: ability to add an abstract
    }
    el.append(contentEl)

    // optional sig-block
    let sigBlock = body.findChild('sig-block')
    if (sigBlock) {
      el.append(
        $$(this.getComponent('sig-block'), { node: sigBlock })
      )
    } else {
      // TODO: means to add a signature
    }

    return el
  }

}

Use a placeholder for body content.


/**
 * A ping federate example of how to use `hapi-auth-saml`
 */
var Hapi = require('hapi');

var port = 7771;
var server = Hapi.createServer(port);

var SamlAuth = require('../lib');

var options = require('./secrets');

server.pack.register(SamlAuth, function (err) {
  var samlOptions = {
    path: options.path,
    protocol: options.protocol,
    entryPoint: options.entryPoint,
    additionalEntryPointParams: options.additionalEntryPointParams,
    cert: options.cert
  };

  if (options.issuer) {
    samlOptions.issuer = options.issuer;
  }

  server.auth.strategy('ping', 'saml', samlOptions);

  server.route({
    method: 'GET',
    path: '/',
    config: {
      handler: function(request, reply) {
        reply("
Homepage (no auth)
Login");
      }
    }
  });

  server.route({
    method: '*',
    path: '/login',
    config: {
      auth: 'ping',
      handler: function(request, reply) {
        reply(request.auth.credentials);
      }
    }
  });

  server.route({
    method: '*',
    path: '/login/callback',
    config: {
      // No auth, otherwise it would be an infinite
      // loop
      handler: function(request, reply) {
        reply(request.auth.credentials);
      }
    }
  });

  server.start(function(err) {
    console.log('Server started at:', server.info.uri);
  });

});

/**
 * A ping federate example of how to use `hapi-auth-saml`
 */
var Hapi = require('hapi');

var port = 7771;
var server = Hapi.createServer(port);

var SamlAuth = require('../lib');

var options = require('./secrets');

server.pack.register(SamlAuth, function (err) {
  var samlOptions = {
    path: options.path,
    protocol: options.protocol,
    entryPoint: options.entryPoint,
    additionalEntryPointParams: options.additionalEntryPointParams,
    cert: options.cert
  };

  if (options.issuer) {
    samlOptions.issuer = options.issuer;
  }

  server.auth.strategy('ping', 'saml', samlOptions);

  server.route({
    method: 'GET',
    path: '/',
    config: {
      handler: function(request, reply) {
        reply("
Homepage (no auth)
Login");
      }
    }
  });

  server.route({
    method: '*',
    path: '/login',
    config: {
      auth: 'ping',
      handler: function(request, reply) {
        reply(request.auth.credentials);
      }
    }
  });

  server.start(function(err) {
    console.log('Server started at:', server.info.uri);
  });

});

Remove callback route. Not needed.


// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later

app.views.BackToTop = Backbone.View.extend({
  events: {
    "click #back-to-top": "backToTop"
  },

  initialize: function() {
    var throttledScroll = _.throttle(this.toggleVisibility, 250);
    $(window).scroll(throttledScroll);
  },

  backToTop: function(evt) {
    evt.preventDefault();
    $("html, body").animate({scrollTop: 0});
  },

  toggleVisibility: function() {
    if($("html, body").scrollTop() > 1000) {
      $("#back-to-top").addClass("visible");
    } else {
      $("#back-to-top").removeClass("visible");
    }
  }
});
// @license-end

// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later

app.views.BackToTop = Backbone.View.extend({
  events: {
    "click #back-to-top": "backToTop"
  },

  initialize: function() {
    var throttledScroll = _.throttle(this.toggleVisibility, 250);
    $(window).scroll(throttledScroll);
  },

  backToTop: function(evt) {
    evt.preventDefault();
    $("html, body").animate({scrollTop: 0});
  },

  toggleVisibility: function() {
    if($(document).scrollTop() > 1000) {
      $("#back-to-top").addClass("visible");
    } else {
      $("#back-to-top").removeClass("visible");
    }
  }
});
// @license-end

Fix back to top icon not appearing on webkit browsers


import { SyncedCron } from 'meteor/percolate:synced-cron';
import sendReports from '../../api/email/server/reports.jsx';
import EndOfMonthEnum from '../../api/settings/EndOfMonthEnum';

SyncedCron.add({
  name: 'Send reports for users with end of month on last day',
  schedule(parser) {
    return parser.recur()
      .on(1)
      .dayOfMonth()
      .on(11)
      .hour();
  },
  job() {
    sendReports(undefined, EndOfMonthEnum.LAST_DAY);
  },
});

SyncedCron.add({
  name: 'Send reports for users with end of month on day 20',
  schedule(parser) {
    return parser.recur()
      .on(21)
      .dayOfMonth()
      .on(11)
      .hour();
  },
  job() {
    sendReports(undefined, EndOfMonthEnum.DAY_20);
  },
});

SyncedCron.start();

import { SyncedCron } from 'meteor/percolate:synced-cron';
import sendReports from '../../api/email/server/reports.jsx';
import EndOfMonthEnum from '../../api/settings/EndOfMonthEnum';

SyncedCron.add({
  name: 'Send reports for users with end of month on last day',
  schedule(parser) {
    console.log('ENV=', process.env.REPORTS_HOUR);
    console.log(typeof process.env.REPORTS_HOUR);
    return parser.recur()
      .on(1)
      .dayOfMonth()
      .on(Number(process.env.REPORTS_HOUR))
      .hour();
  },
  job() {
    sendReports(undefined, EndOfMonthEnum.LAST_DAY);
  },
});

SyncedCron.add({
  name: 'Send reports for users with end of month on day 20',
  schedule(parser) {
    return parser.recur()
      .on(21)
      .dayOfMonth()
      .on(Number(process.env.REPORTS_HOUR))
      .hour();
  },
  job() {
    sendReports(undefined, EndOfMonthEnum.DAY_20);
  },
});

SyncedCron.start();

Change hour value of sending reports to env var


"use strict";

var ko = require("knockout");
var zepto = require("npm-zepto");

ko.bindingHandlers.enterkey = require("./bindings/enterkey.js");

var localForage = require("localforage");
var List = require("./models/list.js");

var list;

var options = {
    apiUrl: window.location.toString().match(/.+\//)[0],
    apiClient: zepto
};

if(location.hash === "") {
    list = new List(options);
    location.hash = list.id();
} else {
    options.id = window.location.hash.replace("#", ""),
    list = new List(options);
}

list.storage(localForage);

list.loadFromStorage(localForage);

ko.applyBindings(list);

"use strict";

var ko = require("knockout");
var zepto = require("npm-zepto");

ko.bindingHandlers.enterkey = require("./bindings/enterkey.js");

var localForage = require("localforage");
var List = require("./models/list.js");

var list;

var options = {
    apiUrl: window.location.toString().match(/.+\//)[0],
    apiClient: zepto
};

if(location.hash === "") {
    list = new List(options);
    location.hash = list.id();
} else {
    options.id = window.location.hash.replace("#", ""),
    list = new List(options);
}

list.storage(localForage);

list.loadFromStorage(localForage);
list.loadFromServer();

ko.applyBindings(list);

Load from server on initialisation


'use strict'; // eslint-disable-line

const { default: ImageminPlugin } = require('imagemin-webpack-plugin');
const imageminMozjpeg = require('imagemin-mozjpeg');

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

module.exports = {
  plugins: [
    new ImageminPlugin({
      optipng: { optimizationLevel: 7 },
      gifsicle: { optimizationLevel: 3 },
      pngquant: { quality: '65-90', speed: 4 },
      svgo: { removeUnknownsAndDefaults: false, cleanupIDs: false },
      plugins: [imageminMozjpeg({ quality: 75 })],
      disable: (config.enabled.watcher),
    }),
  ],
};

'use strict'; // eslint-disable-line

const { default: ImageminPlugin } = require('imagemin-webpack-plugin');
const imageminMozjpeg = require('imagemin-mozjpeg');

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

module.exports = {
  plugins: [
    new ImageminPlugin({
      optipng: { optimizationLevel: 7 },
      gifsicle: { optimizationLevel: 3 },
      pngquant: { quality: '65-90', speed: 4 },
      svgo: {
        plugins: [{ removeUnknownsAndDefaults: false }, { cleanupIDs: false }],
      },
      plugins: [imageminMozjpeg({ quality: 75 })],
      disable: (config.enabled.watcher),
    }),
  ],
};

Fix default SVG optimisation configuration


$(function() {
    var osm = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
        attribution: '© OpenStreetMap contributors'
    });

    var map = L.map('map', {
        center: [47.6210, -122.3328],
        zoom: 13,
        layers: [osm]
    });

    $.getJSON('http://api.onebusaway.org/api/where/vehicles-for-agency/1.json?key=TEST&callback=?', function(data) {
        console.log(data);
    });

    var hash = new L.Hash(map);
});

$(function() {
    var osm = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
        attribution: '© OpenStreetMap contributors'
    });

    var wsUri = 'ws://busdrone.com:28737/';

    var map = L.map('map', {
        center: [47.6210, -122.3328],
        zoom: 13,
        layers: [osm]
    });

    function debug(val) {
      console.log(val);
    }

    function wsConnect() {
      debug('Connecting to ' + wsUri);
      websocket = new WebSocket(wsUri);
      websocket.onopen = function(evt) { onWsOpen(evt) };
      websocket.onmessage = function(evt) { onWsMessage(evt) };
      websocket.onerror = function(evt) { onWsError(evt) };
      websocket.onclose = function(evt) { onWsClose(evt) };
    }

    function onWsOpen(evt) {
      debug('Connected');
    }

    function onWsClose(evt) {
      debug('Disconnected');
      window.setTimeout(wsConnect, Math.random() * 1000 * 25 + 5);
    }

    function onWsMessage(evt) {
      var data = JSON.parse(evt.data);
      if (data.type == 'update_vehicle') {
      } else if (data.type == 'init') {
        data.vehicles.forEach(function(vehicle) {
            L.marker([vehicle.lat, vehicle.lon]).addTo(map);
        });
      } else if (data.type == 'remove_vehicle') {
        debug('remove');
      } else {
        debug('unknown', data)
      }
    }

    wsConnect();

    var hash = new L.Hash(map);
});

Add markers using web sockets


/*
 * Copyright 2012 Netflix, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
jQuery(function() {
    var url = window.location.href;
    var reloadPage = function() {
        window.location.replace(url);
    };
    var statusChecker = function() {
        jQuery.ajax({
            url: url,
            success: reloadPage,
            error: function(jqXHR, textStatus, errorThrown){
                if (jqXHR.status == 503) {
                    window.setTimeout(statusChecker, 5000);
                } else {
                    // If the page is throwing another type of error let the user see it
                    reloadPage();
                }
            }
        });
    }
    window.setTimeout(statusChecker, 5000);
});

/*
 * Copyright 2012 Netflix, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
jQuery(function() {
    var url, reloadPage, statusChecker;
    url = window.location.href;
    reloadPage = function() {
        window.location.replace(url);
    };
    statusChecker = function() {
        jQuery.ajax({
            url: url,
            success: reloadPage,
            error: function(jqXHR, textStatus, errorThrown){
                if (jqXHR.status == 503) {
                    window.setTimeout(statusChecker, 5000);
                } else {
                    // If the page is throwing another type of error let the user see it
                    reloadPage();
                }
            }
        });
    };
    window.setTimeout(statusChecker, 5000);
});

Fix warnings, and use only one var keyword per function.


'use strict';

const models     = require('./../models/');
const docModel   = models.Documents;
const dotenv     = require('dotenv').config();
const docHelper  = require('./helpers/docHelper.js');

const documents = {
  index: (req, res) => {

  },
  create: (req, res) => {
    const decodedUser = req.decoded;
    const document = req.body;

    if (docHelper.checkDocDetails(req, res)) {
      return;
    }

    docModel.findOne({
      where: {
        title: document.title
      }
    }).then((doc) => {
      if (doc) {
        res.status(409).json({
          success: false,
          message: 'A document wih the title exists'
        });
      } else {
        document.ownerId = decodedUser.id;
        docModel.create(document).then((newDoc) => {
          res.status(201).json({
            success: true,
            message: 'Document created',
            data: newDoc
          });
        }).catch(() => {
          res.status(500).json({
            success: false,
            message: 'Server error'
          });
        });
      }
    }).catch(() => {
      res.status(500).json({
        success: false,
        message: 'Server error'
      });
    });

  },

  show: (req, res) => {

  },
  update: (req, res) => {

  },
  delete: (req, res) => {

  }
};

module.exports = documents;

Implement the create document function


/*global CpuSolver GpuSolver Maze */

var RESET_DELAY = 30 * 1000;

function nearest(dim, scale) {
    var floor = Math.floor(dim / scale);
    return (floor % 2 == 0) ? floor - 1 : floor;
}

var solver = null;
function init() {
    var canvas = $('#display')[0],
        scale = 14,
        w = nearest(canvas.width, scale),
        h = nearest(canvas.height, scale),
        maze = Maze.kruskal(w, h);
    canvas.width = w * scale;
    canvas.height = h * scale;
    solver = new GpuSolver(w, h, maze, canvas).draw().animate(reset);
    function reset() {
        window.setTimeout(function() {
            solver.reset(Maze.kruskal(w, h));
            solver.animate(reset);
        }, RESET_DELAY);
    }
}

/* requestAnimationFrame shim */
if (window.requestAnimationFrame == null) {
    window.requestAnimationFrame =
        window.webkitRequestAnimationFrame ||
        window.mozRequestAnimationFrame    ||
        function(callback){
            window.setTimeout(callback, 1000 / 60);
        };
}

$(document).ready(init);

/*global CpuSolver GpuSolver Maze */

var RESET_DELAY = 10 * 1000;

function nearest(dim, scale) {
    var floor = Math.floor(dim / scale);
    return (floor % 2 == 0) ? floor - 1 : floor;
}

var solver = null;
function init() {
    var canvas = $('#display')[0],
        scale = 14,
        w = nearest(canvas.width, scale),
        h = nearest(canvas.height, scale),
        maze = Maze.kruskal(w, h);
    canvas.width = w * scale;
    canvas.height = h * scale;
    solver = new GpuSolver(w, h, maze, canvas).draw().animate(reset);
    function reset() {
        window.setTimeout(function() {
            solver.reset(Maze.kruskal(w, h));
            solver.animate(reset);
        }, RESET_DELAY);
    }
}

/* requestAnimationFrame shim */
if (window.requestAnimationFrame == null) {
    window.requestAnimationFrame =
        window.webkitRequestAnimationFrame ||
        window.mozRequestAnimationFrame    ||
        function(callback){
            window.setTimeout(callback, 1000 / 60);
        };
}

$(document).ready(init);

Decrease reset delay to 10 seconds.


var questions = [
  Q1 = {
    question: "What do HTML means?",
    answer: "HyperText Markup Language"
  },
  Q2 = {
    question: "What do CSS means?",
    answer: "Cascading Style Sheet"
  }
];

var question = document.getElementById('question');

Access h2 element inside flashcard


var URL = location.pathname;

$(function() {
	highlight();
	fetchVersion();
});

function highlight() {
	$("#menu a[href='" + URL + "']").addClass("active");
}

function fetchVersion() {
	var version = $("#version");
	if (!version.length) {
		return;
	}
	$.getJSON("https://api.github.com/repos/thelounge/lounge/tags", function(json) {
		var first = json.shift();
		if (first.name) {
			version.html("version " + first.name);
			version.addClass("show");
		}
	});
}

var URL = location.pathname;

$(function() {
	highlight();
	fetchVersion();
});

function highlight() {
	$("#menu a[href='" + URL + "']").addClass("active");
}

function fetchVersion() {
	var version = $("small#version");
	if (!version.length) {
		return;
	}
	$.getJSON("https://api.github.com/repos/thelounge/lounge/tags", function(json) {
		var first = json.shift();
		if (first.name) {
			version.html("version " + first.name);
			version.addClass("show");
		}
	});
}

Make sure only version on index page gets AJAXed


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

import * as ko from 'knockout';

ko.bindingHandlers.clipboard = {
  init: (element, valueAccessor) => {
    if (!window.Clipboard) {
      console.warn('ko.clipboard.js depends on window.Clipboard');
      return;
    }
    const options = valueAccessor() || {};

    const clipboard = new window.Clipboard(element, {
      target: options.target,
      text: options.text
    });

    if (options.onSuccess) {
      clipboard.on('success', options.onSuccess);
    }

    ko.utils.domNodeDisposal.addDisposeCallback(element, () => {
      clipboard.destroy();
    });
  }
};

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

import * as ko from 'knockout';
import Clipboard from 'clipboard';

ko.bindingHandlers.clipboard = {
  init: (element, valueAccessor) => {
    const options = valueAccessor() || {};

    const clipboard = new Clipboard(element, {
      target: options.target,
      text: options.text
    });

    if (options.onSuccess) {
      clipboard.on('success', options.onSuccess);
    }

    ko.utils.domNodeDisposal.addDisposeCallback(element, () => {
      clipboard.destroy();
    });
  }
};

Fix broken page after reloading the Impala query details in the Job Browser


/*eslint-env node, qunit, mocha */
"use strict";

describe("myApp.view1 module", function() {

    beforeEach(module("myApp.view1"));

    describe("view1 controller", function(){

        it("should ....", inject(function($controller) {
            //spec body
            var view1Ctrl = $controller("View1Ctrl");
            expect(view1Ctrl).toBeDefined();
        }));

    });
});

/*eslint-env node, qunit, mocha */
"use strict";

describe("myApp.postWriter module", function() {

    beforeEach(module("myApp.postWriter"));

    describe("postWriter controller", function(){

        it("should ....", inject(function($controller) {
            //spec body
            var postWriter = $controller("postWriter");
            expect(postWriter).toBeDefined();
        }));

    });
});

Update postWriter tests to work


describe('jasmine.grammar.ContextSpecification', function() {
    var ContextSpecification = jasmine.grammar.ContextSpecification;
    var env;

    beforeEach(function() {
        env = new jasmine.Env();
        jasmine.grammar._currentEnv = env;
    });

    describe('concern', function() {
        it('should create a suite with extended attributes', function() {
            var suite = ContextSpecification.concern('A specific feature of the system', function() {});
            expect(suite.description).toBe('A specific feature of the system');
            expect(suite.tags).toEqual(['concern']);
        });
    });

    describe('context', function() {
        it('should create a suite with extended attributes', function() {
            var suite = ContextSpecification.context('Context within which certain specifications will be valid.', function() {});
            expect(suite.description).toBe('Context within which certain specifications will be valid.');
            expect(suite.tags).toEqual(['context']);
        });
    });

    describe('spec', function() {
        it('should create an end result specification item', function() {
            // we need a suite before a spec
            var parentSuite = ContextSpecification.context('Dummy context', function() {});
            jasmine.grammar.getEnv().currentSuite = parentSuite;

            var spec = ContextSpecification.spec('Observe this outcome', function() {});
            expect(spec.description).toBe('Observe this outcome');
            expect(spec.suite).toBe(parentSuite);
        });
    });
});

describe('jasmine.grammar.ContextSpecification', function() {
    var ContextSpecification = jasmine.grammar.ContextSpecification;
    var env;

    beforeEach(function() {
        env = new jasmine.Env();
        jasmine.grammar._currentEnv = env;
    });

    describe('concern', function() {
        it('creates a suite tagged with "concern"', function() {
            var suite = ContextSpecification.concern('A specific feature of the system', function() {});

            expect(suite.description).toBe('A specific feature of the system');
            expect(suite.tags).toEqual(['concern']);
        });
    });

    describe('context', function() {
        it('creates a suite with tagged with "context"', function() {
            var suite = ContextSpecification.context('Context within which certain specifications will be valid.', function() {});

            expect(suite.description).toBe('Context within which certain specifications will be valid.');
            expect(suite.tags).toEqual(['context']);
        });
    });

    describe('spec', function() {
        it('creates a spec in the current suite', function() {
            var parentSuite = ContextSpecification.context('Dummy context', function() {});
            jasmine.grammar.getEnv().currentSuite = parentSuite;

            var spec = ContextSpecification.spec('Observe this outcome', function() {});

            expect(spec.description).toBe('Observe this outcome');
            expect(spec.suite).toBe(parentSuite);
        });
    });
});

Improve the spec descriptions in ContextSpecification.


function removeRedundantTabs() {
    var t = 0;
    if (django && django.jQuery) {
        django.jQuery('.changeform-tabs-item:contains("General")').each(function(index, tab){
            t++;
            if (t > 1) {
                tab.remove();
            }
        });
    }
}

function addHashToInlinePaginator() {
    // Make sure nested inline paginator links to the same inline tab
    jQuery('.paginator a').each(function(index, btn){
        if (btn.href) {
            btn.href = btn.href.split('#')[0]
            btn.href += document.location.hash;
        }
    });
}

function replaceInlineActivityAddButton() {
    django.jQuery('#activities-group .add-row a').unbind();
    django.jQuery('#activities-group .add-row a').click(function(e) {
        e.preventDefault();
        var path = document.location.pathname;
        path = path.replace('initiatives/initiative/', 'activities/activity/add/?initiative=');
        path = path.replace('/change/', '');
        document.location.href = path
    });
}

window.onload = function() {
    if (!django.jQuery && jQuery) {
        django.jQuery = jQuery;
    }
    replaceInlineActivityAddButton();
    removeRedundantTabs();
    addHashToInlinePaginator();
    window.onhashchange = addHashToInlinePaginator;
};

function removeRedundantTabs() {
  var t = 0;
  if (django && django.jQuery) {
    django
      .jQuery('.changeform-tabs-item:contains("General")')
      .each(function(index, tab) {
        t++;
        if (t > 1) {
          tab.remove();
        }
      });
  }
}

function addHashToInlinePaginator() {
  // Make sure nested inline paginator links to the same inline tab
  jQuery(".paginator a").each(function(index, btn) {
    if (btn.href) {
      btn.href = btn.href.split("#")[0];
      btn.href += document.location.hash;
    }
  });
}

function replaceInlineActivityAddButton() {
  django.jQuery("#activities-group .add-row a").unbind();
  django.jQuery("#activities-group .add-row a").click(function(e) {
    e.preventDefault();
    var path = document.location.pathname;
    path = path.replace(
      "initiatives/initiative/",
      "activities/activity/add/?initiative="
    );
    path = path.replace("/change/", "");
    document.location.href = path;
  });
}

function toggleDeleteButton() {
  if (window.location.hash === "#/tab/module_0/") {
    django.jQuery(".deletelink").show();
  } else {
    django.jQuery(".deletelink").hide();
  }
}

function hideDeleteButton() {
  toggleDeleteButton();
  django.jQuery(window).on("hashchange", function(e) {
    toggleDeleteButton();
  });
}

window.onload = function() {
  if (!django.jQuery && jQuery) {
    django.jQuery = jQuery;
  }
  replaceInlineActivityAddButton();
  removeRedundantTabs();
  addHashToInlinePaginator();
  hideDeleteButton();
  window.onhashchange = addHashToInlinePaginator;
};

Hide delete button on subsequent tabs


/*global angular */

(function () {
    'use strict';

    angular.module('auth', ['Facebook', 'config'])
        .config(['FacebookProvider', 'config', function (FacebookProvider, config) {
            FacebookProvider.init(config.facebook.id);
        }])
        .factory('userService', function ($http, $window, $rootScope, alert) {
            var set = function (user) {
                $rootScope.user = user;
            };

            (function () {
                $http({ method: 'GET', url: '/api/me' }).then(
                    function (user) {
                        set(user.data);
                    },
                    function () {
                        alert.add('Error in retrieving user data.');
                    }
                );
            }());

            return {
                get: function () {
                    return $rootScope.user;
                },
                login: function () {
                    $window.location.href = '/auth/facebook';
                },
                logout: function () {
                    $http({ method: 'GET', url: '/logout' })
                        .success(function () {
                            set(undefined);
                        })
                        .error(function () {
                            alert.add('Error in logging out.', true);
                        });
                }
            };
        });
}());

/*global angular */

(function () {
    'use strict';

    angular.module('auth', ['Facebook', 'config'])
        .config(['FacebookProvider', 'config', function (FacebookProvider, config) {
            FacebookProvider.init(config.facebook.id);
        }])
        .factory('userService', function ($http, $window, $rootScope, alert) {
            var set = function (user) {
                $rootScope.user = user;
            };

            (function () {
                $http({ method: 'GET', url: '/api/me' }).then(
                    function (user) {
                        set(user.data);
                    },
                    function () {
                        alert.add('Error in retrieving user data.');
                    }
                );
            }());

            return {
                get: function () {
                    return $rootScope.user;
                },
                login: function () {
                    $window.location.href = '/auth/facebook';
                },
                logout: function () {
                    $http({ method: 'POST', url: '/logout' })
                        .success(function () {
                            set(undefined);
                        })
                        .error(function () {
                            alert.add('Error in logging out.', true);
                        });
                }
            };
        });
}());

Change the logout form the verb GET to the verb POST (and only)


/*globals Polygon*/
/*exported Grid*/
var Grid = (function() {
  'use strict';

  function Grid( options ) {
    Polygon.call( this );

    options = options || {};

    this.width  = options.width  || 1;
    this.height = options.height || 1;

    this.cols = options.cols || 1;
    this.rows = options.rows || 1;

    this.generate();
  }

  Grid.prototype = Object.create( Polygon.prototype );
  Grid.prototype.constructor = Grid;

  Grid.prototype.generate = function() {
    var colWidth  = this.width  / this.cols,
        rowHeight = this.height / this.rows;

    var x, y;
    for ( y = 0; y < this.rows; y++ ) {
      for ( x = 0; x < this.cols; x++ ) {
        this.vertices.push( colWidth  * x );
        this.vertices.push( rowHeight * y );
      }
    }
  };

  Grid.prototype.drawPath = function( ctx ) {
    var colWidth  = this.width  / this.cols,
        rowHeight = this.height / this.rows;

    var vertexCount = 0.5 * this.vertices.length;

    var x, y;
    for ( var i = 0; i < vertexCount; i++ ) {
      x = i % vertexCount;
      y = Math.floor( i / vertexCount );

      ctx.rect( x * colWidth, y * rowHeight, colWidth, rowHeight );
    }
  };

  return Grid;

}) ();

/*globals Polygon*/
/*exported Grid*/
var Grid = (function() {
  'use strict';

  function Grid( options ) {
    Polygon.call( this );

    options = options || {};

    this.width  = options.width  || 1;
    this.height = options.height || 1;

    this.cols = options.cols || 1;
    this.rows = options.rows || 1;

    this.generate();
  }

  Grid.prototype = Object.create( Polygon.prototype );
  Grid.prototype.constructor = Grid;

  Grid.prototype.generate = function() {
    var colWidth  = this.width  / this.cols,
        rowHeight = this.height / this.rows;

    var x, y;
    for ( y = 0; y < this.rows; y++ ) {
      for ( x = 0; x < this.cols; x++ ) {
        this.vertices.push( colWidth  * x );
        this.vertices.push( rowHeight * y );
      }
    }
  };

  Grid.prototype.drawPath = function( ctx ) {
    var cols = this.cols,
        rows = this.rows;

    var colWidth  = this.width  / cols,
        rowHeight = this.height / rows;

    var vertexCount = 0.5 * this.vertices.length;

    var x, y;
    for ( var i = 0; i < vertexCount; i++ ) {
      x = i % cols;
      y = Math.floor( i / rows );

      ctx.rect( x * colWidth, y * rowHeight, colWidth, rowHeight );
    }
  };

  return Grid;

}) ();

Fix incorrect Grid drawPath method.


/** Allows a node to act as a "root" for a node hierarchy.

    Events:
        onready:boolean Fired when the root node has been
            completely instantiated.

    Attributes:
        ready:boolean Indicates that this root node is now ready for use. This
            starts as undefined and gets set to true when dr.notifyReady is
            called.
*/
define(function(require, exports, module) {
    var dr = require('$LIB/dr/dr.js'),
        JS = require('$LIB/jsclass.js'),
        globalRootRegistry = require('$LIB/dr/globals/globalRootRegistry.js');
    require('$LIB/dr/view/View.js');

    module.exports = dr.RootNode = new JS.Module('RootNode', {
        // Life Cycle //////////////////////////////////////////////////////////////
        initNode: function(parent, attrs) {
            this.callSuper(parent, attrs);
            globalRootRegistry.addRoot(this);
        },

        /** @overrides dr.View */
        destroyAfterOrphaning: function() {
            globalRootRegistry.removeRoot(this);
            this.callSuper();
        },

        // Accessors ///////////////////////////////////////////////////////////////
        set_ready: function(v) {
            if (this.setActual('ready', v, 'boolean', false)) {
                // Notify all descendants in a depth first manner since
                // initialization is now done.
                if (this.ready) this.walk(null, function(node) {node.notifyReady();});
            }
        },

        set_classroot: function(v) {
            this.setSimpleActual('classroot', this);
        }
    });
});

/** Allows a node to act as a "root" for a node hierarchy.

    Events:
        onready:boolean Fired when the root node has been
            completely instantiated.

    Attributes:
        ready:boolean Indicates that this root node is now ready for use. This
            starts as undefined and gets set to true when dr.notifyReady is
            called.
*/
define(function(require, exports, module) {
    var dr = require('$LIB/dr/dr.js'),
        JS = require('$LIB/jsclass.js'),
        globalRootRegistry = require('$LIB/dr/globals/GlobalRootRegistry.js');
    require('$LIB/dr/view/View.js');

    module.exports = dr.RootNode = new JS.Module('RootNode', {
        // Life Cycle //////////////////////////////////////////////////////////////
        initNode: function(parent, attrs) {
            this.callSuper(parent, attrs);
            globalRootRegistry.addRoot(this);
        },

        /** @overrides dr.View */
        destroyAfterOrphaning: function() {
            globalRootRegistry.removeRoot(this);
            this.callSuper();
        },

        // Accessors ///////////////////////////////////////////////////////////////
        set_ready: function(v) {
            if (this.setActual('ready', v, 'boolean', false)) {
                // Notify all descendants in a depth first manner since
                // initialization is now done.
                if (this.ready) this.walk(null, function(node) {node.notifyReady();});
            }
        },

        set_classroot: function(v) {
            this.setSimpleActual('classroot', this);
        }
    });
});

Fix file name for Linux / case-sensitive file systems


var
  schedule = require('node-schedule'),
  tweet = require('./tweet'),
  syncTask = require('./tasks/sync');

// Tasks to run on startup
syncTask.run();

// Update entries every 30 minutes and boot
schedule.scheduleJob('0,30 * * * *', function () {
  syncTask.run();
});

// Events
syncTask.on('new component', function (model) {
  tweet(model, function (err, reply) {
    if (err) { }
  });
});

var
  schedule = require('node-schedule'),
  tweet = require('./tweet'),
  logger = require('./logger'),
  syncTask = require('./tasks/sync');

// Tasks to run on startup
runSync();

// Update entries every 30 minutes and boot
schedule.scheduleJob('0,30 * * * *', runSync);

function runSync () {
  logger.debug.info('SYNC: Running');
  syncTask.run(function (err) {
    if (!err) {
      logger.debug.info('SYNC: Completed');
    } else {
      logger.debug.error('SYNC: Completed, ' + err.length + ' errors found', err[0]);
    }
  });
}

// Events
syncTask.on('new component', function (model) {
  tweet(model, function (err, reply) {
    if (!err) {
      logger.debug.info('TWEET: Tweeted ' + model.repo);
    } else {
      logger.debug.error('TWEET: Attempted to tweet ' + model.repo, err);
    }
  });
});

Add sync logging and tweet logging


require('coffee-script').register();
module.exports = require('./lib/auth/main');

var argv     = require('minimist')(process.argv);

Remove unused command line options parsing code


import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { observer } from 'mobx-react';
import ElectronWebView from 'react-electron-web-view';

import ServiceModel from '../../../models/Service';

@observer
class ServiceWebview extends Component {
  static propTypes = {
    service: PropTypes.instanceOf(ServiceModel).isRequired,
    setWebviewReference: PropTypes.func.isRequired,
    detachService: PropTypes.func.isRequired,
  };

  webview = null;

  componentWillUnmount() {
    const { service, detachService } = this.props;
    detachService({ service });
  }

  render() {
    const {
      service,
      setWebviewReference,
    } = this.props;

    return (
      <ElectronWebView
        ref={(webview) => { this.webview = webview; }}
        autosize
        src={service.url}
        preload="./webview/recipe.js"
        partition={`persist:service-${service.id}`}
        onDidAttach={() => {
          setWebviewReference({
            serviceId: service.id,
            webview: this.webview.view,
          });
        }}
        onUpdateTargetUrl={this.updateTargetUrl}
        useragent={service.userAgent}
        disablewebsecurity={service.recipe.disablewebsecurity}
        allowpopups
      />
    );
  }
}

export default ServiceWebview;

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { observer } from 'mobx-react';
import ElectronWebView from 'react-electron-web-view';

import ServiceModel from '../../../models/Service';

@observer
class ServiceWebview extends Component {
  static propTypes = {
    service: PropTypes.instanceOf(ServiceModel).isRequired,
    setWebviewReference: PropTypes.func.isRequired,
    detachService: PropTypes.func.isRequired,
  };

  webview = null;

  componentWillUnmount() {
    const { service, detachService } = this.props;
    detachService({ service });
  }

  refocusWebview = () => {
    const { webview } = this;
    if (!webview) return;
    webview.view.blur();
    webview.view.focus();
  };

  render() {
    const {
      service,
      setWebviewReference,
    } = this.props;

    return (
      <ElectronWebView
        ref={(webview) => {
          this.webview = webview;
          webview.view.addEventListener('did-stop-loading', this.refocusWebview);
        }}
        autosize
        src={service.url}
        preload="./webview/recipe.js"
        partition={`persist:service-${service.id}`}
        onDidAttach={() => {
          setWebviewReference({
            serviceId: service.id,
            webview: this.webview.view,
          });
        }}
        onUpdateTargetUrl={this.updateTargetUrl}
        useragent={service.userAgent}
        disablewebsecurity={service.recipe.disablewebsecurity}
        allowpopups
      />
    );
  }
}

export default ServiceWebview;

Fix disappearing cursor in services


var Teachers = require('../models/teachers');
var TeacherClasses = require('../models/Teacher_classes');
var Classes = require('../models/classes');
var ClassLessons = require('../models/class_lessons');
var Lessons = require('../models/lessons');

module.exports = {
  getClasses: function(req, res, next) {
    var teacherId = req.body.teacherId;
    //get all classes through TeacherClasses
    //TeacherClasses.getAllClasses(teacherId);
  },
};

//var Teachers = require('../models/teachers');
//var TeacherClasses = require('../models/Teacher_classes');
//var Classes = require('../models/classes');
//var ClassLessons = require('../models/class_lessons');
// var Lessons = require('../models/lessons');
//var RequestedResponses = require('../models/requested_responses');

module.exports = {
  getClasses: function(req, res, next) {
    var teacherId = req.body.teacherId;
    //get all classes through TeacherClasses
    //TeacherClasses.getAllClasses(teacherId);
  },

  getPolls: function(req, res, next) {
    // var lessonId = req.body.lessonId;
    // var allPolls = RequestedResponses.getPolls(lessonId);

  },

  thumbsCheck: function(req, res, next) {
    // var teacherId = req.body.teacherId;
    // var poll = req.body.poll;

    // this fake poll would normally be sent by the client to the socket, to the students' clients
    var poll = {
      responseId: 1,
      type: 'thumbs',
      datetime: new Date(),
      lessonId: 13,
    };
    res.json(poll);
  },

  pollClass: function(req, res, next) {
    // var teacherId = req.body.teacherId;
    // var lessonId = req.body.lessonId;

    // var poll = RequestedResponses.findPoll(lessonId);

    var poll = {
      type: 'poll',
      datetime: new Date(),
      lessonId: 4,
      responseId: 2
    };
    res.json(poll);
  },

};

Create polling helpers for thumbs and multipoll


var Router = Ember.Router.extend({
  rootURL: ENV.rootURL,
  location: 'auto'
});

Router.map(function() {
});

export default Router;

var Router = Ember.Router.extend({
  location: 'auto'
});

Router.map(function() {
});

export default Router;

Remove specifying rootURL from ENV


module.exports = ({
  config
}) => {

  const upload = require('multer')({
    dest: config.upload.path
  });

  return (route, app) => {

    app.post(route,
      upload.any(),
      function(req, res){

        if(req.files.length < 1){
          res.status(400).send('Error: No file attached');
        }

        const fileStats = req.files[0];

        res.send({
          id: fileStats.filename,
          name: fileStats.originalname,
          mimetype: fileStats.mimetype,
          size: fileStats.size
        });
      });
  };
};

module.exports = ({
  config
}) => {

  return function(req, res){

    if(req.files.length < 1){
      res.status(400).send('Error: No file attached');
    }

    const fileStats = req.files[0];

    res.send({
      id: fileStats.filename,
      name: fileStats.originalname,
      mimetype: fileStats.mimetype,
      size: fileStats.size
    });
  };
};

Fix web route for file upload


exports.up = function(knex, Promise) {
  return knex.schema.createTable('course', function(table) {
    table.increments('id').primary();
    table.string('name');
    table.string('code');
  });
};

exports.down = function(knex, Promise) {
  return knex.schema.dropTable('course');
};

// Migrations define the database schema so you can easily
// create tables and modify the schema of your database.
// See: http://knexjs.org/#Migrations

// You can create a migration file template (like this one) with:
// $ knex migrate:make migration_name

// The up function migrates the database forward,
// applying our change to the database schema.
// In this migration, we create a table named 'course' with
// the columns 'id', 'name', and 'code'. 'id' is an
// autoincrementing column, so it will get filled in
// automatically whenever a new row is created.
exports.up = function(knex, Promise) {
  return knex.schema.createTable('course', function(table) {
    table.increments('id').primary();
    table.string('name');
    table.string('code');
  });
};

// The down function specifies the steps to roll back the migration.
// In the up function, we created the 'course' table, so in
// the down function, we drop the 'course' table.
exports.down = function(knex, Promise) {
  return knex.schema.dropTable('course');
};

Add comments to the initial migration.