import React from 'react'
import {connect} from 'react-redux'
import {Link} from 'react-router-dom'
import {compose} from 'recompose'

import {updateUserVideos} from '../actions/userVideos'
import {withDatabaseSubscribe} from './hocs'
import VideoPreviewsList from './VideoPreviewsList'

const mapStateToProps = ({userVideos}) => ({
  userVideos,
})

const enhanceSubs = compose(
  connect(mapStateToProps),
  withDatabaseSubscribe(
    'value',
    (props) => (`user-videos/${props.userId}`),
    (props) => (snapshot) => {
      props.dispatch(updateUserVideos({
        userId: props.userId,
        userVideosSnapshot: snapshot.val(),
      }))
    }
  ),
)

const UserVideos = ({baseUrl, isEditable, userId, userVideos}) => (
  <div>
    {isEditable ?
      <Link to='/videos/new'>New</Link> : ''
    }
    <VideoPreviewsList videoIds={userVideos[userId] ? Object.keys(userVideos[userId]) : []}/>
  </div>
)

export default enhanceSubs(UserVideos)

import React from 'react'
import {connect} from 'react-redux'
import {compose, withHandlers, withProps} from 'recompose'

import {updateUserVideos} from '../actions/userVideos'
import {createVideo, VideoOwnerTypes} from '../actions/videos'
import {withDatabaseSubscribe} from './hocs'
import VideoPreviewsList from './VideoPreviewsList'

const mapStateToProps = ({userVideos}) => ({
  userVideos,
})

const enhanceSubs = compose(
  connect(mapStateToProps),
  withProps(({match}) => ({
    userId: match.params.userId
  })),
  withHandlers(
    {
      onNewVideoSubmit: props => event => {
        event.preventDefault()
        props.dispatch(createVideo(
          {
            videoOwnerType: VideoOwnerTypes.USER_VIDEO,
            ownerId: props.userId
          })
        )
      }
    }
  ),
  withDatabaseSubscribe(
    'value',
    (props) => (`user-videos/${props.userId}`),
    (props) => (snapshot) => {
      props.dispatch(updateUserVideos(
        {
          userId: props.userId,
          userVideosSnapshot: snapshot.val(),
        }))
    }
  ),
)

const UserVideos = ({baseUrl, isEditable, onNewVideoSubmit, userId, userVideos}) => (
  <div>
    <form onSubmit={onNewVideoSubmit}>
      <input
        type='submit'
        value='submit'
      />
    </form>
    <VideoPreviewsList videoIds={userVideos[userId] ? Object.keys(userVideos[userId]) : []}/>
  </div>
)

export default enhanceSubs(UserVideos)

Make a user create video button


import React from 'react';
import {default as KataGroupsData} from '../katagroups.js';

export default class KataGroupsComponent extends React.Component {

  static propTypes = {
    kataGroups: React.PropTypes.instanceOf(KataGroupsData).isRequired
  };

  render() {
    const {kataGroups} = this.props;
    return (

        Menu

Kata groups

              {kataGroups.groups.map(kataGroup =>

                <a href={`#kataGroup=${encodeURIComponent(kataGroup.name)}`} className="pure-menu-link">{kataGroup.name} ({kataGroup.katasCount})

)}

    );
  }
}

import React from 'react';
import {default as KataGroupsData} from '../katagroups.js';

export default class KataGroupsComponent extends React.Component {

  static propTypes = {
    kataGroups: React.PropTypes.instanceOf(KataGroupsData).isRequired
  };

  render() {
    const {kataGroups} = this.props;
    return (

        Menu

Kata groups

              {kataGroups.groups.map(kataGroup =>

                {kataGroup.name} ({kataGroup.katasCount})

)}

    );
  }
}

Use the new `url` property.


/* @flow */

import React, {Component, PropTypes} from 'react';

import Enhancer from '../enhancer';
import StyleKeeper from '../style-keeper';
import StyleSheet from './style-sheet';

function _getStyleKeeper(instance): StyleKeeper {
  if (!instance._radiumStyleKeeper) {
    const userAgent = (
      instance.props.radiumConfig && instance.props.radiumConfig.userAgent
    ) || (
      instance.context._radiumConfig && instance.context._radiumConfig.userAgent
    );
    instance._radiumStyleKeeper = new StyleKeeper(userAgent);
  }

  return instance._radiumStyleKeeper;
}

class StyleRoot extends Component {
  constructor() {
    super(...arguments);

    _getStyleKeeper(this);
  }

  getChildContext() {
    return {_radiumStyleKeeper: _getStyleKeeper(this)};
  }

  render() {
    return (
      <div {...this.props}>
        {this.props.children}
        <StyleSheet />
      </div>
    );
  }
}

StyleRoot.contextTypes = {
  _radiumConfig: PropTypes.object,
  _radiumStyleKeeper: PropTypes.instanceOf(StyleKeeper)
};

StyleRoot.childContextTypes = {
  _radiumStyleKeeper: PropTypes.instanceOf(StyleKeeper)
};

StyleRoot = Enhancer(StyleRoot);

export default StyleRoot;

/* @flow */

import React, {Component, PropTypes} from 'react';

import Enhancer from '../enhancer';
import StyleKeeper from '../style-keeper';
import StyleSheet from './style-sheet';

function _getStyleKeeper(instance): StyleKeeper {
  if (!instance._radiumStyleKeeper) {
    const userAgent = (
      instance.props.radiumConfig && instance.props.radiumConfig.userAgent
    ) || (
      instance.context._radiumConfig && instance.context._radiumConfig.userAgent
    );
    instance._radiumStyleKeeper = new StyleKeeper(userAgent);
  }

  return instance._radiumStyleKeeper;
}

class StyleRoot extends Component {
  constructor() {
    super(...arguments);

    _getStyleKeeper(this);
  }

  getChildContext() {
    return {_radiumStyleKeeper: _getStyleKeeper(this)};
  }

  render() {
    /* eslint-disable no-unused-vars */
    // Pass down all props except config to the rendered div.
    const {radiumConfig, ...otherProps} = this.props;
    /* eslint-enable no-unused-vars */

    return (
      <div {...otherProps}>
        {this.props.children}
        <StyleSheet />
      </div>
    );
  }
}

StyleRoot.contextTypes = {
  _radiumConfig: PropTypes.object,
  _radiumStyleKeeper: PropTypes.instanceOf(StyleKeeper)
};

StyleRoot.childContextTypes = {
  _radiumStyleKeeper: PropTypes.instanceOf(StyleKeeper)
};

StyleRoot = Enhancer(StyleRoot);

export default StyleRoot;

Stop passing radiumConfig prop to div


import Instance from './instance';
import instances from './instances';

export default (node, options = {}) => {
	// Stream was passed instead of `options` object
	if (typeof options.write === 'function') {
		options = {
			stdout: options,
			stdin: process.stdin
		};
	}

	options = {
		stdout: process.stdout,
		stdin: process.stdin,
		debug: false,
		exitOnCtrlC: true,
		...options
	};

	let instance;
	if (instances.has(options.stdout)) {
		instance = instances.get(options.stdout);
	} else {
		instance = new Instance(options);
		instances.set(options.stdout, instance);
	}

	instance.render(node);

	return {
		rerender: instance.render,
		unmount: () => instance.unmount(),
		waitUntilExit: instance.waitUntilExit,
		cleanup: () => instances.delete(options.stdout)
	};
};

import Instance from './instance';
import instances from './instances';

export default (node, options = {}) => {
	// Stream was passed instead of `options` object
	if (typeof options.write === 'function') {
		options = {
			stdout: options,
			stdin: process.stdin
		};
	}

	options = {
		stdout: process.stdout,
		stdin: process.stdin,
		debug: false,
		exitOnCtrlC: true,
		experimental: false,
		...options
	};

	let instance;
	if (instances.has(options.stdout)) {
		instance = instances.get(options.stdout);
	} else {
		instance = new Instance(options);
		instances.set(options.stdout, instance);
	}

	instance.render(node);

	return {
		rerender: instance.render,
		unmount: () => instance.unmount(),
		waitUntilExit: instance.waitUntilExit,
		cleanup: () => instances.delete(options.stdout)
	};
};

Add default value for `experimental` option


(function(win){
    'use strict';

    var paramRe = /:([^\/.\\]+)/g;

    function Router(){
        if(!(this instanceof Router)){
            return new Router();
        }
        this.routes = [];
    }

    Router.prototype.route = function(path, fn){
        paramRe.lastIndex = 0;
        var regexp = path + '', match;
        while(match = paramRe.exec(path)){
            regexp = regexp.replace(match[0], '([^/.\\\\]+)');
        }
        this.routes.push({
            regexp: new RegExp('^' + regexp + '$'),
            callback: fn
        });
        return this;
    };

    Router.prototype.dispatch = function(path){
        path = path || win.location.pathname;
        for(var i = 0, len = this.routes.length, route, matches; i < len; i++){
            route = this.routes[i];
            matches = route.regexp.exec(path);
            if(matches && matches[0]){
                route.callback.apply(null, matches.slice(1));
                return this;
            }
        }
        return this;
    };

    win.Router = Router;

})(this);

(function(win){
    'use strict';

    var paramRe = /:([^\/.\\]+)/g;

    function Router(){
        if(!(this instanceof Router)){
            return new Router();
        }
        this.routes = [];
    }

    Router.prototype.route = function(path, fn){
        paramRe.lastIndex = 0;
        var regexp = path + '', match;
        while(match = paramRe.exec(path)){
            regexp = regexp.replace(match[0], '([^/\\\\]+)');
        }
        this.routes.push({
            regexp: new RegExp('^' + regexp + '$'),
            callback: fn
        });
        return this;
    };

    Router.prototype.dispatch = function(path){
        path = path || win.location.pathname;
        for(var i = 0, len = this.routes.length, route, matches; i < len; i++){
            route = this.routes[i];
            matches = route.regexp.exec(path);
            if(matches && matches[0]){
                route.callback.apply(null, matches.slice(1));
                return this;
            }
        }
        return this;
    };

    win.Router = Router;

})(this);

Fix params with period not being passed to callback


// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file.
//
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery
//= require jquery_ujs
//= require jquery-ui
//= require_tree .
//= require jquery-bramus-progressbar/jquery-bramus-progressbar
//= require general
//= require jquery.history
//= require jquery-warper
//= require layers
//= require unwarped

// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file.
//
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery
//= require jquery_ujs
//= require jquery.ui.all
//= require_tree .
//= require jquery-bramus-progressbar/jquery-bramus-progressbar
//= require general
//= require jquery.history
//= require jquery-warper
//= require layers
//= require unwarped

Fix js manifest for older jquery ui


// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery
//= require jquery_ujs
//= require RRSSB
//= require lazyload
//= require image-preview
//= require script
//= require service-worker-cache-rails

Remove last of the gem


# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

$(function() {
  return console.log($("span").length);
});
$(".status").hover((function() {
  console.log(this);
  return $(this).children(".details").show();
}), function() {
  console.log("bob");
  return $(this).children(".details").hide();
});

$(function() {
  console.log($("span").length);
});
$(".status").hover((function() {
  console.log(this);
  $(this).children(".details").show();
}), function() {
  console.log("bob");
  $(this).children(".details").hide();
});

Fix for conversion from coffee->js


import React from "react";
import classNames from "classnames";

