import {rule, group} from '../../typography-fixer'
import html from '../html'

let frFR_HTML

/**
 * The ruleset for HTML improvement on french typography
 *
 * It includes rules for:
 *
 * - Wrapping ends of common abbreviation in a `<sup>` tag so that `Mmes`
 *   becomes `M<sup>mes</sup>`
 * - Wrapping ordinal number suffix in a `<sup>` tag
 *
 * Finally, the following rulesets are also included:
 *
 * - {@link src/rules/html.js~htmlRuleset}
 *
 * @type {Array<Object>}
 */
export default frFR_HTML = createRuleset().concat(html)

function createRuleset () {
  return group('fr-FR.html', [
    rule('abbrWithSuperText', /Mmes|Mme|Mlles|Mlle|Me|Mgr|Dr|cie|Cie|Sté/, (m) => {
      return `${m[0]}<sup>${m.slice(1, m.length)}</sup>`
    }),
    rule('ordinalNumbers', /(\d)(res|re|es|e|èmes)/, '$1$2')
  ])
}

import {rule, group} from '../../typography-fixer'
import html from '../html'

let frFR_HTML

/**
 * The ruleset for HTML improvement on french typography
 *
 * It includes rules for:
 *
 * - Wrapping ends of common abbreviation in a `<sup>` tag so that `Mmes`
 *   becomes `M<sup>mes</sup>`
 * - Wrapping ordinal number suffix in a `<sup>` tag
 *
 * Finally, the following rulesets are also included:
 *
 * - {@link src/rules/html.js~html}
 *
 * @type {Array<Object>}
 */
export default frFR_HTML = createRuleset().concat(html)

function createRuleset () {
  return group('fr-FR.html', [
    rule('abbrWithSuperText', /Mmes|Mme|Mlles|Mlle|Me|Mgr|Dr|cie|Cie|Sté/, (m) => {
      return `${m[0]}<sup>${m.slice(1, m.length)}</sup>`
    }),
    rule('ordinalNumbers', /(\d)(res|re|es|e|èmes)/, '$1$2')
  ])
}

Fix link typo in docs


// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html

module.exports = function (config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine', '@angular-devkit/build-angular'],
    plugins: [
      require('karma-jasmine'),
      require('karma-chrome-launcher'),
      require('karma-jasmine-html-reporter'),
      require('karma-coverage-istanbul-reporter'),
      require('@angular-devkit/build-angular/plugins/karma')
    ],
    client: {
      clearContext: false // leave Jasmine Spec Runner output visible in browser
    },
    coverageIstanbulReporter: {
      dir: require('path').join(__dirname, '../coverage'),
      reports: ['html', 'lcovonly'],
      fixWebpackSourcePaths: true
    },
    reporters: ['progress', 'kjhtml'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['Chrome'],
    customLaunchers: {
      ChromeHeadlessNoSandbox: {
        base: 'ChromeHeadless',
        flags: ['--no-sandbox']
      }
    },
    singleRun: false
  });
};

// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html

module.exports = function (config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine', '@angular-devkit/build-angular'],
    plugins: [
      require('karma-jasmine'),
      require('karma-chrome-launcher'),
      require('karma-jasmine-html-reporter'),
      require('karma-coverage-istanbul-reporter'),
      require('@angular-devkit/build-angular/plugins/karma')
    ],
    client: {
      clearContext: false // leave Jasmine Spec Runner output visible in browser
    },
    coverageIstanbulReporter: {
      dir: require('path').join(__dirname, '../coverage'),
      reports: ['html', 'lcovonly'],
      fixWebpackSourcePaths: true
    },
    reporters: ['progress', 'kjhtml'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['Chrome'],
    singleRun: false
  });
};

Revert "Add custom launcher for karma"


import xhr from 'proton-shared/lib/fetch/fetch';
import configureApi from 'proton-shared/lib/api';

export default ({ CLIENT_ID, APP_VERSION, API_VERSION, API_URL }) => (UID) => {
    return configureApi({
        xhr,
        UID,
        API_URL,
        CLIENT_ID,
        API_VERSION,
        APP_VERSION
    });
};

import xhr from 'proton-shared/lib/fetch/fetch';
import configureApi from 'proton-shared/lib/api';

export default ({ CLIENT_ID, CLIENT_SECRET, APP_VERSION, API_VERSION, API_URL }) => (UID) => {
    return configureApi({
        xhr,
        UID,
        API_URL,
        CLIENT_ID,
        CLIENT_SECRET,
        API_VERSION,
        APP_VERSION
    });
};

Add support for sending client secret from the config


"use strict";

module.exports.charging = function() {
  if ( process.platform === "darwin") {
    var osxBattery = require("osx-battery");

    return osxBattery().then(res => {
      return res.isCharging || res.fullyCharged;
    });

  }
  else if ( process.platform === "linux") {
    var linuxBattery = require("linux-battery");

    // NOTE: this is not actually tested
    return linuxBattery().then(res => {
      return ( res.state !== "discharging" );
    });
  }
  else {
    // this code is modified from https://github.com/gillstrom/battery-level/blob/master/win.js
    var p = new Promise(function(resolve, reject) {
      var cmd = "WMIC";
      let args = ["Path", "Win32_Battery", "Get", "BatteryStatus"];

      //Other (1)
      //The battery is discharging.
      var exec = require("child_process").execFile;
      exec(cmd, args, function(error, stdout) {
        // console.log("stdout: " + stdout);
        // console.log("stderr: " + stderr);
        if (error !== null) {
          reject(error);
        }

        stdout = parseInt(stdout.trim().split("\n")[1], 10);

        var result = (stdout !== 1);
        resolve(result);
      });
    });

    return p;
  }
};

"use strict";

module.exports.charging = function() {
  if ( process.platform === "darwin") {
    var osxBattery = require("osx-battery");

    return osxBattery().then(res => {
      return res.isCharging || res.fullyCharged;
    }).catch(() => {
      return false;
    });
  }
  else if ( process.platform === "linux") {
    var linuxBattery = require("linux-battery");

    // NOTE: this is not actually tested
    return linuxBattery().then(res => {
      return ( res.state !== "discharging" );
    }).catch(() => {
      return false;
    });
  }
  else {
    // this code is modified from https://github.com/gillstrom/battery-level/blob/master/win.js
    var p = new Promise(function(resolve, reject) {
      var cmd = "WMIC";
      let args = ["Path", "Win32_Battery", "Get", "BatteryStatus"];

      //Other (1)
      //The battery is discharging.
      var exec = require("child_process").execFile;
      exec(cmd, args, function(error, stdout) {
        // console.log("stdout: " + stdout);
        // console.log("stderr: " + stderr);
        if (error !== null) {
          reject(error);
        }

        stdout = parseInt(stdout.trim().split("\n")[1], 10);

        var result = (stdout !== 1);
        resolve(result);
      });
    });

    return p;
  }
};

Add error handling to battery check


import isPlainObject from 'lodash/isplainobject'

function arrayify(fn) {
  return (...args) => {
    const ret = fn(...args)
    return Array.isArray(ret)
      ? ret
      : ret == null
      ? []
      : [ret]
  }
}

export default function createRoute(pattern, ...rest) {
  let options, children
  if (isPlainObject(rest[0]) || typeof rest[0] === 'string') {
    [options, children] = rest
    if (typeof options === 'string') {
      options = {activeKey: options}
    }
  }
  else {
    options = {}
    children = rest[0]
  }
  if (!pattern.includes(':')) {
    // throw new Error(`A route must include at least one unique parameter. Please check the route "${pattern}"`)
  }
  return {
    pattern,
    options: options || {},
    children: arrayify(typeof children === 'function' ? children : () => children)
  }
}

import isPlainObject from 'lodash/isPlainObject'

function arrayify(fn) {
  return (...args) => {
    const ret = fn(...args)
    return Array.isArray(ret)
      ? ret
      : ret == null
      ? []
      : [ret]
  }
}

export default function createRoute(pattern, ...rest) {
  let options, children
  if (isPlainObject(rest[0]) || typeof rest[0] === 'string') {
    [options, children] = rest
    if (typeof options === 'string') {
      options = {activeKey: options}
    }
  }
  else {
    options = {}
    children = rest[0]
  }
  if (!pattern.includes(':')) {
    // throw new Error(`A route must include at least one unique parameter. Please check the route "${pattern}"`)
  }
  return {
    pattern,
    options: options || {},
    children: arrayify(typeof children === 'function' ? children : () => children)
  }
}

Fix bad casing in import


module.exports = function modelLoader(isParent) {
    'use strict';

    if (isParent || process.env.MCTEST) {
        console.log('using mocks/model');
        return require('./mocks/model');
    }

    let ropts = {
        db: 'materialscommons',
        port: 30815
    };

    let r = require('rethinkdbdash')(ropts);
    return require('./db/model')(r);
};

module.exports = function modelLoader(isParent) {
    'use strict';

    if (isParent || process.env.MCTEST) {
        return require('./mocks/model');
    }

    let ropts = {
        db: process.env.MCDB || 'materialscommons',
        port: process.env.MCPORT || 30815
    };

    let r = require('rethinkdbdash')(ropts);
    return require('./db/model')(r);
};

Add environment variables for testing, database and database server port.


var path = require('path');
var moduleEntryPoint = require.resolve('incuna-sass');

var moduleDir = path.dirname(moduleEntryPoint);

function includePaths() {
  return [moduleDir];
}

module.exports = {

  includePaths: includePaths()

};

// This index module is returned by default when this package is required

// Node module imports
var path = require('path');

// Function returns absolute system path to location that module is installed.
// This is useful for referencing the files within this package in grunt tasks
// Uses a technique copied from node-bourbon
function includePaths() {
    var moduleEntryPoint = require.resolve('incuna-sass');
    var entrypointDir = path.dirname(moduleEntryPoint);
    // Module resolves to the main entry point - e.g. node/index.js, so we go up a
    //  level to get the main module root path
    var moduleDir = path.join(entrypointDir, '../');
    return [moduleDir];
}

module.exports = {
    // run modulePath() function immediately and return result as exported value
    includePaths: includePaths()
};

Make sure the node module path is reported correctly


makeThumbNail = function (fileBuffer, extenstion, callback) {
  var gmImageRessource = gm(fileBuffer, 'image.' + extenstion)
, newImage = {
    side: 400
  }
, syncSize = function (gmRessource, callback) {
    gmRessource.size(callback)
  }
, imageSize = Meteor.wrapAsync(syncSize)(gmImageRessource)
, imageRatio = {
    width: imageSize.width / imageSize.height
  , height: imageSize.height / imageSize.width
  }
  newImage.size = {
    height: Math.round(newImage.side / imageRatio.height)
  , width: Math.round(newImage.side / imageRatio.width)
  }
  console.log(imageSize)

  newGmImageRessource = gmImageRessource.resize(newImage.size.width, newImage.size.height)

  function syncToBuffer (gmRessource, callback) {
    gmRessource.toBuffer(callback)
  }

  newImageBuffer = Meteor.wrapAsync(syncToBuffer)(newGmImageRessource)
  callback(null, newImageBuffer)
}

makeThumbNail = function (fileBuffer, extenstion, callback) {
  var gmImageRessource = gm(fileBuffer, 'image.' + extenstion)
, newImage = {
    side: 400
  }

  function syncSize (gmRessource, callback) {
    gmRessource.size(callback)
  }
  var imageSize = Meteor.wrapAsync(syncSize)(gmImageRessource)

  newGmImageRessource = gmImageRessource.resize(400, 400)

  function syncToBuffer (gmRessource, callback) {
    gmRessource.toBuffer(callback)
  }

  newImageBuffer = Meteor.wrapAsync(syncToBuffer)(newGmImageRessource)
  callback(null, newImageBuffer)
}

