import React, { Component } from 'react'

import styles from './ProjectButtons.styl'

export default class extends Component {
  constructor() {
    super()
    this.state = { isSaving: false }
  }

  render() {
    const { isSaving } = this.state
    const { onCancelChanges, onSaveChanges, changesExist } = this.props

    return (
      <div className={styles.container}>
        <button
          onClick={onCancelChanges}
          type="button"
          name="button"
          disabled={isSaving || !changesExist}
          className="btn"
        >
          Cancel
        </button>
        <button
          style={{
            display: 'flex',
            alignItems: 'center',
          }}
          onClick={() => {
            this.setState({ isSaving: true })
            onSaveChanges().then(() => {
              this.setState({ isSaving: false })
            })
          }}
          type="button"
          name="button"
          disabled={isSaving}
          className="btn btn-primary"
        >
          {isSaving ? 'Saving...' : 'Save'}
          <img
            src="/public/img/ripple.svg"
            style={{
              marginLeft: '0.7rem',
              marginRight: '-0.6rem',
              width: isSaving ? '18px' : 0,
            }}
          />
        </button>
      </div>
    )
  }
}

import React, { Component } from 'react'

import styles from './ProjectButtons.styl'

export default class extends Component {
  constructor() {
    super()
    this.state = { isSaving: false }
  }

  render() {
    const { isSaving } = this.state
    const { onCancelChanges, onSaveChanges, changesExist } = this.props

    return (
      <div className={styles.container}>
        <button
          onClick={onCancelChanges}
          type="button"
          name="button"
          disabled={isSaving || !changesExist}
          className="btn"
        >
          Cancel
        </button>
        <button
          style={{
            display: 'flex',
            alignItems: 'center',
          }}
          onClick={() => {
            this.setState({ isSaving: true })
            onSaveChanges().then(() => {
              this.setState({ isSaving: false })
            })
          }}
          type="button"
          name="button"
          disabled={isSaving || !changesExist}
          className="btn btn-primary"
        >
          {isSaving ? 'Saving...' : 'Save'}
          <img
            src="/public/img/ripple.svg"
            style={{
              marginLeft: '0.7rem',
              marginRight: '-0.6rem',
              width: isSaving ? '18px' : 0,
            }}
          />
        </button>
      </div>
    )
  }
}

Disable the save button when there's nothing to save


/* eslint global-require: 0 */
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import createDebounce from 'redux-debounced';
import rootReducer from '../modules/reducer';
import db from '../db';

const middlewares = [
  createDebounce(),
  thunk.withExtraArgument({ db }),
];

if ('production' !== process.env.NODE_ENV) {
  const createLogger = require('redux-logger');

  middlewares.push(createLogger());
}

const createStoreWithMiddleware = applyMiddleware(...middlewares)(createStore);

export default function configureStore(initialState) {
  return createStoreWithMiddleware(
    rootReducer,
    initialState,
    window.devToolsExtension && window.devToolsExtension()
  );
}

/* eslint global-require: 0 */
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import createDebounce from 'redux-debounced';
import rootReducer from '../modules/reducer';
import db from '../db';

const middlewares = [
  createDebounce(),
  thunk.withExtraArgument({ db }),
];

if ('production' !== process.env.NODE_ENV) {
  const createLogger = require('redux-logger');

  middlewares.push(createLogger());
}

const createStoreWithMiddleware = applyMiddleware(...middlewares)(createStore);

export default function configureStore(initialState) {
  return createStoreWithMiddleware(
    rootReducer,
    initialState,
    'production' !== process.env.NODE_ENV && typeof 'undefined' !== window &&
    window.devToolsExtension ? window.devToolsExtension() : f => f
  );
}

Disable redux devtools in production


/*jshint node: true */

module.exports = {

    /**
     * Returns true if and only if the string value of the first argument
     * ends with the given suffix. Otherwise, returns false. Does NOT support
     * the length argument.
     *
     * Normally, one would just use String.prototype.endsWith, but since there
     * are Node versions out there which don't support ES6 yet, this function
     * exists. It either wraps the native method, or alternatively performs
     * the check manually.
     */
    endsWith: function (str, suffix) {

        var subject = str.toString();
        if (typeof subject.endsWith === "function")
            return subject.endsWith(suffix);

        return subject.substr(-suffix.length) === suffix;

    }

};

/*jshint node: true */

module.exports = {

    /**
     * Returns true if and only if the string value of the first argument
     * ends with the given suffix. Otherwise, returns false. Does NOT support
     * the length argument.
     *
     * Normally, one would just use String.prototype.endsWith, but since there
     * are Node versions out there which don't support ES6 yet, this function
     * exists. It either wraps the native method, or alternatively performs
     * the check manually.
     */
    endsWith: function (str, suffix) {

        var subject = str.toString();
        if (typeof subject.endsWith === "function")
            return subject.endsWith(suffix);

        if (suffix === "")
            return true;

        return subject.slice(-suffix.length) === suffix;

    }

};

Fix .endsWith() not matching empty string on 0.10


import React from 'react'
import { NavLink } from 'gatsby'

import styles from './navigation.module.styl'

export default ({ sports }) => (

    <ol className={`${styles.sportsList}`}>
      {sports.map(({ node }, idx) => (
        <li key={idx} className={`${styles.sportItem}`}>

            <NavLink
              to={`/${node.name}`}
              activeClassName={styles.active}
              className={styles.navLink}
            >
              {node.name}
            </NavLink>

          <ol className={`${styles.countriesList}`}>
            {node.countries.map((country, idx) => (
              <li key={idx} className={`${styles.countryItem}`}>

                  <NavLink
                    to={`/${node.name}/${country.name}`}
                    activeClassName={styles.active}
                    className={styles.navLink}
                  >
                    {country.name}
                  </NavLink>

            ))}

      ))}

)

import React from 'react'
import { Link } from 'gatsby'

import styles from './navigation.module.styl'

// this is only active when the location pathname is exactly
// the same as the href.
const isActive = ({ location, href }) => {
  const decodedURI = decodeURI(location.pathname)
  const isCurrent = decodedURI === href
  return {
    className: `${styles.navLink} ${isCurrent ? styles.active : ''}`
  }
}
const ExactNavLink = props => <Link getProps={isActive} {...props} />

// this link will be active when itself or deeper routes
// are current
const isPartiallyActive = ({ location, href }) => {
  const decodedURI = decodeURI(location.pathname)
  const isPartiallyCurrent = decodedURI.startsWith(href)
  return {
    className: `${styles.navLink} ${isPartiallyCurrent ? styles.active : ''}`
  }
}
const PartialNavLink = props => <Link getProps={isPartiallyActive} {...props} />

export default ({ sports }) => (

    <ol className={`${styles.sportsList}`}>
      {sports.map(({ node }, idx) => (
        <li key={idx} className={`${styles.sportItem}`}>

            <PartialNavLink to={`/${node.name}`}>{node.name}</PartialNavLink>

          <ol className={`${styles.countriesList}`}>
            {node.countries.map((country, idx) => (
              <li key={idx} className={`${styles.countryItem}`}>

                  <ExactNavLink to={`/${node.name}/${country.name}`}>
                    {country.name}
                  </ExactNavLink>

            ))}

      ))}

)

Remove NavLink: unexistent in v2; Create two auxiliary components to apply the correct CSS class(es) to the links.


#!/usr/bin/env node

var Mocha = require('mocha');

var mocha = new Mocha();
mocha.reporter('spec').ui('tdd');

mocha.addFile('test/basicPubSub.js');

var runner = mocha.run(function() {
  process.exit(0);
});

#!/usr/bin/env node

var Mocha = require('mocha');

var mocha = new Mocha();
mocha.reporter('spec').ui('tdd');

mocha.addFile('test/basicPubSub.js');

var runner = mocha.run(function(failures) {
  process.on('exit', function() {
    process.exit(failures);
  });
});

Exit with a non-zero status code on test failure(s)


import { Mongo } from 'meteor/mongo';
import sinon from 'sinon';
import jsc from 'jsverify';

export function union (array1, array2) {
  const result = new Set(array1);
  for (const item of array2) {
    result.add(item);
  }
  return Array.from(result);
}

export function property (arbitrary, verify) {
  let error = null;
  const test = jsc.forall(arbitrary, function (...args) {
    try {
      return verify(...args);
    } catch (err) {
      error = err;
    }
    return false;
  });
  return function () {
    try {
      jsc.assert(test);
    } catch (err) {
      if (error) {
        error.message = err.message + '; Original message: ' + error.message;
        throw error;
      }
      throw err;
    }
  };
}

export class Sandbox {
  constructor () {
    this.sandbox = sinon.sandbox.create();
  }
  collection (collection, documents=[]) {
    const testCollection = new Mongo.Collection(null, {
      transform: collection._transform
    });
    [
      'find',
      'findOne',
      'insert',
      'update',
      'remove',
    ].forEach(name => {
      this.sandbox.stub(collection, name, testCollection[name].bind(testCollection));
    });
    for (const doc of documents) {
      testCollection.insert(doc);
    }
  }
  restore () {
    this.sandbox.restore();
  }
}

import { Mongo } from 'meteor/mongo';
import sinon from 'sinon';
import jsc from 'jsverify';

export function property (arbitrary, verify) {
  let error = null;
  const test = jsc.forall(arbitrary, function (...args) {
    try {
      return verify(...args);
    } catch (err) {
      error = err;
    }
    return false;
  });
  return function () {
    try {
      jsc.assert(test);
    } catch (err) {
      if (error) {
        error.message = err.message + '; Original message: ' + error.message;
        throw error;
      }
      throw err;
    }
  };
}

export class Sandbox {
  constructor () {
    this.sandbox = sinon.sandbox.create();
  }
  collection (collection, documents=[]) {
    const testCollection = new Mongo.Collection(null, {
      transform: collection._transform
    });
    [
      'find',
      'findOne',
      'insert',
      'upsert',
      'update',
      'remove',
    ].forEach(name => {
      this.sandbox.stub(collection, name, testCollection[name].bind(testCollection));
    });
    for (const doc of documents) {
      testCollection.insert(doc);
    }
  }
  restore () {
    this.sandbox.restore();
  }
}

Create stub for "upsert" as well


const { MIN_ECMA_VERSION } = require('./constants');

const astTypeToFeatures = getAstTypeToFeatures(require('../data/es-features'));

function getAstTypeToFeatures(esFeatures) {
  const astTypeToFeatures = {};
  for (feature of esFeatures) {
    if (!astTypeToFeatures[feature.astType]) {
      astTypeToFeatures[feature.astType] = [];
    }
    astTypeToFeatures[feature.astType].push(feature);
  }
  return astTypeToFeatures;
}

function getNodeEcmaVersion(node) {
  const features = astTypeToFeatures[node.type];
  const matchedEcmaVersions = features
    ? features
        .filter((feature) => (feature.isMatch ? feature.isMatch(node) : true))
        .map((feature) => feature.ecmaVersion)
    : [];
  return Math.max(...matchedEcmaVersions, MIN_ECMA_VERSION);
}

module.exports = getNodeEcmaVersion;

const { MIN_ECMA_VERSION } = require('./constants');

const astTypeToFeatures = getAstTypeToFeatures(require('../data/es-features'));

function getAstTypeToFeatures(esFeatures) {
  const astTypeToFeatures = {};
  for (const feature of esFeatures) {
    if (!astTypeToFeatures[feature.astType]) {
      astTypeToFeatures[feature.astType] = [];
    }
    astTypeToFeatures[feature.astType].push(feature);
  }
  return astTypeToFeatures;
}

