var gulp = require('gulp');

var defs = [
  {
    title: 'Test Index Title',
    path: '',
    description: 'Test index description',
    twitterImage: '20euro.png',
    openGraphImage: '50euro.png',
    schemaImage: '100euro.png'
  },
  {
    path: '/subpage',
    title: 'Test Subpage Title',
    description: 'Test subpage description',
    twitterImage: '100euro.png',
    openGraphImage: '50euro.png',
    schemaImage: '20euro.png'
  }
];

var opts = {
  publishFromFolder: 'dist',
  assetContext: 'test-path/',
  pageDefs: defs,
  embedCodes: false,
  iframeResize: false,
  entryPoint: path.resolve('src/js/entry-point.jsx'),
}

var builder = require('../../index.js'); // lucify-component-builder
builder(gulp, opts);

var gulp = require('gulp');
var path = require('path');

var defs = [
  {
    title: 'Test Index Title',
    path: '',
    description: 'Test index description',
    twitterImage: '20euro.png',
    openGraphImage: '50euro.png',
    schemaImage: '100euro.png'
  },
  {
    path: '/subpage',
    title: 'Test Subpage Title',
    description: 'Test subpage description',
    twitterImage: '100euro.png',
    openGraphImage: '50euro.png',
    schemaImage: '20euro.png'
  }
];

var opts = {
  publishFromFolder: 'dist',
  assetContext: 'test-path/',
  pageDefs: defs,
  embedCodes: false,
  iframeResize: false,
  entryPoint: path.resolve('src/js/entry-point.jsx'),
}

var builder = require('../../index.js'); // lucify-component-builder
builder(gulp, opts);

Add missing require to test project


function yql_lookup(query, cb_function) {
    var url = 'http://query.yahooapis.com/v1/public/yql?q=' + encodeURIComponent(query) + '&format=json&diagnostics=true';

    //alert(url);

    $.getJSON(url, cb_function);
}

function look_up_news() {

    var feed_url = 'http://mediacloud.org/blog/feed/';

    //alert(google_url);
    yql_lookup("select * from rss where url = '" + feed_url + "'", function (response) {

        var results = response.query.results;

	var news_items = $('#news_items');

        //console.log(results);
        news_items.children().remove();
        news_items.html('');

        $.each(results.item, function (index, element) {
            var title = element.title;
            var link = element.link;

            news_items.append($('<a/>', {
                'href': link
            }).text(title)).append('<br/>');
        });

    });
}

function yql_lookup(query, cb_function) {
    var url = 'http://query.yahooapis.com/v1/public/yql?q=' + encodeURIComponent(query) + '&format=json&diagnostics=true';

    //alert(url);

    $.getJSON(url, cb_function);
}

function look_up_news() {

    // TEMPORARY HACK
    //mediacloud.org is password protected so we can't pull from it directly
    // instead we pull from 'https://blogs.law.harvard.edu/mediacloud2/feed/' and dynamically rewrite the URLs to point to mediacloud.org/blog;
    var feed_url = 'https://blogs.law.harvard.edu/mediacloud2/feed/';

    //alert(google_url);
    yql_lookup("select * from rss where url = '" + feed_url + "'", function (response) {

        var results = response.query.results;

	var news_items = $('#news_items');

        //console.log(results);
        news_items.children().remove();
        news_items.html('');

        $.each(results.item, function (index, element) {
            var title = element.title;
            var link = element.link;

	    link = link.replace('https://blogs.law.harvard.edu/mediacloud2/', 'http://www.mediacloud.org/blog/');
            news_items.append($('<a/>', {
                'href': link
            }).text(title)).append('<br/>');
        });

    });
}

Add work around for the mediacloud.org/blog page being password protected so we can still display the news dynamically on the front page.


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

class SubmitButton extends Component {
  render() {
    const {
      isShowingPositive,
      onNegativeClick,
      onPositiveClick,
      disabled,
      positiveText,
      negativeText
    } = this.props;

    return (
      <StyledSubmitButton
        disabled={disabled}
        type="button"
        value={isShowingPositive ? positiveText : negativeText}
        onClick={isShowingPositive ? onPositiveClick : onNegativeClick}
      />
    );
  }
}

export const StyledSubmitButton = styled.input`
  background-color: #00a8e2;
  transition: background-color 0.25s ease-out, color 0.25s ease-out;
  color: #fff;
  border: none;
  outline: none;
  padding: 15px 45px;
  font-size: 1.1em;
  :hover {
    cursor: pointer;
    background-color: #0090c2;
  }
  :active {
    background-color: #006b8f;
  }
  :disabled {
    background-color: #c4c4c4;
  }
`;

export default SubmitButton;

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

class SubmitButton extends Component {
  render() {
    const {
      isShowingPositive,
      onNegativeClick,
      onPositiveClick,
      disabled,
      positiveText,
      negativeText
    } = this.props;

    return (
      <StyledSubmitButton
        disabled={disabled}
        type="button"
        value={isShowingPositive ? positiveText : negativeText}
        onClick={isShowingPositive ? onPositiveClick : onNegativeClick}
      />
    );
  }
}

export const StyledSubmitButton = styled.input`
  -webkit-appearance: none;
  background-color: #00a8e2;
  transition: background-color 0.25s ease-out, color 0.25s ease-out;
  color: #fff;
  border: none;
  outline: none;
  padding: 15px 45px;
  font-size: 1.1em;
  :hover {
    cursor: pointer;
    background-color: #0090c2;
  }
  :active {
    background-color: #006b8f;
  }
  :disabled {
    background-color: #c4c4c4;
  }
`;

export default SubmitButton;

Set webkit-appearance to none on submit button.


// Copyright 2012 Joyent, Inc.  All rights reserved.

var util = require('util');

var ldap = require('ldapjs');

var Validator = require('../lib/schema/validator');

///--- Globals

var ConstraintViolationError = ldap.ConstraintViolationError;

///--- API

function GroupOfUniqueNames() {
    Validator.call(this, {
        name: 'groupofuniquenames',
        required: {
            uniquemember: 1000000
        },
        optional: {
            description: 1
        },
        strict: true
    });
}
util.inherits(GroupOfUniqueNames, Validator);

GroupOfUniqueNames.prototype.validate = function validate(entry, callback) {
    var members = entry.attributes.uniquemember;

    members.sort();
    for (var i = 0; i < members.length; i++) {
        if (members.indexOf(members[i], i + 1) !== -1) {
            return callback(new ConstraintViolationError(members[i] +
                                                         ' is not unique'));
        }
    }

    return callback();
};

///--- Exports

module.exports = {
    createInstance: function createInstance() {
        return new GroupOfUniqueNames();
    }
};

// Copyright 2013 Joyent, Inc.  All rights reserved.

var util = require('util');

var ldap = require('ldapjs');

var Validator = require('../lib/schema/validator');

///--- Globals

var ConstraintViolationError = ldap.ConstraintViolationError;

///--- API

function GroupOfUniqueNames() {
    Validator.call(this, {
        name: 'groupofuniquenames',
        optional: {
            uniquemember: 1000000,
            description: 1
        },
        strict: true
    });
}
util.inherits(GroupOfUniqueNames, Validator);

GroupOfUniqueNames.prototype.validate = function validate(entry, callback) {
    var members = entry.attributes.uniquemember || [];

    members.sort();
    for (var i = 0; i < members.length; i++) {
        if (members.indexOf(members[i], i + 1) !== -1) {
            return callback(new ConstraintViolationError(members[i] +
                                                         ' is not unique'));
        }
    }

    return callback();
};

///--- Exports

module.exports = {
    createInstance: function createInstance() {
        return new GroupOfUniqueNames();
    }
};

Allow creation of empty user groups


App.ready(function() {
  "use strict";
  var body = $('body');
  var links = $('a:not([data-method="delete"]):not(.has_many_add):not(.dropdown_menu_button):not([target="_blank"])');

  // Add active class on click to style while loading via turbolinks
  // and add loading class to the body element.
  links.click(function() {
    body.addClass('loading');
    $(this).addClass('active');
  });

  // Remove loading and active classes on page load
  body.removeClass('loading');
  links.removeClass('active');
});

App.ready(function() {
  "use strict";
  var body = $('body');
  var links = $('a:not([data-method="delete"]):not(.has_many_add):not(.dropdown_menu_button):not([target="_blank"])');

  // Add active class on click to style while loading via turbolinks
  // and add loading class to the body element.
  links.click(function() {
    body.addClass('loading');
    $(this).addClass('active');
  });

  // Remove loading and active classes on page load
  body.removeClass('loading');
  links.removeClass('active');

  // We also need to make sure to remove the loading classes when the
  // page is restored by Turbolinks when using the back button
  document.addEventListener('page:restore', function() {
    body.removeClass('loading');
    links.removeClass('active');
  });
});

Fix issue with back button and loading indicators


'use strict';

const { hears } = require('telegraf');
const R = require('ramda');

// DB
const { getCommand } = require('../../stores/command');

const capitalize = R.replace(/^./, R.toUpper);

const getRepliedToId = R.path([ 'reply_to_message', 'message_id' ]);

const typeToMethod = type =>
	type === 'text'
		? 'replyWithHTML'
		: `replyWith${capitalize(type)}`;

const runCustomCmdHandler = async (ctx, next) => {
	const { message, state } = ctx;
	const { isAdmin, isMaster } = state;

	const commandName = ctx.match[1].toLowerCase();
	const command = await getCommand({ isActive: true, name: commandName });

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

	const { caption, content, type } = command;
	const role = command.role.toLowerCase();
	if (
		role === 'master' &&
		!isMaster ||
		role === 'admins' &&
		!isAdmin
	) {
		return next();
	}

	const reply_to_message_id = getRepliedToId(message);
	const options = {
		caption,
		disable_web_page_preview: true,
		reply_to_message_id,
	};

	return ctx[typeToMethod(type)](content, options);
};

module.exports = hears(/^!(\w+)/, runCustomCmdHandler);

'use strict';

const { hears } = require('telegraf');
const R = require('ramda');

// DB
const { getCommand } = require('../../stores/command');

const capitalize = R.replace(/^./, R.toUpper);

const getRepliedToId = R.path([ 'reply_to_message', 'message_id' ]);

const typeToMethod = type =>
	type === 'text'
		? 'replyWithHTML'
		: `replyWith${capitalize(type)}`;

const runCustomCmdHandler = async (ctx, next) => {
	const { message, state } = ctx;
	const { isAdmin, isMaster } = state;

	const commandName = ctx.match[1].toLowerCase();
	const command = await getCommand({ isActive: true, name: commandName });

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

	const { caption, content, type } = command;
	const role = command.role.toLowerCase();
	if (
		role === 'master' &&
		!isMaster ||
		role === 'admins' &&
		!isAdmin
	) {
		return next();
	}

	const reply_to_message_id = getRepliedToId(message);
	const options = {
		caption,
		disable_web_page_preview: true,
		reply_to_message_id,
	};

	return ctx[typeToMethod(type)](content, options);
};

module.exports = hears(/^! ?(\w+)/, runCustomCmdHandler);

Allow space before custom command name


var timeDisplay = document.querySelector('#timeDisplay');
var startButton = document.querySelector('#startButton');
var abandonButton = document.querySelector('#abandonButton');
var completeButton = document.querySelector('#completeButton');
var restartButton = document.querySelector('#restartButton');

var buttons = document.querySelectorAll('button');
for (var i = 0; i < buttons.length; i++) {
  var button = buttons[i];
  button.onclick = function () {
    handleInput(this.id);
  };
}

var sessionLength = 25 * 60 * 1000;
var currentTime;
var timeRemaining;
var timer;

function handleInput (id) {
  var buttons = {
    startButton: start,
    completeButton: complete,
    abandonButton: abandon,
    restartButton: restart
  };

  buttons[id]();
}

