exports.register = function(plugin, options, next) {

  // Wait 10 seconds for existing connections to close then exit.
  var stop = function() {
    plugin.servers[0].stop({
      timeout: 10 * 1000
    }, function() {
        process.exit();
      });
  };
  process.on('SIGTERM', stop);
  process.on('SIGINT', stop);

  next();
};

exports.register.attributes = {
  pkg: require('./package.json')
};

exports.register = function(server, options, next) {

  // Wait 10 seconds for existing connections to close then exit.
  var stop = function() {
    server.connections[0].stop({
      timeout: 10 * 1000
    }, function() {
        process.exit();
      });
  };
  process.on('SIGTERM', stop);
  process.on('SIGINT', stop);

  next();
};

exports.register.attributes = {
  pkg: require('./package.json')
};

Upgrade for compatability with Hapi 8


var path = require('path');

module.exports = function (source) {
  if (this.cacheable) {
    this.cacheable();
  }

  var filename = path.basename(this.resourcePath),
      matches = 0,
      processedSource;

  processedSource = source.replace(/React\.createClass\s*\(\s*\{/g, function (match) {
    matches++;
    return '__hotUpdateAPI.createClass({';
  });

  if (!matches) {
    return source;
  }

  return [
    'var __hotUpdateAPI = (function () {',
    '  var React = require("react");',
    '  var getHotUpdateAPI = require(' + JSON.stringify(require.resolve('./getHotUpdateAPI')) + ');',
    '  return getHotUpdateAPI(React, ' + JSON.stringify(filename) + ', module.id);',
    '})();',
    processedSource,
    'if (module.hot) {',
    '  module.hot.accept(function (err) {',
    '    if (err) {',
    '      console.error("Cannot not apply hot update to " + ' + JSON.stringify(filename) + ' + ": " + err.message);',
    '    }',
    '  });',
    '  module.hot.dispose(function () {',
    '    var nextTick = require(' + JSON.stringify(require.resolve('next-tick')) + ');',
    '    nextTick(__hotUpdateAPI.updateMountedInstances);',
    '  });',
    '}'
  ].join('\n');
};

var path = require('path');

module.exports = function (source) {
  if (this.cacheable) {
    this.cacheable();
  }

  var filename = path.basename(this.resourcePath),
      matches = 0,
      processedSource;

  processedSource = source.replace(/[Rr]eact\.createClass\s*\(/g, function (match) {
    matches++;
    return '__hotUpdateAPI.createClass(';
  });

  if (!matches) {
    return source;
  }

  return [
    'var __hotUpdateAPI = (function () {',
    '  var React = require("react");',
    '  var getHotUpdateAPI = require(' + JSON.stringify(require.resolve('./getHotUpdateAPI')) + ');',
    '  return getHotUpdateAPI(React, ' + JSON.stringify(filename) + ', module.id);',
    '})();',
    processedSource,
    'if (module.hot) {',
    '  module.hot.accept(function (err) {',
    '    if (err) {',
    '      console.error("Cannot not apply hot update to " + ' + JSON.stringify(filename) + ' + ": " + err.message);',
    '    }',
    '  });',
    '  module.hot.dispose(function () {',
    '    var nextTick = require(' + JSON.stringify(require.resolve('next-tick')) + ');',
    '    nextTick(__hotUpdateAPI.updateMountedInstances);',
    '  });',
    '}'
  ].join('\n');
};

Allow lowercase React import and using an object reference


module.exports = function () {
    StructureSpawn.prototype.createCustomCreep =
    function(energy, roleName) {
        var numberOfParts = Math.floor(energy / 350);
        var body = [];
            for (let i = 0; i < numberOfParts; i++) {
                body.push(WORK);
            }
            for (let i = 0; i < numberOfParts; i++) {
                body.push(WORK);
            }
            for (let i = 0; i < numberOfParts; i++) {
                body.push(CARRY);
            }
            for (let i = 0; i < numberOfParts; i++) {
                body.push(MOVE);
            }
            for (let i = 0; i < numberOfParts; i++) {
                body.push(MOVE);
            }
            for (let i = 0; i < numberOfParts; i++) {
                body.push(MOVE);
            }
        return this.createCreep(body, undefined, {role: roleName, working: false });
    };

};

module.exports = function () {
    StructureSpawn.prototype.createCustomCreep =
    function(energy, roleName) {
        var numberOfParts = Math.floor(energy / 350);
        var body = [];
            for (let i = 0; i < numberOfParts; i++) {
                body.push(WORK);
            }
            for (let i = 0; i < numberOfParts; i++) {
                body.push(WORK);
            }
            for (let i = 0; i < numberOfParts; i++) {
                body.push(CARRY);
            }
            for (let i = 0; i < numberOfParts; i++) {
                body.push(CARRY);
            }
            for (let i = 0; i < numberOfParts; i++) {
                body.push(MOVE);
            }
            for (let i = 0; i < numberOfParts; i++) {
                body.push(MOVE);
            }
            for (let i = 0; i < numberOfParts; i++) {
                body.push(MOVE);
            }
        return this.createCreep(body, undefined, {role: roleName, working: false });
    };

};

WORK WORK CARRY CARRY MOVE MOVE MOVE


var shell = require('shelljs')

if (exec('git status --porcelain').stdout) {
  console.error('Git working directory not clean. Please commit all chances to release a new package to npm.')
  process.exit(2)
}

var versionIncrement = process.argv[process.argv.length - 1]
var versionIncrements = ['major', 'minor', 'patch']

if (versionIncrements.indexOf(versionIncrement) < 0) {
  console.error('Usage: node release.js major|minor|patch')
  process.exit(1)
}

exec('npm test')

var geotag = execOptional('npm run geotag')

if (geotag.code == 0) {
  exec('git commit -m "Geotag package for release" package.json')
}

exec('npm version ' + versionIncrement)

exec('git push')
exec('git push --tags')
exec('npm publish')

function exec (cmd) {
  var ret = shell.exec(cmd, { silent: true })

  if (ret.code !== 0) {
    console.error(ret.stdout)
    process.exit(1)
  }

  return ret
}

function execOptional (cmd) {
  var ret = shell.exec(cmd, { silent: true })

  if (ret.code !== 0) {
    console.log(ret.stdout)
  }

  return ret
}

var shell = require('shelljs')

if (exec('git status --porcelain').stdout) {
  console.error('Git working directory not clean. Please commit all chances to release a new package to npm.')
  process.exit(2)
}

var versionIncrement = process.argv[process.argv.length - 1]
var versionIncrements = ['major', 'minor', 'patch']

if (versionIncrements.indexOf(versionIncrement) < 0) {
  console.error('Usage: node release.js major|minor|patch')
  process.exit(1)
}

exec('npm test')

var geotag = execOptional('npm run geotag')

if (geotag.code === 0) {
  exec('git commit -m "Geotag package for release" package.json')
}

exec('npm version ' + versionIncrement)

exec('git push')
exec('git push --tags')
exec('npm publish')

function exec (cmd) {
  var ret = shell.exec(cmd, { silent: true })

  if (ret.code !== 0) {
    console.error(ret.stdout)
    process.exit(1)
  }

  return ret
}

function execOptional (cmd) {
  var ret = shell.exec(cmd, { silent: true })

  if (ret.code !== 0) {
    console.log(ret.stdout)
  }

  return ret
}

Use === instead of == to please the standard


// Forest tables

$(document).on('mouseenter', '.forest-table tbody tr', function() {
  var $row = $(this);

  $row.addClass('active');

  $(document).one('turbolinks:before-cache.forestTables', function() {
    $row.removeClass('active');
  });
});

$(document).on('mouseleave', '.forest-table tbody tr', function() {
  var $row = $(this);

  $row.removeClass('active');
});

$(document).on('click', '.forest-table tbody tr', function(e) {
  var $row = $(this);

  if ( !$(e.target).closest('a').length ) {
    var $button = $row.find('a.forest-table__link:first');

    if ( !$button.length ) {
      $button = $row.find('a.btn-primary:first');
    }

    var url = $button.attr('href');

    if ( url ) {
      Turbolinks.visit(url);
    }
  }
});

// Forest tables

$(document).on('mouseenter', '.forest-table tbody tr', function() {
  var $row = $(this);

  $row.addClass('active');

  $(document).one('turbolinks:before-cache.forestTables', function() {
    $row.removeClass('active');
  });
});

$(document).on('mouseleave', '.forest-table tbody tr', function() {
  var $row = $(this);

  $row.removeClass('active');
});

$(document).on('click', '.forest-table tbody tr', function(e) {
  var $row = $(this);

  if ( !$(e.target).closest('a').length ) {
    var $button = $row.find('a.forest-table__link:first');

    if ( !$button.length ) {
      $button = $row.find('a.btn-primary:first');
    }

    var url = $button.attr('href');

    if ( url ) {
      if ( e.metaKey || e.ctrlKey ) {
        window.open( url, '_blank' );
      } else if ( e.shiftKey ) {
        window.open( url, '_blank' );
        window.focus();
      } else {
        Turbolinks.visit(url);
      }
    }
  }
});

Allow modifier key press when opening table rows


// app.shoppingListDirective.js

(function() {
  "use strict";

  angular.module("ShoppingListDirectiveApp")
    .directive("shoppingList", ShoppingList);

  function ShoppingList() {
    const templateUrl = "../templates/shoppingList.view.html";
    const ddo = {
      restrict: "E",
      templateUrl,
      scope: {
        list: "<",
        title: "@"
      },
      controller: "ShoppingListDirectiveController as l",
      bindToController: true
    };

    return ddo;
  }
})();

// app.shoppingListDirective.js

(function() {
  "use strict";

  angular.module("ShoppingListDirectiveApp")
    .directive("shoppingList", ShoppingList);

  function ShoppingList() {
    const templateUrl = "../templates/shoppingList.view.html";
    const ddo = {
      restrict: "E",
      templateUrl,
      scope: {
        list: "<",
        title: "@"
      },
      controller: "ShoppingListDirectiveController as ShoppingListDirectiveCtrl",
      bindToController: true
    };

    return ddo;
  }
})();

Edit controllerAs label for ShoppingListDirectiveController


////	External Links
// Open external links in a new tab.
$(function () {
	$('a').each(function () {
		if ( $(this).href.indexOf(window.location.host) == -1 ) {
			$(this).attr('target', '_blank');
		}
	});
});

////	External Links
// Open external links in a new tab.
$(function () {
	$('a').each(function () {
		if ( $(this).href.indexOf(window.location.host) === -1 ) {
			$(this).attr('target', '_blank');
		}
	});
});

Fix "Expected '===' and instead saw '=='."


var gulp = require('gulp');

var clean = require('gulp-clean');
var zip = require('gulp-zip');

var bases = {
    root: 'dist/'
};

var paths = [
    'core/actions/*',
    'core/common/**',
    'admin/**',
    '!admin/config/*',
    'boxoffice/**',
    '!boxoffice/config/*',
    'customer/**',
    '!customer/config/*',
    'scanner/**',
    '!scanner/config/*',
    'printer/**',
    '!printer/config/*',
    'core/model/*',
    'core/services/*',
    'vendor/**',
    'composer.lock',
    'core/dependencies.php'
];

gulp.task('clean', function() {
    return gulp.src(bases.root)
        .pipe(clean({}));
});

gulp.task('collect', function() {
    return gulp.src(paths, { base: './', dot: true })
        .pipe(gulp.dest(bases.root));
});

gulp.task('zip', function() {
    return gulp.src(bases.root + '**')
        .pipe(zip('ticketbox-server-php.zip'))
        .pipe(gulp.dest(bases.root));
});

gulp.task('default', gulp.series('clean', 'collect', 'zip'));

var gulp = require('gulp');

var clean = require('gulp-clean');
var zip = require('gulp-zip');

var bases = {
    root: 'dist/'
};

var paths = [
    'core/actions/*',
    'core/common/**',
    'admin/**',
    '!admin/config/*',
    'boxoffice/**',
    '!boxoffice/config/*',
    'customer/**',
    '!customer/config/*',
    'scanner/**',
    '!scanner/config/*',
    'printer/**',
    '!printer/config/*',
    'core/model/*',
    'core/services/*',
    'vendor/**',
    'composer.lock',
    'core/dependencies.php',
    'core/.htaccess'
];

gulp.task('clean', function() {
    return gulp.src(bases.root)
        .pipe(clean({}));
});

gulp.task('collect', function() {
    return gulp.src(paths, { base: './', dot: true })
        .pipe(gulp.dest(bases.root));
});

gulp.task('zip', function() {
    return gulp.src(bases.root + '**')
        .pipe(zip('ticketbox-server-php.zip'))
        .pipe(gulp.dest(bases.root));
});

gulp.task('default', gulp.series('clean', 'collect', 'zip'));

Copy core .htaccess file to dist


// TODO Just add fallbacks to config.js
var path = require('path')

module.exports = {
  port: 8080,
  address: '127.0.0.1',

  session_secret: 'asdf',

  database: {
    host: 'mongodb://127.0.0.1/', // MongoDB host
    name: 'vegosvar', // MongoDB database name
  },

  facebook: {
    app_id: '',
    app_secret: '',
    callback: 'http://local.vegosvar.se:8080/auth/facebook/callback'
  },

  root: path.join(__dirname, '..'),
  uploads: path.join(__dirname, '../uploads')
}

// TODO Just add fallbacks to config.js
var path = require('path')

module.exports = {
  port: 8080,
  address: '127.0.0.1',

  session_secret: 'asdf',

  database: {
    host: 'mongodb://127.0.0.1/', // MongoDB host
    name: 'vegosvar', // MongoDB database name
  },

  facebook: {
    app_id: '',
    app_secret: '',
    callback: 'http://local.vegosvar.se:8080/auth/facebook/callback'
  },

  instagram: {
    client_id: '',
    client_secret: '',
    callback: 'http://local.vegosvar.se/admin/auth/instagram/callback',
    scope: 'public_content '
  },

  root: path.join(__dirname, '..'),
  uploads: path.join(__dirname, '../uploads')
}

Add config for instagram authentication


var _ = require("lodash");
var Bacon = require("baconjs");

var Interact = module.exports;

Interact.ask = function (question) {
  var readline = require("readline").createInterface({
    input: process.stdin,
    output: process.stdout
  });

  return Bacon.fromCallback(_.partial(readline.question.bind(readline), question)).doAction(readline.close.bind(readline));
};

Interact.confirm = function(question, rejectionMessage) {
  return Interact.ask(question).flatMapLatest(function(answer) {
    if(_.includes(["yes", "y"], answer)) {
      return true;
    } else {
      return new Bacon.Error(rejectionMessage);
    }
  });
};

var _ = require("lodash");
var Bacon = require("baconjs");

var Interact = module.exports;

Interact.ask = function (question) {
  var readline = require("readline").createInterface({
    input: process.stdin,
    output: process.stdout
  });

  return Bacon.fromCallback(_.partial(readline.question.bind(readline), question)).doAction(readline.close.bind(readline));
};

Interact.confirm = function(question, rejectionMessage, answers) {
  var defaultAnswers = ["yes", "y"];
  var expectedAnswers = typeof answers === "undefined" ? defaultAnswers :  answers;
  return Interact.ask(question).flatMapLatest(function(answer) {
    if(_.includes(expectedAnswers, answer)) {
      return true;
    } else {
      return new Bacon.Error(rejectionMessage);
    }
  });
};

Make Interact.confirm use configurable acceptance strings


(function () {
    var cogClass = function () {};
    cogClass.prototype.exec = function (params, request, response) {
        var oops = this.sys.apiError;
        var sys = this.sys;
        var userID = params.id;
        var userKey = params.key;

        var pages = sys.pages;
        pages.reset();

        var delay = 30;
        if (sys.output_style === 'demo') {
            delay = 0;
        }

        var sql = 'SELECT eventID FROM events WHERE (strftime("%s","now")-strftime("%s",touchDate,"unixepoch"))/60>? AND NOT published=1 and status=0 ORDER BY pageDate DESC;';
        sys.db.all(sql,[delay],function(err,rows){
            if (err) {return oops(response,err,'pending(1)')};

            sys.runEvents(rows,response,processData);

            function processData (data) {

                var page = pages.composeFeed(data,null)

                page = page.replace(/@@USER_ID@@/g,userID)
                    .replace(/@@USER_KEY@@/g,userKey);

                response.writeHead(200, {'Content-Type': 'application/atom+xml'});
                response.end(page)
            }

        });

    }
    exports.cogClass = cogClass;
})();

(function () {
    var cogClass = function () {};
    cogClass.prototype.exec = function (params, request, response) {
        var oops = this.sys.apiError;
        var sys = this.sys;
        var userID = params.id;
        var userKey = params.key;

        var pages = sys.pages;
        pages.reset();

        var delay = 30;
        if (sys.output_style === 'demo') {
            delay = 0;
        }

        delay = 3;

        var sql = 'SELECT eventID FROM events WHERE (strftime("%s","now")-strftime("%s",touchDate,"unixepoch"))/60>? AND NOT published=1 and status=0 ORDER BY pageDate DESC;';
        sys.db.all(sql,[delay],function(err,rows){
            if (err) {return oops(response,err,'pending(1)')};

            sys.runEvents(rows,response,processData);

            function processData (data) {

                var page = pages.composeFeed(data,null)

                page = page.replace(/@@USER_ID@@/g,userID)
                    .replace(/@@USER_KEY@@/g,userKey);

                response.writeHead(200, {'Content-Type': 'application/atom+xml'});
                response.end(page)
            }

        });

    }
    exports.cogClass = cogClass;
})();

Set short delay for demo


module.exports = {
	name: 'commonjs.legacy.index_json/commonjs.legacy.index_js.js'
};

module.exports = {
	name: 'commonjs.legacy.index_json/commonjs.legacy.index_json.js'
};

Fix test input file with incorrect contets


'use strict';

require('es6-promise').polyfill();

var debug = require('../helper/debug')('fn-quantiles');
var LazyFiltersResult = require('../model/lazy-filters-result');
var fnBuckets = require('./fn-buckets');

module.exports = function (datasource) {
  return function fn$quantiles (numBuckets) {
    debug('fn$quantiles(%j)', arguments);
    debug('Using "%s" datasource to calculate quantiles', datasource.getName());
    return new Promise(function (resolve) {
      return resolve(new LazyFiltersResult(function (column, strategy) {
        return fnBuckets(datasource)(column, 'quantiles', numBuckets).then(function (filters) {
          filters.strategy = strategy || '>';
          return new Promise(function (resolve) {
            return resolve(filters);
          });
        });
      }));
    });
  };
};

module.exports.fnName = 'quantiles';

'use strict';

var createBucketsFn = require('./fn-buckets').createBucketsFn;

var FN_NAME = 'quantiles';

module.exports = function (datasource) {
  return createBucketsFn(datasource, FN_NAME, '>');
};

module.exports.fnName = FN_NAME;

Migrate quantiles to buckets creation


import React, { Component } from 'react';
import { expect } from 'chai';
import { mount } from 'enzyme';
import Toolbar from '../index';

describe('Toolbar', () => {
  it('allows children to override the content', (done) => {
    const structure = [class Child extends Component {
      componentDidMount() {
        setTimeout(() => {
          this.props.onOverrideContent(() => {
            setTimeout(() => {
              this.props.onOverrideContent(undefined);
            });
            return ;
          });
        });
      }
      render() {
        return ;
      }
    }];

    const theme = { toolbarStyles: {} };

    const store = {
      subscribeToItem() {},
      unsubscribeFromItem() {},
      getItem: (name) => ({
        getEditorState: () => ({
          getSelection: () => ({ isCollapsed: () => true })
        })
      }[name])
    };

    const wrapper = mount(
      <Toolbar
        store={store}
        theme={theme}
        structure={structure}
      />
    );

    expect(wrapper.find('.initial').length).to.equal(1);

    setTimeout(() => {
      expect(wrapper.find('.initial').length).to.equal(0);
      expect(wrapper.find('.overridden').length).to.equal(1);

      setTimeout(() => {
        expect(wrapper.find('.initial').length).to.equal(1);
        expect(wrapper.find('.overridden').length).to.equal(0);
        done();
      });
    });
  });
});

import React, { Component } from 'react';
import { expect } from 'chai';
import { mount } from 'enzyme';
import Toolbar from '../index';

describe('Toolbar', () => {
  it('allows children to override the content', (done) => {
    const structure = [class Child extends Component {
      componentDidMount() {
        setTimeout(() => {
          this.props.onOverrideContent(() => {
            setTimeout(() => {
              this.props.onOverrideContent(undefined);
            });
            return ;
          });
        });
      }
      render() {
        return ;
      }
    }];

    const theme = { toolbarStyles: {} };

    const store = {
      subscribeToItem() {},
      unsubscribeFromItem() {},
      getItem: (name) => ({
        getEditorState: () => ({
          getSelection: () => ({ isCollapsed: () => true, getHasFocus: () => true })
        })
      }[name])
    };

    const wrapper = mount(
      <Toolbar
        store={store}
        theme={theme}
        structure={structure}
      />
    );

    expect(wrapper.find('.initial').length).to.equal(1);

    setTimeout(() => {
      expect(wrapper.find('.initial').length).to.equal(0);
      expect(wrapper.find('.overridden').length).to.equal(1);

      setTimeout(() => {
        expect(wrapper.find('.initial').length).to.equal(1);
        expect(wrapper.find('.overridden').length).to.equal(0);
        done();
      });
    });
  });
});

Fix test for inline toolbar


/* eslint-disable ember/no-computed-properties-in-native-classes */
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';

export default class CoursePublicationCheckComponent extends Component {
  @service router;

  get showUnlinkIcon() {
    if (!this.args.course.courseObjectives) {
      return false;
    }
    const objectivesWithoutParents = this.args.course.courseObjectives.filter((objective) => {
      const parentIds = objective.hasMany('programYearObjectives').ids();
      return parentIds.length === 0;
    });

    return objectivesWithoutParents.length > 0;
  }
}

/* eslint-disable ember/no-computed-properties-in-native-classes */
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
import { use } from 'ember-could-get-used-to-this';
import ResolveAsyncValue from 'ilios-common/classes/resolve-async-value';

export default class CoursePublicationCheckComponent extends Component {
  @service router;

  @use courseObjectives = new ResolveAsyncValue(() => [this.args.course.courseObjectives, []]);

  get showUnlinkIcon() {
    const objectivesWithoutParents = this.courseObjectives.filter((objective) => {
      return objective.programYearObjectives.length === 0;
    });

    return objectivesWithoutParents.length > 0;
  }
}

Resolve objectives in course publication check


import Ember from 'ember';
import layout from '../templates/components/floating-mobile-buttons';

export default Ember.Component.extend({
  layout,
  tagName: 'ul',
  classNames: ['floating-buttons'],
  classNameBindings: ['active:active', 'bottom:bottom', 'top:top', 'left:left', 'right:right'],
  active: false,
  childrenActive: false,
  hasChildren: false,
  position: 'bottom right',
  didReceiveAttrs(){
    this._super(...arguments);
    let classes = this.get('position').trim().split(" ");
    let vClasses = classes.filter( c => {
      if(c.match(/(bottom|top)/i)){
        return c;
      }
    });
    let hClasses = classes.filter( c => {
      if(c.match(/(right|left)/i)){
        return c;
      }
    });

    if(vClasses.length === 0 || vClasses.length > 1 || hClasses.length === 0 || hClasses.length > 1){
      Ember.assert('The position property must be a string with the values top|bottom and left|right.');
    }

    classes.forEach( c => {
        this.set(c, true);
    });
  },
  didInsertElement(){
    this._super(...arguments);
    this.set('active', true);

    if(Ember.$(`#${this.get('elementId')} .floating-child-button`).length > 1){
      this.set('hasChildren', true);
    }
  },
  actions: {
    toggleChildren(){
      this.toggleProperty('childrenActive');
    }
  }
});

import Ember from 'ember';
import layout from '../templates/components/floating-mobile-buttons';

export default Ember.Component.extend({
  layout,
  tagName: 'ul',
  classNames: ['floating-buttons'],
  classNameBindings: ['active:active', 'bottom:bottom', 'top:top', 'left:left', 'right:right'],
  active: false,
  childrenActive: false,
  hasChildren: false,
  position: 'bottom right',
  didReceiveAttrs(){
    this._super(...arguments);

    this.set('active', true);

    let classes = this.get('position').trim().split(" ");
    let vClasses = classes.filter( c => {
      if(c.match(/(bottom|top)/i)){
        return c;
      }
    });
    let hClasses = classes.filter( c => {
      if(c.match(/(right|left)/i)){
        return c;
      }
    });

    if(vClasses.length === 0 || vClasses.length > 1 || hClasses.length === 0 || hClasses.length > 1){
      Ember.assert('The position property must be a string with the values top|bottom and left|right.');
    }

    classes.forEach( c => {
        this.set(c, true);
    });

    Ember.run.schedule('afterRender', () => {
      if(Ember.$(`#${this.get('elementId')} .floating-child-button`).length > 1){
        this.set('hasChildren', true);
      }
    });
  },
  actions: {
    toggleChildren(){
      this.toggleProperty('childrenActive');
    }
  }
});

Use the afterRender run loop to check if has children


"use strict";

great.Artist = Backbone.Model.extend({
});

great.ArtistsCollection = Backbone.PageableCollection.extend({
    model: great.Artist,
    comparator: "name",
    url: "/great/music/artists/",
    mode: "client",
});

"use strict";

great.Artist = Backbone.Model.extend({
    urlRoot: "/great/music/artists/",
});

great.ArtistsCollection = Backbone.PageableCollection.extend({
    model: great.Artist,
    comparator: "name",
    url: "/great/music/artists/",
    mode: "client",
});

Add a urlRoot to allow use outside of a collection.


import R from 'ramda';
import uncapitalize from './uncapitalize.js';
import pascalCase from './pascal-case.js';

// a -> a
const camelCase = R.compose(uncapitalize, pascalCase);

export default camelCase;

import R from 'ramda';
import compose from './util/compose.js';
import uncapitalize from './uncapitalize.js';
import pascalCase from './pascal-case.js';

// a -> a
const camelCase = compose(uncapitalize, pascalCase);

export default camelCase;

Refactor camelCase function to use custom compose function


$(document).ready(function() {
    console.log( "ready!" );

    var gameForeground = $('#gameForeground')[0];
    var gameFront = $('#gameFront')[0];
    var gameBack = $('#gameBack')[0];
    var gameText = $('#gameText')[0];
    gameCommon.setup(gameForeground, gameFront, gameBack, gameText);

    var video = document.querySelector('video');
    camera.setup(video, gameCommon.onSnapshot, gameCommon.initCallback);

    window.setTimeout(gameCommon.mainLoop, 100);

    $( "body" ).click(function() {
        if (gameCommon.currentGame == gameMenu) {
            var r = confirm("Reset camera?");
            if (r == true) {
                gameCommon.clearup();
                camera.initialized = false;
                camera.startupTime = 0;
            }
        }
    });

});

$(document).ready(function() {
    console.log( "ready!" );

    var gameForeground = $('#gameForeground')[0];
    var gameFront = $('#gameFront')[0];
    var gameBack = $('#gameBack')[0];
    var gameText = $('#gameText')[0];
    gameCommon.setup(gameForeground, gameFront, gameBack, gameText);

    var video = document.querySelector('video');
    camera.setup(video, gameCommon.onSnapshot, gameCommon.initCallback);

    window.setTimeout(gameCommon.mainLoop, 100);

});

Remove reset camera click event


import { compose, createStore, applyMiddleware } from 'redux'
import persistState from 'redux-localstorage'
import thunk from 'redux-thunk'

import RootReducer from 'reducers/RootReducer'

export default createStore(
  RootReducer,
  compose(
    applyMiddleware(thunk),
    persistState(['user']),
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
  ),
)

import { compose, createStore, applyMiddleware } from 'redux'
import persistState from 'redux-localstorage'
import thunk from 'redux-thunk'

import RootReducer from 'reducers/RootReducer'

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose

export default createStore(
  RootReducer,
  composeEnhancers(
    persistState(['user']),
    applyMiddleware(thunk),
  ),
)

Fix Redux DevTools for mobile


jQuery(function($) {
	$('#tipue_search_input').tipuesearch({
		'mode': 'static',
		'contentLocation': '/search_index'
	});

	$("form.no-pageload input").keypress(function(e){
		var k = e.keyCode || e.which;
		if(k == 13){
			e.preventDefault();
		}
	});

	$("form.button-submit input.submit-button").click(function(e){
		this.form.submit();
	});
});

jQuery(function($) {

	//prevent undeclared variable
	var tipuesearch = {"pages": [
		{"title": "", "text":"", "tags":"", "loc":""}
	]};

	$('#tipue_search_input').tipuesearch({
		'mode': 'static',
		'contentLocation': '/search_index'
	});

	$("form.no-pageload input").keypress(function(e){
		var k = e.keyCode || e.which;
		if(k == 13){
			e.preventDefault();
		}
	});

	$("form.button-submit input.submit-button").click(function(e){
		this.form.submit();
	});
});

Fix undefined error when no search file found


var config = require('../config.json');
var modIDs = config.modIDs;

/**
 * A single Facebook post or comment.
 * @class
 */
var Post = module.exports = function (id) {

  /** @member {string} */
  this.id = id;

  /** @member {string} */
  this.from = '';

  /** @member {string} */
  this.message = '';

};

/**
 * Converts a raw JSON-formatted post from a response body into an object.
 * @param {Object} raw
 * @return {Post}
 */
Post.fromRaw = function (raw) {
  var post = new Post(raw.id);
  post.from = raw.from ? raw.from.id : '0';
  post.message = raw.message;
  return post;
};

/**
 * Determines if the poster was a group moderator.
 * @returns {boolean}
 */
Post.prototype.isMod = function () {
  return modIDs.indexOf(this.from) !== -1;
};

/**
 * Determines if the post contains a moderative command. Commands begin with a
 * forward slash, followed by any number of alphanumeric characters.
 * @param {string} cmd
 * @returns {boolean}
 */
Post.prototype.hasCommand = function (cmd) {
  return this.message.match('^/' + cmd + '(?: |$)');
};

var config = require('../config.json');
var modIDs = config.modIDs;

/**
 * A single Facebook post or comment.
 * @class
 */
var Post = module.exports = function (id) {

  /** @member {string} */
  this.id = id;

  /** @member {string} */
  this.from = '';

  /** @member {string} */
  this.message = '';

};

/**
 * Converts a raw JSON-formatted post from a response body into an object.
 * @param {Object} raw
 * @return {Post}
 */
Post.fromRaw = function (raw) {
  var post = new Post(raw.id);
  post.from = raw.from ? raw.from.id : '0';
  post.message = raw.message || '';
  return post;
};

/**
 * Determines if the poster was a group moderator.
 * @returns {boolean}
 */
Post.prototype.isMod = function () {
  return modIDs.indexOf(this.from) !== -1;
};

/**
 * Determines if the post contains a moderative command. Commands begin with a
 * forward slash, followed by any number of alphanumeric characters.
 * @param {string} cmd
 * @returns {boolean}
 */
Post.prototype.hasCommand = function (cmd) {
  return this.message.match('^/' + cmd + '(?: |$)');
};

Fix message processing for sticker comments.


var _ = require("lodash");
var EventEmitter = require("events").EventEmitter;
var util = require("util");
var ItemCollection = require("./itemcollection");

function Deck(options) {
  if (!(this instanceof Deck)) { return new Deck(options); }
  EventEmitter.call(this);

  this.itemCollection = options.itemCollection || new ItemCollection(options.items || []);
  this.itemRenderer = options.itemRenderer;

  this.setViewport(options.viewport); // use default
  this.setLayout(options.layout); // use standard grid layout
}

util.inherits(Deck, EventEmitter);

_.extend(Deck.prototype, {

  addItem: function(item) {
    this.itemCollection.addItem(item);
  },

  addItems: function(items) {
    this.itemCollection.addItems(items);
  },

  removeItem: function(item) {
    this.itemCollection.removeItem(item);
  },

  clear: function() {
    this.itemCollection.clear();
  },

  setViewport: function(viewport) {
    if (this.viewport) {
      // TODO: unbind viewport events?
    }
    this.viewport = viewport;
    this.viewport.setItemRenderer(this.itemRenderer);

    // TODO: set viewport on current layout
  },

  setLayout: function(layout) {
    if (this.layout) {
      this.layout.unbindCollectionEvents();
      this.layout.unbindViewportEvents();
    }
    this.layout = layout;
    this.layout.setItemCollection(this.itemCollection);
    this.layout.setViewport(this.viewport);
  }

});

module.exports = Deck;

var _ = require("lodash");
var EventEmitter = require("events").EventEmitter;
var util = require("util");
var ItemCollection = require("./itemcollection");
var ItemRenderer = require("./itemrenderer");

function Deck(options) {
  if (!(this instanceof Deck)) { return new Deck(options); }
  EventEmitter.call(this);

  this.itemCollection = options.itemCollection || new ItemCollection(options.items || []);
  this.itemRenderer = new ItemRenderer(options.itemRenderer);

  this.setViewport(options.viewport); // use default
  this.setLayout(options.layout); // use standard grid layout
}

util.inherits(Deck, EventEmitter);

_.extend(Deck.prototype, {

  addItem: function(item) {
    this.itemCollection.addItem(item);
  },

  addItems: function(items) {
    this.itemCollection.addItems(items);
  },

  removeItem: function(item) {
    this.itemCollection.removeItem(item);
  },

  clear: function() {
    this.itemCollection.clear();
  },

  setViewport: function(viewport) {
    if (this.viewport) {
      // TODO: unbind viewport events?
    }
    this.viewport = viewport;
    this.viewport.setItemRenderer(this.itemRenderer);

    // TODO: set viewport on current layout
  },

  setLayout: function(layout) {
    if (this.layout) {
      this.layout.unbindCollectionEvents();
      this.layout.unbindViewportEvents();
    }
    this.layout = layout;
    this.layout.setItemCollection(this.itemCollection);
    this.layout.setViewport(this.viewport);
  }

});

module.exports = Deck;

Create ItemRenderer from options passed to Deck


'use strict';

var shell = require('shelljs');
var cmd = require('./cmd');

// Execute msbuild.exe with passed arguments
module.exports = function exec(args) {
    process.exit(shell.exec(cmd(args)).code);
}

'use strict';

var shell = require('shelljs');
var cmd = require('./cmd');

// Execute nuget.exe with passed arguments
module.exports = function exec(args) {
    const result = shell.exec(cmd(args)).code;
    if (result !== 0) {
        console.log();
        console.log(`NuGet failed. ERRORLEVEL '${result}'.'`)
        process.exit(result);
    }
    console.log('NuGet completed successfully.');
}

Fix handling of NuGet errors


'use babel'
const FS = require('fs')
const Path = require('path')
const {View} = require('./view')
const callsite = require('callsite')
import {guessName, installPackages, packagesToInstall} from './helpers'

// Renamed for backward compatibility
if (typeof window.__steelbrain_package_deps === 'undefined') {
  window.__steelbrain_package_deps = new Set()
}

export function install(name = null, enablePackages = false) {
  if (!name) {
    name = guessName(require('callsite')()[1].getFileName())
  }
  const {toInstall, toEnable} = packagesToInstall(name)
  let promise = Promise.resolve()

  if (enablePackages && toEnable.length) {
    promise = toEnable.reduce(function(promise, name) {
      atom.packages.enablePackage(name)
      return atom.packages.activatePackage(name)
    }, promise)
  }
  if (toInstall.length) {
    const view = new View(name, toInstall)
    promise = Promise.all([view.show(), promise]).then(function() {
      return installPackages(toInstall, function(name, status) {
        if (status) {
          view.advance()
        } else {
          atom.notifications.addError(`Error Installing ${name}`, {detail: 'Something went wrong. Try installing this package manually.'})
        }
      })
    })
  }

  return promise
}

'use babel'
const FS = require('fs')
const Path = require('path')
const {View} = require('./view')
const callsite = require('callsite')
import {guessName, installPackages, packagesToInstall} from './helpers'

// Renamed for backward compatibility
if (typeof window.__steelbrain_package_deps === 'undefined') {
  window.__steelbrain_package_deps = new Set()
}

export function install(name = null, enablePackages = false) {
  if (!name) {
    const filePath = require('callsite')()[1].getFileName()
    name = guessName(filePath)
    if (!name) {
      console.log(`Unable to get package name for file: ${filePath}`)
      return Promise.resolve()
    }
  }
  const {toInstall, toEnable} = packagesToInstall(name)
  let promise = Promise.resolve()

  if (enablePackages && toEnable.length) {
    promise = toEnable.reduce(function(promise, name) {
      atom.packages.enablePackage(name)
      return atom.packages.activatePackage(name)
    }, promise)
  }
  if (toInstall.length) {
    const view = new View(name, toInstall)
    promise = Promise.all([view.show(), promise]).then(function() {
      return installPackages(toInstall, function(name, status) {
        if (status) {
          view.advance()
        } else {
          atom.notifications.addError(`Error Installing ${name}`, {detail: 'Something went wrong. Try installing this package manually.'})
        }
      })
    })
  }

  return promise
}

Handle failure in name guessing


"use strict";

var Class = require("./Class");

var CompileError = exports.CompileError = Class.extend({

	initialize: function () {
		switch (arguments.length) {
		case 2: // token, text
			this._filename = arguments[0].filename;
			this._pos = arguments[0].pos;
			this._message = arguments[1];
			break;
		case 3: // filename, pos, text
			this._filename = arguments[0];
			this._pos = arguments[1];
			this._message = arguments[2];
		default:
			throw new Error();
		}
	},

	getFilename: function () {
		return this._filename;
	},

	getPosition: function () {
		return this._pos;
	},

	toString: function () {
		return this._filename + "(" + this._pos + "):" + this._message;
	}

});

var Util = exports.Util = Class.extend({

	$serializeArray: function (a) {
		if (a == null)
			return null;
		var ret = [];
		for (var i = 0; i < a.length; ++i)
			ret[i] = a[i].serialize();
		return ret;
	},

	$serializeNullable: function (v) {
		if (v == null)
			return null;
		return v.serialize();
	}

});

"use strict";

var Class = require("./Class");

var CompileError = exports.CompileError = Class.extend({

	initialize: function () {
		switch (arguments.length) {
		case 2: // token, text
			this._filename = arguments[0].filename;
			this._pos = arguments[0].pos;
			this._message = arguments[1];
			break;
		case 3: // filename, pos, text
			this._filename = arguments[0];
			this._pos = arguments[1];
			this._message = arguments[2];
			break;
		default:
			throw new Error("Unrecognized arguments for CompileError: " + JSON.stringify( Array.prototype.slice.call(arguments) ));
		}
	},

	getFilename: function () {
		return this._filename;
	},

	getPosition: function () {
		return this._pos;
	},

	toString: function () {
		return this._filename + "(" + this._pos + "):" + this._message;
	}

});

var Util = exports.Util = Class.extend({

	$serializeArray: function (a) {
		if (a == null)
			return null;
		var ret = [];
		for (var i = 0; i < a.length; ++i)
			ret[i] = a[i].serialize();
		return ret;
	},

	$serializeNullable: function (v) {
		if (v == null)
			return null;
		return v.serialize();
	}

});
// vim: set noexpandtab:

Fix error handling in lexer


import { expect } from 'chai';
import Immutable from 'immutable';
import { createReducer } from 'rook/lib/redux/createStore';
import reducers from '../modules';
const reducer = createReducer(reducers.random);

describe('redux', () => {
  describe('reducers', () => {
    describe('random', () => {
      const initialState = Immutable.fromJS({});

      it('handles loadOk', () => {
        const curDate = new Date().toString();
        const newState = reducer(initialState, {
          type: 'random/loadOk',
          result: {
            number: 0.5,
            time: curDate
          }
        });

        expect(newState.toJS()).to.deep.equal({
          number: 0.5,
          time: curDate
        });
      });
    });
  });
});

import { expect } from 'chai';
import Immutable from 'immutable';
import createReducer from 'rook/lib/redux/createReducer';
import reducers from '../modules';
const reducer = createReducer(reducers.random);

describe('redux', () => {
  describe('reducers', () => {
    describe('random', () => {
      const initialState = Immutable.fromJS({});

      it('handles loadOk', () => {
        const curDate = new Date().toString();
        const newState = reducer(initialState, {
          type: 'random/loadOk',
          result: {
            number: 0.5,
            time: curDate
          }
        });

        expect(newState.toJS()).to.deep.equal({
          number: 0.5,
          time: curDate
        });
      });
    });
  });
});

Update path to createReducer function


export default Ember.TextField.extend(Ember.TargetActionSupport, {
    change: function() {
        this.triggerAction({
            action: 'search'
        });
    },

    insertNewline: function() {
        this.triggerAction({
            action: 'search'
        });
    }
});

export default Ember.TextField.extend(Ember.TargetActionSupport, {
    change: function() {
        this.triggerAction({
            action: 'search'
        });
    }
});

Fix for search not working when pressing the enter key.


'use strict';

let nock   = require('nock');
let cmd    = require('../../../commands/domains');
let expect = require('chai').expect;

describe('domains', function() {
  beforeEach(() => cli.mockConsole());

  it('shows the domains', function() {
    let api = nock('https://api.heroku.com:443')
      .get('/apps/myapp/domains')
      .reply(200, [
        {"cname": "myapp.com", "hostname": "myapp.com", "kind": "custom"},
        {"cname": null, "hostname": "myapp.herokuapp.com", "kind": "heroku"},
      ]);
    return cmd.run({app: 'myapp', flags: {}})
    .then(() => expect(cli.stdout).to.equal(`=== myapp Heroku Domain
myapp.herokuapp.com

=== myapp Custom Domains
Domain Name  DNS Target
───────────  ──────────
myapp.com    myapp.com
`))
    .then(() => api.done());
  });
});

Fix domains test after pull & npm install


var d3 = window.d3;

var previousD3ma = window.d3.ma;

var d3ma = d3.ma = {};

d3ma.noConflict = function() {
	window.d3ma= previousD3ma;
	return d3ma;
};

d3ma.assert = function(test, message) {
	if(test) { return; }
	throw new Error('[d3.ma] ' + message);
};

d3ma.assert(d3, 'd3.js is required');

d3ma.assert( typeof d3.version === 'string' && d3.version.match(/^3/), 'd3.js version 3 is required' );

d3.ma.version = '0.1.0';

var d3 = window.d3;

var previousD3ma = window.d3.ma;

var d3ma = d3.ma = {};

d3ma.noConflict = function() {
	window.d3ma= previousD3ma;
	return d3ma;
};

d3ma.assert = function(test, message) {
	if(test) { return; }
	throw new Error('[d3.ma] ' + message);
};

d3ma.assert(d3, 'd3.js is required');

d3ma.assert( typeof d3.version === 'string' && d3.version.match(/^3/), 'd3.js version 3 is required' );

d3ma.version = '0.1.0';

Update the d3ma var, make it more global like d3.ma


import React from 'react';

import { makeCourseStatusDisplay } from '../util/util';

class CourseList extends React.Component {
  render() {
    const { dashboard, courseList } = this.props;
    let dashboardLookup = {};
    for (let course of dashboard.courses) {
      dashboardLookup[course.id] = course;
    }

    let tables = courseList.programList.map(program => {
      let courses = courseList.courseList.
        filter(course => course.program === program.id).
        map(course => dashboardLookup[course.id]).
        filter(course => course);

      return

{program.title}

        {
          courses.map(course =>

              {course.title}

              {makeCourseStatusDisplay(course)}

          )
        }

;
    });

    return

      {tables}

;
  }
}

CourseList.propTypes = {
  courseList: React.PropTypes.object.isRequired,
  dashboard: React.PropTypes.object.isRequired,
};

export default CourseList;

import React from 'react';
import _ from 'lodash';

import { makeCourseStatusDisplay } from '../util/util';

class CourseList extends React.Component {
  render() {
    const { dashboard } = this.props;

    let sortedCourses = _.sortBy(dashboard.courses, 'position_in_program');

    let table = sortedCourses.map(course => {
      // id number is not guaranteed to exist here so we need to use the whole
      // object to test uniqueness
      // TODO: fix this when we refactor
      return

          {course.title}

          {makeCourseStatusDisplay(course)}

;
    });

    return

      {table}

;
  }
}

CourseList.propTypes = {
  courseList: React.PropTypes.object.isRequired,
  dashboard: React.PropTypes.object.isRequired,
};

export default CourseList;

Use only information from dashboard API for dashboard display.


import React from 'react';

import createChainableTypeChecker from './utils/createChainableTypeChecker';

function elementType(props, propName, componentName, location, propFullName) {
  const propValue = props[propName];
  const propType = typeof propValue;

  if (React.isValidElement(propValue)) {
    return new Error(
      `Invalid ${location} \`${propFullName}\` of type ReactElement ` +
      `supplied to \`${componentName}\`, expected an element type (a string ` +
      'or a ReactClass).'
    );
  }

  if (propType !== 'function' && propType !== 'string') {
    return new Error(
      `Invalid ${location} \`${propFullName}\` of value \`${propValue}\` ` +
      `supplied to \`${componentName}\`, expected an element type (a string ` +
      'or a ReactClass).'
    );
  }

  return null;
}

export default createChainableTypeChecker(elementType);

import React from 'react';

import createChainableTypeChecker from './utils/createChainableTypeChecker';

function elementType(props, propName, componentName, location, propFullName) {
  const propValue = props[propName];
  const propType = typeof propValue;

  if (React.isValidElement(propValue)) {
    return new Error(
      `Invalid ${location} \`${propFullName}\` of type ReactElement ` +
      `supplied to \`${componentName}\`, expected an element type (a string ` +
      'or a ReactClass).'
    );
  }

  const isSpecial = propValue && propValue.$$typeof;
  if (propType !== 'function' && propType !== 'string' && !isSpecial) {
    return new Error(
      `Invalid ${location} \`${propFullName}\` of value \`${propValue}\` ` +
      `supplied to \`${componentName}\`, expected an element type (a string ` +
      ', component class, or function component).'
    );
  }

  return null;
}

export default createChainableTypeChecker(elementType);

Update for new React types, context, forwardRef, etc


require('dotenv').config();

export const sendSMS = (message) => {

  const formData = new FormData()

  formData.append('body', encodeURIComponent(message))

  formData.append('to', encodeURIComponent(process.env.TO_PHONE))

  formData.append('from', encodeURIComponent(process.env.FROM_PHONE))

  const options = {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
      'Authorization': 'Basic ' + encodeURIComponent(process.env.SID + ':' + process.env.TOKEN)
    },
    body: formData
  }

  fetch(
    'https://api.twilio.com/2010-04-01/Accounts/' + process.env.SID + '/Messages.json',
    options
  ).then((response) => {
    //Parse JSON response
    console.log(response.json())
  })

}

require('dotenv').config()
const twilio = require('twilio')
const client = new twilio(process.env.SID, process.env.TOKEN)

const sendSMS = (message) => {

  client.api.messages.create({
    body: message,
    to: process.env.TO_PHONE,
    from: process.env.FROM_PHONE
  })
  .then((mes) => {
    console.log(mes.sid)
  })
  .catch((err) => {
    console.log(err)
  })

}

module.exports = sendSMS

Add sendSMS method and confirm that it works


import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {
  render() {
    return (

Welcome to React

          To get started, edit src/App.js and save to reload.

    );
  }
}

var tw = 1500000;

function getTimeRemaining(consumed = 0) {
  var t = tw - consumed;
  tw = t;
  var minute = Math.floor((t/1000/60) % 60);
  var seconds = Math.floor((t/1000) % 60);
  return {
    minute, seconds
  };
}

function initializedClock() {
  var timeInterval = setInterval(() => {
    var timex = getTimeRemaining(1000);
    console.log(timex.minute,' : ',timex.seconds);
    if (timex.t <= 0) {
      clearInterval(timeInterval);
    }
  }, 1000);
}

initializedClock();

export default App;

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {
  render() {
    return (

Welcome to React

          To get started, edit src/App.js and save to reload.

    );
  }
}

var tw = 1500000;

function getTimeRemaining(consumed = 0) {
  var t = tw - consumed;
  tw = t;
  var minute = Math.floor((t/1000/60) % 60);
  var seconds = Math.floor((t/1000) % 60);
  return {
    minute, seconds
  };
}

function initializedClock() {
  var timeInterval = setInterval(() => {
    var timex = getTimeRemaining(1000);
    console.log(timex.minute,' : ',('0' + timex.seconds).slice(-2));
    if (timex.t <= 0) {
      clearInterval(timeInterval);
    }
  }, 1000);
}

initializedClock();

export default App;

Add leading to zeros to single digit


var JWTHelper = require('./jwt-helper');
var JWTConfig = require('./jwt-config');

var JWTRequest = {
    setAuthorizationHeader(options, token) {
        if (!options.headers) options.headers = {};
        options.headers[JWTConfig.authHeader] = `${JWTConfig.authPrefix} ${token}`;

        return options;
    },

    handleUnAuthorizedFetch(url, options) {
        return fetch(url, options).then((response) => response.json());
    },

    handleAuthorizedFetch(url, options) {
        return new Promise((resolve, reject) => {
            JWTHelper.getToken().then((token) => {
                if (token && !JWTHelper.isTokenExpired(token)) {
                    options = this.setAuthorizationHeader(options, token);
                    fetch(url, options).then((response) => {
                        resolve(response.json())
                    });
                } else {
                    reject('Token is either not valid or has expired.');
                }
            })
        })
    },

    fetch(url, options, skipAuthorization) {
        options = options || {};

        if (skipAuthorization) {
            return this.handleUnAuthorizedFetch(url, options);
        } else {
            return this.handleAuthorizedFetch(url, options);
        }
    }
};

module.exports = JWTRequest;

var JWTHelper = require('./jwt-helper');
var JWTConfig = require('./jwt-config');

var JWTRequest = {
    setAuthorizationHeader(options, token) {
        if (!options.headers) options.headers = {};
        options.headers[JWTConfig.authHeader] = `${JWTConfig.authPrefix} ${token}`;

        return options;
    },

    handleUnAuthorizedFetch(url, options) {
        return fetch(url, options).then((response) => response.json());
    },

    handleAuthorizedFetch(url, options) {
        return new Promise((resolve, reject) => {
            JWTHelper.getToken().then((token) => {
                if (token && !JWTHelper.isTokenExpired(token)) {
                    options = this.setAuthorizationHeader(options, token);
                    fetch(url, options).then((response) => {
                        resolve(response.json())
                    });
                } else {
                    reject('Token is either not valid or has expired.');
                }
            })
        })
    },

    fetch(url, options) {
        options = options || {};

        if (options.skipAuthorization) {
            return this.handleUnAuthorizedFetch(url, options);
        } else {
            return this.handleAuthorizedFetch(url, options);
        }
    }
};

module.exports = JWTRequest;

Add skipAuthorization to options instead of a variable in our method


'use strict';

const yarpm = require('../lib');

const argv = process.argv.slice(2);

exports.run = function run(options) {
  yarpm(argv, Object.assign({stdin: process.stdin, stdout: process.stdout, stderr: process.stderr}, options || {}))
  // Not sure why, but sometimes the process never exits on Git Bash (MINGW64)
    .then(({code}) => process.exit(code))
    .catch(err => {
      console.error(err);
      process.exit(1);
    });
};

'use strict';

const yarpm = require('../lib');

const argv = process.argv.slice(2);

exports.run = function run(options) {
  yarpm(argv, Object.assign({stdin: process.stdin, stdout: process.stdout, stderr: process.stderr}, options || {}))
  // Not sure why, but sometimes the process never exits on Git Bash (MINGW64)
    .then((res) => process.exit(res.code))
    .catch(err => {
      console.error(err);
      process.exit(1);
    });
};

Make it work on Node 4


'use strict';

// Directive for using http://www.jacklmoore.com/autosize/
angular.module('Teem')
  .directive('textfit',[
  '$timeout',
  function($timeout) {
    return {
      link: function(scope, element, attrs) {
        textFit(element);

        scope.$watch(attrs.ngBind, function() {
         $timeout(() => {
           textFit(element);
         }, 1000); // There should be a better way to do this :-(
       });
      }
    };
  }]);

'use strict';

// Directive for using http://www.jacklmoore.com/autosize/
angular.module('Teem')
  .directive('textfit',[
  '$timeout',
  function($timeout) {
    return {
      link: function(scope, element, attrs) {
        scope.$watch(attrs.ngBind, function() {
         $timeout(() => {
           textFit(element);
         }, 1000); // There should be a better way to do this :-(
       });
      }
    };
  }]);

Remove first call to textFit


'use strict';

describe('Controller: ActionCtrl', function () {

  // load the controller's module
  beforeEach(module('nameApp'));

  var ActionCtrl,
    scope;

  // Initialize the controller and a mock scope
  beforeEach(inject(function ($controller, $rootScope) {
    scope = $rootScope.$new();
    ActionCtrl = $controller('ActionCtrl', {
      $scope: scope
    });
  }));

  it('should attach a list of awesomeThings to the scope', function () {
    expect(scope.awesomeThings.length).toBe(3);
  });
});

'use strict';

describe('Controller: ActionCtrl', function () {

  // load the controller's module
  beforeEach(module('geboHai'));

  var ActionCtrl,
    scope;

  // Initialize the controller and a mock scope
  beforeEach(inject(function ($controller, $rootScope) {
    scope = $rootScope.$new();
    ActionCtrl = $controller('ActionCtrl', {
      $scope: scope
    });
  }));

  it('should attach a list of awesomeThings to the scope', function () {
    expect(scope.awesomeThings.length).toBe(3);
  });
});

Correct app name in ActionCtrl tests


import React from 'react';
import { CircularProgress } from 'material-ui/Progress';
import Text from './Text';

const Loading = ({ message }) => (
  <div className="loading">
    <CircularProgress
      size={300}
      thickness={7}
      mode="indeterminate"
    />
    <Text>{message}</Text>
    <style jsx>{`
      .loading {
        width: 100%;
        text-align: center;
      }
    `}</style>
  </div>
);

export default Loading;

import React from 'react';
import { CircularProgress } from 'material-ui/Progress';
import Text from './Text';

const Loading = ({ message }) => (
  <div className="loading">
    <CircularProgress size={300} mode="indeterminate" />
    <Text>{message}</Text>
    <style jsx>{`
      .loading {
        width: 100%;
        text-align: center;
      }
    `}</style>
  </div>
);

export default Loading;

Remove obsolete prop from CircularProgress.


"use strict";

if (process.env.NO_UPDATE) {
  process.exit(0);
}

const path = require("path");
const fs = require("fs");
const request = require("request");

// Pin to specific version, reflecting the spec version in the readme.
//
// To get the latest commit:
// 1. Go to https://github.com/w3c/web-platform-tests/tree/master/url
// 2. Press "y" on your keyboard to get a permalink
// 3. Copy the commit hash
const commitHash = "5de931eafc9ca8d6de6a138015311812943755ac";

const sourceURL = `https://raw.githubusercontent.com/w3c/web-platform-tests/${commitHash}/url/urltestdata.json`;
const setterSourceURL = `https://raw.githubusercontent.com/w3c/web-platform-tests/${commitHash}/url/setters_tests.json`;

const targetDir = path.resolve(__dirname, "..", "test", "web-platform-tests");

request.get(sourceURL)
  .pipe(fs.createWriteStream(path.resolve(targetDir, "urltestdata.json")));

request.get(setterSourceURL)
  .pipe(fs.createWriteStream(path.resolve(targetDir, "setters_tests.json")));

"use strict";

if (process.env.NO_UPDATE) {
  process.exit(0);
}

const path = require("path");
const fs = require("fs");
const request = require("request");

// Pin to specific version, reflecting the spec version in the readme.
//
// To get the latest commit:
// 1. Go to https://github.com/w3c/web-platform-tests/tree/master/url
// 2. Press "y" on your keyboard to get a permalink
// 3. Copy the commit hash
const commitHash = "634175d64d1f3ec26e2a674b294e71738624c77c";

const sourceURL = `https://raw.githubusercontent.com/w3c/web-platform-tests/${commitHash}/url/urltestdata.json`;
const setterSourceURL = `https://raw.githubusercontent.com/w3c/web-platform-tests/${commitHash}/url/setters_tests.json`;

const targetDir = path.resolve(__dirname, "..", "test", "web-platform-tests");

request.get(sourceURL)
  .pipe(fs.createWriteStream(path.resolve(targetDir, "urltestdata.json")));

request.get(setterSourceURL)
  .pipe(fs.createWriteStream(path.resolve(targetDir, "setters_tests.json")));

Update to include latest web platform tests


const router = require('express').Router()
const nonUser = require('./non-user')
const user = require('./user-only')

router.use((req, res, next) => {
  let loggedIn = false
  if (req.session.user) {
    loggedIn = true
  }
  res.locals = {loggedIn}
  next()
})

router.use('/', nonUser)
router.use('/', user)

module.exports = router

const router = require('express').Router()
const nonUser = require('./non-user')
const user = require('./user-only')

router.use((req, res, next) => {
  let loggedIn = false
  let userId = null
  if (req.session.user) {
    loggedIn = true
    userId = req.session.user.user_id
  }
  res.locals = {loggedIn, userId}
  console.log(res.locals);
  next()
})

router.use('/', nonUser)
router.use('/', user)

module.exports = router

Add user id of logged in user to res.locals


/*global jQuery:false*/
(function($) {
    function equalize() {
        var elements = $('.left, .right');
        var max_height = Math.max.apply(
            null,
            elements.map(function(index, elt) {
                return $(elt).height();
            })
        );
        elements.each(function(index, elt) {
            var last_child = $('section', elt).last()[0];

            $(last_child).css({});
            $(elt).height(max_height);

            var parent_bbox = elt.getBoundingClientRect();
            var last_child_bbox = last_child.getBoundingClientRect();
            var d = parent_bbox.bottom - last_child_bbox.bottom;

            $(last_child).height(last_child_bbox.height + d);
        });
    }
    $(window)
        .load(equalize)
        .on('resize', equalize);
})(jQuery);

/*global jQuery:false*/
(function($) {
    function equalize() {
        var elements = $('.left, .right');
        var max_height = Math.max.apply(
            null,
            elements.map(function(index, elt) {
                return $(elt).height();
            })
        );
        elements.each(function(index, elt) {
            var last_child = $('section', elt).last()[0];

            $(last_child).css({});
            $(elt).height(max_height);

            var parent_bbox = elt.getBoundingClientRect();
            var last_child_bbox = last_child.getBoundingClientRect();
            var d = parent_bbox.bottom - last_child_bbox.bottom;

            $(last_child).height(last_child_bbox.height + d);
        });
    }

    function before_print() {
        $('.left, .right').each(function(index, elt) {
            $(elt).add($('section', elt).last()[0]).removeAttr('style');
        });
    }

    $(window)
        .load(equalize)
        .on('resize', equalize);

    if (window.matchMedia) {
        window.matchMedia('print').addListener(function(mql) {
            if (mql.matches) {
                before_print();
            } else {
                equalize();
            }
        });
    }
})(jQuery);

Disable column equalization before print.


'use strict';

var Driver = global.WebSocket || global.MozWebSocket;
if (Driver) {
	module.exports = function WebSocketBrowserDriver(url) {
		return new Driver(url);
	};
} else {
	module.exports = undefined;
}

FIX error with webpack when WebSocket not exists


// Note: You must restart bin/webpack-watcher for changes to take effect

const merge = require('webpack-merge')
const common = require('./common.js')

const { resolve } = require('path')
const { devServer, publicPath, paths } = require('./configuration.js')

module.exports = merge(common, {
  devtool: 'sourcemap',

  stats: {
    errorDetails: true
  },

  output: {
    pathinfo: true
  },

  devServer: {
    host: devServer.host,
    port: devServer.port,
    contentBase: resolve(paths.output, paths.entry),
    publicPath
  }
})

// Note: You must restart bin/webpack-watcher for changes to take effect

const merge = require('webpack-merge')
const common = require('./common.js')

const { resolve } = require('path')
const { devServer, publicPath, paths } = require('./configuration.js')

module.exports = merge(common, {
  devtool: 'sourcemap',

  stats: {
    errorDetails: true
  },

  output: {
    pathinfo: true
  },

  devServer: {
    host: devServer.host,
    port: devServer.port,
    contentBase: resolve(paths.output, paths.entry),
    proxy: {'/': 'http://localhost:9000'},
    publicPath
  }
})

Use webpack dev server as proxy to rails


const { dev_server: devServer } = require('@rails/webpacker').config;

const isProduction = process.env.NODE_ENV === 'production';
const inDevServer = process.argv.find(v => v.includes('webpack-dev-server'));
const extractCSS = !(inDevServer && (devServer && devServer.hmr)) || isProduction;

module.exports = {
  test: /\.vue(\.erb)?$/,
  use: [{
    loader: 'vue-loader',
    options: {extractCSS,
              // Enabled to prevent annotations from incurring incorrect
              // offsets due to extra whitespace in templates
              // TODO - the next Vue release introduces an even more useful
              // type of whitespace trimming: https://github.com/vuejs/vue/issues/9208#issuecomment-450012518
              preserveWhitespace: false}
  }]
};

const { dev_server: devServer } = require('@rails/webpacker').config;

const isProduction = process.env.NODE_ENV === 'production';
const inDevServer = process.argv.find(v => v.includes('webpack-dev-server'));
const extractCSS = !(inDevServer && (devServer && devServer.hmr)) || isProduction;

module.exports = {
  test: /\.vue(\.erb)?$/,
  use: [{
    loader: 'vue-loader',
    options: {extractCSS,
              // Enabled to prevent annotations from incurring incorrect
              // offsets due to extra whitespace in templates
              whitespace: 'condense'}
  }]
};

Use new Vue whitespace optimization


const MONGOOSE_CONSOLE_DEFAULT_PORT = 8080;
const PROMETHEUS_CONFIGURATION_PATH = '/configuration/prometheus.yml';

var express = require('express');
var bodyParser = require('body-parser');
var fs = require('fs');

var app = express();

var path = __dirname + '';
var port = process.env.CONSOLE_PORT || MONGOOSE_CONSOLE_DEFAULT_PORT;

app.use(express.static(path));
app.use(bodyParser.json()); // NOTE: Supporting JSON-encoded bodies
app.use(express.multipart()); // NOTE: We're saving Prometheus configuration via the server

// NOTE: Configurating server to serve index.html since during the production ...
// build Angular converts its html's to only one file.
app.get('*', function(req, res) {
    res.sendFile(path + '/index.html');
});

app.post('/savefile', function (req, res) {
    var fileName = req.body.fileName;
    var fileContent = req.body.fileContent;

    var stream = fs.createWriteStream(fileName);
    stream.once('open', function () {
        stream.write(fileContent);
        stream.end();
    });

    fs.writeFile(PROMETHEUS_CONFIGURATION_PATH, fileContent, function(err) {
        if(err) {
            return console.log(err);
        }

        console.log("Prometheus configuration has been updated.");
    });
});

app.listen(port);

const MONGOOSE_CONSOLE_DEFAULT_PORT = 8080;
const PROMETHEUS_CONFIGURATION_PATH = '/configuration/prometheus.yml';

var express = require('express');
var bodyParser = require('body-parser');
var fs = require('fs');
var cors = require('cors')

var app = express();

var path = __dirname + '';
var port = process.env.CONSOLE_PORT || MONGOOSE_CONSOLE_DEFAULT_PORT;

app.use(express.static(path));
app.use(bodyParser.json()); // NOTE: Supporting JSON-encoded bodies
app.use(express.multipart()); // NOTE: We're saving Prometheus configuration via the server
// NOTE: CORS configuration
app.use(cors())
app.options('*', cors());

// NOTE: Configurating server to serve index.html since during the production ...
// build Angular converts its html's to only one file.
app.get('*', function(req, res) {
    res.sendFile(path + '/index.html');
});

app.post('/savefile', function (req, res) {
    var fileName = req.body.fileName;
    var fileContent = req.body.fileContent;

    // var stream = fs.createWriteStream(fileName);
    // stream.once('open', function () {
    //     stream.write(fileContent);
    //     stream.end();
    // });
    console.log("File name:", fileName);
    console.log("File content: ", fileContent);

    fs.writeFile(PROMETHEUS_CONFIGURATION_PATH, fileContent, function(err) {
        if(err) {
            return console.log(err);
        }

        console.log("Prometheus configuration has been updated.");
    });
});

app.listen(port);

Add CORS to node server.


/* eslint-disable */
$('.nav-toggle').click(function (e) {
    $('.nav-menu').slideToggle(500);
});

debounce(function(){
    $(window).resize(function(){
        if($(window).width() > 720){
            $('.nav-menu').removeAttr('style');
        }
    });
}, 200);
/* eslint-enable */

/* eslint-disable */
$('.nav-toggle').click(function (e) {
    $('.nav-menu').slideToggle(500);
});

$(window).resize(function(){
    if($(window).width() > 720){
        $('.nav-menu').removeAttr('style');
    }
});
/* eslint-enable */

FIX - поправлен JS для адаптива меню


var keystone = require('keystone');
var Types = keystone.Field.Types;

/**
 * Y Model
 * ==========
 */
var Y = new keystone.List('Y');

Y.add({
	name: { type: Types.Name, required: true, index: true },
	email: { type: Types.Email, initial: true, required: true, index: true },
	password: {
		type: Types.Password,
		initial: true,
		required: true,
		hidden: true,
	},
}, 'Permissions', {
	isAdmin: { type: Boolean, label: 'Can access Keystone', index: true },
});

// Provide access to Keystone
Y.schema.virtual('canAccessKeystone').get(function () {
	return this.isAdmin;
});

/**
 * Relationships
 */
Y.relationship({ ref: 'Post', path: 'posts', refPath: 'author' });

/**
 * Registration
 */
Y.defaultColumns = 'name, email, isAdmin';
Y.register();

var keystone = require('keystone');
var Types = keystone.Field.Types;

/**
 * Y Model
 * ==========
 */
var Y = new keystone.List('Y');

Y.add({
	name: { type: Types.Name, required: true, index: true },
	email: { type: Types.Email, initial: true, required: true, index: true },
	password: {
		type: Types.Password,
		initial: true,
		required: true,
	},
}, 'Permissions', {
	isAdmin: { type: Boolean, label: 'Can access Keystone', index: true },
});

// Provide access to Keystone
Y.schema.virtual('canAccessKeystone').get(function () {
	return this.isAdmin;
});

/**
 * Relationships
 */
Y.relationship({ ref: 'Post', path: 'posts', refPath: 'author' });

/**
 * Registration
 */
Y.defaultColumns = 'name, email, isAdmin';
Y.register();

Remove password as hidden field


import React from 'react';
import Helmet from 'react-helmet';
import Hero from './Hero';

const LandingPageHero = ({ title, headline }, { modals }) => {
  return (

      <Helmet title={title} />
      <Hero
        headline={headline}
        textline={`Increase your productivity, focus on new features, and scale beyond millions of users without
          managing servers.`}
        image={

        }
      >

            Get Started for Free

            6 months free • No credit card required

      </Hero>

  );
};

LandingPageHero.defaultProps = {
  title: 'Build powerful apps in half the time | Syncano',
  headline: Build powerfulapps
in half the time
};

LandingPageHero.contextTypes = {
  modals: React.PropTypes.object
};

export default LandingPageHero;

import React from 'react';
import Helmet from 'react-helmet';
import Hero from './Hero';

const LandingPageHero = ({ title, headline }, { modals }) => {
  return (

      <Helmet title={title} />
      <Hero
        headline={headline}
        textline={`Increase your productivity, focus on new features, and scale beyond millions of users without
          managing servers.`}
        image={

        }
      >

            Get Started for Free

            6 months free • No credit card required

      </Hero>

  );
};

LandingPageHero.defaultProps = {
  title: 'Build powerful apps in half the time | Syncano',
  headline: Build powerful apps
in half the time
};

LandingPageHero.contextTypes = {
  modals: React.PropTypes.object
};

export default LandingPageHero;

Fix typo in "powerful apps"


const buble = require('rollup-plugin-buble');
const nodeResolve = require('rollup-plugin-node-resolve');
const commonJS = require('rollup-plugin-commonjs');

module.exports = {
  frameworks: ['jasmine', 'sinon'],

  files: [
    {
      pattern: '../src/**/*.js',
      watched: process.env.CI === 'true',
      included: false,
    },
    {
      pattern: '../spec/**/*.spec.js',
      watched: process.env.CI === 'true',
    },
  ],

  preprocessors: {
    '../spec/**/*.spec.js': ['rollup'],
  },

  rollupPreprocessor: {
    format: 'iife',
    sourceMap: 'inline',
    plugins: [commonJS(), nodeResolve(), buble()],
  },

  plugins: [
    'karma-jasmine',
    'karma-sinon',
    'karma-rollup-plugin',
    'karma-spec-reporter',
  ],

  reporters: ['spec'],
};

const buble = require('rollup-plugin-buble');
const nodeResolve = require('rollup-plugin-node-resolve');
const commonJS = require('rollup-plugin-commonjs');

module.exports = {
  frameworks: ['jasmine', 'sinon'],

  files: [
    {
      pattern: '../src/**/*.js',
      watched: process.env.CI !== 'true',
      included: false,
    },
    {
      pattern: '../spec/**/*.spec.js',
      watched: process.env.CI !== 'true',
    },
  ],

  preprocessors: {
    '../spec/**/*.spec.js': ['rollup'],
  },

  rollupPreprocessor: {
    format: 'iife',
    sourceMap: 'inline',
    plugins: [commonJS(), nodeResolve(), buble()],
  },

  plugins: [
    'karma-jasmine',
    'karma-sinon',
    'karma-rollup-plugin',
    'karma-spec-reporter',
  ],

  reporters: ['spec'],
};

Fix karma config. Watch files when not CI environment.


// This is the main server file.
//
// It parses the command line and instantiates the two servers for this app:
const async = require('async')
const {cyan, dim, green, red} = require('chalk')
const ExoRelay = require('exorelay');
const N = require('nitroglycerin');
const {name, version} = require('../package.json')
const WebServer = require('./web-server');

function startExorelay (done) {
  global.exorelay = new ExoRelay({serviceName: process.env.SERVICE_NAME,
                                  exocomPort: process.env.EXOCOM_PORT})
  global.exorelay.on('error', (err) => { console.log(red(err)) })
  global.exorelay.on('online', (port) => {
    console.log(`${green('ExoRelay')} online at port ${cyan(port)}`)
    done()
  })
  global.exorelay.listen(process.env.EXORELAY_PORT)
}

function startWebServer (done) {
  const webServer = new WebServer;
  webServer.on('error', (err) => { console.log(red(err)) })
  webServer.on('listening', () => {
    console.log(`${green('HTML server')} online at port ${cyan(webServer.port())}`)
    done()
  })
  webServer.listen(3000)
}

startExorelay( N( () => {
  startWebServer( N( () => {
    console.log(green('all systems go'))
  }))
}))

// This is the main server file.
//
// It parses the command line and instantiates the two servers for this app:
const async = require('async')
const {cyan, dim, green, red} = require('chalk')
const ExoRelay = require('exorelay');
const N = require('nitroglycerin');
const {name, version} = require('../package.json')
const WebServer = require('./web-server')
const port = process.env.PORT || 3000

function startExorelay (done) {
  global.exorelay = new ExoRelay({serviceName: process.env.SERVICE_NAME,
                                  exocomPort: process.env.EXOCOM_PORT})
  global.exorelay.on('error', (err) => { console.log(red(err)) })
  global.exorelay.on('online', (port) => {
    console.log(`${green('ExoRelay')} online at port ${cyan(port)}`)
    done()
  })
  global.exorelay.listen(process.env.EXORELAY_PORT)
}

function startWebServer (done) {
  const webServer = new WebServer;
  webServer.on('error', (err) => { console.log(red(err)) })
  webServer.on('listening', () => {
    console.log(`${green('HTML server')} online at port ${cyan(webServer.port())}`)
    done()
  })
  webServer.listen(port)
}

startExorelay( N( () => {
  startWebServer( N( () => {
    console.log(green('all systems go'))
  }))
}))

Make the web port configurable


// import {replace} from 'react-router-redux'

import {authReceived, meReceived} from 'shared/actions/auth'
import {publishNotification as notify} from 'shared/actions/notifications'

import {HTTP_FORBIDDEN} from 'shared/constants'

const errorsMiddleware = store => next => action => {
  if (action.type === 'ERROR_THROWN') {
    const {error, error: {status, auth}} = action

    console.error(error)

    if (status === HTTP_FORBIDDEN) {
      const {auth: {me}} = store.getState()
      const wasSessionTimeout = me === null

      store.dispatch(authReceived(auth))
      store.dispatch(meReceived(null))

      if (wasSessionTimeout) {
        store.dispatch(notify('error', 'Please login to use Chronograf.'))
      } else {
        store.dispatch(notify('error', 'Session timed out. Please login again.'))
      }
    } else {
      store.dispatch(notify('error', 'Cannot communicate with server.'))
    }
  }

  next(action)
}

export default errorsMiddleware

// import {replace} from 'react-router-redux'

import {authExpired} from 'shared/actions/auth'
import {publishNotification as notify} from 'shared/actions/notifications'

import {HTTP_FORBIDDEN} from 'shared/constants'

const errorsMiddleware = store => next => action => {
  if (action.type === 'ERROR_THROWN') {
    const {error, error: {status, auth}} = action

    console.error(error)

    if (status === HTTP_FORBIDDEN) {
      const {auth: {me}} = store.getState()
      const wasSessionTimeout = me !== null

      next(authExpired(auth))

      if (wasSessionTimeout) {
        store.dispatch(notify('error', 'Session timed out. Please login again.'))
      } else {
        store.dispatch(notify('error', 'Please login to use Chronograf.'))
      }
    } else {
      store.dispatch(notify('error', 'Cannot communicate with server.'))
    }
  }

  next(action)
}

export default errorsMiddleware

Use authExpired action; fix session timeout notification logic


/*  ============================================================================

 Copyright (C) 2006-2016 Talend Inc. - www.talend.com

 This source code is available under agreement available at
 https://github.com/Talend/data-prep/blob/master/LICENSE

 You should have received a copy of the agreement
 along with this program; if not, write to Talend SA
 9 rue Pages 92150 Suresnes, France

 ============================================================================*/

class DatasetsFiltersCtrl {
    constructor() {
        'ngInject';

        // The order is important
        this.datasetsFilters = [
            {
                value: 'RECENT_DATASETS',
                imageUrl: '/assets/images/inventory/recent-datasets.png',
                description: 'RECENT_DATASETS_DESCRIPTION',
                isSelected: true
            },
            {
                value: 'FAVORITE_DATASETS',
                icon: 'f',
                description: 'FAVORITE_DATASETS_DESCRIPTION'
            },
            {
                value: 'CERTIFIED_DATASETS',
                imageUrl: '/assets/images/inventory/certified_no_shadow.png',
                description: 'CERTIFIED_DATASETS_DESCRIPTION'
            },
            {
                value: 'ALL_DATASETS',
                imageUrl: '/assets/images/inventory/all-datasets.png',
                description: 'ALL_DATASETS_DESCRIPTION'
            }
        ];

        this.selectedFilter = this.datasetsFilters[0];
    }

    selectFilter(filter) {
        if (this.importing) {
            return;
        }
        this.selectedFilter.isSelected = false;
        this.selectedFilter = filter;
        this.selectedFilter.isSelected = true;
        this.onFilterSelect({ filter: filter.value });
    }
}

export default DatasetsFiltersCtrl;

/*  ============================================================================

 Copyright (C) 2006-2016 Talend Inc. - www.talend.com

 This source code is available under agreement available at
 https://github.com/Talend/data-prep/blob/master/LICENSE

 You should have received a copy of the agreement
 along with this program; if not, write to Talend SA
 9 rue Pages 92150 Suresnes, France

 ============================================================================*/

class DatasetsFiltersCtrl {
    constructor() {
        'ngInject';

        // The order is important
        this.datasetsFilters = [
            {
                value: 'RECENT_DATASETS',
                imageUrl: '/assets/images/inventory/recent-datasets.png',
                description: 'RECENT_DATASETS_DESCRIPTION',
                isSelected: true
            },
            {
                value: 'FAVORITE_DATASETS',
                icon: 'f',
                description: 'FAVORITE_DATASETS_DESCRIPTION'
            },
            {
                value: 'ALL_DATASETS',
                imageUrl: '/assets/images/inventory/all-datasets.png',
                description: 'ALL_DATASETS_DESCRIPTION'
            }
        ];

        this.selectedFilter = this.datasetsFilters[0];
    }

    selectFilter(filter) {
        if (this.importing) {
            return;
        }
        this.selectedFilter.isSelected = false;
        this.selectedFilter = filter;
        this.selectedFilter.isSelected = true;
        this.onFilterSelect({ filter: filter.value });
    }
}

export default DatasetsFiltersCtrl;

Fix unit test following merge of maintenance/1.2.0 changes.


// Returns a list of resources by IDs or label
export default function(state, resourceName, idsOrLabel) {
  const resourceSlice = state[resourceName];
  if (!resourceSlice) {
    return [];
  }

  const resources = resourceSlice.resources;
  let idsList;

  // This conditional handles the situation where `idsOrLabel` is an ID
  if (typeof idsOrLabel === 'string') {
    const label = resourceSlice.labels[idsOrLabel];
    if (!label) {
      return [];
    }

    const labelIds = label.ids;
    if (!labelIds) {
      return [];
    }

    idsList = labelIds;
  } else {
    idsList = idsOrLabel;
  }

  if (!(idsList && idsList.length)) {
    return [];
  }

  return idsList.map(id => resources[id]).filter(Boolean);
}

// Returns a list of resources by IDs or list name
export default function(state, resourceName, idsOrList) {
  const resourceSlice = state[resourceName];
  if (!resourceSlice) {
    return [];
  }

  const resources = resourceSlice.resources;
  let idsList;

  // This conditional handles the situation where `idsOrList` is an list name
  if (typeof idsOrList === 'string') {
    const list = resourceSlice.lists[idsOrList];
    if (!list) {
      return [];
    }

    idsList = list.ids;
  } else {
    idsList = idsOrList;
  }

  if (!(idsList && idsList.length)) {
    return [];
  }

  return idsList.map(id => resources[id]).filter(Boolean);
}

Update getResources to support lists, not labels


// Here you can add settings which change per environment. This file will be also used by the buildserver to inject environment specific values.
// Settings are made available through the configService.
var environmentSettings = {
	host: 'localhost',
	port: 8001
}

// Leave line below untouched. This how gulp file is also able to use this settings file. ('require' needs a module)
module.exports = environmentSettings;

// Here you can add settings which change per environment. This file will be also used by the buildserver to inject environment specific values.
// Settings are made available through the configService.
var environmentSettings = {
	host: 'localhost',
	port: 8001
}

// Leave lines below untouched. This how gulp file is also able to use this settings file. ('require' needs a module)
if(typeof module !== 'undefined'){
   module.exports = environmentSettings;
};

Fix for module error in console.


Ember.Adapter = Ember.Object.extend({
  find: function(record, id) {
    throw new Error('Ember.Adapter subclasses must implement find');
  },

  findQuery: function(record, id) {
    throw new Error('Ember.Adapter subclasses must implement findQuery');
  },

  findMany: function(record, id) {
    throw new Error('Ember.Adapter subclasses must implement findMany');
  },

  findAll: function(klass, records) {
    throw new Error('Ember.Adapter subclasses must implement findAll');
  },

  load: function(record, id, data) {
    record.load(id, data);
  },

  createRecord: function(record) {
    throw new Error('Ember.Adapter subclasses must implement createRecord');
  },

  saveRecord: function(record) {
    throw new Error('Ember.Adapter subclasses must implement saveRecord');
  },

  deleteRecord: function(record) {
    throw new Error('Ember.Adapter subclasses must implement deleteRecord');
  }
});

Ember.Adapter = Ember.Object.extend({
  find: function(record, id) {
    throw new Error('Ember.Adapter subclasses must implement find');
  },

  findQuery: function(klass, records, params) {
    throw new Error('Ember.Adapter subclasses must implement findQuery');
  },

  findMany: function(klass, records, ids) {
    throw new Error('Ember.Adapter subclasses must implement findMany');
  },

  findAll: function(klass, records) {
    throw new Error('Ember.Adapter subclasses must implement findAll');
  },

  load: function(record, id, data) {
    record.load(id, data);
  },

  createRecord: function(record) {
    throw new Error('Ember.Adapter subclasses must implement createRecord');
  },

  saveRecord: function(record) {
    throw new Error('Ember.Adapter subclasses must implement saveRecord');
  },

  deleteRecord: function(record) {
    throw new Error('Ember.Adapter subclasses must implement deleteRecord');
  }
});

Update Adapter to match API


'use strict';

(function() {
  angular.module('ncsaas')
    .directive('helpicon', ['$document', helpicon]);

  function helpicon($document) {
    return {
      restrict: 'E',
      templateUrl: "views/directives/help-icon.html",
      replace: true,
      scope: {
        helpText: '@'
      },
      link: function ($scope, element) {
        var trigger = element;
        var text = trigger.find('span');
        trigger.css('position', 'relative');
        trigger.bind('click', function(){
          if (!text.hasClass('active')) {
            text.addClass('active');
            text.css({
              'position': 'absolute',
              'top': -(text[0].offsetHeight + 4) + 'px',
              'left': '50%',
              'margin-left': -text[0].offsetWidth/2 + 'px'
            })
          } else {
            text.removeClass('active');
          }
        });
      }
    };
  }

})();

'use strict';

(function() {
  angular.module('ncsaas')
    .directive('helpicon', ['$document', helpicon]);

  function helpicon($document) {
    return {
      restrict: 'E',
      templateUrl: "views/directives/help-icon.html",
      replace: true,
      scope: {
        helpText: '@'
      },
      link: function (scope, element) {
        var trigger = element;
        var text = trigger.find('span');

        trigger.css('position', 'relative');
        trigger.bind('click', function(event){
          if (!text.hasClass('active')) {
            text.addClass('active');
            text.css({
              'position': 'absolute',
              'top': -(text[0].offsetHeight + 4) + 'px',
              'left': '50%',
              'margin-left': -text[0].offsetWidth/2 + 'px'
            })
          } else {
            text.removeClass('active');
          }
          event.stopPropagation();
        });
        $document.bind('click', function() {
          text.removeClass('active');
        })

      }
    };
  }

})();

Hide help text if click outside element.


'use strict';

var userData, offlineUser;
offlineUser = localStorage.getItem('offline_users');

if (offlineUser) {
  userData = JSON.parse(localStorage.getItem('offline_users-' + offlineUser));
  if (userData && userData.offlineData) {
    chrome.extension.sendMessage({
      type: 'userToken',
      apiToken: userData.offlineData.api_token
    });
  }
}

(function() {
  var version, source, s;
  source = `window.TogglButton = { version: "${process.env.VERSION}" }`;
  s = document.createElement('script');
  s.textContent = source;
  document.body.appendChild(s);
})();

document.addEventListener('webkitvisibilitychange', function() {
  if (!document.webkitHidden) {
    chrome.extension.sendMessage({ type: 'sync' }, function() {
      return;
    });
  }
});

chrome.extension.sendMessage({ type: 'sync' }, function() {
  return;
});

'use strict';

var userData, offlineUser;
offlineUser = localStorage.getItem('offline_users');

if (offlineUser) {
  userData = JSON.parse(localStorage.getItem('offline_users-' + offlineUser));
  if (userData && userData.offlineData) {
    chrome.extension.sendMessage({
      type: 'userToken',
      apiToken: userData.offlineData.api_token
    });
  }
}

(function() {
  var source, s;
  const version = chrome.runtime.getManifest().version;
  source = `window.TogglButton = { version: "${version}" }`;
  s = document.createElement('script');
  s.textContent = source;
  document.body.appendChild(s);
})();

document.addEventListener('webkitvisibilitychange', function() {
  if (!document.webkitHidden) {
    chrome.extension.sendMessage({ type: 'sync' }, function() {
      return;
    });
  }
});

chrome.extension.sendMessage({ type: 'sync' }, function() {
  return;
});

Fix process undefined in content script


var gulp = require('gulp');
//var gutil = require('gulp-util');
var fs = require('fs');
var durandal = require('gulp-durandal');
var header = require('gulp-header');
var paths = require('../paths');

gulp.task('durandal', function() {
	var pkg = JSON.parse(fs.readFileSync('./package.json', 'utf-8'));

	var banner = ['/**',
		' * Copyright (c) <%= new Date().getFullYear() %> - <%= author %>',
		' * <%= name %> - <%= description %>',
		' * @built <%= new Date().toISOString() %>',
		' * @version v<%= version %>',
		' * @link <%= homepage %>',
		' * @license <%= license %>',
		' */',
		''].join('\n');

    return durandal({
            baseDir: paths.root,
            main: 'main.js',
            output: 'main.js',
            almond: true,
            minify: true,
            rjsConfigAdapter: function(cfg) {
            	cfg.preserveLicenseComments = true;
            	cfg.generateSourceMaps = false;
	            cfg.uglify2 = {
					output: {
						beautify: false
					},
					compress: {
						sequences: false,
						global_defs: {
							DEBUG: false
						},
						drop_console: true
					},
					warnings: true,
					mangle: false
				};

            	return cfg;
            }
        })
    	.pipe(header(banner, pkg))
        .pipe(gulp.dest(paths.output + paths.root));
});

var gulp = require('gulp');
var fs = require('fs');
var durandal = require('gulp-durandal');
var header = require('gulp-header');
var paths = require('../paths');

gulp.task('durandal', function() {
	var pkg = JSON.parse(fs.readFileSync('./package.json', 'utf-8'));

	var banner = ['/**',
		' * Copyright (c) <%= new Date().getFullYear() %> - <%= author %>',
		' * <%= name %> - <%= description %>',
		' * @built <%= new Date().toISOString() %>',
		' * @version v<%= version %>',
		' * @link <%= homepage %>',
		' * @license <%= license %>',
		' */',
		''].join('\n');

    return durandal({
            baseDir: paths.root,
            main: 'main.js',
            output: 'main.js',
            almond: true,
            minify: true,
            rjsConfigAdapter: function(cfg) {
            	cfg.preserveLicenseComments = true;
            	cfg.generateSourceMaps = false;
	            cfg.uglify2 = {
					output: {
						beautify: false
					},
					compress: {
						sequences: false,
						global_defs: {
							DEBUG: false
						},
						drop_console: true
					},
					warnings: true,
					mangle: false
				};

            	return cfg;
            }
        })
    	.pipe(header(banner, pkg))
        .pipe(gulp.dest(paths.output + paths.root));
});

Add banner to built version


'use strict';

/*
 * Socket.io related things go !
 */

import { log, LOG_TYPES } from './log';
import { decodeJWT }      from './JWT';

const EVENT_TYPES = {
    DISCONNECT      : 'disconnect',
    CONNECTION      : 'connection',
    TOKEN_VALID     : 'token_valid',
    TOKEN_INVALID   : 'token_invalid',
    CONTACT_ONLINE  : 'contact:online',
    CONTACT_OFFLINE : 'contact:offline'
};

let io;

let connections = [];

function createSocketIO(server, app) {
    io = require('socket.io')(server);
    io.on(EVENT_TYPES.CONNECTION, (socket) => {

        socket.on(EVENT_TYPES.DISCONNECT, () => {
            log(`Socket disconnected with id ${socket.id}`, LOG_TYPES.WARN);

            let i = connections.indexOf(socket);
            connections.splice(i, 1);
        });

        decodeJWT(socket.handshake.query.token)
            .then( results => {
                log(`Socket connected with id ${socket.id}`);
                socket.emit('token_valid',  {});
                socket.user = results;
                connections.push(socket);

            })
            .catch(error => {
                log(`Token from ${socket.id} is invalid`, LOG_TYPES.ALERT)
                socket.emit('token_invalid', {});
                socket.disconnect(true);
            })
    });
}

export { io, connections, createSocketIO, EVENT_TYPES }

'use strict';

/*
 * Socket.io related things go !
 */

import { log, LOG_TYPES } from './log';
import { decodeJWT }      from './JWT';
import models             from '../models';

const User = models.User;

const EVENT_TYPES = {
    DISCONNECT      : 'disconnect',
    CONNECTION      : 'connection',
    TOKEN_VALID     : 'token_valid',
    TOKEN_INVALID   : 'token_invalid',
    CONTACT_ONLINE  : 'contact:online',
    CONTACT_OFFLINE : 'contact:offline'
};

let io;

let connections = [];

function createSocketIO(server, app) {
    io = require('socket.io')(server);
    io.on(EVENT_TYPES.CONNECTION, (socket) => {

        socket.on(EVENT_TYPES.DISCONNECT, () => {
            log(`Socket disconnected with id ${socket.id}`, LOG_TYPES.WARN);

            let i = connections.indexOf(socket);
            connections.splice(i, 1);
        });

        decodeJWT(socket.handshake.query.token)
            .then( results => {
                log(`Socket connected with id ${socket.id}`);
                socket.emit('token_valid',  {});
                let lastSeen = Date.now();
                results.lastSeen = lastSeen;
                socket.user = results;
                User.update({ lastSeen: lastSeen }, { where: { id: results.id } });
                connections.push(socket);
            })
            .catch(error => {
                log(`Token from ${socket.id} is invalid`, LOG_TYPES.ALERT)
                socket.emit('token_invalid', {});
                socket.disconnect(true);
            })
    });
}

export { io, connections, createSocketIO, EVENT_TYPES }

Update last seen date when connecting through socket.io


var self = require("sdk/self");
var panel = require("sdk/panel");

var initToolbar = function(freedom) {
  // create toolbarbutton
  var tbb = require("pathfinder/ui/toolbarbutton").ToolbarButton({
    id: "UProxyItem",
    label: "UProxy",
    image: self.data.url("common/ui/icons/uproxy-19.png"),
    panel: initPanel(freedom.communicator)
  });

  tbb.moveTo({
    toolbarID: "nav-bar",
    forceMove: false // only move from palette
  });
};

var initPanel = function(freedomCommunicator) {
  var l10n = JSON.parse(self.data.load("l10n/en/messages.json"));
  var uproxyPanel = panel.Panel({
    contentURL: self.data.url("common/ui/popup.html"),
    width: 450,
    height: 300
  });
  freedomCommunicator.addContentContext(uproxyPanel);
  uproxyPanel.port.on("show", function() {
    uproxyPanel.port.emit("l10n", l10n);
  });
  return uproxyPanel;
};

var freedomEnvironment = require('./init_freedom').InitFreedom();

// TODO: Remove when uproxy.js no longer uses setTimeout
// and replace with the line:
// initToolbar(freedomEnvironment);
require('sdk/timers').setTimeout(initToolbar, 20, freedomEnvironment);

var self = require("sdk/self");
var panel = require("sdk/panel");

var initToolbar = function(freedom) {
  // create toolbarbutton
  var tbb = require("pathfinder/ui/toolbarbutton").ToolbarButton({
    id: "UProxyItem",
    label: "UProxy",
    image: self.data.url("common/ui/icons/uproxy-19.png"),
    panel: initPanel(freedom.communicator)
  });

  tbb.moveTo({
    toolbarID: "nav-bar",
    forceMove: false // only move from palette
  });
};

var initPanel = function(freedomCommunicator) {
  var l10n = JSON.parse(self.data.load("l10n/en/messages.json"));
  var uproxyPanel = panel.Panel({
    contentURL: self.data.url("common/ui/popup.html"),
    width: 450,
    height: 300
  });
  freedomCommunicator.addContentContext(uproxyPanel);
  uproxyPanel.port.on("show", function() {
    uproxyPanel.port.emit("l10n", l10n);
  });
  return uproxyPanel;
};

var freedomEnvironment = require('./init_freedom').InitFreedom();

// TODO: Remove when uproxy.js no longer uses setTimeout
// and replace with the line:
// initToolbar(freedomEnvironment);
require('sdk/timers').setTimeout(initToolbar, 500, freedomEnvironment);

Increase delay for loading toolbarbutton so that other components have a chance to load.


import R from 'ramda';

// a -> a
const spaceCase = R.compose(
  R.trim,
  R.replace(/([a-z])([A-Z])/g, '$1 $2'),
  R.replace(/(\.|-|_)/g, ' ')
);

export default spaceCase;

import R from 'ramda';
import compose from './util/compose.js';

// a -> a
const spaceCase = compose(
  R.trim,
  R.replace(/([a-z])([A-Z])/g, '$1 $2'),
  R.replace(/(\.|-|_)/g, ' ')
);

export default spaceCase;

Refactor spaceCase function to use custom compose function


import omit from 'lodash.omit';
import pick from 'lodash.pick';

const boxProps = [
  'alignContent',
  'alignItems',
  'alignSelf',
  'borderWidth',
  'borderBottomWidth',
  'borderColor',
  'borderLeftWidth',
  'borderRightWidth',
  'borderTint',
  'borderTopWidth',
  'boxSizing',
  'className',
  'display',
  'element',
  'flex',
  'flexBasis',
  'flexDirection',
  'flexGrow',
  'flexShrink',
  'flexWrap',
  'justifyContent',
  'margin',
  'marginHorizontal',
  'marginVertical',
  'marginBottom',
  'marginLeft',
  'marginRight',
  'marginTop',
  'order',
  'overflow',
  'overflowX',
  'overflowY',
  'padding',
  'paddingHorizontal',
  'paddingVertical',
  'paddingBottom',
  'paddingLeft',
  'paddingRight',
  'paddingTop',
  'textAlign',
];

const omitBoxProps = props => omit(props, boxProps);
const pickBoxProps = props => pick(props, boxProps);

export default boxProps;
export { omitBoxProps, pickBoxProps };

import omit from 'lodash.omit';
import pick from 'lodash.pick';

const boxProps = [
  'alignContent',
  'alignItems',
  'alignSelf',
  'borderWidth',
  'borderBottomWidth',
  'borderColor',
  'borderLeftWidth',
  'borderRightWidth',
  'borderTint',
  'borderTopWidth',
  'borderRadius',
  'boxSizing',
  'className',
  'display',
  'element',
  'flex',
  'flexBasis',
  'flexDirection',
  'flexGrow',
  'flexShrink',
  'flexWrap',
  'justifyContent',
  'margin',
  'marginHorizontal',
  'marginVertical',
  'marginBottom',
  'marginLeft',
  'marginRight',
  'marginTop',
  'order',
  'overflow',
  'overflowX',
  'overflowY',
  'padding',
  'paddingHorizontal',
  'paddingVertical',
  'paddingBottom',
  'paddingLeft',
  'paddingRight',
  'paddingTop',
  'textAlign',
];

const omitBoxProps = props => omit(props, boxProps);
const pickBoxProps = props => pick(props, boxProps);

export default boxProps;
export { omitBoxProps, pickBoxProps };

Add borderRadius to the box prop list