define(function(require) {
    'use strict';

    var $ = require('jquery');
    require('bootstrap');

    /**
     * This customization allows to define own focus, click, render, show, lookup functions for Typeahead
     */
    var Typeahead;
    var origTypeahead = $.fn.typeahead.Constructor;
    var origFnTypeahead = $.fn.typeahead;

    Typeahead = function(element, options) {
        var opts = $.extend({}, $.fn.typeahead.defaults, options);
        this.focus = opts.focus || this.focus;
        this.render = opts.render || this.render;
        this.show = opts.show || this.show;
        this.lookup = opts.lookup || this.lookup;
        origTypeahead.apply(this, arguments);
    };

    Typeahead.prototype = origTypeahead.prototype;
    Typeahead.prototype.constructor = Typeahead;

    $.fn.typeahead = function(option) {
        return this.each(function() {
            var $this = $(this);
            var data = $this.data('typeahead');
            var options = typeof option === 'object' && option;
            if (!data) {
                $this.data('typeahead', (data = new Typeahead(this, options)));
            }
            if (typeof option === 'string') {
                data[option]();
            }
        });
    };

    $.fn.typeahead.defaults = origFnTypeahead.defaults;
    $.fn.typeahead.Constructor = Typeahead;
    $.fn.typeahead.noConflict = origFnTypeahead.noConflict;
});

define(function(require) {
    'use strict';

    var $ = require('jquery');
    require('bootstrap');

    /**
     * This customization allows to define own functions for Typeahead
     */
    var Typeahead;
    var origTypeahead = $.fn.typeahead.Constructor;
    var origFnTypeahead = $.fn.typeahead;

    Typeahead = function(element, options) {
        var _this = this;
        var opts = $.extend({}, $.fn.typeahead.defaults, options);

        _.each(opts, function(value, name) {
            _this[name] = value || _this[name];
        });

        origTypeahead.apply(this, arguments);
    };

    Typeahead.prototype = origTypeahead.prototype;
    Typeahead.prototype.constructor = Typeahead;

    $.fn.typeahead = function(option) {
        return this.each(function() {
            var $this = $(this);
            var data = $this.data('typeahead');
            var options = typeof option === 'object' && option;
            if (!data) {
                $this.data('typeahead', (data = new Typeahead(this, options)));
            }
            if (typeof option === 'string') {
                data[option]();
            }
        });
    };

    $.fn.typeahead.defaults = origFnTypeahead.defaults;
    $.fn.typeahead.Constructor = Typeahead;
    $.fn.typeahead.noConflict = origFnTypeahead.noConflict;
});

Add validation functionality to RuleEditorComponent - temp commit (polish of suggestion output)


const assert = require('assert')
const Application = require('spectron').Application

describe('application launch', function () {
  this.timeout(10000)

  beforeEach(function () {
    this.app = new Application({
      path: __dirname + '/../node_modules/.bin/electron',
      args: [__dirname + '/../app/main/index.js']
    })
    return this.app.start()
  })

  afterEach(function () {
    if (this.app && this.app.isRunning()) {
      return this.app.stop()
    }
  })

  it('shows an initial window', function () {
    return this.app.client.getWindowCount().then(function (count) {
      assert.equal(count, 1)
    })
  })
})

const assert = require('assert')
const Application = require('spectron').Application

describe('application launch', function () {
  this.timeout(10000)

  beforeEach(function () {
    this.app = new Application({
      path: require('electron'),
      args: [__dirname + '/../app/main/index.js']
    })
    return this.app.start()
  })

  afterEach(function () {
    if (this.app && this.app.isRunning()) {
      return this.app.stop()
    }
  })

  it('shows an initial window', function () {
    return this.app.client.getWindowCount().then(function (count) {
      assert.equal(count, 1)
    })
  })
})

Use the standard method to get the electron app location


import path from 'path';
import HtmlWebpackPlugin from 'webpack-html-plugin';
import webpack from 'webpack';

const array = (target) => target.filter((item) => item);

export default ({dev, prod}) => ({
    entry: array([
        dev && 'react-hot-loader/patch',
        'babel-polyfill',
        './src/',
    ]),
    output: {
        path: path.resolve(__dirname, 'dist'),
        publicPath: '/',
        filename: 'bundle.js',
    },
    plugins: array([
        new HtmlWebpackPlugin({
            template: 'src/index.html',
            inject: true,
        }),
        dev && new webpack.NoErrorsPlugin(),
    ]),
    module: {
        rules: [
            {
                test: /\.js$/,
                use: 'babel-loader',
                exclude: '/node_modules/',
                options: {
                    presets: ['es2015', 'react'],
                    modules: false,
                },
            },
            {
                test: /\.less$/,
                use: [
                    {loader: 'style-loader'},
                    {loader: 'css-loader'},
                    {loader: 'less-loader'},
                ],
            },
        ],
    },
});

import path from 'path';
import HtmlWebpackPlugin from 'webpack-html-plugin';
import webpack from 'webpack';

/** removes empty items from array */
const array = (target) => target.filter((item) => item);

/** removes empty properties from object */
const object = (target) => Object.keys(target).filter((key) => target[key]).reduce((result, key) => Object.assign({[key]: target[key]}, result), {});

export default ({dev, prod}) => ({
    entry: array([
        dev && 'react-hot-loader/patch',
        'babel-polyfill',
        './src/',
    ]),
    output: {
        path: path.resolve(__dirname, 'dist'),
        publicPath: '/',
        filename: 'bundle.js',
    },
    plugins: array([
        new HtmlWebpackPlugin({
            template: 'src/index.html',
            inject: true,
        }),
        dev && new webpack.NoErrorsPlugin(),
    ]),
    module: {
        rules: [
            {
                test: /\.js$/,
                use: 'babel-loader',
                exclude: '/node_modules/',
                options: {
                    presets: ['es2015', 'react'],
                    modules: false,
                },
            },
            {
                test: /\.less$/,
                use: [
                    {loader: 'style-loader'},
                    {loader: 'css-loader'},
                    {loader: 'less-loader'},
                ],
            },
        ],
    },
});

Remove empty properties from object


const rewrite     = require('connect-modrewrite');
const serveIndex  = require('serve-index');
const serveStatic = require('serve-static');

module.exports = function (grunt) {
    return {
        livereload: {
            options: {
                hostname  : '127.0.0.1',
                port      : 443,
                protocol  : 'https',
                base      : 'dist',
                open      : 'https://localhost.localdomain',
                middleware: (connect, options) => {
                    const middlewares = [
                        require('connect-livereload')(),
                    ];

                    const rules = [
                        '^/binary-static/(.*)$ /$1',
                    ];
                    middlewares.push(rewrite(rules));

                    if (!Array.isArray(options.base)) {
                        options.base = [options.base];
                    }

                    options.base.forEach((base) => {
                        middlewares.push(serveStatic(base));
                    });

                    const directory = options.directory || options.base[options.base.length - 1];
                    middlewares.push(serveIndex(directory));

                    middlewares.push((req, res) => {
                        const path_404 = `${options.base[0]}/404.html`;
                        if (grunt.file.exists(path_404)) {
                            require('fs').createReadStream(path_404).pipe(res);
                            return;
                        }
                        res.statusCode(404); // 404.html not found
                        res.end();
                    });

                    return middlewares;
                }
            }
        },
    };
};

const rewrite     = require('connect-modrewrite');
const serveIndex  = require('serve-index');
const serveStatic = require('serve-static');

module.exports = function (grunt) {
    return {
        livereload: {
            options: {
                hostname  : '0.0.0.0',
                port      : 443,
                protocol  : 'https',
                base      : 'dist',
                open      : 'https://localhost.localdomain',
                middleware: (connect, options) => {
                    const middlewares = [
                        require('connect-livereload')(),
                    ];

                    const rules = [
                        '^/binary-static/(.*)$ /$1',
                    ];
                    middlewares.push(rewrite(rules));

                    if (!Array.isArray(options.base)) {
                        options.base = [options.base];
                    }

                    options.base.forEach((base) => {
                        middlewares.push(serveStatic(base));
                    });

                    const directory = options.directory || options.base[options.base.length - 1];
                    middlewares.push(serveIndex(directory));

                    middlewares.push((req, res) => {
                        const path_404 = `${options.base[0]}/404.html`;
                        if (grunt.file.exists(path_404)) {
                            require('fs').createReadStream(path_404).pipe(res);
                            return;
                        }
                        res.statusCode(404); // 404.html not found
                        res.end();
                    });

                    return middlewares;
                }
            }
        },
    };
};

Allow Local LAN devices to access the livereload machine


import MCStoreBus from './mcstorebus';

class StateStoreService {
    /*@ngInject*/
    constructor() {
        this.states = {};
        this.bus = new MCStoreBus();
    }

    updateState(key, value) {
        this.states[key] = angular.copy(value);
        let val = angular.copy(this.states[key]);
        this.bus.fireEvent(key, val);
    }

    getState(key) {
        if (_.has(this.states, key)) {
            return angular.copy(this.states[key]);
        }

        return null;
    }

    subscribe(state, fn) {
        return this.bus.subscribe(state, fn);
    }
}

angular.module('materialscommons').service('mcStateStore', StateStoreService);

import MCStoreBus from './mcstorebus';

class StateStoreService {
    /*@ngInject*/
    constructor() {
        this.states = {};
        this.bus = new MCStoreBus();
    }

    updateState(key, value) {
        this.states[key] = angular.copy(value);
        let val = this.states[key];
        this.bus.fireEvent(key, val);
    }

    getState(key) {
        if (_.has(this.states, key)) {
            return angular.copy(this.states[key]);
        }

        return null;
    }

    subscribe(state, fn) {
        return this.bus.subscribe(state, fn);
    }
}

angular.module('materialscommons').service('mcStateStore', StateStoreService);

Make contract that client needs to copy or not change value


#!/usr/bin/env node
'use strict';

var program = require('commander'),
    meta = require('../package.json'),
    lint = require('../index');

var detects,
    formatted,
    configPath,
    ignores,
    configOptions = {};

program
  .version(meta.version)
  .usage('[options] <pattern>')
  .option('-c, --config [path]', 'path to custom config file')
  .option('-i, --ignore [pattern]', 'pattern to ignore. For multiple ignores, separate each pattern by `, `')
  .option('-q, --no-exit', 'do not exit on errors')
  .option('-v, --verbose', 'verbose output')
  .parse(process.argv);

if (program.config && program.config !== true) {
  configPath = program.config;
}

if (program.ignore && program.ignore !== true) {
  ignores = program.ignore.split(', ');
  configOptions = {
    'files': {
      'ignore': ignores
    }
  };
}

detects = lint.lintFiles(program.args[0], configOptions, configPath);
formatted = lint.format(detects);

if (program.verbose) {
  lint.outputResults(formatted);
}

if (program.exit) {
  lint.failOnError(detects);
}

#!/usr/bin/env node
'use strict';

var program = require('commander'),
    meta = require('../package.json'),
    lint = require('../index');

var configPath,
    ignores,
    configOptions = {};

var detectPattern = function (pattern) {
  var detects,
      formatted;

  detects = lint.lintFiles(pattern, configOptions, configPath);
  formatted = lint.format(detects);

  if (program.verbose) {
    lint.outputResults(formatted);
  }

  if (program.exit) {
    lint.failOnError(detects);
  }
};

program
  .version(meta.version)
  .usage('[options] <pattern>')
  .option('-c, --config [path]', 'path to custom config file')
  .option('-i, --ignore [pattern]', 'pattern to ignore. For multiple ignores, separate each pattern by `, `')
  .option('-q, --no-exit', 'do not exit on errors')
  .option('-v, --verbose', 'verbose output')
  .parse(process.argv);

if (program.config && program.config !== true) {
  configPath = program.config;
}

if (program.ignore && program.ignore !== true) {
  ignores = program.ignore.split(', ');
  configOptions = {
    'files': {
      'ignore': ignores
    }
  };
}

