'use strict'

const webpack = require('webpack')
const path = require('path')

const configuration = {
  entry: path.resolve(__dirname, 'app'),
  output: {
    path: path.resolve(__dirname, 'public'),
    filename: '[name].js'
  },
  module: {
    loaders: [
      { test: /\.js?$/, loader: 'react-hot-loader', include: path.join(__dirname, 'app') },
      { test: /\.js?$/, loader: 'babel-loader', exclude: /node_modules/ },
      { test: /\.scss$/, loaders: [ 'style-loader', 'css-loader', 'sass-loader' ] }
    ]
  },
  plugins: [
    new webpack.DefinePlugin(
      {
        'process.env.NODE_ENV': JSON.stringify('production')
      }
    ),
    new webpack.optimize.AggressiveMergingPlugin(),
    new webpack.optimize.OccurrenceOrderPlugin()
  ],
  devtool: 'cheap-source-map',
  cache: true
}

module.exports = configuration

'use strict'

const webpack = require('webpack')
const path = require('path')

var HtmlWebpackPlugin = require('html-webpack-plugin')

const configuration = {
  entry: [
    path.resolve(__dirname, 'app')
  ],
  output: {
    path: path.resolve(__dirname, 'public/build/'),
    filename: '[hash].js'
  },
  module: {
    loaders: [
      { test: /\.js?$/, loader: 'babel-loader', exclude: /node_modules/ },
      { test: /\.scss$/, loaders: [ 'style-loader', 'css-loader', 'sass-loader' ] },
      { test: /\.html$/, loaders: [ 'html-loader' ] }
    ]
  },
  plugins: [
    new webpack.DefinePlugin(
      {
        'process.env.NODE_ENV': JSON.stringify('production')
      }
    ),
    new HtmlWebpackPlugin(
      {
        template: path.resolve(__dirname, 'public/index.html'),
        favicon: path.resolve(__dirname, 'public/favicon.ico'),
        inject: true,
        minify: {}
      }
    ),
    new webpack.optimize.AggressiveMergingPlugin(),
    new webpack.optimize.OccurrenceOrderPlugin()
  ],
  devtool: 'cheap-source-map',
  cache: true
}

module.exports = configuration

Add a new build process for the production environment.


import Promise from 'bluebird';
import { lookupServiceAsync } from '../utils/lookup-service';

function findService(fullyQualifiedName) {
  // Get just the service name from the fully qualified name
  let nameParts = fullyQualifiedName.split('.');
  let serviceName = nameParts[nameParts.length - 1];

  // Insert a dash before any capital letters, convert to lowercase, and remove any leading dash
  serviceName = serviceName.replace(/[ABCDEFGHIJKLMNOPQRSTUVWXYZ]/g, '-$&').toLowerCase();
  if (serviceName.startsWith('-')) {
    serviceName = serviceName.substr(1);
  }

  // We should have something like 'video-catalog-service' now, so try and find the service
  return lookupServiceAsync(serviceName).then(hosts => hosts[0]);
}

/**
 * Finds the location for a service from the fully qualified name and returns a Promise that resolves to a
 * string of the IP:Port where that service can be found.
 */
export const findServiceAsync = Promise.method(findService);

import Promise from 'bluebird';
import { lookupServiceAsync } from '../utils/lookup-service';

function findService(fullyQualifiedName) {
  // Get just the service name from the fully qualified name
  let nameParts = fullyQualifiedName.split('.');
  let serviceName = nameParts[nameParts.length - 1];

  // We should have something like 'video-catalog-service' now, so try and find the service
  return lookupServiceAsync(serviceName).then(hosts => hosts[0]);
}

/**
 * Finds the location for a service from the fully qualified name and returns a Promise that resolves to a
 * string of the IP:Port where that service can be found.
 */
export const findServiceAsync = Promise.method(findService);

Use the short service name when resolving Grpc services


Geolocation.latLng()

Template.newIssue.events({
  'submit form': function(){
    event.preventDefault();
    var title = event.target.title.value;
    var description = event.target.description.value;
    var imageURL = Session.get('imageURL');
    console.log(title, description);
    if (title && description && Geolocation.latLng()) {
        Issues.insert({
            title: title,
            description: description,
            status: 'open',
            lat: Geolocation.latLng().lat,
            lng: Geolocation.latLng().lng,
            userID: Meteor.userId(),
            imageURL: imageURL,
            createdAt: new Date(),
            lastModified: new Date()
        });
        Router.go('/issues-list');
    } else {
        console.log("form not valid");
    }
  }
});

Template.dashboard.helpers({
  title: function(){
    return "Status of Submitted"
  },
  issues: function (){
      // Show newest tasks at the top
      return Issues.find({'userID': this.userId}, {sort: {createdAt: 1}})
  }
});

Template.task.helpers({
  label_mapper: function(par){
    var dict = {};
    //Updates labels for submitted issues
    dict["open"] = "-warning";
    dict["rejected"] = "-danger";
    dict["solved"] = "-success";
    dict["pending"] = "-info"
    return dict[par]
  }
});

Geolocation.latLng()

Template.newIssue.events({
  'submit form': function(){
    event.preventDefault();
    var title = event.target.title.value;
    var description = event.target.description.value;
    var imageURL = Session.get('imageURL');
    console.log(title, description);
    if (title && description && Geolocation.latLng()) {
        Issues.insert({
            title: title,
            description: description,
            status: 'open',
            lat: Geolocation.latLng().lat,
            lng: Geolocation.latLng().lng,
            userID: Meteor.userId(),
            imageURL: imageURL,
            createdAt: new Date(),
            lastModified: new Date()
        });
        Router.go('/issues-list');
    } else {
        console.log("form not valid");
    }
  }
});

Template.IssuesList.helpers({
  title: function(){
    return "Status of Submitted"
  },
  issues: function (){
    // Show newest tasks at the top
    return Issues.find({'userID': Meteor.userId()}, {sort: {createdAt: 1}})
  }
});

Template.task.helpers({
  label_mapper: function(par){
    var dict = {};
    //Updates labels for submitted issues
    dict["open"] = "-warning";
    dict["rejected"] = "-danger";
    dict["solved"] = "-success";
    dict["pending"] = "-info"
    return dict[par]
  }
});

Fix issue list after change of authentication package


/**
 * @flow
 */

import bunyan from 'bunyan';
import path from 'path';

import UserSettings from './UserSettings';

class ConsoleRawStream {
  write(rec) {
    if (rec.level < bunyan.INFO) {
      console.log(rec);
    } else if (rec.level < bunyan.WARN) {
      console.info(rec);
    } else if (rec.level < bunyan.ERROR) {
      console.warn(rec);
    } else {
      console.error(rec);
    }
  }
}

let logger = bunyan.createLogger({
  name: 'exponent',
  serializers: bunyan.stdSerializers,
  streams: [
    {
      level: 'debug',
      type: 'rotating-file',
      path: path.join(UserSettings.dotExponentHomeDirectory(), 'log'),
      period: '1d',   // daily rotation
      count: 3,       // keep 3 back copies
    },
    ...(process.env.DEBUG && process.env.NODE_ENV !== 'production' ? [{
      type: 'raw',
      stream: new ConsoleRawStream(),
      closeOnExit: false,
      level: 'debug',
    }] : []),
  ],
});

logger.notifications = logger.child({type: 'notifications'});
logger.global = logger.child({type: 'global'});
logger.DEBUG = bunyan.DEBUG;
logger.INFO = bunyan.INFO;
logger.WARN = bunyan.WARN;
logger.ERROR = bunyan.ERROR;

logger.clearNotification = (id: string) => {
  if (Config.useReduxNotifications) {
    state.store.dispatch(state.actions.notifications.clearGlobal(id));
  }
}

export default logger;

/**
 * @flow
 */

import bunyan from 'bunyan';
import path from 'path';

import UserSettings from './UserSettings';

class ConsoleRawStream {
  write(rec) {
    if (rec.level < bunyan.INFO) {
      console.log(rec);
    } else if (rec.level < bunyan.WARN) {
      console.info(rec);
    } else if (rec.level < bunyan.ERROR) {
      console.warn(rec);
    } else {
      console.error(rec);
    }
  }
}

let logger = bunyan.createLogger({
  name: 'exponent',
  serializers: bunyan.stdSerializers,
  streams: [
    {
      level: 'debug',
      type: 'rotating-file',
      path: path.join(UserSettings.dotExponentHomeDirectory(), 'log'),
      period: '1d',   // daily rotation
      count: 3,       // keep 3 back copies
    },
    ...(process.env.DEBUG && process.env.NODE_ENV !== 'production' ? [{
      type: 'raw',
      stream: new ConsoleRawStream(),
      closeOnExit: false,
      level: 'debug',
    }] : []),
  ],
});

logger.notifications = logger.child({type: 'notifications'});
logger.global = logger.child({type: 'global'});
logger.DEBUG = bunyan.DEBUG;
logger.INFO = bunyan.INFO;
logger.WARN = bunyan.WARN;
logger.ERROR = bunyan.ERROR;

export default logger;

Remove stray unused redux notif function.


// GEOSEARCH /////////////////////////////////////////////////////////

window.setupGeosearch = function() {
  $('#geosearch').keypress(function(e) {
    if((e.keyCode ? e.keyCode : e.which) != 13) return;

    var search = $(this).val();

    if (!runHooks('geoSearch', search)) {
      return;
    }

    $.getJSON(NOMINATIM + encodeURIComponent(search), function(data) {
      if(!data || !data[0]) return;
      var b = data[0].boundingbox;
      if(!b) return;
      var southWest = new L.LatLng(b[0], b[2]),
          northEast = new L.LatLng(b[1], b[3]),
          bounds = new L.LatLngBounds(southWest, northEast);
      window.map.fitBounds(bounds);
      if(window.isSmartphone()) window.smartphone.mapButton.click();
    });
    e.preventDefault();
  });
  $('#geosearchwrapper img').click(function(){
    map.locate({setView : true});;
  });
}

// GEOSEARCH /////////////////////////////////////////////////////////

window.setupGeosearch = function() {
  $('#geosearch').keypress(function(e) {
    if((e.keyCode ? e.keyCode : e.which) != 13) return;

    var search = $(this).val();

    if (!runHooks('geoSearch', search)) {
      return;
    }

    $.getJSON(NOMINATIM + encodeURIComponent(search), function(data) {
      if(!data || !data[0]) return;
      var b = data[0].boundingbox;
      if(!b) return;
      var southWest = new L.LatLng(b[0], b[2]),
          northEast = new L.LatLng(b[1], b[3]),
          bounds = new L.LatLngBounds(southWest, northEast);
      window.map.fitBounds(bounds);
      if(window.isSmartphone()) window.smartphone.mapButton.click();
    });
    e.preventDefault();
  });
  $('#geosearchwrapper img').click(function(){
    map.locate({setView : true, maxZoom: 13});;
  });
}

Set maxZoom = 13 for desktop locate button too.


import nextRoutes from 'next-routes';

const pages = nextRoutes();

pages
  .add('signin', '/signin/:token?')
  .add('createEvent', '/:parentCollectiveSlug/events/(new|create)')
  .add('events-iframe', '/:collectiveSlug/events/iframe')
  .add('event', '/:parentCollectiveSlug/events/:eventSlug')
  .add('editEvent', '/:parentCollectiveSlug/events/:eventSlug/edit')
  .add('events', '/:collectiveSlug/events')
  .add('tiers', '/:collectiveSlug/tiers')
  .add('editTiers', '/:collectiveSlug/tiers/edit')
  .add('orderCollectiveTier', '/:collectiveSlug/order/:TierId/:amount?/:interval?', 'createOrder')
  .add('orderEventTier', '/:collectiveSlug/events/:eventSlug/order/:TierId', 'createOrder')
  .add('donate', '/:collectiveSlug/:verb(donate|pay|contribute)/:amount?/:interval?', 'createOrder')
  .add('tiers-iframe', '/:collectiveSlug/tiers/iframe')
  .add('transactions', '/:collectiveSlug/transactions')
  .add('nametags', '/:parentCollectiveSlug/events/:eventSlug/nametags')
  .add('button', '/:collectiveSlug/:verb(contribute|donate)/button')
  .add('collective', '/:slug')
  .add('editCollective', '/:slug/edit')

  module.exports = pages;