Refactor makeThumbNail to produce images with min400 x min400


var $ = require('jquery');
var _ = require('underscore');
var View = require('cdb/core/view');
var Model = require('cdb/core/model');
var PaginatorView = require('./paginator_view');
var searchTemplate = require('./search_paginator_template.tpl');

module.exports = PaginatorView.extend({

  _TEMPLATE: searchTemplate,
  className: 'Widget-nav is-hidden Widget-contentSpaced',

  toggle: function() {
    this[ !this.viewModel.isSearchEnabled() ? 'hide' : 'show' ]();
  }

});

var $ = require('jquery');
var _ = require('underscore');
var View = require('cdb/core/view');
var Model = require('cdb/core/model');
var PaginatorView = require('./paginator_view');
var searchTemplate = require('./search_paginator_template.tpl');

module.exports = PaginatorView.extend({

  _TEMPLATE: searchTemplate,
  className: 'Widget-nav is-hidden Widget-contentSpaced',

  toggle: function() {
    this[ !this.viewModel.isSearchEnabled() ? 'hide' : 'show' ]();
  },

  _onSearchClicked: function() {
    this.options.originModel.cleanSearch();
    this.viewModel.toggleSearch();
  }

});

Clean search (input, data, ...) when search view is disabled


'use strict';

var Mixin = require('../../utils/mixin'),
    inherits = require('util').inherits;

var ErrorReportingMixinBase = module.exports = function (host, onParseError) {
    Mixin.call(this, host);

    this.posTracker = null;
    this.onParseError = onParseError;
};

inherits(ErrorReportingMixinBase, Mixin);

ErrorReportingMixinBase.prototype._getOverriddenMethods = function (mxn) {
    return {
        _err: function (code) {
            mxn.onParseError({
                code: code,
                line: mxn.posTracker.line,
                col: mxn.posTracker.col,
                offset: mxn.posTracker.offset
            });
        }
    };
};

'use strict';

var Mixin = require('../../utils/mixin'),
    inherits = require('util').inherits;

var ErrorReportingMixinBase = module.exports = function (host, onParseError) {
    Mixin.call(this, host);

    this.posTracker = null;
    this.onParseError = onParseError;
};

inherits(ErrorReportingMixinBase, Mixin);

ErrorReportingMixinBase.prototype._getOverriddenMethods = function (mxn) {
    return {
        _err: function (code, details) {
            mxn.onParseError({
                code: code,
                line: mxn.posTracker.line,
                col: mxn.posTracker.col,
                offset: mxn.posTracker.offset,
                details: details
            });
        }
    };
};

Add details field to error


'use strict';

let passport = require('passport');
let LocalStrategy = require('passport-local').Strategy;

const AUTH_ERROR = {message: 'Invalid email or password.'};

let localPassport = {
    localAuthenticate,
    setup
};

function localAuthenticate(userService, email, password, done) {
    userService.getByEmail(email.toLowerCase())
        .then(user => {
            if (!user) {
                return done(null, false, AUTH_ERROR);
            }
            user.authenticate(password, function (authError, authenticated) {
                if (authError) {
                    return done(authError);
                }
                if (authenticated) {
                    return done(null, user);
                } else {
                    return done(null, false, AUTH_ERROR);
                }
            });
        })
        .catch(err => {
            console.log('ERROR!');
            done(err)
        });
}

function setup(userService, config) {
    passport.use(new LocalStrategy({
            usernameField: 'email',
            passwordField: 'password' // this is the virtual field on the model
        },
        function (email, password, done) {
            return localAuthenticate(userService, email, password, done);
        }));
}

module.exports = localPassport;

Build gc-orders-server from commit bd221fb on branch master


(function() {
    "use strict";

    var app = angular.module("TendrlModule");

    app.controller("clusterDetailController", clusterDetailController);

    /*@ngInject*/
    function clusterDetailController($state, $stateParams, utils, $scope, $rootScope) {

        var vm = this;
        vm.tabList = { "Host": 1 };
        vm.setTab = setTab;
        vm.isTabSet = isTabSet;

        /* Adding clusterId in scope so that it will be accessible inside child directive */
        $scope.clusterId = $stateParams.clusterId;

        if (!$rootScope.clusterData) {
            $state.go("cluster");
        } else {

            vm.clusterObj = utils.getClusterDetails($scope.clusterId);
            vm.clusterName = vm.clusterObj.cluster_name || "NA";
            if (vm.clusterObj.sds_name === "glusterfs") {
                vm.tabList.FileShare = 2;
            } else {
                vm.tabList.Pool = 2;
                vm.tabList.RBD = 3;
            }
            vm.activeTab = vm.tabList["Host"];
        }

        function setTab(newTab) {
            vm.activeTab = newTab;
        }

        function isTabSet(tabNum) {
            return vm.activeTab === tabNum;
        }

    }

})();

(function() {
    "use strict";

    var app = angular.module("TendrlModule");

    app.controller("clusterDetailController", clusterDetailController);

    /*@ngInject*/
    function clusterDetailController($state, $stateParams, utils, $scope, $rootScope) {

        var vm = this;
        vm.tabList = { "Host": 1 };
        vm.setTab = setTab;
        vm.isTabSet = isTabSet;

        /* Adding clusterId in scope so that it will be accessible inside child directive */
        $scope.clusterId = $stateParams.clusterId;

        if (!$rootScope.clusterData) {
            utils.getObjectList("Cluster")
                .then(function(data) {
                    $rootScope.clusterData = data;
                    _setClusterDetail();
            });

        } else {
            _setClusterDetail();
        }

        function _setClusterDetail() {
            vm.clusterObj = utils.getClusterDetails($scope.clusterId);
            vm.clusterName = vm.clusterObj.cluster_name || "NA";
            if (vm.clusterObj.sds_name === "glusterfs") {
                vm.tabList.FileShare = 2;
            } else {
                vm.tabList.Pool = 2;
                vm.tabList.RBD = 3;
            }
            vm.activeTab = vm.tabList["Host"];
        }

        function setTab(newTab) {
            vm.activeTab = newTab;
        }

        function isTabSet(tabNum) {
            return vm.activeTab === tabNum;
        }

    }

})();

Fix for cluster detail page


window.addEventListener('load', function load(event) {
	window.removeEventListener('load', load, false);
	MarkdownViewer.init();
}, false);

