var ImagePreloader = require('./ImagePreloader/');
var Overlay = require('./Overlay/');

function QueryLoader(element, options) {
    'use strict';
    this.element = element;
    this.options = options;

    //The default options
    this.defaultOptions = {
        onComplete: function() {},
        onLoadComplete: function() {},
        backgroundColor: "#000",
        barColor: "#fff",
        overlayId: 'qLoverlay',
        barHeight: 1,
        percentage: false,
        deepSearch: true,
        completeAnimation: "fade",
        minimumTime: 500
    };

    //children
    this.overlay = null;

    if (element !== null) {
        this.init();
    }
}

QueryLoader.prototype.init = function () {
    'use strict';
    this.extend(this.options, this.defaultOptions);
};

QueryLoader.prototype.extend = function (base, adding) {
    'use strict';
    if (typeof base === "undefined") {
        base = {};
    }

    for (var property in adding) {
        if (adding.hasOwnProperty(property)) {
            base[property] = adding[property];
        }
    }

    return base;
};

QueryLoader.prototype.createOverlay = function () {
    'use strict';
    this.overlay = new Overlay(this.element);
};

module.exports = QueryLoader;

var ImagePreloader = require('./ImagePreloader/');
var Overlay = require('./Overlay/');

function QueryLoader(element, options) {
    'use strict';
    this.element = element;
    this.options = options;

    //The default options
    this.defaultOptions = {
        onComplete: function() {},
        onLoadComplete: function() {},
        backgroundColor: "#000",
        barColor: "#fff",
        overlayId: 'qLoverlay',
        barHeight: 1,
        percentage: false,
        deepSearch: true,
        completeAnimation: "fade",
        minimumTime: 500
    };

    //children
    this.overlay = null;
    this.preloader = null;

    if (element !== null) {
        this.init();
    }
}

QueryLoader.prototype.init = function () {
    'use strict';
    this.extend(this.options, this.defaultOptions);
};

QueryLoader.prototype.extend = function (base, adding) {
    'use strict';
    if (typeof base === "undefined") {
        base = {};
    }

    for (var property in adding) {
        if (adding.hasOwnProperty(property)) {
            base[property] = adding[property];
        }
    }

    return base;
};

QueryLoader.prototype.createOverlay = function () {
    'use strict';
    this.overlay = new Overlay(this.element);
};

QueryLoader.prototype.createPreloader = function () {
    'use strict';
    this.preloader = new ImagePreloader(this.element);
};

module.exports = QueryLoader;

Create preloader object and set property


import * as tt from 'typescript-definition-tester'

describe('TypeScript definitions', function () {
  it('should compile against index.d.ts', (done) => {
    tt.compileDirectory(
      __dirname + '/typescript',
      fileName => fileName.match(/\.ts$/),
      () => done()
    )
  })
})

import * as ts from 'typescript'
import * as tt from 'typescript-definition-tester'

describe('TypeScript definitions', function () {
  it('should compile against index.d.ts', (done) => {
    tt.compileDirectory(
      __dirname + '/typescript',
      fileName => fileName.match(/\.ts$/),
      // This matches what's in tsconfig.json
      { target: ts.ScriptTarget.ES2017 },
      (error) => done(error)
    )
  })
})

Fix TypeScript tests so they fail for errors :)


/**
 * `Message` constructor.
 *
 * @api protected
 */
function Message(queue, message, headers, deliveryInfo) {
  // Crane uses slash ('/') separators rather than period ('.')
  this.topic = deliveryInfo.routingKey.replace(/\./g, '/');
  this.headers = {};
  if (deliveryInfo.contentType) { headers['content-type'] = deliveryInfo.contentType; }
  // TODO: only set body if it has been parsed, otherwise set `data`
  this.body = message;

  this._q = queue;
}

/**
 * Acknowledge message.
 *
 * Examples:
 *
 *     msg.ack();
 *
 * @api public
 */
Message.prototype.ack = function() {
  this._q.shift();
}

/**
 * Expose `Message`.
 */
module.exports = Message;

/**
 * `Message` constructor.
 *
 * @api protected
 */
function Message(queue, message, headers, deliveryInfo) {
  // Crane uses slash ('/') separators rather than period ('.')
  this.topic = deliveryInfo.routingKey.replace(/\./g, '/');
  this.headers = headers;
  if (deliveryInfo.contentType) { this.headers['content-type'] = deliveryInfo.contentType; }
  // TODO: only set body if it has been parsed, otherwise set `data`
  this.body = message;

  this._q = queue;
}

/**
 * Acknowledge message.
 *
 * Examples:
 *
 *     msg.ack();
 *
 * @api public
 */
Message.prototype.ack = function() {
  this._q.shift();
}

/**
 * Expose `Message`.
 */
module.exports = Message;

Fix to set headers correctly.


import {json} from "body-parser";
import {Router} from "express";

import * as validate from "./validate-middleware";
import * as convert from "./convert";

export default function convexpress (options) {
    const router = Router().use(json());
    router.swagger = {
        swagger: "2.0",
        host: options.host,
        basePath: options.basePath || "/",
        info: options.info,
        consumes: ["application/json"],
        produces: ["application/json"],
        paths: {}
    };
    router.convroute = function (route) {
        // Attach route to router
        router[route.method](
            route.path,
            [validate.middleware(route.parameters)].concat(route.middleware || []),
            route.handler
        );
        // Update the swagger document
        const swaggerPath = convert.path(route.path);
        router.swagger.paths[swaggerPath] = {
            ...router.swagger.paths[swaggerPath],
            [route.method]: {
                description: route.description,
                parameters: convert.parameters(route.parameters),
                responses: {
                    ...route.responses,
                    ...validate.responses
                }
            }
        };
        // Allow method chaining
        return router;
    };
    router.serveSwagger = function (path = "/swagger.json") {
        router.get(path, (req, res) => res.status(200).send(router.swagger));
    };
    return router;
}

import {json} from "body-parser";
import {Router} from "express";

import * as validate from "./validate-middleware";
import * as convert from "./convert";

export default function convexpress (options) {
    const router = Router().use(json());
    router.swagger = {
        swagger: "2.0",
        host: options.host,
        basePath: options.basePath || "/",
        info: options.info,
        consumes: ["application/json"],
        produces: ["application/json"],
        paths: {}
    };
    router.convroute = function (route) {
        // Attach route to router
        router[route.method](
            route.path,
            [validate.middleware(route.parameters)].concat(route.middleware || []),
            route.handler
        );
        // Update the swagger document
        const swaggerPath = convert.path(route.path);
        router.swagger.paths[swaggerPath] = {
            ...router.swagger.paths[swaggerPath],
            [route.method]: {
                description: route.description,
                parameters: convert.parameters(route.parameters),
                responses: {
                    ...route.responses,
                    ...validate.responses
                }
            }
        };
        // Allow method chaining
        return router;
    };
    router.serveSwagger = function (path = "/swagger.json") {
        router.get(path, (req, res) => res.status(200).send(router.swagger));
        return router;
    };
    return router;
}

Allow method chaining after serveSwagger


// karaoke-forever string to artist/title parser defaults
module.exports = {
  // regex or string; artist/song get split around this match (default='-')
  delimiter: '-',
  // bool; whether artist is on left side of delimiter (default=true)
  artistFirst: true,
  // string; override Artist for songs in this file's scope (default='')
  artist: '',

  // each stage is configured with regexes and/or strings and
  // simply removes matches by default (repl=''); use an array
  // to pass a replacement param/string, e.g. [find, repl]
  replacements: {
    // applied to input string before split to Artist/Title
    preSplit: [
      // remove non-digits follwed by digits
      /[\D]+[\d]+/i,
      // remove digits between non-word characters
      /\W*\d+\W*/i,
      // remove text between (), [], or {}
      /[([{].*[)\]}]/ig,
    ],
    // applied to both Artist and Title after split
    postSplit: [
      // correct for "..., The"
      [/(.*)(, The)$/i, 'The $1'],
    ],
    // applied to Artist after split
    artist: [
      // Last, First [Middle] -> First [Middle] Last
      [/^(\w+?), ?(\w* ?\w*.?)$/ig, '$2 $1'],
    ],
    // applied to Title after split
    title: [
    ],
  }
}

// karaoke-forever string to artist/title parser defaults
module.exports = {
  // regex or string; artist/song get split around this match (default='-')
  delimiter: '-',
  // bool; whether artist is on left side of delimiter (default=true)
  artistFirst: true,
  // string; override Artist for songs in this file's scope (default='')
  artist: '',

  // each stage is configured with regexes and/or strings and
  // simply removes matches by default (repl=''); use an array
  // to pass a replacement param/string, e.g. [find, repl]
  replacements: {
    // applied to input string before split to Artist/Title
    preSplit: [
      // at least 2 word chars followed by at least 3 digits
      /\D{2,}[^\s]\d{3,}/i,
      // track numbers
      /^[\d\-.\s]+/,
      // remove text between (), [], or {}
      /[([{].*[)\]}]/ig,
    ],
    // applied to both Artist and Title after split
    postSplit: [
      // correct for "..., The"
      [/(.*)(, The)$/i, 'The $1'],
    ],
    // applied to Artist after split
    artist: [
      // Last, First [Middle] -> First [Middle] Last
      [/^(\w+?), ?(\w* ?\w*.?)$/ig, '$2 $1'],
    ],
    // applied to Title after split
    title: [
    ],
  }
}

Improve filename parser regex (hopefully)


'use strict';

var assert = require('assert');
var data = require('../src/data'),
  vlfield = require('../src/field');

describe('getUrl', function() {
  //TODO write tests for getUrl
});

describe('getStats', function() {
  //TODO write more tests
});

Remove tests for things that don’t exist


(function(angular) {
    'use strict';
    var SignalRService = function ($window, url) {
        this.jQuery = $window.jQuery;
        this._url = url;
    };

    SignalRService.prototype.getHub = function (hubName) {
        var hub = this.jQuery.connection[hubName];
        this.jQuery.connection.hub.url = this._url;
        return hub;
    };

    SignalRService.prototype.start = function () {
        this.jQuery.connection.hub.start();
        return this;
    };

    var SignalRProvider = function () {
        this._url = '/signalr';
    };

    SignalRProvider.prototype.url = function (value) {
        if (angular.isUndefined(value)) {
            return this._url;
        }
        this._url = value;
        return this;
    };

    SignalRProvider.prototype.$get = function ($window) {
        return new SignalRService($window, this.url());
    };

    angular.module('eehSignalR', []).provider('eehSignalR', SignalRProvider);
}(angular));

