/**
 * typestring - In-memory TypeScript compiler
 * https://github.com/gavinhungry/typestring
 */

(function() {
  'use strict';

  var fs  = require('fs');
  var tap = require('tap');
  var ts  = require('typestring');

  tap.test('no/undefined input results in an empty string', function(t) {
    var js = ts.compile();
    t.equal(js, '', 'undefined input outputs empty string');

    js = ts.compile('');
    t.equal(js, '', 'empty string input outputs empty string');

    js = ts.compile('   ');
    t.equal(js, '', 'whitespace input outputs empty string');

    t.end();
  });

  tap.test('simple module input results in correct output', function(t) {
    var simpleClassJS = 'var Foo = (function () {\n    function Foo() {\n        this.bar = 2;\n    }\n    return Foo;\n})();\n';

    var js = ts.compile('class Foo { public bar = 2; }');
    t.equal(js, simpleClassJS, 'inline class outputs expected result');

    js = ts.compile('/// <reference path="foo.ts" />', {
      'foo.ts': 'class Foo { public bar = 2; }'
    });

    t.equal(js, simpleClassJS, 'reference path class outputs expected result');

    t.end();
  });

})();

/**
 * typestring - In-memory TypeScript compiler
 * https://github.com/gavinhungry/typestring
 */

(function() {
  'use strict';

  var fs  = require('fs');
  var tap = require('tap');
  var ts  = require('../typestring');

  tap.test('no/undefined input results in an empty string', function(t) {
    var js = ts.compile();
    t.equal(js, '', 'undefined input outputs empty string');

    js = ts.compile('');
    t.equal(js, '', 'empty string input outputs empty string');

    js = ts.compile('   ');
    t.equal(js, '', 'whitespace input outputs empty string');

    t.end();
  });

  tap.test('simple module input results in correct output', function(t) {
    var simpleClassJS = 'var Foo = (function () {\n    function Foo() {\n        this.bar = 2;\n    }\n    return Foo;\n})();\n';

    var js = ts.compile('class Foo { public bar = 2; }');
    t.equal(js, simpleClassJS, 'inline class outputs expected result');

    js = ts.compile('/// <reference path="foo.ts" />', {
      'foo.ts': 'class Foo { public bar = 2; }'
    });

    t.equal(js, simpleClassJS, 'reference path class outputs expected result');

    t.end();
  });

})();

Use relative path to typestring for tests


import Ember from 'ember';

export default function loadAll(model, relationship, dest, options = {}) {
    var page = options.page || 1;
    var query = {
        'page[size]': 10,
        page: page
    };
    query = Ember.merge(query, options || {});
    model.set('query-params', query);

    return model.query(relationship, query).then(results => {
        dest.pushObjects(results.toArray());

        var total = results.meta.pagination.total;
        var pageSize = results.meta.pagination.per_page;
        var remaining = total - (page * pageSize);
        if (remaining > 0) {
            query.page = page + 1;
            query['page[size]'] = pageSize;
            return loadAll(model, relationship, dest, query);
        }
    });
}

import Ember from 'ember';

export default function loadAll(model, relationship, dest, options = {}) {
    var page = options.page || 1;
    var query = {
        'page[size]': 10,
        page: page
    };
    query = Ember.merge(query, options || {});
    Ember.set(model, 'query-params', query);

    return model.query(relationship, query).then(results => {
        dest.pushObjects(results.toArray());

        var total = results.meta.pagination.total;
        var pageSize = results.meta.pagination.per_page;
        var remaining = total - (page * pageSize);
        if (remaining > 0) {
            query.page = page + 1;
            query['page[size]'] = pageSize;
            return loadAll(model, relationship, dest, query);
        }
    });
}

Use Ember.set instead of model.set


export default {
  apiKey: '', // Set your Engine API key here
  logging: {
    level: 'DEBUG' // Engine Proxy logging level. DEBUG, INFO, WARN or ERROR
  }
};

export default {
  apiKey: process.env.ENGINE_API_KEY, // Set your Apollo Engine API key
  logging: {
    level: 'DEBUG' // Engine Proxy logging level. DEBUG, INFO, WARN or ERROR
  }
};

Use the env API key as suggested by Apollo Engine documentation


$(function() {
  window.resources = new ResourceList;

  window.AppView = Backbone.View.extend({
    el: $('#app'),

    resourceTemplate: _.template($('#resourceTemplate').html()),

    initialize: function() {
      _.bindAll(this, 'addResource', 'refreshResources', 'loadResource', 'changeResources');
      resources.bind('add', this.addResource);
      resources.bind('change', this.changeResources);
      resources.bind('remove', this.removeResource);
      resources.bind('refresh', this.refreshResources);
      resources.fetch();
    },

    addResource: function(resource) {
      console.log(resource);
      console.log(resource.elementID);
      var html = this.resourceTemplate({ id: resource.elementID(), url: resource.url(), name: resource.get('name') });
      this.$("#resourceList").append(html);
    },

    refreshResources: function() {
      resources.each(this.addResource);
      Backbone.history.start()
    },

    changeResources: function() {
      this.$("#resourceList").html('');
      resources.each(this.addResource);
    },

    removeResource: function(models) {
      console.log(models);
      console.log('remove resource event handler');
    }
  });
});

$(function() {
  window.resources = new ResourceList;

  window.AppView = Backbone.View.extend({
    el: $('#app'),

    resourceTemplate: _.template($('#resourceTemplate').html()),

    initialize: function() {
      _.bindAll(this, 'addResource', 'refreshResources', 'loadResource', 'changeResources', 'removeResource');
      resources.bind('add', this.addResource);
      resources.bind('change', this.changeResources);
      resources.bind('remove', this.removeResource);
      resources.bind('refresh', this.refreshResources);
      resources.fetch();
    },

    addResource: function(resource) {
      var html = this.resourceTemplate({ id: resource.elementID(), url: resource.url(), name: resource.get('name') });
      this.$("#resourceList").append(html);
    },

    refreshResources: function() {
      resources.each(this.addResource);
      Backbone.history.start()
    },

    changeResources: function() {
      this.$("#resourceList").html('');
      resources.each(this.addResource);
    },

    removeResource: function(model) {
      this.$("#" + model.elementID()).remove();
    }
  });
});

Remove the relevant list item from the AppView after deleting a resource


var textPattern = /^text\/*/;

module.exports = function parseResponse(response) {
    var key, contentType;

    for (key in response) {
        if (Buffer.isBuffer(response[key])) {
            if (key === 'value') {
                if (response.content_type) {
                    contentType = getContentType(response);
                    if (contentType === 'application/json') {
                        response[key] = JSON.parse(response[key]);
                    } else if (textPattern.test(contentType)) {
                        response[key] = response[key].toString();
                    }
                } else {
                    response[key] = response[key].toString();
                }
            } else if (key !== 'vclock') {
                response[key] = response[key].toString();
            }
        } else if (typeof response[key] === 'object') {
            response[key] = parseResponse(response[key]);
        }
    }

    return response;
};

function getContentType(response) {
    return response.content_type.toString().toLowerCase();
}

var textPattern = /^text\/*/;

module.exports = function parseResponse(response) {
    var key, contentType;

    for (key in response) {
        if (Buffer.isBuffer(response[key])) {
            if (key === 'value') {
                if (response.content_type && response[key].length !== 0) {
                    contentType = getContentType(response);
                    if (contentType === 'application/json') {
                        response[key] = JSON.parse(response[key]);
                    } else if (textPattern.test(contentType)) {
                        response[key] = response[key].toString();
                    }
                } else {
                    response[key] = response[key].toString();
                }
            } else if (key !== 'vclock') {
                response[key] = response[key].toString();
            }
        } else if (typeof response[key] === 'object') {
            response[key] = parseResponse(response[key]);
        }
    }

    return response;
};

function getContentType(response) {
    return response.content_type.toString().toLowerCase();
}

Fix parsing empty content values


CRM.$(function($) {
  var $messageField = $('#customData .custom-group-Letter_To input[data-crm-custom="Letter_To:Message_Field"]')
  createEntityRef($messageField, $('#profile_id').val());

  $('#profile_id').change( function() {
    $messageField.crmEntityRef('destroy');
    $messageField.val('');
    createEntityRef($messageField, $('#profile_id').val());
  });

  function createEntityRef($field, profileId) {
    noteFields = getNoteFields();

    $field.crmEntityRef({
      entity: 'UFField',
      placeholder: '- Select Field -',
      api: {
        params: {
          uf_group_id: profileId,
          field_name: {"IN": noteFields}
        }
      },
      select: {minimumInputLength: 0},
    });
  }

  function getNoteFields() {
    var noteFields = ['activity_details'];
    CRM.api3('CustomGroup', 'get', {
      sequential: 1,
      extends: 'Activity',
      'api.CustomField.get': {
        data_type: 'Memo',
        return: 'name'
      }
    }).done(function(result) {
      result['values'].forEach(function(val) {
        val['api.CustomField.get']['values'].forEach(function(field) {
          noteFields.push(field.name);
        });
      });
      return noteFields;
    });
  }
});

CRM.$(function($) {
  var $messageField = $('#customData .custom-group-Letter_To input[data-crm-custom="Letter_To:Message_Field"]');
  $messageField.attr({
    placeholder: '- Select Field -',
    allowClear: 'true',
  });
  createEntityRef($messageField, $('#profile_id').val());

  $('#profile_id').change( function() {
    $messageField.crmEntityRef('destroy');
    $messageField.val('');
    createEntityRef($messageField, $('#profile_id').val());
  });

  function createEntityRef($field, profileId) {
    noteFields = getNoteFields();

    $field.crmEntityRef({
      entity: 'UFField',
      placeholder: '- Select Field -',
      api: {
        params: {
          uf_group_id: profileId,
          field_name: {"IN": noteFields}
        }
      },
      select: {minimumInputLength: 0},
    });
  }

  function getNoteFields() {
    var noteFields = ['activity_details'];
    CRM.api3('CustomGroup', 'get', {
      sequential: 1,
      extends: 'Activity',
      'api.CustomField.get': {
        data_type: 'Memo',
        return: 'name'
      }
    }).done(function(result) {
      result['values'].forEach(function(val) {
        val['api.CustomField.get']['values'].forEach(function(field) {
          noteFields.push(field.name);
        });
      });
      return noteFields;
    });
  }
});

Allow message field to be deselected


(function (window){
    // SYNC
    var sync = $('#zip_sync').required().and().custom('sync', function (value) {
        var ok;

        //ZIP CODE: 90040060
        $.ajax({
            'url': 'https://api.mercadolibre.com/countries/BR/zip_codes/' + value,
            'dataType': 'json',
            'type': 'GET',
            'async': false
        })
        .done(function () {
            ok = true;
        })
        .error(function () {
            ok = false;
        });

        return ok;

    }, 'Message Sync.');
}(this));

(function (window){

    // Creates a required validation
    var sync = $('#zip_sync')
        .required()
        .and()

        // Creates a custom validation wich will make an ajax call syncr.
        .custom('sync', function (value) {

            // This flag will be returned by custom validation after ajax call finishes.
            var ok;

            $.ajax({
                'url': 'https://api.mercadolibre.com/countries/BR/zip_codes/' + value,
                'dataType': 'json',
                'type': 'GET',
                'async': false
            })
            .done(function () {
                ok = true;
            })
            .error(function () {
                ok = false;
            });

            return ok;

        }, 'Please, enter a valid zip code.');
}(this));

Add som comments to sync version.