if (!MarkdownViewer) {

	var MarkdownViewer = {

		init: function() {
			var appcontent = document.getElementById('appcontent');
			if (appcontent)
				appcontent.addEventListener('DOMContentLoaded', this.onPageLoad, true);
		},

		onPageLoad: function(aEvent) {
			var document = aEvent.originalTarget;
			var markdownFileExtension = /\.m(arkdown|kdn?|d(o?wn)?)(#.*)?$/i;

			if (document.location.protocol !== "view-source:"
				&& markdownFileExtension.test(document.location.href)) {

				var content = document.firstChild;
				content.innerHTML = '<!DOCTYPE html>' +
				                    '' +
				                    '    <title></title>' +
				                    '    <meta charset="utf-8" />' +
				                    '    <link rel="stylesheet" type="text/css" href="resource://mdvskin/markdown-viewer.css" />' +
				                    '' +
				                    '' +
				                        marked(content.textContent) +
				                    '';

				document.title = document.body.firstChild.textContent.substr(0, 50).replace('<', '<').replace('>', '>');
			}
		}
	};
}

window.addEventListener('load', function load(event) {
	window.removeEventListener('load', load, false);
	MarkdownViewer.init();
}, false);

if (!MarkdownViewer) {

	var MarkdownViewer = {

		init: function() {
			var appcontent = document.getElementById('appcontent');
			if (appcontent)
				appcontent.addEventListener('DOMContentLoaded', this.onPageLoad, true);
		},

		onPageLoad: function(aEvent) {
			var document = aEvent.originalTarget;
			var markdownFileExtension = /\.m(arkdown|kdn?|d(o?wn)?)(#.*)?(.*)$/i;

			if (document.location.protocol !== "view-source:"
				&& markdownFileExtension.test(document.location.href)) {

				var content = document.firstChild;
				content.innerHTML = '<!DOCTYPE html>' +
				                    '' +
				                    '    <title></title>' +
				                    '    <meta charset="utf-8" />' +
				                    '    <link rel="stylesheet" type="text/css" href="resource://mdvskin/markdown-viewer.css" />' +
				                    '' +
				                    '' +
				                        marked(content.textContent) +
				                    '';

				document.title = document.body.firstChild.textContent.substr(0, 50).replace('<', '<').replace('>', '>');
			}
		}
	};
}

Fix regexp to detect markdown file (URL with GET parameters)


export const ADD: string = 'ADD'
export const SHOW: string = 'SHOW'
export const DISMISS: string = 'DISMISS'

export function add (item) {
  return {
    type: ADD,
    payload: item
  }
}

export function show (item) {
  return {
    type: SHOW,
    payload: item
  }
}

export function dismiss () {
  return {
    type: DISMISS
  }
}

export const ADD: string = 'SNACKBAR.ADD'
export const SHOW: string = 'SNACKBAR.SHOW'
export const DISMISS: string = 'SNACKBAR.DISMISS'

export function add (item) {
  return {
    type: ADD,
    payload: item
  }
}

export function show (item) {
  return {
    type: SHOW,
    payload: item
  }
}

export function dismiss () {
  return {
    type: DISMISS
  }
}

Add namespace for redux action for better scope


var RowConstants = require('./constants/row_constants.js'),
    QueryConstants = require('./constants/query_constants.js'),
    client = require('./client.js'),
    Dispatcher = require('./dispatcher.js');

var actions = {
  listQueries(callback) {
    client.list((err, res) => {
        if (!err) {
          Dispatcher.handleViewAction({
            actionType: QueryConstants.QUERY_LISTED,
            value: res
          });
          if (callback) callback();
        }
    });
  },

  deleteQuery(id) {
    client.deleteQuery(id, (err, res) => {
        if (!err) {
            actions.listQueries();
        }
    });
  },

  saveQuery(name, query) {
    client.saveQuery(name, query, (err, res) => {
        console.log(arguments);
    });
  },

  runQuery(query) {
    Dispatcher.handleViewAction({
      actionType: RowConstants.QUERY_START
    });
    client.runQuery(query, (err, res) => {
      if (!err) {
        Dispatcher.handleViewAction({
          actionType: RowConstants.QUERY_DONE,
          value: res
        });
      }
    });
  }
};

module.exports = actions;

var RowConstants = require('./constants/row_constants.js'),
    QueryConstants = require('./constants/query_constants.js'),
    client = require('./client.js'),
    Dispatcher = require('./dispatcher.js');

var actions = {
  listQueries(callback) {
    client.list((err, res) => {
        if (!err) {
          Dispatcher.handleViewAction({
            actionType: QueryConstants.QUERY_LISTED,
            value: res
          });
          if (callback) callback();
        }
    });
  },

  deleteQuery(id) {
    client.deleteQuery(id, (err, res) => {
        if (!err) {
            actions.listQueries();
        }
    });
  },

  saveQuery(name, query) {
    client.saveQuery(name, query, (err, res) => {
        console.log(arguments);
    });
  },

  runQuery(query) {
    Dispatcher.handleViewAction({
      actionType: RowConstants.QUERY_START
    });
    client.runQuery(query, (err, res) => {
      if (!err && res) {
        Dispatcher.handleViewAction({
          actionType: RowConstants.QUERY_DONE,
          value: res
        });
      }
    });
  }
};

module.exports = actions;

Test not just that there's no error, but also that there's a result


import React, { Component } from 'react';
import { Router, Route, Switch } from 'react-router-dom';
import { hot } from 'react-hot-loader';
import SignUpPage from './SignUp/SignUpPage';
import LoginPage from './Login/LoginPage';
import IndexPage from './Index';

class App extends Component {
  render() {
    return (
            <Router history = { this.props.history }>
                <Switch>
                    <Route path = '/signup' component = { SignUpPage } />
                    <Route path = '/login' component = { LoginPage } />
                    <Route path = '/' component = { IndexPage } />
                </Switch>
            </Router>
    );
  }
}

export default hot(module)(App);

import React, { Component } from 'react';
import { Router, Route, Switch } from 'react-router-dom';
import { hot } from 'react-hot-loader';
import SignUpPage from './SignUp/SignUpPage';
import LoginPage from './Login/LoginPage';
import IndexPage from './Index';
import Books from './Books';

class App extends Component {
  render() {
    return (
            <Router history = { this.props.history }>
                <Switch>
                    <Route exact path = '/signup' component = { SignUpPage } />
                    <Route exact path = '/login' component = { LoginPage } />
                    <Route exact path = '/' component = { IndexPage } />
                    <Route path = '/books' component = { Books } />
                </Switch>
            </Router>
    );
  }
}

export default hot(module)(App);

Add route for getting books


import liqen from 'liqen'
export const CALL_API = Symbol('call api')

export default store => next => action => {
  const callAPI = action[CALL_API]

  if (typeof callAPI === 'undefined') {
    return next(action)
  }

  const { ref, target, tag } = callAPI

  // Send a pending
  next({
    type: 'CREATE_ANNOTATION_PENDING',
    ref,
    target,
    tag
  })

  // Send to the server the update
  liqen()
    .annotations
    .create({
      article_id: 1,
      target: {
        type: 'TextQuoteSelector',
        prefix: target.prefix,
        exact: target.exact,
        suffix: target.suffix
      },
      tags: [tag]
    })
    .then(({id}) => next({
      type: 'CREATE_ANNOTATION_SUCCESS',
      ref,
      id,
      target,
      tag
    }))
    .catch(() => next({
      type: 'CREATE_ANNOTATION_FAILURE',
      ref,
      target,
      tag
    }))
}

import liqen from 'liqen'
import fakeLiqen from '../../server/local-liqen'
import cookies from 'cookies-js'

export const CALL_API = Symbol('call api')

export default store => next => action => {
  const callAPI = action[CALL_API]

  if (typeof callAPI === 'undefined') {
    return next(action)
  }

  const token = cookies.get('access_token')
  let core = liqen(token)

  if (process.env.NODE_ENV === 'development') {
    core = fakeLiqen(token)
  }

  const { ref, target, tag } = callAPI

  // Send a pending
  next({
    type: 'CREATE_ANNOTATION_PENDING',
    ref,
    target,
    tag
  })

  // Send to the server the update
  core
    .annotations
    .create({
      article_id: 1,
      target: {
        type: 'TextQuoteSelector',
        prefix: target.prefix,
        exact: target.exact,
        suffix: target.suffix
      },
      tags: [tag]
    })
    .then(({id}) => next({
      type: 'CREATE_ANNOTATION_SUCCESS',
      ref,
      id,
      target,
      tag
    }))
    .catch(() => next({
      type: 'CREATE_ANNOTATION_FAILURE',
      ref,
      target,
      tag
    }))
}

Set token and fake liqen in redux middleware


import Client from 'socket.io-client';

/**
 * Registers the `connect` command.
 *
 * @param vorpal The Vorpal instance.
 */
export function connectFactory(vorpal) {
  vorpal
    .command('connect <host>')
    .description('Connects to a Rotonde instance.')
    .action(function (args, callback) {
      const { host } = args;
      const socket = Client(host);
      socket.on('connect', () => {
        this.log(`Connected to ${host}`);
      });
      callback();
    });
}

import Client from 'socket.io-client';

/**
 * Registers the `connect` command.
 *
 * @param vorpal The Vorpal instance.
 */
export function connectFactory(vorpal) {
  vorpal
    .command('connect <instance>')
    .description('Connects to a Rotonde instance.')
    .action(function (args, callback) {
      const { host } = args;
      const socket = Client(host);
      socket.on('connect', () => {
        this.log(`Connected to ${host}`);
      });
      callback();
    });
}

Use "instance" instead of "host"


//query
import './query/ends_with';
import './query/includes';
import './query/is_alpha';
import './query/is_alpha_digit';
import './query/is_blank';
import './query/is_digit';
import './query/is_empty';
import './query/is_lower_case';
import './query/is_numeric';
import './query/is_string';
import './query/is_upper_case';
import './query/starts_with';

//manipulate
import './manipulate/trim';
import './manipulate/trim_left';
import './manipulate/trim_right';

//query
import './query/ends_with';
import './query/includes';
import './query/is_alpha';
import './query/is_alpha_digit';
import './query/is_blank';
import './query/is_digit';
import './query/is_empty';
import './query/is_lower_case';
import './query/is_numeric';
import './query/is_string';
import './query/is_upper_case';
import './query/matches';
import './query/starts_with';

//manipulate
import './manipulate/trim';
import './manipulate/trim_left';
import './manipulate/trim_right';

Include matches unit tests in main bundle


import {
  define,
  props,
  withComponent,
  css,
  hasNativeShadowDomSupport,
  withPreact,
  withHyperHTML,
  sanitizeBoltClasses
} from '@bolt/core';

import ClipboardJS from 'clipboard';

@define
export class BoltCopyToClipboard extends withHyperHTML() {
  static is = 'bolt-copy-to-clipboard';

  constructor() {
    super();
  }

  clickHandler(event) {
    event.preventDefault(); // Prevent the default link behavior
  }

  connecting() {
    this.copyLink = this.querySelector('.js-bolt-copy-to-clipboard__default');
    this.parentElem = this.querySelector('.js-bolt-copy-to-clipboard');

    this.copyLink.addEventListener('click', this.clickHandler);

    this.clipboardInstance = new ClipboardJS(this.copyLink); // ClipboardJS adds it's own event listener

    /*
     * [1] Adds a class onClick after successful copy and enables the first set of animations
     * [2] Waits until the first set of animations complete and adds the last class for last animations
     */
    this.clipboardInstance.on('success', () => {
      this.parentElem.classList.add('is-copied'); // [1]
      setTimeout(() => { // [2]
        this.parentElem.classList.add('is-transitioning');
      }, 2000);
    });
  }

  disconnecting() {
    this.clipboardInstance.destroy();
    this.copyLink.removeEventListener('click', this.clickHandler);
  }
}

import {
  define,
  props,
  withComponent,
  css,
  hasNativeShadowDomSupport,
  withPreact,
  BoltComponent,
  sanitizeBoltClasses
} from '@bolt/core';

import ClipboardJS from 'clipboard';

@define
export class BoltCopyToClipboard extends BoltComponent() {
  static is = 'bolt-copy-to-clipboard';

  constructor() {
    super();
  }

  clickHandler(event) {
    event.preventDefault(); // Prevent the default link behavior
  }

  connecting() {
    this.copyLink = this.querySelector('.js-bolt-copy-to-clipboard__default');
    this.parentElem = this.querySelector('.js-bolt-copy-to-clipboard');

    this.copyLink.addEventListener('click', this.clickHandler);

    this.clipboardInstance = new ClipboardJS(this.copyLink); // ClipboardJS adds it's own event listener

    /*
     * [1] Adds a class onClick after successful copy and enables the first set of animations
     * [2] Waits until the first set of animations complete and adds the last class for last animations
     */
    this.clipboardInstance.on('success', () => {
      this.parentElem.classList.add('is-copied'); // [1]
      setTimeout(() => { // [2]
        this.parentElem.classList.add('is-transitioning');
      }, 2000);
    });
  }

  disconnecting() {
    this.clipboardInstance.destroy();
    this.copyLink.removeEventListener('click', this.clickHandler);
  }
}

Copy to Clipboard -- Swap out withHyperHTML in favor of BoltComponent to fix console / render errors


(function () {
    'use strict';

    define(function () {
        return [
            {
                key: 'first',
                labelText: 'Field One'
            },
            {
                key: 'second',
                labelText: 'Field Two'
            },
            {
                key: 'third',
                labelText: 'Field Three'
            }
        ];
    });
}());

(function () {
    'use strict';

    define(function () {
        return [
            {
                key: 'first',
                labelText: 'Field One',
                sort: true
            },
            {
                key: 'second',
                labelText: 'Field Two',
                sort: function (value) {
                    return value ? value.toString().toLowerCase() : undefined;
                }
            },
            {
                key: 'third',
                labelText: 'Field Three'
            }
        ];
    });
}());

Add sort settings to fixture config.


import React, { PropTypes } from 'react'

class Todo extends React.Component {
  static propTypes = {
    todo: PropTypes.any.isRequired,
    deleteButtonVisible: PropTypes.bool,
    onCompleted: PropTypes.func.isRequired,
    onDelete: PropTypes.func.isRequired,
    onDeleteButtonVisibilityChanged: PropTypes.func.isRequired
  };

  render () {
    const textStyle = this.props.todo.completed ? {'textDecoration': 'line-through'} : {}
    let deleteButtonClassName = 'delete-button close'
    if (!this.props.deleteButtonVisible) {
      deleteButtonClassName += ' delete-button-hidden'
    }
    return (

          <input type='checkbox' checked={this.props.todo.completed ? 'checked' : ''} onChange={() => { this.props.onCompleted(this.props.todo) } } />

            {this.props.todo.text}

          <button type='button' className={deleteButtonClassName} aria-label='Close' onClick={this.handleDelete.bind(this)}>
            ×
          </button>

    )
  }

  handleMouseEnter (event) {
    this.props.onDeleteButtonVisibilityChanged(this.props.todo)
  }

  handleMouseLeave (event) {
    this.props.onDeleteButtonVisibilityChanged(undefined)
  }

  handleDelete () {
    this.props.onDelete(this.props.todo)
  }
}

export default Todo

import React, { PropTypes } from 'react'

class Todo extends React.Component {
  static propTypes = {
    todo: PropTypes.any.isRequired,
    deleteButtonVisible: PropTypes.bool,
    onCompleted: PropTypes.func.isRequired,
    onDelete: PropTypes.func.isRequired,
    onDeleteButtonVisibilityChanged: PropTypes.func.isRequired
  };

  render () {
    const textStyle = this.props.todo.completed ? {'textDecoration': 'line-through'} : {}
    let deleteButtonClassName = 'delete-button close'
    if (!this.props.deleteButtonVisible) {
      deleteButtonClassName += ' delete-button-hidden'
    }
    return (

          <input type='checkbox' checked={this.props.todo.completed ? 'checked' : ''} onChange={() => { this.props.onCompleted(this.props.todo) } } />

            {this.props.todo.text}

          <button type='button' className={deleteButtonClassName} aria-label='Close' onClick={this.props.onDelete.bind(this, this.props.todo)}>
            ×
          </button>

    )
  }
}

export default Todo

Remove extra event handler methods from todo


var mix = require('mix');
var path = require('path');
var vfs = require('vinyl-fs');

module.exports = function (options) {
    var base = path.resolve(options.base);

    var globs;
    if (typeof options.globs === 'string') {
        globs = [options.globs];
    } else {
        globs = options.globs;
    }
    globs = globs.map(function (g) { return path.join(base, g) });

    function readTree(callback) {
        var stream = vfs.src(globs, {
            base: base
        });
        var files = [];
        stream.on('data', function (file) {
            files.push(file);
        });
        stream.on('end', function () {
            callback(new mix.Tree(files.map(fileToNode)));
        }.bind(this));
    }

    function fileToNode(file) {
        return {
            name: path.relative(base, file.path),
            base: base,
            data: file.contents,
            mode: file.stat.mode
        };
    }

    return new mix.Stream(function (sink) {
        vfs.watch(globs, {}, pushNext);
        pushNext();

        function pushNext() {
            readTree(sink.push);
        }
    });
};

var mix = require('mix');
var path = require('path');
var vfs = require('vinyl-fs');

module.exports = function (options) {
    var base = path.resolve(options.base);

    var globs;
    if (typeof options.globs === 'string') {
        globs = [options.globs];
    } else {
        globs = options.globs;
    }
    globs = globs.map(function (g) { return path.join(base, g) });

    function fileToNode(file) {
        return {
            name: path.relative(base, file.path),
            base: base,
            data: file.contents,
            mode: file.stat.mode
        };
    }

    return new mix.Stream(function (sink) {
        var watcher = new mix.Watcher();
        watcher.on('change', pushNext);

        pushNext();

        function pushNext() {
            readTree(sink.push);
        }

        function readTree(callback) {
            var stream = vfs.src(globs, {
                base: base
            });
            var files = [];
            stream.on('data', function (file) {
                watcher.add(file.path);
                files.push(file);
            });
            stream.on('end', function () {
                callback(new mix.Tree(files.map(fileToNode)));
            }.bind(this));
        }

        return function dispose() {
            watcher.dispose();
        };
    });
};

Migrate the files plugin to use Watcher


'use strict';

var util = require('util');
var common = require('../../modules/common-functions');

module.exports = {
  'Start page': common.startPage,

  'Categories of law (Your problem)': function(client) {
    client
      .assert.urlContains('/problem')
      .assert.containsText('h1', 'What do you need help with?')
      .click('input[name="categories"][value="clinneg"]')
      .assert.attributeEquals('input[name="categories"][value="clinneg"]', 'checked', 'true')
      .submitForm('form')
    ;
  },

  'Face-to-face page': function(client) {
    client
      .waitForElementVisible('.legal-adviser-search', 5000)
      .assert.urlContains('/face-to-face')
      .assert.containsText('h1', 'You may be able to get free advice from a legal adviser')
    ;

    client.end();
  }

};

'use strict';

var common = require('../../modules/common-functions');

module.exports = {
  'Start page': common.startPage,

  'Categories of law (Your problem)': function(client) {
    client
      .assert.urlContains('/problem')
      .assert.containsText('h1', 'What do you need help with?')
      .click('input[name="categories"][value="clinneg"]')
      .assert.attributeEquals('input[name="categories"][value="clinneg"]', 'checked', 'true')
      .submitForm('form')
    ;
  },

  'Face-to-face page': function(client) {
    client
      .waitForElementVisible('.legal-adviser-search', 5000)
      .assert.urlContains('/face-to-face')
      .assert.containsText('h1', 'You may be able to get free advice from a legal adviser')
    ;
  },

  'Find legal adviser search': function(client) {
    client
      .setValue('input[name="postcode"]', 'w22dd')
      .submitForm('form')
      .assert.urlContains('/face-to-face')
      .waitForElementVisible('.search-results-container', 5000)
      .assert.containsText('.results-location', 'W2 2DD')
    ;

    client.end();
  }

};

Add simple 'Find legal adviser' search test


module.exports = {
  /*
  ** Headers of the page
  */
  head: {
    title: 'CSS Frameworks',
    titleTemplate: '%s - CSS Frameworks',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: 'Compare CSS frameworks.' }
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
      { rel: 'stylesheet', href: 'https://fonts.googleapis.com/css?family=Lato' }
    ]
  },
  css: [{
    src: '~assets/css/main.scss',
    lang: 'scss'
  }],
  /*
  ** Customize the progress-bar color
  */
  loading: { color: '#685143' },
  /*
  ** Build configurationwebpack
  */
  build: {
    /*
    ** Run ESLINT on save
    */
    extend (config, ctx) {
      if (ctx.dev && ctx.isClient) {
        config.module.rules.push({
          enforce: 'pre',
          test: /\.(js|vue)$/,
          loader: 'eslint-loader',
          exclude: /(node_modules)/
        })
      }
    },
    vendor: [
      '~/assets/js/shared.js'
    ]
  }
}