if (program.args.length === 0) {
  detectPattern(null);
}
else {
  program.args.forEach(function (path) {
    detectPattern(path);
  });
}

Allow multiple paths for CLI


/**
 * Ember.js plugin
 *
 * Patches event handler callbacks and ajax callbacks.
 */
;(function(window, Raven, Ember) {
'use strict';

// quit if Ember isn't on the page
if (!Ember) {
    return;
}

var _oldOnError = Ember.onerror;
Ember.onerror = function EmberOnError(error) {
    Raven.captureException(error);
    if (typeof _oldOnError === 'function') {
        _oldOnError.call(this, error);
    }
};
Ember.RSVP.on('error', function (reason) {
    if (reason instanceof Error) {
        Raven.captureException(reason);
    } else {
        try {
            throw new Error('Unhandled Promise error detected');
        } catch (err) {
            Raven.captureException(err, {extra: {reason: reason}});
        }
    }
});

}(window, window.Raven, window.Ember));

/**
 * Ember.js plugin
 *
 * Patches event handler callbacks and ajax callbacks.
 */
;(function(window, Raven, Ember) {
'use strict';

// quit if Ember isn't on the page
if (!Ember) {
    return;
}

var _oldOnError = Ember.onerror;
Ember.onerror = function EmberOnError(error) {
    Raven.captureException(error);
    if (typeof _oldOnError === 'function') {
        _oldOnError.call(this, error);
    }
};
Ember.RSVP.on('error', function (reason) {
    if (reason instanceof Error) {
        Raven.captureException(reason, {extra: {context: 'Unhandled RSVP error'}});
    } else {
        try {
            throw new Error('Unhandled Promise error detected');
        } catch (err) {
            Raven.captureException(err, {extra: {reason: reason}});
        }
    }
});

}(window, window.Raven, window.Ember));

Include context in RSVP handler.


/* global require, module */
var EmberApp = require('ember-cli/lib/broccoli/ember-app');

module.exports = function(defaults) {
  var vendorFiles = {};

  if (EmberApp.env() !== 'test') {
    vendorFiles['jquery.js'] = null;
  }

  var app = new EmberApp(defaults, {
    storeConfigInMeta: false,

    fingerprint: {
      enabled: false,
    },

    vendorFiles: vendorFiles,

    gzip: {
      keepUncompressed: true,
    },
  });

  // Use `app.import` to add additional libraries to the generated
  // output files.
  //
  // If you need to use different assets in different
  // environments, specify an object as the first parameter. That
  // object's keys should be the environment name and the values
  // should be the asset to use in that environment.
  //
  // If the library that you are including contains AMD or ES6
  // modules that you would like to import into your application
  // please specify an object with the list of modules as keys
  // along with the exports of each module as its value.

  app.import('vendor/shims/openlayers.js');
  app.import('vendor/shims/ol3-cesium.js');

  app.import('bower_components/remarkable/dist/remarkable.js');
  app.import('vendor/shims/remarkable.js');

  return app.toTree();
};

/* global require, module */
var EmberApp = require('ember-cli/lib/broccoli/ember-app');

module.exports = function(defaults) {
  var vendorFiles = {};

  if (EmberApp.env() !== 'test') {
    vendorFiles['jquery.js'] = null;
  }

  var app = new EmberApp(defaults, {
    storeConfigInMeta: false,

    fingerprint: {
      enabled: false,
    },

    vendorFiles: vendorFiles,

    gzip: {
      keepUncompressed: true,
    },
  });

  // Use `app.import` to add additional libraries to the generated
  // output files.
  //
  // If you need to use different assets in different
  // environments, specify an object as the first parameter. That
  // object's keys should be the environment name and the values
  // should be the asset to use in that environment.
  //
  // If the library that you are including contains AMD or ES6
  // modules that you would like to import into your application
  // please specify an object with the list of modules as keys
  // along with the exports of each module as its value.

  app.import('vendor/shims/openlayers.js');
  app.import('vendor/shims/ol3-cesium.js');

  app.import({
    development: 'bower_components/remarkable/dist/remarkable.js',
    production: 'bower_components/remarkable/dist/remarkable.min.js',
  });
  app.import('vendor/shims/remarkable.js');

  return app.toTree();
};

Use minified "remarkable" version in production


exports.up = (knex, Promise) => Promise.all([
    knex.schema.createTable('users', table => {
        table.uuid('id')
            .primary()
            .defaultTo(knex.raw('uuid_generate_v4()'));
        table.string('username', 20)
            .unique();
        table.string('password');
        table.float('saldo')
            .defaultTo(0);
    }),

    knex.schema.createTable('transactionTypes', table => {
        table.increments('id')
            .primary();
        table.string('typename');
    }),

    knex.schema.createTable('transactions', table => {
        table.increments('id')
            .primary();
        table.integer('typeId')
            .unsigned()
            .references('id')
            .inTable('transactionTypes')
            .onDelete('SET NULL');
        table.uuid('userId')
            .references('id')
            .inTable('users')
            .onDelete('SET NULL');
        table.timestamp('timestamp')
            .defaultTo(knex.raw('now()'));
        table.float('oldSaldo');
        table.float('newSaldo');
        table.string('comment');
    })
]);

exports.down = (knex, Promise) => Promise.all([
    knex.schema.dropTable('transactions'),
    knex.schema.dropTable('users'),
    knex.schema.dropTable('transactionTypes')
]);

exports.up = (knex, Promise) => Promise.all([
    knex.schema.createTable('users', table => {
        table.uuid('id')
            .primary()
            .defaultTo(knex.raw('uuid_generate_v4()'));
        table.string('username', 20)
            .notNullable()
            .unique();
        table.string('password')
            .notNullable();
        table.float('saldo')
            .notNullable()
            .defaultTo(0);
    }),

    knex.schema.createTable('transactions', table => {
        table.increments('id')
            .primary();
        table.uuid('userId')
            .notNullable()
            .references('id')
            .inTable('users')
            .onDelete('SET NULL');
        table.timestamp('timestamp')
            .defaultTo(knex.raw('now()'));
        table.float('oldSaldo')
            .notNullable();
        table.float('newSaldo')
            .notNullable();
        table.string('comment')
            .nullable();
    })
]);

exports.down = (knex, Promise) => Promise.all([
    knex.schema.dropTable('transactions'),
    knex.schema.dropTable('users')
]);

Remove transactionTypes table and specify column nullablility


import React from 'react'
import DefaultLayout from './DefaultLayout'
import {Router, Route, NotFound, Redirect} from 'router:@sanity/base/router'
import locationStore from 'datastore:@sanity/base/location'
import SanityIntlProvider from 'component:@sanity/base/sanity-intl-provider'

class DefaultLayoutRouter extends React.Component {
  constructor() {
    super()
    this.state = {}
    this.handleNavigate = this.handleNavigate.bind(this)
  }

  componentWillMount() {
    this.pathSubscription = locationStore
      .state
      .subscribe({next: event => this.setState({location: event.location})})
  }

  componentWillUnmount() {
    this.pathSubscription.unsubscribe()
  }

  handleNavigate(newUrl, options) {
    locationStore.actions.navigate(newUrl)
  }

  render() {
    const {location} = this.state
    if (!location) {
      return null
    }

    return (
      <SanityIntlProvider>
        <Router location={location} navigate={this.handleNavigate}>
          <Route path="/:site/*" component={DefaultLayout} />
          <Redirect path="/" to="/some-site" />
          <NotFound component={() =>
Not found
} />
        </Router>
      </SanityIntlProvider>
    )
  }
}

export default DefaultLayoutRouter

import React from 'react'
import DefaultLayout from './DefaultLayout'
import {Router, Route, NotFound, Redirect} from 'router:@sanity/base/router'
import locationStore from 'datastore:@sanity/base/location'
import SanityIntlProvider from 'component:@sanity/base/sanity-intl-provider'
import LoginWrapper from 'component:@sanity/base/login-wrapper'

class DefaultLayoutRouter extends React.Component {
  constructor() {
    super()
    this.state = {}
    this.handleNavigate = this.handleNavigate.bind(this)
  }

  componentWillMount() {
    this.pathSubscription = locationStore
      .state
      .subscribe({next: event => this.setState({location: event.location})})
  }

  componentWillUnmount() {
    this.pathSubscription.unsubscribe()
  }

  handleNavigate(newUrl, options) {
    locationStore.actions.navigate(newUrl)
  }

  render() {
    const {location} = this.state
    if (!location) {
      return null
    }

    return (
      <SanityIntlProvider>
        <LoginWrapper>
          <Router location={location} navigate={this.handleNavigate}>
            <Route path="/:site/*" component={DefaultLayout} />
            <Redirect path="/" to="/some-site" />
            <NotFound component={() =>
Not found
} />
          </Router>
        </LoginWrapper>
      </SanityIntlProvider>
    )
  }
}

export default DefaultLayoutRouter

Put loginwrapper inside main router


define(['views/user/login', 'views/user/signup', 'views/header', 'views/order/vl_orders'],
  function (UserLogin, UserSignup, HeaderView, OrdersView) {

    var Ui = {};

    var loginView = new UserLogin()
    var signupView = new UserSignup()
    var headerView = new HeaderView({el: '#header'})
    var ordersView = new OrdersView();

    Ui.initialize = function () {
      headerView.setUserData(window.localStorage.getItem('User') || {});
    };

    Ui.showHome = function () {
      loginView.render();
    };

    Ui.showSignup = function () {
      signupView.render();
    }

    Ui.showOrders = function() {
      ordersView.render();
    }

    // This always receive a JSON object with a standard API error
    Ui.error = function (err) {
      alert("Error: " + err.message);
    }

    // This always receive a jQuery error object from an API call
    Ui.errorAPI = function (res) {
      alert("Error: " + res.responseJSON.error.message);
    }

    // Event subscription

    Backbone.on('api:login:error', function (data, res) {
      Ui.error(res.responseJSON.error);
    });

    Backbone.on('api:signup:error', function (data, res) {
      Ui.error(res.responseJSON.error);
    });

    return Ui;
  });

define([
    'backbone',
    'api',
    'collections/c_orders',
    'views/user/login',
    'views/user/signup',
    'views/header',
    'views/order/vl_orders'],
  function (Backbone, Api, CollectionOrder, UserLogin, UserSignup, HeaderView, OrdersView) {

    var Ui = {};

    var loginView = new UserLogin()
    var signupView = new UserSignup()
    var headerView = new HeaderView({el: '#header'})
    var ordersView = new OrdersView();

    Ui.initialize = function () {
      headerView.setUserData(Backbone.localStorage.getItem('user'));
    };

    Ui.showHome = function () {
      loginView.render();
    };

    Ui.showSignup = function () {
      signupView.render();
    }

    Ui.showOrders = function () {
      var orders = new CollectionOrder();
      orders.fetch({
        success: ordersView.render.bind(ordersView),
        error: Ui.errorBackbone
      });
    }

    Ui.errorBackbone  = function (data, res) {
      alert("Error: " + res.responseJSON.error.message);
    }

    // This always receive a JSON object with a standard API error
    Ui.error = function (err, err2) {
      alert("Error: " + err.message);
    }

    // This always receive a jQuery error object from an API call
    Ui.errorAPI = function (res) {
      alert("Error: " + res.responseJSON.error.message);
    }

    // Event subscription

    Backbone.on('api:login:error', function (res) {
      Ui.error(res.responseJSON.error);
    });

    Backbone.on('api:signup:error', function (res) {
      Ui.error(res.responseJSON.error);
    });

    return Ui;
  });

Use convenience localStorage functions from Backbone Use Backbone collection to fetch orders Added new error function to Ui Fix parameters for event callbacks


import has from 'lodash/has';
import isPlainObject from 'lodash/isPlainObject';
import isArray from 'lodash/isArray';

const UUID_KEY_PATTERN = /__.+uuid__/i;

