import React from 'react';
import LayoutWrap from 'components/LayoutWrap';
import LayoutContent from 'components/LayoutContent';
import LayoutSidebar from 'components/LayoutSidebar';
import Container from 'components/Container';
import Section from 'components/Section';

import PetitionHeader from 'containers/PetitionHeader';
import PetitionBody from 'containers/PetitionBody';
import PetitionSidebar from 'containers/PetitionSidebar';

const Petition = ({ preview }) => (

      <Container>
        <PetitionHeader />
      </Container>

    <Container>
      <LayoutWrap>
        <LayoutContent>
          <PetitionBody />
        </LayoutContent>
        <LayoutSidebar>
          <PetitionSidebar />
        </LayoutSidebar>
      </LayoutWrap>
    </Container>

);

export default Petition;

import React from 'react';
import LayoutWrap from 'components/LayoutWrap';
import LayoutContent from 'components/LayoutContent';
import LayoutSidebar from 'components/LayoutSidebar';
import Container from 'components/Container';
import Section from 'components/Section';

import PetitionHeader from 'containers/PetitionHeader';
import PetitionBody from 'containers/PetitionBody';
import PetitionResponse from 'containers/PetitionResponse';
import PetitionSidebar from 'containers/PetitionSidebar';

const Petition = ({ preview }) => (

      <Container>
        <PetitionHeader />
      </Container>

    <Container>
      <LayoutWrap>
        <LayoutContent>
          <PetitionBody />
          <PetitionResponse />
        </LayoutContent>
        <LayoutSidebar>
          <PetitionSidebar />
        </LayoutSidebar>
      </LayoutWrap>
    </Container>

);

export default Petition;

Bring back PetitionResponse to petition detail page


import React from 'react';
import {
  Col,
  FormControl,
  FormGroup,
} from 'react-bootstrap';

export function renderTextArea(handleTextChange, textFilter) {
  return (
    <Col lg={8} md={8} sm={12} xs={12}>
      <FormGroup style={{marginBottom: '5px',}}>
        <FormControl
          componentClass='textarea'
          onChange={handleTextChange}
          placeholder='Name'
          style={{height: '34px', resize: 'none',}}
          value={textFilter}
        />
      </FormGroup>
    </Col>
  );
}

import React from 'react';
import {
  Col,
  FormControl,
  FormGroup,
  Glyphicon,
  InputGroup,
} from 'react-bootstrap';

export function renderTextArea(handleTextChange, textFilter) {
  return (
    <Col lg={8} md={8} sm={12} xs={12}>
      <FormGroup style={{marginBottom: '5px',}}>
        <InputGroup>
          <FormControl
            onChange={handleTextChange}
            placeholder='Name'
            type='text'
            value={textFilter}
          />
          <InputGroup.Addon>
            <Glyphicon glyph='search' />
          </InputGroup.Addon>
        </InputGroup>
      </FormGroup>
    </Col>
  );
}

Add magnifying glass for ux


module.exports = function(environment) {
  var ENV = {};

  if(environment === 'production') {
    ENV["azure-tables"] = {
      storageAccount: "my-storage-account",
      storageAccessKey: "my-access-key"

      // You can also connect using your connection string, set it as:
      // connectionString: "my-connection-string"
    };

    ENV["azure-blob"] = {
      storageAccount: "my-storage-account",
      storageAccessKey: "my-access-key",
      containerName: "my-container-name" // defaults to 'emberdeploy'

      // You can also connect using your connection string, set it as:
      // connectionString: "my-connection-string"
    };
  }

  // add other environments as needed
}

module.exports = function(environment) {
  var ENV = {};

  if(environment === 'production' || !environment) {
    ENV["azure-tables"] = {
      storageAccount: "my-storage-account",
      storageAccessKey: "my-access-key"

      // You can also connect using your connection string, set it as:
      // connectionString: "my-connection-string"
    };

    ENV["azure-blob"] = {
      storageAccount: "my-storage-account",
      storageAccessKey: "my-access-key",
      containerName: "my-container-name" // defaults to 'emberdeploy'

      // You can also connect using your connection string, set it as:
      // connectionString: "my-connection-string"
    };
  }

  // add other environments as needed
}

Use production environment by default


/*
 * App config for production.
 */
module.exports = {
  environment: 'production',
  connection: {
    host: 'localhost',
    port: 3000
  },
  db: 'data/dsw-dashboard.sqlite',
  startDate: '2014-07-01'
};

/*
 * App config for production.
 */
module.exports = {
  environment: 'production',
  db: 'data/dsw-dashboard.sqlite',
  startDate: '2014-07-01'
};

Remove connection info so Heroku can set its own


module.exports = {
    users: [ // ADD YOUR USERNAME AT THE TOP
        'mattclaw',
        'crankeye',
        'cgroner',
        'tejohnso',
        'sp1d3rx',
        'evsie001',
        'msied',
        'd7p',
        'kasperlewau',
        'kennethrapp',
        'briansoule',
        'qguv',
        'ianwalter',
        'williamle8300',
        'jeffawang',
        'sorpaas',
        'dylnclrk',
        'dcancel',
        'theprofessor117',
        'roryokane',
        'shedd',
        'LeandroLovisolo',
        'euank',
        'humd',
        'bwlang',
        'barretts',
        'pksjce',
        'tito0224',
        'hemanth',
        'treygriffith',
        'megamattron',
        'pents90',
        'michaelnovakjr',
        'markbao',
        'kalmi',
        'lablayers',
        'dfjones'
    ]
};

module.exports = {
    users: [ // ADD YOUR USERNAME AT THE TOP
        'julbaxter'
        'mattclaw',
        'crankeye',
        'cgroner',
        'tejohnso',
        'sp1d3rx',
        'evsie001',
        'msied',
        'd7p',
        'kasperlewau',
        'kennethrapp',
        'briansoule',
        'qguv',
        'ianwalter',
        'williamle8300',
        'jeffawang',
        'sorpaas',
        'dylnclrk',
        'dcancel',
        'theprofessor117',
        'roryokane',
        'shedd',
        'LeandroLovisolo',
        'euank',
        'humd',
        'bwlang',
        'barretts',
        'pksjce',
        'tito0224',
        'hemanth',
        'treygriffith',
        'megamattron',
        'pents90',
        'michaelnovakjr',
        'markbao',
        'kalmi',
        'lablayers',
        'dfjones'
    ]
};

Add my name to user list.


import Ember from 'ember';
import ENV from '../config/environment';
import { Bindings } from 'ember-pusher/bindings';

// used by the Application controller

export default Ember.Mixin.create(Bindings, {
  logPusherEvents: (ENV.environment === "development"),
  PUSHER_SUBSCRIPTIONS: {
    activities: ["push"]
  },
  userChannel: function() {
    console.log(this.get("session.username"));
  }.property("session.username"),
  subscribeToMyChannel: function() {
    var channelName = this.get("username") + '_channel';
    this.pusher.wire(this, channelName, ['notify']);
  }.observes("userChannel"),
  init: function() {
    this._super();
    this.subscribeToMyChannel();
  },
  actions: {
    push: function(data) {
      this.store.pushPayload('activity', data);
    },
    notify: function(data) {
      this.store.pushPayload('notification', data);
      var activity = data.activities[0];
      Ember.$.post(ENV.api + "/notifications/" + data.notification.id + "/ack");
      switch (activity.type) {
        case "ProposalComment":
          this.get("notifier").show("On your proposal for " + activity.wordId, {name: "New Comment", route: ["proposal.index", activity.proposalId]});
          break;
        case "ProposalClosed":
          this.get("notifier").show("Your proposal for " + activity.wordId + " was " + activity.finalState, {name: "Proposal", route: ["proposal.index", activity.proposalId]});
          break;
      }

    }
  },
});

import Ember from 'ember';
import ENV from '../config/environment';
import { Bindings } from 'ember-pusher/bindings';

// used by the Application controller

export default Ember.Mixin.create(Bindings, {
  logPusherEvents: (ENV.environment === "development"),
  PUSHER_SUBSCRIPTIONS: {
    activities: ["push"]
  },
  userChannel: function() {
    console.log(this.get("session.username"));
  }.property("session.username"),
  subscribeToMyChannel: function() {
    var channelName = this.get("username") + '_channel';
    this.pusher.wire(this, channelName, ['notify']);
  }.observes("userChannel"),
  init: function() {
    this._super();
    this.subscribeToMyChannel();
  },
  actions: {
    push: function(data) {
      this.store.pushPayload('activity', data);
    },
    notify: function(data) {
      this.store.pushPayload('notification', data);
      var activity = data.activities[0];
      Ember.$.post(ENV.api + "/notifications/" + data.notification.id + "/ack");
      switch (activity.type) {
        case "ProposalComment":
          this.get("notifier").show("On your proposal for " + activity.wordName, {name: "New Comment", route: ["proposal.index", activity.proposalId]});
          break;
        case "ProposalClosed":
          this.get("notifier").show("Your proposal for " + activity.wordName + " was " + activity.finalState, {name: "Proposal", route: ["proposal.index", activity.proposalId]});
          break;
      }

    }
  },
});

Fix notifications to use the wordName rather than wordId


const externals = require('./externals.conf.js');
const base = require('./karma.conf.js');

module.exports = function(config) {
  if (!process.env.SAUCE_USERNAME || !process.env.SAUCE_ACCESS_KEY) {
    console.log('Make sure the SAUCE_USERNAME and SAUCE_ACCESS_KEY environment variables are set.')
    process.exit(1)
  }

  // Apply base config
  base(config);

  // Then add Sauce Labs setup
  const customLaunchers = {
    sl_safari: {
      base: 'SauceLabs',
      browserName: 'safari',
      version: '11',
    },
    sl_firefox: {
      base: 'SauceLabs',
      browserName: 'firefox',
      version: '56',
    },
  };
  const reporters = config.reporters;
  reporters.push('saucelabs');

  // Apply additional config
  config.set({
    reporters,
    customLaunchers,
    browsers: config.browsers.concat(Object.keys(customLaunchers)),
    sauceLabs: {
      testName: 'NoFlo UI browser tests',
      recordScreenshots: false,
      connectOptions: {
        port: 5757,
        logfile: 'sauce_connect.log'
      },
      public: 'public'
    },
    captureTimeout: 120000,
    concurrency: 1,
  });
}

const externals = require('./externals.conf.js');
const base = require('./karma.conf.js');

module.exports = function(config) {
  if (!process.env.SAUCE_USERNAME || !process.env.SAUCE_ACCESS_KEY) {
    console.log('Make sure the SAUCE_USERNAME and SAUCE_ACCESS_KEY environment variables are set.')
    process.exit(1)
  }

  // Apply base config
  base(config);

  // Then add Sauce Labs setup
  const customLaunchers = {
    sl_safari: {
      base: 'SauceLabs',
      browserName: 'safari',
      version: '11',
    },
    sl_firefox: {
      base: 'SauceLabs',
      browserName: 'firefox',
      version: '56',
    },
  };
  const reporters = config.reporters;
  reporters.push('saucelabs');

  // Apply additional config
  config.set({
    reporters,
    customLaunchers,
    browsers: config.browsers.concat(Object.keys(customLaunchers)),
    sauceLabs: {
      startConnect: false,
      tunnelIdentifier: process.env['TRAVIS_JOB_NUMBER'],
      testName: 'NoFlo UI browser tests',
      recordScreenshots: false,
      connectOptions: {
        port: 5757,
        logfile: 'sauce_connect.log'
      },
      public: 'public'
    },
    captureTimeout: 120000,
    concurrency: 1,
  });
}

Use the tunnel identifier from Travis


var skyBox = require('./sky_box'),
    TWEEN = require('tween.js'),
    global = require('../lib/util').global(),
    raf = require('raf-component'),
    THREE = require('three');