function getNodeEcmaVersion(node) {
  const features = astTypeToFeatures[node.type];
  const matchedEcmaVersions = features
    ? features
        .filter((feature) => (feature.isMatch ? feature.isMatch(node) : true))
        .map((feature) => feature.ecmaVersion)
    : [];
  return Math.max(...matchedEcmaVersions, MIN_ECMA_VERSION);
}

module.exports = getNodeEcmaVersion;

Add const to feature for of loop


var index = require('../src/index');

describe('index', function() {
  it('should provide the Hyperdeck class', function() {
    index.Hyperdeck.should.be.ok;
  });
});

var index = require('../src/index');

describe('index', function() {
  it('should provide the Hyperdeck class', function() {
    index.Hyperdeck.should.be.ok;
  });

  it('should provide the HyperdeckCore class', function() {
    index.HyperdeckCore.should.be.ok;
  });
});

Add test to check for "HyperdeckCore"


import Ember from 'ember';
import { module, test, skip } from 'qunit';
import startApp from 'ttz/tests/helpers/start-app';

let application;

module('Acceptance | Index', {
  beforeEach() {
    application = startApp();
  },

  afterEach() {
    Ember.run(application, 'destroy');
  }
});

test('with no users, shows message', function(assert) {
  assert.expect(3);

  authenticateSession();

  visit('/');

  andThen(function() {
    assert.equal(currentURL(), '/');

    assert.equal(find('.user-profile').length, 0, 'shows no users');

    assert.equal(find('.cta').text().trim(), 'No users found.',
                 'shows message that no users are found');
  });
});

skip('shows a list of users grouped by timezone', function(assert) {
});

skip('distant timezones have more space between them', function(assert) {
});

skip('can search for users by name', function(assert) {
});

import Ember from 'ember';
import { module, test, skip } from 'qunit';
import startApp from 'ttz/tests/helpers/start-app';

let application;

module('Acceptance | Index', {
  beforeEach() {
    application = startApp();
  },

  afterEach() {
    Ember.run(application, 'destroy');
  }
});

test('with no users, shows message', function(assert) {
  assert.expect(3);

  authenticateSession();

  visit('/');

  andThen(function() {
    assert.equal(currentURL(), '/', 'shows index page');

    assert.equal(find('.user-profile').length, 0, 'shows no users');

    assert.equal(find('.cta').text().trim(), 'No users found.',
                 'shows message that no users are found');
  });
});

test('shows a list of users grouped by timezone', function(assert) {
  assert.expect(4);

  authenticateSession();

  let tzOffsets = [-25200, -25200, 3600, 7200, 7200];
  let users = tzOffsets.map(tzo => server.create('user', { 'tz_offset': tzo }));

  visit('/');

  andThen(function() {
    assert.equal(find('.user-profile').length, 5, 'shows 5 user profiles');

    assert.equal(find('.timezone-group').length, 3,
                 'users grouped into 3 timezones');

    assert.equal(find('.timezone-column').length, 10,
                 'shows 11 total timezone columns');

    assert.equal(find('.timezone-column--empty').length, 7,
                 '8 of the timezone columns are empty');
  });
});

skip('distant timezones have more space between them', function(assert) {
});

skip('can search for users by name', function(assert) {
});

Add acceptance test for showing users


'use strict';

var validation = require('../lib/index')
, app = require('./app')
, should = require('should')
, request = require('supertest');

describe.only('set default values', function() {
  describe('when the values are  missing', function() {
  // Expect default values to be set
    it('should return the request with default values', function(done) {
      request(app)
        .post('/defaults')
        .send({firstname: "Jane", lastname: "Doe"})
        .expect(200)
        .end(function (err, res) {
          var response = JSON.parse(res.text);
          response.username.should.equal("jane-doe");
          done();
        });
    });
  });

})

'use strict';

var validation = require('../lib/index')
, app = require('./app')
, should = require('should')
, request = require('supertest');

describe('set default values', function() {
  describe('when the values are  missing', function() {
  // Expect default values to be set
    it('should return the request with default values', function(done) {
      request(app)
        .post('/defaults')
        .send({firstname: "Jane", lastname: "Doe"})
        .expect(200)
        .end(function (err, res) {
          var response = JSON.parse(res.text);
          response.username.should.equal("jane-doe");
          done();
        });
    });
  });

})

Remove .only() call for tests. Sorry


var assert = require('assert');

describe('Landing page', function() {
  it('should have the correct title', function(done, server, client) {
    // Get the title with some Laika mumbo jumbo and a dash of jQuery:
    var title = client.evalSync(function() {
      var titleText = $('title').text();

      emit('return', titleText);
    });

    // Check if it's correct.
    assert.equal(title, 'mds');

    // All tests end with the 'done' callback.
    done();
  });

  it('should display "Hello world!"', function(done, server, client) {
    var paragraph = client.evalSync(function() {
      emit('return', $('p').text());
    });

    assert.equal(paragraph, "Hello world!");
    done();
  });
});

var assert = require('assert');

describe('Landing page', function() {
  it('should have the correct title', function(done, server, client) {
    // Get the title with some Laika mumbo jumbo and a dash of jQuery:
    var title = client.evalSync(function() {
      var titleText = $('title').text();

      // Laika test functions don't end with a return call. Instead, they use
      // emit('return', anything);

      // You can pass anything, object or whatever you want.
      emit('return', titleText);
    });

    // Check if it's correct.
    assert.equal(title, 'mds');

    // All tests end with the 'done' callback.
    done();
  });

  it('should display "Hello world!"', function(done, server, client) {
    var paragraph = client.evalSync(function() {
      emit('return', $('p').text());
    });

    assert.equal(paragraph, "Hello world!");
    done();
  });
});

Comment tests better, a bit


'use strict';

angular.module('inventory.controllers')
  .controller('OrderTableCtrl', ['$scope', 'orderService', 'formService',
    function ($scope, orderService, formService) {
      $scope.inventoryOrders = $scope.parts.inventoryOrders;

      $scope.deliveredOrders = function (orders) {
        return orderService.selectDelivered(orders);
      };

      $scope.isDelivered = function (order) {
        return orderService.isDelivered(order.status);
      };

      $scope.openEditOrderForm = function (inventoryOrder) {
        var modalInstance = null;

        $scope.orderFormInputs = {
          id: inventoryOrder.id,
          statusChangedDate: new Date()
        };

        $scope.statusList = orderService.nextStatusList(inventoryOrder.status);

        $scope.editOrder = function () {
          orderService.edit($scope.orderFormInputs)
            .then(function () {
              $scope.reloadProduct();
              modalInstance.close();
            });
        };

        $scope.removeOrderAlert = function () {
          var removeAlertInstance = null;

          removeAlertInstance.close();

          $scope.removeAlertMessage = 'Are you sure to remove the order?';

          $scope.removeAlertFunc = function () {
            orderService.remove($scope.orderFormInputs.id)
              .then(function () {
                $scope.reloadProduct();
                removeAlertInstance.close();
              });
          };

          modalInstance = formService.openRemoveAlert($scope);
        };

        modalInstance = formService.openForm({
          templateUrl: '/views/inventories/modals/edit-order-form.html',
          scope: $scope
        });
      };
    }]);

'use strict';

angular.module('inventory.controllers')
  .controller('OrderTableCtrl', ['$scope', 'orderService', 'formService',
    function ($scope, orderService, formService) {
      $scope.inventoryOrders = $scope.parts.inventoryOrders;

      $scope.deliveredOrders = function (orders) {
        return orderService.selectDelivered(orders);
      };

      $scope.isDelivered = function (order) {
        return orderService.isDelivered(order.status);
      };

      $scope.openEditOrderForm = function (inventoryOrder) {
        var modalInstance = null;

        $scope.orderFormInputs = {
          id: inventoryOrder.id,
          statusChangedDate: new Date()
        };

        $scope.statusList = orderService.nextStatusList(inventoryOrder.status);

        $scope.editOrder = function () {
          orderService.edit($scope.orderFormInputs)
            .then(function () {
              $scope.reloadProduct();
              modalInstance.close();
            });
        };

        $scope.removeOrderAlert = function () {
          var removeAlertInstance = null;

          modalInstance.close();

          $scope.removeAlertMessage = 'Are you sure to remove the order?';

          $scope.removeAlertFunc = function () {
            orderService.remove($scope.orderFormInputs.id)
              .then(function () {
                $scope.reloadProduct();
                removeAlertInstance.close();
              });
          };

          removeAlertInstance = formService.openRemoveAlert($scope);
        };

        modalInstance = formService.openForm({
          templateUrl: '/views/inventories/modals/edit-order-form.html',
          scope: $scope
        });
      };
    }]);

Fix removing the order of inventory


import React from 'react';
import { Route } from 'react-router';
import Application from '../../app/scripts/containers/Application.jsx';
import Login from '../../app/scripts/pages/Login.jsx';

export default function(store) {
  return (
    <Route path="/" component={Application}>
      <Route path="/login" component={Login} />
    </Route>
  );
}

import React from 'react';
import { Route } from 'react-router';
import Application from '../../app/scripts/containers/Application.jsx';
import Login from '../../app/scripts/pages/Login.jsx';
import Mobilizations from '../../app/scripts/containers/Mobilizations.jsx';
import ListMobilizations from '../../app/scripts/pages/ListMobilizations.jsx';
import TopMenu from '../../app/scripts/components/TopMenu.jsx';

export default function(store) {
  return (
    <Route component={Application}>
      <Route path="/login" component={Login} />
      <Route component={Mobilizations}>
        <Route path="/" components={{main: ListMobilizations, topMenu: TopMenu}} />
      </Route>
    </Route>
  );
}

Add route to mobilizations list


// @flow

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

const DEFAULT_MAX_AGE = 15 * 60 * 1000 // 15 minutes
const STORE = 'api-cacher'

class Cacher {
  load(options: {
    key: string,
    maxAge?: ?number,
  }): ?any {
    let { key, maxAge } = options
    let storage: Cache = JSON.parse(localStorage.getItem(STORE) || '{}')
    let item = storage[key]
    if (!item) {
      return null
    }
    let createdAt = new Date(item.createdAt).getTime()
    let actualMaxAge = (maxAge || DEFAULT_MAX_AGE)
    if (new Date().getTime() - createdAt > actualMaxAge) {
      delete storage[key]
      localStorage.setItem(STORE, JSON.stringify(storage))
      return null
    }
    console.log(`%cFrom cache ${key}`, 'color: #777A8B', item.data)
    return item.data
  }

  save(options: {
    key: string,
    data: any,
  }) {
    let { key, data } = options
    let storage: Cache = JSON.parse(localStorage.getItem(STORE) || '{}')
    storage[key] = {
      data,
      createdAt: new Date().toISOString(),
    }
    localStorage.setItem(STORE, JSON.stringify(storage))
  }
}

export default new Cacher()

// @flow

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

const MAX_ITEMS = 100

const DEFAULT_MAX_AGE = 15 * 60 * 1000 // 15 minutes
const STORE = 'api-cacher'

class Cacher {
  load(options: {
    key: string,
    maxAge?: ?number,
  }): ?any {
    let { key, maxAge } = options
    let storage: Cache = JSON.parse(localStorage.getItem(STORE) || '{}')
    let item = storage[key]
    if (!item) {
      return null
    }
    let createdAt = new Date(item.createdAt).getTime()
    let actualMaxAge = (maxAge || DEFAULT_MAX_AGE)
    if (new Date().getTime() - createdAt > actualMaxAge) {
      delete storage[key]
      localStorage.setItem(STORE, JSON.stringify(storage))
      return null
    }
    console.log(`%cFrom cache ${key}`, 'color: #777A8B', item.data)
    return item.data
  }

