import path from 'path';
import webpack from 'webpack';
import config from '../webpack.config';

describe('example', () => {
  it('combines loader results into one object', () =>
    new Promise(resolve => {
      webpack(config, (error, stats) => {
        const bundlePath = path.resolve(
          stats.compilation.compiler.outputPath,
          stats.toJson().assetsByChunkName.main,
        );
        expect(require(bundlePath)).toEqual({
          raw: '---\ntitle: Example\n---\n\nSome markdown\n',
          frontmatter: { title: 'Example' },
          content: '
Some markdown
\n',
        });
        resolve();
      });
    }));
});

import path from 'path';
import webpack from 'webpack';
import config from '../webpack.config';

describe('example', () => {
  jest.setTimeout(10000); // 10 second timeout
  it('combines loader results into one object', () =>
    new Promise(resolve => {
      webpack(config, (error, stats) => {
        const bundlePath = path.resolve(
          stats.compilation.compiler.outputPath,
          stats.toJson().assetsByChunkName.main,
        );
        expect(require(bundlePath)).toEqual({
          raw: '---\ntitle: Example\n---\n\nSome markdown\n',
          frontmatter: { title: 'Example' },
          content: '
Some markdown
\n',
        });
        resolve();
      });
    }));
});

Use a 10-second timeout for the Jest test


(function () {
    "use strict";
    if (window.ol && ol.source && !ol.source.VectorEventType) {
        // HACK: monkey patch event types not present in current OL6 repackage build
        // @see https://github.com/openlayers/openlayers/blob/main/src/ol/source/VectorEventType.js
        ol.source.VectorEventType = {
            ADDFEATURE: 'addfeature',
            CHANGEFEATURE: 'changefeature',
            CLEAR: 'clear',
            REMOVEFEATURE: 'removefeature'
        };
    }
    if (window.ol && !ol.ObjectEventType) {
        // HACK: monkey patch event types not present in current OL6 repackage build
        // @see https://github.com/openlayers/openlayers/blob/main/src/ol/ObjectEventType.js
        ol.ObjectEventType = {
            PROPERTYCHANGE: 'propertychange'
        };
    }
    if (window.ol && ol.interaction && !ol.interaction.ModifyEventType) {
        // HACK: monkey patch event types not present in current OL6 repackage build
        // @see https://github.com/openlayers/openlayers/blob/main/src/ol/interaction/Modify.js#L64
        ol.interaction.ModifyEventType = {
            MODIFYSTART: 'modifystart',
            MODIFYEND: 'modifyend'
        };
    }
})();

(function () {
    "use strict";
    if (window.ol && ol.source && !ol.source.VectorEventType) {
        // HACK: monkey patch event types not present in current OL6 repackage build
        // @see https://github.com/openlayers/openlayers/blob/main/src/ol/source/VectorEventType.js
        ol.source.VectorEventType = {
            ADDFEATURE: 'addfeature',
            CHANGEFEATURE: 'changefeature',
            CLEAR: 'clear',
            REMOVEFEATURE: 'removefeature'
        };
    }
    if (window.ol && !ol.ObjectEventType) {
        // HACK: monkey patch event types not present in current OL6 repackage build
        // @see https://github.com/openlayers/openlayers/blob/main/src/ol/ObjectEventType.js
        ol.ObjectEventType = {
            PROPERTYCHANGE: 'propertychange'
        };
    }
    if (window.ol && ol.interaction && !ol.interaction.ModifyEventType) {
        // HACK: monkey patch event types not present in current OL6 repackage build
        // @see https://github.com/openlayers/openlayers/blob/main/src/ol/interaction/Modify.js#L64
        ol.interaction.ModifyEventType = {
            MODIFYSTART: 'modifystart',
            MODIFYEND: 'modifyend'
        };
    }
    if (window.ol && ol.interaction && !ol.interaction.TranslateEventType) {
        // HACK: monkey patch event types not present in current OL6 repackage build
        // @see https://github.com/openlayers/openlayers/blob/main/src/ol/interaction/Translate.js#L12
        ol.interaction.TranslateEventType = {
            TRANSLATESTART: 'translatestart',
            TRANSLATING: 'translating',
            TRANSLATEEND: 'translateend'
        };
    }
})();

Fix "moveFeature" tool initialization errors


module.exports = /* glsl */ `
#if !defined(USE_TANGENTS) && (defined(USE_NORMAL_MAP) || defined(USE_CLEAR_COAT_NORMAL_MAP))
  //http://www.thetenthplanet.de/archives/1180
  mat3 cotangentFrame(vec3 N, vec3 p, vec2 uv) {
    // get edge vectors of the pixel triangle
    vec3 dp1 = dFdx(p);
    vec3 dp2 = dFdy(p);
    vec2 duv1 = dFdx(uv);
    vec2 duv2 = dFdy(uv);

    // solve the linear system
    vec3 dp2perp = cross(dp2, N);
    vec3 dp1perp = cross(N, dp1);
    vec3 T = dp2perp * duv1.x + dp1perp * duv2.x;
    vec3 B = dp2perp * duv1.y + dp1perp * duv2.y;

    // construct a scale-invariant frame
    float invmax = 1.0 / sqrt(max(dot(T,T), dot(B,B)));
    return mat3(normalize(T * invmax), normalize(B * invmax), N);
  }

  vec3 perturb(vec3 map, vec3 N, vec3 V, vec2 texcoord) {
    mat3 TBN = cotangentFrame(N, -V, texcoord);
    return normalize(TBN * map);
  }
#endif
`

module.exports = /* glsl */ `
#if !defined(USE_TANGENTS) && (defined(USE_NORMAL_MAP) || defined(USE_CLEAR_COAT_NORMAL_MAP))
  //http://www.thetenthplanet.de/archives/1180
  mat3 cotangentFrame(vec3 N, vec3 p, vec2 uv) {
    // get edge vectors of the pixel triangle
    highp vec3 dp1 = dFdx(p);
    highp vec3 dp2 = dFdy(p);
    highp vec2 duv1 = dFdx(uv);
    highp vec2 duv2 = dFdy(uv);

    // solve the linear system
    vec3 dp2perp = cross(dp2, N);
    vec3 dp1perp = cross(N, dp1);
    vec3 T = dp2perp * duv1.x + dp1perp * duv2.x;
    vec3 B = dp2perp * duv1.y + dp1perp * duv2.y;

    // construct a scale-invariant frame
    float invmax = 1.0 / sqrt(max(dot(T,T), dot(B,B)));
    return mat3(normalize(T * invmax), normalize(B * invmax), N);
  }

  vec3 perturb(vec3 map, vec3 N, vec3 V, vec2 texcoord) {
    mat3 TBN = cotangentFrame(N, -V, texcoord);
    return normalize(TBN * map);
  }
#endif
`

Use highp for derivatives calculations in perturb normal


/*!
{
  "name": "CSS Animations",
  "property": "cssanimations",
  "caniuse": "css-animation",
  "polyfills": ["transformie", "csssandpaper"],
  "tags": ["css"],
  "warnings": ["Android < 4 will pass this test, but can only animate a single property at a time"],
  "notes": [{
    "name" : "Article: 'Dispelling the Android CSS animation myths'",
    "href": "http://goo.gl/CHVJm"
  }]
}
!*/
/* DOC
Detects wether or not elements can be animated using CSS
*/
define(['Modernizr', 'testAllProps'], function( Modernizr, testAllProps ) {
  Modernizr.addTest('cssanimations', testAllProps('animationName', 'a', true));
});

/*!
{
  "name": "CSS Animations",
  "property": "cssanimations",
  "caniuse": "css-animation",
  "polyfills": ["transformie", "csssandpaper"],
  "tags": ["css"],
  "warnings": ["Android < 4 will pass this test, but can only animate a single property at a time"],
  "notes": [{
    "name" : "Article: 'Dispelling the Android CSS animation myths'",
    "href": "http://goo.gl/OGw5Gm"
  }]
}
!*/
/* DOC
Detects wether or not elements can be animated using CSS
*/
define(['Modernizr', 'testAllProps'], function( Modernizr, testAllProps ) {
  Modernizr.addTest('cssanimations', testAllProps('animationName', 'a', true));
});

Fix broken link in comment


// 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 foundation
//= require turbolinks
//= require_tree
//= require d3

$(function(){ $(document).foundation(); });

var loader = document.getElementById('loader');
startLoading();
finishedLoading();
function startLoading() {
  loader.className = '';
}
function finishedLoading() {
  loader.className = 'done';
  setTimeout(function() {
    loader.className = 'hide';
  }, 5000);
}

$('a.close').click(function() {
   var qqq = $(this).closest('.modal');
   $(qqq).removeClass('active');
});

// 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 foundation
//= require turbolinks
//= require_tree
//= require d3

$(function(){ $(document).foundation(); });

var loader = document.getElementById('loader');
startLoading();
function startLoading() {
  loader.className = '';
}
function finishedLoading() {
  loader.className = 'done';
  setTimeout(function() {
    loader.className = 'hide';
  }, 5000);
}

Remove finishedLoading() call, remove a.close on click method - not using, it was there for trying out items purposes