function countdown () {
  var oldTime = currentTime;
  currentTime = Date.now();
  timeRemaining -= currentTime - oldTime;
  if (timeRemaining <= 0) {
    timeRemaining = 0;
    end();
  } else {
    updateDOM();
  }
}

function end () {
  stopTimer();
  showElement(restartButton);
}

function stopTimer () {
  window.clearInterval(timer);
}

function start () {
  hideElement(startButton);
  showElement(abandonButton);
  showElement(completeButton);
  startTimer();
}

function startTimer () {
  currentTime = Date.now();
  timeRemaining = sessionLength + 900;
  updateDOM();
  timer = window.setInterval(countdown, 10);
}

function complete () {
  stopTimer();
  // sendCompleteRequest();
}

function abandon () {
  stopTimer();
  // sendAbandonRequest();
}

function restart () {
  hideElement(restartButton);
  startTimer();
}

function updateDOM () {
  timeDisplay.innerText = formatTime(timeRemaining);
}

function formatTime (time) {
  var seconds = Math.floor((time / 1000) % 60).toString();
  var minutes = Math.floor((time / (60 * 1000)) % 60).toString();
  if (seconds.length < 2) seconds = '0' + seconds;
  if (minutes.length < 2) minutes = '0' + minutes;
  return '' + minutes + ':' + seconds;
}

function hideElement (elem) {
  elem.style.display = 'none';
}

function showElement (elem) {
  elem.style.display = 'block';
}

Add basic code for timer with commented lines where requests will go


import Notification from 'flarum/components/Notification';
import username from 'flarum/helpers/username';

export default class NewPostNotification extends Notification {
  icon() {
    return 'star';
  }

  href() {
    const notification = this.props.notification;
    const discussion = notification.subject();
    const content = notification.content() || {};

    return app.route.discussion(discussion, content.postNumber);
  }

  content() {
    return app.translator.trans('flarum-subscriptions.forum.notifications:new_post_text', {user: this.props.notification.sender()});
  }
}

import Notification from 'flarum/components/Notification';
import username from 'flarum/helpers/username';

export default class NewPostNotification extends Notification {
  icon() {
    return 'star';
  }

  href() {
    const notification = this.props.notification;
    const discussion = notification.subject();
    const content = notification.content() || {};

    return app.route.discussion(discussion, content.postNumber);
  }

  content() {
    return app.translator.trans('flarum-subscriptions.forum.notifications.new_post_text', {user: this.props.notification.sender()});
  }
}

Fix typo in new post notification translation key


import Ember from 'ember';

const { RSVP } = Ember;

function preloadRecord(record, toPreload) {
  return preloadAll([record], toPreload).then(() => {
    return record;
  });
}

function preloadAll(records, toPreload) {
    switch(Ember.typeOf(toPreload)) {
      case 'object':
        const properties = Object.keys(toPreload);
        return RSVP.all(properties.map((p) => {
          return RSVP.all(records.map((record) => {
            return record.get(p);
          })).then((data) => {
            const subRecords = data.reduce((prev, cur) => prev.concat(cur.toArray()), []);
            return preloadAll(subRecords, toPreload[p]);
          });
        })).then(() => records);
      case 'string':
        return RSVP.all(records.map((record) => record.get(toPreload)))
          .then(() => records);
      default: throw 'Illegal Argument';
    }
}

function preload(thing, toPreload) {
  if (thing.then) {
    return thing.then(() => {
      return Ember.isArray(thing) ? preloadAll(thing, toPreload) : preloadRecord(thing, toPreload);
    });
  }
  else {
    return Ember.isArray(thing) ? preloadAll(thing, toPreload) : preloadRecord(thing, toPreload);
  }

}

export default preload;

import Ember from 'ember';

const { RSVP } = Ember;

function getPromise(object, property) {
  return RSVP.resolve(Ember.get(object, property));
}

function preloadRecord(record, toPreload) {
  if (!record) {
    return RSVP.resolve(record);
  }

  switch(Ember.typeOf(toPreload)) {
    case 'string':
      return getPromise(record, toPreload).then(() => record);
    case 'array':
      return RSVP.all(toPreload.map((p) => preloadRecord(record, p))).then(() => record);
    case 'object':
      return RSVP.all(Object.keys(toPreload).map((p) =>
        getPromise(record, p).then((data) => preload(data, toPreload[p])))).then(() => record);
    default: throw 'Illegal Argument';
  }
}

function preloadAll(records, toPreload) {
  return RSVP.all(records.map((record) => preload(record, toPreload)));
}

function preload(thing, toPreload) {
  return RSVP.resolve(thing).then(() => {
    return Ember.isArray(thing) ? preloadAll(thing, toPreload) : preloadRecord(thing, toPreload);
  });
}

export default preload;

Refactor preload to handle more cases


cinema.models.LayerModel = Backbone.Model.extend({

    constructor: function (defaults, options) {
        Backbone.Model.call(this, {}, options);
        if (typeof defaults === 'string') {
            this.setFromString(defaults);
        } else if (defaults) {
            this.set('state', defaults);
        }
    },

    /**
     * Convert an object that maps layer identifiers to color-by values into
     * a single string that is consumable by other parts of the application.
     */
    serialize: function () {
        var query = '';

        _.each(this.attributes, function (v, k) {
            query += k + v;
        });

        return query;
    },

    /**
     * Convert a query string to an object that maps layer identifiers to
     * their color-by value. The query string is a sequence of two-character
     * pairs, where the first character identifies the layer ID and the second
     * character identifies which field it should be colored by.
     *
     * The query string is then saved to the model.
     */
    unserialize: function (query) {
        var obj = {};

        if (query.length % 2) {
            return console.error('Query string "' + query + '" has odd length.');
        }

        for (var i = 0; i < query.length; i += 2) {
            obj[query[i]] = query[i + 1];
        }

        return obj;

    },

    /**
     * Set the layers by a "query string".
     */
    setFromString: function (query) {
        var obj = this.unserialize(query);

        this.set('state', obj);
    }
});

cinema.models.LayerModel = Backbone.Model.extend({

    constructor: function (defaults, options) {
        Backbone.Model.call(this, {}, options);
        if (typeof defaults === 'string') {
            this.setFromString(defaults);
        } else if (defaults) {
            this.set('state', defaults);
        }
    },

    /**
     * Convert an object that maps layer identifiers to color-by values into
     * a single string that is consumable by other parts of the application.
     */
    serialize: function () {
        var query = '';

        _.each(this.get('state'), function (v, k) {
            query += k + v;
        });

        return query;
    },

    /**
     * Convert a query string to an object that maps layer identifiers to
     * their color-by value. The query string is a sequence of two-character
     * pairs, where the first character identifies the layer ID and the second
     * character identifies which field it should be colored by.
     *
     * The query string is then saved to the model.
     */
    unserialize: function (query) {
        var obj = {};

        if (query.length % 2) {
            return console.error('Query string "' + query + '" has odd length.');
        }

        for (var i = 0; i < query.length; i += 2) {
            obj[query[i]] = query[i + 1];
        }

        return obj;

    },

    /**
     * Set the layers by a "query string".
     */
    setFromString: function (query) {
        var obj = this.unserialize(query);

        this.set('state', obj);
    }
});

Fix layer model serialize method to use new organization


const DrawCard = require('../../../drawcard.js');

class PutToTheTorch extends DrawCard {
    canPlay(player, card) {
        if(player !== this.controller || this !== card) {
            return false;
        }

        var currentChallenge = this.game.currentChallenge;

        if(!currentChallenge || currentChallenge.winner !== this.controller || currentChallenge.attacker !== this.controller || currentChallenge.strengthDifference < 5 ||
                currentChallenge.challengeType !== 'military') {
            return false;
        }

        return true;
    }

    play(player) {
        if(this.controller !== player) {
            return;
        }

        this.game.promptForSelect(player, {
            activePromptTitle: 'Select a location to discard',
            waitingPromptTitle: 'Waiting for opponent to use ' + this.name,
            cardCondition: card => card.inPlay && card.controller !== player && card.getType() === 'location',
            onSelect: (p, card) => this.onCardSelected(p, card)
        });
    }

    onCardSelected(player, card) {
        card.controller.moveCard(card, 'discard pile');

        this.game.addMessage('{0} uses {1} to discard {2}', player, this, card);

        return true;
    }
}

PutToTheTorch.code = '01042';

module.exports = PutToTheTorch;

const DrawCard = require('../../../drawcard.js');

class PutToTheTorch extends DrawCard {
    canPlay(player, card) {
        if(player !== this.controller || this !== card) {
            return false;
        }

        var currentChallenge = this.game.currentChallenge;

        if(!currentChallenge || currentChallenge.winner !== this.controller || currentChallenge.attackingPlayer !== this.controller || currentChallenge.strengthDifference < 5 ||
                currentChallenge.challengeType !== 'military') {
            return false;
        }

        return true;
    }

    play(player) {
        if(this.controller !== player) {
            return;
        }

        this.game.promptForSelect(player, {
            activePromptTitle: 'Select a location to discard',
            waitingPromptTitle: 'Waiting for opponent to use ' + this.name,
            cardCondition: card => card.inPlay && card.controller !== player && card.getType() === 'location',
            onSelect: (p, card) => this.onCardSelected(p, card)
        });
    }

    onCardSelected(player, card) {
        card.controller.discardCard(card);

        this.game.addMessage('{0} uses {1} to discard {2}', player, this, card);

        return true;
    }
}

PutToTheTorch.code = '01042';

module.exports = PutToTheTorch;

Fix put to the torch and allow it to use saves


var readFile = require('fs').readFile;
var assert = require('assert');
var exec = require('child_process').exec;
var join = require('path').join;

// Test the expected output.
exec('node .', function(err, stdout, stderr) {
    if (err) {
        throw err;
    }

    readFile(join(__dirname, 'expected.txt'), 'UTF-8', function(err, exp) {
        if (err) {
            throw err;
        }

        var expected = exp.split('\r\n');
        var actual = stdout.split('\n');

        for (var i = 0, mx = expected.length; i < mx; i++) {
            assert.equal(actual[i], expected[i],
                "Expected '" + actual[i] + "' to be '" + expected[i] + "' on line " + (i + 1) + ".");
        }

        console.log('All tests pass!');
    });
});

var readFile = require('fs').readFile;
var assert = require('assert');
var exec = require('child_process').exec;
var join = require('path').join;
var platform = require('os').platform;

// Test the expected output.
exec('node .', function(err, stdout, stderr) {
    if (err) {
        throw err;
    }

    readFile(join(__dirname, 'expected.txt'), 'UTF-8', function(err, exp) {
        if (err) {
            throw err;
        }

        var expected = platform() == 'win32' ? exp.split('\r\n') : exp.split('\n');
        var actual = stdout.split('\n');

        for (var i = 0, mx = expected.length; i < mx; i++) {
            assert.equal(actual[i], expected[i],
                "Expected '" + actual[i] + "' to be '" + expected[i] + "' on line " + (i + 1) + ".");
        }

        console.log('All tests pass!');
    });
});

Add test support for Windows


// Native
import path from 'path'

// Packages
import test from 'ava'
import {transformFile} from 'babel-core'

// Ours
import plugin from '../babel'
import read from './_read'

const transform = file => (
  new Promise((resolve, reject) => {
    transformFile(path.resolve(__dirname, file), {
      plugins: [
        plugin
      ]
    }, (err, data) => {
      if (err) {
        return reject(err)
      }
      resolve(data)
    })
  })
)

test('works with stateless', async t => {
  const {code} = await transform('./fixtures/stateless.js')
  const out = await read('./fixtures/stateless.out.js')
  t.is(code, out.trim())
})

test('works with class', async t => {
  const {code} = await transform('./fixtures/class.js')
  const out = await read('./fixtures/class.out.js')
  t.is(code, out.trim())
})