(function (angular) {
    'use strict';
    var SignalRService = function (jQuery) {
        this.jQuery = jQuery;
    };

    SignalRService.prototype.getHub = function (hubName) {
        return this.jQuery.connection[hubName];
    };

    var SignalRProvider = function () {
        this._url = '/signalr';
        this._proxies = {};
    };

    SignalRProvider.prototype.url = function (value) {
        if (angular.isUndefined(value)) {
            return this._url;
        }
        this._url = value;
        return this;
    };

    SignalRProvider.prototype.proxies = function (value) {
        if (angular.isUndefined(value)) {
            return this._proxies;
        }
        this._proxies = value;
        return this;
    };

    SignalRProvider.prototype.$get = function ($rootScope, $window) {
        var jQuery = $window.jQuery;
        var self = this;
        angular.forEach(jQuery.connection.hub.proxies, function (proxy, proxyName) {
            if (!self._proxies.hasOwnProperty(proxyName)) {
                return;
            }
            angular.forEach(self._proxies[proxyName], function (methodName) {
                proxy.client[methodName] = function () {
                    var args = [];
                    angular.forEach(arguments, function (argument) {
                        args.push(argument);
                    });
                    $rootScope.$broadcast('eehSignalR:' + methodName, args);
                    $rootScope.$apply();
                };
            });
        });
        jQuery.connection.hub.url = this.url();
        jQuery.connection.hub.start();
        return new SignalRService(jQuery);
    };

    angular.module('eehSignalR', []).provider('eehSignalR', SignalRProvider);
}(angular));

Add support for native Angular event broadcasting


import {get} from 'utils/ajax'

import {fixtureJSONFeed} from 'src/status/fixtures'
// TODO: remove async/await & object return, uncomment get(url) when proxy route implemented
export const fetchJSONFeed = async url => {
  return await {data: fixtureJSONFeed}
  // get(url)
}

// import {get} from 'utils/ajax'

import {fixtureJSONFeed} from 'src/status/fixtures'
// TODO: remove async/await & object return, uncomment get(url) when proxy route implemented
// export const fetchJSONFeed = async url => {
export const fetchJSONFeed = async () => {
  return await {data: fixtureJSONFeed}
  // get(url)
}

Comment out linter errors temporarily


var xmlHttp = new XMLHttpRequest();

xmlHttp.onreadystatechange = function() {
  if (xmlHttp.readyState === 4) {
    var releasesData = JSON.parse(xmlHttp.responseText);
    var totalDownloads = releasesData.reduce(
      function (total, current) {
        return total + current.assets[0].download_count;
      },
      145579 // pre-GitHub download total
    );

    document.getElementById("download-count").innerText = totalDownloads + " downloads";
  }
};

xmlHttp.open("GET", "https://api.github.com/repos/LiveSplit/LiveSplit/releases");
xmlHttp.send();

var totalDownloads = 145579; // pre-GitHub download total

var handleApiResponse = function() {
  if (xmlHttp.readyState === 4) {
    var releasesData = JSON.parse(xmlHttp.responseText);
    totalDownloads += releasesData.reduce(
      function (total, current) {
        return total + current.assets[0].download_count;
      },
      0
    );

    var linkHeader = xmlHttp.getResponseHeader("Link");
    var nextUrlMatch = linkHeader.match(/<(.+)>; rel="next"/);
    if (nextUrlMatch) {
      xmlHttp = new XMLHttpRequest();
      xmlHttp.onreadystatechange = handleApiResponse;
      xmlHttp.open("GET", nextUrlMatch[1]);
      xmlHttp.send();
    } else {
      document.getElementById("download-count").innerText = totalDownloads + " downloads";
    }
  }
};

var xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = handleApiResponse;
xmlHttp.open("GET", "https://api.github.com/repos/LiveSplit/LiveSplit/releases");
xmlHttp.send();

Fix download count not including more than 30 releases


import openDevToolsWindow from './openWindow';

export default function createMenu() {
  const menus = [
    { id: 'devtools-left', title: 'To left' },
    { id: 'devtools-right', title: 'To right' },
    { id: 'devtools-bottom', title: 'To bottom' },
    { id: 'devtools-panel', title: 'Open in a chrome panel (enable in Chrome settings)' },
    { id: 'devtools-remote', title: 'Open Remote DevTools' }
  ];

  let shortcuts = {};
  chrome.commands.getAll(commands => {
    commands.forEach(({ name, shortcut }) => {
      shortcuts[name] = shortcut;
    });
  });

  menus.forEach(({ id, title }) => {
    chrome.contextMenus.create({
      id: id,
      title: title + (shortcuts[id] ? ' (' + shortcuts[id] + ')' : ''),
      contexts: ['all']
    });
  });

  chrome.contextMenus.onClicked.addListener(({ menuItemId }) => {
    openDevToolsWindow(menuItemId);
  });
}

import openDevToolsWindow from './openWindow';

export default function createMenu() {
  const menus = [
    { id: 'devtools-left', title: 'To left' },
    { id: 'devtools-right', title: 'To right' },
    { id: 'devtools-bottom', title: 'To bottom' },
    { id: 'devtools-panel', title: 'Open in a chrome panel (enable in Chrome settings)' },
    { id: 'devtools-remote', title: 'Open Remote DevTools' }
  ];

  let shortcuts = {};
  chrome.commands.getAll(commands => {
    commands.forEach(({ name, shortcut }) => {
      shortcuts[name] = shortcut;
    });
  });

  menus.forEach(({ id, title }) => {
    chrome.contextMenus.create({
      id: id,
      title: title + (shortcuts[id] ? ' (' + shortcuts[id] + ')' : ''),
      contexts: ['all']
    });
  });
}

chrome.contextMenus.onClicked.addListener(({ menuItemId }) => {
  openDevToolsWindow(menuItemId);
});

Fix context menu listener when event page becomes inactive


angular.module('proxtop').controller('MainController', ['$scope', 'ipcManager', '$state', 'notification', '$mdToast', '$translate', 'settings', '$mdDialog', 'open', '$window', 'debounce',
    function($scope, ipcManager, $state, notification, $mdToast, $translate, settings, $mdDialog, open, $window, debounce) {
        const ipc = ipcManager($scope);
        ipc.once('check-login', function(ev, result) {
            if(result) {
                ipc.send('watchlist-update');
                $state.go('profile');
            } else {
                $state.go('login');
            }
        });

        $translate.use(settings.get('general').language);
        ipc.send('check-login');
}]);

angular.module('proxtop').controller('MainController', ['$scope', 'ipcManager', '$state', '$mdToast', '$translate', 'settings',
    function($scope, ipcManager, $state, $mdToast, $translate, settings) {
        const ipc = ipcManager($scope);
        ipc.once('check-login', function(ev, result) {
            if(result) {
                ipc.send('watchlist-update');
                $state.go('profile');
            } else {
                $state.go('login');
            }
        });

        $translate.use(settings.get('general').language);
        ipc.send('check-login');
}]);

Fix error in last commit


jQuery(function() {
  jQuery('body').hide().show();
});

jQuery(function($) {
    if (/chrom(e|ium)/.test(navigator.userAgent.toLowerCase())) {
        $('body').css('opacity', '1.0')
    }
})

Use another jquery for chrome font bug


const sheet = document.createElement('style');

sheet.innerHTML = `
@media screen {
  #viewSourceHeader.📖-view-source-header {
    display: block !important;
  }
}
.📖-view-source-header {
  display: none;
  top: 0;
  left: 0;
  right: unset;
  bottom: unset;
  padding: 1em;
  background: transparent;
  box-shadow: none;
}
.📖-logo {
  width: 32px;
  height: 32px;
  background: url(http://evanbrooks.info/bindery/assets/logo.svg) no-repeat;
  background-size: contain;
  vertical-align: middle;
  margin-right: 0.8rem;
  display: inline-block;
}
`;

const segments = window.location.pathname.split('/');
let id = segments.pop();
if (id === '') id = segments.pop();

const buttons = document.createElement('div');
buttons.classList.add('📖-view-source-header');
buttons.classList.add('📖-controls');
buttons.id = 'viewSourceHeader';
buttons.innerHTML = `

  View Source ↗
`;

document.body.appendChild(buttons);
document.head.appendChild(sheet);

const sheet = document.createElement('style');

sheet.innerHTML = `
@media screen {
  #viewSourceHeader.📖-view-source-header {
    display: block !important;
  }
}
.📖-view-source-header {
  display: none;
  top: 0;
  left: 0;
  right: unset;
  bottom: unset;
  padding: 1em;
  background: transparent;
  box-shadow: none;
}
`;

const segments = window.location.pathname.split('/');
let id = segments.pop();
if (id === '') id = segments.pop();

const buttons = document.createElement('div');
buttons.classList.add('📖-view-source-header');
buttons.classList.add('📖-controls');
buttons.id = 'viewSourceHeader';
buttons.innerHTML = `
  View Source ↗
`;

document.head.appendChild(sheet);
document.body.appendChild(buttons);

Remove logo from viewSource button


class GelatoRouter extends Backbone.Router {

  execute(callback, args, name) {
    if (this.page) {
      this.page.remove();
    }

    this.trigger('navigate:before', args, name);
    callback && callback.apply(this, args);
    this.trigger('navigate:after', args, name);
  }

  getQueryString() {
    const location = window.location;

    let query = '';
    if (location.hash.length) {
      query = location.hash.substring(location.hash.indexOf('?') + 1);
    } else {
      query = location.search.substring(1);
    }

    const params = query.split('&');
    for (let i = 0; i < params.length; i++) {
      const pair = params[i].split('=');

      if (pair[0] === name) {
        return pair[1];
      }
    }

    return null;
  }

  isRunning() {
    return Backbone.History.started;
  }

  start(options) {
    return Backbone.history.start(options);
  }

}

Gelato = Gelato || {};

Gelato.Router = GelatoRouter;

class GelatoRouter extends Backbone.Router {

  execute(callback, args, name) {
    if (this.page) {
      this.page.remove();
    }

    this.trigger('navigate:before', args, name);
    callback && callback.apply(this, args);
    this.trigger('navigate:after', args, name);
  }

  getQueryString(name) {
    const location = window.location;

    let query = '';
    if (location.hash.length) {
      query = location.hash.substring(location.hash.indexOf('?') + 1);
    } else {
      query = location.search.substring(1);
    }

    const params = query.split('&');
    for (let i = 0; i < params.length; i++) {
      const pair = params[i].split('=');

      if (pair[0] === name) {
        return pair[1];
      }
    }

    return null;
  }

  isRunning() {
    return Backbone.History.started;
  }

  start(options) {
    return Backbone.history.start(options);
  }

}

Gelato = Gelato || {};

Gelato.Router = GelatoRouter;

Include missing argument in router query string method


import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
import { find, fillIn } from 'ember-native-dom-helpers';
import { skip } from 'qunit';

moduleForComponent('medium-editor', 'Integration | Component | medium editor', {
  integration: true
});

test('it renders', function(assert) {
  assert.expect(1);

  this.render(hbs`{{medium-editor}}`);
  assert.equal(find('.medium-editor-element').innerHTML, '');
});