export default function stripUUID(value) {
  if (isPlainObject(value)) {
    // eslint-disable-next-line
    for (const key in value) {
      if (has(value, key)) {
        if (UUID_KEY_PATTERN.test(key)) {
          delete value[key];
        } else {
          const oldValue = value[key];
          const newValue = stripUUID(oldValue);
          if (newValue !== oldValue) {
            value[key] = newValue;
          }
        }
      }
    }
  } else if (isArray(value)) {
    value.forEach(v => stripUUID(v));
  }

  return value;
}

import has from 'lodash/has';
import isPlainObject from 'lodash/isPlainObject';
import isArray from 'lodash/isArray';

const UUID_KEY_PATTERN = /__.+uuid__/i;
const OLD_KEY = 'zent-design-uuid';

export default function stripUUID(value) {
  if (isPlainObject(value)) {
    // eslint-disable-next-line
    for (const key in value) {
      if (has(value, key)) {
        if (OLD_KEY === key || UUID_KEY_PATTERN.test(key)) {
          delete value[key];
        } else {
          const oldValue = value[key];
          const newValue = stripUUID(oldValue);
          if (newValue !== oldValue) {
            value[key] = newValue;
          }
        }
      }
    }
  } else if (isArray(value)) {
    value.forEach(v => stripUUID(v));
  }

  return value;
}

Add compatibility to old uuid key


// We use factories for the models we want to test so just set
// empty fixtures here to keep the DS.FixtureAdapter quiet
App.CurrentUser.FIXTURES = [];
App.WallPost.FIXTURES = [];
App.ProjectPhase.FIXTURES = [];
App.TaskSearch.FIXTURES = [];
App.Organization.FIXTURES = [];
App.Project.FIXTURES = [];
App.TaskFile.FIXTURES = [];
App.TaskMember.FIXTURES = [];
App.TaskSearch.FIXTURES = [];
App.Task.FIXTURES = [];
App.Page.FIXTURES = [];
App.PartnerOrganization.FIXTURES = [];
App.Quote.FIXTURES = [];
App.Project.FIXTURES = [];
App.WallPost.FIXTURES = [];

// We use factories for the models we want to test so just set
// empty fixtures here to keep the DS.FixtureAdapter quiet
App.CurrentUser.FIXTURES = [];
App.WallPost.FIXTURES = [];
App.ProjectPhase.FIXTURES = [];
App.TaskSearch.FIXTURES = [];
App.Organization.FIXTURES = [];
App.Project.FIXTURES = [];
App.TaskFile.FIXTURES = [];
App.TaskMember.FIXTURES = [];
App.TaskSearch.FIXTURES = [];
App.Task.FIXTURES = [];
App.Page.FIXTURES = [];
App.Quote.FIXTURES = [];
App.Project.FIXTURES = [];
App.WallPost.FIXTURES = [];

Remove PartnerOrg Fixture from tests.


/*
 * Socket.io Communication
 */

// module dependencies
var crypto = require('crypto');

// variable declarations
var socketCodes = {};

module.exports = function(socket) {
  // establish connection
  socket.emit('pair:init', {});

  // receive device type
  socket.on('pair:deviceType', function(data) {
    // if deviceType is 'pc', generate a unique code and send to PC
    if(data.deviceType == 'pc') {
      // generate a code
      var code = crypto.randomBytes(3).toString('hex');

      // ensure uniqueness
      while(code in socketCodes) {
        code = crypto.randomBytes(3).toString('hex');
      }

      // store pairing code / socket assocation
      socketCodes[code] = this;
      socket.code = code;

      // show code on PC
      socket.emit('pair:sendCode', { code: code });
    }
    // if deviceType is 'mobile', check if submitted code is valid and pair
    else if(data.deviceType == 'mobile') {
      socket.on('pair:getCode', function(data) {
        if(data.code in socketCodes) {
          // save the code for controller commands
          socket.code = data.code;

          // initialize the controller
          socket.emit('pair:connected', {});

          // start the PC
          socketCodes[data.code].emit('pair:connected', {});
        }
        else {
          socket.emit('pair:fail', {});
          socket.disconnect();
        }
      });
    }
  });
};

/*
 * Socket.io Communication
 */

// module dependencies
var crypto = require('crypto');

// variable declarations
var socketCodes = {};

module.exports = function(socket) {
  // establish connection
  socket.emit('pair:init', {});

  // pair mobile and PC
  // Reference: http://blog.artlogic.com/2013/06/21/phone-to-browser-html5-gaming-using-node-js-and-socket-io/
  socket.on('pair:deviceType', function(data) {
    // if deviceType is 'pc', generate a unique code and send to PC
    if(data.deviceType == 'pc') {
      // generate a code
      var code = crypto.randomBytes(3).toString('hex');

      // ensure code is unique
      while(code in socketCodes) {
        code = crypto.randomBytes(3).toString('hex');
      }

      // store code / socket assocation
      socketCodes[code] = this;
      socket.code = code;

      // show code on PC
      socket.emit('pair:sendCode', { code: code });
    }
    // if deviceType is 'mobile', check if submitted code is valid and pair
    else if(data.deviceType == 'mobile') {
      socket.on('pair:getCode', function(data) {
        if(data.code in socketCodes) {
          // save the code for mobile commands
          socket.code = data.code;

          // start mobile connection
          socket.emit('pair:connected', {});

          // start PC connection
          socketCodes[data.code].emit('pair:connected', {});
        }
        else {
          socket.emit('pair:fail', {});
          socket.disconnect();
        }
      });
    }
  });
};

Add comments to pairing section


'use strict';

/*
 * Tanura demo app.
 *
 * This is the main script of the app demonstrating Tanura's features.
 */

/**
 * Bootstrapping routine.
 */
window.onload = () => {
    console.log("Tanura demo.");
}

'use strict';

/*
 * Tanura demo app.
 *
 * This is the main script of the app demonstrating Tanura's features.
 */

/**
 * Bootstrapping routine.
 */
window.onload = () => {
    console.log("Initializing Tanura...");
    tanura.init({audio: true, video: true});
}

Update the demo to use tanura.init().


angular.module('materialscommons').component('mcExperimentTaskDetails', {
    templateUrl: 'app/project/experiments/experiment/components/tasks/mc-experiment-task-details.html',
    controller: MCExperimentTaskDetailsComponentController,
    bindings: {
        task: '='
    }
});

/*@ngInject*/
function MCExperimentTaskDetailsComponentController($scope, editorOpts, currentTask, templates,
                                                    template, experimentsService, $stateParams, toast) {
    let ctrl = this;
    ctrl.currentTask = currentTask.get();
    var t = templates.getTemplate('As Received');
    template.set(t);

    $scope.editorOptions = editorOpts({height: 25, width: 20});

    ctrl.selectedTemplate = (templateId, processId) => {
        console.log('selectedTemplate', templateId, processId);
    };

    ctrl.updateTaskNote = () => {
        console.log('updateTaskNote');
        if (ctrl.task.note === null) {
            ctrl.task.note = "";
        }

        experimentsService.updateTask($stateParams.project_id, $stateParams.experiment_id, ctrl.task.id,
            {note: ctrl.task.note})
            .then(
                () => null,
                () => toast.error('Unable to update task note.')
            );
    };
}

angular.module('materialscommons').component('mcExperimentTaskDetails', {
    templateUrl: 'app/project/experiments/experiment/components/tasks/mc-experiment-task-details.html',
    controller: MCExperimentTaskDetailsComponentController,
    bindings: {
        task: '='
    }
});

/*@ngInject*/
function MCExperimentTaskDetailsComponentController($scope, editorOpts, currentTask, templates,
                                                    template, experimentsService, $stateParams, toast) {
    let ctrl = this;
    ctrl.currentTask = currentTask.get();
    var t = templates.getTemplate('As Received');
    template.set(t);

    $scope.editorOptions = editorOpts({height: 25, width: 20});

    ctrl.selectedTemplate = (templateId, processId) => {
        console.log('selectedTemplate', templateId, processId);
    };

    ctrl.updateTaskNote = () => {
        if (ctrl.task.note === null) {
            return;
        }

        experimentsService.updateTask($stateParams.project_id, $stateParams.experiment_id, ctrl.task.id,
            {note: ctrl.task.note})
            .then(
                () => null,
                () => toast.error('Unable to update task note.')
            );
    };
}

Return if note is null.


var confy = require('../index');
var testCase = require('nodeunit').testCase;

confy.configFile = __dirname + '/.confy';
confy.clean();
process.ENV.EDITOR = __dirname + '/editorcmd';

module.exports = testCase({
  setUp: function (callback) {
    confy.clean();
    callback();
  },
  tearDown: function (callback) {
    confy.clean();
    callback();
  },
  editor: function(t) {
    confy.get('foo', { require: { foo: "" } }, function(err, res) {
      t.ok(!err, 'error is null');
      t.deepEqual(res, { foo: 'bar' }, 'editor write file');
      t.done();
    });
  }
});

var confy = require('../index');
var testCase = require('nodeunit').testCase;

confy.configFile = __dirname + '/.confy';
confy.clean();
process.env.EDITOR = __dirname + '/editorcmd';

module.exports = testCase({
  setUp: function (callback) {
    confy.clean();
    callback();
  },
  tearDown: function (callback) {
    confy.clean();
    callback();
  },
  editor: function(t) {
    confy.get('foo', { require: { foo: "" } }, function(err, res) {
      t.ok(!err, 'error is null');
      t.deepEqual(res, { foo: 'bar' }, 'editor write file');
      t.done();
    });
  }
});

Fix process.ENV is not supported v0.6.x


import { db, Rat } from '../db'
import Query from './index'

/**
 * A class representing a rat query
 */
class NicknameQuery extends Query {
  /**
   * Create a sequelize rat query from a set of parameters
   * @constructor
   * @param params
   * @param connection
   */
  constructor (params, connection) {
    super(params, connection)

    if (params.nickname) {
      let formattedNickname = params.nickname.replace(/\[(.*?)]$/g, '')
      this._query.where.nicknames = {
        $overlap:  db.literal(`ARRAY[${db.escape(params.nickname)}, ${db.escape(formattedNickname)}]::citext[]`)
      }
      delete this._query.where.nickname
    }

    this._query.attributes = [
      'id',
      'createdAt',
      'updatedAt',
      'email',
      'displayRatId',
      [db.cast(db.col('nicknames'), 'text[]'), 'nicknames']
    ]

    this._query.include = [{
      model: Rat,
      as: 'rats',
      required: false,
      attributes: {
        exclude: [
          'deletedAt'
        ]
      }
    }]
  }
}

module.exports = NicknameQuery

import { db, Rat } from '../db'
import Query from './index'

/**
 * A class representing a rat query
 */
class NicknameQuery extends Query {
  /**
   * Create a sequelize rat query from a set of parameters
   * @constructor
   * @param params
   * @param connection
   */
  constructor (params, connection) {
    super(params, connection)

    if (params.nickname) {
      let formattedNickname = params.nickname.replace(/\[(.*?)]$/g, '')
      this._query.where.nicknames = {
        $overlap:  [params.nickname, formattedNickname]
      }
      delete this._query.where.nickname
    }

    this._query.attributes = [
      'id',
      'createdAt',
      'updatedAt',
      'email',
      'displayRatId',
      'nicknames'
    ]

    this._query.include = [{
      model: Rat,
      as: 'rats',
      required: false,
      attributes: {
        exclude: [
          'deletedAt'
        ]
      }
    }]
  }
}

module.exports = NicknameQuery

Remove some lingering citext stuff from nickname query


//@flow
import {Readable} from 'stream';
import RarFileChunk from './rar-file-chunk';
export default class RarStream extends Readable {
  _rarFileChunks: RarFileChunk[];
  _byteOffset: number = 0;

  constructor(...rarFileChunks: RarFileChunk[]){
    super();
    this._next(rarFileChunks);
  }