import { selectPiece } from "store/actions";

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

    this.selectSquare = this.selectSquare.bind(this);
  }

  squareCoords() {
    return { rank: this.props.rank, file: this.props.file };
  }

  selectSquare() {
    var { store } = this.props;
    console.log(`Clicked: ${this.props.file}${this.props.rank}`);

    if (this.props.piece != undefined) {
      store.dispatch(selectPiece(this.squareCoords()));
    }

    console.log(store.getState().selectedSquare);
  };

  isSelectedSquare() {
    var { store } = this.props;

    return this.squareCoords().rank == store.getState().selectedSquare.rank
      && this.squareCoords().file == store.getState().selectedSquare.file;
  }

  render() {
    if (this.props.piece == undefined) {
      var squareClass = "board-square";
    }
    else {
      var squareClass = classNames(
        "board-square",
        this.props.piece.type,
        this.props.piece.colour,
        { "selected": this.isSelectedSquare() }
      )
    }

    return <div className={squareClass} onClick={this.selectSquare} />
  }
}

export default ChessBoardSquare;

import React from "react";
import classNames from "classnames";

import { selectPiece } from "store/actions";

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

    this.selectSquare = this.selectSquare.bind(this);
  }

  squareCoords() {
    return { rank: this.props.rank, file: this.props.file };
  }

  selectSquare() {
    var { store } = this.props;
    console.log(`Clicked: ${this.props.file}${this.props.rank}`);

    if (this.props.piece != undefined) {
      store.dispatch(selectPiece(this.squareCoords()));
    }

    console.log(store.getState().selectedSquare);
  };

  isSelectedSquare() {
    var { store } = this.props;

    if (store.getState().selectedSquare == null) {
      return false;
    }
    else {
      return this.squareCoords().rank == store.getState().selectedSquare.rank
        && this.squareCoords().file == store.getState().selectedSquare.file;
    }
  }

  render() {
    if (this.props.piece == undefined) {
      var squareClass = "board-square";
    }
    else {
      var squareClass = classNames(
        "board-square",
        this.props.piece.type,
        this.props.piece.colour,
        { "selected": this.isSelectedSquare() }
      )
    }

    return <div className={squareClass} onClick={this.selectSquare} />
  }
}

export default ChessBoardSquare;

Fix problem with null selectedSquare


import Session from "./Session.js";

let session = new Session();

loadPlugins()
  .then(() => session.connect());

async function loadPlugins()
{
  let pluginEntryScripts = <%- JSON.stringify(entryScripts) %>;
  let importPromises = [];

  for(let entryScript of pluginEntryScripts) {
    let importPromise = import(entryScript).then(loadPlugin);

    importPromises.push(importPromise);
  }

  await Promise.all(importPromises);
}

function loadPlugin(module)
{
  module.default(session);
}

import Session from "./Session.js";
<% for(let i = 0; i < entryScripts.length; i++) { -%>
import { default as plugin<%- i %> } from "<%- entryScripts[i] %>";
<% } -%>

let session = new Session();

loadPlugins()
  .then(() => session.connect());

async function loadPlugins()
{
<% for(let i = 0; i < entryScripts.length; i++) { -%>
  plugin<%- i %>(session);
<% } -%>
}

Remove dynamic import in client source


function createDoc(browser, docType) {
  const devServer = browser.globals.devServerURL;

  browser.url(`${devServer}/`);
  browser.expect.element('#app').to.be.visible.before(5000);

  browser.execute(() => {
    const className = `.${docType}`;
    const numDocs = document.querySelectorAll(className).length;

    return numDocs;
  }, [docType], (numDocs) => {
    try {
      const createButton = `#button-create-${docType}`;
      const numDocsAfterCreate = numDocs.value + 1;

      browser
        .click(createButton);

      browser
        .expect(numDocs.value).to.equal(numDocsAfterCreate);
    } catch (error) {
      console.log(error);
    }
  });
}
describe('FileManager\'s create file/folder button', function() {
  after((browser, done) => {
    browser.end(() => done());
  });

  it('should create a new file', (browser) => {
    createDoc(browser, 'file');
  });

  it('should create a new folder', (browser) => {
    createDoc(browser, 'folder');
  });
});

function createDoc(browser, docType) {
  const devServer = browser.globals.devServerURL;

  browser.url(`${devServer}/`);
  browser.expect.element('#app').to.be.visible.before(5000);

  browser.execute(() => {
    const className = `.${docType}`;
    const numDocs = document.querySelectorAll(className).length;

    return numDocs;
  }, [docType], (numDocs) => {
    try {
      const createButton = `#button-create-${docType}`;
      const numDocsAfterCreate = numDocs.value + 1;

      browser
        .click(createButton)
        .pause(500);

      browser
        .expect(numDocs.value).to.equal(numDocsAfterCreate);
    } catch (error) {
      console.log(error);
    }
  });
}
describe('FileManager\'s create file/folder button', function() {
  after((browser, done) => {
    browser.end(() => done());
  });

  it('should create a new file', (browser) => {
    createDoc(browser, 'file');
  });

  it('should create a new folder', (browser) => {
    createDoc(browser, 'folder');
  });
});

Add pause after button click to handle delays


module.exports = function (grunt) {
  var _ = require('lodash'),
      scsslint = require('./lib/scss-lint').init(grunt);

  grunt.registerMultiTask('scsslint', 'Validate `.scss` files with `scss-lint`.', function() {
    var done = this.async(),
        files = this.filesSrc,
        fileCount = this.filesSrc.length,
        target = this.target,
        opts;

    opts = this.options({
      config: '.scss-lint.yml',
      reporterOutput: null
    });

    grunt.verbose.writeflags(opts, 'scss-lint options');

    grunt.log.writeln('Running scss-lint on ' + target);

    scsslint.lint(files, opts, function (results) {
      var success = _.isEmpty(results);

      if (success) {
        grunt.log.oklns(fileCount + ' files are lint free');
      } else {
        grunt.log.writeln(result);
      }

      if (opts.reporterOutput) {
        grunt.log.writeln('Results have been written to: ' + opts.reporterOutput);
      }

      done(success);
    });
  });
};

module.exports = function (grunt) {
  var _ = require('lodash'),
      scsslint = require('./lib/scss-lint').init(grunt);

  grunt.registerMultiTask('scsslint', 'Validate `.scss` files with `scss-lint`.', function() {
    var done = this.async(),
        files = this.filesSrc,
        fileCount = this.filesSrc.length,
        target = this.target,
        opts;

    opts = this.options({
      config: '.scss-lint.yml',
      reporterOutput: null
    });

    grunt.verbose.writeflags(opts, 'scss-lint options');

    grunt.log.writeln('Running scss-lint on ' + target);

    scsslint.lint(files, opts, function (results) {
      var success = _.isEmpty(results);

      if (success) {
        grunt.log.oklns(fileCount + ' files are lint free');
      } else {
        grunt.log.writeln(results);
      }

      if (opts.reporterOutput) {
        grunt.log.writeln('Results have been written to: ' + opts.reporterOutput);
      }

      done(success);
    });
  });
};

Fix typo in logging results on error.


var async = require("async");
var path = require("path");
var url = require("url");

var utils = require("./lib/utils");

module.exports = function(options, callback) {
  utils.list_files(options, function(err, objects) {
    if (err) {
      callback(err);
      return;
    }

    var q = async.queue(function(translation, callback) {
      var local_path_split = url.parse(translation).pathname.split(path.sep);
      var local_path = path.join(options.dir, local_path_split[2], local_path_split[3]);

      utils.stream_url_to_file(translation, local_path, callback);
    }, 16);

    q.push(objects, function(err) {
      if (err) {
        q.tasks.length = 0;
        callback(err);
      }
    });

    q.drain = callback;
  });
};

var async = require("async");
var path = require("path");
var url = require("url");

var utils = require("./lib/utils");

module.exports = function(options, callback) {
  utils.list_files(options, function(err, objects) {
    if (err) {
      callback(err);
      return;
    }

    var q = async.queue(function(translation, callback) {
      var local_path_split = url.parse(translation).pathname.split("/");
      var local_path = path.join(options.dir, local_path_split[2], local_path_split[3]);

      utils.stream_url_to_file(translation, local_path, callback);
    }, 16);

    q.push(objects, function(err) {
      if (err) {
        q.tasks.length = 0;
        callback(err);
      }
    });

    q.drain = callback;
  });
};

Use / as a separator for urls


var _ = require('lodash');

module.exports = {
    // Return non-parsed html
    // since blocks are by default non-parsable, a simple identity method works fine
    html: _.identity,

    // Highlight a code block
    // This block can be replaced by plugins
    code: function(blk) {
        return {
            html: false,
            body: blk.body
        };
    }
};

var _ = require('lodash');

module.exports = {
    // Return non-parsed html
    // since blocks are by default non-parsable, a simple identity method works fine
    html: _.identity,

    // Highlight a code block
    // This block can be replaced by plugins
    code: function(blk) {
        return {
            html: false,
            body: blk.body
        };
    },

    // Render some markdown to HTML
    markdown: function(blk) {
        return this.book.renderInline('markdown', blk.body)
        .then(function(out) {
            return { body: out };
        });
    },
    asciidoc: function(blk) {
        return this.book.renderInline('asciidoc', blk.body)
        .then(function(out) {
            return { body: out };
        });
    },
    markup: function(blk) {
        return this.book.renderInline(this.ctx.file.type, blk.body)
        .then(function(out) {
            return { body: out };
        });
    }
};

Add block "markdown", "asciidoc" and "markup"


import {Router} from 'express'
import models from '../models';
import tasks from './tasks'

export default () => {
	let app = Router({mergeParams: true})

	app.use('/:userId/tasks', tasks())

	app.get('/', (req, res) => {
		models.User.findAll({
			attributes: ['id', 'email']
		}).then((users) => {
			res.send(users)
		})
	})

	app.get('/new', (req, res) => {
		res.render('users/new')
	})

	app.get('/:id', (req, res) => {
		models.User.find({
			where: {
				id: req.params.id
			},
			include: [models.Task]
		}).then((user) => {
			res.render('users/show', {user})
		})
	})

	app.post('/', (req, res) => {
		models.User.create(req.body).then((user) => {
			req.session.userId = user.dataValues.id
			req.app.locals.userId = user.dataValues.id
			req.session.save()
			// add validations and errors
			res.send(user);
		})
	})
	return app
}

import {Router} from 'express'
import models from '../models';
import tasks from './tasks'

export default () => {
	let app = Router({mergeParams: true})

	app.use('/:userId/tasks', tasks())

	app.get('/:id', (req, res) => {
		models.User.find(req.params.id).then((users) => {
			res.send(users)
		})
	})

	app.get('/new', (req, res) => {
		res.render('users/new')
	})

	app.get('/:id', (req, res) => {
		models.User.find({
			where: {
				id: req.params.id
			},
			include: [models.Task]
		}).then((user) => {
			res.render('users/show', {user})
		})
	})

	app.post('/', (req, res) => {
		models.User.create(req.body).then((user) => {
			req.session.userId = user.dataValues.id
			req.app.locals.userId = user.dataValues.id
			req.session.save()
			// add validations and errors
			res.send(user);
		})
	})
	return app
}

Update user POST and GET routes


require('./harness').run();

var recvCount = 0;
var body = "hello world";

