var $ = require('jQuery');
var fieldRegistry = require('kwf/frontend-form/field-registry');
var Field = require('kwf/frontend-form/field/field');
var kwfExtend = require('kwf/extend');

var TextArea = kwfExtend(Field, {
    initField: function() {
        this.el.select('textarea').each((function(index, input) {
            $(input).on('keypress', (function() {
                this.el.trigger('kwfUp-form-change', this.getValue());
            }).bind(this));
            this._initPlaceholder(input);
        }).bind(this));
    },
    getFieldName: function() {
        return this.el.find('textarea').get(0).name;
    },
    getValue: function() {
        return this.el.find('textarea').get(0).value;
    },
    clearValue: function() {
        this.el.select('textarea').get(0).value = '';
    },
    clearValue: function(value) {
        this.el.select('textarea').get(0).value = value;
    }
});

fieldRegistry.register('kwfFormFieldTextArea', TextArea);
module.exports = TextArea;

var $ = require('jQuery');
var fieldRegistry = require('kwf/frontend-form/field-registry');
var Field = require('kwf/frontend-form/field/field');
var kwfExtend = require('kwf/extend');

var TextArea = kwfExtend(Field, {
    initField: function() {
        var input = this.el.find('textarea');
        if (input) {
            input.on('keypress', (function () {
                this.el.trigger('kwfUp-form-change', this.getValue());
            }).bind(this));
            this._initPlaceholder(input);
        }
    },
    getFieldName: function() {
        return this.el.find('textarea').get(0).name;
    },
    getValue: function() {
        return this.el.find('textarea').get(0).value;
    },
    clearValue: function() {
        this.el.find('textarea').get(0).value = '';
    },
    clearValue: function(value) {
        this.el.find('textarea').get(0).value = value;
    }
});

fieldRegistry.register('kwfFormFieldTextArea', TextArea);
module.exports = TextArea;

Fix textarea in frontend port to jquery


var path = require('path')
var pgPath;
//support both pg & pg.js
//this will eventually go away when i break native bindings
//out into their own module
try {
  pgPath = path.dirname(require.resolve('pg'))
} catch(e) {
  pgPath = path.dirname(require.resolve('pg.js')) + '/lib'
}

module.exports.Result = require(path.join(pgPath, 'result.js'))
module.exports.prepareValue = require(path.join(pgPath, 'utils.js')).prepareValue

//support both pg & pg.js
//this will eventually go away when i break native bindings
//out into their own module
try {
  module.exports.Result = require('pg/lib/result.js')
  module.exports.prepareValue = require('pg/lib/utils.js').prepareValue
} catch(e) {
  module.exports.Result = require('pg.js/lib/result.js')
  module.exports.prepareValue = require('pg.js/lib/utils.js').prepareValue
}

Fix require for webpack compatibility


var assert = require('assert');
var InputScanner = require('../../src/core/inputscanner').InputScanner;

describe('IndexScanner', function() {
  describe('new', function() {
    it('should return empty scanner when input is not present', function() {
      assert.equal(new InputScanner().hasNext(), false);
    });
  });
});

var assert = require('assert');
var InputScanner = require('../../src/core/inputscanner').InputScanner;

describe('IndexScanner', function() {
  describe('new', function() {
    it('should return empty scanner when input is not present', function() {
      assert.equal(new InputScanner().hasNext(), false);
    });
  });

  describe('peek', function() {
    it('should return value at index passed as parameter', function() {
      var value = 'hello'
      var inputText = new InputScanner(value);
      assert.equal(inputText.peek(3),value[3])
    });
  });

  describe('peek without parameters', function() {
    it('should return value at index 0 if parameter is not present', function() {
      var value = 'hello'
      var inputText = new InputScanner(value);
      assert.equal(inputText.peek(),value[0])
    });
  });

  describe('next', function() {
    it('should return the value at current index and increments the index', function() {
      var value = 'hello'
      var inputText = new InputScanner(value);
      assert.equal(inputText.next(),value[0]);
      assert.equal(inputText.next(),value[1])
    });
  });

  describe('test', function() {
    it('should return whether the pattern is matched or not', function() {
      var value = 'hello'
      var pattern = /hel/;
      var index = 0
      var inputText = new InputScanner(value);
      assert.equal(inputText.test(pattern,index),true)
    });
  });

  describe('testChar', function() {
    it('should return whether pattern matched or not for particular index', function() {
      var value = 'hello'
      var pattern = /e/;
      var index = 1;
      var inputText = new InputScanner(value);
      assert.equal(inputText.testChar(pattern,index),true)
    });
  });

});

Add unit tests for input scanner


/*
 * Javacript for the API Configuration
 */

/* jshint browser: true */
/* jshint -W097 */
/* globals apiRequest */
'use strict';

export class apiConfiguration {
  constructor(resource, resolve, reject) {
    var obj = this;
    apiRequest('GET', resource, 'maxResults=all')
      .then(function(response) {
        var v = JSON.parse(response.responseText).data;
        v.forEach(function(e) {
          obj[e.field] = e;
        });
        if (resolve) {
          resolve(response);
        }
      })
      .catch(function(response) {
        if (response instanceof Error) { throw response; }
        if (reject) {
          reject(response);
        }
      });
  }
}

/*
 * Javacript for the API Configuration
 */

/* jshint browser: true */
/* jshint -W097 */
/* globals apiRequest */
'use strict';

export class apiConfiguration {
  constructor(resource, resolve, reject) {
    var obj = this;
    apiRequest('GET', resource, 'maxResults=all')
      .then(function(response) {
        var v = JSON.parse(response.responseText).data;
        if (v) {
          v.forEach(function(e) {
            obj[e.field] = e;
          });
        }
        if (resolve) {
          resolve(response);
        }
      })
      .catch(function(response) {
        if (response instanceof Error) { throw response; }
        if (reject) {
          reject(response);
        }
      });
  }
}

Handle case with no configuration options


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

foam.CLASS({
  package: 'foam.nanos.menu',
  name: 'SubMenu',
  extends: 'foam.nanos.menu.AbstractMenu',

  requires: [ 'foam.nanos.menu.SubMenuView' ],

  methods: [
    function createView(X, menu, parent) {
      return this.SubMenuView.create({menu: menu, parent: parent}, X);
    },

    function launch(X, menu, parent) {
      var view = this.createView(X, menu, parent);
      view.open();
    }
  ]
});

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

foam.CLASS({
  package: 'foam.nanos.menu',
  name: 'SubMenu',
  extends: 'foam.nanos.menu.AbstractMenu',

  requires: [ 'foam.nanos.menu.SubMenuView' ],

  methods: [
    // Code below is for conventional style menus which pop-up,
    // which no longer happens as opening a menu just opens the tree view.
    /*
    function createView(X, menu, parent) {
      return this.SubMenuView.create({menu: menu, parent: parent}, X);
    },

    function launch(X, menu, parent) {
      var view = this.createView(X, menu, parent);
      view.open();
    }
    */
  ]
});

Fix shadow appearing to right of menus when sub-menus opened.


'use strict';

var httpclient = require('../../http-client');

function messageListener(db, from, channel, message) {

  var match = /(https?:\/\/[^ ]+)/.exec(message)

  if (match) {

    var res = httpclient(db, match[1]);
    var match = /<title>(.+)<\/title>/.exec(res);

    if (match) {
      var decoded = match[1].replace(/&#\d+;/gm,function(s) {
        return String.fromCharCode(s.match(/\d+/gm)[0]);
      });
      decoded = decoded.replace(/&/g, "&")
                       .replace(/</g, "<")
                       .replace(/>/g, ">")
                       .replace(/"/g, "\"")
                       .replace(/'/g, "'");
      return [ { to: channel, message: decoded } ] ;
    }

  }

}

module.exports = messageListener;

'use strict';

var httpclient = require('../../http-client');

function messageListener(db, from, channel, message) {

  var match = /(https?:\/\/[^ ]+)/.exec(message)

  if (match) {

    var res = httpclient(db, match[1]);
    var match = /<title>(.+?)<\/title>/.exec(res);

    if (match) {
      var decoded = match[1].replace(/&#\d+;/gm,function(s) {
        return String.fromCharCode(s.match(/\d+/gm)[0]);
      });
      decoded = decoded.replace(/&/g, "&")
                       .replace(/</g, "<")
                       .replace(/>/g, ">")
                       .replace(/"/g, "\"")
                       .replace(/'/g, "'");
      return [ { to: channel, message: decoded } ] ;
    }

  }

}

module.exports = messageListener;

Use lazy regex for parsing html <title/>


export const dbURI = (() => {
  if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') {
    return 'mongodb://localhost/trelloCloneApi';
  } else {
    return 'mongodb://mongo:27017/trelloCloneApi';
  }
})();

export const port = (() => {
  if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') {
    return 3001;
  } else {
    return 80;
  }
})();

export const dbTestURI = 'mongodb://localhost/trelloCloneApiTest';

export const secret = 'apiTest';

export const dbURI = (() => {
  if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') {
    return 'mongodb://localhost/trelloCloneApi';
  } else {
    return 'mongodb://mongo:27017/trelloCloneApi';
  }
})();

export const usersMicroserviceUrl = (() => {
  if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') {
    return 'http://localhost:3002';
  } else {
    return 'http://usersmicroservice:3002';
  }
})();

export const dbTestURI = 'mongodb://localhost/trelloCloneApiTest';

export const secret = 'apiTest';

export const port = 3001;

Use port 3001 as a default port


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

moduleForModel('user', 'Unit | Model | user', {
  // Specify the other units that are required for this test.
  needs: [
    'model:organization',
    'model:organization-membership',
    'model:user-category',
    'model:user-role',
    'model:user-skill'
  ]
});

test('it exists', function(assert) {
  let model = this.subject();
  // let store = this.store();
  assert.ok(!!model);
});

import { moduleForModel, test } from 'ember-qunit';
import { testForHasMany } from '../../helpers/relationship';

moduleForModel('user', 'Unit | Model | user', {
  // Specify the other units that are required for this test.
  needs: [
    'model:organization',
    'model:organization-membership',
    'model:user-category',
    'model:user-role',
    'model:user-skill'
  ]
});

test('it exists', function(assert) {
  let model = this.subject();
  // let store = this.store();
  assert.ok(!!model);
});

test('it has all of its attributes', function(assert) {
  assert.expect(15);

  const user = this.subject();
  const attributes = Object.keys(user.toJSON());

  assert.ok(attributes.includes('base64PhotoData'), 'should have base64PhotoData attribute');
  assert.ok(attributes.includes('biography'), 'should have biography attribute');
  assert.ok(attributes.includes('email'), 'should have email attribute');
  assert.ok(attributes.includes('firstName'), 'should have firstName attribute');
  assert.ok(attributes.includes('insertedAt'), 'should have insertedAt attribute');
  assert.ok(attributes.includes('lastName'), 'should have lastName attribute');
  assert.ok(attributes.includes('name'), 'should have name attribute');
  assert.ok(attributes.includes('password'), 'should have password attribute');
  assert.ok(attributes.includes('photoLargeUrl'), 'should have photoLargeUrl attribute');
  assert.ok(attributes.includes('photoThumbUrl'), 'should have photoThumbUrl attribute');
  assert.ok(attributes.includes('state'), 'should have state attribute');
  assert.ok(attributes.includes('stateTransition'), 'should have stateTransition attribute');
  assert.ok(attributes.includes('twitter'), 'should have twitter attribute');
  assert.ok(attributes.includes('username'), 'should have username attribute');
  assert.ok(attributes.includes('website'), 'should have website attribute');
});

testForHasMany('user', 'organizationMemberships');
testForHasMany('user', 'userCategories');
testForHasMany('user', 'userRoles');
testForHasMany('user', 'userSkills');