  save(options: {
    key: string,
    data: any,
  }) {
    let { key, data } = options
    let storage: Cache = JSON.parse(localStorage.getItem(STORE) || '{}')
    let keys = Object.keys(storage)
    if (keys.length >= MAX_ITEMS + 10) {
      keys.sort((a, b) => new Date(storage[a].createdAt).getTime() - new Date(storage[b].createdAt).getTime())
      for (let i = 0; i <= keys.length - MAX_ITEMS; i += 1) {
        delete storage[keys[i]]
      }
    }
    storage[key] = {
      data,
      createdAt: new Date().toISOString(),
    }
    localStorage.setItem(STORE, JSON.stringify(storage))
  }
}

export default new Cacher()

Set a maximum cached API calls requests to 100


var path  = require("path");
var _     = require("lodash");

// -----------------------------------------------------------------------------
// Make tags snake case
// -----------------------------------------------------------------------------
function cleanTag(tag){
  return _.snakeCase(tag);
}

// Regex used to parse date from file name
var dateRegEx = /(\d{4})-(\d{1,2})-(\d{1,2})-(.*)/;

// -----------------------------------------------------------------------------
// Use the filename to build a directory structure using the date
// -----------------------------------------------------------------------------
function filename2date(filePath){
  var result = {};
  var basename = path.basename(filePath, '.md');
  var match = dateRegEx.exec(basename);
  if(match) {
    var year = match[1];
    var month = match[2];
    var day = match[3];
    var basename = match[4];
    result.date = moment(month + '-' + day + '-' + year, "MM-DD-YYYY");
    result.url = '/' + year + '/' + month + '/' + day + '/' + basename + '.html';
    if(!result.title){
      result.title = basename.replace(/_/g, ' ');
    }
  }
  return result;
}

module.exports = {
  cleanTag: cleanTag,
  filename2date: filename2date
};

var path  = require("path");
var _     = require("lodash");

// -----------------------------------------------------------------------------
// Make tags snake case
// -----------------------------------------------------------------------------
function cleanTag(tag){
  return _.snakeCase(tag);
}

// Regex used to parse date from file name
var dateRegEx = /(\d{4})-(\d{1,2})-(\d{1,2})-(.*)/;

// -----------------------------------------------------------------------------
// Use the filename to build a directory structure using the date
// -----------------------------------------------------------------------------
function filename2date(filePath){
  var result = {};
  var basename = path.basename(filePath, '.md');
  var match = dateRegEx.exec(basename);
  if(match) {
    var year = match[1];
    var month = match[2];
    var day = match[3];
    var basename = match[4];
    result.date = month + '-' + day + '-' + year, "MM-DD-YYYY";
    result.url = '/' + year + '/' + month + '/' + day + '/' + basename + '.html';
    if(!result.title){
      result.title = basename.replace(/_/g, ' ');
    }
  }
  return result;
}

module.exports = {
  cleanTag: cleanTag,
  filename2date: filename2date
};

Use moment at the last minute


class ExperimentsService {
    /*@ngInject*/
    constructor(projectsAPI) {
        this.projectsAPI = projectsAPI;
    }

    getAllForProject(projectID) {
        return this.projectsAPI(projectID).one('experiments').getList();
    }

    createForProject(projectID, experiment) {
        return this.projectsAPI(projectID).one('experiments').customPOST(experiment);
    }
}

angular.module('materialscommons').service('experimentsService', ExperimentsService);

class ExperimentsService {
    /*@ngInject*/
    constructor(projectsAPI) {
        this.projectsAPI = projectsAPI;
    }

    getAllForProject(projectID) {
        return this.projectsAPI(projectID).one('experiments').getList();
    }

    createForProject(projectID, experiment) {
        return this.projectsAPI(projectID).one('experiments').customPOST(experiment);
    }

    getForProject(projectID, experimentID) {
        return this.projectsAPI(projectID).one('experiments', experimentID).customGET();
    }
}

angular.module('materialscommons').service('experimentsService', ExperimentsService);

Add call to get a single experiment for a project.


var codeMelon = codeMelon || {};

(function(_c) {
    _c.utils = _c.utils || {};

    test( "dedupe test", function() {
        var arr = ['cat', 'dog', 'man', 'cat', 'mouse', 'dog'],
            result = _c.utils.uniq(arr),
            i;

        strictEqual(result.length, arr.length - 2, "correct number of elements eliminated");
        for (i = 0; i < arr.length; i++) {
            ok(result.indexOf(arr[i]) > -1, "result contains all items in original array");
        }
    });
})(codeMelon);

var codeMelon = codeMelon || {};

(function(_c) {
    _c.utils = _c.utils || {};

    test( "dedupe test on array", function() {
        var arr = ['cat', 'dog', 'man', 'cat', 'mouse', 'dog'],
            result = _c.utils.uniq(arr),
            i;

        strictEqual(result.length, arr.length - 2, "correct number of elements eliminated");
        for (i = 0; i < arr.length; i++) {
            ok(result.indexOf(arr[i]) > -1, "result contains all items in original array");
        }
    });
    test( "dedupe test on string", function() {
        var testcase = 'abcabcdef',
            result = _c.utils.uniq(testcase),
            i;

        strictEqual(result.length, testcase.length - 3, "correct number of characters eliminated");
        for (i = 0; i < testcase.length; i++) {
            ok(result.indexOf(testcase[i]) > -1, "result contains all items in original string");
        }
    });
})(codeMelon);

Test uniq when string is input.


var Hapi = require('hapi');
var server = new Hapi.Server(3000);

server.route({
    method: 'GET',
    path: '/',
    handler: function (request, reply) {
        reply('Transaction Monitoring');
    }
});

server.route({
    method: 'GET',
    path: '/{name}',
    handler: function (request, reply) {
        console.log(request.params.name);
        var spawn = require('child_process').spawn,
        casper = spawn('casperjs', ['test', './siteTests/' + request.params.name + '.js']);

        casper.stdout.on('data', function (data) {
            console.log('' + data);
        });

        casper.on('exit', function(code){
            if(code== 10){
                rdata = '<status>ok</status>';
            }
            else{
                rdata = '<status>fail</status>';
            }
            reply(rdata);
        });
    }
});

server.start(function () {
    console.log('Server running at:', server.info.uri);
});

var Hapi = require('hapi');
var server = new Hapi.Server(3000);

server.route({
    method: 'GET',
    path: '/',
    handler: function (request, reply) {
        reply('Transaction Monitoring');
    }
});

server.route({
    method: 'GET',
    path: '/{name}',
    handler: function (request, reply) {
        console.log(request.params.name);
        var spawn = require('child_process').spawn,
        start = new Date();
        casper = spawn('casperjs', ['test', './siteTests/' + request.params.name + '.js']);

        casper.stdout.on('data', function (data) {
            console.log('' + data);
        });

        casper.on('exit', function(code){
            end = new Date()
            elapsed = start - end
            elapsed = elapsed * -1
            if(code== 10){
                rdata = '<pingdom_http_custom_check><status>ok</status><response_time>' + elapsed + '</response_time></pingdom_http_custom_check>';
            }
            else{
                rdata = '<pingdom_http_custom_check><status>down</status><elapsed>' + elapsed + '</time><pingdom_http_custom_check>';
            }
            reply(rdata).type('text/xml');
        });
    }
});

server.start(function () {
    console.log('Server running at:', server.info.uri);
});

Put responses in pingdom xml format


$(function() {
    $('[data-behaviour="character-limited"]').each(function() {
        $(this).on("keyup", characterCounter);
    });

    function characterCounter() {
        var $field = $(this);
        var maxlength = parseInt($field.attr("maxlength"));
        var remainingChars = maxlength - $field.val().length;
        console.log(remainingChars);
    }

});

$(function() {
    $('[data-behaviour="character-limited"]').each(function() {
        $(this).on("keyup", characterCounter);
    });

    function characterCounter() {
        var $field = $(this);
        var $count = $field.next().children(".character_limited_counter");

        var maxlength      = parseInt($field.attr("maxlength"));
        var remainingChars = maxlength - $field.val().length;

        $count.html(remainingChars);
    }

});

Update span element with remaining chars count


'use strict'

var fs = require('fs')
var path = require('path')

var expect = require('chai').expect
var postcss = require('postcss')

var themePlugin = require('../index')

var CSS_INPUT_FILE = __dirname + '/css/test.css'

function normalizeString (string) {
  return string.split('\n').map(line => line.trim()).join('')
}

function realpath (filePath) {
  return fs.realpathSync(path.join(__dirname, filePath))
}

describe('postcss-theme', function () {
  let cssOutput

  it('runs successfully', function () {
    return postcss([
      themePlugin({
        themePath: __dirname + '/css/themes/default'
      })
    ])
    .process(fs.readFileSync(CSS_INPUT_FILE), { from: CSS_INPUT_FILE, to: path.basename(CSS_INPUT_FILE) })
    .then(function (result) {
      cssOutput = result.css
    })
  })

  it('produces the expected output', () => {
    expect(normalizeString(cssOutput)).to.equal(normalizeString(`
      @value black, white from "${realpath('./css/themes/default/colors.css')}";

      .test-1 {
        composes: no-borders from "${realpath('./css/themes/default/base-styles.css')}";
        color: black;
      }

      .test-2 {
        color: white;
      }
    `))
  })
})

'use strict'

var fs = require('fs')
var path = require('path')

var expect = require('chai').expect
var postcss = require('postcss')

var themePlugin = require('../index')

var CSS_INPUT_FILE = __dirname + '/css/test.css'

function normalizeString (string) {
  return string
    .split('\n')
    .map(function (line) {
      return line.trim()
    })
    .join('')
}

function realpath (filePath) {
  return fs.realpathSync(path.join(__dirname, filePath))
}

describe('postcss-theme', function () {
  let cssOutput

  it('runs successfully', function () {
    return postcss([
      themePlugin({
        themePath: __dirname + '/css/themes/default'
      })
    ])
    .process(fs.readFileSync(CSS_INPUT_FILE), { from: CSS_INPUT_FILE, to: path.basename(CSS_INPUT_FILE) })
    .then(function (result) {
      cssOutput = result.css
    })
  })

  it('produces the expected output', () => {
    expect(normalizeString(cssOutput)).to.equal(normalizeString(`
      @value black, white from "${realpath('./css/themes/default/colors.css')}";

      .test-1 {
        composes: no-borders from "${realpath('./css/themes/default/base-styles.css')}";
        color: black;
      }

      .test-2 {
        color: white;
      }
    `))
  })
})

Fix test for Node 0.12


global.chai = require("chai");
global.expect = chai.expect;
global.sinon = require("sinon");

var sinonChai = require("sinon-chai");
chai.use(sinonChai);

var app = require('../lib/app');
var supertest = require("supertest");
global.request = supertest(app);

global.chai = require("chai");
global.expect = chai.expect;
global.sinon = require("sinon");

var sinonChai = require("sinon-chai");
chai.use(sinonChai);

process.env.NODE_ENV = 'test';

var app = require('../lib/app');
var supertest = require("supertest");
global.request = supertest(app);

Add a test environment tag


import jsdom from 'jsdom';

const doc = jsdom.jsdom('<!doctype html>');
const win = doc.defaultView;

global.document = doc;
global.window = win;

Object.keys(window).forEach((key) => {
  if (!(key in global)) {
    global[key] = window[key];
  }
});

import jsdom from 'jsdom';
import chai from 'chai';
import chaiImmutable from 'chai-immutable';