connection.addListener('ready', function () {
  puts("connected to " + connection.serverProperties.product);

  var e = connection.exchange('node-purge-fanout', {type: 'fanout'});
  var q = connection.queue('node-purge-queue', function() {
    q.bind(e, "*")
    q.on('queueBindOk', function() {
      puts("publishing 1 json messages");
      e.publish('ackmessage.json1', { name: 'A' });

      puts('Purge queue')
      q.purge().addCallback(function(ok){
        puts('Deleted '+ok.messageCount+' messages')
        assert.equal(1,ok.messageCount)
        puts("publishing another json messages");
        e.publish('ackmessage.json2', { name: 'B' });
      })

      q.on('basicConsumeOk', function () {
        setTimeout(function () {
          // wait one second to receive the message, then quit
          connection.end();
        }, 1000);
      });

      q.subscribe({ ack: true }, function (json) {
        recvCount++;
        puts('Got message ' + JSON.stringify(json));
        if (recvCount == 1) {
          assert.equal('B', json.name);
          q.shift();
         } else {
          throw new Error('Too many message!');
         }
      })
    })
  });
});

process.addListener('exit', function () {
  assert.equal(1, recvCount);
});

require('./harness').run();

var recvCount = 0;
var body = "hello world";

connection.addListener('ready', function () {
  puts("connected to " + connection.serverProperties.product);

  var e = connection.exchange('node-purge-fanout', {type: 'fanout', confirm: true});
  var q = connection.queue('node-purge-queue', function() {
    q.bind(e, "*");
    q.on('queueBindOk', function() {
      puts("publishing 1 json message");

      e.publish('ackmessage.json1', { name: 'A' }, {}, function() {

        puts('Purge queue');
        q.purge().addCallback(function(ok){
          puts('Deleted '+ok.messageCount+' message');
          assert.equal(1,ok.messageCount);
          puts("publishing another json message");
          e.publish('ackmessage.json2', { name: 'B' });
        });

        q.on('basicConsumeOk', function () {
          setTimeout(function () {
            // wait one second to receive the message, then quit
            connection.end();
          }, 1000);
        });

        q.subscribe({ ack: true }, function (json) {
          recvCount++;
          puts('Got message ' + JSON.stringify(json));
          if (recvCount == 1) {
            assert.equal('B', json.name);
            q.shift();
           } else {
            throw new Error('Too many message!');
           }
        });

      });

    });
  });
});

process.addListener('exit', function () {
  assert.equal(1, recvCount);
});

Fix race condition in purge test.


/* From w3schools */

/* When the user clicks on the button,
toggle between hiding and showing the dropdown content */
$(function () {
    $('.c-dropdown__btn').click(function (event) {
        event.preventDefault();
        $(this).nextAll(".c-dropdown__cont").toggleClass('c-dropdown__cont--hide');
    });
});

// Close the dropdown if the user clicks outside of it
window.onclick = function(event) {
    if (!event.target.matches('.c-dropdown__btn')) {
        $(".c-dropdown__cont").addClass('c-dropdown__cont--hide');
    }
}

/* From w3schools */

/* When the user clicks on the button,
toggle between hiding and showing the dropdown content */
$(function () {
    $('.c-site-nav').on('click', '.c-dropdown__btn', function (event) {
        event.preventDefault();
        $(this).nextAll(".c-dropdown__cont").toggleClass('c-dropdown__cont--hide');
        $(this).nextAll(".mean-expand").click();
    });
});

// Close the dropdown if the user clicks outside of it
window.onclick = function(event) {
    if (!event.target.matches('.c-dropdown__btn')) {
        $(".c-dropdown__cont").addClass('c-dropdown__cont--hide');
    }
}

Fix nav issue when using mean menu


import path from 'path';
import webpack from 'webpack';
import CommonsChunkPlugin from 'webpack/lib/optimize/CommonsChunkPlugin';

const PROD = process.env.NODE_ENV || 0;

module.exports = {
  devtool: PROD ? false : 'eval',

  entry: {
    app: './app/assets/scripts/App.js',
    vendor: [
      'picturefill',
      './app/assets/_compiled/modernizr'
    ]
  },

  output: {
    path: __dirname + '/app/assets/_compiled',
    publicPath: '/assets/_compiled/',
    filename: '[name].js',
    chunkFilename: '_chunk/[name]_[chunkhash].js'
  },

  plugins: [
    new webpack.optimize.UglifyJsPlugin({
      compress: PROD ? true : false,
      output: {
        comments: false
      }
    }),

    new CommonsChunkPlugin({
      children: true,
      minChunks: 2
    })
  ],

  module: {
    loaders: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      }
    ]
  }
};

import path from 'path';
import webpack from 'webpack';
import CommonsChunkPlugin from 'webpack/lib/optimize/CommonsChunkPlugin';

const PROD = process.env.NODE_ENV || 0;

module.exports = {
  devtool: PROD ? false : 'eval-cheap-module-source-map',

  entry: {
    app: './app/assets/scripts/App.js',
    vendor: [
      'picturefill',
      './app/assets/_compiled/modernizr'
    ]
  },

  output: {
    path: __dirname + '/app/assets/_compiled',
    publicPath: '/assets/_compiled/',
    filename: '[name].js',
    chunkFilename: '_chunk/[name]_[chunkhash].js'
  },

  plugins: [
    new webpack.optimize.UglifyJsPlugin({
      compress: PROD ? true : false,
      output: {
        comments: false
      }
    }),

    new CommonsChunkPlugin({
      children: true,
      minChunks: 2
    })
  ],

  module: {
    loaders: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      }
    ]
  }
};

Improve source maps for JS


import { extend } from 'flarum/extend';
import DiscussionListItem from 'flarum/components/DiscussionListItem';
import DiscussionPage from 'flarum/components/DiscussionPage';
import DiscussionHero from 'flarum/components/DiscussionHero';

import tagsLabel from '../common/helpers/tagsLabel';
import sortTags from '../common/utils/sortTags';

export default function() {
  // Add tag labels to each discussion in the discussion list.
  extend(DiscussionListItem.prototype, 'infoItems', function(items) {
    const tags = this.props.discussion.tags();

    if (tags && tags.length) {
      items.add('tags', tagsLabel(tags), 10);
    }
  });

  // Include a discussion's tags when fetching it.
  extend(DiscussionPage.prototype, 'params', function(params) {
    params.include.push('tags');
  });

  // Restyle a discussion's hero to use its first tag's color.
  extend(DiscussionHero.prototype, 'view', function(view) {
    const tags = sortTags(this.props.discussion.tags());

    if (tags && tags.length) {
      const color = tags[0].color();
      if (color) {
        view.attrs.style = {backgroundColor: color};
        view.attrs.className += ' DiscussionHero--colored';
      }
    }
  });

  // Add a list of a discussion's tags to the discussion hero, displayed
  // before the title. Put the title on its own line.
  extend(DiscussionHero.prototype, 'items', function(items) {
    const tags = this.props.discussion.tags();

    if (tags && tags.length) {
      items.add('tags', tagsLabel(tags, {link: true}), 5);
    }
  });
}

import { extend } from 'flarum/extend';
import DiscussionListItem from 'flarum/components/DiscussionListItem';
import DiscussionHero from 'flarum/components/DiscussionHero';

import tagsLabel from '../common/helpers/tagsLabel';
import sortTags from '../common/utils/sortTags';

export default function() {
  // Add tag labels to each discussion in the discussion list.
  extend(DiscussionListItem.prototype, 'infoItems', function(items) {
    const tags = this.props.discussion.tags();

    if (tags && tags.length) {
      items.add('tags', tagsLabel(tags), 10);
    }
  });

  // Restyle a discussion's hero to use its first tag's color.
  extend(DiscussionHero.prototype, 'view', function(view) {
    const tags = sortTags(this.props.discussion.tags());

    if (tags && tags.length) {
      const color = tags[0].color();
      if (color) {
        view.attrs.style = {backgroundColor: color};
        view.attrs.className += ' DiscussionHero--colored';
      }
    }
  });

  // Add a list of a discussion's tags to the discussion hero, displayed
  // before the title. Put the title on its own line.
  extend(DiscussionHero.prototype, 'items', function(items) {
    const tags = this.props.discussion.tags();

    if (tags && tags.length) {
      items.add('tags', tagsLabel(tags, {link: true}), 5);
    }
  });
}

Remove an obsolete method extension


import req from 'superagent';

import {dispatch} from '../dispatcher';

import {Action} from '../actions';

import meta from '../meta';

const DIR_CONFIG='/resource/config/app/moe/somesim',
	  DIR_DOC='/resource/markdown/app/moe/somesim';

export default ()=>{
	req
		.get(DIR_CONFIG+'/somesim.json?='+meta.version)
		.end((err,res)=>{
			dispatch(Action.RecieveEquipList(res.body));
		});

	req
		.get(DIR_CONFIG+'/flower.json?='+meta.version)
		.end((err,res)=>{
			dispatch(Action.RecieveFlowerList(res.body));
		});

	req
		.get(DIR_CONFIG+'/pallet.json?='+meta.version)
		.end((err,res)=>{
			dispatch(Action.RecieveStainList(res.body));
		});

	req
		.get(DIR_DOC+'/readme.md?='+meta.version)
		.end((err,res)=>{
			dispatch(Action.RecieveReadme(res.text));
		});

	req
		.get(DIR_DOC+'/update.md?='+meta.version)
		.end((err,res)=>{
			dispatch(Action.RecieveUpdateLog(res.text));
		});
}

import req from 'superagent';

import {dispatch} from '../dispatcher';

import {Action} from '../actions';

import meta from '../meta';

const DIR_CONFIG='/resource/config/app/moe/somesim',
	  DIR_DOC='/resource/markdown/app/moe/somesim';

export default ()=>{
	req
		.get(DIR_CONFIG+'/somesim.json?='+meta.version)
		.end((err,res)=>{
			dispatch(Action.RecieveEquipList(JSON.parse(res.text)));
		});

	req
		.get(DIR_CONFIG+'/flower.json?='+meta.version)
		.end((err,res)=>{
			dispatch(Action.RecieveFlowerList(JSON.parse(res.text)));
		});

	req
		.get(DIR_CONFIG+'/pallet.json?='+meta.version)
		.end((err,res)=>{
			dispatch(Action.RecieveStainList(JSON.parse(res.text)));
		});

	req
		.get(DIR_DOC+'/readme.md?='+meta.version)
		.end((err,res)=>{
			dispatch(Action.RecieveReadme(res.text));
		});

	req
		.get(DIR_DOC+'/update.md?='+meta.version)
		.end((err,res)=>{
			dispatch(Action.RecieveUpdateLog(res.text));
		});
}

Change property to be access


'use strict';

const format = require('util').format;
const log = require('util').log;

const Promise = require('bluebird');
const db = require('../shared/db');

module.exports = {
    insert: insertEntries,
    delete: deleteEntries
};

function deleteEntries(distribution) {
    return Promise.using(db(), function(client) {
        log(format('Deleting all the entries for %s...', distribution));
        return client.queryAsync({
            name: 'delete_entries',
            text: 'delete from source where distribution = $1',
            values: [distribution]
        }).then(function() {
            log(format('Entries for %s deleted.', distribution));
            return distribution;
        });
    });
}

