var GameOfLife = (function() {
    var ALIVE = "alive", DEAD = "dead";

    function Cell(initialState) {
        var state = initialState || DEAD;

        function getNumberOfAlive(neighbours) {
            var nbAliveCells = 0;
            neighbours.forEach(function (neighbor) {
                if (neighbor.isAlive()) {
                    nbAliveCells += 1;
                }
            });
            return nbAliveCells;
        }

        return {
            setNeighbours: function (neighbours) {
                if(getNumberOfAlive(neighbours) < 2) {
                    state = DEAD;
                }
            },
            isAlive: function() {
                return state === ALIVE;
            }
        };
    }

    function AliveCell() {
        return new Cell(ALIVE);
    }

    function DeadCell() {
        return new Cell(DEAD);
    }

    return {
        AliveCell: AliveCell,
        DeadCell: DeadCell
    };
})();

describe('Live cell', function () {
    it('should die when it has fewer than two live neighbours', function () {
        var cell = new GameOfLife.AliveCell();

        cell.setNeighbours([new GameOfLife.AliveCell()]);

        expect(cell.isAlive()).toBe(false);
    });
});

var GameOfLife = (function() {
    var ALIVE = "alive", DEAD = "dead";

    function Cell(initialState) {
        var state = initialState || DEAD;

        function getNumberOfAlive(neighbours) {
            var nbAliveCells = 0;
            neighbours.forEach(function (neighbor) {
                if (neighbor.isAlive()) {
                    nbAliveCells += 1;
                }
            });
            return nbAliveCells;
        }

        return {
            setNeighbours: function (neighbours) {
                if(getNumberOfAlive(neighbours) < 2) {
                    state = DEAD;
                }
            },
            isAlive: function() {
                return state === ALIVE;
            }
        };
    }

    function AliveCell() {
        return new Cell(ALIVE);
    }

    function DeadCell() {
        return new Cell(DEAD);
    }

    return {
        AliveCell: AliveCell,
        DeadCell: DeadCell
    };
})();

describe('Live cell', function () {
    it('should die when it has fewer than two live neighbours', function () {
        var cell = new GameOfLife.AliveCell();

        cell.setNeighbours([new GameOfLife.AliveCell()]);

        expect(cell.isAlive()).toBe(false);
    });
    it('should stay alive when it has at least two live neighbours', function () {
        var cell = new GameOfLife.AliveCell();

        cell.setNeighbours([new GameOfLife.AliveCell(), new GameOfLife.AliveCell()]);

        expect(cell.isAlive()).toBe(true);
    });
});

Add test for use case when at least two neightbours are alive


var Listing = require('../models/listing.js');
var app = require('../../server/app.js');

var listings = {};

//Controller method - retrieve all listings from DB
listings.getAll = function(callback) {
  // Listing.findAll({ order: ['createdAt', 'DESC'] })
  Listing.findAll()
    .then(function(listings) {
      callback(200, listings);
    })
    .catch(function(error) {
      console.error(error);
      callback(404, error);
    });
};

//Controller method - add a listings to DB
listings.addOne = function(listing, callback) {
  Listing.create(listing)
    .then(function(listing) {
      callback(201, listing);
    })
    .catch(function(error) {
      console.error(error);
      callback(404, error);
    });
};

module.exports = listings;

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

var listings = {};

//Controller method - retrieve all listings from DB
listings.getAll = function(callback) {
  // Listing.findAll({ order: ['createdAt', 'DESC'] })
  db.Listing.findAll()
    .then(function(listings) {
      callback(200, listings);
    })
    .catch(function(error) {
      console.error(error);
      callback(404, error);
    });
};

//Controller method - add a listings to DB
listings.addOne = function(listing, callback) {
  db.Listing.create(listing)
    .then(function(listing) {
      callback(201, listing);
    })
    .catch(function(error) {
      console.error(error);
      callback(404, error);
    });
};

module.exports = listings;

Fix listings controller issue of requiring model


var express = require('express');
var passport = require('passport');
var OpenIDConnectStrategy = require('passport-openidconnect');
var db = require('../db');

passport.use(new OpenIDConnectStrategy({
  issuer: 'https://server.example.com',
  authorizationURL: 'https://server.example.com/authorize',
  tokenURL: 'https://server.example.com/token',
  userInfoURL: 'https://server.example.com/userinfo',
  clientID: process.env['CLIENT_ID'],
  clientSecret: process.env['CLIENT_SECRET'],
  callbackURL: '/oauth2/redirect',
  scope: [ 'profile' ],
  state: true
},
function verify(issuer, profile, cb) {
  return cb(null, profile);
}));

passport.serializeUser(function(user, cb) {
  process.nextTick(function() {
    cb(null, { id: user.id, username: user.username, name: user.displayName });
  });
});

passport.deserializeUser(function(user, cb) {
  process.nextTick(function() {
    return cb(null, user);
  });
});

var router = express.Router();

router.get('/login', passport.authenticate('openidconnect'));

router.get('/oauth2/redirect', passport.authenticate('openidconnect', {
  successReturnToOrRedirect: '/',
  failureRedirect: '/login'
}));

router.post('/logout', function(req, res, next) {
  req.logout();
  res.redirect('/');
});

module.exports = router;

var express = require('express');
var passport = require('passport');
var OpenIDConnectStrategy = require('passport-openidconnect');

passport.use(new OpenIDConnectStrategy({
  issuer: 'https://server.example.com',
  authorizationURL: 'https://server.example.com/authorize',
  tokenURL: 'https://server.example.com/token',
  userInfoURL: 'https://server.example.com/userinfo',
  clientID: process.env['CLIENT_ID'],
  clientSecret: process.env['CLIENT_SECRET'],
  callbackURL: '/oauth2/redirect',
  scope: [ 'profile' ]
}, function verify(issuer, profile, cb) {
  return cb(null, profile);
}));

passport.serializeUser(function(user, cb) {
  process.nextTick(function() {
    cb(null, { id: user.id, username: user.username, name: user.displayName });
  });
});

passport.deserializeUser(function(user, cb) {
  process.nextTick(function() {
    return cb(null, user);
  });
});

var router = express.Router();

router.get('/login', passport.authenticate('openidconnect'));

router.get('/oauth2/redirect', passport.authenticate('openidconnect', {
  successReturnToOrRedirect: '/',
  failureRedirect: '/login'
}));

router.post('/logout', function(req, res, next) {
  req.logout();
  res.redirect('/');
});

module.exports = router;

Remove unused require statement, and unnecessary state option.


const isDate = (key) => key === "date";

module.exports = function (initialData, fields, maxValues) {
    if (initialData.length > maxValues) {
        initialData = initialData.slice(initialData.length - this.limit);
    }

    var columnData = {
      'date': []
    };

    fields.forEach(f => columnData[f] = []);

    initialData.forEach((value) => {
      for (let key of Object.keys(value)) {
        if (!columnData[key]) continue;
        columnData[key].push(isDate(key) ? new Date(value[key]) : value[key]);
      }
    });

    var columns = [];
    for (let key of Object.keys(columnData)) {
      if (isDate(key)) {
        columns.push(['x'].concat(columnData[key]));
      }

      else columns.push([key].concat(columnData[key]));
    }

    return columns;
};

const isDate = (key) => key === "date";

module.exports = function (initialData, fields, maxValues) {
    if (initialData.length > maxValues) {
        initialData = initialData.slice(initialData.length - maxValues);
    }

    var columnData = {
      'date': []
    };

    fields.forEach(f => columnData[f] = []);

    initialData.forEach((value) => {
      for (let key of Object.keys(value)) {
        if (!columnData[key]) continue;
        columnData[key].push(isDate(key) ? new Date(value[key]) : value[key]);
      }
    });

    var columns = [];
    for (let key of Object.keys(columnData)) {
      if (isDate(key)) {
        columns.push(['x'].concat(columnData[key]));
      }

      else columns.push([key].concat(columnData[key]));
    }

    return columns;
};

Correct variable name to maxValues


/*************************************************
 * Copyright (c) 2015 Ansible, Inc.
 *
 * All Rights Reserved
 *************************************************/

import {templateUrl} from '../../shared/template-url/template-url.factory';

export default {
    name: 'notifications',
    route: '/notification_templates',
    templateUrl: templateUrl('notifications/notification-templates-list/list'),
    controller: 'notificationTemplatesListController'
};

/*************************************************
 * Copyright (c) 2015 Ansible, Inc.
 *
 * All Rights Reserved
 *************************************************/

import {templateUrl} from '../../shared/template-url/template-url.factory';

export default {
    name: 'notifications',
    route: '/notification_templates',
    templateUrl: templateUrl('notifications/notification-templates-list/list'),
    controller: 'notificationTemplatesListController',
    ncyBreadcrumb: {
        parent: 'setup',
        label: 'NOTIFICATION TEMPLATES'
    },
};

Fix breadcrumb for notification templates page


var shouldAddEventListener;
// define a handler
function doc_keyUp(e) {
    // this would test if keyCode 85 (u) or keyCode 73 (i) was pressed
    if (e.keyCode == 85) {
      console.log("Keyboard for like triggered.");
      document.querySelector("#top-level-buttons > ytd-toggle-button-renderer:nth-child(1) > a").click();
    } else if (e.keyCode == 73) {
      console.log("Keyboard for dislike triggered.");
      document.querySelector("#top-level-buttons > ytd-toggle-button-renderer:nth-child(2) > a").click();
    }
}

// register the handler
if ((shouldAddEventListener == null ? true : shouldAddEventListener)) {
    shouldAddEventListener = false;
    console.log("Adding YouTube like keyboard shortcut event listener...")
    document.addEventListener('keyup', doc_keyUp, false);
}

var shouldAddEventListener;
// define a handler
function doc_keyUp(e) {
    // this would test if keyCode 219 ([) or keyCode 221 (]) was pressed
    if (e.keyCode == 219) {
      console.log("Keyboard for like triggered.");
      document.querySelector("#top-level-buttons > ytd-toggle-button-renderer:nth-child(1) > a").click();
    } else if (e.keyCode == 221) {
      console.log("Keyboard for dislike triggered.");
      document.querySelector("#top-level-buttons > ytd-toggle-button-renderer:nth-child(2) > a").click();
    }
}

// register the handler
if ((shouldAddEventListener == null ? true : shouldAddEventListener)) {
    shouldAddEventListener = false;
    console.log("Adding YouTube like keyboard shortcut event listener...")
    document.addEventListener('keyup', doc_keyUp, false);
}

Change to [ and ]


var _ = require('underscore');
var StylesFactory = require('../../styles-factory');
var StyleFormDefaultModel = require('../style-form-default-model');

module.exports = StyleFormDefaultModel.extend({

  parse: function (r) {
    var geom = r.geom;
    var attrs = {
      fill: r.fill,
      stroke: r.stroke,
      blending: r.blending,
      resolution: r.resolution
    };
    var isAggregatedType = _.contains(StylesFactory.getAggregationTypes(), r.type);

    if (isAggregatedType || (geom && geom.getSimpleType() === 'polygon')) {
      delete attrs.fill.size;
    }

    if (geom && geom.getSimpleType() === 'line') {
      delete attrs.fill;
    }

    if (r.type === 'heatmap') {
      attrs = _.omit(attrs, 'stroke', 'blending');
    } else {
      attrs = _.omit(attrs, 'resolution');
    }

    return attrs;
  },

  _onChange: function () {
    this._styleModel.set(_.clone(this.attributes));
  }

});

var _ = require('underscore');
var StylesFactory = require('../../styles-factory');
var StyleFormDefaultModel = require('../style-form-default-model');