  pushData(stream: Readable, chunk: ?(Buffer | string)) : ?boolean {
      if (!super.push(chunk)){
        stream.pause();
      }
  }

  _next(rarFileChunks: RarFileChunk[]) {
    const chunk = rarFileChunks.shift();
    if(!chunk){
      this.push(null);
    } else {
      chunk.getStream().then((stream) => {
        stream.on('data', (data) => this.pushData(stream, data));
        stream.on('end', () => this._next(rarFileChunks));
      });
    }
  }
  _read (){
    this.resume();
  }
}

//@flow
import {Readable} from 'stream';
import RarFileChunk from './rar-file-chunk';
export default class RarStream extends Readable {
  constructor(...rarFileChunks: RarFileChunk[]){
    super();
    this._next(rarFileChunks);
  }

  pushData(stream: Readable, chunk: ?(Buffer | string)) : ?boolean {
      if (!super.push(chunk)){
        stream.pause();
      }
  }

  _next(rarFileChunks: RarFileChunk[]) {
    const chunk = rarFileChunks.shift();
    if(!chunk){
      this.push(null);
    } else {
      chunk.getStream().then((stream) => {
        stream.on('data', (data) => this.pushData(stream, data));
        stream.on('end', () => this._next(rarFileChunks));
      });
    }
  }

  _read (){
    this.resume();
  }
}

Remove class state from RarStream


import React, { Component } from 'react';
import '../styles/WeaveElement.css';

class WeaveElement extends Component {
  constructor() {
    super();

    this.state = {red: false};
    this.handleClick = this.handleClick.bind(this);
  }

  render() {
    const style = this.state.red
      ? "WeaveElement redWeaveElement"
      : "WeaveElement whiteWeaveElement";

    return (

);
  }

  handleClick(e) {
    if (this.state.red) this.setState({'red': false});
    else this.setState({'red': true});
  }
}

export default WeaveElement;

import React, { Component } from 'react';
import '../styles/WeaveElement.css';

class WeaveElement extends Component {
  constructor() {
    super();
    this.handleClick = this.handleClick.bind(this);
  }

  componentState() {
    const row = this.props.row;
    const col = this.props.col;
    const weaves = this.props.currentState.weaves;

    if (weaves[row]) {
      if (weaves[row][col] === undefined || weaves[row][col] === false) {
          return false;
      } else {
        return true;
      }
    } else {
      return false;
    }
  }

  render() {
    const style = this.componentState()
      ? "WeaveElement redWeaveElement"
      : "WeaveElement whiteWeaveElement";

    return (

);
  }

  handleClick(e) {
    if (this.componentState()) this.props.offClick(this.props.row, this.props.col);
    else this.props.onClick(this.props.row, this.props.col);
  }
}

export default WeaveElement;

Add method componentState and remove inner state


export { Avatar } from './Avatar';
export Button from './Button';
export FloatingAction from './FloatingAction';
export FullScreenTextArea from './FullScreenTextArea';
export Icon from './Icon';
export User from './User';

export { Avatar } from './Avatar';
export Button from './Button';
export FullScreenTextArea from './FullScreenTextArea';
export Icon from './Icon';
export User from './User';

Remove floating action from export


'use strict';
var yeoman = require('yeoman-generator');

module.exports = yeoman.generators.Base.extend({
  initializing: function() {
    this.option('gh_page_type', {
      type: String,
      required: true,
      desc: 'Github page type (user or project)'
    });
    this._set_branch_option();
  },

  _set_branch_option: function() {
    this.options.branch_name = this.options.gh_page_type === 'user' ? 'master' : 'gh-pages';
  },

  writing: function() {
    this.fs.copyTpl(
      this.templatePath('gulpfile.js'),
      this.destinationPath('gulpfile.js'),
      this.options
    );
  }
});

'use strict';
var yeoman = require('yeoman-generator');

module.exports = yeoman.generators.Base.extend({
  initializing: function() {
    this.option('gh_page_type', {
      type: String,
      required: true,
      desc: 'Github page type (user, organization, or project)'
    });
    this._set_branch_option();
  },

  _set_branch_option: function() {
    var is_user_or_org = (/user|organization/i).test(this.options.gh_page_type);
    this.options.branch_name = is_user_or_org ? 'master' : 'gh-pages';
  },

  writing: function() {
    this.fs.copyTpl(
      this.templatePath('gulpfile.js'),
      this.destinationPath('gulpfile.js'),
      this.options
    );
  }
});

Adjust gulp generator to support github orgs


"use strict";

annotationApp.directive('morphForm', function() {
  return {
    restrict: 'E',
    scope: true,
    templateUrl: 'templates/morph_form.html'
  };
});

Use strict in morphForm directive


// Project configuration.

var pkg = require("../package");

module.exports = {
	"package" : pkg,
	"paths" : {
		"private":  "Resources/Private",
		"public":   "Resources/Public",
	},
	"Sass" : {
		"sassDir" : "Resources/Private/Sass",
		"cssDir" : "Resources/Public/Stylesheets"
	},
	"JavaScripts" : {
		"devDir" : "Resources/Private/Javascripts",
		"distDir" : "Resources/Public/Javascripts",
		"config" : "Resources/Private/Javascripts/Main.js",
		"requireJS" : "Resources/Private/Javascripts/Libaries/RequireJS/require",
		"compileDistFile" : "Resources/Public/Javascripts/Main.min.js"
	},
	"Images" : {
		"devDir" : "Resources/Private/Images",
		"distDir" : "Resources/Public/Images",
		"optimizationLevel" : 5
	},
	"packageIsDefault" : (pkg.name === "t3b_template") ? true : false // Check if the defaults in 'package.json' are customized.
};

// Project configuration.

var pkg = require("../package");

module.exports = {
	"package" : pkg,
	"paths" : {
		"private":  "Resources/Private",
		"public":   "Resources/Public",
	},
	"Sass" : {
		"sassDir" : "Resources/Private/Sass",
		"cssDir" : "Resources/Public/Stylesheets"
	},
	"JavaScripts" : {
		"devDir" : "Resources/Private/Javascripts",
		"distDir" : "Resources/Public/Javascripts",
		"config" : "Resources/Private/Javascripts/Main.js",
		"requireJS" : "Resources/Private/Javascripts/Libaries/RequireJS/require",
		"compileDistFile" : "Resources/Public/Javascripts/Main.min.js"
	},
	"Images" : {
		"devDir" : "Resources/Private/Images",
		"distDir" : "Resources/Public/Images",
		"optimizationLevel" : 5
	},
	"packageIsDefault" : pkg.name === "t3b_template" // Check if the defaults in 'package.json' are customized.
};

Simplify the grunt 'packageIsDefault' check


import Sequelize from 'sequelize'

import logger from '../helpers/logger'
import config from './config'

const { url, dialect } = config[process.env.NODE_ENV]

export default new Sequelize(url, {
  logging: msg => {
    logger.debug(msg)
  },
  dialect,
  operatorsAliases: false, // @TODO Remove this option in sequelize@>=5.0
})

import Sequelize from 'sequelize'

import config from './config'
import logger from '../helpers/logger'

const { url, dialect } = config[process.env.NODE_ENV]

export default new Sequelize(url, {
  dialect,
  logging: msg => {
    logger.debug(msg)
  },
})

Remove unneded database options after upgrading to Sequelize 5


import { placeStylesheet } from "../../common/styles";

window.Buttercup = {

    fetchArchives: function() {
        return new Promise(function(resolve, reject) {
            let timeout = setTimeout(() => reject(new Error("Timed-out getting archive list")), 200);
            chrome.runtime.sendMessage({ command: "get-archive-states" }, function(response) {
                clearTimeout(timeout);
                resolve(response);
            });
        });
    },

    lockArchive: function(name) {
        return new Promise(function(resolve, reject) {
            let timeout = setTimeout(() => reject(new Error("Timed-out locking archive")), 200);
            chrome.runtime.sendMessage({ command: "lock-archive", name }, function(response) {
                clearTimeout(timeout);
                if (response && response.ok) {
                    resolve(response);
                } else {
                    reject(new Error(response.error));
                }
            });
        });
    }

};

placeStylesheet();

import { placeStylesheet } from "../../common/styles";

window.Buttercup = {

    fetchArchives: function() {
        return new Promise(function(resolve, reject) {
            let timeout = setTimeout(() => reject(new Error("Timed-out getting archive list")), 200);
            chrome.runtime.sendMessage({ command: "get-archive-states" }, function(response) {
                clearTimeout(timeout);
                resolve(response);
            });
        });
    },

    lockArchive: function(name) {
        return new Promise(function(resolve, reject) {
            let timeout = setTimeout(() => reject(new Error("Timed-out locking archive")), 4000);
            chrome.runtime.sendMessage({ command: "lock-archive", name }, function(response) {
                clearTimeout(timeout);
                if (response && response.ok) {
                    resolve(response);
                } else {
                    reject(new Error(response.error));
                }
            });
        });
    }

};

placeStylesheet();

Increase timeout for locking archives


Package.describe({
  summary: "Run tests interactively in the browser",
  version: '1.0.6'
});

Package.onUse(function (api) {
  // XXX this should go away, and there should be a clean interface
  // that tinytest and the driver both implement?
  api.use('tinytest');
  api.use('bootstrap@1.0.1');
  api.use('underscore');

  api.use('session');
  api.use('reload');

  api.use(['blaze', 'templating', 'spacebars',
           'ddp', 'tracker'], 'client');

  api.addFiles('diff_match_patch_uncompressed.js', 'client');

  api.addFiles('diff_match_patch_uncompressed.js', 'client');

  api.addFiles([
    'driver.css',
    'driver.html',
    'driver.js'
  ], "client");

  api.use('autoupdate', 'server', {weak: true});
  api.use('random', 'server');
  api.addFiles('autoupdate.js', 'server');
});

Package.describe({
  summary: "Run tests interactively in the browser",
  version: '1.0.6',
  documentation: null
});

Package.onUse(function (api) {
  // XXX this should go away, and there should be a clean interface
  // that tinytest and the driver both implement?
  api.use('tinytest');
  api.use('bootstrap@1.0.1');
  api.use('underscore');

  api.use('session');
  api.use('reload');

  api.use(['blaze', 'templating', 'spacebars',
           'ddp', 'tracker'], 'client');

  api.addFiles('diff_match_patch_uncompressed.js', 'client');

  api.addFiles('diff_match_patch_uncompressed.js', 'client');

  api.addFiles([
    'driver.css',
    'driver.html',
    'driver.js'
  ], "client");

  api.use('autoupdate', 'server', {weak: true});
  api.use('random', 'server');
  api.addFiles('autoupdate.js', 'server');
});

Test in browser doesn't need docs right now


// https://github.com/kni-labs/rrssb
var popupCenter = function(url, title, w, h) {
  // Fixes dual-screen position                         Most browsers      Firefox
  var dualScreenLeft =
    window.screenLeft !== undefined ? window.screenLeft : screen.left;
  var dualScreenTop =
    window.screenTop !== undefined ? window.screenTop : screen.top;

  var width = window.innerWidth
    ? window.innerWidth
    : document.documentElement.clientWidth
      ? document.documentElement.clientWidth
      : screen.width;
  var height = window.innerHeight
    ? window.innerHeight
    : document.documentElement.clientHeight
      ? document.documentElement.clientHeight
      : screen.height;

  var left = width / 2 - w / 2 + dualScreenLeft;
  var top = height / 3 - h / 3 + dualScreenTop;

  var newWindow = window.open(
    url,
    title,
    "scrollbars=yes, width=" +
      w +
      ", height=" +
      h +
      ", top=" +
      top +
      ", left=" +
      left
  );

  // Puts focus on the newWindow
  if (newWindow && newWindow.focus) {
    newWindow.focus();
  }
};

document.querySelectorAll(".popup").forEach(function(el) {
    el.addEventListener("click", function(e) {
      popupCenter(el.getAttribute("href"), "TODO", 580, 470);
      e.preventDefault();
  });
});