function insertEntries(sources) {
    log(format('Inserting %d entries for %s...', sources.length, sources[0].distribution));
    // Pseudo query builder
    let params = [];
    let chunks = [];
    for (let i = 0; i < sources.length; i++) {
        let source = sources[i];
        let valuesClause = [];

        params.push(source.name);
        valuesClause.push('$' + params.length);

        params.push(source.distribution);
        valuesClause.push('$' + params.length);

        params.push(source.version);
        valuesClause.push('$' + params.length);

        chunks.push('(' + valuesClause.join(', ') + ')');
    }
    return Promise.using(db(), function(client) {
        return client.queryAsync({
            name: 'insert_sources',
            text: 'insert into source(name, distribution, version) values ' +
                chunks.join(', '),
            values: params
        }).then(function() {
            log(format('Entries for %s inserted.', sources[0].distribution));
        });
    });
}

'use strict';

const format = require('util').format;
const log = require('util').log;

const Promise = require('bluebird');
const db = require('../shared/db');

module.exports = {
    insert: insertEntries,
    delete: deleteEntries
};

function deleteEntries(distribution) {
    return Promise.using(db(), function(client) {
        log(format('Deleting all the entries for %s...', distribution));
        return client.queryAsync({
            name: 'delete_entries',
            text: 'delete from source where distribution = $1',
            values: [distribution]
        }).then(function() {
            log(format('Entries for %s deleted.', distribution));
            return distribution;
        });
    });
}

function insertEntries(sources) {
    log(format('Inserting %d entries for %s...', sources.length, sources[0].distribution));
    return Promise.map(sources, insertEntry).then(function() {
        log(format('Entries for %s inserted.', sources[0].distribution));
    });
}

function insertEntry(source) {
    return Promise.using(db(), function(client) {
        return client.queryAsync({
            name: 'insert_source',
            text: 'insert into source(name, distribution, version) values ($1, $2, $3)',
            values: [source.name, source.distribution, source.version]
        });
    });
}

Use one query per source instead of a single query


import Ember from 'ember'
import Authenticated from 'ember-simple-auth/mixins/authenticated-route-mixin'

const { get, set } = Ember

export default Ember.Route.extend(Authenticated, {
  pageTitle: 'Media',
  toggleDropzone () {
    $('body').toggleClass('dz-open')
  },
  actions: {
    uploadImage (file) {
      const record = this.store.createRecord('file', {
        filename: get(file, 'name'),
        contentType: get(file, 'type'),
        length: get(file, 'size')
      })

      $('body').removeClass('dz-open')

      var settings = {
        url: '/api/upload',
        headers: {}
      }

      file.read().then(url => {
        if (get(record, 'url') == null) {
          set(record, 'url', url)
        }
      })

      this.get('session').authorize('authorizer:token', (headerName, headerValue) => {
        settings.headers[headerName] = headerValue

        file.upload(settings).then(response => {
          //set(record, 'url', response.headers.Location)
          console.log(response)
          return record.save()
        }, () => {
          record.rollback()
        })
      })
    },
    willTransition () {
      $('body').unbind('dragenter dragleave', this.toggleDropzone)
    },
    didTransition () {
      $('body').on('dragenter dragleave', this.toggleDropzone)
    }
  },
  model () {
    return this.store.findAll('file')
  }
})

import Ember from 'ember'
import Authenticated from 'ember-simple-auth/mixins/authenticated-route-mixin'

const { get, set } = Ember

export default Ember.Route.extend(Authenticated, {
  pageTitle: 'Media',
  toggleDropzone () {
    $('body').toggleClass('dz-open')
  },
  actions: {
    uploadImage (file) {
      const record = this.store.createRecord('file', {
        filename: get(file, 'name'),
        contentType: get(file, 'type'),
        length: get(file, 'size')
      })

      $('body').removeClass('dz-open')

      var settings = {
        url: '/api/upload',
        headers: {}
      }

      file.read().then(url => {
        if (get(record, 'url') == null) {
          set(record, 'url', url)
        }
      })

      this.get('session').authorize('authorizer:token', (headerName, headerValue) => {
        settings.headers[headerName] = headerValue

        file.upload(settings).then(response => {
          for (var property in response.body) {
            set(record, property, response.body[property])
          }

          return record.save()
        }, () => {
          record.rollback()
        })
      })
    },
    willTransition () {
      $('body').unbind('dragenter dragleave', this.toggleDropzone)
    },
    didTransition () {
      $('body').on('dragenter dragleave', this.toggleDropzone)
    }
  },
  model () {
    return this.store.findAll('file')
  }
})

Add record details after upload


export const CC_LICENSES = [
  {
    value: 'Creative Commons Attribution 4.0 International',
    url: 'https://creativecommons.org/licenses/by/4.0/legalcode',
  },
  {
    value: 'Creative Commons Attribution-ShareAlike 4.0 International',
    url: 'https://creativecommons.org/licenses/by-sa/4.0/legalcode',
  },
  {
    value: 'Creative Commons Attribution-NoDerivatives 4.0 International',
    url: 'https://creativecommons.org/licenses/by-nd/4.0/legalcode',
  },
  {
    value: 'Creative Commons Attribution-NonCommercial 4.0 International',
    url: 'https://creativecommons.org/licenses/by-nc/4.0/legalcode',
  },
  {
    value: 'Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International',
    url: 'https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode',
  },
  {
    value: 'Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International',
    url: 'https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode',
  },
];

export const NONE = 'None';
export const PUBLIC_DOMAIN = 'Public Domain';
export const OTHER = 'other';
export const COPYRIGHT = 'copyright';

export const CC_LICENSES = [
  {
    value: 'Creative Commons Attribution 3.0',
    url: 'https://creativecommons.org/licenses/by/3.0/legalcode',
  },
  {
    value: 'Creative Commons Attribution 4.0 International',
    url: 'https://creativecommons.org/licenses/by/4.0/legalcode',
  },
  {
    value: 'Creative Commons Attribution-ShareAlike 4.0 International',
    url: 'https://creativecommons.org/licenses/by-sa/4.0/legalcode',
  },
  {
    value: 'Creative Commons Attribution-NoDerivatives 4.0 International',
    url: 'https://creativecommons.org/licenses/by-nd/4.0/legalcode',
  },
  {
    value: 'Creative Commons Attribution-NonCommercial 4.0 International',
    url: 'https://creativecommons.org/licenses/by-nc/4.0/legalcode',
  },
  {
    value: 'Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International',
    url: 'https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode',
  },
  {
    value: 'Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International',
    url: 'https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode',
  },
];

export const NONE = 'None';
export const PUBLIC_DOMAIN = 'Public Domain';
export const OTHER = 'other';
export const COPYRIGHT = 'copyright';

Add Creative Commons Attribution 3.0 license


Fix white blue colors for color picker


function getMusic(element) {
    element = $(element);
    var info_send = {
        //method: 'info',   // TODO possible method to get the song data: song 'info' or song 'id'
        artist: element.data('artist'),
        album: element.data('album'),
        title: element.data('title')
    };

    console.log(info_send);

    $.ajax({
        type: 'GET',
        url: '/api/getMusic',
        data: info_send,
        dataType: 'json',   // The type of data that you're expecting back from the server.
        //contentType: 'application/json; charset=utf-8', // Used 'application/json' only when is NOT GETing data with query string.
        success: function (data) {
            var player = $('#player');
            var params = {
                sourceType: 'youtube',  // TODO, read from server.
                source: data.music_path,
                infoAlbumArt: data.image_path,   // Currently not used.
                infoAlbumTitle: data.album,
                infoArtist: data.artist,
                infoTitle: data.title,
                infoLabel: data.label,
                infoYear: data.year
            };
            player.trigger('loadNew', params);
        },
        error: function (data) {
            console.log("Error:");
            console.log(data);
        }
    });
}

function getMusic(element) {
    element = $(element);
    var info_send = {
        //method: 'info',   // TODO possible method to get the song data: song 'info' or song 'id'
        artist: element.data('artist'),
        album: element.data('album'),
        title: element.data('title')
    };

    console.log(info_send);

    $.ajax({
        type: 'GET',
        url: '/api/getMusic',
        data: info_send,
        dataType: 'json',   // The type of data that you're expecting back from the server.
        //contentType: 'application/json; charset=utf-8', // Used 'application/json' only when is NOT GETing data with query string.
        success: function (data) {
            var player = $('#player');
            var params = {
                sourceType: 'youtube',  // TODO, read from server.
                source: data.music_path,
                infoAlbumArt: data.image_path,   // Currently not used.
                infoAlbumTitle: data.album,
                infoArtist: data.artist,
                infoTitle: data.title,
                infoLabel: data.label,
                infoYear: data.year
            };
            player.trigger('loadNew', params);

            // Reset rating stars color.
            $('#rating-1').css('color', '#aaa');
            $('#rating-2').css('color', '#aaa');
            $('#rating-3').css('color', '#aaa');
            $('#rating-4').css('color', '#aaa');
            $('#rating-5').css('color', '#aaa');
        },
        error: function (data) {
            console.log("Error:");
            console.log(data);
        }
    });
}

Add reset rating stars color when reload video.


import { component } from "d3-component";
import { select, local } from "d3-selection";

const codeMirrorLocal = local();

// User interface component for the code editor.
export default component("div", "shadow")
  .create(function ({ onChange }){
    const codeMirror = codeMirrorLocal
      .set(this, CodeMirror(this, {
        lineNumbers: false,
        mode: "htmlmixed"
      }));

    // Inlet provides the interactive sliders and color pickers.
    Inlet(codeMirror);

    codeMirror.on("change", function (editor, change){
      if(change.origin === "setValue") return;
      onChange(codeMirror.getValue());
    });
  })
  .render(function ({ content }){
    const codeMirror = codeMirrorLocal.get(this);
    if(codeMirror.getValue() !== content){ // TODO use timestamp here?
      codeMirror.setValue(content);
    }
  });

//export default function (dispatch, actions){
//  return function (selection, state){
//    var editor = selection.selectAll(".editor").data([1]);
//    editor = editor.merge(
//      editor.enter().append("div")
//        .attr("class", "editor shadow")
//        .each(function (){
//        }));
//
//    if(state.html){
//      editor.each(function (){
//      });
//    }
//  };
//}

import { component } from "d3-component";
import { select, local } from "d3-selection";

const codeMirrorLocal = local();

// User interface component for the code editor.
export default component("div", "shadow")
  .create(function (){
    const my = codeMirrorLocal.set(this, {
      codeMirror: CodeMirror(this, {
        lineNumbers: false,
        mode: "htmlmixed"
      }),
      onChange: undefined // Will be set in the render hook.
    });

    my.codeMirror.on("change", function (editor, change){
      if(change.origin === "setValue") return;
      my.onChange && my.onChange(my.codeMirror.getValue());
    });

    // Inlet provides the interactive sliders and color pickers.
    Inlet(my.codeMirror);
  })
  .render(function ({ content, onChange }){
    const my = codeMirrorLocal.get(this);
    if(my.codeMirror.getValue() !== content){ // TODO use timestamp here?
      my.codeMirror.setValue(content);
    }
    my.onChange = onChange;
  });

//export default function (dispatch, actions){
//  return function (selection, state){
//    var editor = selection.selectAll(".editor").data([1]);
//    editor = editor.merge(
//      editor.enter().append("div")
//        .attr("class", "editor shadow")
//        .each(function (){
//        }));
//
//    if(state.html){
//      editor.each(function (){
//      });
//    }
//  };
//}