test('it correctly adds at in the username', function(assert) {
  assert.expect(1);

  let model = this.subject({ username: 'johndoe' });

  assert.equal(model.get('atUsername'), '@johndoe');
});

test('it correctly returns twitterUrl', function(assert) {
  assert.expect(1);

  let model = this.subject({ twitter: 'johndoe' });

  assert.equal(model.get('twitterUrl'), 'https://twitter.com/johndoe');
});

Update user model unit tests


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

moduleFor('route:user', 'Unit | Route | user', {
  // Specify the other units that are required for this test.
  // needs: ['controller:foo']
});

test('it exists', function(assert) {
  let route = this.subject();
  assert.ok(route);
});

Fix linting errors in user unit test.


(function ($) {

  function addCSSRule (selector, rules) {
    $('head').append('');
  }

  $.fn.sticky = function () {
    addCSSRule('.stuck', 'position: fixed !important; top: 0 !important; z-index: 10')
    addCSSRule('.stuck-wedge', 'padding: 0 !important; margin: 0 !important; height: 0')

    var elem = $(this),
        initialOffset = elem.offset().top,
        wedge = $('

').prependTo(elem.parent()),
        stuck = false

    $(window).on('scroll', function () {
      var currentPosition = $(window).scrollTop(),
          offset = elem.offset().top

      if (currentPosition < initialOffset && offset !== initialOffset && !stuck) {

        // Update the initialOffset, because the DOM has caused the element's position to change
        initialOffset = offset

      } else if (currentPosition >= Math.max(offset, initialOffset)) {

        if (stuck) return;

        // The page has scrolled past the top position of the element, so fix it and
        // apply its height as a margin to the next visible element so it doesn't jump
        elem.addClass('stuck')
        wedge.css('height', elem.height() + 'px')
        stuck = true

      } else {

        if(!stuck) return;

        // Unstick, because the element can now rest in its original position
        elem.removeClass('stuck')
        wedge.css('height', '')
        stuck = false
      }
    })
  }
})(jQuery)

(function ($) {

  function addCSSRule (selector, rules) {
    $('head').append('<style type="text/css">' + selector + ' { ' + rules + ' }</style>');
  }

  $.fn.sticky = function () {
    addCSSRule('.stuck', 'position: fixed !important; top: 0 !important; z-index: 10')
    addCSSRule('.stuck-wedge', 'padding: 0 !important; margin: 0 !important; height: 0')

    var elem = $(this),
        initialOffset = elem.offset().top,
        wedge = $('<div data-wedge></div>').prependTo(elem.parent()),
        stuck = false

    $(window).on('scroll', function () {
      var currentPosition = $(window).scrollTop(),
          offset = elem.offset().top

      if (currentPosition < initialOffset && offset !== initialOffset && !stuck) {

        // Update the initialOffset, because the DOM has caused the element's position to change
        initialOffset = offset

      } else if (currentPosition >= Math.max(offset, initialOffset)) {

        if (stuck) return;

        // The page has scrolled past the top position of the element, so fix it and
        // apply its height as a margin to the next visible element so it doesn't jump
        elem.data('stuck', true)
        wedge.css('height', elem.height() + 'px')
        stuck = true

      } else {

        if(!stuck) return;

        // Unstick, because the element can now rest in its original position
        elem.removeData('stuck')
        wedge.css('height', '')
        stuck = false
      }
    })
  }
})(jQuery)

Use data attributes instead of classes


const express = require('express');
const hbs = require('hbs');

// instantiate Express.js
const app = express();

// Tell Handlebars where to look for partials
hbs.registerPartials(__dirname +  '/views/partials');

// Set Handlebars as default templating engine
app.set('view engine', 'hbs');

app.use(express.static('/public'));

// root route
app.get('/', (req, res) => {
  res.render('home.hbs', {
    pageTitle: 'Home Page'
  });
});

// route for e-commerce site
app.get('/shop', (req, res) => {
  res.render('shop.hbs', {
    pageTitle: 'E-Commerce Shop'
  });
});

// Specify port and run local server
let port = 3000;

app.listen(port, () => {
  console.log(`listening on ${port}`);
});

const express = require('express');
const hbs = require('hbs');

// instantiate Express.js
const app = express();

// Tell Handlebars where to look for partials
hbs.registerPartials(__dirname +  '/views/partials');

// Set Handlebars as default templating engine
app.set('view engine', 'hbs');

// Point app towards stylesheets
app.use(express.static(__dirname + '/public'));

// root route
app.get('/', (req, res) => {
  res.render('home.hbs', {
    pageTitle: 'Home Page'
  });
});

// route for e-commerce site
app.get('/shop', (req, res) => {
  res.render('shop.hbs', {
    pageTitle: 'E-Commerce Shop'
  });
});

// Specify port and run local server
let port = 3000;

app.listen(port, () => {
  console.log(`listening on ${port}`);
});

Add path towards static assets


// Import the Node modules
var Twit     = require('twit'),              // Twitter API Client
    IOServer = require('socket.io'),         // Client-side communication
    config   = require('./authentication.js');  // Twitter Credentials

var topics = [];

// Create a new instance of the Socket Server
var port = 8000,
    io = new IOServer(port);

console.log('Listening for incoming connections in port ' + port);

io.on('connect', function(socket) {

    console.log('Client', socket.id, 'connected.');
});

// Import the Node modules
var Twit     = require('twit'),              // Twitter API Client
    IOServer = require('socket.io'),         // Client-side communication
    config   = require('./authentication.js');  // Twitter Credentials

var topics = [];

// Create a new instance of the Socket Server
var port = 8000,
    io = new IOServer(port);

console.log('Listening for incoming connections in port ' + port);

io.on('connect', function(socket) {

    console.log('Client', socket.id, 'connected.');

    socket.on('add', function(topic) {
        // Adds the new topic to the topic list
        topics.push({
          word: topic.word,
          socket: socket
        });

        console.log('Adding the topic "' + topic.word + '"');
    });

});

Add 'add' event when io.on and add feedback to confirm


import ReactDOM from 'react-dom';
import React, { PropTypes } from 'react';
import { Provider } from 'react-redux';

import createStore from '../../app/store/configureStore';
import InjectCardDepView from '../../app/containers/InjectCardDepView';
import InjectCardDepViewToggler from '../../app/containers/InjectCardDepViewToggler';
import { CHROME_SYNC_ITEMS } from '../../app/constants';

export default class Root extends React.Component {
  render() {
    return (
      <Provider store={this.props.store}>
        <div>
          <InjectCardDepViewToggler />
          <InjectCardDepView />
        </div>
      </Provider>
    );
  }
}

Root.propTypes = {
  store: PropTypes.object.isRequired
};

chrome.storage.sync.get(CHROME_SYNC_ITEMS, obj => {
  // const initialState = JSON.parse(app || '{}');
  const initialState = obj || {};
  console.log('initialState =', initialState);

  window.addEventListener('load', () => {
    const injectDOM = document.createElement('div');
    injectDOM.style.display = 'none';
    document.getElementsByTagName('body')[0].appendChild(injectDOM);
    ReactDOM.render(
      <Root store={createStore(initialState)} />,
      injectDOM
    );
  });
});

import ReactDOM from 'react-dom';
import React, { PropTypes } from 'react';
import { Provider } from 'react-redux';

import createStore from '../../app/store/configureStore';
import InjectCardDepView from '../../app/containers/InjectCardDepView';
import InjectCardDepViewToggler from '../../app/containers/InjectCardDepViewToggler';
import { CHROME_SYNC_ITEMS } from '../../app/constants';

export default class Root extends React.Component {
  render() {
    return (
      <Provider store={this.props.store}>
        <div>
          <InjectCardDepViewToggler />
          <InjectCardDepView />
        </div>
      </Provider>
    );
  }
}

Root.propTypes = {
  store: PropTypes.object.isRequired
};

chrome.storage.sync.get(CHROME_SYNC_ITEMS, obj => {
  // const initialState = JSON.parse(app || '{}');
  const initialState = obj || {};
  console.log('initialState =', initialState);

  window.addEventListener('load', () => {
    const injectDOM = document.createElement('div');
    injectDOM.style.display = 'none';
    document.body.appendChild(injectDOM);
    ReactDOM.render(
      <Root store={createStore(initialState)} />,
      injectDOM
    );
  });
});

Refactor body DOM selector to the body prop


define(function(require) {
    var $ = require('jquery');
    var IPython = require('base/js/namespace');
    var models = require('./models');
    var views = require('./views');

    function load() {
        if (!IPython.notebook_list) return;
        var base_url = IPython.notebook_list.base_url;
        $('head').append(
            $('<link>')
            .attr('rel', 'stylesheet')
            .attr('type', 'text/css')
            .attr('href', base_url + 'nbextensions/nb_conda/conda.css')
        );

        $.ajax(base_url + 'nbextensions/nb_conda/tab.html', {
            dataType: 'html',
            success: function(env_html, status, xhr) {
                // Configure Conda tab
                $(".tab-content").append($(env_html));
                $("#tabs").append(
                    $('<li>')
                    .append(
                        $('<a>')
                        .attr('href', '#conda')
                        .attr('data-toggle', 'tab')
                        .text('Conda')
                        .click(function (e) {
                            window.history.pushState(null, null, '#conda');

                            models.environments.load();
                            models.available.load();
                        })
                    )
                );

                views.EnvView.init();
                views.AvailView.init();
                views.InstalledView.init();

                models.environments.view = views.EnvView;
                models.available.view = views.AvailView;
                models.installed.view = views.InstalledView;
            }
        });
    }
    return {
        load_ipython_extension: load
    };
});

define(function(require) {
    var $ = require('jquery');
    var IPython = require('base/js/namespace');
    var models = require('./models');
    var views = require('./views');

    function load() {
        if (!IPython.notebook_list) return;
        var base_url = IPython.notebook_list.base_url;
        $('head').append(
            $('<link>')
            .attr('rel', 'stylesheet')
            .attr('type', 'text/css')
            .attr('href', base_url + 'nbextensions/nb_conda/conda.css')
        );

        $.ajax(base_url + 'nbextensions/nb_conda/tab.html', {
            dataType: 'html',
            success: function(env_html, status, xhr) {
                // Configure Conda tab
                $(".tab-content").append($(env_html));
                $("#tabs").append(
                    $('<li>')
                    .append(
                        $('<a>')
                        .attr('id', 'conda_tab')
                        .attr('href', '#conda')
                        .attr('data-toggle', 'tab')
                        .text('Conda')
                        .click(function (e) {
                            window.history.pushState(null, null, '#conda');

                            models.environments.load();
                            models.available.load();
                        })
                    )
                );

                views.EnvView.init();
                views.AvailView.init();
                views.InstalledView.init();

                models.environments.view = views.EnvView;
                models.available.view = views.AvailView;
                models.installed.view = views.InstalledView;

                if(window.location.hash === '#conda') {
                    $('#conda_tab').click();
                }
            }
        });
    }
    return {
        load_ipython_extension: load
    };
});

Load conda tab at startup if url points there


'use strict';

var should = require('should');

var Anyfetch = require('../lib/index.js');

// Tests to write:
// getStatus()
// getCompany()
// getSubcompanyById()
// postUser()
// updateCompany()
// getDocumentById(123).getRaw()
// getDocumentById(123, cb)

describe('getStatus', function(){
  it('should send a request to the correct endpoint', function(done){
    Anyfetch.getStatus(function(err, res){
      // TODO: Test value here with should
      done();
    });
  });
});

describe('getDocumentById', function(){
  describe('subfunctions', function(){
    it('should return synchronously an object containing only functions', function(){
      var ret = Anyfetch.getDocumentById(123);
      // TODO: Test that `ret` is an object of functions
    });
  });
});

'use strict';

var should = require('should');

var Anyfetch = require('../lib/index.js');
var configuration = require('../config/configuration.js');

// Tests to write:
// getStatus()
// getCompany()
// getSubcompanyById()
// postUser()
// postCompanyUpdate()
// getDocumentById(123).getRaw()
// getDocumentById(123, cb)

var testEndpoint = function(name){

  describe(name, function(){
    describe('the request', function(){
      var expected = configuration.apiDescriptors[name];
      var r = null;

      before(function(done){
        // TODO: support id, identifier
        Anyfetch[name](function(err, res) {
          r = res;
          done(err);
        });
      });

      it('should use the correct verb', function(){
        r.req.method.should.equal(expected.verb);
      });
      it('should target the correct endpoint', function(){
        r.req.path.should.equal(expected.endpoint);
      });
      it('should have the expected return code', function(){
        r.res.statusCode.should.equal(expected.expectedStatus);
      });
    });
  });

};

testEndpoint('getStatus');
testEndpoint('getIndex');
testEndpoint('getCompany');
testEndpoint('postCompanyUpdate');
testEndpoint('getDocuments');
testEndpoint('getUsers');

describe('getDocumentById', function(){
  describe('subfunctions', function(){
    it('should return synchronously an object containing only functions', function(){
      var ret = Anyfetch.getDocumentById(123);
      // TODO: Test that `ret` is an object of functions
    });
  });
});

Test the request being sent


import React from 'react';
import PropTypes from 'prop-types';
import { c } from 'ttag';

import { SETTINGS_VIEW } from '../../constants';

const { DAY, WEEK, MONTH, YEAR, PLANNING } = SETTINGS_VIEW;

const ViewPreferenceSelector = ({ className = 'pm-field w100', loading = false, disabled = false, view, onChange, ...rest }) => {
    const options = [
        { text: c('Calendar view').t`Day`, value: DAY },
        { text: c('Calendar view').t`Week`, value: WEEK },
        { text: c('Calendar view').t`Month`, value: MONTH },
        { text: c('Calendar view').t`Year`, value: YEAR },
        { text: c('Calendar view').t`Agenda`, value: PLANNING },
    ].filter(Boolean);

    return (
        <select
            disabled={loading || disabled}
            className={className}
            title={c('Action').t`Select calendar view`}
            value={view}
            onChange={({ target }) => onChange(+target.value)}
            {...rest}
        >
            {options.map(({ text, value }) => {
                return (
                    <option key={value} value={value}>
                        {text}
                    </option>
                );
            })}
        </select>
    );
};

ViewPreferenceSelector.propTypes = {
    disabled: PropTypes.bool,
    loading: PropTypes.bool,
    className: PropTypes.string,
    view: PropTypes.oneOf([DAY, WEEK, MONTH, YEAR, PLANNING]),
    onChange: PropTypes.func
};

export default ViewPreferenceSelector;

import React from 'react';
import PropTypes from 'prop-types';
import { c } from 'ttag';

import { SETTINGS_VIEW } from '../../constants';

const { DAY, WEEK, MONTH, YEAR, PLANNING } = SETTINGS_VIEW;

const ViewPreferenceSelector = ({
    className = 'pm-field w100',
    loading = false,
    disabled = false,
    view,
    onChange,
    ...rest
}) => {
    const options = [
        { text: c('Calendar view').t`Day`, value: DAY },
        { text: c('Calendar view').t`Week`, value: WEEK },
        { text: c('Calendar view').t`Month`, value: MONTH }
        // not activated for beta
        //{ text: c('Calendar view').t`Year`, value: YEAR },
        //{ text: c('Calendar view').t`Agenda`, value: PLANNING },
    ].filter(Boolean);

    return (
        <select
            disabled={loading || disabled}
            className={className}
            title={c('Action').t`Select calendar view`}
            value={view}
            onChange={({ target }) => onChange(+target.value)}
            {...rest}
        >
            {options.map(({ text, value }) => {
                return (
                    <option key={value} value={value}>
                        {text}
                    </option>
                );
            })}
        </select>
    );
};

ViewPreferenceSelector.propTypes = {
    disabled: PropTypes.bool,
    loading: PropTypes.bool,
    className: PropTypes.string,
    view: PropTypes.oneOf([DAY, WEEK, MONTH, YEAR, PLANNING]),
    onChange: PropTypes.func
};

export default ViewPreferenceSelector;

Fix - remove Year and Agenda view in General settings


/**
 * @depends TableLookupOscillator.js
 */
var Triangle = new Class({
    Extends: TableLookupOscillator,
    initialize: function(audiolet, frequency) {
        TableLookupOscillator.prototype.initialize.apply(this, [audiolet,
                                                                Triangle.TABLE,
                                                                frequency]);
    }
});

Triangle.TABLE = [];
for (var i = 0; i < 8192; i++) {
    // Smelly, but looks right...
    Triangle.TABLE.push(Math.abs(((i - 2048) / 8192) % 1 * 2 - 1) * 2 - 1);
}

/**
 * @depends TableLookupOscillator.js
 */
var Triangle = new Class({
    Extends: TableLookupOscillator,
    initialize: function(audiolet, frequency) {
        TableLookupOscillator.prototype.initialize.apply(this, [audiolet,
                                                                Triangle.TABLE,
                                                                frequency]);
    }
});

Triangle.TABLE = [];
for (var i = 0; i < 8192; i++) {
    // Smelly, but looks right...
    Triangle.TABLE.push(Math.abs(((((i - 2048) / 8192) % 1) + 1) % 1* 2 - 1) * 2 - 1);
}

Fix triangle wave for the modulo of a negative number issue


// @flow

const TIME_FORMAT = 'h:mma'
import {timezone} from '@frogpond/constants'
import moment from 'moment-timezone'

export const parseTime = (now: moment) => (
	time: string | false,
): null | moment => {
	// either pass `false` through or return a parsed time
	if (time === false) {
		return null
	}

	// interpret in Central time
	let m = moment.tz(time, TIME_FORMAT, true, timezone())

	// and set the date to today
	m.dayOfYear(now.dayOfYear())

	let sixMonthsAgo = moment(now).subtract(6, 'months')

	if (m.isBefore(sixMonthsAgo)) {
		m.add(1, 'year')
	}

	return m
}

// @flow

const TIME_FORMAT = 'h:mma'
import {timezone} from '@frogpond/constants'
import moment from 'moment-timezone'

// TODO: Do "dayOfYear" handling better so that we don't need to handle wrapping at
// the 6 month mark. (See #3375 for why this function changed.)
export const parseTime = (now: moment) => (
	time: string | false,
): null | moment => {
	// either pass `false` through or return a parsed time
	if (time === false) {
		return null
	}

	// interpret in Central time
	let m = moment.tz(time, TIME_FORMAT, true, timezone())

	// and set the date to today
	m.dayOfYear(now.dayOfYear())

	let sixMonthsAgo = moment(now).subtract(6, 'months')

	if (m.isBefore(sixMonthsAgo)) {
		m.add(1, 'year')
	}

	return m
}

Duplicate commentary on dayOfYear handling


/* Copyright (C) 2018 Canonical Ltd. */
'use strict';

const React = require('react');

const ExpertCard = require('../expert-card/expert-card');
const EXPERTS = require('../expert-card/experts');

const jsTestUtils = require('../../utils/component-test-utils');

describe('ExpertCard', function() {
  function renderComponent(options={}) {
    return jsTestUtils.shallowRender(
      <ExpertCard
        expert={options.expert || EXPERTS['spicule']}
        staticURL="/media">
        Content
      </ExpertCard>, true);
  }

  it('can render', () => {
    const renderer = renderComponent();
    const output = renderer.getRenderOutput();
    const expected = (

          Juju expert partners

        Content

);
    expect(output).toEqualJSX(expected);
  });
});

/* Copyright (C) 2018 Canonical Ltd. */
'use strict';

const React = require('react');
const enzyme = require('enzyme');

const ExpertCard = require('../expert-card/expert-card');
const EXPERTS = require('../expert-card/experts');

describe('ExpertCard', function() {

  const renderComponent = (options = {}) => enzyme.shallow(
    <ExpertCard
      expert={options.expert || EXPERTS['spicule']}
      staticURL="/media">
      Content
    </ExpertCard>
  );

  it('can render', () => {
    const wrapper = renderComponent();
    const expected = (

          Juju expert partners

        Content

);
    assert.compareJSX(wrapper, expected);
  });
});

Update expert card tests to use enzyme.


var app = angular.module('sprintBoard', ['ui.bootstrap']);

app.controller('boardController', function($scope, $http, $uibModal) {
    var promise = $http.get(API_ROOT + 'board/' + BOARD_HASH);
    promise.then(function(data){
        $scope.board = data.data;
        console.log(data.data.cards);
    });
    $scope.addCard = function(){
        var modal = $uibModal.open({
            templateUrl: 'js/templates/modal-add-card.html',
            controller: 'ModalAddCardController'
        });
    };
});

app.controller('ModalAddCardController', function($scope, $uibModalInstance, $http){
    $scope.cancel = function() {
        $uibModalInstance.dismiss('cancel');
    };
    $scope.submit = function() {
        var body = {};
        body.name = $scope.card.name;
        var promise = $http.post(API_ROOT + 'board/' + BOARD_HASH + '/card', body);
        promise.then(function(){
            $uibModalInstance.dismiss('cancel');
            // TODO: refresh views
        })
    }
});

var app = angular.module('sprintBoard', ['ui.bootstrap']);

app.controller('boardController', function($scope, $http, $uibModal) {

    $scope.updateBoard = function(){
        var promise = $http.get(API_ROOT + 'board/' + BOARD_HASH);
        promise.then(function(data){
            $scope.board = data.data;
            console.log(data.data.cards);
        });
    };

    $scope.updateBoard();

    $scope.$on('boardUpdateEvent', function() {
        $scope.updateBoard();
    });

    $scope.addCard = function(){
        var modal = $uibModal.open({
            templateUrl: 'js/templates/modal-add-card.html',
            controller: 'ModalAddCardController'
        });
    };
});

app.controller('ModalAddCardController', function($scope, $uibModalInstance, $http, $rootScope){
    $scope.cancel = function() {
        $uibModalInstance.dismiss('cancel');
    };
    $scope.submit = function() {
        var body = {};
        body.name = $scope.card.name;
        var promise = $http.post(API_ROOT + 'board/' + BOARD_HASH + '/card', body);
        promise.then(function(){
            $uibModalInstance.dismiss();
            $rootScope.$broadcast('boardUpdateEvent');
        })
    }
});

Refresh view when adding a new card


const debug = require('debug')('server:app');
const serverRegistration = require('./server-registration');
const heartBeat = require('./heart-beat');
module.exports = function () {
  // 'function' needed as we use 'this'
  debug('Config');
  const app = this;
  heartBeat();
}

const debug = require('debug')('server:app');
const serverRegistration = require('./server-registration');
const heartBeat = require('./heart-beat');
const Parse = require('parse/node').Parse;
var config = require('config');
module.exports = function () {
  // 'function' needed as we use 'this'
  debug('Server Config');
  // Configure parse server here.
  Parse.serverURL = config.parseServer.url;
  Parse.initialize(config.parseServer.appId, config.parseServer.key);
  const app = this;
  heartBeat();

}

Configure parse on task server startup


"use strict";

var app = angular.module("VinculacionApp", ['ui.router', 'ngAnimate']);

app.config(['$stateProvider', '$urlRouterProvider',function($stateProvider, $urlRouterProvider) {
	$urlRouterProvider.otherwise('/login');

	// Por el primer sprint solo las primeras tres rutas
	// estaran disponibles para los alumnos.

	$stateProvider

		.state('login', {
			url: '/login',
			templateUrl: '../templates/login.html'
		})

		.state('registro', {
			url: '/registro',
			templateUrl: '../templates/registro.html'
		})

		.state('home', {
			url: '/home',
			templateUrl: '../templates/home.html'
		})

		.state('home.clases', {
			url: '/clases',
			templateUrl: '../templates/clases.html'
		})

		.state('home.proyectos', {
			url: '/proyectos',
			templateUrl: '../templates/proyectos.html'
		})

		.state('home.proyecto', {
			url: '/proyecto',
			templateUrl: '../templates/proyecto.html'
		});
}]);

"use strict";

var app = angular.module("VinculacionApp", ['ui.router', 'ngAnimate']);

app.config(['$stateProvider', '$urlRouterProvider',function($stateProvider, $urlRouterProvider) {
	$urlRouterProvider.otherwise('/');

	$stateProvider

		.state('landing', {
			url: '/',
			templateUrl: '../templates/landing.html'
		})

		.state('registro', {
			url: '/registro',
			templateUrl: '../templates/registro.html'
		})

		.state('home', {
			url: '/home',
			templateUrl: '../templates/dashboard.html'
		})

		.state('home.proyectos', {
			url: '/proyectos',
			templateUrl: '../templates/proyectos.html'
		})

		.state('home.proyecto', {
			url: '/proyecto',
			templateUrl: '../templates/proyecto.html'
		})

		.state('home.horas', {
			url: '/horas',
			templateUrl: '../templates/horas.html'
		})

		.state('home.solicitudes', {
			url: '/solicitudes',
			templateUrl: '../templates/solicitudes.html'
		})

		.state('home.solicitud', {
			url: '/solicitud',
			templateUrl: '../templates/solicitud.html'
		});

}]);

Fix routes for a better state hierarchy


Application.Services.factory('watcher',
    function () {
        var watcherService = {};

        watcherService.watch = function (scope, variable, fn) {
            scope.$watch(variable, function (newval, oldval) {
                if (!newval && !oldval) {
                    return;
                }
                else if (newval == "" && oldval) {
                    fn(oldval);
                } else {
                    fn(newval);
                }
            });
        };

        return watcherService;
    });

Application.Services.factory('watcher',
    function () {
        var watcherService = {};

        watcherService.watch = function (scope, variable, fn) {
            scope.$watch(variable, function (newval, oldval) {
                if (!newval && !oldval) {
                    return;
                }
                else if (newval === "" && oldval) {
                    fn(oldval);
                } else {
                    fn(newval);
                }
            });
        };

        return watcherService;
    });

Fix equality statement to use === rather than ==.


$(function() {
	testModel = new Models.ItemListing({
		id: 0,
		name: "Test Item",
		price: "(USD) $5",
		location: "Singapore",
		buyers: [0],
		owner: 0,
		imageUrl: 'http://placehold.it/96x96'
	});

	yourCollection = new Models.ItemListings();

	yourView = new Views.ListingView({
		collection: yourCollection,
		el: document.getElementById('you-listing')
	});

	yourCollection.add(testModel);
});

$(function() {
	testModel = new Models.ItemListing({
		id: 0,
		name: "Test Item",
		price: "(USD) $5",
		location: "Singapore",
		buyers: [0],
		owner: 0,
		imageUrl: 'http://placehold.it/96x96'
	});

	yourCollection = new Models.ItemListings();
	friendCollection = new Models.ItemListings();
	featuredCollection = new Models.ItemListings();
	publicCollection = new Models.ItemListings();

	yourView = new Views.ListingView({
		collection: yourCollection,
		el: $('#you-listing')[0]
	});

	friendView = new Views.ListingView({
		collection: friendCollection,
		el: $('#friend-listing')[0]
	});

	featuredView = new Views.ListingView({
		collection: featuredCollection,
		el: $('#featured-listing')[0]
	});

	publicView = new Views.ListingView({
		collection: publicCollection,
		el: $('#public-listing')[0]
	})

	yourCollection.add(testModel);
});

Set up other views and collections.


import Ember from 'ember';

export default Ember.Component.extend({
  canShoErrors: false,

  fieldNameDidChange: Ember.on('init', Ember.observer('fieldName', function() {
    let fieldName = this.get('fieldName');
    let fieldValidations = 'model.validations.attrs.' + fieldName;
    Ember.mixin(this, {
      fieldErrorMessages: Ember.computed(fieldValidations + '.messages', 'canShowErrors', function() {
        if(this.get('canShowErrors')) {
          return this.get(fieldValidations + '.messages');
        } else {
          return [];
        }
      }),
      fieldHasErrors: Ember.computed(fieldValidations + '.isValid', 'canShowErrors', function() {
        if(this.get('canShowErrors')) {
          return (this.get(fieldValidations + '.isValid') === false);
        } else {
          return false;
        }
      }),
    });
  })),

  wrapperErrorClass: Ember.computed('fieldHasErrors', function() {
    if(this.get('fieldHasErrors')) {
      return 'has-error';
    } else {
      return '';
    }
  }),

  focusOut() {
    this.set('canShowErrors', true);
  },
});

import Ember from 'ember';

export default Ember.Component.extend({
  canShowErrors: false,

  fieldNameDidChange: Ember.on('init', Ember.observer('fieldName', function() {
    let fieldName = this.get('fieldName');
    let fieldValidations = 'model.validations.attrs.' + fieldName;
    Ember.mixin(this, {
      fieldErrorMessages: Ember.computed(fieldValidations + '.messages', 'canShowErrors', function() {
        if(this.get('canShowErrors')) {
          return this.get(fieldValidations + '.messages');
        } else {
          return [];
        }
      }),
      fieldHasErrors: Ember.computed(fieldValidations + '.isValid', 'canShowErrors', function() {
        if(this.get('canShowErrors')) {
          return (this.get(fieldValidations + '.isValid') === false);
        } else {
          return false;
        }
      }),
    });
  })),

  wrapperErrorClass: Ember.computed('fieldHasErrors', function() {
    if(this.get('fieldHasErrors')) {
      return 'has-error';
    } else {
      return '';
    }
  }),

  // Don't show errors until the field has been unfocused. This prevents all
  // the inline errors from showing up on initial render.
  focusOut() {
    this.set('canShowErrors', true);
  },

  // Anytime the model changes, reset the error display so errors aren't
  // displayed until the field is unfocused again.
  //
  // This helps handle modals where the same form might be reused multiple
  // times. Without this, errors would show up immediately the second time the
  // modal is opened if all the fields were unfocused the first time the modal
  // was opened.
  hideErrorsOnModelChange: Ember.observer('model', function() {
    this.set('canShowErrors', false);
  }),
});

Improve inline error display handling for modals.


define([
  'extensions/views/single-stat'
],
function (SingleStatView) {
  var ResponeTimeNumberView = SingleStatView.extend({

    changeOnSelected: true,

    getValue: function () {
      var responseTime = Math.round(this.collection.getAverageResponseTime());
      return this.formatDuration(responseTime, 4);
    },

    labelPrefix: 'mean ',

    getLabel: function () {
      var period = this.collection.query.get('period'),
          periodLabel = period === 'day' ? '30 days' : '24 hours';

      return this.labelPrefix + 'for the last ' + periodLabel;
    },

    getValueSelected: function (selection) {
      return this.formatDuration(selection.selectedModel.get('avgresponse'), 4);
    },

    getLabelSelected: function (selection) {
      var model = selection.selectedModel;
      var start = model.get('_start_at');
      var end = model.get('_end_at');

      return [
        start.format('ha'),
        ' to ',
        end.format('ha'),
        ',<br>',
        start.format('D MMMM YYYY')
      ].join('');
    }
  });

  return ResponeTimeNumberView;
});

define([
  'extensions/views/single-stat'
],
function (SingleStatView) {
  var ResponeTimeNumberView = SingleStatView.extend({

    changeOnSelected: true,

    getValue: function () {
      var responseTime = Math.round(this.collection.getAverageResponseTime());
      return this.formatDuration(responseTime, 4);
    },

    labelPrefix: 'mean ',

    periodLabel: {
      hour: '24 hours',
      day: '30 days'
    },

    getLabel: function () {
      var period = this.collection.query.get('period');

      return this.labelPrefix + 'for the last ' + this.periodLabel[period];
    },

    getValueSelected: function (selection) {
      return this.formatDuration(selection.selectedModel.get('avgresponse'), 4);
    },

    getLabelSelected: function (selection) {
      var model = selection.selectedModel;
      var start = model.get('_start_at');
      var end = model.get('_end_at');

      return [
        start.format('ha'),
        ' to ',
        end.format('ha'),
        ',<br>',
        start.format('D MMMM YYYY')
      ].join('');
    }
  });

  return ResponeTimeNumberView;
});

Move period label to a config property


const axios = require('axios');
const through2 = require('through2');
const JSONStream = require('JSONStream');

function processRow(filing, enc, next) {
    if (filing.fec_file_id) {
        filing.fec_file_id = parseInt(
            filing.fec_file_id
                .replace('FEC-', '')
                .replace('SEN-','')
        );
    }

    this.push(filing);

    next();
}

module.exports = async options => {
    if (!options.key) {
        throw new Error('Error: key needed to use FEC API,' +
            'specify using options.key, ' +
            'or get one at https://api.data.gov/signup/');
    }

    const outStream = options.out || process.stdout;
    const baseURL = options.baseURL || 'https://api.open.fec.gov/v1/';
    const sort = options.sort || '-receipt_date';
    const perPage = options.perPage || 100;
    const page = options.page || 1;
    const endpoint = options.committee ?
        `committee/${options.committee}/filings/` :
        'efile/filings';
    const apiKey = options.key;

    const fecAPI = axios.create({
        baseURL
    });

    const response = await fecAPI.get(endpoint, {
        params: {
            sort,
            per_page: perPage,
            page,
            api_key: apiKey
        },
        responseType: 'stream'
    });

    return response.data
        .pipe(JSONStream.parse('results.*'))
        .pipe(
            through2.obj(
                processRow
            )
        );
};

const fetch = require('isomorphic-unfetch');
const streamify = require('into-stream');

module.exports = async options => {
    if (!options.key) {
        throw new Error('Error: key needed to use FEC API,' +
            'specify using options.key, ' +
            'or get one at https://api.data.gov/signup/');
    }

    const baseURL = options.baseURL || 'https://api.open.fec.gov/v1/';
    const endpoint = options.committee ?
        `committee/${options.committee}/filings/` :
        'efile/filings';

    const params = {
        sort: options.sort || '-receipt_date',
        per_page: options.perPage || 100,
        page: options.page || 1,
        api_key: options.key
    };

    // https://stackoverflow.com/a/34209399
    const query = Object.keys(params)
        .map(k => `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`)
        .join('&');

    const response = await fetch(`${baseURL}${endpoint}?${query}`);
    const data = await response.json();

    return streamify.object(data.results.map(filing => {
        if (filing.fec_file_id) {
            return {
                ...filing,
                fec_file_id: parseInt(
                    filing.fec_file_id
                        .replace('FEC-', '')
                        .replace('SEN-','')
                )
            }
        }

        return filing;
    }));
};

Migrate API call to use fetch


'use strict';

var findup = require('findup-sync');
var spawnSync = require('child_process').spawnSync;
var gruntPath = findup('node_modules/grunt-cli/bin/grunt', {cwd: __dirname});

process.title = 'grunth';

var harmonyFlags = [
    '--es-staging',
    '--harmony_scoping',
//    '--harmony_modules', // We have `require` and ES6 modules are still in flux
//    '--harmony_proxies', // `new Proxy({}, {})` throws
    '--harmony_generators',
    '--harmony_numeric_literals',
    '--harmony_strings',
    '--harmony_arrays',
];

module.exports = function cli(params) {
    spawnSync('node',
        harmonyFlags.concat([gruntPath]).concat(params),
        {
            cwd: process.cwd(),
            stdio: 'inherit',
        }
    );
};

'use strict';

var findup = require('findup-sync');
var spawnSync = require('child_process').spawnSync;
var gruntPath = findup('node_modules/grunt-cli/bin/grunt', {cwd: __dirname});

process.title = 'grunth';

var harmonyFlags = [
    '--es-staging',
    '--harmony_scoping',
//    '--harmony_modules', // We have `require` and ES6 modules are still in flux
//    '--harmony_proxies', // `new Proxy({}, {})` throws
    '--harmony_generators',
    '--harmony_numeric_literals',
    '--harmony_strings',
    '--harmony_arrays',
//    '--harmony_arrow_functions', // No lexical `this`; (function (){return(()=>this)()}.bind(2))() === 2
];

module.exports = function cli(params) {
    spawnSync('node',
        harmonyFlags.concat([gruntPath]).concat(params),
        {
            cwd: process.cwd(),
            stdio: 'inherit',
        }
    );
};

Add & comment out --harmony_arrow_functions


function ddg_spice_gifs(res) {
    if(!res || !res.data || !res.data.length){ return; }

    var searchTerm = DDG.get_query().replace(/gif+/i,'').trim();

    var items = res.data.map(function(item){
        return {
            h: item.images.original.url,
            j: item.images.original.url,
            u: item.url,
            ih: item.images.original.height,
            iw: item.images.original.width
        }
    });

    Spice.add({
        id: 'gifs',
        name: 'Gifs',

        data: items,

        meta: {
            sourceName: 'Giphy',
            sourceUrl: 'http://giphy.com/search/' + searchTerm,
            sourceIcon: true,
            count: res.pagination.count,
            total: res.pagination.total_count,
            itemType: 'Gifs'
        },

        view: 'Images'
    });
}

function ddg_spice_gifs(res) {
    if(!res || !res.data || !res.data.length){ return; }

    var searchTerm = DDG.get_query().replace(/gifs?/i,'').trim();

    Spice.add({
        id: 'gifs',
        name: 'Gifs',

        data: res.data,
	normalize: function(item) {
	    return {
		h: item.images.original.url,
		j: item.images.original.url,
		u: item.url,
		ih: item.images.original.height,
		iw: item.images.original.width
	    };
	},
        meta: {
            sourceName: 'Giphy',
            sourceUrl: 'http://giphy.com/search/' + searchTerm,
            sourceIcon: true,
            count: res.pagination.count,
            total: res.pagination.total_count,
            itemType: 'Gifs'
        },

        view: 'Images'
    });
}

Move map to normalize function.


function configureAdminMongo () {
  var AdminCollection = new Meteor.Collection('_PureAdmin-Admins');
  PureAdmin.isAdmin = function(userId) {
    if(userId === null) {
      return false;
    }
    if(AdminCollection.find().count() === 0) {
      AdminCollection.insert({user: userId});
      return true;
    } else if( typeof AdminCollection.findOne({user: userId}) !== 'undefined' ) {
      return true;
    }
    return false;
  }
}

Meteor.startup(function () {
  // check if there is an admin function configured
  if(typeof PureAdmin.isAdmin === "function") {
    return;
  }
  configureAdminMongo();
});

function configureAdminMongo () {
  var AdminCollection = new Mongo.Collection('_PureAdmin-Admins');
  PureAdmin.isAdmin = function(userId) {
    if(userId === null) {
      return false;
    }
    if(AdminCollection.find().count() === 0) {
      AdminCollection.insert({user: userId});
      return true;
    } else if( typeof AdminCollection.findOne({user: userId}) !== 'undefined' ) {
      return true;
    }
    return false;
  }
}

Meteor.startup(function () {
  // check if there is an admin function configured
  if(typeof PureAdmin.isAdmin === "function") {
    return;
  }
  configureAdminMongo();
});

Use Mongo instead of Meteor


'use strict';

/**
 * @ngdoc service
 * @name dashApp.Campuses
 * @description
 * # Campuses
 * Factory in the dashApp.
 */
angular.module('dashApp')
  .factory('Campuses', function () {
    // Service logic
    // ...

    var meaningOfLife = 42;

    // Public API here
    return {
      someMethod: function () {
        return meaningOfLife;
      }
    };
  });

'use strict';

/**
 * @ngdoc service
 * @name dashApp.Campuses
 * @description
 * # Campuses
 * Factory in the dashApp.
 */
angular.module('dashApp')
  .factory('Campuses', function (Restangular) {
    return Restangular.service('campuses');
  });

Implement Campuses service using Restangular


import passport from 'passport';
import passportLocal from 'passport-local';
import bcrypt from 'bcrypt';
import models from '../models';
import helpers from '../helpers';

const LocalStrategy = passportLocal.Strategy;
const User = models.User;

passport.serializeUser((sessionUser, done) => {
  done(null, sessionUser.id);
});

passport.deserializeUser((id, done) => {
  User.findById(id, (err, sessionUser) => done(err, sessionUser));
});

passport.use(new LocalStrategy({ passReqToCallback: true }, (req, username, password, done) => {
  User.findOne({
    where: {
      username
    }
  }).then((users) => {
    if (!users) {
      return done({ message: 'Username not found' }, false);
    }
    // Compare database salt to the hash of incoming password
    const reqPasswordHash = bcrypt.hashSync(req.body.password, users.salt);

    if (users.password === reqPasswordHash) {
      return done(null, users);
    }
    return done({ message: 'Username and password do not match' }, false);
  }).catch(err => done(err));
}
));

module.exports = passport;

import passport from 'passport';
import passportLocal from 'passport-local';
import bcrypt from 'bcrypt';
import models from '../models';
// import helpers from '../helpers';

const LocalStrategy = passportLocal.Strategy;
const User = models.User;

passport.serializeUser((sessionUser, done) => {
  done(null, sessionUser.id);
});

passport.deserializeUser((id, done) => {
  User.findById(id, (err, sessionUser) => done(err, sessionUser));
});

passport.use(new LocalStrategy({ passReqToCallback: true }, (req, username, password, done) => {
  User.findOne({
    where: {
      username
    }
  }).then((users) => {
    if (!users) {
      return done({ message: 'Username not found' }, false);
    }
    // Compare database salt to the hash of incoming password
    const reqPasswordHash = bcrypt.hashSync(req.body.password, users.salt);

    if (users.password === reqPasswordHash) {
      return done(null, users);
    }
    return done({ message: 'Username and password do not match' }, false);
  }).catch(err => done(err));
}
));

module.exports = passport;

Remove reference to deleted file


import * as Backbone from 'backbone';
import Items from '../collections/items';
import SearchBoxView from '../views/searchBox-view';
import SearchResultsView from '../views/searchResults-view';
import AppView from '../views/app-view';
import Events from '../helpers/backbone-events';
import DocumentSet from '../helpers/search';

class AppRouter extends Backbone.Router {

  get routes() {
    return {
      '': 'loadDefault',
      'search/(?q=*queryString)': 'search'
    };
  }

  initialize() {
    this.listenTo(Events, 'router:go', this.go);
  }

  go(route) {
    this.navigate(route, {trigger: true});
  }

  loadDefault() {
    new AppView();
  }

    let q = queryString.substring(2);
  search(queryString) {

    DocumentSet.search(q).then(function(result) {
      let currentDocuments = new DocumentSet(result).documents;
      let docCollection = new Items(currentDocuments);
      new SearchResultsView({collection: docCollection}).render();
    });
  }

}

export default AppRouter;

import * as Backbone from 'backbone';
import Items from '../collections/items';
import SearchBoxView from '../views/searchBox-view';
import SearchResultsView from '../views/searchResults-view';
import AppView from '../views/app-view';
import Events from '../helpers/backbone-events';
import DocumentSet from '../helpers/search';

class AppRouter extends Backbone.Router {

  get routes() {
    return {
      '': 'loadDefault',
      'search/(?q=*queryString)': 'search'
    };
  }

  initialize() {
    this.listenTo(Events, 'router:go', this.go);
  }

  go(route) {
    this.navigate(route, {trigger: true});
  }

  loadDefault() {
    new AppView();
  }

  search(queryString) {

    DocumentSet.search(q).then(function(result) {
      let currentDocuments = new DocumentSet(result).documents;
      let docCollection = new Items(currentDocuments);
      new SearchResultsView({collection: docCollection}).render();
    });
    queryString = queryString || '';
  }

}

export default AppRouter;

Simplify how query string is passed to search


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

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

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

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

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

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

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

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

Fix zoom range to the corresponding range for Openmaptiles


'use strict';

import VueRouter from 'vue-router';

import TaskListView from '../views/TaskListView';
import IndexView from '../views/IndexView';

export default new VueRouter({
  mode: 'history',

  routes: [
    {
      path: '/',
      // name: 'Index',
      component: IndexView
    },

    {
      path: '/hello/:name',
      // name: 'HelloWorld',
      component: IndexView
    },

    {
      path: '/task-list',
      // name: 'TaskList',
      component: TaskListView
    },

    {
      path: '/async',
      // name: 'Async',
      component: function(resolve) {
        // Webpack creates a seperate bundle with code splitting
        require(['../views/AsyncView.vue'], resolve);
      }
    },

    {
      path: '*',
      name: '404',
      component: IndexView
    }
  ]
});

'use strict';

import VueRouter from 'vue-router';

import TaskListView from '../views/TaskListView';
import IndexView from '../views/IndexView';

export default new VueRouter({
  mode: 'history',

  routes: [
    {
      path: '/',
      name: 'Index',
      component: IndexView
    },

    {
      path: '/hello/:name',
      name: 'HelloWorld',
      component: IndexView
    },

    {
      path: '/task-list',
      name: 'TaskList',
      component: TaskListView
    },

    {
      path: '/async',
      name: 'Async',
      component: function(resolve) {
        // Webpack creates a seperate bundle with code splitting
        // and handles the async loading via JSONP for us
        require(['../views/AsyncView.vue'], resolve);
      }
    },

    {
      path: '*',
      name: '404',
      component: IndexView
    }
  ]
});

Update comment and add named routes


import makeUrl from './makeUrl';

export default function makeApiUrl(endpoint, queryParams = {}) {
  return makeUrl(`${process.env.REACT_APP_SERVER_BASE}${process.env.REACT_APP_API_BASE}${endpoint}`, queryParams);
}
export function makeCharacterApiUrl(characterId = null, region = null, realm = null, name = null, fields = undefined) {
  return makeApiUrl(`character/${characterId ? `${characterId}/` : ''}${region}/${encodeURIComponent(realm)}/${encodeURIComponent(name)}`, {
    fields,
  });
}

import makeUrl from './makeUrl';

export default function makeApiUrl(endpoint, queryParams = {}) {
  return makeUrl(`${process.env.REACT_APP_SERVER_BASE}${process.env.REACT_APP_API_BASE}${endpoint}`, queryParams);
}
export function makeCharacterApiUrl(characterId = null, region = null, realm = null, name = null, fields = undefined) {
  const parts = ['character'];
  if (characterId) {
    parts.push(characterId);
  }
  if (region && realm && name) {
    parts.push(region);
    parts.push(realm);
    parts.push(name);
  }

  return makeApiUrl(parts.map(part => encodeURIComponent(part)).join('/'), {
    fields,
  });
}

Fix API url when region/realm/name is unknown


import React, {Component} from 'react';
import { Link } from 'react-router-dom';
import NavbarLogo from './NavbarLogo.js';
import '../stylesheets/components/navbar.css';

class NavComponent extends Component{

	render() {
		return (

			<nav>
				<Link to="/">HOME</Link>
				<Link to="/instructions">CB TEST</Link>
        <Link to="/hue-test" onClick={this.resetHueTest}>HUE TEST</Link><span> | </span>
				<Link to="/color-view">CB SIMULATOR</Link>
	      <Link to="/about-us">ABOUT US</Link>
	      <Link to="/contact-us">CONTACT US</Link>
			</nav>
		);
	}
}

export default NavComponent;

import React, {Component} from 'react';
import { Link } from 'react-router-dom';
import NavbarLogo from './NavbarLogo.js';
import '../stylesheets/components/navbar.css';

class NavComponent extends Component{

	render() {
		return (

			<nav>
				<Link to="/">HOME</Link>
				<Link to="/instructions">CB TEST</Link>
        <Link to="/hue-test" onClick={this.resetHueTest}>HUE TEST</Link>
				<Link to="/color-view">CB SIMULATOR</Link>
	      <Link to="/about-us">ABOUT US</Link>
	      <Link to="/contact-us">CONTACT US</Link>
			</nav>
		);
	}
}

export default NavComponent;

Delete span form nav bar on the header.


var nodemailer = require('nodemailer');
var transporter = nodemailer.createTransport({
  service: 'Mailgun',
  auth: {
    user: process.env.MAILGUN_USERNAME,
    pass: process.env.MAILGUN_PASSWORD
  }
});

/**
 * GET /contact
 */
exports.contactGet = function(req, res) {
  res.render('contact', {
    title: 'Contact'
  });
};

/**
 * POST /contact
 */
exports.contactPost = function(req, res) {
  req.assert('name', 'Name cannot be blank').notEmpty();
  req.assert('email', 'Email is not valid').isEmail();
  req.assert('email', 'Email cannot be blank').notEmpty();
  req.assert('message', 'Message cannot be blank').notEmpty();
  req.sanitize('email').normalizeEmail({ remove_dots: false });

  var errors = req.validationErrors();

  if (errors) {
    req.flash('error', errors);
    return res.redirect('/contact');
  }

  var mailOptions = {
    from: req.body.name + ' ' + '<'+ req.body.email + '>',
    to: 'your@email.com',
    subject: 'Contact Form',
    text: req.body.message
  };

  transporter.sendMail(mailOptions, function(err) {
    req.flash('success', { msg: 'Thank you! Your message has been submitted.' });
    res.redirect('/contact');
  });
};

var nodemailer = require('nodemailer');
var transporter = nodemailer.createTransport({
  service: 'Mailgun',
  auth: {
    user: process.env.MAILGUN_USERNAME,
    pass: process.env.MAILGUN_PASSWORD
  }
});

/**
 * GET /contact
 */
exports.contactGet = function(req, res) {
  res.render('contact', {
    title: 'Contact'
  });
};

/**
 * POST /contact
 */
exports.contactPost = function(req, res) {
  req.assert('name', 'Name cannot be blank').notEmpty();
  req.assert('email', 'Email is not valid').isEmail();
  req.assert('email', 'Email cannot be blank').notEmpty();
  req.assert('message', 'Message cannot be blank').notEmpty();
  req.sanitize('email').normalizeEmail({ remove_dots: false });

  var errors = req.validationErrors();

  if (errors) {
    req.flash('error', errors);
    return res.redirect('/contact');
  }

  var mailOptions = {
    from: req.body.name + ' ' + '<'+ req.body.email + '>',
    to: process.env.APP_EMAIL_ADDRESS,
    subject: 'Contact Form',
    text: req.body.message
  };

  transporter.sendMail(mailOptions, function(err) {
    req.flash('success', { msg: 'Thank you! Your message has been submitted.' });
    res.redirect('/contact');
  });
};

Send c/form email to address specified in env var


import { polyfill } from 'es6-promise';
polyfill();
import fetch from 'isomorphic-fetch';
import * as config from '../constants/app_config';
import * as actions from '../actions/';
import * as sql from '../constants/sql_queries';

const requestCrashData = () => ({
  type: actions.CRASHES_ALL_REQUEST
});

const recieveCrashData = (json) => ({
  type: actions.CRASHES_ALL_SUCCESS,
  json
});

const receiveCrashDataErr = (error) => ({
  type: actions.CRASHES_ALL_ERROR,
  error
});

const fetchCrashData = () => ({
  const query = sql.crashesByDate();
});

export default fetchCrashData;

import { polyfill } from 'es6-promise';
import fetch from 'isomorphic-fetch';
import * as config from '../constants/app_config';
import * as actions from '../actions/';
import * as sql from '../constants/sql_queries';

polyfill();

const requestCrashStatsData = () => ({
  type: actions.CRASHES_ALL_REQUEST
});

const recieveCrashStatsData = json => ({
  type: actions.CRASHES_ALL_SUCCESS,
  json
});

const receiveCrashStatsError = error => ({
  type: actions.CRASHES_ALL_ERROR,
  error
});

const fetchCrashStatsData = ({ startDate, endDate }) => {
  const query = sql.statsDate({ startDate, endDate });
  const url = `${config.sql_endpoint}q=${query}`;
  return (dispatch) => {
    dispatch(requestCrashStatsData());
    return fetch(url)
      .then(res => res.json())
      .then(json => dispatch(recieveCrashStatsData(json.rows)))
      .catch(error => dispatch(receiveCrashStatsError(error)));
  };
};

export default fetchCrashStatsData;

Add async action for default sql query


import { gql } from 'react-apollo';

// ===========
//	 QUERIES
// ===========

const _listObjectsFromS3 = (params) => {
	return gql`
	query ListObjectsFromS3($MaxKeys:Int,$Prefix:String,$StartAfter:String){
		_listObjectsFromS3(MaxKeys:$MaxKeys,Prefix:$Prefix,StartAfter:$StartAfter){ ${params} }
	}`;
}

export {
	_listObjectsFromS3
};

import { gql } from 'react-apollo';

// ===========
//	 QUERIES
// ===========

const _listObjectsFromS3 = (params) => {
	return gql`
	query ListObjectsFromS3($MaxKeys:Int,$Prefix:String,$StartAfter:String){
		_listObjectsFromS3(MaxKeys:$MaxKeys,Prefix:$Prefix,StartAfter:$StartAfter){ ${params} }
	}`;
}

const _getObject = (params) => {
	return gql`
	query GetObject($key:String!){
		_getObject(key:$key){ ${params} }
	}`;
}

export {
	_listObjectsFromS3,
	_getObject
};

Add get object graphql query


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

import prettyFormat from 'pretty-format'
import {CONFIGS_DIR, PACKAGE_DIR} from '../../paths'
import {getSettings} from '../toolbox'
import {join} from 'path'

let {debugConfigs, debugToolbox} = getSettings()

const CONFIG = {
  rootDir: join(PACKAGE_DIR, 'src'),
  testRegex: '__tests__',
  testPathIgnorePatterns: ['node_modules', '__fixture__', '__fixtures__'],
  transform: {
    '^.+\\.(jsx?)$': join(CONFIGS_DIR, 'jest', 'babel-transform.js'),
  },
  verbose: true,
}

if (debugConfigs || debugToolbox)
  console.log('Jest config: ', prettyFormat(CONFIG))

module.exports = CONFIG

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

import prettyFormat from 'pretty-format'
import {CONFIGS_DIR, PACKAGE_DIR} from '../../paths'
import {getSettings} from '../toolbox'
import {join} from 'path'

let {debugConfigs, debugToolbox} = getSettings()

const CONFIG = {
  rootDir: join(PACKAGE_DIR, 'src'),
  testRegex: '__tests__',
  testPathIgnorePatterns: ['node_modules', '__fixture__', '__fixtures__'],
  testURL: 'http://localhost',
  transform: {
    '^.+\\.(jsx?)$': join(CONFIGS_DIR, 'jest', 'babel-transform.js'),
  },
  verbose: true,
}

if (debugConfigs || debugToolbox)
  console.log('Jest config: ', prettyFormat(CONFIG))

module.exports = CONFIG

Add testURL to jest config


(function() {
	angular.module('c4cWebsite.actions')
	.factory('ActionService', ['$q', 'Tabletop', actionService]);

	/**
	  * Returns a promise when the action service is loaded
	  */
	function actionService($q, Tabletop) {
		var service = init(),
			actionSheet;

		return service;

		function init() {
			var service = {
				findBySlug: findBySlug,
				findByPage: findByPage,
			}

			var deferred = $q.defer();

			Tabletop.then(function(TabletopSheets) {
				actionSheet = TabletopSheets[0]["Actions"].all();

				deferred.resolve(service);
			});

			return deferred.promise;
		};

	/**
	  * Find an action by slug
	  */
	function findBySlug(slug) {
		angular.forEach(actionSheet, function(row) {
			if (row["Slug"] == slug)
			  return row;
		});
	};

	/**
	  * Find an action by slug
	  */
	function findByPage(page) {
		angular.forEach(actionSheet, function(row) {
			if (row["page slug"] == page)
			  return row;
		});
	};

	};
})();

(function() {
	angular.module('c4cWebsite.actions')
	.factory('ActionService', ['$q', 'Tabletop', actionService]);

	/**
	  * Returns a promise when the action service is loaded
	  */
	function actionService($q, Tabletop) {
		var service = init(),
			actionSheet;

		return service;

		function init() {
			var service = {
				findBySlug: findBySlug,
				findByPage: findByPage,
			}

			var deferred = $q.defer();

			Tabletop.then(function(TabletopSheets) {
				actionSheet = TabletopSheets[0]["Actions"].all();

				deferred.resolve(service);
			});

			return deferred.promise;
		};

	/**
	  * Find an action by slug
	  */
	function findBySlug(slug) {
		for (var i=0; i<actionSheet.length; i++) {
			var row = actionSheet[i];

			if (row["Slug"] == slug)
			  return row;
		}
	};

	/**
	  * Find an action by slug
	  */
	function findByPage(page) {
		for (var i=0; i<actionSheet.length; i++) {
			var row = actionSheet[i];

			if (row["page slug"] == page)
			  return row;
		}
	};

	};
})();

Fix syntax on findByPage findBySlug


'use strict';

import Singleton from './singleton';

// Due to the various ways packages can be included in JavaScript (e.g. <script>
// tags vs included via webpack), it's possible for a single application to
// have multiple copies of the OpenTracing code.  Try to ensure there's only one
// singleton even in such scenarios.
var singleton;
if (typeof window !== 'undefined' && window.__opentracing_singleton) {
    singleton = window.__opentracing_singleton;
} else if (typeof global !== 'undefined' && global.__opentracing_singleton) {
    singleton = global.__opentracing_singleton;
} else {
    singleton = new Singleton();
}
module.exports = singleton;

'use strict';

import Singleton from './singleton';

// Due to the various ways packages can be included in JavaScript (e.g. <script>
// tags vs included via webpack), it's possible for a single application to
// have multiple copies of the OpenTracing code.  Try to ensure there's only one
// singleton even in such scenarios.
let handleWindow = (typeof window !== 'undefined') ? window : {};
let handleGlobal = (typeof global !== 'undefined') ? global : {};

let singleton = handleWindow.__opentracing_singleton ||
    handleGlobal.__opentracing_singleton;
if (!singleton) {
    singleton = new Singleton();
    handleWindow.__opentracing_singleton = singleton;
    handleGlobal.__opentracing_singleton = singleton;
}

module.exports = singleton;

Handle the case of <script> and webpack


import { Actions } from '../constants';
import firebase from '../firebase';
import uuid from 'uuid';

export default {
  startNewDialogueCreation() {
    return { type: Actions.StartCreatingNewDialogue };
  },

  discardNewDialogue() {
    return { type: Actions.DiscardNewDialogue };
  },

  create(dialogue) {
    return (dispatch) => {
      dispatch({ type: Actions.SavingNewDialogue });
      const id = uuid.v4();
      dialogue = Object.assign({}, dialogue, { id });
      firebase.database().ref(`dialogues/${id}`).set(dialogue).then(
        () => {
          dispatch({
            type: Actions.NewDialogueSaved,
            dialogue
          });
        },
        (error) => {
          console.error('Failed to save new dialogue.', error);
        }
      );
    };
  },

  load() {
    return (dispatch) => {
      dispatch({ type: Actions.FetchingDialogues });
      firebase.database().ref('dialogues').once('value').then((dialogues) => {
        dispatch({
          type: Actions.ReceiveDialogues,
          dialogues: dialogues.val() || {}
        });
      });
    };
  }
};

import { Actions } from '../constants';
import firebase from '../firebase';
import uuid from 'uuid';

export default {
  startNewDialogueCreation() {
    return { type: Actions.StartCreatingNewDialogue };
  },

  discardNewDialogue() {
    return { type: Actions.DiscardNewDialogue };
  },

  create(dialogue) {
    return (dispatch) => {
      dispatch({ type: Actions.SavingNewDialogue });
      const id = uuid.v4();
      const createdOn = new Date().getTime();
      dialogue = Object.assign({}, dialogue, {
        id,
        createdOn
      });
      firebase.database().ref(`dialogues/${id}`).set(dialogue).then(
        () => {
          dispatch({
            type: Actions.NewDialogueSaved,
            dialogue
          });
        },
        (error) => {
          console.error('Failed to save new dialogue.', error);
        }
      );
    };
  },

  load() {
    return (dispatch) => {
      dispatch({ type: Actions.FetchingDialogues });
      firebase.database().ref('dialogues')
        .orderByChild('createdOn').once('value').then((dialogues) => {
        dispatch({
          type: Actions.ReceiveDialogues,
          dialogues: dialogues.val() || {}
        });
      });
    };
  }
};

Add sorting by creation date on dialogues


export class CellError extends Error {
  constructor(msg, details) {
    super(msg)
    this.details = details
  }
}

export class SyntaxError extends CellError {
  get type() { return 'engine' }
  get name() { return 'syntax' }
}

export class ContextError extends CellError {
  get type() { return 'engine' }
  get name() { return 'context' }
}

export class GraphError extends CellError {
  get type() { return 'graph' }
}

export class UnresolvedInputError extends GraphError {
  get name() { return 'unresolved' }
}

export class CyclicDependencyError extends GraphError {
  get trace() {
    return this.details.trace
  }
  get name() { return 'cyclic'}
}

export class OutputCollisionError extends GraphError {
  get name() { return 'collision'}
}

export class RuntimeError extends CellError {
  get type() { return 'runtime' }
  get name() { return 'runtime' }
}

export class ValidationError extends CellError {
  get type() { return 'runtime' }
  get name() { return 'validation' }
}

export class CellError extends Error {
  constructor(msg, details) {
    super(msg)
    this.details = details
  }

  static cast(err) {
    if (err instanceof CellError) {
      return err
    } else {
      return new RuntimeError(err.message, err)
    }
  }
}

export class SyntaxError extends CellError {
  get type() { return 'engine' }
  get name() { return 'syntax' }
}

export class ContextError extends CellError {
  get type() { return 'engine' }
  get name() { return 'context' }
}

export class GraphError extends CellError {
  get type() { return 'graph' }
}

export class UnresolvedInputError extends GraphError {
  get name() { return 'unresolved' }
}

export class CyclicDependencyError extends GraphError {
  get trace() {
    return this.details.trace
  }
  get name() { return 'cyclic'}
}

export class OutputCollisionError extends GraphError {
  get name() { return 'collision'}
}

export class RuntimeError extends CellError {
  get type() { return 'runtime' }
  get name() { return 'runtime' }
}

export class ValidationError extends CellError {
  get type() { return 'runtime' }
  get name() { return 'validation' }
}

Introduce a helper to cast errors to a CellError


/**
 * @file:   Subclass of LCS which allows to observe progress of the algorithm
 *          by intercepting function calls.
 */

DeltaJS.lcs.InstallLCSRecorder = function(lcs, recorder) {
    var orig = {};

    orig.compute = lcs.compute;
    lcs.compute = function(callback, T, limit) {
        if (typeof limit === 'undefined') {
            limit = this.defaultLimit();
        }
        recorder.onCompute.call(recorder, callback, T, limit);
        orig.compute.apply(this, arguments);
    }

    orig.nextSnakeHeadForward = lcs.nextSnakeHeadForward;
    lcs.nextSnakeHeadForward = function(head, k, kmin, kmax, V) {
        var k0 = orig.nextSnakeHeadForward.apply(this, arguments);
        var d = kmax;
        recorder.onSnakeHead.call(recorder, head, k0, d, false);
        return k0;
    }

    orig.nextSnakeHeadBackward = lcs.nextSnakeHeadBackward;
    lcs.nextSnakeHeadBackward = function(head, k, kmin, kmax, V) {
        var k0 = orig.nextSnakeHeadBackward.apply(this, arguments);
        var d = kmax-this.delta;
        recorder.onSnakeHead.call(recorder, head, k0, d, true);
        return k0;
    }

    orig.middleSnake = lcs.middleSnake;
    lcs.middleSnake = function(lefthead, righthead) {
        var d = orig.middleSnake.apply(this, arguments);
        recorder.onMiddleSnake.call(recorder, lefthead, righthead, d);
        return d;
    }
}

/**
 * @file:   Subclass of LCS which allows to observe progress of the algorithm
 *          by intercepting function calls.
 */

DeltaJS.lcs.InstallLCSRecorder = function(lcs, recorder) {
    var orig = {};

    orig.compute = lcs.compute;
    lcs.compute = function(callback, T, limit) {
        if (typeof limit === 'undefined') {
            limit = this.defaultLimit();
        }
        recorder.onCompute.call(recorder, callback, T, limit);
        orig.compute.apply(this, arguments);
    }

    orig.nextSnakeHeadForward = lcs.nextSnakeHeadForward;
    lcs.nextSnakeHeadForward = function(head, k, kmin, kmax, limits, V) {
        var k0 = orig.nextSnakeHeadForward.apply(this, arguments);
        var d = kmax;
        recorder.onSnakeHead.call(recorder, head, k0, d, false);
        return k0;
    }

    orig.nextSnakeHeadBackward = lcs.nextSnakeHeadBackward;
    lcs.nextSnakeHeadBackward = function(head, k, kmin, kmax, limits, V) {
        var k0 = orig.nextSnakeHeadBackward.apply(this, arguments);
        var d = kmax-limits.delta;
        recorder.onSnakeHead.call(recorder, head, k0, d, true);
        return k0;
    }

    orig.middleSnake = lcs.middleSnake;
    lcs.middleSnake = function(lefthead, righthead) {
        var d = orig.middleSnake.apply(this, arguments);
        recorder.onMiddleSnake.call(recorder, lefthead, righthead, d);
        return d;
    }
}

Fix new method signature and moved delta variable in lcs recorder


// @flow
import React from 'react'
import type {Props} from './clickable-box'
import {TouchableHighlight, TouchableWithoutFeedback} from 'react-native'
import {globalColors} from '../styles'

const ClickableBox = ({onClick, onLongPress, style, children, underlayColor, onPressIn, onPressOut, feedback = true}: Props) => {
  if (onClick) {
    if (feedback) {
      return <TouchableHighlight
        disabled={!onClick}
        onPress={onClick}
        onPressIn={onPressIn}
        onPressOut={onPressOut}
        onLongPress={onLongPress}
        style={{...boxStyle, ...style}}
        underlayColor={underlayColor || globalColors.black_10}>
        {children}
      </TouchableHighlight>
    } else {
      return <TouchableWithoutFeedback
        onPressIn={onPressIn}
        onPressOut={onPressOut}
        style={{...boxStyle, ...style}}
        onPress={onClick}
        onLongPress={onLongPress}>
        {children}
      </TouchableWithoutFeedback>
    }
  } else {
    return children
  }
}

const boxStyle = {
  borderRadius: 3,
}

export default ClickableBox

// @flow
import React from 'react'
import type {Props} from './clickable-box'
import Box from './box'
import {TouchableHighlight, TouchableWithoutFeedback} from 'react-native'
import {globalColors} from '../styles'

const ClickableBox = ({onClick, onLongPress, style, children, underlayColor, onPressIn, onPressOut, feedback = true}: Props) => {
  if (onClick) {
    if (feedback) {
      return <TouchableHighlight
        disabled={!onClick}
        onPress={onClick}
        onPressIn={onPressIn}
        onPressOut={onPressOut}
        onLongPress={onLongPress}
        style={{...boxStyle, ...style}}
        underlayColor={underlayColor || globalColors.black_10}>
        {children}
      </TouchableHighlight>
    } else {
      return <TouchableWithoutFeedback
        onPressIn={onPressIn}
        onPressOut={onPressOut}
        style={{...boxStyle, ...style}}
        onPress={onClick}
        onLongPress={onLongPress}>
        {children}
      </TouchableWithoutFeedback>
    }
  } else {
    return <Box
      style={{...boxStyle, ...style}}>
      {children}
    </Box>
  }
}

const boxStyle = {
  borderRadius: 3,
}

export default ClickableBox

Fix ClickableBox dropping styles when not clickable


const Base = require('./Base.js')

class FilterBase extends Base {
  constructor () {
    super()

    this.filters = this.getField('filters', {})
  }

  toObject () {
    if (Base.isMongoDatabase) {
      const map = new Map()
      const filters = this.filters
      for (const key in filters) {
        map.set(key, filters[key])
      }
      return {
        filters: map
      }
    }
    return {
      filters: this.filters
    }
  }
}
module.exports = FilterBase

const Base = require('./Base.js')

class FilterBase extends Base {
  constructor (data) {
    super(data)

    this.filters = this.getField('filters', {})
  }

  toObject () {
    if (Base.isMongoDatabase) {
      const map = new Map()
      const filters = this.filters
      for (const key in filters) {
        map.set(key, filters[key])
      }
      return {
        filters: map
      }
    }
    return {
      filters: this.filters
    }
  }
}

module.exports = FilterBase

Fix missing data param in constructor


// Define time interval model

NetVisInterval = function(startEvents, endEvents, prevInterval) {
	this.startEvents = startEvents;
	this.endEvents = endEvents;
	this._starts = startEvents[0]._t; // as momentjs object
	this._ends = endEvents[0]._t; // as momentjs object
	this.starts = this._starts.toISOString(); // as ISO timestamp
	this.ends = this._ends.toISOString();

	if (prevInterval) {
		this.messages = prevInterval.messages.slice(0); // copying array instance
		this.nodes = 	prevInterval.nodes.slice(0);
	} else {
		this.messages = [];
		this.nodes = [];
	}

	for(var i=0; i< this.startEvents.length; i++) {
		var event = this.startEvents[i];
		switch (event.event) {
			case "nodeEntered":
				this.nodes.push(event.node);
				break;
			case "messageSent":
				this.messages.push(event.message);
				break;
			case "messageReceived":
				for(var j = this.messages.length - 1; j >= 0; j--) {
				    if(this.messages[j].id === event.message.id) {
				       this.messages.splice(j, 1);
				    }
				}
		}
	}
};

// Define time interval model

NetVisInterval = function(startEvents, endEvents, prevInterval) {
	this.startEvents = startEvents;
	this.endEvents = endEvents;
	this._starts = startEvents[0]._t; // as momentjs object
	this._ends = endEvents[0]._t; // as momentjs object
	this.starts = this._starts.toISOString(); // as ISO timestamp
	this.ends = this._ends.toISOString();

	if (prevInterval) {
		this.messages = prevInterval.messages.slice(0); // copying array instance
		this.nodes = 	prevInterval.nodes.slice(0);
	} else {
		this.messages = [];
		this.nodes = [];
	}

	for(var i=0; i< this.startEvents.length; i++) {
		var event = this.startEvents[i];
		switch (event.event) {
			case "nodeEntered":
				this.nodes.push(event.node);
				break;
			case "nodeExited":
				for(var j = this.nodes.length - 1; j >= 0; j--) {
					if(this.nodes[j].id === event.node.id) {
						this.nodes.splice(j, 1);
					}
				}
			case "messageSent":
				this.messages.push(event.message);
				break;
			case "messageReceived":
				for(var j = this.messages.length - 1; j >= 0; j--) {
				    if(this.messages[j].id === event.message.id) {
				       this.messages.splice(j, 1);
				    }
				}
		}
	}
};

Hide node after nodeExited event


module.exports = {
  singleQuote: true,
  trailingComma: 'es5',
  printWidth: 100,
};

module.exports = {
  arrowParens: 'avoid',
  singleQuote: true,
  trailingComma: 'es5',
  printWidth: 100,
};

Set `arrowParens` option to `avoid`


let React = require("react")
let ReactDOM = require("react-dom")
let Colr = require('colr')
let draggable = require('./higher_order_components/draggable.js')
let { div } = React.DOM

@draggable({
  updateClientCoords({clientX, clientY}) {
    let rect = ReactDOM.findDOMNode(this).getBoundingClientRect()
    let hue = this.getScaledValue((rect.bottom - clientY) / rect.height)
    let colr = Colr.fromHsv(hue, this.props.hsv.s, this.props.hsv.v)

    this.props.colrLink.requestChange(colr)
  },
})
export default class extends React.Component {
  static displayName = "HueSlider"
  static defaultProps = Object.assign(require("../../default_props.js"))

  render() {
    return (
      <div
        className="frigb-slider frigb-vertical"
        onMouseDown={this.props.startDragging}
        onTouchStart={this.props.startDragging}
      >
        <div
          className="frigb-track"
        />
        <div
          className="frigb-pointer"
          style={{
            "bottom": this.props.getPercentageValue(this.props.hsv.h),
          }}
        />
      </div>
    )
  }
}

let React = require("react")
let ReactDOM = require("react-dom")
let Colr = require('colr')
let draggable = require('./higher_order_components/draggable.js')

@draggable({
  updateClientCoords({clientY}) {
    let rect = ReactDOM.findDOMNode(this).getBoundingClientRect()
    let hue = this.getScaledValue((rect.bottom - clientY) / rect.height)
    let colr = Colr.fromHsv(hue, this.props.hsv.s, this.props.hsv.v)

    this.props.colrLink.requestChange(colr)
  },
})
export default class extends React.Component {
  static displayName = "HueSlider"
  static defaultProps = Object.assign(require("../../default_props.js"))

  render() {
    return (
      <div
        className="frigb-slider frigb-vertical"
        onMouseDown={this.props.startDragging}
        onTouchStart={this.props.startDragging}
      >
        <div
          className="frigb-track"
        />
        <div
          className="frigb-pointer"
          style={{
            "bottom": this.props.getPercentageValue(this.props.hsv.h),
          }}
        />
      </div>
    )
  }
}

Drop unused variables (make Uglify happy)


import React from 'react';

export default class Point extends React.Component {
  constructor(props) {
    super(props);
    let colorClass;
    if (props.color === 0) {
      colorClass = 'white';
    } else if (props.color === 1) {
      colorClass = 'black';
    } else {
      ;
    }
    this.state = {
      color: colorClass
    }
  }

  render() {
    const { x, y } = this.props;
    return (

{ x }, { y }

    )
  }
}

import React from 'react';

export default class Point extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      color: this.getColor(),
    }
    this.setColor = this.setColor.bind(this);
  }

  getColor(color = this.props.color) {
    if (color === 0) {
      return 'white';
    } else if (color === 1) {
      return 'black';
    }
  }

  /* Right now this only adds a white Stone, will eventually
   * need to know about the current User.
  */
  setColor(e) {
    let color = this.props.color > 1 ? 0 : this.props.color;
    this.setState({ color:  this.getColor(color) })
  }

  render() {
    const { x, y } = this.props;
    return (

{ x }, { y }

    )
  }
}