import nextRoutes from 'next-routes';

const pages = nextRoutes();

pages
  .add('widgets', '/widgets')
  .add('tos', '/tos')
  .add('privacypolicy', '/privacypolicy')
  .add('signin', '/signin/:token?')
  .add('button', '/:collectiveSlug/:verb(contribute|donate)/button')
  .add('createEvent', '/:parentCollectiveSlug/events/(new|create)')
  .add('events-iframe', '/:collectiveSlug/events/iframe')
  .add('event', '/:parentCollectiveSlug/events/:eventSlug')
  .add('editEvent', '/:parentCollectiveSlug/events/:eventSlug/edit')
  .add('events', '/:collectiveSlug/events')
  .add('tiers', '/:collectiveSlug/tiers')
  .add('editTiers', '/:collectiveSlug/tiers/edit')
  .add('orderCollectiveTier', '/:collectiveSlug/order/:TierId/:amount?/:interval?', 'createOrder')
  .add('orderEventTier', '/:collectiveSlug/events/:eventSlug/order/:TierId', 'createOrder')
  .add('donate', '/:collectiveSlug/:verb(donate|pay|contribute)/:amount?/:interval?', 'createOrder')
  .add('tiers-iframe', '/:collectiveSlug/tiers/iframe')
  .add('transactions', '/:collectiveSlug/transactions')
  .add('expenses', '/:collectiveSlug/expenses')
  .add('nametags', '/:parentCollectiveSlug/events/:eventSlug/nametags')
  .add('collective', '/:slug')
  .add('editCollective', '/:slug/edit')

  module.exports = pages;

Fix for /widgets, /tos, /privacypolicy, /:slug/:verb/button


'use strict'

var chalk = require('chalk')
var deepEqual = require('deep-equal')
var indent = require('./indent')
var os = require('os')

const CHECK = '\u2713'
const CROSS = '\u2717'
const PASS_COLOR = 'green'
const FAIL_COLOR = 'red'

module.exports = class Result {
  constructor (runnable, options) {
    options = options || {}
    this.runnable = runnable
    this.error = options.error
    this.results = options.results || []
    this.actual = options.actual
    this.expected = options.expected

    if (this.error === undefined && (this.actual !== undefined || this.expected !== undefined)) {
      this.error = !deepEqual(this.actual, this.expected, { strict: true })
    }
  }

  isErroring () {
    return Boolean(this.error || this.results.some(result => result.isErroring()))
  }

  toString () {
    var isErroring = this.isErroring()
    var status = isErroring ? CROSS : CHECK
    var color = isErroring ? FAIL_COLOR : PASS_COLOR

    return chalk[color](`${status} ${this.runnable}`)
  }

  toTree () {
    var indented = this.results.map(result => indent(result.toTree()))
    return [this.toString()].concat(indented).join(os.EOL)
  }
}

'use strict'

var chalk = require('chalk')
var deepEqual = require('deep-equal')
var indent = require('./indent')
var os = require('os')

const CHECK = '✓'
const CROSS = '✗'
const PASS_COLOR = 'green'
const FAIL_COLOR = 'red'

module.exports = class Result {
  constructor (runnable, options) {
    options = options || {}
    this.runnable = runnable
    this.error = options.error
    this.results = options.results || []
    this.actual = options.actual
    this.expected = options.expected

    if (this.error === undefined && (this.actual !== undefined || this.expected !== undefined)) {
      this.error = !deepEqual(this.actual, this.expected, { strict: true })
    }
  }

  isErroring () {
    return Boolean(this.error || this.results.some(result => result.isErroring()))
  }

  toString () {
    var isErroring = this.isErroring()
    var status = isErroring ? CROSS : CHECK
    var color = isErroring ? FAIL_COLOR : PASS_COLOR

    return chalk[color](`${status} ${this.runnable}`)
  }

  toTree () {
    var indented = this.results.map(result => indent(result.toTree()))
    return [this.toString()].concat(indented).join(os.EOL)
  }
}

Use unicode special characters directly in source


export function apply(issue, jiraClientAPI) {
  if(issue === null || issue.fields.status.statusCategory.colorName !== 'yellow') {
    return Promise.reject(new Error(`Cannot commit against this issue ${issue.key}`));
  }

  return Promise.resolve(true);
}

export function apply(issue, jiraClientAPI) {
  if(issue === null || issue.fields.status.statusCategory.colorName !== 'yellow') {
    return Promise.reject(new Error(`Cannot commit against this issue ${issue.key}. Make sure the issue exists and has a yellow status`));
  }

  return Promise.resolve(true);
}

Add additional issue status error info


import Geometric from './geometric/geometric.js'

export default class Background
{
  static draw(p)
  {
    Background.changeColor(p)
    Background.translateCamera(p)
    Background.translateCameraByMouse(p)

    Geometric.draw(p)
  }

  static changeColor(p)
  {
    const hexColorMax          = 255
    const radianX2             = p.PI * 2
    const fps                  = 60
    const fpsGear              = 1 / 20

    const radianPerFrame       = radianX2 / fps
    const radianPerFrameGeared = radianPerFrame * fpsGear
    const fpsReal              = fps / fpsGear
    const frame                = p.frameCount % fpsReal
    const radian               = radianPerFrameGeared * frame
    const sine                 = p.sin(radian)
    const sineMapped1          = (sine + 1) / 2
    const hexColor             = sineMapped1 * hexColorMax

    p.background(hexColor)
  }

  static translateCamera(p)
  {
    const xTranslate = p.frameCount * 0.01
    const yTranslate = 0
    const zTranslate = -(p.windowHeight / 2)

    p.translate(xTranslate, yTranslate, zTranslate)
  }

  static translateCameraByMouse(p)
  {
    const xTranslate = -p.mouseX / 10
    const yTranslate = -p.mouseY / 10

    p.translate(xTranslate, yTranslate)
  }
}

import Geometric from './geometric/geometric.js'

export default class Background
{
  static draw(p)
  {
    Background.changeColor(p)
    Background.translateCamera(p)
    Background.translateCameraByMouse(p)

    Geometric.draw(p)
  }

  static changeColor(p)
  {
    const hexColorMax          = 255
    const radianX2             = p.PI * 2
    const fps                  = 60
    const fpsGear              = 1 / 20

    const radianPerFrame       = radianX2 / fps
    const radianPerFrameGeared = radianPerFrame * fpsGear
    const fpsReal              = fps / fpsGear
    const frame                = p.frameCount % fpsReal
    const radian               = radianPerFrameGeared * frame
    const sine                 = p.sin(radian)
    const sineMapped1          = (sine + 1) / 2
    const hexColor             = sineMapped1 * hexColorMax

    p.background(hexColor)
  }

  static translateCamera(p)
  {
    const xTranslate = p.frameCount * 0.01
    const yTranslate = 0

    let zTranslate = -(p.width / 3)
    if (window.screen.width < 600) {
      zTranslate = window.screen.width / 5
    }

    p.translate(xTranslate, yTranslate, zTranslate)
  }

  static translateCameraByMouse(p)
  {
    const xTranslate = -p.mouseX / 10
    const yTranslate = -p.mouseY / 10

    p.translate(xTranslate, yTranslate)
  }
}

Fix main camera zTranslation responsive issue


var requirejs = require('requirejs');

var PageConfig = requirejs('page_config');

var get_dashboard_and_render = require('./server/mixins/get_dashboard_and_render');

var renderContent = function (req, res, model) {
  model.set(PageConfig.commonConfig(req));

  var ControllerClass = model.get('controller');
  var controller = new ControllerClass({
    model: model,
    url: req.originalUrl
  });

  controller.once('ready', function () {
    res.set('Cache-Control', 'public, max-age=600');
    if (model.get('published') !== true) {
      res.set('X-Robots-Tag', 'none');
    }
    req['spotlight-dashboard-slug'] = model.get('slug');
    res.send(controller.html);
  });

  controller.render({ init: true });

  return controller;
};

var setup = function (req, res) {
  var client_instance = setup.get_dashboard_and_render(req, res, setup.renderContent);
  //I have no idea what this does, can't find anything obvious in the docs or this app.
  client_instance.set('script', true);
  client_instance.setPath(req.url.replace('/performance', ''));
};

setup.renderContent = renderContent;
setup.get_dashboard_and_render = get_dashboard_and_render;

module.exports = setup;

var requirejs = require('requirejs');

var PageConfig = requirejs('page_config');

var get_dashboard_and_render = require('./server/mixins/get_dashboard_and_render');

var renderContent = function (req, res, model) {
  model.set(PageConfig.commonConfig(req));

  var ControllerClass = model.get('controller');
  var controller = new ControllerClass({
    model: model,
    url: req.originalUrl
  });

  controller.once('ready', function () {
    res.set('Cache-Control', 'public, max-age=600');
    if (model.get('published') !== true) {
      res.set('X-Robots-Tag', 'none');
    }
    req['spotlight-dashboard-slug'] = model.get('slug');
    res.send(controller.html);
  });

  controller.render({ init: true });

  return controller;
};

var setup = function (req, res) {
  var client_instance = setup.get_dashboard_and_render(req, res, setup.renderContent);
  client_instance.set('script', true);
  client_instance.setPath(req.url.replace('/performance', ''));
};

setup.renderContent = renderContent;
setup.get_dashboard_and_render = get_dashboard_and_render;

module.exports = setup;

Remove comment about client_instance script


// Code based largely on this module:
// https://www.npmjs.org/package/git-credential
function parseOutput(data, callback) {
    var output = {};

    if (data) {
        output = data.toString('utf-8')
            .split('\n')
            .map(function (line) {
                return line.split('=');
            })
            .filter(function (lineItems) {
                // Filter out empty lines
                return lineItems.length === 2;
            })
            .reduce(function (obj, val) {
                obj[val[0].trim()] = val[1].trim();
                return obj;
            }, {});
    }

    callback(null, output);
}

module.exports = parseOutput;

// Code based largely on this module:
// https://www.npmjs.org/package/git-credential
function parseOutput(data, callback) {
    var output = {};

    if (data) {
        output = data.toString('utf-8')
            .split('\n')
            .map(function (line) {
                var index = line.indexOf('=');
                if (index !== -1) {
                    return [line.substr(0, index), line.substr(index + 1)];
                } else {
                    return line;
                }
            })
            .filter(function (lineItems) {
                // Filter out empty lines
                return lineItems.length === 2;
            })
            .reduce(function (obj, val) {
                obj[val[0].trim()] = val[1].trim();
                return obj;
            }, {});
    }

    callback(null, output);
}

module.exports = parseOutput;

Return password when it contains =


const initState = {};

export default (state = initState, action) => {
    switch (action.type) {
        case "GET_STATIONBOARD_REQUESTED":
            const { stationId } = action.payload;
            return {
                ...state,
                [stationId]: {
                    data: [],
                    pending: true,
                },
            };
        case "GET_STATIONBOARD_FULFILLED":
            const { stationboard } = action.payload;
            return {
                ...state,
                [action.payload.stationId]: {
                    ...state[action.payload.stationId],
                    data: stationboard.map(
                        ({
                            category,
                            number,
                            to,
                            stop: { departureTimestamp },
                        }) => ({
                            category,
                            number,
                            to,
                            departureTimestamp,
                        }),
                    ),
                    pending: false,
                },
            };
        default:
            return state;
    }
};

const initState = {};