module.exports = function scene (config) {
  var distance = 1000,
      camera,
      scene,
      renderer;

  camera = new THREE.PerspectiveCamera(30, global.innerWidth / global.innerHeight, 0.1, 100000000);
  camera.up = new THREE.Vector3(0, 0, 1);
  camera.useQuaternion = true;
  camera.position.y = distance * 10;
  scene = new THREE.Scene();

  renderer = new THREE.WebGLRenderer({ antialias: true });
  config.maxAnisotropy = renderer.getMaxAnisotropy();

  skyBox(scene, config.sky_box);

  scene.add(new THREE.PointLight(0xffffff, 1, 0));
  scene.add(new THREE.AmbientLight(0x333333));

  skyBox(scene, config.sky_box);

  return {
    setSize: setSize,
    render: render,
    domCanvas: renderer.domElement,
    scene: scene,
    camera: camera
  };

  function setSize (width, height) {
    camera.aspect = width / height;
    camera.updateProjectionMatrix();
    renderer.setSize(width, height);
  }

  function render () {
    raf(render);
    TWEEN.update();
    renderer.render(scene, camera);
  }
};

var skyBox = require('./sky_box'),
    TWEEN = require('tween.js'),
    global = require('../lib/util').global(),
    raf = require('raf-component'),
    THREE = require('three');

module.exports = function scene (config) {
  var distance = 1000,
      camera,
      world,
      renderer;

  camera = new THREE.PerspectiveCamera(30, global.innerWidth / global.innerHeight, 0.1, 100000000);
  camera.up = new THREE.Vector3(0, 0, 1);
  camera.useQuaternion = true;
  camera.position.y = distance * 5;
  camera.position.z = distance * 2;
  world = new THREE.Scene();

  renderer = new THREE.WebGLRenderer({ antialias: true });
  config.maxAnisotropy = renderer.getMaxAnisotropy();

  skyBox(world, config.sky_box);

  var point = new THREE.PointLight(0xffffff, 1, 0);
  point.position.y = 4000;
  point.position.z = 1000;

  world.add(point);
  world.add(new THREE.AmbientLight(0x333333));

  skyBox(world, config.sky_box);

  return {
    setSize: setSize,
    render: render,
    domCanvas: renderer.domElement,
    scene: world,
    camera: camera
  };

  function setSize (width, height) {
    camera.aspect = width / height;
    camera.updateProjectionMatrix();
    renderer.setSize(width, height);
  }

  function render () {
    raf(render);
    TWEEN.update();
    renderer.render(world, camera);
  }
};

Move point light into position behind camera


import assert from 'assert';
import RawMetadata from '../rawmetadata.js';

const fromMetadataJsonToKataGroups = (metadataJson) => {
  return RawMetadata.toKataGroups(metadataJson);
};

describe('create KataGroups from the raw metadata', function() {

  const group = {items: [{id: 1}]};
  const anotherGroup = {items: [{id: 2}]};

  it('for one group only one KataGroup is created', function() {
    const groupedMetadataJson = {
      groups: {'group name': group}
    };

    var kataGroups = fromMetadataJsonToKataGroups(groupedMetadataJson);
    assert.equal(kataGroups.length, 1);
  });

  it('two groups two KataGroups are created', function() {
    const groupedMetadataJson = {
      groups: {
        'group name': group,
        'group name1': anotherGroup
      }
    };

    var kataGroups = fromMetadataJsonToKataGroups(groupedMetadataJson);
    assert.equal(kataGroups.length, 2);
  });
});

import assert from 'assert';
import RawMetadata from '../rawmetadata.js';

const fromMetadataJsonToKataGroups = (metadataJson) => {
  return RawMetadata.toKataGroups(metadataJson);
};

class Kata {
  static withId(id) {
    return {id};
  }
}

describe('create KataGroups from the raw metadata', function() {

  const group = {items: [Kata.withId(1)]};
  const anotherGroup = {items: [Kata.withId(2)]};

  it('for one group only one KataGroup is created', function() {
    const groupedMetadataJson = {
      groups: {'group name': group}
    };

    var kataGroups = fromMetadataJsonToKataGroups(groupedMetadataJson);
    assert.equal(kataGroups.length, 1);
  });

  it('two groups two KataGroups are created', function() {
    const groupedMetadataJson = {
      groups: {
        'group name': group,
        'group name1': anotherGroup
      }
    };

    var kataGroups = fromMetadataJsonToKataGroups(groupedMetadataJson);
    assert.equal(kataGroups.length, 2);
  });
});

Make data construction a bit simpler to understand.


(function(root) {

  function ArrayBufferToBuffer(ab) {
    var buffer = new Buffer(ab.byteLength);
    var view = new Uint8Array(ab);
    for (var i = 0; i < buffer.length; ++i) {
        buffer[i] = view[i];
    }
    return buffer;
  }

  if (typeof exports !== 'undefined') {
    if (typeof module !== 'undefined' && module.exports) {
      exports = module.exports = ArrayBufferToBuffer;
    }
    exports.ArrayBufferToBuffer = ArrayBufferToBuffer;
  } else if (typeof define === 'function' && define.amd) {
    define([], function() {
      return ArrayBufferToBuffer;
    });
  } else {
    root.ArrayBufferToBuffer = ArrayBufferToBuffer;
  }

})(this);

(function(root) {
  var isArrayBufferSupported = new Buffer(new Uint8Array([1]).buffer)[0] === 1;

  var ArrayBufferToBuffer = isArrayBufferSupported ? ArrayBufferToBufferAsArgument : ArrayBufferToBufferPerElement;

  function ArrayBufferToBufferAsArgument (ab) {
    return new Buffer(ab);
  }

  function ArrayBufferToBufferPerElement (ab) {
    var buffer = new Buffer(ab.byteLength);
    var view = new Uint8Array(ab);
    for (var i = 0; i < buffer.length; ++i) {
        buffer[i] = view[i];
    }
    return buffer;
  }

  if (typeof exports !== 'undefined') {
    if (typeof module !== 'undefined' && module.exports) {
      exports = module.exports = ArrayBufferToBuffer;
    }
    exports.ArrayBufferToBuffer = ArrayBufferToBuffer;
  } else if (typeof define === 'function' && define.amd) {
    define([], function() {
      return ArrayBufferToBuffer;
    });
  } else {
    root.ArrayBufferToBuffer = ArrayBufferToBuffer;
  }

})(this);

Enhance performance due to node4x buffer


var React = require('react'),
	Field = require('../Field');

module.exports = Field.create({

	displayName: 'BooleanField',

	valueChanged: function(event) {
		this.props.onChange({
			path: this.props.path,
			value: event.target.checked
		});
	},

	renderUI: function() {

		var input, fieldClassName = 'field-ui';

		if (this.props.indent) {
			fieldClassName += ' field-indented';
		}

		if (this.shouldRenderField()) {
			input = (

						<input type='checkbox' name={this.props.path} id={this.props.path} value='true' checked={this.props.value} onChange={this.valueChanged} />
						{this.props.label}

			);
		} else {
			var state = this.props.value ? 'checked' : 'unchecked';
			var imgSrc = '/keystone/images/icons/16/checkbox-' + state + '.png';
			input = (

					{this.props.label}

			);
		}

		return
{input}
;
	}

});

var React = require('react'),
	Field = require('../Field');

module.exports = Field.create({

	displayName: 'BooleanField',

	valueChanged: function(event) {
		this.props.onChange({
			path: this.props.path,
			value: event.target.checked
		});
	},

	renderUI: function() {

		var input, fieldClassName = 'field-ui';

		if (this.props.indent) {
			fieldClassName += ' field-indented';
		}

		if (this.shouldRenderField()) {
			input = (

						<input type='checkbox' name={this.props.path} id={this.props.path} value='true' checked={this.props.value} onChange={this.valueChanged} />
						{this.props.label}

					{this.renderNote()}

			);
		} else {
			var state = this.props.value ? 'checked' : 'unchecked';
			var imgSrc = '/keystone/images/icons/16/checkbox-' + state + '.png';
			input = (

					{this.props.label}

{this.renderNote()}

			);
		}

		return
{input}
;
	}

});

Fix Boolean field - show notes


define(function(require, exports, module) {

var modesByName = require("ace/ext/modelist").modesByName;

modesByName.javascript.extensions += "|run|build";

var primaryModes = ["c_cpp","clojure","coffee","csharp","css","dart","golang",
    "html","jade","java","javascript","json","less","lua","perl","php","python",
    "ruby","scala","scss","sh","stylus","sql","text","typescript","xml","xquery",
    "yaml"];

var hiddenModes = ["text", "snippets"];
var fileExtensions = Object.create(null);
var modesByCaption = Object.create(null);
var customExtensions = Object.create(null);
Object.keys(modesByName).forEach(function(name) {
    var mode = modesByName[name];
    modesByCaption[mode.caption] = mode;

    mode.id = name;
    if (primaryModes.indexOf(mode.name) != -1)
        mode.order = 100000;
    else if (hiddenModes.indexOf(mode.name) != -1)
        mode.order = -1;
    else
        mode.order = 0;

    mode.extensions.split("|").forEach(function(ext) {
        if (!fileExtensions[ext])
            fileExtensions[ext] = name;
    });
});

module.exports = {
    byName: modesByName,
    extensions: fileExtensions,
    customExtensions: customExtensions,
    byCaption: modesByCaption
}
});

define(function(require, exports, module) {

var modesByName = require("ace/ext/modelist").modesByName;

modesByName.javascript.extensions += "|run|build|outline";

var primaryModes = ["c_cpp","clojure","coffee","csharp","css","dart","golang",
    "html","jade","java","javascript","json","less","lua","perl","php","python",
    "ruby","scala","scss","sh","stylus","sql","text","typescript","xml","xquery",
    "yaml"];

var hiddenModes = ["text", "snippets"];
var fileExtensions = Object.create(null);
var modesByCaption = Object.create(null);
var customExtensions = Object.create(null);
Object.keys(modesByName).forEach(function(name) {
    var mode = modesByName[name];
    modesByCaption[mode.caption] = mode;

    mode.id = name;
    if (primaryModes.indexOf(mode.name) != -1)
        mode.order = 100000;
    else if (hiddenModes.indexOf(mode.name) != -1)
        mode.order = -1;
    else
        mode.order = 0;

    mode.extensions.split("|").forEach(function(ext) {
        if (!fileExtensions[ext])
            fileExtensions[ext] = name;
    });
});

module.exports = {
    byName: modesByName,
    extensions: fileExtensions,
    customExtensions: customExtensions,
    byCaption: modesByCaption
}
});

Add highlighting for outline files


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

gulp.task('default', ['package'], function() {
  // place code for your default task here
});

gulp.task('package', function() {
  return gulp.src(['./*', 'vendor/*', '!archive.zip', '!gulpfile.js', '!package.json'], { base: '.' })
    .pipe(zip('archive.zip'))
    .pipe(gulp.dest('.//'));
});

gulp.task('vendor', ['vendor-rtc', 'vendor-rtc-ios']);

gulp.task('vendor-rtc', function() {
  return gulp
    .src('node_modules/rtc/dist/*')
    .pipe(gulp.dest('vendor/'));
});

gulp.task('vendor-rtc-ios', function() {
  return gulp
    .src('node_modules/rtc-plugin-nicta-ios/dist/*')
    .pipe(gulp.dest('vendor/'));
});

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

gulp.task('default', ['package'], function() {
  // place code for your default task here
});

gulp.task('package', function() {
  return gulp.src([
    './*',
    'vendor/*',
    'icons/*',
    '!archive.zip',
    '!gulpfile.js',
    '!package.json'
  ], { base: '.' })
  .pipe(zip('archive.zip'))
  .pipe(gulp.dest('.//'));
});

gulp.task('vendor', ['vendor-rtc', 'vendor-rtc-ios']);

gulp.task('vendor-rtc', function() {
  return gulp
    .src('node_modules/rtc/dist/*')
    .pipe(gulp.dest('vendor/'));
});

gulp.task('vendor-rtc-ios', function() {
  return gulp
    .src('node_modules/rtc-plugin-nicta-ios/dist/*')
    .pipe(gulp.dest('vendor/'));
});

Add icon files to the archive also


var gulp = require('gulp');
var jshint = require('gulp-jshint');
var mocha = require('gulp-mocha');
var gutil = require('gulp-util');

gulp.task('lint', function() {
    return gulp.src(['./src/**/*.js', './test/**/*.js'])
        .pipe(jshint())
        .pipe(jshint.reporter('default'));
});

function test(srcPath) {
    return gulp.src(srcPath)
        .pipe(mocha())
        .on('error', gutil.log);
}