Allow Stones to be added


const fs = require("fs");
const config = {
  kafkaHost: "localhost:9093",
  logger: {
      debug: _ => {},
      info: msg => console.log(msg),
      warn: msg => console.log(msg),
      error: msg => console.log(msg)
  },
  groupId: "example-group",
  clientName: "example-name",
  workerPerPartition: 1,
  options: {
      autoconnect: false,
      ssl: true,
      sslOptions: {
        // https://nodejs.org/dist/latest-v8.x/docs/api/tls.html#tls_tls_createsecurecontext_options
        rejectUnauthorized: true,
        key: fs.readFileSync("../kafka-setup/certs/ca-key"),
        cert: fs.readFileSync("../kafka-setup/certs/ca-cert"),
        ca:[fs.readFileSync("../kafka-setup/certs/ca-cert")],
        passphrase: "nodesinek"
      },
      sessionTimeout: 8000,
      protocol: ["roundrobin"],
      fromOffset: "latest",
      fetchMaxBytes: 1024 * 1024,
      fetchMinBytes: 1,
      fetchMaxWaitMs: 10,
      heartbeatInterval: 250,
      retryMinTimeout: 250,
      autoCommit: true,
      autoCommitIntervalMs: 1000,
      requireAcks: 1,
      ackTimeoutMs: 100,
      partitionerType: 3
  }
};