test('it sets initial value and updates it', function(assert) {
  assert.expect(1);

  this.render(hbs`{{medium-editor "<h1>Value</h1>"}}`);

  assert.equal(find('h1').innerHTML, 'Value');

  skip('value didUpdateAttrs test', function() {
    this.set('value', '
New value
');
    assert.equal(this.$('h2').text(), 'New value');
  });
});

skip('onChange action', function() {
  test('it should trigger onChange action when content changed', async function(assert) {
    assert.expect(1);

    this.set('onChange', (actual) => {
      assert.equal(actual, '
typed value
');
    });
    this.render(hbs`{{medium-editor onChange=(action onChange)}}`);

    await fillIn('.medium-editor-element', '
typed value
');
  });
});

import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
import { find } from 'ember-native-dom-helpers';
import MediumEditor from 'medium-editor';

const meClass = '.medium-editor-element';

moduleForComponent('medium-editor', 'Integration | Component | medium editor', {
  integration: true
});

test('it renders', function(assert) {
  assert.expect(1);

  this.render(hbs`{{medium-editor}}`);
  assert.equal(find(meClass).innerHTML, '');
});

test('it sets initial value and updates it', function(assert) {
  assert.expect(2);

  this.set('tempValue', '
Value
');
  this.render(hbs`{{medium-editor tempValue}}`);

  assert.equal(find('h1').innerHTML, 'Value');

  this.set('tempValue', '
New value
');
  assert.equal(find('h2').innerHTML, 'New value');
});

test('it should trigger onChange action when content changed', function(assert) {
  assert.expect(1);

  this.set('onChange', (actual) => {
    assert.equal(actual, '
typed value
');
  });
  this.render(hbs`{{medium-editor onChange=(action onChange)}}`);

  let editor = MediumEditor.getEditorFromElement(find(meClass));
  editor.setContent('
typed value
');
});

Update tests to test onChange and value.


//
// Reusable, domain-specific prop types
//
import PropTypes from "prop-types"

// could be an enum if this is a fixed set of strings?
export const category = PropTypes.string

export const user = PropTypes.shape({
  given_name: PropTypes.string,
  online_at: PropTypes.number,
  is_facilitator: PropTypes.boolean,
})

export const users = PropTypes.arrayOf(PropTypes.object)

export const retroChannel = PropTypes.shape({
  on: PropTypes.func,
  push: PropTypes.func,
})

export const idea = PropTypes.shape({
  id: PropTypes.number,
  user: PropTypes.object,
  body: PropTypes.string,
  category,
})

export const stage = PropTypes.oneOf([
  "prime-directive",
  "idea-generation",
  "voting",
  "action-items",
  "closed",
])

export const ideas = PropTypes.arrayOf(idea)

//
// Reusable, domain-specific prop types
//
import PropTypes from "prop-types"

// could be an enum if this is a fixed set of strings?
export const category = PropTypes.string

export const user = PropTypes.shape({
  given_name: PropTypes.string,
  online_at: PropTypes.number,
  is_facilitator: PropTypes.boolean,
})

export const users = PropTypes.arrayOf(PropTypes.object)

export const retroChannel = PropTypes.shape({
  on: PropTypes.func,
  push: PropTypes.func,
})

export const idea = PropTypes.shape({
  id: PropTypes.number,
  user: PropTypes.object,
  body: PropTypes.string,
  category,
})

export const stage = PropTypes.oneOf([
  "prime-directive",
  "idea-generation",
  "voting",
  "action-items",
  "closed",
])

export const votes = PropTypes.arrayOf(PropTypes.object)

export const ideas = PropTypes.arrayOf(idea)

Add 'votes' to application's proptypes


const configuration = require('../config')
const fetch = require('node-fetch')
const { URL } = require('url')

async function checkDeliveryService () {
  const config = configuration.get()
  const { deliveryServiceURL, bot } = config
  if (!deliveryServiceURL) {
    return
  }
  const url = new URL(deliveryServiceURL)
  const { hostname, port } = url
  const res = await fetch(`http://${hostname}:${port}/health`, {
    headers: {
      Authorization: `Bot ${bot.token}`
    }
  })
  if (!res.ok) {
    throw new Error(`Bad status code ${res.status}`)
  }
}

module.exports = checkDeliveryService

const configuration = require('../config')
const fetch = require('node-fetch')
const { URL } = require('url')

async function checkDeliveryService () {
  const config = configuration.get()
  const { deliveryServiceURL } = config
  if (!deliveryServiceURL) {
    return
  }
  const url = new URL(deliveryServiceURL)
  const { hostname, port } = url
  const res = await fetch(`http://${hostname}:${port}/health`)
  if (!res.ok) {
    throw new Error(`Bad status code ${res.status}`)
  }
}

module.exports = checkDeliveryService

Remove auth from delivery service health check


function renderThumbNail( data, type, row, meta ) {

	if(! window.dogList) {
		window.dogList = {};
		window.dogList.snapshotUrl = '${SNAPSHOTURL}';
		window.dogList.previewfileKey = '${PREVIEWKEY}';
		window.dogList.renderSnapshotThumbnail = function(data, type, row, meta) {
			var html = '
';
			$.each(data, function(index, value) {
				html += '
';
				html += '
';
			});
			html += '
';
			return html;
		};

		$('#dogTable').on('click', '.thumbNailLink', function() {
			 $('#image-file-key').val(window.dogList.previewfileKey + this.id);
	         $('#image-preview-form').trigger('submit');
	         $('#image-preview').modal({keyboard:true, show:true});
	    });
	}

	return window.dogList.renderSnapshotThumbnail(data, type, row, meta);
}

function renderThumbNail( data, type, row, meta ) {

	if(! window.dogList) {
		window.dogList = {};
		window.dogList.snapshotUrl = '${SNAPSHOTURL}';
		window.dogList.previewfileKey = '${PREVIEWKEY}';
		window.dogList.renderSnapshotThumbnail = function(data, type, row, meta) {
			var html = '';
			var colLength = 12 / $(data).length;
			$.each(data, function(index, value) {
				var cls = "thumbnail col-xs-"  + colLength;
				html += '
';
				html += '
';
			});

			return html;
		};

		$('#dogTable').on('click', '.thumbNailLink', function() {
			 $('#image-file-key').val(window.dogList.previewfileKey + this.id);
	         $('#image-preview-form').trigger('submit');
	         $('#image-preview').modal({keyboard:true, show:true});
	    });
	}

	return window.dogList.renderSnapshotThumbnail(data, type, row, meta);
}

Fix layout of dogs datatable


var get = Ember.get;

FP.PromiseModel = Ember.ObjectProxy.extend(Ember.PromiseProxyMixin, {

  // forward on all content's functions where it makes sense to do so
  _setupContentForwarding: function() {
    var obj = get(this, "content");
    if (!obj) { return; }

    for (var prop in obj) {
      if (!this[prop] && typeof obj[prop] === "function") {
        this._forwardToContent(prop);
      }
    }
  }.observes("content").on("init"),

  _forwardToContent: function(prop) {
    this[prop] = function() {
      var content = this.get("content");
      return content[prop].apply(content, arguments);
    };
  }

});

var set = Ember.set,
    get = Ember.get,
    resolve = Ember.RSVP.resolve;

// reimplemented private method from Ember, but with setting
// _settingFromFirebase so we can avoid extra saves down the line

function observePromise(proxy, promise) {
  promise.then(function(value) {
    set(proxy, 'isFulfilled', true);
    value._settingFromFirebase = true;
    set(proxy, 'content', value);
    value._settingFromFirebase = false;
  }, function(reason) {
    set(proxy, 'isRejected', true);
    set(proxy, 'reason', reason);
    // don't re-throw, as we are merely observing
  });
}

FP.PromiseModel = Ember.ObjectProxy.extend(Ember.PromiseProxyMixin, {

  // forward on all content's functions where it makes sense to do so
  _setupContentForwarding: function() {
    var obj = get(this, "content");
    if (!obj) { return; }

    for (var prop in obj) {
      if (!this[prop] && typeof obj[prop] === "function") {
        this._forwardToContent(prop);
      }
    }
  }.observes("content").on("init"),

  _forwardToContent: function(prop) {
    this[prop] = function() {
      var content = this.get("content");
      return content[prop].apply(content, arguments);
    };
  },

  // re-implemented from Ember so we can call our own observePromise
  promise: Ember.computed(function(key, promise) {
    if (arguments.length === 2) {
      promise = resolve(promise);
      observePromise(this, promise);
      return promise.then(); // fork the promise.
    } else {
      throw new Ember.Error("PromiseProxy's promise must be set");
    }
  })

});

Set _settingFromFirebase on the PromiseModel when it's resolved


/* global requirejs */

function _hasEmberData() {
  let matchRegex = /^ember-data/i;
  return !!(Object.keys(requirejs.entries).find(e => !!e.match(matchRegex)));
}

/**
  @hide
*/
export const hasEmberData = _hasEmberData();

/**
  @hide
*/
export function isDsModel(m) {
  return m && typeof m.eachRelationship === 'function';
}

/* global requirejs */

function _hasEmberData() {
  let matchRegex1 = /^ember-data/i;
  let matchRegex2 = /^@ember-data/i;

  return !!(Object.keys(requirejs.entries).find(e => !!e.match(matchRegex2) || !!e.match(matchRegex1) ));
}

/**
  @hide
*/
export const hasEmberData = _hasEmberData();

/**
  @hide
*/
export function isDsModel(m) {
  return m && typeof m.eachRelationship === 'function';
}

Fix regex that checks if ember data is present to allow for difference in namespace


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

var LocationSchema   = new Schema({
	name: String,
	city: String,
	address: String,
	img: String,
	description: String,
	email: String,
	sage_uid: String,
	sage_product_id: String,
	sage_taxtype_id: String,
	sage_message: String,
	datatill_group_id: Number,
	bank_account: Number,
	bank_code: String,
	community_manager_name: String,
	community_manager_email: String,
	community_manager_tel: String,
	mail_header: String,
	_deleted: { type: Boolean, default: false, index: true },
});

LocationSchema.set("_perms", {
	admin: "crud",
	user: "r",
	all: "r"
});

module.exports = mongoose.model('Location', LocationSchema);

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

var LocationSchema   = new Schema({
	name: String,
	city: String,
	address: String,
	img: String,
	description: String,
	email: String,
	sage_uid: String,
	sage_product_id: String,
	sage_taxtype_id: String,
	sage_message: String,
	datatill_group_id: Number,
	bank_account: Number,
	bank_code: String,
	community_manager_name: String,
	community_manager_email: String,
	community_manager_tel: String,
	mail_template: String,
	_deleted: { type: Boolean, default: false, index: true },
});

LocationSchema.set("_perms", {
	admin: "crud",
	user: "r",
	all: "r"
});

module.exports = mongoose.model('Location', LocationSchema);

Rename location email_header to email_template


'use strict';

const _path = require('path');

class Listing {

  /**
   * Create path listing
   * @param path {string} Filepath
   */
  constructor({path}){
    let stat = _path.parse(path);

    /**
     * @type {string}
     */
    this._name = stat.name;

    /**
     * @type {string}
     */
    this._base = stat.base;

    /**
     * @type {string}
     */
    this._path = path;
  }

  /**
   * Get name of file (Without extension)
   * @return {string}
   */
  name(){ return this._name; }

  /**
   * Get base file name (With extension)
   * @return {string}
   */
  base(){ return this._base; }

  /**
   * Get full file path
   * @return {string}
   */
  path(){ return this._path; }

  /**
   * Require listing
   * @return {*|Object}
   */
  require(){
    return require(this._path);
  }
}

module.exports = Listing;

'use strict';

const _path = require('path');

class Listing {

  /**
   * Create path listing
   * @param path {string} Filepath
   */
  constructor({path}){
    let stat = _path.parse(path);

    /**
     * @type {string}
     */
    this._name = stat.name;

    /**
     * @type {string}
     */
    this._base = stat.base;

    /**
     * @type {string}
     */
    this._path = path;
  }

  /**
   * Get name of file (Without extension)
   * @return {string}
   */
  name(){ return this._name; }

  /**
   * Get base file name (With extension)
   * @return {string}
   */
  base(){ return this._base; }

  /**
   * Get full file path
   * @return {string}
   */
  path(){ return this._path; }

  /**
   * Require listing
   * @return {*|Object}
   */
  require(){
    return require(this.path());
  }
}

module.exports = Listing;

Use getter method for listing path


var async = require('async');

var AndThen = function(obj, ev) {
    var args,
        triggerCallbacks,
        callbacks = [];

    triggerCallbacks = function() {
        for(var i = 0; i < callbacks.length; ++i) {
            var callback = callbacks.splice(i)[0];

            async.nextTick(function() {
                callback.apply(callback, args);
            });
        }
    };

    obj.on(ev, function() {
        args = arguments;
        triggerCallbacks();
    });

    this.do = function(callback) {
        callbacks.push(callback);

        if (typeof(args) != 'undefined') {
            triggerCallbacks();
        }
    };

    this.removeCallback = function(callback) {
        var i = callbacks.indexOf(callback);

        if (i != -1) {
            callbacks.splice(i);
        }
    };
};

module.exports = AndThen;

var async = require('async');

var AndThen = function(obj, ev) {
    var args,
        triggerCallbacks,
        objCallback,
        callbacks = [];

    triggerCallbacks = function() {
        for(var i = 0; i < callbacks.length; ++i) {
            var callback = callbacks.splice(i)[0];

            async.nextTick(function() {
                callback.apply(callback, args);
            });
        }
    };

    this.on = function(obj, ev) {
        objCallback = function() {
            args = arguments;
            triggerCallbacks();
        };
        obj.on(ev, objCallback);
    };
    this.on(obj, ev);

    this.off = function() {
        obj.removeCallback(objCallback);
    };

    this.addCallback = function(callback) {
        callbacks.push(callback);

        if (typeof(args) != 'undefined') {
            triggerCallbacks();
        }
    };

    this.do = this.addCallback;

    this.removeCallback = function(callback) {
        var i = callbacks.indexOf(callback);

        if (i != -1) {
            callbacks.splice(i);
            return true;
        } else {
            return false;
        }
    };
};

module.exports = AndThen;

Add on method, and addCallback method with do as alias


/*
 * stompit.connect
 * Copyright (c) 2013 Graham Daws <graham.daws@gmail.com>
 * MIT licensed
 */

var net     = require('net');
var util    = require('./util');
var Client  = require('./client');

function connect(){

    var args = net._normalizeConnectArgs(arguments);

    var options = util.extend({
        host: 'localhost',
        connectHeaders: {}
    }, args[0]);

    if(options.port === undefined || typeof options.port === 'function'){
        options.port = 61613;
    }

    var cb = args[1];

    var client, socket, timeout;

    var cleanup = function(){

        if(timeout){
            clearTimeout(timeout);
        }

        client.removeListener('error', onError);
        client.removeListener('connect', onConnected);
    };

    var onError = function(error){
        cleanup();
        cb(error);
    };

    var onConnected = function(){
        cleanup();
        client.emit('socket-connect');
        client.connect(util.extend({host: options.host}, options.connectHeaders), cb);
    };

    if(options.hasOwnProperty('timeout')){
        var timeout = setTimeout(function(){
            client.destroy(client.createTransportError('connect timed out'));
        }, options.timeout);
    }

    socket = net.connect(options, onConnected);

    client = new Client(socket, options);

    client.on('error', onError);

    return client;
}

module.exports = connect;

/*
 * stompit.connect
 * Copyright (c) 2013 Graham Daws <graham.daws@gmail.com>
 * MIT licensed
 */

var net     = require('net');
var util    = require('./util');
var Client  = require('./client');

function connect(){

    var args = net._normalizeConnectArgs(arguments);

    var options = util.extend({
        host: 'localhost',
        connectHeaders: {}
    }, args[0]);

    if(options.port === undefined || typeof options.port === 'function'){
        options.port = 61613;
    }

    var cb = args[1];

    var client, socket, timeout;

    var cleanup = function(){

        if(timeout){
            clearTimeout(timeout);
        }

        client.removeListener('error', onError);
        client.removeListener('connect', onConnected);
    };

    var onError = function(error){
        cleanup();
        if(typeof cb === "function"){
            cb(error);
        }
    };

    var onConnected = function(){
        cleanup();
        client.emit('socket-connect');
        client.connect(util.extend({host: options.host}, options.connectHeaders), cb);
    };

    if(options.hasOwnProperty('timeout')){
        var timeout = setTimeout(function(){
            client.destroy(client.createTransportError('connect timed out'));
        }, options.timeout);
    }

    socket = net.connect(options, onConnected);

    client = new Client(socket, options);

    client.on('error', onError);

    return client;
}

module.exports = connect;

Check cb is a function before calling it


module.exports = {
  /**
   * @param {string} testFilePath - file path of a test
   * @param {array} tags - tags to match
   * @returns {boolean} true if specified test matches given tag
   */
  tags: function (testFilePath, tags) {

    try {
      test = require(testFilePath);
    } catch (e) {
      // could not load test module
      return false;
    }

    return this.checkModuleTags(test, tags);
  },

  /**
   * @param {object} test - test module
   * @param {array} tags - tags to match
   * @returns {boolean}
   */
  checkModuleTags: function (test, tags) {
    var testTags = test.tags;
    var match    = false;

    if (typeof tags === 'string') {
      tags = [tags];
    }

    if (!Array.isArray(testTags)) {
      return false;
    }

    tags = tags.map(function (tag) {
      return tag.toLowerCase();
    });

    testTags
      .map(function (testTag) {
        return testTag.toLowerCase();
      })
      .some(function (testTag) {
        match = (tags.indexOf(testTag) !== -1);
        return match;
      });

    return match;
  }
};

module.exports = {
  /**
   * @param {string} testFilePath - file path of a test
   * @param {array} tags - tags to match
   * @returns {boolean} true if specified test matches given tag
   */
  tags: function (testFilePath, tags) {
    var test;

    try {
      test = require(testFilePath);
    } catch (e) {
      // could not load test module
      return false;
    }

    return this.checkModuleTags(test, tags);
  },

  /**
   * @param {object} test - test module
   * @param {array} tags - tags to match
   * @returns {boolean}
   */
  checkModuleTags: function (test, tags) {
    var testTags = test.tags;
    var match    = false;

    if (typeof tags === 'string') {
      tags = [tags];
    }

    if (!Array.isArray(testTags)) {
      return false;
    }

    tags = tags.map(function (tag) {
      return tag.toLowerCase();
    });

    testTags
      .map(function (testTag) {
        return testTag.toLowerCase();
      })
      .some(function (testTag) {
        match = (tags.indexOf(testTag) !== -1);
        return match;
      });

    return match;
  }
};

Fix jsdoc error due to undeclared variable


var util = require('../src/util/seeds');

exports.seed = function(knex, Promise) {
  const cities = {};

  return knex('cities').select('*')
  .then(rows => {
    rows.forEach(city => {
      cities[city.name] = city.id;
    });
  })
  .then(() => util.insertOrUpdate(knex, 'radios', {
    id: 1,
    name: 'Rakkauden Wappuradio',
    city_id: cities['Tampere'],
    stream: null,   // TODO
    website: 'https://wappuradio.fi/',
  }))
  .then(() => util.insertOrUpdate(knex, 'radios', {
    id: 2,
    name: 'Radiodiodi',
    city_id: cities['Otaniemi'],
    stream: null,   // TODO
    website: 'https://radiodiodi.fi/',
  }));
}

var util = require('../src/util/seeds');

exports.seed = function(knex, Promise) {
  const cities = {};

  return knex('cities').select('*')
  .then(rows => {
    rows.forEach(city => {
      cities[city.name] = city.id;
    });
  })
  .then(() => util.insertOrUpdate(knex, 'radios', {
    id: 1,
    name: 'Rakkauden Wappuradio',
    city_id: cities['Tampere'],
    stream: 'http://stream.wappuradio.fi/wappuradio.mp3',
    website: 'https://wappuradio.fi/',
  }))
  .then(() => util.insertOrUpdate(knex, 'radios', {
    id: 2,
    name: 'Radiodiodi',
    city_id: cities['Otaniemi'],
    stream: null,   // TODO
    website: 'https://radiodiodi.fi/',
  }));
}

Add stream uri for wappuradio


;(function() {
  "use strict";

  require("BaseController").init({
    siteName: "Amazon Music",
    playPause: "[playeraction=togglePlay]",
    playNext: "[playeraction=next]",
    playPrev: "[playeraction=previous]",
    mute: "#volumeIcon",

    playState: "#mp3Player .playing",
    song: "#nowPlayingSection .title"
  });
})();

;(function() {
  "use strict";

  var controller = require("BaseController");
  controller.init({
    siteName: "Amazon Music",
    playPause: "[playeraction=togglePlay]",
    playNext: "[playeraction=next]",
    playPrev: "[playeraction=previous]",
    mute: "#volumeIcon",
    like: "#thumbsUp > span",
    dislike: "#thumbsDown > span",

    playState: "#mp3Player .playing",
    song: "#nowPlayingSection .title"
  });
  /* Overrides */
  controller.playControlsClick = function(div) {
    this.fireEvent = function(element, event, data){
      var evt = document.createEvent("HTMLEvents");
      evt.initEvent(event, true, true );
      if (data) evt.data = data;
      return!document.querySelector(element).dispatchEvent(evt);
    };
    this.fireEvent(div, "mousedown");
    this.fireEvent(div, "mouseup");
    this.fireEvent(div, "mouseout");
  };
  controller.playPause = function() {
    this.playControlsClick("*[playeraction='togglePlay']");
  };
  controller.playNext = function() {
    this.playControlsClick("*[playeraction='next']");
  };
  controller.playPrev = function() {
    this.playControlsClick("*[playeraction='previous']");
  };
})();

Fix Amazon and add Like/Dislike


isic.views.TermsAcceptanceView = isic.View.extend({
    events: {
        'click #isic-terms-accept': function (event) {
            if (girder.currentUser) {
                girder.currentUser.setAcceptTerms(function () {
                    // Refresh page
                    Backbone.history.loadUrl();
                });
                // TODO: disable buttons while request is pending
            } else {
                window.sessionStorage.setItem('acceptTerms', 'true');
                Backbone.history.loadUrl();
            }
        },
        'click #isic-terms-reject': function (event) {
            girder.events.trigger('g:navigateTo', isic.views.FrontPageView);
        }
    },

    initialize: function () {
        this.termsOfUseWidget = new isic.views.TermsOfUseWidget({
            parentView: this
        });

        this.render();
    },

    render: function () {
        this.$el.html(isic.templates.termsAcceptancePage());

        this.termsOfUseWidget.setElement(
            this.$('#isic-terms-of-use-container')).render();

        return this;
    }
}, {
    // Static methods
    hasAcceptedTerms: function () {
        if (girder.currentUser) {
            return girder.currentUser.canAcceptTerms();
        } else {
            return window.sessionStorage.getItem('acceptTerms') === 'true';
        }
    }
});

isic.router.route('termsAcceptance', 'termsAcceptance', function () {
    girder.events.trigger('g:navigateTo', isic.views.TermsAcceptanceView);
});

isic.views.TermsAcceptanceView = isic.View.extend({
    events: {
        'click #isic-terms-accept': function (event) {
            // Disable buttons while request is pending
            var buttons = this.$('.isic-terms-agreement-button-container button');
            buttons.prop('disabled', true);

            if (girder.currentUser) {
                girder.currentUser.setAcceptTerms(function () {
                    // Refresh page
                    Backbone.history.loadUrl();
                });
            } else {
                window.sessionStorage.setItem('acceptTerms', 'true');
                Backbone.history.loadUrl();
            }
        },
        'click #isic-terms-reject': function (event) {
            girder.events.trigger('g:navigateTo', isic.views.FrontPageView);
        }
    },

    initialize: function () {
        this.termsOfUseWidget = new isic.views.TermsOfUseWidget({
            parentView: this
        });

        this.render();
    },

    render: function () {
        this.$el.html(isic.templates.termsAcceptancePage());

        this.termsOfUseWidget.setElement(
            this.$('#isic-terms-of-use-container')).render();

        return this;
    }
}, {
    // Static methods
    hasAcceptedTerms: function () {
        if (girder.currentUser) {
            return girder.currentUser.canAcceptTerms();
        } else {
            return window.sessionStorage.getItem('acceptTerms') === 'true';
        }
    }
});

isic.router.route('termsAcceptance', 'termsAcceptance', function () {
    girder.events.trigger('g:navigateTo', isic.views.TermsAcceptanceView);
});


(function () {
    "use strict";

    function NodeNameController($scope) {

        var vm = this;
        var element = angular.element($scope.model.currentStep.element);

        vm.error = false;

        vm.initNextStep = initNextStep;

        function initNextStep() {
            if(element.val().lowerCase() === 'Home') {
                $scope.model.nextStep();
            } else {
                vm.error = true;
            }
        }

    }

    angular.module("umbraco").controller("Umbraco.Tours.UmbIntroCreateContent.NodeNameController", NodeNameController);
})();

(function () {
    "use strict";

    function NodeNameController($scope) {

        var vm = this;
        var element = angular.element($scope.model.currentStep.element);

        vm.error = false;

        vm.initNextStep = initNextStep;

        function initNextStep() {
            if(element.val().toLowerCase() === 'home') {
                $scope.model.nextStep();
            } else {
                vm.error = true;
            }
        }

    }

    angular.module("umbraco").controller("Umbraco.Tours.UmbIntroCreateContent.NodeNameController", NodeNameController);
})();

Update validation in Create Content tour


import React from 'react';
import * as styles from './PopOver.scss';

const PopOver = ({ next, active, first, last, previous, xPos, yPos, children }) => {
  if (!active) {
    return null;
  }
  const text = last ? 'End' : 'Next';
  return (

        {children}

        {first ?

 :
          <button onClick={previous}>Prev</button>
        }
        <button onClick={next}>{text}</button>

  );
};

export default PopOver;

import React from 'react';
import * as styles from './PopOver.scss';

const PopOver = ({
  order,
  text,
  xPos,
  yPos,
  totalLength,
  tutorialActive,
  currentlyDisplayedTutorial,
  viewNext,
  viewPrevious,
  closeTutorial
}) => {
  const first = order === 1;
  const last = order === totalLength;
  const active = tutorialActive && currentlyDisplayedTutorial === order;
  const buttonText = last ? 'End' : 'Next';
  const nextAction = last ? closeTutorial : viewNext;
  if (!active) {
    return null;
  }
  return (

        {text}

        {first ? (
) : (<button onClick={viewPrevious}>Prev</button>)}
        <button onClick={nextAction}>{buttonText}</button>

  );
};

export default PopOver;

Connect popover to redux store


Application.Services.factory('Events',  ['$filter', EventsService]);

function EventsService($filter) {
    var service = {
        date: '',

        addConvertedTime: function (project) {
            project.reviews = service.update(project.open_reviews);
            project.samples = service.update(project.samples);
            project.processes = service.update(project.processes);
            project.notes = service.update(project.notes);
            project.drafts = service.update(project.drafts);
            return project;
        },

        update: function (items) {
            items.forEach(function (item) {
                var item_date = new Date(0);
                item_date.setUTCSeconds(item.mtime.epoch_time);
                item.converted_mtime = Date.UTC(item_date.getUTCFullYear(), item_date.getUTCMonth(), item_date.getUTCDay());
            });
            return items;
        },

        prepareCalendarEvent: function (items) {
            var calendar_event = [];
            if (items.length !== 0) {
                var grouped_by_convertedtime = $filter('groupBy')(items, 'converted_mtime');
                Object.keys(grouped_by_convertedtime).forEach(function (key) {
                    var d = new Date(0);
                    var value = grouped_by_convertedtime[key][0];
                    d.setUTCSeconds(value.mtime.epoch_time);
                    calendar_event.push({title: value.title, start: d});
                });
                return calendar_event;
            }

        },

        updateDate: function (project, date) {
            project.date = date;
            return project;
        }
    };
    return service;
}

Application.Services.factory('Events',  ['$filter', EventsService]);

function EventsService($filter) {
    var service = {
        date: '',

        addConvertedTime: function (project) {
            project.reviews = service.update(project.open_reviews);
            project.samples = service.update(project.samples);
            project.processes = service.update(project.processes);
            project.notes = service.update(project.notes);
            project.drafts = service.update(project.drafts);
            return project;
        },

        update: function (items) {
            items.forEach(function (item) {
                var item_date = new Date(0);
                item_date.setUTCSeconds(item.mtime.epoch_time);
                item.converted_mtime = Date.UTC(item_date.getUTCFullYear(), item_date.getUTCMonth(), item_date.getUTCDay());
            });
            return items;
        },

        prepareCalendarEvent: function (items) {
            var calendar_event = [];
            if (items.length !== 0) {
                var grouped_by_convertedtime = $filter('groupBy')(items, 'converted_mtime');
                Object.keys(grouped_by_convertedtime).forEach(function (key) {
                    var d = new Date(0);
                    var value = grouped_by_convertedtime[key][0];
                    d.setUTCSeconds(value.mtime.epoch_time);
                    calendar_event.push({title: value.title, start: d});
                });
            }
            return calendar_event;
        },

        updateDate: function (project, date) {
            project.date = date;
            return project;
        }
    };
    return service;
}

Fix method to always return a value.


'use strict'

const Stream = require('stream')
const Base = require('./base')
let Browserify
let Babelify

class PluginBrowserify extends Base {
  constructor() {
    super()
    this.registerTag(['Compiler-Browserify'], function(name, value, options) {
      options.Browserify = value === 'true'
    })
  }
  compile(contents, options) {
    return this.executeTags(contents, options).then(function(contents) {
      if (!options.Browserify) return contents
      Browserify = Browserify || require('browserify')
      const Readable = new Stream.Readable()
      Readable._read = function() {
        Readable.push(contents)
        Readable.push(null)
      }
      let browserified = new Browserify(Readable, {
        basedir: options.internal.file.directory,
        debug: options.SourceMap,
        fullPaths: false,
        filename: options.internal.file.path,
      })
      if (options.Babel) {
        Babelify = Babelify || require('babelify').configure({ optional: ["runtime"] })
        browserified = browserified.transform(Babelify)
      }
      return new Promise(function(resolve, reject) {
        browserified.bundle(function(err, buffer) {
          if (err) reject(err)
          else resolve(buffer.toString())
        })
      })
    })
  }
}

module.exports = PluginBrowserify

'use strict'

const Path = require('path')
const Stream = require('stream')
const Base = require('./base')
let Browserify
let Babelify

class PluginBrowserify extends Base {
  constructor() {
    super()
    this.registerTag(['Compiler-Browserify'], function(name, value, options) {
      options.Browserify = value === 'true'
    })
  }
  compile(contents, options) {
    return this.executeTags(contents, options).then(function(contents) {
      if (!options.Browserify) return contents
      Browserify = Browserify || require('browserify')
      const Readable = new Stream.Readable()
      Readable._read = function() {
        Readable.push(contents)
        Readable.push(null)
      }
      let browserified = new Browserify(Readable, {
        basedir: options.internal.file.directory,
        debug: options.SourceMap,
        fullPaths: false,
        filename: options.internal.file.path,
        paths: [Path.join(__dirname, '../node_modules/')]
      })
      if (options.Babel) {
        Babelify = Babelify || require('babelify').configure({ optional: ["runtime"] })
        browserified = browserified.transform(Babelify)
      }
      return new Promise(function(resolve, reject) {
        browserified.bundle(function(err, buffer) {
          if (err) reject(err)
          else resolve(buffer.toString())
        })
        browserified.pipeline.on('file', function(file){
          options.internal.imports.push(file)
        })
      })
    })
  }
}

module.exports = PluginBrowserify

Fix resolution of babel helper module Also Make the ES6 imports refreshable


/*
 * Copyright (C) 2015 Glyptodon LLC
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 * Module which augments Guacamole's existing URL routing, adding support for
 * legacy-style client URLs (the format used in 0.9.7 and older).
 */
angular.module('legacyUrl', [
    'auth'
]);

// Include legacyUrl module in index module dependencies, such that the
// existing routing can be modified
angular.module('index').requires.push('legacyUrl');

/*
 * Copyright (C) 2015 Glyptodon LLC
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 * Module which augments Guacamole's existing URL routing, adding support for
 * legacy-style client URLs (the format used in 0.9.7 and older).
 */
angular.module('legacyUrl', [
    'auth',
    'navigation'
]);

// Include legacyUrl module in index module dependencies, such that the
// existing routing can be modified
angular.module('index').requires.push('legacyUrl');

Add required "navigation" module dependency (for ClientIdentifier).


Hummingbird.PostCommentComponent = Ember.Component.extend({
  classNames: ["status-update-panel"],

  didInsertElement: function() {
    this.$(".status-form").focus(function() {
      self.$(".status-form").autosize({append: "\n"});
      self.$(".panel-footer").slideDown(200);
    });
    this.$(".status-form").blur(function() {
      if (self.$(".status-form").val().replace(/\s/g, '').length === 0) {
        self.$(".status-form").val('');
        self.$(".panel-footer").slideUp(200, function() {
          self.$(".status-form").trigger("autosize.destroy");
        });
      }
    });
  },

  willClearRender: function() {
    this.$(".status-form").trigger("autosize.destroy");
  },

  actions: {
    submitPost: function() {
      var _this = this
        , newPost = this.get('newPost');

      if (newPost.length > 0) {
        this.set('inFlight', true);
        return Ember.$.ajax({
          url: "/users/" + _this.get('username') + "/comment.json",
          data: { comment: newPost },
          type: "POST",
          success: function (payload) {
            _this.setProperties({
              newPost: "",
              inFlight: false
            });
            window.location.href = window.location.href;
          },
          error: function () {
            alert("Failed to save comment");
          }
        });
      }
    }
  }
});

Hummingbird.PostCommentComponent = Ember.Component.extend({
  classNames: ["status-update-panel"],

  didInsertElement: function() {
    var self = this;
    this.$(".status-form").focus(function() {
      self.$(".status-form").autosize({append: "\n"});
      self.$(".panel-footer").slideDown(200);
    });
    this.$(".status-form").blur(function() {
      if (self.$(".status-form").val().replace(/\s/g, '').length === 0) {
        self.$(".status-form").val('');
        self.$(".panel-footer").slideUp(200, function() {
          self.$(".status-form").trigger("autosize.destroy");
        });
      }
    });
  },

  willClearRender: function() {
    this.$(".status-form").trigger("autosize.destroy");
  },

  actions: {
    submitPost: function() {
      var _this = this
        , newPost = this.get('newPost');

      if (newPost.length > 0) {
        this.set('inFlight', true);
        return Ember.$.ajax({
          url: "/users/" + _this.get('username') + "/comment.json",
          data: { comment: newPost },
          type: "POST",
          success: function (payload) {
            _this.setProperties({
              newPost: "",
              inFlight: false
            });
            window.location.href = window.location.href;
          },
          error: function () {
            alert("Failed to save comment");
          }
        });
      }
    }
  }
});

Bring back the post button.


// disable experimental navigator.credentials
chrome.webFrame.setGlobal("navigator.credentials.get", function () {
  return new Promise((resolve, reject) => { resolve(false) })
})

chrome.webFrame.setGlobal("navigator.credentials.store", function () {
  return new Promise((resolve, reject) => { resolve(false) })
})

// disable battery status API
chrome.webFrame.setGlobal("navigator.getBattery", function () {
  return new Promise((resolve, reject) => {
    resolve({ charging: false, chargingTime: Infinity, dischargingTime: Infinity, level: 1, onchargingchange: null, onchargingtimechange: null, ondischargingtimechange: null, onlevelchange: null })
  })
})

if (chrome.contentSettings.doNotTrack == 'allow') {
  executeScript("window.Navigator.prototype.__defineGetter__('doNotTrack', () => { return 1 })")
}

if (chrome.contentSettings.ads == 'block') {
  chrome.webFrame.setGlobal("window.google_onload_fired", true)
}

// disable experimental navigator.credentials
chrome.webFrame.setGlobal("navigator.credentials.get", function () {
  return new Promise((resolve, reject) => { resolve(false) })
})

chrome.webFrame.setGlobal("navigator.credentials.store", function () {
  return new Promise((resolve, reject) => { resolve(false) })
})

// disable battery status API
chrome.webFrame.setGlobal("navigator.getBattery", function () {
  return new Promise((resolve, reject) => { reject(new Error('navigator.getBattery not supported.')) })
})

if (chrome.contentSettings.doNotTrack == 'allow') {
  executeScript("window.Navigator.prototype.__defineGetter__('doNotTrack', () => { return 1 })")
}

if (chrome.contentSettings.ads == 'block') {
  chrome.webFrame.setGlobal("window.google_onload_fired", true)
}

Revert "don’t return an error for `setGlobal` because it will break some scripts and is also a fingerprinting risk"


import { join } from 'path'
import json from '../util/json'

const saguiScripts = {
  'start': 'npm run develop',
  'test': 'NODE_ENV=test sagui test',
  'test-watch': 'NODE_ENV=test sagui test --watch',
  'develop': 'sagui develop',
  'build': 'sagui build',
  'dist': 'NODE_ENV=production sagui dist'
}

export default function (projectPath) {
  const packagePath = join(projectPath, 'package.json')
  const packageJSON = json.read(packagePath)

  json.write(packagePath, {
    ...packageJSON,
    scripts: {
      ...saguiScripts,
      ...withoutDefaults(packageJSON.scripts)
    }
  })
}

/**
 * Remove default configurations generated by NPM that can be overwriten
 * We don't want to overwrite any user configured scripts
 */
function withoutDefaults (scripts) {
  const defaultScripts = {
    'test': 'echo \"Error: no test specified\" && exit 1'
  }

  return Object.keys(scripts)
    .filter((key) => !scripts[key] !== '' && scripts[key] !== defaultScripts[key])
    .reduce((filtered, key) => {
      return {
        ...filtered,
        [key]: scripts[key]
      }
    }, {})
}

import { join } from 'path'
import json from '../util/json'

const saguiScripts = {
  'start': 'npm run develop',
  'test': 'NODE_ENV=test karma start',
  'test:watch': 'npm test -- --no-single-run --auto-watch',
  'develop': 'webpack-dev-server --port 3000 --host 0.0.0.0',
  'build': 'webpack',
  'dist': 'NODE_ENV=production webpack -p'
}

export default function (projectPath) {
  const packagePath = join(projectPath, 'package.json')
  const packageJSON = json.read(packagePath)

  json.write(packagePath, {
    ...packageJSON,
    scripts: {
      ...saguiScripts,
      ...withoutDefaults(packageJSON.scripts)
    }
  })
}

/**
 * Remove default configurations generated by NPM that can be overwriten
 * We don't want to overwrite any user configured scripts
 */
function withoutDefaults (scripts) {
  const defaultScripts = {
    'test': 'echo \"Error: no test specified\" && exit 1'
  }

  return Object.keys(scripts)
    .filter((key) => !scripts[key] !== '' && scripts[key] !== defaultScripts[key])
    .reduce((filtered, key) => {
      return {
        ...filtered,
        [key]: scripts[key]
      }
    }, {})
}

Update bootstrapped package.json to use Webpack and Karma CLIs


angular
  .module('crosswatch')
  .directive('user', user);

function user() {
  var directive = {
    link: link,
    scope: true,
    template: '{{::user}} ' +
    '' +
    '()' +
    ' ',
    restrict: 'E'
  };
  return directive;

  function link(scope, element, attrs) {
    if (typeof attrs.name !== 'undefined') {
      scope.user = attrs.name;
    } else {
      scope.user = scope.event.user;
    }
  }
}

angular
  .module('crosswatch')
  .directive('user', user);

function user() {
  var directive = {
    link: link,
    scope: true,
    template: '{{::user}} ' +
    '' +
    '()' +
    ' ',
    restrict: 'E'
  };
  return directive;

  function link(scope, element, attrs) {
    if (typeof attrs.name !== 'undefined') {
      scope.user = attrs.name;
    } else {
      scope.user = scope.event.user;
    }

    var split = scope.user.split(':');
    scope.user = split[split.length - 1]
  }
}

Fix link to user page and contribs


module.exports = Pagination;

function Pagination() {}

Pagination.prototype.view = __dirname + '/views';
Pagination.prototype.style = __dirname + '/styles';
Pagination.prototype.name = 'pagination';
Pagination.prototype.components = [];

require('./operations');
require('./actions');
require('./viewhelpers');

Pagination.prototype.init = function(model) {
  model.start('visibleNumbers', 'activePage', 'pages', this.getVisibleNumbers);
};

var _ = require('lodash');

module.exports = Pagination;

function Pagination() {}

Pagination.prototype.view = __dirname + '/views';
Pagination.prototype.style = __dirname + '/styles';
Pagination.prototype.name = 'pagination';
Pagination.prototype.components = [];

require('./operations');
require('./actions');
require('./viewhelpers');

Pagination.prototype.init = function(model) {
  if(typeof model.get('end') !== 'undefined') this.initPages();

  model.start('visibleNumbers', 'activePage', 'pages', this.getVisibleNumbers);
};

Pagination.prototype.initPages = function () {
  this.model.fn('pages', function (end) {
    return _.range(1, end + 1);
  });

  this.model.start('pages', 'end', 'pages');
  this.model.get('pages');
};

Add support for end attribute within pagination component


module.exports = {
  build: "npm start",
  networks: {
    development: {
      host: "localhost",
      port: 8545,
      network_id: "*"
    },
    testnet: {
      host: "localhost",
      port: 8545,
      network_id: 3
    },
    mainnet: {
      host: "localhost",
      port: 8545,
      network_id: 1
    }
  }
};

module.exports = {
  build: "npm start",
  networks: {
    development: {
      host: "localhost",
      port: 8545,
      network_id: "*"
    },
    testnet: {
      host: "localhost",
      port: 8545,
      network_id: 3
    },
    mainnet: {
      host: "localhost",
      port: 8545,
      network_id: 1,
      gas: 1990000,
      from: '0xFa6F2D7cC987d592556ac07392b9d6395bfcc379'
    }
  }
};

Add gas and deploy account


import React from 'react'
import ReactDOM from 'react-dom'
import ReactTestUtils from 'react-dom/test-utils'
import renderer from 'react-test-renderer'

import { App } from './App'

it('smoke test', () => {
  const div = document.createElement('div')
  ReactDOM.render(<App />, div)
})

it('snapshot test', () => {
  const tree = renderer.create(
    <App />
  ).toJSON()
  expect(tree).toMatchSnapshot()
})

it('state change test', () => {
  let app = ReactTestUtils.renderIntoDocument(<App />)

  // Get first node with childKeys
  let inputElement = ReactTestUtils.scryRenderedDOMComponentsWithTag(app, 'input')[2]
  ReactTestUtils.Simulate.change(inputElement)
  expect(inputElement.checked).toBe(true)
})

import React from 'react'
import ReactDOM from 'react-dom'
import ReactTestUtils from 'react-dom/test-utils'
import renderer from 'react-test-renderer'

import { App } from './App'

it('smoke test', () => {
  const div = document.createElement('div')
  ReactDOM.render(<App />, div)
})

it('snapshot test', () => {
  const tree = renderer.create(
    <App />
  ).toJSON()
  expect(tree).toMatchSnapshot()
})

it('state change test', () => {
  const app = ReactTestUtils.renderIntoDocument(<App />)

  // Get first node with childKeys
  const inputElement = ReactTestUtils.scryRenderedDOMComponentsWithTag(app, 'input')[2]
  ReactTestUtils.Simulate.change(inputElement)
  expect(inputElement.checked).toBe(true)
})

Use const in place of let


/**
 * Poster v0.1.0
 * A React webapp to list upcoming movies and maintain a watchlist, powered by TMDb
 *
 * Author: Kushal Pandya <kushalspandya@gmail.com> (https://doublslash.com)
 * Date: 01 June, 2016
 * License: MIT
 *
 * React App Main Layout Component
 */

import React from "react";

import Header from "./Header/Header";
import Jumbotron from "./Section/Jumbotron";
import Footer from "./Footer/Footer";

export default
class Home extends React.Component {
    constructor() {
        super();
    }

    render() {
        return (

                    <Jumbotron hide={this.props.params.movieId ? true : false}/>
                    {this.props.children}

        );
    }
}

/**
 * Poster v0.1.0
 * A React webapp to list upcoming movies and maintain a watchlist, powered by TMDb
 *
 * Author: Kushal Pandya <kushalspandya@gmail.com> (https://doublslash.com)
 * Date: 01 June, 2016
 * License: MIT
 *
 * React App Main Layout Component
 */

import React from "react";
import { connect } from "react-redux";

import { PosterAction, loadSearch } from "../../actions/poster";

import Header from "./Header/Header";
import Jumbotron from "./Section/Jumbotron";
import Footer from "./Footer/Footer";

class Home extends React.Component {
    constructor() {
        super();
    }

    handleSearch(query) {
        this.context.router.push({
            pathname: 'search',
            query: {
                query: query
            }
        });

        this.props.loadSearch(query);
    }

    render() {
        let routeChildren = this.props.children;
        const currentChild = (routeChildren && routeChildren.type.WrapperComponent) || '';
        if (currentChild.name === 'SearchResults')
        {
            routeChildren = React.Children.map(this.props.children, (child) => React.cloneElement(child, {
                loadCompleted: this.props.loadCompleted,
                movies: this.props.movies
            }));
        }

        return (

                    <Jumbotron
                        hide={this.props.params.movieId ? true : false}
                        handleSearch={this.handleSearch.bind(this)}
                    />
                    {routeChildren}

        );
    }
}

/*
 * Declare Context Properties
 */
Home.contextTypes = {
    router: React.PropTypes.object.isRequired
};

const stateToProps = (state, ownProps) => {
    return {
        loadCompleted: state.poster.loadSearchResultsCompleted,
        movies: state.poster.searchResults
    };
}

const dispatchToProps = (dispatch, ownProps) => {
    return {
        loadSearch: (query) => {
            return dispatch(loadSearch(query));
        }
    };
}

const HomeView = connect(
    stateToProps,
    dispatchToProps
)(Home);

export default HomeView;

Handle search action using redux.


(function() {
	function startup() {
		var navigatorToolbox = document.getElementById("navigator-toolbox");
		navigatorToolbox.iconsize = "small";
		navigatorToolbox.setAttribute("iconsize", "small");
		var reloadButton = document.getElementById("reload-button");
		reloadButton.style.visibility = "visible";
		var stopButton = document.getElementById("stop-button");
		stopButton.style.visibility = "visible";
		window.removeEventListener("load", startup, false);
	}

	function shutdown() {
		window.removeEventListener("unload", shutdown, false);
	}

	window.addEventListener("load", startup, false);
	window.addEventListener("unload", shutdown, false);
})();

// Disable shift click from opening window
// Fixes https://github.com/Webconverger/webconverger-addon/issues/18
var ffWhereToOpenLink = whereToOpenLink;

whereToOpenLink = function(e, ignoreButton, ignoreAlt) {
	var where = ffWhereToOpenLink(e, ignoreButton, ignoreAlt);
	if (where == "window") {
		where = "tab";
	}
	return where;
}

(function() {
	function startup() {
		window.removeEventListener("load", startup, false);
		var navigatorToolbox = document.getElementById("navigator-toolbox");
		navigatorToolbox.iconsize = "small";
		navigatorToolbox.setAttribute("iconsize", "small");
		var reloadButton = document.getElementById("reload-button");
		reloadButton.style.visibility = "visible";
		var stopButton = document.getElementById("stop-button");
		stopButton.style.visibility = "visible";

    var tabSwitchInterval = 0;
    try {
        tabSwitchInterval = Services.prefs.getIntPref("extensions.webconverger.tabswitchinterval");
    } catch (e) {}
    if (tabSwitchInterval > 0) {
      window.setInterval(function() {
        var visibleTabs = gBrowser.visibleTabs;
        if (visibleTabs.length == 1) {
          // Don't do anything if there is only one tab
          return;
        }
        var selectedIndex = visibleTabs.indexOf(gBrowser.selectedTab);
        selectedIndex = selectedIndex +1;
        if (selectedIndex == visibleTabs.length) {
          selectedIndex = 0;
        }
        gBrowser.selectTabAtIndex(selectedIndex);
      }, tabSwitchInterval);
    }
	}

	function shutdown() {
		window.removeEventListener("unload", shutdown, false);
	}

	window.addEventListener("load", startup, false);
	window.addEventListener("unload", shutdown, false);
})();

// Disable shift click from opening window
// Fixes https://github.com/Webconverger/webconverger-addon/issues/18
var ffWhereToOpenLink = whereToOpenLink;

whereToOpenLink = function(e, ignoreButton, ignoreAlt) {
	var where = ffWhereToOpenLink(e, ignoreButton, ignoreAlt);
	if (where == "window") {
		where = "tab";
	}
	return where;
}

Allow for switching of tabs automatically


var sms = {
  send: function(phone, message, method, successCallback, failureCallback) {
    phone = sms.convertPhoneToArray(phone);

    cordova.exec(
      successCallback,
      failureCallback,
      'Sms',
      'send',
      [phone, message, method]
    );
  },

  convertPhoneToArray: function(phone) {
    if(typeof phone === 'string' && phone.indexOf(',') !== -1) {
      phone = phone.split(',');
    }
    if(Object.prototype.toString.call(phone) !== '[object Array]') {
      phone = [phone];
    }
    return phone;
  }
};

module.exports = sms;

var sms = {
  send: function(phone, message, method, success, failure) {
    phone = sms.convertPhoneToArray(phone);

    cordova.exec(
      success,
      failure,
      'Sms',
      'send',
      [phone, message, method]
    );
  },

  convertPhoneToArray: function(phone) {
    if(typeof phone === 'string' && phone.indexOf(',') !== -1) {
      phone = phone.split(',');
    }
    if(Object.prototype.toString.call(phone) !== '[object Array]') {
      phone = [phone];
    }
    return phone;
  }
};

module.exports = sms;

Change callback names in the js


const mongoose = require('mongoose')
mongoose.Promise = global.Promise
const slug = require('slugs')

const gameSchema = new mongoose.Schema({
  title: {
    type: String,
    trim: true,
    required: 'Please specify a title'
  },
  slug: String,
  description: String,
  release: {
    type: Date,
    required: 'Please specify a release date'
  },
  platforms: [String],
  created: {
    type: Date,
    default: Date.now
  }
})

gameSchema.pre('save', function(next) {
  if (!this.isModified('title')) {
    return next()
  }
  this.slug = slug(this.name)
  next()
})

module.exports = mongoose.model('Game', gameSchema)

const mongoose = require('mongoose')
mongoose.Promise = global.Promise
const slug = require('slugs')

const gameSchema = new mongoose.Schema({
  title: {
    type: String,
    trim: true,
    required: 'Please specify a title'
  },
  slug: String,
  description: String,
  release: {
    type: Date,
    required: 'Please specify a release date'
  },
  platforms: [String],
  created: {
    type: Date,
    default: Date.now
  }
})

gameSchema.pre('save', function(next) {
  if (!this.isModified('title')) {
    return next()
  }
  this.slug = slug(this.title)
  next()
})

module.exports = mongoose.model('Game', gameSchema)

Fix small bug with slugs


const webpack = require('webpack')
const PACKAGE = require('./package.json')
const TerserPlugin = require('terser-webpack-plugin')

module.exports = {
  entry: {
    list: './src/index.js',
    'list.min': './src/index.js',
  },
  output: {
    path: __dirname + '/dist',
    filename: '[name].js',
    library: 'List',
  },
  devtool: 'cheap-module-source-map',
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /(node_modules)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
          },
        },
      },
    ],
  },
  devServer: {
    inline: true,
  },
  plugins: [
    new webpack.BannerPlugin({
      banner:
        'List.js v' +
        PACKAGE.version +
        ' (' +
        PACKAGE.homepage +
        ') by ' +
        PACKAGE.author.name +
        ' (' +
        PACKAGE.author.url +
        ')',
    }),
  ],
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        include: /\.min\.js$/,
        extractComments: false,
        terserOptions: {
          format: {
            comments: /^! List.js v.*/,
          },
          mangle: true,
        },
      }),
    ],
  },
}