gulp.task('commitTests', function() {
    return test('./test/commit/**/*.js');
});

gulp.task('acceptanceTests', function() {
    return test('./test/acceptance/**/*.js');
});

gulp.task('watch', ['lint', 'commitTests', 'acceptanceTests']);

gulp.task('default', ['watch'], function() {
    gulp.watch(['./src/**/*.js', './test/**/*.js'], ['watch']);
});

var gulp = require('gulp');
var jshint = require('gulp-jshint');
var mocha = require('gulp-mocha');
var gutil = require('gulp-util');

gulp.task('lint', function() {
    return gulp.src(['./src/**/*.js', '!./src/static/bower_components{,**}',
                     './test/**/*.js'])
        .pipe(jshint())
        .pipe(jshint.reporter('default'));
});

function test(srcPath) {
    return gulp.src(srcPath, {read: false})
        .pipe(mocha())
        .on('error', gutil.log);
}

gulp.task('commitTests', function() {
    return test('./test/commit/**/*.js');
});

gulp.task('acceptanceTests', function() {
    return test('./test/acceptance/**/*.js');
});

gulp.task('watch', ['lint', 'commitTests', 'acceptanceTests']);

gulp.task('default', ['watch'], function() {
    gulp.watch(['./src/**/*.js', '!./src/static/bower_coponents{,**}',
                './test/**/*.js'], ['watch']);
});

Exclude bower components from linting


'use strict';

var gulp		    = require('gulp');
var runSequence     = require('run-sequence');
var taskLoader		= require('gulp-task-loader');
var $	 			= require('./gulp.config.js');

// load all tasks from folder 'tasks'
// tasks named after task file name
taskLoader('tasks');

gulp.task('default', function(cb){
	runSequence('build', cb);
});

/**
 * Clean dist folders
 * Test and validate code
 * Optimize angular files
 * Create dist module
 *
 * @param  {Function} done - callback when complete
 */
gulp.task('build', function(cb) {
	runSequence('clean', 'test', 'angular', cb);
});

/**
 * Vet the code
 * Start the tests using karma
 *
 * @param  {Function} done - callback when complete
 */
gulp.task('test', function(cb) {
	runSequence('vet', 'karma', cb);
});

/**
 * Bump the version and create dist module
 * --type=pre will bump the prerelease version *.*.*-x
 * --type=patch or no flag will bump the patch version *.*.x
 * --type=minor will bump the minor version *.x.*
 * --type=major will bump the major version x.*.*
 * --version=1.2.3 will bump to a specific version and ignore other flags
 *
 * @param  {Function} done - callback when complete
 */
gulp.task('release', function(cb) {
	 runSequence('bump', 'module', cb);
});

'use strict';

var gulp		    = require('gulp');
var runSequence     = require('run-sequence');
var taskLoader		= require('gulp-task-loader');
var $	 			= require('./gulp.config.js');

// load all tasks from folder 'tasks'
// tasks named after task file name
taskLoader('tasks');

gulp.task('default', function(cb){
	runSequence('build', cb);
});

/**
 * Clean dist folders
 * Test and validate code
 * Optimize angular files
 * Create dist module
 *
 * @param  {Function} done - callback when complete
 */
gulp.task('build', function(cb) {
	runSequence('clean', 'test', 'angular', 'doc', cb);
});

/**
 * Vet the code
 * Start the tests using karma
 *
 * @param  {Function} done - callback when complete
 */
gulp.task('test', function(cb) {
	runSequence('vet', 'karma', cb);
});

/**
 * Bump the version and create dist module
 * --type=pre will bump the prerelease version *.*.*-x
 * --type=patch or no flag will bump the patch version *.*.x
 * --type=minor will bump the minor version *.x.*
 * --type=major will bump the major version x.*.*
 * --version=1.2.3 will bump to a specific version and ignore other flags
 *
 * @param  {Function} done - callback when complete
 */
gulp.task('release', function(cb) {
	 runSequence('bump', 'module', cb);
});

Add doc task to build


'use strict';

var React = require('react');

var Table = require('./table.jsx');

var App = React.createClass({
    render() {
        var config = {
            columns: [
                {
                    property: 'name',
                    editable: true,
                    header: 'Name',
                },
                {
                    property: 'position',
                    header: 'Position',
                },
                {
                    property: 'salary',
                    header: 'Salary',
                    formatter: (salary) => salary.toFixed(2)
                }
            ]
        };
        var data = [
            {
                name: 'Jack Jackson',
                position: 'Boss',
                salary: 10000,
            },
            {
                name: 'Bo Bobson',
                position: 'Contractor',
                salary: 4650.9234,
            },
            {
                name: 'Cecilia Robertson',
                position: 'Client',
                salary: 6499.1038
            }
        ];

        return (

        );
    }
});

module.exports = App;

'use strict';

var React = require('react');

var Table = require('./table.jsx');

var App = React.createClass({
    render() {
        var countries = {
            'de': 'Germany',
            'fi': 'Finland',
            'se': 'Sweden'
        };
        var config = {
            columns: [
                {
                    property: 'name',
                    editable: true,
                    header: 'Name',
                },
                {
                    property: 'position',
                    header: 'Position',
                },
                {
                    property: 'country',
                    header: 'Country',
                    formatter: (country) => countries[country]
                },
                {
                    property: 'salary',
                    header: 'Salary',
                    formatter: (salary) => salary.toFixed(2)
                },
            ]
        };
        var data = [
            {
                name: 'Jack Jackson',
                position: 'Boss',
                salary: 10000,
                country: 'se',
            },
            {
                name: 'Bo Bobson',
                position: 'Contractor',
                salary: 4650.9234,
                country: 'de',
            },
            {
                name: 'Cecilia Robertson',
                position: 'Client',
                salary: 6499.1038,
                country: 'fi',
            }
        ];

        return (

        );
    }
});

module.exports = App;

Add an example with a country lookup


import '../core/String';  // For String#capitalise
import { isApple } from '../ua/UA';

const platformKeys = {
    ArrowUp: '↑',
    ArrowDown: '↓',
    ArrowRight: '→',
    ArrowLeft: '←',
    Alt: isApple ? '⌥' : 'Alt-',
    Cmd: isApple ? '⌘' : 'Ctrl-',
    Ctrl: isApple ? '⌃' : 'Ctrl-',
    Meta: isApple ? '⌘' : 'Meta-',
    Shift: isApple ? '⇧' : 'Shift-',
    Escape: 'Esc',
    Enter: isApple ? '↵' : 'Enter',
    Backspace: isApple ? '⌫' : 'Backspace',
    Delete: isApple ? '⌦' : 'Delete',
};

/**
    Function: O.formatKeyForPlatform

    Parameters:
        shortcut - {String} The keyboard shorcut, in the same format as
                   taken by <O.GlobalKeyboardShortcuts#register>.

    Returns:
        {String} The shortcut formatted for display on the user's platform.
*/
export default function formatKeyForPlatform ( shortcut ) {
    return shortcut.split( '-' ).map(
        key => platformKeys[ key ] || key.capitalise()
    ).join( '' );
}

import '../core/String';  // For String#capitalise
import { isApple } from '../ua/UA';

const platformKeys = {
    ArrowUp: '↑',
    ArrowDown: '↓',
    ArrowRight: '→',
    ArrowLeft: '←',
    Alt: isApple ? '⌥' : 'Alt-',
    Cmd: isApple ? '⌘' : 'Ctrl-',
    Ctrl: isApple ? '⌃' : 'Ctrl-',
    Meta: isApple ? '⌘' : 'Meta-',
    Shift: isApple ? '⇧' : 'Shift-',
    Escape: 'Esc',
    Enter: isApple ? '↵' : 'Enter',
    Backspace: isApple ? '⌫' : 'Backspace',
    Delete: isApple ? '⌦' : 'Delete',
};

const modifierOrder = ( isApple ?
    [ 'Ctrl', 'Alt', 'Shift', 'Cmd', 'Meta' ] :
    [ 'Meta', 'Cmd', 'Ctrl', 'Alt', 'Shift' ]
).reduce( ( order, x, index ) => {
    order[x] = index + 1;
    return order;
}, {} );

const sortModifierKeys = function ( a, b ) {
    return ( modifierOrder[a] || 9 ) - ( modifierOrder[b] || 9 );
};

/**
    Function: O.formatKeyForPlatform

    Parameters:
        shortcut - {String} The keyboard shorcut, in the same format as
                   taken by <O.GlobalKeyboardShortcuts#register>.

    Returns:
        {String} The shortcut formatted for display on the user's platform.
*/
export default function formatKeyForPlatform ( shortcut ) {
    return shortcut.split( '-' ).sort( sortModifierKeys ).map(
        key => platformKeys[ key ] || key.capitalise()
    ).join( '' );
}

Enforce platform specific ordering of modifiers


import Polymer from '../../polymer';

require('../../components/ha-label-badge');

export default new Polymer({
  is: 'ha-entity-marker',

  properties: {
    hass: {
      type: Object,
    },

    entityId: {
      type: String,
      value: '',
      reflectToAttribute: true,
    },

    state: {
      type: Object,
      computed: 'computeState(entityId)',
    },

    icon: {
      type: Object,
      computed: 'computeIcon(state)',
    },

    image: {
      type: Object,
      computed: 'computeImage(state)',
    },

    value: {
      type: String,
      computed: 'computeValue(state)',
    },
  },

  listeners: {
    tap: 'badgeTap',
  },

  badgeTap(ev) {
    ev.stopPropagation();
    if (this.entityId) {
      this.async(() => window.hass.moreInfoActions.selectEntity(this.entityId), 1);
    }
  },

  computeState(entityId) {
    return entityId && window.hass.reactor.evaluate(window.hass.entityGetters.byId(entityId));
  },

  computeIcon(state) {
    return !state && 'home';
  },

  computeImage(state) {
    return state && state.attributes.entity_picture;
  },

  computeValue(state) {
    return state &&
      state.entityDisplay.split(' ').map(part => part.substr(0, 1)).join('');
  },
});

import Polymer from '../../polymer';

require('../../components/ha-label-badge');

/*
Leaflet clones this element before adding it to the map. This messes up
our Poylmer object and we lose the reference to the `hass` object.

That's why we refer here to window.hass instead of the hass property.
*/

export default new Polymer({
  is: 'ha-entity-marker',

  properties: {
    hass: {
      type: Object,
    },

    entityId: {
      type: String,
      value: '',
      reflectToAttribute: true,
    },

    state: {
      type: Object,
      computed: 'computeState(entityId)',
    },

    icon: {
      type: Object,
      computed: 'computeIcon(state)',
    },

    image: {
      type: Object,
      computed: 'computeImage(state)',
    },

    value: {
      type: String,
      computed: 'computeValue(state)',
    },
  },

  listeners: {
    tap: 'badgeTap',
  },

  badgeTap(ev) {
    ev.stopPropagation();
    if (this.entityId) {
      this.async(() => window.hass.moreInfoActions.selectEntity(this.entityId), 1);
    }
  },

  computeState(entityId) {
    return entityId && window.hass.reactor.evaluate(window.hass.entityGetters.byId(entityId));
  },

  computeIcon(state) {
    return !state && 'home';
  },

  computeImage(state) {
    return state && state.attributes.entity_picture;
  },

  computeValue(state) {
    return state &&
      state.entityDisplay.split(' ').map(part => part.substr(0, 1)).join('');
  },
});

Add comment explaining weird map behavior


var browserify = require('browserify');
var gulp = require('gulp');
var source = require('vinyl-source-stream');
var buffer = require('vinyl-buffer');
var uglify = require('gulp-uglify');
var sourcemaps = require('gulp-sourcemaps');
var watchify = require('watchify');
var mocha = require('gulp-mocha');
var gutil = require('gulp-util');

var bundler = watchify(browserify({
    entries: ['./src/vl'],
    exclude: ['d3','topojson', 'lodash', '../lib/vega'],
    debug: true
  }));

function bundle() {
  return bundler
    .bundle()
    .pipe(source('vegalite.js'))
    .pipe(buffer())
      .pipe(sourcemaps.init({loadMaps: true}))
      // Add transformation tasks to the pipeline here.
      // .pipe(uglify())
    .pipe(sourcemaps.write('./'))
    .pipe(gulp.dest('.'));
}