Use correct listener in CodeMirror callbacks


"use strict";

const hasDebug = process.argv.indexOf("--debug") > -1;

const debug = (!hasDebug) ?
                    function() {} :
                    (message) => console.log(message);

debug.on = function() { return hasDebug; }

module.exports = debug;

"use strict";

const hasDebug = process.argv.indexOf("--debug") > -1;

const debug = (!hasDebug) ?
                    function() {} :
                    () => console.log.apply(console, arguments);

debug.on = function() { return hasDebug; };

module.exports = debug;

Debug should output everything pls


// Method that performs basic string compression using the counts of repeated characters

function compressStr(str) {
	var output = "", // will return this variable as final compressed string
		currChar = "", // represents character we are searching for in string
		currCount = "", // counts number of times character is repeated
		// maxCount = ""; // maximum count

	// iterate through entire string
	for (i = 0; i < str.length; i++) {
		// if the character we are searching for in string matches the character being looked at
		if ( currChar !== str[i]) {
			output = output + currChar + currCount; // output is sum of the output we have so far plus the character we have been searching for in our string plus the number of times that character is repeated
			// maxCount = Math.max(maxCount, currCount); // choose the larger value between maxCount and currCount
			currChar = str[i]; // move on to the next character
			currCount = 1; // count resets to 1 since we have moved on to the next character
		} else { // if there is a match add to the repeated count
			currCount++;
		}

		output = output + currChar + currCount; // wrap up our final output by doing the same thing to what we did under the scenario where the character we are searching for in string matches the character being looked at
		// maxCount = Math.max(maxCount, currCount);

		return output;
	}
}

// Method that performs basic string compression using the counts of repeated characters

function compressStr(str) {
	var output = "", // will return this variable as final compressed string
		currChar = "", // represents character we are searching for in string
		currCount = ""; // counts number of times character is repeated

	// iterate through entire string
	for (i = 0; i < str.length; i++) {
		// if the character we are searching for in string matches the character being looked at
		if (currChar !== str[i]) {
			output = output + currChar + currCount; // output is sum of the output we have so far plus the character we have been searching for in our string plus the number of times that character is repeated
			currChar = str[i]; // move on to the next character
			currCount = 1; // count resets to 1 since we have moved on to the next character
		} else { // if there is a match add to the repeated count
			currCount++;
		}
	}
	output = output + currChar + currCount; // wrap up our final output by doing the same thing to what we did under the scenario where currChar does not match the character being looked at

	return output;
}

// test cases
compressStr("aaaaaaaa"); // expect to return "a8"
compressStr("abbcccdddd"); // expect to return "a1b2c3d4"

Debug and add test cases


'use strict';

var pdf = require( './../lib' );

var x;
var mu;
var sigma;
var v;
var i;

for ( i = 0; i < 10; i++ ) {
	x = Math.random() * 10;
	mu = Math.random() * 10 - 5;
	sigma = Math.random() * 20;
	v = pdf( x, mu, sigma );
	console.log( 'x: %d, mu: %d, sigma: %d, f(x;mu,sigma): %d', x, mu, sigma, v );
}

'use strict';

var pdf = require( './../lib' );

var sigma;
var mu;
var x;
var v;
var i;

for ( i = 0; i < 10; i++ ) {
	x = Math.random() * 10;
	mu = Math.random() * 10 - 5;
	sigma = Math.random() * 20;
	v = pdf( x, mu, sigma );
	console.log( 'x: %d, mu: %d, sigma: %d, f(x;mu,sigma): %d', x, mu, sigma, v );
}

Reorder vars according to length


app.controller('HomeController', ['$http', 'AuthFactory', function($http, AuthFactory) {
  console.log('HomeController running');
  var self = this;
  self.userStatus = AuthFactory.userStatus;

  self.dataArray = [{
      src: '../assets/images/carousel/genome-sm.jpg'
    },
    {
      src: '../assets/images/carousel/falkirk-wheel-sm.jpg'
    },
    {
      src: '../assets/images/carousel/iss-sm.jpg'
    },
    {
      src: '../assets/images/carousel/shark-sm.jpg'
    },
    {
      src: '../assets/images/carousel/snowflake-sm.jpg'
    },
    {
      src: '../assets/images/carousel/virus-sm.jpg'
    },
    {
      src: '../assets/images/carousel/rock-formation-sm.jpg'
    },
    {
      src: '../assets/images/carousel/circuit-board-sm.jpg'
    }
  ];
}]);

app.controller('HomeController', ['$http', 'AuthFactory', function($http, AuthFactory) {
  console.log('HomeController running');
  var self = this;
  self.userStatus = AuthFactory.userStatus;

  self.dataArray = [
    { src: '../assets/images/carousel/genome-sm.jpg' },
    { src: '../assets/images/carousel/falkirk-wheel-sm.jpg' },
    { src: '../assets/images/carousel/iss-sm.jpg' },
    { src: '../assets/images/carousel/shark-sm.jpg' },
    { src: '../assets/images/carousel/snowflake-sm.jpg' },
    { src: '../assets/images/carousel/virus-sm.jpg' },
    { src: '../assets/images/carousel/rock-formation-sm.jpg' },
    { src: '../assets/images/carousel/circuit-board-sm.jpg' }
  ];
}]);

Refactor carousel image references for better readability


'use babel'

import minimatch from 'minimatch'

export function showError(e) {
  atom.notifications.addError(`[Linter] ${e.message}`, {
    detail: e.stack,
    dismissable: true
  })
}

export function shouldTriggerLinter(linter, wasTriggeredOnChange, scopes) {
  if (wasTriggeredOnChange && !linter.lintOnFly) {
    return false
  }
  return scopes.some(function (scope) {
    return linter.grammarScopes.indexOf(scope) !== -1
  })
}

export function requestUpdateFrame(callback) {
  setTimeout(callback, 100)
}

export function debounce(callback, delay) {
  let timeout = null
  return function(arg) {
    clearTimeout(timeout)
    timeout = setTimeout(() => {
      callback.call(this, arg)
    }, delay)
  }
}

export function isPathIgnored(filePath, ignoredGlob) {
  const projectPaths = atom.project.getPaths()
  const projectPathsLength = projectPaths.length
  let   repository = null
  for (let i = 0; i < projectPathsLength; ++i) {
    const projectPath = projectPaths[i]
    if (filePath.indexOf(projectPath) === 0) {
      repository = atom.project.getRepositories()[i]
      break
    }
  }
  if (repository !== null && repository.isProjectAtRoot() && repository.isPathIgnored(filePath)) {
    return true
  }
  return minimatch(filePath, ignoredGlob)
}

'use babel'

import minimatch from 'minimatch'

export function showError(e) {
  atom.notifications.addError(`[Linter] ${e.message}`, {
    detail: e.stack,
    dismissable: true
  })
}

export function shouldTriggerLinter(linter, wasTriggeredOnChange, scopes) {
  if (wasTriggeredOnChange && !linter.lintOnFly) {
    return false
  }
  return scopes.some(function (scope) {
    return linter.grammarScopes.indexOf(scope) !== -1
  })
}

export function isPathIgnored(filePath, ignoredGlob) {
  const projectPaths = atom.project.getPaths()
  const projectPathsLength = projectPaths.length
  let   repository = null
  for (let i = 0; i < projectPathsLength; ++i) {
    const projectPath = projectPaths[i]
    if (filePath.indexOf(projectPath) === 0) {
      repository = atom.project.getRepositories()[i]
      break
    }
  }
  if (repository !== null && repository.isProjectAtRoot() && repository.isPathIgnored(filePath)) {
    return true
  }
  return minimatch(filePath, ignoredGlob)
}

export function messageKey(message) {
  return (message.text || message.html) + '$' + message.class + '$' + message.name + '$' + (
    message.range ? (message.range.start.column + ':' + message.range.start.row + message.range.end.column + ':' + message.range.end.row) : ''
  )
}

Add a message key helper; also cleanup unused


'use babel'

import Html2JsView from './html2js-view'
import html2js from './html-2-js'
import { CompositeDisposable } from 'atom'

export default {

  html2JsView: null,
  modalPanel: null,
  subscriptions: null,

  activate (state) {
    this.subscriptions = new CompositeDisposable()

    this.html2JsView = new Html2JsView(state.html2JsViewState)
    this.html2JsView.onSubmit((text) => {
      atom.workspace.open().then((textEditor) => {
        let htmlJs = html2js(text, {})
        textEditor.setText(htmlJs)
      })
    })

    this.subscriptions.add(atom.commands.add('atom-workspace', {
      'html2js:toggle': () => this.html2JsView.toggle()
    }))
  },

  deactivate () {
    this.subscriptions.dispose()
    this.subscriptions = null
    this.html2JsView.destroy()
    this.html2JsView = null
  },

  serialize () {
    return {
      html2JsViewState: this.html2JsView.serialize()
    }
  }
}

'use babel'

import Html2JsView from './html2js-view'
import html2js from './html-2-js'
import { CompositeDisposable } from 'atom'

export default {

  html2JsView: null,
  modalPanel: null,
  subscriptions: null,

  activate (state) {
    this.subscriptions = new CompositeDisposable()

    this.html2JsView = new Html2JsView(state.html2JsViewState)
    this.html2JsView.onSubmit((text) => {
      atom.workspace.open().then((textEditor) => {
        let htmlJs = html2js(text, {})
        textEditor.setText(htmlJs)
        textEditor.setGrammar(atom.grammars.grammarForScopeName('source.js'))
      })
    })

    this.subscriptions.add(atom.commands.add('atom-workspace', {
      'html2js:toggle': () => this.html2JsView.toggle()
    }))
  },

  deactivate () {
    this.subscriptions.dispose()
    this.subscriptions = null
    this.html2JsView.destroy()
    this.html2JsView = null
  },

  serialize () {
    return {
      html2JsViewState: this.html2JsView.serialize()
    }
  }
}

Set JS grammar to newly created pane texteditor


module.exports = {
	options: {
		map: true,
		processors: [
			require("pixrem")(), // add fallbacks for rem units for IE8+
			require("autoprefixer")({ browsers: "> 1%, last 2 versions, ie >= 8" }), // add vendor prefixes
		]
	},
	default: {
		src: "dist/stylesheets/*.css"
	}
};

module.exports = {
	options: {
		map: true,
		processors: [
			require("pixrem")({ html: false, atrules: true }), // add fallbacks for rem units for IE8+
			require("autoprefixer")({ browsers: "> 1%, last 2 versions, ie >= 8" }), // add vendor prefixes
		]
	},
	default: {
		src: "dist/stylesheets/*.css"
	}
};

Update post css task to add rem fallbacks properly for IE8


#!/usr/bin/env node

'use strict';

const dependencyTree = require('../');
const program = require('commander');

program
  .version(require('../package.json').version)
  .usage('[options] <filename>')
  .option('-d, --directory <path>', 'location of files of supported filetypes')
  .option('-c, --require-config <path>', 'path to a requirejs config')
  .option('-w, --webpack-config <path>', 'path to a webpack config')
  .option('--list-form', 'output the list form of the tree (one element per line)')
  .parse(process.argv);

let tree;

const options = {
  filename: program.args[0],
  root: program.directory,
  config: program.requireConfig,
  webpackConfig: program.webpackConfig
};