module.exports = config;

const fs = require("fs");
const config = {
  kafkaHost: "localhost:9093",
  logger: {
      debug: _ => {},
      info: msg => console.log(msg),
      warn: msg => console.log(msg),
      error: msg => console.log(msg)
  },
  groupId: "example-group",
  clientName: "example-name",
  workerPerPartition: 1,
  options: {
      ssl: true,
      sslOptions: {
        // https://nodejs.org/dist/latest-v8.x/docs/api/tls.html#tls_tls_createsecurecontext_options
        rejectUnauthorized: true,
        key: fs.readFileSync("../kafka-setup/certs/ca-key"),
        cert: fs.readFileSync("../kafka-setup/certs/ca-cert"),
        ca:[fs.readFileSync("../kafka-setup/certs/ca-cert")],
        passphrase: "nodesinek"
      },
      sessionTimeout: 8000,
      protocol: ["roundrobin"],
      fromOffset: "latest",
      fetchMaxBytes: 1024 * 1024,
      fetchMinBytes: 1,
      fetchMaxWaitMs: 10,
      heartbeatInterval: 250,
      retryMinTimeout: 250,
      autoCommit: true,
      autoCommitIntervalMs: 1000,
      requireAcks: 1,
      ackTimeoutMs: 100,
      partitionerType: 3
  }
};