gulp.task('mocha', function() {
    return gulp.src(['test/test.js'], { read: false })
        .pipe(mocha({ reporter: 'list' }))
        .on('error', gutil.log);
});

gulp.task('watch-mocha', function() {
    gulp.watch(['src/**', 'test/**'], ['mocha']);
});

bundler.on('update', bundle);

gulp.task('build', bundle);

gulp.task('default', ['build', 'watch-mocha']);

var browserify = require('browserify');
var gulp = require('gulp');
var source = require('vinyl-source-stream');
var buffer = require('vinyl-buffer');
var uglify = require('gulp-uglify');
var sourcemaps = require('gulp-sourcemaps');
var watchify = require('watchify');
var mocha = require('gulp-mocha');
var gutil = require('gulp-util');

var bundler = watchify(browserify({
    entries: ['./src/vl'],
    exclude: ['d3','topojson', 'lodash', '../lib/vega'],
    debug: true
  }));

function bundle() {
  return bundler
    .bundle()
    .pipe(source('vegalite.js'))
    .pipe(buffer())
      .pipe(sourcemaps.init({loadMaps: true}))
      // Add transformation tasks to the pipeline here.
      // .pipe(uglify())
    .pipe(sourcemaps.write('./'))
    .pipe(gulp.dest('.'));
}

gulp.task('mocha', function() {
    return gulp.src(['test/test.js'], { read: false })
        .pipe(mocha({ reporter: 'list' }))
        .on('error', gutil.log);
});

gulp.task('watch-mocha', function() {
    gulp.watch(['src/**', 'test/**'], ['mocha']);
});

bundler.on('update', bundle);

gulp.task('build', bundle);

gulp.task('default', ['build']); // TODO: add 'watch-mocha'

Remove tests form gulp until they got fixed


const gulp = require('gulp');
const mocha = require('gulp-mocha');

gulp.task('test', () =>
    gulp.src('./tests/test-*', {read: false})
        .pipe(mocha())
);

gulp.task('watch', () =>
    gulp.watch('src/**', ['test'])
);

const gulp = require('gulp');
const mocha = require('gulp-mocha');

gulp.task('test', () =>
    gulp.src('./tests/test-*', {read: false})
        .pipe(mocha())
);

gulp.task('watch', () =>
    gulp.watch(['src/**', 'tests/**'], ['test'])
);

Watch for changes in the tests directory


define(function(require) {

  var test = require('../../test')
  var $ = require('../../third-party').jQuery
  var count = 0

  // normal case
  require('./red.css')
  test.assert($('#red').width() === 200, '#red width should be 200')

  // async case
  test.assert($('#blue').width() !== 200, '#blue width should not be 200. The actual value is ' + $('#blue').width())
  require.async('./blue.css', function() {
    setTimeout(function() {
      test.assert($('#blue').width() === 200, '#blue width should be 200 now')
      done()
    }, 100)
  })

  // not-existed case
  // 注意：Opera 下不会触发回调
  require.async('./not-existed.css', function() {
    test.print('[PASS] 404 is ok')
    done()
  })

  // don't load multi times
  var linkCount = document.getElementsByTagName('link').length
  require.async('./red.css', function() {
    var currentLinkCount = document.getElementsByTagName('link').length
    test.assert(currentLinkCount === linkCount, 'do NOT load duplicate link')
    done()
  })

  var MAX = 3
  if (window.opera) MAX--

  function done() {
    if (++count === MAX) {
      test.done()
    }
  }

})

define(function(require) {

  var test = require('../../test')
  var $ = require('../../third-party').jQuery
  var count = 0

  // normal case
  require('./red.css')
  test.assert($('#red').width() === 200, '#red width should be 200')

  // async case
  test.assert($('#blue').width() !== 200, '#blue width should not be 200. The actual value is ' + $('#blue').width())
  require.async('./blue.css', function() {
    setTimeout(function() {
      test.assert($('#blue').width() === 200, '#blue width should be 200 now')
      done()
    }, 500) // 留够时间给 css 渲染
  })

  // not-existed case
  // 注意：Opera 下不会触发回调
  require.async('./not-existed.css', function() {
    test.print('[PASS] 404 is ok')
    done()
  })

  // don't load multi times
  var linkCount = document.getElementsByTagName('link').length
  require.async('./red.css', function() {
    var currentLinkCount = document.getElementsByTagName('link').length
    test.assert(currentLinkCount === linkCount, 'do NOT load duplicate link')
    done()
  })

  var MAX = 3
  if (window.opera) MAX--

  function done() {
    if (++count === MAX) {
      test.done()
    }
  }

})

Make load-css test more robust


import React from 'react';
import { Tab } from 'semantic-ui-react';
import Img from 'react-image-smooth-loading';

import genreToIcon from './mapGenres';
import styles from './styles.scss';

const bannedGenres = [
  'seen live'
];

class GenresTab extends React.Component {
  constructor(props) {
    super(props);
  }

  onGenreClick (genreName) {
    this.props.history.push('/tag/' + genreName);
  }

  render () {
    let {
      genres
    } = this.props;

    return (
      <Tab.Pane attached={false}>

          {
            typeof genres !== 'undefined'
              ? _.filter(genres, genre => !_.includes(bannedGenres, genre.name)).map((tag, i) => {
                return (

 this.onGenreClick(tag.name)}
                  >

                      { tag.name }

                );
              })
              : null
          }

      </Tab.Pane>
    );
  }
}

export default GenresTab;

Fix genre background image URI