module.exports = StyleFormDefaultModel.extend({

  parse: function (r) {
    var geom = r.geom;
    var attrs = {
      fill: r.fill,
      stroke: r.stroke,
      blending: r.blending,
      resolution: r.resolution
    };
    var isAggregatedType = _.contains(StylesFactory.getAggregationTypes(), r.type);

    if (isAggregatedType || (geom && geom.getSimpleType() === 'polygon')) {
      if (attrs.fill.size) {
        attrs.fill = _.omit(attrs.fill, 'size');
      }
    }

    if (geom && geom.getSimpleType() === 'line') {
      attrs = _.omit(attrs.fill);
    }

    if (r.type === 'heatmap') {
      attrs = _.omit(attrs, 'stroke', 'blending');
    } else {
      attrs = _.omit(attrs, 'resolution');
    }

    return attrs;
  },

  _onChange: function () {
    this._styleModel.set(_.clone(this.attributes));
  }

});

Create a new object instead of modifying original


/** This code is to be ran on client side
 * so that the browser can execute JS for node correctly.
 */

var inherits = function (ctor, superCtor) {
  /** See nodejs lib sys.
   */
  var tempCtor = function(){};
  tempCtor.prototype = superCtor.prototype;
  ctor.super_ = superCtor;
  ctor.prototype = new tempCtor();
  ctor.prototype.constructor = ctor;
};

var stdio_stdout = {
  write: function(data) {
    data = data.replace(/\n/g, '<br />');
    document.body.innerHTML += data;
  }
};

process = {
  exit: function(){},
  argv: ['-v', '-d'],
  browser: true,
  memoryUsage: function() {return 'Not available'},
  stdout: stdio_stdout,
  stdio: stdio_stdout,
  binding: function() {return {
    writeError: function(data) {console.error(data)}
  }},
  inherits: inherits
};

if(!Object.keys) {
  // The function is only defined in V8.
  Object.keys = function(obj) {
    var keys = new Array();
    for(var key in obj) {
      keys.push(key);
    }
    return keys;
  }
}

/** This code is to be ran on client side
 * so that the browser can execute JS for node correctly.
 */

var inherits = function (ctor, superCtor) {
  /** See nodejs lib sys.
   */
  var tempCtor = function(){};
  tempCtor.prototype = superCtor.prototype;
  ctor.super_ = superCtor;
  ctor.prototype = new tempCtor();
  ctor.prototype.constructor = ctor;
};

var stdio_stdout = {
  write: function(data) {
    data = data.replace(/\n/g, '<br />');
    document.body.innerHTML += data;
  }
};

process = {
  exit: function(){},
  argv: ['-v', '-d'],
  browser: true,
  memoryUsage: function() {return 'Not available'},
  stdout: stdio_stdout,
  stdio: stdio_stdout,
  binding: function() {return {
    writeError: function(data) {console.error(data)}
  }},
  inherits: inherits
};

__dirname = 'browser';

if(!Object.keys) {
  // The function is only defined in V8.
  Object.keys = function(obj) {
    var keys = new Array();
    for(var key in obj) {
      keys.push(key);
    }
    return keys;
  }
}

Add __dirname var for browser.


angular.module('codebrag.commits')

    .directive('scrollCommitsList', function(events) {

        var idToScrollTo = '#commits-list-end';

        return {
            restrict: 'A',
            link: function(scope, el) {
                scope.$on(events.moreCommitsLoaded, function() {
                    var options = {
                        duration: 1000,
                        easing:'easeOutCirc'
                    };
                    el.scrollTo(el.find(idToScrollTo), options);
                })
            }
        }
    });

angular.module('codebrag.commits')

    .directive('scrollCommitsList', function(events) {

        return {
            restrict: 'A',
            link: function(scope, el) {
                scope.$on(events.nextCommitsLoaded, function() {
                    el.animate({scrollTop: el[0].scrollHeight});
                });
                scope.$on(events.previousCommitsLoaded, function() {
                    el.animate({scrollTop: 0});
                })
            }
        }
    });

Change scrolling directive. It doesn't require separate element to scroll to.


/*
 This file is a part of libertysoil.org website
 Copyright (C) 2016  Loki Education (Social Enterprise)

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU Affero General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU Affero General Public License for more details.

 You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
import React from 'react';

export default class IndexPage extends React.Component {
  static fetchData(router, store) {
    if (!store.getState().getIn(['current_user', 'id'])) {
      return { status: 307, redirectTo: '/all' };
    }

    return { status: 307, redirectTo: '/feed' };
  }

  render() {
    return null;
  }
}

/*
 This file is a part of libertysoil.org website
 Copyright (C) 2016  Loki Education (Social Enterprise)

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU Affero General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU Affero General Public License for more details.

 You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
import React from 'react';

export default class IndexPage extends React.Component {
  static fetchData(router, store) {
    if (!store.getState().getIn(['current_user', 'id'])) {
      return { status: 307, redirectTo: '/welcome' };
    }

    return { status: 307, redirectTo: '/feed' };
  }

  render() {
    return null;
  }
}

Fix index page to redirect new users to /welcome instead of /all


export default {
  /* jshint expr: true */
  create (opts) {
    var elem = document.createElement(opts.extends || opts.id);
    opts.extends && elem.setAttribute('is', opts.id);
    return elem;
  },
  filterDefinitions (elem, defs) {
    var attrs = elem.attributes;
    var definitions = [];
    var isAttr = attrs.is;
    var isAttrValue = isAttr && (isAttr.value || isAttr.nodeValue);
    var tag = elem.tagName.toLowerCase();
    var definition = defs[isAttrValue || tag];

    if (definition && definition.type === this) {
      let tagToExtend = definition.extends;

      if (isAttrValue) {
        if (tag === tagToExtend) {
          definitions.push(definition);
        }
      } else if (!tagToExtend) {
        definitions.push(definition);
      }
    }

    return definitions;
  },
  selector (opts) {
    return opts.extends ? `${opts.extends}[is="${opts.id}"]` : opts.id;
  }
};

export default {
  /* jshint expr: true */
  create (opts) {
    var elem = document.createElement(opts.extends || opts.id);
    opts.extends && elem.setAttribute('is', opts.id);
    return elem;
  },
  filterDefinitions (elem, defs) {
    var attrs = elem.attributes;
    var definitions = [];
    var isAttr = attrs.is;
    var isAttrValue = isAttr && (isAttr.value || isAttr.nodeValue);
    var tag = elem.tagName.toLowerCase();
    var definition = defs[isAttrValue || tag];

    if (definition && definition.type === this) {
      let tagToExtend = definition.extends;

      if (isAttrValue) {
        if (tag === tagToExtend) {
          definitions.push(definition);
        }
      } else if (!tagToExtend) {
        definitions.push(definition);
      }
    }

    return definitions;
  }
};

Remove selector method because it's not needed.


import React, { Component } from 'react';
import PropTypes from 'prop-types';
import DatePicker from 'material-ui/DatePicker';
import FieldTitle from '../../util/FieldTitle';

export const datify = input => {
    if (!input) {
        return null;
    }

    const date = input instanceof Date ? input : new Date(input);
    if (isNaN(date)) {
        throw new Error(`Invalid date: ${input}`);
    }

    return date;
};

class DateInput extends Component {
    onChange = (_, date) => this.props.input.onChange(date);

    render() {
        const { input, isRequired, label, meta: { touched, error }, options, source, elStyle, resource } = this.props;

        return (<DatePicker
            {...input}
            errorText={touched && error}
            floatingLabelText={<FieldTitle label={label} source={source} resource={resource} isRequired={isRequired} />}
            DateTimeFormat={Intl.DateTimeFormat}
            container="inline"
            autoOk
            value={datify(input.value)}
            onChange={this.onChange}
            style={elStyle}
            {...options}
        />);
    }
}

DateInput.propTypes = {
    addField: PropTypes.bool.isRequired,
    elStyle: PropTypes.object,
    input: PropTypes.object,
    isRequired: PropTypes.bool,
    label: PropTypes.string,
    meta: PropTypes.object,
    options: PropTypes.object,
    resource: PropTypes.string,
    source: PropTypes.string,
};

DateInput.defaultProps = {
    addField: true,
    options: {},
};

export default DateInput;

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import DatePicker from 'material-ui/DatePicker';
import FieldTitle from '../../util/FieldTitle';

export const datify = input => {
    if (!input) {
        return null;
    }

    const date = input instanceof Date ? input : new Date(input);
    if (isNaN(date)) {
        throw new Error(`Invalid date: ${input}`);
    }

    return date;
};

class DateInput extends Component {
    onChange = (_, date) => this.props.input.onChange(date);
    onBlur = () => this.props.input.onBlur();

    render() {
        const { input, isRequired, label, meta: { touched, error }, options, source, elStyle, resource } = this.props;

        return (<DatePicker
            {...input}
            errorText={touched && error}
            floatingLabelText={<FieldTitle label={label} source={source} resource={resource} isRequired={isRequired} />}
            DateTimeFormat={Intl.DateTimeFormat}
            container="inline"
            autoOk
            value={datify(input.value)}
            onChange={this.onChange}
            onBlur={this.onBlur}
            style={elStyle}
            {...options}
        />);
    }
}

DateInput.propTypes = {
    addField: PropTypes.bool.isRequired,
    elStyle: PropTypes.object,
    input: PropTypes.object,
    isRequired: PropTypes.bool,
    label: PropTypes.string,
    meta: PropTypes.object,
    options: PropTypes.object,
    resource: PropTypes.string,
    source: PropTypes.string,
};

DateInput.defaultProps = {
    addField: true,
    options: {},
};

export default DateInput;

Fix a redux-form onBlur related issue


/* eslint-env mocha */
const BubbleSort = require('../../../src').Algorithms.Sorting.BubbleSort;
const assert = require('assert');

describe('BubbleSort', () => {
  it('should have no data when empty initialization', () => {
    const inst = new BubbleSort();
    assert.equal(inst.size, 0);
    assert.deepEqual(inst.unsortedList, []);
    assert.deepEqual(inst.sortedList, []);
  });

  it('should sort the array', () => {
    const inst = new BubbleSort([2, 1, 3, 4]);
    assert.equal(inst.size, 4);

    assert.deepEqual(inst.unsortedList, [2, 1, 3, 4]);
    assert.deepEqual(inst.sortedList, [1, 2, 3, 4]);
    assert.equal(inst.toString(), '1, 2, 3, 4');
  });
});

/* eslint-env mocha */
const BubbleSort = require('../../../src').Algorithms.Sorting.BubbleSort;
const assert = require('assert');

describe('BubbleSort', () => {
  it('should have no data when empty initialization', () => {
    const inst = new BubbleSort();
    assert.equal(inst.size, 0);
    assert.deepEqual(inst.unsortedList, []);
    assert.deepEqual(inst.sortedList, []);
  });

  it('should sort the array', () => {
    const inst = new BubbleSort([2, 1, 3, 4]);
    assert.equal(inst.size, 4);

    assert.deepEqual(inst.unsortedList, [2, 1, 3, 4]);
    assert.deepEqual(inst.sortedList, [1, 2, 3, 4]);
    assert.equal(inst.toString(), '1, 2, 3, 4');
  });

  it('should sort 2 element array', () => {
    const inst = new BubbleSort([2, 1]);
    assert.equal(inst.size, 2);

    assert.deepEqual(inst.unsortedList, [2, 1]);
    assert.deepEqual(inst.sortedList, [1, 2]);
    assert.equal(inst.toString(), '1, 2');
  });

  it('should sort 1 element array', () => {
    const inst = new BubbleSort([1]);
    assert.equal(inst.size, 1);

    assert.deepEqual(inst.unsortedList, [1]);
    assert.deepEqual(inst.sortedList, [1]);
    assert.equal(inst.toString(), '1');
  });
});