const webpack = require('webpack')
const PACKAGE = require('./package.json')
const TerserPlugin = require('terser-webpack-plugin')

module.exports = {
  entry: {
    list: './src/index.js',
    'list.min': './src/index.js',
  },
  output: {
    path: __dirname + '/dist',
    filename: '[name].js',
    library: 'List',
  },
  devtool: 'cheap-module-source-map',
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /(node_modules)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
          },
        },
      },
    ],
  },
  devServer: {
    inline: true,
  },
  plugins: [],
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        include: /\.min\.js$/,
        extractComments: false,
        terserOptions: {
          format: {
            comments: /^! List.js v.*/,
          },
          mangle: true,
        },
      }),
    ],
  },
}

Remove banner plugin since it don't work with new release strategy


/*!
 * Copyright 2016 Joji Doi
 * Licensed under the MIT license
 */

function getMediaFilePath(mediaItem) {
  return "ext-content/" + mediaItem.dir + '/' + mediaItem.title +
    mediaItem.file_ext;
}

function getMediaThumbFilePath(mediaItem) {
  return "ext-content/" + mediaItem.dir + '/thumbs/' + mediaItem.title +
    mediaItem.file_ext;
}

/*
 * Load media_list from the json file
 */
$.getJSON("data/media_files_list.json", function(result) {
  var mediaFiles = $(result).filter(function() {
    return this.category == "photos";
  });
  $.each(mediaFiles, function(i, mediaItem) {
    var aTag = $("<a/>", {
      "href": getMediaFilePath(mediaItem)
    });
    var img = $("<img/>", {
      "src": getMediaThumbFilePath(mediaItem)
    });
    img.appendTo(aTag);
    aTag.appendTo("#files_list");
  });
  $(".galleria").galleria({
    responsive: true,
    height: 0.5
  });
  Galleria.run('.galleria');

}); // end of getJSON()