const doc = jsdom.jsdom('<!doctype html>');
const win = doc.defaultView;

global.document = doc;
global.window = win;

Object.keys(window).forEach((key) => {
  if (!(key in global)) {
    global[key] = window[key];
  }
});

chai.use(chaiImmutable);

Support chai expectations for immutables.


function getLogin(){
  return window.location.hash.slice(1)
}

var foundTemplate = $('#template').html();
Mustache.parse(foundTemplate);

var loadData = function(login, cb){
  if(login){
    var searchURL = 'https://api.github.com/search/issues?q=type:pr+author:'+login+'&sort=created&order=asc&per_page=1'
    $.getJSON(searchURL, function(data){
      if(data.items.length > 0){
        $.getJSON(data.items[0].pull_request.url, function(data){
          cb(data)
        })
      } else {
        cb(null)
      }
    }).error(function() { renderError(getLogin()+" isn't a valid GitHub login") })
  }
}

var renderData = function(pullRequestData){
  if(pullRequestData){
    $('#main').html(Mustache.render(foundTemplate, pullRequestData))
  } else {
    renderError('It doesn\'t look like '+getLogin()+' has sent a pull request yet.')
  }
}

var renderError = function (message) {
  $('#main').html("<p>"+message+"</p>")
}

$(window).on('hashchange',function(){
  loadData(getLogin(), renderData)
});

$('#user-form').submit(function(){
  window.location.hash = $('#login')[0].value
  $('#login')[0].value = ""
  return false
})

loadData(getLogin(), renderData)

function getLogin(){
  return window.location.hash.slice(1)
}

var foundTemplate = $('#template').html();
Mustache.parse(foundTemplate);

function loadData(login, cb){
  if(login){
    var searchURL = 'https://api.github.com/search/issues?q=type:pr+author:'+login+'&sort=created&order=asc&per_page=1'
    $.getJSON(searchURL, function(data){
      if(data.items.length > 0){
        $.getJSON(data.items[0].pull_request.url, function(data){
          cb(data)
        })
      } else {
        cb(null)
      }
    }).error(function() { renderError(getLogin()+" isn't a valid GitHub login") })
  }
}

function renderData(pullRequestData){
  if(pullRequestData){
    $('#main').html(Mustache.render(foundTemplate, pullRequestData))
  } else {
    renderError('It doesn\'t look like '+getLogin()+' has sent a pull request yet.')
  }
}

function renderError(message){
  $('#main').html("<p>"+message+"</p>")
}

$(window).on('hashchange',function(){
  loadData(getLogin(), renderData)
});

$('#user-form').submit(function(){
  window.location.hash = $('#login')[0].value
  $('#login')[0].value = ""
  return false
})

loadData(getLogin(), renderData)

Define all functions the same way


#!/usr/bin/env node

'use strict'
var meow = require('meow')
var hacktoberFest = require('./main.js')

const cli = meow({
  flags: {
    year: {
      type: 'number',
      alias: 'y',
      default: 0,
    },
    help: {
      type: 'boolean',
      alias: 'h',
    },
  },
})

if (cli.flags.help) {
  cli.showHelp(0)
}

hacktoberFest
  .getHacktoberfestStats(cli.input[0], cli.flags.year)
  .then((stats) => {
    console.log(stats)
  })
  .catch((error) => {
    console.error(error.message)
    process.exit(1)
  })

Fix errors related to missing shebang


var configs = require('/config/sso.js').config();

var server = require('/modules/server.js');
server.init(configs);

var user = require('/modules/user.js');
user.init(configs);

var event = require('event');

var STORE_CONFIG_PATH = '/_system/config/sso/configs/sso.json';

var server = require('/modules/server.js');
server.init(configs);

var user = require('/modules/user.js');
user.init(configs);

var log = new Log();

event.on('tenantCreate', function (tenantId) {
    var carbon = require('carbon'),
        system = server.systemRegistry(tenantId);
    system.put(STORE_CONFIG_PATH, {
        content: JSON.stringify({
            "permissions": configs.permissions
        }),
        mediaType: 'application/json'
    });
});

event.on('tenantLoad', function (tenantId) {
    var carbon = require('carbon'),
        config = server.configs(tenantId),
        reg = server.systemRegistry(tenantId),
        um = server.userManager(tenantId);

    //check whether tenantCreate has been called
    if (!reg.exists(STORE_CONFIG_PATH)) {
    }
    event.emit('tenantCreate', tenantId);

    config[user.USER_OPTIONS] = {
        "permissions": configs.permissions
    };
});

var configs = require('/config/sso.js').config();

var server = require('/modules/server.js');
server.init(configs);

var user = require('/modules/user.js');
user.init(configs);

var event = require('event');

var STORE_CONFIG_PATH = '/_system/config/sso/configs/sso.json';

var server = require('/modules/server.js');
var ssoTenantConfig = require('/config/sso-tenant.json');
server.init(configs);
var user = require('/modules/user.js');
var storeModule = require('store');
user.init(configs);

var log = new Log();

storeModule.server.init(configs);
storeModule.user.init(configs);
event.on('tenantCreate', function (tenantId) {
    var carbon = require('carbon'),
        system = server.systemRegistry(tenantId);
    system.put(STORE_CONFIG_PATH, {
        content: JSON.stringify(ssoTenantConfig),
        mediaType: 'application/json'
    });
});

event.on('tenantLoad', function (tenantId) {
    var carbon = require('carbon'),
        config = server.configs(tenantId),
        reg = server.systemRegistry(tenantId),
        um = server.userManager(tenantId);

    //check whether tenantCreate has been called
    if (!reg.exists(STORE_CONFIG_PATH)) {
    }
    event.emit('tenantCreate', tenantId);

    config[user.USER_OPTIONS] = {
        "permissions": configs.permissions
    };
});

//Cause the super tenant to be load
var SUPER_TENANT = -1234;
event.emit('tenantLoad', SUPER_TENANT);

Add initializaion of user and server module,put sso tenant configuration into registry,Trigger super tenant TenantLoad event


const { expect } = require('chai')
const { metadata } = require('../../../../../src/lib/urls')

describe('Metadata', () => {
  it('endpoint should return headquarter type', () => {
    cy.request(metadata.headquarterType()).as('headquarterType')
    cy.get('@headquarterType').then((response) => {
      expect(response.status).to.equal(200)
      expect(response.body).to.equal(
        JSON.stringify([
          {
            id: '3e6debb4-1596-40c5-aa25-f00da0e05af9',
            name: 'ukhq',
            disabled_on: null,
          },
          {
            id: 'eb59eaeb-eeb8-4f54-9506-a5e08773046b',
            name: 'ehq',
            disabled_on: null,
          },
          {
            id: '43281c5e-92a4-4794-867b-b4d5f801e6f3',
            name: 'ghq',
            disabled_on: null,
          },
        ])
      )
    })
  })
})

const { expect } = require('chai')
const { metadata } = require('../../../../../src/lib/urls')

describe('Metadata', () => {
  it('endpoint should return headquarter type', () => {
    cy.request(metadata.headquarterType()).as('headquarterType')
    cy.get('@headquarterType').then((response) => {
      expect(response.status).to.equal(200)
      expect(JSON.stringify(response.body)).to.equal(
        JSON.stringify([
          {
            id: '3e6debb4-1596-40c5-aa25-f00da0e05af9',
            name: 'ukhq',
            disabled_on: null,
          },
          {
            id: 'eb59eaeb-eeb8-4f54-9506-a5e08773046b',
            name: 'ehq',
            disabled_on: null,
          },
          {
            id: '43281c5e-92a4-4794-867b-b4d5f801e6f3',
            name: 'ghq',
            disabled_on: null,
          },
        ])
      )
    })
  })
})

Fix e2e test on headquarter metadata


var Proxy = require('./proxy');

console.log('Background page initialised');

var config = {
  tcp: {
    address: '127.0.0.1',
    port: 9009
  },
  consoleType: 'basic' // 'advanced' or 'basic'
};

var proxy;

chrome.app.runtime.onLaunched.addListener(function() {
  console.log('App launched.');
  startProxy(config.tcp.address, config.tcp.port);
  launchWindow();
});

chrome.runtime.onSuspend.addListener(function() {
  console.log('App is being suspended');
  stopProxy();
});

function startProxy(address, port, handler) {
  if (proxy) {
    console.log('Disconnect proxy');
    proxy.disconnect();
  }

  proxy = new Proxy(address, port);
}

function stopProxy() {
  proxy.disconnect();
  proxy = null;
}

function pathForConsoleType(type) {
  return config.consoleType === 'basic'
          ? 'ui/main.html'
          : 'ui/console.html';
}

function launchWindow() {
  chrome.app.window.create(pathForConsoleType(config.consoleType), {
    id: 'main-window',
    bounds: {
      width: 800,
      height: 600,
      left: 100,
      top: 100
    },
    minWidth: 800,
    minHeight: 600
  });
}

var Proxy = require('./proxy');

console.log('Background page initialised');

var config = {
  tcp: {
    address: '127.0.0.1',
    port: 9009
  },
  consoleType: 'basic' // 'advanced' or 'basic'
};

var proxy;

chrome.app.runtime.onLaunched.addListener(function() {
  console.log('App launched.');
  startProxy(config.tcp.address, config.tcp.port);
  launchWindow();
});

chrome.runtime.onSuspend.addListener(function() {
  console.log('App is being suspended');
  stopProxy();
});

function startProxy(address, port, handler) {
  if (proxy) {
    console.log('Disconnect proxy');
    proxy.disconnect();
  }

  proxy = new Proxy(address, port);
}

function stopProxy() {
  proxy.disconnect();
  proxy = null;
}

function pathForConsoleType(type) {
  return config.consoleType === 'basic'
          ? 'ui/main.html'
          : 'ui/console.html';
}

function launchWindow() {
  chrome.app.window.create(pathForConsoleType(config.consoleType), {
    id: 'main-window',
    bounds: {
      width: 800,
      height: 300,
      left: 100,
      top: 100
    },
    minWidth: 800,
    minHeight: 600
  });
}

Reduce size of console window on app startup


define(["handlebars"], function(Handlebars) {
  var buildMap = {};

  return {

    // http://requirejs.org/docs/plugins.html#apiload
    load: function (name, parentRequire, onload, config) {

      if (config.isBuild) {
        // Use node.js file system module to load the template.
        // Sorry, no Rhino support.
        var fs = nodeRequire("fs");
        var fsPath = config.dirBaseUrl + "/" + name + ".hbs";
        buildMap[name] = fs.readFileSync(fsPath).toString();
        onload();
      }
      else {
        // In browsers use the text-plugin to the load template. This way we
        // don't have to deal with ajax stuff
        parentRequire(["text!" + name + ".hbs"], function(raw) {
          // Just return the compiled template
          onload(Handlebars.compile(raw));
        });
      }

    },

    // http://requirejs.org/docs/plugins.html#apiwrite
    write: function (pluginName, name, write) {
      var compiled = Handlebars.precompile(buildMap[name]);
      // Write out precompiled version of the template function as AMD
      // definition.
      write(
        "define('hbs!" + name + "', ['handlebars'], function(Handlebars){ \n" +
          "return Handlebars.template(" + compiled.toString() + ");\n" +
        "});\n"
      );
    }

  };
});