'use strict';
const CommandUtil = require('../src/command_util').CommandUtil;
const l10nFile = __dirname + '/../l10n/commands/kill.yml';
const l10n = require('../src/l10n')(l10nFile);
const util = require('util');
exports.command = (rooms, items, players, npcs, Commands) => {
    return (args, player) => {
        const room = rooms.getAt(player.getLocation());
        const npc = CommandUtil.findNpcInRoom(npcs, args, room, player, true);

        if (!npc) {
            player.sayL10n(l10n, 'TARGET_NOT_FOUND');
            return;
        }
        if (npc.isPacifist()) {
            player.sayL10n(l10n, 'KILL_PACIFIST');
            return;
        }

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

        util.log(player.getName() + ' is on the offensive...');
        npc.emit('combat', player, room, players, npcs, rooms, cleanup);

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

'use strict';
const CommandUtil = require('../src/command_util').CommandUtil;
const l10nFile = __dirname + '/../l10n/commands/kill.yml';
const l10n = require('../src/l10n')(l10nFile);
const util = require('util');
exports.command = (rooms, items, players, npcs, Commands) => {
    return (args, player) => {
        const room = rooms.getAt(player.getLocation());
        const npc = CommandUtil.findNpcInRoom(npcs, args, room, player, true);

        if (!npc) {
            player.sayL10n(l10n, 'TARGET_NOT_FOUND');
            return;
        }
        if (npc.isPacifist()) {
            player.sayL10n(l10n, 'KILL_PACIFIST');
            return;
        }

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

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

        if (npc.isInCombat() === player) {
          player.say('You are already fighting them!');
          return;
        }
        if (npc.isInCombat()) {
          player.say('They are busy fighting someone else, no fair!');
          return;
        }
        npc.emit('combat', player, room, players, npcs, rooms, cleanup);

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

Add some checks to see if the npc is already fighting... for now.


var worker = new Worker('servo-worker.js');
var renderer;

worker.addEventListener("message", function(event) {
    var cmd = event.data[0];
    var data = event.data[1];

    switch(cmd) {
    case "log":
        console.log("WORKER", data);
        break;
    case "warn":
        console.warn("WORKER", data);
        break;
    case "error":
        console.error("WORKER", data);
        break;
    }
});

window.onload = function() {
    new TreeRenderer(worker, document.getElementById("n1"));
    renderer = new IFrameRenderer(worker, document.getElementById("renderframe"));

    var form = document.getElementById("url");
    form.onsubmit = function(e) {
        document.getElementById('n1').innerHTML = '';
        var url = form.url.value;
        renderer.setBaseHref(url);
        worker.postMessage(["load", url]);
        e.preventDefault();
    }
};

var worker = new Worker('servo-worker.js');
var renderer;

worker.addEventListener("message", function(event) {
    var cmd = event.data[0];
    var data = event.data[1];

    switch(cmd) {
    case "log":
        console.log("WORKER", data);
        break;
    case "warn":
        console.warn("WORKER", data);
        break;
    case "error":
        console.error("WORKER", data);
        break;
    }
});

window.onload = function() {
    new TreeRenderer(worker, document.getElementById("n1"));
    renderer = new IFrameRenderer(worker, document.getElementById("renderframe"));

    var form = document.getElementById("url");
    form.url.value = window.location.href.split('/').slice(0, -1).join('/') + '/foo.html';
    form.onsubmit = function(e) {
        document.getElementById('n1').innerHTML = '';
        var url = form.url.value;
        renderer.setBaseHref(url);
        worker.postMessage(["load", url]);

        e.preventDefault();
    }

    var create = document.getElementById('new-element');
    create.onsubmit = function(ev) {
        worker.postMessage(["create", create.name.value]);
        ev.preventDefault();
    }
};

Set the location bar to an absolute url at startup.


hexo.extend.tag.register('fa', function(args) {
    return '<i class="fa ' + args.map(function(arg) { return 'fa-' + arg; }).join(' ') + '"></i>';
});

hexo.extend.tag.register('fa', function(args) {
    return '';
});

hexo.extend.tag.register('fas', function(args) {
    return '';
});

hexo.extend.tag.register('fab', function(args) {
    return '';
});

hexo.extend.tag.register('far', function(args) {
    return '';
});

hexo.extend.tag.register('fal', function(args) {
    return '';
});

Add FA 5 prefixes to hexo extended tags


module.exports = {
  priority: 5,
  process: async container => {
    const { client, msg, isPrivate, data, db } = container
    try {
      if (isPrivate) {
        let channel = await client.getDMChannel(msg.author.id)
        container.settings = new db.Guild({ id: channel.id })
        return container
      }
      let settings = await data.Guild.fetch(msg.guild.id)
      if (!settings) {
        settings = new db.Guild({ id: msg.guild.id })
      }
      settings.deleted = false
      await settings.save()
      container.settings = settings
      return container
    } catch (err) {
      throw err
    }
  }
}

module.exports = {
  priority: 5,
  process: async container => {
    const { client, msg, isPrivate, data, db } = container
    try {
      if (isPrivate) {
        const channel = await client.getDMChannel(msg.author.id)
        container.settings = new db.Guild({ id: channel.id })
        return container
      }
      const settings = await data.Guild.fetch(msg.guild.id)
      settings.deleted = false
      await settings.save()
      container.settings = settings
      return container
    } catch (err) {
      throw err
    }
  }
}

Fix middleware instantiating new Guild entry


import { getDocument, updateDocument } from '../services/pouchdbService';
import { isEqual } from 'lodash';

import {
  NEW_POUCHDB,
  LOAD_EBUDGIE,
  INITIAL_LOAD,
} from '../constants/ActionTypes';

const storage = store => next => async action => {
  const prevState = store.getState();
  const result = next(action);
  const nextState = store.getState();

  const isNotLoadAction = action.type !== NEW_POUCHDB && action.type !== LOAD_EBUDGIE;
  const documentHasChanged = !isEqual(prevState.ebudgie, nextState.ebudgie);

  if (isNotLoadAction && documentHasChanged) {
    try {
      if (action.type === INITIAL_LOAD) {
        await updateDocument({
          ...nextState.ebudgie,
        });
      } else {
        const storedDocument = await getDocument(prevState.pouchdb.dbName);
        await updateDocument({
          ...nextState.ebudgie,
          _rev: storedDocument._rev,
        });
      }
    } catch (e) {

    }
  }

  return result;
};

export default storage;

import { getDocument, updateDocument } from '../services/pouchdbService';
import { isEqual } from 'lodash';

import {
  NEW_POUCHDB,
  LOAD_EBUDGIE,
  INITIAL_LOAD,
} from '../constants/ActionTypes';

const applyChanges = async (nextState, prevState) => {
  try {
    const storedDocument = await getDocument(prevState.pouchdb.dbName);
    await updateDocument({
      ...nextState.ebudgie,
      _rev: storedDocument._rev,
    });
  } catch (e) {
    if (e.status === 409) {
      await applyChanges(nextState, prevState);
    } else {
      throw e;
    }
  }
}

const storage = store => next => async action => {
  const prevState = store.getState();
  const result = next(action);
  const nextState = store.getState();

  const isNotLoadAction = action.type !== NEW_POUCHDB && action.type !== LOAD_EBUDGIE;
  const documentHasChanged = !isEqual(prevState.ebudgie, nextState.ebudgie);

  if (isNotLoadAction && documentHasChanged) {
    try {
      if (action.type === INITIAL_LOAD) {
        await updateDocument({
          ...nextState.ebudgie,
        });
      } else {
        await applyChanges(nextState, prevState);
      }
    } catch (e) {

    }
  }

  return result;
};

export default storage;

Fix pouchdb middleware to handle conflict and retry to update a document


var prefixToInfix = require("./tools/prefix-to-infix");

module.exports = {
    js: require("./functions/js"),
    def: require("./functions/def"),
    let: require("./functions/let"),
    "`": require("./functions/`"),
    try: require("./functions/try"),
    catch: require("./functions/catch"),
    ".-": require("./functions/.-"),
    ".": require("./functions/..js"),
    do: require("./functions/do"),
    if: require("./functions/if"),
    fn: require("./functions/fn"),
    "=": require("./functions/=")
};

function defPrefixToInfix(operand, maxArgs) {
    module.exports[operand] = prefixToInfix(operand, maxArgs);
}

["<", "+", "-", "*", "/"].forEach(function(symbol) {
    defPrefixToInfix(symbol);
});

var prefixToInfix = require("./tools/prefix-to-infix");

module.exports = {
    js: require("./functions/js"),
    def: require("./functions/def"),
    let: require("./functions/let"),
    "`": require("./functions/`"),
    try: require("./functions/try"),
    catch: require("./functions/catch"),
    ".-": require("./functions/.-"),
    ".": require("./functions/..js"),
    do: require("./functions/do"),
    if: require("./functions/if"),
    fn: require("./functions/fn"),
    "=": require("./functions/=")
};

function defPrefixToInfix(operand, maxArgs) {
    module.exports[operand] = prefixToInfix(operand, maxArgs);
}

["<", "+", "-", "*", "/", "%"].forEach(function(symbol) {
    defPrefixToInfix(symbol);
});

Add % operator for modulo division


$('#dripbot-title').css({
	"display": "inline-block",
	"margin-right": "20px"
});

$('#dripbot').css({
    "text-align": "left"
});

$('#dripbot-toggle.stop').css({
    "background-color": "#e9656d",
    "color": "white",
	"margin-top": "-10px"
});

$('#dripbot ul li p').css({
	"margin-bottom":"5px",
	"margin-right": "40px",
	"display": "inline-block"
});

$('img#dripbot-logo').css({
	"margin-bottom": "10px",
	"margin-right": "5px"
});

$('div#dripbot-update').css({
	"background-color": "#47a447",
	"padding": "10px"
});

$('div#dripbot-update h1').css({
	"font-weight": "800",
});

$('#save-button').css({
	"background-color": "#47a447",
	"color": "white",
	"margin-left": "20px"
})

$('#dripbot-title').css({
	"display": "inline-block",
	"margin-right": "20px"
});

$('#dripbot').css({
    "text-align": "left"
});

$('#dripbot-toggle.stop').css({
    "background-color": "#e9656d",
    "color": "white",
	"margin-top": "-10px"
});

$('#dripbot ul li p').css({
	"margin-bottom":"5px",
	"margin-right": "40px",
	"display": "inline-block"
});

$('img#dripbot-logo').css({
	"margin-bottom": "10px",
	"margin-right": "5px"
});

$('div#dripbot-update').css({
	"background-color": "#47a447",
	"padding": "10px"
});

$('div#dripbot-update h1').css({
	"font-weight": "800",
});

$('#save-game').css({
	"background-color": "#47a447",
	"color": "white",
	"margin-left": "20px"
})

Fix typo in button name.


Finance = {
  currencyOptions: {
    symbol: '€',
    negativeFormat: "-%s%n",
    digitGroupSymbol: '.',
    decimalSymbol: ','
  }
};

$(document).on("page:change", function(){
  var financeTable = $("table.finance-table");

  if(financeTable.length > 0){
    new FinanceTable(financeTable).create();
  }

  var businessCaseTable = $("#business_case_table");

  if(businessCaseTable.length > 0){

    $.ajax({
      url: businessCaseTable.data('url'),
      type: "GET",
      success: function(){
        businessCaseTable.find("table tbody td.editable span.static").
          formatCurrency(Finance.currencyOptions);
      }
    });
  };
});

Finance = {
  currencyOptions: {
    symbol: '€',
    negativeFormat: "-%s%n"
  }
};

$(document).on("page:change", function(){
  var financeTable = $("table.finance-table");

  if(financeTable.length > 0){
    new FinanceTable(financeTable).create();
  }

  var businessCaseTable = $("#business_case_table");

  if(businessCaseTable.length > 0){

    $.ajax({
      url: businessCaseTable.data('url'),
      type: "GET",
      success: function(){
        businessCaseTable.find("table tbody td.editable span.static").
          formatCurrency(Finance.currencyOptions);
      }
    });
  };
});

Use decimal point money formatting


;(function($, win, doc) {
  var $win = $(win);
  var $doc = $(doc);

  // Enable CSS active pseudo styles in Mobile Safari
  $doc.on({
    touchstart: function() {}
  });
})(jQuery, window, document);

Enable CSS active pesudo styles in Mobile Safari


const path = require('path');
var eslintFormatter = require('eslint-formatter-pretty');
const clientDir = '../src/client/';

module.exports = env => {
  return {
    entry: path.resolve(__dirname, clientDir, 'client.js'),
    output: {
      filename: 'bundle.js',
      path: path.resolve(__dirname, '../dist'),
      pathinfo: !env.prod,
    },
    context: path.resolve(__dirname, clientDir),
    devtool: env.prod ? 'source-map' : 'eval',
    bail: env.prod,
    module: {
      loaders: [
        { test: /\.(js|jsx)$/, loader: 'babel!eslint', exclude: /node_modules/ },
        { test: /\.css$/, loader: 'style!css' },
      ],
    },
    eslint: {
        formatter: eslintFormatter
    }
  };
};

const path = require('path');
const eslintFormatter = require('eslint-formatter-pretty');
const clientDir = '../src/client/';

module.exports = env => {
  return {
    entry: path.resolve(__dirname, clientDir, 'client.js'),
    output: {
      filename: 'bundle.js',
      path: path.resolve(__dirname, '../dist'),
      pathinfo: !env.prod,
    },
    context: path.resolve(__dirname, clientDir),
    devtool: env.prod ? 'source-map' : 'eval',
    bail: env.prod,
    module: {
      loaders: [
        { test: /\.(js|jsx)$/, loader: 'babel!eslint', exclude: /node_modules/ },
        { test: /\.css$/, loader: 'style!css' },
      ],
    },
    eslint: {
        formatter: eslintFormatter
    }
  };
};

Use const instead of var


'use strict';

/**
 * @ngdoc function
 * @name ausEnvApp.controller:SearchCtrl
 * @description
 * # SearchCtrl
 * Controller of the ausEnvApp
 */
angular.module('ausEnvApp')
  .controller('SearchCtrl', function ($scope,$filter,staticData,selection,spatialFoci) {
    $scope.selection = selection;
    $scope.loadingPolygons = false;
    staticData.unwrap($scope,'options',spatialFoci.regionTypes);

    $scope.$watch('selection.mapMode',function(newVal){
      if(newVal==='Grid') {
        selection.lastRegionType = selection.regionType;
        selection.regionType=null;
      } else {
        if(!selection.regionType) {
          selection.regionType = selection.lastRegionType;
        }
      }
    });

    $scope.regionTypeChanged = function(newOption) {
      if(!newOption){
        selection.mapMode = 'Grid';
        return;
      }
      selection.mapMode = 'Polygon';
      $scope.loadingPolygons = true;
      newOption.jsonData().then(function(data){
        selection.availableFeatures = data.features.map(function(f){
          return {
            name:f.properties[newOption.labelField],
            feature:f
          };
        });
        selection.availableFeatures.sort(function(a,b){return a.name.localeCompare(b.name);});
      });
    }; //regionTypeChanged

    $scope.canUseSearchText = function() {
      return ((!selection.isMapModeGrid()) && (selection.regionType !== null));
    };  //canUseSearchText
  });

'use strict';

/**
 * @ngdoc function
 * @name ausEnvApp.controller:SearchCtrl
 * @description
 * # SearchCtrl
 * Controller of the ausEnvApp
 */
angular.module('ausEnvApp')
  .controller('SearchCtrl', function ($scope,$filter,staticData,selection,spatialFoci) {
    $scope.selection = selection;
    $scope.loadingPolygons = false;
    staticData.unwrap($scope,'options',spatialFoci.regionTypes);

    $scope.$watch('selection.mapMode',function(newVal){
      if(newVal==='Grid') {
        selection.lastRegionType = selection.regionType;
        selection.regionType=null;
      } else {
        if(!selection.regionType) {
          selection.regionType = selection.lastRegionType;
        }
      }
    });

    $scope.regionTypeChanged = function(newOption) {
      if(!newOption){
        selection.mapMode = 'Grid';
        return;
      }
      selection.mapMode = 'Polygon';
      $scope.loadingPolygons = true;
      newOption.jsonData().then(function(data){
        selection.availableFeatures = data.features.map(function(f){
          return {
            name:f.properties[newOption.labelField],
            feature:f
          };
        });
        selection.availableFeatures.sort(function(a,b){return a.name.localeCompare(b.name);});
        $scope.loadingPolygons = false;
      });
    }; //regionTypeChanged

    $scope.canUseSearchText = function() {
      return ((!selection.isMapModeGrid()) && (selection.regionType !== null));
    };  //canUseSearchText
  });

Fix for the never disappearing loading icon


var path = require('path');
var webpack = require('webpack');
var Clean = require('clean-webpack-plugin');

var plugins = [
  new Clean(['dist'])
];

module.exports = {
  entry: path.resolve(__dirname, 'src/index.js'),
  plugins: plugins,
  externals: {
    clappr: 'Clappr',
    "clappr-zepto": "clappr-zepto"
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        loader: 'babel',
        query: {
            compact: true,
        }
      },
      {
        test: /\.sass$/,
        loaders: ['css', 'sass?includePaths[]=' + path.resolve(__dirname, "./node_modules/compass-mixins/lib")],
      },
      {
        test: /\.html/, loader: 'html?minimize=false'
      },
    ],
  },
  resolve: {
    extensions: ['', '.js'],
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    publicPath: '<%=baseUrl%>/',
    filename: 'clappr-thumbnails-plugin.js',
    library: 'ClapprThumbnailsPlugin',
    libraryTarget: 'umd',
  },
};

var path = require('path');
var webpack = require('webpack');
var Clean = require('clean-webpack-plugin');

var plugins = [
  new Clean(['dist'])
];

module.exports = {
  entry: path.resolve(__dirname, 'src/index.js'),
  plugins: plugins,
  externals: {
    clappr: 'Clappr'
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        loader: 'babel',
        query: {
            compact: true,
        }
      },
      {
        test: /\.sass$/,
        loaders: ['css', 'sass?includePaths[]=' + path.resolve(__dirname, "./node_modules/compass-mixins/lib")],
      },
      {
        test: /\.html/, loader: 'html?minimize=false'
      },
    ],
  },
  resolve: {
    extensions: ['', '.js'],
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'clappr-thumbnails-plugin.js',
    library: 'ClapprThumbnailsPlugin',
    libraryTarget: 'umd',
  },
};

Remove "clappr-zepto" external and "publlicPath" from webpack conf


'use strict';

var app = require('../../util/app');
var viewLocals = require('../../middleware/view-locals');
var siteNav = require('../../middleware/site-navigation');
var battlegroundData = require('../data/').battlegroundData;
var forecastData = require('../data/').forecastData;
var parties = require('uk-political-parties');
var debug = require('debug')('projections-index');
var filters = require('../../util/filters');