/*!
 * Copyright 2016 Joji Doi
 * Licensed under the MIT license
 */

function getMediaFilePath(mediaItem) {
  return "ext-content/" + mediaItem.dir + '/' + mediaItem.title +
    mediaItem.file_ext;
}

function getMediaThumbFilePath(mediaItem) {
  return "ext-content/" + mediaItem.dir + '/thumbs/' + mediaItem.title +
    mediaItem.file_ext;
}

/*
 * Load media_list from the json file
 */
$.getJSON("data/media_files_list.json", function(result) {
  var mediaFiles = $(result).filter(function() {
    return this.category == "photos";
  });
  var datas = [];
  $.each(mediaFiles, function(i, mediaItem) {
    datas.push({
      image: getMediaFilePath(mediaItem),
      thumb: getMediaThumbFilePath(mediaItem)
    });
  });
  Galleria.run('.galleria', {
    thumbnails: "lazy",
    dataSource: datas,
    responsive: true,
    height: 0.5,
    autoplay: true
  });

  // lazy load small chunks of thumbnails at a time
  Galleria.ready(function() {
    this.lazyLoadChunks(30, 1000); // 30 thumbs per second
  })
}); // end of getJSON()

Use lazyLoadChunks API to load small chunks at a time


/*
 * grunt-contrib-requirejs
 * http://gruntjs.com/
 *
 * Copyright (c) 2014 Tyler Kellen, contributors
 * Licensed under the MIT license.
 */