var DISCORD = (function(){
  var regexMessageRequest = /\/channels\/(\d+)\/messages[^a-z]/;

  return {
    /*
     * Sets up a callback hook to trigger whenever a message request returns a response (the callback is given the channel ID and message array).
     */
    setupMessageRequestHook: function(callback){
      HOOKS.onAjaxResponse(function(args, req){
        var match = args[1].match(regexMessageRequest);

        if (match){
          var channel = match[1];
          var messages = JSON.parse(req.response);

          callback(channel, messages);
        }
      });
    },

    /*
     * Returns an object containing the selected server name, selected channel name and ID, and the object type.
     * For types DM and GROUP, the server and channel names are identical.
     */
    getSelectedChannel: function(){
      var obj;
      var channelListEle = DOM.cls("private-channels");

      if (channelListEle.length !== 0){
        var channel = DOM.cls("selected", channelListEle[0])[0];

        if (!channel || !channel.classList.contains("private")){
          return null;
        }
        else{
          var linkSplit = DOM.tag("a", channel)[0].getAttribute("href").split("/");
          var name = [].find.call(DOM.cls("channel-name", channel)[0].childNodes, node => node.nodeType === Node.TEXT_NODE).nodeValue;

          obj = {
            server: name,
            channel: name,
            id: linkSplit[linkSplit.length-1],
            type: DOM.cls("status", channel).length ? "DM" : "GROUP"
          };
        }
      }
      else{
        channelListEle = DOM.cls("guild-channels");

        if (channelListEle.length === 0){
          return null;
        }
        else{
          var linkSplit = DOM.tag("a", channel)[0].getAttribute("href").split("/");
          var name = DOM.tag("span", DOM.cls("guild-header")[0]).innerHTML;

          obj = {
            server: DOM.tag("span", DOM.cls("guild-header")[0])[0].innerHTML,
            channel: DOM.cls("channel-name", DOM.cls("selected", channelListEle[0])[0])[0].innerHTML,
            id: linkSplit[linkSplit.length-1],
            type: "SERVER"
          };
        }
      }

      return obj.channel.length === 0 ? null : obj;
    }
  };
})();

Add a Discord message request hook and a function to retrieve selected channel info


import jsPDF from 'jspdf';
import * as simple from '../selectors/simple';

const exportPdf = state => {
  const doc = new jsPDF({
    unit: 'in',
    format: [11, 8.5] // US Letter; eventually should allow A4
  });

  doc.text(simple.getArchetype(state), 1, 1);

  // Downloads
  doc.save('character.pdf');
};

export default exportPdf;

import jsPDF from 'jspdf';
import * as simple from '../selectors/simple';

const sizeUSLetter = [279.4, 215.9];
const defaultLineWidth = 0.2;
const dotRadius = 1.5;
const dotSpacing = 0.75;

const printDots = (doc, dots, maxDots, x, y) => {
  doc.setLineWidth(defaultLineWidth);

  for (let i = 0; i < maxDots; i++) {
    const xOffset = dotRadius + i * dotRadius * 2 + i * dotSpacing;
    const style = i < dots ? 'FD' : 'S';
    doc.circle(x + xOffset, y, dotRadius, style);
  }
};

const exportPdf = state => {
  const doc = new jsPDF({
    unit: 'mm',
    format: sizeUSLetter // eventually should allow A4
  });

  doc.text(simple.getArchetype(state), 1, 1);
  printDots(doc, 5, 10, 0, 50);

  // Downloads
  doc.save('character.pdf');
};

export default exportPdf;

Print dots, switch PDF to mm


import {
    Forms,
    Select,
    Dropdown,
    Modal,
    Datepicker,
    Timepicker
} from 'materialize-css';

document.addEventListener('DOMContentLoaded', function() {
  // Init select box
  const selects = document.querySelectorAll('select');
  const selectInstances = M.FormSelect.init(selects, {});

  // Init datepicker
  // The format has been set up following Drupal time field standards.
  // TODO: Create a fallback which takes the value from the field data attrs.
  const datepickers = document.querySelectorAll('.datepicker');
  const datepikerInstances = M.Datepicker.init(datepickers, {'format': 'yyyy-m-d'});

  // Init timepicker
  // Twelve hours type isn't supported by Drupal..
  // TODO: Create a fallback function that convert the value to requirements.
  const timepickers = document.querySelectorAll('.timepicker');
  const timepickerInstances = M.Timepicker.init(timepickers, {'twelveHour': false});
});

Update spacing when importing components.


// Test the game of life script

const test = require('ava');
const gol = require('../../app/js/home/gol.js');

test('properly initializes game of life', (t) => {
  const g = new gol.Game();
  t.true(g instanceof gol.Game);
});

// Test the game of life script

const test = require('ava');
const gol = require('../../app/js/home/gol.js');

test('Properly initializes game of life', (t) => {
  const g = new gol.Game(15, 15);
  t.true(g instanceof gol.Game);
  t.is(g.board.length, 15);
  t.is(g.board[0].length, 15);
});

test('Game of life boardSize works', (t) => {
  const g = new gol.Game(15, 15);
  t.deepEqual(g.boardSize, [15, 15]);
  g.board.push(new Array(15).fill(false));
  t.deepEqual(g.boardSize, [16, 15]);
  for (let i = 0; i < g.boardSize[0]; i += 1) {
    g.board[i].push(false);
  }
  t.deepEqual(g.boardSize, [16, 16]);
});

Add another game of life test


'use strict';

angular.module('Keyboard', [])

.service('KeyboardManager', ['$document', function($document) {

    var keyboardMap = {
        37: 'left',
        38: 'up',
        39: 'right',
        40: 'down'
    };

    // Initialize the keyboard event binding
    this.init =function() {
        var self = this;
        this.keyEventHandlers = [];
        $document.bind('keydown', function(e) {
            var key = keyboardMap[e.which];

            if (key) {
                e.preventDefault();
                self._handleKeyEvent(key, e);
            }
        });
    };

    // Handle key event by calling callbacks
    this._handleKeyEvent = function(key, e) {
        var callbacks = this.keyEventHandlers;
        if (callbacks) {
            for (var i = 0; i < callbacks.length; i++) {
                var cb = callbacks[i];
                cb(key ,e);
            }
        }
    };

    // Bind event handlers to get called when an event is fired
    this.keyEventHandlers = [];
    this.on = function(cb) {
        this.keyEventHandlers.push(cb);
    };
}]);

'use strict';

angular.module('Keyboard', [])

.service('KeyboardManager', ['$document', function($document) {

    var keyboardMap = {
        37: 'left',
        38: 'up',
        39: 'right',
        40: 'down'
    };

    // Initialize the keyboard event binding
    this.init =function() {
        var self = this;
        this.keyEventHandlers = [];
        $document.bind('keydown', function(e) {
            var key = keyboardMap[e.which];

            if (key) {
                e.preventDefault();
                self._handleKeyEvent(key, e);
            }
        });
    };

    // Handle key event by calling callbacks
    this._handleKeyEvent = function(key, e) {
        var callbacks = this.keyEventHandlers;
        if (callbacks) {
            for (var i = 0; i < callbacks.length; i++) {
                var cb = callbacks[i];
                cb(key ,e);
            }
        }
    };

    // Bind event handlers to get called when an event is fired
    this.on = function(cb) {
        this.keyEventHandlers.push(cb);
    };
}]);

Delete duplicate keyEventHandlers initialize statement.


import React from 'react';

import Text from 'src/Text';
import DebugBox from '../DebugBox';

function BasicTextExample() {
    return (
        <div>
            <DebugBox>
                <Text basic="Basic Text" />
            </DebugBox>

            <DebugBox>
                <Text
                    align="center"
                    basic="Basic Text"
                    aside="I am center-aligned" />
            </DebugBox>

            <DebugBox>
                <Text
                    align="right"
                    basic="A Long Long Long Basic Text"
                    aside="I am right-aligned"
                    tag="Tag" />
            </DebugBox>
        </div>
    );
}

export default BasicTextExample;

import React from 'react';

import Text from 'src/Text';
import DebugBox from '../DebugBox';

function BasicTextExample() {
    return (
        <div>
            <DebugBox>
                <Text basic="Basic Text" />
            </DebugBox>

            <DebugBox>
                <Text
                    align="center"
                    basic="Basic Text"
                    aside="I am center-aligned" />
            </DebugBox>

            <DebugBox>
                <Text
                    align="right"
                    basic="A Long Long Long Basic Text"
                    aside="I am right-aligned"
                    tag="Tag" />
            </DebugBox>

            <DebugBox>
                <Text aside="Aside Only Text" />
            </DebugBox>
        </div>
    );
}

export default BasicTextExample;

Add example for aside-only <Text>


'use strict';

module.exports = function(grunt) {
    grunt.registerMultiTask('swagger', 'Generate Source from Swagger files', function(){
        var fs = require('fs');
        var request = require('request');

        var done = this.async();
        var options = this.options();
        var url = this.data.url;
        var dest = options.dest;
        grunt.file.mkdir(dest);

        var count = options.apis.length;
        grunt.log.writeln('Using api: ' + url);
        options.apis.forEach(function(api){
            var swagger = fs.readFileSync(api.swagger);
            request({
                uri: url + '/angular.jq',
                qs: { module: api.module, service: api.service, 'new-module': api.newModule },
                headers: { 'Content-Type': 'text/json; utf-8' },
                body: swagger
            }, function(error, response, body){
                if(response === undefined) {
                    grunt.fail.fatal('Couldn\'t connect to server');
                }
                if(response.statusCode !== 200) {
                    grunt.fail.fatal('Server replied: ' + response.statusCode);
                }
                fs.writeFileSync(dest + '/' + api.service + '.js', body);
                grunt.log.writeln(dest + '/' + api.service + '.js written (' + api.module + '.' + api.service + ')');
                count--;
                if(count === 0) {
                    done();
                }
            });
        });
    });
};

'use strict';

module.exports = function(grunt) {
    grunt.registerMultiTask('swagger', 'Generate Source from Swagger files', function(){
        var fs = require('fs');
        var request = require('request');

        var done = this.async();
        var options = this.options();
        var url = this.data.url;
        var dest = options.dest;
        var failIfOffline = options.failIfOffline;
        grunt.file.mkdir(dest);

        var count = options.apis.length;
        grunt.log.writeln('Using api: ' + url);
        options.apis.forEach(function(api){
            var swagger = fs.readFileSync(api.swagger);
            request({
                uri: url + '/angular.jq',
                qs: { module: api.module, service: api.service, 'new-module': api.newModule },
                headers: { 'Content-Type': 'text/json; utf-8' },
                body: swagger
            }, function(error, response, body){
                if(response === undefined) {
                    if(failIfOffline) {
                        grunt.fail.fatal('Couldn\'t connect to server');
                    } else {
                        grunt.log.writeln('Skipped ' + dest + '/' + api.service + '.js');
                        count--;
                        if(count === 0) {
                            done();
                        }
                        return;
                    }
                }
                if(response.statusCode !== 200) {
                    grunt.fail.fatal('Server replied: ' + response.statusCode);
                }
                fs.writeFileSync(dest + '/' + api.service + '.js', body);
                grunt.log.writeln(dest + '/' + api.service + '.js written (' + api.module + '.' + api.service + ')');
                count--;
                if(count === 0) {
                    done();
                }
            });
        });
    });
};

Add offline support for swagger generation


var express = require('express');
var DataStore = require('./regard-data-store.js');

var router = express.Router();
var dataStore = new DataStore('regard', 'website');

router.get('/userevents/:id', function (req, res, next) {
  var id = req.params.id;

  dataStore.getEventsForUser(id).then(function (events) {
    res.json({
      userevents: [{
        _id: id,
        events: JSON.parse(events)
      }]
    });
  });
});

router.get('/userevents/:id/delete-data', function (req, res, next) {
  var id = req.params.id;

  dataStore.deleteData(id).then(function () {
    res.send(200);
  }, next);
});

module.exports = router;

var express = require('express');
var DataStore = require('./regard-data-store.js');

var router = express.Router();
var dataStore = new DataStore('regard', 'website');