const path = require('path')

const dataPath = path.join(__dirname, process.env.repoDataPath || 'data.json')
const frameworks = require(dataPath)

module.exports = {
  /*
  ** Headers of the page
  */
  head: {
    title: 'CSS Frameworks',
    titleTemplate: '%s - CSS Frameworks',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: 'Compare CSS frameworks.' }
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
      { rel: 'stylesheet', href: 'https://fonts.googleapis.com/css?family=Lato' }
    ]
  },
  css: [{
    src: '~assets/css/main.scss',
    lang: 'scss'
  }],
  /*
  ** Customize the progress-bar color
  */
  loading: { color: '#685143' },
  /*
  ** Build configurationwebpack
  */
  build: {
    /*
    ** Run ESLINT on save
    */
    extend (config, ctx) {
      if (ctx.dev && ctx.isClient) {
        config.module.rules.push({
          enforce: 'pre',
          test: /\.(js|vue)$/,
          loader: 'eslint-loader',
          exclude: /(node_modules)/
        })
      }
    }
  },
  env: {
    frameworks
  }
}

Change the way to load json


/**
 * Copy this file to `local/linked-packages.js` and uncomment any modules as desired to link them
 * for local development.
 *
 * These will be resolved as aliases in the webpack config. Note that you will need to ensure that
 * the packages are installed (via `yarn install`) in each respective folder
 *
 * It's recommended to use the `yarn startLocal` script to run the app, as it will automatically
 * start the webpack development server for the `viz` repo when needed.
 *
 * You may add as other modules to this list as well.
 */
var packages = {
  // '@tidepool/viz': process.env.TIDEPOOL_DOCKER_VIZ_DIR || '../viz',
  // 'tideline': process.env.TIDEPOOL_DOCKER_TIDELINE_DIR || '../tideline',
  // 'tidepool-platform-client': process.env.TIDEPOOL_DOCKER_PLATFORM_CLIENT_DIR || '../platform-client',
};

module.exports = {
  list: () => console.log(Object.keys(packages).join(',')),
  packages: packages,
}

/**
 * Copy this file to `config/linked-packages.js` and uncomment any modules as desired to link them
 * for local development.
 *
 * These will be resolved as aliases in the webpack config. Note that you will need to ensure that
 * the packages are installed (via `yarn install`) in each respective folder
 *
 * It's recommended to use the `yarn startLocal` script to run the app, as it will automatically
 * start the webpack development server for the `viz` repo when needed.
 *
 * You may add as other modules to this list as well.
 */
var packages = {
  // '@tidepool/viz': process.env.TIDEPOOL_DOCKER_VIZ_DIR || '../viz',
  // 'tideline': process.env.TIDEPOOL_DOCKER_TIDELINE_DIR || '../tideline',
  // 'tidepool-platform-client': process.env.TIDEPOOL_DOCKER_PLATFORM_CLIENT_DIR || '../platform-client',
};

module.exports = {
  list: () => console.log(Object.keys(packages).join(',')),
  packages: packages,
}

Fix incorrect path in linked packages instructions


var _ = require('lodash')
var webpackConfig = require('./webpack.config')

var config = {

  frameworks: ['mocha', 'effroi'],

  preprocessors: {
    'tests/index.js': ['webpack', 'sourcemap']
  },

  files: [
    'tests/index.js'
  ],

  // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
  reporters: ['progress', 'coverage'],

  // this watcher watches when bundled files are updated
  autoWatch: true,

  webpack: _.extend(webpackConfig, {
    entry: undefined,
    // this watcher watches when source files are updated
    watch: true,
    module: _.extend(webpackConfig.module, {
      postLoaders: [{
        test: /\.js/,
        exclude: /(test|node_modules)/,
        loader: 'istanbul-instrumenter'
      }]
    })
  }),

  webpackServer: {
    noInfo: true
  },

  client: {
    useIframe: true,
    captureConsole: true,
    mocha: {
      ui: 'qunit'
    }
  },

  browsers: [process.env.TRAVIS ? 'Firefox' : 'Chrome'],
  browserNoActivityTimeout: 30000,

  coverageReporter: {
    reporters: [
      {type: 'html', dir: 'coverage/'},
      {type: 'text-summary'}
    ]
  }
}

module.exports = function (c) {
  c.set(config)
}

module.exports.config = config

var _ = require('lodash')
var webpackConfig = require('./webpack.config')

var config = {

  frameworks: ['mocha', 'effroi'],

  preprocessors: {
    'tests/index.js': ['webpack', 'sourcemap']
  },

  files: [
    'tests/index.js'
  ],

  // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
  reporters: ['progress', 'coverage'],

  // this watcher watches when bundled files are updated
  autoWatch: true,

  webpack: _.extend(webpackConfig, {
    entry: undefined,
    // this watcher watches when source files are updated
    watch: true,
    devtool: 'inline-source-map',
    module: _.extend(webpackConfig.module, {
      postLoaders: [{
        test: /\.js/,
        exclude: /(test|node_modules)/,
        loader: 'istanbul-instrumenter'
      }]
    })
  }),

  webpackServer: {
    noInfo: true
  },

  client: {
    useIframe: true,
    captureConsole: true,
    mocha: {
      ui: 'qunit'
    }
  },

  browsers: [process.env.TRAVIS ? 'Firefox' : 'Chrome'],
  browserNoActivityTimeout: 30000,

  coverageReporter: {
    reporters: [
      {type: 'html', dir: 'coverage/'},
      {type: 'text-summary'}
    ]
  }
}

module.exports = function (c) {
  c.set(config)
}

module.exports.config = config

Bring back source maps in tests