function* home(next) {
  var battlegrounds = yield battlegroundData();
  var forecast = yield forecastData('seats');
  var updated = forecast.updated;

  debug(forecast);

  forecast = forecast.data.map(function(d){
    d.Party = parties.electionForecastToCode(d.Party);
    return d;
  });

  yield this.render('projections-index', { // jshint ignore:line
    page: {
      title: 'The 4 key UK general election battles',
      summary: 'Four different types of local contest will shape the most uncertain UK general election in memory',
      dateModified: updated instanceof Date ? 'Last updated ' + filters.ftdate(updated) : ''
    },
    groups: battlegrounds,
    overview: forecast
  });
  yield next;
}

function main() {
  return app()

        .use(siteNav())

        .use(viewLocals())

        .router()

        // forecast home page
        .get('home', '/', home);
}

module.exports = main;
if (!module.parent) main().listen(process.env.PORT || 5000);

'use strict';

var app = require('../../util/app');
var viewLocals = require('../../middleware/view-locals');
var siteNav = require('../../middleware/site-navigation');
var battlegroundData = require('../data/').battlegroundData;
var forecastData = require('../data/').forecastData;
var parties = require('uk-political-parties');
var debug = require('debug')('projections-index');
var filters = require('../../util/filters');

function* home(next) {
  var battlegrounds = yield battlegroundData();
  var forecast = yield forecastData('seats');
  var updated = forecast.updated;

  debug(forecast);

  forecast = forecast.data.map(function(d){
    d.Party = parties.electionForecastToCode(d.Party);
    return d;
  });

  yield this.render('projections-index', { // jshint ignore:line
    page: {
      title: 'The 4 key UK general election battles',
      summary: 'Four different types of local contest will shape the most uncertain UK general election in memory',
      dateModified: updated instanceof Date ? 'Updated daily. Last updated ' + filters.ftdate(updated) : ''
    },
    groups: battlegrounds,
    overview: forecast
  });
  yield next;
}

function main() {
  return app()

        .use(siteNav())

        .use(viewLocals())

        .router()

        // forecast home page
        .get('home', '/', home);
}

module.exports = main;
if (!module.parent) main().listen(process.env.PORT || 5000);

Revert to "Updated daily" msg.


#!/usr/bin/env node

const cli = require('../lib/cli');
cli.execute(process.argv, (run) => {
  if (!run || run.reportedTests().length) {
    process.exit(1);
  } else {
    process.exit(0);
  }
});

#!/usr/bin/env node

const cli = require('../lib/cli');
cli.execute(process.argv, (run) => {
  if (run || run.reportedTests().length < 1) {
    process.exit(0);
  } else {
    process.exit(1);
  }
});

Fix error when using -h


import { EditorState } from "draft-js";

const changeCurrentBlockType = (
  editorState,
  type,
  text,
  blockMetadata = {}
) => {
  const currentContent = editorState.getCurrentContent();
  const selection = editorState.getSelection();
  const key = selection.getStartKey();
  const blockMap = currentContent.getBlockMap();
  const block = blockMap.get(key);
  const data = block.getData().merge(blockMetadata);
  const newBlock = block.merge({ type, data, text: text || "" });
  const newSelection = selection.merge({
    anchorOffset: 0,
    focusOffset: 0,
  });
  const newContentState = currentContent.merge({
    blockMap: blockMap.set(key, newBlock),
    selectionAfter: newSelection,
  });
  return EditorState.push(editorState, newContentState, "change-block-type");
};

export default changeCurrentBlockType;

import { EditorState } from "draft-js";

const changeCurrentBlockType = (
  editorState,
  type,
  text,
  blockMetadata = {}
) => {
  const currentContent = editorState.getCurrentContent();
  const selection = editorState.getSelection();
  const key = selection.getStartKey();
  const blockMap = currentContent.getBlockMap();
  const block = blockMap.get(key);
  const data = block.getData().merge(blockMetadata);
  const newBlock = block.merge({ type, data, text: text });
  const newContentState = currentContent.merge({
    blockMap: blockMap.set(key, newBlock),
    selectionAfter: selection,
  });
  return EditorState.push(editorState, newContentState, "change-block-type");
};

export default changeCurrentBlockType;

Fix changing the current block type


// Register 'usergroups' special setting values fetcher.
// Allows to use `values: usergroups` in setting definitions.

'use strict';

var _ = require('lodash');

module.exports = function (N) {
  function usergroups(env, callback) {
    N.models.users.UserGroup
        .find()
        .select('_id short_name')
        .sort('_id')
        .setOptions({ lean: true })
        .exec(function (err, groups) {

      callback(err, _.map(groups, function (group) {
        var translation = 'admin.users.usergroup_names.' + group.short_name;

        return {
          name:  group.short_name
        , value: group._id
        , title: env && env.helpers.t.exists(translation) ?
                 env.helpers.t(translation)        :
                 group.short_name
        };
      }));
    });
  }

  N.wire.before('init:settings', function (extensions) {
    extensions.values['usergroups'] = usergroups;
  });
};

// Register 'usergroups' special setting values fetcher.
// Allows to use `values: usergroups` in setting definitions.

'use strict';

var _ = require('lodash');

module.exports = function (N) {
  function usergroups(env, callback) {
    N.models.users.UserGroup
        .find()
        .select('_id short_name')
        .sort('_id')
        .setOptions({ lean: true })
        .exec(function (err, groups) {

      callback(err, _.map(groups, function (group) {
        var translation = 'admin.users.usergroup_names.' + group.short_name;

        return {
          name:  group.short_name
        , value: group._id
        , title: env && env.helpers.t.exists(translation) ?
                 env.helpers.t(translation)        :
                 group.short_name
        };
      }));
    });
  }

  N.wire.before('init:settings', function (sandbox) {
    sandbox.selectionListFetchers['usergroups'] = usergroups;
  });
};

Rename settings' `extensions.values` to `sandbox.selectionListFetchers`.


/* ==========================================================================
   Assign

   Code copied from the following with moderate modifications :

   - https://github.com/maslennikov/shallow-extend
     Copyright (c) 2014 Alexey Maslennikov
   ========================================================================== */

'use strict';

/**
* @param {Object} object - JavaScript object.
* @returns {boolean} True if object is plain Javascript object.
*/
function _isPlainObject( object ) {
  return Object.prototype.toString.call( object ) === '[object Object]';
}

/**
* Copies properties of all sources to the destination object overriding its own
* existing properties. When assigning from multiple sources, fields of every
* next source will override same named fields of previous sources.
*
* @param {Object} destination object.
* @returns {Object} assigned destination object.
*/
function assign( destination ) {
  destination = destination || {};

  for ( var i = 1; i < arguments.length; i++ ) {
    var source = arguments[i] || {};
    for ( var key in source ) {
      if ( source.hasOwnProperty( key ) ) {
        var value = source[key];
        if ( _isPlainObject( value ) ) {
          assign( destination[key] = {}, value );
        } else {
          destination[key] = source[key];
        }
      }
    }
  }
  return destination;
}

// Expose public methods.
module.exports = { assign: assign };

/* ==========================================================================
   Assign

   Code copied from the following with moderate modifications :

   - https://github.com/maslennikov/shallow-extend
     Copyright (c) 2014 Alexey Maslennikov
   ========================================================================== */

'use strict';

/**
* @param {Object} object - JavaScript object.
* @returns {boolean} True if object is plain Javascript object.
*/
function _isPlainObject( object ) {
  return Object.prototype.toString.call( object ) === '[object Object]';
}

/**
* Copies properties of all sources to the destination object overriding its own
* existing properties. When assigning from multiple sources, fields of every
* next source will override same named fields of previous sources.
*
* @param {Object} destination object.
* @returns {Object} assigned destination object.
*/
function assign( destination ) {
  destination = destination || {};
  var hasOwnProp;
  for ( var i = 1; i < arguments.length; i++ ) {
    var source = arguments[i] || {};
    hasOwnProp = Object.hasOwnProperty.bind( source );
    for ( var key in source ) {
      if ( hasOwnProp( key ) ) {
        var value = source[key];
        if ( _isPlainObject( value ) ) {
          assign( destination[key] = {}, value );
        } else {
          destination[key] = source[key];
        }
      }
    }
  }
  return destination;
}

// Expose public methods.
module.exports = { assign: assign };

Fix for the burea page on Android 4.1


'use babel';

import React from 'react';
import TodoItem from './TodoItem';

const TodoListBackgroundMessage = () => (
  <background-tips>

        Newly added todos will be listed here

  </background-tips>
);

const TodoList = ({ todos, setTodoStatus, deleteTodo, onTitleClick }) => {
  return (

      {
        todos.map(todo => (
          <TodoItem
            key={todo._id}
            todo={todo}
            setTodoStatus={setTodoStatus}
            deleteTodo={deleteTodo}
            onTitleClick={onTitleClick}
          />
        ))
      }
      { (!todos || todos.length==0) && <TodoListBackgroundMessage /> }

  );
};

export default TodoList;

'use babel';

import React from 'react';
import TodoItem from './TodoItem';

const TodoListBackgroundMessage = () => (
  <background-tips>

        Newly added todos will be listed here

  </background-tips>
);

const TodoList = ({ todos, setTodoStatus, deleteTodo, onTitleClick }) => {
  return (

      {
        _.sortBy(todos, ['createdAt']).map(todo => (
          <TodoItem
            key={todo._id}
            todo={todo}
            setTodoStatus={setTodoStatus}
            deleteTodo={deleteTodo}
            onTitleClick={onTitleClick}
          />
        ))
      }
      { (!todos || todos.length==0) && <TodoListBackgroundMessage /> }

  );
};

export default TodoList;

Sort todos by creation time


// const usersController = require('../controllers/users');
import users from '../controllers/users';
import books from '../controllers/books';

module.exports = (app) => {
  app.get('/api', (req, res) => res.status(200).send({
    message: 'Welcome to Hello-books API',
  }));

  app.post('/api/users', users.createUser);
  app.post('/api/books', books.createBook);
};

Update route for user signup


import usersControllers from '../controllers';
import loginControllers from '../controllers';

const usersController = usersControllers.users;
const loginController = loginControllers.login;

module.exports = (app) => {
  app.get('/api', (req, res) => res.status(200).send({
    message: 'Welcome to the Todos API!',
  }));

  app.post('/api/user/signin', loginController.login);

  app.post('/api/user/signup', usersController.create);

  // An API route that allow users create broadcast groups:
  // POST: /api/group
  app.post('/api/group', (req, res) => res.send('Create a group'));

  // An API route that allow users add other users to groups:
  app.post('/api/group/:groupid/user', (req, res) => res.send(`Added new user to ${req.params.groupid}.`));

  // An API route that allows a logged in user post messages to created groups:
  app.post('/api/group/:groupid/message', (req, res) => res.send(`Thanks for posting ur message to ${req.params.groupid}`));

  // An API route that allows a logged in user retrieve messages that have been
  // posted to groups he/she belongs to:
  app.get('/api/group/:groupid/messages', (req, res) => res.send(`Welcome! here are the messages for ${req.params.groupid}`));

  // Root route
  app.get('*', (req, res) => res.send('Sorry, the page u requested does not exist'));
};

import authControllers from '../controllers';

const usersController = authControllers.users;
const loginController = authControllers.login;

module.exports = (app) => {
  app.get('/api', (req, res) => res.status(200).send({
    message: 'Welcome to the Todos API!',
  }));

  app.post('/api/user/signin', loginController.login);

  app.post('/api/user/signup', usersController.create);

  // An API route that allow users create broadcast groups:
  // POST: /api/group
  app.post('/api/group', (req, res) => res.send('Create a group'));

  // An API route that allow users add other users to groups:
  app.post('/api/group/:groupid/user', (req, res) => res.send(`Added new user to ${req.params.groupid}.`));

  // An API route that allows a logged in user post messages to created groups:
  app.post('/api/group/:groupid/message', (req, res) => res.send(`Thanks for posting ur message to ${req.params.groupid}`));

  // An API route that allows a logged in user retrieve messages that have been
  // posted to groups he/she belongs to:
  app.get('/api/group/:groupid/messages', (req, res) => res.send(`Welcome! here are the messages for ${req.params.groupid}`));

  // Root route
  app.get('*', (req, res) => res.send('Sorry, the page u requested does not exist'));
};