router.get('/userevents/:id', function (req, res, next) {
  var id = req.params.id;

  dataStore.getEventsForUser(id).then(function (events) {
    res.json({
      userevents: [{
        _id: id,
        events: JSON.parse(events)
      }]
    });
  });
});

router.post('/userevents/:id/delete-data', function (req, res, next) {
  var id = req.params.id;

  dataStore.deleteData(id).then(function () {
    res.send(200);
  }, next);
});

module.exports = router;

Make this a post because it deletes data


// Model
var Board = {
  // initialize generates the pieces to display on the board to start the game
  initial: function initial(){
    // Creates all the 32 squares where pieces can be placed
    for (i = 0; i <= 31; i++){
      if (i <= 11){ // Creates all 12 green pieces
        $(".board").append($.parseHTML(''));
      } else if (i >= 12 && i <= 19){ // creates all 12 empty squares
        $(".board").append($.parseHTML(''));
      } else if (i >= 20){ // creates all 12 blue pieces
        $(".board").append($.parseHTML(''));
      }
    }
  },

}

// Model
var Board = {
  // initialize generates the pieces to display on the board to start the game
  initial: function initial(){
    var board = []; // this will save the initial state of the board
    // Creates all the 32 squares where pieces can be placed
    for (i = 0; i <= 31; i++){
      if (i <= 11){ // Creates all 12 green pieces
        $(".board").append($.parseHTML(''));
        board.push("green");
      } else if (i >= 12 && i <= 19){ // creates all 12 empty squares
        $(".board").append($.parseHTML(''));
        board.push("empty");
      } else if (i >= 20){ // creates all 12 blue pieces
        $(".board").append($.parseHTML(''));
        board.push("blue");
      }
    }
    return board;
  }, // end of initial

} // end of Board

Add an array into Board.initial to store the initial state of the boar when the game is started


import React from 'react'
import Header from './Header'
const { shape, string } = React.PropTypes

const Details = React.createClass({
  propTypes: {
    show: shape({
      title: string,
      year: string,
      poster: string,
      trailer: string,
      description: string
    })
  },
  render () {
    const { title, description, year, poster, trailer } = this.props.show
    return (

{title}

({year})

          <img src={`/public/img/posters/${poster}`} />

{description}

          <iframe
            src={`https://www.youtube-nocookie.com/embed/${trailer}?rel=0&controls=0&showinfo=0`}
            frameBorder='0'
            allowFullScreen
          />

    )
  }
})

Details.propTypes = {
  params: React.PropTypes.object
}

export default Details

import React from 'react'
import axios from 'axios'
import Header from './Header'
const { shape, string } = React.PropTypes

const Details = React.createClass({
  propTypes: {
    show: shape({
      title: string,
      year: string,
      poster: string,
      trailer: string,
      description: string,
      imdbID: string
    })
  },
  getInitialState () {
    return {
      omdbData: {}
    }
  },
  componentDidMount () {
    axios.get(`http://www.omdbapi.com/?i=${this.props.show.imdbID}`)
      // returns promise
      .then((response) => {
        // this refers to Details - call setState on Details. Using arrow function for .then allows us not to need to add .bind(this) after then
        this.setState({omdbData: response.data})
      })
      .catch((error) => console.error('axios error', error))
  },
  render () {
    const { title, description, year, poster, trailer } = this.props.show
    let rating
    // rating will contain either loading state component or data from the api
    if (this.state.omdbData.imdbRating) {
      rating =
{this.state.omdbData.imdbRating}

    } else {
      rating =
    }
    return (

{title}

({year})

          {rating}
          <img src={`/public/img/posters/${poster}`} />

{description}

          <iframe
            src={`https://www.youtube-nocookie.com/embed/${trailer}?rel=0&controls=0&showinfo=0`}
            frameBorder='0'
            allowFullScreen
          />

    )
  }
})

Details.propTypes = {
  params: React.PropTypes.object
}

export default Details

Add OMDb API call with axios and lifecycle hooks.


'use strict';

/**
 * @ngdoc directive
 * @name vlui.directive:bookmarkList
 * @description
 * # bookmarkList
 */
angular.module('vlui')
  .directive('bookmarkList', function (Bookmarks, consts) {
    return {
      templateUrl: 'bookmarklist/bookmarklist.html',
      restrict: 'E',
      replace: true,
      require: '^vlModal',
      scope: {
        highlighted: '='
      },
      link: function postLink(scope, element, attrs, modalController) {
        scope.deactivate = function() {
          modalController.close();
        }
        scope.Bookmarks = Bookmarks;
        scope.consts = consts;
      }
    };
  });

'use strict';

/**
 * @ngdoc directive
 * @name vlui.directive:bookmarkList
 * @description
 * # bookmarkList
 */
angular.module('vlui')
  .directive('bookmarkList', function (Bookmarks, consts, Logger) {
    return {
      templateUrl: 'bookmarklist/bookmarklist.html',
      restrict: 'E',
      replace: true,
      require: '^vlModal',
      scope: {
        highlighted: '='
      },
      link: function postLink(scope, element, attrs, modalController) {
        scope.deactivate = function() {
          Logger.logInteraction(Logger.actions.BOOKMARK_CLOSE);
          modalController.close();
        }
        scope.Bookmarks = Bookmarks;
        scope.consts = consts;
      }
    };
  });

Move logging for bookmarks close from voyager to vlui


var Compressor = require('node-minify').minify;

exports.init = function(grunt) {

	// Refactored out the `min_max_info` and `concat` helpers here
	// because `grunt.helper` is removed in future Grunt versions
	var minMax = require('grunt-lib-contrib').init(grunt).minMaxInfo,
		concat = function(source) {
			// Kinda hacky, but that’s how I roll…
			return source.map(function(filepath) {
				return grunt.file.read(filepath);
			}).join('\n');
		};

	return function(options) {
		var source = grunt.file.expand(options.source),
			destination = options.destination,
			max = concat(source),
			min,
			report = options.report;

		// Ugly hack to create the destination path automatically if needed
		grunt.file.write(destination, '');

		// Minify all the things!
		new Compressor({
			'type': 'yui-' + options.type,
			'fileIn': source,
			'fileOut': destination,
			'callback': function(error) {
				if (error) {
					grunt.warn(error);
					return options.fn();
				}
				min = grunt.file.read(destination);
				grunt.log.writeln('File `' + destination + '` created.');
				minMax(min, max, report);
				// Let Grunt know the asynchronous task has completed
				options.fn();
			}
		});
	};
};

var Compressor = require('node-minify').minify;

exports.init = function(grunt) {

	// Refactored out the `min_max_info` and `concat` helpers here
	// because `grunt.helper` is removed in future Grunt versions
	var minMax = require('grunt-lib-contrib').init(grunt).minMaxInfo,
		concat = function(source) {
			// Kinda hacky, but that’s how I roll…
			return source.map(function(filepath) {
				return grunt.file.read(filepath);
			}).join('\n');
		};

	return function(options) {
		var source = grunt.file.expand(options.source),
			destination = options.destination,
			max = concat(source),
			min,
			report = options.report;

		if (source.indexOf(destination) === -1) {
			// Ugly hack to create the destination path automatically if needed
		   grunt.file.write(destination, '');
		}

		// Minify all the things!
		new Compressor({
			'type': 'yui-' + options.type,
			'fileIn': source,
			'fileOut': destination,
			'callback': function(error) {
				if (error) {
					grunt.warn(error);
					return options.fn();
				}
				min = grunt.file.read(destination);
				grunt.log.writeln('File `' + destination + '` created.');
				minMax(min, max, report);
				// Let Grunt know the asynchronous task has completed
				options.fn();
			}
		});
	};
};

Fix issue when source and destination has the same path


#!/usr/bin/env node

var adventure = require('adventure');
var shop = adventure('javascript-adventure');
var path = require('path');

var problems = [ '01_Hello_World', '02_Functions' ];
problems.forEach(function (prob) {
    shop.add(prob.replace(/_/g, ' '), function () {
        return require(path.join(__dirname, '/problems/' + prob.toLowerCase()))});
});

shop.execute(process.argv.slice(2));

#!/usr/bin/env node

var adventure = require('adventure');
var shop = adventure('javascript-adventure');
var path = require('path');

// Specify all the problems here
var problems = [ '01_Hello_World', '02_Functions' ];
problems.forEach(function (prob) {
    shop.add(prob.replace(/_/g, ' '), function () {
        return require(path.join(__dirname, '/problems/' + prob.toLowerCase()))});
});

shop.execute(process.argv.slice(2));

Update text format to UNIX


const toSay = 'whats up dood';
module.exports = function(bot){
	bot.addCommand('dood', 'dood', '[<user>]', USER_LEVEL_NORMAL, false, function(event){
		if(!event.params[0]){
			bot.message(event.target, toSay);
		}else if(event.params && event.params.length < 3){
			event.params.forEach(function(user){
				bot.message(event.target, user + ',');
			});
			bot.message(event.target, toSay);
		}else{
			bot.message(event.target, event.source.nick + ': You can not greet this many!');
		}
	});
}

const toSay = 'whats up dood';
module.exports = function(bot){
	bot.addCommand('dood', 'dood', '[<user>]', USER_LEVEL_NORMAL, false, function(event){
		if(!event.params[0]){
			bot.message(event.target, toSay);
		}else if(event.params && event.params.length < 5){
			event.params.forEach(function(user){
				bot.message(event.target, user + ',');
			});
			bot.message(event.target, toSay);
		}else{
			bot.message(event.target, event.source.nick + ': You can not greet this many!');
		}
	});
}

Increase max users you can dood


'use strict';
var generators = require('yeoman-generator');
var extend = require('lodash').merge;

module.exports = generators.Base.extend({
  constructor: function () {
    generators.Base.apply(this, arguments);

    this.option('generateInto', {
      type: String,
      required: false,
      defaults: '',
      desc: 'Relocate the location of the generated files.'
    });

    this.option('es2015', {
      required: false,
      defaults: false,
      desc: 'Allow ES2015 syntax'
    });
  },

  writing: function () {
    var pkg = this.fs.readJSON(this.destinationPath(this.options.generateInto, 'package.json'), {});

    var eslintConfig = {
      extends: 'xo-space',
      env: {
        mocha: true
      }
    };
    var devDep = {
      'eslint': '^2.1.0',
      'eslint-config-xo-space': '^0.12.0'
    };

    if (this.options.es2015) {
      devDep['babel-eslint'] = '^4.1.8';
      devDep['eslint-plugin-babel'] = '^3.1.0';
      eslintConfig.extends = 'xo-space/esnext';
    }

    extend(pkg, {
      devDependencies: devDep,
      eslintConfig: eslintConfig
    });

    this.fs.writeJSON(this.destinationPath(this.options.generateInto, 'package.json'), pkg);
  }
});

'use strict';
var generators = require('yeoman-generator');
var extend = require('lodash').merge;

module.exports = generators.Base.extend({
  constructor: function () {
    generators.Base.apply(this, arguments);

    this.option('generateInto', {
      type: String,
      required: false,
      defaults: '',
      desc: 'Relocate the location of the generated files.'
    });

    this.option('es2015', {
      required: false,
      defaults: false,
      desc: 'Allow ES2015 syntax'
    });
  },

  writing: function () {
    var pkg = this.fs.readJSON(this.destinationPath(this.options.generateInto, 'package.json'), {});

    var eslintConfig = {
      extends: 'xo-space',
      env: {
        mocha: true
      }
    };
    var devDep = {
      'eslint': '^2.1.0',
      'eslint-config-xo-space': '^0.12.0'
    };

    if (this.options.es2015) {
      devDep['babel-eslint'] = '^4.1.8';
      devDep['eslint-plugin-babel'] = '^3.1.0';
    }

    extend(pkg, {
      devDependencies: devDep,
      eslintConfig: eslintConfig
    });

    this.fs.writeJSON(this.destinationPath(this.options.generateInto, 'package.json'), pkg);
  }
});