export default (state = initState, action) => {
    switch (action.type) {
        case "GET_STATIONBOARD_REQUESTED":
            const { stationId } = action.payload;
            return {
                ...state,
                [stationId]: {
                    data: [],
                    pending: true,
                },
            };
        case "GET_STATIONBOARD_FULFILLED":
            const { stationboard } = action.payload;
            return {
                ...state,
                [action.payload.stationId]: {
                    ...state[action.payload.stationId],
                    data: stationboard.map(
                        ({
                            category,
                            number,
                            to,
                            stop: { departureTimestamp },
                            passList: checkpoints,
                        }) => ({
                            category,
                            number,
                            to,
                            departureTimestamp,
                            checkpoints,
                        }),
                    ),
                    pending: false,
                },
            };
        default:
            return state;
    }
};

Add checkpoints to the stationboard objects


/**
 * 12-22-2016
 * ~~ Scott Johnson
 */

/** List jshint ignore directives here. **/
/* jslint node: true */
/* jshint esversion: 6 */
/*eslint-env es6*/

// Stop jshint from complaining about the promise.catch() syntax.
/* jslint -W024 */

var util = require( './lib/bob-util.js' );
var Batch = require( './lib/bob-batch.js' );
var Bob = module.exports = {};

Bob.createBatch = function(){
    var self = Batch.apply( this, arguments );
    return self;
};// /createBatch()

Bob.watch = function(){
    return util.watch.apply( this, arguments );
};// /watch()

Bob.log = function(){
    return util.log.apply( this, arguments );
};// /log()

/**
 * 12-22-2016
 * ~~ Scott Johnson
 */

/** List jshint ignore directives here. **/
/* jslint node: true */
/* jshint esversion: 6 */
/*eslint-env es6*/

// Stop jshint from complaining about the promise.catch() syntax.
/* jslint -W024 */

var util = require( './lib/bob-util.js' );
var Batch = require( './lib/bob-batch.js' );
var Bob = module.exports = {};

Bob.createBatch = function(){
    var self = Batch.apply( this, arguments );
    return self;
};// /createBatch()

Bob.createJob = function(){
    var batch = Bob.createBatch();
    return batch.createJob.apply( batch, arguments );
};// /createJob()

Bob.watch = function(){
    return util.watch.apply( this, arguments );
};// /watch()

Bob.log = function(){
    return util.log.apply( this, arguments );
};// /log()

Create jobs directly through bob.


// Welcome!
// Add your github user if you accepted the challenge!

var players = [
	'raphamorim',
	'israelst',
	'afonsopacifer',
	'rafaelfragosom',
    'brunokinoshita',
	'paulinhoerry',
	'enieber',
    'alanrsoares'
];

module.exports = players;

// Welcome!
// Add your github user if you accepted the challenge!

var players = [
	'raphamorim',
	'israelst',
	'afonsopacifer',
	'rafaelfragosom',
    'brunokinoshita',
	'paulinhoerry',
	'enieber',
    'alanrsoares',
    'brunodsgn'
];

module.exports = players;

Add brunodsgn as new challenger


import { Component, Children, PropTypes } from "react";
import withSideEffect from "react-side-effect";

const supportedHTML4Attributes = {
  "bgColor": "bgcolor"
};

class BodyAttributes extends Component {
  render() {
    return Children.only(this.props.children);
  }
}

BodyAttributes.propTypes = {
  children: PropTypes.node.isRequired
};

function reducePropsToState(propsList) {
  const attrs = {};
  propsList.forEach(function (props) {
    const transformedAttrs = transformHTML4Props(props);
    Object.assign(attrs, props, transformedAttrs);
  });
  return attrs;
}

function handleStateChangeOnClient(attrs) {
  for (const key in attrs) {
    document.body.setAttribute(key, attrs[key]);
  }
}

function transformHTML4Props(props) {
  const transformedProps = {};

  // Provide support for HTML4 attributes on the body tag for
  // e-mail purposes. Convert tags to ones oy-vey can translate
  // during the render.
  Object.keys(supportedHTML4Attributes).forEach(propName => {
    if (props.hasOwnProperty(propName)) {
      const name = supportedHTML4Attributes[propName];
      const value = props[propName];
      const transformedProp = { [`data-oy-${name}`]: value };
      Object.assign(transformedProps, transformedProp);
    }
  });
  return transformedProps;
}

export default withSideEffect(
  reducePropsToState,
  handleStateChangeOnClient
)(BodyAttributes);

import { Component, Children, PropTypes } from "react";
import withSideEffect from "react-side-effect";

const supportedHTML4Attributes = {
  "bgColor": "bgcolor"
};

class BodyAttributes extends Component {
  render() {
    return Children.only(this.props.children);
  }
}

BodyAttributes.propTypes = {
  children: PropTypes.node.isRequired
};

function reducePropsToState(propsList) {
  const attrs = {};
  propsList.forEach(function (props) {
    const transformedAttrs = transformHTML4Props(props);
    Object.assign(attrs, props, transformedAttrs);
  });
  return attrs;
}

function handleStateChangeOnClient(attrs) {
  for (const key in attrs) {
    document.body.setAttribute(key, attrs[key]);
  }
}

function transformHTML4Props(props) {
  const transformedProps = {};

  /*
   * Provide support for HTML4 attributes on the body tag for
   * e-mail purposes. Convert tags to ones oy-vey can translate
   * during the render.
   *
   * Note: Only attributes that are white-listed by oy-vey will be rendered
   *
   */
  Object.keys(supportedHTML4Attributes).forEach(propName => {
    if (props.hasOwnProperty(propName)) {
      const name = supportedHTML4Attributes[propName];
      const value = props[propName];
      const transformedProp = { [`data-oy-${name}`]: value };
      Object.assign(transformedProps, transformedProp);
    }
  });
  return transformedProps;
}

export default withSideEffect(
  reducePropsToState,
  handleStateChangeOnClient
)(BodyAttributes);

Improve comment around transformed attributes


// block scope - let
// To do: make all tests pass, leave the asserts unchanged!

describe('`let` restricts the scope of the variable to the current block', () => {

  describe('`let` vs. `var`', () => {

    it('`var` works as usual', () => {
      if (true) {
        var varX = true;
      }
      assert.equal(varX, true);
    });

    it('`let` restricts scope to inside the block', () => {
      if(true) {
        let letX = true;
      }
      assert.throws(() => console.log(letX));
    });

  });

  it('`let` use in `for` loops', () => {
    let obj = {x: 1};
    for (let key in obj) {}
    assert.throws(() => console.log(key));
  });

  it('create artifical scope, using curly braces', () => {
    {
      let letX = true;
    }
    assert.throws(() => console.log(letX));
  });

});

// block scope - let
// To do: make all tests pass, leave the asserts unchanged!

describe('`let` restricts the scope of the variable to the current block', () => {

  describe('`let` vs. `var`', () => {

    it('`var` works as usual', () => {
      if (true) {
        let varX = true;
      }
      assert.equal(varX, true);
    });

    it('`let` restricts scope to inside the block', () => {
      if (true) {
        var letX = true;
      }
      assert.throws(() => console.log(letX));
    });

  });

  describe('`let` usage', () => {

    it('`let` use in `for` loops', () => {
      let obj = {x: 1};
      for (var key in obj) {}
      assert.throws(() => console.log(key));
    });

    it('create artifical scope, using curly braces', () => {
      {
        var letX = true;
      }
      assert.throws(() => console.log(letX));
    });

  });

});

Make it nicer and break it, to be a kata :).


let selectENV = (env) => {
  if (env === 'development') {
    return 'postgres://localhost:5432/todos';
  } else if (env === 'test') {
    return 'postgres://localhost:5432/todos_test_db';
  }
}

module.exports = { selectENV };

let selectENV = (env) => {
  if (env === 'development') {
    return 'postgres://localhost:5432/todos';
  } else if (env === 'test') {
    return 'postgres://localhost:5432/todos_test';
  }
}

module.exports = { selectENV };

Fix typo in setDev func


"use strict";

// For conditions of distribution and use, see copyright notice in LICENSE
/*
 *      @author Tapani Jamsa
 *      @author Erno Kuusela
 *      @author Toni Alatalo
 *      Date: 2013
 */

var app = new Application();

app.host = "localhost"; // IP to the Tundra server
app.port = 2345; // and port to the server

app.start();

// app.viewer.useCubes = true; // Use wireframe cube material for all objects

app.connect(app.host, app.port);

"use strict";

// For conditions of distribution and use, see copyright notice in LICENSE
/*
 *      @author Tapani Jamsa
 *      @author Erno Kuusela
 *      @author Toni Alatalo
 *      Date: 2013
 */

var app = new Application();

var host = "localhost"; // IP to the Tundra server
var port = 2345; // and port to the server

app.start();

// app.viewer.useCubes = true; // Use wireframe cube material for all objects

app.connect(host, port);

Make host and port standard variables


import Phaser from 'phaser-ce';
import { getConfig } from './config';
import TutorialState from './TutorialState';

export default class WreckSam {

    constructor() {
        const config = getConfig();
        this.game = new Phaser.Game(config);

        this.game.state.add('tutorial', new TutorialState());
    }

    start(state) {
        this.game.state.start(state);
    }

    pause(){
        this.game.lockRender = true;
    }

    unpause(){
        this.game.lockRender = false;
    }

    destroy() {
        this.game.destroy();

        this.game = null;
    }

}

import Phaser from 'phaser-ce';
import { getConfig } from './config';
import TutorialState from './TutorialState';

export default class WreckSam {

    constructor() {
        const config = getConfig();
        this.game = new Phaser.Game(config);

        this.game.state.add('tutorial', new TutorialState());
    }

    start(state) {
        this.game.state.start(state);
    }

    pause(){
        this.game.paused = true;
    }

    unpause(){
        this.game.paused = false;
    }

    destroy() {
        this.game.destroy();

        this.game = null;
    }

}

Use game paused property instead of lockRender


// Loader to create the Ember.js application
/*global require */

window.App = require('ghost/app')['default'].create();

// Loader to create the Ember.js application
/*global require */

if (!window.disableBoot) {
    window.App = require('ghost/app')['default'].create();
}

Add initial client unit test.


function makeRegistry( base ) {
    return {
        _data: {},
        add: function(name, func) {
            // precautionary case conversion, as later querying of
            // the registry by function-caller uses lower case as well.
            name = name.toLowerCase();

            if (this._data.hasOwnProperty(name)) {
                //TODO warn
            }
            this._data[name] = func;
        },
        addMultiple: function(functions) {
            Object.keys(functions).forEach(
                function(name) {
                    this.add(name, functions[name]);
                }.bind(this));
        },
        get: function(name) {
            return this._data[name] || ( base && base.get( name ));
        },
        inherit : function() {
            return makeRegistry( this );
        }
    };
}

module.exports = makeRegistry( null );

function makeRegistry( base ) {
    return {
        _data: {},
        add: function(name, func) {
            // precautionary case conversion, as later querying of
            // the registry by function-caller uses lower case as well.
            name = name.toLowerCase();

            if (this._data.hasOwnProperty(name)) {
                //TODO warn
            }
            this._data[name] = func;
        },
        addMultiple: function(functions) {
            Object.keys(functions).forEach(
                function(name) {
                    this.add(name, functions[name]);
                }.bind(this));
        },
        get: function(name) {
            return this._data[name] || ( base && base.get( name ));
        },
        getLocalFunctions: function() {
            return this._data;
        },
        inherit: function() {
            return makeRegistry( this );
        },
        create: function(base) {
            return makeRegistry(base);
        }
    };
}

module.exports = makeRegistry( null );

Add create() and getLocalFunctions() to function registry so it can be used for plugins


import postcss from "postcss"
import fs from "fs"
import gs from "glob-stream"
import rcLoader from "rc-loader"
import { Transform } from "stream"
import plugin from "./plugin"