module.exports = function(config) {
  config.set({
    frameworks: ['mocha', 'chai'],
    files: [
      'bower_components/versal-component-runtime/dist/runtime.min.js',
      'index.html',
      'test/*_spec.js',
      {pattern: 'index.js', included: false},
      {pattern: 'test/test_gadget.html', included: false}
    ],
    reporters: ['progress'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO, // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    autoWatch: true,
    browsers: ['Firefox']
  });
};

module.exports = function(config) {
  config.set({
    frameworks: ['mocha', 'chai'],
    files: [
      'bower_components/versal-component-runtime/dist/runtime.min.js',
      'index.html',
      'test/*_spec.js',
      {pattern: 'index.js', included: false},
      {pattern: 'test/test_gadget.html', included: false}
    ],
    reporters: ['dots'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO, // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    autoWatch: true,
    browsers: ['Firefox']
  });
};

Use dots reporter instead of progress


import React from 'react'
import PropTypes from 'prop-types'
import CircularProgress from 'material-ui/CircularProgress'

class FullScreenProgress extends React.Component {
  render () {
    const {containerStyle, progressStyle} = this.props

    return (
      <div
        style={containerStyle}>
        <CircularProgress
          {...progressStyle} />
      </div>)
  }
}

FullScreenProgress.propTypes = {
  containerStyle: PropTypes.object,
  progressStyle: PropTypes.object
}

FullScreenProgress.defaultProps = {
  containerStyle: {
    width: '100vw',
    height: '100vh',
    boxSizing: 'border-box',
    display: 'flex',
    justifyContent: 'center',
    alignItems: 'center'
  },

  progressStyle: {
    size: 60,
    thickness: 7
  }
}

export default FullScreenProgress

import React from 'react'
import PropTypes from 'prop-types'
import CircularProgress from 'material-ui/CircularProgress'

class FullScreenProgress extends React.Component {
  render () {
    const {containerStyle, progressStyle} = this.props

    return (
      <div
        style={containerStyle}>
        <CircularProgress
          {...progressStyle} />
      </div>)
  }
}

FullScreenProgress.propTypes = {
  containerStyle: PropTypes.object,
  progressStyle: PropTypes.object
}

FullScreenProgress.defaultProps = {
  containerStyle: {
    width: '100vw',
    height: '100vh',
    maxWidth: '100%',
    boxSizing: 'border-box',
    display: 'flex',
    justifyContent: 'center',
    alignItems: 'center'
  },

  progressStyle: {
    size: 60,
    thickness: 7
  }
}

export default FullScreenProgress

Add a max width to 100vw component to avoid horizontal scroll.


'use strict';

const express = require('express');
const BodyParser  = require('body-parser');
const Handlers = require('../util').Handlers;
const Err = require('../util').Err;

exports.attach = function (app, storage) {
  app.get('/v1/health', require('./health'));
  app.use('/v1/health', Handlers.allowed('GET'));
  app.get('/v1/token/default', require('./token')(storage));
  app.use('/v1/token', Handlers.allowed('GET'));

  // Backend endpoints
  app.get('/v1/secret/:token/:path(*)', require('./secret')(storage));
  app.use('/v1/secret', Handlers.allowed('GET'));
  app.get('/v1/cubbyhole/:token/:path', require('./cubbyhole')(storage));
  app.use('/v1/cubbyhole', Handlers.allowed('GET'));
  app.get('/v1/credential/:token/:mount/:role', require('./credential')(storage));
  app.use('/v1/credential', Handlers.allowed('GET'));

  app.use(BodyParser.json());

  app.post('/v1/transit/:token/decrypt', require('./transit')(storage));
  app.use('/v1/transit', Handlers.allowed('POST'));

  app.use(Err);
};

'use strict';

const express = require('express');
const BodyParser  = require('body-parser');
const Handlers = require('../util').Handlers;
const Err = require('../util').Err;

exports.attach = function (app, storage) {
  app.get('/v1/health', require('./health'));
  app.get('/v1/token/default', require('./token')(storage));

  // Backend endpoints
  app.get('/v1/secret/:token/:path(*)', require('./secret')(storage));
  app.get('/v1/cubbyhole/:token/:path', require('./cubbyhole')(storage));
  app.get('/v1/credential/:token/:mount/:role', require('./credential')(storage));

  app.use(BodyParser.json());
  app.post('/v1/transit/:token/decrypt', require('./transit')(storage));
  app.use('/v1/transit', Handlers.allowed('POST'));

  app.use(Handlers.allowed('GET'));
  app.use(Err);
};

Clean up route handling so it's more readable.


/*global app, me, client*/
"use strict";

var _ = require('underscore');
var HumanModel = require('human-model');
var logger = require('andlog');

module.exports = HumanModel.define({
    type: 'call',
    initialize: function (attrs) {
        this.contact.onCall = true;
    },
    session: {
        contact: 'object',
        jingleSession: 'object',
        state: ['string', true, 'inactive'],
        multiUser: ['boolean', true, false]
    },
    end: function (reasonForEnding) {
        var reason = reasonForEnding || 'success';
        this.contact.onCall = false;
        if (this.jingleSession) {
            this.jingleSession.end({
                condition: reason
            });
        }
        this.collection.remove(this);
    }
});

/*global app, me, client*/
"use strict";

var _ = require('underscore');
var HumanModel = require('human-model');
var logger = require('andlog');

module.exports = HumanModel.define({
    type: 'call',
    initialize: function (attrs) {
        this.contact.onCall = true;
    },
    session: {
        contact: 'object',
        jingleSession: 'object',
        state: ['string', true, 'inactive'],
        multiUser: ['boolean', true, false]
    },
    end: function (reasonForEnding) {
        var reason = reasonForEnding || 'success';
        this.contact.onCall = false;
        if (this.jingleSession) {
            this.jingleSession.end(reasonForEnding);
        }
        this.collection.remove(this);
    }
});

Fix excess reason condition wrapping


module.exports = function (context) {
  return {
    $: function (expression) {
      return expression
    },

    extend: {
      extendables: {},
      add: function (selector, cssString) {
        this.extendables[selector] = {
          css: cssString,
          selectors: []
        }
      },
      that: function (extendable, selector) {
        if(!this.extendables[extendable]) return false
        this.extendables[extendable]['selectors'].push(selector)
        context.onDone.push(this.extendMaker(extendable))
      },
      extendMaker: function (extendable) {
        var self = this
        return function () {
          var returnVal
          if(!self.extendables[extendable]) return ''
          returnVal = [self.extendables[extendable].selectors].concat(self.extendables[extendable].selectors).join(', ') + ' {' + self.extendables[extendable].css + '}\n'
          self.extendables[extendable] = false
          return returnVal
        }
      }
    }
  }
}

module.exports = function (context) {
  return {
    $: function (expression) {
      return expression
    },

    extend: {
      extendables: {},
      add: function (selector, cssString) {
        this.extendables[selector] = {
          css: cssString,
          selectors: []
        }
      },
      that: function (extendable, selector) {
        if(!this.extendables[extendable]) return false
        this.extendables[extendable]['selectors'].push(selector)
        context.onDone.push(this.extendMaker(extendable))
      },
      extendMaker: function (extendable) {
        var self = this
        return function () {
          var returnVal
          if(!self.extendables[extendable]) return ''
          returnVal = [extendable].concat(self.extendables[extendable].selectors).join(', ') + ' {' + self.extendables[extendable].css + '}\n'
          self.extendables[extendable] = false
          return returnVal
        }
      }
    }
  }
}

Fix a bug in extend


// Download the Node helper library from twilio.com/docs/node/install
// These vars are your accountSid and authToken from twilio.com/user/account
var accountSid = 'ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
var authToken = "your_auth_token";
var client = require('twilio')(accountSid, authToken);

client.calls.create({
    url: "http://demo.twilio.com/docs/voice.xml",
    to: "+14155551212",
    from: "+18668675309",
    statusCallback: "https://www.myapp.com/events",
    statusCallbackMethod: "POST",
    statusCallbackEvent: ["initiated", "ringing", "answered", "completed"],
    method: "GET"
}, function(err, call) {
    process.stdout.write(call.sid);
});

// Download the Node helper library from twilio.com/docs/node/install
// These vars are your accountSid and authToken from twilio.com/user/account
var accountSid = 'ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
var authToken = "your_auth_token";
var client = require('twilio')(accountSid, authToken);

client.calls.create({
    url: "http://demo.twilio.com/docs/voice.xml",
    to: "+14155551212",
    from: "+18668675309",
    statusCallback: "https://www.myapp.com/events",
    statusCallbackMethod: "POST",
    statusCallbackEvent: "initiated ringing answered completed",
    method: "GET"
}, function(err, call) {
    process.stdout.write(call.sid);
});

Update based on feedback from @nash-md


import React, { Component } from 'react';
import { Link } from 'react-router';
import ChannelDropdown from '../../containers/ChannelDropdown/ChannelDropdown';

/* component styles */
import { styles } from './styles.scss';

export class Header extends Component {
  render() {
    return (
      <header className={`${styles}`}>

              <Link to="/" className="logo">

Twitch Chat Visualizer

              </Link>

              <ChannelDropdown />

    );
  }
}

import React, { Component } from 'react';
import { Link } from 'react-router';
import ChannelDropdown from '../../containers/ChannelDropdown/ChannelDropdown';

/* component styles */
import { styles } from './styles.scss';

export class Header extends Component {
  render() {
    return (
      <header className={`${styles}`}>

              <ChannelDropdown />

    );
  }
}

Move logo link out of header component and into child, simplify markup


import value from 'object-path';

export const purender = ({raw}, ...values) => ({
  [Symbol.toStringTag]: 'purender',

  shouldComponentUpdate(...args) {
    const watchlist = String.raw({raw}, ...values).split(' ');

    return watchlist.reduce((m, n) => {
      const [props, state] = args,
            next = {props, state},
            [type, ...path] = n.split('.');

      return m || value.get(this[type], path) !== value.get(next[type], path);
    }, false);
  }
});

import value from 'object-path';

export const purender = ({raw}, ...values) => ({
  [Symbol.toStringTag]: 'purender',

  shouldComponentUpdate(...args) {
    const watchlist = String.raw({raw}, ...values).split(/\s+/);

    return watchlist.reduce((m, n) => {
      const [props, state] = args,
            next = {props, state},
            [type, ...path] = n.split('.');

      return m || value.get(this[type], path) !== value.get(next[type], path);
    }, false);
  }
});

Remove all white space characters in `watchlist`


const Discord = require('discord.js');
let embed = new Discord.RichEmbed();
exports.run = (client, message) => {
  message.delete();
  let args = message.content.split(' ').splice(1).join(' ');
  const res = client.users.filter(u => u.discriminator === `${args}`).map(u => u.username);
  if(res.length === 0) {
    embed.setAuthor(`No users found with ${args}`)
    .setColor("#53A6F3")
    .setTimestamp();
    message.channel.send({ embed })
  } else {
    embed.setAuthor(`Users Found With ${args}`)
    .setDescription(`${res.join('\n')}`)
    .setColor("#53A6F3")
    .setTimestamp();
    message.channel.send({ embed })
  }
}

const Discord = require('discord.js');
let embed = new Discord.RichEmbed();
exports.run = (client, message) => {
  message.delete();
  let args = message.content.split(" ").splice(1).join(" ");
  const res = client.users.filter(u => u.discriminator === `${args}`).map(u => u.username);
  if(res.length === 0) {
    embed.setAuthor(`No users found with ${args}`)
    .setColor("#53A6F3")
    .setTimestamp();
    message.channel.send({ embed })
  } else {
    embed.setAuthor(`Users Found With ${args}`)
    .setDescription(`${res.join('\n')}`)
    .setColor("#53A6F3")
    .setTimestamp();
    message.channel.send({ embed })
  }
}

Fix string - Must used double quote


import data from '../util/data';

export default function (opts) {
  const { attribute } = opts;

  return function (name, oldValue, newValue) {
    const propertyName = data(this, 'attributeLinks')[name];

    if (propertyName) {
      const propertyData = data(this, `api/property/${propertyName}`);
      if (!propertyData.settingProperty) {
        const propOpts = this.constructor.properties[propertyName];
        this[propertyName] = propOpts.deserialize ? propOpts.deserialize(newValue) : newValue;
      }
    }

    if (attribute) {
      attribute(this, {
        name: name,
        newValue: newValue === null ? undefined : newValue,
        oldValue: oldValue === null ? undefined : oldValue
      });
    }
  };
}

import data from '../util/data';

export default function (opts) {
  const { attribute } = opts;

  return function (name, oldValue, newValue) {
    const propertyName = data(this, 'attributeLinks')[name];

    if (propertyName) {
      const propertyData = data(this, `api/property/${propertyName}`);
      if (!propertyData.settingProperty) {
        const propOpts = this.constructor.properties[propertyName];
        this[propertyName] = newValue !== null && propOpts.deserialize ? propOpts.deserialize(newValue) : newValue;
      }
    }

    if (attribute) {
      attribute(this, {
        name: name,
        newValue: newValue === null ? undefined : newValue,
        oldValue: oldValue === null ? undefined : oldValue
      });
    }
  };
}

Fix issue where value might be null in which case it's safe to just set directly on the property.


const promise = require("bluebird");
const options = {
  // Initialization Options
  promiseLib: promise, // use bluebird as promise library
  capSQL: true, // when building SQL queries dynamically, capitalize SQL keywords
};
const pgp = require("pg-promise")(options);
const connectionString = process.env.DATABASE_URL;
const parse = require("pg-connection-string").parse;
var db;
checkConnection();

function checkConnection() {
  let config;
  try {
    config = parse(connectionString);
    if (process.env.NODE_ENV === "test" || config.host === "localhost") {
      config.ssl = false;
    } else {
      config.ssl = true;
    }
    console.log(config);
    console.log()
  } catch (e) {
    console.error("# Error parsing DATABASE_URL environment variable");
  }

  db = pgp(config);
}

const promise = require("bluebird");
const { SUPPORTED_LANGUAGES } = require("./../constants.js");
const { find } = require("lodash");
const options = {
  // Initialization Options
  promiseLib: promise, // use bluebird as promise library
  capSQL: true, // when building SQL queries dynamically, capitalize SQL keywords
};
const pgp = require("pg-promise")(options);
const connectionString = process.env.DATABASE_URL;
const parse = require("pg-connection-string").parse;
var db;
checkConnection();
getLocalizationData();

function checkConnection() {
  let config;
  try {
    config = parse(connectionString);
    if (process.env.NODE_ENV === "test" || config.host === "localhost") {
      config.ssl = false;
    } else {
      config.ssl = true;
    }
  } catch (e) {
    console.error("# Error parsing DATABASE_URL environment variable");
  }

  db = pgp(config);
}

function getLocalizationData() {
  db.any(`SELECT * FROM localized_texts`)
    .then(function(data) {
      data.forEach(data => {

      });
    })
    .catch(function(error) {
      console.log(error);
    });
}

Prepare fetching of localization data


/*global require,__dirname*/
var connect = require('connect');
connect.createServer(
 connect.static(__dirname)
).listen(8080);

/*global require,__dirname*/
/*jshint es3:false*/
var connect = require('connect');
connect.createServer(
 connect.static(__dirname)
).listen(8080);

Fix warning with new JSHint version.


function viewLogIn() {

  var login_form = templates.login;
  $('.section').html(login_form);

  $('.form-login').submit(function (e) {
    e.preventDefault();
    var email = $('.fl-user-and-access__email').val();
    var password = $('.fl-user-and-access__password').val();
    postLogin(email, password);
  });
}

function viewLogIn() {

    var login_form = templates.login;
    $('.section').html(login_form);

    $('.form-login').submit(function (e) {
        e.preventDefault();
        loadingBtn($('#login'));
        var email = $('.fl-user-and-access__email').val();
        var password = $('.fl-user-and-access__password').val();
        postLogin(email, password);
    });
}

Add loading icon to login screen


import React from 'react';
import { forEach } from 'lodash'

import ControllButton from '../controllButton';
import { getUniqueId } from '../../lib/utils';

const PreheatPresets = ({ presets, onClick }) => (

    {Object.keys(presets).map(preset => {
      return (

        <ControllButton onClick={() => { onClick(presets[preset].value); }}>{preset}</ControllButton>

);
    })}

);

export default PreheatPresets;

import React from 'react';
import { forEach } from 'lodash'

import ControllButton from '../controllButton';
import { getUniqueId } from '../../lib/utils';

const PreheatPresets = ({ presets, onClick }) => (

    {Object.keys(presets).map(preset => {
      return (

        <ControllButton onClick={() => { onClick(presets[preset].value); }}>{presets[preset].name}</ControllButton>

);
    })}

);

export default PreheatPresets;

Fix wrong name being used at temperature preset component


import createElement from 'virtual-dom/create-element'
import diff from 'virtual-dom/diff'
import patch from 'virtual-dom/patch'
import VText from 'virtual-dom/vnode/vtext'
import { getScheduler } from './scheduler-assignment'

export default class FullObservationWidget {
  constructor (observation) {
    this.type = 'Widget'
    this.observation = observation
    this.domNode = null
    this.vnode = null
    this.observationSubscription = null
  }

  init () {
    this.trackObservation()
    this.vnode = this.getObservationValue()
    this.domNode = createElement(this.vnode)
    return this.domNode
  }

  update (previous, domNode) {
    this.domNode = domNode
    this.trackObservation()
    this.vnode = this.getObservationValue()
    patch(domNode, diff(previous.vnode, this.vnode))
    previous.destroy()
  }

  destroy () {
    this.observationSubscription.dispose()
    this.observationSubscription = null
    this.domNode = null
    this.vnode = null
  }

  getObservationValue () {
    let value = this.observation.getValue()
    if (typeof value === 'string') {
      return new VText(value)
    } else {
      return value
    }
  }

  trackObservation () {
    this.observationSubscription = this.observation.onDidChangeValue((newVnode) => {
      getScheduler().updateDocument(() => {
        if (this.domNode) {
          patch(this.domNode, diff(this.vnode, newVnode))
          this.vnode = newVnode
        }
      })
    })
  }
}

import createElement from 'virtual-dom/create-element'
import diff from 'virtual-dom/diff'
import patch from 'virtual-dom/patch'
import VText from 'virtual-dom/vnode/vtext'
import { getScheduler } from './scheduler-assignment'

export default class FullObservationWidget {
  constructor (observation) {
    this.type = 'Widget'
    this.observation = observation
    this.domNode = null
    this.vnode = null
    this.observationSubscription = null
  }

  init () {
    this.trackObservation()
    this.vnode = this.getObservationValue()
    this.domNode = createElement(this.vnode)
    return this.domNode
  }

  update (previous, domNode) {
    this.domNode = domNode
    this.trackObservation()
    this.vnode = this.getObservationValue()
    patch(domNode, diff(previous.vnode, this.vnode))
    previous.destroy()
  }

  destroy () {
    this.observationSubscription.dispose()
    this.observationSubscription = null
    this.domNode = null
    this.vnode = null
  }

  getObservationValue () {
    let value = this.observation.getValue()
    if (typeof value === 'string') {
      return new VText(value)
    } else {
      return value
    }
  }

  trackObservation () {
    this.observationSubscription = this.observation.onDidChangeValue(() => {
      getScheduler().updateDocument(() => {
        if (this.domNode) {
          let newVnode = this.getObservationValue()
          patch(this.domNode, diff(this.vnode, newVnode))
          this.vnode = newVnode
        }
      })
    })
  }
}

Convert strings to VText nodes


import Registry from './registry';

const initInView = () => {

    const getElements = (selector) =>
        [].slice.call(document.querySelectorAll(selector));

    const inViewport = (element, offset = 0) => {
        let bounds = element.getBoundingClientRect();
        return bounds.bottom > offset
            && bounds.right > offset
            && window.innerWidth - bounds.left > offset
            && window.innerHeight - bounds.top > offset;
    };

    const throttle = (fn, threshold, context) => {
        let prev = 0;
        return () => {
            let now  = new Date().getTime();
            if (now - prev > threshold) {
                fn.call(context);
                prev = now;
            }
        };
    };

    let catalog = { history: [] };

    let inView = (selector) => {
        let elements = getElements(selector);
        if (catalog.history.indexOf(selector) > -1) {
            catalog[selector].elements = elements;
        } else {
            catalog[selector] = new Registry(elements);
            catalog.history.push(selector);
        }
        return catalog[selector];
    };

    inView.is = inViewport;

    return inView;

};

export default initInView();

import Registry from './registry';

const inView = () => {

    const getElements = (selector) =>
        [].slice.call(document.querySelectorAll(selector));

    const inViewport = (element, offset = 0) => {
        let bounds = element.getBoundingClientRect();
        return bounds.bottom > offset
            && bounds.right > offset
            && window.innerWidth - bounds.left > offset
            && window.innerHeight - bounds.top > offset;
    };

    const throttle = (fn, threshold, context) => {
        let prev = 0;
        return () => {
            let now  = new Date().getTime();
            if (now - prev > threshold) {
                fn.call(context);
                prev = now;
            }
        };
    };

    let catalog = { history: [] };

    let control = (selector) => {
        let elements = getElements(selector);
        if (catalog.history.indexOf(selector) > -1) {
            catalog[selector].elements = elements;
        } else {
            catalog[selector] = new Registry(elements);
            catalog.history.push(selector);
        }
        return catalog[selector];
    };

    control.is = inViewport;

    return control;

};

export default inView();

Rename returned interface to control and init to inView


const DrawCard = require('../../drawcard.js');

class AdeptOfTheWaves extends DrawCard {
    setupCardAbilities() {
        this.action({
            title: 'Grant Covert to a character',
            target: {
                cardType: 'character',
                cardCondition: card => card.location === 'play area'
            },
            handler: context => {
                this.game.addMessage('{0} uses {1} to grant Covet during Water conflicts to {2}', this.controller, this, context.target);
                this.untilEndOfPhase(ability => ({
                    match: context.target,
                    condition: () => this.game.currentConflict && this.game.currentConflict.conflictRing === 'water',
                    effect: ability.effects.addKeyword('covert')
                }));
            }
        });
    }
}

AdeptOfTheWaves.id = 'adept-of-the-waves';

module.exports = AdeptOfTheWaves;

const DrawCard = require('../../drawcard.js');

class AdeptOfTheWaves extends DrawCard {
    setupCardAbilities() {
        this.action({
            title: 'Grant Covert to a character',
            target: {
                cardType: 'character',
                cardCondition: card => card.location === 'play area'
            },
            handler: context => {
                this.game.addMessage('{0} uses {1} to grant Covert during Water conflicts to {2}', this.controller, this, context.target);
                this.untilEndOfPhase(ability => ({
                    match: context.target,
                    condition: () => this.game.currentConflict && this.game.currentConflict.conflictRing === 'water',
                    effect: ability.effects.addKeyword('covert')
                }));
            }
        });
    }
}

AdeptOfTheWaves.id = 'adept-of-the-waves';

module.exports = AdeptOfTheWaves;

Fix typo on Adept of the Waves


(function($) {
    $.fn.geoLocation = function(callback) {
        if (callback && typeof(callback) === 'function') {
            var options = {
              enableHighAccuracy: true,
              timeout: 5000,
              maximumAge: 0 //Always request the current location
            };

            function success(position) {
                callback({
                    success: true,
                    position: position.coords
                })
            }

            function error(err) {
                callback({
                    success: false,
                    error: err
                })
            };

            if ('geolocation' in navigator) { //Verify basic support
                navigator.geolocation.getCurrentPosition(success, error, options);
            } else {
                error({code: 999, message: 'Geopositioning is not supported by the current device'})
            }
        } else {
            console.log('No callback function provided')
        }
    }
}(jQuery));

(function($) {
  $.fn.geoLocation = function(callback, data) {
    if (callback && typeof(callback) === 'function') {
      var options = {
        enableHighAccuracy: true,
        timeout: 5000,
        maximumAge: 0 //Always request the current location
      }

      function success(position) {
        callback({
          success: true,
          position: position.coords
        }, data)
      }

      function error(err) {
        callback({
          success: false,
          error: err
        }, data)
      }

      if ('geolocation' in navigator) { //Verify basic support
        navigator.geolocation.getCurrentPosition(success, error, options);
      } else {
        error({
          success: false,
          error: 'Geopositioning is not supported by the current device'
        }, data)
      }
    } else {
      console.log('No callback function provided')
    }
  }
}(jQuery));

Add option to pass data back to callback


(function () {
    'use strict';

    angular
        .module('dpMap')
        .factory('measure', measureFactory);

    measureFactory.$inject = ['$document', 'L', 'MEASURE_CONFIG'];

    function measureFactory ($document, L, MEASURE_CONFIG) {
        return {
            initialize: initialize
        };

        function initialize (leafletMap) {
            var measureControl = new L.Control.Measure(MEASURE_CONFIG),
                leafletMeasureHtml;

            measureControl.addTo(leafletMap);

            leafletMeasureHtml = measureControl.getContainer();

            //Add a class to leaflet-measure control
            leafletMeasureHtml.className += ' s-leaflet-measure';

            $document[0].querySelector('.js-leaflet-measure').appendChild(leafletMeasureHtml);
        }
    }
})();

(function () {
    'use strict';

    angular
        .module('dpMap')
        .factory('measure', measureFactory);

    measureFactory.$inject = ['$document', '$rootScope', 'L', 'MEASURE_CONFIG', 'store', 'ACTIONS'];

    function measureFactory ($document, $rootScope, L, MEASURE_CONFIG, store, ACTIONS) {
        return {
            initialize: initialize
        };

        function initialize (leafletMap) {
            var measureControl = new L.Control.Measure(MEASURE_CONFIG),
                leafletMeasureHtml;

            measureControl.addTo(leafletMap);

            leafletMeasureHtml = measureControl.getContainer();
            leafletMeasureHtml.className += ' s-leaflet-measure';
            $document[0].querySelector('.js-leaflet-measure').appendChild(leafletMeasureHtml);

            leafletMap.on('measurestart', function () {
                $rootScope.$applyAsync(function () {
                    store.dispatch({
                        type: ACTIONS.HIDE_ACTIVE_OVERLAYS
                    });
                });
            });
        }
    }
})();

Hide the active overlays on 'measurestart'.


/**
 * Initialization code for the home page.
 */

'use strict';

var $ = require('jquery');
var $osf = require('js/osfHelpers');
var quickSearchProject = require('js/quickProjectSearchPlugin');
var newAndNoteworthy = require('js/newAndNoteworthyPlugin');
var meetingsAndConferences = require('js/meetingsAndConferencesPlugin');
var m = require('mithril');

$(document).ready(function(){
    m.mount(document.getElementById('addQuickProjectSearchWrap'), m.component(quickSearchProject, {}));
    m.mount(document.getElementById('newAndNoteworthyWrap'), m.component(newAndNoteworthy, {}));
    m.mount(document.getElementById('hostingAMeetingWrap'), m.component(meetingsAndConferences, {}));
});

/**
 * Initialization code for the home page.
 */

'use strict';

var $ = require('jquery');
var $osf = require('js/osfHelpers');
var quickSearchProject = require('js/quickProjectSearchPlugin');
var newAndNoteworthy = require('js/newAndNoteworthyPlugin');
var meetingsAndConferences = require('js/meetingsAndConferencesPlugin');
var m = require('mithril');

$(document).ready(function(){
    m.mount(document.getElementById('newAndNoteworthyWrap'), m.component(newAndNoteworthy, {}));
    m.mount(document.getElementById('hostingAMeetingWrap'), m.component(meetingsAndConferences, {}));
    m.mount(document.getElementById('addQuickProjectSearchWrap'), m.component(quickSearchProject, {}));

});

Reorder mounted elements. QuickSearch needs to load last.


const appInsightsKey = process.env.FORTIS_SERVICES_APPINSIGHTS_KEY;

let client;

function setup() {
  if (appInsightsKey) {
    const appInsights = require('applicationinsights');
    appInsights.setup(appInsightsKey);
    appInsights.start();
    client = appInsights.getClient(appInsightsKey);
  }
}

function trackDependency(promiseFunc, dependencyName, callName) {
  if (!client) {
    console.log('Application insights not enabled');
    return promiseFunc;
  }

  function dependencyTracker(...args) {
    return new Promise((resolve, reject) => {
      const start = new Date();
      promiseFunc(...args)
      .then(returnValue => {
        const duration = new Date() - start;
        const success = true;
        client.trackDependency(dependencyName, callName, duration, success);
        resolve(returnValue);
      })
      .catch(err => {
        const duration = new Date() - start;
        const success = false;
        client.trackDependency(dependencyName, callName, duration, success);
        reject(err);
      });
    });
  }

  return dependencyTracker;
}

module.exports = {
  trackDependency: trackDependency,
  setup: setup
};

const appInsightsKey = process.env.FORTIS_SERVICES_APPINSIGHTS_KEY;

let client;
let consoleLog = console.log;
let consoleError = console.error;
let consoleWarn = console.warn;

function setup() {
  if (appInsightsKey) {
    const appInsights = require('applicationinsights');
    appInsights.setup(appInsightsKey);
    appInsights.start();
    client = appInsights.getClient(appInsightsKey);
    console.log = trackTrace(/* INFO */ 1, consoleLog);
    console.error = trackTrace(/* ERROR */ 3, consoleError);
    console.warn = trackTrace(/* WARNING */ 2, consoleWarn);
  }
}

function trackTrace(level, localLogger) {
  return (message) => {
    if (client) {
      client.trackTrace(message, level);
    }
    localLogger(message);
  };
}

function trackDependency(promiseFunc, dependencyName, callName) {
  if (!client) return promiseFunc;

  function dependencyTracker(...args) {
    return new Promise((resolve, reject) => {
      const start = new Date();
      promiseFunc(...args)
      .then(returnValue => {
        const duration = new Date() - start;
        const success = true;
        client.trackDependency(dependencyName, callName, duration, success);
        resolve(returnValue);
      })
      .catch(err => {
        const duration = new Date() - start;
        const success = false;
        client.trackDependency(dependencyName, callName, duration, success);
        reject(err);
      });
    });
  }

  return dependencyTracker;
}

module.exports = {
  trackDependency: trackDependency,
  setup: setup
};

Send all console calls to app insights as traces


import { createSelector } from 'reselect';
import { enabledPluginNamesSelector } from '../selectors';
import { EXTENSION_TYPE_SETTINGS_TAB } from './constants';
import { uiExtensionMap } from './uiExtensionStorage';

export const createUiExtensionSelectorByType = (type) =>
  createSelector(enabledPluginNamesSelector, (pluginNames) =>
    Array.from(uiExtensionMap.entries())
      .filter(([name]) => pluginNames.includes(name))
      .map(([, extensions]) => extensions)
      .reduce((acc, val) => acc.concat(val), [])
      .filter((extension) => extension.type === type),
  );

export const uiExtensionSettingsTabsSelector = createUiExtensionSelectorByType(
  EXTENSION_TYPE_SETTINGS_TAB,
);

import { createSelector } from 'reselect';
import { EXTENSION_TYPE_SETTINGS_TAB } from './constants';
import { enabledPluginNamesSelector } from '../selectors';
import { uiExtensionMap } from './uiExtensionStorage';

export const createUiExtensionSelectorByType = (type) =>
  createSelector(enabledPluginNamesSelector, (pluginNames) =>
    Array.from(uiExtensionMap.entries())
      .filter(([name]) => pluginNames.includes(name))
      .map(([, extensions]) => extensions)
      .reduce((acc, val) => acc.concat(val), [])
      .filter((extension) => extension.type === type),
  );

export const uiExtensionSettingsTabsSelector = createUiExtensionSelectorByType(
  EXTENSION_TYPE_SETTINGS_TAB,
);

Allow plugins to add own tabs to project settings


import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import Snackbar from 'material-ui/Snackbar';
import { hideNotification as hideNotificationAction } from '../../actions/notificationActions' ;

class Notification extends React.Component {
    handleRequestClose = () => {
        this.props.hideNotification();
    };

    render() {
        const style = {};
        if (this.props.type === 'warning') {
            style.backgroundColor = '#ff4081';
        }
        if (this.props.type === 'confirm') {
            style.backgroundColor = '#00bcd4';
        }
        return (<Snackbar
            open={!!this.props.message}
            message={this.props.message}
            autoHideDuration={4000}
            onRequestClose={this.handleRequestClose}
            bodyStyle={style}
        />);
    }
}

Notification.propTypes = {
    message: PropTypes.string,
    type: PropTypes.string.isRequired,
    hideNotification: PropTypes.func.isRequired,
};

Notification.defaultProps = {
    type: 'info',
};

const mapStateToProps = (state) => ({
    message: state.admin.notification.text,
    type: state.admin.notification.type,
});

export default connect(
  mapStateToProps,
  { hideNotification: hideNotificationAction },
)(Notification);

import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import Snackbar from 'material-ui/Snackbar';
import { hideNotification as hideNotificationAction } from '../../actions/notificationActions' ;

function getStyles(context) {
    if (!context) return { primary1Color: '#00bcd4', accent1Color: '#ff4081' };
    const {
      muiTheme: {
        baseTheme: {
          palette: {
              primary1Color,
              accent1Color,
          },
        },
      },
    } = context;
    return { primary1Color, accent1Color };
}

class Notification extends React.Component {
    handleRequestClose = () => {
        this.props.hideNotification();
    };

    render() {
        const style = {};
        const { primary1Color, accent1Color } = getStyles(this.context);
        if (this.props.type === 'warning') {
            style.backgroundColor = accent1Color;
        }
        if (this.props.type === 'confirm') {
            style.backgroundColor = primary1Color;
        }
        return (<Snackbar
            open={!!this.props.message}
            message={this.props.message}
            autoHideDuration={4000}
            onRequestClose={this.handleRequestClose}
            bodyStyle={style}
        />);
    }
}

Notification.propTypes = {
    message: PropTypes.string,
    type: PropTypes.string.isRequired,
    hideNotification: PropTypes.func.isRequired,
};

Notification.defaultProps = {
    type: 'info',
};

Notification.contextTypes = {
    muiTheme: PropTypes.object.isRequired,
};

const mapStateToProps = (state) => ({
    message: state.admin.notification.text,
    type: state.admin.notification.type,
});

export default connect(
  mapStateToProps,
  { hideNotification: hideNotificationAction },
)(Notification);

Use theme colors in notifications


#!/usr/bin/env node

// Renders the bower.json template and prints it to stdout

var template = {
  name: "graphlib",
  version: require("../../package.json").version,
  main: [ "dist/graphlib.core.js", "dist/graphlib.core.min.js" ],
  ignore: [
    ".*",
    "README.md",
    "CHANGELOG.md",
    "Makefile",
    "browser.js",
    "dist/graphlib.js",
    "dist/graphlib.min.js",
    "index.js",
    "karma*",
    "lib/**",
    "package.json",
    "src/**",
    "test/**"
  ],
  dependencies: {
    "lodash": "^2.4.1"
  }
};

console.log(JSON.stringify(template, null, 2));

#!/usr/bin/env node

// Renders the bower.json template and prints it to stdout

var packageJson = require("../../package.json");

var template = {
  name: packageJson.name,
  version: packageJson.version,
  main: ["dist/" + packageJson.name + ".core.js", "dist/" + packageJson.name + ".core.min.js"],
  ignore: [
    ".*",
    "README.md",
    "CHANGELOG.md",
    "Makefile",
    "browser.js",
    "dist/" + packageJson.name + ".js",
    "dist/" + packageJson.name + ".min.js",
    "index.js",
    "karma*",
    "lib/**",
    "package.json",
    "src/**",
    "test/**"
  ],
  dependencies: packageJson.dependencies
};

console.log(JSON.stringify(template, null, 2));

Check in improved bower generation script


/**
 * Prepare request configuration, deeply merging some specific keys
 */

var assign   = require('./assign')
var toArray  = require('./toArray')

var DEFAULTS = {
  baseURL    : '/',
  basePath   : '',
  body       : undefined,
  params     : undefined,
  headers    : {
    'Accept': 'application/json'
  },
  method     : 'GET',
  path       : '',
  query      : {},
  timeout    : 15000,
  type       : 'application/json',
  buildQuery : function(query) { return query },
  beforeSend : function(ajax) { return ajax },
  onResponse : function(response) { return response.body },
  onError    : function(error) { return error },
  Promise    : global.Promise
}

module.exports = function prepare (/* options list */) {
  var options = [ DEFAULTS ].concat(toArray(arguments))

  return options.reduce(function (memo, next) {

    return next ? assign(memo, next, {
      body    : next.body ? assign(memo.body, next.body) : next.body,
      params  : assign(memo.params, next.params),
      query   : assign(memo.query, next.query),
      headers : assign(memo.headers, next.headers)
    }) : memo
  }, {})
}

/**
 * Prepare request configuration, deeply merging some specific keys
 */

var assign   = require('./assign')
var toArray  = require('./toArray')

var DEFAULTS = {
  baseURL    : '/',
  basePath   : '',
  params     : undefined,
  headers    : {
    'Accept': 'application/json'
  },
  method     : 'GET',
  path       : '',
  query      : {},
  timeout    : 15000,
  type       : 'application/json',
  buildQuery : function(query) { return query },
  beforeSend : function(ajax) { return ajax },
  onResponse : function(response) { return response.body },
  onError    : function(error) { return error },
  Promise    : global.Promise
}

module.exports = function prepare (/* options list */) {
  var options = [ DEFAULTS ].concat(toArray(arguments))

  return options.reduce(function (memo, next) {

    return next ? assign(memo, next, {
      params  : assign(memo.params, next.params),
      query   : assign(memo.query, next.query),
      headers : assign(memo.headers, next.headers)
    }) : memo
  }, {})
}

Remove body from configuration options


/* eslint-env mocha */
import Chai from 'chai';
import ChaiString from 'chai-string';
import Sinon from 'sinon';
import SinonChai from 'sinon-chai';

Chai.use(ChaiString);
Chai.use(SinonChai);

beforeEach(function() {
  this.sandbox = Sinon.sandbox.create({
    injectInto: this,
    properties: ['spy', 'stub', 'mock'],
    useFakeTimers: false,
    useFakeServer: false
  });
});
afterEach(function() {
  this.sandbox.restore();
});

process.on('unhandledRejection', (err) => {
  throw err;
});

Throw unhandled rejections in mocha tests


'use strict';

const AWS = require('aws-sdk');
const Err = require('./error');
const upload = require('multer')();
const rp = require('request-promise');

// Hit the Vault health check endpoint to see if we're actually working with a Vault server

/**
 * Checks whether there is an actual Vault server running at the Vault endpoint
 *
 * @returns {Promise}
 */
const checkVault = function() {
  const VAULT_ENDPOINT = Config.get('vault:endpoint');

  return rp({uri: `${VAULT_ENDPOINT}/sys/health`, json: true})
    .then(() => true)
    .catch(() => false);
};

module.exports = function Index(app) {
  app.get('/', function(req, res, next) {
    const KMS = new AWS.KMS({region: Config.get('aws:region')});

    if (Config.get('aws:key')) {
      return res.render('index', {title: 'ACS'});
    }

    return new Promise((resolve, reject) => {
      KMS.listAliases({}, (err, data) => {
        if (err) {
          reject(err);
        }

        resolve(data);
      });
    }).then((keys) => {
      res.render('index', {title: 'ACS', kms: keys.Aliases});
    }).catch((err) => {
      next(err);
    });
  });

  app.post('/v1/vault', upload.none(), require('./vault')());
  app.post('/v1/kms', upload.none(), require('./kms')());
  app.use(Err);
};

'use strict';

const AWS = require('aws-sdk');
const Err = require('./error');
const upload = require('multer')();
const rp = require('request-promise');

// Hit the Vault health check endpoint to see if we're actually working with a Vault server

/**
 * Checks whether there is an actual Vault server running at the Vault endpoint
 *
 * @returns {Promise}
 */
const checkVault = function() {
  const VAULT_ENDPOINT = Config.get('vault:endpoint');

  return rp({uri: `${VAULT_ENDPOINT}/sys/health`, json: true})
    .then(() => true)
    .catch(() => false);
};

/**
 * Gets a list of KMS keys and their aliases
 *
 * @returns {Promise}
 */
const getKmsKeys = function() {
  const KMS = new AWS.KMS({region: Config.get('aws:region')});

  if (Config.get('aws:key')) {
    return Promise.resolve([]);
  }

  return new Promise((resolve, reject) => {
    KMS.listAliases({}, (err, data) => {
      if (err) {
        reject(err);
      }

      resolve(data);
    });
  });
};

module.exports = function Index(app) {
  app.get('/', function(req, res, next) {

    }).catch((err) => {
      next(err);
    });
  });

  app.post('/v1/vault', upload.none(), require('./vault')());
  app.post('/v1/kms', upload.none(), require('./kms')());
  app.use(Err);
};