Use normal xo config for eslint even with es2015


var KWHC = {
    init: function() {
        // Bind device buttons
        $('.device__button').on('click', this.controlDevice);
    },
    controlDevice: function(e) {
        var action = $(e.currentTarget).data('action');
        var name = $(e.currentTarget).parent('.device').data('name');
        var url = window.baseUrl + 'control/device/' + name + '/' + action;
        $.post(url, function(data) {
            console.log(data);
        });
    }

};

window.baseUrl = '';
window._wkhc = KWHC;
window._wkhc.init();

var KWHC = {
    init: function() {
        // Bind device buttons
        $('.device__button').on('click', this.controlDevice)
    },
    controlDevice: function(e) {
        var action = $(e.currentTarget).data('action')
        var name = $(e.currentTarget).parent('.device').data('name')

        var url = window.baseUrl
                  + 'control/device/'
                  + encodeURIComponent(name) + '/'
                  + encodeURIComponent(action)
        $.post(url, function(data) {
            console.log(data)
        })
    }
}

window.baseUrl = ''
window._wkhc = KWHC
window._wkhc.init()

Remove semicolons. URL-encode stuff in request.


import React, { Component } from 'react'
import { Text } from '@hackclub/design-system'

const URL = 'https://finder.hackclub.com'

export default class extends Component {
  componentWillMount() {
    window.location = URL
  }

  render() {
    return (
      <Text>
        Redirecting you to {URL}
      </Text>
    )
  }
}

import React, { Component } from 'react'

const URL = 'https://finder.hackclub.com'

export default class extends Component {
  componentWillMount() {
    window.location = URL
  }

  render() {
    return (

        Redirecting you to {URL}

    )
  }
}

Remove design-system from finder.hackclub.com redirect


var d3 = require('d3');

var dataSet = [];
for (var i = 0; i < 50; i++) {
  dataSet[i] = Math.random()*300;
}

d3.select('.l1')
  .selectAll('div')
  .data(dataSet)
  .enter()
  .append('div')
  .attr('class','bar')
  .style("height", function (d) { return d + 'px' });

var d3 = require('d3');

var dataSet = [];
for (var i = 0; i < 20; i++) {
  dataSet[i] = Math.random()*300;
}

d3.select('.l1')
  .selectAll('div')
  .data(dataSet)
  .enter()
  .append('div')
  .attr('class','bar')
  .style("height", function (d) { return d + 'px' });

Decrease the number of bars in lesson 1


var pingPong = function(userNumber) {
  if ((userNumber % 3 === 0) || (userNumber % 5 === 0)) {
    return true;
  } else {
    return false;
  }
};

$(function() {
  $("form#game").submit(function(event) {

    event.preventDefault();
  });
});

var pingPong = function(userNumber) {
  if ((userNumber % 3 === 0) || (userNumber % 5 === 0)) {
    return true;
  } else {
    return false;
  }
};

$(function() {
  $("form#game").submit(function(event) {
    $("#outputList").empty();

    var number = parseInt($("#userNumber").val())
    var output = pingPong(number)

    event.preventDefault();
  });
});

Add variables and code for emptying output list at the beginning of a new number submission


'use strict';

const _ = require('underscore');

function buildSlots(slots) {
  const res = {};
  _.each(slots, function(value, key) {
    res[key] = {
      name: key,
      value: value
    };
  });
  return res;
}

function buildSession(e) {
  return e ? e.sessionAttributes : {};
}

function init(optionsArg) {
  let options = {};
  let isNew = true;

  // public API

  const api = {
    init,
    buildRequest
  };

  function buildRequest(intentName, slots, prevEvent) {
    if (!options.appId) throw String('AppId not specified. Please run events.init(appId) before building a Request');
    const res = { // override more stuff later as we need
      session: {
        sessionId: options.sessionId,
        application: {
          applicationId: options.appId
        },
        attributes: buildSession(prevEvent),
        user: {
          userId: options.userId,
          accessToken: options.accessToken
        },
        new: isNew
      },
      request: {
        type: 'IntentRequest',
        requestId: options.requestId,
        locale: options.locale,
        timestamp: (new Date()).toISOString(),
        intent: {
          name: intentName,
          slots: buildSlots(slots)
        }
      },
      version: '1.0'
    };
    isNew = false;
    return res;
  }

  return api;
}

module.exports = init;

'use strict';

const _ = require('underscore');

function buildSlots(slots) {
  const res = {};
  _.each(slots, (value, key) => {
    res[key] = {
      name: key,
      value: value
    };
  });
  return res;
}

function buildSession(e) {
  return e ? e.sessionAttributes : {};
}

function init(options) {
  let isNew = true;

  // public API

  const api = {
    init,
    buildRequest
  };

  function buildRequest(intentName, slots, prevEvent) {
    if (!options.appId) throw String('AppId not specified. Please run events.init(appId) before building a Request');
    const res = { // override more stuff later as we need
      session: {
        sessionId: options.sessionId,
        application: {
          applicationId: options.appId
        },
        attributes: buildSession(prevEvent),
        user: {
          userId: options.userId,
          accessToken: options.accessToken
        },
        new: isNew
      },
      request: {
        type: 'IntentRequest',
        requestId: options.requestId,
        locale: options.locale,
        timestamp: (new Date()).toISOString(),
        intent: {
          name: intentName,
          slots: buildSlots(slots)
        }
      },
      version: '1.0'
    };
    isNew = false;
    return res;
  }

  return api;
}

module.exports = {init};

Fix error on the object returned


import React from 'react';
import {
  Route,
  Switch,
  Redirect,
} from 'react-router-dom';

import ComingSoon from 'features/ComingSoon';
import NotFoundView from 'components/NotFound';
import Beta from 'features/Beta';

export default (
  <Switch>
    <Route exact path="/" component={ComingSoon} />
    <Route path='/beta' component={Beta} />
    <Route path="/404" component={NotFoundView} />
    <Redirect from="*" to="/404" />
  </Switch>
);

import React from 'react';
import {
  Route,
  Switch,
  Redirect,
} from 'react-router-dom';

import ComingSoon from 'features/ComingSoon';
import NotFoundView from 'components/NotFound';
import Beta from 'features/Beta';

export default (
  <Switch>
    <Route exact path="/" component={Beta} />
    <Route path='/legacy' component={ComingSoon} />
    <Route path="/404" component={NotFoundView} />
    <Redirect from="*" to="/404" />
  </Switch>
);

Use Beta as main entry point


import React from 'react';
import { createStore } from 'redux';
import { browserHistory } from 'react-router';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import rootReducer from '../../src/reducers';
import Routes from '../../src/Routes';
import App from '../../src/containers/App';

const configureStore = initialState => createStore(
  rootReducer,
  initialState,
);

describe('Container | App', () => {
  it('renders Routes component', () => {
    const wrapper = shallow(<App history={browserHistory} store={configureStore()}/>);

    expect(wrapper.find(Routes)).to.have.lengthOf(1);
  });
});

import React from 'react';
import { createStore } from 'redux';
import { createBrowserHistory } from 'history';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import rootReducer from '../../src/reducers';
import Routes from '../../src/Routes';
import App from '../../src/containers/App';

const configureStore = initialState => createStore(
  rootReducer,
  initialState,
);

describe('Container | App', () => {
  it('renders Routes component', () => {
    const wrapper = shallow(<App history={createBrowserHistory()} store={configureStore()}/>);

    expect(wrapper.find(Routes)).to.have.lengthOf(1);
  });
});

Update unit test for App container


// Karma configuration file, see link for more information
// https://karma-runner.github.io/0.13/config/configuration-file.html

module.exports = function (config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine', 'angular-cli'],
    plugins: [
      require('karma-jasmine'),
      require('karma-chrome-launcher'),
      require('karma-remap-istanbul'),
      require('angular-cli/plugins/karma')
    ],
    files: [
      { pattern: './src/test.ts', watched: false },
      { pattern: './src/assets/**', watched: false, included: false, nocache: false, served: true }
    ],
    preprocessors: {
      './src/test.ts': ['angular-cli']
    },
    proxies: {
      '/assets': '/base/src/assets'
    },
    remapIstanbulReporter: {
      reports: {
        html: 'coverage',
        lcovonly: './coverage/coverage.lcov'
      }
    },
    angularCli: {
      config: './angular-cli.json',
      environment: 'dev'
    },
    reporters: ['progress', 'karma-remap-istanbul'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['Chrome'],
    singleRun: false
  });
};

// Karma configuration file, see link for more information
// https://karma-runner.github.io/0.13/config/configuration-file.html

module.exports = function (config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine', 'angular-cli'],
    plugins: [
      require('karma-jasmine'),
      require('karma-chrome-launcher'),
      require('karma-remap-istanbul'),
      require('angular-cli/plugins/karma')
    ],
    files: [
      { pattern: './src/test.ts', watched: false },
      { pattern: './src/assets/**', watched: false, included: false, nocache: false, served: true }
    ],
    preprocessors: {
      './src/test.ts': ['angular-cli']
    },
    proxies: {
      '/assets': '/base/src/assets'
    },
    remapIstanbulReporter: {
      reports: {
        html: 'coverage',
        lcovonly: './coverage/coverage.lcov'
      }
    },
    angularCli: {
      config: './angular-cli.json',
      environment: 'dev'
    },
    mime: {
      'text/x-typescript': ['ts','tsx']
    },
    reporters: ['progress', 'karma-remap-istanbul'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['Chrome'],
    singleRun: false
  });
};

Fix unit tests failing due to incorrect mime type of the served test file


}

// auto-load Promise polyfill if needed in the browser
var doPolyfill = typeof Promise === 'undefined';

// document.write
if (typeof document !== 'undefined') {
  var scripts = document.getElementsByTagName('script');
  $__curScript = scripts[scripts.length - 1];
  if ($__curScript.defer || $__curScript.async)
    $__curScript = document.currentScript;
  if (doPolyfill) {
    var curPath = $__curScript.src;
    var basePath = curPath.substr(0, curPath.lastIndexOf('/') + 1);
    window.systemJSBootstrap = bootstrap;
    document.write(
      '<' + 'script type="text/javascript" src="' + basePath + 'system-polyfills.js">' + '<' + '/script>'
    );
  }
  else {
    bootstrap();
  }
}
// importScripts
else if (typeof importScripts !== 'undefined') {
  var basePath = '';
  try {
    throw new Error('_');
  } catch (e) {
    e.stack.replace(/(?:at|@).*(http.+):[\d]+:[\d]+/, function(m, url) {
      $__curScript = { src: url };
      basePath = url.replace(/\/[^\/]*$/, '/');
    });
  }
  if (doPolyfill)
    importScripts(basePath + 'system-polyfills.js');
  bootstrap();
}
else {
  $__curScript = typeof __filename != 'undefined' ? { src: __filename } : null;
  bootstrap();
}

})();

}

// auto-load Promise polyfill if needed in the browser
var doPolyfill = typeof Promise === 'undefined';

// document.write
if (typeof document !== 'undefined') {
  var scripts = document.getElementsByTagName('script');
  $__curScript = scripts[scripts.length - 1];
  if (document.currentScript && ($__curScript.defer || $__curScript.async))
    $__curScript = document.currentScript;
  if (doPolyfill) {
    var curPath = $__curScript.src;
    var basePath = curPath.substr(0, curPath.lastIndexOf('/') + 1);
    window.systemJSBootstrap = bootstrap;
    document.write(
      '<' + 'script type="text/javascript" src="' + basePath + 'system-polyfills.js">' + '<' + '/script>'
    );
  }
  else {
    bootstrap();
  }
}
// importScripts
else if (typeof importScripts !== 'undefined') {
  var basePath = '';
  try {
    throw new Error('_');
  } catch (e) {
    e.stack.replace(/(?:at|@).*(http.+):[\d]+:[\d]+/, function(m, url) {
      $__curScript = { src: url };
      basePath = url.replace(/\/[^\/]*$/, '/');
    });
  }
  if (doPolyfill)
    importScripts(basePath + 'system-polyfills.js');
  bootstrap();
}
else {
  $__curScript = typeof __filename != 'undefined' ? { src: __filename } : null;
  bootstrap();
}

})();