test('ignores when attribute is absent', async t => {
  const {code} = await transform('./fixtures/absent.js')
  const out = await read('./fixtures/absent.out.js')
  t.is(code, out.trim())
})

// Native
import path from 'path'

// Packages
import test from 'ava'
import {transformFile} from 'babel-core'

// Ours
import plugin from '../dist/babel'
import read from './_read'

const transform = file => (
  new Promise((resolve, reject) => {
    transformFile(path.resolve(__dirname, file), {
      plugins: [
        plugin
      ]
    }, (err, data) => {
      if (err) {
        return reject(err)
      }
      resolve(data)
    })
  })
)

test('works with stateless', async t => {
  const {code} = await transform('./fixtures/stateless.js')
  const out = await read('./fixtures/stateless.out.js')
  t.is(code, out.trim())
})

test('works with class', async t => {
  const {code} = await transform('./fixtures/class.js')
  const out = await read('./fixtures/class.out.js')
  t.is(code, out.trim())
})

test('ignores when attribute is absent', async t => {
  const {code} = await transform('./fixtures/absent.js')
  const out = await read('./fixtures/absent.out.js')
  t.is(code, out.trim())
})

Make tests read from the right dir


// Wrap your stuff in this module pattern for dependency injection
(function ($, ContentBlocks) {
    // Add your custom input to the fieldTypes object as a function
    // The dom variable contains the injected field (from the template)
    // and the data variable contains field information, properties etc.
    ContentBlocks.fieldTypes.checkblock = function(dom, data) {
        var input = {
            // Some optional variables can be defined here
        };

        // Do something when the input is being loaded
        input.init = function() {
        }

        // Get the data from this input, it has to be a simple object.
        input.getData = function() {
            return {
                value:$(document.getElementById('checkblock_' + data.generated_id)).is(':checked')
            }
        }

        // Always return the input variable.
        return input;
    }
})(vcJquery, ContentBlocks);

// Wrap your stuff in this module pattern for dependency injection
(function ($, ContentBlocks) {
    // Add your custom input to the fieldTypes object as a function
    // The dom variable contains the injected field (from the template)
    // and the data variable contains field information, properties etc.
    ContentBlocks.fieldTypes.checkblock = function(dom, data) {
        var input = {
            // Some optional variables can be defined here
        };

        // Do something when the input is being loaded
        input.init = function() {
            $(dom.find('#checkblock_' + data.generated_id)).prop('checked', data.value);
        }

        // Get the data from this input, it has to be a simple object.
        input.getData = function() {
            return {
                value:$(dom.find('#checkblock_' + data.generated_id)).is(':checked')
            }
        }

        // Always return the input variable.
        return input;
    }
})(vcJquery, ContentBlocks);

Fix checkboxes not ticking themselves


enchant();

window.onload = function () {
  var core = new Core(320, 320);

  core.onload = function () {
    core.rootScene.backgroundColor = '#eee';

    var field = new TextField(160, 24);
    field.x = (core.width - field.width) / 2;
    field.y = 64;
    field.placeholder = 'what is your name ?';

    field.style.border = '2px solid #f6c';
    field.style.borderRadius = '4px';

    field.onfocus = function () {
      label.text = 'onfocus called!!';
    };
    field.onblur = function () {
      label.text = 'onblur called!!';
    };
    field.onreturn = function () {
      label.text = 'onreturn called!!<br>' + this.value + '!!';
    };

    core.rootScene.addChild(field);

    var label = new Label();
    label.font = '32px Arial';
    label.width = 320;
    label.height = 32;
    label.x = 0;
    label.y = core.height - label.height * 2;
    label.textAlign = 'center';
    core.rootScene.addChild(label);
  };

  core.start();
};

enchant();

window.onload = function () {
  var core = new Core(320, 320);

  core.onload = function () {
    core.rootScene.backgroundColor = '#eee';

    // Create an instance of enchant.TextField
    var textField = new TextField(160, 24);

    // Set position
    textField.x = (core.width - textField.width) / 2;
    textField.y = 64;

    // Set placeholder
    textField.placeholder = 'what is your name ?';

    // Set styles
    textField.style.border = '2px solid #f6c';
    textField.style.borderRadius = '4px';

    // Define events
    textField.onfocus = function () {
      label.text = 'onfocus called!!';
    };
    textField.onblur = function () {
      label.text = 'onblur called!!';
    };
    textField.onreturn = function () {
      label.text = 'onreturn called!!<br>' + this.value + '!!';
    };

    // Add to the scene
    core.rootScene.addChild(textField);

    var label = new Label();
    label.font = '32px Arial';
    label.width = 320;
    label.height = 32;
    label.x = 0;
    label.y = core.height - label.height * 2;
    label.textAlign = 'center';
    core.rootScene.addChild(label);
  };

  core.start();
};

Add some comments and rename TextField instance


var w = 300,
    gens = 10;

var c = document.getElementById('cs');
var ctx = c.getContext('2d');

var ruleset = [0,1,0,1,1,0,1,0].reverse();

var cells = [];
for (var i=0; i<w; i++) {
  cells[i] = 0;
}
cells[Math.ceil(w/2)] = 1;

function rules(l,c,r) {
  var rule = '' + l + c + r;
  return ruleset[parseInt(rule, 2)];
}

function draw(h) {
  var size=2;
  for (var i =0; i < cells.length; i++) {
    if (cells[i] === 0) {
      ctx.fillStyle = 'rgb(200,200,100)';
    } else {
      ctx.fillStyle = 'rgb(0,155,100)';
      console.log(i);
    }

    ctx.fillRect(i*size,h*size,size,size);
  }
}

function generate() {
  var newgen = [];
  for(var i=0; i < cells.length; i++) {
    l = cells[i-1];
    c = cells[i];
    r = cells[i+1];
    newgen[i] = rules(l, c, r);
  }
  cells = newgen;
}

for (var i=0; i < gens; i++) {
  draw(i);
  generate();
}

var w = 600,
    gens = 800;

var c = document.getElementById('cs');
var ctx = c.getContext('2d');

var ruleset = [0,1,0,1,1,0,1,0].reverse();

var cells = [];
for (var i=0; i<w; i++) {
  cells[i] = 0;
}
cells[Math.ceil(w/2)] = 1;

function rules(l,c,r) {
  var rule = '' + l + c + r;
  return ruleset[parseInt(rule, 2)];
}

function draw(h) {
  var size=1;
  for (var i =0; i < cells.length; i++) {
    if (cells[i] === 0) {
      ctx.fillStyle = 'rgb(100,200,200)';
    } else {
      ctx.fillStyle = 'rgb(100,55,100)';
    }

    ctx.fillRect(i*size,h*size,size,size);
  }
}

function generate() {
  var newgen = [];
  for(var i=0; i < cells.length; i++) {
    l = cells[i-1];
    c = cells[i];
    r = cells[i+1];
    newgen[i] = rules(l, c, r);
  }
  cells = newgen;
}

for (var i=0; i < gens; i++) {
  draw(i);
  generate();
}

Tweak the color and size a bit


var ko = require('knockout-client'),
	vm = require('./viewModel');

vm.wtf();
ko.applyBindings(vm);

var ko = require('knockout-client'),
	vm = require('./viewModel');

ko.applyBindings(vm);

Remove increment on refresh / initial visit


import React from 'react'
import { webcastPropType } from '../utils/webcastUtils'

const EmbedTwitch = (props) => {
  const channel = props.webcast.channel
  const iframeSrc = `https://player.twitch.tv/?channel=${channel}`
  return (
    <iframe
      src={iframeSrc}
      frameBorder="0"
      scrolling="no"
      height="100%"
      width="100%"
    />
  )
}

EmbedTwitch.propTypes = {
  webcast: webcastPropType.isRequired,
}

export default EmbedTwitch

import React from 'react'
import { webcastPropType } from '../utils/webcastUtils'

const EmbedTwitch = (props) => {
  const channel = props.webcast.channel
  const iframeSrc = `https://player.twitch.tv/?channel=${channel}`
  return (
    <iframe
      src={iframeSrc}
      frameBorder="0"
      scrolling="no"
      height="100%"
      width="100%"
      allowFullScreen
    />
  )
}

EmbedTwitch.propTypes = {
  webcast: webcastPropType.isRequired,
}

export default EmbedTwitch

Fix gameday2 not allowing fullscreen Twitch