Move KMS key retrieval to its own function.


import { Server } from 'ws'
import debugLog from '../debugLog.js'
import LambdaFunctionPool from '../lambda/index.js'
import serverlessLog from '../serverlessLog.js'
import { createUniqueId } from '../utils/index.js'

export default class WebSocketServer {
  constructor(options, webSocketClients, sharedServer) {
    this._lambdaFunctionPool = new LambdaFunctionPool()
    this._options = options

    this._server = new Server({
      server: sharedServer,
    })

    this._webSocketClients = webSocketClients

    this._init()
  }

  _init() {
    this._server.on('connection', (webSocketClient /* request */) => {
      console.log('received connection')

      const connectionId = createUniqueId()

      debugLog(`connect:${connectionId}`)

      this._webSocketClients.addClient(webSocketClient, connectionId)
    })
  }

  addRoute(functionName, functionObj, route) {
    this._webSocketClients.addRoute(functionName, functionObj, route)
    // serverlessLog(`route '${route}'`)
  }

  async start() {
    const { host, httpsProtocol, websocketPort } = this._options

    serverlessLog(
      `Offline [websocket] listening on ws${
        httpsProtocol ? 's' : ''
      }://${host}:${websocketPort}`,
    )
  }

  // no-op, we're re-using the http server
  stop() {}
}