Check for document.currentScript before assigning it


'use strict';

var express = require('express');
var mongoose = require('mongoose');
var port = process.env.PORT || 3000;

var app = express();
app.use(express.static(__dirname + '/build'));

var mongoUri = process.env.MONGO_WISHLIST_APP_URI || 'mongodb://localhost/wishlistApp';

var wishlistRoutes = express.Router();
require('./routes/wishlist-routes')(wishlistRoutes);
app.use('/api', wishlistRoutes);

mongoose.connect(mongoUri, function(err) {
  if (err) console.error('MongoDB connection error:', err);
  else console.log('MongoDB successfully connected to', mongoUri);
});

app.listen(port, function() {
  console.log('Server now listening on port', port, '...');
});

'use strict';

var express = require('express');
var mongoose = require('mongoose');
var port = process.env.PORT || 3000;

var app = express();
app.use(express.static(__dirname + '/public'));

var mongoUri = process.env.MONGO_WISHLIST_APP_URI || 'mongodb://localhost/wishlistApp';

var wishlistRoutes = express.Router();
require('./routes/wishlist-routes')(wishlistRoutes);
app.use('/api', wishlistRoutes);

mongoose.connect(mongoUri, function(err) {
  if (err) console.error('MongoDB connection error:', err);
  else console.log('MongoDB successfully connected to', mongoUri);
});

app.listen(port, function() {
  console.log('Server now listening on port', port, '...');
});

Change /build to /public to match current directory structure


var R = require('ramda');
var util = require('util');
var jomini = require('jomini');

function flattenHistory(history) {
  var result = [];

  var isDate = R.pipe(R.split('.'), R.length, R.gt(2));
  var split = R.partition(isDate, R.keys(history));

  var initial = {
    date: new Date(Date.UTC(1, 0, 1)),
    events: R.omit(split[1], history)
  };

  // Some of the dates may have multiple events spread over multiple
  // instances, for eg: '1800.1.1': [{owner: 'FRA'}, {religion: 'reformed'}].
  // We want to combine these arrays into a single object
  var acct = function(acc, val) {
    var valArr = util.isArray(history[val]) ? history[val] : [history[val]];
    return R.append({
      date: jomini.toDate(val),
      events: R.foldl(R.mixin, {}, valArr)
    }, acc);
  }

  var arr = R.foldl(acct, [], split[1])
  return split[0].length > 0 ? R.prepend(initial, arr) : arr;
}

module.exports = flattenHistory;

var R = require('ramda');
var util = require('util');
var jomini = require('jomini');

function flattenHistory(history) {
  var isDate = R.pipe(R.split('.'), R.length, R.gt(2));
  var split = R.partition(isDate, R.keys(history));

  var initial = {
    date: new Date(Date.UTC(1, 0, 1)),
    events: R.omit(split[1], history)
  };

  // Some of the dates may have multiple events spread over multiple
  // instances, for eg: '1800.1.1': [{owner: 'FRA'}, {religion: 'reformed'}].
  // We want to combine these arrays into a single object
  var acct = function(acc, val) {
    var valArr = util.isArray(history[val]) ? history[val] : [history[val]];
    return R.append({
      date: jomini.toDate(val),
      events: R.foldl(R.mixin, {}, valArr)
    }, acc);
  };

  var arr = R.foldl(acct, [], split[1]);
  return split[0].length > 0 ? R.prepend(initial, arr) : arr;
}

module.exports = flattenHistory;

Clean up some dead code


import DataLoader from 'dataloader';
import qs from 'qs';
import gravity from '../apis/gravity';
import cache from '../cache';
import timer from './timer';
import debug from 'debug';

let log = debug('info');

export let gravityLoader = new DataLoader(paths => Promise.all(paths.map(path => {
  let start = timer.start(path);

  return new Promise((resolve, reject) => {
    cache
      .get(path)
      .then((data) => {
        log(`Cached: ${path}`);
        timer.end(start);

        resolve(data);

        gravity(path)
          .then(data => {
            log(`Refreshing: ${path}`);
            cache.set(path, data);
          })

      }, () => {
        gravity(path)
          .then((data) => {
            log(`Requested: ${path}`);
            timer.end(start);
            cache.set(path, data);
            resolve(data);
          });
      });
  });

})), { batch: false, cache: true });

export default (path, options = {}) => {
  let queryString = qs.stringify(options, { arrayFormat: 'brackets' });
  return gravityLoader.load(`${path}?${queryString}`);
};

import qs from 'qs';
import gravity from '../apis/gravity';
import httpLoader from './http';

export let gravityLoader = httpLoader(gravity);

export default (path, options = {}) => {
  let queryString = qs.stringify(options, { arrayFormat: 'brackets' });
  return gravityLoader.load(`${path}?${queryString}`);
};

Convert Gravity's loader to use the generic loader


var utils = require('./utils');

module.exports.create = function (id, client) {
  var instance = {};

  /*

  */
  instance.id = 'radio.' + id;

  instance.volume = function (params) {
    return sendCommandForAction('volume', params);
  };

  instance.playRandom = function (params) {
    return sendCommandForAction('random', params);
  };

  instance.play = function (params) {
    return sendCommandForAction('play', params);
  };

  function sendCommandForAction(action, params) {
    params.action = action;
    return client.createExchange({ exchangeName: 'radiodan' })
                 .then(function () {
                    return sendCommandToExchange(params);
                  })
                 .then(null, utils.failedPromiseHandler());
  }

  function sendCommandToExchange(params) {
    return client.sendCommand({
      exchangeName : 'radiodan',
      topicKey     : instance.id + '.command',
      command      : params
    });
  }

  return instance;
};

var utils  = require('./utils');

module.exports.create = function (id, client) {
  var instance = {},
      logger   = utils.logger(__filename);

  /*

  */
  instance.id = 'radio.' + id;

  instance.volume = function (params) {
    return sendCommandForAction('volume', params);
  };

  instance.playRandom = function (params) {
    return sendCommandForAction('random', params);
  };

  instance.play = function (params) {
    return sendCommandForAction('play', params);
  };

  function sendCommandForAction(action, params) {
    params.action = action;
    return client.createExchange({ exchangeName: 'radiodan' })
                 .then(function () {
                    return sendCommandToExchange(params);
                  })
                 .then(null, utils.failedPromiseHandler());
  }

  function sendCommandToExchange(params) {
    return client.sendCommand({
      exchangeName : 'radiodan',
      topicKey     : instance.id + '.command',
      command      : params
    });
  }

  return instance;
};

Create logger on Deck creation


/*!
 * Author:        Pierre-Henry Soria <hello@ph7cms.com>
 * Copyright:     (c) 2020, Pierre-Henry Soria. All Rights Reserved.
 * License:       GNU General Public License; See PH7.LICENSE.txt and PH7.COPYRIGHT.txt in the root directory.
 */

const disagreedRedirectLink = "https://google.com";

$(document).ready(function () {
    let oDisclaimer = new Disclaimer();

    if (!oDisclaimer.isAccepted()) {
        oDisclaimer.centerDialog();
        oDisclaimer.loadDialog();
    }

    document.getElementById('agree-over18').onclick = function () {
        oDisclaimer.disableDialog();
        oDisclaimer.setAccepted();
    };
    document.getElementById('disagree-under18').onclick = function () {
        location.href = disagreedRedirectLink
    }
});

/*!
 * Author:        Pierre-Henry Soria <hello@ph7cms.com>
 * Copyright:     (c) 2020, Pierre-Henry Soria. All Rights Reserved.
 * License:       GNU General Public License; See PH7.LICENSE.txt and PH7.COPYRIGHT.txt in the root directory.
 */

const disagreedRedirectLink = "https://google.com"; // <= URL to show when the visitor is likely to be under 18yrs

$(document).ready(function () {
    const oDisclaimer = new Disclaimer();

    if (!oDisclaimer.isAccepted()) {
        oDisclaimer.loadDialog();
        oDisclaimer.centerDialog();
    }

    document.getElementById('agree-over18').onclick = function () {
        oDisclaimer.disableDialog();
        oDisclaimer.setAccepted();
    };
    document.getElementById('disagree-under18').onclick = function () {
        location.href = disagreedRedirectLink
    }
});

Use const keyword for object var


var mqtt = require('mqtt'),
    fs = require('fs'),
    url = require('url'),
    config = require('config');

module.exports.connect = function () {
  var caCert,
      mqttBroker = url.format( config.get('brokerNode') ),
      instance;

  if (config.get('broker.caFile')) {
    caCert = fs.readFileSync('./certs/' + config.get('broker.caFile'));
  }

  console.log('Connecting to: ', mqttBroker);

  instance = mqtt.connect(mqttBroker, { ca: caCert });

  return instance;
}

/*
  ciot/kit0/<device>/<action>/<type>
*/
module.exports.topicForParams = function topic(p) {
  return [ 'ciot/kit0', p.device, p.action, p.type ].join('/')
}

var mqtt = require('mqtt'),
    fs = require('fs'),
    url = require('url'),
    config = require('config');

module.exports.connect = function () {
  var caCert,
      mqttBroker = url.format( config.get('brokerNode') ),
      instance;

  if (config.get('brokerNode.caFile')) {
    caCert = fs.readFileSync('./certs/' + config.get('brokerNode.caFile'));
  }

  console.log('Connecting to: ', mqttBroker);

  instance = mqtt.connect(mqttBroker, { ca: caCert });

  return instance;
}

/*
  ciot/kit0/<device>/<action>/<type>
*/
module.exports.topicForParams = function topic(p) {
  return [ 'ciot/kit0', p.device, p.action, p.type ].join('/')
}

Fix typo fetching config for node broker connection


var assert = require("assert");
var n = require("./types").namedTypes;

function Path(node, parent) {
    assert.ok(this instanceof Path);
    n.Node.assert(node);
    if (parent) {
        assert.ok(parent instanceof Path);
    } else {
        parent = null;
    }

    Object.defineProperties(this, {
        node: { value: node },
        parent: { value: parent }
    });
}

var Pp = Path.prototype;

Pp.cons = function(child) {
    return new Path(child, this);
};

Pp.needsParens = function() {
    if (!this.parent)
        return false;

    var node = this.node;
    var parent = this.parent.node;

    if (n.UnaryExpression.check(node))
        return n.MemberExpression.check(parent)
            && parent.object === node;

    return false; // TODO
};

exports.Path = Path;

var assert = require("assert");
var n = require("./types").namedTypes;

function Path(node, parent) {
    assert.ok(this instanceof Path);
    n.Node.assert(node);
    if (parent) {
        assert.ok(parent instanceof Path);
    } else {
        parent = null;
    }

    Object.defineProperties(this, {
        node: { value: node },
        parent: { value: parent }
    });
}

var Pp = Path.prototype;

Pp.cons = function(child) {
    return new Path(child, this);
};

Pp.needsParens = function() {
    if (!this.parent)
        return false;

    var node = this.node;
    var parent = this.parent.node;

    if (n.UnaryExpression.check(node))
        return n.MemberExpression.check(parent)
            && parent.object === node;

    if (isBinary(node)) {
        if (n.CallExpression.check(parent) &&
            parent.callee === node)
            return true;

        if (n.UnaryExpression.check(parent))
            return true;

        if (n.MemberExpression.check(parent) &&
            parent.object === node)
            return true;
    }

    return false; // TODO
};

function isBinary(node) {
    return n.BinaryExpression.check(node)
        || n.LogicalExpression.check(node);
}