module.exports = function(grunt) {
  'use strict';

  var requirejs = require('requirejs');

  // TODO: extend this to send build log to grunt.log.ok / grunt.log.error
  // by overriding the r.js logger (or submit issue to r.js to expand logging support)
  requirejs.define('node/print', [], function() {
    return function print(msg) {
      if (msg.substring(0, 5) === 'Error') {
        grunt.log.errorlns(msg);
        grunt.fail.warn('RequireJS failed.');
      } else {
        grunt.log.oklns(msg);
      }
    };
  });

  grunt.registerMultiTask('requirejs', 'Build a RequireJS project.', function() {

    var done = this.async();
    var options = this.options({
      logLevel: 0,
      done: function(done, response){
        done();
      }
    });
    // The following catches errors in the user-defined `done` function and outputs them.
    var tryCatch = function(fn, done, output) {
      try {
        fn(done, output);
      } catch(e) {
        grunt.fail.warn('There was an error while processing your done function: "' + e + '"');
      }
    };

    requirejs.optimize(options, tryCatch.bind(null, options.done, done));
  });
};

/*
 * grunt-contrib-requirejs
 * http://gruntjs.com/
 *
 * Copyright (c) 2014 Tyler Kellen, contributors
 * Licensed under the MIT license.
 */

module.exports = function(grunt) {
  'use strict';

  var requirejs = require('requirejs');
  var LOG_LEVEL_TRACE = 0, LOG_LEVEL_WARN = 2;

  // TODO: extend this to send build log to grunt.log.ok / grunt.log.error
  // by overriding the r.js logger (or submit issue to r.js to expand logging support)
  requirejs.define('node/print', [], function() {
    return function print(msg) {
      if (msg.substring(0, 5) === 'Error') {
        grunt.log.errorlns(msg);
        grunt.fail.warn('RequireJS failed.');
      } else {
        grunt.log.oklns(msg);
      }
    };
  });

  grunt.registerMultiTask('requirejs', 'Build a RequireJS project.', function() {

    var done = this.async();
    var options = this.options({
      logLevel: grunt.option('verbose') ? LOG_LEVEL_TRACE : LOG_LEVEL_WARN,
      done: function(done, response){
        done();
      }
    });
    // The following catches errors in the user-defined `done` function and outputs them.
    var tryCatch = function(fn, done, output) {
      try {
        fn(done, output);
      } catch(e) {
        grunt.fail.warn('There was an error while processing your done function: "' + e + '"');
      }
    };

    requirejs.optimize(options, tryCatch.bind(null, options.done, done));
  });
};