export default function ({ files, config } = {}) {
  const stylelintConfig = config || rcLoader("stylelint")
  if (!stylelintConfig) {
    throw new Error("No stylelint config found")
  }

  const linter = new Transform({ objectMode: true })

  linter._transform = function (chunk, enc, callback) {
    if (files) {
      const filepath = chunk.path
      fs.readFile(filepath, "utf8", (err, css) => {
        if (err) { linter.emit("error",  err) }
        lint({ css, filepath: filepath }, callback)
      })
    } else {
      lint({ css: chunk }, callback)
    }
  }

  function lint({ css, filepath }, callback) {
    const processOptions = {}
    if (filepath) {
      processOptions.from = filepath
    }
    postcss()
      .use(plugin(stylelintConfig))
      .process(css, processOptions)
      .then(result => {
        callback(null, result)
      })
      .catch(err => {
        linter.emit("error", new Error(err))
      })
  }

  if (files) {
    const fileStream = gs.create(files)
    return fileStream.pipe(linter)
  }

  return linter
}

import postcss from "postcss"
import fs from "fs"
import gs from "glob-stream"
import { Transform } from "stream"
import plugin from "./plugin"

export default function ({ files, config } = {}) {
  const linter = new Transform({ objectMode: true })

  linter._transform = function (chunk, enc, callback) {
    if (files) {
      const filepath = chunk.path
      fs.readFile(filepath, "utf8", (err, css) => {
        if (err) { linter.emit("error",  err) }
        lint({ css, filepath: filepath }, callback)
      })
    } else {
      lint({ css: chunk }, callback)
    }
  }

  function lint({ css, filepath }, callback) {
    const processOptions = {}
    if (filepath) {
      processOptions.from = filepath
    }
    postcss()
      .use(plugin(config))
      .process(css, processOptions)
      .then(result => {
        callback(null, result)
      })
      .catch(err => {
        linter.emit("error", new Error(err))
      })
  }

  if (files) {
    const fileStream = gs.create(files)
    return fileStream.pipe(linter)
  }

  return linter
}

Add bin to package.json and move rc-loading to plugin.js


/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2020
 */

import Realm from 'realm';

export class TemperatureBreachConfiguration extends Realm.Object {}

TemperatureBreachConfiguration.schema = {
  name: 'TemperatureBreachConfiguration',
  primaryKey: 'id',
  properties: {
    id: 'string',
    minimumTemperature: { type: 'double', optional: true },
    maximumTemperature: { type: 'double', optional: true },
    duration: { type: 'double', optional: true },
    description: { type: 'string', optional: true },
    colour: { type: 'string', optional: true },
    location: { type: 'Location', optional: true },
    type: 'string',
  },
};

export default TemperatureBreachConfiguration;

/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2020
 */

import Realm from 'realm';

export class TemperatureBreachConfiguration extends Realm.Object {
  toJSON() {
    return {
      id: this.id,
      minimumTemperature: this.minimumTemperature,
      maximumTemperature: this.maximumTemperature,
      duration: this.duration,
      description: this.description,
      colour: this.colour,
      locationID: this.location?.id ?? '',
      type: this.type,
    };
  }
}

TemperatureBreachConfiguration.schema = {
  name: 'TemperatureBreachConfiguration',
  primaryKey: 'id',
  properties: {
    id: 'string',
    minimumTemperature: { type: 'double', optional: true },
    maximumTemperature: { type: 'double', optional: true },
    duration: { type: 'double', optional: true },
    description: { type: 'string', optional: true },
    colour: { type: 'string', optional: true },
    location: { type: 'Location', optional: true },
    type: 'string',
  },
};

export default TemperatureBreachConfiguration;

Add breach config adapter method


const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')
const morgan = require('morgan')
const {sequelize} = require('./models')
const config = require('./config/config')

const app = express()
// Seting up middleware
app.use(morgan('combined'))
app.use(bodyParser.json())
app.use(cors())

app.get('/', (req, res) => {
	res.send('Hello world')
})

app.get('/status', (req, res) => {
	res.send({
		message: 'Hello world'
	})
})

require('./routes.js')(app)

sequelize.sync({force: true}).then(() => {
	app.listen(config.port, () => {
		console.log(`Server started at http://127.0.0.1:${config.port}`)
	});
})

const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')
const morgan = require('morgan')
const {sequelize} = require('./models')
const config = require('./config/config')

const app = express()
// Seting up middleware
app.use(morgan('combined'))
app.use(bodyParser.json())
app.use(cors())

app.get('/', (req, res) => {
	res.send('Hello world')
})

app.get('/status', (req, res) => {
	res.send({
		message: 'Hello world'
	})
})

require('./routes.js')(app)

sequelize.sync({force: false}).then(() => {
	app.listen(config.port, () => {
		console.log(`Server started at http://127.0.0.1:${config.port}`)
	});
})

Disable migration on start server


(function(global){

  // For Android 4.3- (included)
  document.body.addEventListener('click', function(e) {
    if (!e.path) {
      e.path = [];
      var t = e.target;
      while (t !== document) {
        e.path.push(t);
        t = t.parentNode;
      }
      e.path.push(document);
      e.path.push(window);
    }
  }, true)

})(window);

(function(global){

  // For Android 4.3- (included)
  var pathFill = function() {
    var e = arguments[0];

    if (!e.path) {
      e.path = [];
      var t = e.target;
      while (t !== document) {
        e.path.push(t);
        t = t.parentNode;
      }
      e.path.push(document);
      e.path.push(window);
    }
  }

  document.body.addEventListener('click', pathFill);
  document.body.addEventListener('click', pathFill, true);

})(window);

Handle event when bubbles event and catch event.


var orm = require('orm');
var db = orm.connect('sqlite://db.sqlite');

function init(callback) {
    db.sync(callback);
}

module.exports = {
    init: init
};

var orm = require('orm');
var db = orm.connect('sqlite://' + __dirname + '/db.sqlite');

function init(callback) {
    db.sync(callback);
}

module.exports = {
    init: init
};

Use correct directory for sqlite database


import 'react-datepicker/dist/react-datepicker.css'
import '../App.css'
import React, { Component } from 'react'
import { Field, reduxForm } from 'redux-form'
import { Form } from 'semantic-ui-react'
import DatePicker from 'react-datepicker'
import moment from 'moment'
import styled from 'styled-components'

const StyledForm = styled.div`width: 100%;`

class NewEngagementForm extends Component {
  state = {
    startDate: moment()
  }

  handleChange = date => {
    this.setState({
      startDate: date
    })
  }

  handleSubmit = () => {
    this.props.handleSubmit(this.state.startDate)
  }

  render() {
    return (
      <Form onSubmit={this.handleSubmit}>
        <Form.Field width={5}>
          <StyledForm>
            <DatePicker
              onChange={this.handleChange}
              selected={this.state.startDate}
              showTimeSelect
              dateFormat="LLL"
            />
          </StyledForm>
        </Form.Field>
        <Form.Field>
          <Field name="email" component="input" type="text" />
        </Form.Field>
        <input type="submit" />
      </Form>
    )
  }
}

export default reduxForm({
  form: 'newEngagement'
})(NewEngagementForm)

import 'react-datepicker/dist/react-datepicker.css'
import '../App.css'
import React, { Component } from 'react'
import { Field, reduxForm } from 'redux-form'
import { Form } from 'semantic-ui-react'
import DatePicker from 'react-datepicker'
import moment from 'moment'
import styled from 'styled-components'

const StyledForm = styled.div`width: 100%;`

class NewEngagementForm extends Component {
  state = {
    startDate: moment()
  }

  handleChange = date => {
    this.setState({
      startDate: date
    })
  }

  handleSubmit = () => {
    this.props.handleSubmit(this.state.startDate)
  }

  render() {
    return (
      <Form onSubmit={this.handleSubmit}>
        <Form.Field>
          <StyledForm>
            <DatePicker
              onChange={this.handleChange}
              selected={this.state.startDate}
              showTimeSelect
              dateFormat="LLL"
            />
          </StyledForm>
        </Form.Field>

        <input type="submit" />
      </Form>
    )
  }
}

export default reduxForm({
  form: 'newEngagement'
})(NewEngagementForm)

Remove unneeded input form from new engagement form


var expect      = require('chai').expect;
var Unsubscribe = require('../../lib/resources/unsubscribe');
var helper      = require('../test_helper');

describe('Unsubscribe', function() {
  var server;

  beforeEach(function() {
    server = helper.server(helper.port, helper.requests);
  });

  afterEach(function() {
    server.close();
  });

  describe('#create', function() {
    it('creates an unsubscribe', function() {
      var unsubscribe = new Unsubscribe(helper.client);
      var params = { person_email: 'foo@example.com' };

      return unsubscribe.create(params).then(function(response) {
        expect(response.person_email).to.eq('foo@example.com');
      });;
    });
  });
});

var expect      = require('chai').expect;
var Unsubscribe = require('../../lib/resources/Unsubscribe');
var helper      = require('../test_helper');

describe('Unsubscribe', function() {
  var server;

  beforeEach(function() {
    server = helper.server(helper.port, helper.requests);
  });

  afterEach(function() {
    server.close();
  });

  describe('#create', function() {
    it('creates an unsubscribe', function() {
      var unsubscribe = new Unsubscribe(helper.client);
      var params = { person_email: 'foo@example.com' };

      return unsubscribe.create(params).then(function(response) {
        expect(response.person_email).to.eq('foo@example.com');
      });;
    });
  });
});

Fix case sensitive require for unsubscribes


'use strict';

const builder = require('botbuilder');
const timesheet = require('./timesheet');

module.exports = exports = [(session) => {

    builder.Prompts.text(session, 'Please tell me your domain user?');

}, (session, results, next) => {

    session.send('Ok. Searching for your stuff...');
    session.sendTyping();

    timesheet
        .searchColleagues(results.response)
        .then((colleagues) => {
            session.userData = colleagues[0];
            session.userData.impersonated = true;
        }).catch((ex) => {
            console.log(ex);
        }).finally(() => {
            next();
        });

}, (session) => {

    if (!session.impersonated) {
        console.log('Oops! Couldn\'t impersonate');
        return session.endDialog('Oops! Couldn\'t impersonate');
    }
    session.endDialog('(y)');
}];

'use strict';

const builder = require('botbuilder');
const timesheet = require('./timesheet');

module.exports = exports = [(session) => {

    builder.Prompts.text(session, 'Please tell me your domain user?');

}, (session, results, next) => {

    session.send('Ok. Searching for your stuff...');
    session.sendTyping();

    timesheet
        .searchColleagues(results.response)
        .then((colleagues) => {
            console.log('Found %s', colleagues.length);

            session.userData = colleagues[0];
            session.userData.impersonated = true;
        }).catch((ex) => {
            console.log(ex);
        }).finally(() => {
            next();
        });

}, (session) => {

    if (!session.userData.impersonated) {
        return session.endDialog('Oops! Couldn\'t impersonate');
    }
    session.endDialog('(y)');
}];

Fix impersonated user check issue.


(function(){
  "use strict";
  xtag.register("sam-tabbar", {
    lifecycle: {
      created: function() {
        if (!this.role) {
          this.role = "tablist";
        }
      },
      inserted: function() {
        this.activeTabId = this.querySelector("[role='tab'][data-start-active]").id;
      },
      removed: function() {},
      attributeChanged: function() {}
    },
    events: {
      "press": function (event) {
        var el = event.originalTarget;
        //Checks if a tab was pressed
        if (!el || el.getAttribute("role") !== "tab") return;
        this.setTab(el.id, true);
      }
    },
    accessors: {
      role: {
        attribute: {}
      }
    },
    methods: {
      setTab: function (tabid, fireEvent) {
        var eventName = "tabChange",
            result = true;
        //Checks if person is trying to set to currently active tab
        if (this.activeTabId === tabid) {
          eventName = "activeTabPress"
          result = false;
        } else {
          document.getElementById(this.activeTabId).dataset.active = false;
          this.activeTabId = tabid;
          document.getElementById(this.activeTabId).dataset.active = true;
        }
        if (fireEvent) xtag.fireEvent(this, eventName, {detail: this.activeTabId});
        return result;
    }
    }
  });

})();