Test Bubble Sort: Test for small arrays


/*global Blacklight */

(function($) {
  'use strict';

  Blacklight.doResizeFacetLabelsAndCounts = function() {
    // adjust width of facet columns to fit their contents
    function longer (a,b){ return b.textContent.length - a.textContent.length; }

    $('ul.facet-values, ul.pivot-facet').each(function(){
      var longest = $(this).find('span.facet-count').sort(longer).first();
      var clone = longest.clone()
        .css('visibility','hidden').css('width', 'auto');
      $('span.facet-count:visible:first').append(clone);
      $(this).find('.facet-count').first().width(clone.width());
      clone.remove();
    });
  };

  Blacklight.onLoad(function() {
    Blacklight.doResizeFacetLabelsAndCounts();
  });
})(jQuery);

/*global Blacklight */

(function($) {
  'use strict';

  Blacklight.doResizeFacetLabelsAndCounts = function() {
    // adjust width of facet columns to fit their contents
    function longer(a, b) {
      return b.textContent.length - a.textContent.length;
    }

    $('ul.facet-values, ul.pivot-facet').each(function() {
      var longest = $(this)
        .find('span.facet-count')
        .sort(longer)[0];

      if (longest && longest.textContent) {
        var width = longest.textContent.length + 1 + 'ch';
        $(this)
          .find('.facet-count')
          .first()
          .width(width);
      }
    });
  };

  Blacklight.onLoad(function() {
    Blacklight.doResizeFacetLabelsAndCounts();
  });
}(jQuery));

Update to latest blacklight facet wrapping helper


'use strict';
var _ = require('lodash');

var objects = {
  user: {
    mutable: true,
    value: '0x01'
  },
  session: {
    mutable: true,
    value: '0x02'
  },
  app: {
    mutable: true,
    value: '0x03'
  },
  collaborator: {
    mutable: true,
    value: '0x04'
  },
  env: {
    mutable: true,
    value: '0x05'
  },
  public_key: {
    mutable: false,
    value: '0x06'
  },
  private_key: {
    mutable: false,
    value: '0x07'
  },
  claim: {
    mutable: false,
    value: '0x08'
  },
  keyring: {
    mutable: true,
    value: '0x09'
  },
  keyring_member: {
    mutable: false,
    value: '0x0A'
  },
  credential: {
    mutable: false,
    value: '0x0B'
  },
  verification_code: {
    mutable: false,
    value: '0x0C'
  },
};

objects.name = function(b) {
  b = ''+b; // coerce to string
  return _.findKey(objects, (val, name) => {
    return val.value === b;
  });
};

objects.value = function(name) {
  return objects[name].value;
};

module.exports = objects;

'use strict';
var _ = require('lodash');

var objects = {
  user: {
    mutable: true,
    value: '0x01'
  },
  session: {
    mutable: true,
    value: '0x02'
  },
  app: {
    mutable: true,
    value: '0x03'
  },
  collaborator: {
    mutable: true,
    value: '0x04'
  },
  env: {
    mutable: true,
    value: '0x05'
  },
  public_key: {
    mutable: false,
    value: '0x06'
  },
  private_key: {
    mutable: false,
    value: '0x07'
  },
  claim: {
    mutable: false,
    value: '0x08'
  },
  keyring: {
    mutable: true,
    value: '0x09'
  },
  keyring_member: {
    mutable: false,
    value: '0x0A'
  },
  credential: {
    mutable: false,
    value: '0x0B'
  },
  verification_code: {
    mutable: false,
    value: '0x0C'
  },
  org: {
    mutable: false,
    value: '0x0D'
  },
  orgs_member: {
    mutable: true,
    value: '0x0E'
  },
};

objects.name = function(b) {
  b = ''+b; // coerce to string
  return _.findKey(objects, (val, name) => {
    return val.value === b;
  });
};

objects.value = function(name) {
  return objects[name].value;
};

module.exports = objects;

Add object types for orgs


import Ember from 'ember';

const {
  $
} = Ember;

const log = [];
const addItem = (event, xhr, settings)=> log.push({ event, xhr, settings });

let getItemForSerializer = ()=> 'item';

let filterFunction = ()=> true;

const LoggerObject = {
  clear: ()=> (log.length = 0),

  getSerialized: ()=> JSON.stringify(log.filter(filterFunction).map(getItemForSerializer)),

  setFilterFunction(func) {
    if (typeof func === 'function') {
      filterFunction = func;
    }
  },

  setGetItemForSerializer(func) {
    if (typeof func === 'function') {
      getItemForSerializer = func;
    }
  }
};

export function initLogger(options) {
  LoggerObject.setGetItemForSerializer(options.getItemForSerializer);
  LoggerObject.setFilterFunction(options.filter);
  window[options.globalName] = LoggerObject;
  $(document).ajaxComplete(addItem);
}

import Ember from 'ember';

const {
  $
} = Ember;

const log = [];
const addItem = (event, xhr, settings)=> log.push({ event, xhr, settings });

let getItemForSerializer = ()=> 'item';

let filterFunction = ()=> true;

const LoggerObject = {
  clear: ()=> (log.length = 0),

  getSerialized: ()=> JSON.stringify(log.filter(filterFunction).map(getItemForSerializer)),

  setFilterFunction(func) {
    if (typeof func === 'function') {
      filterFunction = func;
    }

    return LoggerObject;
  },

  setGetItemForSerializer(func) {
    if (typeof func === 'function') {
      getItemForSerializer = func;
    }

    return LoggerObject;
  },

  register(name) {
    window[name] = LoggerObject;

    return LoggerObject;
  },

  subscribe() {
    $(document).ajaxComplete(addItem);

    return LoggerObject;
  }
};

export function initLogger(options) {
  LoggerObject
    .setGetItemForSerializer(options.getItemForSerializer)
    .setFilterFunction(options.filter)
    .register(options.globalName)
    .subscribe();
}

Add changing for loggerObject methods


import { Model as <%= className %>Mixin<%if (projections) {%>, defineProjections<%}%> } from '../mixins/regenerated/models/<%= name %>';
import <%if(parentModelName) {%><%= parentClassName %>Model from './<%= parentModelName %>';<%}else{%>{ Projection } from 'ember-flexberry-data';<%}%>

let Model = <%if(parentModelName) {%><%= parentClassName %>Model.extend<%}else{%>Projection.Model.extend<%}%>(<%= className %>Mixin, {

});

<%if(parentModelName) {%>Model.reopenClass({
  _parentModelName: '<%= parentModelName %>'
});<%}%>

<%if(projections) {%>defineProjections(Model);<%}%>

export default Model;

Fix JSCS in generated models


/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule RCTLog
 * @flow
 */
'use strict';

var BatchedBridge = require('BatchedBridge');

var invariant = require('fbjs/lib/invariant');

var levelsMap = {
  log: 'log',
  info: 'info',
  warn: 'warn',
  error: 'error',
  fatal: 'error',
};

class RCTLog {
  // level one of log, info, warn, error, mustfix
  static logIfNoNativeHook() {
    var args = Array.prototype.slice.call(arguments);
    var level = args.shift();
    var logFn = levelsMap[level];
    invariant(
      logFn,
      'Level "' + level + '" not one of ' + Object.keys(levelsMap)
    );
    if (typeof global.nativeLoggingHook === 'undefined' || level === 'warn') {
      // We already printed in xcode, so only log here if using a js debugger
      // or if we're trying to send a yellow box from native down.
      console[logFn].apply(console, args);
    }

    return true;
  }
}

BatchedBridge.registerCallableModule(
  'RCTLog',
  RCTLog
);

module.exports = RCTLog;

/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule RCTLog
 * @flow
 */
'use strict';

var BatchedBridge = require('BatchedBridge');

var invariant = require('fbjs/lib/invariant');

var levelsMap = {
  log: 'log',
  info: 'info',
  warn: 'warn',
  error: 'error',
  fatal: 'error',
};

class RCTLog {
  // level one of log, info, warn, error, mustfix
  static logIfNoNativeHook() {
    var args = Array.prototype.slice.call(arguments);
    var level = args.shift();
    var logFn = levelsMap[level];
    invariant(
      logFn,
      'Level "' + level + '" not one of ' + Object.keys(levelsMap)
    );
    if (typeof global.nativeLoggingHook === 'undefined') {
      // We already printed in xcode, so only log here if using a js debugger
      console[logFn].apply(console, args);
    }

    return true;
  }
}

BatchedBridge.registerCallableModule(
  'RCTLog',
  RCTLog
);

module.exports = RCTLog;

Stop throwing yellew boxes when we warn from native


var Router = require('koa-router')
var middleware = require('../../utils/middleware')

module.exports = UsersController
function UsersController () {
  var auth = middleware.auth()
  var routes = new Router()
  .get(
    '/api/users/current',
    auth,
    UsersController.current
  )

  return routes.middleware()
}

/**
 * GET /users/current
 * Returns current user model
 */
UsersController.current = function* current () {
  var user = this.state.user.toJSON()
  delete user.hashedPassword
  this.body = user
}

const Router = require('koa-router')
const middleware = require('../../utils/middleware')

module.exports = UsersController
function UsersController () {
  const auth = middleware.authenticate()
  const routes = new Router()
  .get(
    '/api/users/current',
    auth,
    UsersController.current
  )

  return routes.middleware()
}

/**
 * GET /users/current
 * Returns current user model
 */
UsersController.current = function* current () {
  this.body = this.state.user
}

Use const in Users controller


import { injectGlobal } from 'styled-components';

/* eslint no-unused-expressions: 0 */
injectGlobal`
  html,
  body,
  #app {
    background-color: rgba(74, 74, 74, 0.5);
  }

  #app {
    overflow: scroll;
  }

  .cc-window {
    font-family: 'Lato', sans-serif !important;
  }

  .cc-compliance a,
  .cc-compliance a:link,
  .cc-compliance a:visited {
    transition: background-color 0.25s ease-in-out;
    color: #d3d3d3;
  }
  .cc-compliance a:hover,
  .cc-compliance a:active {
    cursor: pointer;
    background-color: #78c8b4 !important;
  }

  .cc-link,
  .cc-link:link,
  .cc-link:visited {
    transition: color 0.25s ease-in-out;
    cursor: pointer;
    color: #95a3b3 !important;
  }

  .cc-link:hover,
  .cc-link:active {
    color: #78c8b4 !important;
  }
`;

import { injectGlobal } from 'styled-components';

/* eslint no-unused-expressions: 0 */
injectGlobal`
  html,
  body,
  #app {
    background-color: rgba(74, 74, 74, 0.5);
  }

  .cc-window {
    font-family: 'Lato', sans-serif !important;
  }

  .cc-compliance a,
  .cc-compliance a:link,
  .cc-compliance a:visited {
    transition: background-color 0.25s ease-in-out;
    color: #d3d3d3;
  }
  .cc-compliance a:hover,
  .cc-compliance a:active {
    cursor: pointer;
    background-color: #78c8b4 !important;
  }

  .cc-link,
  .cc-link:link,
  .cc-link:visited {
    transition: color 0.25s ease-in-out;
    cursor: pointer;
    color: #95a3b3 !important;
  }

  .cc-link:hover,
  .cc-link:active {
    color: #78c8b4 !important;
  }
`;

Fix to css causing unneeded scroll bar being visible.


class Reservation {
  constructor(id, partySize) {
  this.id = id;
  this.party_of = partySize;
}

  update() {
    var reservation = { reservation: this };
    $.ajax({
        method: 'patch',
        url: 'reservations/' + this.id + '.json',
        data: reservation
      }).done(function(){

      })
    };
  };

class Reservation {
  constructor(id, partySize) {
  this.id = id;
  this.party_of = partySize;
}