module.exports = config;

Remove autoConnect option from sample


export const SET_STATE = 'SET_STATE';
export const ADD_MESSAGE = 'ADD_MESSAGE';
export const UPDATE_MESSAGE = 'UPDATE_MESSAGE';
export const DELETE_MESSAGE = 'DELETE_MESSAGE';

export function setState(state) {
  return {
    type: SET_STATE,
    state
  };
}

export function addMessage(message) {
  return {
    type: ADD_MESSAGE,
    message
  };
}

export function updateMessage(message) {
  return {
    type: UPDATE_MESSAGE,
    message
  };
}

export function deleteMessage(message) {
  return {
    type: DELETE_MESSAGE,
    message
  };
}

export function requestDelete(message) {
  return function (dispatch) {
    dispatch(deleteMessage(message));
    return fetch(message.url + 'z', {method: 'DELETE'});
  }
}

Create action for deleting messages from server


'use strict'

var spawn = require('child_process').spawn

function Switcher (version) {
  var binArgs = {
    n: ['n', version],
    nvm: [process.env.SHELL, '-c', 'source $NVM_DIR/nvm.sh; nvm use ' + version]
  }

  function switcher (bin) {
    var args = binArgs[bin]
    bin = args.shift()
    return spawn(bin, args, { stdio: 'inherit' })
  }

  switcher.binaries = Object.keys(binArgs)
  return switcher
}