(function(){
  "use strict";
  xtag.register("sam-tabbar", {
    lifecycle: {
      created: function() {
        if (!this.role) {
          this.role = "tablist";
        }
      },
      inserted: function() {
        this.activeTabId = this.querySelector("[role='tab'][data-start-active]").id;
      },
      removed: function() {},
      attributeChanged: function() {}
    },
    events: {
      "press": function (event) {
        var el = event.originalTarget;
        //Checks if a tab was pressed
        if (!el || el.getAttribute("role") !== "tab") return;
        this.setTab(el.id, true);
      }
    },
    accessors: {
      role: {
        attribute: {}
      }
    },
    methods: {
      setTab: function (tabid, fireEvent) {
        var eventName = "tabChange";
        if (!this.querySelector("[id='"+tabid+"'][role='tab']")) {
          console.error("Cannot set to unknown tabid");
          return false;
        }
        //Checks if person is trying to set to currently active tab
        if (this.activeTabId === tabid) {
          eventName = "activeTabPress"
        } else {
          document.getElementById(this.activeTabId).dataset.active = false;
          this.activeTabId = tabid;
          document.getElementById(this.activeTabId).dataset.active = true;
        }
        if (fireEvent) xtag.fireEvent(this, eventName, {detail: this.activeTabId});
        return true;
    }
    }
  });

})();

Add handler for when setTab is given a non-exsitant tab id


define([
    'jquery'
],
function($) {
    return function() {
        var self = this;
        if (!self.sprite && self.lasyEmoji[0]) {
            var pickerTop = self.picker.offset().top,
                pickerBottom = pickerTop + self.picker.height() + 20;
            self.lasyEmoji.each(function() {
                var e = $(this), top = e.offset().top;
                if (top > pickerTop && top < pickerBottom) {
                    e.attr("src", e.data("src")).removeClass("lazy-emoji");
                }
            })
            self.lasyEmoji = self.lasyEmoji.filter(".lazy-emoji");
        }
    }
});

define([
    'jquery'
],
function($) {
    return function() {
        var self = this;
        if (!self.sprite && self.lasyEmoji[0] && self.lasyEmoji.eq(0).is(".lazy-emoji")) {
            var pickerTop = self.picker.offset().top,
                pickerBottom = pickerTop + self.picker.height() + 20;
            self.lasyEmoji.each(function() {
                var e = $(this), top = e.offset().top;
                if (top > pickerTop && top < pickerBottom) {
                    e.attr("src", e.data("src")).removeClass("lazy-emoji");
                }
            })
            self.lasyEmoji = self.lasyEmoji.filter(".lazy-emoji");
        }
    }
});

Fix 'disconnected from the document' error


var nocache = require('..')

var assert = require('assert')
var connect = require('connect')
var request = require('supertest')

describe('nocache', function () {
  it('sets headers properly', function (done) {
    var app = connect()
    app.use(function (req, res, next) {
      res.setHeader('ETag', 'abc123')
      next()
    })
    app.use(nocache())
    app.use(function (req, res) {
      res.end('Hello world!')
    })

    request(app).get('/')
    .expect('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate')
    .expect('Pragma', 'no-cache')
    .expect('Expires', '0')
    .expect('ETag', 'abc123')
    .end(done)
  })

  it('names its function and middleware', function () {
    assert.equal(nocache.name, 'nocache')
    assert.equal(nocache().name, 'nocache')
  })
})

var nocache = require('..')

var assert = require('assert')
var connect = require('connect')
var request = require('supertest')

describe('nocache', function () {
  it('sets headers properly', function (done) {
    var app = connect()
    app.use(function (req, res, next) {
      res.setHeader('ETag', 'abc123')
      next()
    })
    app.use(nocache())
    app.use(function (req, res) {
      res.end('Hello world!')
    })

    request(app).get('/')
    .expect('Surrogate-Control', 'no-store')
    .expect('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate')
    .expect('Pragma', 'no-cache')
    .expect('Expires', '0')
    .expect('ETag', 'abc123')
    .end(done)
  })

  it('names its function and middleware', function () {
    assert.equal(nocache.name, 'nocache')
    assert.equal(nocache().name, 'nocache')
  })
})

Add missing test for `Surrogate-Control` header


/* eslint import/no-extraneous-dependencies: ["error", {"devDependencies": true}] */
/* eslint-env node, mocha */
import test from 'ava';
import endpoint from '../src/endpoint';

test('happy ponies', () => {
  const fetch = () => null;
  api(null, null, {
    baseUri: 'http://api.example.com/v1',
    endpoints: [
      {
        endpoint: '/happy/ponies/{id}',
        method: 'GET',
        requiredParams: ['id'],
        optionalParams: ['lastSeenId'],
      }
    ]
  });
});

test.skip('returns a request object with the correct url', (t) => {
  const testUrl = 'http://api.example.com/v1/';
  const Request = (url) => {
    t.true(url === testUrl);
  };
  endpoint(Request);
});

test.skip('should append a trailing slash if one is missing in the given url', (t) => {
  const testUrl = 'http://api.example.com/v1';
  const Request = (url) => {
    t.true(url === `${testUrl}/`);
  };
  endpoint(Request);
});

/* eslint import/no-extraneous-dependencies: ["error", {"devDependencies": true}] */
/* eslint-env node, mocha */
import test from 'ava';
import endpoint from '../src/endpoint';

test('endpoint returns correctly partially applied function', (t) => {
  const endpointConfig = {
    uri: 'http://example.com',
  };
  const init = () => {};
  const Request = (uriOpt, initOpt) => {
    t.true(uriOpt === endpointConfig.uri);
    t.true(initOpt.toString() === init.toString());
  };
  endpoint(Request, init)(endpointConfig);
});

test.skip('returns a request object with the correct url', (t) => {
  const testUrl = 'http://api.example.com/v1/';
  const Request = (url) => {
    t.true(url === testUrl);
  };
  endpoint(Request);
});

test.skip('should append a trailing slash if one is missing in the given url', (t) => {
  const testUrl = 'http://api.example.com/v1';
  const Request = (url) => {
    t.true(url === `${testUrl}/`);
  };
  endpoint(Request);
});

Add spec for endpoints function


import './main.sass'
import 'babel-core/polyfill'

import React from 'react'
import thunk from 'redux-thunk'
import createLogger from 'redux-logger'
import { Router } from 'react-router'
import createBrowserHistory from 'history/lib/createBrowserHistory'
import { createStore, applyMiddleware, combineReducers } from 'redux'
import { Provider } from 'react-redux'
import * as reducers from './reducers'
import { analytics, uploader, requester } from './middleware'
import App from './containers/App'

const logger = createLogger({ collapsed: true })
const createStoreWithMiddleware = applyMiddleware(thunk, uploader, requester, analytics, logger)(createStore)
const reducer = combineReducers(reducers)
const store = createStoreWithMiddleware(reducer)

function createRedirect(from, to) {
  return {
    path: from,
    onEnter(nextState, transition) {
      transition.to(to)
    },
  }
}

const rootRoute = {
  path: '/',
  component: App,
  childRoutes: [
    createRedirect('onboarding', '/onboarding/communities'),
    require('./routes/Onboarding'),
  ],
}

const element = (
  <Provider store={store}>
    {() =>
      <Router history={createBrowserHistory()} routes={rootRoute} />
    }
  </Provider>
)

React.render(element, document.getElementById('root'))

import './main.sass'
import 'babel-core/polyfill'

import React from 'react'
import thunk from 'redux-thunk'
import createLogger from 'redux-logger'
import { Router } from 'react-router'
import createBrowserHistory from 'history/lib/createBrowserHistory'
import { createStore, applyMiddleware, combineReducers } from 'redux'
import { Provider } from 'react-redux'
import * as reducers from './reducers'
import { analytics, uploader, requester } from './middleware'
import App from './containers/App'

const logger = createLogger({ collapsed: true })
const createStoreWithMiddleware = applyMiddleware(thunk, uploader, requester, analytics, logger)(createStore)
const reducer = combineReducers(reducers)
const store = createStoreWithMiddleware(reducer)

function createRedirect(from, to) {
  return {
    path: from,
    onEnter(nextState, replaceState) {
      replaceState(nextState, to)
    },
  }
}

const rootRoute = {
  path: '/',
  component: App,
  childRoutes: [
    createRedirect('onboarding', '/onboarding/communities'),
    require('./routes/Onboarding'),
  ],
}

const element = (
  <Provider store={store}>
    {() =>
      <Router history={createBrowserHistory()} routes={rootRoute} />
    }
  </Provider>
)

React.render(element, document.getElementById('root'))

Fix an issue with redirects


var ienoopen = require('..')

var assert = require('assert')
var connect = require('connect')
var request = require('supertest')

describe('ienoopen', function () {
  beforeEach(function () {
    this.app = connect()
    this.app.use(ienoopen())
    this.app.use(function (req, res) {
      res.setHeader('Content-Disposition', 'attachment; filename=somefile.txt')
      res.end('Download this cool file!')
    })
  })

  it('sets header properly', function (done) {
    request(this.app).get('/')
      .expect('X-Download-Options', 'noopen', done)
  })

  it('names its function and middleware', function () {
    assert.equal(ienoopen.name, 'ienoopen')
    assert.equal(ienoopen().name, 'ienoopen')
  })
})

var ienoopen = require('..')

var assert = require('assert')
var connect = require('connect')
var request = require('supertest')

describe('ienoopen', function () {
  beforeEach(function () {
    this.app = connect()
    this.app.use(ienoopen())
    this.app.use(function (req, res) {
      res.setHeader('Content-Disposition', 'attachment; filename=somefile.txt')
      res.end('Download this cool file!')
    })
  })

  it('sets header properly', function () {
    return request(this.app).get('/')
      .expect('X-Download-Options', 'noopen')
  })

  it('names its function and middleware', function () {
    assert.equal(ienoopen.name, 'ienoopen')
    assert.equal(ienoopen().name, 'ienoopen')
  })
})

Use promises instead of callbacks in test


'use strict';

const electron = require('electron');
const { app, BrowserWindow } = electron;

let mainWindow; // Ensures garbage collection does not remove the window

app.on('ready', () => {
  // Creates the application window and sets its dimensions to fill the screen
  const { width, height } = electron.screen.getPrimaryDisplay().workAreaSize;
  mainWindow = new BrowserWindow({
    width,
    height
  });

  // Loads index.html in as the main application page
  mainWindow.loadURL(`file://${__dirname}/index.html`);

  mainWindow.on('closed', () => {
    mainWindow = null; // allow window to be garbage collected
  });
});

'use strict';

const electron = require('electron');
const { app, BrowserWindow } = electron;
const path = require('path');
const url = require('url');

let mainWindow; // Ensures garbage collection does not remove the window

app.on('ready', () => {
  // Creates the application window and sets its dimensions to fill the screen
  const { width, height } = electron.screen.getPrimaryDisplay().workAreaSize;
  mainWindow = new BrowserWindow({
    width,
    height
  });

  // Loads index.html in as the main application page
  mainWindow.loadURL(url.format({
    pathname: path.join(__dirname, 'index.html'),
    protocol: 'file:',
    slashes: true
  }));

  mainWindow.on('closed', () => {
    mainWindow = null; // allow window to be garbage collected
  });
});

Change method of loading index.html


'use strict';

var expect = require('chai').expect;
var rm     = require('../');