Refactor code using es6 syntax


import authControllers from '../controllers';

const usersController = authControllers.users;
const loginController = authControllers.login;
const groupsController = authControllers.group;
const messagesController = authControllers.messages;

module.exports = (app) => {
  app.get('/api', (req, res) => res.status(200).send({
    message: 'Welcome to the Todos API!',
  }));

  app.post('/api/user/signin', loginController.login);

  app.post('/api/user/signup', usersController.create);

  // An API route that allow users create broadcast groups:
  // POST: /api/group
  app.post('/api/group', groupsController.create);

  // An API route that allow users add other users to groups:
  app.post('/api/group/:groupid/user', (req, res) => res.send(`Added new user to ${req.params.groupid}.`));

  // An API route that allows a logged in user post messages to created groups:
  app.post('/api/group/:groupid/message', messagesController.create);

  // An API route that allows a logged in user retrieve messages that have been
  // posted to groups he/she belongs to:
  app.get('/api/group/:groupid/messages', (req, res) => res.send(`Welcome! here are the messages for ${req.params.groupid}`));

  // Root route
  app.get('*', (req, res) => res.send('Sorry, the page u requested does not exist'));
};

import authControllers from '../controllers';

const usersController = authControllers.users;
const loginController = authControllers.login;
const groupsController = authControllers.group;
const messagesController = authControllers.messages;

module.exports = (app) => {
  app.get('/api', (req, res) => res.status(200).send({
    message: 'Welcome to the Todos API!',
  }));

  app.post('/api/user/signin', loginController.login);

  app.post('/api/user/signup', usersController.create);

  // An API route that allow users create broadcast groups:
  // POST: /api/group
  app.post('/api/group', groupsController.create);

  // An API route that allow users add other users to groups:
  app.post('/api/group/:groupid/user', (req, res) => res.send(`Added new user to ${req.params.groupid}.`));

  // An API route that allows a logged in user post messages to created groups:
  app.post('/api/group/:groupid/message', messagesController.create);

  // An API route that allows a logged in user retrieve messages that have been
  // posted to groups he/she belongs to:
  app.get('/api/group/:groupid/messages', groupsController.list);

  // Root route
  app.get('*', (req, res) => res.send('Sorry, the page u requested does not exist'));
};

Add route function to retrieve group messages


/**
 * Module dependencies.
 */
var utils = require('./utils');

/**
 * `Entry` constructor.
 *
 * An entry is conceptually a reverse route, used to map from a controller
 * action to the URL pattern which dispatches to it.
 *
 * @api private
 */
function Entry(controller, action, pattern) {
  this.controller = controller;
  this.action = action;
  this.pattern = pattern;
  utils.pathRegexp(pattern, this.keys = []);
};

/**
 * Build path.
 *
 * Builds a path for the entry, substituting any placeholders with the
 * corresponding value from `options`.
 *
 * @param {Object} options
 * @return {String}
 * @api protected
 */
Entry.prototype.path = function(options) {
  options = options || {};

  var self = this
    , path = this.pattern;
  this.keys.forEach(function(key) {
    if (!key.optional) {
      if (!options[key.name]) { throw new Error('Unable to substitute value for ":' + key.name + '" in URL pattern "' + self.pattern + '"'); }
      path = path.replace(':' + key.name, options[key.name]);
    } else {
      var replacement = options[key.name] ? '$1' + options[key.name] : '';
      path = path.replace(new RegExp('(\\.?\\/?):' + key.name + '\\?'), replacement);
    }
  });

  return path;
}

/**
 * Expose `Entry`.
 */
module.exports = Entry;

/**
 * Module dependencies.
 */
var utils = require('./utils');

/**
 * `Entry` constructor.
 *
 * An entry is conceptually a reverse route, used to map from a controller
 * action to the URL pattern which dispatches to it.
 *
 * @api private
 */
function Entry(controller, action, pattern) {
  this.controller = controller;
  this.action = action;
  this.pattern = pattern;
  utils.pathRegexp(pattern, this.keys = []);
};

/**
 * Build path.
 *
 * Builds a path for the entry, substituting any placeholders with the
 * corresponding value from `options`.
 *
 * @param {Object} options
 * @return {String}
 * @api protected
 */
Entry.prototype.path = function(options) {
  options = options || {};

  var self = this
    , path = this.pattern;
  this.keys.forEach(function(key) {
    if (!key.optional) {
      if (!options[key.name] && options[key.name] !== 0) { throw new Error('Unable to substitute value for ":' + key.name + '" in URL pattern "' + self.pattern + '"'); }
      path = path.replace(':' + key.name, options[key.name]);
    } else {
      var replacement = options[key.name] ? '$1' + options[key.name] : '';
      path = path.replace(new RegExp('(\\.?\\/?):' + key.name + '\\?'), replacement);
    }
  });

  return path;
}

/**
 * Expose `Entry`.
 */
module.exports = Entry;

Fix to support path substitution with 0 value.


// Shim to avoid requiring Velocity in Node environments, since it
// requires window. Note that this just no-ops the components so
// that they'll render, rather than doing something clever like
// statically rendering the end state of any provided animations.
//
// TODO(finneganh): Circle back on jsdom to see if we can run full
// Velocity with it in place. This come up in:
// https://github.com/twitter-fabric/velocity-react/issues/119
// but there may have been different loading issues in that case,
// not a global incompatibility with jsdom.
if (typeof window === 'undefined' || navigator.userAgent.includes("Node.js") || navigator.userAgent.includes("jsdom")) {

  var Velocity = function() {};
  Velocity.Utilities = {};
  Velocity.Utilities.removeData = function() {};
  Velocity.velocityReactServerShim = true;
  module.exports = Velocity;

} else {
  // this is how velocity-ui finds the Velocity instance, so lets make sure we find the right instance
  var g = (window.jQuery || window.Zepto || window);

  // require Velocity if it doesn't already exist
  module.exports = g.Velocity ? g.Velocity : require('velocity-animate');

}

// Shim to avoid requiring Velocity in Node environments, since it
// requires window. Note that this just no-ops the components so
// that they'll render, rather than doing something clever like
// statically rendering the end state of any provided animations.
//
// TODO(finneganh): Circle back on jsdom to see if we can run full
// Velocity with it in place. This come up in:
// https://github.com/twitter-fabric/velocity-react/issues/119
// but there may have been different loading issues in that case,
// not a global incompatibility with jsdom.
if (typeof window === 'undefined' || navigator.userAgent.indexOf("Node.js") !== -1 || navigator.userAgent.indexOf("jsdom") !== -1) {

  var Velocity = function() {};
  Velocity.Utilities = {};
  Velocity.Utilities.removeData = function() {};
  Velocity.velocityReactServerShim = true;
  module.exports = Velocity;

} else {
  // this is how velocity-ui finds the Velocity instance, so lets make sure we find the right instance
  var g = (window.jQuery || window.Zepto || window);

  // require Velocity if it doesn't already exist
  module.exports = g.Velocity ? g.Velocity : require('velocity-animate');

}

Switch to use String.prototype.indexOf instead of String.prototype.includes to support IE


/* global BackboneWizard, Backbone, $ */

window.BackboneWizard = {
    Models: {},
    Collections: {},
    Views: {},
    Controllers: {},
    Routers: {},
    init: function () {
        'use strict';

        var self = this;

        self.itemList = new BackboneWizard.Collections.ItemList();
        self.transaction = new BackboneWizard.Models.Transaction();
        self.state = new BackboneWizard.Models.State({ 'current': 'index' });

        $.get('./appData.json').done(function (data) {
            self.transaction.set(data);
        });

        $.get('./itemData.json').done(function (data) {
            self.itemList.add(data, { merge: true });
        });

        self.wizardRouter = new BackboneWizard.Routers.WizardRouter();

        Backbone.history.start();
    }
};

$(document).ready(function () {
    'use strict';
    BackboneWizard.init();
});

/* global BackboneWizard, Backbone, $ */

window.BackboneWizard = {
    Models: {},
    Collections: {},
    Views: {},
    Controllers: {},
    Routers: {},
    init: function () {
        'use strict';

        var self = this;

        self.itemList = new BackboneWizard.Collections.ItemList();
        self.transaction = new BackboneWizard.Models.Transaction();
        self.state = new BackboneWizard.Models.State({ 'current': 'index' });

        $.get('./appData.json').done(function (data) {
            self.transaction.set(data);
        });

        $.get('./itemData.json').done(function (data) {
            self.itemList.add(data, { merge: true });
        });

        self.wizardRouter = new BackboneWizard.Routers.WizardRouter();

        Backbone.history.start({ root: '/backbone/' });
    }
};

$(document).ready(function () {
    'use strict';
    BackboneWizard.init();
});

Set root option on history.start.


'use strict';