define(["handlebars"], function(Handlebars) {
  var buildMap = {},
      templateExtension = ".hbs";

  return {

    // http://requirejs.org/docs/plugins.html#apiload
    load: function (name, parentRequire, onload, config) {

      // Get the template extension.
      var ext = (config.hbs && config.hbs.templateExtension ? config.hbs.templateExtension : templateExtension);

      if (config.isBuild) {
        // Use node.js file system module to load the template.
        // Sorry, no Rhino support.
        var fs = nodeRequire("fs");
        var fsPath = config.dirBaseUrl + "/" + name + ext;
        buildMap[name] = fs.readFileSync(fsPath).toString();
        onload();
      } else {
        // In browsers use the text-plugin to the load template. This way we
        // don't have to deal with ajax stuff
        parentRequire(["text!" + name + ext], function(raw) {
          // Just return the compiled template
          onload(Handlebars.compile(raw));
        });
      }

    },

    // http://requirejs.org/docs/plugins.html#apiwrite
    write: function (pluginName, name, write) {
      var compiled = Handlebars.precompile(buildMap[name]);
      // Write out precompiled version of the template function as AMD
      // definition.
      write(
        "define('hbs!" + name + "', ['handlebars'], function(Handlebars){ \n" +
          "return Handlebars.template(" + compiled.toString() + ");\n" +
        "});\n"
      );
    }

  };
});

Allow customizing a template extension in requirejs config


$(document).on("ready", function() {

});

$(document).on("ready", function() {

  // $("a").click(".snowflake", function(event){
  //   event.preventDefault();
  //   event.stopPropagation();
  //   debugger
  //   // var target = $(event.target).parent().find("#vote_count")
  //   $.ajax({
  //     type: "POST",
  //     url : $(this).attr('href'),
  //     dataType: "json",
  //   }).done(function(response){
  //     debugger
  //     alert("Success!")
  //   }).fail(function(response){
  //     console.log(response);
  //   });
  // });

  $(".post-comment").on("click", function(event){
    event.preventDefault();
    // debugger;
    $.ajax({
      url : $(event.target).attr('href'),
      data: $(this).serialize(),
    }).done(function(response){
      debugger;
      console.log("success!");
    }).fail(function(response){
      alert("You are unable to post a comment.");
    });
  });

});

Create ajax return for posting comment


"use strict";

var slugTestCases = [
    ["", ""]
    ,[" Jack & Jill like numbers 1,2,3 and 4 and silly characters ?%.$!/",
      "jack-jill-like-numbers-123-and-4-and-silly-characters"]
    ,["Un éléphant à l'orée du bois", "un-elephant-a-loree-du-bois"]
    ,["Iñtërnâtiônàlizætiøn", "internationalizaetion"]
];

describe("slugify-service", function() {
    beforeEach(module("slugifier"));

    describe("slugify", function() {
        it("should produce a correct slug from string input", inject(function(Slug) {
            var input, expected;
            for (var i = 0; i < slugTestCases.length; i++) {
                input = slugTestCases[i][0];
                expected = slugTestCases[i][1];
                expect(Slug.slugify(input)).toEqual(expected);
            }
        }));
    });
});

"use strict";

var slugTestCases = [
    ["", ""]
    ,[" Jack & Jill like numbers 1,2,3 and 4 and silly characters ?%.$!/",
      "jack-jill-like-numbers-123-and-4-and-silly-characters"]
    ,["Un éléphant à l'orée du bois", "un-elephant-a-loree-du-bois"]
    ,["Iñtërnâtiônàlizætiøn", "internationalizaetion"]
    ,['щЫПЮ', 'schipyu']
];

describe("slugify-service", function() {
    beforeEach(module("slugifier"));

    describe("slugify", function() {
        it("should produce a correct slug from string input", inject(function(Slug) {
            var input, expected;
            for (var i = 0; i < slugTestCases.length; i++) {
                input = slugTestCases[i][0];
                expected = slugTestCases[i][1];
                expect(Slug.slugify(input)).toEqual(expected);
            }
        }));
    });
});

Add test for cyrillic characters.


var businessman = businessman || {}

describe( 'businessman specs', function () {

    var stores

    it( 'Install Worker', function ( done ) {
        businessman.subscribe( 'create_client_store', ( data ) => {
            stores = data
            expect( data ).to.be.ok()
            done()
        } )
        businessman.install( '/dist/sample-worker.js' )
    } )

    it( 'Store for clients includes dispatch() and subscribe()', function () {
        for ( let store in stores ) {
            expect( stores[ store ] ).to.have.property( 'dispatch' )
            expect( stores[ store ] ).to.have.property( 'subscribe' )
        }
    } )

} )

var businessman = businessman || {}

describe( 'businessman specs', function () {

    var stores

    it( 'Install Worker', function ( done ) {
        businessman.subscribe( 'create_client_store', ( data ) => {
            stores = data
            expect( data ).to.be.ok()
            done()
        } )
        businessman.install( '/dist/sample-worker.js' )
    } )

    it( 'Store for clients includes dispatch() and subscribe()', function () {
        for ( let store in stores ) {
            expect( stores[ store ] ).to.have.property( 'dispatch' )
            expect( stores[ store ] ).to.have.property( 'subscribe' )
            expect( stores[ store ].dispatch ).to.be.an( 'function' )
            expect( stores[ store ].subscribe ).to.be.an( 'function' )
        }
    } )

} )

Test that it is a method


var title = document.querySelector("title");
if (title!=null){
	var observer = new MutationObserver(function(mutations){
		mutations.forEach(function(mutation){
			if (mutation.addedNodes.length===0 && mutation.removedNodes.length===0){
				var newTitle = mutation.addedNodes[0].nodeValue;
				var oldTitle = mutation.removedNodes[0].nodeValue;
				chrome.runtime.sendMessage({"titleChange":{"newTitle":newTitle,"oldTitle":oldTitle}});
			}
		});
	});
	observer.observe(title,{childList:true,characterData:true,characterDataOldValue:true});
}

var title = document.querySelector("title");
if (title!=null){
	var observer = new MutationObserver(function(mutations){
		mutations.forEach(function(mutation){
			if (mutation.addedNodes.length===1 && mutation.removedNodes.length===1){
				var newTitle = mutation.addedNodes[0].nodeValue;
				var oldTitle = mutation.removedNodes[0].nodeValue;
				chrome.runtime.sendMessage({"titleChange":{"newTitle":newTitle,"oldTitle":oldTitle}});
			}
		});
	});
	observer.observe(title,{childList:true,characterData:true,characterDataOldValue:true});
}

Fix typo with MutationObserver length


var SDL = module.exports = require('./build/default/node-sdl.node');

// Easy event emitter based event loop.  Started automatically when the first
// listener is added.
var events;
Object.defineProperty(SDL, 'events', {
  get: function () {
    if (events) return events;
    events = new (require('events').EventEmitter);
    var now = Date.now();
    setInterval(function () {
      var after = Date.now();
      var delta = after - now;
      now = after;
      var data;
      while (data = SDL.pollEvent()) {
        events.emit(data.type, data);
      }
      events.emit('tick', delta);
    }, 16);
    return events;
  }
});

var SDL = module.exports = require('./build/default/node-sdl.node');

// Easy event emitter based event loop.  Started automatically when the first
// listener is added.
var events;
Object.defineProperty(SDL, 'events', {
  get: function () {
    if (events) return events;
    events = new (require('events').EventEmitter);
    var now = Date.now();
    setInterval(function () {
      var after = Date.now();
      var delta = after - now;
      now = after;
      var data;
      while (data = SDL.pollEvent()) {
        events.emit('event', data);
        events.emit(data.type, data);
      }
      events.emit('tick', delta);
    }, 16);
    return events;
  }
});

Add back in global event emitter


var keystone = require('keystone');

exports = module.exports = function(req, res) {

	var query = req.body.query;
	var cookie = req.cookies.cookieCountry;

	// https://docs.mongodb.com/manual/reference/operator/query/regex/
	var q = keystone.list("Country").model.find({'name': {$regex: '.*' + query + '.*', $options: 'i'}});

	q.exec(function(err, result){
		if(result == ''){
		console.log('The country you have searched doesn not exist in our database yet...')
		}
		else{
		var result = JSON.stringify(result);
		result = result.substring(1,result.length);
		result = result.substring(0,result.length-1);
		county = JSON.parse(result);

		//extract the 'name' from results
		//var countryName = result.name; or result['name'];-> doesn't work
		//console.log(countryName);

		//https://stackoverflow.com/questions/7042340/error-cant-set-headers-after-they-are-sent-to-the-client
		res.cookie('country', county._id).redirect("/");
		}
	});
}

var keystone = require('keystone');

exports = module.exports = function(req, res) {

	var view = new keystone.View(req, res),
		query = req.body.query;

	// https://docs.mongodb.com/manual/reference/operator/query/regex/
	var q = keystone.list("Country").model.find({'name': {$regex: '.*' + query + '.*', $options: 'i'}});

	q.exec(function(err, result){
		flashErrors: true;

		if(result == '' || err){
		console.log('The country you have searched doesn not exist in our database yet...');
		req.flash('error', 'The country you have searched doesn not exist in our database yet...');
		view.render(keystone.lang + '/errors/404');
		}
		else{
		var result = JSON.stringify(result);
		result = result.substring(1,result.length);
		result = result.substring(0,result.length-1);
		country = JSON.parse(result);

		//https://stackoverflow.com/questions/7042340/error-cant-set-headers-after-they-are-sent-to-the-client
		res.cookie('country', country._id).redirect("/");
		}
	});
}

Handle Error in Search form with a flash message and 404 view


var webpack = require('webpack');

var HtmlWebpackPlugin = require('html-webpack-plugin'),
    ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
  entry: {
    index: './src/demo/index.js'
  },
  output: {
    path: './target/demo',
    filename: '[name].js'
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/demo/index.html',
      minify: {collapseWhitespace: true}
    }),
    new ExtractTextPlugin('[name].css')
  ],
  module: {
    loaders: [
      {test: /\.js$/, loader: 'babel'},
      {test: /\.less$/, loader: ExtractTextPlugin.extract('style', 'css!less?strictUnits=true&strictMath=true')},
      {test: /\.(png|jpg)$/, loader: 'file-loader?name=[name].[ext]'}
    ]
  }
};

var webpack = require('webpack');

var HtmlWebpackPlugin = require('html-webpack-plugin'),
    ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
  entry: {
    index: './src/demo/index.js'
  },
  output: {
    path: './target/demo',
    filename: '[name].js'
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/demo/index.html',
      minify: {collapseWhitespace: true}
    }),
    new ExtractTextPlugin('[name].css')
  ],
  module: {
    loaders: [
      {test: /\.js$/, loader: 'babel', exclude: /node_modules/},
      {test: /\.less$/, loader: ExtractTextPlugin.extract('style', 'css!less?strictUnits=true&strictMath=true')},
      {test: /\.(png|jpg)$/, loader: 'file-loader?name=[name].[ext]'}
    ]
  }
};

Exclude node_modules from Babel processing


//used for the media picker dialog
angular.module("umbraco").controller("Umbraco.Dialogs.ContentPickerController",
	function ($scope, eventsService, $log) {
	var dialogOptions = $scope.$parent.dialogOptions;
	$scope.dialogTreeEventHandler = $({});

	$scope.dialogTreeEventHandler.bind("treeNodeSelect", function(ev, args){
		args.event.preventDefault();
		args.event.stopPropagation();

		eventsService.publish("Umbraco.Dialogs.ContentPickerController.Select", args, "hello").then(function(args){
			if(dialogOptions && dialogOptions.multipicker){
				$(args.event.target.parentElement)
					.find("i.umb-tree-icon")
					.attr("class", "icon umb-tree-icon sprTree icon-check blue");

				$scope.select(args.node);
			}else{
				$scope.submit(args.node);
			}

		});

	});
});