describe('1rm', function () {

  // 400# x 4
  var expectations = {
    brzycki: 436,
    epley: 453,
    lander: 441,
    lombardi: 459,
    mayhew: 466,
    oconner: 440,
    wathan: 451
  };

  Object.keys(expectations).forEach(function (method) {
    it('can estimate with the #' + method + ' method', function () {
      expect(rm[method](400, 4)).to.be.closeTo(expectations[method], 1);
    });
  });

});

'use strict';

var expect = require('chai').expect;
var rm     = require('../');

describe('1rm', function () {

  // 400# x 4
  var expectations = {
    epley: 453,
    brzycki: 436,
    lander: 441,
    lombardi: 459,
    mayhew: 466,
    oconner: 440,
    wathan: 451
  };

  Object.keys(expectations).forEach(function (method) {
    it('can estimate with the #' + method + ' method', function () {
      expect(rm[method](400, 4)).to.be.closeTo(expectations[method], 1);
    });
  });

});

Order tests to match source


var expect = require('chai').expect,
    hh = require('../index');

describe('#method', function () {
    it('hh.method is a function', function () {
        expect(hh.method).a('function');
    });
});

var assert = require('chai').assert,
    hh = require('../index');

describe('#hh.method()', function () {
    it('should be a function', function () {
       assert.typeOf(hh.method, 'function', 'hh.method is a function');
   });
});

Change tests to assert style


const express = require('express')
const app = express()
const path = require('path')
var cors = require('cors')
var bodyParser = require('body-parser')
// var pg = require('pg')
// var format = require('pg-format')
// var client = new pg.Client()
// var getTimeStamp = require('./get-timestamp.js')
// var timestamp = getTimeStamp

app.use(bodyParser.urlencoded({extended: false}))
app.use(bodyParser.text())

app.use(cors())

app.use(express.static('client/build'))

app.get('/', function (req, res) {
  res.sendFile(path.join(__dirname, '/client/build/index.html'))
})

app.post('/', function (req, res) {
  var thought = req.body.text
  // var thought = 'cool stuff'
  console.log('We received this from the client: ' + thought)
  /* client.connect(function (err) {
    if (err) throw err
    var textToDB = format('INSERT INTO thoughtentries (date, thought) VALUES(%L, %L);', timestamp, thought)
    client.query(textToDB, function (err, result) {
      if (err) throw err
      console.log(result.rows[0])
      client.end(function (err) {
        if (err) throw err
      })
    }) */
})

app.listen(3000, function () {
  console.log('listening on 3000')
})

const express = require('express')
const app = express()
const path = require('path')
var cors = require('cors')
var bodyParser = require('body-parser')
// var pg = require('pg')
// var format = require('pg-format')
// var client = new pg.Client()
// var getTimeStamp = require('./get-timestamp.js')
// var timestamp = getTimeStamp

app.use(bodyParser.urlencoded({extended: false}))
app.use(bodyParser.text())

app.use(cors())

app.use(express.static('client/build'))

app.get('/', function (req, res) {
  res.sendFile(path.join(__dirname, '/client/build/index.html'))
})

app.post('/', function (req, res) {
  var thought = req.body
  res.end('done')
  console.log('We received this from the client: ' + thought)
  /* client.connect(function (err) {
    if (err) throw err
    var textToDB = format('INSERT INTO thoughtentries VALUES(%L, %L)', timestamp, thought)
    client.query(textToDB, function (err, result) {
      if (err) throw err
      console.log(result.rows[0])
      client.end(function (err) {
        if (err) throw err
      })
    })
  }) */
  return
})

app.listen(3000, function () {
  console.log('listening on 3000')
})

Add res.end() to end the POST request


Meteor.subscribe('regions');
Meteor.subscribe('applications');

Template.applications.helpers({
  applications: function() {
    return Applications.find({}, {limit: 10});
  },

  formatDate: function(date) {
    return date.getDate() + '/' + (date.getMonth() + 1) + '/' + date.getFullYear();
  },

  regionName: function(id) {
    return Regions.findOne({id: id}).name;
  },
});

Template.applications.rendered = function() {
  $('#applications').dataTable({
    searching: false,
    scrollX: true,
    pagingType: 'full_numbers',
    language: {
      decimal: ',',
      thousands: '.',
    },
  });
};

Meteor.subscribe('regions');
Meteor.subscribe('applications');

Template.applications.helpers({
  applications: function() {
    return Applications.find({}, {limit: 10});
  },

  formatDate: function(date) {
    if (typeof date === 'undefined') {
      return null;
    }

    return date.getDate() + '/' + (date.getMonth() + 1) + '/' + date.getFullYear();
  },

  regionName: function(id) {
    if (typeof id === 'undefined') {
      return null;
    }

    return Regions.findOne({id: id}).name;
  },
});

Template.applications.rendered = function() {
  $('#applications').dataTable({
    searching: false,
    scrollX: true,
    pagingType: 'full_numbers',
    language: {
      decimal: ',',
      thousands: '.',
    },
  });
};

Fix exceptions when application region or date is undefined


import React from 'react/addons';
import Backbone from 'backbone';
import Router from 'react-router';

export default React.createClass({
    displayName: "Name",

    propTypes: {
        provider: React.PropTypes.instanceOf(Backbone.Model).isRequired
    },

    render: function () {
        let provider = this.props.provider;
        return (

{provider.get('name')}

                <Router.Link className=" btn btn-default" to = "all-providers" >

                {" Back to All Providers" }
                </Router.Link>

        );

    }

});

import React from 'react/addons';
import Backbone from 'backbone';
import Router from 'react-router';

export default React.createClass({
    displayName: "Name",

    propTypes: {
        provider: React.PropTypes.instanceOf(Backbone.Model).isRequired
    },

    render: function () {
        let provider = this.props.provider;
        return (

{provider.get('name')}

                <Router.Link className="btn btn-default" to="all-providers" >

                {" Back to All Providers" }
                </Router.Link>

        );

    }

});

Correct CSS class name typo


const path = require('path');
const webpack = require('webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = {
    entry: {
        app: './src/app/app.js'
    },
    module: {
        rules: [
            { test: /\.html$/,
              use: 'raw-loader' }
        ]
    },
    plugins: [
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin({
            template: './src/index.html',
            inject: 'head'
        }),
        new CopyWebpackPlugin([
            { from: './src/assets/',
              to: 'assets/' },
            { from: './src/styles/',
              to: 'styles/' },
            { from: './src/app/lib/stockfish.js',
              to: 'lib/stockfish.js' }
        ])
    ],
    output: {
        filename: 'app.js',
        path: path.resolve(__dirname, 'dist')
    },
};

const path = require('path');
const webpack = require('webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = {
    entry: {
        app: './src/app/app.js'
    },
    module: {
        rules: [
            { test: /\.html$/,
              use: 'raw-loader' }
        ]
    },
    plugins: [
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin({
            template: './src/index.html',
            inject: 'head'
        }),
        new CopyWebpackPlugin({patterns: [
            { from: './src/assets/', to: 'assets/' },
            { from: './src/styles/', to: 'styles/' },
            { from: './src/app/lib/stockfish.js', to: 'lib/stockfish.js' }
        ]})
    ],
    output: {
        filename: 'app.js',
        path: path.resolve(__dirname, 'dist')
    },
};

Update webpack copy plugin configuration


(function(ext) {
    // Cleanup function when the extension is unloaded
    ext._shutdown = function() {};

    // Status reporting code
    // Use this to report missing hardware, plugin or unsupported browser
    ext._getStatus = function() {
        return {status: 2, msg: 'Ready'};
    };

	    ext.my_first_block = function(callback) {
        wait = 1;
        window.setTimeout(function() {
            callback();
        }, wait*1000);
    };

    // Block and block menu descriptions
    var descriptor = {
        blocks: [
			['', 'My First Block', 'my_first_block'],
			['r', '%n ^ %n', 'power', 2, 3],
        ]
    };

    // Register the extension
    ScratchExtensions.register('Kinect2Scratch', descriptor, ext);
})({});

(function(ext) {
    // Cleanup function when the extension is unloaded
    ext._shutdown = function() {};

    // Status reporting code
    // Use this to report missing hardware, plugin or unsupported browser
    ext._getStatus = function() {
        return {status: 2, msg: 'Ready'};
    };

	ext.my_first_block = function() {

    };

	    ext.power = function(base, exponent) {
        return Math.pow(base, exponent);
    };

    // Block and block menu descriptions
    var descriptor = {
        blocks: [
			['', 'My First Block', 'my_first_block'],
			['r', '%n ^ %n', 'power', 2, 3],
        ]
    };

    // Register the extension
    ScratchExtensions.register('Kinect2Scratch', descriptor, ext);
})({});

Stop terrible idea; make reporter block do something


/* global Pebble navigator */
function pebbleSuccess(e) {
  // do nothing
}
function pebbleFailure(e) {
  console.error(e);
}

var reportPhoneBatt;

Pebble.addEventListener('ready', function(e) {
  if (navigator.getBattery) {
    navigator.getBattery().then(function (battery) {
      reportPhoneBatt = function () {
        Pebble.sendAppMessage({
          'PHONE_BATT_LEVEL': Math.floor(battery.level * 100),
          'PHONE_BATT_CHARGING': battery.charging ? 1 : 0
        }, pebbleSuccess, pebbleFailure);
      };
      battery.addEventListener('levelchange', function() {
        console.log('Level change: '+ (battery.level * 100) +'%');
        reportPhoneBatt();
      });
      battery.addEventListener('chargingchange', reportPhoneBatt);
      reportPhoneBatt();
    });
  } else {
    console.error('No navigator.getBattery');
    console.error('User agent: '+navigator.userAgent);
  }
});
Pebble.addEventListener('appmessage', function(e) {
  if (e.payload.QUERY_PHONE_BATT && reportPhoneBatt) {
    return reportPhoneBatt();
  }
});

/* global Pebble navigator */
function pebbleSuccess(e) {
  // do nothing
}
function pebbleFailure(e) {
  console.error(e);
}

var reportPhoneBatt;

Pebble.addEventListener('ready', function(e) {
  if (navigator.getBattery) {
    navigator.getBattery().then(function (battery) {
      reportPhoneBatt = function () {
        Pebble.sendAppMessage({
          'PHONE_BATT_LEVEL': Math.floor(battery.level * 100),
          'PHONE_BATT_CHARGING': battery.charging ? 1 : 0
        }, pebbleSuccess, pebbleFailure);
      };
      battery.addEventListener('levelchange', function() {
        console.log('Level change: '+ (battery.level * 100) +'%');
        reportPhoneBatt();
      });
      battery.addEventListener('chargingchange', reportPhoneBatt);
      reportPhoneBatt();
    });
  } else if (navigator.userAgent) {
    console.error('No navigator.getBattery');
    console.error('User agent: '+navigator.userAgent);
  } else {
    console.log('No navigator.userAgent, probably running in emulator');
  }
});
Pebble.addEventListener('appmessage', function(e) {
  if (e.payload.QUERY_PHONE_BATT && reportPhoneBatt) {
    return reportPhoneBatt();
  }
});

Add log message about starting in the emulator


Elm.Native.Now = {};

Elm.Native.Now.make = function(localRuntime) {

  localRuntime.Native = localRuntime.Native || {};

  localRuntime.Native.Now = localRuntime.Native.Now || {};

  if (localRuntime.Native.Now.values) {
    return localRuntime.Native.Now.values;
  }

  var Result = Elm.Result.make(localRuntime);

  return localRuntime.Native.Now.values = {
    loadTime: (new window.Date).getTime()
  };

};

Elm.Native.Now = {};

Elm.Native.Now.make = function(localRuntime) {

  localRuntime.Native = localRuntime.Native || {};

  localRuntime.Native.Now = localRuntime.Native.Now || {};

  if (localRuntime.Native.Now.values) {
    return localRuntime.Native.Now.values;
  }

  var Result = Elm.Result.make(localRuntime);

  return localRuntime.Native.Now.values = {
    loadTime: (new Date()).getTime()
  };

};