describe('faSurface', function() {
  var element, $compile, $scope, $famous;

  beforeEach(module('famous.angular'));

  beforeEach(inject(function(_$compile_, _$rootScope_, _$famous_) {
    $compile = _$compile_;
    $scope = _$rootScope_.$new();
    $famous = _$famous_;
    element = $compile('

')($scope);
  }));

  describe('should accept attribute', function() {

    it('fa-background-color', function() {

    });

    it('fa-color', function() {

    });

    it('fa-origin', function() {

    });

    it('fa-translate', function() {

    });

    it('fa-rotate-z', function() {

    });

    it('fa-skew', function() {

    });

    it('fa-size', function() {

    });

    it('fa-class', function() {

    });
  });

});

'use strict';

describe('faSurface', function() {
  var element, $compile, $scope, $famous;
  var compileFaSurface, getSurfaceProperties;

  beforeEach(module('famous.angular'));

  beforeEach(inject(function(_$compile_, _$rootScope_, _$famous_) {
    $compile = _$compile_;
    $scope = _$rootScope_.$new();
    $famous = _$famous_;

    compileFaSurface = function(attr) {
      return $compile('<fa-surface ' + attr + '></fa-surface>')($scope);
    };

    // faSurface must be an angular element so that .scope() can be called on it
    getSurfaceProperties = function(faSurface) {
      var scope = faSurface.scope();
      var surface = scope.isolate[scope.$id].renderNode;
      return surface.getProperties();
    };
  }));

  ddescribe('should accept attribute', function() {

    it("fa-background-color to set the surface's color", function() {
    // Have to escape the third level of quotes for string literals
      var faSurface = compileFaSurface("fa-background-color='\"#97DED\"'");
      var properties = getSurfaceProperties(faSurface);
      expect(properties.backgroundColor).toBeDefined();
      expect(properties.backgroundColor).toEqual("#97DED");
    });

    it('fa-color', function() {

    });

    it('fa-origin', function() {

    });

    it('fa-translate', function() {

    });

    it('fa-rotate-z', function() {

    });

    it('fa-skew', function() {

    });

    it('fa-size', function() {

    });

    it('fa-class', function() {

    });
  });

});

Set up background-color test for faSurface.


;(function()
{
	// CommonJS
	typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null;

	function Brush()
	{
		this.regexList = [
			{ regex: /^\+\+\+ .*$/gm,	css: 'color2' },	// new file
			{ regex: /^\-\-\- .*$/gm,	css: 'color2' },	// old file
			{ regex: /^\s.*$/gm,			css: 'color1' },
			{ regex: /^@@.*@@.*$/gm,	css: 'variable' },	// location
			{ regex: /^\+.*$/gm,		css: 'comments' },	// additions
			{ regex: /^\-.*$/gm,		css: 'color3' }		// deletions
			];
	};

	Brush.prototype	= new SyntaxHighlighter.Highlighter();
	Brush.aliases	= ['diff', 'patch'];

	SyntaxHighlighter.brushes.Diff = Brush;

	// CommonJS
	typeof(exports) != 'undefined' ? exports.Brush = Brush : null;
})();

;(function()
{
	// CommonJS
	typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null;

	function Brush()
	{
		this.regexList = [
			{ regex: /^\+\+\+ .*$/gm,	css: 'color2' },	// new file
			{ regex: /^\-\-\- .*$/gm,	css: 'color2' },	// old file
			{ regex: /^\s.*$/gm,		css: 'color1' },	// unchanged
			{ regex: /^@@.*@@.*$/gm,	css: 'variable' },	// location
			{ regex: /^\+.*$/gm,		css: 'comments' },	// additions
			{ regex: /^\-.*$/gm,		css: 'color3' }		// deletions
			];
	};

	Brush.prototype	= new SyntaxHighlighter.Highlighter();
	Brush.aliases	= ['diff', 'patch'];

	SyntaxHighlighter.brushes.Diff = Brush;

	// CommonJS
	typeof(exports) != 'undefined' ? exports.Brush = Brush : null;
})();

Comment on 'unchanged' line for consistency


var insert = require('./insert')

insert('find cursor options', [{
  hello: 'world1'
}, {
  hello: 'world2'
}], function (db, t, done) {
  var cursor = db.a.find().limit(1).skip(1)
  var runs = 0

  cursor.next(function loop (err, doc) {
    if (!doc) {
      t.equal(runs, 1)
      done()
      return
    }
    t.error(err)
    t.equal(doc.hello, 'world2')
    t.equal(typeof doc, 'object')
    runs++
    cursor.next(loop)
  })
})

var insert = require('./insert')

insert('find cursor options', [{
  hello: 'world1'
}, {
  hello: 'world2'
}], function (db, t, done) {
  var cursor = db.a.find().sort({_id: 1}).limit(1).skip(1)
  var runs = 0

  cursor.next(function loop (err, doc) {
    if (!doc) {
      t.equal(runs, 1)
      done()
      return
    }
    t.error(err)
    t.equal(doc.hello, 'world2')
    t.equal(typeof doc, 'object')
    runs++
    cursor.next(loop)
  })
})

Use cursor sort order to avoid tests fail due to sort order


'use strict'

const sanitize = require('sanitize-html')

exports.html = html
exports.duration = duration

const allowedTags = [
  'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol', 'li',
  'b', 'i', 'strong', 'em', 'code', 'br', 'div', 'pre'
]

function html (str) {
  if (typeof str !== 'string') return null
  return sanitize(str, {
    allowedTags: allowedTags
  })
}

function duration (str) {
  if (typeof str !== 'string' || str === '') return null

  const tokens = str.split(':')
  const l = tokens.length

  let multipliers = (() => {
    if (l === 1) return [1]
    if (l === 2) return [60, 1]
    if (l > 2) return [3600, 60, 1]
  })()

  let s = tokens.reduce((acc, token, i) => {
    if (i > 2) return acc
    let m = multipliers[i]
    return acc + parseInt(token, 10) * m
  }, 0)

  return isNaN(s) || s === 0 ? null : s
}

'use strict'

const sanitize = require('sanitize-html')

exports.html = html
exports.duration = duration

const allowedTags = [
  'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol', 'li',
  'b', 'i', 'strong', 'em', 'code', 'br', 'div', 'pre'
]

function html (str) {
  if (typeof str !== 'string') return null
  return sanitize(str, {
    allowedTags: allowedTags
  })
}

function duration (str) {
  if (typeof str !== 'string' || str === '') return null

  const tokens = str.split(':').slice(0, 3)
  const m = Math.pow(60, tokens.length - 1)

  const [s] = tokens.reduce((acc, token) => {
    let [s, m] = acc
    return [s + token * m, m / 60]
  }, [0, m])

  return isNaN(s) || s === 0 ? null : s
}

Use Math to transform duration to seconds


module.exports = {
  toggleMenu: function (context) {
    context.dispatch('TOGGLE_LAYOUT_MENU')
  },
  openMenu: function (context) {
    context.dispatch('OPEN_LAYOUT_MENU')
  },
  closeMenu: function (context) {
    context.dispatch('CLOSE_LAYOUT_MENU')
  }
}

module.exports = {
  sidebar: {
    toggle: function (context) {
      context.dispatch('TOGGLE_SIDEBAR')
    },
    open: function (context) {
      context.dispatch('OPEN_SIDEBAR')
    },
    close: function (context) {
      context.dispatch('CLOSE_SIDEBAR')
    }
  }
}

Move sidebar layout actions to sidebar object


'use strict';

module.exports = function(config, ircbot) {
	ircbot.addListener('raw', function(message) {
		switch (message.command) {
			case 'rpl_whoisidle':
				ircbot._addWhoisData(message.args[1], 'signon', new Date(parseInt(message.args[3]) * 1000));
				break;
			case '338':
				ircbot._addWhoisData(message.args[1], 'actual_host', message.args[2]);
				break;
			case '671':
				ircbot._addWhoisData(message.args[1], 'secure_connection', true);
				break;
		}
	});

	ircbot.constructor.prototype.remoteWhois = function(nick, callback) {
		if (typeof callback === 'function') {
			var callbackWrapper = function(info) {
				if (info.nick.toLowerCase() == nick.toLowerCase()) {
					this.removeListener('whois', callbackWrapper);
					return callback.apply(this, arguments);
				}
			};
			this.addListener('whois', callbackWrapper);
		}
		this.send('WHOIS', nick, nick);
	};
};

'use strict';

module.exports = function(config, ircbot) {
	ircbot.addListener('raw', function(message) {
		switch (message.command) {
			case 'rpl_whoisidle':
				ircbot._addWhoisData(message.args[1], 'signon', new Date(parseInt(message.args[3]) * 1000));
				break;
			case '338':
				ircbot._addWhoisData(message.args[1], 'actual_host', message.args[2]);
				break;
			case '671':
				ircbot._addWhoisData(message.args[1], 'secure_connection', true);
				break;
		}
	});

	ircbot.constructor.prototype.remoteWhois = function(nick, callback) {
		if (typeof callback === 'function') {
			var callbackWrapper = function(info) {
				let whoisNick = '';

				try {
					whoisNick = info.nick.toLowerCase();
				}
				catch (e) {}

				if (whoisNick == nick.toLowerCase()) {
					this.removeListener('whois', callbackWrapper);
					return callback.apply(this, arguments);
				}
			};
			this.addListener('whois', callbackWrapper);
		}
		this.send('WHOIS', nick, nick);
	};
};

Make sure info.nick is always set


#!/usr/bin/env node
(function() {
  'use strict';

  var program = require('commander');
  var packageInfo = require('../package.json');
  var openurl = require('openurl');

  program
    .usage('[options]')
    .description('Radiian creates Ansible provisioning files for immutable infrastructure on AWS.\n' +
    '  Run `radiian init` to begin.')
    .version(packageInfo.version)
    .option('-o, --online-help', 'Opens online help');

  program
    .command('init', 'initiate/start radiian')
    .action(function(input) {
      if(input !== 'init') {
        console.log('\nerror: unknown option `' + input + '`\n');
      }
    });

  program.parse(process.argv);

  // If no arguments are passed, display help menu
  if (!process.argv.slice(2).length) { program.help(); }

  if (program.onlineHelp) {
    openurl.open('https://github.com/radify/radiian#readme');
  }

}());

#!/usr/bin/env node
(function() {
  'use strict';

  var program = require('commander');
  var packageInfo = require('../package.json');
  var openurl = require('openurl');

  program
    .usage('[options]')
    .description('Radiian creates Ansible provisioning files for immutable infrastructure on AWS.\n' +
    '  Run `radiian init` to begin.')
    .version(packageInfo.version)
    .option('-o, --online-help', 'Opens online help');

  program
    .command('init', 'initiate/start radiian')
    .action(function(input) {
      if(input !== 'init') {
        console.log('\nError: unknown option `' + input + '`. Run `radiian --help` for help.\n');
      }
    });

  program.parse(process.argv);

  // If no arguments are passed, display help menu
  if (!process.argv.slice(2).length) { program.help(); }

  if (program.onlineHelp) {
    openurl.open('https://github.com/radify/radiian#readme');
  }

}());

Add more info to bad command warning


/* eslint-env node */
const EmberAddon = require('ember-cli/lib/broccoli/ember-addon')

module.exports = function (defaults) {
  var app = new EmberAddon(defaults, {
    babel: {
      optional: ['es7.decorators']
    },
    snippetSearchPaths: ['tests/dummy/app'],
    'ember-cli-mocha': {
      useLintTree: false
    }
  })

  app.import('bower_components/sinonjs/sinon.js')

  /*
    This build file specifes the options for the dummy test app of this
    addon, located in `/tests/dummy`
    This build file does *not* influence how the addon or the app using it
    behave. You most likely want to be modifying `./index.js` or app's build file
  */

  return app.toTree()
}

/* eslint-env node */
const EmberAddon = require('ember-cli/lib/broccoli/ember-addon')

module.exports = function (defaults) {
  var app = new EmberAddon(defaults, {
    babel: {
      optional: ['es7.decorators']
    },
    snippetSearchPaths: ['tests/dummy/app']
  })

  app.import('bower_components/sinonjs/sinon.js')

  /*
    This build file specifes the options for the dummy test app of this
    addon, located in `/tests/dummy`
    This build file does *not* influence how the addon or the app using it
    behave. You most likely want to be modifying `./index.js` or app's build file
  */

  return app.toTree()
}

Remove useLintTree from ember-cli-mocha configuration


var log = require('npmlog')
  , gcr = require('./gcr')
  , spawn = require('child_process').spawn
  , utils = exports

utils.genSSHKey = function(cb) {
  var args = [
      '-t'
    , 'rsa'
    , '-f'
    , '~/.ssh/gcr'
    , '-N'
    , '""'
  ]
  var child = spawn('ssh-keygen', args, {
      env: process.env
    , cwd: process.cwd()
  })
  child.stdout.pipe(process.stdout)
  child.stderr.pipe(process.stderr)
  child.on('exit', function(code) {
    if (code !== 0) {
      return cb && cb(new Error('Exited with code: '+code))
    }
    return cb && cb()
  })
}

var log = require('npmlog')
  , gcr = require('./gcr')
  , exec = require('child_process').exec
  , utils = exports

utils.genSSHKey = function(cb) {
  var cmd = 'ssh-keygen -t rsa -f ~/.ssh/gcr -N ""'
  exec(cmd, {
      env: process.env
    , cwd: process.cwd()
  }, function(err, stdout, stderr) {
    if (err) return cb && cb(err)
    return cb && cb(null, stdout)
  })
}

Use child_process.exec instead of child_process.spawn


function affirm(elem) {
    var id = $(elem).parent().parent().attr('id');
    $.get( '/affirm', {fid: id} )
        .done( function(data) {
            $(elem).remove();
        });
}
function start(elem) {
    var eid = $(elem).parent().parent().attr('id');
    $.get( '/start', {eid: eid} )
        .done( function(data) {
            $(elem).remove();
        });
}
function pay(elem) {
    var eid = $(elem).parent().parent().attr('id');
    $.get( '/pay', {eid: eid} )
        .done( function(data) {
            $(elem).remove();
        });
}

function participate(elem) {
    var eid = $(elem).parent().parent().attr('id');
    $.get( '/participate', {eid: eid} )
        .done( function(data) {
            $(elem).remove();
        });

}

function affirm(elem) {
    var id = $(elem).parent().parent().attr('id');
    $.get( '/affirm', {fid: id} )
        .done( function(data) {
            $(elem).hide('slow', function() { $(elem).remove() });
        });
}
function start(elem) {
    var eid = $(elem).parent().parent().attr('id');
    $.get( '/start', {eid: eid} )
        .done( function(data) {
            $(elem).hide('slow', function() { $(elem).remove() });
        });
}
function pay(elem) {
    var eid = $(elem).parent().parent().attr('id');
    $.get( '/pay', {eid: eid} )
        .done( function(data) {
            $(elem).hide('slow', function() { $(elem).remove() });
        });
}

function participate(elem) {
    var eid = $(elem).parent().parent().attr('id');
    $.get( '/participate', {eid: eid} )
        .done( function(data) {
            $(elem).hide('slow', function() { $(elem).remove() });
        });

}

Remove animation for buttons on view.


import "../scss/main.scss";
import "../css/shim.css";

import "./viewer.js";
import "./map.js";
import "./search/search.js";

window.console.log('main.js loaded');

import "../scss/main.scss";
import "../css/shim.css";

import "./viewer.js";
import "./map.js";
import "./search/search.js";

window.console.log('kap.js loaded');

Change the startup message in the console