// https://github.com/kni-labs/rrssb
var popupCenter = function(url) {
  var w = 580;
  var h = 470;
  // Fixes dual-screen position                         Most browsers      Firefox
  var dualScreenLeft =
    window.screenLeft !== undefined ? window.screenLeft : screen.left;
  var dualScreenTop =
    window.screenTop !== undefined ? window.screenTop : screen.top;

  var width = window.innerWidth
    ? window.innerWidth
    : document.documentElement.clientWidth
      ? document.documentElement.clientWidth
      : screen.width;
  var height = window.innerHeight
    ? window.innerHeight
    : document.documentElement.clientHeight
      ? document.documentElement.clientHeight
      : screen.height;

  var left = width / 2 - w / 2 + dualScreenLeft;
  var top = height / 3 - h / 3 + dualScreenTop;

  var newWindow = window.open(
    url,
    "share-popup",
    "scrollbars=yes, width=" +
      w +
      ", height=" +
      h +
      ", top=" +
      top +
      ", left=" +
      left
  );

  // Puts focus on the newWindow
  if (newWindow && newWindow.focus) {
    newWindow.focus();
  }
};

document.querySelectorAll(".popup").forEach(function(el) {
    el.addEventListener("click", function(e) {
      popupCenter(el.getAttribute("href"));
      e.preventDefault();
  });
});

Remove title w and h params in popupCenter


noUiSlider.create(slider, {
    start: 80,
    range: {
        min: 0,
        max: 100
    },
    cssPrefix: 'noUi-', // defaults to 'noUi-',
    cssClasses: {
        // Full list of classnames to override.
        // Does NOT extend the default classes.
        // Have a look at the source for the full, current list:
        // https://github.com/leongersen/noUiSlider/blob/master/src/js/options.js#L398
    }
});

noUiSlider.create(slider, {
    start: 80,
    range: {
        min: 0,
        max: 100
    },
    cssPrefix: 'noUi-', // defaults to 'noUi-',
    cssClasses: {
        // Full list of classnames to override.
        // Does NOT extend the default classes.
        // Have a look at the source for the full, current list:
        // https://github.com/leongersen/noUiSlider/blob/master/src/nouislider.js#L880
    }
});

Remove dead link to full list of class names


/*globals chrome,console */
/*jslint indent:2,browser:true, node:true */
var PromiseCompat = require('es6-promise').Promise;

var oAuthRedirectId = "freedom.oauth.redirect.handler";

var ChromeIdentityAuth = function() {
  "use strict";
};

ChromeIdentityAuth.prototype.initiateOAuth = function(redirectURIs, continuation) {
  "use strict";
  var i;
  if (typeof chrome !== 'undefined' &&
      typeof chrome.permissions !== 'undefined' && //cca doesn't support chrome.permissions yet
      typeof chrome.identity !== 'undefined') {
    for (i = 0; i < redirectURIs.length; i += 1) {
      if (redirectURIs[i].indexOf('https://') === 0 &&
          redirectURIs[i].indexOf('.chromiumapp.org') > 0) {
        continuation({
          redirect: redirectURIs[i],
          state: oAuthRedirectId + Math.random()
        });
        return true;
      }
    }
  }

  return false;
};

ChromeIdentityAuth.prototype.launchAuthFlow = function(authUrl, stateObj, continuation) {
  chrome.identity.launchWebAuthFlow({
    url: authUrl,
    interactive: true
  }, function(stateObj, continuation, responseUrl) {
    continuation(responseUrl);
  }.bind({}, stateObj, continuation));
};

/**
 * If we have access to chrome.identity, use the built-in support for oAuth flows
 * chrome.identity exposes a very similar interface to core.oauth.
 */
module.exports = ChromeIdentityAuth;

/*globals chrome,console */
/*jslint indent:2,browser:true, node:true */
var PromiseCompat = require('es6-promise').Promise;

var oAuthRedirectId = "freedom.oauth.redirect.handler";

var ChromeIdentityAuth = function() {
  "use strict";
};

ChromeIdentityAuth.prototype.initiateOAuth = function(redirectURIs, continuation) {
  "use strict";
  var i;
  if (typeof chrome !== 'undefined' &&
      typeof chrome.identity !== 'undefined') {
    for (i = 0; i < redirectURIs.length; i += 1) {
      if (redirectURIs[i].indexOf('https://') === 0 &&
          redirectURIs[i].indexOf('.chromiumapp.org') > 0) {
        continuation({
          redirect: redirectURIs[i],
          state: oAuthRedirectId + Math.random()
        });
        return true;
      }
    }
  }

  return false;
};

ChromeIdentityAuth.prototype.launchAuthFlow = function(authUrl, stateObj, continuation) {
  chrome.identity.launchWebAuthFlow({
    url: authUrl,
    interactive: true
  }, function(stateObj, continuation, responseUrl) {
    continuation(responseUrl);
  }.bind({}, stateObj, continuation));
};

/**
 * If we have access to chrome.identity, use the built-in support for oAuth flows
 * chrome.identity exposes a very similar interface to core.oauth.
 */
module.exports = ChromeIdentityAuth;

Remove superfluous check in core.oauth


// Program to determine if string is a palindrome

function checkPalindrome(str) {

	// identify non-alphanumeric characters through regular expression
	var regEx = /[^A-Za-z0-9]/g;

	// lower case all characters in string and remove all non-alphanumeric characters
	var lowCaseReg = str.toLowerCase().replace(regEx, '');

	// check for palindrome with loop
	for (var i = 0; i < (str.length)/2; i++) {
		if (str[i] !== str[(str.length) - 1 - i]) { // loop will go on as long as characters match
			return false;
		}
	}
	return true; // string is palindrome
}

// Program to determine if string is a palindrome

function checkPalindrome(str) {

	// identify non-alphanumeric characters through regular expression
	var regEx = /[^A-Za-z0-9]/g;

	// lower case all characters in string and remove all non-alphanumeric characters
	var lowCaseReg = str.toLowerCase().replace(regEx, '');

	// check for palindrome with loop
	for (var i = 0; i < (lowCaseReg.length)/2; i++) {
		if (lowCaseReg[i] !== lowCaseReg[(lowCaseReg.length) - 1 - i]) { // loop will go on as long as characters match
			return false;
		}
	}
	return true; // string is palindrome
}

// test cases
checkPalindrome("racecar"); // expect true
checkPalindrome("race car"); // expect true
checkPalindrome("nope"); // expect false
checkPalindrome("not palindrome"); // expect false
checkPalindrome("A man, a plan, a canal. Panama"); // expect true
checkPalindrome("0_0 (: /-\ :) 0_0"); // expect true

Add test cases to check for palindrome in string function


angular.module('cat-buddy.services', [])
.factory('Animals', function($http) {
  var getAllFromInternetByZipCode = function(animalName, zipCode, offset) {
    offset = offset || 0;
    return $http({
      method: 'GET',
      url: 'http://api.petfinder.com/pet.find',
      data: {
        key: PETFINDER_API_KEY, //to fix
        animal: animalName,
        count: 20,
        offset: offset,
        location: zipCode,
        format: 'json'
      }
    })
    .then(function(resp){
      return resp.petfinder.pets;
    });
  };

  var getAllFromSaved = function() {
    return $http({
      method: 'GET',
      url: '/api/' + animalName
    });
  };

  var addOneToSaved = function(animal) {
    return $http({
      method: 'POST',
      url: '/api/' + animalName,
      data: animal
    });
  };

  return {
    getAllFromInternetByZipCode: getAllFromInternetByZipCode,
    getAllFromSaved: getAllFromSaved,
    addOneToSaved: addOneToSaved
  };
});

angular.module('cat-buddy.services', [])
.factory('Animals', function($http) {
  var getAllFromInternetByZipCode = function(animalName, zipCode, count, offset) {
    offset = offset || 0;
    return $http({
      method: 'GET',
      url: 'http://api.petfinder.com/pet.find',
      data: {
        key: PETFINDER_API_KEY,
        animal: animalName,
        count: count,
        offset: offset,
        location: zipCode,
        format: 'json'
      }
    })
    .then(function(resp){
      return resp.petfinder.pets;
    });
  };

  var getAllFromSaved = function() {
    return $http({
      method: 'GET',
      url: '/api/' + animalName
    });
  };

  var addOneToSaved = function(animal) {
    return $http({
      method: 'POST',
      url: '/api/' + animalName,
      data: animal
    });
  };

  return {
    getAllFromInternetByZipCode: getAllFromInternetByZipCode,
    getAllFromSaved: getAllFromSaved,
    addOneToSaved: addOneToSaved
  };
});

Remove magic number for "count" in get requests


(function() {
  'use strict';

  angular
    .module('orders')
    .controller('ReciptController', ReciptController);

  ReciptController.$inject = ['$scope', 'OrdersService', '$stateParams', '$location'];

  function ReciptController($scope, OrdersService, $stateParams, $location) {
    var vm = this;
    vm.order = {};

    // Recipt controller logic
    // ...
    $scope.date = new Date();

    $scope.recipt = function(){
      console.log($stateParams.orderId);
      var getorder = OrdersService.get({ orderId: $stateParams.orderId }, function() {
        vm.order = getorder;
        console.log(getorder);
      });
    };
    $scope.print = function(){
       /*window.print();*/
        var bdhtml=window.document.body.innerHTML;
        var sprnstr="<!--startprint-->";
        var eprnstr="<!--endprint-->";
        var prnhtml=bdhtml.substring(bdhtml.indexOf(sprnstr)+17);
        prnhtml=prnhtml.substring(0,prnhtml.indexOf(eprnstr));
        console.log(prnhtml);
        window.document.body.innerHTML=prnhtml;
        window.print();
    };

    $scope.close = function(){
      // $state.go('home',{});
      $location.path('/');
    };
  }
})();

(function() {
  'use strict';

  angular
    .module('orders')
    .controller('ReciptController', ReciptController);

  ReciptController.$inject = ['$timeout', '$state', '$scope', 'OrdersService', '$stateParams', '$location'];

  function ReciptController($timeout, $state,$scope, OrdersService, $stateParams, $location) {
    var vm = this;
    vm.order = {};

    // Recipt controller logic
    // ...
    $scope.date = new Date();

    $scope.recipt = function(){
      console.log($stateParams.orderId);
      var getorder = OrdersService.get({ orderId: $stateParams.orderId }, function() {
        vm.order = getorder;
        console.log(getorder);
      });
    };
    $scope.print = function(){
       /*window.print();*/
        var bdhtml=window.document.body.innerHTML;
        var sprnstr="<!--startprint-->";
        var eprnstr="<!--endprint-->";
        var prnhtml=bdhtml.substring(bdhtml.indexOf(sprnstr)+17);
        prnhtml=prnhtml.substring(0,prnhtml.indexOf(eprnstr));
        window.document.body.innerHTML=prnhtml;
        window.print();
        // window.close();
        // $timeout(function () { window.close(); }, 40);
        // $state.go('recipt',{ orderId: $stateParams.orderId });
    };
/*
    $scope.onPrintFinished = function(print){
      window.close();
    };*/

    $scope.close = function(){
      // $state.go('home',{});
      $location.path('/');
    };
  }
})();

Add method to go back after print in the recipt page


var downloadLocales = require('webmaker-download-locales');
var glob = require('glob');
var fs = require('fs-extra');

// Which languages should we pull down?
// Don't include en-US because that's committed into git already
var supportedLanguages = ['id', 'en_CA', 'es_CL', 'fr', 'nl', 'es_MX', 'cs', 'sv', 'bn_BD', 'sw', 'hi_IN'];

module.exports = function (callback) {

    glob('!./locale/!(en_US)/**.json', function (err, files) {
        files.forEach(function (file) {
            fs.removeSync(file);
        });
        downloadLocales({
            app: 'webmaker-app',
            dir: 'locale',
            languages: supportedLanguages
        }, callback);
    });
};

