var fields = ['skin', 'location', 'ink', 'layering', 'scarring', 'colors'];
var form;

$(function() {
  form = document.forms[0];

  $('input').change(onChange);
});

function onChange(e) {
  var scale = {};
  var field_count = 0;
  for (var i = 0; i < fields.length; ++i) {
    var field = fields[i];
    if (form[field].value) {
      scale[field] = form[field].value;
      ++field_count;
    }
  }

  var total_points = 0;
  for (var field in scale) {
    $('.' + field + '-points').text(scale[field]);
    total_points += parseInt(scale[field]);
  }

  if (field_count == fields.length) {
    $('.total-points').text(total_points);
  }
}

var fields = ['skin', 'location', 'ink', 'layering', 'scarring', 'colors'];
var form;

$(function() {
  form = document.forms[0];

  $('input').change(onChange);
  onChange();
});

function onChange() {
  var scale = {};
  var field_count = 0;
  for (var i = 0; i < fields.length; ++i) {
    var field = fields[i];
    if (form[field].value) {
      scale[field] = form[field].value;
      ++field_count;
    }
  }

  var total_points = 0;
  for (var field in scale) {
    $('.' + field + '-points').text(scale[field]);
    total_points += parseInt(scale[field]);
  }

  if (field_count == fields.length) {
    $('.total-points').text(total_points);
  }
}

Fix an issue where the scores would not show if a user left the page, then returned using the browser's back/forward buttons (since the form values would be saved, but the DOM not updated by our logic).


/* jshint node: true */
'use strict';

module.exports = {
  name: 'ember-plupload',

  included: function (app) {
    this._super.included(app);
    if (process.env.EMBER_ENV === 'development') {
      app.import('bower_components/plupload/js/moxie.js');
      app.import('bower_components/plupload/js/plupload.dev.js');
    } else {
      app.import('bower_components/plupload/js/plupload.full.min.js');
    }
    app.import('bower_components/plupload/js/Moxie.swf', {
      destDir: 'assets'
    });
    app.import('bower_components/plupload/js/Moxie.xap', {
      destDir: 'assets'
    });
    app.import('bower_components/dinosheets/dist/dinosheets.amd.js', {
      exports: {
        'dinosheets': ['default']
      }
    });

    app.import('vendor/styles/ember-plupload.css');
  }
};

/* jshint node: true */
'use strict';

module.exports = {
  name: 'ember-plupload',

  included: function (app) {
    this._super.included(app);
    if (process.env.EMBER_ENV === 'development') {
      app.import('bower_components/plupload/js/moxie.js');
      app.import('bower_components/plupload/js/plupload.dev.js');
    } else {
      app.import('bower_components/plupload/js/plupload.dev.js');
    }
    app.import('bower_components/plupload/js/Moxie.swf', {
      destDir: 'assets'
    });
    app.import('bower_components/plupload/js/Moxie.xap', {
      destDir: 'assets'
    });
    app.import('bower_components/dinosheets/dist/dinosheets.amd.js', {
      exports: {
        'dinosheets': ['default']
      }
    });

    app.import('vendor/styles/ember-plupload.css');
  }
};

Use plupload.dev.js instead of full, as it contains fixes for PUT option.


import { USER_ROLES } from '../constants';

const { ADMIN_ROLE, MEMBER_ROLE } = USER_ROLES;

export const hasPaidMail = ({ Subscribed }) => Subscribed & 1;
export const hasPaidVpn = ({ Subscribed }) => Subscribed & 4;
export const isPaid = ({ Subscribed }) => Subscribed;
export const isFree = (user) => !isPaid(user);
export const isAdmin = ({ Role }) => Role === ADMIN_ROLE;
export const isMember = ({ Role }) => Role === MEMBER_ROLE;
export const isSubUser = ({ OrganizationPrivateKey }) => typeof OrganizationPrivateKey !== 'undefined';
export const isDelinquent = ({ Delinquent }) => Delinquent;

export const getInfo = (User) => {
    return {
        isAdmin: isAdmin(User),
        isMember: isMember(User),
        isFree: isFree(User),
        isPaid: isPaid(User),
        isSubUser: isSubUser(User),
        isDelinquent: isDelinquent(User),
        hasPaidMail: hasPaidMail(User),
        hasPaidVpn: hasPaidVpn(User)
    };
};

import { USER_ROLES } from '../constants';

const { ADMIN_ROLE, MEMBER_ROLE } = USER_ROLES;

export const hasPaidMail = ({ Subscribed }) => Subscribed & 1;
export const hasPaidVpn = ({ Subscribed }) => Subscribed & 4;
export const isPaid = ({ Subscribed }) => Subscribed;
export const isPrivate = ({ Private }) => Private === 1;
export const isFree = (user) => !isPaid(user);
export const isAdmin = ({ Role }) => Role === ADMIN_ROLE;
export const isMember = ({ Role }) => Role === MEMBER_ROLE;
export const isSubUser = ({ OrganizationPrivateKey }) => typeof OrganizationPrivateKey !== 'undefined';
export const isDelinquent = ({ Delinquent }) => Delinquent;

export const getInfo = (User) => {
    return {
        isAdmin: isAdmin(User),
        isMember: isMember(User),
        isFree: isFree(User),
        isPaid: isPaid(User),
        isPrivate: isPrivate(User),
        isSubUser: isSubUser(User),
        isDelinquent: isDelinquent(User),
        hasPaidMail: hasPaidMail(User),
        hasPaidVpn: hasPaidVpn(User)
    };
};

Add isPrivate flag to user


#!/usr/bin/env node

'use strict';

var pkg            = require('../package.json')
var cli            = require('yargs');
var updateNotifier = require('update-notifier');

updateNotifier({ pkg }).notify();

cli
  .strict()
  .version()
  .usage('collider [--version] [--help] <command> [<args>]')
  .command('run', 'Run an existing project in the current directory', require('../lib/commands/run'))
  .command('new <name>', 'Create a new project in the current directory', require('../lib/commands/new'))
  .help()
  .argv;

var commands = cli.argv._;

// If no commands are given, then show help.
if(commands.length === 0) {
  cli.showHelp();
}

#!/usr/bin/env node

'use strict';

var pkg            = require('../package.json')
var yargs          = require('yargs');
var updateNotifier = require('update-notifier');

updateNotifier({ pkg }).notify();

yargs
  .strict()
  .version()
  .usage('collider [--version] [--help] <command> [<args>]')
  .command('run', 'Run an existing project in the current directory', require('../lib/commands/run'))
  .command('new <name>', 'Create a new project in the current directory', require('../lib/commands/new'))
  .help()
  .argv;

var commands = cli.argv._;

// If no commands are given, then show help.
if(commands.length === 0) {
  cli.showHelp();
}

Rename cli var to yargs


"use strict";
exports.__esModule = true;
var fs = require("fs");

// read in the /index.ts

// use regex to find a console log for printing the version and update it for the new version

// update /index.ts with the new content

console.log("update src/index.ts with version:", process.env.npm_package_version);