module.exports = Switcher

'use strict'

var spawn = require('child_process').spawn

function Switcher (version) {
  var binArgs = {
    n: ['n', version],
    nvm: [process.env.SHELL, '-c', 'source $NVM_DIR/nvm.sh; nvm install ' + version]
  }

  function switcher (bin) {
    var args = binArgs[bin]
    bin = args.shift()
    return spawn(bin, args, { stdio: 'inherit' })
  }

  switcher.binaries = Object.keys(binArgs)
  return switcher
}

module.exports = Switcher

Use install for nvm to get auto-install behavior


import OpcodeLabels from './opcode-labels.js';

const isUndefined = a => typeof a === 'undefined';

/**
 * Convert monitors from VM format to what the GUI needs to render.
 * - Convert opcode to a label and a category
 * @param {string} block.id - The id of the monitor block
 * @param {string} block.spriteName - Present only if the monitor applies only to the sprite
 *     with given target ID. The name of the target sprite when the monitor was created
 * @param {string} block.opcode - The opcode of the monitor
 * @param {object} block.params - Extra params to the monitor block
 * @param {string|number|Array} block.value - The monitor value
 * @return {object} The adapted monitor with label and category
 */
export default function ({id, spriteName, opcode, params, value}) {
    let {label, category, labelFn} = OpcodeLabels(opcode);

    // Use labelFn if provided for dynamic labelling (e.g. variables)
    if (!isUndefined(labelFn)) label = labelFn(params);

    // Append sprite name for sprite-specific monitors
    if (spriteName) {
        label = `${spriteName}: ${label}`;
    }

    // If value is a number, round it to six decimal places
    if (typeof value === 'number') {
        value = Number(value.toFixed(6));
    }

    return {id, label, category, value};
}