//used for the media picker dialog
angular.module("umbraco").controller("Umbraco.Dialogs.ContentPickerController",
	function ($scope, eventsService, $log) {
	var dialogOptions = $scope.$parent.dialogOptions;
	$scope.dialogTreeEventHandler = $({});

	$scope.dialogTreeEventHandler.bind("treeNodeSelect", function(ev, args){
		args.event.preventDefault();
		args.event.stopPropagation();

		eventsService.publish("Umbraco.Dialogs.ContentPickerController.Select", args).then(function(args){
			if(dialogOptions && dialogOptions.multipicker){
				$(args.event.target.parentElement)
					.find("i.umb-tree-icon")
					.attr("class", "icon umb-tree-icon sprTree icon-check blue");

				$scope.select(args.node);
			}else{
				$scope.submit(args.node);
			}

		});

	});
});

Remove old debug valu from content picker


'use strict'

/**
 * Copyright 2016-present, Dustin Deus (deusdustin@gmail.com)
 * All rights reserved.
 *
 * This source code is licensed under the MIT-style license found in the
 * LICENSE file in the root directory of this source tree.
 *
 */

const SafeStringify = require('fast-safe-stringify')

class Encoder {
  static encode(msg) {
    try {
      return {
        value: SafeStringify(msg)
      }
    } catch (error) {
      return {
        error
      }
    }
  }
}

module.exports = Encoder

'use strict'

/**
 * Copyright 2016-present, Dustin Deus (deusdustin@gmail.com)
 * All rights reserved.
 *
 * This source code is licensed under the MIT-style license found in the
 * LICENSE file in the root directory of this source tree.
 *
 */

const SafeStringify = require('fast-safe-stringify')

/**
 * Why do coerce to a Number?
 * https://github.com/davidmarkclements/flatstr#how-does-it-work
 */

class Encoder {
  static encode(msg) {
    try {
      return {
        value: Number(SafeStringify(msg))
      }
    } catch (error) {
      return {
        error
      }
    }
  }
}

module.exports = Encoder

Improve serialization performance by V8 trick


FilterCheckboxes = {
  title: 'Checkboxes'
, name: 'checkboxes'
, items: [{
    label: 'Example'
  , name: 'example'
  }]
, wrapperClassName: 'filterCheckboxes'
, init: function () {
    var self = this
    self.list = document.querySelector('.' + self.wrapperClassName + ' ' + self.wrapperClassName + '__list')
    if(self.list) {
      self.items.forEach(function (item) {
        self.list.appendChild(Blaze.renderWithData(Template.filterCheckboxesCheckbox, item))
      })
      self.allElements = self.list.querySelectorAll(self.wrapperClassName + '__item')
      self.setEvents()
    }
  }
, setEvents: function () {
    var self = this
    _.each(self.allElements, function (element) {
      element.addEventListener('change', function () {
        self.updateTrueList()
      })
    })
  }
, _trueList: []
, _trueListDep: new Tracker.Dependency
, getTrueList: function () {
    var self = this
    _trueListDep.depend()
    return self._trueList
  }
, updateTrueList: function () {
    var self = this
    self._trueList = []
    _.each(self.allElements, function (element) {
      if(element.checked) {
        self._trueList.push(element.name)
      }
    })
    _trueListDep.changed()
  }
}

Template.registerHelper('filterCheckboxesSection', function (name, label) {
  return Blaze.renderWithData(Template.filterCheckboxes, {
    name: name
  , label: label
  })
})

Remove an old, unused helper


import initContract from 'truffle-contract'
import contractDefinition from 'contracts/SimpleStorage.json'

export const getAccounts = web3 =>
  new Promise((resolve, reject) => {
    web3.eth.getAccounts(
      (error, accounts) => (error ? reject(error) : resolve(accounts))
    )
  })

export const getContractInstance = async web3 => {
  const contract = initContract(contractDefinition)
  contract.setProvider(web3.currentProvider)

  // Dirty hack for web3@1.0.0 support for localhost testrpc
  // see https://github.com/trufflesuite/truffle-contract/issues/56#issuecomment-331084530
  if (typeof contract.currentProvider.sendAsync !== 'function') {
    contract.currentProvider.sendAsync = function () {
      return contract.currentProvider.send.apply(
        contract.currentProvider, arguments
      )
    }
  }

  const instance = await contract.deployed()
  return instance
}

import initContract from 'truffle-contract'
import contractDefinition from 'contracts/DQuestions.json'

export const getAccounts = web3 =>
  new Promise((resolve, reject) => {
    web3.eth.getAccounts(
      (error, accounts) => (error ? reject(error) : resolve(accounts))
    )
  })

export const getContractInstance = async web3 => {
  const contract = initContract(contractDefinition)
  contract.setProvider(web3.currentProvider)

  // Dirty hack for web3@1.0.0 support for localhost testrpc
  // see https://github.com/trufflesuite/truffle-contract/issues/56#issuecomment-331084530
  if (typeof contract.currentProvider.sendAsync !== 'function') {
    contract.currentProvider.sendAsync = function () {
      return contract.currentProvider.send.apply(
        contract.currentProvider, arguments
      )
    }
  }

  const instance = await contract.deployed()
  return instance
}

Load DQuestions contract instead of SimpleStorage


Package.describe({
  name: "mdg:camera",
  summary: "Photos with one function call on desktop and mobile.",
  version: "1.1.4",
  git: "https://github.com/meteor/mobile-packages"
});

Cordova.depends({
  "org.apache.cordova.camera":"0.3.2"
});

Package.onUse(function(api) {
  api.export('MeteorCamera');
  api.use(["templating", "session", "ui", "blaze", "less", "reactive-var"]);
  api.versionsFrom("METEOR@0.9.2");

  api.addFiles('photo.html');
  api.addFiles('photo.js');
  api.addFiles("camera.less", ["web.browser"]);
  api.addFiles('photo-browser.js', ['web.browser']);
  api.addFiles('photo-cordova.js', ['web.cordova']);
});

Package.describe({
  name: "mdg:camera",
  summary: "Photos with one function call on desktop and mobile.",
  version: "1.1.4",
  git: "https://github.com/meteor/mobile-packages"
});

Cordova.depends({
  "org.apache.cordova.camera":"0.3.2"
});

Package.onUse(function(api) {
  api.export('MeteorCamera');
  api.use(["templating", "session", "ui", "blaze", "less@1.0.0||2.0.0", "reactive-var"]);
  api.versionsFrom("METEOR@0.9.2");

  api.addFiles('photo.html');
  api.addFiles('photo.js');
  api.addFiles("camera.less", ["web.browser"]);
  api.addFiles('photo-browser.js', ['web.browser']);
  api.addFiles('photo-cordova.js', ['web.cordova']);
});

Update LESS dep for Meteor 1.2


var WebSocket = require('ws');
var reHttpSignalhost = /^http(.*)$/;
var reTrailingSlash = /\/$/;

function connect(signalhost) {
  // if we have a http/https signalhost then do some replacement magic to push across
  // to ws implementation (also add the /primus endpoint)
  if (reHttpSignalhost.test(signalhost)) {
    signalhost = signalhost
      .replace(reHttpSignalhost, 'ws$1')
      .replace(reTrailingSlash, '') + '/primus';
  }

  return new WebSocket(signalhost);
}

module.exports = function(signalhost, callback) {
  var ws = connect(signalhost);

  ws.once('open', function() {
    // close the test socket
    ws.close();

    callback(null, {
      connect: connect
    });
  });
};

var WebSocket = require('ws');
var reHttpSignalhost = /^http(.*)$/;
var reTrailingSlash = /\/$/;

function connect(signalhost) {
  // if we have a http/https signalhost then do some replacement magic to push across
  // to ws implementation (also add the /primus endpoint)
  if (reHttpSignalhost.test(signalhost)) {
    signalhost = signalhost
      .replace(reHttpSignalhost, 'ws$1')
      .replace(reTrailingSlash, '') + '/primus';
  }

  return new WebSocket(signalhost);
}

module.exports = function(signalhost, opts, callback) {
  var ws = connect(signalhost);

  ws.once('open', function() {
    // close the test socket
    ws.close();

    callback(null, {
      connect: connect
    });
  });
};

Update vanilla websocket connector to accept an opts argument


const knuthShuffle = (arr) => {
  for (let i = 0; i < arr.length; i++) {
    // Generate a random number between 0 and i
    let rnd = Math.floor((Math.random() * (i + 1)));

    // Swap the element in position i with the element in position rnd
    [arr[i], arr[rnd]] = [arr[rnd], arr[i]];
  }

  return arr;
};

console.log(knuthShuffle([1, 2, 3, 4, 5, 6, 7, 8]));

const knuthShuffle = (arr) => {
  for (let i = 0; i < arr.length; i++) {
    /*
     * Generate a random number between 0 and i.
     * IMPORTANT: do not generate a random number between 0 and length, because
     * this would generate N^N possibilities, whereas a shuffle only has N!
     * permutations. Randomizing from 0 to length would result in a biased
     * shuffling algorithm.
     */
    let rnd = Math.floor((Math.random() * (i + 1)));

    // Swap the element in position i with the element in position rnd
    [arr[i], arr[rnd]] = [arr[rnd], arr[i]];
  }

  return arr;
};

console.log(knuthShuffle([1, 2, 3, 4, 5, 6, 7, 8]));

Add note regarding unbiased shuffles


var path = require("path");
var HTMLPlugin = require("html-webpack-plugin");
var CleanPlugin = require("clean-webpack-plugin");

var config = {

    entry: "./src/js/app.js",

    module: {
        loaders: [
            {
                test: /\.js$/,
                exclude: /(node_modules)/,
                loaders: ["babel-loader", "eslint-loader"]
            },

            {test: /\.css$/, loader: "style!css"},
            {test: /\.woff$/, loader: "url?limit=100000"}
        ]
    },

    resolve: {
        alias: {virtool: path.resolve(__dirname, "./src")}
    },

    node: {
        fs: "empty"
    },

    output: {
        path: "dist",
        filename: "app.[hash].js"
    },

    eslint: {
        configFile: "./.eslintrc"
    },

    plugins: [
        new HTMLPlugin({
            filename: "index.html",
            title: "Virtool",
            favicon: "./src/images/favicon.ico",
            template: "./src/index.html",
            inject: "body"
        }),

        new CleanPlugin(["dist"], {
            verbose: true
        })
    ],

    progress: true,
    colors: true,
    watch: true
};

module.exports = config;

var path = require("path");
var HTMLPlugin = require("html-webpack-plugin");
var CleanPlugin = require("clean-webpack-plugin");

var config = {

    entry: "./src/js/app.js",

    module: {
        loaders: [
            {
                test: /\.js$/,
                exclude: /(node_modules)/,
                loaders: ["babel-loader", "eslint-loader"]
            },

            {test: /\.css$/, loader: "style!css"},
            {test: /\.woff$/, loader: "url?limit=100000"}
        ]
    },

    resolve: {
        alias: {virtool: path.resolve(__dirname, "./src")}
    },

    node: {
        fs: "empty"
    },

    output: {
        path: "dist",
        filename: "app.[hash].js",
        publicPath: "/static/"
    },

    eslint: {
        configFile: "./.eslintrc"
    },

    plugins: [
        new HTMLPlugin({
            filename: "index.html",
            title: "Virtool",
            favicon: "./src/images/favicon.ico",
            template: "./src/index.html",
            inject: "body"
        }),

        new CleanPlugin(["dist"], {
            verbose: true
        })
    ],

    progress: true,
    colors: true,
    watch: true
};