var downloadLocales = require('webmaker-download-locales');
var glob = require('glob');
var fs = require('fs-extra');

// Which languages should we pull down?
// Don't include en-US because that's committed into git already
var supportedLanguages = ['id', 'en_CA', 'es_CL', 'fr', 'nl', 'es_MX', 'cs', 'sv', 'bn_BD', 'sw', 'hi_IN',
    'es', 'el', 'pt', 'es_AR', 'zh_TW', 'pt_BR', 'zh_CN', 'tr_TR', 'da_DK', 'fi', 'ru', 'sq', 'es_MX'];

module.exports = function (callback) {

    glob('!./locale/!(en_US)/**.json', function (err, files) {
        files.forEach(function (file) {
            fs.removeSync(file);
        });
        downloadLocales({
            app: 'webmaker-app',
            dir: 'locale',
            languages: supportedLanguages
        }, callback);
    });
};

Enable other locales that fully translated on Transifex


"use strict";

var dummy
  , hotplate = require('hotplate')
  , path = require('path')
;

// exports.sendResponse = function(method, res, obj){
exports.sendResponse = function(res, obj, status){

  var status;
  var isOK;

  status = status || 200;

  isOK =  (status >= 200 && status < 300) || // allow any 2XX error code
           status === 304;                   // or, get it out of the cache

  if( isOK ){
    res.json( status, obj );
  } else {

    obj = typeof(obj) == 'object' ? obj : { };

    var error = {};

    // Assign "legal" keys
    if( obj.message ) error.message = obj.message;
    if( obj.errors )  error.errors  = obj.errors;
    if( obj.data )    error.data    = obj.data;
    if( obj.emit )    error.emit    = obj.emit;

    res.json(status, error);
  }
}

hotplate.hotEvents.on( 'pageElements', 'hotCoreErrorProtocol', function( done ){

  done( null, {
    jses:  [ 'hotFixErrorResponse.js']
  });

});

hotplate.hotEvents.on( 'clientPath', 'hotCoreErrorProtocol', function( done ){
  done( null, path.join(__dirname, '../client') );
})

"use strict";

var dummy
  , hotplate = require('hotplate')
  , path = require('path')
;

// exports.sendResponse = function(method, res, obj){
exports.sendResponse = function(res, obj, status){

  var status;
  var isOK;

  status = status || 200;

  isOK =  (status >= 200 && status < 300) || // allow any 2XX error code
           status === 304;                   // or, get it out of the cache

  if( isOK ){
    res.status( status ).json( obj );
  } else {

    obj = typeof(obj) == 'object' ? obj : { };

    var error = {};

    // Assign "legal" keys
    if( obj.message ) error.message = obj.message;
    if( obj.errors )  error.errors  = obj.errors;
    if( obj.data )    error.data    = obj.data;
    if( obj.emit )    error.emit    = obj.emit;

    res.status( status ).json( error );
  }
}

hotplate.hotEvents.on( 'pageElements', 'hotCoreErrorProtocol', function( done ){

  done( null, {
    jses:  [ 'hotFixErrorResponse.js']
  });

});

hotplate.hotEvents.on( 'clientPath', 'hotCoreErrorProtocol', function( done ){
  done( null, path.join(__dirname, '../client') );
})

Update to latest Express API


import { join } from 'path'
import buildWebpackConfig from './config/build-webpack-config'
import buildKarmaConfig from './config/build-karma-config'
import startDevelop from './action/start-develop'
import runTest from './action/run-test'
import install from './action/install'
import json from './util/json'

export default {
  develop (env, options) {
    check(env)

    const webpackConfig = buildWebpackConfig(env, options)
    startDevelop(webpackConfig)
  },

  test (env, options) {
    check(env)

    const webpackConfig = buildWebpackConfig(env, options)
    const karmaConfig = buildKarmaConfig(env, options, webpackConfig)
    runTest(karmaConfig)
  },

  install (env, options) {
    check(env)

    install(env.projectPath)
  }
}

function check ({ projectPath }) {
  const packagePath = join(projectPath, 'package.json')
  const packageJSON = json.read(packagePath)
  if (packageJSON.name === 'sagui') throw new InvalidUsage()
}

export class InvalidUsage extends Error {
  constructor () {
    super()
    this.name = 'InvalidUsage'
  }
}

import { join } from 'path'
import { statSync } from 'fs'
import buildWebpackConfig from './config/build-webpack-config'
import buildKarmaConfig from './config/build-karma-config'
import startDevelop from './action/start-develop'
import runTest from './action/run-test'
import install from './action/install'
import json from './util/json'

export default {
  develop (env, options) {
    check(env)

    const webpackConfig = buildWebpackConfig(env, options)
    startDevelop(webpackConfig)
  },

  test (env, options) {
    check(env)

    const webpackConfig = buildWebpackConfig(env, options)
    const karmaConfig = buildKarmaConfig(env, options, webpackConfig)
    runTest(karmaConfig)
  },

  install (env, options) {
    check(env)

    install(env.projectPath)
  }
}

function check ({ projectPath }) {
  const packagePath = join(projectPath, 'package.json')
  if (!fileExists(packagePath)) throw new InvalidUsage()

  const packageJSON = json.read(packagePath)
  if (packageJSON.name === 'sagui') throw new InvalidUsage()
}

function fileExists (file) {
  try {
    statSync(file)
    return true
  } catch (e) {
    return false
  }
}

export class InvalidUsage extends Error {
  constructor () {
    super()
    this.name = 'InvalidUsage'
  }
}

Fix check that would crash if folder didn’t contain a package.json file


export default class UserLocation {
  constructor({
    apiKey,
    cacheTtl = 604800, // 7 days
    fallback = 'exact', // If IP-based geolocation fails
    specificity = 'general',
  }) {
    let coordsLoaded = false;

    const coords = {
      latitude: null,
      longitude: null,
      accuracy: null,
    };

    const promise = new Promise((resolve, reject) => {
      if (coordsLoaded) {
        resolve(coords);
      } else if (specificity === 'exact') {
        navigator.geolocation.getCurrentPosition(
          (pos) => {
            coordsLoaded = true;
            coords.latitude = pos.coords.latitude;
            coords.longitude = pos.coords.longitude;
            coords.accuracy = pos.coords.accuracy;
            resolve(coords);
          },
          (err) => {
            reject(`${err.message} (error code: ${err.code})`);
          }
        );
      } else if (specificity === 'general') {
        // Use GeoIP lookup to get general area
      } else {
        throw new Error('Invalid configuration value for location specificity.');
      }
    });

    console.log(apiKey, cacheTtl, fallback);

    return promise;
  }
}

export default class UserLocation {
  constructor({
    apiKey = null,
    cacheTtl = 604800, // 7 days
    fallback = 'exact', // If IP-based geolocation fails
    specificity = 'general',
  }) {
    let coordsLoaded = false;

    const coords = {
      latitude: null,
      longitude: null,
      accuracy: null,
    };

    if (apiKey === null && (specificity === 'general' || fallback === 'general')) {
      throw new Error('An API key must be included when using GeoCarrot\'s GeoIP lookup.');
    }

    const promise = new Promise((resolve, reject) => {
      if (coordsLoaded) {
        resolve(coords);
      } else if (specificity === 'exact') {
        navigator.geolocation.getCurrentPosition(
          (pos) => {
            coordsLoaded = true;
            coords.latitude = pos.coords.latitude;
            coords.longitude = pos.coords.longitude;
            coords.accuracy = pos.coords.accuracy;
            resolve(coords);
          },
          (err) => {
            reject(`${err.message} (error code: ${err.code})`);
          }
        );
      } else if (specificity === 'general') {
        // Use GeoIP lookup to get general area
      } else {
        throw new Error('Invalid configuration value for location specificity.');
      }
    });

    console.log(apiKey, cacheTtl, fallback);

    return promise;
  }
}

Add a check to ensure apiKey is included in config when using GeoCarrot’s GeoIP lookup.


'use strict';
import backbone from 'backbone';
import _ from 'underscore';

class Person extends backbone.Model {
    getFullName() {
        return this.get('firstName') + ' ' + this.get('lastName');
    }
}
// Using benmccormick's suggestion
// for decorators to apply properties to class
// before instantiation
// https://github.com/jashkenas/backbone/issues/3560#issuecomment-113709224
function props(value) {
    return function decorator(target) {
        _.extend(target.prototype, value);
    }
}
@props({
    events: {
        'click': function() { console.log('clicked!'); }
    }
})
class Hello extends backbone.View {
    initialize() {
        this.person = new Person({
            firstName: 'George',
            lastName: 'Washington'
        });
    }
    render() {
        this.$el.html('Hello, ' + this.person.getFullName() + '.');
    }
}

var myView = new Hello({el: document.getElementById('root')});
myView.render();

'use strict';
import backbone from 'backbone';
import _ from 'underscore';

class Person extends backbone.Model {
    getFullName() {
        return this.get('firstName') + ' ' + this.get('lastName');
    }
}
// Using benmccormick's suggestion
// for decorators to apply properties to class
// before instantiation
// https://github.com/jashkenas/backbone/issues/3560#issuecomment-113709224
function props(value) {
    return function decorator(target) {
        _.extend(target.prototype, value);
    }
}
@props({
    events: {
        'click': function() { console.log('clicked!'); }
    }
})
class Hello extends backbone.View {
    render() {
        this.$el.html('Hello, ' + this.model.getFullName() + '.');
    }
}

var myView = new Hello({el: document.getElementById('root'), model: new Person({
    firstName: 'George',
    lastName: 'Washington'
})});
myView.render();

Move model to this.model in constructor


import Vue from 'vue';
import Countdown from '../src/vue-countdown';
import {getVM} from './helpers';

describe('Vue countdown component', () => {
    it('sets passed label correctly', () => {
        const vm = getVM(Countdown, {
            propsData: {
                message: 'Boom'
            }
        });

        expect(vm.$data.label).toBe('Boom');
    });

    it('initializes timer properly', () => {
        const vm = getVM(Countdown, {
            propsData: {
                seconds: 5
            }
        });

        expect(vm.$el.querySelector('.vue-countdown--time').textContent.trim()).toBe('00:00:05');
    });

    it('counts to zero', (done) => {
        const vm = getVM(Countdown, {
            propsData: {
                seconds: 1
            }
        });

        setTimeout(() => {
            expect(vm.$el.querySelector('.vue-countdown--time').textContent.trim()).toBe('Time\'s up!');

            done();
        }, 1000);
    });
});

import Vue from 'vue';
import Countdown from '../src/vue-countdown';
import {getVM} from './helpers';

describe('Vue countdown component', () => {
    it('sets passed label correctly', () => {
        const vm = getVM(Countdown, {
            propsData: {
                message: 'Boom'
            }
        });

        expect(vm.$data.label).toBe('Boom');
    });

    it('initializes timer properly', () => {
        const vm = getVM(Countdown, {
            propsData: {
                seconds: 5
            }
        });

        expect(vm.$el.querySelector('.vue-countdown--time').textContent.trim()).toBe('00:00:05');
    });

    it('counts to zero', (done) => {
        const vm = getVM(Countdown, {
            propsData: {
                seconds: 1
            }
        });

        setTimeout(() => {
            expect(vm.$el.querySelector('.vue-countdown--time').textContent.trim()).toBe('Time\'s up!');

            done();
        }, 1000);
    });

    it('triggers an event when timer has finished', (done) => {
        const vm = new Vue({
            methods: {
                handleTimeExpire() {
                    // should be called
                }
            },
            render: h => h(Countdown, {
                props: {
                    seconds: 1
                },
                on: {
                    'time-expire': vm.handleTimeExpire
                }
            })
        })

        spyOn(vm, 'handleTimeExpire');

        vm.$mount();

        setTimeout(() => {
            expect(vm.handleTimeExpire).toHaveBeenCalled();

            done();
        }, 1000);
    });
});