  update(callback) {
    var reservation = { reservation: this };
    $.ajax({
        method: 'patch',
        url: 'reservations/' + this.id + '.json',
        data: reservation
      }).done(function(){
        callback();
      })
    };
  };

Add callback parameter to update function.


$(document).ready(function(){
  $(".restaurant-info").hide();

  $(".restaurant-tab-container").on("click", function(event) {
    var restaurant = $(this).find(".restaurant-tab").prop('id');
    var restaurantId = getRestaurantId(restaurant)
    $("#restaurant-info-" + restaurantId).slideToggle("slow");
  })

  function checkAllWaittime() {
    $(".restaurants-wrapper").children().each(function() {
    var htmlId = $(this).prop('id')
    console.log(htmlId);
    getWaittime(getRestaurantId(htmlId));
  })
  }

  function getWaittime(restaurantId) {
    var restaurantUrl = "/restaurants/" + restaurantId +"/waittime"
    $.ajax({
      method: "get",
      url: restaurantUrl,
      data: { id: restaurantId }
    }).done(function(response){
      console.log(response)
      $("#restaurant-" + restaurantId).find(".waittime-display").replaceWith("
" + response + " minutes
")
    })
  }

  function getRestaurantId(htmlId) {
    var match = htmlId.match(/\d+/g);
    return match[0];
  }

  setInterval(checkAllWaittime, 60000)
})

$(document).ready(function(){
  $(".restaurant-info").hide();

  $(".restaurant-tab-container").on("click", function(event) {
    var restaurant = $(this).find(".restaurant-tab").prop('id');
    var restaurantId = getRestaurantId(restaurant)
    $("#restaurant-info-" + restaurantId).slideToggle("slow");
  })

  function checkAllWaittime() {
    $(".restaurants-wrapper").children().each(function() {
    var htmlId = $(this).find('.restaurant-tab').prop('id')
    console.log(htmlId);
    getWaittime(getRestaurantId(htmlId));
  })
  }

  function getWaittime(restaurantId) {
    var restaurantUrl = "/restaurants/" + restaurantId +"/waittime"
    $.ajax({
      method: "get",
      url: restaurantUrl,
      data: { id: restaurantId }
    }).done(function(response){
      console.log(response)
      $("#restaurant-" + restaurantId).find(".waittime-display").replaceWith("
" + response + " minutes
")
    })
  }

  function getRestaurantId(htmlId) {
    var match = htmlId.match(/\d+/g);
    return match[0];
  }

  setInterval(checkAllWaittime, 60000)
})

Fix bug with restaurant div


'use strict';

// Node.js core.
const execSync = require('child_process').execSync;
const path = require('path');

// Logger.
const logger = require('strapi-utils').logger;

module.exports = (scope, success, error) => {
  const knex  = require(path.resolve(`${scope.rootPath}/node_modules/knex`))({
    client: scope.client.module,
    connection: Object.assign(scope.database.settings, {
      user: scope.database.settings.username
    })
  });

  knex.raw('select 1+1 as result').then(() => {
    logger.info('The app has been connected to the database successfully');
    knex.destroy();
    execSync(`rm -r ${scope.rootPath}`);

    logger.info('Copying the dashboard...');

    success();
  })
  .catch(() => {
    logger.warn('Database connection has failed! Make sure your database is running.');
    error();
  });
};

'use strict';

// Node.js core.
const execSync = require('child_process').execSync;
const path = require('path');

// Logger.
const logger = require('strapi-utils').logger;

module.exports = (scope, success, error) => {
  const knex  = require(path.resolve(`${scope.rootPath}/node_modules/knex`))({
    client: scope.client.module,
    connection: Object.assign({}, scope.database.settings, {
      user: scope.database.settings.username
    })
  });

  knex.raw('select 1+1 as result').then(() => {
    logger.info('The app has been connected to the database successfully');
    knex.destroy();
    execSync(`rm -r ${scope.rootPath}`);

    logger.info('Copying the dashboard...');

    success();
  })
  .catch(() => {
    logger.warn('Database connection has failed! Make sure your database is running.');
    error();
  });
};

Remove user key from generated databases.json file


'use strict';

var $ = require('jquery')
    , io = require('socket.io-client')
    , game = require('./game')
    , primus;

console.log('connecting to server ...');

/* global Primus */
primus = Primus.connect();

primus.on('open', function() {
    console.log('connection established');
});

// event handler for when this client joins a room
primus.on('client.joinRoom', function(roomId) {
    console.log('joined room', roomId);
});

// event handler for initializing the client
primus.on('client.init', function(config) {
    // remove the canvas if it was already created
    // this may happen if the server gets restarted mid-game
    $('canvas').remove();

    // run the game
    game.run(primus, config);
});

// event handler for when an error occurs
primus.on('error', function error(err) {
    console.error('Something horrible has happened', err.stack);
});

'use strict';

var $ = require('jquery')
    , game = require('./game')
    , primus;

console.log('connecting to server ...');

/* global Primus */
primus = Primus.connect();

primus.on('open', function() {
    console.log('connection established');
});

// event handler for when this client joins a room
primus.on('client.joinRoom', function(roomId) {
    console.log('joined room', roomId);
});

// event handler for initializing the client
primus.on('client.init', function(config) {
    // remove the canvas if it was already created
    // this may happen if the server gets restarted mid-game
    $('canvas').remove();

    // run the game
    game.run(primus, config);
});

// event handler for when an error occurs
primus.on('error', function error(err) {
    console.error('Something horrible has happened', err.stack);
});

Remove an unused require statement


app.directive("manaCurveChart", function($timeout) {
  return {
    restrict: "E",
    template: "Mana curve

",
    scope: {
      curve: "="
    },
    link: function(scope, elem, attrs) {
      scope.$watch("curve", function(curve) {
        if (!curve) {
          return;
        }

        var data = new google.visualization.DataTable();
        data.addColumn("string", "Converted Mana Cost");
        data.addColumn("number", "Count");
        for (var i = 0; i < curve.length; i++) {
          var item = curve[i];
          data.addRow([item[0], item[1]]);
        }

        var options = {
          backgroundColor: "#eee",
          legend: { position: "none" },
          fontSize: 12,
          fontName: "'Helvetica Neue', Helvetica, Arial, sans-serif;",
          chartArea: {
            width: "100%",
            height: "90%"
          },
          titleTextStyle: {
            fontSize: 14,
            bold: false
          }
        };

        $timeout(function() {
          var chart = new google.visualization.ColumnChart(elem.children("div")[0]);
          chart.draw(data, options);
        }, 1000);
      });
    }
  }
});

app.directive("manaCurveChart", function($timeout) {
  return {
    restrict: "E",
    template: "Mana curve

",
    scope: {
      curve: "="
    },
    link: function(scope, elem, attrs) {
      scope.$watch("curve", function(curve) {
        if (!curve) {
          return;
        }

        var data = new google.visualization.DataTable();
        data.addColumn("string", "Converted Mana Cost");
        data.addColumn("number", "Count");
        for (var i = 0; i < curve.length; i++) {
          var item = curve[i];
          data.addRow([item[0], item[1]]);
        }

        var options = {
          backgroundColor: "#eee",
          legend: { position: "none" },
          fontSize: 12,
          fontName: "'Helvetica Neue', Helvetica, Arial, sans-serif;",
          chartArea: {
            width: "100%",
            height: "80%"
          },
          titleTextStyle: {
            fontSize: 14,
            bold: false
          }
        };

        $timeout(function() {
          var chart = new google.visualization.ColumnChart(elem.children("div")[0]);
          chart.draw(data, options);
        }, 1000);
      });
    }
  }
});

Reduce height of mana curve chart


window._trackJs = {
    onError: function(payload, error) {

        // ignore an error caused by DealPly (http://www.dealply.com/) chrome extension
        if (payload.message.indexOf("DealPly") > 0) {
            return false;
        }

        // this error is reported when a post request returns error, i.e. html body
        // the details provided in this case are completely useless, thus discarded
        if (payload.message.indexOf("Unexpected token <") > 0) {
            return false;
        }

        payload.network = payload.network.filter(function(item) {

            // ignore random errors from Intercom
            if (item.statusCode === 403 && payload.message.indexOf("intercom") > 0) {
                return false;
            }

            return true;
        });

        return true;
    }
};

window._trackJs = {
    onError: function(payload, error) {

        function itemExistInList(item, list) {
            for (var i = 0; i < list.length; i++) {
                if (item.indexOf(list[i]) > -1) {
                    return true;
                }
            }
            return false;
        }

        var ignorableErrors = [
            // General script error, not actionable
            "[object Event]",
            // General script error, not actionable
            "Script error.",
            // an error caused by DealPly (http://www.dealply.com/) chrome extension
            "DealPly",
            // this error is reported when a post request returns error, i.e. html body
            // the details provided in this case are completely useless, thus discarded
            "Unexpected token <"
        ];

        if (itemExistInList(payload.message, ignorableErrors)) {
            return false;
        }

        payload.network = payload.network.filter(function(item) {

            // ignore random errors from Intercom
            if (item.statusCode === 403 && payload.message.indexOf("intercom") > -1) {
                return false;
            }

            return true;
        });

        return true;
    }
};

Add improved logic to ignore errors in TrackJs


var DOCUMENTATION_OPTIONS = {
    URL_ROOT: '',
    VERSION: '0.1.9',
    LANGUAGE: 'None',
    COLLAPSE_INDEX: false,
    FILE_SUFFIX: '.html',
    HAS_SOURCE: true,
    SOURCELINK_SUFFIX: '.txt'
};

var DOCUMENTATION_OPTIONS = {
    URL_ROOT: '',
    VERSION: '0.1.10',
    LANGUAGE: 'None',
    COLLAPSE_INDEX: false,
    FILE_SUFFIX: '.html',
    HAS_SOURCE: true,
    SOURCELINK_SUFFIX: '.txt'
};

Update docs after building Travis build 219 of regro/regolith


import markdown from "markdown-it";
const md = markdown({
  xhtmlOut: true,
  breaks: true
});

export default class ExpandablePanelController {
  constructor($timeout, $sce) {
    "ngInject";
    Object.assign(this, {
      expandable: false,
      expanded: false,
      parsed: ""
    });
    $timeout( () => {

      // more then 10 lines?
      if (this.content.match(/\n/mg).length > 9) {
        this.expandable = true;
      }

      if (this.markdown) {
        this.parsed = $sce.trustAsHtml(md.render(this.content));
      }

    });
  }
  toggle() {
    this.expanded = !this.expanded;
  }
}

import markdown from "markdown-it";
const md = markdown({
  xhtmlOut: true,
  breaks: true
});

export default class ExpandablePanelController {
  constructor($sce) {
    "ngInject";
    Object.assign(this, {
      $sce,
      expandable: false,
      expanded: false,
      parsed: ""
    });
  }
  $onChanges(changes) {
    if (changes.content && changes.content.currentValue) {
      let content = changes.content.currentValue;
      // more then 10 lines?
      if (content.match(/\n/mg).length > 9) {
        this.expandable = true;
      }

      if (this.markdown) {
        this.parsed = this.$sce.trustAsHtml(md.render(content));
      }
    }
  }
  toggle() {
    this.expanded = !this.expanded;
  }
}

Use $onChanges to react to updated content


/**
 * Created by dmorina on 27/06/15.
 */

/**
 * mdTemplateCompilerDirective
 * @namespace crowdsource.template.directives
 */
(function () {
    'use strict';
    angular
        .module('crowdsource.template.directives')
        .directive('mdTemplateCompiler', mdTemplateCompilerDirective);

    function mdTemplateCompilerDirective($parse, $sce, $compile){
        return {
            restrict: 'A',
            link: function(scope, element, attrs){
                var item =  $sce.parseAsHtml(attrs.mdTemplateCompiler);

                var getItem = function () {
                    return item(scope);
                };
                scope.$watch(getItem, function (newValue) {

                    var template = angular.element(newValue);
                    var linkFn = $compile(template);
                    var el = linkFn(scope);
                    if (element.children().length){
                        $(element.children()[0]).replaceWith(el);
                    }
                    else {
                        element.append(el);
                    }
                });

            }
        };
    }
})();

/**
 * Created by dmorina on 27/06/15.
 */

/**
 * mdTemplateCompilerDirective
 * @namespace crowdsource.template.directives
 */
(function () {
    'use strict';
    angular
        .module('crowdsource.template.directives')
        .directive('mdTemplateCompiler', mdTemplateCompilerDirective);

    function mdTemplateCompilerDirective($parse, $sce, $compile, $timeout, Template) {
        return {
            restrict: 'A',
            replace: true,
            scope: {
                mdTemplateCompiler: '=',
                editor: '='
            },
            link: function (scope, element, attrs, ctrl) {
                scope.item = scope.mdTemplateCompiler;

                var templateComponents = Template.getTemplateComponents(scope);

                function update(newField, oldField) {
                    var format = _.find(templateComponents, function (item) {
                        return item.type == newField.type;
                    });

                    if (newField.hasOwnProperty('isSelected') && newField.isSelected && scope.editor) {
                        newField.toView = format.toEditor;
                    } else {
                        newField.toView = format.toHTML;
                    }

                    // TODO: Make this more robust to handle any CSV format - with quotes, commas
                    if (newField.hasOwnProperty('choices') && _.isString(scope.item.choices)) {
                        var choices = scope.item.choices;

                        scope.item.options = String(choices).split(',').map(function (item) {
                            return _.trim(item);
                        })
                    }

                    var template = newField.toView();
                    var el = angular.element(template);
                    element.html(el);
                    $compile(el)(scope);
                }

                scope.editor = scope.editor || false;

                scope.$watch('mdTemplateCompiler', function (newField, oldField) {

                    if (scope.editor) {
                        if (!newField.hasOwnProperty('isSelected') || newField.isSelected == undefined || newField.isSelected !== oldField.isSelected) {
                            update(newField, oldField);
                        }
                    } else {
                        update(newField, oldField);
                    }

                }, scope.editor);

            }
        };
    }
})();

Allow switching template and display for field


import assert from 'assert';
import { parse } from 'graphql';
import { validate } from 'graphql/validation';
import { buildASTSchema } from 'graphql/utilities/buildASTSchema';

import { InputObjectValuesHaveDescriptions } from '../../src/rules/input_object_values_have_descriptions';

describe('InputObjectValuesHaveDescriptions rule', () => {
  it('catches input object type values that have no description', () => {
    const ast = parse(`
      type Query {
        hello: String
      }

      input User {
        username: String

        # Description
        withDescription: String
      }
    `);

    const schema = buildASTSchema(ast);
    const errors = validate(schema, ast, [InputObjectValuesHaveDescriptions]);

    assert.equal(errors.length, 1);

    assert.equal(errors[0].ruleName, 'input-object-values-have-descriptions');
    assert.equal(
      errors[0].message,
      'The input value `User.username` is missing a description.'
    );
    assert.deepEqual(errors[0].locations, [{ line: 7, column: 9 }]);
  });

  it('ignores arguments that have no description', () => {
    const ast = parse(`
      type Query {
        hello(argumentWithoutDescription: String): String
      }
    `);

    const schema = buildASTSchema(ast);
    const errors = validate(schema, ast, [InputObjectValuesHaveDescriptions]);

    assert.equal(errors.length, 0);
  });
});

import assert from 'assert';
import { parse } from 'graphql';
import { validate } from 'graphql/validation';
import { buildASTSchema } from 'graphql/utilities/buildASTSchema';

import { InputObjectValuesHaveDescriptions } from '../../src/rules/input_object_values_have_descriptions';
import { expectFailsRule, expectPassesRule } from '../assertions';

describe('InputObjectValuesHaveDescriptions rule', () => {
  it('catches input object type values that have no description', () => {
    expectFailsRule(
      InputObjectValuesHaveDescriptions,
      `
      type Query {
        hello: String
      }

      input User {
        username: String

        # Description
        withDescription: String
      }
    `,
      [
        {
          message: 'The input value `User.username` is missing a description.',
          locations: [{ line: 7, column: 9 }],
        },
      ]
    );
  });

  it('ignores arguments that have no description', () => {
    expectPassesRule(
      InputObjectValuesHaveDescriptions,
      `
      type Query {
        hello(argumentWithoutDescription: String): String
      }
    `
    );
  });
});

Use test helpers in InputObjectValuesHaveDescriptions tests


/*
  Free Code Camp Challenge  - Mutations
  https://www.freecodecamp.com/challenges/mutations
*/

function mutation(arr) {
  var firstElement = arr[0].toLowerCase();
  var secondElement = arr[1].toLowerCase();
  var contains = 0;

  for (var i = 0; i < secondElement.length; i++) {
    contains = firstElement.indexOf(secondElement[i]);

    if (contains !== - 1) {
      return true;
    }
  }

  return false;
}

console.log(mutation(["hello", "hey"]));

/*
  Free Code Camp Challenge  - Mutations
  https://www.freecodecamp.com/challenges/mutations
*/

function mutation(arr) {
  var firstElement = arr[0].toLowerCase();
  var secondElement = arr[1].toLowerCase();
  var contains = 0;

  for (var i = 0; i < secondElement.length; i++) {
    contains = firstElement.indexOf(secondElement[i]);

    if (contains !== - 1) {
      return true;
    }
  }

  return false;
}

mutation(["hello", "hey"]);

Remove console.log the invocation function


// Program that identifies common element(s) that exist within both two arrays

function intersection(firstArray, secondArray) {
	var hashMap = {};
	var commonElements = [];

	// create hashmap with elements of first array as keys
	arrOne.forEach(function(element) {
		hashMap[element] = 1;
	})

	// use hashmap's O(1) look up time to check if an element in second array exists in the hash (i.e. first array)
	secondArray.forEach(function(element) { // iterate through entire second array
		if (hashMap[element] === 1) { // if element in second array exists in hashmap
			commonElements.push(element); // add matching element into output array
		}
	});
}

// Program that identifies common element(s) that exist within both two arrays

function intersection(firstArray, secondArray) {
	var hashMap = {};
	var commonElements = [];

	// create hashmap with elements of first array as keys
	arrOne.forEach(function(element) {
		hashMap[element] = 1;
	})

	// use hashmap's O(1) look up time to check if an element in second array exists in the hash (i.e. first array)
	secondArray.forEach(function(element) { // iterate through entire second array
		if (hashMap[element] === 1) { // if element in second array exists in hashmap
			commonElements.push(element); // add matching element into output array
			hashMap[element]++; // implement counter to keep track of what we already added
		}
	});
}

Implement counter to keep track of what we already added to output array


import React, { Component } from 'react';
import ReactCSSTransitionReplace from 'react-css-transition-replace';
import { connect } from 'react-redux';
import store from '../store.js';

class Description extends Component {

    constructor(props) {
        super(props);
    }

    isFinished() {
        return (this.props.names.index + 1) > this.props.names[this.props.gender.selected].length
    }

    showDesc() {
        if (!this.isFinished()) {
            return this.props.names[this.props.gender.selected][this.props.names.index].desc
        }
    }

    showID() {
        if (!this.isFinished()) {
            return this.props.names[this.props.gender.selected][this.props.names.index].id
        } else {
            return "Onki navn eftir"
        }
    }

    render() {
        return (

                <ReactCSSTransitionReplace
                    transitionName="fade-wait"
                    transitionEnterTimeout={600}
                    transitionLeaveTimeout={600}>

{this.showDesc()}

                </ReactCSSTransitionReplace>

        );
    }
}

const mapStateToProps = function (store) {

    return {
        names: store.names,
        gender: store.gender
    };
}

export default connect(mapStateToProps)(Description);

Add new class to description


import React from 'react';
import { Link } from 'react-router';
import { Menu, Segment } from 'semantic-ui-react';
import DynamicLinks from './DynamicLinks';

const Navbar = ({ id, location, logout }) =>
  <Segment inverted>
    <Menu inverted borderless>
      <Menu.Item
        as={Link}
        to="landing"
        className="logo"
      >
        <img alt="raptor" src="/client/src/assets/half-raptor.png" />
      </Menu.Item>
      <Menu.Item as={Link} to="/about" color="green" name="About" active={location === '/about'} />
      <Menu.Item as={Link} to="/listings" name="Listings" color="green" active={location === '/listings'} />
      <Menu.Item as={Link} to="/dashboard" color="green" name="customer dashboard" active={location === '/dashboard'} />
      <Menu.Item as={Link} to="/addlisting" color="green" name="addlisting" active={location === '/addlisting'} />
      <DynamicLinks location={location} id={id} logout={logout} />
    </Menu>
  </Segment>;

export default Navbar;

Navbar.propTypes = {
  location: React.PropTypes.string.isRequired,
};

import React from 'react';
import { Link } from 'react-router';
import { Menu, Segment } from 'semantic-ui-react';
import DynamicLinks from './DynamicLinks';

const Navbar = ({ id, location, logout }) =>
  <Segment inverted>
    <Menu inverted borderless>
      <Menu.Item
        as={Link}
        to="landing"
        className="logo"
      >
        <img alt="raptor" src="/client/src/assets/half-raptor.png" />
      </Menu.Item>
      <Menu.Item as={Link} to="/about" color="green" name="About Us" active={location === '/about'} />
      <Menu.Item as={Link} to="/listings" name="Listings" color="green" active={location === '/listings'} />
      <Menu.Item as={Link} to="/addlisting" color="green" name="addlisting" active={location === '/addlisting'} />
      <DynamicLinks location={location} id={id} logout={logout} />
    </Menu>
  </Segment>;

export default Navbar;

Navbar.propTypes = {
  location: React.PropTypes.string.isRequired,
};

Remove unneccessary Customer Dashboard navbar link


import React from 'react'
import darkBaseTheme from 'material-ui/styles/baseThemes/darkBaseTheme'
import getMuiTheme from 'material-ui/styles/getMuiTheme'
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
import {DrivedMainMenu} from '../../containers/drived_main_menu/DrivedMainMenu'
import AppBar from 'material-ui/AppBar'
import {DrivedGroupList} from '../../containers/drived_group_list/DrivedGroupList'
import {DrivedGroupDialog} from '../../containers/drived_group_dialog/DrivedGroupDialog'
import './app.css'

export default class App extends React.Component {
  render() {
    return (
      <MuiThemeProvider muiTheme={getMuiTheme(darkBaseTheme)}>
        <div>
          <AppBar
            title="Статистика сообществ ВКонтакте"
            showMenuIconButton={false}
            iconElementRight={<DrivedMainMenu />} />
          <DrivedGroupList />
          <DrivedGroupDialog>
            Dump text.
          </DrivedGroupDialog>
        </div>
      </MuiThemeProvider>
    )
  }
}

import React from 'react'
import darkBaseTheme from 'material-ui/styles/baseThemes/darkBaseTheme'
import getMuiTheme from 'material-ui/styles/getMuiTheme'
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
import {DrivedMainMenu} from '../../containers/drived_main_menu/DrivedMainMenu'
import AppBar from 'material-ui/AppBar'
import {DrivedGroupList} from '../../containers/drived_group_list/DrivedGroupList'
import {DrivedGroupDialog} from '../../containers/drived_group_dialog/DrivedGroupDialog'
import './app.css'

export default class App extends React.Component {
  render() {
    return (
      <MuiThemeProvider muiTheme={getMuiTheme(darkBaseTheme)}>
        <div>
          <AppBar
            title="Статистика сообществ ВКонтакте"
            showMenuIconButton={false}
            iconElementRight={<DrivedMainMenu />} />
          <DrivedGroupList />
          <DrivedGroupDialog />
        </div>
      </MuiThemeProvider>
    )
  }
}

Correct an use of the DrivedGroupDialog container


/*
---

description: Monkey patching the Form.Validator to alter its behavior and extend it into doing more

requires:
 - MooTools More

license: @TODO

...
*/

if(!Koowa) var Koowa = {};

(function($){

    Koowa.Validator = new Class({

        Extends: Form.Validator.Inline,

        options: {
            onShowAdvice: function(input, advice) {
                advice.addEvent('click', function(){
                    input.focus();
                });
            }
        }

    });

})(document.id);

/*
---

description: Monkey patching the Form.Validator to alter its behavior and extend it into doing more

requires:
 - MooTools More

license: @TODO

...
*/

if(!Koowa) var Koowa = {};

(function($){

    Koowa.Validator = new Class({

        Extends: Form.Validator.Inline,

        options: {
        	//Needed to make the TinyMCE editor validation function properly
        	ignoreHidden: false,

            onShowAdvice: function(input, advice) {
                advice.addEvent('click', function(){
                    input.focus();
                });
            }
        }

    });

})(document.id);

Set ignoreHidden to false to allow TinyMCE editor validation to function properly.


'use strict';

const path = require('path');

/*
 * ECMAScript 6
 */
module.exports = {
	parserOptions: {
		ecmaVersion: 6,
		sourceType: 'module',
		ecmaFeatures: {
			experimentalObjectRestSpread: true
		}
	},
	rules: {
		'arrow-body-style': [
			'error',
			'as-needed',
			{
				requireReturnForObjectLiteral: true
			}
		],
		'arrow-parens': ['error', 'as-needed'],
		'arrow-spacing': [
			'error',
			{
				before: true,
				after: true
			}
		],
		'generator-star-spacing': ['error', 'after'],
		'no-confusing-arrow': [
			'warn',
			{
				allowParens: true
			}
		],
		'no-duplicate-imports': 'error',
		'no-var': 'error',
		'object-shorthand': [
			'error',
			'always',
			{
				avoidQuotes: true
			}
		],
		'prefer-spread': 'warn',
		'prefer-template': 'error'
	},
	env: {
		es6: true
	},
	extends: path.resolve(__dirname, './index.js')
};

'use strict';

const path = require('path');

/*
 * ECMAScript 6
 */
module.exports = {
	extends: path.resolve(__dirname, './index.js'),
	parserOptions: {
		ecmaVersion: 6,
		sourceType: 'module',
		ecmaFeatures: {
			experimentalObjectRestSpread: true
		}
	},
	env: {
		es6: true
	},
	rules: {
		'arrow-body-style': [
			'error',
			'as-needed',
			{
				requireReturnForObjectLiteral: true
			}
		],
		'arrow-parens': ['error', 'as-needed'],
		'arrow-spacing': [
			'error',
			{
				before: true,
				after: true
			}
		],
		'generator-star-spacing': ['error', 'after'],
		'no-confusing-arrow': [
			'warn',
			{
				allowParens: true
			}
		],
		'no-duplicate-imports': 'error',
		'no-var': 'error',
		'object-shorthand': [
			'error',
			'always',
			{
				avoidQuotes: true
			}
		],
		'prefer-spread': 'warn',
		'prefer-template': 'error'
	}
};

Move `extends` and `env` up


const config = require('./config.json')

// Environment variable in Docker container and Heroku if available
config.bot.token = !process.env.DRSS_BOT_TOKEN || process.env.DRSS_BOT_TOKEN === 'drss_docker_token' ? (config.bot.token || 's') : process.env.DRSS_BOT_TOKEN

// process.env.MONGODB_URI is intended for use by Heroku
config.database.uri = process.env.DRSS_DATABASE_URI || process.env.MONGODB_URI || config.database.uri

// Heroku deployment configuration
config.bot.prefix = process.env.DRSS_BOT_PREFIX || config.bot.prefix
config.bot.controllerIds = process.env.DRSS_BOT_CONTROLLER ? process.env.DRSS_BOT_CONTROLLER_IDS.replace(' ', '').split(',').map(id => id.trim()) : config.bot.controllerIds
config.feeds.refreshTimeMinutes = Number(process.env.DRSS_FEEDS_REFRESH_TIME_MINUTES) || config.feeds.refreshTimeMinutes
config.feeds.defaultMessage = process.env.DRSS_FEEDS_DEFAULT_MESSAGE || config.feeds.defaultMessage

module.exports = config

const config = require('./config.json')

// Environment variable in Docker container and Heroku if available
config.bot.token = !process.env.DRSS_BOT_TOKEN || process.env.DRSS_BOT_TOKEN === 'drss_docker_token' ? (config.bot.token || 's') : process.env.DRSS_BOT_TOKEN

// process.env.MONGODB_URI is intended for use by Heroku
config.database.uri = process.env.DRSS_DATABASE_URI || process.env.MONGODB_URI || config.database.uri

// Heroku deployment configuration
config.bot.prefix = process.env.DRSS_BOT_PREFIX || config.bot.prefix
config.bot.controllerIds = process.env.DRSS_BOT_CONTROLLER_IDS ? process.env.DRSS_BOT_CONTROLLER_IDS.split(/\s*,\s*/) : config.bot.controllerIds
config.feeds.refreshTimeMinutes = Number(process.env.DRSS_FEEDS_REFRESH_TIME_MINUTES) || config.feeds.refreshTimeMinutes
config.feeds.defaultMessage = process.env.DRSS_FEEDS_DEFAULT_MESSAGE || config.feeds.defaultMessage

module.exports = config

Fix non existent env var and simplify string operations


describe("LogFmt", function() {
  // API
  var LogFmt, $httpBackend;

  // Load required modules.
  beforeEach(angular.mock.module("logfmt"));

  beforeEach(inject(function($injector) {
    LogFmt = $injector.get("LogFmt");
  }));

  describe("Basic logging to console", function() {
    it("should contain the logfmt service", function() {
      expect(LogFmt).not.toBe(null);
    });

    it("should log to console", function() {
      spyOn(console, "log");
      LogFmt.log({message: "Test"});
      expect(console.log).toHaveBeenCalled();
    });

    it("should log a simple message to the console", function() {
      LogFmt.getCurrentTimestamp = jasmine.createSpy("getCurrentTimestamp() spy").andReturn("14-5-2014-00:00:00");
      spyOn(console, "log");
      LogFmt.log({message: "Test"});
      expect(console.log).toHaveBeenCalledWith("timestamp=14-5-2014-00:00:00 message=Test");
    });
  });
});

describe("LogFmt", function() {
  // API
  var LogFmt, $httpBackend;

  // Load required modules.
  beforeEach(angular.mock.module("logfmt"));

  beforeEach(inject(function($injector) {
    LogFmt = $injector.get("LogFmt");
  }));

  describe("Basic logging to console", function() {
    it("should contain the logfmt service", function() {
      expect(LogFmt).not.toBe(null);
    });

    it("should log to console", function() {
      spyOn(console, "log");
      LogFmt.log({message: "Test"});
      expect(console.log).toHaveBeenCalled();
    });

    it("should log a simple message to the console", function() {
      LogFmt.getCurrentTimestamp = jasmine.createSpy("getCurrentTimestamp() spy").andReturn("14-5-2014-00:00:00");
      spyOn(console, "log");
      LogFmt.log({message: "Test"});
      expect(console.log).toHaveBeenCalledWith("timestamp=14-5-2014-00:00:00 message=Test");
    });
  });

  describe("Advanced key-value logging to console", function() {
    it("should log correctly if a message has a space", function() {
      LogFmt.getCurrentTimestamp = jasmine.createSpy("getCurrentTimestamp() spy").andReturn("14-5-2014-00:00:00");
      spyOn(console, "log");
      LogFmt.log({message: "Test message"});
      expect(console.log).toHaveBeenCalledWith("timestamp=14-5-2014-00:00:00 message=\"Test message\"");
    });
  })
});

Add a failing test simply to test Travis


if (!global.document || !global.window) {
  var jsdom = require('jsdom').jsdom;

  global.document = jsdom('', null, {
    FetchExternalResources   : ['script'],
    ProcessExternalResources : ['script'],
    MutationEvents           : '2.0',
    QuerySelector            : false
  });

  global.window = document.createWindow();
  global.navigator = global.window.navigator;
}

global.$ = global.jQuery = require('jquery');

var proxyquire = require('proxyquire').noCallThru();
var sinon = require('sinon');
var chai = require('chai');
var sinonChai = require('sinon-chai');
var chaiJquery = require('chai-jquery');

chai.use(sinonChai);
chai.use(chaiJquery);

global.sinon = sinon;
global.expect = chai.expect;
global.proxyquire = proxyquire;

if (!global.document || !global.window) {
  var jsdom = require('jsdom').jsdom;

  global.document = jsdom('', null, {
    FetchExternalResources   : ['script'],
    ProcessExternalResources : ['script'],
    MutationEvents           : '2.0',
    QuerySelector            : false
  });

  global.window = document.createWindow();
  global.navigator = global.window.navigator;
  global.location = global.window.location;
}

global.$ = global.jQuery = require('jquery');

var proxyquire = require('proxyquire').noCallThru();
var sinon = require('sinon');
var chai = require('chai');
var sinonChai = require('sinon-chai');
var chaiJquery = require('chai-jquery');

chai.use(sinonChai);
chai.use(chaiJquery);

global.sinon = sinon;
global.expect = chai.expect;
global.proxyquire = proxyquire;

Fix for location undefined bug in mocha


$.fn.storeAutocomplete = function() {
  this.select2({
    minimumInputLength: 1,
    multiple: true,
    initSelection: function(element, callback) {
      $.get(Spree.routes.store_search, { ids: element.val() }, function(data) {
        callback(data)
      })
    },
    ajax: {
      url: Spree.routes.store_search,
      datatype: 'json',
      data: function(term, page) {
        return { q: term }
      },
      results: function(data, page) {
        return { results: data }
      }
    },
    formatResult: function(store) {
      return store.name;
    },
    formatSelection: function(store) {
      return store.name;
    },
    id: function(store) {
      return store.id
    }
  });
}

$(document).ready(function () {
  $('.store_picker').storeAutocomplete();
})

$.fn.storeAutocomplete = function() {
  this.select2({
    minimumInputLength: 1,
    multiple: true,
    initSelection: function(element, callback) {
      $.get(Spree.routes.store_search, { ids: element.val() }, function(data) {
        callback(data.stores)
      })
    },
    ajax: {
      url: Spree.routes.store_search,
      datatype: 'json',
      data: function(term, page) {
        return { q: term }
      },
      results: function(data, page) {
        return { results: data.stores }
      }
    },
    formatResult: function(store) {
      return store.name;
    },
    formatSelection: function(store) {
      return store.name;
    },
    id: function(store) {
      return store.id
    }
  });
}

$(document).ready(function () {
  $('.store_picker').storeAutocomplete();
})

Fix admin ui for store promo rules


import onBackspace from './utils/onBackspace';

// Block-Types to be handled will be stored here
let types = [];
const cleanupEmptyPlugin = (options = {}) => {
  types = options.types || [];
  return {
    handleKeyCommand(command, { getEditorState, setEditorState }) {
      const editorState = getEditorState();
      let newEditorState = null;
      if (command.indexOf('backspace') === 0) {
        newEditorState = onBackspace(editorState, types);
      }

      if (newEditorState) {
        setEditorState(newEditorState);
        return true;
      } return false;
    },
  };
};

export default cleanupEmptyPlugin;

// Use this to add one type to the list
export const cleanupType = item => types.push(item);

// Use this to add multiple types to the list
export const cleanupTypes = items => types.push(...items);

import onBackspace from './utils/onBackspace';

// Block-Types to be handled will be stored here
let types = [];
const cleanupEmptyPlugin = (config = {}) => {
  types = config.types || [];
  return {
    handleKeyCommand(command, { getEditorState, setEditorState }) {
      const editorState = getEditorState();
      let newEditorState = null;
      if (command.indexOf('backspace') === 0) {
        newEditorState = onBackspace(editorState, types);
      }

      if (newEditorState) {
        setEditorState(newEditorState);
        return true;
      } return false;
    },
  };
};

export default cleanupEmptyPlugin;

// Use this to add one type to the list
export const cleanupType = item => types.push(item);

// Use this to add multiple types to the list
export const cleanupTypes = items => types.push(...items);

Rename `options` param to `config` to bring it inline with other plugins


(function() {
  'use strict';

  angular.module('app.states')
    .run(appRun);

  /** @ngInject */
  function appRun(routerHelper) {
    routerHelper.configureStates(getStates());
  }

  function getStates() {
    return {
      'marketplace.details': {
        url: '/:serviceTemplateId',
        templateUrl: 'app/states/marketplace/details/details.html',
        controller: StateController,
        controllerAs: 'vm',
        title: 'Service Template Details',
        resolve: {
          dialogs: resolveDialogs,
          serviceTemplate: resolveServiceTemplate
        }
      }
    };
  }

  /** @ngInject */
  function resolveServiceTemplate($stateParams, CollectionsApi) {
    var options = {attributes: ['picture', 'picture.image_href']};

    return CollectionsApi.get('service_templates', $stateParams.serviceTemplateId, options);
  }

  /** @ngInject */
  function resolveDialogs($stateParams, CollectionsApi) {
    var options = {expand: true, attributes: 'content'};

    return CollectionsApi.query('service_templates/' + $stateParams.serviceTemplateId + '/service_dialogs', options);
  }

  /** @ngInject */
  function StateController(dialogs, serviceTemplate) {
    var vm = this;

    vm.title = 'Service Template Details';
    vm.dialogs = dialogs.resources[0].content;
    vm.serviceTemplate = serviceTemplate;
  }
})();

(function() {
  'use strict';

  angular.module('app.states')
    .run(appRun);

  /** @ngInject */
  function appRun(routerHelper) {
    routerHelper.configureStates(getStates());
  }

  function getStates() {
    return {
      'marketplace.details': {
        url: '/:serviceTemplateId',
        templateUrl: 'app/states/marketplace/details/details.html',
        controller: StateController,
        controllerAs: 'vm',
        title: 'Service Template Details',
        resolve: {
          dialogs: resolveDialogs,
          serviceTemplate: resolveServiceTemplate
        }
      }
    };
  }

  /** @ngInject */
  function resolveServiceTemplate($stateParams, CollectionsApi) {
    var options = {attributes: ['picture', 'picture.image_href']};

    return CollectionsApi.get('service_templates', $stateParams.serviceTemplateId, options);
  }

  /** @ngInject */
  function resolveDialogs($stateParams, CollectionsApi) {
    var options = {expand: 'resources', attributes: 'content'};

    return CollectionsApi.query('service_templates/' + $stateParams.serviceTemplateId + '/service_dialogs', options);
  }

  /** @ngInject */
  function StateController(dialogs, serviceTemplate) {
    var vm = this;

    vm.title = 'Service Template Details';
    vm.dialogs = dialogs.resources[0].content;
    vm.serviceTemplate = serviceTemplate;
  }
})();

Fix issue when API wasn't returning correct service dialog results


import { ADMIN_SIGNED_IN, AUTH_SIGNED_IN, AUTH_AUTHENTICATED } from 'common/actionTypes/auth';

const defaultState = {
  id: '',
  username: '',
  fullName: '',
  permissions: 0,
  reloadPage: false,
  loggedIn: false,
  registered: false,
  authenticated: false,
};

const auth = (state = defaultState, action) => {
  switch (action.type) {
    case AUTH_SIGNED_IN: {
      return {
        ...state,
        username: action.data.username,
        fullName: action.data.full_name,
        loggedIn: true,
        registered: action.data.completedRegistration,
        id: action.data.id,
        permissions: action.data.permissions,
      };
    }

    case AUTH_AUTHENTICATED: {
      return {
        ...state,
        authenticated: action.data.authenticated,
      };
    }

    case ADMIN_SIGNED_IN: {
      return {
        ...state,
        reloadPage: true,
      };
    }

    default:
      return state;
  }
};

export default auth;

import { ADMIN_SIGNED_IN, AUTH_SIGNED_IN, AUTH_AUTHENTICATED } from 'common/actionTypes/auth';

const defaultState = {
  id: '',
  username: '',
  fullName: '',
  permissions: 0,
  reloadPage: false,
  loggedIn: false,
  registered: false,
  authenticated: false,
  canVote: false,
};

const auth = (state = defaultState, action) => {
  switch (action.type) {
    case AUTH_SIGNED_IN: {
      return {
        ...state,
        canVote: action.data.canVote,
        username: action.data.username,
        fullName: action.data.full_name,
        loggedIn: true,
        registered: action.data.completedRegistration,
        id: action.data.id,
        permissions: action.data.permissions,
      };
    }

    case AUTH_AUTHENTICATED: {
      return {
        ...state,
        authenticated: action.data.authenticated,
      };
    }

    case ADMIN_SIGNED_IN: {
      return {
        ...state,
        reloadPage: true,
      };
    }

    default:
      return state;
  }
};

export default auth;

Store canVote in state when user connects


'use strict';

angular.module('products').controller('ProductsController', ['$scope', '$stateParams', '$location', 'Products',
	function($scope, $stateParams, $location, Products) {

    // List Products
    $scope.find = function() {
      $scope.products = Products.query();
    };

    // CREATE new Product
    $scope.create = function() {
      // Create new Product object
      var product = new Products ({
        category: this.category,
        name: this.name,
        quantityPerUnit: this.quantityPerUnit,
        unitPrice: this.unitPrice,
        unitsInStock: this.unitsInStock,
        unitsOnOrder: this.unitsOnOrder
      });

      // Redirect after save
      product.$save(function(response) {
        $location.path('products/' + response._id);

        // Clear form fields
        $scope.name = '';
      }, function(errorResponse) {
        $scope.error = errorResponse.data.message;
      });
    };

    // READ single Product
    $scope.findOne = function() {
      $scope.product = Products.get({
        productId: $stateParams.productId
      });
    };

	}
]);

'use strict';

angular.module('products').controller('ProductsController', ['$scope', '$stateParams', '$location', 'Products',
	function($scope, $stateParams, $location, Products) {

    var appendCategory = function appendCategory(p) {
      p.category = $filter('filter')($scope.categories, {_id: p.category})[0];
    };

    // List Products
    $scope.find = function() {
      Products.query(function loadedProducts(products) {
        products.forEach(appendCategory);
        $scope.products = products;
      });
    };

    // CREATE new Product
    $scope.create = function() {
      // Create new Product object
      var product = new Products ({
        category: this.category,
        name: this.name,
        quantityPerUnit: this.quantityPerUnit,
        unitPrice: this.unitPrice,
        unitsInStock: this.unitsInStock,
        unitsOnOrder: this.unitsOnOrder
      });

      // Redirect after save
      product.$save(function(response) {
        $location.path('products/' + response._id);

        // Clear form fields
        $scope.name = '';
      }, function(errorResponse) {
        $scope.error = errorResponse.data.message;
      });
    };

    // READ single Product
    $scope.findOne = function() {
      $scope.product = Products.get({
        productId: $stateParams.productId
      });
    };

	}
]);

Correct method for listing Products in Angular Controller.


function ComponentTextfield( id ) {
	var self = _ComponentFormControl(id);

	self.setDefaultState('text-value');
	self.bind = function() {
		self.attachChangeAction( self.node(), self.identifier() );
	};
	self.registerAction('change', function() {
		self.setState('text-value', self.node().value);
	});
	self.select = function() {
		if( self.node() ) {
			self.node().select();
		}
	};
	self.textValue = function() {
		self.setState('text-value', self.node().value);
		return self.getState('text-value');
	};
	self.setTextValue = function( value ) {
		self.setState('text-value', value);
		self.node().value = value;
	};
	self.enable = function() {
		self._enabled = true;
		self.node().readOnly = false;
		self.node().style.backgroundColor = (self.__previousBackgroundColor ? self.__previousBackgroundColor : '#fff');
	};
	self.disable = function() {
		self._enabled = false;
		self.node().readOnly = true;
		self.__previousBackgroundColor = self.node().style.backgroundColor;
		self.node().style.backgroundColor = '#ddd';
	};
	return self;
}

function ComponentTextfield( id ) {
	var self = _ComponentFormControl(id);

	self.setDefaultState('text-value');
	self.bind = function() {
		self.attachChangeAction( self.node(), self.identifier() );
	};
	self.registerAction('change', function() {
		self.setState('text-value', self.node().value);
	});
	self.select = function() {
		if( self.node() ) {
			self.node().select();
		}
	};
	self.textValue = function() {
		self.setState('text-value', self.node().value);
		return self.getState('text-value');
	};
	self.setTextValue = function( value ) {
		self.setState('text-value', value);
		self.node().value = value;
	};
	self.enable = function() {
		self._enabled = true;
		self.node().readOnly = false;
	};
	self.disable = function() {
		self._enabled = false;
		self.node().readOnly = true;
	};
	return self;
}

Modify Textfiled component to not change background to dark grey when it is disabled.


/** @jsx React.DOM */
'use strict';

var React = require('react/addons');

var CollectionStore = require('../stores/CollectionStore.js');
var CollectionActions = require('../actions/CollectionActions.js');

var Covers = React.createClass({
  getInitialState: function() {
    return {
      list: [],
    };
  },

  componentDidMount: function() {
    CollectionStore.addChangeListener(this._onChange);
    CollectionActions.fetch(["Root"]);
  },

  componentWillUnmount: function() {
    CollectionStore.removeChangeListener(this._onChange);
  },

  render: function() {
    var covers = this.state.list.map(function(item) {
      return <Cover path={["Root"].concat(item.Key)} key={item.Key} item={item} />;
    });

    return (

        {covers}

    );
  },

  _onChange: function(path) {
    if (path === CollectionStore.pathToKey(["Root"])) {
      this.setState({
        list: CollectionStore.getCollection(["Root"]).Groups,
      });
    }
  },
});

var Cover = React.createClass({
  getInitialState: function() {
    return {
      expanded: false,
    };
  },

  render: function() {
    return (

    );
  },
});

module.exports = Covers;

/** @jsx React.DOM */
'use strict';

var React = require('react/addons');

var CollectionStore = require('../stores/CollectionStore.js');
var CollectionActions = require('../actions/CollectionActions.js');

var Covers = React.createClass({
  getInitialState: function() {
    return {
      list: [],
    };
  },

  componentDidMount: function() {
    CollectionStore.addChangeListener(this._onChange);
    CollectionActions.fetch(["Root"]);
  },

  componentWillUnmount: function() {
    CollectionStore.removeChangeListener(this._onChange);
  },

  render: function() {
    var covers = this.state.list.map(function(item) {
      return <Cover path={["Root"].concat(item.Key)} key={item.Key} item={item} />;
    });

    return (

        {covers}

    );
  },

  _onChange: function(path) {
    if (path === CollectionStore.pathToKey(["Root"])) {
      this.setState({
        list: CollectionStore.getCollection(["Root"]).Groups.slice(0, 30),
      });
    }
  },
});

var Cover = React.createClass({
  getInitialState: function() {
    return {
      expanded: false,
    };
  },

  render: function() {
    return (

    );
  },
});

module.exports = Covers;

Add temporary limit to Cover view to stop rebuilding artwork


((function (App) {
  'use strict';

  App.View.HeaderView = Backbone.View.extend({

    events: {
      'click .js-mobile-menu': 'toggleDrawer',
      'click .js-search-button': 'toggleSearch',
    },

    initialize: function () {
      this.drawer = this.el.querySelector('.js-mobile-drawer');
      this.searchContainer = this.el.querySelector('.js-search');
      this.el.classList.add('initialized');
      this._setListeners();
    },

    /**
     * Set the listeners not attached to any DOM element of this.el
     */
    _setListeners: function () {
      document.body.addEventListener('click', function (e) {
        if ($(e.target).closest(this.searchContainer).length) return;
        this.toggleSearch(false);
      }.bind(this));
    },

    toggleDrawer: function () {
      var opened = this.drawer.classList.toggle('-opened');
      var overflow = 'auto';
      if (opened) overflow = 'hidden';
      document.querySelector('body').style.overflow = overflow;
    },

    /**
     * Toggle the visibility of the search container
     * @param {boolean} [show] Force the search to expand or contract
     */
    toggleSearch: function(show) {
      this.searchContainer.classList.toggle('-expanded', show);
    }

  });
})(this.App));

((function (App) {
  'use strict';

  App.View.HeaderView = Backbone.View.extend({

    events: {
      'click .js-mobile-menu': 'toggleDrawer',
      'click .js-search-button': 'onClickSearchButton',
    },

    initialize: function () {
      this.drawer = this.el.querySelector('.js-mobile-drawer');
      this.searchContainer = this.el.querySelector('.js-search');
      this.el.classList.add('initialized');
      this._setListeners();
    },

    /**
     * Set the listeners not attached to any DOM element of this.el
     */
    _setListeners: function () {
      document.body.addEventListener('click', function (e) {
        if ($(e.target).closest(this.searchContainer).length) return;
        this.toggleSearch(false);
      }.bind(this));
    },

    onClickSearchButton: function () {
      this.toggleSearch();
    },

    toggleDrawer: function () {
      var opened = this.drawer.classList.toggle('-opened');
      var overflow = 'auto';
      if (opened) overflow = 'hidden';
      document.querySelector('body').style.overflow = overflow;
    },

    /**
     * Toggle the visibility of the search container
     * @param {boolean} [show] Force the search to expand or contract
     */
    toggleSearch: function(show) {
      this.searchContainer.classList.toggle('-expanded', show);
    }

  });
})(this.App));

Fix a bug where the search dropdown wouldn't toggle


/**
 *  class EjsEngine
 *
 *  Engine for the EJS compiler. You will need `ejs` Node module installed
 *  in order to use [[Mincer]] with `*.ejs` files:
 *
 *      npm install ejs
 *
 *
 *  ##### SUBCLASS OF
 *
 *  [[Template]]
 **/

'use strict';

// 3rd-party
var ejs; // initialized later

// internal
var Template = require('../template');

////////////////////////////////////////////////////////////////////////////////

// Class constructor
var EjsEngine = module.exports = function EjsEngine() {
  Template.apply(this, arguments);
};

require('util').inherits(EjsEngine, Template);

// Check whenever ejs module is loaded
EjsEngine.prototype.isInitialized = function () {
  return !!ejs;
};

// Autoload ejs library
EjsEngine.prototype.initializeEngine = function () {
  ejs = this.require('ejs');
};

// Render data
EjsEngine.prototype.evaluate = function (context, locals, callback) {
  try {
    callback(null, ejs.render(this.data, {scope: context, locals: locals}));
  } catch (err) {
    callback(err);
  }
};

/**
 *  class EjsEngine
 *
 *  Engine for the EJS compiler. You will need `ejs` Node module installed
 *  in order to use [[Mincer]] with `*.ejs` files:
 *
 *      npm install ejs
 *
 *
 *  ##### SUBCLASS OF
 *
 *  [[Template]]
 **/

'use strict';

// 3rd-party
var ejs; // initialized later

// internal
var Template = require('../template');

////////////////////////////////////////////////////////////////////////////////

// Class constructor
var EjsEngine = module.exports = function EjsEngine() {
  Template.apply(this, arguments);
};

require('util').inherits(EjsEngine, Template);

// Check whenever ejs module is loaded
EjsEngine.prototype.isInitialized = function () {
  return !!ejs;
};

// Autoload ejs library
EjsEngine.prototype.initializeEngine = function () {
  ejs = this.require('ejs');
};

// Render data
EjsEngine.prototype.evaluate = function (context, locals, callback) {
  try {
    callback(null, ejs.render(this.data, {scope: context, locals: locals, filename: context.pathname}));
  } catch (err) {
    callback(err);
  }
};

Add the filename to ejs for `include` directive


import { applyMiddleware, createStore, compose } from 'redux';
import { routerMiddleware } from 'react-router-redux';
import thunk from 'redux-thunk';
import createLogger from 'redux-logger';
import persistState, { mergePersistedState } from 'redux-localstorage';
import adapter from 'redux-localstorage/lib/adapters/localStorage';
import filter from 'redux-localstorage-filter';

import rootReducer from '../reducers';

export default function configureStore(history) {
  const logger = createLogger({
    level: process.env.NODE_ENV === 'production' ? 'error' : 'log',
    collapsed: true,
  });
  const reduxRouterMiddleware = routerMiddleware(history);
  const reducers = compose(mergePersistedState())(rootReducer);
  const storageAuth = compose(filter('authentication'))(adapter(window.localStorage));
  const storageLang = compose(filter('language'))(adapter(window.localStorage));

  let createCustomStore = compose(
    persistState(storageAuth, 'authentication'),
    persistState(storageLang, 'language'),
  )(createStore);

  createCustomStore = applyMiddleware(
    reduxRouterMiddleware,
    thunk,
    logger,
  )(createCustomStore);

  const store = createCustomStore(reducers);

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

  return store;
}

import { applyMiddleware, createStore, compose } from 'redux';
import { routerMiddleware } from 'react-router-redux';
import thunk from 'redux-thunk';
import createLogger from 'redux-logger';
import persistState, { mergePersistedState } from 'redux-localstorage';
import adapter from 'redux-localstorage/lib/adapters/localStorage';
import filter from 'redux-localstorage-filter';

import rootReducer from '../reducers';

export default function configureStore(history) {
  const logger = createLogger({
    level: process.env.NODE_ENV === 'production' ? 'error' : 'log',
    collapsed: true,
  });
  const reduxRouterMiddleware = routerMiddleware(history);
  const reducers = compose(mergePersistedState())(rootReducer);
  const storageAuth = compose(filter('authentication'))(adapter(window.localStorage));
  const storageLang = compose(filter('language'))(adapter(window.localStorage));

  let createCustomStore = compose(
    persistState(storageAuth, 'authentication'),
    persistState(storageLang, 'language'),
  )(createStore);

  const middlewares = [
    reduxRouterMiddleware,
    thunk,
  ];

  if (process.env.NODE_ENV !== 'production') {
    middlewares.push(logger);
  }

  createCustomStore = applyMiddleware(...middlewares)(createCustomStore);

  const store = createCustomStore(reducers);

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

  return store;
}

Remove Redux logs in production


var glimpse = require('glimpse'),
    requestRepository = require('../repository/request-repository.js'),
    // TODO: Not sure I need to store the requests
    _requests = {};

function requestsChanged(targetRequests) {
    glimpse.emit('shell.request.summary.changed', targetRequests);
}

// Clear Request
(function() {
    function clearRequest() {
        glimpse.emit('shell.request.detail.changed', null);
    }

    glimpse.on('shell.request.detail.closed', clearRequest);
})();

// Found Data
(function() {
    function dataFound(payload) {
        // TODO: Really bad hack to get things going atm
        _requests[payload.newRequest.id] = payload.newRequest;

        glimpse.emit('shell.request.detail.changed', payload.newRequest);
    }

    // External data coming in
    glimpse.on('data.request.detail.found', dataFound);
})();

// Trigger Requests
(function() {
    function triggerRequest(payload) {
        if (!FAKE_SERVER) {
            requestRepository.triggerGetDetailsFor(payload.id);
        }
    }

    glimpse.on('data.request.detail.requested', triggerRequest);
})();

var glimpse = require('glimpse'),
    requestRepository = require('../repository/request-repository.js'),
    // TODO: Not sure I need to store the requests
    _requests = {},
    _requestSelectedId = null;

function requestsChanged(targetRequests) {
    glimpse.emit('shell.request.summary.changed', targetRequests);
}

// Clear Request
(function() {
    function clearRequest() {
        _requestSelectedId = null;

        glimpse.emit('shell.request.detail.changed', null);
    }

    glimpse.on('shell.request.detail.closed', clearRequest);
})();

// Found Data
(function() {
    function dataFound(payload) {
        // TODO: Really bad hack to get things going atm
        _requests[payload.newRequest.id] = payload.newRequest;

        if (payload.newRequest.id === _requestSelectedId) {
            glimpse.emit('shell.request.detail.changed', payload.newRequest);
        }
    }

    // External data coming in
    glimpse.on('data.request.detail.found', dataFound);
})();

// Trigger Requests
(function() {
    function triggerRequest(payload) {
        _requestSelectedId = payload.requestId;

        if (!FAKE_SERVER) {
            requestRepository.triggerGetDetailsFor(payload.requestId);
        }
    }

    glimpse.on('data.request.detail.requested', triggerRequest);
})();

Fix bug where selected requests would pop open the details page even when not needed


(function (window, document, $) {
  var app = window.devsite;

  app.pages.signup = function () {
    var $btn = $('.notify-btn');

    $btn.on('click', function() {
      $btn.addClass('disabled');
      $.post('/api/developer-plus/coming-soon', {
        email: $('input[name="email"]').val()
      }, function(data, status, xhr) {
        $btn.addClass('btn-success')
          .removeClass('btn-primary')
          .text('Success!');

        setTimeout(function () {
          $('input[name="email"]').val('');
          $btn.addClass('btn-primary')
            .removeClass('btn-success disabled')
            .text('Notify Me!');
        }, 2500);
      })
        .fail(function(e) {
          $btn.addClass('btn-danger')
            .removeClass('btn-primary')
            .text('Error');

          setTimeout(function() {
            $btn.addClass('btn-primary')
              .removeClass('btn-danger disabled')
              .text('Notify Me!');
          }, 2500);
        });
    });
  };
}(window, document, jQuery));

(function (window, document, $) {
  var app = window.devsite;

  app.pages.signup = function () {
    var $btn = $('.notify-btn');

    $btn.on('click', function() {
      $btn.addClass('disabled');
      $.post('/api/developer-plus/coming-soon', {
        email: $('input[name="email"]').val().trim()
      }, function(data, status, xhr) {
        $btn.addClass('btn-success')
          .removeClass('btn-primary')
          .text('Success!');

        setTimeout(function () {
          $('input[name="email"]').val('');
          $btn.addClass('btn-primary')
            .removeClass('btn-success disabled')
            .text('Notify Me!');
        }, 2500);
      })
        .fail(function(e) {
          $btn.addClass('btn-danger')
            .removeClass('btn-primary')
            .text('Error');

          setTimeout(function() {
            $btn.addClass('btn-primary')
              .removeClass('btn-danger disabled')
              .text('Notify Me!');
          }, 2500);
        });
    });
  };
}(window, document, jQuery));

Trim email address before submitting to API.