$(document).ready(function() {

  $("#progress").on('submit', '.new_rating', function(event) {
    event.preventDefault();
    const $form = $(this),
          url = $form.attr('action'),
          method = $form.attr('method'),
          data = $form.serialize();

    $.ajax({
      url: url,
      method: method,
      data: data,
      dataType: "json"
    })
    .success(function(response) {
      $form.closest('.modal')[0].style.display = "none";
      // update score
      $form.closest('.collection-item').find('.current-score').text(response[0].score);
      // update goal partial
      $form.closest('.collection-item').find('.rating-partials-container')
        .prepend('

Score: ' + response[0].score +
          '

Comment: ' + response[0].comment + '

');
      // update progress bar
      $('.current-level').text(response[1]);
      $('.progress-bar').css("width:" + response[2] + "%");
    })
      .fail(function(error) {
      console.log(error);
    })
  });
});

$(document).ready(function() {

  $("#progress").on('submit', '.new_rating', function(event) {
    event.preventDefault();
    const $form = $(this),
          url = $form.attr('action'),
          method = $form.attr('method'),
          data = $form.serialize();

    $.ajax({
      url: url,
      method: method,
      data: data,
      dataType: "json"
    })
    .success(function(response) {
      $form.trigger('reset');
      $form.closest('.modal')[0].style.display = "none";
      // update score
      $form.closest('.collection-item').find('.current-score').text(response[0].score);
      // update goal partial
      $form.closest('.collection-item').find('.rating-partials-container')
        .prepend('

Score: ' + response[0].score +
          '

Comment: ' + response[0].comment + '

');
      // update progress bar
      $('.current-level').text(response[1]);
      $('.progress-bar').css("width:" + response[2] + "%");
    })
      .fail(function(error) {
      console.log(error);
    })
  });
});

Refactor rating jquery to clear rating form on submit


import React, { Component } from "react";
import styled from "styled-components";

import { Row, RowContent, ColumnContainer } from "../SharedComponents.js";

import Loading from "../loading.js";

import List from "./List.js";
import CreateList from "./CreateList.js";
import Notes from "./Notes.js";

class ListsView extends Component {
  render() {
    const { user, lists, loading } = this.props;

    if (loading) {
      return <Loading />;
    }

    return (
      <ColumnContainer>
        <Row>
          <RowContent>
            Du är: {user.nick}
          </RowContent>
        </Row>

        <ListsContainer>
          {lists.map(list => <List key={list.id} list={list} user={user} />)}
          <Notes />
          {user.isAdmin && <CreateList />}
        </ListsContainer>
      </ColumnContainer>
    );
  }
}

const ListsContainer = styled(Row)`
  padding-top: 2em;
`;

export default ListsView;

import React, { Component } from "react";
import styled from "styled-components";

import { Row, RowContent, ColumnContainer } from "../SharedComponents.js";

import Loading from "../loading.js";

import List from "./List.js";
import CreateList from "./CreateList.js";
import Notes from "./Notes.js";

class ListsView extends Component {
  render() {
    const { user, lists, loading } = this.props;

    if (loading) {
      return <Loading />;
    }

    return (
      <ColumnContainer>
        <Row>
          <RowContent>
            Du är: {user.nick}
          </RowContent>
        </Row>

        <MainContainer>
          <ListContainer width={(lists.length - 2) * 20}>
            {lists.map(list => <List key={list.id} list={list} user={user} />)}
          </ListContainer>
          {user.isAdmin && <CreateList />}
          <Notes />
        </MainContainer>
      </ColumnContainer>
    );
  }
}

const ListContainer = Row.extend`
  margin-left: -${props => props.width}em;
`;

const MainContainer = styled(Row)`
  padding-top: 2em;
`;

export default ListsView;

Move lists to the left


#!/usr/bin/env node
'use strict';
const program = require('commander');
const addLineNumbers = require('add-line-numbers');
const fs = require('fs');
const listAndRun = require('./list-and-run').listAndRun;
const path = '/tmp/te';

program.parse(process.argv);

const line = '\n' + program.args;
fs.appendFileSync(path, line);

listAndRun();

#!/usr/bin/env node
'use strict';
const program = require('commander');
const fs = require('fs');
const listAndRun = require('./list-and-run').listAndRun;
const path = '/tmp/te';

program.parse(process.argv);

const line = program.args;
const file = fs.readFileSync(path, 'utf8');

var lines = [];
if (file.length) {
    var lines = file.split('\n');
}
lines.push(line);

fs.writeFileSync(path, lines.join('\n'));

listAndRun();

Make sure append doesn't create an empty line when the first line of code is added


'use strict';

module.exports = function setLastUpdatedTimeToLayergroup () {
    return function setLastUpdatedTimeToLayergroupMiddleware (req, res, next) {
        const { mapConfigProvider, analysesResults } = res.locals;
        const layergroup = res.body;

        mapConfigProvider.createAffectedTables((err, affectedTables) => {
            if (err) {
                return next(err);
            }

            if (!affectedTables) {
                return next();
            }

            var lastUpdateTime = affectedTables.getLastUpdatedAt();

            lastUpdateTime = getLastUpdatedTime(analysesResults, lastUpdateTime) || lastUpdateTime;

            // last update for layergroup cache buster
            layergroup.layergroupid = layergroup.layergroupid + ':' + lastUpdateTime;
            layergroup.last_updated = new Date(lastUpdateTime).toISOString();

            res.locals.cache_buster = lastUpdateTime;

            next();
        });
    };
};

function getLastUpdatedTime (analysesResults, lastUpdateTime) {
    if (!Array.isArray(analysesResults)) {
        return lastUpdateTime;
    }
    return analysesResults.reduce(function (lastUpdateTime, analysis) {
        return analysis.getNodes().reduce(function (lastNodeUpdatedAtTime, node) {
            var nodeUpdatedAtDate = node.getUpdatedAt();
            var nodeUpdatedTimeAt = (nodeUpdatedAtDate && nodeUpdatedAtDate.getTime()) || 0;
            return nodeUpdatedTimeAt > lastNodeUpdatedAtTime ? nodeUpdatedTimeAt : lastNodeUpdatedAtTime;
        }, lastUpdateTime);
    }, lastUpdateTime);
}

'use strict';

module.exports = function setLastUpdatedTimeToLayergroup () {
    return function setLastUpdatedTimeToLayergroupMiddleware (req, res, next) {
        const { mapConfigProvider, analysesResults } = res.locals;
        const layergroup = res.body;

        mapConfigProvider.createAffectedTables((err, affectedTables) => {
            if (err) {
                return next(err);
            }

            if (!affectedTables) {
                res.locals.cache_buster = 0;
                layergroup.layergroupid = `${layergroup.layergroupid}:${res.locals.cache_buster}`;
                layergroup.last_updated = new Date(res.locals.cache_buster).toISOString();

                return next();
            }

            var lastUpdateTime = affectedTables.getLastUpdatedAt();

            lastUpdateTime = getLastUpdatedTime(analysesResults, lastUpdateTime) || lastUpdateTime;

            // last update for layergroup cache buster
            layergroup.layergroupid = layergroup.layergroupid + ':' + lastUpdateTime;
            layergroup.last_updated = new Date(lastUpdateTime).toISOString();

            res.locals.cache_buster = lastUpdateTime;

            next();
        });
    };
};

function getLastUpdatedTime (analysesResults, lastUpdateTime) {
    if (!Array.isArray(analysesResults)) {
        return lastUpdateTime;
    }
    return analysesResults.reduce(function (lastUpdateTime, analysis) {
        return analysis.getNodes().reduce(function (lastNodeUpdatedAtTime, node) {
            var nodeUpdatedAtDate = node.getUpdatedAt();
            var nodeUpdatedTimeAt = (nodeUpdatedAtDate && nodeUpdatedAtDate.getTime()) || 0;
            return nodeUpdatedTimeAt > lastNodeUpdatedAtTime ? nodeUpdatedTimeAt : lastNodeUpdatedAtTime;
        }, lastUpdateTime);
    }, lastUpdateTime);
}

Set cache buster equal to 0 when there is no affected tables in the mapconfig


import $ from 'jquery';

export default function() {
    const $mobileMenuTrigger = $('[data-mobilemenu]');
    const $header = $('.header');
    const headerHeight = $('.header').outerHeight();
    const $mobileMenu = $('#mobile-menu');

    function mobileMenu() {
        $mobileMenuTrigger.on('click', toggleMobileMenu);
    }

    function toggleMobileMenu(e) {
        e.preventDefault();

        calculateMobileMenuOffset();
        targetMenuItemID = $mobileMenuTrigger.data('mobilemenu');

        $mobileMenuTrigger.toggleClass('is-open').attr('aria-expanded', (i, attr) => {
            return attr === 'true' ? 'false' : 'true';
        });

        $mobileMenu.toggleClass('is-open').attr('aria-hidden', (i, attr) => {
            return attr === 'true' ? 'false' : 'true';
        });
    }

    function calculateMobileMenuOffset() {
        $mobileMenu.attr('style', (i, attr) => {
            return attr !== `top:${headerHeight}px` ? `top:${headerHeight}px` : 'top:auto';
        });

        $header.attr('style', (i, attr) => {
            return attr === 'height:100%' ? 'height:auto' : 'height:100%';
        });
    }

    if ($mobileMenuTrigger.length) {
        mobileMenu();
    }
}

import $ from 'jquery';

export default function() {
    const $mobileMenuTrigger = $('[data-mobilemenu]');
    const $header = $('.header');
    const headerHeight = $('.header').outerHeight();
    const $mobileMenu = $('#mobile-menu');

    function mobileMenu() {
        $mobileMenuTrigger.on('click', toggleMobileMenu);
    }

    function toggleMobileMenu(e) {
        e.preventDefault();

        calculateMobileMenuOffset();

        $mobileMenuTrigger.toggleClass('is-open').attr('aria-expanded', (i, attr) => {
            return attr === 'true' ? 'false' : 'true';
        });

        $mobileMenu.toggleClass('is-open').attr('aria-hidden', (i, attr) => {
            return attr === 'true' ? 'false' : 'true';
        });
    }

    function calculateMobileMenuOffset() {
        $mobileMenu.attr('style', (i, attr) => {
            return attr !== `top:${headerHeight}px` ? `top:${headerHeight}px` : 'top:auto';
        });

        $header.attr('style', (i, attr) => {
            return attr === 'height:100%' ? 'height:auto' : 'height:100%';
        });
    }

    if ($mobileMenuTrigger.length) {
        mobileMenu();
    }
}

Fix mobile hamburger menu contents not appearing


import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import jwtDecode from 'jwt-decode';
import styles from './styles.scss';

import Login from './Login';
import Signup from './Signup';
import Info from './Info';

import { addUser } from '../../actions/user';

const mapDispatchToProps = dispatch => ({
  addUserFromToken: token => dispatch(addUser(jwtDecode(token))),
});

class Auth extends Component {
  state = {
    isNewUser: false
  }

  toggleNewUser = () => {
    this.setState({ isNewUser: !this.state.isNewUser });
  }

  render() {
    const { addUserFromToken } = this.props;
    const { isNewUser } = this.state;
    return (

          {
            isNewUser
            ? <Signup switchForm={this.toggleNewUser} addUser={addUserFromToken} />
            : <Login switchForm={this.toggleNewUser} addUser={addUserFromToken} />
          }
          <Info
            message={isNewUser ? 'Take a part in creating better video games!' : 'Welcome back!'}
            isNewUser={isNewUser}
          />

    );
  }
}

Auth.propTypes = {
  addUserFromToken: PropTypes.func.isRequired
};

export default connect(null, mapDispatchToProps)(Auth);

import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import jwtDecode from 'jwt-decode';
import styles from './styles.scss';

import Login from './Login';
import Signup from './Signup';
import Info from './Info';
import Controls from '../Controls';

import { addUser } from '../../actions/user';

const mapDispatchToProps = dispatch => ({
  addUserFromToken: token => dispatch(addUser(jwtDecode(token))),
});

class Auth extends Component {
  state = {
    isNewUser: false
  }

  toggleNewUser = () => {
    this.setState({ isNewUser: !this.state.isNewUser });
  }

  render() {
    const { addUserFromToken } = this.props;
    const { isNewUser } = this.state;
    return (

        <Controls />

          {
            isNewUser
            ? <Signup switchForm={this.toggleNewUser} addUser={addUserFromToken} />
            : <Login switchForm={this.toggleNewUser} addUser={addUserFromToken} />
          }
          <Info
            message={isNewUser ? 'Take a part in creating better video games!' : 'Welcome back!'}
            isNewUser={isNewUser}
          />

    );
  }
}

Auth.propTypes = {
  addUserFromToken: PropTypes.func.isRequired
};

export default connect(null, mapDispatchToProps)(Auth);

Add control buttons to auth


import _ from 'lodash';
import React from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { fetchPosts } from '../actions';

class PostsIndex extends React.Component {
  componentDidMount() {
    this.props.fetchPosts();
  }

  renderPosts() {
    return _.map(this.props.posts, (post) => {
      return (

          {post.title}

      );
    });
  }

  render() {
    return (

          <Link className='btn btn-primary' to='/posts/new'>
            Add a Post
          </Link>

Posts

          { this.renderPosts() }

    )
  }
}

function mapStateToProps(state) {
  return { posts: state.posts };
}

export default connect(mapStateToProps, { fetchPosts: fetchPosts })(PostsIndex);

import _ from 'lodash';
import React from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { fetchPosts } from '../actions';

class PostsIndex extends React.Component {
  componentDidMount() {
    this.props.fetchPosts();
  }

  renderPosts() {
    return _.map(this.props.posts, (post) => {
      return (

          <Link to={`/posts/${post.id}`}>
            {post.title}
          </Link>

      );
    });
  }

  render() {
    return (

          <Link className='btn btn-primary' to='/posts/new'>
            Add a Post
          </Link>

Posts

          { this.renderPosts() }

    )
  }
}

function mapStateToProps(state) {
  return { posts: state.posts };
}

export default connect(mapStateToProps, { fetchPosts: fetchPosts })(PostsIndex);

Add link to link to each page


Posts = new Mongo.Collection('posts');

Posts.allow({
  insert: function(userId, doc) {
    // only allow posting if you are logged in
    return !! userId;
  }
});

Posts = new Mongo.Collection('posts');

Meteor.methods({
  postInsert: function(postAttributes) {
    check(Meteor.userId(), String);
    check(postAttributes, {
      title: String,
      url: String
    });
    var user = Meteor.user();
    var post = _.extend(postAttributes, {
      userId: user._id,
      author: user.username,
      submitted: new Date()
    });
    var postId = Posts.insert(post);
    return {
      _id: postId
    };
  }
});

Use Meteor.methods to set up post (including user)


/**
 * Testing includes
 */

var should = require('should');

/**
 * Unit under test
 */

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

describe('mqtt', function() {
  describe('#createClient', function() {
    it('should return an MqttClient', function() {
      var c = mqtt.createClient();

      c.should.be.instanceOf(mqtt.MqttClient);
    });
  });

  describe('#createSecureClient', function() {
    it('should return an MqttClient', function() {
      var c = mqtt.createClient();

      c.should.be.instanceOf(mqtt.MqttClient);
    });
    it('should throw on incorrect args');
  });

  describe('#createServer', function() {
    it('should return an MqttServer', function() {
      var s = mqtt.createServer();

      s.should.be.instanceOf(mqtt.MqttServer);
    });
  });

  describe('#createSecureServer', function() {
    it('should return an MqttSecureServer', function() {
      var s = mqtt.createSecureServer(
        __dirname + '/helpers/private-key.pem',
        __dirname + '/helpers/public-cert.pem'
      );
      s.should.be.instanceOf(mqtt.MqttSecureServer);
    });
  });
});

/**
 * Testing includes
 */

var should = require('should');

/**
 * Unit under test
 */

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

describe('mqtt', function() {
  describe('#createClient', function() {
    it('should return an MqttClient', function() {
      var c = mqtt.createClient();

      c.should.be.instanceOf(mqtt.MqttClient);
    });
  });

  describe('#createSecureClient', function() {
    it('should return an MqttClient', function() {
      var c = mqtt.createClient();

      c.should.be.instanceOf(mqtt.MqttClient);
    });
    it('should throw on incorrect args');
  });

  describe('#createServer', function() {
    it('should return an MqttServer', function() {
      var s = mqtt.createServer();

      s.should.be.instanceOf(mqtt.MqttServer);
    });
  });

  describe('#createSecureServer', function() {
    it('should return an MqttSecureServer', function() {
      var s = mqtt.createSecureServer(
        __dirname + '/helpers/private-key.pem',
        __dirname + '/helpers/public-cert.pem'
      );
      s.should.be.instanceOf(mqtt.MqttSecureServer);
    });
  });

  describe('#createConnection', function() {
    it('should return an MqttConnection', function() {
      var c = mqtt.createConnection();

      c.should.be.instanceOf(mqtt.MqttConnection);
    });
  });
});

Add test for MqttConnection creation


'use strict';

var OCL = require('openchemlib');

module.exports = function getConnectivityMatrix(options={}) {
    var {
        sdt,
        mass
    } = options;

    this.ensureHelperArrays(OCL.Molecule.cHelperNeighbours);
    var nbAtoms=this.getAllAtoms();
    var result=new Array(nbAtoms);
    for (var i=0; i<nbAtoms; i++) {
        result[i]=new Array(nbAtoms).fill(0);
        result[i][i]=(mass) ? OCL.Molecule.cRoundedMass[this.getAtomicNo(i)] : 1;
    }

    for (var i=0; i<nbAtoms; i++) {
        for (var j=0; j<this.getAllConnAtoms(i); j++) {
            result[i][this.getConnAtom(i,j)]=(sdt) ? this.getConnBondOrder(i,j) : 1;
        }
    }
    return result;
}

'use strict';

var OCL = require('openchemlib');

module.exports = function getConnectivityMatrix(options) {
    var options = options || {};
    var sdt=options.sdt;
    var mass=options.mass;

    this.ensureHelperArrays(OCL.Molecule.cHelperNeighbours);
    var nbAtoms=this.getAllAtoms();
    var result=new Array(nbAtoms);
    for (var i=0; i<nbAtoms; i++) {
        result[i]=new Array(nbAtoms).fill(0);
        result[i][i]=(mass) ? OCL.Molecule.cRoundedMass[this.getAtomicNo(i)] : 1;
    }

    for (var i=0; i<nbAtoms; i++) {
        for (var j=0; j<this.getAllConnAtoms(i); j++) {
            result[i][this.getConnAtom(i,j)]=(sdt) ? this.getConnBondOrder(i,j) : 1;
        }
    }
    return result;
}

Make it compatible with node 4.0


;(function() {
  "use strict";

  angular.
    module("bookApp").
    component("bookApp", {
      template: `

Add new book

{{book.title}}

{{book.rate}}

{{book.author}}

{{book.descroption}}

      `,
      controller: BookAppController
    });

  BookAppController.$inject = ["$scope"];

  function BookAppController($scope) {
    let vm = this,
        bookRef = firebase.database().ref("Books");

    vm.books = [];

    bookRef.on("value", function(snapshot) {
      // vm.users.push(snapshot.val());
      snapshot.forEach(function(subSnap) {
        vm.books.push(subSnap.val());
      });

      if(!$scope.$$phase) $scope.$apply();
    });
  }
})();

Add New Class To Link


window.atthemovies = {
  init: function() {
    var body = document.querySelectorAll("body")[0];
    var jsController = body.getAttribute("data-js-controller");
    var jsAction = body.getAttribute("data-js-action");

    this.pageJS("atthemovies." + jsController + ".init", window);
    this.pageJS("atthemovies." + jsController + ".init" + jsAction, window);
  },

  pageJS: function(functionName, context) {
    var namespaces = functionName.split(".");
    var func = namespaces.pop();

    for (var i = 0; i < namespaces.length; i++) {
      if(context[namespaces[i]]) {
        context = context[namespaces[i]];
      } else {
        return null;
      }
    }
    if(typeof context[func] === "function") {
      return context[func].apply(context, null);
    } else {
      return null;
    }
  }
}

window.atthemovies = {
  init: function() {
    var body = document.querySelectorAll("body")[0];
    var jsController = body.getAttribute("data-js-controller");
    var jsAction = body.getAttribute("data-js-action");

    this.pageJS("atthemovies." + jsController + ".init", window);
    this.pageJS("atthemovies." + jsController + ".init" + jsAction, window);
  },

  // pass a string to call a namespaced function that may not exist
  pageJS: function(functionString, context) {
    var namespaces = functionString.split(".");
    var functionName = namespaces.pop();

    // recursively loop into existing namespaces, else return harmlessly
    for (var i = 0; i < namespaces.length; i++) {
      if(context[namespaces[i]]) {
        context = context[namespaces[i]];
      } else {
        return null;
      }
    }

    // call the function if it exists in this context, else return harmlessly
    if(typeof context[functionName] === "function") {
      return context[functionName].apply(context, null);
    } else {
      return null;
    }
  }
}

Tidy up JS, add commentary


function doAjax(options, retries, dfr) {
  $.ajax(options).pipe(function(data) {dfr.resolve(data);}, function(jqXhr) {
    if (retries > 0) {
      setTimeout(doAjax, 100, options, retries - 1, dfr);
    } else {
      dfr.reject(jqXhr);
    }
  });
}

export default function retryAjax(options) {
  var dfr = $.Deferred();
  doAjax(options, 10, dfr);
  return dfr;
}

function doAjax(options, retries, dfr) {
  $.ajax(options).pipe(function(data, textStatus, jqXHR) {
    dfr.resolve(data, textStatus, jqXHR);
  }, function(jqXhr, textStatus, errorThrown) {
    if (retries > 0) {
      setTimeout(doAjax, 100, options, retries - 1, dfr);
    } else {
      dfr.reject(jqXhr, textStatus, errorThrown);
    }
  });
}

export default function retryAjax(options) {
  var dfr = $.Deferred();
  doAjax(options, 10, dfr);
  return dfr;
}

Return all arguments from AJAX


/*
 * Copyright 2013, All Rights Reserved.
 *
 * Code licensed under the BSD License:
 * https://github.com/node-gh/gh/blob/master/LICENSE.md
 *
 * @author Zeno Rocha <zno.rocha@gmail.com>
 * @author Eduardo Lundgren <zno.rocha@gmail.com>
 */

module.exports = function(grunt) {

    grunt.loadNpmTasks('grunt-jsbeautifier');

    grunt.initConfig({
        jsbeautifier: {
            files: ['bin/*.js', 'lib/**/*.js', 'test/**/*.js', '*.js'],
            options: {
                config: '.jsbeautifyrc'
            }
        }
    });

    grunt.registerTask('format', ['jsbeautifier']);
};

/*
 * Copyright 2013, All Rights Reserved.
 *
 * Code licensed under the BSD License:
 * https://github.com/node-gh/gh/blob/master/LICENSE.md
 *
 * @author Zeno Rocha <zno.rocha@gmail.com>
 * @author Eduardo Lundgren <zno.rocha@gmail.com>
 */

module.exports = function(grunt) {

    grunt.initConfig({
        jsbeautifier: {
            files: ['bin/*.js', 'lib/**/*.js', 'test/**/*.js', '*.js'],
            options: {
                config: '.jsbeautifyrc'
            }
        },

        jshint: {
            options: grunt.file.readJSON('.jshintrc'),
            gruntfile: 'Gruntfile.js',
            files: {
                src: [
                  'bin/*.js',
                  'lib/**/*.js'
                ]
            },
            test: {
                options: {
                    globals: {
                        describe: true,
                        it: true,
                        beforeEach: true,
                        afterEach: true,
                        before: true,
                        after: true
                    }
                },
                src: 'test/**/*.js'
            }
        },
        }
    });

    grunt.loadNpmTasks('grunt-contrib-jshint');
    grunt.loadNpmTasks('grunt-jsbeautifier');
    grunt.registerTask('format', ['jsbeautifier']);
};

Add jshint as grunt task


module.exports = function(grunt) {
  grunt.initConfig({
    pkg : grunt.file.readJSON( 'package.json' ),
    env: {
      casper: {
        BASEURL: "http://www.google.com",
        PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:./node_modules/.bin",
        PHANTOMJS_EXECUTABLE: "node_modules/casperjs/node_modules/.bin/phantomjs"
      }
    },
    casperjs: {
      options: {
        includes: ['includes/include1.js'],
        test: true
      },
      files: ['tests/**/*.js']
    }
  });
  // casperJS Frontend tests
  grunt.loadNpmTasks( 'grunt-casperjs' );
  grunt.loadNpmTasks( 'grunt-env' );

  // grunt for Frontend testing
  grunt.registerTask('default', [ 'env:casper', 'casperjs']);
};

module.exports = function(grunt) {
  grunt.initConfig({
    pkg : grunt.file.readJSON( 'package.json' ),
    env: {
      casper: {
        BASEURL: "http://www.google.com",
        PHANTOMJS_EXECUTABLE: "node_modules/casperjs/node_modules/.bin/phantomjs"
      }
    },
    casperjs: {
      options: {
        includes: ['includes/include1.js'],
        test: true
      },
      files: ['tests/**/*.js']
    }
  });
  // casperJS Frontend tests
  grunt.loadNpmTasks( 'grunt-casperjs' );
  grunt.loadNpmTasks( 'grunt-env' );

  grunt.registerTask('extendPATH', 'Add the npm bin dir to PATH', function() {
	var pathDelimiter = ':';
	var extPath = 'node_modules/.bin';
	var pathArray = process.env.PATH.split( pathDelimiter );
	pathArray.unshift( extPath );
	process.env.PATH = pathArray.join( pathDelimiter );
	grunt.log.writeln('Extending PATH to be ' + process.env.PATH);
  });

  // grunt for Frontend testing
  grunt.registerTask('default', [ 'env:casper', 'extendPATH', 'casperjs']);
};

Extend the PATH env var instead of replacing it


/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */

"use strict";

import {spawn} from 'child_process';

const EXPOSE_TEST_SCRIPT = './scripts/analyse';

class Tester {

    constructor(file) {
        this.file = file;
        this.out = "";
    }

    build(done) {

        let env = process.env;
        env.EXPOSE_EXPECTED_PC = this.file.expectPaths;

        let prc = spawn(EXPOSE_TEST_SCRIPT, [this.file.path], {
            env: env
        });

        prc.stdout.setEncoding('utf8');
        prc.stderr.setEncoding('utf8');
        prc.stdout.on('data', data => this.out += data.toString());
        prc.stderr.on('data', data => this.out += data.toString());

        prc.on('close', code => done(code));
    }
}

export default Tester;

/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */

"use strict";

import {spawn} from 'child_process';

const EXPOSE_TEST_SCRIPT = './scripts/analyse';

class Tester {

    constructor(file) {
        this.file = file;
        this.out = "";
    }

    build(done) {

        let env = process.env;
        env.EXPOSE_EXPECTED_PC = this.file.expectPaths;

        let prc = spawn(EXPOSE_TEST_SCRIPT, [this.file.path], {
            env: env
        });

        prc.stdout.setEncoding('utf8');
        prc.stderr.setEncoding('utf8');
        prc.stdout.on('data', data => this.out += data.toString());
        prc.stderr.on('data', data => this.out += data.toString());

        const SECOND = 1000;
        const TIME_WARNING = 30;
        let longRunningMessage = setTimeout(() => console.log(`\rTEST WARNING: Test case ${this.file.path} is taking an excessive amount of time to complete (over ${TIME_WARNING}s)`), TIME_WARNING * SECOND);

        prc.on('close', code => {
            clearTimeout(longRunningMessage);
            done(code)
        });
    }
}

export default Tester;

Print warnings when tests are taking ages so we can see which ones it is


/*jslint indent: 2, plusplus: true */
/*global $: false, document: false, togglbutton: false, createTag:false, window: false, MutationObserver: false */
'use strict';

// Individual Work Item & Backlog page
togglbutton.render('.witform-layout-content-container:not(.toggl)', {observe: true}, function () {
  var link,
    description = $('.work-item-form-id span').innerText + ' ' + $('.work-item-form-title input').value,
    project = $('.menu-item.l1-navigation-text.drop-visible .text').textContent.trim(),
    container = $('.work-item-form-header-controls-container'),
    vs_activeClassContent = $('.hub-list .menu-item.currently-selected').textContent.trim();

  link = togglbutton.createTimerLink({
    className: 'visual-studio-online',
    description: description,
    projectName: project
  });

  if (vs_activeClassContent === "Work Items*" || vs_activeClassContent === "Backlogs") {
    container.appendChild(link);
  }
});

/*jslint indent: 2, plusplus: true */
/*global $: false, document: false, togglbutton: false, createTag:false, window: false, MutationObserver: false */
'use strict';

// Individual Work Item & Backlog page
togglbutton.render('.witform-layout-content-container:not(.toggl)', {observe: true}, function () {
  var link,
    description = $('.work-item-form-id span').innerText + ' ' + $('.work-item-form-title input').value,
    project = $('.tfs-selector span').innerText,
    container = $('.work-item-form-header-controls-container'),
    vs_activeClassContent = $('.commandbar.header-bottom > .commandbar-item > .displayed').innerText;

  link = togglbutton.createTimerLink({
    className: 'visual-studio-online',
    description: description,
    projectName: project
  });

  if (vs_activeClassContent === "Work Items" || vs_activeClassContent === "Backlogs") {
    container.appendChild(link);
  }
});

Change VSTS integration for markup change


(function() {
  function selector(v) {
    return '[touch-action="' + v + '"]';
  }
  function rule(v) {
    return '{ -ms-touch-action: ' + v + '; touch-action: ' + v + '; }';
  }
  var attrib2css = [
    {
      rule: 'none',
      selectors: [
        'none',
        'user'
      ]
    },
    'pan-x',
    'pan-y',
    {
      rule: 'pan-x pan-y',
      selectors: [
        'scroll',
        'pan-x pan-y',
        'pan-y pan-x'
      ]
    }
  ];
  var styles = '';
  attrib2css.forEach(function(r) {
    if (String(r) === r) {
      styles += selector(r) + rule(r);
    } else {
      styles += r.selectors.map(selector) + rule(r.rule);
    }
  });
  var el = document.createElement('style');
  el.textContent = styles;
  document.head.appendChild(el);
})();

(function() {
  function selector(v) {
    return '[touch-action="' + v + '"]';
  }
  function rule(v) {
    return '{ -ms-touch-action: ' + v + '; touch-action: ' + v + '; }';
  }
  var attrib2css = [
    {
      rule: 'none',
      selectors: [
        'none',
        'user'
      ]
    },
    'pan-x',
    'pan-y',
    {
      rule: 'pan-x pan-y',
      selectors: [
        'scroll',
        'pan-x pan-y',
        'pan-y pan-x'
      ]
    }
  ];
  var styles = '';
  attrib2css.forEach(function(r) {
    if (String(r) === r) {
      styles += selector(r) + rule(r);
    } else {
      styles += r.selectors.map(selector) + rule(r.rule);
    }
  });
  var el = document.createElement('style');
  el.textContent = styles;
  // Use querySelector instead of document.head to ensure that in
  // ShadowDOM Polyfill that we have a wrapped head to append to.
  var h = document.querySelector('head');
  // document.write + document.head.appendChild = crazytown
  // use insertBefore instead for correctness in ShadowDOM Polyfill
  h.insertBefore(el, h.firstChild);
})();

Fix inserting touch action stylesheet for Shadow DOM Polyfill


let zlib = require('zlib'),
    NBT = require('kld-nbt'),
    Tag = NBT.Tag,
    ReadBuffer = NBT.ReadBuffer;

module.exports = class Chunk {
    constructor() {
        this.data = null;
    }

    static loadCompressedChunk(compressedData, compressionFormat) {
        var data;

        if (compressionFormat == 1) {
            data = zlib.gunzipSync(compressedData);
        }
        else if (compressionFormat == 2) {
            data = zlib.unzipSync(compressedData);
        }
        else {
            throw new Error("Unrecognized chunk compression format: " + compressionFormat);
        }

        let readBuffer = new ReadBuffer(data);
        let result = new Chunk();

        result.data = Tag.readFromBuffer(readBuffer);
        result.data.loadFromBuffer(readBuffer);

        return result;
    }
};

let zlib = require('zlib'),
    NBT = require('kld-nbt'),
    Tag = NBT.Tag,
    ReadBuffer = NBT.ReadBuffer,
    utils = require('./utils');

module.exports = class Chunk {
    constructor() {
        this.data = null;
    }

    static loadCompressedChunk(compressedData, compressionFormat) {
        var data;

        if (compressionFormat == 1) {
            data = zlib.gunzipSync(compressedData);
        }
        else if (compressionFormat == 2) {
            data = zlib.unzipSync(compressedData);
        }
        else {
            throw new Error("Unrecognized chunk compression format: " + compressionFormat);
        }

        let readBuffer = new ReadBuffer(data);
        let result = new Chunk();

        result.data = Tag.readFromBuffer(readBuffer);
        result.data.loadFromBuffer(readBuffer);

        return result;
    }

    getBlockType(position) {
        let section = this.getSection(position);
        var result = null;

        if (section !== null) {
            let blocks = section.findTag("Blocks");
            let data = section.findTag("Data");

            if (blocks !== null && data != null) {
                let blockOffset = utils.blockOffsetFromPosition(position);

                let type = blocks.byteValues[blockOffset];
                let dataByte = blocks.byteValues[blockOffset >> 1];
                let data = (blockOffset % 2 == 0) ? dataByte & 0x0F : (dataByte >> 4) & 0x0F;

                result = {
                    type: type,
                    data: data
                };
            }
        }

        return result;
    }

    getSection(position) {
        var result = null;

        if (this.data !== null && 0 <= position.y && position.y < 256) {
            let chunkY = position.y >> 4;
            let level = this.data.findTag("Level");
            let sections = level.findTag("Sections");

            for (var i = 0; i < sections.elements.length; i++) {
                let candidate = sections.elements[i];
                let yIndex = candidate.findTag("Y");

                if (yIndex !== null && yIndex.byteValue === chunkY) {
                    result = candidate;
                    break;
                }
            }
        }

        // TODO: generate new empty section for y index

        return result;
    }
};

Add ability to get block type for a position


var _ = require('lodash');
var React = require('react');

var RequirementSet = require("./requirementSet");

var AreaOfStudy = React.createClass({
	render: function() {
		console.log('area-of-study render')

		var areaDetails = []//getAreaOfStudy(this.props.name, this.props.type);

		var requirementSets = _.map(areaDetails.sets, function(reqset) {
			return RequirementSet( {key:reqset.description,
				name: reqset.description,
				needs: reqset.needs,
				count: reqset.count,
				requirements: reqset.reqs,
				courses: this.props.courses});
		}, this);

		return React.DOM.article( {id:areaDetails.id, className:"area-of-study"},
			React.DOM.details(null,
				React.DOM.summary(null, React.DOM.h1(null, areaDetails.title)),
				requirementSets
			)
		);
	}
});

module.exports = AreaOfStudy

var _ = require('lodash');
var React = require('react');

var RequirementSet = require("./requirementSet");

var AreaOfStudy = React.createClass({
	render: function() {
		console.log('area-of-study render')

		var areaDetails = this.props;//getAreaOfStudy(this.props.name, this.props.type);

		var requirementSets = _.map(areaDetails.sets, function(reqset) {
			return RequirementSet({
				key:reqset.description,
				name: reqset.description,
				needs: reqset.needs,
				count: reqset.count,
				requirements: reqset.reqs,
				courses: this.props.courses
			});
		}, this);

		return React.DOM.article( {id:areaDetails.id, className:"area-of-study"},
			React.DOM.details(null,
				React.DOM.summary(null, React.DOM.h1(null, areaDetails.title)),
				requirementSets
			)
		);
	}
});

module.exports = AreaOfStudy

Clean up the area of study headings


'use strict';

// Display Trial Data for debugging
angular.module('core').directive('displayTrialData', function() {
  return {
    restrict: 'AE',
    template: '

Trial Data

Subject: {{trialDataJson()}}

'
  }
});

'use strict';

// Display Trial Data for debugging
angular.module('core').directive('displayTrialData', function() {
  return {
    restrict: 'AE',
    template: '

Trial Data

{{trialDataJson()}}

'
  }
});

Remove 'Subject:' from displayTrialData text


module.exports = () => ({
  plugins: {
    'postcss-easy-import': {
      extensions: [
        '.pcss',
        '.css',
        '.postcss',
        '.sss'
      ]
    },
    'stylelint': {
      configFile: '.stylelintrc'
    },
    'postcss-mixins': {},
    'postcss-nesting': {},
    'postcss-custom-media': {},
    'postcss-selector-not': {},
    'postcss-discard-comments': {},
    'autoprefixer': {
      browsers: [
        '> 1%',
        'last 2 versions',
        'Firefox ESR',
        'not ie <= 11'
      ]
    },
    'cssnano': {
      preset: 'default'
    },
    'postcss-reporter': {
      clearReportedMessages: true,
      noPlugin: true
    }
  }
});

module.exports = () => ({
  plugins: {
    'postcss-easy-import': {
      extensions: [
        '.pcss',
        '.css',
        '.postcss',
        '.sss'
      ]
    },
    stylelint: {
      configFile: '.stylelintrc'
    },
    'postcss-mixins': {},
    'postcss-nesting': {},
    'postcss-custom-media': {},
    'postcss-selector-not': {},
    'postcss-discard-comments': {},
    autoprefixer: {
      browsers: [
        '> 1%',
        'last 2 versions',
        'Firefox ESR',
        'not ie <= 11'
      ]
    },
    cssnano: {
      preset: 'default'
    },
    'postcss-reporter': {
      clearReportedMessages: true,
      noPlugin: true
    }
  }
});

Fix quoted postcss plugins name


export default (app, route) => {
  route.named('hello')
    .maps('/hello')
    .to('hello:index')

  route.named('foo').maps('/foo/{bar}').to(async (req, res, {bar}) => {
    res.end(`Foo: ${bar}`)
  })

  route.mounted('/bar', route => {
    route.named('bar_index').maps('/').to(async (req, res) => {
      res.end("Bar Index")
    })

    route.named('bar_create').maps('/create').to(async (req, res) => {
      res.end("Bar Create")
    })
  })

  route.named('missing').maps('/missing').to('default:missingRoute')

  route.named('default').maps('/').to('default:index')
}

export default (app, route) => {
  route.named('hello')
    .maps('/hello')
    .to('hello:index')

  route.named('foo').maps('/foo/{bar}').to(async ({req, res}, {bar}) => {
    res.end(`Foo: ${bar}`)
  })

  route.mounted('/bar', route => {
    route.named('bar_index').maps('/').to(async ({req, res}) => {
      res.end("Bar Index")
    })

    route.named('bar_create').maps('/create').to(async ({req, res}) => {
      res.end("Bar Create")
    })
  })

  route.named('missing').maps('/missing').to('default:missingRoute')

  route.named('default').maps('/').to('default:index')
}

Fix args of inline controllers to use context


const path = require('path');
const webpack = require('webpack');
const WebpackNotifierPlugin = require('webpack-notifier');
const styleLintPlugin = require('stylelint-webpack-plugin');
const autoprefixer = require('autoprefixer');

const env = process.env.NODE_ENV;

module.exports = {
    devtool: env === 'production' ? 'source-map' : 'eval',
    entry: './src',
    output: {
        path: path.join(__dirname, 'dist'),
        filename: 'app.js',
        publicPath: '/',
    },
    plugins: env === 'production' ? [
        new webpack.DefinePlugin({
            'process.env': {
                NODE_ENV: JSON.stringify('production'),
            },
        }),
        new webpack.optimize.DedupePlugin(),
        new webpack.optimize.UglifyJsPlugin({
            compress: {
                // dead_code: true,
            },
        }),
    ] : [
        new styleLintPlugin(),
        new webpack.HotModuleReplacementPlugin(),
        new webpack.NoErrorsPlugin(),
        new WebpackNotifierPlugin({ title: 'Next-gen Build' }),
    ],
    module: {
        loaders: [
            { test: /\.js$/, exclude: /node_modules/, loader: 'babel' },
            { test: /\.css$/, exclude: /node_modules/, loader: 'style!css?modules&importLoaders=1&localIdentName=[local]_[hash:base64:5]' },
            { test: /\.js$/, exclude: /node_modules/, loader: 'eslint' },
        ],
        // noParse: /babel/,
    },
    postcss: [ autoprefixer({ browsers: ['last 2 versions'] }) ],
};

const path = require('path');
const webpack = require('webpack');
const WebpackNotifierPlugin = require('webpack-notifier');
const styleLintPlugin = require('stylelint-webpack-plugin');

const env = process.env.NODE_ENV;

module.exports = {
    devtool: env === 'production' ? 'source-map' : 'eval',
    entry: './src',
    output: {
        path: path.join(__dirname, 'dist'),
        filename: 'app.js',
    },
    plugins: env === 'production' ? [
        new webpack.DefinePlugin({
            'process.env': {
                NODE_ENV: JSON.stringify('production'),
            },
        }),
        new webpack.optimize.DedupePlugin(),
        new webpack.optimize.UglifyJsPlugin()
    ] : [
        new styleLintPlugin(),
        new webpack.HotModuleReplacementPlugin(),
        new webpack.NoErrorsPlugin(),
        new WebpackNotifierPlugin({ title: 'Next-gen Build' }),
    ],
    module: {
        loaders: [
            { test: /\.js$/, exclude: /node_modules/, loader: 'babel' },
            { test: /\.js$/, exclude: /node_modules/, loader: 'eslint' },
            { test: /\.css$/, exclude: /node_modules/, loader: 'style!css?modules&importLoaders=1&localIdentName=[local]_[hash:base64:5]' },
        ],
    },
};

Simplify webpack, unsuccessfull at fixing sourcemap output


import { Store } from 'consus-core/flux';
import StudentStore from './student-store';

let checkouts = [];
let checkoutsByActionId = new Object(null);

class CheckoutStore extends Store {

    getCheckoutByActionId(actionId) {
        return checkoutsByActionId[actionId];
    }

}

const store = new CheckoutStore();

store.registerHandler('NEW_CHECKOUT', data => {
    let checkout = {
        studentId: data.studentId,
        itemAddresses: data.itemAddresses
    };

    if (StudentStore.hasOverdueItem(data.studentId)){
        throw new Error('Student has overdue item');
    }else {
        checkoutsByActionId[data.actionId] = checkout;
        checkouts.push(checkout);
    }
});

export default store;

import { Store } from 'consus-core/flux';
import StudentStore from './student-store';
import ItemStore from './item-store';

let checkouts = [];
let checkoutsByActionId = new Object(null);

class CheckoutStore extends Store {

    getCheckoutByActionId(actionId) {
        return checkoutsByActionId[actionId];
    }

}

const store = new CheckoutStore();

store.registerHandler('NEW_CHECKOUT', data => {
    let checkout = {
        studentId: data.studentId,
        itemAddresses: data.itemAddresses
    };
    data.itemAddresses.forEach(itemAddress => {
        if (ItemStore.getItemByAddress(itemAddress).status !== 'AVAILABLE') {
            throw new Error('An item in the cart is not available for checkout.');
        }
    });
    if (StudentStore.hasOverdueItem(data.studentId)) {
        throw new Error('Student has overdue item');
    } else {
        checkoutsByActionId[data.actionId] = checkout;
        checkouts.push(checkout);
    }
});

export default store;

Verify that the cart does not contain an unavailable item


'use strict';

var gulp = require('gulp');

var $ = require('gulp-load-plugins')();

var wiredep = require('wiredep');

gulp.task('test', function () {
  var bowerDeps = wiredep({
    directory: 'app/bower_components',
    exclude: ['bootstrap-sass-official'],
    dependencies: true,
    devDependencies: true
  });

  var testFiles = bowerDeps.js.concat([
    'app/scripts/**/*.js',
    'test/unit/**/*.js'
  ]);

  return gulp.src(testFiles)
    .pipe($.karma({
      configFile: 'test/karma.conf.js',
      action: 'run'
    }))
    .on('error', function (err) {
      // Make sure failed tests cause gulp to exit non-zero
      throw err;
    });
});

'use strict';

var gulp = require('gulp');

var $ = require('gulp-load-plugins')();

var wiredep = require('wiredep');

var bowerDeps = wiredep({
  directory: 'app/bower_components',
  exclude: ['bootstrap-sass-official'],
  dependencies: true,
  devDependencies: true
});

var testFiles = bowerDeps.js.concat([
  'app/scripts/**/*.js',
  'test/unit/**/*.js'
]);

gulp.task('test', function () {
  return gulp.src(testFiles)
    .pipe($.karma({
      configFile: 'test/karma.conf.js',
      action: 'run'
    }))
    .on('error', function (err) {
      // Make sure failed tests cause gulp to exit non-zero
      throw err;
    });
});

gulp.task('watch:test', function () {
  return gulp.src(testFiles)
    .pipe($.karma({
      configFile: 'test/karma.conf.js',
      action: 'watch'
    }))
    .on('error', function (err) {
      // Make sure failed tests cause gulp to exit non-zero
      throw err;
    });
});

Refactor unit tests, add watch task.


'use strict';

// scripts-model.js - A mongoose model
//
// See http://mongoosejs.com/docs/models.html
// for more of what you can do here.

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

const scriptsSchema = new Schema({
  text: { type: String, required: true },
  createdAt: { type: Date, 'default': Date.now },
  updatedAt: { type: Date, 'default': Date.now }
});

const scriptsModel = mongoose.model('scripts', scriptsSchema);

module.exports = scriptsModel;

'use strict';

// scripts-model.js - A mongoose model
//
// See http://mongoosejs.com/docs/models.html
// for more of what you can do here.

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

const scriptsSchema = new Schema({
  issue: { type: String, required: true },
  title: { type: String, required: true },
  text: { type: String, required: true },
  createdAt: { type: Date, 'default': Date.now },
  updatedAt: { type: Date, 'default': Date.now }
});

const scriptsModel = mongoose.model('scripts', scriptsSchema);

module.exports = scriptsModel;

Add script issue and title fields


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

function reduce(obj, props = {}){
  return Object.keys(obj)
  .reduce((props, key) => {
    const value = obj[key];
    if (value !== undefined) props[key] = value;
    return props;
  }, props);
}

const AsyncCreatable = React.createClass({
	displayName: 'AsyncCreatableSelect',

	focus () {
		this.select.focus();
	},

	render () {
		return (
			<Select.Async {...this.props}>
				{(asyncProps) => (
					<Select.Creatable {...this.props}>
						{(creatableProps) => (
							<Select
								{...reduce(asyncProps, reduce(creatableProps, {}))}
								onInputChange={(input) => {
									creatableProps.onInputChange(input);
									return asyncProps.onInputChange(input);
								}}
								ref={(ref) => {
									this.select = ref;
									creatableProps.ref(ref);
									asyncProps.ref(ref);
								}}
							/>
						)}
					</Select.Creatable>
				)}
			</Select.Async>
		);
	}
});

module.exports = AsyncCreatable;

import React from 'react';
import createClass from 'create-react-class';
import Select from './Select';

function reduce(obj, props = {}){
  return Object.keys(obj)
  .reduce((props, key) => {
    const value = obj[key];
    if (value !== undefined) props[key] = value;
    return props;
  }, props);
}

const AsyncCreatable = createClass({
	displayName: 'AsyncCreatableSelect',

	focus () {
		this.select.focus();
	},

	render () {
		return (
			<Select.Async {...this.props}>
				{(asyncProps) => (
					<Select.Creatable {...this.props}>
						{(creatableProps) => (
							<Select
								{...reduce(asyncProps, reduce(creatableProps, {}))}
								onInputChange={(input) => {
									creatableProps.onInputChange(input);
									return asyncProps.onInputChange(input);
								}}
								ref={(ref) => {
									this.select = ref;
									creatableProps.ref(ref);
									asyncProps.ref(ref);
								}}
							/>
						)}
					</Select.Creatable>
				)}
			</Select.Async>
		);
	}
});

module.exports = AsyncCreatable;

Migrate rest of src overto createClass pkg


// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import Router from 'vue-router'
import App from './App'
import Home from './Home'

import Eagle from 'eagle.js'

import slideshows from './slideshows/slideshows.js'
/* eslint-disable no-new */

Vue.use(Eagle)
Vue.use(Router)

var routes = []
slideshows.list.forEach(function (slideshow) {
  routes.push({
    path: '/' + slideshow.infos.path,
    component: slideshow
  })
})
routes.push({ path: '*', component: Home })

routes.push({
  path: '/',
  name: 'Home',
  component: Home
})
console.log(routes)

var router = new Router({
  routes
  // hashbang: true
  // mode: 'history'
})

new Vue({
  el: '#app',
  router,
  template: '<App/>',
  components: { App }
})

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import Router from 'vue-router'
import App from './App'
import Home from './Home'

import Eagle from 'eagle.js'
import 'eagle.js/dist/eagle.css'

import slideshows from './slideshows/slideshows.js'
/* eslint-disable no-new */

Vue.use(Eagle)
Vue.use(Router)

var routes = []
slideshows.list.forEach(function (slideshow) {
  routes.push({
    path: '/' + slideshow.infos.path,
    component: slideshow
  })
})
routes.push({ path: '*', component: Home })

routes.push({
  path: '/',
  name: 'Home',
  component: Home
})
console.log(routes)

var router = new Router({
  routes
  // hashbang: true
  // mode: 'history'
})

new Vue({
  el: '#app',
  router,
  template: '<App/>',
  components: { App }
})

Load dist css file in example


$(document).ready(function () {

    $(".menu-btn a").click(function () {
        $(".overlay").fadeToggle(200);
        $(this).toggleClass('btn-open').toggleClass('btn-close');
    });

    $('.overlay').on('click', function () {
        $(".overlay").fadeToggle(200);
        $(".menu-btn a").toggleClass('btn-open').toggleClass('btn-close');
    });

    $('.menu a').on('click', function () {
        $(".overlay").fadeToggle(200);
        $(".menu-btn a").toggleClass('btn-open').toggleClass('btn-close');
    });

});

(function ($) {

    $(".menu-btn a").click(function () {
        event.preventDefault();
        $(".overlay").fadeToggle(200);
        $(this).toggleClass('btn-open').toggleClass('btn-close');
    });

    $('.overlay').on('click', function () {
        $(".overlay").fadeToggle(200);
        $(".menu-btn a").toggleClass('btn-open').toggleClass('btn-close');
    });

    $('.menu a').on('click', function () {
        event.preventDefault();
        $(".overlay").fadeToggle(200);
        $(".menu-btn a").toggleClass('btn-open').toggleClass('btn-close');
    });

}(jQuery));

Fix js to preventDefault and work more places


/*
requestFrame / cancelFrame
*/"use strict"

var array = require("prime/es5/array")

var requestFrame = global.requestAnimationFrame ||
                   global.webkitRequestAnimationFrame ||
                   global.mozRequestAnimationFrame ||
                   global.oRequestAnimationFrame ||
                   global.msRequestAnimationFrame ||
                   function(callback){
                       return setTimeout(callback, 1e3 / 60)
                   }

var callbacks = []

var iterator = function(time){
    var split = callbacks.splice(0)
    for (var i = 0, l = split.length; i < l; i++) split[i](time || (time = +(new Date)))
}

var cancel = function(callback){
    var io = array.indexOf(callbacks, callback)
    if (io > -1) callbacks.splice(io, 1)
}

var request = function(callback){
    var i = callbacks.push(callback)
    if (i === 1) requestFrame(iterator)
    return function(){
        cancel(callback)
    }
}

exports.request = request
exports.cancel = cancel

/*
requestFrame / cancelFrame
*/"use strict"

var array = require("prime/es5/array")

var requestFrame = global.requestAnimationFrame ||
                   global.webkitRequestAnimationFrame ||
                   global.mozRequestAnimationFrame ||
                   global.oRequestAnimationFrame ||
                   global.msRequestAnimationFrame ||
                   function(callback){
                       return setTimeout(callback, 1e3 / 60)
                   }

var callbacks = []

var iterator = function(time){
    var split = callbacks.splice(0, callbacks.length)
    for (var i = 0, l = split.length; i < l; i++) split[i](time || (time = +(new Date)))
}

var cancel = function(callback){
    var io = array.indexOf(callbacks, callback)
    if (io > -1) callbacks.splice(io, 1)
}

var request = function(callback){
    var i = callbacks.push(callback)
    if (i === 1) requestFrame(iterator)
    return function(){
        cancel(callback)
    }
}

exports.request = request
exports.cancel = cancel

Fix requestFrame in IE, where splice requires the deleteCount argument.


function deepEqual(obj1, obj2) {
    if (typeof obj1 != "object" || obj1 == null ||
        typeof obj2 != "object" || obj2 == null) return obj1 === obj2;

    let keys1 = Object.keys(obj1), keys2 = Object.keys(obj2);

    if (keys1.length != keys2.length) return false;

    for (let key of keys1) {
        if (!(keys2.includes(key) &&
              deepEqual(obj1[key], obj2[key]))) return false;
    }
    return true;
}

function deepEqual(a, b) {
	if (!(isObject(a) && isObject(b))) return a === b;

	for (let key of Object.keys(a)) {
		if (!(key in b)) return false;
		if (!deepEqual(a[key], b[key])) return false;
	}

	return true;
}

function isObject(a) {
	return typeof a == "object" && a != null;
}

Add chapter 4, exercise 4


var
    EchoRule  = require('../lib/rules/echo.js'),
    Heartbeat = require('../lib/rules/heartbeat.js')
    ;

var rules = [];

rules.push(new EchoRule());
rules.push(new Heartbeat());

module.exports = rules;

var
    EchoRule  = require('../lib/rules/echo.js'),
    Heartbeat = require('../lib/rules/heartbeat.js'),
    LoadAverage = require('../lib/rules/load-average.js')
    ;

var rules = [];

rules.push(new EchoRule());
rules.push(new Heartbeat());
rules.push(new LoadAverage({
    warn: {
        1: 1.5,
        5: 0.9,
        15: 0.7
    },
    critical: {
        1: 1.5,
        5: 1.5,
        15: 1.5
    }
}));

module.exports = rules;

Add the `LoadAverage` rule to the example


(function() {
    "use strict";

    var lastKeys = "";
    function fetchRedlines() {
        var baseUrl = $('#baseUrl').val();
        var artworkUrl = $('#artworkUrl').val();
        $.get(baseUrl + 'index').done(function(keys) {
            if (lastKeys === keys) {
                return;
            }
            lastKeys = keys;

            var items = [];
            var requests = $.map(keys.split('\n'), function(key) {
                return $.get(baseUrl + key).done(function(data) {
                    var json = JSON.parse(data);
                    var feedback = $('<div/>').text(json.feedback).addClass('feedback');
                    var artwork = $('<div/>').append($('<img/>').attr('src', artworkUrl))
                                             .append($('<img/>').attr('src', json.image).addClass('redline'))
                                             .addClass('drawing');
                    var item = $('<div/>').append(feedback)
                                          .append(artwork)
                                          .attr('id', key);
                    items.push(item);
                }).fail(function() {
                    console.log('Failed to fetch data for key ' + key);
                });
            });

            $.when.apply($, requests).done(function () {
                $('#items').empty();
                $.each(items, function(i, item) {
                    $('#items').append(item);
                });
                $('#submissions').text(items.length);
            });

        }).fail(function() {
            console.log('Failed to fetch index file');
        });
    }

    $(function() {
        fetchRedlines();
        setInterval(fetchRedlines, 1000);
    });
})();

(function() {
    "use strict";

    var lastKeys = "";
    function fetchRedlines() {
        var baseUrl = $('#baseUrl').val();
        var artworkUrl = $('#artworkUrl').val();
        $.get(baseUrl + 'index').done(function(keys) {
            if (lastKeys === keys) {
                return;
            }
            lastKeys = keys;

            var items = [];
            var requests = $.map(keys.split('\n'), function(key) {
                return $.get(baseUrl + key).done(function(data) {
                    var json = JSON.parse(data);
                    var feedback = $('<div/>').text(json.feedback).addClass('feedback');
                    var artwork = $('<div/>').append($('<img/>').attr('src', artworkUrl))
                                             .append($('<img/>').attr('src', json.image).addClass('redline'))
                                             .addClass('drawing');
                    var item = $('<div/>').append(feedback)
                                          .append(artwork)
                                          .attr('id', key);
                    items.push(item);
                }).fail(function() {
                    console.log('Failed to fetch data for key ' + key);
                });
            });

            $.when.apply($, requests).done(function () {
                $('#items').empty();
                $.each(items, function(i, item) {
                    $('#items').append(item);
                });
                $('#submissions').text(items.length);
            });

        }).fail(function() {
            console.log('Failed to fetch index file');
        });
    }

    $(function() {
        fetchRedlines();
        setInterval(fetchRedlines, 5000);
    });
})();

Increase interval to 5 seconds


var electron = require('electron');  // Module to control application life.
var process = require('process');
const {app} = electron;
const {BrowserWindow} = electron;
let win;  // Ensure that our win isn't garbage collected

app.on('ready', function() {
  // Create the browser window.
  if (process.platform == 'win32')
    win = new BrowserWindow({width: 670, height: 510, frame: true, resizable: false});
  else
    win = new BrowserWindow({width: 640, height: 480, frame: true, resizable: false});

  // Load the main interface
  win.loadURL('file://' + __dirname + '/index.html');

  // Uncomment this line to open the DevTools upon launch.
  //win.webContents.openDevTools({'mode':'undocked'});

  win.on('closed', function() {
    // Dereference the window object so our app exits
    win = null;
  });
});

// Quit when all windows are closed.
app.on('window-all-closed', function() {
  app.quit();
});

var electron = require('electron');  // Module to control application life.
var process = require('process');
const {app} = electron;
const {BrowserWindow} = electron;
let win;  // Ensure that our win isn't garbage collected

app.on('ready', function() {
  // Create the browser window.
  if (process.platform == 'win32')
    win = new BrowserWindow({width: 670, height: 510, frame: true, resizable: false});
  else
    win = new BrowserWindow({width: 640, height: 480, frame: true, resizable: false});

  // Load the main interface
  win.loadURL('file://' + __dirname + '/index.html');

  // Uncomment this line to open the DevTools upon launch.
  //win.webContents.openDevTools({'mode':'undocked'});

  //Disable the menubar for dev versions
  win.setMenu(null);

  win.on('closed', function() {
    // Dereference the window object so our app exits
    win = null;
  });
});

// Quit when all windows are closed.
app.on('window-all-closed', function() {
  app.quit();
});

Disable the menubar when running from source


var browserify = require('browserify-middleware')
var babelify = require('babelify')

module.exports = function (entries, brOptions, baOptions) {
  brOptions = brOptions || {}
  brOptions.transform = brOptions.transform || []
  baOptions = baOptions || {}
  baOptions.stage = baOptions.stage || 1
  brOptions.transform.unshift(babelify.configure(baOptions))

  return browserify(entries, brOptions)
}

var browserify = require('browserify-middleware')
var babelify = require('babelify')

function babelifyMiddleware(entries, brOptions, baOptions) {
  brOptions = brOptions || {}
  brOptions.transform = brOptions.transform || []
  baOptions = baOptions || {}
  baOptions.stage = baOptions.stage || 1
  brOptions.transform.unshift(babelify.configure(baOptions))

  return browserify(entries, brOptions)
}

babelifyMiddleware.browserifySettings = browserify.settings

module.exports = babelifyMiddleware

Make browserify default settings accessible


/* Define your initial state here.
 *
 * If you change the type from object to something else, do not forget to update
 * src/container/App.js accordingly.
 */
const initialState = {};

module.exports = function(state = initialState, action) {
  /* Keep the reducer clean - do not mutate the original state. */
  //let nextState = Object.assign({}, state);

  switch(action.type) {
    /*
    case 'YOUR_ACTION': {
      // Modify next state depending on the action and return it
      return nextState;
    } break;
    */
    default: {
      /* Return original state if no actions were consumed. */
      return state;
    }
  }
}

import {LOGIN} from '../actions/const';

/* Define your initial state here.
 *
 * If you change the type from object to something else, do not forget to update
 * src/container/App.js accordingly.
 */
const initialState = {
  login: false
};

module.exports = function(state = initialState, action) {
  /* Keep the reducer clean - do not mutate the original state. */
  let nextState = Object.assign({}, state);

  switch(action.type) {
    case LOGIN: {
      nextState.login = true;
      return nextState;
    }

    default: {
      /* Return original state if no actions were consumed. */
      return state;
    }
  }
}

Change login state When LOGIN action was triggered.


function DomStorage() {
	var cache = {};
	function getFromCache(selector){
		if(!cache[selector]){
			cache[selector] = $(selector);
		}
		return cache[selector];
	}
	function updateInCache(selector){
		if(!cache[selector]){
			throw new TypeError("The selector you want to update does not exist in cache");
		}
		else{
			cache[selector] = $(selector);
		}
	}
	function getCache(){
		return cache;
	}
	function flushCache(){
		cache = {};
	}
	return {
		get:getFromCache,
		update:updateInCache,
		getCache:getCache,
		flush:flushCache
	};
}

function DomStorage() {
	var cache = {};
	function getFromCache(selector){
		if(!selector)
			throw new TypeError("Please provide a selector");
		if(!cache[selector]){
			cache[selector] = $(selector);
		}
		return cache[selector];
	}
	function updateInCache(selector){
		if(!selector)
			throw new TypeError("Please provide a selector");
		if(!cache[selector]){
			throw new TypeError("The selector you want to update does not exist in cache");
		}
		else{
			cache[selector] = $(selector);
		}
	}
	function getCache(){
		return cache;
	}
	function flushCache(){
		cache = {};
	}
	return {
		get:getFromCache,
		update:updateInCache,
		getCache:getCache,
		flush:flushCache
	};
}
class DomCache{
	constructor{

	}
}

Check if a user has given a valid selector when trying to get or update


import config from '../config'
import server from '../server/main'
import _debug from 'debug'

const debug = _debug('app:bin:server')
const port = config.server_port
const host = config.server_host

server.listen(port)
debug(`Server is now running at ${host}:${port}.`)

import config from '../config'
import server from '../server/main'
import _debug from 'debug'

const debug = _debug('app:bin:server')
const port = config.server_port
const host = config.server_host

server.listen(port)

debug(`Server is now running at http://${host}:${port}.`)

Make the app link clickable


'use strict';

var _ = require('lodash');

//
// Observe quicksearch focus to tweak icon style
//
N.wire.once('navigate.done', function () {
  $('.navbar-search .search-query')
    .focus(function () { $(this).next('div').addClass('focused'); })
    .blur(function () { $(this).next('div').removeClass('focused'); });
});

//
// Update "active" tab of the navbar_menu when moving to another page.
//
N.wire.on('navigate.done', function navbar_menu_change_active(target) {
  var apiPath = target.apiPath, tabs, active;

  function matchLengthOf(subject) {
    var index  = 0
      , length = Math.min(apiPath.length, subject.length);

    while (index < length &&
           subject.charCodeAt(index) === apiPath.charCodeAt(index)) {
      index += 1;
    }

    return index;
  }

  tabs = $('#navbar_menu').find('[data-api-path]');
  tabs.removeClass('active');

  // Select the most specific tab - with the longest API path match.
  active = _.max(tabs, function (tab) {
    return matchLengthOf($(tab).attr('data-api-path'));
  });

  $(active).addClass('active');
});

'use strict';

var _ = require('lodash');

//
// Observe quicksearch focus to tweak icon style
//
N.wire.once('navigate.done', function () {
  $('.navbar-search .search-query')
    .focus(function () { $(this).next('div').addClass('focused'); })
    .blur(function () { $(this).next('div').removeClass('focused'); });
});

//
// Update "active" tab of the navbar_menu when moving to another page.
//
N.wire.on('navigate.done', function navbar_menu_change_active(target) {
  var targetPath = target.apiPath.split('.'), tabs, active;

  tabs = $('#navbar_menu').find('[data-api-path]');
  tabs.removeClass('active');

  // Select the most specific tab - with the longest API path match.
  active = _.max(tabs, function (tab) {
    var tabPath = $(tab).data('apiPath').split('.')
      , index   = -1
      , length  = Math.min(tabPath.length, targetPath.length);

    do { index += 1; }
    while (index < length && tabPath[index] === targetPath[index]);

    return index;
  });

  $(active).addClass('active');
});

Fix tab selection on the navbar.


Remove capture group from URL reg exp.


module.exports = function(target) {
  if (tasks[target])
    console.log(tasks[target].help)
  else
    console.log("No help documentation exists for " + target + ".")
}

var fs = require('fs')
module.exports = function(target) {
  fs.readFile('doc/' + target + '.md', function(err, data) {
  	if (err)
  		console.log("No help documentation exists for " + target + ".")
  	else
  		console.log(data)
  })
}

Load markdown docs in help command


var Readable = require('stream').Readable;
var path = require('path-posix');
var util = require('util');
var File = require('vinyl');

// Because the index.html files in our site don't necessarily
// come from individual files, it's easiest for us to just
// create a stream that emits Vinyl File objects rather than
// using gulp.src().
function IndexFileStream(indexStatic) {
  Readable.call(this, {
    objectMode: true
  });
  this._baseDir = __dirname;
  this._indexStatic = indexStatic;
  this._urls = indexStatic.URLS.slice();
}

util.inherits(IndexFileStream, Readable);

IndexFileStream.prototype._read = function() {
  if (this._urls.length == 0)
    return this.push(null);
  var url = this._urls.pop();
  var indexFile = path.join(
    this._baseDir,
    path.join.apply(path, url.split('/').slice(1, -1)),
    'index.html'
  );
  this.push(new File({
    cwd: this._baseDir,
    base: this._baseDir,
    path: indexFile,
    contents: new Buffer(this._indexStatic.generate(url, {
      baseURL: path.relative(url, '/')
    }))
  }));
};

module.exports = IndexFileStream;

var Readable = require('stream').Readable;
var posixPath = require('path-posix');
var util = require('util');
var File = require('vinyl');

// Because the index.html files in our site don't necessarily
// come from individual files, it's easiest for us to just
// create a stream that emits Vinyl File objects rather than
// using gulp.src().
function IndexFileStream(indexStatic) {
  Readable.call(this, {
    objectMode: true
  });
  this._baseDir = __dirname;
  this._indexStatic = indexStatic;
  this._urls = indexStatic.URLS.slice();
}

util.inherits(IndexFileStream, Readable);

IndexFileStream.prototype._read = function() {
  if (this._urls.length == 0)
    return this.push(null);
  var url = this._urls.pop();
  var indexFile = posixPath.join(
    this._baseDir,
    posixPath.join.apply(posixPath, url.split('/').slice(1, -1)),
    'index.html'
  );
  this.push(new File({
    cwd: this._baseDir,
    base: this._baseDir,
    path: indexFile,
    contents: new Buffer(this._indexStatic.generate(url, {
      baseURL: posixPath.relative(url, '/')
    }))
  }));
};

module.exports = IndexFileStream;

Change path name to posixPath


angular
    .module('ngSharepoint.Lists')
    .factory('JSOMConnector', function($q, $sp) {
        return ({
            getLists: function() {
                return $q(function(resolve, reject) {
                    var context = $sp.getContext();
                    var lists = context.get_web().get_lists();
                    context.load(lists);
                    context.executeQueryAsync(function(sender, args) {
                        var result = [];
                        var listEnumerator = lists.getEnumerator();
                        while (listEnumerator.moveNext()) {
                            var list = lists.get_current();
                            result.push(list);
                        }
                        resolve(result);
                    }, reject);
                });
            }
        });
    });

angular
    .module('ngSharepoint.Lists')
    .factory('JSOMConnector', function($q, $sp) {
        return ({
            getLists: function() {
                return $q(function(resolve, reject) {
                    var context = $sp.getContext();
                    var lists = context.get_web().get_lists();
                    context.load(lists);
                    context.executeQueryAsync(function() {
                        var result = [];
                        var listEnumerator = lists.getEnumerator();
                        while (listEnumerator.moveNext()) {
                            var list = listEnumerator.get_current();
                            result.push(list);
                        }
                        resolve(result);
                    }, reject);
                });
            }
        });
    });

Use enumerator instead of lists


import React, { Component } from 'react'
import firebase, { reference, signIn } from '../firebase';
import { pick, map, extend } from 'lodash';
import Input from './Input';
import MessageContainer from './MessageContainer';
// import Search from './Search';
// import Users from './Users';

export default class Application extends Component {
  constructor() {
    super();
    this.state = {
      messages: [],
    }
  }

  // componentDidMount() {
  //   firebase.database().ref('messages').on('value', (snapshot) => {
  //   });
  // }

  storeMessage(message) {
    firebase.database().ref('messages').push(Object.assign(message, { id: Date.now() }));
    this.state.messages.push(Object.assign(message, { id: Date.now() }))
    this.setState({ messages: this.state.messages })

  }

  render() {

    return (

          {/* <Search /> */}

          <MessageContainer messages={this.state.messages}/>

          {/* <Users /> */}

          <Input sendMessage={this.storeMessage.bind(this)}/>

    )
  }
}

import React, { Component } from 'react'
import firebase, { reference, signIn } from '../firebase';
import { pick, map, extend } from 'lodash';
import Input from './Input';
import MessageContainer from './MessageContainer';
// import Search from './Search';
// import Users from './Users';

export default class Application extends Component {
  constructor() {
    super();
    this.state = {
      messages: [],
    }
  }

  componentDidMount() {
    firebase.database().ref('messages').on('value', (snapshot) => {
      const itemsFromFirebase = this.createArrayFromFB(snapshot.val());
      this.setState({ messages: itemsFromFirebase ? itemsFromFirebase : [] })
    });
  }

  createArrayFromFB(object) {
    const firebaseKeys = object ? Object.keys(object) : [];
    const messages = [];
    firebaseKeys.map((key) => {
      let singleMessage = object[key]
      singleMessage['firebaseId'] = key
      messages.push(singleMessage);
    });
    return messages
  }

  storeMessage(message) {
    firebase.database().ref('messages').push(Object.assign(message, { id: Date.now() }));
    this.state.messages.push(Object.assign(message, { id: Date.now() }))
    this.setState({ messages: this.state.messages })

  }

  render() {

    return (

          {/* <Search /> */}

          <MessageContainer messages={this.state.messages}/>

          {/* <Users /> */}

          <Input sendMessage={this.storeMessage.bind(this)}/>

    )
  }
}

Add items to firebase and repopulate on page load or refresh


(function(document, MazeGenerator, MazePainter, MazeInteraction) {
  'use strict';

  // DOM Elements
  var canvas = document.getElementById('maze');
  var bGenerate = document.getElementById('bGenerate');
  var bSolve = document.getElementById('bSolve');

  // Start painting loop
  MazePainter.startPainting();

  bGenerate.addEventListener('click', function() {

    // Initialize modules
    MazeGenerator.init(canvas.width, canvas.height, 20);
    MazePainter.init(canvas, 20, '#fff', '#f00', '#000', '#0f0', '#0f0', '#0f0', '#00f');
    MazeGenerator.generate();
    MazeGenerator.selectEntry();
    MazeGenerator.selectExit();

    MazeInteraction.init(canvas, 20, MazeGenerator.getMaze());
    MazeInteraction.startListeningUserEvents();
  });

  bSolve.addEventListener('click', function() {
    if (MazePainter.isMazePainted()) {
      MazeGenerator.solve();
    }
  });

}(document, MazeGenerator, MazePainter, MazeInteraction));

(function(document, MazeGenerator, MazePainter, MazeInteraction) {
  'use strict';

  // DOM Elements
  var canvas = document.getElementById('maze');
  var bGenerate = document.getElementById('bGenerate');
  var bSolve = document.getElementById('bSolve');

  // Initialization variables
  var cellSize = 20;
  var cellColor = '#fff';
  var frontierColor = '#f00';
  var wallColor = '#000';
  var entryColor = '#0f0';
  var exitColor = '#0f0';
  var solutionColor = '#0f0';
  var userSolutionColor = '#00f';

  // Start painting loop
  MazePainter.startPainting();

  bGenerate.addEventListener('click', function() {

    // Initialize modules
    MazeGenerator.init(canvas.width, canvas.height, cellSize);
    MazePainter.init(canvas, cellSize, cellColor, frontierColor, wallColor, entryColor, exitColor, solutionColor, userSolutionColor);
    MazeGenerator.generate();
    MazeGenerator.selectEntry();
    MazeGenerator.selectExit();

    MazeInteraction.init(canvas, cellSize, MazeGenerator.getMaze());
    MazeInteraction.startListeningUserEvents();
  });

  bSolve.addEventListener('click', function() {
    if (MazePainter.isMazePainted()) {
      MazeGenerator.solve();
    }
  });

}(document, MazeGenerator, MazePainter, MazeInteraction));

Use variables to initialize the maze


import { TextNode, TEXT } from 'substance'

export default class Heading extends TextNode {}
Heading.schema = {
  type: 'heading',
  level: { type: 'number', default: 1 },
  content: TEXT()
}

import { TextNode, TEXT } from 'substance'
import { RICH_TEXT_ANNOS, EXTENDED_FORMATTING, LINKS_AND_XREFS, INLINE_NODES } from './modelConstants'

export default class Heading extends TextNode {}
Heading.schema = {
  type: 'heading',
  level: { type: 'number', default: 1 },
  content: TEXT(RICH_TEXT_ANNOS.concat(EXTENDED_FORMATTING).concat(LINKS_AND_XREFS).concat(INLINE_NODES).concat(['break']))
}

Allow annotations and line-breaks in headings.


#!/usr/bin/env node

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

var script = path.join(__dirname, 'postinstall')
if (process.platform === 'win32') {
  script += '.cmd'
} else {
  script += '.sh'
}

// Read + execute permission
fs.chmodSync(script, fs.constants.S_IRUSR | fs.constants.S_IXUSR)
fs.chmodSync(path.join(__dirname, '..', 'bin', 'apm'), fs.constants.S_IRUSR | fs.constants.S_IXUSR)
fs.chmodSync(path.join(__dirname, '..', 'bin', 'npm'), fs.constants.S_IRUSR | fs.constants.S_IXUSR)
fs.chmodSync(path.join(__dirname, '..', 'bin', 'python-interceptor.sh'), fs.constants.S_IRUSR | fs.constants.S_IXUSR)

var child = cp.spawn(script, [], { stdio: ['pipe', 'pipe', 'pipe'], shell: true })
child.stderr.pipe(process.stderr)
child.stdout.pipe(process.stdout)

#!/usr/bin/env node

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

var script = path.join(__dirname, 'postinstall')
if (process.platform === 'win32') {
  script += '.cmd'
} else {
  script += '.sh'
}

// Make sure all the scripts have the necessary permissions when we execute them
// (npm does not preserve permissions when publishing packages on Windows,
// so this is especially needed to allow apm to be published successfully on Windows)
fs.chmodSync(script, 0o755)
fs.chmodSync(path.join(__dirname, '..', 'bin', 'apm'), 0o755)
fs.chmodSync(path.join(__dirname, '..', 'bin', 'npm'), 0o755)
fs.chmodSync(path.join(__dirname, '..', 'bin', 'python-interceptor.sh'), 0o755)

var child = cp.spawn(script, [], { stdio: ['pipe', 'pipe', 'pipe'], shell: true })
child.stderr.pipe(process.stderr)
child.stdout.pipe(process.stdout)

Mark all scripts as 0o755


'use strict';

let fs = require('fs'),
  path = require('path'),
  Promise = require('bluebird'),
  filters = require('./filters');

Promise.promisifyAll(fs);

/*
 Find and parse any browser scripts in rootFolder.
 Returns a Promise that resolves to an array of scripts.
 Each script is represented by an object with the following keys:
 - default: true if script is bundled with Browsertime.
 - path: the path to the script file on disk
 - source: the javascript source
 */
function parseBrowserScripts(rootFolder, isDefault) {
  function toFullPath(filename) {
    return path.join(rootFolder, filename);
  }

  function fileToScriptObject(filepath) {
    return fs.readFileAsync(filepath, 'utf8')
      .then((contents) => {
        return {
          default: isDefault,
          path: filepath,
          source: contents
        };
      });
  }

  return fs.readdirAsync(rootFolder)
    .map(toFullPath)
    .filter(filters.onlyFiles)
    .filter(filters.onlyWithExtension('.js'))
    .map(fileToScriptObject);
}

module.exports = {
  parseBrowserScripts: parseBrowserScripts,
  defaultScripts: parseBrowserScripts(path.resolve(__dirname, '..', '..', 'browserscripts'))
};

'use strict';

let fs = require('fs'),
  path = require('path'),
  Promise = require('bluebird'),
  filters = require('./filters');

Promise.promisifyAll(fs);

/*
 Find and parse any browser scripts in rootFolder.
 Returns a Promise that resolves to an array of scripts.
 Each script is represented by an object with the following keys:
 - default: true if script is bundled with Browsertime.
 - path: the path to the script file on disk
 - source: the javascript source
 */
function parseBrowserScripts(rootFolder, isDefault) {
  function toFullPath(filename) {
    return path.join(rootFolder, filename);
  }

  function fileToScriptObject(filepath) {
    return fs.readFileAsync(filepath, 'utf8')
      .then((contents) => {
        return {
          default: isDefault,
          path: filepath,
          source: contents
        };
      });
  }

  return fs.readdirAsync(rootFolder)
    .map(toFullPath)
    .filter(filters.onlyFiles)
    .filter(filters.onlyWithExtension('.js'))
    .map(fileToScriptObject);
}

module.exports = {
  parseBrowserScripts: parseBrowserScripts,
  get defaultScripts() {
    return parseBrowserScripts(path.resolve(__dirname, '..', '..', 'browserscripts'));
  }
};

Configure Bluebird before first use of Promise.


"use strict";

/**
 * Properties to prefix.
 */
var postcss = require("postcss"),
		props = [
			"hyphens",
			"line-break",
			"text-align-last",
			"text-emphasis",
			"text-emphasis-color",
			"text-emphasis-style",
			"word-break",
			// writing modes
			"writing-mode",
			"text-orientation",
			"text-combine-upright"
		];

/**
 * PostCSS plugin to prefix ePub3 properties.
 * @param {Object} style
 */
function plugin(style) {
	style.eachDecl(function(decl) {
		if (decl.value) {
			if (props.indexOf(decl.prop) >= 0) {
				decl.parent.insertBefore(decl, decl.clone({prop: "-epub-" + decl.prop}));
			}
		}
	});
}

/*
 * ...and export for use...
 */
module.exports = {
	postcss: plugin,
	process: function(css, opts) {
		return postcss(plugin).process(css, opts).css;
	}
};

"use strict";

/**
 * Properties to prefix.
 */
var postcss = require("postcss"),
		props = [
			// text
			"hyphens",
			"line-break",
			"text-align-last",
			"text-emphasis",
			"text-emphasis-color",
			"text-emphasis-style",
			"word-break",
			// writing modes
			"writing-mode",
			"text-orientation",
			"text-combine-upright",
			// text to speech
			"cue",
			"cue-before",
			"cue-after",
			"pause",
			"rest",
			"speak",
			"speak-as",
			"voice-family"
		];

/**
 * PostCSS plugin to prefix ePub3 properties.
 * @param {Object} style
 */
function plugin(style) {
	style.eachDecl(function(decl) {
		if (decl.value) {
			if (props.indexOf(decl.prop) >= 0) {
				decl.parent.insertBefore(decl, decl.clone({prop: "-epub-" + decl.prop}));
			}
		}
	});
}

/*
 * ...and export for use...
 */
module.exports = {
	postcss: plugin,
	process: function(css, opts) {
		return postcss(plugin).process(css, opts).css;
	}
};

Add speech prefixes - still required in 2012, so should be left in due to reader issues


var net = require('net');
var config = require('hyperflowMonitoringPlugin.config.js');

var MonitoringPlugin = function () {
};

MonitoringPlugin.prototype.sendMetrics = function () {
    var that = this;
    //TODO: Create connection once and then try to reuse it
    var parts = config.metricCollector.split(':');
    var host = parts[0];
    var port = parts[1];
    var client = net.connect({host: host, port: port}, function () {
        var metricReport = config.serverName + ' nTasksLeft ' + that.getTasksLeft() + ' ' + parseInt(Date.now() / 1000) + '\r\n';
        client.write(metricReport);
        client.destroy();
    });
};

MonitoringPlugin.prototype.getTasksLeft = function () {
    return this.engine.nTasksLeft;
};

MonitoringPlugin.prototype.init = function (rcl, wflib, engine) {
    if (this.hasOwnProperty('initialized') && this.initialized === true) {
        return;
    }
    this.rcl = rcl;
    this.wflib = wflib;
    this.engine = engine;

    var that = this;
    setInterval(function () {
        that.sendMetrics();
    }, 1000);

    this.initialized = true;
};

module.exports = MonitoringPlugin;

var net = require('net');
var config = require('hyperflowMonitoringPlugin.config.js');

var MonitoringPlugin = function () {
};

MonitoringPlugin.prototype.sendMetrics = function () {
    var that = this;
    //TODO: Create connection once and then try to reuse it
    var parts = config.metricCollector.split(':');
    var host = parts[0];
    var port = 9001;
    if (parts.length > 1) {
        port = parseInt(parts[1]);
    }
    var client = net.connect({host: host, port: port}, function () {
        var metricReport = config.serverName + ' nTasksLeft ' + that.getTasksLeft() + ' ' + parseInt(Date.now() / 1000) + '\r\n';
        client.write(metricReport);
        client.destroy();
    });
};

MonitoringPlugin.prototype.getTasksLeft = function () {
    return this.engine.nTasksLeft;
};

MonitoringPlugin.prototype.init = function (rcl, wflib, engine) {
    if (this.hasOwnProperty('initialized') && this.initialized === true) {
        return;
    }
    this.rcl = rcl;
    this.wflib = wflib;
    this.engine = engine;

    var that = this;
    setInterval(function () {
        that.sendMetrics();
    }, 1000);

    this.initialized = true;
};

module.exports = MonitoringPlugin;

Handle supplying metric collector by env var.


const webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server');
const webpackConfig = require('./webpack.config.local');

new WebpackDevServer(webpack(webpackConfig), {
    publicPath: '/dist/',
    hot: true,
    historyApiFallback: true
}).listen(3000, 'localhost', (err) => {
    if (err) {
        console.log(err);
    }
    console.log(`Webpack dev server listening at localhost:3000`);
});

const webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server');
const args = process.argv;
const webpackConfig = require(`./webpack.config.${args[2].substring(2)}`);

new WebpackDevServer(webpack(webpackConfig), {
    publicPath: '/dist/',
    hot: true,
    historyApiFallback: true
}).listen(3000, 'localhost', (err) => {
    if (err) {
        console.log(err);
    }
    console.log(`Webpack dev server listening at localhost:3000`);
});

Add option to choose between local and npm


import m from 'mithril';
import AppComponent from '../../app/scripts/components/app.js';
import AIPlayer from '../../app/scripts/models/ai-player.js';
import { qs } from './utils.js';

export function _before() {
  // Minimize the transition duration to speed up tests (interestingly, a
  // duration of 0ms will prevent transitionEnd from firing)
  const style = document.createElement('style');
  style.innerHTML = '* {transition-duration: 200ms !important;}';
  document.head.appendChild(style);
  // Also zero out the AI Player's delay between each swift movement
  AIPlayer.waitDelay = 0;
}

export function _beforeEach() {
  document.body.appendChild(document.createElement('main'));
  m.mount(qs('main'), AppComponent);
}

export function _afterEach() {
  m.mount(qs('main'), null);
}

import m from 'mithril';
import AppComponent from '../../app/scripts/components/app.js';
import AIPlayer from '../../app/scripts/models/ai-player.js';
import { qs } from './utils.js';

export function _before() {
  // Minimize the transition duration to speed up tests (interestingly, a
  // duration of 0ms will prevent transitionEnd from firing)
  const style = document.createElement('style');
  style.innerHTML = '* {transition-duration: 100ms !important;}';
  document.head.appendChild(style);
  // Also zero out the AI Player's delay between each swift movement
  AIPlayer.waitDelay = 0;
}

export function _beforeEach() {
  document.body.appendChild(document.createElement('main'));
  m.mount(qs('main'), AppComponent);
}

export function _afterEach() {
  m.mount(qs('main'), null);
}

Reduce test transition-delay from 200ms to 100ms