if (program.listForm) {
  tree = dependencyTree.toList(options);

  tree.forEach(function(node) {
    console.log(node);
  });

} else {
  tree = dependencyTree(options);

  console.log(JSON.stringify(tree));
}

#!/usr/bin/env node

'use strict';

const dependencyTree = require('../');
const program = require('commander');

program
  .version(require('../package.json').version)
  .usage('[options] <filename>')
  .option('-d, --directory <path>', 'location of files of supported filetypes')
  .option('-c, --require-config <path>', 'path to a requirejs config')
  .option('-w, --webpack-config <path>', 'path to a webpack config')
  .option('-t, --ts-config <path>', 'path to a typescript config')
  .option('--list-form', 'output the list form of the tree (one element per line)')
  .parse(process.argv);

let tree;

const options = {
  filename: program.args[0],
  root: program.directory,
  config: program.requireConfig,
  webpackConfig: program.webpackConfig,
  tsConfig: program.tsConfig
};

if (program.listForm) {
  tree = dependencyTree.toList(options);

  tree.forEach(function(node) {
    console.log(node);
  });

} else {
  tree = dependencyTree(options);

  console.log(JSON.stringify(tree));
}

Add --tsconfig CLI option to specify a TypeScript config file


import React from 'react';
import InlineEdit from 'react-edit-inline';
import Ansible from '../../utils/Ansible';

var NameEdit = React.createClass({
  propTypes: {
    name: React.PropTypes.string,
    id: React.PropTypes.string
  },
  dataChange(data) {
    Ansible.sendMessage('custom_names', {
      id: this.props.id,
      name: data.name
    });
  },
  render() {
    return (
      <div>
        <InlineEdit
          className="static"
          activeClassName="editing"
          text={this.props.name}
          change={this.dataChange}
          paramName="name"
          style = {{
            backgroundColor: 'white',
            minWidth: 150,
            display: 'inline-block',
            margin: 0,
            padding: 0,
            fontSize:15
          }}
        />
      </div>
    );
  }
});

export default NameEdit;

import React from 'react';
import InlineEdit from 'react-edit-inline';
import Ansible from '../../utils/Ansible';

var NameEdit = React.createClass({
  propTypes: {
    name: React.PropTypes.string,
    id: React.PropTypes.string
  },
  dataChange(data) {
    var x = new RegExp("^[A-Za-z][A-Za-z0-9]+$");
    if (x.test(data.name)) {
      Ansible.sendMessage('custom_names', {
        id: this.props.id,
        name: data.name
      });
    }
  },
  render() {
    return (
      <div>
        <InlineEdit
          className="static"
          activeClassName="editing"
          text={this.props.name}
          change={this.dataChange}
          paramName="name"
          style = {{
            backgroundColor: 'white',
            minWidth: 150,
            display: 'inline-block',
            margin: 0,
            padding: 0,
            fontSize:15
          }}
        />
      </div>
    );
  }
});

export default NameEdit;

Check new peripheral names against regex


import React from 'react';
import Map, {GoogleApiWrapper} from 'google-maps-react';

import { searchNearby } from 'utils/googleApiHelpers';

export class Container extends React.Component {
	onReady(mapProps, map) {
		// when map is ready and
		const { google } = this.props;
		const opts = {
			location: map.center,
			radius: '500',
			types: ['cafe']
		}
		searchNearby(google, map, opts)
			.then((results, pagination) => {
				// we got some results and a pagination object
			}).catch((status, result) => {
				// there was an error
			})
	}
	render() {
		return (
			<div>
				<Map
					onReady={this.onReady.bind(this)}
					google={this.props.google} />
			</div>
		)
	}
}

export default GoogleApiWrapper({
	apiKey: __GAPI_KEY__
})(Container);

import React from 'react';
import Map, {GoogleApiWrapper} from 'google-maps-react';

import { searchNearby } from 'utils/googleApiHelpers';

export class Container extends React.Component {
	constructor(props) {
		// super initializes this
		super(props);

		this.state = {
			places: [],
			pagination: null
		}
	}
	onReady(mapProps, map) {
		// when map is ready
		const { google } = this.props;
		const opts = {
			location: map.center,
			radius: '500',
			types: ['cafe']
		}
		searchNearby(google, map, opts)
			.then((results, pagination) => {
				// we got some results and a pagination object
				this.setState({
					places: results,
					pagination
				})
			}).catch((status, result) => {
				// there was an error
			})
	}
	render() {
		return (

				<Map
					onReady={this.onReady.bind(this)}
					google={this.props.google}
					visible={false} >

					{this.state.places.map(place => {
						return (
{place.name}
)
					})}

				</Map>

		)
	}
}

export default GoogleApiWrapper({
	apiKey: __GAPI_KEY__
})(Container);

Set Main container to be stateful


import stampit from 'stampit';
import {Meta, Model} from './base';

const ApiKeyMeta = Meta({
  name: 'apiKey',
  pluralName: 'apiKeys',
  endpoints: {
    'detail': {
      'methods': ['delete', 'get'],
      'path': '/v1/instances/{instanceName}/api_keys/{id}/'
    },
    'list': {
      'methods': ['post', 'get'],
      'path': '/v1/instances/{instanceName}/api_keys/'
    }
  }
});

const ApiKey = stampit()
  .compose(Model)
  .setMeta(ApiKeyMeta);

export default ApiKey;

import stampit from 'stampit';
import {Meta, Model} from './base';

const ApiKeyMeta = Meta({
  name: 'apiKey',
  pluralName: 'apiKeys',
  endpoints: {
    'detail': {
      'methods': ['delete', 'patch', 'put', 'get'],
      'path': '/v1/instances/{instanceName}/api_keys/{id}/'
    },
    'list': {
      'methods': ['post', 'get'],
      'path': '/v1/instances/{instanceName}/api_keys/'
    }
  }
});

const ApiKey = stampit()
  .compose(Model)
  .setMeta(ApiKeyMeta);

export default ApiKey;

Add methods to api key's detail endpoint


import React from 'react'
import { parsePost } from '../parsers/PostParser'

class PostsAsGrid extends React.Component {
  static propTypes = {
    posts: React.PropTypes.object,
    json: React.PropTypes.object,
    currentUser: React.PropTypes.object,
    gridColumnCount: React.PropTypes.number,
  }

  renderColumn(posts) {
    const { json, currentUser } = this.props
    return (

        {posts.map((post) => {
          return (
            <article ref={`postGrid_${post.id}`} key={post.id} className="Post PostGrid">
              {parsePost(post, json, currentUser)}

          )
        })}

    )
  }

  render() {
    const { posts, gridColumnCount } = this.props
    if (!gridColumnCount) { return null }
    const columns = []
    for (let i = 0; i < gridColumnCount; i++) {
      columns.push([])
    }
    for (const index in posts.data) {
      if (posts.data[index]) {
        columns[index % gridColumnCount].push(posts.data[index])
      }
    }
    return (

        {columns.map((column) => {
          return this.renderColumn(column)
        })}

    )
  }
}

export default PostsAsGrid

import React from 'react'
import { parsePost } from '../parsers/PostParser'

class PostsAsGrid extends React.Component {
  static propTypes = {
    posts: React.PropTypes.object,
    json: React.PropTypes.object,
    currentUser: React.PropTypes.object,
    gridColumnCount: React.PropTypes.number,
  }

  renderColumn(posts, index) {
    const { json, currentUser } = this.props
    return (
      <div className="Column" key={`column_${index}`}>
        {posts.map((post) => {
          return (
            <article ref={`postGrid_${post.id}`} key={post.id} className="Post PostGrid">
              {parsePost(post, json, currentUser)}

          )
        })}

    )
  }

  render() {
    const { posts, gridColumnCount } = this.props
    if (!gridColumnCount) { return null }
    const columns = []
    for (let i = 0; i < gridColumnCount; i++) {
      columns.push([])
    }
    for (const index in posts.data) {
      if (posts.data[index]) {
        columns[index % gridColumnCount].push(posts.data[index])
      }
    }
    return (

        {columns.map((column, index) => {
          return this.renderColumn(column, index)
        })}

    )
  }
}

export default PostsAsGrid

Fix an issue with grid post rendering.


'use strict';
var users = require('../../app/controllers/users');
 var fellows = require('../../app/controllers/fellows');

module.exports = function(app) {
	// Setting up the bootcamp api
	app.route('/camps')
<<<<<<< HEAD
		.get(fellows.list_camp) // login and authorization required
=======
		.get(fellows.list_camp); // login and authorization required
>>>>>>> d27202af5ed201b57b4a5a78a95f3bdc88556d1a
		// .post(fellows.create_camp); //by admin

	app.route('/camps/:campId')
		.get(fellows.read_camp) // login and authorization required
		.put(fellows.update_camp) // login and authorization required
		.delete(fellows.delete_camp); // login and authorization required

	app.route('/camps/:campId/fellows');
		//.get(fellows.list_applicant) // login and authorization required
		// .post(fellows.create_applicant); //by admin

	app.route('/camps/:campId/fellows/:fellowId')
		.get(fellows.read_applicant) // login and authorization required
		.delete(fellows.delete_applicant); // login and authorization required by admin

	// Finish by binding the fellow middleware
	app.param('fellowId', fellows.fellowByID);

};

'use strict';
var users = require('../../app/controllers/users');
 var fellows = require('../../app/controllers/fellows');

module.exports = function(app) {
	// Setting up the bootcamp api
	app.route('/camps')
		.get(fellows.list_camp); // login and authorization required
		// .post(fellows.create_camp); //by admin

	app.route('/camps/:campId')
		.get(fellows.read_camp) // login and authorization required
		.put(fellows.update_camp) // login and authorization required
		.delete(fellows.delete_camp); // login and authorization required

	app.route('/camps/:campId/fellows');
		//.get(fellows.list_applicant) // login and authorization required
		// .post(fellows.create_applicant); //by admin

	app.route('/camps/:campId/fellows/:fellowId')
		.get(fellows.read_applicant) // login and authorization required
		.delete(fellows.delete_applicant); // login and authorization required by admin

	// Finish by binding the fellow middleware
	app.param('fellowId', fellows.fellowByID);

};

Reset fellow server js file