module.exports = config;

Set webpack publicPath to /static/


;(function(window) {
  'use strict';

  var angular = window.angular;
  var ImageLoader = window.ImageLoader;
  var ismobile = window.navigator.userAgent.match(/(iPad)|(iPhone)|(iPod)|(android)|(webOS)/i);

  angular
    .module('tuchong-daily')
    .service('imageLoader', [
      '$ionicLoading',
      '$timeout',
      imageLoader
    ]);

  function imageLoader($ionicLoading, $timeout) {
    this.load = load;

    function load(uri, callback) {
      $ionicLoading.show();

      var img = new BlobImage(ismobile ? uri : proxy(uri));
      img.element.onload = success;
      img.element.onerror = fail;

      function success() {
        $ionicLoading.hide();
        callback(img.blobURL);
        img = null;
      }

      function fail() {
        $ionicLoading.show({
          template: '图片加载失败，请稍后再试试...'
        });
        img = null;
      }

      function proxy(realuri) {
        return realuri.replace(
          'http://photos.tuchong.com',
          'http://localhost:8100/photos'
        );
      }
    }
  }
})(this);

;(function(window) {
  'use strict';

  var angular = window.angular;
  var ImageLoader = window.ImageLoader;
  var ismobile = window.navigator.userAgent.match(/(iPad)|(iPhone)|(iPod)|(android)|(webOS)/i);

  angular
    .module('tuchong-daily')
    .service('imageLoader', [
      '$ionicLoading',
      '$timeout',
      imageLoader
    ]);

  function imageLoader($ionicLoading, $timeout) {
    this.load = load;

    function load(uri, callback) {
      $ionicLoading.show();

      var img = new BlobImage(ismobile ? uri : proxy(uri));
      img.element.onload = success;
      img.element.onerror = fail;

      function success() {
        $ionicLoading.hide();
        callback(img.blobURL);
        img = null;
      }

      function fail() {
        $ionicLoading.show({
          template: '图片加载失败，请稍后再试试...'
        });
        img = null;

        $timeout($ionicLoading.hide, 1000);
      }

      function proxy(realuri) {
        return realuri.replace(
          'http://photos.tuchong.com',
          'http://localhost:8100/photos'
        );
      }
    }
  }
})(this);

Remove loading directive after request fails


'use strict';

module.exports = () => ({
    'title': 'Service Resource Definition',
    'description': 'Defines the resources, units, metering, aggregation and rating formulas used to meter a particular service',
    'type': 'object',
    'required': ['id', 'resources', 'aggregations'],
    'properties': {
        'id': {
            'type': 'string'
        },
        'resources': {
            'type': 'array',
            'minItems': 1,
            'items': {
                'type': 'object',
                'required': ['units'],
                'properties': {
                    'name': {
                        'type': 'string'
                    },
                    'units' : {
                        'type': 'array',
                        'minItems': 1,
                        'items': {
                            'type': 'object',
                            'required': ['name', 'quantityType'],
                            'properties': {
                                'name': {
                                    'type': 'string'
                                },
                                'quantityType': {
                                    'enum' : [ 'DELTA', 'CURRENT']
                                }
                            },
                            'additionalProperties': false
                        },
                        'additionalItems': false
                    }
                },
                'additionalProperties': false
            },
            'additionalItems': false
        },
        'aggregations': {
            'type': 'array',
            'items': {
                'type': 'object',
                'required': ['id', 'unit', 'formula'],
                'properties': {
                    'id': {
                      'type': 'string'
                    },
                    'unit': {
                        'type': 'string'
                    },
                    'aggregationGroup': {
                        'type': 'object',
                        'required': ['name'],
                        'properties': {
                            'name': {
                                'enum': ['daily', 'monthly']
                            },
                            'additionalProperties': false
                        }
                    },
                    'formula': {
                    },
                    'accumulate': {
                    },
                    'aggregate': {
                    },
                    'rate': {
                    }
                },
                'additionalProperties': false
            },
            'additionalItems': false
        }
    },
    'additionalProperties':  false
});

'use strict';

// Service resource definition schema

const _ = require('underscore');

const schema = require('cf-abacus-schema');

const clone = _.clone;
const extend = _.extend;

const string = schema.types.string;
const enumType = schema.types.enumType;
const object = schema.types.object;
const arrayOf = schema.types.arrayOf;

// Unit schema
const unit = object({ name: string(), quantityType: enumType(['DELTA', 'CURRENT']) }, ['name', 'quantityType']);

// Resource schema
const resource = object({ name: string(), units: arrayOf(unit) }, ['units']);

// AggregationGroup schema
const aggregationGroup = object({ name: enumType(['daily', 'monthly']) }, ['name']);

// Aggregation schema
const aggregation = object({ id: string(), unit: string(), aggregationGroup: aggregationGroup, formula: {}, accumulate: {}, aggregate: {}, rate: {} }, ['id', 'unit', 'formula']);

// Export our public functions
module.exports = () => extend(clone(object({ id: string(), resources: arrayOf(resource), aggregations: arrayOf(aggregation) }), ['id', 'resources', 'aggregations']),
    { title: 'Service Resource Definition', description : 'Defines the resources, units, metering, accumulation, aggregation and rating formulas used to meter a particular service' }
);

Refactor service resource definition schema to use JSON Schema primitive data type descriptor functions


$(function() {
  $("#credit-card input, #credit-card select").attr("disabled", false);

  $("#new_user, .edit_user").submit(function() {
    var form = this;
    $("#user_submit").attr("disabled", true);
    $("#credit-card input, #credit-card select").attr("name", "");
    $("#credit-card-errors").hide();

    if (!$("#credit-card").is(":visible")) {
      $("#credit-card input, #credit-card select").attr("disabled", true);
      return true;
    }

    var card = {
      number:   $("#credit_card_number").val(),
      expMonth: $("#_expiry_date_2i").val(),
      expYear:  $("#_expiry_date_1i").val(),
      cvc:      $("#cvv").val()
    };

    Stripe.createToken(card, function(status, response) {
      if (status === 200) {
        $("#user_last_4_digits").val(response.card.last4);
        $("#user_stripe_token").val(response.id);
        form.submit();
      } else {
        $("#stripe-error-message").text(response.error.message);
        $("#credit-card-errors").show();
        $("#user_submit").attr("disabled", false);
      }
    });

    return false;
  });

  $("#change-card a").click(function() {
    $("#change-card").hide();
    $("#credit-card").show();
    $("#credit_card_number").focus();
    return false;
  });
});

$(function() {
  $("#credit-card input, #credit-card select").attr("disabled", false);

  $("form:has(#credit-card)").submit(function() {
    var form = this;
    $("#user_submit").attr("disabled", true);
    $("#credit-card input, #credit-card select").attr("name", "");
    $("#credit-card-errors").hide();

    if (!$("#credit-card").is(":visible")) {
      $("#credit-card input, #credit-card select").attr("disabled", true);
      return true;
    }

    var card = {
      number:   $("#credit_card_number").val(),
      expMonth: $("#_expiry_date_2i").val(),
      expYear:  $("#_expiry_date_1i").val(),
      cvc:      $("#cvv").val()
    };

    Stripe.createToken(card, function(status, response) {
      if (status === 200) {
        $("#user_last_4_digits").val(response.card.last4);
        $("#user_stripe_token").val(response.id);
        form.submit();
      } else {
        $("#stripe-error-message").text(response.error.message);
        $("#credit-card-errors").show();
        $("#user_submit").attr("disabled", false);
      }
    });

    return false;
  });

  $("#change-card a").click(function() {
    $("#change-card").hide();
    $("#credit-card").show();
    $("#credit_card_number").focus();
    return false;
  });
});

Fix selector for update credit card form


/* @flow */

import { CompositeDisposable, Disposable } from 'atom'

import Linter from './main'
import type { UI, Linter as LinterProvider } from './types'

// Internal variables
let instance

const idleCallbacks = new Set()

export default {
  activate() {
    this.subscriptions = new CompositeDisposable()

    instance = new Linter()
    this.subscriptions.add(instance)

    const linterDepsCallback = window.requestIdleCallback(function linterDepsInstall() {
      idleCallbacks.delete(linterDepsCallback)
      if (!atom.inSpecMode()) {
        // eslint-disable-next-line global-require
        require('atom-package-deps').install('linter', true)
      }
    })
    idleCallbacks.add(linterDepsCallback)
  },
  consumeLinter(linter: LinterProvider): Disposable {
    const linters = [].concat(linter)
    for (const entry of linters) {
      instance.addLinter(entry)
    }
    return new Disposable(() => {
      for (const entry of linters) {
        instance.deleteLinter(entry)
      }
    })
  },
  consumeUI(ui: UI): Disposable {
    const uis = [].concat(ui)
    for (const entry of uis) {
      instance.addUI(entry)
    }
    return new Disposable(() => {
      for (const entry of uis) {
        instance.deleteUI(entry)
      }
    })
  },
  provideIndie(): Object {
    return indie => instance.addIndie(indie)
  },
  deactivate() {
    idleCallbacks.forEach(callbackID => window.cancelIdleCallback(callbackID))
    idleCallbacks.clear()
    this.subscriptions.dispose()
  },
}

/* @flow */

import { CompositeDisposable, Disposable } from 'atom'

import Linter from './main'
import type { UI, Linter as LinterProvider } from './types'

// Internal variables
let instance

export default {
  activate() {
    this.subscriptions = new CompositeDisposable()

    instance = new Linter()
    this.subscriptions.add(instance)

    this.subscriptions.add(
      atom.packages.onDidActivateInitialPackages(function() {
        if (!atom.inSpecMode()) {
          require('atom-package-deps').install('linter', true)
        }
      }),
    )
  },
  consumeLinter(linter: LinterProvider): Disposable {
    const linters = [].concat(linter)
    for (const entry of linters) {
      instance.addLinter(entry)
    }
    return new Disposable(() => {
      for (const entry of linters) {
        instance.deleteLinter(entry)
      }
    })
  },
  consumeUI(ui: UI): Disposable {
    const uis = [].concat(ui)
    for (const entry of uis) {
      instance.addUI(entry)
    }
    return new Disposable(() => {
      for (const entry of uis) {
        instance.deleteUI(entry)
      }
    })
  },
  provideIndie(): Object {
    return indie => instance.addIndie(indie)
  },
  deactivate() {
    this.subscriptions.dispose()
  },
}

Install pacakge-deps on initial activate of packages


export const defaultTypes = {
  '2.x': [
    'string',
    'long',
    'integer',
    'short',
    'byte',
    'double',
    'float',
    'date',
    'boolean',
    'binary',
    'object',
    'nested',
    'geo_point',
    'geo_shape',
    'ip',
    'completion',
    'token_count',
    'murmur3',
    'attachment'
  ],
  '5.x': [
    'text',
    'keyword',
    'long',
    'integer',
    'short',
    'byte',
    'double',
    'float',
    'date',
    'boolean',
    'binary',
    'object',
    'nested',
    'geo_point',
    'geo_shape',
    'ip',
    'completion',
    'token_count',
    'murmur3',
    'attachment'
  ]
};
export const defaultIndexType = [
  'analyzed',
  'not_analyzed'
];
export const readLinks = {
  subfield: 'https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html',
  extraProperties: 'https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html',
  importMapping: 'https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html',
  importAnalyzer: 'https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html'
};