exports.Path = Path;

Handle simple BinaryExpressions in Path.prototype.needsParens.


chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    // 'use strict';
    if (request.action && sender.tab) {
      switch (request.action) {
      case 'getSelection':
        sendResponse(window.getSelection().toString());
      }
    }
  });

chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    // 'use strict';
    if (request.action && sender.id) {
      switch (request.action) {
      case 'getSelection':
        sendResponse(window.getSelection().toString());
      }
    }
  });

Fix confition to check sender


/*
Copyright (c) 2013, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License.
See the accompanying LICENSE file for terms.
*/

'use strict';

// The `pathTo()` function is written in ES3 so it's serializable and able to
// run in all JavaScript environments.
module.exports = function pathTo(routeMap) {
    return function (routeName, context) {
        var route = routeMap[routeName],
            path, keys, i, len, key, param, regex;

        if (!route) { return ''; }

        path = route.path;
        keys = route.keys;

        if (context && (len = keys.length)) {
            for (i = 0; i < len; i += 1) {
                key   = keys[i];
                param = key.name || key;
                regex = new RegExp('[:*]' + param + '\\b');
                path  = path.replace(regex, context[param]);
            }
        }

        // TMN Specific
        if(!context.site && window && window.TMNState && window.TMNState.site){
            context.site = window.TMNState.site;
        }
        // Replace missing params with empty strings.
        // pathTo is not correctly handling express optional parameters of the form /blogs/:id/:tag?
        // lop off the ? off the end of the result
        return path.replace(/([:*])([\w\-]+)?/g, '').replace(/[?]$/, '');
    };
};

/*
Copyright (c) 2013, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License.
See the accompanying LICENSE file for terms.
*/

'use strict';

// The `pathTo()` function is written in ES3 so it's serializable and able to
// run in all JavaScript environments.
module.exports = function pathTo(routeMap) {
    return function (routeName, context) {
        var route = routeMap[routeName],
            path, keys, i, len, key, param, regex;

        if (!route) { return ''; }

        path = route.path;
        keys = route.keys;

        if (context && (len = keys.length)) {
            for (i = 0; i < len; i += 1) {
                key   = keys[i];
                param = key.name || key;
                regex = new RegExp('[:*]' + param + '\\b');
                path  = path.replace(regex, context[param]);
            }
        }

        // TMN Specific
        // apply site option if not specified.
        if(!context.site){
            try{
                var isServer = typeof window === "undefined";

                if(isServer){
                    context.site = global.appConfig.app.sport;
                } else {
                    context.site = window.TMNState.site;
                }
            } catch(e){
                console.error("Failed to apply missing site param to pathTo", e);
            }
        }
        // Replace missing params with empty strings.
        // pathTo is not correctly handling express optional parameters of the form /blogs/:id/:tag?
        // lop off the ? off the end of the result
        return path.replace(/([:*])([\w\-]+)?/g, '').replace(/[?]$/, '');
    };
};

Fix TMN specific site param


'use strict';

var express = require('express');
var passport = require('passport');
var path = require('path');

var routes = require('./routes');

var rootDir = path.dirname(require.main.filename);

module.exports = function (app) {
  var appDir = path.join(rootDir, 'app');
  if (app.get('env') === 'production') {
    appDir = path.join(rootDir, 'build');
  }

  app.get('/auth/github', passport.authenticate('github', {scope: ['gist']}));
  app.get('/auth/github/callback', passport.authenticate('github', {
    successRedirect: '/'
  }));

  app.get('/api/gists/:id', routes.gists.show);

  app.use(express.static(appDir));

  // Catch-all route for HTML5 URLs
  app.get('*', function (req, res) {
    res.sendfile(path.join(appDir, 'index.html'));
  });
};

'use strict';

var express = require('express');
var passport = require('passport');
var path = require('path');

var routes = require('./routes');

var rootDir = path.dirname(require.main.filename);

module.exports = function (app) {
  var appDir = path.join(rootDir, 'app');
  var maxAge = 0;
  if (app.get('env') === 'production') {
    appDir = path.join(rootDir, 'build');
    maxAge = 86400000;
  }

  app.get('/auth/github', passport.authenticate('github', {scope: ['gist']}));
  app.get('/auth/github/callback', passport.authenticate('github', {
    successRedirect: '/'
  }));

  app.get('/api/gists/:id', routes.gists.show);

  app.use(express.static(appDir, {maxAge: maxAge}));

  // Catch-all route for HTML5 URLs
  app.get('*', function (req, res) {
    res.sendfile(path.join(appDir, 'index.html'));
  });
};

Add max age to static requests in production.


var db = require('./db');

module.exports = function (server, cookieParser, sessionStore) {
    var io = require('socket.io').listen(server);
    var SessionSockets = require('session.socket.io');
    var sessionSockets = new SessionSockets(io, sessionStore, cookieParser);

    sessionSockets.on('connection', function (err, socket, session) {
        var found;
        if (err || !session.user) {
            socket.emit('login:unauthorized');
            return;
        }

        socket.emit('login:success', session.user);

        socket.session = session;

        socket.on('start', function () {
            db.users.findOne({login: session.user}, function (err, user) {
                socket.user = user;
                io.sockets.$emit('start', socket);
            });
        });
    });

    return io;
};

var db = require('./db');

module.exports = function (server, cookieParser, sessionStore) {
    var io = require('socket.io').listen(server);
    var SessionSockets = require('session.socket.io');
    var sessionSockets = new SessionSockets(io, sessionStore, cookieParser);

    var env = process.env.NODE_ENV || 'development';

    if ('production' == env) {
        io.set('log level', 1);
    }

    sessionSockets.on('connection', function (err, socket, session) {
        var found;
        if (err || !session.user) {
            socket.emit('login:unauthorized');
            return;
        }

        socket.emit('login:success', session.user);

        socket.session = session;

        socket.on('start', function () {
            db.users.findOne({login: session.user}, function (err, user) {
                socket.user = user;
                io.sockets.$emit('start', socket);
            });
        });
    });

    return io;
};

Reduce log messages in production


/*
 * tuxedo
 * https://github.com/goliatone/tuxedo
 * Created with gbase.
 * Copyright (c) 2014 goliatone
 * Licensed under the MIT license.
 */
/* jshint strict: false, plusplus: true */
/*global define: false, require: false, module: false, exports: false */
var Handlebars = require('handlebars'),
    helpers = require('./helpers'),
    faker = require('faker');

module.exports = {
    parse: function(string, options) {
        options = options || {};

        // Merge the built-in helpers with any that are passed in the options
        var combinedHelpers = {};
        Handlebars.Utils.extend(combinedHelpers, helpers);

        Handlebars.Utils.extend(combinedHelpers, faker.Date);
        Handlebars.Utils.extend(combinedHelpers, faker.Name);
        Handlebars.Utils.extend(combinedHelpers, faker.Image);
        Handlebars.Utils.extend(combinedHelpers, faker.Lorem);
        Handlebars.Utils.extend(combinedHelpers, faker.Address);
        Handlebars.Utils.extend(combinedHelpers, faker.Company);
        Handlebars.Utils.extend(combinedHelpers, faker.Internet);
        Handlebars.Utils.extend(combinedHelpers, faker.PhoneNumber);

        Handlebars.Utils.extend(combinedHelpers, require('handlebars-helper-moment')());
        Handlebars.Utils.extend(combinedHelpers, options.helpers);

        // Reset indexes on each parse
        return Handlebars.compile(string)(options.data, {
            helpers: combinedHelpers
        });
    }
};

/*
 * tuxedo
 * https://github.com/goliatone/tuxedo
 * Created with gbase.
 * Copyright (c) 2014 goliatone
 * Licensed under the MIT license.
 */
/* jshint strict: false, plusplus: true */
/*global define: false, require: false, module: false, exports: false */
var Handlebars = require('handlebars'),
    helpers = require('./helpers'),
    momentHelper = require('handlebars-helper-moment')(),
    faker = require('faker');

// Merge the built-in helpers with any that are passed in the options
var CoreHelpers = {};
Handlebars.Utils.extend(CoreHelpers, helpers);

Handlebars.Utils.extend(CoreHelpers, faker.Date);
Handlebars.Utils.extend(CoreHelpers, faker.Name);
Handlebars.Utils.extend(CoreHelpers, faker.Image);
Handlebars.Utils.extend(CoreHelpers, faker.Lorem);
Handlebars.Utils.extend(CoreHelpers, faker.Address);
Handlebars.Utils.extend(CoreHelpers, faker.Company);
Handlebars.Utils.extend(CoreHelpers, faker.Internet);
Handlebars.Utils.extend(CoreHelpers, faker.PhoneNumber);

Handlebars.Utils.extend(CoreHelpers, momentHelper);

module.exports = {
    parse: function(string, options) {
        options = options || {};

        var mergeHelpers = {};
        Handlebars.Utils.extend(mergeHelpers, CoreHelpers);
        Handlebars.Utils.extend(mergeHelpers, options.helpers);

        // Reset indexes on each parse
        return Handlebars.compile(string)(options.data, {
            helpers: mergeHelpers
        });
    }
};

Move things, clean up stuff...


/**
 * Run test specs in node environment
 * Usage:
 *   $ cd seajs
 *   $ node tests/runner-node.js
 */

require("../lib/sea-node")
var test = require("./test")

var specs = [
  "module/anonymous",
  "module/anywhere",
  "module/cache",
  "module/chain",
  "module/circular",
  "module/define",
  "module/dependencies",
  "module/duplicate-load",
  "module/singleton",
  "module/exports",
  "module/jsonp",
  "module/load-css",
  "module/math",
  "module/method",
  "module/multi-load",
  "module/multi-versions",
  "module/override",
  "module/public-api",
  "module/require-async",
  "module/transitive"
]

// go
test.run(specs)

Add test runner for Node.js


// Define BuoyRequest
var BuoyRequest = (function () {
  var getData = function(buoy_id, callback) {
    Zepto.ajax(
        {
          url: dataFeedUrl(buoy_id),
          type: 'GET',
          dataType: 'json',
          cache: false,
          success: function (result) {
            callback(result);
          }
        }
    );
  }

  var dataFeedUrl = function(buoy_id) {
    return '../../json/' + buoy_id + '.json';
  }

  return {
    getData : getData
  }
})();

// Define BuoyRequest
var BuoyRequest = (function () {
  var getData = function(buoy_id, callback) {
    Zepto.ajax(
        {
          url: dataFeedUrl(buoy_id),
          type: 'GET',
          dataType: 'json',
          cache: false,
          success: function (result) {
            callback(result);
          }
        }
    );
  }

  var dataFeedUrl = function(buoy_id) {
    return 'json/' + buoy_id + '.json';
  }

  return {
    getData : getData
  }
})();

Update json file location for Kiosk.


(function (ns, klass) {

  ns.Response = klass = {}

  // conditions array
  klass.conditions = [];

  klass.init = function() {
    // hookup edit location links
    $("a.edit_location_link").click(function(e){ klass.show_location_picker(e); return false; });

    // initialize conditions
    $.each(klass.conditions, function(i, cond){ cond.init(); });
  }

  // shows the map and location search box
  klass.show_location_picker = function(event) {
    // store existing gps if any
    var location_box = $(event.target).parents("div.control").find("input.qtype_location")[0];
    // create and intialize location picker dialog
    new ELMO.LocationPicker(location_box);
    $('#location-picker-modal').modal('show');

  }

}(ELMO));