$(function () {
  $(document).on({
    ajaxStart: function() { $('body').addClass("loading");    },
    ajaxStop: function() { $('body').removeClass("loading"); }
  })

  $( "#request_form" ).submit(function( event ) {
    event.preventDefault();

    $('.results_container').slideUp( "slow");
    $.post( "/call", { challenge_number: $("#challenge_number").val(), weeks: $("#weeks").val() }).done(function( data ) {
        $('.results_container .result').html('');

        $.each(data, function(key, value) {
            $('.results_container .results').append("
"+value+"
");
        });

        $('.results_container').slideDown( "slow");
    });

  });

});

$(function () {
  $(document).on({
    ajaxStart: function() { $('body').addClass("loading");    },
    ajaxStop: function() { $('body').removeClass("loading"); }
  })

  $( "#request_form" ).submit(function( event ) {
    event.preventDefault();

    $('.results_container').slideUp( "slow");
    $.post( "/call", { challenge_number: $("#challenge_number").val(), weeks: $("#weeks").val() }).done(function( data ) {
        $('.results_container .result').html('');

        if (data) {
          $.each(data, function(key, value) {
              $('.results_container .results').append("
"+value+"
");
          });
        } else {
          $('.results_container .results').append("
None
");
        }

        $('.results_container').slideDown( "slow");
    });

  });

});

Fix view for empty response


import React, { Component, PropTypes } from 'react';
import fetchData from '../../actions/fetchData';
import Toast from '../../components/Toast';
import Modals from '../Modals';
import types from '../../utils/types';
import SocketEvents from '../../utils/socketEvents';
import './Main.scss';

import MainNav from '../MainNav';

export default class Main extends Component {
  static propTypes = {
    ...types.entries,
    ...types.sections,
    site: PropTypes.object.isRequired,
    socket: PropTypes.object.isRequired,
    ui: PropTypes.object.isRequired,
    user: PropTypes.object.isRequired,
    location: PropTypes.object.isRequired,
    children: PropTypes.object.isRequired,
    dispatch: PropTypes.func.isRequired,
  };

  componentDidMount() {
    const { dispatch, socket } = this.props;

    fetchData();

    const events = new SocketEvents(socket, dispatch);
    events.listen();
  }

  render() {
    const { user, entries, sections, fields, assets, ui, dispatch, site } = this.props;
    if (user.isFetching
      || entries.isFetching
      || sections.isFetching
      || assets.isFetching
      || fields.isFetching) return null;

    return (
      <main className="main">
        <MainNav siteName={site.siteName} />
        {React.cloneElement(this.props.children, {
          ...this.props,
          key: this.props.location.pathname,
        })}

        <div className="toasts">
          {ui.toasts.map(t => <Toast dispatch={dispatch} key={t.dateCreated} {...t} />)}
        </div>
        <Modals {...this.props} />
      </main>
    );
  }
}

import React, { Component, PropTypes } from 'react';
import fetchData from '../../actions/fetchData';
import Toast from '../../components/Toast';
import Modals from '../Modals';
import types from '../../utils/types';
import SocketEvents from '../../utils/socketEvents';
import './Main.scss';

import MainNav from '../MainNav';

export default class Main extends Component {
  static propTypes = {
    ...types.entries,
    ...types.sections,
    site: PropTypes.object.isRequired,
    socket: PropTypes.object.isRequired,
    ui: PropTypes.object.isRequired,
    user: PropTypes.object.isRequired,
    location: PropTypes.object.isRequired,
    children: PropTypes.object.isRequired,
    dispatch: PropTypes.func.isRequired,
  };

  componentDidMount() {
    const { dispatch, socket } = this.props;

    fetchData();

    const events = new SocketEvents(socket, dispatch);
    events.listen();
  }

  render() {
    const { ui, dispatch, site } = this.props;
    if (Object.keys(this.props).some(key => this.props[key].isFetching)) return null;

    return (
      <main className="main">
        <MainNav siteName={site.siteName} />
        {React.cloneElement(this.props.children, {
          ...this.props,
          key: this.props.location.pathname,
        })}

        <div className="toasts">
          {ui.toasts.map(t => <Toast dispatch={dispatch} key={t.dateCreated} {...t} />)}
        </div>
        <Modals {...this.props} />
      </main>
    );
  }
}

Check for isFetching with .some


'use strict';

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

/**
 * Extract the React Native module paths for a given directory
 *
 * @param {String} rootDir
 * @return {Object}
 */
function getReactNativeExternals(rootDir) {
  var externals = {};
  var file;

  var walk = function(dir) {
    fs.readdirSync(dir).forEach(function(mod) {
      file = path.resolve(dir, mod);
      if (fs.lstatSync(file).isDirectory()) {
        walk(file);
      } else if (path.extname(mod) === '.js') {
        mod = mod.replace(/\.js$/, '');
        // Only externalize RN's "React" dependency (uppercase).
        if (mod !== 'react') {
          externals[mod] = 'commonjs ' + mod;
        }
      }
    });
  }

  walk(rootDir);

  // 'react-native' is aliased as `React` in the global object
  externals['react-native'] = 'React';

  return externals;
}

module.exports = getReactNativeExternals;

'use strict';

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

/**
 * Extract the React Native module paths for a given directory
 *
 * @param {String} rootDir
 * @return {Object}
 */
function getReactNativeExternals(rootDir) {
  var externals = {};
  var file;

  var walk = function(dir) {
    fs.readdirSync(dir).forEach(function(mod) {
      file = path.resolve(dir, mod);
      if (fs.lstatSync(file).isDirectory()) {
        walk(file);
      } else if (path.extname(mod) === '.js') {
        mod = mod.replace(/(\.android|\.ios)?\.js$/, '');
        // FIXME(mj):
        // This is a temporary hack until we move to getting the dependencies
        // from the RN packager.
        // See: https://github.com/mjohnston/react-native-webpack-server/issues/23
        if (mod === 'StaticContainer') {
          mod = 'StaticContainer.react';
        }
        // Only externalize RN's "React" dependency (uppercase).
        if (mod !== 'react') {
          externals[mod] = 'commonjs ' + mod;
        }
      }
    });
  }

  walk(rootDir);

  // 'react-native' is aliased as `React` in the global object
  externals['react-native'] = 'React';

  return externals;
}

module.exports = getReactNativeExternals;

Fix for requiring `*.ios.js` modules


var Character = require('./character');
var React = require('react');
var _ = require('underscore')

var CharactersList = React.createClass({
  render: function () {
    return (
      <div>
      {
        this.props.items.map(function(item, index) {
          var name = item.name;
          var image = item.image;
          var description = item.description;

          return <Character key={index} name={name} image={image} description={description}/>;
        })
      }
      </div>
    );
  }
});

module.exports = CharactersList;

var Character = require('./character');
var React = require('react');
var _ = require('underscore')

var CharactersList = React.createClass({
  render: function () {
    return (
      <div>
      {
        this.props.items.map(function(item, index) {
          var name = item.name;
          var image = item.thumbnail.path+'.'+item.thumbnail.extension;
          var description = item.description;

          return <Character key={index} name={name} image={image} description={description}/>;
        })
      }
      </div>
    );
  }
});

module.exports = CharactersList;

Fix for the image prop of the character


import appReducer from './app/reducer';
import createLogger from 'redux-logger';
import fetch from 'isomorphic-fetch';
import injectDependencies from './lib/injectDependencies';
import promiseMiddleware from 'redux-promise-middleware';
import stateToJS from './lib/stateToJS';
import validate from './validate';
import {applyMiddleware, createStore} from 'redux';

export default function configureStore(initialState) {
  const dependenciesMiddleware = injectDependencies(
    {fetch},
    {validate}
  );
  const middlewares = [
    dependenciesMiddleware,
    promiseMiddleware
  ];

  if (process.env.NODE_ENV !== 'production') { // eslint-disable-line no-undef
    const logger = createLogger({
      collapsed: () => true,
      transformer: stateToJS
    });
    middlewares.push(logger);
  }

  const store = applyMiddleware(...middlewares)(createStore)(
    appReducer, initialState);

  if (module.hot) { // eslint-disable-line no-undef
    // Enable Webpack hot module replacement for reducers.
    module.hot.accept('./app/reducer', () => { // eslint-disable-line no-undef
      const nextAppReducer = require('./app/reducer'); // eslint-disable-line no-undef
      store.replaceReducer(nextAppReducer);
    });
  }

  return store;
}

import appReducer from './app/reducer';
import createLogger from 'redux-logger';
import fetch from 'isomorphic-fetch';
import injectDependencies from './lib/injectDependencies';
import promiseMiddleware from 'redux-promise-middleware';
import stateToJS from './lib/stateToJS';
import validate from './validate';
import {applyMiddleware, createStore} from 'redux';

export default function configureStore(initialState) {
  const dependenciesMiddleware = injectDependencies(
    {fetch},
    {validate}
  );
  const middlewares = [
    dependenciesMiddleware,
    promiseMiddleware
  ];
  const loggerEnabled =
    process.env.IS_BROWSER && // eslint-disable-line no-undef
    process.env.NODE_ENV !== 'production'; // eslint-disable-line no-undef

  if (loggerEnabled) {
    const logger = createLogger({
      collapsed: () => true,
      transformer: stateToJS
    });
    middlewares.push(logger);
  }

  const store = applyMiddleware(...middlewares)(createStore)(
    appReducer, initialState);

  if (module.hot) { // eslint-disable-line no-undef
    // Enable Webpack hot module replacement for reducers.
    module.hot.accept('./app/reducer', () => { // eslint-disable-line no-undef
      const nextAppReducer = require('./app/reducer'); // eslint-disable-line no-undef
      store.replaceReducer(nextAppReducer);
    });
  }

  return store;
}

Enable Flux logger only for dev


'use babel'

import {Emitter, CompositeDisposable} from 'atom'
import Validate from './validate'
import Indie from './indie'

export default class IndieRegistry {
  constructor() {
    this.subscriptions = new CompositeDisposable()
    this.emitter = new Emitter()

    this.indieLinters = new Set()
    this.subscriptions.add(this.emitter)
  }

  register(linter) {
    Validate.linter(linter, true)
    const indieLinter = new Indie(linter)

    this.subscriptions.add(indieLinter)
    this.indieLinters.add(indieLinter)

    indieLinter.onDidDestroy(() => {
      this.indieLinters.delete(indieLinter)
    })
    indieLinter.onDidUpdateMessages(messages => {
      this.emitter.emit('did-update-messages', {linter: indieLinter, messages})
    })
    this.emit('observe', indieLinter)

    return indieLinter
  }
  unregister(indieLinter) {
    if (this.indieLinters.has(indieLinter)) {
      indieLinter.dispose()
    }
  }

  // Private method
  observe(callback) {
    this.indieLinters.forEach(callback)
    return this.emitter.on('observe', callback)
  }
  // Private method
  onDidUpdateMessages(callback) {
    return this.emitter.on('did-update-messages', callback)
  }

  dispose() {
    this.subscriptions.dispose()
  }
}

'use babel'

import {Emitter, CompositeDisposable} from 'atom'
import Validate from './validate'
import Indie from './indie'

export default class IndieRegistry {
  constructor() {
    this.subscriptions = new CompositeDisposable()
    this.emitter = new Emitter()

    this.indieLinters = new Set()
    this.subscriptions.add(this.emitter)
  }

  register(linter) {
    Validate.linter(linter, true)
    const indieLinter = new Indie(linter)

    this.subscriptions.add(indieLinter)
    this.indieLinters.add(indieLinter)

    indieLinter.onDidDestroy(() => {
      this.indieLinters.delete(indieLinter)
    })
    indieLinter.onDidUpdateMessages(messages => {
      this.emitter.emit('did-update-messages', {linter: indieLinter, messages})
    })
    this.emit('observe', indieLinter)

    return indieLinter
  }
  has(indieLinter) {
    return this.indieLinters.has(indieLinter)
  }
  unregister(indieLinter) {
    if (this.indieLinters.has(indieLinter)) {
      indieLinter.dispose()
    }
  }

  // Private method
  observe(callback) {
    this.indieLinters.forEach(callback)
    return this.emitter.on('observe', callback)
  }
  // Private method
  onDidUpdateMessages(callback) {
    return this.emitter.on('did-update-messages', callback)
  }

  dispose() {
    this.subscriptions.dispose()
  }
}

Add a new has method


/**
 * checks if there are scores which can be combined
 */
function checkValidity (scores) {
  if (scores == null) {
    throw new Error('There must be a scores object parameter')
  }
  if (Object.keys(scores).length <= 0) {
    throw new Error('At least one score must be passed')
  }
}

/**
 * Score canidate based on the mean value of two or more parent Qs.
 */
class MeanQ {

  /**
   * Returns the mean value of the given scores.
   * @param scores - obect that contains the keys and scores
   *                 for each used scoring method
   * @return mean value of given scores
   */
  combine (scores) {
    checkValidity(scores)

    var sum = 0
    Object.keys(scores).map((key, index, arr) => {
      sum += scores[key]
    })
    return sum / Object.keys(scores).length
  }
}

/**
 * Chooses the biggest score out of all possible scores
 */
class LargestQ {

  /**
   * combines all scores by choosing the largest score
   */
  combine (scores) {
    checkValidity(scores)

    var largest = 0.0
    Object.keys(scores).map((key, index, arr) => {
      if (scores[key] >= largest) {
        largest = scores[key]
      }
    })
    return largest
  }

}

module.exports = {
  Mean: MeanQ,
  Largest: LargestQ
}

/**
 * checks if there are scores which can be combined
 */
function checkValidity (scores) {
  if (scores == null) {
    throw new Error('There must be a scores object parameter')
  }
  if (Object.keys(scores).length <= 0) {
    throw new Error('At least one score must be passed')
  }
}

/**
 * Score canidate based on the mean value of two or more parent Qs.
 */
class MeanQ {

  /**
   * Returns the mean value of the given scores.
   * @param scores - obect that contains the keys and scores
   *                 for each used scoring method
   * @return mean value of given scores
   */
  combine (scores) {
    checkValidity(scores)

    var sum = 0
    Object.keys(scores).map((key, index, arr) => {
      sum += scores[key]
    })
    return sum / Object.keys(scores).length
  }
}

/**
 * Chooses the biggest score out of all possible scores
 */
class LargestQ {

  /**
   * combines all scores by choosing the largest score
   */
  combine (scores) {
    checkValidity(scores)
    return Math.max.apply(null, Object.values(scores))
  }

}

module.exports = {
  Mean: MeanQ,
  Largest: LargestQ
}

Refactor LargestQ.combine() to use built-ins.


var util = require('util');
var MapStoreMapConfigProvider = require('./mapstore_mapconfig_provider');

function DummyMapConfigProvider(mapConfig, params) {
    if (!(this instanceof DummyMapConfigProvider)) {
        return new DummyMapConfigProvider(mapConfig, params);
    }

    MapStoreMapConfigProvider.call(this, undefined, params);

    this.mapConfig = mapConfig;
}

util.inherits(DummyMapConfigProvider, MapStoreMapConfigProvider);

module.exports = DummyMapConfigProvider;

DummyMapConfigProvider.prototype.getMapConfig = function(callback) {
    return callback(null, this.mapConfig, this.params, {});
};

var util = require('util');
var MapStoreMapConfigProvider = require('./mapstore_mapconfig_provider');

function DummyMapConfigProvider(mapConfig, params) {
    MapStoreMapConfigProvider.call(this, undefined, params);

    this.mapConfig = mapConfig;
}

util.inherits(DummyMapConfigProvider, MapStoreMapConfigProvider);

module.exports = DummyMapConfigProvider;

DummyMapConfigProvider.prototype.getMapConfig = function(callback) {
    return callback(null, this.mapConfig, this.params, {});
};

Remove instance check as it's an internal class and have full control


const root = __dirname + '/../../../..';

require('@babel/register')({
  root,
  cwd: root,
  configFile: root + '/packages/server/babel.config.js',
  extensions: ['.js', '.jsx', '.ts', '.tsx']
});

require.extensions['.scss'] = () => {};
require.extensions['.css'] = () => {};
require.extensions['.less'] = () => {};

Object.assign(global, require('../../jest.config').globals);

const { setup } = require('@gqlapp/testing-server-ts');
require('..');

module.exports = setup;

const root = __dirname + '/../../../..';

require('@babel/register')({
  root,
  cwd: root,
  configFile: root + '/packages/server/babel.config.js',
  extensions: ['.js', '.jsx', '.ts', '.tsx'],
  cache: false
});

require.extensions['.scss'] = () => {};
require.extensions['.css'] = () => {};
require.extensions['.less'] = () => {};

Object.assign(global, require('../../jest.config').globals);

const { setup } = require('@gqlapp/testing-server-ts');
require('..');

module.exports = setup;

Work around babel-plugin-import-graphql caching intolerance


function quantify(data, unit, value) {
    if (value) {
        if (value > 1 || value < -1)
            unit += 's';

        data.push(value + ' ' + unit);
    }

    return data;
}

module.exports = function prettySeconds(seconds) {

    var prettyString = '',
        data = [];

    if (typeof seconds === 'number') {

        data = quantify(data, 'day',    parseInt(seconds / 86400));
        data = quantify(data, 'hour',   parseInt((seconds % 86400) / 3600));
        data = quantify(data, 'minute', parseInt((seconds % 3600) / 60));
        data = quantify(data, 'second', seconds % 60);

        var length = data.length,
            i;

        for (i = 0; i < length; i++) {

            if (prettyString.length > 0)
                if (i == length - 1)
                    prettyString += ' and ';
                else
                    prettyString += ', ';

            prettyString += data[i];
        }
    }

    return prettyString;
};

function quantify(data, unit, value) {
    if (value) {
        if (value > 1 || value < -1)
            unit += 's';

        data.push(value + ' ' + unit);
    }

    return data;
}

module.exports = function prettySeconds(seconds) {

    var prettyString = '',
        data = [];

    if (typeof seconds === 'number') {

        data = quantify(data, 'day',    parseInt(seconds / 86400));
        data = quantify(data, 'hour',   parseInt((seconds % 86400) / 3600));
        data = quantify(data, 'minute', parseInt((seconds % 3600) / 60));
        data = quantify(data, 'second', Math.floor(seconds % 60));

        var length = data.length,
            i;

        for (i = 0; i < length; i++) {

            if (prettyString.length > 0)
                if (i == length - 1)
                    prettyString += ' and ';
                else
                    prettyString += ', ';

            prettyString += data[i];
        }
    }

    return prettyString;
};

Trim decimal places off seconds


// Description:
//   Pick and tag a random user that has to do shitty work, replies when the bot
//   hears @noen. This script also supports mentions of @aktive and @nye.

const _ = require('lodash');
const members = require('../lib/members');

const prefixes = [
  'Time to shine',
  "The work doesn't do itself",
  'Get going',
  'lol rekt',
  'About time you got picked',
  'RIP',
  'Move it'
];

const createMention = username => `@${username}`;

module.exports = robot => {
  robot.hear(/@noen-nye/i, msg => {
    members('?new=true')
      .then(members => {
        if (!members.length) {
          return;
        }

        const luckyMember = _.sample(members);
        const mention = createMention(luckyMember.slack);
        const cheesyPrefix = _.sample(prefixes);
        msg.send(`${cheesyPrefix} ${mention}`);
      })
      .catch(error => msg.send(error.message));
  });

  robot.hear(/@noen/i, msg => {
    // Reply with a cheesy message and a random picked mention.
    members('?active=true')
      .then(members => {
        if (!members.length) {
          return;
        }

        const luckyMember = _.sample(members);
        const mention = createMention(luckyMember.slack);
        const cheesyPrefix = _.sample(prefixes);
        msg.send(`${cheesyPrefix} ${mention}`);
      })
      .catch(error => msg.send(error.message));
  });

  robot.hear(/@aktive|@active/i, msg => {
    // Reply with a message containing mentions of all active users.
    members('?active=true')
      .then(members => {
        if (members.length === 0) {
          return;
        }

        msg.send(members.map(member => createMention(member.slack)).join(', '));
      })
      .catch(error => msg.send(error.message));
  });

  robot.hear(/@nye/i, msg => {
    members('?new=true')
      .then(members => {
        if (members.length === 0) {
          return;
        }

        msg.send(members.map(member => createMention(member.slack)).join(', '));
      })
      .catch(error => msg.send(error.message));
  });
};

// Description:
// Get all phone numbers for active members

const _ = require('lodash');
const members = require('../lib/members');

module.exports = robot => {
  robot.respond(/nummer/i, msg => {
    members('?active=true')
      .then(members => {
        if (members.length === 0) {
          return;
        }
        msg.send(
          members
            .map(
              m =>
                `*${m.name}*:${'\t'} ${m.phone_number.substr(
                  0,
                  3
                )} ${m.phone_number
                  .substr(3)
                  .match(/.{1,2}/g)
                  .join(' ')}`
            )
            .sort()
            .join('\n')
        );
      })
      .catch(error => msg.send(error.message));
  });
};

Remove duplicate posts by ababot


'use strict';

const {app} = require('electron').remote;
const JsonDB = require('node-json-db');

class DomainUtil {
    constructor() {
        this.db = new JsonDB(app.getPath('userData') + '/domain.json', true, true);
    }

    getDomains() {
        return this.db.getData('/domains');
    }

    addDomain() {
        const servers = {
            url: 'https://chat.zulip.org',
            alias: 'Zulip 2',
            avatar: 'https://chat.zulip.org/static/images/logo/zulip-icon-128x128.271d0f6a0ca2.png'
        }
        db.push("/domains[]", servers, true);
    }
}

module.exports = DomainUtil;

'use strict';

const {app} = require('electron').remote;
const JsonDB = require('node-json-db');

class DomainUtil {
    constructor() {
        this.db = new JsonDB(app.getPath('userData') + '/domain.json', true, true);
    }

    getDomains() {
        return this.db.getData('/domains');
    }

    addDomain() {
        const servers = {
            url: 'https://chat.zulip.org',
            alias: 'Zulip 2333',
            icon: 'https://chat.zulip.org/static/images/logo/zulip-icon-128x128.271d0f6a0ca2.png'
        }
        this.db.push("/domains[]", servers, true);
    }

    removeDomains() {
        this.db.delete("/domains");
    }
}

module.exports = DomainUtil;

Change domain config schema and update DomainUtil.


/* @flow */
export default function extractUsedProps(
  rule: Function,
  theme: Object = {}
): Array<string> {
  const usedProps = []

  // if the browser doesn't support proxies
  // we simply return an empty props object
  // see https://github.com/rofrischmann/fela/issues/468
  if (typeof Proxy === 'undefined') {
    return usedProps
  }

  const handler = props => ({
    get(target, key) {
      if (typeof target[key] === 'object' && target[key] !== null) {
        props.push(key)
        return target[key]
      }
      props.push(key)
      return target[key]
    },
  })

  const proxy = new Proxy({ theme }, handler(usedProps))
  rule(proxy)
  return usedProps
}

/* @flow */
export default function extractUsedProps(
  rule: Function,
  theme: Object = {}
): Array<string> {
  const usedProps = []

  // if the browser doesn't support proxies
  // we simply return an empty props object
  // see https://github.com/rofrischmann/fela/issues/468
  if (typeof Proxy === 'undefined') {
    return usedProps
  }

  const handler = props => ({
    get(target, key) {
      if (typeof target[key] === 'object' && target[key] !== null) {
        props.push(key)
        return target[key]
      }
      props.push(key)
      return target[key]
    },
  })

  const proxy = new Proxy({ theme }, handler(usedProps))
  try {
    rule(proxy)
    return usedProps
  } catch (err) {
    return []
  }
}

Handle exception extracting used props


module.exports = function(grunt) {

    grunt.config.merge({
        imagemin: {
            slider: {
                files: [{
                    expand: true,
                    cwd: 'cacao/modules/slider/images',
                    src: ['**/*.{png,jpg,gif}'],
                    dest: '<%= global.dest %>/layout/images'
                }]
            }
        }
    });

    grunt.registerTask('build-slider', ['imagemin:slider']);

};

module.exports = function(grunt) {

    grunt.config.merge({
        imagemin: {
            slider: {
                files: [{
                    expand: true,
                    cwd: 'bower_components/cacao/modules/slider/images',
                    src: ['**/*.{png,jpg,gif}'],
                    dest: '<%= global.dest %>/layout/images'
                }]
            }
        }
    });

    grunt.registerTask('build-slider', ['imagemin:slider']);

};

Update path of slider images.


/* global describe, it, before */
var request = require('supertest')
var assert = require('assert')
var path = require('path')
var fs = require('fs')

/**
 * Basic sanity checks on the dev server
 */
describe('The prototype kit', function () {
  var app

  before(function (done) {
    app = require('../../server')
    done()
  })

  it('should generate assets into the /public folder', function () {
    assert.doesNotThrow(function () {
      fs.accessSync(path.resolve(__dirname, '../../public/javascripts/application.js'))
      fs.accessSync(path.resolve(__dirname, '../../public/images/favicon.ico'))
      fs.accessSync(path.resolve(__dirname, '../../public/stylesheets/application.css'))
    })
  })

  it('should send with a well formed response for the index page', function (done) {
    request(app)
      .get('/')
      .expect('Content-Type', /text\/html/)
      .expect(200, done)
  })
})

/* eslint-env mocha */
var request = require('supertest')
var app = require('../../server.js')
var path = require('path')
var fs = require('fs')
var assert = require('assert')

/**
 * Basic sanity checks on the dev server
 */
describe('The prototype kit', function () {
  it('should generate assets into the /public folder', function () {
    assert.doesNotThrow(function () {
      fs.accessSync(path.resolve(__dirname, '../../public/javascripts/application.js'))
      fs.accessSync(path.resolve(__dirname, '../../public/images/favicon.ico'))
      fs.accessSync(path.resolve(__dirname, '../../public/stylesheets/application.css'))
    })
  })

  it('should send with a well formed response for the index page', function (done) {
    request(app)
      .get('/')
      .expect('Content-Type', /text\/html/)
      .expect(200)
      .end(function (err, res) {
        if (err) {
          done(err)
        } else {
          done()
        }
      })
  })
})

Simplify the sanity test check


/**
 * Created by ChengFuLin on 2015/6/10.
 */
var factoryAnalyzedCFG = require('../../lib/dujs').factoryAnalyzedCFG,
    should = require('should');

describe('ModelFactory', function () {
    "use strict";
    describe('Factory Method', function () {
        it('should support to create empty Model', function () {
            var analyzedCFG = factoryAnalyzedCFG.create();
            should.not.exist(analyzedCFG._testonly_._graph);
            should.exist(analyzedCFG._testonly_._relatedScopes);
            analyzedCFG._testonly_._relatedScopes.length.should.eql(0);
            should.exist(analyzedCFG._testonly_._dupairs);
            analyzedCFG._testonly_._dupairs.size.should.eql(0);
        });
    });
});

/*
 * Test cases for ModelFactory
 * @lastmodifiedBy ChengFuLin(chengfulin0806@gmail.com)
 * @lastmodifiedDate 2015-08-14
 */
var factoryModel = require('../../lib/dujs/modelfactory');
var should = require('should');

describe('ModelFactory', function () {
    "use strict";
    describe('public methods', function () {
	    describe('create', function () {
		    it('should support to create empty Model', function () {
			    var model = factoryModel.create();
			    should.not.exist(model._testonly_._graph);
			    should.exist(model._testonly_._relatedScopes);
			    model._testonly_._relatedScopes.length.should.eql(0);
			    should.exist(model._testonly_._dupairs);
			    model._testonly_._dupairs.size.should.eql(0);
		    });
	    });
    });
});

Refactor test cases for ModelFactory


import React from 'react'

import CreaditCardPayment from './credit_card_payment.js'
import Paypal from './paypal.js'

export default (props) => {
  return props.user_payments.payment_sent
    ? SuccessfulPayment(props)
    : (props.user_payments.braintree_error ? Error() : PaymentOptions(props))
  }

const Error = () =>

There has been a network error. Please refresh the browser.

const PaymentOptions = (props) =>

Online Payment

If you would prefer to pay by PayPal

Alternatively pay by card

    <CreaditCardPayment {...props} />

  // <Paypal {...props} />

const SuccessfulPayment = ({ user_payments }) =>

Successful Payment

      Thank you for your payment of £{user_payments.amount_entered}.
      Your reference for that payment is {user_payments.payment_sent.reference}.

import React from 'react'

import CreaditCardPayment from './credit_card_payment.js'
import Paypal from './paypal.js'

export default (props) => {
  return props.user_payments.payment_sent
    ? SuccessfulPayment(props)
    : (props.user_payments.braintree_error ? Error() : PaymentOptions(props))
  }

const Error = () =>

There has been a network error. Please refresh the browser.

const PaymentOptions = (props) =>

Online Payment

Pay using PayPal

    <Paypal {...props} />

Alternatively pay by card

    <CreaditCardPayment {...props} />

const SuccessfulPayment = ({ user_payments }) =>

Successful Payment

      Thank you for your payment of £{user_payments.amount_entered}.
      Your reference for that payment is {user_payments.payment_sent.reference}.

Add paypal component to online payments.


// Sieve of Eratosthenes

/*RULES:
  Function takes one parameter
  Return an array of all prime numbers from 0-parameter
*/

/*PSEUDOCODE:
  1) Find square root of parameter
  2) Create an array of numbers from 0-parameter
  3) Loop through numbers, but stop until square root (if a float, stop after number rounded up?)
  4) Return array
*/

function sieveOfEratosthenes(num){
  var squareRoot = Math.sqrt(num);
}

// Sieve of Eratosthenes

/*RULES:
  Function takes one parameter
  Return an array of all prime numbers from 0-parameter
*/

/*PSEUDOCODE:
  1) Find square root of parameter
  2) Create an array of numbers from 0-parameter
  3) Loop through numbers, but stop until square root (if a float, stop after number rounded up?)
  4) Return array
*/

function sieveOfEratosthenes(num){
  var squareRoot = Math.sqrt(num);
  var arr = [...Array(num + 1)].map((x, i) => i)

}

Create var arr that creates the needed array depending on the given param


'use strict';

/*
 * /create [name] - Creates a voice-chat channel. Optionally names it, otherwise names it Username-[current game]
 *
 */

class Create{
    constructor (bot, config) {
        this.bot = bot;
        this.config = config;
    }

    get command () { return 'create' }

    // Message input, user id <snowflake>, guild id <snowflake>, channel id <snowflake>
    onCommandEntered(message, username, uid, gid, cid) {
        console.log("Got command, sending");
        this.bot.sendMessage({
            to: cid,
            message: 'Hello ' + username + ' [' + message + ']',
        }, (err, res) => {
            console.log('CB', err, res);
        });

        var split = message.split(' ');
        var givenName = 'Temporary Channel';
        if (split.length > 1) split.slice(0, split.length).join(' ');

        var channelName = this.config.tempChannelNamePrefix + username + ': ' + givenName;

        console.log('Creating channel', channelName);
        this.bot.createTemporaryChannel(channelName, gid);
    }

    checkUserPermissions(uid, server) {
        return true;
    }
}

module.exports = Create;

'use strict';

/*
 * /create [name] - Creates a voice-chat channel. Optionally names it, otherwise names it Username-[current game]
 *
 */

class Create{
    constructor (bot, config) {
        this.bot = bot;
        this.config = config;
    }

    get command () { return 'create' }

    // Message input, user id <snowflake>, guild id <snowflake>, channel id <snowflake>
    onCommandEntered(message, username, uid, gid, cid) {

        var split = message.split(' ');
        var givenName = 'Temporary Channel';

        // Check if user is in game, then name it that
        var user = this.bot.users[uid];
        if (user.game !== null) {
            givenName = user.game.name;
        }

        // Check if name is defined, use that
        if (split.length > 1) split.slice(0, split.length).join(' ');

        var channelName = this.config.tempChannelNamePrefix + username + ': ' + givenName;

        console.log('Creating channel', channelName);
        this.bot.createTemporaryChannel(channelName, gid);
    }

    checkUserPermissions(uid, server) {
        return true;
    }
}

module.exports = Create;

Add temp channel init with game name


// Sample Lambda Function to send notifications via text when an  AWS Health event happens
var AWS = require('aws-sdk');
var sns = new AWS.SNS();

// define configuration
const phoneNumber =''; // Insert phone number here. For example, a U.S. phone number in E.164 format would appear as +1XXX5550100

//main function which gets AWS Health data from Cloudwatch event
exports.handler = (event, context, callback) => {
    //extract details from Cloudwatch event
    eventName = event.detail.eventTypeCode
    healthMessage = 'The following AWS Health event type has occured: ' + eventName + ' For more details, please see https://phd.aws.amazon.com/phd/home?region=us-east-1#/dashboard/open-issues';
    //prepare message for SNS to publish
    var snsPublishParams = {
        Message: healthMessage,
        PhoneNumber: phoneNumber,
    };
    sns.publish(snsPublishParams, function(err, data) {
    if (err) {
        const snsPublishErrorMessage = `Error publishing AWS Health event to SNS`;
        console.log(snsPublishErrorMessage, err);
        callback(snsPublishErrorMessage);
        }
    else {
        const snsPublishSuccessMessage = `Successfully got details from AWS Health event, ${eventName} and sent SMS via SNS.`;
        console.log(snsPublishSuccessMessage, data);
        callback(null, snsPublishSuccessMessage); //return success
        }
    });
};

// Sample Lambda Function to send notifications via text when an  AWS Health event happens
var AWS = require('aws-sdk');
var sns = new AWS.SNS();

//main function which gets AWS Health data from Cloudwatch event
exports.handler = (event, context, callback) => {
    //get phone number from Env Variable
    var phoneNumber = process.env.PHONE_NUMBER;
    //extract details from Cloudwatch event
    eventName = event.detail.eventTypeCode
    healthMessage = 'The following AWS Health event type has occured: ' + eventName + ' For more details, please see https://phd.aws.amazon.com/phd/home?region=us-east-1#/dashboard/open-issues';
    //prepare message for SNS to publish
    var snsPublishParams = {
        Message: healthMessage,
        PhoneNumber: phoneNumber,
    };
    sns.publish(snsPublishParams, function(err, data) {
    if (err) {
        const snsPublishErrorMessage = `Error publishing AWS Health event to SNS`;
        console.log(snsPublishErrorMessage, err);
        callback(snsPublishErrorMessage);
        }
    else {
        const snsPublishSuccessMessage = `Successfully got details from AWS Health event, ${eventName} and sent SMS via SNS.`;
        console.log(snsPublishSuccessMessage, data);
        callback(null, snsPublishSuccessMessage); //return success
        }
    });
};

Switch phone number to Environment Variable (instead of const)


// Javascript specific to guide admin
$(function() {
  var sortable_opts = {
    axis: "y",
    handle: "a.accordion-toggle",
    stop: function(event, ui) {
      $('.part').each(function (i, elem) {
        $(elem).find('input.order').val(i + 1);
        ui.item.find("a.accordion-toggle").addClass("highlight");
        setTimeout(function() { $("a.accordion-toggle.highlight").removeClass("highlight") }, 20 )
      });
    }
  }
  $('#parts').sortable(sortable_opts)
      .find("a.accordion-toggle").css({cursor: 'move'});

  $('input.title').
    on('change', function () {
      var elem = $(this);
      var value = elem.val();

      // Set slug on change.
      var slug_field = elem.closest('.part').find('.slug');
      if (slug_field.text() === '') {
        slug_field.val(GovUKGuideUtils.convertToSlug(value));
      }

      // Set header on change.
      var header = elem.closest('fieldset').prev('h3').find('a');
      header.text(value);
    });
});

// Javascript specific to guide admin
// When we add a new part, ensure we add the auto slug generator handler
$(document).on('nested:fieldAdded:parts', function(event){
  addAutoSlugGeneration();
});

function addAutoSlugGeneration() {
  $('input.title').
    on('change', function () {
      var elem = $(this);
      var value = elem.val();

      // Set slug on change.
      var slug_field = elem.closest('.part').find('.slug');
      if (slug_field.text() === '') {
        slug_field.val(GovUKGuideUtils.convertToSlug(value));
      }

      // Set header on change.
      var header = elem.closest('fieldset').prev('h3').find('a');
      header.text(value);
    });
}

$(function() {
  var sortable_opts = {
    axis: "y",
    handle: "a.accordion-toggle",
    stop: function(event, ui) {
      $('.part').each(function (i, elem) {
        $(elem).find('input.order').val(i + 1);
        ui.item.find("a.accordion-toggle").addClass("highlight");
        setTimeout(function() { $("a.accordion-toggle.highlight").removeClass("highlight") }, 20 )
      });
    }
  }
  $('#parts').sortable(sortable_opts)
      .find("a.accordion-toggle").css({cursor: 'move'});
      addAutoSlugGeneration();
});

Fix auto slug generation for newly added parts


App.Search.SearchPanelUi = Ext.extend(Ext.form.FormPanel, {
  title: 'Search criteria',
  labelWidth: 100,
  labelAlign: 'left',
  layout: 'form',
  tbar: {
    xtype: 'toolbar',
    items: [{
      text: 'Add language',
      icon: urlRoot + 'images/add.png',
      cls: 'x-btn-text-icon',
      ref: '../addLanguageButton'
    }, '-', {
      text: 'Save search',
      icon: urlRoot + 'images/disk.png'
    }, {
      text: 'Saved searches',
      icon: urlRoot + 'images/folder_explore.png'
    }, '-', {
      text: 'Reset form',
      icon: urlRoot + 'images/cancel.png'
    }, '-', {
      text: '<b>Search</b>',
      icon: urlRoot + 'images/zoom.png'
    }]
  },

  initComponent: function() {
    App.Search.SearchPanelUi.superclass.initComponent.call(this);

    this.setupSubcomponents();
  },

  /* Setup */

  setupSubcomponents: function() {
    this.addLanguageButton.on('click', this.onAddLanguageClicked, this);
  },

  /* Event handlers */

  onAddLanguageClicked: function() {
    alert('hei');
  }
});

App.Search.SearchPanelUi = Ext.extend(Ext.form.FormPanel, {
  title: 'Search criteria',
  labelWidth: 100,
  labelAlign: 'left',
  layout: 'form',
  tbar: {
    xtype: 'toolbar',
    items: [{
      text: 'Add language',
      icon: urlRoot + 'images/add.png',
      cls: 'x-btn-text-icon',
      ref: '../addLanguageButton'
    //}, '-', {
      //text: 'Save search',
      //icon: urlRoot + 'images/disk.png'
    //}, {
      //text: 'Saved searches',
      //icon: urlRoot + 'images/folder_explore.png'
    }, '-', {
      text: 'Reset form',
      icon: urlRoot + 'images/cancel.png'
    }, '-', {
      text: '<b>Search</b>',
      icon: urlRoot + 'images/zoom.png'
    }]
  },

  initComponent: function() {
    App.Search.SearchPanelUi.superclass.initComponent.call(this);

    this.setupSubcomponents();
  },

  /* Setup */

  setupSubcomponents: function() {
    this.addLanguageButton.on('click', this.onAddLanguageClicked, this);
  },

  /* Event handlers */

  onAddLanguageClicked: function() {
    alert('hei');
  }
});

Hide search saving buttons for now


'use strict';

angular.module('arethusa').constant('VERSION', {
  revision: '<%= sha %>',
  date: '<%= new Date().toJSON() %>'
});

'use strict';

angular.module('arethusa').constant('VERSION', {
  revision: '<%= sha %>',
  date: '<%= new Date().toJSON() %>',
  repository: 'http://github.com/latin-language-toolkit/arethusa'
});

Add the repo to the version CONSTANT


angular.module('MovnThereUI').factory('AuthFactory', function($http, $window, ServerUrl) {
    'use strict';

    var login = function(credentials) {

        return $http
            .post(ServerUrl + 'login', credentials)
            .success(function(response) {
                $window.sessionStorage.setItem('movnThereUI.user', response.token);

                $http.defaults.headers.common['Authorization'] = 'Token token=' + $window.sessionStorage.getItem('movnThereUIUI.user');
            });
    };

    var signup = function(credentials) {
        var params = {
            user: credentials
        };

        return $http
        .post(ServerUrl + 'signup', params)
        .success(function(response) {
            $window.sessionStorage.setItem('movnThereUI.user', response.token);

            $http.defaults.headers.common['Authorization'] = 'Token token=' + $window.sessionStorage.getItem('movnThereUI.user');
        });
    };

    var logout = function(credentials) {
        return $http
            .post(ServerUrl + '/logout')
            .success(function(response) {
                $window.sessionStorage.removeItem('movnThereUI.user');
            });
    };

    var isAuthenticated = function() {
        return !!$window.sessionStorage.getItem('movnThereUI.user');
    };

    return {
        login: login,
        signup: signup,
        logout: logout,
        isAuthenticated: isAuthenticated
    };
});

angular.module('MovnThereUI').factory('AuthFactory', function($http, $window, ServerUrl) {
    'use strict';

    var login = function(credentials) {

        return $http
            .post(ServerUrl + 'login', credentials)
            .success(function(response) {
                $window.sessionStorage.setItem('movnThereUI.user', response.token);

                $http.defaults.headers.common['Authorization'] = 'Token token=' + $window.sessionStorage.getItem('movnThereUI.user');
            });
    };

    var signup = function(credentials) {
        var params = {
            user: credentials
        };

        return $http
        .post(ServerUrl + 'signup', params)
        .success(function(response) {
            $window.sessionStorage.setItem('movnThereUI.user', response.token);

            $http.defaults.headers.common['Authorization'] = 'Token token=' + $window.sessionStorage.getItem('movnThereUI.user');
        });
    };

    var logout = function(credentials) {
        return $http
            .post(ServerUrl + '/logout')
            .success(function(response) {
                $window.sessionStorage.removeItem('movnThereUI.user');
            });
    };

    var isAuthenticated = function() {
        return !!$window.sessionStorage.getItem('movnThereUI.user');
    };

    return {
        login: login,
        signup: signup,
        logout: logout,
        isAuthenticated: isAuthenticated
    };
});

Fix sessionStorage name to retrieve token.


import WebMiddle, { PropTypes } from 'webmiddle';
import request from 'request';

const HttpRequest =
({ name, contentType, url, method = 'GET', body = {}, httpHeaders = {}, cookies = {} }) => {
  // TODO: cookies
  return new Promise((resolve, reject) => {
    request({
      uri: url,
      method,
      form: body,
      headers: httpHeaders,
      jar: true, // remember cookies for future use
    }, (error, response, content) => {
      if (!error && response.statusCode === 200) {
        resolve({ name, contentType, content });
      } else {
        reject(error || response.statusCode);
      }
    });
  });
};

HttpRequest.propTypes = {
  name: PropTypes.string.isRequired,
  contentType: PropTypes.string.isRequired,
  url: PropTypes.string.isRequired,
  method: PropTypes.string,
  body: PropTypes.oneOfType([
    PropTypes.object,
    PropTypes.string,
  ]),
  httpHeaders: PropTypes.object,
  cookies: PropTypes.object,
};

export default HttpRequest;

import WebMiddle, { PropTypes } from 'webmiddle';
import request from 'request';

const HttpRequest =
({ name, contentType, url, method = 'GET', body = {}, httpHeaders = {}, cookies = {} }) => {
  // TODO: cookies
  return new Promise((resolve, reject) => {
    const isJsonBody = httpHeaders && httpHeaders['Content-Type'] === 'application/json';

    if (typeof body === 'object' && body !== null) {
      // body as string
      if (isJsonBody) {
        body = JSON.stringify(body);
      } else {
        // default: convert to form data
        body = Object.keys(body).reduce((list, prop) => {
          const value = body[prop];
          list.push(`${encodeURIComponent(prop)}=${encodeURIComponent(value)}`);
          return list;
        }, []).join('&');
      }
    }

    request({
      uri: url,
      method,
      body,
      headers: {
        // default form content type needs to be explicitly set
        // (request doesn't do it automatically when using the body property)
        'Content-Type': !isJsonBody ? 'application/x-www-form-urlencoded' : undefined,
        ...httpHeaders,
      },
      jar: true, // remember cookies for future use
    }, (error, response, content) => {
      if (!error && response.statusCode === 200) {
        resolve({ name, contentType, content });
      } else {
        reject(error || response.statusCode);
      }
    });
  });
};

HttpRequest.propTypes = {
  name: PropTypes.string.isRequired,
  contentType: PropTypes.string.isRequired,
  url: PropTypes.string.isRequired,
  method: PropTypes.string,
  body: PropTypes.oneOfType([
    PropTypes.object,
    PropTypes.string,
  ]),
  httpHeaders: PropTypes.object,
  cookies: PropTypes.object,
};

export default HttpRequest;

Fix request body conversion and passing


v1.1.3 - Provides a criticial bug fix related to persistent sessions

v1.2.1 - Provides a criticial bug fix related to persistent sessions

v1.2.1 - (1.2.0) Feature release: Custom themes, code folding and more.

Bump 'latest' channel to 1.2.1


/*global Package*/
Package.describe({
  name: 'dschnare:meteor-components-ioc-plugin',
  version: '0.2.0',
  // Brief, one-line summary of the package.
  summary: 'A plugin for Meteor Components that integrates IOC Containers.',
  // URL to the Git repository containing the source code for this package.
  git: 'https://github.com/dschnare/meteor-components-ioc-plugin',
  // By default, Meteor will default to using README.md for documentation.
  // To avoid submitting documentation, set this field to null.
  documentation: 'README.md'
});

Package.onUse(function(api) {
  api.versionsFrom('1.2.1');
  api.use('ecmascript', 'client');
  api.use('xamfoo:reactive-obj', 'client');
  api.use('ejson', 'client');
  api.use([
    'dschnare:meteor-components@0.5.0',
    'dschnare:ioc-container@0.2.0'
  ], 'client', { weak: true });
  api.addFiles('meteor-components-ioc-plugin.js', 'client');
  api.export('ComponentRootIoc', 'client');
});

Package.onTest(function(api) {
  api.use('ecmascript', 'client');
  api.use('tinytest', 'client');
  api.use('dschnare:meteor-components-ioc-plugin', 'client');
  api.addFiles('meteor-components-ioc-plugin-tests.js', 'client');
});

/*global Package*/
Package.describe({
  name: 'dschnare:meteor-components-ioc-plugin',
  version: '0.2.0',
  // Brief, one-line summary of the package.
  summary: 'A plugin for Meteor Components that integrates IOC Containers.',
  // URL to the Git repository containing the source code for this package.
  git: 'https://github.com/dschnare/meteor-components-ioc-plugin',
  // By default, Meteor will default to using README.md for documentation.
  // To avoid submitting documentation, set this field to null.
  documentation: 'README.md'
});

Package.onUse(function(api) {
  api.versionsFrom('1.2.1');
  api.use('ecmascript', 'client');
  api.use('xamfoo:reactive-obj@0.5.0', 'client');
  api.use('ejson', 'client');
  api.use([
    'dschnare:meteor-components@0.5.0',
    'dschnare:ioc-container@0.2.0'
  ], 'client', { weak: true });
  api.addFiles('meteor-components-ioc-plugin.js', 'client');
  api.export('ComponentRootIoc', 'client');
});

Package.onTest(function(api) {
  api.use('ecmascript', 'client');
  api.use('tinytest', 'client');
  api.use('dschnare:meteor-components-ioc-plugin', 'client');
  api.addFiles('meteor-components-ioc-plugin-tests.js', 'client');
});

Add version constraint for reactive-obj


import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, find } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import { later } from '@ember/runloop';

module('Integration | Component | loading bar', function(hooks) {
  setupRenderingTest(hooks);

  test('it renders', async function (assert) {
    const bar = '.bar';
    this.set('isLoading', true);

    // don't `await` this render as the internal task that never stops will keep this test running forever
    render(hbs`{{loading-bar isLoading=isLoading}}`);
    // we need to give the bar a moment to actually change state and we
    // cannot use wait here because the task actually keeps running forever preventing
    // any kind of settled state
    await later(async () => {
      assert.ok(find(bar).getAttribute('value').trim() > 0);
      this.set('isLoading', false);
      await later(() => {
        assert.equal(find(bar).getAttribute('value').trim(), 0);
      }, 2000);
    }, 1000);
  });
});

import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, find, waitFor } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import { later } from '@ember/runloop';

module('Integration | Component | loading bar', function(hooks) {
  setupRenderingTest(hooks);

  test('it renders', async function (assert) {
    const bar = '.bar';
    const emptyBar = '.bar[value="0"]';
    this.set('isLoading', true);

    // don't `await` this render as the internal task that never stops will keep this test running forever
    render(hbs`{{loading-bar isLoading=isLoading}}`);
    await waitFor(bar);
    // we need to give the bar a moment to actually change state and we
    // cannot use wait here because the task actually keeps running forever preventing
    // any kind of settled state
    await later(async () => {
      assert.ok(find(bar).getAttribute('value').trim() > 0);
      this.set('isLoading', false);
      await waitFor(emptyBar);
      assert.equal(find(bar).getAttribute('value').trim(), 0);
    }, 500);
  });
});

Improve async loading bar test


var camshaftReference = require('../../../src/analysis/camshaft-reference');

describe('src/analysis/camshaft-reference', function () {
  describe('.getSourceNamesForAnalysisType', function () {
    it('should return the source names for a given analyses type', function () {
      expect(camshaftReference.getSourceNamesForAnalysisType('source')).toEqual([]);
      expect(camshaftReference.getSourceNamesForAnalysisType('point-in-polygon')).toEqual(['points_source', 'polygons_source']);
      expect(camshaftReference.getSourceNamesForAnalysisType('trade-area')).toEqual(['source']);
    });
  });

  describe('.getParamNamesForAnalysisType', function () {
    it('should return the params names for a given analyses type', function () {
      expect(camshaftReference.getParamNamesForAnalysisType('source')).toEqual(['query']);
      expect(camshaftReference.getParamNamesForAnalysisType('point-in-polygon')).toEqual(['points_source', 'polygons_source']);
      expect(camshaftReference.getParamNamesForAnalysisType('trade-area')).toEqual([ 'source', 'kind', 'time', 'isolines', 'dissolved', 'provider' ]);
    });
  });
});

var camshaftReference = require('../../../src/analysis/camshaft-reference');

describe('src/analysis/camshaft-reference', function () {
  describe('.getSourceNamesForAnalysisType', function () {
    it('should return the source names for a given analyses type', function () {
      expect(camshaftReference.getSourceNamesForAnalysisType('source')).toEqual([]);
      expect(camshaftReference.getSourceNamesForAnalysisType('point-in-polygon')).toEqual(['points_source', 'polygons_source']);
      expect(camshaftReference.getSourceNamesForAnalysisType('trade-area')).toEqual(['source']);
    });
  });

  describe('.getParamNamesForAnalysisType', function () {
    it('should return the params names for a given analyses type', function () {
      expect(camshaftReference.getParamNamesForAnalysisType('source')).toEqual(['query']);
      expect(camshaftReference.getParamNamesForAnalysisType('point-in-polygon')).toEqual(['points_source', 'polygons_source']);
      expect(camshaftReference.getParamNamesForAnalysisType('trade-area')).toEqual([ 'source', 'kind', 'time', 'isolines', 'dissolved' ]);
    });
  });
});

Remove expected/optional param as it was removed from reference


/**
 * @module client/main
 */
'use strict';

var app = require('./app');
require('angular');

/**
 * Each 'index' generated via grunt process dynamically includes all browserify common-js modules
 * in js bundle
 */
require('./controllers/index');
require('./services/index');
require('./router');

var io = require('./lib/socket.io');
var socket = io.connect('http://localhost:3000');

socket.on('postSequence', function (data) {
  console.log('postSequence: ', data);
});

/**
 * Bundle of all templates to be attached to $templateCache generated by grunt process
 */
var views = require('./build/views/viewBundle');

/**
 * Pre-load template cache with compiled jade templates included in JS bundle
 */
app.run(['$rootScope',
  '$templateCache',
  function (
    $rootScope,
    $templateCache) {
    Object.keys(views.Templates).forEach(function (viewName) {
      $templateCache.put(viewName, views.Templates[viewName]());
    });
  }
]);

/**
 * DOM-ready event, start app
 */
angular.element(document).ready(function () {
  angular.bootstrap(document, ['app']);
});

/**
 * @module client/main
 */
'use strict';

var app = require('app');
require('angular');

/**
 * Each 'index' generated via grunt process dynamically includes all browserify common-js modules
 * in js bundle
 */
require('./controllers/index');
require('./services/index');
require('./router');

var io = require('./lib/socket.io');
var socket = io.connect('http://localhost:3000');

socket.on('postSequence', function (data) {
  console.log('postSequence: ', data);
});

/**
 * Bundle of all templates to be attached to $templateCache generated by grunt process
 */
var views = require('./build/views/viewBundle');

/**
 * Pre-load template cache with compiled jade templates included in JS bundle
 */
app.run(['$rootScope',
  '$templateCache',
  function (
    $rootScope,
    $templateCache) {
    Object.keys(views.Templates).forEach(function (viewName) {
      $templateCache.put(viewName, views.Templates[viewName]());
    });
  }
]);

/**
 * DOM-ready event, start app
 */
angular.element(document).ready(function () {
  angular.bootstrap(document, ['app']);
});

Change argument to require from relative to global path


'use babel';

import completions from '../data/completions';

class CurryProvider {
  constructor() {
    this.scopeSelector = '.source.curry';
    this.disableForScopeSelector = '.source.curry .comment';

    this.suggestionPriority = 2;
    this.filterSuggestions = true;

    this.acpTypes = new Map([['types', 'type'], ['constructors', 'tag'], ['functions', 'function']]);
  }

  getSuggestions({prefix}) {
    if (prefix) {
      const suggestions = [];

      for (const module of completions) {
        for (const [key, type] of this.acpTypes) {
          const createSugg = this.createSuggestion.bind(null, module.name, type);

          const matches = module[key].map(createSugg);

          suggestions.push(...matches);
        }
      }

      return suggestions;
    }

    return null;
  }

  createSuggestion(module, type, suggestion) {
    return {
      text: suggestion.name,
      description: suggestion.description,
      type: type,
      leftLabel: module,
      rightLabel: suggestion.typeSig
    };
  }
}

export default new CurryProvider();

'use babel';

import completions from '../data/completions';

class CurryProvider {
  constructor() {
    this.scopeSelector = '.source.curry';
    this.disableForScopeSelector = '.source.curry .comment';

    this.suggestionPriority = 2;
    this.filterSuggestions = true;

    this.acpTypes = new Map([['types', 'type'], ['constructors', 'tag'], ['functions', 'function']]);
  }

  getSuggestions({prefix}) {
    if (!prefix) {
      return null;
    }

    const suggestions = [];

    for (const module of completions) {
      for (const [key, type] of this.acpTypes) {
        const createSugg = this.createSuggestion.bind(null, module.name, type);

        const matches = module[key].map(createSugg);

        suggestions.push(...matches);
      }
    }

    return suggestions;
  }

  createSuggestion(module, type, suggestion) {
    return {
      text: suggestion.name,
      description: suggestion.description,
      type: type,
      leftLabel: module,
      rightLabel: suggestion.typeSig
    };
  }
}

export default new CurryProvider();

Return null as early as possible if the prefix is invalid


var exec = require('cordova/exec');

var PLUGIN_NAME = 'SystemSound';

var SystemSound = {
    playSound: function(cb) {
        exec(cb, null, PLUGIN_NAME, 'playSound', []);
    }
};

module.exports = SystemSound;

var exec = require('cordova/exec');

var PLUGIN_NAME = 'SystemSound';

var SystemSound = {
    playSound: function(cb) {
        exec(cb, null, PLUGIN_NAME, 'playSound', [phrase]);
    }
};

module.exports = SystemSound;

Fix exec function with no argument


import { Meteor } from 'meteor/meteor';

// In the client and server entry points, we make sure the
// bundler loads the correct thing. Here, we just need to
// make sure that we require the right one.
export default function getClientStreamClass() {
  // The static analyzer of the bundler specifically looks
  // for direct calls to 'require', so it won't treat the
  // below calls as a request to include that module.
  const notRequire = require;

  if (Meteor.isClient) {
    return notRequire('../client/stream_client_sockjs').default;
  } else {
    /* Meteor.isServer */
    return notRequire('../server/stream_client_nodejs').default;
  }
}

import { Meteor } from 'meteor/meteor';

// In the client and server entry points, we make sure the
// bundler loads the correct thing. Here, we just need to
// make sure that we require the right one.
export default function getClientStreamClass() {
  // The static analyzer of the bundler specifically looks
  // for static calls to 'require', so it won't treat the
  // below calls as a request to include that module.
  //
  // That means stream_client_nodejs won't be included on
  // the client, as desired.
  const modulePath = Meteor.isClient
    ? '../client/stream_client_sockjs'
    : '../server/stream_client_nodejs';

  return require(modulePath).default;
}

Switch to more concise require as suggested by Ben


'use strict';

var Harmonia = require('harmonia');

// Create a new server that listens to a given queue
var harmonia = new Harmonia.Server('rpc');

harmonia.route({
  method : 'math.add',
  module : './math/add.js'
});

harmonia.route({
  method : 'math.subtract',
  module : './math/subtract.js',
});

harmonia.route({
  method : 'math.divide',
  module : './math/divide.js',
});

// Start the server
harmonia.listen('amqp://localhost');

// Make some requests using the Harmonia client
var client = Harmonia.Client.createClient('amqp://localhost', 'request', 'math.add');

client.call('math.add', { x : 15, y : 5 })
  .then(function(result) {
    console.log('add', result.content);
  });

client.call('math.subtract', { x : 15, y : 5 })
  .then(function(result) {
    console.log('subtract', result.content);
  });

client.call('math.divide', { x : 15, y : 5 })
  .then(function(result) {
    console.log('divide', result.content);
  });

'use strict';

var Harmonia = require('harmonia');

// Create a new server that listens to a given queue
var harmonia = new Harmonia.Server('rpc');

harmonia.route({
  method : 'math.add',
  module : './math/add.js'
});

harmonia.route({
  method : 'math.subtract',
  module : './math/subtract.js',
});

harmonia.route({
  method : 'math.divide',
  module : './math/divide.js',
});

// Start the server
harmonia.listen('amqp://localhost');

// Make some requests using the Harmonia client
var client = Harmonia.Client.createClient('amqp://localhost', 'request');

client.then(function(client) {
  client.call('math.add', { x : 15, y : 5 })
  .then(function(result) {
    console.log('add', result.content);
  });

  client.call('math.subtract', { x : 15, y : 5 })
  .then(function(result) {
    console.log('subtract', result.content);
  });

  client.call('math.divide', { x : 15, y : 5 })
  .then(function(result) {
    console.log('divide', result.content);
  });
});

Update Harmonia client example to reflect changes in 0.4


var gulp            = require('gulp'),
    csslint         = require('gulp-csslint'),
    jshint          = require('gulp-jshint'),
    rename          = require('gulp-rename'),
    uglify          = require('gulp-uglify'),
    autoprefixer    = require('gulp-autoprefixer'),
    minifyCSS       = require('gulp-minify-css'),
    stylish         = require('jshint-stylish');

gulp.task('csslint', function () {
    return gulp.src('src/imagelightbox.css')
        .pipe(csslint('.csslintrc'))
        .pipe(csslint.reporter())
});

gulp.task('minify:css', function () {
    return gulp.src('src/imagelightbox.css')
        .pipe(autoprefixer({
            browsers: ['last 2 versions', 'ie >= 7', 'Firefox ESR', 'Android >= 2.3'],
            cascade: false
        }))
        .pipe(minifyCSS())
        .pipe(rename('imagelightbox.min.css'))
        .pipe(gulp.dest('dist/'));
});

gulp.task('jshint', function () {
    return gulp.src('src/imagelightbox.js')
        .pipe(jshint())
        .pipe(jshint.reporter(stylish));
});

gulp.task('minify:js', function () {
    return gulp.src('src/imagelightbox.js')
        .pipe(uglify())
        .pipe(rename('imagelightbox.min.js'))
        .pipe(gulp.dest('dist/'));
});

gulp.task('default', ['csslint', 'minify:css', 'jshint', 'minify:js']);

var gulp = require('gulp'),
    csslint = require('gulp-csslint'),
    jshint = require('gulp-jshint'),
    rename = require('gulp-rename'),
    uglify = require('gulp-uglify'),
    autoprefixer = require('gulp-autoprefixer'),
    minifyCSS = require('gulp-minify-css'),
    stylish = require('jshint-stylish');

gulp.task('csslint', function () {
    return gulp.src('src/imagelightbox.css')
        .pipe(csslint('.csslintrc'))
        .pipe(csslint.reporter())
});

gulp.task('minify:css', function () {
    return gulp.src('src/imagelightbox.css')
        .pipe(autoprefixer({
            browsers: ['last 2 versions', 'ie >= 7', 'Firefox ESR', 'Android >= 2.3'],
            cascade: false,
        }))
        .pipe(minifyCSS())
        .pipe(rename('imagelightbox.min.css'))
        .pipe(gulp.dest('dist/'));
});

gulp.task('jshint', function () {
    return gulp.src('src/imagelightbox.js')
        .pipe(jshint())
        .pipe(jshint.reporter(stylish));
});

gulp.task('minify:js', function () {
    return gulp.src('src/imagelightbox.js')
        .pipe(uglify())
        .pipe(rename('imagelightbox.min.js'))
        .pipe(gulp.dest('dist/'));
});

gulp.task('default', ['csslint', 'minify:css', 'jshint', 'minify:js']);

Remove code alignment, add trailing comma, add EOF newline