import { Server } from 'ws'
import debugLog from '../debugLog.js'
import serverlessLog from '../serverlessLog.js'
import { createUniqueId } from '../utils/index.js'

export default class WebSocketServer {
  constructor(options, webSocketClients, sharedServer) {
    this._options = options

    this._server = new Server({
      server: sharedServer,
    })

    this._webSocketClients = webSocketClients

    this._init()
  }

  _init() {
    this._server.on('connection', (webSocketClient /* request */) => {
      console.log('received connection')

      const connectionId = createUniqueId()

      debugLog(`connect:${connectionId}`)

      this._webSocketClients.addClient(webSocketClient, connectionId)
    })
  }

  addRoute(functionName, functionObj, route) {
    this._webSocketClients.addRoute(functionName, functionObj, route)
    // serverlessLog(`route '${route}'`)
  }

  async start() {
    const { host, httpsProtocol, websocketPort } = this._options

    serverlessLog(
      `Offline [websocket] listening on ws${
        httpsProtocol ? 's' : ''
      }://${host}:${websocketPort}`,
    )
  }

  // no-op, we're re-using the http server
  stop() {}
}

Remove unused lambda function pool from websocket server


var fixtures = require('node-require-directory')(__dirname);

exports.load = function *(specificFixtures) {
  if (typeof specificFixtures === 'string') {
    specificFixtures = [specificFixtures];
  }

  try {
    yield exports.unload();

    var keys = Object.keys(fixtures).filter(function(key) {
      if (specificFixtures && specificFixtures.indexOf(key) === -1) {
        return false;
      }
      return true;
    });
    for (var i = 0; i < keys.length; ++i) {
      exports[keys[i]] = yield fixtures[keys[i]].load();
    }
  } catch (e) {
    console.error(e);
  }
};