fs.readFile("./index.ts", 'utf8', function(err, data) {
    if (err) throw err;
    var re = /(.*[a-zA-Z]\s\-\s)(.*)(\"\)\;.*)/;
    //console.log("before", data)
    data = data.replace(re, "$1"+process.env.npm_package_version+"$3");//#BUILD_VIA_NPM_VERSION_PATCH_TO_DISPLAY_VERSION_HERE#", process.env.npm_package_version);
    //console.log("after", data)
    fs.writeFile("./index.ts", data, function(err) {
        if (err) throw err;
        console.log("Updated ./index.ts with inserted version ", process.env.npm_package_version);
    });
});

'use strict';
exports.__esModule = true;
const fs = require('fs');

// read in the /index.ts

// use regex to find a console log for printing the version and update it for the new version

// update /index.ts with the new content

console.log('update src/index.ts with version:', process.env.npm_package_version);

fs.readFile('./index.ts', 'utf8', function(err, data) {
	if (err) throw err;
	const re = /(.*[a-zA-Z]\s-\s)(.*)(["|']\);.*)/;
	//console.log("before", data)
	//#BUILD_VIA_NPM_VERSION_PATCH_TO_DISPLAY_VERSION_HERE#", process.env.npm_package_version);
	data = data.replace(re, '$1' + process.env.npm_package_version + '$3');
	//console.log("after", data)
	fs.writeFile('./index.ts', data, function(err) {
		if (err) throw err;
		console.log('Updated ./index.ts with inserted version ', process.env.npm_package_version);
	});
});

Copy version after lint, support `'`


var assert = require('assert');

describe('linter', function () {
    var linter = require('../../lib/linter');

    describe('lint', function () {
        it('should return array of errors', function () {
            var source = '.foo{ color:red; }';
            var path = 'test.less';
            var actual;

            var config = {
                spaceAfterPropertyColon: {
                    enabled: true,
                    style: 'one_space'
                },
                spaceBeforeBrace: {
                    enabled: true,
                    style: 'one_space'
                }
            };

            actual = linter.lint(source, path, config);

            assert.equal(2, actual.length);
        });
    });

    describe('parseAST', function () {
        it('should return an AST', function () {
            var source = '.foo { color: red; }';
            var ast = linter.parseAST(source);

            assert.ok(ast.toCSS); // If the returned object has the 'toCSS' method, we'll consider it a success
        });
    });
});

var assert = require('assert');

describe('linter', function () {
    var linter = require('../../lib/linter');

    describe('lint', function () {
        it('should return array of errors', function () {
            var source = '.foo{ color:red; }';
            var path = 'test.less';
            var actual;

            var config = {
                spaceAfterPropertyColon: {
                    enabled: true,
                    style: 'one_space'
                },
                spaceBeforeBrace: {
                    enabled: true,
                    style: 'one_space'
                }
            };

            actual = linter.lint(source, path, config);

            assert.ok(actual.length === 2);
        });
    });

    describe('parseAST', function () {
        it('should return an AST', function () {
            var source = '.foo { color: red; }';
            var ast = linter.parseAST(source);

            assert.ok(ast.toCSS); // If the returned object has the 'toCSS' method, we'll consider it a success
        });
    });
});

Use assert.ok() instead of assert.equals()


import React from 'react'
import PropTypes from 'prop-types'
import { compose, withProps } from 'recompose'
import QRCode from 'qrcode.react'
import { withRouter } from 'next/router'

import { StaticLayout } from '../components/layouts'
import { withLogging } from '../lib'

const propTypes = {
  shortname: PropTypes.string.isRequired,
}

const QR = ({ shortname }) => {
  const joinLink = process.env.APP_JOIN_URL
    ? `${process.env.APP_JOIN_URL}/${shortname}`
    : `${process.env.APP_BASE_URL}/join/${shortname}`

  return (
    <StaticLayout pageTitle="QR">

{joinLink.replace(/^https?:\/\//, '')}

        <QRCode size={700} value={joinLink} />

    </StaticLayout>
  )
}
QR.propTypes = propTypes

export default compose(
  withRouter,
  withLogging(),
  withProps(({ router }) => ({
    shortname: router.query.shortname,
  }))
)(QR)

import React from 'react'
import PropTypes from 'prop-types'
import { compose, withProps } from 'recompose'
import QRCode from 'qrcode.react'
import { withRouter } from 'next/router'

import { StaticLayout } from '../components/layouts'
import { withLogging } from '../lib'

const propTypes = {
  shortname: PropTypes.string.isRequired,
}

const QR = ({ shortname }) => {
  const joinLink = process.env.APP_JOIN_URL
    ? `${process.env.APP_JOIN_URL}/${shortname}`
    : `${process.env.APP_BASE_URL}/join/${shortname}`

  return (
    <StaticLayout pageTitle="QR">

{joinLink.replace(/^https?:\/\//, '')}

        <QRCode size={700} value={`${process.env.APP_BASE_URL}/join/${shortname}`} />

    </StaticLayout>
  )
}
QR.propTypes = propTypes

export default compose(
  withRouter,
  withLogging(),
  withProps(({ router }) => ({
    shortname: router.query.shortname,
  }))
)(QR)

Change embedded join link to direct klicker.uzh.ch domain


/* eslint-env node */
module.exports = {
  test_page: 'tests/index.html?hidepassed',
  disable_watching: true,
  launch_in_ci: [
    'Chrome'
  ],
  launch_in_dev: [
    'Chrome'
  ],
  browser_args: {
    Chrome: {
      mode: 'ci',
      args: [
        '--disable-gpu',
        '--headless',
        '--remote-debugging-port=9222',
        '--window-size=1440,900'
      ]
    },
  }
};

/* eslint-env node */
module.exports = {
  test_page: 'tests/index.html?hidepassed',
  disable_watching: true,
  launch_in_ci: [
    'Chrome'
  ],
  launch_in_dev: [
    'Chrome'
  ],
  browser_args: {
    Chrome: {
      mode: 'ci',
      args: [
        process.env.TRAVIS ? '--no-sandbox' : null,

        '--disable-gpu',
        '--headless',
        '--remote-debugging-port=9222',
        '--window-size=1440,900'
      ].filter(Boolean)
    },
  }
};

Disable chrome sandboxing on travis


module.exports = {
  framework: 'mocha',
  'test_page': 'tests/index.html?hidepassed&coverage',
  'disable_watching': true,
  'launch_in_ci': [
    'Chrome'
  ],
  'launch_in_dev': [
    'Chrome'
  ]
}

var Reporter = require('ember-test-utils/reporter')

module.exports = {
  disable_watching: true,
  framework: 'mocha',
  launch_in_ci: [
    'Chrome'
  ],
  launch_in_dev: [
    'Chrome'
  ],
  reporter: new Reporter(),
  test_page: 'tests/index.html?hidepassed'
}

Remove Firefox from CI as it is problematic


$( document ).ready(function(){
  $('#add_to_calendar').click(function(e){
    e.preventDefault();
    var $form = $(this).closest('form');
    $.ajax({
      url: '/emails',
      type: 'POST',
      data: $form.serialize(),
      dataType: 'script',
      success: function(){console.log('Email sent!');},
      failure: function(){alert('Email could not be sent!');}
    });
  });
});

$( document ).ready(function(){
  $('#add_to_calendar').click(function(e){
    e.preventDefault();
    var $form = $(this).closest('form');
    $.ajax({
      url: '/emails',
      type: 'POST',
      data: $form.serialize(),
      dataType: 'script',
      success: function(){
        $form.find('input[id="email_email_address"]').val("");
        console.log('Email sent!');
      },
      failure: function(){alert('Email could not be sent!');}
    });
  });
});

Clear email address field after email is sent


import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { getContext, mapProps, compose } from 'recompose';

import { visibleRowIdsSelector, classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/localSelectors';

const ComposedTableBodyContainer = OriginalComponent => compose(
  getContext({
    components: PropTypes.object,
    selectors: PropTypes.object,
  }),
  connect(state => ({
    visibleRowIds: visibleRowIdsSelector(state),
    className: classNamesForComponentSelector(state, 'TableBody'),
    style: stylesForComponentSelector(state, 'TableBody'),
  })),
  mapProps(props => ({
    Row: props.components.Row,
    ...props
  })),
  // withHandlers({
  //   Row: props => props.components.Row
  // })
)(({ Row, visibleRowIds, style, className }) => (
  <OriginalComponent
    rowIds={visibleRowIds}
    Row={Row}
    style={style}
    className={className}
  />
));

export default ComposedTableBodyContainer;

import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { getContext, mapProps, compose } from 'recompose';

import { visibleRowIdsSelector, classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/localSelectors';

const ComposedTableBodyContainer = OriginalComponent => compose(
  getContext({
    components: PropTypes.object,
    selectors: PropTypes.object,
  }),
  mapProps(props => ({
    Row: props.components.Row,
    visibleRowIdsSelector: props.selectors.visibleRowIdsSelector,
    ...props
  })),
  connect((state, props) => ({
    visibleRowIds: props.visibleRowIdsSelector(state),
    className: classNamesForComponentSelector(state, 'TableBody'),
    style: stylesForComponentSelector(state, 'TableBody'),
  })),
  // withHandlers({
  //   Row: props => props.components.Row
  // })
)(({ Row, visibleRowIds, style, className }) => (
  <OriginalComponent
    rowIds={visibleRowIds}
    Row={Row}
    style={style}
    className={className}
  />
));

export default ComposedTableBodyContainer;

Fix virtual scrolling for table with local data.


'use strict';

const through = require('through2');
const gutil = require('gulp-util');
const fm = require('front-matter')

const PluginError = gutil.PluginError;
const PLUGIN_NAME = 'front-matter';

module.exports = () => {
  return through.obj(function(file, encoding, cb) {
    /////////////////////////////
    // Errors
    if (file.isNull()) {
      return cb();
    }
    if (file.isStream()) {
      this.emit('error', new PluginError(PLUGIN_NAME, 'Streams are not supported!'));
      return cb();
    }

    /////////////////////////////
    // Manipulate
    let content = fm(file.contents.toString());

    if (!file.meta) {
      file.meta = content.attributes;
    }
    else {
      file.meta = {};
    }

    file.meta.today = Date.now();

    file.contents = new Buffer(content.body);

    this.push(file);

    cb();
  });
}

'use strict';

const through = require('through2');
const gutil = require('gulp-util');
const fm = require('front-matter')

const PluginError = gutil.PluginError;
const PLUGIN_NAME = 'front-matter';

module.exports = () => {
  return through.obj(function(file, encoding, cb) {
    /////////////////////////////
    // Errors
    if (file.isNull()) {
      return cb();
    }
    if (file.isStream()) {
      this.emit('error', new PluginError(PLUGIN_NAME, 'Streams are not supported!'));
      return cb();
    }

    /////////////////////////////
    // Manipulate
    let content = fm(file.contents.toString());

    file.meta = content.attributes;
    file.meta.today = Date.now();

    file.contents = new Buffer(content.body);

    this.push(file);

    cb();
  });
}

Clean up front matter plugin logic


$([IPython.events]).on('notebook_loaded.Notebook', function(){
    // add here logic that should be run once per **notebook load**
    IPython.notebook.metadata.language = 'ruby' ;
});

$([IPython.events]).on('app_initialized.NotebookApp', function(){
    // add here logic that shoudl be run once per **page load**
    $.getScript('/static/components/codemirror/mode/ruby/ruby.js');
    IPython.CodeCell.options_default['cm_config']['mode'] = 'ruby';
    IPython.CodeCell.options_default['cm_config']['indentUnit'] = 2;
});

$([IPython.events]).on('notebook_loaded.Notebook', function(){
    // add here logic that should be run once per **notebook load**
    IPython.notebook.metadata.language = 'ruby' ;
});

$([IPython.events]).on('app_initialized.NotebookApp', function(){
    // add here logic that shoudl be run once per **page load**
    CodeMirror.requireMode('ruby', function(){
         console.log('Ruby mode should now be availlable in codemirror.');
    })
    IPython.CodeCell.options_default['cm_config']['mode'] = 'ruby';
    IPython.CodeCell.options_default['cm_config']['indentUnit'] = 2;
});

Use CodeMirror RequireMode instead of getScript.


describe('menuCtrl tests', function() {
  describe('instantiation and scope tests', function() {
    var $httpBackend, $rootScope, createController;
    beforeEach(function() {
      module('starter');
      module('starter.controllers');
      module('starter.services');

    });

    beforeEach(inject(function($injector) {
      var $controller = $injector.get('$controller');
      $rootScope = $injector.get('$rootScope');
      createController = function() {
        return $controller('menuCtrl', {'$scope': $rootScope});
      };

    }));

    it('should be defined and initialized', (function() {
      var controller = createController();
      expect(controller).toBeDefined();
      expect($rootScope).toBeDefined();
    }));

    it('should know if has been canceled Redirect',(function() {
      var controller = createController();

      expect(controller).toBeDefined();
      expect($rootScope).toBeDefined();
      expect($rootScope.cancelRedirect).toBeDefined();

      spyOn($rootScope,'cancelRedirect');
      $rootScope.cancelRedirect();

      expect($rootScope.cancelRedirect).toHaveBeenCalled();

    }));

    it('should close app',(function() {
      var controller = createController();

      expect(controller).toBeDefined();
      expect($rootScope).toBeDefined();
      expect($rootScope.closeApp).toBeDefined();

      spyOn($rootScope,'closeApp');
      $rootScope.closeApp();
      expect($rootScope.closeApp).toHaveBeenCalled( );
      expect($rootScope.closeApp).toHaveBeenCalledWith( );

      spyOn(ionic.Platform,'exitApp');
      ionic.Platform.exitApp();
      expect(ionic.Platform.exitApp).toHaveBeenCalled();
      expect(ionic.Platform.exitApp).toHaveBeenCalledWith();

    }));

  });
});

describe('menuCtrl tests', function() {
  describe('instantiation and scope tests', function() {
    var $httpBackend, $rootScope, createController;
    beforeEach(function() {
      module('starter');
      module('starter.controllers');
      module('starter.services');

    });

    beforeEach(inject(function($injector) {
      var $controller = $injector.get('$controller');
      $rootScope = $injector.get('$rootScope');
      createController = function() {
        return $controller('menuCtrl', {'$scope': $rootScope});
      };

    }));

    it('should be defined and initialized', (function() {
      var controller = createController();
      expect(controller).toBeDefined();
      expect($rootScope).toBeDefined();
    }));

    it('should know if has been canceled Redirect', inject(function($state) {
      spyOn($state, 'go');
      var controller = createController();
      $rootScope.cancelRedirect();
      expect($state.go).toHaveBeenCalledWith('menu.home');
    }));

    it('should close app', inject(function($state) {
      spyOn($state, 'go');
      var controller = createController();
      $rootScope.closeApp();
      expect($state.go).not.toHaveBeenCalledWith('menu.home');
    }));

  });
});

Fix test for cancel redirect and close app


function onOpen() {
  var spreadsheet = SpreadsheetApp.getActive();
  spreadsheet.addMenu('Time Tracking', [{ "name": 'Lauch Timer', "functionName": 'launchTimer_'}]);
}

function launchTimer_() {
  var ui = SpreadsheetApp.getUi();
  ui.showSidebar(HtmlService.createHtmlOutputFromFile('timer.html'));
}

function beginTimeSheetEntry() {
  var cell = SpreadsheetApp.getActiveSheet().getActiveCell();
  cell.setValue(new Date());
  return [cell.getRow(), cell.getColumn()];
}

Add function to write when timer begins to the active cell.


'use strict';

require('../test_helper');

var Factory = require('factory-lady'),
    mongoose = require('mongoose');

mongoose.model('ConfigVersion', new mongoose.Schema({
  version: {
    type: Date,
    unique: true,
  },
  config: mongoose.Schema.Types.Mixed,
}, { collection: 'config_versions' }));

Factory.define('config_version', mongoose.testConnection.model('ConfigVersion'), {
  version: function(callback) {
    callback(new Date());
  },
});

'use strict';

require('../test_helper');

var Factory = require('factory-lady'),
    mongoose = require('mongoose');

mongoose.model('RouterConfigVersion', new mongoose.Schema({
  version: {
    type: Date,
    unique: true,
  },
  config: mongoose.Schema.Types.Mixed,
}, { collection: 'config_versions' }));

Factory.define('config_version', mongoose.testConnection.model('RouterConfigVersion'), {
  version: function(callback) {
    callback(new Date());
  },
});

Fix conflicting model in tests with the api-umbrella-config module.


var exec = require('child_process').exec;
var gulp = require('gulp');

gulp.task('test', function() {
  var path = './node_modules/mocha-phantomjs/bin/mocha-phantomjs';
  var command = '/test/index.html';

  exec(path + ' ' + command, function (error, stdout, stderr) {
    if (stdout) {
      console.log(stdout);
    } else if (stderr) {
      console.log(stderr);
    } else if (error) {
      console.log(error);
    }
  });
});

var exec = require('child_process').exec;
var gulp = require('gulp');

gulp.task('test', function() {
  var path = './node_modules/mocha-phantomjs/bin/mocha-phantomjs';
  var command = '/test/index.html';

  exec(path + ' ' + command, function (error, stdout, stderr) {
    if (stdout) {
      console.log(stdout);
    } else if (stderr) {
      console.error(stderr);
    } else if (error) {
      console.error(error);
    }
  });
});

Use console.error in gulp test task


var gulp = require('gulp');
var stylus = require('gulp-stylus');
var stylint = require('gulp-stylint');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');

var stylFiles = './assets/styl/*.styl';

function bsjs(file) {
    return './assets/bootstrap-stylus-5.0.2/js/' + file + '.js'
};
var bootstrapJsFiles = [bsjs('collapse'), bsjs('button'), bsjs('dropdown')];

gulp.task('stylus-lint', function() {
    return gulp.src(stylFiles)
        .pipe(stylint({
            config: './assets/styl/.stylintrc'
        }))
        .pipe(stylint.reporter())
        .pipe(stylint.reporter('fail'));
});

gulp.task('stylus-compile', function() {
    return gulp.src(stylFiles)
        .pipe(stylus({
            compress: true
        }))
        .pipe(gulp.dest('./assets/css'))
});

gulp.task('stylus', ['stylus-lint', 'stylus-compile']);

gulp.task('bootstrap-js', function() {
    return gulp.src(bootstrapJsFiles)
        .pipe(concat('bootstrap.min.js'))
        .pipe(uglify())
        .pipe(gulp.dest('./assets/js'))
});

gulp.task('watch', function() {
    gulp.watch([stylFiles, './assets/bootstrap-stylus-5.0.2/bootstrap/*.styl'], ['stylus']);
});

gulp.task('default', ['stylus', 'bootstrap-js']);

var gulp = require('gulp');
var stylus = require('gulp-stylus');
var stylint = require('gulp-stylint');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');

var stylFiles = './assets/styl/*.styl';

function bsjs(file) {
    return './assets/bootstrap-stylus-5.0.2/js/' + file + '.js'
};
var bootstrapJsFiles = [bsjs('collapse'), bsjs('button'), bsjs('dropdown')];

gulp.task('stylus-lint', function() {
    return gulp.src(stylFiles)
        .pipe(stylint({
            config: './assets/styl/.stylintrc'
        }))
        .pipe(stylint.reporter())
        .pipe(stylint.reporter('fail'));
});

gulp.task('stylus-compile', function() {
    return gulp.src(stylFiles)
        .pipe(stylus({
            compress: true
        }))
        .pipe(gulp.dest('./assets/css'))
});

gulp.task('stylus', ['stylus-lint', 'stylus-compile']);

gulp.task('bootstrap-js', function() {
    return gulp.src(bootstrapJsFiles)
        .pipe(concat('bootstrap.min.js'))
        .pipe(uglify())
        .pipe(gulp.dest('./assets/js'))
});

gulp.task('watch', function() {
    gulp.watch([stylFiles, './assets/bootstrap-stylus-5.0.2/bootstrap/*.styl'], ['stylus-compile']);
});

gulp.task('default', ['stylus', 'bootstrap-js']);

Remove stylint from gulp watch


'use strict';

var gulp = require('gulp');
var jshint = require('gulp-jshint');
var stylish = require('jshint-stylish');

gulp.task('jshint', function() {
  return gulp.src(['*.js', 'lib/*.js', 'spec/*.js']).
    pipe(jshint()).
    pipe(jshint.reporter(stylish));
});

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

gulp.task('test', function() {
  return gulp.src('spec/*.js').
    pipe(jasmine());
});

gulp.task('default', [ 'jshint', 'test' ]);

'use strict';

var gulp = require('gulp');
var jshint = require('gulp-jshint');
var stylish = require('jshint-stylish');

gulp.task('jshint', function() {
  return gulp.src(['*.js', 'lib/*.js', 'spec/*.js']).
    pipe(jshint()).
    pipe(jshint.reporter(stylish)).
    pipe(jshint.reporter('fail'));
});

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

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

gulp.task('default', [ 'jshint', 'test' ]);

Make 'gulp test' depends on 'gulp jshint'.


var browserify = require('browserify');
var gulp = require('gulp');
var glob = require('glob');
var reactify = require('reactify');
var source = require('vinyl-source-stream');
var shell = require('gulp-shell');

/*
  gulp browserify:

  Bundles all .js files in the /js/ directory into one file in /build/bundle.js
  that will be imported onto all pages
*/

gulp.task('browserify', function() {
	  var files = glob.sync('./js/**/*.js');
    var b = browserify();
    for (var i = 0; i < files.length; i++) {
      var matches = /(\w+)\.js$/.exec(files[i]);
      b.require(files[i], {expose: matches[1]});
    }
    b.require('xhpjs');
		return b
			.bundle()
			.pipe(source('bundle.js'))
			.pipe(gulp.dest('./build/'));
});

gulp.task('install-php', shell.task([
	'hhvm composer.phar install'
]));

gulp.task('autoload', shell.task([
	'hhvm composer.phar dump-autoload'
]));

gulp.task('default', ['install-php','browserify']);

var browserify = require('browserify');
var gulp = require('gulp');
var glob = require('glob');
var reactify = require('reactify');
var source = require('vinyl-source-stream');
var shell = require('gulp-shell');

/*
  gulp browserify:

  Bundles all .js files in the /js/ directory into one file in /build/bundle.js
  that will be imported onto all pages
*/

gulp.task('browserify', function() {
	  var files = glob.sync('./js/**/*.js');
    var b = browserify();
    for (var i = 0; i < files.length; i++) {
      var matches = /(\w+)\.js$/.exec(files[i]);
      b.require(files[i], {expose: matches[1]});
    }
    b.require('xhpjs');
		return b
			.bundle()
			.pipe(source('bundle.js'))
			.pipe(gulp.dest('./build/'));
});

gulp.task('install-php', shell.task([
	'hhvm composer.phar install'
]));

gulp.task('autoload', shell.task([
	'hhvm composer.phar dump-autoload'
]));

gulp.task('serve', shell.task([
    'hhvm -m server -p 8080'
]));

gulp.task('default', ['install-php','browserify','serve']);

Add gulp command for running server


var gulp = require('gulp'),
	jshint = require('gulp-jshint'),
	mocha = require('gulp-mocha');

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

gulp.task('lint', function () {
	return gulp.src(['./lib/*.js', './bin/*.js'])
		.pipe(jshint())
		.pipe(jshint.reporter('default', {verbose: true}));
});

gulp.task('test', function () {
	return gulp.src('./test/telldus.js', {read: false})
		.pipe(mocha({reporter: 'nyan'}));
});

var gulp = require('gulp'),
	jshint = require('gulp-jshint'),
	mocha = require('gulp-mocha');

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

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

gulp.task('test', function () {
	return gulp.src('./test/telldus.js', {read: false})
		.pipe(mocha({reporter: 'nyan'}));
});

Add lint and test to default gulp task


var gulp = require('gulp'),
    concat = require('gulp-concat'),
    rename = require('gulp-rename'),
    uglify = require('gulp-uglify'),
    gulpif = require('gulp-if'),
    react = require('gulp-react');

var paths = [
  'wrapper-header.js',
  'cosmos.js',
  'lib/**/*.js',
  'mixins/**/*.js',
  'components/**/*.jsx',
  'wrapper-footer.js'
];

gulp.task('build', function() {
  gulp.src(paths)
    .pipe(gulpif(/\.jsx$/, react()))
    .pipe(concat('cosmos.js'))
    .pipe(gulp.dest('build'))
    .pipe(uglify())
    .pipe(rename('cosmos.min.js'))
    .pipe(gulp.dest('build'));
});

// Rerun the task when a file changes
gulp.task('watch', function () {
  gulp.watch(paths.core, ['build']);
});

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

var gulp = require('gulp'),
    concat = require('gulp-concat'),
    rename = require('gulp-rename'),
    uglify = require('gulp-uglify'),
    gulpif = require('gulp-if'),
    react = require('gulp-react');

var paths = [
  'wrapper-header.js',
  'cosmos.js',
  'lib/**/*.js',
  'mixins/**/*.js',
  'components/**/*.jsx',
  'wrapper-footer.js'
];

gulp.task('build', function() {
  gulp.src(paths)
    .pipe(gulpif(/\.jsx$/, react()))
    .pipe(concat('cosmos.js'))
    .pipe(gulp.dest('build'))
    .pipe(uglify())
    .pipe(rename('cosmos.min.js'))
    .pipe(gulp.dest('build'));
});

// Rerun the task when a file changes
gulp.task('watch', function () {
  gulp.watch(paths, ['build']);
});

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

Fix paths in watch gulp task


'use strict';

var gulp = require('gulp'),
    browserify = require('browserify'),
    transform = require('vinyl-transform'),
    uglify = require('gulp-uglify'),
    sourcemaps = require('gulp-sourcemaps'),
    jasmine = require('gulp-jasmine'),
    sass = require('gulp-sass');

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

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

gulp.task('build', ['javascript', 'css']);

gulp.task('css', function () {
    gulp.src('./src/css/bowler.scss')
        .pipe(sourcemaps.init())
        .pipe(sass())
        .pipe(sourcemaps.write('./'))
        .pipe(gulp.dest('./dist/css'));
});

gulp.task('javascript', function () {
    var browserified = transform(function (filename) {
        var b = browserify({ entries: filename, debug: true });
        return b.bundle();
    });

    return gulp.src('./src/js/bowler.js')
        .pipe(browserified)
        .pipe(sourcemaps.init({ loadMaps: true }))
        .pipe(uglify())
        .pipe(sourcemaps.write('./'))
        .pipe(gulp.dest('./dist/js'));
});

gulp.task('test', ['javascript'], function () {
    return gulp.src('./spec/**/*.js')
        .pipe(jasmine());
});

'use strict';

var gulp = require('gulp'),
    browserify = require('browserify'),
    transform = require('vinyl-transform'),
    uglify = require('gulp-uglify'),
    sourcemaps = require('gulp-sourcemaps'),
    jasmine = require('gulp-jasmine'),
    sass = require('gulp-sass');

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

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

gulp.task('build', ['javascript', 'css']);

gulp.task('css', function () {
    gulp.src('./src/scss/bowler.scss')
        .pipe(sourcemaps.init())
        .pipe(sass({ outputStyle: 'compressed' }))
        .pipe(sourcemaps.write('./'))
        .pipe(gulp.dest('./dist/css'));
});

gulp.task('javascript', function () {
    var browserified = transform(function (filename) {
        var b = browserify({ entries: filename, debug: true });
        return b.bundle();
    });

    return gulp.src('./src/js/bowler.js')
        .pipe(browserified)
        .pipe(sourcemaps.init({ loadMaps: true }))
        .pipe(uglify())
        .pipe(sourcemaps.write('./'))
        .pipe(gulp.dest('./dist/js'));
});

gulp.task('test', ['javascript'], function () {
    return gulp.src('./spec/**/*.js')
        .pipe(jasmine());
});

Set sass output style to compressed


import mongoose from 'mongoose';
const Schema = mongoose.Schema;

const todoSchema = new Schema({
  text: { type: 'String', required: true },
  completed: { type: 'Boolean', required: true },
  cuid: { type: 'String', required: true }
});

export default mongoose.model('Todo', todoSchema);

import mongoose from 'mongoose';
const Schema = mongoose.Schema;

const todoSchema = new Schema({
  text: { type: 'String', required: true },
  completed: { type: 'Boolean', required: true },
  cuid: { type: 'String', required: true },
});

export default mongoose.model('Todo', todoSchema);

Add dangling comma to fix linting


global.simulatency = {}

global.simulatency.timeout = 100

inject(Meteor.server.stream_server.server._events, 'connection', function (args, done) {
  inject(args[0], 'write', function (args, done) {
    setTimeout(done, global.simulatency.timeout)
  })
  done()
})

function httpDelay (args, done) {
  global.simulatency.timeout =
    getCookie(args[0].headers.cookie, 'lag')[1]
    || global.simulatency.timeout
  setTimeout(done, global.simulatency.timeout)
}

inject(WebApp.httpServer._events, 'upgrade', httpDelay)
inject(WebApp.httpServer._events, 'request', httpDelay)

global.simulatency = {}

global.simulatency.timeout = 0

inject(Meteor.server.stream_server.server._events, 'connection', function (args, done) {
  inject(args[0], 'write', function (args, done) {
    setTimeout(done, global.simulatency.timeout)
  })
  done()
})

function httpDelay (args, done) {
  global.simulatency.timeout =
    getCookie(args[0].headers.cookie, 'lag')[1]
    || global.simulatency.timeout
  setTimeout(done, global.simulatency.timeout)
}

inject(WebApp.httpServer._events, 'upgrade', httpDelay)
inject(WebApp.httpServer._events, 'request', httpDelay)

Set a more sensible default


var darkColor;
var lightColor;

self.port.on("colors", function(colors) {
    darkColor = colors[0];
    lightColor = colors[1];
    checkElementContrast(document.all[0]);
});

function checkElementContrast(element)
{
    var isFgUndefined = (getComputedStyle(element).color == getDefaultComputedStyle(element).color);
    var isBgUndefined = (getComputedStyle(element).backgroundColor == getDefaultComputedStyle(element).backgroundColor)
                      && (getComputedStyle(element).backgroundImage == 'none'); //Background image is not set

    if (isFgUndefined && isBgUndefined) {
        // Both undefined, continue with children
        var children = element.children
        for (var i=0; i < children.length; i++) {
            // Don't look at non-renderable elements
            switch (element.children[i].nodeName) {
                case "HEAD":
                case "TITLE":
                case "META":
                case "SCRIPT":
                case "IMG":
                case "STYLE":
                    break;
                default:
                    checkElementContrast(element.children[i]);
            }
        }
    } else if (isFgUndefined) {
        element.style.color = darkColor;
    } else if (isBgUndefined) {
        element.style.backgroundColor = lightColor;
    }

    return;
}

var darkColor;
var lightColor;

self.port.on("colors", function(colors) {
    darkColor = colors[0];
    lightColor = colors[1];
    // Now replace document colors
    checkElementContrast(document.all[0]);

    // Seperately check input and textarea nodes
    var inputs = document.getElementsByTagName("input");
    for (var i=0; i < inputs.length; i++) {
        checkElementContrast(inputs[i]);
    }

    var texts = document.getElementsByTagName("textarea");
    for (var i=0; i < texts.length; i++) {
        checkElementContrast(texts[i]);
    }
});

function checkElementContrast(element)
{
    var isFgUndefined = (getComputedStyle(element).color == getDefaultComputedStyle(element).color);
    var isBgUndefined = (getComputedStyle(element).backgroundColor == getDefaultComputedStyle(element).backgroundColor)
                      && (getComputedStyle(element).backgroundImage == 'none'); //Background image is not set

    if (isFgUndefined && isBgUndefined) {
        // Both undefined, continue with children
        var children = element.children
        for (var i=0; i < children.length; i++) {
            // Don't look at non-renderable elements
            switch (element.children[i].nodeName) {
                case "HEAD":
                case "TITLE":
                case "META":
                case "SCRIPT":
                case "IMG":
                case "STYLE":
                    break;
                default:
                    checkElementContrast(element.children[i]);
            }
        }
    } else if (isFgUndefined) {
        element.style.color = darkColor;
    } else if (isBgUndefined) {
        element.style.backgroundColor = lightColor;
    }

    return;
}

Add checks for input and textarea elements, as they do not inherit styles.


'use strict';
const assertJump = require('./helpers/assertJump');

var Ownable = artifacts.require('../contracts/ownership/Ownable.sol');

contract('Ownable', function(accounts) {
  let ownable;

  beforeEach(async function() {
    ownable = await Ownable.new();
  });

  it('should have an owner', async function() {
    let owner = await ownable.owner();
    assert.isTrue(owner !== 0);
  });

  it('changes owner after transfer', async function() {
    let other = accounts[1];
    await ownable.transferOwnership(other);
    let owner = await ownable.owner();

    assert.isTrue(owner === other);
  });

  it('should prevent non-owners from transfering', async function() {
    const other = accounts[2];
    const owner = await ownable.owner.call();
    assert.isTrue(owner !== other);
    try {
      await ownable.transferOwnership(other, {from: other});
    } catch(error) {
      assertJump(error);
    }
  });

  it('should guard ownership against stuck state', async function() {
    let originalOwner = await ownable.owner();
    try {
      await ownable.transferOwnership(null, {from: originalOwner});
    } catch(error) {
      assertJump(error);
    }
  });

});

'use strict';
const assertJump = require('./helpers/assertJump');

var Ownable = artifacts.require('../contracts/ownership/Ownable.sol');

contract('Ownable', function(accounts) {
  let ownable;

  beforeEach(async function() {
    ownable = await Ownable.new();
  });

  it('should have an owner', async function() {
    let owner = await ownable.owner();
    assert.isTrue(owner !== 0);
  });

  it('changes owner after transfer', async function() {
    let other = accounts[1];
    await ownable.transferOwnership(other);
    let owner = await ownable.owner();

    assert.isTrue(owner === other);
  });

  it('should prevent non-owners from transfering', async function() {
    const other = accounts[2];
    const owner = await ownable.owner.call();
    assert.isTrue(owner !== other);
    try {
      await ownable.transferOwnership(other, {from: other});
    } catch(error) {
      assertJump(error);
    }
  });

  it('should guard ownership against stuck state', async function() {
    let originalOwner = await ownable.owner();
    try {
      await ownable.transferOwnership(null, {from: originalOwner});
      assert.fail()
    } catch(error) {
      assertJump(error);
    }
  });

});

Add assert to prevent regression


$(document).foundation();

$('#all_username').keydown(function(event) {
    if (event.keyCode == 13) {
      var reportAPI = "http://localhost:5000/v1/api/watson/data/"+$("#all_username").val()+"/reports";

      $('#all_entries tbody').empty();
      $.getJSON(reportAPI, function(data) {
        $.each(data, function(n, i) {
          console.log(i);
          $('#all_entries tbody').append('
'+i.user+'	'+i.date+'	'+i.payload+'
');
        });
      });

    };
});

$("#new_button").click(function() {

  var reportAPI = "http://localhost:5000/v1/api/watson/data/"+$("#new_username").val()+"/reports/"+$("#new_date").val();

  $.ajax({
    type: 'PUT',
    dataType: 'json',
    url: reportAPI,
    headers: {"X-HTTP-Method-Override": "PUT"}
  });
});

$(document).foundation();

$('#all_username').keydown(function(event) {
    if (event.keyCode == 13) {
      var reportAPI = "http://localhost:5000/v1/api/watson/data/"+$("#all_username").val()+"/reports";

      $('#all_entries tbody').empty();
      $.getJSON(reportAPI, function(data) {
        $.each(data, function(n, i) {
          console.log(i);
          var payload = JSON.stringify(i.payload);
          $('#all_entries tbody').append('
'+i.user+'	'+i.date+'	'+ payload +'
');
        });
      });

    };
});

$("#new_button").click(function() {

  var reportAPI = "http://localhost:5000/v1/api/watson/data/"+$("#new_username").val()+"/reports/"+$("#new_date").val();

  $.ajax({
    type: 'PUT',
    dataType: 'json',
    url: reportAPI,
    headers: {"X-HTTP-Method-Override": "PUT"}
  });
});

Add better object output on dashboard


var mongoose     = require('mongoose');
var Schema       = mongoose.Schema;

var Objectid = mongoose.Schema.Types.ObjectId;

var ProductTypeSchema   = new Schema({
	name: { type: String, required: true, index: true },
	fire_action: String,
	price_recommendation_formula: String,
	bookable: { type: Boolean, default: false },
	bookable_noun: String,
	bookable_time_units: Number,
	_owner_id: Objectid,
	_deleted: { type: Boolean, default: false, index: true },
}, {
	timestamps: true
});

ProductTypeSchema.set("_perms", {
	setup: "crud",
	admin: "r",
	all: "r"
});

module.exports = mongoose.model('ProductType', ProductTypeSchema);

const mongoose     = require('mongoose');
const Schema       = mongoose.Schema;
const Objectid = mongoose.Schema.Types.ObjectId;
const friendly = require("mongoose-friendly");

const ProductTypeSchema   = new Schema({
	name: { type: String, required: true, index: true },
	urlid: { type: String, unique: true, index: true },
	fire_action: String,
	price_recommendation_formula: String,
	bookable: { type: Boolean, default: false },
	bookable_noun: String,
	bookable_time_units: String,
	img: String,
	_owner_id: Objectid,
	_deleted: { type: Boolean, default: false, index: true },
}, {
	timestamps: true
});

ProductTypeSchema.plugin(friendly, {
	source: 'name',
	friendly: 'urlid'
});

ProductTypeSchema.set("_perms", {
	setup: "crud",
	admin: "r",
	all: "r"
});

module.exports = mongoose.model('ProductType', ProductTypeSchema);

Enhance product type for bookable items


/*jslint*/

(function(){

    var testElement = '
'+
                        ''+
                      '
';

    function teardown() {
        $('#testElement').remove();
    }

    function setup() {
        $('body').append(testElement);
    }

    module("Context binding", {teardown: teardown, setup: setup });

        test("Context inside viewController init", function() {

            var viewController = {
               init : function init() {
                   equal(init, this.init, 'context of this, is this view controller');
                   this.test();
               },
               test : function() {
                   ok(true, 'test function can be accessed from init function, using this');
                   this.test2();
               },
               test2 : function() {
                   ok(true, 'test2 function can be accessed from test function, using this');
               }
            };

            $('#testElement').capture(viewController);
            expect(3);

        });

        test("Context inside viewController events", function() {

            var viewController = {
                onclick : {
                    '.link': function(e) {
                        equal(e.target, $('#testElement .link')[0], 'e target is element clicked on');
                        equal(this.element[0], $('#testElement')[0], 'this.element is the viewController bound unto');
                    }
                }
            };

            $('#testElement').capture(viewController);

            $('#testElement .link').click();

        });

}());

/*jslint*/

(function(){

    var testElement = '
'+
                        ''+
                      '
';

    function teardown() {
        $('#testElement').remove();
    }

    function setup() {
        $('body').append(testElement);
    }

    module("Context binding", {teardown: teardown, setup: setup });

        test("Context inside viewController init", function() {

            var viewController = {
               init : function init() {
                   // IE doesn't recognise named functions reference and contexual function ref as the same in
                   // in this instance. We must convert toString() to test equality
                   equal(init.toString(), this.init.toString(), 'context of this, is this view controller');
                   this.test();
               },
               test : function() {
                   ok(true, 'test function can be accessed from init function, using this');
                   this.test2();
               },
               test2 : function() {
                   ok(true, 'test2 function can be accessed from test function, using this');
               }
            };

            $('#testElement').capture(viewController);
            expect(3);

        });

        test("Context inside viewController events", function() {

            var viewController = {
                onclick : {
                    '.link': function(e) {
                        equal(e.target, $('#testElement .link')[0], 'e target is element clicked on');
                        equal(this.element[0], $('#testElement')[0], 'this.element is the viewController bound unto');
                    }
                }
            };

            $('#testElement').capture(viewController);

            $('#testElement .link').click();

        });

}());

Fix for test in IE. Conditions were passing, but due to IE's JS implementation of named functions, it didn't recognise the equality test as equal. Further info in test.


// Register ContentNode types (including meta information).

ContentNode.types = {
  "document": {
    name: "Document",
    allowedChildren: ["section"],
    properties: [
      {
        "key": "title",
        "name": "Title",
        "defaultValue": "A document title"
      }
    ]
  },
  "section": {
    name: "Section",
    allowedChildren: ["text", "image"],
    properties: [
      {
        "key": "name",
        "name": "Section name",
        "defaultValue": "Hi, I'm a new section "
      }
    ]
  },
  "text": {
    name: "Text",
    allowedChildren: [],
    properties: [
      {
        "key": "content",
        "name": "Content",
        "defaultValue": "Hi, I'm a another text node."
      }
    ]
  },
  "image": {
    name: "Image",
    allowedChildren: [],
    properties: [
      {
        "key": "url",
        "name": "Image URL",
        "defaultValue": null
      }
    ]
  }
};

// Register ContentNode types (including meta information).

ContentNode.types = {
  "/type/document": {
    name: "Document",
    allowedChildren: ["section"],
    properties: [
      {
        "key": "title",
        "name": "Title",
        "defaultValue": "A document title"
      }
    ]
  },
  "/type/section": {
    name: "Section",
    allowedChildren: ["text", "image"],
    properties: [
      {
        "key": "name",
        "name": "Section name",
        "defaultValue": "Hi, I'm a new section "
      }
    ]
  },
  "/type/text": {
    name: "Text",
    allowedChildren: [],
    properties: [
      {
        "key": "content",
        "name": "Content",
        "defaultValue": "Hi, I'm a another text node."
      }
    ]
  },
  "/type/image": {
    name: "Image",
    allowedChildren: [],
    properties: [
      {
        "key": "url",
        "name": "Image URL",
        "defaultValue": null
      }
    ]
  }
};

Move over to Data.Graph compliant types.


var $ = require('jquery');

$(document).ready(function() {
  var $body = $('body');

  // Allow links to specify a HTTP method using `data-method`
  $('[data-method]').on('click', function(event) {
    event.preventDefault();

    var url = $(this).attr('href');
    var method = $(this).data('method');

    var csrfToken = $('meta[name=csrf-token]').attr('content');

    var $form = $('<form method="post" action="' + url + '"></form>');
    var metadataInput = '<input name="_method" value="' + method + '" type="hidden" />';

    if (csrfToken !== undefined) {
      metadataInput += '<input name="_token" value="' + csrfToken + '" type="hidden" />';
    }

    $form.hide().append(metadataInput);
    $body.append($form);

    $form.submit();
  });
});

var $ = require('jquery');

$(document).ready(function() {
  var $body = $('body');

  // Allow links to specify a HTTP method using `data-method`
  $('[data-method]').on('click', function(event) {
    event.preventDefault();

    var url = $(this).attr('href');
    var method = $(this).data('method');

    var formItem = $(this).data('form-item');
    var formValue = $(this).data('form-value');

    var csrfToken = $('meta[name=csrf-token]').attr('content');

    var $form = $('<form method="post" action="' + url + '"></form>');
    var metadataInput = '<input name="_method" value="' + method + '" type="hidden" />';

    if (csrfToken !== undefined) {
      metadataInput += '<input name="_token" value="' + csrfToken + '" type="hidden" />';
    }

    if (formItem !== undefined && formValue !== undefined) {
      metadataInput += '<input name="' + formItem + '" value="' + formValue + '" type="hidden" />';
    }

    $form.hide().append(metadataInput);
    $body.append($form);

    $form.submit();
  });
});

Allow one field payload on method links.


/*global describe, it */
'use strict';

var assert = require('assert'),
    fs = require('fs'),
    wayback = require('../lib/wayback');

describe('wayback', function() {
  it('should throw an error on timeout', function(done) {
    // Force a timeout
    this.timeout(10000);
    var url = 'http://automata.cc/thumb-chuck-wiimote.png',
        options = {'timeout': 100};
    wayback.getClosest(url, options, function(err, resp) {
      assert.ok(err != null);
      assert.ok(err instanceof Error);
      assert.ok(resp === null);
      done();
    });
  });
});

/*global describe, it */
'use strict';

var assert = require('assert'),
    fs = require('fs'),
    wayback = require('../lib/wayback');

var url = 'http://automata.cc/thumb-chuck-wiimote.png';

describe('wayback', function() {
  describe('getClosest', function() {
    it('should get the closest url on archive', function(done) {
      wayback.getClosest(url, function(err, resp) {
        assert.ok(err === null);
        assert.ok(resp instanceof Object);
        assert.equal(resp.available, true);
        done();
      });
    });
    it('should get the closest url while using options', function(done) {
      this.timeout(6000);
      var options = {'timeout': 5000};
      wayback.getClosest(url, options, function(err, resp) {
        assert.ok(err === null);
        assert.ok(resp instanceof Object);
        assert.equal(resp.available, true);
        done();
      });
    });
    it('should throw an error on timeout', function(done) {
      // Force a timeout
      this.timeout(10000);
      var options = {'timeout': 100};
      wayback.getClosest(url, options, function(err, resp) {
        assert.ok(err != null);
        assert.ok(err instanceof Error);
        assert.ok(resp === null);
        done();
      });
    });
  });
});

Add more tests to getClosest


/*jslint browser: true*/
/*global require, define */

/**
 * @module app
 */
define('app', ['yaga'], function (Yaga) {
    'use strict';
    window.Yaga = Yaga;
});

require(['app']);

/*jslint browser: true*/
/*global require, define */

/**
 * @module app
 */
define('app', ['yaga', 'jquery', 'yaga-map', 'yaga-map-popup', 'yaga-ui-page', 'yaga-storage', 'yaga-storage-local', 'yaga-geo-json', 'yaga-layer-tile', 'yaga-ui-panel', 'yaga-map-marker', 'yaga-ui-splash-screen', 'yaga-map-icon'], function (Yaga, $, Map, Popup, Page, storage, ls, gj, tile, Panel, Marker, sc, Icon) {
    'use strict';
    window.Yaga = Yaga;
    window.Map = Map;
    window.Popup = Popup;
    window.Page = Page;
    window.Store = storage;
    window.ls = ls;
    window.gj = gj;
    window.tile = tile;
    window.Panel = Panel;
    window.Marker = Marker;
    window.Icon = Icon;
    window.sc = sc;
    sc.create();

    var map, page, osm, state;
    map = Map.create();
    map.activate();

    osm = tile.create();
    osm.show();

    state = gj.create({
        "type": "Feature",
        "properties": {"party": "Republican"},
        "geometry": {
            "type": "Polygon",
            "coordinates": [[
                [-104.05, 48.99],
                [-97.22,  48.98],
                [-96.58,  45.94],
                [-104.03, 45.94],
                [-104.05, 48.99]
            ]]
        }
    });

    state.show();

    page = Page.create({content: {content: map.domRoot}, title: 'YAGA - Yet another geo application'});

    window.document.body.appendChild(page.domRoot);
    $(window.document).ready(function () {
        page.open();
    });
});

require(['app']);

Add some hooks to get access in development mode


var crypto = require('crypto')
  , qs = require('querystring')
  ;

function sha1 (key, body) {
  return crypto.createHmac('sha1', key).update(body).digest('base64')
}

function hmacsign (httpMethod, base_uri, params, consumer_secret, token_secret, body) {
  // adapted from https://dev.twitter.com/docs/auth/oauth
  var base =
    httpMethod + "&" +
    encodeURIComponent(  base_uri ) + "&" +
    Object.keys(params).sort().map(function (i) {
      // big WTF here with the escape + encoding but it's what twitter wants
      return encodeURIComponent(qs.escape(i)) + "%3D" + encodeURIComponent(qs.escape(params[i]))
    }).join("%26")
  var key = consumer_secret + '&'
  if (token_secret) key += token_secret
  return sha1(key, base)
}

exports.hmacsign = hmacsign

var crypto = require('crypto')
  , qs = require('querystring')
  ;

function sha1 (key, body) {
  return crypto.createHmac('sha1', key).update(body).digest('base64')
}

function hmacsign (httpMethod, base_uri, params, consumer_secret, token_secret, body) {
  // adapted from https://dev.twitter.com/docs/auth/oauth
  var base =
    httpMethod + "&" +
    encodeURIComponent(  base_uri ) + "&" +
    Object.keys(params).sort().map(function (i) {
      // big WTF here with the escape + encoding but it's what twitter wants
      return encodeURIComponent(escape(i)) + "%3D" + encodeURIComponent(escape(params[i]))
    }).join("%26")
  var key = consumer_secret + '&'
  if (token_secret) key += token_secret
  return sha1(key, base)
}

exports.hmacsign = hmacsign

Fix encoding of characters like (


// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/docs/referenceConf.js

/*global jasmine */
var SpecReporter = require('jasmine-spec-reporter');

exports.config = {
  allScriptsTimeout: 11000,
  specs: [
    './e2e/**/*.e2e-spec.ts'
  ],
  capabilities: {
    'browserName': 'chrome'
  },
  directConnect: true,
  baseUrl: 'http://localhost:4200/',
  framework: 'jasmine',
  jasmineNodeOpts: {
    showColors: true,
    defaultTimeoutInterval: 30000,
    print: function() {}
  },
  useAllAngular2AppRoots: true,
  beforeLaunch: function() {
    require('ts-node').register({
      project: 'e2e'
    });
  },
  onPrepare: function() {
    jasmine.getEnv().addReporter(new SpecReporter());
  }
};

// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/docs/referenceConf.js

/*global jasmine */
var SpecReporter = require('jasmine-spec-reporter');

exports.config = {
  allScriptsTimeout: 11000,
  getPageTimeout: 60000,
  specs: [
    './e2e/**/*.e2e-spec.ts'
  ],
  capabilities: {
    'browserName': 'chrome'
  },
  directConnect: true,
  baseUrl: 'http://localhost:4200/',
  framework: 'jasmine',
  jasmineNodeOpts: {
    showColors: true,
    defaultTimeoutInterval: 30000,
    print: function() {}
  },
  useAllAngular2AppRoots: true,
  beforeLaunch: function() {
    require('ts-node').register({
      project: 'e2e'
    });
  },
  onPrepare: function() {
    jasmine.getEnv().addReporter(new SpecReporter());
  }
};

Set getPageTimeout to 60 seconds


gAudio = (function(context) {
  AudioChannel = function() {
    this.gain = context.createGain();
    this.gain.gain.value = 0;
    this.gain.connect(context.destination);

    return this;
  }
  AudioChannel.prototype.start = function() {
    var now = context.currentTime;
    this.gain.gain.linearRampToValueAtTime(0, now);
    this.gain.gain.linearRampToValueAtTime(0.1, now + 0.01);
    this.osc = context.createOscillator();
    this.osc.connect(this.gain);
    this.osc.start();
  }
  AudioChannel.prototype.stop = function(delay) {
    this.osc.stop(delay);
  }

  return {
    // context: context,
    eChan1: new AudioChannel(),
    startBolt: function() {
      var now = context.currentTime;
      this.eChan1.start();
      this.eChan1.osc.frequency.linearRampToValueAtTime(1200, now);
      this.eChan1.osc.frequency.linearRampToValueAtTime(400, now + 0.1);
      this.eChan1.gain.gain.linearRampToValueAtTime(0.1, now + 0.1);
      this.eChan1.gain.gain.linearRampToValueAtTime(0, now + 0.11);
      this.eChan1.stop(now + 0.11);
    }
  }
})(new AudioContext());

gAudio = (function(context) {
  AudioChannel = function() {
    this.gain = context.createGain();
    this.gain.gain.value = 0;
    this.gain.connect(context.destination);

    return this;
  }
  AudioChannel.prototype.start = function(freq, delay) {
    var start = context.currentTime;
    if (typeof delay === 'number') start += delay;
    this.gain.gain.linearRampToValueAtTime(0, start);
    this.gain.gain.linearRampToValueAtTime(0.1, start + 0.01);
    this.osc = context.createOscillator();
    this.osc.connect(this.gain);
    if (freq > 0) this.osc.frequency.value = freq;
    this.osc.start(start);
  }
  AudioChannel.prototype.stop = function(delay) {
    this.osc.stop(delay);
  }

  return {
    eChan1: new AudioChannel(),
    startBolt: function() {
      var now = context.currentTime;
      this.eChan1.start(now);
      this.eChan1.osc.frequency.linearRampToValueAtTime(1200, now);
      this.eChan1.osc.frequency.linearRampToValueAtTime(400, now + 0.1);
      this.eChan1.gain.gain.linearRampToValueAtTime(0.1, now + 0.1);
      this.eChan1.gain.gain.linearRampToValueAtTime(0, now + 0.11);
      this.eChan1.stop(now + 0.11);
    }
  }
})(new AudioContext());

Update AudioChannel start to accept start frequency and delay


import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';

// =========== Components ===========
import App from '../components/app';
import NotFound from '../components/public-pages/not-found';
// Layout
import Header from '../components/navigation/header';
import Footer from '../components/navigation/footer';
// authentication
import Register from '../components/auth/register';
import SignIn from '../components/auth/sign-in';
import Profile from '../components/auth/profile';

export default (
  <Router>
    <div>
      <Header />
      <Switch>
        <Route exact path="/" component={App} />
        <Route path="/register" component={Register} />
        <Route path="/sign_in" component={SignIn} />
        <Route path="/profile" component={Profile} />
        <Route component={NotFound} />
      </Switch>
      <footer />
    </div>
  </Router>
)

import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';

// =========== Components ===========
import App from '../components/app';
import NotFound from '../components/public-pages/not-found';
// Products
import Products from '../components/products/';
// Layout
import Header from '../components/navigation/header';
import Footer from '../components/navigation/footer';
// authentication
import Register from '../components/auth/register';
import SignIn from '../components/auth/sign-in';
import Profile from '../components/auth/profile';
// Hide order component
import RequireAuth from '../components/auth/hoc/require-auth';
import HideAuth from '../components/auth/hoc/hide-auth';

export default (
  <Router>
    <div>
      <Header />
      <Switch>
        <Route exact path="/" component={App} />
        <Route path="/register" component={HideAuth(Register)} />
        <Route path="/sign_in" component={HideAuth(SignIn)} />
        <Route path="/profile" component={RequireAuth(Profile)} />
        <Route path="/products" component={HideAuth(Products)} />
        <Route component={NotFound} />
      </Switch>
      <footer />
    </div>
  </Router>
)

Hide components if user is logged


/* globals $, symbols */

// Disable form submission (eg. via Enter key)
$('form').submit(false)

$('textarea, input, select').on('change keyup', function () {
  updateQrCode()
})

const updateQrCode = function () {
  const size = 350

  $('#error-container').hide()
  $('#qr').empty()
  $('#qr').qrcode({
    render: $('input[name=qr-format]:checked').val(),
    width: size,
    height: size,
    size: size,
    color: '#3a3',
    text: $('#qr-data').val(),
    textOverlay: $('#qr-overlay').val(),
    textOverlayTransparency: !$('#qr-overlay-transparency').is(':checked'),
    textOverlayBorder: $('#qr-overlay-border').is(':checked'),
    ecLevel: 'H',
    minVersion: 5,
    symbols: symbols[$('#qr-overlay-font').val()]
  })
}

updateQrCode()

/* globals $, symbols */

// Disable form submission (eg. via Enter key)
$('form').submit(false)

$('textarea, input, select').on('change keyup', function () {
  updateQrCode()
})

const updateQrCode = function () {
  const size = 350

  $('#error-container').hide()
  $('#qr').empty()
  $('#qr').qrcode({
    render: $('input[name=qr-format]:checked').val(),
    width: size,
    height: size,
    size: size,
    color: '#3a3',
    text: $('#qr-data').val(),
    textOverlay: $('#qr-overlay').val(),
    textOverlayTransparency: !$('#qr-overlay-transparency').is(':checked'),
    textOverlayBorder: $('#qr-overlay-border').is(':checked'),
    ecLevel: 'H',
    minVersion: 5,
    symbols: symbols[$('#qr-overlay-font').val()]
  })
}

// https://stackoverflow.com/a/21903119/1323144
const getUrlParameter = function (sParam) {
  const sPageURL = window.location.search.substring(1)
  const sURLVariables = sPageURL.split('&')
  let sParameterName
  let i

  for (i = 0; i < sURLVariables.length; i++) {
    sParameterName = sURLVariables[i].split('=')

    if (sParameterName[0] === sParam) {
      return typeof sParameterName[1] === 'undefined' ? true : decodeURIComponent(sParameterName[1])
    }
  }
  return false
}

// On page load, set data+text from url param if present
$(function () {
  const tParam = getUrlParameter('t')
  const dParam = getUrlParameter('d')
  if (tParam) {
    $('#qr-data').val(dParam)
    $('#qr-overlay').val(tParam)
  }
  // Refresh QR code
  updateQrCode()
})

Set initial data/text values from URL param


var express = require('express');
var router = express.Router();

/* Note: this is boilerplate and has NOT been implemented yet */
router.get('/', function(req,res,next){
  res.render('contacts_list')
})

/* Note: this is boilerplate and has NOT been implemented yet */
router.get('/{id}', function(req, res, next){
  res.render('contact_info', {contact_id: id})
})

/* Note: this is boilerplate and has NOT been implemented yet */
router.post('/', function(req, res, next){
  res.render('contacts_create', {data: req.params}) // Is this right?
})

/* Note: this is boilerplate and has NOT been implemented yet */
router.put('/{id}', function(req, res, next){
  res.render('contacts_update', {group_id: id, data: req.params}) // Is this right?
})

/* Note: this is boilerplate and has NOT been implemented yet */
router.delete('/{id}', function(req, res, next){
  res.render('contacts_delete', {group_id: id})
})

module.exports = router;

var express = require('express');
var router = express.Router();
var dbClient = require('mariasql');

/* Note: this is boilerplate and has NOT been implemented yet */
router.get('/', function(req,res,next){
  //res.render('contacts_list')
})

/* Note: this is boilerplate and has NOT been implemented yet */
router.get('/{id}', function(req, res, next){
  //res.render('contact_info', {contact_id: id})
})

/* Note: this is boilerplate and has NOT been implemented yet */
router.post('/', function(req, res, next){
  //res.render('contacts_create', {data: req.params}) // Is this right?

  // Add a random contact
  let client = new dbClient({
    host: 'localhost',
    user: 'root',
    password: ''
  });

  let prep = client.prepare("INSERT INTO `voluble`.`contacts` (`first_name`, `surname`, `email_address`, `default_servicechain`) VALUES (?, ?, ?, '1')")
  client.query(prep([req.body.first_name, req.body.surname, req.body.email_address]), function(err, rows){
    if (err)
      throw err;

    console.dir(rows);
  })

  res.send(`Inserted ${req.body.first_name} ${req.body.surname}!`);

})

/* Note: this is boilerplate and has NOT been implemented yet */
router.put('/{id}', function(req, res, next){
  res.render('contacts_update', {group_id: id, data: req.params}) // Is this right?
})

/* Note: this is boilerplate and has NOT been implemented yet */
router.delete('/{id}', function(req, res, next){
  res.render('contacts_delete', {group_id: id})
})

module.exports = router;

Add new contact to DB from POST request


/* jshint node: true */

module.exports = function(environment) {
  var ENV = {
    i18n: {
      defaultLocale: 'en',
    },
    emblemOptions: {
      blueprints: false
    },
    modulePrefix: 'irene',
    environment: environment,
    rootURL: '/',
    locationType: 'auto',
    EmberENV: {
      FEATURES: {
        // Here you can enable experimental features on an ember canary build
        // e.g. 'with-controller': true
      }
    },

    APP: {
      // Here you can pass flags/options to your application instance
      // when it is created
    },
    'ember-simple-auth': {
      loginEndPoint: '/login',
      checkEndPoint: '/check',
      logoutEndPoint: '/logout'
    }
  };

  if (environment === 'development') {
    // ENV.APP.LOG_RESOLVER = true;
    // ENV.APP.LOG_ACTIVE_GENERATION = true;
    // ENV.APP.LOG_TRANSITIONS = true;
    // ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
    // ENV.APP.LOG_VIEW_LOOKUPS = true;
  }

  if (environment === 'test') {
    // Testem prefers this...
    ENV.locationType = 'none';

    // keep test console output quieter
    ENV.APP.LOG_ACTIVE_GENERATION = false;
    ENV.APP.LOG_VIEW_LOOKUPS = false;

    ENV.APP.rootElement = '#ember-testing';
  }

  if (environment === 'production') {

  }

  return ENV;
};

/* jshint node: true */

module.exports = function(environment) {
  var ENV = {
    i18n: {
      defaultLocale: 'en',
    },
    emblemOptions: {
      blueprints: false
    },
    modulePrefix: 'irene',
    environment: environment,
    rootURL: '/',
    locationType: 'auto',
    EmberENV: {
      FEATURES: {
        // Here you can enable experimental features on an ember canary build
        // e.g. 'with-controller': true
      }
    },

    APP: {
      // Here you can pass flags/options to your application instance
      // when it is created
    },
    'ember-simple-auth': {
      loginEndPoint: '/login',
      checkEndPoint: '/check',
      logoutEndPoint: '/logout'
    }
  };

  if (environment === 'production') {
    ENV['ember-cli-mirage'] = {
      enabled: true
    };
  }

  if (environment === 'development') {
    // ENV.APP.LOG_RESOLVER = true;
    // ENV.APP.LOG_ACTIVE_GENERATION = true;
    // ENV.APP.LOG_TRANSITIONS = true;
    // ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
    // ENV.APP.LOG_VIEW_LOOKUPS = true;
  }

  if (environment === 'test') {
    // Testem prefers this...
    ENV.locationType = 'none';

    // keep test console output quieter
    ENV.APP.LOG_ACTIVE_GENERATION = false;
    ENV.APP.LOG_VIEW_LOOKUPS = false;

    ENV.APP.rootElement = '#ember-testing';
  }

  if (environment === 'production') {

  }

  return ENV;
};

Enable mirage on production for now


'use strict'

import angular from 'angular'
import ngMockE2E from 'angular-mocks/ngMockE2E'

let status

export default angular.module('payment.backend', [
  ngMockE2E
])
.value('backend', {
  succeeds () {
    status = 200
  }
  fails () {
    status = 402
  }
})
.run(server)
.name

server.$inject = ['$httpBackend']
function server ($httpBackend) {
  $httpBackend.whenPOST('/payments').respond(() => {
    return [status]
  })
}

'use strict'

import angular from 'angular'
import ngMockE2E from 'angular-mocks/ngMockE2E'

let status

export default angular.module('payment.backend', [
  ngMockE2E
])
.value('backend', {
  succeeds: () => {
    status = 200
  },
  fails: () => {
    status = 402
  }
})
.run(server)
.name

server.$inject = ['$httpBackend']
function server ($httpBackend) {
  $httpBackend.whenPOST('/payments').respond(() => {
    return [status]
  })
}

Fix syntax error in backend


import commands from './commands';
import { client as slackClient } from './slack';

const TEAM_DOMAIN = process.env.SLACK_TEAM_DOMAIN;
const TOKEN = process.env.SLACK_COMMAND_TOKEN;
const CHANNEL = process.env.SLACK_COMMAND_CHANNEL;

function validate(hookData) {
  if (TEAM_DOMAIN !== hookData.team_domain) {
    return Promise.reject(new Error('Invalid team domain.'));
  }
  if (TOKEN !== hookData.token) {
    return Promise.reject(new Error('Invalid token.'));
  }
  if (CHANNEL !== hookData.channel_id) {
    return Promise.reject(new Error('Invalid channel'));
  }
  return Promise.resolve(true);
}

function hookRequest(req, res) {
  const hookData = req.body;
  const commandFn = commands[hookData.command];

  res.set('Content-Type', 'text/plain');
  if (!commandFn) {
    return res.send(400, 'No command given');
  }
  validate(hookData)
    .then(() => res.send(200, ''))
    .catch((err) => res.send(500, err.message))
    .then(() => commandFn(hookData))
    .then((response) =>
      slackClient().send({ channel: hookData.channel_name, text: response })
    )
    .catch((err) => console.error('Error processing webhook:', err));
}

export default hookRequest;

import commands from './commands';
import { client as slackClient } from './slack';

const TEAM_DOMAIN = process.env.SLACK_TEAM_DOMAIN;
const TOKEN = process.env.SLACK_COMMAND_TOKEN;
const CHANNEL = process.env.SLACK_COMMAND_CHANNEL;

function validate(hookData) {
  if (TEAM_DOMAIN !== hookData.team_domain) {
    return Promise.reject(new Error('Invalid team domain.'));
  }
  if (TOKEN !== hookData.token) {
    return Promise.reject(new Error('Invalid token.'));
  }
  if (CHANNEL !== hookData.channel_id) {
    return Promise.reject(new Error('Invalid channel'));
  }
  return Promise.resolve(true);
}

function hookRequest(req, res) {
  const hookData = req.body;
  const commandFn = commands[hookData.command];

  res.set('Content-Type', 'text/plain');
  if (!commandFn) {
    return res.send(400, 'No command given');
  }
  validate(hookData)
    .then(() => res.send(200, 'Ok, processing.'))
    .catch((err) => res.send(500, err.message))
    .then(() => commandFn(hookData))
    .then((response) =>
      slackClient().send({ channel: hookData.channel_name, text: response })
    )
    .catch((err) => console.error('Error processing webhook:', err));
}

export default hookRequest;

Add acknowledgement about command processing


module.exports = {
  extends: [
    'plugin:ember/recommended',
    require.resolve('./base.js')
  ],
  env: {
    'browser': true
  },
  rules: {
    'new-cap': [ 'error', { 'capIsNewExceptions': [ 'A' ] } ],

    // Custom rules
    'ember/no-empty-attrs': 'error',
    // We actually do not want to turn this rule off. However,
    // the current rule has a bug. This rule does not take into
    // account the new module import syntax. Until this is fixed,
    // this rule should be turned off.
    'ember/no-global-jquery': 'off'
  }
};

module.exports = {
  extends: [
    'plugin:ember/recommended',
    require.resolve('./base.js')
  ],
  env: {
    'browser': true
  },
  rules: {
    'new-cap': [ 'error', { 'capIsNewExceptions': [ 'A' ] } ],
    'no-console': [ 'error', { 'allow': [ 'warn', 'error' ] } ],

    // Custom rules
    'ember/no-empty-attrs': 'error',
    // We actually do not want to turn this rule off. However,
    // the current rule has a bug. This rule does not take into
    // account the new module import syntax. Until this is fixed,
    // this rule should be turned off.
    'ember/no-global-jquery': 'off'
  }
};

Update no-console rule for Ember


import Vue from 'vue';
import Router from 'vue-router';
import FileManager from 'components/file-manager/FileManager';
import Content from 'components/editor-viewer/Content';

Vue.use(Router);

export default new Router({
  routes: [
    {
      path: '/',
      name: 'fileManager',
      component: FileManager,
    },
    {
      path: '/content',
      name: 'Content',
      component: Content,
    },
  ],
});

import Vue from 'vue';
import Router from 'vue-router';
import FileManager from 'components/file-manager/FileManager';
import Content from 'components/editor-viewer/Content';

Vue.use(Router);

export default new Router({
  mode: 'history',
  scrollBehavior(to, from, savedPosition) {
    if (to.hash) {
      return { selector: to.hash };
    } else {
      return { x: 0, y: 0 };
    }
  },
  routes: [
    {
      path: '/',
      name: 'fileManager',
      component: FileManager,
    },
    {
      path: '/content',
      name: 'Content',
      component: Content,
    },
  ],
});

Change vue router to use history mode


import { expect } from 'chai';

import {
  setTheme,
  setStoredThemeObservable,
} from '../../../src/notebook/epics/theming';

describe('setTheme', () => {
  it('creates an action of type SET_THEME with a theme', () => {
    expect(setTheme('disco')).to.deep.equal({type: 'SET_THEME', theme: 'disco'})
  });
});

describe('setStoredThemeObservable', () => {
  it('returns an observable', () => {
    const setStoredThemeObs = setStoredThemeObservable('disco');
    expect(setStoredThemeObs.subscribe).to.not.be.null;
  });
});

import chai, { expect } from 'chai';

import sinon from 'sinon';
import sinonChai from 'sinon-chai';

chai.use(sinonChai);

import {
  setTheme,
  setStoredThemeObservable,
} from '../../../src/notebook/epics/theming';

import storage from 'electron-json-storage';

describe('setTheme', () => {
  it('creates an action of type SET_THEME with a theme', () => {
    expect(setTheme('disco')).to.deep.equal({type: 'SET_THEME', theme: 'disco'})
  });
});

describe('setStoredThemeObservable', () => {
  it('returns an observable', () => {
    const setStoredThemeObs = setStoredThemeObservable('disco');
    expect(setStoredThemeObs.subscribe).to.not.be.null;
  });
  it('should set the value in the store', (done) => {
    const setStoredThemeObs = setStoredThemeObservable('disco');
    const set = sinon.spy(storage, 'set');

    setStoredThemeObs.subscribe(() => {
      expect(set).to.be.called;
      done();
    });
  });
  it('should return an error if not given a theme', (done) => {
    const setStoredThemeObs = setStoredThemeObservable();
    const set = sinon.spy(storage, 'set');

    setStoredThemeObs.subscribe(() => {
      throw new Error('Observable invalidly set theme.');
    }, (error) => {
      expect(set).to.be.called;
      expect(error.message).to.equal('Must provide JSON and key');
    });

    done();
  });
});

Add tests for error in setTheme


// models/shift.js

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

  const mongoose = require('mongoose');

  const ItemSchema = new mongoose.Schema({
    username: { type: String, required: true, unique: true },
    password: { type: String, required: true },
  });

  const User = mongoose.model('User', ItemSchema);

  module.exports = User;
}

"use strict";

const mongoose = require("mongoose");

const ItemSchema = new mongoose.Schema({
    username:       { type: String, required: true, unique: true },
    password:       { type: String, required: true },
    email:          { type: String, required: false },
    firstName:      { type: String, required: false },
    lastName:       { type: String, required: false }
});

const User = mongoose.model("User", ItemSchema);

module.exports = User;

Add email and name fields to the database User model


export const CLASSNAME = 'list-graph';

export const SCROLLBAR_WIDTH = 6;
export const COLUMNS = 5;
export const ROWS = 5;

// An empty path is equal to inline SVG.
export const ICON_PATH = '';

// -1 = desc, 1 = asc
export const DEFAULT_SORT_ORDER = -1;

export const DEFAULT_BAR_MODE = 'one';

export const HIGHLIGHT_ACTIVE_LEVEL = true;
export const ACTIVE_LEVEL = 0;
export const NO_ROOT_ACTIVE_LEVEL_DIFF = 0;
export const QUERYING = false;
export const HIDE_OUTWARDS_LINKS = false;

export const TRANSITION_LIGHTNING_FAST = 150;
export const TRANSITION_FAST = 200;
export const TRANSITION_SEMI_FAST = 250;
export const TRANSITION_NORMAL = 333;
export const TRANSITION_SLOW = 666;
export const TRANSITION_SLOWEST = 1;

// Gradient colors
export const COLOR_ORANGE = '#f23e00';
export const COLOR_NEGATIVE_RED = '#e0001c';
export const COLOR_POSITIVE_GREEN = '#60bf00';

export const CLASSNAME = 'list-graph';

export const SCROLLBAR_WIDTH = 6;
export const COLUMNS = 5;
export const ROWS = 5;

// An empty path is equal to inline SVG.
export const ICON_PATH = '';

// -1 = desc, 1 = asc
export const DEFAULT_SORT_ORDER = -1;

export const DEFAULT_BAR_MODE = 'one';

export const HIGHLIGHT_ACTIVE_LEVEL = true;
export const ACTIVE_LEVEL = 0;
export const NO_ROOT_ACTIVE_LEVEL_DIFF = 0;
export const QUERYING = false;
export const HIDE_OUTWARDS_LINKS = false;
export const SHOW_LINK_LOCATION = false;

export const TRANSITION_LIGHTNING_FAST = 150;
export const TRANSITION_FAST = 200;
export const TRANSITION_SEMI_FAST = 250;
export const TRANSITION_NORMAL = 333;
export const TRANSITION_SLOW = 666;
export const TRANSITION_SLOWEST = 1;

// Gradient colors
export const COLOR_ORANGE = '#f23e00';
export const COLOR_NEGATIVE_RED = '#e0001c';
export const COLOR_POSITIVE_GREEN = '#60bf00';

Add an option to show the location of where hidden links point to.


/**
 * @file stat.js
 * @license MIT
 * @copyright 2017 Karim Alibhai.
 */

const fs = require('fs')
const chalk = require('chalk')
const solver = require('solver')

const tools = process.env.TOOLS.split(',')
const results = {}
const colors = {
  gulp: 'magenta',
  grunt: 'yellow',
  fly: 'blue',
  brunch: 'green'
}

let max = [Infinity, [undefined]]

function bold(str, should) {
  if (should) {
    return chalk.underline(chalk.bold(str))
  }

  return str
}

tools
  .map(tool => {
    results[tool] = solver.data(
      fs.readFileSync(`${__dirname}/${tool}/bench.log`, 'utf8')
        .split(/\r?\n/g)
        .filter(n => !isNaN(+n))
        .map(n => +n)
    )

    return tool
  })
  .sort((a, b) => (+results[a].average() > +results[b].average()))
  .map(tool => {
    console.log('  ' + chalk[colors[tool]](tool) + ' ' + bold('average: %s ns', max[1].indexOf(tool) !== -1), results[tool].average())
    console.log('  ' + chalk[colors[tool]](tool) + ' ' + bold('deviation: %s%', max[1].indexOf(tool) !== -1), results[tool].deviation().relative * 100)
    console.log('')
  })

/**
 * @file stat.js
 * @license MIT
 * @copyright 2017 Karim Alibhai.
 */

'use strict'

const fs = require('fs')
const chalk = require('chalk')
const solver = require('solver')

const tools = process.env.TOOLS.split(',')
const results = {}
const colors = {
  gulp: 'magenta',
  grunt: 'yellow',
  fly: 'blue',
  brunch: 'green'
}

let max = [Infinity, [undefined]]

function bold(str, should) {
  if (should) {
    return chalk.underline(chalk.bold(str))
  }

  return str
}

tools
  .map(tool => {
    results[tool] = solver.data(
      fs.readFileSync(`${__dirname}/${tool}/bench.log`, 'utf8')
        .split(/\r?\n/g)
        .filter(n => !isNaN(+n))
        .map(n => +n)
    )

    return tool
  })
  .sort((a, b) => (+results[a].average() > +results[b].average()))
  .map(tool => {
    console.log('  ' + chalk[colors[tool]](tool) + ' ' + bold('average: %s ns', max[1].indexOf(tool) !== -1), results[tool].average())
    console.log('  ' + chalk[colors[tool]](tool) + ' ' + bold('deviation: %s%', max[1].indexOf(tool) !== -1), results[tool].deviation().relative * 100)
    console.log('')
  })

Add explicit use strict for node v4


/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2020
 */

import Realm from 'realm';

export class Sensor extends Realm.Object {}

Sensor.schema = {
  name: 'Sensor',
  primaryKey: 'id',
  properties: {
    id: 'string',
    macAddress: { type: 'string', optional: true },
    name: { type: 'string', optional: true },
    batteryLevel: { type: 'double', default: 0 },
    sensorLogs: { type: 'linkingObjects', objectType: 'SensorLog', property: 'sensor' },
  },
};

export default Sensor;

/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2020
 */

import Realm from 'realm';
import { NativeModules } from 'react-native';

const { BleManager } = NativeModules;

const BLUE_MAESTRO_ID = 307;

export class Sensor extends Realm.Object {
  static async startScan(macAddress = '') {
    return BleManager.getDevices(BLUE_MAESTRO_ID, macAddress);
  }

  async sendCommand(command) {
    return BleManager.sendCommand(BLUE_MAESTRO_ID, this.macAddress, command);
  }

  async blink() {
    return this.sendCommand('*blink');
  }

  async resetLogFrequency() {
    return this.sendCommand('*lint300');
  }

  async resetAdvertisementFrequency() {
    return this.sendCommand('*sadv1000');
  }

  async downloadLogs() {
    return this.sendCommand('*logall');
  }
}

Sensor.schema = {
  name: 'Sensor',
  primaryKey: 'id',
  properties: {
    id: 'string',
    macAddress: { type: 'string', optional: true },
    name: { type: 'string', optional: true },
    batteryLevel: { type: 'double', default: 0 },
    sensorLogs: { type: 'linkingObjects', objectType: 'SensorLog', property: 'sensor' },
  },
};

export default Sensor;

Add sensor native module methods


YUI.add('dp-dashboardblockrandomgifplugin', function (Y) {

    Y.namespace('DP');

    Y.DP.DashboardBlockRandomGifPlugin = Y.Base.create('dpDashboardBlockRandomGifPlugin', Y.Plugin.Base, [], {
        initializer: function () {
            this.get('host').addBlock(
                new Y.DP.DashboardBlockRandomGifView({
                    priority: 100
                })
            );
        },
    }, {
        NS: 'dpDashboardBlockRandomGifPlugin',
    });

    Y.eZ.PluginRegistry.registerPlugin(
        Y.DP.DashboardBlockRandomGifPlugin, ['dashboardBlocksView']
    );
});

YUI.add('dp-dashboardblockrandomgifplugin', function (Y) {

    Y.namespace('DP');

    Y.DP.DashboardBlockRandomGifPlugin = Y.Base.create('dpDashboardBlockRandomGifPlugin', Y.Plugin.Base, [], {
        initializer: function () {
            this.get('host').addBlock(
                new Y.DP.DashboardBlockRandomGifView({
                    priority: 2000
                })
            );
        },
    }, {
        NS: 'dpDashboardBlockRandomGifPlugin',
    });

    Y.eZ.PluginRegistry.registerPlugin(
        Y.DP.DashboardBlockRandomGifPlugin, ['dashboardBlocksView']
    );
});

Make sure the random gif dashboard block is the first one


import React, { Component } from 'react';

import VideoPreview from './VideoPreview';

class VideoPreviewsList extends Component {
  render() {
    return (

    );
  }
}

VideoPreviewsList.propTypes = {
}

export default VideoPreviewsList;

import React, { Component } from 'react';

import VideoPreview from './VideoPreview';

class VideoPreviewsList extends Component {
  render() {
    return (

        {this.props.videoPreviews.map((videoPreview, index) => ()

            <VideoPreview {...videoPreview} />

        ))}

    );
  }
}

VideoPreviewsList.propTypes = {
  videosPreviews: React.PropTypes.arrayOf(
    React.PropTypes.shape({
      videoId: React.PropTypes.string.isRequired,
      videoTitle: React.PropTypes.string.isRequired,
      videoDescription: React.PropTypes.string.isRequired,
    }).isRequired
  ).isRequired
}

export default VideoPreviewsList;

Add Proptypes and expand previewslist


export const sizing = (textSize, align) => ({
  p: {
    props: {
      size: textSize,
      margin: 'small',
      align,
      style: {
        marginTop: 0,
      },
    },
  },
  h1: {
    props: {
      margin: 'small',
      align,
      style: {
        marginTop: 0,
      },
    },
  },
  h2: {
    props: {
      margin: 'small',
      align,
      style: {
        marginTop: 0,
      },
    },
  },
  h3: {
    props: {
      margin: 'small',
      align,
      style: {
        marginTop: 0,
      },
    },
  },
  h4: {
    props: {
      margin: 'small',
      align,
      style: {
        marginTop: 0,
      },
    },
  },
  h5: {
    props: {
      margin: 'small',
      align,
      style: {
        marginTop: 0,
      },
    },
  },
});

export const sizing = (textSize, align) => ({
  a: {
    props: {
      target: '_blank',
    },
  },
  p: {
    props: {
      size: textSize,
      margin: 'small',
      align,
      style: {
        marginTop: 0,
      },
    },
  },
  h1: {
    props: {
      margin: 'small',
      align,
      style: {
        marginTop: 0,
      },
    },
  },
  h2: {
    props: {
      margin: 'small',
      align,
      style: {
        marginTop: 0,
      },
    },
  },
  h3: {
    props: {
      margin: 'small',
      align,
      style: {
        marginTop: 0,
      },
    },
  },
  h4: {
    props: {
      margin: 'small',
      align,
      style: {
        marginTop: 0,
      },
    },
  },
  h5: {
    props: {
      margin: 'small',
      align,
      style: {
        marginTop: 0,
      },
    },
  },
});

Add target blank for anchors in paragraph block.


var Ripple = require('ripple-lib')
var request = require('request')

var address = process.env.RIPPLE_ADDRESS || 'rwqhRo41zXZKooNbDNUqQ5dTRrQPQ1BDF7'
var lastTxId = process.env.LAST_TX_ID || 'E0785E2C6F7A7F84E03B05635A15C4992EA65532D650FF5CB4CE18BA3707DAD8';

function getNextNotification() {
  base = 'http://simple-ripple-listener.herokuapp.com/api/v1/';
  url = base + 'address/' + address + '/next_notification/' + lastTxId;
  request.get({ url: url, json: true }, function(err, resp, body) {
    if (body.notification) {
      lastTxId = body.notification.txHash;
      console.log('got a new notification!');
      console.log(body.notification);
      handleNewNotification(body.notification);
    } else {
      console.log('no payments, sleep 2 sec');
      setTimeout(getNextNotification, 2000);
    }
  })
}

function handleNewNotification(notification) {
  console.log(notification);
  getNextNotification();
}

getNextNotification();

var Ripple = require('ripple-lib')
var request = require('request')

var address = process.env.RIPPLE_ADDRESS;
var lastTxId = process.env.LAST_TX_ID;

function getNextNotification() {
  base = 'http://simple-ripple-listener.herokuapp.com/api/v1/';
  url = base + 'address/' + address + '/next_notification/' + lastTxId;
  request.get({ url: url, json: true }, function(err, resp, body) {
    if (body.notification) {
      lastTxId = body.notification.txHash;
      handleNewNotification(body.notification);
      getNextNotification();
    } else {
      setTimeout(getNextNotification, 2000);
    }
  })
}

function handleNewNotification(notification) {
  // Inbound
  // -- Create ripple transaction

  // Outbound
  // -- Find and update ripple transaction

  console.log(notification);
}

getNextNotification();

Add function to handle new ripple notifications


//smooth-scroller.js

(function($) {
  $.fn.smoothScroller = function(options) {
    options = $.extend({}, $.fn.smoothScroller.defaults, options);
    var el = $(this);

    $(options.scrollEl).animate({
      scrollTop: el.offset().top - $(options.scrollEl).offset().top - options.offset
    }, options.speed, options.ease, function() {
      var hash = el.attr('id');

      if(hash.length) {
        if(history.pushState) {
          history.pushState(null, null, '#' + hash);
        } else {
          document.location.hash = hash;
        }
      }

      el.trigger('smoothScrollerComplete');
    });

    return this;
  };

  $.fn.smoothScroller.defaults = {
    speed: 400,
    ease: 'swing',
    scrollEl: 'body',
    offset: 0
  };

  $('body').on('click', '[data-smoothscroller]', function(e) {
    e.preventDefault();
    var href = $(this).attr('href');

    if(href.indexOf('#') === 0) {
      $(href).smoothScroller();
    }
  });
}(jQuery));

//smooth-scroller.js

(function($) {
  $.fn.smoothScroller = function(options) {
    options = $.extend({}, $.fn.smoothScroller.defaults, options);
    var el = $(this);

    $(options.scrollEl).animate({
      scrollTop: el.offset().top - $(options.scrollEl).offset().top - options.offset
    }, options.speed, options.ease, function() {
      var hash = el.attr('id');

      if(hash.length) {
        if(history.pushState) {
          history.pushState(null, null, '#' + hash);
        } else {
          document.location.hash = hash;
        }
      }

      el.trigger('smoothScrollerComplete');
    });

    return this;
  };

  $.fn.smoothScroller.defaults = {
    speed: 400,
    ease: 'swing',
    scrollEl: 'body,html',
    offset: 0
  };

  $('body').on('click', '[data-smoothscroller]', function(e) {
    e.preventDefault();
    var href = $(this).attr('href');

    if(href.indexOf('#') === 0) {
      $(href).smoothScroller();
    }
  });
}(jQuery));

Add html to scrollEl param to fix FF


Package.describe({
  summary: "JavaScript.next-to-JavaScript-of-today compiler",
  version: "0.0.42"
});

Package._transitional_registerBuildPlugin({
  name: "harmony-compiler",
  use: [],
  sources: [
    "plugin/compiler.js"
  ],
  npmDependencies: {"traceur": "0.0.42"}
});

Package.on_use(function(api, where) {
  where = where || ['client', 'server'];

  // The location of this runtime file is not supposed to change:
  // https://github.com/google/traceur-compiler/commit/49ad82f89c593b12ac0bcdfcfd05028e79676c78
  api.add_files(".npm/plugin/harmony-compiler/node_modules/traceur/bin/traceur-runtime.js", where);
});

// Package.on_test(function (api) {
//   api.use(["harmony", "tinytest"]);
//   api.add_files("tests/test.js", ["client"]);
//   api.add_files([
//   ], ["client", "server"]);
// });

Package.describe({
  summary: "JavaScript.next-to-JavaScript-of-today compiler",
  version: "0.0.42"
});

Package._transitional_registerBuildPlugin({
  name: "harmony-compiler",
  use: [],
  sources: [
    "plugin/compiler.js"
  ],
  npmDependencies: {"traceur": "0.0.42"}
});

Package.on_use(function(api, where) {
  where = where || ['client', 'server'];

  // The location of this runtime file is not supposed to change:
  // http://git.io/B2s0Tg
  api.add_files(".npm/plugin/harmony-compiler/node_modules/traceur/bin/traceur-runtime.js", where);
});

// Package.on_test(function (api) {
//   api.use(["harmony", "tinytest"]);
//   api.add_files("tests/test.js", ["client"]);
//   api.add_files([
//   ], ["client", "server"]);
// });

Use GitHub link shortener for comment link


import MSinglex from '../m-singlex.vue';

export const UMenu = {
    name: 'u-menu',
    groupName: 'u-menu-group',
    childName: 'u-menu-item',
    extends: MSinglex,
    props: {
        router: { type: Boolean, default: true },
    },
    data() {
        return {
            parentVM: undefined,
        };
    },
    created() {
        let popperChildVM = this.$parent;
        while (popperChildVM && popperChildVM.$options.name !== 'm-popper-child')
            popperChildVM = popperChildVM.$parent;
        if (popperChildVM && popperChildVM.parentVM)
            this.parentVM = popperChildVM.parentVM;

        this.$on('select', ({ itemVM }) => {
            this.router && itemVM.navigate();
            // this.parentVM && this.parentVM.toggle(false);
        });
    },
};

export { UMenuItem } from './item.vue';
export { UMenuGroup } from './group.vue';
export { UMenuDivider } from './divider.vue';

export default UMenu;

import MSinglex from '../m-singlex.vue';

export const UMenu = {
    name: 'u-menu',
    groupName: 'u-menu-group',
    childName: 'u-menu-item',
    extends: MSinglex,
    props: {
        router: { type: Boolean, default: true },
    },
    data() {
        return {
            parentVM: undefined,
        };
    },
    created() {
        this.$on('select', ({ itemVM }) => {
            this.router && itemVM.navigate();
            this.$parent && this.$parent.close && this.$parent.close();
        });
    },
};

export { UMenuItem } from './item.vue';
export { UMenuGroup } from './group.vue';
export { UMenuDivider } from './divider.vue';

export default UMenu;

Fix popper close after clicking <u-menu>


const jpegThumbnail = dataUrl => new Promise((resolve, reject) => {
    const image = new Image();
    image.onload = () => {
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');

        const maxDimension = 96; // 3x the maximum displayed size of 32px

        if (image.height < 1 || image.width < 1) {
            canvas.width = canvas.height = maxDimension;
            // drawImage can fail if image height/width is less than 1
            // Use blank image; the costume is too small to render anyway
            ctx.fillStyle = 'white'; // Create white background, since jpeg doesn't have transparency
            ctx.fillRect(0, 0, maxDimension, maxDimension);
        } else {
            if (image.height > image.width) {
                canvas.height = maxDimension;
                canvas.width = (maxDimension / image.height) * image.width;
            } else {
                canvas.width = maxDimension;
                canvas.height = (maxDimension / image.width) * image.height;
            }
            ctx.fillStyle = 'white';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
        }
        resolve(canvas.toDataURL('image/jpeg', 0.92 /* quality */)); // Default quality is 0.92
    };
    image.onerror = err => {
        reject(err);
    };
    image.src = dataUrl;
});

export default jpegThumbnail;

const jpegThumbnail = dataUrl => new Promise((resolve, reject) => {
    const image = new Image();
    image.onload = () => {
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');

        const maxDimension = 96; // 3x the maximum displayed size of 32px

        if (image.height < 1 || image.width < 1) {
            canvas.width = canvas.height = maxDimension;
            // drawImage can fail if image height/width is less than 1
            // Use blank image; the costume is too small to render anyway
            ctx.fillStyle = 'white'; // Create white background, since jpeg doesn't have transparency
            ctx.fillRect(0, 0, canvas.width, canvas.height);
        } else {
            if (image.height > image.width) {
                canvas.height = maxDimension;
                canvas.width = (maxDimension / image.height) * image.width;
            } else {
                canvas.width = maxDimension;
                canvas.height = (maxDimension / image.width) * image.height;
            }
            ctx.fillStyle = 'white';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
        }
        resolve(canvas.toDataURL('image/jpeg', 0.92 /* quality */)); // Default quality is 0.92
    };
    image.onerror = err => {
        reject(err);
    };
    image.src = dataUrl;
});

export default jpegThumbnail;

Use canvas width/height for futureproofing