(function (ns, klass) {

  ns.Response = klass = {}

  // conditions array
  klass.conditions = [];

  klass.init = function() {
    // hookup edit location links
    $("a.edit_location_link").click(function(e){ klass.show_location_picker(e); return false; });

    // initialize conditions
    $.each(klass.conditions, function(i, cond){ cond.init(); });

    // enable select2 for user selector
    $('#response_user_id').select2();
  }

  // shows the map and location search box
  klass.show_location_picker = function(event) {
    // store existing gps if any
    var location_box = $(event.target).parents("div.control").find("input.qtype_location")[0];
    // create and intialize location picker dialog
    new ELMO.LocationPicker(location_box);
    $('#location-picker-modal').modal('show');

  }

}(ELMO));

Use select2 for Response.user_id field


function patchMouseEvent (e) {
  return {
    deltaX: e.dx || 0,
    deltaY: e.dy || 0,
    deltaZ: 0,
    deltaMode: 0,
    x: e.x,
    y: e.y,
    offsetX: e.x,
    offsetY: e.y,
    altKey: e.option,
    shiftKey: e.shift,
    ctrlKey: e.ctrl,
    metaKey: e.cmd
  }
}

module.exports = patchMouseEvent

function patchMouseEvent (e) {
  return {
    deltaX: e.dx || 0,
    deltaY: e.dy || 0,
    deltaZ: 0,
    deltaMode: 0,
    button: e.buttonNumber || 0,
    buttons: e.buttonNumber || 0,
    x: e.x,
    y: e.y,
    offsetX: e.x,
    offsetY: e.y,
    altKey: e.option,
    shiftKey: e.shift,
    ctrlKey: e.ctrl,
    metaKey: e.cmd
  }
}

module.exports = patchMouseEvent

Add support for mouse buttons


var gulp = require('gulp');

require('require-dir')('./tasks', { recurse: true });

gulp.task('default', gulp.parallel(require('./config').tasks.default.tasks));

// legacy faux aliases

gulp.task('styles', gulp.series('css'));
gulp.task('scripts.lint', gulp.series('scripts.site.lint'));
gulp.task('scripts.lint.full', gulp.series('scripts.site.lint.full'));

Add support for old task names


var { State, Navigation } = require('react-router');
var RouteHandler = require('react-router/modules/mixins/RouteHandler');

// mixin for viewlists
// works with react-router and gives some helper functions
// to manage viewLists

module.exports = {
  mixins: [
    State,
    RouteHandler,
    Navigation,
  ],

  routedViewListProps() {
    return {
      scrollToStep: this.scrollToStep(),
      onViewEntered: this._handleViewEntered
    };
  },

  scrollToStep() {
    return this.numActiveRoutes() - this.getRouteDepth();
  },

  routedSubRoute() {
    return this.hasChildRoute() && (
      this.getRouteHandler(Object.assign(this.props, { key: this.subRouteKey() }))
    );
  },

  // todo: debug why this is called more than it should be
  _handleViewEntered(i) {
    if (i === 0 && this.numActiveRoutes() > this.getRouteDepth())
      setTimeout(this.goBack, 1);
  },

  numActiveRoutes() {
    return this.getRoutes().length;
  },

  hasChildRoute() {
    return this.numActiveRoutes() > this.getRouteDepth();
  },

  subRouteKey() {
    return this.getRoutes().reverse()[0].name + this.getParams().id;
  }
};

var { State, Navigation } = require('react-router');
var RouteHandler = require('react-router/modules/mixins/RouteHandler');

// mixin for viewlists
// works with react-router and gives some helper functions
// to manage viewLists

module.exports = {
  mixins: [
    State,
    RouteHandler,
    Navigation,
  ],

  routedViewListProps() {
    return {
      scrollToStep: this.scrollToStep(),
      onViewEntered: this._handleViewEntered
    };
  },

  scrollToStep() {
    return this.numActiveRoutes() - this.getRouteDepth();
  },

  routedSubRoute() {
    return this.hasChildRoute() && (
      this.getRouteHandler(Object.assign(this.props, { key: this.subRouteKey() }))
    );
  },

  // todo: debug why this is called more than it should be
  _handleViewEntered(i) {
    if (i === 0 && this.numActiveRoutes() > this.getRouteDepth()) {
      var r = this.getRoutes().reverse();
      r.shift();
      this.transitionTo(r[0].path);
    }
  },

  numActiveRoutes() {
    return this.getRoutes().length;
  },

  hasChildRoute() {
    return this.numActiveRoutes() > this.getRouteDepth();
  },

  subRouteKey() {
    return this.getRoutes().reverse()[0].name + this.getParams().id;
  }
};

Make routedViewListMixin smarter, use pop routes rather than goBack


"use strict";

const Client = require("./client")();

const Provider = require("../lib/provider")({
  Client,
});

const Notification = require("../lib/notification");
const token = require("./lib/token");

module.exports = {
  Provider,
  Notification,
  Client,
  token,
};

"use strict";

const Client = require("./client")();

const Provider = require("../lib/provider")({
  Client,
});

const Notification = require("../lib/notification");
const token = require("../lib/token");

module.exports = {
  Provider,
  Notification,
  Client,
  token,
};

Use the correct path for mocks


var getGradient = function () {
    var red1 = Math.floor(Math.random() * 256);
    var green1 = Math.floor(Math.random() * 256);
    var blue1 = Math.floor(Math.random() * 256);

    var red2 = Math.floor(Math.random() * 256);
    var green2 = Math.floor(Math.random() * 256);
    var blue2 = Math.floor(Math.random() * 256);

    var rgb1 = 'rgb(' + red1 + ',' + green1 + ',' + blue1 + ')';
    var rgb2 = 'rgb(' + red2 + ',' + green2 + ',' + blue2 + ')';

    var webkitLinearGradient = '-webkit-linear-gradient(to left,' + rgb1 + ',' + rgb2 + ')';
    var linearGradient = 'linear-gradient(to left,' + rgb1 + ',' + rgb2 + ')';

    $('body').css('background-color', rgb1);
    $('#quote').css('color', rgb1);
};

var getGradient = function () {
    var red1 = Math.floor(Math.random() * 256);
    var green1 = Math.floor(Math.random() * 256);
    var blue1 = Math.floor(Math.random() * 256);

    var red2 = Math.floor(Math.random() * 256);
    var green2 = Math.floor(Math.random() * 256);
    var blue2 = Math.floor(Math.random() * 256);

    var rgb1 = 'rgb(' + red1 + ',' + green1 + ',' + blue1 + ')';
    var rgb2 = 'rgb(' + red2 + ',' + green2 + ',' + blue2 + ')';

    var webkitLinearGradient = '-webkit-linear-gradient(to left,' + rgb1 + ',' + rgb2 + ')';
    var linearGradient = 'linear-gradient(to left,' + rgb1 + ',' + rgb2 + ')';

    $('body').css('background-color', rgb1);
    $('button').css('background-color', rgb1);
    $('#quote').css('color', rgb1);
    $('#author').css('color', rgb1);
};

Add styles for buttons and author


import DS from 'ember-data';

export default DS.Transform.extend({
  deserialize: function(serialized) {
    let map = {
      1: 'Single',
      2: 'Double',
      3: 'Triple',
      4: 'Quadruple',
      5: 'Quintiple',
    };
    return map[serialized];
  },

  serialize: function(deserialized) {
    let map = {
      'Single': 1,
      'Double': 2,
      'Triple': 3,
      'Quadruple': 4,
      'Quintiple': 5,
    };
    return map[deserialized];
  }
});

import DS from 'ember-data';

export default DS.Transform.extend({
  deserialize: function(serialized) {
    let map = {
      1: 'Single',
      2: 'Double',
      3: 'Triple',
      4: 'Quadruple',
      5: 'Quintuple',
    };
    return map[serialized];
  },

  serialize: function(deserialized) {
    let map = {
      'Single': 1,
      'Double': 2,
      'Triple': 3,
      'Quadruple': 4,
      'Quintuple': 5,
    };
    return map[deserialized];
  }
});

Correct spelling of panel option


(function(){

  angular
    .module('app')
    .controller('TableController', [
      'tableService',
      TableController
    ]);

  function TableController(tableService) {
    var vm = this;

    vm.tableData = [];

    tableService
      .loadAllItems()
      .then(function(tableData) {
        vm.tableData = [].concat(tableData);
      });
  }

})();

(function(){

  angular
    .module('app')
    .controller('TableController', [
      'tableService',
      TableController
    ]);

  function TableController(tableService) {
    var vm = this;

    vm.tableData = [];

    tableService
      .loadAllItems()
      .then(function(tableData) {
        vm.tableData = [].concat(tableData);
      });
  }

  function AddColumnsController(tableService) {
    var vm = this;

    vm.tableData = [];

    tableService
      .loadAllItems()
      .then(function(tableData) {
        vm.tableData = [].concat(tableData);
      });
  }

  function AddColumnsController(tableService) {
    var vm = this;

    vm.tableData = [];

    tableService
      .loadAllItems()
      .then(function(tableData) {
        vm.tableData = [].concat(tableData);
      });
  }

  function AddColumnsController(tableService) {
    var vm = this;

    vm.tableData = [];

    tableService
      .loadAllItems()
      .then(function(tableData) {
        vm.tableData = [].concat(tableData);
      });
  }

  function AddColumnsController(tableService) {
    var vm = this;

    vm.tableData = [];

    tableService
      .loadAllItems()
      .then(function(tableData) {
        vm.tableData = [].concat(tableData);
      });
  }

  function AddColumnsController(tableService) {
    var vm = this;

    vm.tableData = [];

    tableService
      .loadAllItems()
      .then(function(tableData) {
        vm.tableData = [].concat(tableData);
      });
  }

})();

Add columns controller and funcions for column manager.


var helper = require('specHelper');

describe("Nodyn globals", function() {
  it('should pass testGlobal', function() {
    expect(typeof global).toBe('object');
  });

  it('should pass testProcess', function() {
    expect(typeof global.process).toBe('object');
    expect(typeof process).toBe('object');
  });

  it('should pass testConsole', function() {
    expect(typeof global.console).toBe('object');
    expect(typeof console).toBe('object');
  });

  it('should pass testRequire', function() {
    expect(typeof global.require).toBe('function');
    expect(typeof require).toBe('function');
    expect(typeof require.resolve).toBe('function');
    expect(typeof require.cache).toBe('object');
    expect(typeof require.extensions).toBe('object');
  });

  it('should pass test__filename', function() {
    expect(typeof __filename).toBe('string');
    expect(__filename).toMatch(/.*globalSpec.js/);
  });

  it('should pass test__dirname', function() {
    expect(typeof __dirname).toBe('string');
  });
});

var helper = require('specHelper');

describe("Nodyn globals", function() {
  it('should pass testGlobal', function() {
    expect(typeof global).toBe('object');
  });

  it('should pass testProcess', function() {
    expect(typeof global.process).toBe('object');
    expect(typeof process).toBe('object');
  });

  it('should pass testConsole', function() {
    expect(typeof global.console).toBe('object');
    expect(typeof console).toBe('object');
  });

  it('should pass testRequire', function() {
    expect(typeof global.require).toBe('function');
    expect(typeof require).toBe('function');
    // TODO these should pass
    // expect(typeof require.resolve).toBe('function');
    // expect(typeof require.cache).toBe('object');
    // expect(typeof require.extensions).toBe('object');
  });

  it('should pass test__filename', function() {
    expect(typeof __filename).toBe('string');
    expect(__filename).toMatch(/.*globalSpec.js/);
  });

  it('should pass test__dirname', function() {
    expect(typeof __dirname).toBe('string');
  });
});

Remove require tests that should be passing. wtf