Use native Date() instead of window date


'use strict';

/* Unit tests for file client/scripts/arcademode/store/configureStore.js. */
import { expect } from 'chai';
import configureStore from '../../../../../client/scripts/arcademode/store/configureStore';

describe('configureStore()', () => {
  it('should do return an object', () => {
    const store = configureStore();
    expect(typeof store).to.equal('object');
    const state = store.getState();
    expect(state).not.to.empty;
  });

  it('should do accept dispatched actions', () => {
    const store = configureStore();
    store.dispatch({ type: 'DUMMY' });
  });
});

'use strict';

/* Unit tests for file client/scripts/arcademode/store/configureStore.js. */
import { expect } from 'chai';
import configureStore from '../../../../../client/scripts/arcademode/store/configureStore';

describe('Store: configureStore()', () => {
  it('should return an object representing the store', () => {
    const store = configureStore();
    expect(typeof store).to.equal('object');
    const state = store.getState();
    expect(state).not.to.be.empty;
  });

  it('should accept dispatched actions and update its state', () => {
    const store = configureStore();
    store.dispatch({ type: 'MODAL_CLOSE' });
    expect(store.getState().getIn(['modal', 'modal'])).to.be.false;
  });
});

Test store, dispatch, and state


// Return promise for given async function

'use strict';

var f      = require('es5-ext/lib/Function/functionalize')
  , concat = require('es5-ext/lib/List/concat').call
  , slice  = require('es5-ext/lib/List/slice/call')

  , deferred  = require('./deferred')

  , apply;

apply = function (fn, scope, args, resolve) {
	fn.apply(scope, concat(args, function (error, result) {
		if (error == null) {
			resolve((arguments.length > 2) ? slice(arguments, 1) : result);
		} else {
			resolve(error);
		}
	}));
}

exports = module.exports = f(function () {
	var d = deferred();
	apply(this, null, arguments, d.resolve);
	return d.promise;
});

exports._apply = apply;

// Return promise for given async function

'use strict';

var f       = require('es5-ext/lib/Function/functionalize')
  , slice   = require('es5-ext/lib/List/slice/call')
  , toArray = require('es5-ext/lib/List/to-array').call

  , deferred  = require('./deferred')

  , apply;

apply = function (fn, scope, args, resolve) {
	fn.apply(scope, toArray(args).concat(function (error, result) {
		if (error == null) {
			resolve((arguments.length > 2) ? slice(arguments, 1) : result);
		} else {
			resolve(error);
		}
	}));
}

exports = module.exports = f(function () {
	var d = deferred();
	apply(this, null, arguments, d.resolve);
	return d.promise;
});

exports._apply = apply;

Update up to changes in es5-ext


import styles from './InterestGroup.css';
import React from 'react';
import InterestGroup from './InterestGroup';
import Button from 'app/components/Button';
import { Link } from 'react-router';

export type Props = {
  interestGroups: Array
};

const InterestGroupList = (props: Props) => {
  const groups = props.interestGroups.map((group, key) => (
    <InterestGroup group={group} key={key} />
  ));
  return (

Interessegrupper

            Her finner du all praktisk informasjon knyttet til
            våre interessegrupper.

        <Link to={'/interestgroups/create'} className={styles.link}>
          <Button>Lag ny interessegruppe</Button>
        </Link>

{groups}

  );
};

export default InterestGroupList;

import styles from './InterestGroup.css';
import React from 'react';
import InterestGroup from './InterestGroup';
import Button from 'app/components/Button';
import { Link } from 'react-router';
import NavigationTab, { NavigationLink } from 'app/components/NavigationTab';

export type Props = {
  interestGroups: Array
};

const InterestGroupList = (props: Props) => {
  const groups = props.interestGroups.map((group, key) => (
    <InterestGroup group={group} key={key} />
  ));
  const showCreate = props.loggedIn;
  return (

          <NavigationTab title="Interessegrupper">
            <NavigationLink to={`/`}>Hjem</NavigationLink>
          </NavigationTab>

            Her finner du all praktisk informasjon knyttet til
            våre interessegrupper.

          {showCreate && (
            <Link to={'/interestgroups/create'} className={styles.link}>
              <Button>Lag ny interessegruppe</Button>
            </Link>
          )}

{groups}

  );
};

export default InterestGroupList;

Use NavigationTab in InterestGroup list


'use strict';

var stylefmt = require('stylefmt');
var data = '';

// Get options if needed
if (process.argv.length > 2) {
  var opts = JSON.parse(process.argv[2]);
  process.chdir(opts.file_path);
}

process.stdin.on('data', function(css) {
  data += css;
});

process.stdin.on('end', function() {
  try {
    process.stdout.write(stylefmt.process(data));
  } catch (err) {
    throw err;
  }
});

'use strict';

var stylefmt = require('stylefmt');
var data = '';

// Get options if needed
if (process.argv.length > 2) {
  var opts = JSON.parse(process.argv[2]);
  process.chdir(opts.file_path);
}

process.stdin.on('data', function(css) {
  data += css;
});

process.stdin.on('end', function() {
  stylefmt.process(data).then(function(result) {
    try {
      process.stdout.write(result.css);
    } catch (err) {
      throw err;
    }
  });
});

Update for new postcss promises


'use strict';

/**
 * Returns a BlockingResponse object with a redirect URL if the request URL
 * matches a file type extension.
 *
 * @param {object} request
 * @return {object|undefined} the blocking response
 */
function requestInterceptor(request) {
  var url = request.url;
  var hasParamTs = /\?.*ts=/;
  var hasExtGo = /\.go/;

  if (!hasParamTs.test(url) && hasExtGo.test(url))
    return {redirectUrl: addTabSizeParam(url, 2)};
}

/**
 * Returns a URL with the query param ts=size included.
 *
 * @param {string} url
 * @param {number} size
 * @return {string}
 */
function addTabSizeParam(url, size) {
  var urlWithTs = new Url(url);
  urlWithTs.query.ts = size;
  return urlWithTs.toString();
}

chrome.webRequest.onBeforeRequest.addListener(
  requestInterceptor,
  {urls: ['https://github.com/*']},
  ['blocking']
);

'use strict';

var tabSize = 2;

/**
 * Returns a BlockingResponse object with a redirect URL if the request URL
 * matches a file type extension.
 *
 * @param {object} request
 * @return {object|undefined} the blocking response
 */
function requestInterceptor(request) {
  var url = request.url;
  var hasParamTs = /\?.*ts=/;
  var hasExtGo = /\.go/;

  if (!hasParamTs.test(url) && hasExtGo.test(url))
    return {redirectUrl: addTabSizeParam(url, tabSize)};
}

/**
 * Returns a URL with the query param ts=size included.
 *
 * @param {string} url
 * @param {number} size
 * @return {string}
 */
function addTabSizeParam(url, size) {
  var urlWithTs = new Url(url);
  urlWithTs.query.ts = size;
  return urlWithTs.toString();
}

chrome.webRequest.onBeforeRequest.addListener(
  requestInterceptor,
  {urls: ['https://github.com/*']},
  ['blocking']
);

chrome.storage.sync.get({tabSize: tabSize}, function(items) {
  tabSize = items.tabSize;
});

chrome.storage.onChanged.addListener(function(items) {
  tabSize = items.tabSize.newValue;
});

Use tab size from Chrome storage


module.exports = {
  printWidth: 100,
  tabWidth: 2,
  useTabs: false,
  semi: true,
  singleQuote: true,
  quoteProps: 'consistent',
  trailingComma: 'all',
  bracketSpacing: true,
  arrowParens: 'always',
};

module.exports = {
  printWidth: 100,
  tabWidth: 2,
  useTabs: false,
  semi: true,
  singleQuote: true,
  quoteProps: 'consistent',
  trailingComma: 'all',
  bracketSpacing: true,
  arrowParens: 'always',
  endOfLine: 'lf',
};

Set endOfLine to "lf" in prettier-config


(function () {
  'use strict';
  var extend = require('extend'),
      defaults = require('./protractor.conf');

  exports.config = extend(defaults.config, {
    // --- uncomment to use mac mini's ---
    // seleniumAddress: 'http://clas-mac-mini.local:4444/wd/hub',
    // baseUrl: 'http://Marcos-MacBook-Pro-2.local:8001/',

    multiCapabilities: [
      {
        browserName: 'chrome'
      },
      {
        browserName: 'firefox'
      }
    ]
  });
})();

(function () {
  'use strict';
  var extend = require('extend'),
      defaults = require('./protractor.conf');

  exports.config = extend(defaults.config, {
    // --- uncomment to use mac mini's ---
    // seleniumAddress: 'http://clas-mac-mini.local:4444/wd/hub',
    // baseUrl: 'http://Marcos-MacBook-Pro-2.local:8001/',

    multiCapabilities: [
      {
        browserName: 'chrome',
        'chromeOptions': {
          args: ['--test-type']
        }
      },
      {
        browserName: 'firefox'
      }
    ]
  });
})();

Remove chrome warnings during e2e tests


describe("Implement queue with two stacks", function() {
  const Queue = new QueueTwoStacks();
  Queue.enqueue(1);
  Queue.enqueue(2);
  Queue.enqueue(3);

  describe("enqueue()", function() {
    it("appends an element to tail", function() {
      Queue.enqueue(4);
      const expected = [1,2,3,4];
      expect(Queue.inStack).toEqual(expected);
    })
    it("appends an element correctly after dequeing", function() {
      Queue.dequeue();
      Queue.enqueue(5);
      const expected = [2,3,4,5];
      expect(Queue.inStack).toEqual(expected);
    })
  })
  describe("dequeue()", function () {
    it("removes an element from head", function () {
      Queue.dequeue();
      const expected = [3,4,5];
      expect(Queue.inStack).toEqual(expected);
    })
  })
  describe("when queue is empty", function() {
    it("throws an error", function() {
      Queue.dequeue();
      Queue.dequeue();
      Queue.dequeue();
      expect(function() {
        Queue.dequeue();
      }).toThrow();
    })
  })
})

describe("Implement queue with two stacks", function() {
  const Queue = new QueueTwoStacks();
  Queue.enqueue(1);
  Queue.enqueue(2);
  Queue.enqueue(3);

  describe("enqueue()", function() {
    it("appends an element to tail", function() {
      Queue.enqueue(4);
      const expected = [1,2,3,4];
      expect(Queue.inStack).toEqual(expected);
    })
    it("appends an element correctly after dequeing", function() {
      Queue.dequeue();
      Queue.enqueue(5);
      const expected = [2,3,4,5];
      expect(Queue.inStack).toEqual(expected);
    })
  });
  describe("dequeue()", function () {
    it("removes an element from head", function () {
      Queue.dequeue();
      const expected = [3,4,5];
      expect(Queue.inStack).toEqual(expected);
    });
    it("throws an error when queue is empty", function() {
      Queue.dequeue();
      Queue.dequeue();
      Queue.dequeue();
      expect(function() {
        Queue.dequeue();
      }).toThrow();
    })
  });
})

Move code to correct semantics in spec


import React from 'react';
import './TicTacToe.scss';
import { connect }            from 'react-redux';
import ticTacToeActions from 'actions/tictactoe';

import GameBoard from './components/GameBoard';

const mapStateToProps = (state) => {
  return {
    playerTurn: state.tictactoe.playerTurn
  };
};

class TicTacToe extends React.Component {
  static propTypes = {
    playerTurn: React.PropTypes.bool,
    computer_move: React.PropTypes.func
  }

  componentWillReceiveProps (propObj) {
    if (!propObj.playerTurn) {
      this.props.computer_move();
    }
    if (propObj.winner && propObj.winner.result === 'draw') {
      // do very cool modal fadein
    } else if (propObj.winner) {
      // do very cool modal fadein with computer winning
    }
    // nothing else, the player can't win
  }