Add test for time-expire event when timer finishes


'use strict';
const CommandUtil = require('../src/command_util').CommandUtil;
const l10nFile = __dirname + '/../l10n/commands/kill.yml';
const _ = require('../src/helpers');
const l10n = require('../src/l10n')(l10nFile);
const util = require('util');

exports.command = (rooms, items, players, npcs, Commands) => {
    return (args, player) => {
        const room = rooms.getAt(player.getLocation());
        const npc = CommandUtil.findNpcInRoom(npcs, args, room, player, true);

        if (!npc) {
            return player.warn(`Kill ${args}? If you can find them, maybe.`);
        }
        if (!npc.isPacifist) { console.log('fucked up ', npc); }
        if (npc.isPacifist()) {
            return player.warn(`${npc.getShortDesc()} radiates a calming aura.`);
        }

        if (!player.hasEnergy(5)) { return player.noEnergy(); }

        util.log(player.getName() + ' is on the offensive...');

        const fightingPlayer = _.has(npc.getInCombat(), player);

        if (fightingPlayer) {
          player.say('You are already fighting them!');
          return;
        } else if (npc.isInCombat()) {
          player.say('They are busy fighting someone else, no fair!');
          return;
        }

        npc.emit('combat', player, room, players, npcs, rooms, cleanup);

        function cleanup(success) {
            // cleanup here...
        }
    };
};

'use strict';
const CommandUtil = require('../src/command_util').CommandUtil;
const l10nFile = __dirname + '/../l10n/commands/kill.yml';
const _ = require('../src/helpers');
const l10n = require('../src/l10n')(l10nFile);
const util = require('util');

exports.command = (rooms, items, players, npcs, Commands) => {
    return (args, player) => {
        const room = rooms.getAt(player.getLocation());
        const npc = CommandUtil.findNpcInRoom(npcs, args, room, player, true);

        if (!npc) {
            return player.warn(`Kill ${args}? If you can find them, maybe.`);
        }

        if (npc.isPacifist()) {
            return player.warn(`${npc.getShortDesc()} radiates a calming aura.`);
        }

        if (!player.hasEnergy(5)) { return player.noEnergy(); }

        util.log(player.getName() + ' is on the offensive...');

        const fightingPlayer = _.has(npc.getInCombat(), player);

        if (fightingPlayer) {
          return player.say(`You are already fighting that ${npc.getShortDesc()}!`);
        } else if (npc.isInCombat()) {
          return player.say('That ${npc.getShortDesc()} is busy fighting someone else, no fair!');
        }

        npc.emit('combat', player, room, players, npcs, rooms, cleanup);

        function cleanup(success) {
            // cleanup here...
        }
    };
};

Remove unneeded console logs, improve messaging to player.


import Ember from 'ember';

export default Ember.Route.extend({
  model(params) {
    return this.store.find('seq', params.lang + "-" + params.seq).then(
      (model) => model,
      (error) => null);
  },
  afterModel(model) {
    this._super(model);
    if(Ember.isEmpty(model)) {
      this.notifier.error("No such word found!");
      this.transitionTo("application");
    } else {
      this.tracker.log("word", "viewed");
      Ember.$(document).attr('title', 'What does \"' + model.get("text") + '\" mean?');
    }

  },
});

import Ember from 'ember';
import ENV from '../config/environment';

export default Ember.Route.extend({
  model(params) {
    const key = params.lang + "-" + params.seq;
    const _this = this;
    return Ember.$.getJSON(ENV.api + "/seqs/" + key).then(
      (data) => {
        _this.store.pushPayload('wordset', data);
        return _this.store.getById('seq', key);
      }
    )
  },
  afterModel(model) {
    this._super(model);
    if(Ember.isEmpty(model)) {
      this.notifier.error("No such word found!");
      this.transitionTo("application");
    } else {
      this.tracker.log("word", "viewed");
      Ember.$(document).attr('title', 'What does \"' + model.get("text") + '\" mean?');
    }
  },
  setupController(controller, model) {
    this._super(controller, model);
    console.log("Setup controller", controller)
    controller.set("isEditing", false);
  }
});

Set up the controller after a route change wrt editing


// Continuous delivery server
const { spawn } = require('child_process')
const { resolve } = require('path')
const { createServer } = require('http')
const { urlencoded } = require('body-parser')

const hostname = '127.0.0.1'
const port = 80
const server = createServer((req, res) => {
  const { headers, method, url } = request

  // When a successful build has happened, kill the process, triggering a restart
  if (req.method === 'POST' && req.url === '/webhook') {
    // Send response
    res.statusCode = 200
    res.end()

    let body = []
    request
      .on('error', err => {
        console.error(err)
      })
      .on('data', chunk => {
        body.push(chunk)
      })
      .on('end', () => {
        body = Buffer.concat(body).toString()
        const data = urlencoded(body)
        console.log(data)
      })
  }

  res.statusCode = 404
  res.end()
}).listen(port)

// Continuous delivery server
const { spawn } = require('child_process')
const { resolve } = require('path')
const { createServer } = require('http')
const { urlencoded } = require('body-parser')

const hostname = '127.0.0.1'
const port = 80
const server = createServer((req, res) => {
  const { headers, method, url } = req

  // When a successful build has happened, kill the process, triggering a restart
  if (req.method === 'POST' && req.url === '/webhook') {
    // Send response
    res.statusCode = 200
    res.end()

    let body = []
    request
      .on('error', err => {
        console.error(err)
      })
      .on('data', chunk => {
        body.push(chunk)
      })
      .on('end', () => {
        body = Buffer.concat(body).toString()
        const data = urlencoded(body)
        console.log(data)
      })
  }

  res.statusCode = 404
  res.end()
}).listen(port)

Fix typo in CD server.


const h = require('virtual-dom/h');

module.exports = function (data) {
  const {gameID, game} = data;
  return h('div', {className: 'room'}, [
    game.playing ? null : h('form', {method: 'post', action: `/${gameID}`}, [
      h('button', {type: 'button', id: 'min'}, '-'),
      h('input', {type: 'number', name: 'max-score', min: '1', value: game.maxScore}),
      h('button', {type: 'button', id: 'max'}, '+'),
      h('button', {type: 'submit'}, 'Start')
    ]),
    h('a', {href: '/new-player/' + gameID}, 'Wanna play along?'),
    h('div', {id: 'players'}, [
      h('ul', Object.keys(game.players).map(playerID => {
        const player = game.players[playerID];
        return h('li', {
          style: {
            backgroundColor: `rgb(${player.color[1]}, ${player.color[0]}, ${player.color[2]})`
          },
          dataPlayerId: playerID
        }, 'Score: ' + player.score)
      }))
    ])
  ]);
};

const h = require('virtual-dom/h');

module.exports = function (data) {
  const {gameID, game} = data;
  return h('div', {className: 'room'}, [
    game.playing ? null : h('form', {method: 'post', action: `/${gameID}`}, [
      h('input', {type: 'number', name: 'max-score', min: '1', value: game.maxScore}),
      h('button', {type: 'submit'}, 'Start')
    ]),
    h('a', {href: '/new-player/' + gameID}, 'Wanna play along?'),
    h('div', {id: 'players'}, [
      h('ul', Object.keys(game.players).map(playerID => {
        const player = game.players[playerID];
        return h('li', {
          style: {
            backgroundColor: `rgb(${player.color[1]}, ${player.color[0]}, ${player.color[2]})`
          },
          dataPlayerId: playerID
        }, 'Score: ' + player.score)
      }))
    ])
  ]);
};

Remove + / - buttons


/* eslint no-console:0 */

// RailsUjs is *required* for links in Lucky that use DELETE, POST and PUT.
import RailsUjs from "rails-ujs";

// Turbolinks is optional. Learn more: https://github.com/turbolinks/turbolinks/
import Turbolinks from "turbolinks";

RailsUjs.start();
Turbolinks.start();

/* eslint no-console:0 */

// RailsUjs is *required* for links in Lucky that use DELETE, POST and PUT.
// Though it says "Rails" it actually works with any framework.
import RailsUjs from "rails-ujs";

// Turbolinks is optional. Learn more: https://github.com/turbolinks/turbolinks/
import Turbolinks from "turbolinks";

RailsUjs.start();
Turbolinks.start();

// If using Turbolinks, you can attach events to page load like this:
//
// document.addEventListener("turbolinks:load", function() {
//   ...
// })

Clarify how to attach events and RailsUjs


import DS from 'ember-data';
import ApplicationSerializer from '../serializers/application';

export default ApplicationSerializer.extend({

  normalize: function (modelClass, resourceHash, prop) {
    var normalizedHash = resourceHash;
    var normalizedFasiliteter = [];

    // Maps `fasiliteter` object to an array of objects with the key as `type` and value as `kommentar`
    if (resourceHash.fasiliteter) {
      for (var key in resourceHash.fasiliteter) {
        normalizedFasiliteter.push({type: key, kommentar: resourceHash.fasiliteter[key]});
      }
      normalizedHash.fasiliteter = normalizedFasiliteter;
    }

    return this._super(modelClass, normalizedHash, prop);
  },

  serialize: function(snapshot, options) {
    var json = this._super(snapshot, options);

    // Maps `fasiliteter` array back to object
    if (json.fasiliteter && json.fasiliteter.length) {
      var serializedFasiliteter = {};
      for (var i = 0; i < json.fasiliteter.length; i++) {
        serializedFasiliteter[json.fasiliteter[i]['type']] = json.fasiliteter[i]['kommentar'] || '';
      }
      json.fasiliteter = serializedFasiliteter;
    }

    return json;
  }

});

import DS from 'ember-data';
import ApplicationSerializer from '../serializers/application';

export default ApplicationSerializer.extend({

  normalize: function (modelClass, resourceHash, prop) {
    var normalizedHash = resourceHash;
    var normalizedFasiliteter = [];

    // Maps `fasiliteter` object to an array of objects with the key as `type` and value as `kommentar`
    if (resourceHash.fasiliteter) {
      for (var key in resourceHash.fasiliteter) {
        normalizedFasiliteter.push({type: key, kommentar: resourceHash.fasiliteter[key]});
      }
      normalizedHash.fasiliteter = normalizedFasiliteter;
    }

    return this._super(modelClass, normalizedHash, prop);
  },

  serialize: function(snapshot, options) {
    var json = this._super(snapshot, options);

    // Maps `fasiliteter` array back to object
    if (json.fasiliteter && json.fasiliteter.length) {
      var serializedFasiliteter = {};
      for (var i = 0; i < json.fasiliteter.length; i++) {
        serializedFasiliteter[json.fasiliteter[i]['type']] = json.fasiliteter[i]['kommentar'] || '';
      }
      json.fasiliteter = serializedFasiliteter;
    }

    // Update `tilrettelagt_for` with types from `tilrettelegginger`
    if (json.tilrettelegginger && json.tilrettelegginger.length) {
      json.tilrettelagt_for = json.tilrettelegginger.mapBy('type');
    }

    return json;
  }

});

Update tilrettelagt_for array with types from tilrettelegginger on save


/* eslint-disable object-shorthand */
module.exports = {
  // We need to make sure that the transpiler hit the linoleum source since we are
  // not building to npm or simliar.
  ignore: function(filename) {
    return ((/node_modules/.test(filename))
        && !(/linoleum(-[^/]*)?\/electron/.test(filename))
        && !(/linoleum(-[^/]*)?\/src/.test(filename))
        && !(/linoleum(-[^/]*)?\/tasks/.test(filename)))
        || (/\$.*\$/.test(filename));
  },
  sourceMap: 'inline',
  auxiliaryCommentBefore: 'istanbul ignore start',
  auxiliaryCommentAfter: 'istanbul ignore end',
  presets: [
    require.resolve('babel-preset-react'),
    require.resolve('babel-preset-es2015'),
    require.resolve('babel-preset-stage-0')
  ]
};