(function (window) {
	"use strict";

	var document = window.document,
		console = window.console,
		$ = window.jQuery,
		grace = window.grace;

	$.fn.graceCollapse = function (options) {

		var defaults = {
			switchBtnClass:'.tab-btn',
			switchTabClass:'.tab-content',
			animationDuration: 500
		};

		options = $.extend({}, defaults, options);

		function initEach() {
			var $graceCollapse = $(this);

			$graceCollapse.find(options.switchBtnClass).each(function(){
				var tabContent = $(this).siblings(options.switchTabClass);
				var orgHeight;
				if(!tabContent.is(":visible")){
					tabContent.show();
					orgHeight = tabContent.height();
					tabContent.hide();
				}else{
					orgHeight = tabContent.height();
				}
				$(this).click(function(){
					var curHeight = $(this).siblings(options.switchTabClass).height();
					if(curHeight <= 0 || tabContent.is(":hidden")){
						tabContent.css({display:"block", height:0,opacity:0}).animate({'height': orgHeight,'opacity': 1},options.animationDuration);
					}else{
						tabContent.animate({'height':0,'opacity': 0},options.animationDuration);
					}
				});
			});
		}

		return this.each(initEach);
	};

})(window);

(function (window) {
	"use strict";

	var document = window.document,
		console = window.console,
		$ = window.jQuery,
		grace = window.grace;

	$.fn.graceCollapse = function (options) {

		var defaults = {
			switchBtnClass:'.tab-btn',
			switchBtnToggleClass:'tab-btn-closed',
			switchTabClass:'.tab-content',
			animationDuration: 500
		};

		options = $.extend({}, defaults, options);

		function initEach() {
			var $graceCollapse = $(this);

			$graceCollapse.find(options.switchBtnClass).each(function(){
				var tabContent = $(this).siblings(options.switchTabClass);
				var orgHeight;
				if(!tabContent.is(":visible")){
					tabContent.show();
					orgHeight = tabContent.height();
					tabContent.hide();
				}else{
					orgHeight = tabContent.height();
				}
				$(this).click(function(){
					var curHeight = $(this).siblings(options.switchTabClass).height();
					if(curHeight <= 0 || tabContent.is(":hidden")){
						tabContent.css({display:"block", height:0,opacity:0}).animate({'height': orgHeight,'opacity': 1},options.animationDuration);
						$(this).removeClass(options.switchBtnToggleClass);
					}else{
						tabContent.animate({'height':0,'opacity': 0},options.animationDuration);
						$(this).addClass(options.switchBtnToggleClass);
					}
				});
			});
		}

		return this.each(initEach);
	};

})(window);

Add Collapse Button ToggleClass Function!


module.exports = {
    trailingComma: 'es5',
    tabWidth: 4,
    semi: true,
    singleQuote: true,
    useTabs: false,
    printWidth: 120,
    bracketSpacing: false,
    arrowParens: 'always',
    endOfLine: 'lf',
    braceStyle: '1tbs',
};

module.exports = {
    trailingComma: 'es5',
    tabWidth: 4,
    semi: true,
    singleQuote: true,
    useTabs: false,
    printWidth: 120,
    bracketSpacing: false,
    arrowParens: 'always',
    endOfLine: 'lf',
    braceStyle: 'psr-2',
};

Set bracestyle to recommended psr-2


/*
 * STORY-BASED SNAPSHOT HELPER FUNCTIONS
 *
 * For more information, please visit:
 * https://github.com/storybooks/storybook/tree/master/addons/storyshots
 */

const getExcludeKindRegex = ({ arrayOfStoryKind = [] } = {}) => {
	const storyKindString = arrayOfStoryKind.join('|');
	return new RegExp(`^(?!(${storyKindString})$)[a-z A-Z0-9s]+$`, 'g');
};

// If a Storybook story should not be tested by Storyshots, please add
// the suffix `NoTest` to the story's name.
const skipStoryShotTest = 'NoTest';

// eslint-disable-next-line import/prefer-default-export
export { getExcludeKindRegex, skipStoryShotTest };

/*
 * STORY-BASED SNAPSHOT HELPER FUNCTIONS
 *
 * For more information, please visit:
 * https://github.com/storybooks/storybook/tree/master/addons/storyshots
 */

const getExcludeKindRegex = ({ arrayOfStoryKind = [] } = {}) => {
	const storyKindString = arrayOfStoryKind.join('|');
	return new RegExp(`^(?!(${storyKindString})$)[a-z A-Z0-9s]+$`, 'g');
};

// Use with `./exclude-story-config` to create a REGEX config that adds stories instead of removing them
const getIncludeKindRegex = ({ arrayOfStoryKind = [] } = {}) => {
	const storyKindString = arrayOfStoryKind.join('|');
	return new RegExp(`^(${storyKindString})$`, 'g');
};

// If a Storybook story should not be tested by Storyshots, please add
// the suffix `NoTest` to the story's name.
const skipStoryShotTest = 'NoTest';

// eslint-disable-next-line import/prefer-default-export
export { getExcludeKindRegex, getIncludeKindRegex, skipStoryShotTest };

Add story kind include helper


/* Copyright 2015 Bloomberg Finance L.P.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var bqplot = require('bqplot');

var jupyterlab_widgets = require('@jupyterlab/nbwidgets');

/**
 * The widget manager provider.
 */
module.exports.default = {
  id: 'jupyter.extensions.bqplot',
  requires: [jupyterlab_widgets.INBWidgetExtension],
  activate: function(app, widgets) {
      widgets.registerWidget({
          name: 'bqplot',
          version: bqplot.version,
          exports: bqplot
      });
    },
  autoStart: true
};

/* Copyright 2015 Bloomberg Finance L.P.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var bqplot = require('bqplot');
require("bqplot/css/bqplot.css");

var jupyterlab_widgets = require('@jupyterlab/nbwidgets');

/**
 * The widget manager provider.
 */
module.exports.default = {
  id: 'jupyter.extensions.bqplot',
  requires: [jupyterlab_widgets.INBWidgetExtension],
  activate: function(app, widgets) {
      widgets.registerWidget({
          name: 'bqplot',
          version: bqplot.version,
          exports: bqplot
      });
    },
  autoStart: true
};

Add back css for JLab plugin.


'use strict';

function togglePrivacyQuestionsAnsweredStatusField(){
    let privacyQuestionsStatusContainer = $('.privacy-questions-container');
    let privacyQuestionsEnabledCheckbox = $(".privacy-questions-toggle");
    if (privacyQuestionsEnabledCheckbox.is(':checked')) {
        privacyQuestionsStatusContainer.find('input[type="radio"]').prop('disabled', false);
    } else {
        privacyQuestionsStatusContainer.find('input[type="radio"]').prop('disabled', true);
    }
}

$(document).ready(function(){
    $(".privacy-questions-toggle").on('change', togglePrivacyQuestionsAnsweredStatusField);
    togglePrivacyQuestionsAnsweredStatusField();
});

'use strict';

function togglePrivacyQuestionsAnsweredStatusField(){
    let privacyQuestionsStatusContainer = $('.privacy-questions-container');
    let privacyQuestionsEnabledCheckbox = $(".privacy-questions-toggle");
    if (privacyQuestionsEnabledCheckbox.is(':checked')) {
        privacyQuestionsStatusContainer.parent().show(200);
    } else {
        privacyQuestionsStatusContainer.parent().hide();
    }
}

$(document).ready(function(){
    $(".privacy-questions-toggle").on('change', togglePrivacyQuestionsAnsweredStatusField);
    togglePrivacyQuestionsAnsweredStatusField();
});

Change privacy question status behaviour


var fs = require('fs');
var http = require('http-stack');
var assert = require('assert');
var icecast = require('icecast-stack');

exports['test 4EverFloyd-1.dump'] = function() {
  var stream = fs.createReadStream(__dirname + "/dumps/4EverFloyd-1.dump");
  var req = new http.HttpRequestStack(stream);
  req.on('response', onResponse);
  function onResponse(res) {
    assert.ok(res.headers);
    assert.equal(res.headers['icy-metaint'], 8192);

    var ice = new icecast.IcecastReadStack(req, res.headers['icy-metaint']);
    ice.on('metadata', onMetadata);
    ice.on('end', onEnd);
  }
  var metadataCount = 0;
  function onMetadata(title) {
    metadataCount++;
    var parsed = icecast.parseMetadata(title);
    assert.equal(parsed.StreamTitle, 'Shine on You Crazy Diamond, Pts. 1-5 - Shine on You Crazy Diamond, Pts. 1-5');
    assert.equal(parsed.StreamUrl, 'http:\/\/www.4EverFloyd.com');
  }
  function onEnd() {
    assert.equal(metadataCount, 1);
  }
}

var fs = require('fs');
var http = require('http-stack');
var assert = require('assert');
var icecast = require('../');

exports['test 4EverFloyd-1.dump'] = function() {
  var stream = fs.createReadStream(__dirname + "/dumps/4EverFloyd-1.dump");
  var req = new http.HttpRequestStack(stream);
  req.on('response', onResponse);
  function onResponse(res) {
    assert.ok(res.headers);
    assert.equal(res.headers['icy-metaint'], 8192);

    var ice = new icecast.IcecastReadStack(req, res.headers['icy-metaint']);
    ice.on('metadata', onMetadata);
    ice.on('end', onEnd);
  }
  var metadataCount = 0;
  function onMetadata(title) {
    metadataCount++;
    var parsed = icecast.parseMetadata(title);
    assert.equal(parsed.StreamTitle, 'Shine on You Crazy Diamond, Pts. 1-5 - Shine on You Crazy Diamond, Pts. 1-5');
    assert.equal(parsed.StreamUrl, 'http:\/\/www.4EverFloyd.com');
  }
  function onEnd() {
    assert.equal(metadataCount, 1);
  }
}

Fix the test case for npm 1.0


var semver = require('semver');
var createError = require('./createError');

function decompose(endpoint) {
    var regExp = /^(?:([\w\-]|(?:[\w\.\-]+[\w\-])?)=)?([^\|#]+)(?:#(.*))?$/;
    var matches = endpoint.match(regExp);
    var target;

    if (!matches) {
        throw createError('Invalid endpoint: ' + endpoint, 'EINVEND');
    }

    target = matches[3];

    return {
        name: matches[1] || '',
        source: matches[2],
        target: !target || target === 'latest' ? '*' : target
    };
}

function compose(decEndpoint) {
    var composed = '';

    if (decEndpoint.name) {
        composed += decEndpoint.name + '=';
    }

    composed += decEndpoint.source;

    if (decEndpoint.target) {
        composed += '#' + decEndpoint.target;
    }

    return composed;
}

function json2decomposed(key, value) {
    var endpoint = key + '=';

    if (value === 'latest' || semver.valid(value) != null || semver.validRange(value) != null) {
        endpoint += key + '#' + value;
    } else {
        endpoint += value;
    }

    return decompose(endpoint);
}

module.exports.decompose = decompose;
module.exports.compose = compose;
module.exports.json2decomposed = json2decomposed;

var createError = require('./createError');

function decompose(endpoint) {
    var regExp = /^(?:([\w\-]|(?:[\w\.\-]+[\w\-])?)=)?([^\|#]+)(?:#(.*))?$/;
    var matches = endpoint.match(regExp);
    var target;

    if (!matches) {
        throw createError('Invalid endpoint: ' + endpoint, 'EINVEND');
    }

    target = matches[3];

    return {
        name: matches[1] || '',
        source: matches[2],
        target: !target || target === 'latest' ? '*' : target
    };
}

function compose(decEndpoint) {
    var composed = '';

    if (decEndpoint.name) {
        composed += decEndpoint.name + '=';
    }

    composed += decEndpoint.source;

    if (decEndpoint.target) {
        composed += '#' + decEndpoint.target;
    }

    return composed;
}

function json2decomposed(key, value) {
    var endpoint = key + '=';
    var split = value.split('#');

    // If # was found, the source was specified
    if (split.length > 1) {
        endpoint += split[0] + '#' + split[1];
    // Otherwise use the key as the source
    } else {
        endpoint += key + '#' + split[0];
    }

    return decompose(endpoint);
}

module.exports.decompose = decompose;
module.exports.compose = compose;
module.exports.json2decomposed = json2decomposed;

Fix json to decomposed endpoint transformation.