Set default log level to WARN (or TRACE, for verbose)


define(function(require) {
  var module = require("ui/modules").get("kibana-chord");

  module.controller("ChordController", function($scope, Private) {
    $scope.$watch("esResponse", function(response) {
      $scope.dataDump = JSON.stringify(response, null, 2);
    });
  });

  function ChordProvider(Private) {
    var TemplateVisType = Private(require("ui/template_vis_type/TemplateVisType"));
    var Schemas = Private(require("ui/Vis/Schemas"));

    return new TemplateVisType({
      name: "chord",
      title: "Chord Diagram",
      icon: "fa-pie-chart", // TODO make this look like a chord diagram
      description: "Visualize network flows with a Chord Diagram.",
      requiresSearch: true,
      template: require("plugins/kibana-chord/chord.html"),
      schemas: new Schemas([
        {
          group: "metrics",
          name: "weight",
          title: "Chord Weight",
          min: 1,
          max: 1,
          aggFilter: ["count", "sum"]
        },
        {
          group: "buckets",
          name: "src",
          title: "Chord Sources",
          min: 1,
          max: 1,
          aggFilter: "terms"
        },
        {
          group: "buckets",
          name: "dest",
          title: "Chord Destinations",
          min: 1,
          max: 1,
          aggFilter: "terms"
        }
      ])
    });
  }

  require("ui/registry/vis_types").register(ChordProvider);

  return ChordProvider;

});