/* eslint-disable object-shorthand */
module.exports = {
  // We need to make sure that the transpiler hit the linoleum source since we are
  // not building to npm or simliar.
  ignore: function(filename) {
    return ((/node_modules/.test(filename))
        && !(/linoleum(-[^/]*)?\/(electron|src|tasks|Gulpfile)/.test(filename)))
        || (/\$.*\$/.test(filename));
  },
  sourceMap: 'inline',
  auxiliaryCommentBefore: 'istanbul ignore start',
  auxiliaryCommentAfter: 'istanbul ignore end',
  presets: [
    require.resolve('babel-preset-react'),
    require.resolve('babel-preset-es2015'),
    require.resolve('babel-preset-stage-0')
  ]
};

Simplify bable includes and add Gulpfiles


import buble from '@rollup/plugin-buble'

export default {
  input: 'src/commands.js',
  output: {
    dir: 'dist',
    format: 'cjs',
    sourcemap: true
  },
  plugins: [buble()],
  external(id) { return !/^[\.\/]/.test(id) }
}

import buble from '@rollup/plugin-buble'

export default {
  input: './src/commands.js',
  output: {
    dir: 'dist',
    format: 'cjs',
    sourcemap: true
  },
  plugins: [buble()],
  external(id) { return !/^[\.\/]/.test(id) }
}

Add missing ./ prefix in rollup input


module.exports = (function(){

	// GET /users/:id/media?lat=<LAT>&lng=<LNG>&time=<TIME>
	return function* collection(id) {

		// Twitter Requests
    var twitterDef = require('q').defer()
    var TwitterManager = require('../media/twitter');
    var twitterGranuals = twitterDef.promise.then(TwitterManager.search);
  //   var twitterDef = require('q').defer()

    // var instagramDef = require('q').defer()
    var InstagramManager = require('../media/instagram');
    // var instagramGranuals = instagramDef.promise.then(InstagramManager.search);
    var instagramGranuals = InstagramManager.search(this.request.url);

    // Flickr Requests
    // var FlickrManager = require('../media/flickr');
    // var flickrGranuals = yield FlickrManager.search(this.request.url);

    // Creating a universal capsul object
    var capsul = {
      "user_id": id,
      "latitude": require('../../helpers').paramsForUrl(this.request.url).lat,
      "longitude": require('../../helpers').paramsForUrl(this.request.url).lng,
      "timestamp": require('../../helpers').paramsForUrl(this.request.url).time,
      "data": []
    }

    // def.resolve(this.request.url)
    // var instaGranuals = def.promise.then(InstagramManager.search);
    // capsul.data.push(instagramGranuals)

    twitterDef.resolve(this.request.url)
    // instagramDef.resolve(this.request.url)

    capsul.data.push(twitterGranuals);
    capsul.data.push(instagramGranuals)

		this.body = yield capsul;
	}
})();

module.exports = (function(){

	// GET /users/:id/media?lat=<LAT>&lng=<LNG>&time=<TIME>
	return function* collection(id) {
    var Q = require('q');
    var TwitterManager = require('../media/twitter');
    var InstagramManager = require('../media/instagram');

    // Twitter Requests
    var twitterDef = Q.defer();
    var twitterGranuals = twitterDef.promise
                          .then(TwitterManager.search);

    // Instagram Requests
    var instagramDef = Q.defer();
    var instagramGranuals = instagramDef.promise
                            .then(InstagramManager.search);

    // Flickr Requests
    // var FlickrManager = require('../media/flickr');
    // var flickrGranuals = yield FlickrManager.search(this.request.url);

    // Creating a universal capsul object
    var capsul = {
      "user_id": id,
      "latitude": require('../../helpers').paramsForUrl(this.request.url).lat,
      "longitude": require('../../helpers').paramsForUrl(this.request.url).lng,
      "timestamp": require('../../helpers').paramsForUrl(this.request.url).time,
      "data": []
    }

    twitterDef.resolve(this.request.url)
    instagramDef.resolve(this.request.url)

    capsul.data.push(twitterGranuals);
    capsul.data.push(instagramGranuals);

		this.body = yield capsul;
	}
})();

Refactor to use promises for social media service responses.


var artistController = require('../controllers/artist-controller');
var genreController = require('../controllers/genre-controller');

module.exports = function(app, passport) {
  app.get('/auth/facebook', passport.authenticate('facebook', { scope : 'email' }));

  app.get('/auth/facebook/callback',
  passport.authenticate('facebook', {
    failureRedirect : '/'
  }), function(req, res) {
    return res.status(200).send(req.user);
  });

  app.get('/profile', function(req, res) {
    return res.send(200);
  });

  app.post('/api/artist/:id/like', artistController.likeArtist);
  app.get('/api/artist/recommendation', artistController.getArtistRecommendations);

  app.get('/api/genre', genreController.getGenres);
  app.get('/api/genre/artists', genreController.getArtistsByGenre);

  app.get('*', function(req, res) {
    return res.render('index.html');
  });
};

var artistController = require('../controllers/artist-controller');
var genreController = require('../controllers/genre-controller');

module.exports = function(app, passport) {
  app.get('/auth/facebook', passport.authenticate('facebook', { scope : 'email' }));

  app.get('/auth/facebook/callback',
  passport.authenticate('facebook', {
    failureRedirect : '/',
    successRedirect : '/'
  }));

  app.get('/profile', function(req, res) {
    return res.send(200);
  });

  app.post('/api/artist/:id/like', artistController.likeArtist);
  app.get('/api/artist/recommendation', artistController.getArtistRecommendations);

  app.get('/api/genre', genreController.getGenres);
  app.get('/api/genre/artists', genreController.getArtistsByGenre);

  app.get('*', function(req, res) {
    return res.render('index.html');
  });
};

Change route file to be used later


$.extend( true, $.fn.dataTable.defaults, {
    "autoWidth": false,
    "columnDefs": [
        { width: '10%', targets: 0 },
        { width: '15%', targets: 1 },
        { width: '10%', targets: 2 },
        { width: '10%', targets: 3 },
        { width: '25%', targets: 4 },
        { width: '10%', targets: 5 },
        { width: '10%', targets: 6 },
    ],
    "order": [[ 0, "asc" ]]
} );

$(document).ready(function() {
    $('#items').DataTable();

    // Breadcum
    $('#breadcrumb-home').append(en.cms.breadcrumb.home);
    $('#breadcrumb-user').append(en.cms.breadcrumb.user);

    // Table headers
    $('#header-username').append(en.cms.header.username);
    $('#header-email').append(en.cms.header.email);
    $('#header-firstname').append(en.cms.header.firstname);
    $('#header-lastname').append(en.cms.header.lastname);
    $('#header-lastlogin').append(en.cms.header.lastlogin);
    $('#header-isstaff').append(en.cms.header.isstaff);
    $('#header-isactive').append(en.cms.header.isactive);

    $('#main-title').append(en.cms.breadcrumb.user);

    $('#add').append(en.cms.action.add_user);
} );

$.extend( true, $.fn.dataTable.defaults, {
    "autoWidth": false,
    "columnDefs": [
        { width: '10%', targets: 0 },
        { width: '10%', targets: 1 },
        { width: '10%', targets: 2 },
        { width: '10%', targets: 3 },
        { width: '20%', targets: 4 },
        { width: '10%', targets: 5 },
        { width: '10%', targets: 6 },
        { width: '20%', targets: 7 },
    ],
    "order": [[ 0, "asc" ]]
} );

$(document).ready(function() {
    $('#items').DataTable();

    // Breadcum
    $('#breadcrumb-home').append(en.cms.breadcrumb.home);
    $('#breadcrumb-user').append(en.cms.breadcrumb.user);

    // Table headers
    $('#header-username').append(en.cms.header.username);
    $('#header-email').append(en.cms.header.email);
    $('#header-firstname').append(en.cms.header.firstname);
    $('#header-lastname').append(en.cms.header.lastname);
    $('#header-lastlogin').append(en.cms.header.lastlogin);
    $('#header-isstaff').append(en.cms.header.isstaff);
    $('#header-isactive').append(en.cms.header.isactive);

    $('#main-title').append(en.cms.breadcrumb.user);

    $('#add').append(en.cms.action.add_user);
} );

Fix DataTable in User page


const productionConfig = {
  apiBase: 'http://api.storypalette.net/v1/',
  socketBase: 'http://api.storypalette.net/',
  environment: 'production',
  port: 8882,
};

const developmentConfig = {
  apiBase: 'http://localhost:8880/v1/',
  socketBase: 'http://localhost:8880/',
  environment: 'local',
  port: 8882,
}

const config = (process.env.NODE_ENV === 'development') ? developmentConfig : productionConfig;

module.exports = config;

const productionConfig = {
  //apiBase: 'http://api.storypalette.net/v1/',
  //socketBase: 'http://api.storypalette.net/',
  apiBase: 'http://storypalette-server.herokuapp.com/v1/',
  socketBase: 'http://storypalette-server.herokuapp.com/',
  environment: 'production',
  port: 8882,
};

const developmentConfig = {
  apiBase: 'http://localhost:8880/v1/',
  socketBase: 'http://localhost:8880/',
  environment: 'local',
  port: 8882,
}

const config = (process.env.NODE_ENV === 'development') ? developmentConfig : productionConfig;

module.exports = config;

Use heroku api base for now


window.onload = function() {
  var xScale = new Plottable.LinearScale();
  var yScale = new Plottable.LinearScale();

  var xAxis = new Plottable.XAxis(xScale, "bottom");
  var yAxis = new Plottable.YAxis(yScale, "left");

  // A DataSource is a Plottable object that maintains data and metadata, and updates dependents when it changes
  // In the previous example, we implicitly created a DataSource by putting the data directly into the Renderer constructor
  var gitDataSource = new Plottable.DataSource(gitData);
  var renderer = new Plottable.LineRenderer(gitDataSource, xScale, yScale);

  // We define an accessor function that the renderer will use to access a "Perspective" into the DataSource
  function dayAccessor(d) {
    return d.day;
  }
  // By calling renderer.project, we tell the renderer to set the "x" attribute using the dayAccessor function
  // and to project it through the xScale. This creates a binding between the data and the scale, so that the
  // scale automatically sets its domain, and will update its domain if the data changes
  renderer.project("x", dayAccessor, xScale);

  // If Plottable gets a string as an accessor argument, it will automatically turn it into a key function, as follows:
  // project(attr, "total_commits", scale) == project(attr, function(d) {return d.total_commits}, scale)
  renderer.project("y", "total_commits", yScale);

  // This accessor is somewhat more sophisticated - it performs some data aggregation on-the-fly for renderering
  function linesAddedAccessor(d) {
    var total = 0;
    d.changes.forEach(function(c) {
      total += c.additions;
    });
    return total;
  }

  // Make a LogScale. Since the range doesn't correspond to the layout bounds of a renderer, we need to set the range ourselves.
  var radiusScale = new Plottable.LogScale().range([1, 10]);

  renderer.project("r", linesAddedAccessor, radiusScale)

  var chart = new Plottable.Table([
                    [yAxis, renderer],
                    [null,  xAxis   ]
                  ]);

  chart.renderTo("#chart");
}

window.onload = function() {
  var xScale = new Plottable.LinearScale();
  var yScale = new Plottable.LinearScale();

  var xAxis = new Plottable.XAxis(xScale, "bottom");
  var yAxis = new Plottable.YAxis(yScale, "left");
  var renderer = new Plottable.LineRenderer(gitData, xScale, yScale);

  function getXDataValue(d) {
    return d.day;
  }
  renderer.project("x", getXDataValue, xScale);

  function getYDataValue(d) {
    return d.total_commits;
  }
  renderer.project("y", getYDataValue, yScale);

  var chart = new Plottable.Table([
                    [yAxis, renderer],
                    [null,  xAxis   ]
                  ]);

  chart.renderTo("#chart");
}

Revert "Update customProjector example to use a scale. Doesn't work yet (pending merge to gh-pages)"