/*
Copyright 2019 New Vector Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from 'react';
import PropTypes from 'prop-types';

export default class Field extends React.PureComponent {
    static propTypes = {
        // The field's id, which binds the input and label together.
        id: PropTypes.string.isRequired,
        // The field's <input> type. Defaults to "text".
        type: PropTypes.string,
        // The field's label string.
        label: PropTypes.string,
        // The field's placeholder string.
        placeholder: PropTypes.string,
        // All other props pass through to the <input>.
    }

    render() {
        const extraProps = Object.assign({}, this.props);

        // Remove explicit props
        delete extraProps.id;
        delete extraProps.type;
        delete extraProps.placeholder;
        delete extraProps.label;

        return

            <input id={this.props.id}
                type={this.props.type || "text"}
                placeholder={this.props.placeholder}
                {...extraProps}
            />
            {this.props.label}

;
    }
}

/*
Copyright 2019 New Vector Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from 'react';
import PropTypes from 'prop-types';

export default class Field extends React.PureComponent {
    static propTypes = {
        // The field's ID, which binds the input and label together.
        id: PropTypes.string.isRequired,
        // The field's <input> type. Defaults to "text".
        type: PropTypes.string,
        // The field's label string.
        label: PropTypes.string,
        // The field's placeholder string.
        placeholder: PropTypes.string,
        // All other props pass through to the <input>.
    }

    render() {
        const extraProps = Object.assign({}, this.props);

        // Remove explicit props
        delete extraProps.id;
        delete extraProps.type;
        delete extraProps.placeholder;
        delete extraProps.label;

        return

            <input id={this.props.id}
                type={this.props.type || "text"}
                placeholder={this.props.placeholder}
                {...extraProps}
            />
            {this.props.label}

;
    }
}

Tweak comment about field ID


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

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

  return new WebSocket(signalhost);
}

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

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

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

var WebSocket = require('ws');
var reHttpSignalhost = /^http(.*)$/;
var reTrailingSlash = /\/$/;
var pingers = [];
var PINGHEADER = 'primus::ping::';
var PONGHEADER = 'primus::pong::';

function connect(signalhost) {
  var socket;

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

  socket = new WebSocket(signalhost);

  socket.on('message', function(data) {
    if (data.slice(0, PONGHEADER.length) === PONGHEADER) {
      queuePing(socket);
    }
  });

  queuePing(socket);
  return socket;
}

function queuePing(socket) {
  pingers.push(socket);
}

setInterval(function() {
  pingers.splice(0).forEach(function(socket) {
    if (socket.readyState === 1) {
      socket.send(PINGHEADER + Date.now(), function(err) {
        if (err) {
          console.log('could not send ping: ', err);
        }
      });
    }
  });
}, 10e3);

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

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

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

Improve node socket connection to ping as per what a primus server expects


var Reporter = require('../reporter');

var lcovRecord = function(data) {
  var str = "",
      lineHandled = 0,
      lineFound = 0,
      fileName = data.fileName;

  if(this.options.lcovOptions && this.options.lcovOptions.renamer){
    fileName = this.options.lcovOptions.renamer(fileName);
  }

  str += 'SF:' + fileName + '\n';
  data.lines.forEach(function(value, num) {
    if (value !== null) {
      str += 'DA:' + num + ',' + value + '\n';
      lineFound += 1;
      if (value > 0) {
        lineHandled += 1;
      }
    }
  });

  str += 'LF:' + lineFound + '\n';
  str += 'LH:' + lineHandled + '\n';
  str += 'end_of_record';
  return str;
};
/**
 * LCOVReporter outputs lcov formatted coverage data
 * from the test run
 *
 * @class  LCOVReporter
 * @param {Object} options hash of options interpreted by reporter
 */
module.exports = Reporter.extend({
  name: 'lcov',
  defaultOutput: 'lcov.dat',
  transform: function(coverageData) {
    var data = coverageData.fileData.map(lcovRecord, this);
    return data.join('\n');
  }
});

var Reporter = require('../reporter');

var lcovRecord = function(data) {
  var str = '',
      lineHandled = 0,
      lineFound = 0,
      fileName = data.fileName;

  if (this.options.cliOptions && this.options.cliOptions.lcovOptions && this.options.cliOptions.lcovOptions.renamer){
    fileName = this.options.cliOptions.lcovOptions.renamer(fileName);
  }

  str += 'SF:' + fileName + '\n';
  data.lines.forEach(function(value, num) {
    if (value !== null) {
      str += 'DA:' + num + ',' + value + '\n';
      lineFound += 1;
      if (value > 0) {
        lineHandled += 1;
      }
    }
  });

  str += 'LF:' + lineFound + '\n';
  str += 'LH:' + lineHandled + '\n';
  str += 'end_of_record';
  return str;
};
/**
 * LCOVReporter outputs lcov formatted coverage data
 * from the test run
 *
 * @class  LCOVReporter
 * @param {Object} options hash of options interpreted by reporter
 */
module.exports = Reporter.extend({
  name: 'lcov',
  defaultOutput: 'lcov.dat',
  transform: function(coverageData) {
    var data = coverageData.fileData.map(lcovRecord, this);
    return data.join('\n');
  }
});

Fix renamer, lcov check was not accessing the proper object


var schema = require('signalk-schema');

function BaseWidget(id, options, streamBundle, instrumentPanel) {
  this.instrumentPanel = instrumentPanel;
}

BaseWidget.prototype.setActive = function(value) {
  this.options.active = value;
  this.instrumentPanel.onWidgetChange(this);
}

BaseWidget.prototype.getLabelForPath = function(path) {
  var i18nElement = schema.i18n.en[path];
  return i18nElement ?
    i18nElement.shortName || i18nElement.longName || "??" :
    path
}

BaseWidget.prototype.getUnitForPath = function(path) {
  var i18nElement = schema.i18n.en[path];
  return i18nElement ? i18nElement.unit : undefined;
}

module.exports = BaseWidget;

var schema = require('signalk-schema');

function BaseWidget(id, options, streamBundle, instrumentPanel) {
  this.instrumentPanel = instrumentPanel;
}

BaseWidget.prototype.setActive = function(value) {
  this.options.active = value;
  this.instrumentPanel.onWidgetChange(this);
}

BaseWidget.prototype.getLabelForPath = function(path) {
  var i18nElement = schema.i18n.en[path];
  return i18nElement ?
    i18nElement.shortName || i18nElement.longName || "??" :
    path
}

BaseWidget.prototype.getUnitForPath = function(path) {
  var i18nElement = schema.i18n.en[path];
  return i18nElement ? i18nElement.unit : undefined;
}

BaseWidget.prototype.displayValue = function(value) {
  if(typeof value === 'number') {
    var v = Math.abs(value);
    if(v >= 50) {
      return value.toFixed(0);
    } else if(v >= 10) {
      return value.toFixed(1);
    } else {
      return value.toFixed(2);
    }
  }
  return value;
}

module.exports = BaseWidget;

Add shared function for range sensitive rounding


/*global describe, it*/
/*
 * mochify.js
 *
 * Copyright (c) 2014 Maximilian Antoni <mail@maxantoni.de>
 *
 * @license MIT
 */
'use strict';

var assert = require('assert');
var run    = require('./fixture/run');

describe('stack', function () {
  this.timeout(3000);

  it('does not screw up xunit', function (done) {
    run('passes', ['-R', 'xunit'], function (code, stdout) {
      var lines = stdout.split('\n');
      assert.equal(lines[1].substring(0, lines[1].length - 3),
          '<testcase classname="test" name="passes" time="0');
      assert.equal(lines[2], '</testsuite>');
      assert.equal(code, 0);
      done();
    });
  });

});

/*global describe, it*/
/*
 * mochify.js
 *
 * Copyright (c) 2014 Maximilian Antoni <mail@maxantoni.de>
 *
 * @license MIT
 */
'use strict';

var assert = require('assert');
var run    = require('./fixture/run');

describe('stack', function () {
  this.timeout(3000);

  it('does not screw up xunit', function (done) {
    run('passes', ['-R', 'xunit'], function (code, stdout) {
      var lines = stdout.split('\n');
      var expect = '<testcase classname="test" name="passes" time="0';
      assert.equal(lines[1].substring(0, expect.length), expect);
      assert.equal(lines[2], '</testsuite>');
      assert.equal(code, 0);
      done();
    });
  });

});

Fix test case to not fail due to varying timing (again)


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

/**
 * Plugin to show app settings in results list
 * @param  {String} term
 */
const settingsPlugin = (term, callback) => {
  if (!term.match(/^(show\s+)?(settings|preferences)\s*/i)) return;
  callback([{
    icon: '/Applications/Cerebro.app',
    title: 'Cerebro settings',
    id: `settings`,
    getPreview: () => <Settings />
  }]);
};

export default {
  name: 'Cerebro settings',
  icon: '/Applications/Cerebro.app',
  keyword: 'Settings',
  fn: settingsPlugin
};

import React from 'react';
import search from 'lib/search';
import Settings from './Settings';

// Settings plugin name
const NAME = 'Cerebro Settings';

// Phrases that used to find settings plugins
const KEYWORDS = [
  NAME,
  'Cerebro Preferences'
];

/**
 * Plugin to show app settings in results list
 * @param  {String} term
 */
const settingsPlugin = (term, callback) => {
  const found = search(KEYWORDS, term).length > 0;
  if (found) {
    const results = [{
      icon: '/Applications/Cerebro.app',
      title: NAME,
      term: NAME,
      id: 'settings',
      getPreview: () => <Settings />
    }];
    callback(results);
  }
}

export default {
  name: NAME,
  fn: settingsPlugin
};

Improve search if settings plugin Now available by settings and preferences words


const withOffline = require("next-offline");

const nextConfig = {
  target: "serverless",
  future: {
    webpack5: true,
  },
  workboxOpts: {
    swDest: "static/service-worker.js",
    runtimeCaching: [
      {
        urlPattern: /^https?.*/,
        handler: "NetworkFirst",
        options: {
          cacheName: "https-calls",
          networkTimeoutSeconds: 15,
          expiration: {
            maxEntries: 150,
            maxAgeSeconds: 30 * 24 * 60 * 60
          },
          cacheableResponse: {
            statuses: [0, 200]
          }
        }
      }
    ]
  }
};

module.exports = withOffline(nextConfig);

const withOffline = require("next-offline");

const nextConfig = {
  target: "serverless",
  workboxOpts: {
    runtimeCaching: [
      {
        urlPattern: /^https?.*/,
        handler: "NetworkFirst",
        options: {
          cacheName: "offlineCache",
          expiration: {
            maxEntries: 200
          }
        }
      }
    ]
  }
};

module.exports = withOffline(nextConfig);

Swap back to webpack 4, adjust next-offline


/* Retrieve any previously set cookie and send to content script */

browser.tabs.onUpdated.addListener(cookieUpdate);

function getActiveTab() {
  return browser.tabs.query({active: true, currentWindow: true});
}

function cookieUpdate(tabId, changeInfo, tab) {
  getActiveTab().then((tabs) => {
    // get any previously set cookie for the current tab
    var gettingCookies = browser.cookies.get({
      url: tabs[0].url,
      name: "bgpicker"
    });
    gettingCookies.then((cookie) => {
      if (cookie) {
        var cookieVal = JSON.parse(cookie.value);
        browser.tabs.sendMessage(tabs[0].id, {image: cookieVal.image});
        browser.tabs.sendMessage(tabs[0].id, {color: cookieVal.color});
      }
    });
  });
}

/* Retrieve any previously set cookie and send to content script */

function getActiveTab() {
  return browser.tabs.query({active: true, currentWindow: true});
}

function cookieUpdate() {
  getActiveTab().then((tabs) => {
    // get any previously set cookie for the current tab
    var gettingCookies = browser.cookies.get({
      url: tabs[0].url,
      name: "bgpicker"
    });
    gettingCookies.then((cookie) => {
      if (cookie) {
        var cookieVal = JSON.parse(cookie.value);
        browser.tabs.sendMessage(tabs[0].id, {image: cookieVal.image});
        browser.tabs.sendMessage(tabs[0].id, {color: cookieVal.color});
      }
    });
  });
}

// update when the tab is updated
browser.tabs.onUpdated.addListener(cookieUpdate);
// update when the tab is activated
browser.tabs.onActivated.addListener(cookieUpdate);

Update on tab activate as well as update