define(function(require) {
  var module = require("ui/modules").get("kibana-chord");

  module.controller("ChordController", function($scope, Private, $element) {
    var div = $element[0];

    $scope.$watch("esResponse", function(response) {
      $scope.dataDump = JSON.stringify(response, null, 2);
    });
  });

  function ChordProvider(Private) {
    var TemplateVisType = Private(require("ui/template_vis_type/TemplateVisType"));
    var Schemas = Private(require("ui/Vis/Schemas"));

    return new TemplateVisType({
      name: "chord",
      title: "Chord Diagram",
      icon: "fa-pie-chart", // TODO make this look like a chord diagram
      description: "Visualize network flows with a Chord Diagram.",
      requiresSearch: true,
      template: require("plugins/kibana-chord/chord.html"),
      schemas: new Schemas([
        {
          group: "metrics",
          name: "weight",
          title: "Chord Weight",
          min: 1,
          max: 1,
          aggFilter: ["count", "sum"]
        },
        {
          group: "buckets",
          name: "src",
          title: "Chord Sources",
          min: 1,
          max: 1,
          aggFilter: "terms"
        },
        {
          group: "buckets",
          name: "dest",
          title: "Chord Destinations",
          min: 1,
          max: 1,
          aggFilter: "terms"
        }
      ])
    });
  }

  require("ui/registry/vis_types").register(ChordProvider);

  return ChordProvider;

});

Add hook to get DOM from Angular


const esprima = require('esprima');
var source = 'answer = 42; hola = 5; isCold = "Si"; answer = 50;';
const tokens = esprima.tokenize(source);
//console.log(tokens);

var identificadores = tokens.filter(function (el) {
    return (el.type === "Identifier");
});
console.log("El código es: ");
console.log(source);
//console.log("Los identificadores son: ");
//console.log(identificadores);
function arrayObjectIndexOf(myArray, searchTerm, property) {
    for(var i = 0, len = myArray.length; i < len; i++) {
        if (myArray[i][property] === searchTerm) return i;
    }
    return -1;
}
var armarArray = [];
console.log("El array de Identificadores es: ");
identificadores.forEach(function (element) {
    var indice = arrayObjectIndexOf(armarArray,element.value, "id");
    if(indice == -1){ // No encontró el identificador, lo agrega.
        armarArray.push(new Identifier(element.value,1));
    } else { // Encontró el identificador, suma uno.
        armarArray[indice].quantity++;
    }
});

console.log(armarArray);

function Identifier(id, quantity) {
    this.id = id;
    this.quantity = quantity;
}

const esprima = require('esprima');
var source = 'answer = 42; hola = 5; isCold = "Si"; answer = 50;';
const tokens = esprima.tokenize(source);
//console.log(tokens);

var identificadores = tokens.filter(function (el) {
    return (el.type === "Identifier");
});

console.log("El código es: ");
console.log(source);

//console.log("Los identificadores son: ");
//console.log(identificadores);

var armarArray = [];

identificadores.forEach(function (element) {
    var indice = arrayObjectIndexOf(armarArray,element.value, "id");
    if(indice == -1){ // No encontró el identificador, lo agrega.
        armarArray.push(new Identifier(element.value,1));
    } else { // Encontró el identificador, suma uno.
        armarArray[indice].quantity++;
    }
});

console.log("El array de Identificadores es: ");
console.log(armarArray);

function arrayObjectIndexOf(myArray, searchTerm, property) {
    for(var i = 0, len = myArray.length; i < len; i++) {
        if (myArray[i][property] === searchTerm) return i;
    }
    return -1;
}

function Identifier(id, quantity) {
    this.id = id;
    this.quantity = quantity;
}

Add identifiers function and create an array of identifiers and their number of times they appear


var childProcess = require("child_process")
var path = require("path")

var env = process.env
env.NODE_ENV = "test"

var options = {
	env:   env,
	stdio: "inherit",
}

var command = path.join(".", "node_modules", ".bin", "mocha")
if (process.platform == "win32") command += ".cmd"
try {
	childProcess.spawn(command, options)
} catch (ex) {}

var childProcess = require("child_process")
var path = require("path")

var env = process.env
env.NODE_ENV = "test"

var options = {
  env: env
}

var command = path.join(".", "node_modules", ".bin", "mocha")
if (process.platform == "win32") command += ".cmd"
var run = childProcess.spawn(command, [], options)
run.stdout.pipe(process.stdout)
run.stderr.pipe(process.stderr)
run.on('close', function(code) {
  process.exit(code)
})

Fix up test runner for windows


"use strict";

var assert = require("assert");
var comfy = require("../src/comfy");

var env = {
  "REQUIRED_PROP": "required_value",
  "OPTIONAL_PROP": "optional_value"
};

var config = comfy.build(function(c) {
  c.optional("optional_prop", "not_this");
  c.optional("non_existant_optional_prop", "fallback_value");

  c.required("required_prop");
}, env /* use the custom env object instead of process.env */);

assert.equal(config.requiredProp, "required_value");
assert.equal(config.optionalProp, "optional_value");
assert.equal(config.nonExistantOptionalProp, "fallback_value");

console.log("OK!");

"use strict";

var assert = require("assert");
var comfy = require("../src/comfy");

var env = {
  "REQUIRED_PROP": "required_value",
  "OPTIONAL_PROP": "optional_value"
};

var config = comfy.build(function(c) {
  c.optional("optional_prop", "not_this");
  c.optional("non_existant_optional_prop", "fallback_value");

  c.required("required_prop");
}, env /* use the custom env object instead of process.env */);

assert.equal(config.requiredProp, "required_value");
assert.equal(config.required_prop, "required_value");

assert.equal(config.optionalProp, "optional_value");
assert.equal(config.optional_prop, "optional_value");

assert.equal(config.nonExistantOptionalProp, "fallback_value");

console.log("OK!");

Add checks for preserving the original snake_case version of property names


window = {};

var assert = require('assert');
var model = require('../../lib/model')({
    memory: true,
    noConnect: true
});

describe('Model', function () {
    describe('interface', function () {
        it('should expose expected objects', function () {
            assert.equal(typeof model.history, 'object');
            assert.equal(typeof model.user, 'object');
            assert.equal(typeof model.apps, 'object');
        });

        it('should expose expected functions', function () {
            assert.equal(typeof model.restore, 'function');
            assert.equal(typeof model.save, 'function');
            assert.equal(typeof model.observe, 'function');
        });
    });

    describe('first run', function () {
        it('should have the expected history', function () {
            assert.deepEqual(model.history, {
                ftu: true,
                path: '/ftu'
            });
        });

        it('should have the expected user', function () {
            // @todo
            assert.deepEqual(model.user, {
                name: null,
                location: null,
                avatar: null
            });
        });

        it('should have the expected apps', function () {
            // @todo
            assert.equal(model.apps.length, 1);
        });
    });
});

window = {};

var assert = require('assert');
var model = require('../../lib/model')({
    memory: true,
    noConnect: true
});

describe('Model', function () {
    describe('interface', function () {
        it('should expose expected objects', function () {
            assert.equal(typeof model.history, 'object');
            assert.equal(typeof model.user, 'object');
            assert.equal(typeof model.apps, 'object');
        });

        it('should expose expected functions', function () {
            assert.equal(typeof model.restore, 'function');
            assert.equal(typeof model.save, 'function');
            assert.equal(typeof model.observe, 'function');
        });
    });

    describe('first run', function () {
        it('should have the expected history', function () {
            assert.deepEqual(model.history, {
                ftu: true,
                path: '/ftu'
            });
        });

        it('should have the expected user', function () {
            // @todo
            assert.deepEqual(model.user, {
                name: null,
                location: null,
                avatar: null
            });
        });

        it('should have the expected apps', function () {
            // @todo
            assert.equal(model.apps.length, 0);
        });
    });
});

Fix unit test assertion for number of pre-included apps


import _ from 'lodash';
import { FETCH_POSTS } from '../actions';

export default function(state = {}, action) {
  switch (action.type) {
    case FETCH_POSTS:
      return _.mapKeys(action.payload.data, 'id');
    default:
      return state;
  }
}

import _ from 'lodash';
import { FETCH_POSTS, FETCH_POST } from '../actions';

export default function(state = {}, action) {
  switch (action.type) {
    case FETCH_POST:
      const post = action.payload.data;
      // const newState = { ...state  };
      // newState[post.id] = post;
      // return newState;

      return { ...state, [action.payload.data.id]: action.payload.data };
    case FETCH_POSTS:
      return _.mapKeys(action.payload.data, 'id');
    default:
      return state;
  }
}

Add FETCH_POST case to reducer