exports.unload = function *() {
  yield sequelize.sync({ force: true });
};

var fixtures = require('node-require-directory')(__dirname);

exports.load = function *(specificFixtures) {
  if (typeof specificFixtures === 'string') {
    specificFixtures = [specificFixtures];
  }

  var prevSetting = sequelize.options.logging;
  sequelize.options.logging = false;
  try {
    yield exports.unload();

    var keys = Object.keys(fixtures).filter(function(key) {
      if (specificFixtures && specificFixtures.indexOf(key) === -1) {
        return false;
      }
      return true;
    });
    for (var i = 0; i < keys.length; ++i) {
      exports[keys[i]] = yield fixtures[keys[i]].load();
    }
  } catch (e) {
    console.error(e);
  }
  sequelize.options.logging = prevSetting;
};

exports.unload = function *() {
  var prevSetting = sequelize.options.logging;
  sequelize.options.logging = false;
  yield sequelize.sync({ force: true });
  sequelize.options.logging = prevSetting;
};

Disable logging sql when creating tables


import { Map } from 'immutable';

import {
  SIZE_3,
  BLACK
} from './markerConstants';

export const initialMarker = Map({
  size: SIZE_3,
  color: BLACK
});

import { CHANGE_COLOR, CHANGE_SIZE } from './../../src/client/app/toolbar/toolbarActions';

const markerReducer = (state = initialMarker, action) => {
  switch (action.type) {
    case CHANGE_COLOR:
      return state.set('color', action.color);
    case CHANGE_SIZE:
      return state.set('size', action.size);
    default:
      return state;
  }
};

export default markerReducer;

import { Map } from 'immutable';

import {
  BLACK,
  SIZE_3,
  DRAW
} from './markerConstants';

export const initialMarker = Map({
  color: BLACK,
  size: SIZE_3,
  mode: DRAW
});

import {
  CHANGE_COLOR,
  CHANGE_SIZE,
  CHANGE_MODE
} from './../../src/client/app/toolbar/toolbarActions';

const markerReducer = (state = initialMarker, action) => {
  switch (action.type) {
    case CHANGE_COLOR:
      return state.set('color', action.color);
    case CHANGE_SIZE:
      return state.set('size', action.size);
    case CHANGE_MODE:
      return state.set('size', action.size);
    default:
      return state;
  }
};

export default markerReducer;

Add mode field to marker state, add reducer case for CHANGE_MODE


'use babel'

/* @flow */

import type { Message } from '../types'

export function visitMessage(message: Message) {
  const messageFile = message.version === 1 ? message.filePath : message.location.file
  const messageRange = message.version === 1 ? message.range : message.location.position
  atom.workspace.open(messageFile, { searchAllPanes: true }).then(function() {
    const textEditor = atom.workspace.getActiveTextEditor()
    if (textEditor && textEditor.getPath() === messageFile) {
      textEditor.setCursorBufferPosition(messageRange.start)
    }
  })
}

export function htmlToText(html: any) {
  const element = document.createElement('div')
  if (typeof html === 'string') {
    element.innerHTML = html
  } else {
    element.appendChild(html.cloneNode(true))
  }
  /* eslint-disable no-irregular-whitespace */
  // NOTE: Convert   to regular whitespace
  return element.textContent.replace(/ /g, ' ')
  /* eslint-enable no-irregular-whitespace */
}

'use babel'

/* @flow */

import type { Message } from '../types'

const nbsp = String.fromCodePoint(160)

export function visitMessage(message: Message) {
  const messageFile = message.version === 1 ? message.filePath : message.location.file
  const messageRange = message.version === 1 ? message.range : message.location.position
  atom.workspace.open(messageFile, { searchAllPanes: true }).then(function() {
    const textEditor = atom.workspace.getActiveTextEditor()
    if (textEditor && textEditor.getPath() === messageFile) {
      textEditor.setCursorBufferPosition(messageRange.start)
    }
  })
}

export function htmlToText(html: any) {
  const element = document.createElement('div')
  if (typeof html === 'string') {
    element.innerHTML = html
  } else {
    element.appendChild(html.cloneNode(true))
  }
  // NOTE: Convert   to regular whitespace
  return element.textContent.replace(new RegExp(nbsp, 'g'), ' ')
}

Use a non-hacky way to replace nbsp