export const defaultTypes = {
  '2.x': [
    'string',
    'long',
    'integer',
    'short',
    'byte',
    'double',
    'float',
    'date',
    'boolean',
    'binary',
    'object',
    'nested',
    'geo_point',
    'geo_shape',
    'ip',
    'completion',
    'token_count',
    'murmur3',
    'attachment'
  ],
  '5.x': [
    'text',
    'keyword',
    'long',
    'integer',
    'short',
    'byte',
    'double',
    'float',
    'date',
    'boolean',
    'binary',
    'object',
    'nested',
    'geo_point',
    'geo_shape',
    'ip',
    'completion',
    'token_count',
    'murmur3',
    'attachment'
  ]
};
export const defaultIndexType = [
  'analyzed',
  'not_analyzed'
];
export const readLinks = {
  subfield: 'https://github.com/appbaseio/gem/blob/dev/README.md#how-to-map-a-sub-field',
  extraProperties: 'https://github.com/appbaseio/gem/blob/dev/README.md#what-other-mapping-parameters-are-available',
  importMapping: 'https://github.com/appbaseio/gem/blob/dev/README.md#how-to-create-a-new-mapping',
  importAnalyzer: 'https://github.com/appbaseio/gem/blob/dev/README.md#how-to-create-a-custom-analyzer'
};

Attach links to read more


#!/usr/bin/env node

var ted = require('../ted')
var sh = require('shelljs')
var path = require('path')
var exec = require('child_process').execFile

// Install an array of submodules with package.json
var installModules = function (paths, whatTheyAre) {
  paths.forEach(function (path, index) {
    installModule(path)
  })
}

// Installed a single module dependencie based on Path
var installModule = function (modulePath) {

  var cwd = path.dirname(modulePath)

  // What is going on log
  console.log(
    ted.say.happy('npm install'),
    ted.say.warn('->'),
    ted.say.happy(cwd)
  )

  var installing = exec('npm', ['install', '-d'], {
    cwd: cwd
  }, function (err, out, erout) {
    // Good output
    if (out) {
      console.log(ted.say.debug(out))
    }

    // Bad output
    if (err) {
      console.log(ted.say.error(err))
    }
  })

  // When everything is done, tell me what happened
  installing.on('close', function (exit, error) {
    if (exit === 0 && !error) {
      console.log(cwd, ted.say.happy('done'))
    }

    if (error) {
      console.log(ted.say.error(error))
    }
  })
}

module.exports = installModules

#!/usr/bin/env node

var ted = require('../ted')
var sh = require('shelljs')
var path = require('path')
var exec = require('child_process').execFile

// Install an array of submodules with package.json
var installModules = function (paths, whatTheyAre) {
  paths.forEach(function (path, index) {
    installModule(path)
  })
}

// Installed a single module dependencie based on Path
var installModule = function (modulePath) {

  var cwd = path.dirname(modulePath)

  // What is going on log
  console.log(
    ted.say.happy('npm install'),
    ted.say.warn('->'),
    ted.say.happy(cwd)
  )

  var installing = exec('npm', ['install', '-d', '-s'], {
    cwd: cwd
  }, function (err, out, erout) {
    // Good output
    if (out) {
      console.log(ted.say.debug(out))
    }

    // Bad output
    if (err) {
      console.log(ted.say.error(err))
    }
  })

  // When everything is done, tell me what happened
  installing.on('close', function (exit, error) {
    if (exit === 0 && !error) {
      console.log(cwd, ted.say.happy('done'))
    }

    if (error) {
      console.log(ted.say.error(error))
    }
  })
}

module.exports = installModules

Add `-s` option to `npm install`


'use strict';

/**
 * @ngdoc function
 * @name tagrefineryGuiApp.controller:WorkflowCtrl
 * @description
 * # WorkflowCtrl
 * Controller of the tagrefineryGuiApp
 */
angular.module('tagrefineryGuiApp')
  .controller('WorkflowCtrl', ["$scope", "socket", function ($scope, socket) {
    var that = this;

    // State variables
    that.guided = true;
    that.showStep = false;
    that.currentStep = 0;

    ////////////////////////////////////////////////
    // Start
    ////////////////////////////////////////////////

    socket.on('isGuided', function (data) {
      that.guided = data == "true";

      socket.emit("getParameters", "");
    });

    that.ok = function()
    {
      that.showStep = true;
    };

    that.next = function()
    {
      that.showStep = false;
      that.currentStep++;

      // Let the child apply changes
      $scope.$broadcast("apply");
    };

    that.apply = function()
    {
      // Let the child apply changes
      $scope.$broadcast("apply");
    }

  }]);

'use strict';

/**
 * @ngdoc function
 * @name tagrefineryGuiApp.controller:WorkflowCtrl
 * @description
 * # WorkflowCtrl
 * Controller of the tagrefineryGuiApp
 */
angular.module('tagrefineryGuiApp')
  .controller('WorkflowCtrl', ["$scope", "socket", function ($scope, socket) {
    var that = this;

    // State variables
    that.guided = true;
    that.showStep = false;
    that.currentStep = 0;

    ////////////////////////////////////////////////
    // Start
    ////////////////////////////////////////////////

    socket.on('isGuided', function (data) {
      that.guided = data == "true";

      socket.emit("getParameters", "");
    });

    that.ok = function()
    {
      that.showStep = true;
    };

    that.next = function()
    {
      that.showStep = false;
      that.currentStep++;

      // Let the child apply changes
      $scope.$broadcast("apply");
    };

    that.apply = function()
    {
      // Let the child apply changes
      $scope.$broadcast("apply");

      socket.emit("computeWorkflow", "");
    }

  }]);

Send computeWorkflow message when tag is deselected


const glob = require('glob');
const path = require('path');

module.exports = {
  entry: {
    'index': './src/index.js',
    'test': ['babel-polyfill', ...glob.sync('./test/**/*-test.js')],
  },
  output: {
    path: path.resolve(__dirname, 'build'),
    publicPath: '/build/',
    filename: '[name].js',
  },
  devtool: '#source-map',
  module: {
    rules: [
      {
        use: {
          loader: 'babel-loader',
          options: {
            presets: [['es2015', {'modules': false}]],
            plugins: ['dynamic-import-system-import'],
          },
        },
        resource: {
          test: /\.js$/,
          exclude: /node_modules\/(?!(autotrack|dom-utils))/,
        },
      },
    ],
  },
  stats: 'minimal',
};

const glob = require('glob');
const path = require('path');

module.exports = {
  entry: {
    'index': './src/index.js',
    'test': ['babel-polyfill', ...glob.sync('./test/*-test.js')],
  },
  output: {
    path: path.resolve(__dirname, 'build'),
    publicPath: '/build/',
    filename: '[name].js',
  },
  devtool: '#source-map',
  module: {
    rules: [
      {
        use: {
          loader: 'babel-loader',
          options: {
            presets: [['es2015', {'modules': false}]],
            plugins: ['dynamic-import-system-import'],
          },
        },
        resource: {
          test: /\.js$/,
          exclude: /node_modules\/(?!(autotrack|dom-utils))/,
        },
      },
    ],
  },
  stats: 'minimal',
};

Remove the glob matcher for Windows compat


const path = require('path')
// eslint-disable-next-line import/no-unresolved
const slsw = require('serverless-webpack')
const CopyWebpackPlugin = require('copy-webpack-plugin')

const addBabelPolyfill = (slsEntries) => {
  return Object.keys(slsEntries)
    .reduce((acc, key) => Object.assign(acc, {
      [key]: ['babel-polyfill', slsEntries[key]]
    }), {})
}

module.exports = {
  entry: addBabelPolyfill(slsw.lib.entries),
  target: 'node',
  module: {
    loaders: [{
      test: /\.js$/,
      loaders: ['babel-loader'],
      include: __dirname,
      exclude: /node_modules/
    }]
  },
  output: {
    libraryTarget: 'commonjs',
    path: path.join(__dirname, '.webpack'),
    filename: '[name].js'
  },
  externals: [
    'aws-sdk'
  ],
  plugins: [
    new CopyWebpackPlugin([
          { from: '.serverless-secret.json' }
    ])
  ]
}

const path = require('path')
// eslint-disable-next-line import/no-unresolved
const slsw = require('serverless-webpack')
const CopyWebpackPlugin = require('copy-webpack-plugin')

const addBabelPolyfill = (slsEntries) => {
  return Object.keys(slsEntries)
    .reduce((acc, key) => Object.assign(acc, {
      [key]: ['babel-polyfill', 'source-map-support/register', slsEntries[key]]
    }), {})
}

module.exports = {
  entry: addBabelPolyfill(slsw.lib.entries),
  target: 'node',
  devtool: 'source-map',
  module: {
    loaders: [{
      test: /\.js$/,
      loaders: ['babel-loader'],
      include: __dirname,
      exclude: /node_modules/
    }]
  },
  output: {
    libraryTarget: 'commonjs',
    path: path.join(__dirname, '.webpack'),
    filename: '[name].js'
  },
  externals: [
    'aws-sdk'
  ],
  plugins: [
    new CopyWebpackPlugin([
          { from: '.serverless-secret.json' }
    ])
  ]
}

Add source map to get prettier stacktrace.


function tinyMceInit(textAreaId) {
    var editor;

    tinymce.init({
        selector: '#' + textAreaId,
        plugins: "lists",
        setup: editor => {
            editor = editor;
        }
    });
}

function tinyMceInit(textAreaId) {
    var editor;

    tinymce.init({
        selector: '#' + textAreaId,
        menubar: false,
        plugins: [
          'advlist autolink lists link image charmap print preview anchor',
          'searchreplace visualblocks code fullscreen',
          'insertdatetime media table contextmenu paste code'
        ],
        toolbar: 'undo redo | insert | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image',
        content_css: '//www.tinymce.com/css/codepen.min.css'
    });
}

Add new options it TinyMce


/* @flow */

import React, { Component } from 'react';
import {
  Text,
  View,
  Image
} from 'react-native';
import Button from '../../components/Button';
import images from '../../config/images';
import styles from './styles'

export class Home extends Component {
  static navigationOptions = {
    header: {visible: false},
  };
  render() {
    const { navigate } = this.props.navigation;
    return (
      <View style={styles.container}>
        <Image source={images.logo} style={styles.logo}/>
        <View style={styles.buttons}>
          <Button text="New Game" onPress={() => navigate("NewGame")}></Button>
          <Button text="Continue Game" onPress={() => console.log("Continue Game button pressed!")}></Button>
          <Button text="Settings" onPress={() => console.log("Settings button pressed!")}></Button>
        </View>
      </View>
    );
  }
};

/* @flow */

import React, { Component } from 'react';
import {
  Text,
  View,
  Image
} from 'react-native';
import Button from '../../components/Button';
import images from '../../config/images';
import styles from './styles'
import I18n from 'react-native-i18n'

export class Home extends Component {
  static navigationOptions = {
    header: {visible: false},
  };
  render() {
    const { navigate } = this.props.navigation;
    return (
      <View style={styles.container}>
        <Image source={images.logo} style={styles.logo}/>
        <View style={styles.buttons}>
          <Button text={I18n.t('newGame')} onPress={() => navigate("NewGame")}></Button>
          <Button text={I18n.t('continueGame')} onPress={() => console.log("Continue Game button pressed!")}></Button>
          <Button text={I18n.t('settings')} onPress={() => console.log("Settings button pressed!")}></Button>
        </View>
      </View>
    );
  }
};

I18n.fallbacks = true
I18n.translations = {
  en: {
    newGame: "New Game",
    continueGame: "Continue Game",
    settings: "Settings"
  },
  de: {
    newGame: "Neues Spiel",
    continueGame: "Spiel fortsetzen",
    settings: "Einstellungen"
  }
}

Add i18n to home screen