  render () {
    return (

Tic-Tac-Toe

        <GameBoard />

    );
  }
}

export default connect(mapStateToProps, ticTacToeActions)(TicTacToe);

import React from 'react';
import './TicTacToe.scss';
import { connect }            from 'react-redux';
import ticTacToeActions from 'actions/tictactoe';

import GameBoard from './components/GameBoard';

const mapStateToProps = (state) => {
  return {
    playerTurn: state.tictactoe.playerTurn,
    winner: state.tictactoe.winner
  };
};

class TicTacToe extends React.Component {
  static propTypes = {
    playerTurn: React.PropTypes.bool,
    computer_move: React.PropTypes.func
  }

  componentWillReceiveProps (propObj) {
    if (!propObj.playerTurn) {
      this.props.computer_move();
    }
    if (propObj.winner && propObj.winner.result === 'draw') {
      // do very cool modal fadein
      console.log('DRAW');
    } else if (propObj.winner) {
      // do very cool modal fadein with computer winning
      console.log('YOU LOST');
    }
    // nothing else, the player can't win
  }

  render () {
    return (

Tic-Tac-Toe

Open your console to see messages

You can't win

        <GameBoard />

    );
  }
}

export default connect(mapStateToProps, ticTacToeActions)(TicTacToe);

Add console statements for initial notification


import { moduleForModel, test } from 'ember-qunit';

moduleForModel('canvas', 'Unit | Model | canvas', {
  // Specify the other units that are required for this test.
  needs: 'model:op model:pulseEvent model:team model:user'.w()
});

test('it exists', function(assert) {
  const model = this.subject();
  assert.ok(Boolean(model));
});

import { moduleForModel, test } from 'ember-qunit';

moduleForModel('canvas', 'Unit | Model | canvas', {
  // Specify the other units that are required for this test.
  needs: 'model:comment model:op model:pulseEvent model:team model:user'.w()
});

test('it exists', function(assert) {
  const model = this.subject();
  assert.ok(Boolean(model));
});

Add missing model dependency for canvas model test


class Atom {
  constructor(state) {
    this.state = state;
    this.watches = {};
  }

  reset(state) {
    return this._change(state);
  }

  swap(f, ...args) {
    return this._change(f(this.state, ...args));
  }

  deref() {
    return this.state;
  }

  addWatch(k, f) {
    // if (this.watches[key]) {
    //   console.warn(`adding a watch with an already registered key: ${k}`);
    // }
    this.watches[k] = f;
    return this;
  }

  removeWatch(k) {
    // if (!this.watches[key]) {
    //   console.warn(`removing a watch with an unknown key: ${k}`);
    // }
    delete this.watches[key];
    return this;
  }

  _change(newState) {
    const { state, watches } = this;
    Object.keys(watches).forEach(k => watches[k](k, state, newState));
    this.state = newState;
    return this.state;
  }
}

export default function atom(state) {
  return new Atom(state);
}

class Atom {
  constructor(state) {
    this.state = state;
    this.watches = {};
  }

  reset(state) {
    return this._change(state);
  }

  swap(f, ...args) {
    return this._change(f(this.state, ...args));
  }

  deref() {
    return this.state;
  }

  addWatch(k, f) {
    // if (this.watches[key]) {
    //   console.warn(`adding a watch with an already registered key: ${k}`);
    // }
    this.watches[k] = f;
    return this;
  }

  removeWatch(k) {
    // if (!this.watches[key]) {
    //   console.warn(`removing a watch with an unknown key: ${k}`);
    // }
    delete this.watches[key];
    return this;
  }

  _change(newState) {
    const { state, watches } = this;
    this.state = newState;
    Object.keys(watches).forEach(k => watches[k](k, state, newState));
    return this.state;
  }
}

export default function atom(state) {
  return new Atom(state);
}

Update atom state before calling watches


var test = require("test");
require("a");
try {
    require("A");
    test.assert(false, "should fail to require alternate spelling");
} catch (error) {
}
test.print("DONE", "info");

var test = require("test");
try {
    require("a");
    require("A");
    test.assert(false, "should fail to require alternate spelling");
} catch (error) {
}
test.print("DONE", "info");

Update case sensitivity test to capture errors on first require


/* @flow */

import * as Constants from '../constants/favorite'
import {logoutDone} from '../constants/login'
import type {FavoriteAction} from '../constants/favorite'
import type {Folder} from '../constants/types/flow-types'

type State = {
  folders: ?Array<Folder>
}

const initialState = {
  folders: null
}

export default function (state: State = initialState, action: FavoriteAction): State {
  switch (action.type) {
    case Constants.favoriteList:
      return {
        ...state,
        folders: action.payload && action.payload.folders
      }
    case logoutDone: {
      return {
        ...state,
        folders: null
      }
    }
    default:
      return state
  }
}

/* @flow */

import * as Constants from '../constants/favorite'
import {logoutDone} from '../constants/login'
import type {FavoriteAction} from '../constants/favorite'
import type {Folder} from '../constants/types/flow-types'

type State = {
  folders: ?Array<Folder>
}

const initialState = {
  folders: null
}

export default function (state: State = initialState, action: FavoriteAction): State {
  switch (action.type) {
    case Constants.favoriteList:
      return {
        ...state,
        folders: action.payload && action.payload.folders
      }
    case logoutDone: {
      return {
        ...initialState
      }
    }
    default:
      return state
  }
}

Use initial state instead of setting folder manually


var gulp = require ('gulp');

gulp.task ( 'html', ['css'], function() {
  gulp.src('src/main/resources/html/**')
  .pipe ( gulp.dest( './build/') );

  gulp.src('src/test/resources/**')
  .pipe ( gulp.dest('./build/data/'));

  gulp.src(['node_modules/dat-gui/vendor/dat*.js'])
  .pipe ( gulp.dest('./build/js/'));

  // "Vendor" packages
  // Bootstrap
  gulp.src(['node_modules/bootstrap/dist/**'])
  .pipe ( gulp.dest('./build'));

  // Font Awesome
  gulp.src(['node_modules/font-awesome/fonts/**'])
  .pipe( gulp.dest('./build/fonts'));
  gulp.src(['node_modules/font-awesome/css/**'])
  .pipe( gulp.dest('./build/css'));

  // JQuery
  gulp.src(['node_modules/jquery/dist/**'])
  .pipe( gulp.dest('./build/js'));

  // Dropzone file upload
gulp.src(['node_modules/dropzone/downloads/*js'])
.pipe(gulp.dest('./build/js'));
gulp.src(['node_modules/dropzone/downloads/css/**'])
.pipe(gulp.dest('./build/css'));
gulp.src(['node_modules/dropzone/downloads/images/**'])
.pipe(gulp.dest('./build/images'));
});

var gulp = require ('gulp');

gulp.task ( 'html', ['css'], function() {
  gulp.src('src/main/resources/html/**')
  .pipe ( gulp.dest( './build/') );

  gulp.src('src/main/resources/js/**')
  .pipe ( gulp.dest( './build/js/') );

  gulp.src('src/main/resources/images/**')
  .pipe ( gulp.dest( './build/images/') );

  gulp.src('src/test/resources/**')
  .pipe ( gulp.dest('./build/data/'));

  gulp.src(['node_modules/dat-gui/vendor/dat*.js'])
  .pipe ( gulp.dest('./build/js/'));

  // XTK Local build (if available)
  // should copy for production
  gulp.src(['../X/utils/xtk.js'])
  .pipe ( gulp.dest('./build/js'));

  // "Vendor" packages
  // Bootstrap
  gulp.src(['node_modules/bootstrap/dist/**'])
  .pipe ( gulp.dest('./build'));

  // Font Awesome
  gulp.src(['node_modules/font-awesome/fonts/**'])
  .pipe( gulp.dest('./build/fonts'));
  gulp.src(['node_modules/font-awesome/css/**'])
  .pipe( gulp.dest('./build/css'));

  // JQuery
  gulp.src(['node_modules/jquery/dist/**'])
  .pipe( gulp.dest('./build/js'));

  // Dropzone file upload
  gulp.src(['node_modules/dropzone/downloads/*js'])
  .pipe(gulp.dest('./build/js'));
  gulp.src(['node_modules/dropzone/downloads/css/**'])
  .pipe(gulp.dest('./build/css'));
  gulp.src(['node_modules/dropzone/downloads/images/**'])
  .pipe(gulp.dest('./build/images'));
});

Include JS and XTK in build


import React from 'react';
import PropTypes from 'prop-types';
import { Switch } from '@blueprintjs/core';
import { Themes } from '../../../containers/enums';

const ColorsPanel = ({ theme, setTheme }) =>

Themes

    <Switch
      label="Dark Theme"
      checked={theme === Themes.DARK}
      onChange={() =>
        setTheme(theme === Themes.DARK ? Themes.LIGHT : Themes.DARK)}
      className="pt-large w-fit-content"
    />

;

ColorsPanel.propTypes = {
  theme: PropTypes.string.isRequired,
  setTheme: PropTypes.func.isRequired
};

export default ColorsPanel;

import React from 'react';
import PropTypes from 'prop-types';
import { RadioGroup, Radio } from '@blueprintjs/core';
import { Themes } from '../../../containers/enums';

const ColorsPanel = ({ theme, setTheme }) =>
  <div className="mt-1">
    <RadioGroup
      label="Themes"
      selectedValue={theme}
      onChange={e => setTheme(e.target.value)}
    >
      <Radio label="Dark Mode" value={Themes.DARK} />
      <Radio label="Light Mode" value={Themes.LIGHT} />
    </RadioGroup>
  </div>;

ColorsPanel.propTypes = {
  theme: PropTypes.string.isRequired,
  setTheme: PropTypes.func.isRequired
};

export default ColorsPanel;

Select themes as radio buttons


const url_for_static = require('../base/url').url_for_static;
const moment         = require('moment');

const check_new_release = function() { // calling this method is handled by GTM tags
    const last_reload = localStorage.getItem('new_release_reload_time');
    // prevent reload in less than 10 minutes
    if (last_reload && +last_reload + (10 * 60 * 1000) > moment().valueOf()) return;
    const currect_hash = $('script[src*="binary.min.js"],script[src*="binary.js"]').attr('src').split('?')[1];
    const xhttp = new XMLHttpRequest();
    xhttp.onreadystatechange = function() {
        if (+xhttp.readyState === 4 && +xhttp.status === 200) {
            const latest_hash = xhttp.responseText;
            if (latest_hash && latest_hash !== currect_hash) {
                localStorage.setItem('new_release_reload_time', moment().valueOf());
                window.location.reload(true);
            }
        }
    };
    xhttp.open('GET', url_for_static() + 'version?' + Math.random().toString(36).slice(2), true);
    xhttp.send();
};

module.exports = {
    check_new_release: check_new_release,
};

const url_for_static = require('../base/url').url_for_static;
const moment         = require('moment');

const check_new_release = function() { // calling this method is handled by GTM tags
    const last_reload = localStorage.getItem('new_release_reload_time');
    // prevent reload in less than 10 minutes
    if (last_reload && +last_reload + (10 * 60 * 1000) > moment().valueOf()) return;
    localStorage.setItem('new_release_reload_time', moment().valueOf());
    const currect_hash = $('script[src*="binary.min.js"],script[src*="binary.js"]').attr('src').split('?')[1];
    const xhttp = new XMLHttpRequest();
    xhttp.onreadystatechange = function() {
        if (+xhttp.readyState === 4 && +xhttp.status === 200) {
            const latest_hash = xhttp.responseText;
            if (latest_hash && latest_hash !== currect_hash) {
                window.location.reload(true);
            }
        }
    };
    xhttp.open('GET', url_for_static() + 'version?' + Math.random().toString(36).slice(2), true);
    xhttp.send();
};

module.exports = {
    check_new_release: check_new_release,
};

Check for new release in 10 minutes intervals