var ready = function() {
	$('#FontSize a').click(function() {
		$('.content').css("font-size", this.dataset.size);
	});

	$('#FontFamily a').click(function(){
		$('.content').css("font-family", this.innerHTML);
	});

	$('#Leading a').click(function(){
		$('.content').css("line-height", this.dataset.spacing);
	});

	$('#Background a').click(function() {
		$('body').css("background-color", $(this).css('background-color'));
	});
};

$(document).ready(ready);
$(document).on('page:load', ready);

// http://www.quirksmode.org/js/cookies.html
function createCookie(name, value, days) {
	var expires;

	if (days) {
		var date = new Date();
		date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
		expires = "; expires=" + date.toGMTString();
	} else {
		expires = "";
	}
	document.cookie = encodeURIComponent(name) + "=" + encodeURIComponent(value) + expires + "; path=/";
}

function readCookie(name) {
	var nameEQ = encodeURIComponent(name) + "=";
	var ca = document.cookie.split(';');
	for (var i = 0; i < ca.length; i++) {
		var c = ca[i];
		while (c.charAt(0) === ' ') c = c.substring(1, c.length);
		if (c.indexOf(nameEQ) === 0) return decodeURIComponent(c.substring(nameEQ.length, c.length));
	}
	return null;
}

var ready = function() {
	var size = readCookie('font-size');
	var family = readCookie('font-family');
	var height = readCookie('line-height');
	var background = readCookie('background');

	if (size) $('.content').css("font-size", size);
	if (family) $('.content').css("font-family", family);
	if (height) $('.content').css("line-height", height);
	if (background) $('body').css("background-color", background);

	$('#FontSize a').click(function() {
		$('.content').css("font-size", this.dataset.size);
		createCookie('font-size', this.dataset.size);
	});

	$('#FontFamily a').click(function(){
		$('.content').css("font-family", this.innerHTML);
		createCookie('font-family', this.innerHTML);
	});

	$('#Leading a').click(function(){
		$('.content').css("line-height", this.dataset.spacing);
		createCookie('line-height', this.dataset.spacing);
	});

	$('#Background a').click(function() {
		$('body').css("background-color", $(this).css('background-color'));
		createCookie('background', $(this).css('background-color'));
	});
};

$(document).ready(ready);
$(document).on('page:load', ready);

Use cookies to persist formatting options


$(function() {
  if ($('small.error').length > 0) {
    $('html, body').animate({
      scrollTop: ($('small.error').first().offset().top)
    },500);
  }
});

$(function() {
  if ($('small.error').length > 0) {
    $('html, body').animate({
      scrollTop: ($('small.error').first().offset().top-100)
    },500);
  }
});

Improve scrolling to error feature


var config = require('../../config/config.js');
var express = require('express');
var router = express.Router();
var mongoose = require('mongoose');
var Observation = mongoose.model('Observation');
var realtime = require('../realtime/realtime.js');

module.exports = function (app) {
    app.use('/observations', router);
};

router.post('/', function (req, res, next) {
    var newObservation = new Observation(req.body);
    newObservation.save(function (err, doc, n) {
        if (err) {
            console.log(err);
            if (err.name === "ValidationError") {
                return next({ status: 422, message: "Invalid observation data" });
            } else {
                return next(err);
            }
        }
        //realtime.notifyObservation(req.body);
        return res.status(201).location('/observations/' + newObservation._id).end();
    });
});

var config = require('../../config/config.js');
var express = require('express');
var router = express.Router();
var mongoose = require('mongoose');
var Observation = mongoose.model('Observation');
var realtime = require('../realtime/realtime.js');

module.exports = function (app) {
    app.use('/observations', router);
};

router.post('/', function (req, res, next) {
    var newObservation = new Observation(req.body);
    newObservation.save(function (err, doc, n) {
        if (err) {
            console.log(err);
            if (err.name === "ValidationError") {
                return next({ status: 422, message: "Invalid observation data" });
            } else {
                return next(err);
            }
        }
        realtime.notifyObservation(req.body);
        return res.status(201).location('/observations/' + newObservation._id).end();
    });
});

Fix bug by restoring calls to socket.io service


var fs = require('fs');

module.exports = function (context, xmlZipBlob) {
    context.log('Node.js blob trigger function processed blob:', xmlZipBlob);
    context.log(`typeof xmlZipBlob:`, typeof xmlZipBlob);

    fs.writeFile('xmlZip.txt', "Hello World", 'utf8', (err) => {
        if (err) {
            throw err;
        }

        context.log('saved blob to loal file called xmlZip.zip');
        context.done();
    });
};

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

module.exports = function (context, xmlZipBlob) {
    context.log('Node.js blob trigger function processed blob:', xmlZipBlob);
    context.log(`typeof xmlZipBlob:`, typeof xmlZipBlob);

    var tempDirectory = process.env["TMP"];
    var tempFileName = "xmlZip.zip";
    var fileDestination = path.join(tempDirectory, tempFileName);

    context.log('Writing blob file to: ', fileDestination);

    fs.writeFile(fileDestination, xmlZipBlob, function (error, result) {
        if (error) {
            throw error;
        }

        context.log('saved blob to loal file called: ', tempFileName);
        context.done();
    });
};

Update xmlBlob function to write to temp directory


 (function() {
     function timecode() {
         return function(seconds) {
             var seconds = Number.parseFloat(seconds);

             if (Number.isNaN(seconds)) {
                return '-:--';
                }
             var wholeSeconds = Math.floor(seconds);
             var minutes = Math.floor(wholeSeconds / 60);
             var remainingSeconds = wholeSeconds % 60;

             var output = minutes + ':';

             if (remainingSeconds < 10) {
                 output += '0';
             }

             output += remainingSeconds;

             return output;
         };
     }

     angular
         .module('blocJams')
         .filter('timecode', timecode);
 })();

 (function() {
     function timecode() {
         return function(seconds) {

         /**
             var seconds = Number.parseFloat(seconds);

             if (Number.isNaN(seconds)) {
                return '-:--';
                }
             var wholeSeconds = Math.floor(seconds);
             var minutes = Math.floor(wholeSeconds / 60);
             var remainingSeconds = wholeSeconds % 60;

             var output = minutes + ':';

             if (remainingSeconds < 10) {
                 output += '0';
             }

             output += remainingSeconds;

             return output;
        */

             var output = buzz.toTimer(seconds);
             return output;
         };
     }

     angular
         .module('blocJams')
         .filter('timecode', timecode);
 })();

Use buzz.toTimer() method for time filter


const html = require('choo/html');
const callcount = require('./callcount.js');
const chai = require('chai');
const expect = chai.expect;

// Skip a test if the browser does not support locale-based number formatting
function ifLocaleSupportedIt (test) {
  if (window.Intl && window.Intl.NumberFormat) {
    it(test);
  }
  else {
    it.skip(test);
  }
}

describe('callcount component', () => {
  ifLocaleSupportedIt('should properly format call total >=1000 with commas', () => {
    let state = {totalCalls: '123456789'};
    let result = callcount(state);
    expect(result.textContent).to.contain('123,456,789');
  });

  ifLocaleSupportedIt('should properly format call total < 1000 without commas', () => {
    const totals = '123';
    let state = {totalCalls: totals};
    let result = callcount(state);
    expect(result.textContent).to.contain(totals);
    expect(result.textContent).to.not.contain(',');
  });

  ifLocaleSupportedIt('should not format zero call total', () => {
    const totals = '0';
    let state = {totalCalls: totals};
    let result = callcount(state);
    expect(result.textContent).to.contain(totals);
    expect(result.textContent).to.not.contain(',');
  });

});

const html = require('choo/html');
const callcount = require('./callcount.js');
const chai = require('chai');
const expect = chai.expect;

// Skip a test if the browser does not support locale-based number formatting
function ifLocaleSupportedIt (name, test) {
  if (window.Intl && window.Intl.NumberFormat) {
    it(name, test);
  }
  else {
    it.skip(name, test);
  }
}

describe('callcount component', () => {
  ifLocaleSupportedIt('should properly format call total >=1000 with commas', () => {
    let state = {totalCalls: '123456789'};
    let result = callcount(state);
    expect(result.textContent).to.contain('123,456,789');
  });

  ifLocaleSupportedIt('should properly format call total < 1000 without commas', () => {
    const totals = '123';
    let state = {totalCalls: totals};
    let result = callcount(state);
    expect(result.textContent).to.contain(totals);
    expect(result.textContent).to.not.contain(',');
  });

  ifLocaleSupportedIt('should not format zero call total', () => {
    const totals = '0';
    let state = {totalCalls: totals};
    let result = callcount(state);
    expect(result.textContent).to.contain(totals);
    expect(result.textContent).to.not.contain(',');
  });

});

Fix support check that skips tests on ALL browsers


function startTime() {
    var today=new Date();
    var h=checkTime(today.getHours());
    var m=checkTime(today.getMinutes());
    var s=checkTime(today.getSeconds());
    var timeString = s%2===0 ? h + ":" + m + ":" + s : h + ":" + m + " " + s;
    document.getElementById("time").innerHTML = timeString;
    var twentyFour = 10.625;
    var sixty = 4.25;
    var c1 = Math.round(twentyFour*h).toString(16);
    var c2 = Math.round(sixty*m).toString(16);
    var c3 = Math.round(sixty*s).toString(16);
    [c1, c2, c3].forEach(function (c) {
      c = c.length === 2 ? c : "0"+c;
    });
    document.getElementById("center-container").style.backgroundColor = "#" + c1 + c2 + c3;
    var t = setTimeout(function(){ startTime(); },500);
}

function checkTime(i) {
    if (i<10) {i = "0" + i;}  // add zero in front of numbers < 10
    return i;
}

startTime();

function startTime() {
    var today=new Date();
    var h=checkTime(today.getHours());
    var m=checkTime(today.getMinutes());
    var s=checkTime(today.getSeconds());
    var timeString = s%2===0 ? h + ":" + m + ":" + s : h + ":" + m + " " + s;
    document.getElementById("time").innerHTML = timeString;
    var twentyFour = 10.625;
    var sixty = 4.25;
    var backgroundColor = "#";
    [
      Math.round(twentyFour*h).toString(16),
      Math.round(sixty*m).toString(16),
      Math.round(sixty*s).toString(16)
    ].forEach(function (c) {
      backgroundColor += c.length === 2 ? c : "0" + c;
    });
    document.getElementById("center-container").style.backgroundColor = backgroundColor;
    var t = setTimeout(function(){ startTime(); },500);
}

function checkTime(i) {
    if (i<10) {i = "0" + i;}  // add zero in front of numbers < 10
    return i;
}

startTime();

Fix bug in background color


DDG.require('maps',function(){
    ddg_spice_maps_maps = function(response) {

        if (!response || !response.length) { return Spice.failed('maps'); }

        // OSM sends back a bunch of places, just want the first one for now
        response = [response[0]];

        if (!DDG.isRelevant(response[0].display_name.toLowerCase())) {
            return Spice.failed('maps');
        }

        Spice.add({
            data: response,
            id: "maps",
            name: "maps",
            model: "Place"
        });

    };
});

DDG.require('maps',function(){
    ddg_spice_maps_maps = function(response) {

        if (!response) { return Spice.failed('maps'); }

        // OSM sends back a bunch of places, just want the first one for now
        response = response.features[0];

        Spice.add({
            data: response,
            id: "maps",
            name: "maps",
            model: "Place"
        });

    };
});

Handle mapbox geocoder response data