import OpcodeLabels from './opcode-labels.js';

const isUndefined = a => typeof a === 'undefined';

/**
 * Convert monitors from VM format to what the GUI needs to render.
 * - Convert opcode to a label and a category
 * @param {string} block.id - The id of the monitor block
 * @param {string} block.spriteName - Present only if the monitor applies only to the sprite
 *     with given target ID. The name of the target sprite when the monitor was created
 * @param {string} block.opcode - The opcode of the monitor
 * @param {object} block.params - Extra params to the monitor block
 * @param {string|number|Array} block.value - The monitor value
 * @return {object} The adapted monitor with label and category
 */
export default function ({id, spriteName, opcode, params, value}) {
    let {label, category, labelFn} = OpcodeLabels(opcode);

    // Use labelFn if provided for dynamic labelling (e.g. variables)
    if (!isUndefined(labelFn)) label = labelFn(params);

    // Append sprite name for sprite-specific monitors
    if (spriteName) {
        label = `${spriteName}: ${label}`;
    }

    // If value is a number, round it to six decimal places
    if (typeof value === 'number') {
        value = Number(value.toFixed(6));
    }

    // Turn the value to a string, for handle boolean values
    if (typeof value === 'boolean') {
        value = value.toString();
    }

    // Lists can contain booleans, which should also be turned to strings
    if (Array.isArray(value)) {
        value = value.map(item => item.toString());
    }

    return {id, label, category, value};
}

Fix booleans not showing up in monitors