import { computed } from '@ember/object';
import Navbar from 'ember-bootstrap/components/base/bs-navbar';

export default Navbar.extend({
  classNameBindings: ['breakpointClass', 'backgroundClass'],

  type: 'light',

  /**
   * Defines the responsive toggle breakpoint size. Options are the standard
   * two character Bootstrap size abbreviations. Used to set the `navbar-expand-*`
   * class.
   *
   * @property toggleBreakpoint
   * @type String
   * @default 'md'
   * @public
   */
  toggleBreakpoint: 'lg',

  /**
   * Sets the background color for the navbar. Can be any color
   * in the set that composes the `bg-*` classes.
   *
   * @property backgroundColor
   * @type String
   * @default 'light'
   * @public
   */
  backgroundColor: 'light',

  breakpointClass: computed('toggleBreakpoint', function() {
    let toggleBreakpoint = this.get('toggleBreakpoint');

    return `navbar-expand-${toggleBreakpoint}`;
  }),

  backgroundClass: computed('backgroundColor', function() {
    let backgroundColor = this.get('backgroundColor');

    return `bg-${backgroundColor}`;
  }),

  _validPositions: ['fixed-top', 'fixed-bottom', 'sticky-top'],

  _positionPrefix: ''
});

import { computed } from '@ember/object';
import Navbar from 'ember-bootstrap/components/base/bs-navbar';

export default Navbar.extend({
  classNameBindings: ['breakpointClass', 'backgroundClass'],

  type: computed('appliedType', {
    get() {
      return this.get('appliedType');
    },

    set(key, value) { // eslint-disable-line no-unused
      let newValue = (!value || value === 'default') ? 'light' : value;
      this.set('appliedType', newValue);
      return newValue;
    }
  }),

  appliedType: 'light',

  /**
   * Defines the responsive toggle breakpoint size. Options are the standard
   * two character Bootstrap size abbreviations. Used to set the `navbar-expand-*`
   * class.
   *
   * @property toggleBreakpoint
   * @type String
   * @default 'md'
   * @public
   */
  toggleBreakpoint: 'lg',

  /**
   * Sets the background color for the navbar. Can be any color
   * in the set that composes the `bg-*` classes.
   *
   * @property backgroundColor
   * @type String
   * @default 'light'
   * @public
   */
  backgroundColor: 'light',

  breakpointClass: computed('toggleBreakpoint', function() {
    let toggleBreakpoint = this.get('toggleBreakpoint');

    return `navbar-expand-${toggleBreakpoint}`;
  }),

  backgroundClass: computed('backgroundColor', function() {
    let backgroundColor = this.get('backgroundColor');

    return `bg-${backgroundColor}`;
  }),

  _validPositions: ['fixed-top', 'fixed-bottom', 'sticky-top'],

  _positionPrefix: ''
});

Enforce compatibility of the `undefined` and `default` setting for navbar color scheme. This makes the demo work for the default, but the inverse still doesn't work because of different scheme names.


var list = document.createElement('ul');
var info = document.createElement('p');
var html = document.querySelector('html');

info.textContent = 'Below is a dynamic list. Click anywhere outside the list to add a new list item. Click an existing list item to change its text to something else.';

document.body.appendChild(info);
document.body.appendChild(list);

html.onclick = function() {
  var listItem = document.createElement('li');
  var listContent = prompt('What content do you want the list item to have?');
  listItem.textContent = listContent;
  list.appendChild(listItem);

  listItem.onclick = function(e) {
    e.stopPropagation();
    var listContent = prompt('Enter new content for your list item');
    this.textContent = listContent;
  }
}

const list = document.createElement('ul');
const info = document.createElement('p');
const html = document.querySelector('html');

info.textContent = 'Below is a dynamic list. Click anywhere outside the list to add a new list item. Click an existing list item to change its text to something else.';

document.body.appendChild(info);
document.body.appendChild(list);

html.onclick = function() {
  const listItem = document.createElement('li');
  const listContent = prompt('What content do you want the list item to have?');
  listItem.textContent = listContent;
  list.appendChild(listItem);

  listItem.onclick = function(e) {
    e.stopPropagation();
    const listContent = prompt('Enter new content for your list item');
    this.textContent = listContent;
  }
}

Use const instead of var as according to best practices


/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// The list below should be kept in sync with:
// https://raw.githubusercontent.com/mozilla/fxa-content-server/master/server/config/production-locales.json

module.exports = [
  'ar',
  'az',
  'bg',
  'cs',
  'cy',
  'da',
  'de',
  'dsb',
  'en',
  'en-GB',
  'es',
  'es-AR',
  'es-CL',
  'et',
  'fa',
  'ff',
  'fi',
  'fr',
  'fy',
  'he',
  'hi-IN',
  'hsb',
  'hu',
  'it',
  'ja',
  'kk',
  'ko',
  'lt',
  'nl',
  'pa',
  'pl',
  'pt',
  'pt-BR',
  'pt-PT',
  'ro',
  'rm',
  'ru',
  'sk',
  'sl',
  'sq',
  'sr',
  'sr-LATN',
  'sv',
  'sv-SE',
  'tr',
  'uk',
  'zh-CN',
  'zh-TW'
]

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// The list below should be kept in sync with:
// https://raw.githubusercontent.com/mozilla/fxa-content-server/master/server/config/production-locales.json

module.exports = [
  'az',
  'bg',
  'cs',
  'cy',
  'da',
  'de',
  'dsb',
  'en',
  'en-GB',
  'es',
  'es-AR',
  'es-CL',
  'et',
  'fa',
  'ff',
  'fi',
  'fr',
  'fy',
  'he',
  'hi-IN',
  'hsb',
  'hu',
  'it',
  'ja',
  'kk',
  'ko',
  'lt',
  'nl',
  'pa',
  'pl',
  'pt',
  'pt-BR',
  'pt-PT',
  'ro',
  'rm',
  'ru',
  'sk',
  'sl',
  'sq',
  'sr',
  'sr-LATN',
  'sv',
  'sv-SE',
  'tr',
  'uk',
  'zh-CN',
  'zh-TW'
]

Revert "feat(locale): add Arabic locale support"


import DataLoader from 'dataloader';
import client from 'util/client';

export default () =>
  new DataLoader(async categoryQueries => {
    const body = [];

    categoryQueries.forEach(({ id, first, before, after }) => {
      // TODO error independently?
      if (before && after) {
        throw new Error('Use of before & after is prohibited.');
      }

      body.push({ index: 'articles', type: 'doc' });

      body.push({
        query: {
          nested: {
            path: 'articleCategories',
            query: {
              term: {
                'articleCategories.categoryId': id,
              },
            },
          },
        },
        size: first ? first : 10,
        sort: before ? [{ updatedAt: 'desc' }] : [{ updatedAt: 'asc' }],
        search_after: before || after ? before || after : undefined,
      });
    });

    return (await client.msearch({
      body,
    })).responses.map(({ hits }, idx) => {
      if (!hits || !hits.hits) return [];

      const categoryId = categoryQueries[idx].id;
      return hits.hits.map(({ _id, _source: { articleCategories } }) => {
        // Find corresponding articleCategory and insert articleId
        //
        const articleCategory = articleCategories.find(
          articleCategory => articleCategory.categoryId === categoryId
        );
        articleCategory.articleId = _id;
        return articleCategory;
      });
    });
  });

import DataLoader from 'dataloader';
import client from 'util/client';

export default () =>
  new DataLoader(
    async categoryQueries => {
      const body = [];

      categoryQueries.forEach(({ id, first, before, after }) => {
        // TODO error independently?
        if (before && after) {
          throw new Error('Use of before & after is prohibited.');
        }

        body.push({ index: 'articles', type: 'doc' });

        body.push({
          query: {
            nested: {
              path: 'articleCategories',
              query: {
                term: {
                  'articleCategories.categoryId': id,
                },
              },
            },
          },
          size: first ? first : 10,
          sort: before ? [{ updatedAt: 'desc' }] : [{ updatedAt: 'asc' }],
          search_after: before || after ? before || after : undefined,
        });
      });

      return (await client.msearch({
        body,
      })).responses.map(({ hits }, idx) => {
        if (!hits || !hits.hits) return [];

        const categoryId = categoryQueries[idx].id;
        return hits.hits.map(({ _id, _source: { articleCategories } }) => {
          // Find corresponding articleCategory and insert articleId
          //
          const articleCategory = articleCategories.find(
            articleCategory => articleCategory.categoryId === categoryId
          );
          articleCategory.articleId = _id;
          return articleCategory;
        });
      });
    },
    {
      cacheKeyFn: ({ id, first, before, after }) =>
        `/${id}/${first}/${before}/${after}`,
    }
  );

Implement cacheKeyFn and fix lint


import Ember from "ember";
export default Ember.Mixin.create({
    /**
     * For use with the inventory-type ahead.  When an inventory item is selected, resolve the selected
     * inventory item into an actual model object and set is as inventoryItem.
     */
    inventoryItemChanged: function() {
        var selectedInventoryItem = this.get('selectedInventoryItem');
        if (!Ember.isEmpty(selectedInventoryItem)) {
            selectedInventoryItem.id = selectedInventoryItem._id.substr(10);
            this.store.find('inventory', selectedInventoryItem._id.substr(10)).then(function(item) {
                this.set('inventoryItem', item);
                Ember.run.once(this, function(){
                    this.get('model').validate();
                });
            }.bind(this));
        }
    }.observes('selectedInventoryItem'),
});

import Ember from "ember";
export default Ember.Mixin.create({
    /**
     * For use with the inventory-type ahead.  When an inventory item is selected, resolve the selected
     * inventory item into an actual model object and set is as inventoryItem.
     */
    inventoryItemChanged: function() {
        var selectedInventoryItem = this.get('selectedInventoryItem');
        if (!Ember.isEmpty(selectedInventoryItem)) {
            selectedInventoryItem.id = selectedInventoryItem._id.substr(10);
            this.store.find('inventory', selectedInventoryItem._id.substr(10)).then(function(item) {
                item.reload().then(function() {
                    this.set('inventoryItem', item);
                    Ember.run.once(this, function(){
                        this.get('model').validate();
                    });
                }.bind(this), function(err) {
                    console.log("ERROR reloading inventory item", err);
                });
            }.bind(this));
        }
    }.observes('selectedInventoryItem'),
});

Make sure inventory item is completely loaded


var listeners = {};

exports.addEventListener = function(name, func) {
	if (!listeners[name]) {
		listeners[name] = [];
	}
	listeners[name].push(func);
	return exports;
};

exports.hasEventListeners = function(name) {
	return !!listeners[name];
};

exports.clearEventListeners = function() {
	listeners = {};
	return exports;
};

exports.fireEvent = function(name, data) {
	if (listeners[name]) {
		for (var l in listeners[name]) {
			listeners[name][l](data);
		}
	}
	return exports;
};

exports.curryFireEvent = function(name, data) {
	return function() {
		return exports.fireEvent(name, data);
	}
};

exports.removeEventListener = function(name, func) {
	if (listeners[name]) {
		for (var l in listeners[name]) {
			if (listeners[name][l] === func) {
				listeners[name].splice(l, 1);
				break;
			}
		}
	}
	return exports;
};

var listeners = {};

exports.addEventListener = function(name, func) {
	if (!listeners[name]) {
		listeners[name] = [];
	}
	listeners[name].push(func);
	return exports;
};

exports.hasEventListeners = function(name) {
	return !!listeners[name];
};

exports.clearEventListeners = function() {
	listeners = {};
	return exports;
};

exports.fireEvent = function(name, data) {
	if (listeners[name]) {
		var args = Array.prototype.slice.call(arguments, 1);
		for (var l in listeners[name]) {
			listeners[name][l].apply(this, args);
		}
	}
	return exports;
};

exports.curryFireEvent = function(name, data) {
	var args = arguments;
	return function() {
		return exports.fireEvent.apply(this, args);
	};
};

exports.removeEventListener = function(name, func) {
	if (listeners[name]) {
		for (var l in listeners[name]) {
			if (listeners[name][l] === func) {
				listeners[name].splice(l, 1);
				break;
			}
		}
	}
	return exports;
};

Allow Multiple Arguments to Events


module.exports = {
  semi: false,
  tabWidth: 2,
  singleQuote: true,
  proseWrap: 'always',
}

module.exports = {
  semi: false,
  tabWidth: 2,
  singleQuote: true,
  proseWrap: 'always',
  overrides: [
    {
      files: 'src/**/*.mdx',
      options: {
        parser: 'mdx',
      },
    },
  ],
}

Configure prettier to format mdx


import update from 'immutability-helper'

import ActionTypes from '../actionTypes'

const initialState = {
  isLoading: false,
  lastRequestAt: undefined,
  latestActivities: [],
  unreadMessagesCount: 0,
}

export default (state = initialState, action) => {
  switch (action.type) {
  case ActionTypes.AUTH_TOKEN_EXPIRED_OR_INVALID:
  case ActionTypes.AUTH_LOGOUT:
    return update(state, {
      lastRequestAt: { $set: undefined },
      latestActivities: [],
      unreadMessagesCount: 0,
    })
  case ActionTypes.USER_STATUS_REQUEST:
    return update(state, {
      isLoading: { $set: true },
    })
  case ActionTypes.USER_STATUS_SUCCESS:
    return update(state, {
      isLoading: { $set: false },
      lastRequestAt: { $set: Date.now() },
      latestActivities: { $set: action.payload.latestActivities },
      unreadMessagesCount: { $set: action.payload.unreadMessagesCount },
    })
  case ActionTypes.USER_STATUS_FAILURE:
    return update(state, {
      isLoading: { $set: false },
    })
  default:
    return state
  }
}

import update from 'immutability-helper'

import ActionTypes from '../actionTypes'

const initialState = {
  isLoading: false,
  lastRequestAt: undefined,
  latestActivities: [],
  unreadMessagesCount: 0,
}

export default (state = initialState, action) => {
  switch (action.type) {
  case ActionTypes.AUTH_TOKEN_EXPIRED_OR_INVALID:
  case ActionTypes.AUTH_LOGOUT:
    return initialState
  case ActionTypes.USER_STATUS_REQUEST:
    return update(state, {
      isLoading: { $set: true },
    })
  case ActionTypes.USER_STATUS_SUCCESS:
    return update(state, {
      isLoading: { $set: false },
      lastRequestAt: { $set: Date.now() },
      latestActivities: { $set: action.payload.latestActivities },
      unreadMessagesCount: { $set: action.payload.unreadMessagesCount },
    })
  case ActionTypes.USER_STATUS_FAILURE:
    return update(state, {
      isLoading: { $set: false },
    })
  default:
    return state
  }
}

Fix wrong usage of update


'use strict';

(function() {

  angular.module('ncsaas')
    .directive('sidebar', ['$state', sidebar]);

  function sidebar($state, $uibModal) {
    return {
      restrict: 'E',
      scope: {
        items: '=',
        context: '='
      },
      templateUrl: 'views/directives/sidebar.html',
      link: function(scope) {
        scope.$state = $state;
      }
    };
  }

  angular.module('ncsaas')
    .directive('workspaceSelectToggle', ['$uibModal', workspaceSelectToggle]);

  function workspaceSelectToggle($uibModal) {
    return {
      restrict: 'E',
      template: '<button class="btn btn-primary dim minimalize-styl-2" ng-click="selectWorkspace()">'+
                '<i class="fa fa-bars"></i> Select workspace</button>',
      link: function(scope) {
        scope.selectWorkspace = function() {
          $uibModal.open({
            templateUrl: 'views/directives/select-workspace.html',
            controller: 'SelectWorkspaceController',
            controllerAs: 'Ctrl',
            bindToController: true,
            size: 'lg'
          })
        }
      }
    }
  }

})();

'use strict';

(function() {

  angular.module('ncsaas')
    .directive('sidebar', ['$state', sidebar]);

  function sidebar($state, $uibModal) {
    return {
      restrict: 'E',
      scope: {
        items: '=',
        context: '='
      },
      templateUrl: 'views/directives/sidebar.html',
      link: function(scope) {
        scope.$state = $state;
      }
    };
  }

  angular.module('ncsaas')
    .directive('workspaceSelectToggle', ['$uibModal', workspaceSelectToggle]);

  function workspaceSelectToggle($uibModal) {
    return {
      restrict: 'E',
      template: '<button class="btn btn-primary minimalize-styl-2" ng-click="selectWorkspace()">'+
                '<i class="fa fa-bars"></i> Select workspace</button>',
      link: function(scope) {
        scope.selectWorkspace = function() {
          $uibModal.open({
            templateUrl: 'views/directives/select-workspace.html',
            controller: 'SelectWorkspaceController',
            controllerAs: 'Ctrl',
            bindToController: true,
            size: 'lg'
          })
        }
      }
    }
  }

})();

Use normal button for select workspace button (SAAS-1389)


import { connect } from 'react-redux';
import { Component } from 'react';
import PropTypes from 'prop-types';
import { setConfig } from 'widget-editor';

import { browserHistory } from 'react-router';
import actions from './auth-actions';
import initialState from './auth-reducer-initial-state';
import * as reducers from './auth-reducer';

const mapStateToProps = state => ({ session: state.auth.session });

class AuthContainer extends Component {
  componentWillMount() {
    const { location } = this.props;
    const { token } = location;

    if (token) {
      this.handleAuthenticationAction();
      localStorage.setItem('token', token);
    } else {
      browserHistory.push('/');
    }

    // We update the widget editor's config
    setConfig({ userToken: token || null });
  }

  handleAuthenticationAction() {
    const { logInSuccess } = this.props;
    logInSuccess(true);
  }

  render() {
    return false;
  }
}

AuthContainer.propTypes = {
  location: PropTypes.object.isRequired,
  logInSuccess: PropTypes.func.isRequired
};

export { actions, reducers, initialState };

export default connect(mapStateToProps, actions)(AuthContainer);

import { connect } from 'react-redux';
import { Component } from 'react';
import PropTypes from 'prop-types';
import { setConfig } from 'widget-editor';

import { browserHistory } from 'react-router';
import actions from './auth-actions';
import initialState from './auth-reducer-initial-state';
import * as reducers from './auth-reducer';

const mapStateToProps = state => ({ session: state.auth.session });

class AuthContainer extends Component {
  componentWillMount() {
    const { location } = this.props;
    const { query } = location;
    const { token } = query;

    if (token) {
      this.handleAuthenticationAction();
      localStorage.setItem('token', token);
    } else {
      browserHistory.push('/');
    }

    // We update the widget editor's config
    setConfig({ userToken: token || null });
  }

  handleAuthenticationAction() {
    const { logInSuccess } = this.props;
    logInSuccess(true);
  }

  render() {
    return false;
  }
}

AuthContainer.propTypes = {
  location: PropTypes.object.isRequired,
  logInSuccess: PropTypes.func.isRequired
};

export { actions, reducers, initialState };

export default connect(mapStateToProps, actions)(AuthContainer);

Fix a bug where the user wouldn't be able to log in


var queue = require('queue-async');
var _ = require('underscore');

function MapConfigNamedLayersAdapter(overviewsApi) {
    this.overviewsApi = overviewsApi;
}

module.exports = MapConfigNamedLayersAdapter;

MapConfigNamedLayersAdapter.prototype.getLayers = function(username, layers, callback) {
  var self = this;

  if (!layers) {
      return callback(null);
  }

  var augmentLayersQueue = queue(layers.length);

  function augmentLayer(layer, done) {
      self.overviewsApi.getOverviewsMetadata(username, layer.options.sql, function(err, metadata){
           if (err) {
               done(err, layer);
           } else {
               if ( !_.isEmpty(metadata) ) {
                   // layer = _.extend({}, layer, { overviews: metadata });
               }
               done(null, layer);
           }
      });
  }

  function layersAugmentQueueFinish(err, layers) {
    if (err) {
        return callback(err);
    }

    if (!layers || layers.length === 0) {
        return callback(new Error('Missing layers array from layergroup config'));
    }

    return callback(null, layers);
  }

  layers.forEach(function(layer) {
      augmentLayersQueue.defer(augmentLayer, layer);
  });
  augmentLayersQueue.awaitAll(layersAugmentQueueFinish);

};

var queue = require('queue-async');
var _ = require('underscore');

function MapConfigNamedLayersAdapter(overviewsApi) {
    this.overviewsApi = overviewsApi;
}

module.exports = MapConfigNamedLayersAdapter;

MapConfigNamedLayersAdapter.prototype.getLayers = function(username, layers, callback) {
  var self = this;

  if (!layers) {
      return callback(null);
  }

  var augmentLayersQueue = queue(layers.length);

  function augmentLayer(layer, done) {
      self.overviewsApi.getOverviewsMetadata(username, layer.options.sql, function(err, metadata){
           if (err) {
               done(err, layer);
           } else {
               if ( !_.isEmpty(metadata) ) {
                   layer = _.extend({}, layer, { overviews: metadata });
               }
               done(null, layer);
           }
      });
  }

  function layersAugmentQueueFinish(err, layers) {
    if (err) {
        return callback(err);
    }

    if (!layers || layers.length === 0) {
        return callback(new Error('Missing layers array from layergroup config'));
    }

    return callback(null, layers);
  }

  layers.forEach(function(layer) {
      augmentLayersQueue.defer(augmentLayer, layer);
  });
  augmentLayersQueue.awaitAll(layersAugmentQueueFinish);

};

Bring in code commented out for tests


import chai from 'chai';
import irc from 'irc';
import discord from 'discord.js';
import Bot from '../lib/bot';
import config from './fixtures/single-test-config.json';
import caseConfig from './fixtures/case-sensitivity-config.json';
import DiscordStub from './stubs/discord-stub';
import ClientStub from './stubs/irc-client-stub';
import { validateChannelMapping } from '../lib/validators';

chai.should();

describe('Channel Mapping', () => {
  before(() => {
    irc.Client = ClientStub;
    discord.Client = DiscordStub;
  });

  it('should fail when not given proper JSON', () => {
    const wrongMapping = 'not json';
    function wrap() {
      validateChannelMapping(wrongMapping);
    }

    (wrap).should.throw('Invalid channel mapping given');
  });

  it('should not fail if given a proper channel list as JSON', () => {
    const correctMapping = { '#channel': '#otherchannel' };
    function wrap() {
      validateChannelMapping(correctMapping);
    }

    (wrap).should.not.throw();
  });

  it('should clear channel keys from the mapping', () => {
    const bot = new Bot(config);
    bot.channelMapping['#discord'].should.equal('#irc');
    bot.invertedMapping['#irc'].should.equal('#discord');
    bot.channels[0].should.equal('#irc channelKey');
  });

  it('should lowercase IRC channel names', () => {
    const bot = new Bot(caseConfig);
    bot.channelMapping['#discord'].should.equal('#irc');
    bot.channelMapping['#otherDiscord'].should.equal('#otherirc');
  });
});

import chai from 'chai';
import irc from 'irc';
import discord from 'discord.js';
import Bot from '../lib/bot';
import config from './fixtures/single-test-config.json';
import caseConfig from './fixtures/case-sensitivity-config.json';
import DiscordStub from './stubs/discord-stub';
import ClientStub from './stubs/irc-client-stub';
import { validateChannelMapping } from '../lib/validators';

chai.should();

describe('Channel Mapping', () => {
  before(() => {
    irc.Client = ClientStub;
    discord.Client = DiscordStub;
  });

  it('should fail when not given proper JSON', () => {
    const wrongMapping = 'not json';
    function wrap() {
      validateChannelMapping(wrongMapping);
    }

    (wrap).should.throw('Invalid channel mapping given');
  });

  it('should not fail if given a proper channel list as JSON', () => {
    const correctMapping = { '#channel': '#otherchannel' };
    function wrap() {
      validateChannelMapping(correctMapping);
    }

    (wrap).should.not.throw();
  });

  it('should clear channel keys from the mapping', () => {
    const bot = new Bot(config);
    bot.channelMapping['#discord'].should.equal('#irc');
    bot.invertedMapping['#irc'].should.equal('#discord');
    bot.channels.should.contain('#irc channelKey');
  });

  it('should lowercase IRC channel names', () => {
    const bot = new Bot(caseConfig);
    bot.channelMapping['#discord'].should.equal('#irc');
    bot.channelMapping['#otherDiscord'].should.equal('#otherirc');
  });
});

Update check to look for presence, not equality (order unnecessary)


module.exports = {
  normalizeEntityName: function() {},

  afterInstall: function(options) {
    // We assume that handlebars, ember, and jquery already exist
    return this.addBowerPackagesToProject([
      {
        // Antiscroll seems to be abandoned by its original authors. We need
        // two things: (1) a version in package.json, and (2) the name of the
        // package must be "antiscroll" to satisfy ember-cli.
        'name': 'git://github.com/azirbel/antiscroll.git#90391fb371c7be769bc32e7287c5271981428356'
      },
      {
        'name': 'jquery-mousewheel',
        'target': '~3.1.4'
      },
      {
        'name': 'jquery-ui',
        'target': '~1.11.4'
      }
    ]);
  }
};

module.exports = {
  normalizeEntityName: function() {},

  afterInstall: function(options) {
    // We assume that handlebars, ember, and jquery already exist
    return this.addBowerPackagesToProject([
      {
        // Antiscroll seems to be abandoned by its original authors. We need
        // two things: (1) a version in package.json, and (2) the name of the
        // package must be "antiscroll" to satisfy ember-cli.
        'name': 'git://github.com/Addepar/antiscroll.git#e0d1538cf4f3fd61c5bedd6168df86d651f125da'
      },
      {
        'name': 'jquery-mousewheel',
        'target': '~3.1.4'
      },
      {
        'name': 'jquery-ui',
        'target': '~1.11.4'
      }
    ]);
  }
};

Use correct version of antiscroll in blueprint


$(function() {
  // Change 'title' to something else first
  $('tr[title]').each(function() {
    $(this).attr('xtitle', $(this).attr('title')).removeAttr('title');
  })
    .children().children(':input')
      // Set up event handlers
      .bt({trigger: ['helperon', 'helperoff'],
             titleSelector: "parent().parent().attr('xtitle')",
             killTitle: false,
             fill: 'rgba(135, 206, 250, .9)',
             positions: ['bottom', 'top', 'right'],
          })
      .bind('focus', function() {
                $(this).trigger('helperon');
              })
      .bind('blur', function() {
                $(this).trigger('helperoff');
              })
    .parent()
      .bind('mouseover', function() {
                $(this).children(':input').trigger('helperon');
              })
      .bind('mouseleave', function() {
                $(this).children(':input').trigger('helperoff');
              });
});

Make tooltips work when tabbing


var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

var player;
function onYouTubeIframeAPIReady() {
  player = new YT.Player('player', {
    height: '390',
    width: '640',
    videoId: 'GxfwZMdSt4w',
    events: {
      'onReady': onPlayerReady
    }
  });
}

function onPlayerReady(event) {
  event.target.playVideo();
  player.setVolume(30);
}

var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

var player;
function onYouTubeIframeAPIReady() {
  player = new YT.Player('player', {
    height: '390',
    width: '640',
    videoId: 'c24En0r-lXg',
    events: {
      'onReady': onPlayerReady
    }
  });
}

function onPlayerReady(event) {
  event.target.playVideo();
  player.setVolume(30);
}

Change video ID (see extended log)


/* application entry point */

// require mithril globally for convenience
window.m = require('mithril');

// cordova plugins polyfills for browser
if (!window.cordova) require('./cordovaPolyfills.js');

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

var home = require('./ui/home');
var login = require('./ui/login');
var play = require('./ui/play');
var seek = require('./ui/seek');

function main() {

  m.route(document.body, '/', {
    '/': home,
    '/login': login,
    '/seek/:id': seek,
    '/play/:id': play
  });

  if (utils.hasNetwork()) session.refresh(true);

  window.cordova.plugins.Keyboard.disableScroll(true);

  if (window.gaId) window.analytics.startTrackerWithId(window.gaId);

  setTimeout(function() {
    window.navigator.splashscreen.hide();
  }, 500);
}

document.addEventListener('deviceready',
  // i18n must be loaded before any rendering happens
  utils.ƒ(i18n.loadPreferredLanguage, main),
  false
);

/* application entry point */

// require mithril globally for convenience
window.m = require('mithril');

// cordova plugins polyfills for browser
if (!window.cordova) require('./cordovaPolyfills.js');

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

var home = require('./ui/home');
var login = require('./ui/login');
var play = require('./ui/play');
var seek = require('./ui/seek');

function onResume() {
  session.refresh(true);
}

function main() {

  m.route(document.body, '/', {
    '/': home,
    '/login': login,
    '/seek/:id': seek,
    '/play/:id': play
  });

  // refresh data once and on app resume
  if (utils.hasNetwork()) session.refresh(true);
  document.addEventListener('resume', onResume, false);

  // iOs keyboard hack
  // TODO we may want to remove this and call only on purpose
  window.cordova.plugins.Keyboard.disableScroll(true);

  if (window.gaId) window.analytics.startTrackerWithId(window.gaId);

  setTimeout(function() {
    window.navigator.splashscreen.hide();
  }, 500);
}

document.addEventListener('deviceready',
  // i18n must be loaded before any rendering happens
  utils.ƒ(i18n.loadPreferredLanguage, main),
  false
);

Refresh data on app going foreground


/**
* Questions.js
*
* @description :: TODO: You might write a short summary of how this model works and what it represents here.
* @docs        :: http://sailsjs.org/#!documentation/models
*/

module.exports = {

  attributes: {
        text: 'string',
        type: {
            type: 'string',
            enum: ['email', 'range', 'freetext']
        },
  }
};

/**
* Questions.js
*
* @description :: TODO: You might write a short summary of how this model works and what it represents here.
* @docs        :: http://sailsjs.org/#!documentation/models
*/

module.exports = {

  attributes: {
        text: 'string',
        type: {
            type: 'string',
            enum: ['string', 'text', 'range']
        },
  }
};

Modify question type to allow the following type: string, text or range


// This just dumps out an ast for your viewing pleasure.

include("dumpast.js");

function process_js(ast) {
  dump_ast(ast);
}

include("beautify.js");

function process_js(ast) {
  _print('// process_js');
  _print(js_beautify(uneval(ast))
    .replace(/op: (\d+),/g,
        function (hit, group1) {
          return 'op: ' + decode_op(group1) + '(' + group1 + '),'
        })
    .replace(/type: (\d+),/g,
        function (hit, group1) {
          return 'type: ' + decode_type(group1) + '(' + group1 + '),'
        })
    .replace(/{/g, '<span>{')
    .replace(/}/g, '}</span>'));
}

var global = this;
var optable = null, toktable;
function decode_op(opcode) {
  if (!optable) {
    optable = [];
      for (let key in global) {
	if (key.indexOf("JSOP_") == 0) {
	  optable[global[key]] = key;
	}
      }
  }
  if (opcode in optable)
    return optable[opcode];
  return opcode;
}

function decode_type(opcode) {
  if (!toktable) {
    toktable = [];
    for (let key in global) {
      if (key.indexOf("TOK_") == 0) {
	toktable[global[key]] = key;
      }
    }
  }
  if (opcode in toktable)
    return toktable[opcode];
  return opcode;
}

Make jshydra output match dehydra's.


import React, { PropTypes } from 'react';

const Quote = ({quote}) => (

{quote.value}

{quote.author}

)

Quote.propTypes = {
  quote: PropTypes.object.isRequired
}

export default Quote;

import React, { PropTypes } from 'react';

const Quote = ({quote}) => (

{quote.value}

- {quote.author}

)

Quote.propTypes = {
  quote: PropTypes.object.isRequired
}

export default Quote;

Add classes to quote component


#!/usr/bin/env node

/*eslint-disable no-var */
var path = require('path');
var spawner = require('child_process');

exports.getFullPath = function(script){
    return path.join(__dirname, script);
};

// Respawn ensuring proper command switches
exports.respawn = function respawn(script, requiredArgs, hostProcess) {
    if (!requiredArgs || requiredArgs.length === 0) {
        requiredArgs = ['--harmony'];
    }
    var args = hostProcess.execArgv.slice();
    for (var i = 0; i < requiredArgs.length; i += 1) {
        if (args.indexOf(requiredArgs[i]) < 0) {
            args.push(requiredArgs[i]);
        }
    }
    var execScript = exports.getFullPath(script);
    spawner.spawn(hostProcess.execPath, args.concat(execScript, hostProcess.argv.slice(2)), {
        cwd: hostProcess.cwd(),
        stdio: 'inherit'
    });
};

/* istanbul ignore if */
if (require.main === module) {
    var argv = require('yargs')
        .usage('Usage: $0 [options] <cfgFile>')
        .demand(1, 1, 'A valid configuration file must be provided')
        .describe({
            checkCfg: 'Check the validity of the configuration file without starting the bot'
        })
        .argv;
    //respawn, ensuring required command switches
    exports.respawn('cli', ['--harmony', '--harmony_arrow_functions'], process);
}
/*eslint-enable no-var */

#!/usr/bin/env node

/*eslint-disable no-var */
var path = require('path');
var spawner = require('child_process');

exports.getFullPath = function(script){
    return path.join(__dirname, script);
};

// Respawn ensuring proper command switches
exports.respawn = function respawn(script, requiredArgs, hostProcess) {
    if (!requiredArgs || requiredArgs.length === 0) {
        requiredArgs = ['--harmony'];
    }
    var args = hostProcess.execArgv.slice();
    for (var i = 0; i < requiredArgs.length; i += 1) {
        if (args.indexOf(requiredArgs[i]) < 0) {
            args.push(requiredArgs[i]);
        }
    }
    var execScript = exports.getFullPath(script);
    spawner.spawn(hostProcess.execPath, args.concat(execScript, hostProcess.argv.slice(2)), {
        cwd: hostProcess.cwd(),
        stdio: 'inherit'
    });
};

/* istanbul ignore if */
if (require.main === module) {
    var argv = require('yargs')
        .usage('Usage: $0 <cfgFile> [options]')
        .demand(1, 1, 'A valid configuration file must be provided')
        .describe({
            checkCfg: 'Check the validity of the configuration file without starting the bot'
        })
        .argv;
    //respawn, ensuring required command switches
    exports.respawn('cli', ['--harmony', '--harmony_arrow_functions'], process);
}
/*eslint-enable no-var */

Fix ordering on command line


angular
  .module('app')
  .factory('apiService', [
    '$http',
    function($http) {
      return {
        client: $http.get('/api/bower'),
        server: $http.get('/api/package')
      };
    }
  ])
;

angular
  .module('app')
  .factory('apiService', [
    '$http',
    function($http) {
      return {
        client: $http.get('../bower.json'),
        server: $http.get('../package.json')
      };
    }
  ])
;

Use relative paths for bower/package.json


const validate = (values) => {
  const errors = {};
  if (!values.title || values.title.trim() === '') {
    errors.title = 'Book title is required';
  }
  if (!values.author || values.author.trim() === '') {
    errors.author = 'Book author is required';
  }
  if (!values.description || values.description.trim() === '') {
    errors.description = 'Book description is required';
  }
  if (!values.subject) {
    errors.subject = 'Book subject is required';
  }
  if (!values.description || values.description.trim() === '') {
    errors.description = 'Book description is required';
  }
  if (!values.imageURL || values.imageURL.trim() === '') {
    errors.imageURL = 'ImageURL is required';
  }
  if (!values.quantity || values.quantity.trim() === '') {
    errors.quantity = 'quantity is required';
  }
  if (values.quantity && values.quantity <= 0) {
    errors.quantity = 'quantity must be greater than 0';
  }
  return errors;
};

export default validate;

const validate = (values) => {
  const errors = {};
  if (!values.title || values.title.trim() === '') {
    errors.title = 'Book title is required';
  }
  if (!values.author || values.author.trim() === '') {
    errors.author = 'Book author is required';
  }
  if (!values.description || values.description.trim() === '') {
    errors.description = 'Book description is required';
  }
  if (!values.subject) {
    errors.subject = 'Book subject is required';
  }
  if (!values.description || values.description.trim() === '') {
    errors.description = 'Book description is required';
  }
  if (!values.imageURL || values.imageURL.trim() === '') {
    errors.imageURL = 'ImageURL is required';
  }
  if (!values.quantity) {
    errors.quantity = 'quantity is required';
  }
  if (values.quantity && values.quantity <= 0) {
    errors.quantity = 'quantity must be greater than 0';
  }
  return errors;
};

export default validate;

Fix bug in validation method


var test = require('ava');
var unescapeCss = require('../lib/unescape-css');

test('should unescape plain chars', function (t) {
  t.is(unescapeCss('Romeo \\+ Juliette'), 'Romeo + Juliette');
});

test('should unescape ASCII chars', function (t) {
  t.is(unescapeCss('\\34\\32'), '42');
});

test('should unescape Unicode chars', function (t) {
  t.is(unescapeCss('I \\2665  NY'), 'I ♥ NY');
});

var test = require('ava');
var unescapeCss = require('../lib/unescape-css');

test('unescapes plain chars', function (t) {
  t.is(unescapeCss('Romeo \\+ Juliette'), 'Romeo + Juliette');
});

test('unescapes ASCII chars', function (t) {
  t.is(unescapeCss('\\34\\32'), '42');
});

test('unescapes Unicode chars', function (t) {
  t.is(unescapeCss('I \\2665  NY'), 'I ♥ NY');
});

Remove word "should" from test descriptions


(function ($) {

    $.fn.dragster = function (options) {
        var settings = $.extend({
            enter: $.noop,
            leave: $.noop
        }, options);

        return this.each(function () {
            var first = false,
                second = false,
                $this = $(this);

            $this.on({
                dragenter: function () {
                    if (first) {
                        return second = true;
                    } else {
                        first = true;
                        $this.trigger('dragster:enter');
                    }
                },
                dragleave: function () {
                    if (second) {
                        second = false;
                    } else if (first) {
                        first = false;
                    }
                    if (!first && !second) {
                        $this.trigger('dragster:leave');
                    }
                },
                'dragster:enter': settings.enter,
                'dragster:leave': settings.leave
            });
        });
    };

}(jQuery));

(function ($) {

    $.fn.dragster = function (options) {
        var settings = $.extend({
            enter: $.noop,
            leave: $.noop,
            over: $.noop
        }, options);

        return this.each(function () {
            var first = false,
                second = false,
                $this = $(this);

            $this.on({
                dragenter: function (event) {
                    if (first) {
                        return second = true;
                    } else {
                        first = true;
                        $this.trigger('dragster:enter', event);
                    }
                    event.preventDefault();
                },
                dragleave: function (event) {
                    if (second) {
                        second = false;
                    } else if (first) {
                        first = false;
                    }
                    if (!first && !second) {
                        $this.trigger('dragster:leave', event);
                    }
                    event.preventDefault();
                },
                dragover: function (event) {
                    event.preventDefault();
                },
                'dragster:enter': settings.enter,
                'dragster:leave': settings.leave,
                'dragster:over': settings.over
            });
        });
    };

}(jQuery));

Fix drop event with preventDefault


var args = arguments[0] || {};

if (args.hasOwnProperty('itemIndex')) {
  // Edit mode
  var myModel = Alloy.Collections.tasks.at(args.itemIndex);
  $.text.value = myModel.get('text');
} else {
  // Add mode
  var myModel = Alloy.createModel('tasks');
  myModel.set("status", "pending");
}

// Focus on 1st input
function windowOpened() {
  $.text.focus();
}

function saveBtnClicked() {
  myModel.set("text", $.text.value);
  myModel.set("lastModifiedDate", Alloy.Globals.moment().toISOString());

  // insert if add, update if edit
  myModel.save();

  // Refresh home screen
  args.refreshCollection();

  Alloy.Globals.pageStack.back();
}

var args = arguments[0] || {};

if (args.hasOwnProperty('itemIndex')) {
  // Edit mode
  var myModel = Alloy.Collections.tasks.at(args.itemIndex);
  $.text.value = myModel.get('text');
} else {
  // Add mode
  var myModel = Alloy.createModel('tasks');
  myModel.set("status", "pending");
}

// Focus on 1st input
function windowOpened() {
  $.text.focus();
}

function saveBtnClicked() {

  if ($.text.value.length === 0) {
    var alertDialog = Ti.UI.createAlertDialog({
      title: 'Error',
      message: 'Enter task title at least',
      buttons: ['OK']
    });
    alertDialog.addEventListener('click', function() {
      $.text.focus();
    });
    alertDialog.show();

    return false;
  }

  myModel.set("text", $.text.value);
  myModel.set("lastModifiedDate", Alloy.Globals.moment().toISOString());

  // insert if add, update if edit
  myModel.save();

  // Refresh home screen
  args.refreshCollection();

  Alloy.Globals.pageStack.back();
}

Validate the only 1 field we have right now


$(document).ready(function() {

  $('.side-nav-container').hover(function() {
    $(this).addClass('is-showed');
    $('.kudo').addClass('hide');
  }, function() {
    $(this).removeClass('is-showed');
    $('.kudo').removeClass('hide');
  });

  $(window).scroll(function () {
    var logotype = $('.logotype');
    var buttonNav = $('.side-nav__button');

    if ($(window).scrollTop() > 150) {
      logotype.addClass('is-showed');
      buttonNav.addClass('no-opacity');
    } else {
      logotype.removeClass('is-showed');
      buttonNav.removeClass('no-opacity')
    }
  });

});

$(document).ready(function () {

    $('.side-nav-container').hover(function () {
        $(this).addClass('is-showed');
        $('.kudo').addClass('hide');
    }, function() {
        $(this).removeClass('is-showed');
        $('.kudo').removeClass('hide');
    });

    $(window).scroll(function () {
        var logotype = $('.logotype'),
            buttonNav = $('.side-nav__button'),
            scrollTop = $(window).scrollTop(),
            windowHeight = $(window).outerHeight(),
            kudoSide = $('.kudo');
            kudoBottom = $('.instapaper'); // $('.kudo-bottom')
            kudoBottomPosition = kudoBottom.offset().top; // instapaper for now while adding kudo-bottom elem

        if ( scrollTop > 150) {
            logotype.addClass('is-showed');
            buttonNav.addClass('no-opacity');
        } else {
            logotype.removeClass('is-showed');
            buttonNav.removeClass('no-opacity')
        }

        if ( scrollTop + windowHeight > kudoBottomPosition) {
            kudoSide.addClass('hide');
        } else {
            kudoSide.removeClass('hide');
        }
    });
});

Add animation for kudo-side when kudo-bottom is showed


/*
Copyright 2015, 2016 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

'use strict';

var React = require('react');
var DateUtils = require('matrix-react-sdk/lib/DateUtils');

module.exports = React.createClass({
    displayName: 'MessageTimestamp',

    render: function() {
        var date = new Date(this.props.ts);
        return (
            <span className="mx_MessageTimestamp">
                { DateUtils.formatTime(date) }
            </span>
        );
    },
});

/*
Copyright 2015, 2016 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

'use strict';

var React = require('react');
var DateUtils = require('matrix-react-sdk/lib/DateUtils');

module.exports = React.createClass({
    displayName: 'MessageTimestamp',

    render: function() {
        var date = new Date(this.props.ts);
        return (
            <span className="mx_MessageTimestamp" title={ DateUtils.formatDate(date) }>
                { DateUtils.formatTime(date) }
            </span>
        );
    },
});

Add date tooltip to timestamps


// The rules ultimately override any rules defined in legacy/rules/style.js
module.exports = {
    rules: {
        // Enforce function expressions
        // http://eslint.org/docs/rules/func-style
        'func-style': ['error', 'expression'],

        // enforce that `let` & `const` declarations are declared together
        // http://eslint.org/docs/rules/one-var
        'one-var': ['error', 'never']
    }
};

// The rules ultimately override any rules defined in legacy/rules/style.js
module.exports = {
    rules: {
        // Enforce function expressions
        // http://eslint.org/docs/rules/func-style
        'func-style': ['error', 'expression'],

        // enforce that `let` & `const` declarations are declared together
        // http://eslint.org/docs/rules/one-var
        'one-var': ['error', 'never'],

        // enforce spacing around infix operators
        // http://eslint.org/docs/rules/space-infix-ops
        'space-infix-ops': 'error'
    }
};

Add new rule for spacing around infix operators


import { ActionConstants as actions } from '../actions'
import { NETWORK_MAIN, NETWORK_TEST } from '../actions/network'

export default function network(state = {}, action) {
    switch (action.type) {
        case actions.network.SWITCH: {
            return {
                ...state,
                net: action.network
            }
        }
        case actions.network.SET_BLOCK_HEIGHT: {
            const newBlockHeight = {
                ...state.blockHeight,
                [state.net]: action.blockHeight
            }
            return {
                ...state,
                blockHeight: newBlockHeight
            }
        }
        case actions.network.TOGGLE: {
            return {
                ...state,
                net: state.net === NETWORK_MAIN ? NETWORK_TEST : NETWORK_MAIN
            }
        }
        default:
            return state
    }
}

import { ActionConstants as actions } from '../actions'
import { NETWORK_MAIN, NETWORK_TEST } from '../actions/network'

export default function network(state = {}, action) {
    switch (action.type) {
        case actions.network.SWITCH: {
            return {
                ...state,
                net: action.network
            }
        }
        case actions.network.SET_BLOCK_HEIGHT: {
            const newBlockHeight = {
                ...state.blockHeight,
                [state.net]: action.blockHeight
            }
            return {
                ...state,
                blockHeight: newBlockHeight
            }
        }
        case actions.network.TOGGLE: {
            return {
                ...state,
                net: state.net === NETWORK_MAIN ? NETWORK_TEST : NETWORK_MAIN
            }
        }
        case actions.wallet.RESET_STATE:
            return {
                ...state,
                blockHeight: {
                    TestNet: 0,
                    MainNet: 0
                }
            }
        default:
            return state
    }
}

Fix updating of balance after a logout followed by a quick login


'use strict';

describe('controllers', function() {
  var httpBackend, scope, createController;
  beforeEach(module('polyflix'));

  beforeEach(inject(function($rootScope, $httpBackend, $controller) {
    httpBackend = $httpBackend;
    scope = $rootScope.$new();
    httpBackend.whenGET(mocks.configUrl).respond(
      JSON.stringify(mocks.configResults)
    );
    httpBackend.whenGET(mocks.allMoviesUrl).respond(
      JSON.stringify(mocks.allMoviesResults)
    );

    createController = function() {
        return $controller('MoviesCtrl', {
            '$scope': scope
        });
    };
  }));

  describe('controller', function() {
    it('should exist', function() {
      var controller = createController();
      expect(controller).toBeDefined();
    });
  });
});

'use strict';

describe('controllers', function() {
  var httpBackend, scope, createController;
  beforeEach(module('polyflix'));

  beforeEach(inject(function($rootScope, $httpBackend, $controller) {
    httpBackend = $httpBackend;
    scope = $rootScope.$new();
    httpBackend.whenGET(mocks.configUrl).respond(
      JSON.stringify(mocks.configResults)
    );
    httpBackend.whenGET(mocks.allMoviesUrl).respond(
      JSON.stringify(mocks.allMoviesResults)
    );
    httpBackend.whenDELETE(mocks.deleteUrl).respond(
      JSON.stringify(mocks.deleteResults)
    );

    createController = function() {
        return $controller('MoviesCtrl', {
            '$scope': scope
        });
    };
  }));

  describe('controller', function() {
    it('should exist', function() {
      var controller = createController();
      httpBackend.flush();
      expect(controller).toBeDefined();
    });
  });
});

Add mocks to movies controller tests


/*
 * Run in: http://adventofcode.com/day/10
 */

;(function () {
  let input = document.querySelector('.puzzle-input').textContent

  console.log(
    'Day09/first:',
    first(input)
  )

  console.log(
    'Day09/second:',
    second(input)
  )

  function first (input) {
    return Array(40).fill().reduce(lookAndSay, input).length
  }

  function second (input) {
    return Array(50).fill().reduce(lookAndSay, input).length
  }

  function lookAndSay (input) {
    let ex = /(\d)\1*/g
    return input.match(ex).map(el => el.length + el[0]).join('')
  }
}())

/*
 * Run in: http://adventofcode.com/day/10
 */

;(function () {
  let input = document.querySelector('.puzzle-input').textContent

  console.log(
    'Day10/first:',
    first(input)
  )

  console.log(
    'Day10/second:',
    second(input)
  )

  function first (input) {
    return Array(40).fill().reduce(lookAndSay, input).length
  }

  function second (input) {
    return Array(50).fill().reduce(lookAndSay, input).length
  }

  function lookAndSay (input) {
    let ex = /(\d)\1*/g
    return input.match(ex).map(el => el.length + el[0]).join('')
  }
}())

Fix typo on console message


'use strict';
angular.module('com.module.sandbox')
  .controller('SandboxFormsCtrl', function ($scope, CoreService) {

    var now = new Date();

    $scope.formOptions = {};

    $scope.formData = {
      name: null,
      description: null,
      startDate: now,
      startTime: now,
      endDate: now,
      endTime: now
    };

    $scope.formFields = [{
      key: 'name',
      type: 'input',
      templateOptions: {
        label: 'Name'
      }
    }, {
      key: 'description',
      type: 'textarea',
      templateOptions: {
        label: 'Description'
      }
    }, {
      key: 'startDate',
      type: 'datepicker',
      templateOptions: {
        label: 'Start Date'
      }
    }, {
      key: 'startTime',
      type: 'timepicker',
      templateOptions: {
        label: 'Start Time'
      }
    }, {
      key: 'endDate',
      type: 'datepicker',
      templateOptions: {
        label: 'End Date'
      }
    }, {
      key: 'endTime',
      type: 'timepicker',
      templateOptions: {
        label: 'End Time'
      }
    }];

    $scope.formOptions = {};

    $scope.onSubmit = function (data) {
      CoreService.alertSuccess('Good job!', JSON.stringify(data, null, 2));
    };
  });

'use strict';
angular.module('com.module.sandbox')
  .controller('SandboxFormsCtrl', function ($scope, CoreService) {

    var now = new Date();

    $scope.formOptions = {};

    $scope.formData = {
      name: null,
      description: null,
      startDate: now,
      startTime: now,
      endDate: now,
      endTime: now
    };

    $scope.formFields = [{
      key: 'name',
      type: 'input',
      templateOptions: {
        label: 'Name'
      }
    }, {
      key: 'description',
      type: 'textarea',
      templateOptions: {
        label: 'Description'
      }
    }, {
      key: 'startDate',
      type: 'datepicker',
      templateOptions: {
        label: 'Start Date'
      }
    }, {
      key: 'startTime',
      type: 'timepicker',
      templateOptions: {
        label: 'Start Time'
      }
    }, {
      key: 'endDate',
      type: 'datepicker',
      templateOptions: {
        label: 'End Date'
      }
    }, {
      key: 'endTime',
      type: 'timepicker',
      templateOptions: {
        label: 'End Time'
      }
    }];

    $scope.onSubmit = function (data) {
      CoreService.alertSuccess('Good job!', JSON.stringify(data, null, 2));
    };
  });

Remove obsolete object from scope


'use strict';

const Game = require('./game');

// todo: this class got refactored away to almost nothing. does it still have value?
// hmm, yes, since we are decorating it

class GameEngine {
  constructor(repositorySet, commandHandlers) {
    this._repositorySet = repositorySet;
    this._handlers = commandHandlers;
  }

  createGame() {
    return new Game(this._repositorySet, this._handlers);
  }
}

module.exports = GameEngine;

'use strict';

const Game = require('./game-state');

// todo: this class got refactored away to almost nothing. does it still have value?
// hmm, yes, since we are decorating it

class GameEngine {
  constructor(repositorySet, commandHandlers) {
    this._repositorySet = repositorySet;
    this._handlers = commandHandlers;
  }

  createGame() {
    return new Game(this._repositorySet, this._handlers);
  }
}

module.exports = GameEngine;

Fix module name in require after rename


Package.describe({
    name: '255kb:cordova-disable-select',
    version: '1.0.2',
    summary: 'Disables user selection and iOS magnifying glass / longpress menu in Cordova applications.',
    git: 'https://github.com/255kb/cordova-disable-select',
    documentation: 'README.md'
});

Package.onUse(function(api) {
    api.versionsFrom('1.2');

    Cordova.depends({
        'cordova-plugin-ios-longpress-fix': '1.1.0'
    });

    api.addFiles(['client/cordova-disable-select.css'], 'client');
});

Package.describe({
    name: '255kb:cordova-disable-select',
    version: '1.0.2',
    summary: 'Disables user selection and iOS magnifying glass / longpress menu in Cordova applications.',
    git: 'https://github.com/255kb/cordova-disable-select',
    documentation: 'README.md'
});

Package.onUse(function(api) {
    api.versionsFrom('1.2');

    Cordova.depends({
        'cordova-plugin-ios-longpress-fix': '1.1.0'
    });

    api.addFiles(['client/cordova-disable-select.css'], 'web.cordova');
});

Add css file as 'web.cordova' is more accurate.


import React from 'react';

export default function LogoutButton(props) {
  return (

      {props.email}
      Sign Out

  );
}

LogoutButton.propTypes = {
  email: React.PropTypes.string.isRequired,
  signOut: React.PropTypes.string.isRequired,
};

import React from 'react';

export default function LogoutButton(props) {
  return (

      {props.email}
      Sign Out

  );
}

LogoutButton.propTypes = {
  email: React.PropTypes.string.isRequired,
  signOut: React.PropTypes.func.isRequired,
};

Fix logout button prop type warning


const { join } = require('path')
const { writeFile } = require('fs').promises
const { default: players } = require('../lib/players')

const generateSinglePlayers = async () => {
  for (const { key, name } of players) {
    const file = `
      const { createReactPlayer } = require('./lib/ReactPlayer')
      const Player = require('./lib/players/${name}').default
      module.exports = createReactPlayer([Player])
    `
    await writeFile(join('.', `${key}.js`), file)
  }
}

generateSinglePlayers()

const { join } = require('path')
const { writeFile } = require('fs').promises
const { default: players } = require('../lib/players')

const generateSinglePlayers = async () => {
  for (const { key, name } of players) {
    const file = `
      const createReactPlayer = require('./lib/ReactPlayer').createReactPlayer
      const Player = require('./lib/players/${name}').default
      module.exports = createReactPlayer([Player])
    `
    await writeFile(join('.', `${key}.js`), file)
  }
}

generateSinglePlayers()

Fix single player imports on IE11


import { moduleForComponent, skip } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
import notificationsStub from 'wherehows-web/tests/stubs/services/notifications';
import userStub from 'wherehows-web/tests/stubs/services/current-user';
import sinon from 'sinon';

moduleForComponent(
  'datasets/containers/dataset-acl-access',
  'Integration | Component | datasets/containers/dataset acl access',
  {
    integration: true,

    beforeEach() {
      this.register('service:current-user', userStub);
      this.register('service:notifications', notificationsStub);

      this.inject.service('current-user');
      this.inject.service('notifications');

      this.server = sinon.createFakeServer();
      this.server.respondImmediately = true;
    },

    afterEach() {
      this.server.restore();
    }
  }
);
// Skipping, due to investigate test failure in future PR
skip('it renders', function(assert) {
  this.server.respondWith('GET', /\/api\/v2\/datasets.*/, [
    200,
    { 'Content-Type': 'application/json' },
    JSON.stringify({})
  ]);
  this.render(hbs`{{datasets/containers/dataset-acl-access}}`);

  assert.equal(
    this.$()
      .text()
      .trim(),
    'JIT ACL is not currently available for this dataset platform'
  );
});

import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
import notificationsStub from 'wherehows-web/tests/stubs/services/notifications';
import userStub from 'wherehows-web/tests/stubs/services/current-user';
import sinon from 'sinon';

moduleForComponent(
  'datasets/containers/dataset-acl-access',
  'Integration | Component | datasets/containers/dataset acl access',
  {
    integration: true,

    beforeEach() {
      this.register('service:current-user', userStub);
      this.register('service:notifications', notificationsStub);

      this.inject.service('current-user');
      this.inject.service('notifications');

      this.server = sinon.createFakeServer();
      this.server.respondImmediately = true;
    },

    afterEach() {
      this.server.restore();
    }
  }
);

test('it renders', function(assert) {
  this.server.respondWith('GET', /\/api\/v2\/datasets.*/, [
    200,
    { 'Content-Type': 'application/json' },
    JSON.stringify({})
  ]);
  this.render(hbs`{{datasets/containers/dataset-acl-access}}`);

  assert.equal(
    this.$()
      .text()
      .trim(),
    'JIT ACL is not currently available for this dataset platform'
  );
});

Undo skip test after fix


import Ember from 'ember';
import redux from 'npm:redux';
import reducers from '../reducers/index';
import enhancers from '../enhancers/index';
import optional from '../reducers/optional';
import middlewareConfig from '../middleware/index';

const { assert, isArray } = Ember;

// Util for handling the case where no setup thunk was created in middleware
const noOp = () => {};

// Handle "classic" middleware exports (i.e. an array), as well as the hash option
const extractMiddlewareConfig = (mc) => {
  assert(
    'Middleware must either be an array, or a hash containing a `middleware` property',
    isArray(mc) || mc.middleware
  );
  return isArray(mc) ? { middleware: mc } : mc;
}

// Destructure the middleware array and the setup thunk into two different variables
const { middleware, setup = noOp } = extractMiddlewareConfig(middlewareConfig);

var { createStore, applyMiddleware, combineReducers, compose } = redux;
var createStoreWithMiddleware = compose(applyMiddleware(...middleware), enhancers)(createStore);

export default Ember.Service.extend({
    init() {
        this.store = createStoreWithMiddleware(optional(combineReducers(reducers)));
        setup(this.store);
        this._super(...arguments);
    },
    getState() {
        return this.store.getState();
    },
    dispatch(action) {
        return this.store.dispatch(action);
    },
    subscribe(func) {
        return this.store.subscribe(func);
    }
});

import Ember from 'ember';
import redux from 'npm:redux';
import reducers from '../reducers/index';
import enhancers from '../enhancers/index';
import optional from '../reducers/optional';
import middlewareConfig from '../middleware/index';

const { assert, isArray, K } = Ember;

// Handle "classic" middleware exports (i.e. an array), as well as the hash option
const extractMiddlewareConfig = (mc) => {
  assert(
    'Middleware must either be an array, or a hash containing a `middleware` property',
    isArray(mc) || mc.middleware
  );
  return isArray(mc) ? { middleware: mc } : mc;
}

// Destructure the middleware array and the setup thunk into two different variables
const { middleware, setup = K } = extractMiddlewareConfig(middlewareConfig);

var { createStore, applyMiddleware, combineReducers, compose } = redux;
var createStoreWithMiddleware = compose(applyMiddleware(...middleware), enhancers)(createStore);

export default Ember.Service.extend({
    init() {
        this.store = createStoreWithMiddleware(optional(combineReducers(reducers)));
        setup(this.store);
        this._super(...arguments);
    },
    getState() {
        return this.store.getState();
    },
    dispatch(action) {
        return this.store.dispatch(action);
    },
    subscribe(func) {
        return this.store.subscribe(func);
    }
});

Use Ember.K instead of noOp


import _ from 'lodash'

const grouping = [
  { title: 'Custom Song', criteria: song => song.custom },
  { title: 'Tutorial', criteria: song => song.tutorial },
  { title: 'Unreleased', criteria: song => song.unreleased },
  {
    title: 'New Songs',
    criteria: song =>
      song.added && Date.now() - Date.parse(song.added) < 14 * 86400000,
    sort: song => song.added,
    reverse: true
  },
  { title: '☆', criteria: () => true }
]

export function groupSongsIntoCategories (songs) {
  let groups = grouping.map(group => ({
    input: group,
    output: { title: group.title, songs: [] }
  }))
  for (let song of songs) {
    for (let { input, output } of groups) {
      if (input.criteria(song)) {
        output.songs.push(song)
        break
      }
    }
  }
  for (let { input, output } of groups) {
    if (input.sort) output.songs = _.sortBy(output.songs, input.sort)
    if (input.reverse) output.songs.reverse()
  }
  return _(groups)
    .map('output')
    .filter(group => group.songs.length > 0)
    .value()
}

export default groupSongsIntoCategories

import _ from 'lodash'

const grouping = [
  { title: 'Custom Song', criteria: song => song.custom },
  { title: 'Tutorial', criteria: song => song.tutorial },
  { title: 'Unreleased', criteria: song => song.unreleased },
  {
    title: 'New Songs',
    criteria: song =>
      song.added && Date.now() - Date.parse(song.added) < 60 * 86400000,
    sort: song => song.added,
    reverse: true
  },
  { title: '☆', criteria: () => true }
]

export function groupSongsIntoCategories (songs) {
  let groups = grouping.map(group => ({
    input: group,
    output: { title: group.title, songs: [] }
  }))
  for (let song of songs) {
    for (let { input, output } of groups) {
      if (input.criteria(song)) {
        output.songs.push(song)
        break
      }
    }
  }
  for (let { input, output } of groups) {
    if (input.sort) output.songs = _.sortBy(output.songs, input.sort)
    if (input.reverse) output.songs.reverse()
  }
  return _(groups)
    .map('output')
    .filter(group => group.songs.length > 0)
    .value()
}

export default groupSongsIntoCategories

Put songs as new for 2 months


const is = require('is');
const url = require('url');
/**
 * Returns an object containing the required properties to make an
 * http request.
 * @module DELETE/sanitizeArguments
 * @param {array} a - Arguments
 * @return {object} args - "Sanitized" arguments
 */
module.exports = function sanitizeArguments(a = []) {
  const args = {};

  // The first argument can either be an url string or an options object
  if (is.string(a[0])) args.url = a[0];
  else if (is.object(a[0])) args.options = a[0];

  // Lets always use an request options object
  if (!args.options) args.options = {};
  if (args.url) {
    const urlObj = url.parse(args.url);
    args.options.hostname = urlObj.hostname;
    args.options.port = urlObj.port;
    args.options.path = urlObj.path;
    args.options.method = 'DELETE';
    if (args.url.indexOf('https') !== -1)
      args.options.protocol = 'https:';
    else
      args.options.protocol = 'http:';
  }

  // Is there a callback?
  if (is.function(a[1])) args.cb = a[1];

  return args;
};

const is = require('is');
const url = require('url');
/**
 * Returns an object containing the required properties to make an
 * http request.
 * @module DELETE/sanitizeArguments
 * @param {array} a - Arguments
 * @return {object} args - "Sanitized" arguments
 */
module.exports = function sanitizeArguments(a = []) {
  const args = {};

  // The first argument can either be an url string or an options object
  if (is.string(a[0])) args.url = a[0];
  else if (is.object(a[0])) args.options = a[0];

  // Lets always use an request options object
  if (!args.options) args.options = {};

  // Fill request object with data from url (if set)
  if (args.url) {
    const urlObj = url.parse(args.url);
    args.options.hostname = urlObj.hostname;
    args.options.port = urlObj.port;
    args.options.path = urlObj.path;
    args.options.method = 'DELETE';
    if (args.url.indexOf('https') !== -1)
      args.options.protocol = 'https:';
    else
      args.options.protocol = 'http:';
  }

  // Is there a callback?
  if (is.function(a[1])) args.cb = a[1];

  return args;
};

DELETE correct options object + protocol handling


import React from 'react';
import Invocation from './invocation';
import StatusLine from './status_line';

export default React.createClass({
    getInitialState() {
        return {vcsData: {
            isRepository: true,
            branch: 'name',
            status: 'clean'
        }};
    },
    componentWillMount() {
        this.props.terminal
            .on('invocation', this.forceUpdate.bind(this))
            .on('vcs-data', (data) => { this.setState({vcsData: data}) });
    },
    handleKeyDown(event) {
        // Ctrl+l
        if (event.ctrlKey && event.keyCode === 76) {
            this.props.terminal.clearInvocations();

            event.stopPropagation();
            event.preventDefault();
        }
    },
    render() {
        var invocations = this.props.terminal.invocations.map((invocation) => {
            return (
                <Invocation key={invocation.id} invocation={invocation}/>
            )
        });

        return (

                    {invocations}

                <StatusLine currentWorkingDirectory={this.props.terminal.currentDirectory}
                            vcsData={this.state.vcsData}/>

        );
    }
});

import React from 'react';
import Invocation from './invocation';
import StatusLine from './status_line';

export default React.createClass({
    getInitialState() {
        return {vcsData: {
            isRepository: true,
            branch: 'name',
            status: 'clean'
        }};
    },
    componentWillMount() {
        this.props.terminal
            .on('invocation', this.forceUpdate.bind(this))
            .on('vcs-data', (data) => { this.setState({vcsData: data}) });
    },
    handleKeyDown(event) {
        // Ctrl+L.
        if (event.ctrlKey && event.keyCode === 76) {
            this.props.terminal.clearInvocations();

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

        // Cmd+D.
        if (event.metaKey && event.keyCode === 68) {
            window.DEBUG = !window.DEBUG;

            event.stopPropagation();
            event.preventDefault();
            this.forceUpdate();
            console.log(`Debugging mode has been ${window.DEBUG ? 'enabled' : 'disabled'}.`);
        }
    },
    render() {
        var invocations = this.props.terminal.invocations.map((invocation) => {
            return (
                <Invocation key={invocation.id} invocation={invocation}/>
            )
        });

        return (

                    {invocations}

                <StatusLine currentWorkingDirectory={this.props.terminal.currentDirectory}
                            vcsData={this.state.vcsData}/>

        );
    }
});

Add a shortcut to toggle debug mode.


import Ember from 'ember'

import AFField, { DEFAULT_CONFIG as FIELD_DEFAULT_CONFIG } from '../components/af-field'

import i18n from '../mixins/i18n'
import loc from '../mixins/loc'
import restless from '../mixins/restless'
import validations from '../mixins/validations'

const AF_FIELD_MIXINS = { i18n, loc, restless, validations }

export default Ember.Service.extend({
  config: Object.assign({}, FIELD_DEFAULT_CONFIG, {
    prompt: 'Select',
    fieldPlugins: []
  }),
  configure (arg) {
    if (typeof arg === 'function') {
      arg(this.config)
    } else {
      Ember.$.extend(true, this.config, arg)
    }

    // TODO: localize plugin logic instead of tossing them onto the base class
    this.config.fieldPlugins.forEach((plugin) => {
      if (plugin instanceof Ember.Mixin) {
        AFField.reopen(plugin)
      } else if (AF_FIELD_MIXINS[plugin]) {
        AFField.reopen(AF_FIELD_MIXINS[plugin])
      } else {
        Ember.warn(`Not a valid plugin: ${plugin}`)
      }
    })

    return this
  }
})

import Ember from 'ember'

import { DEFAULT_CONFIG as FIELD_DEFAULT_CONFIG } from '../components/af-field'

import i18n from '../mixins/i18n'
import loc from '../mixins/loc'
import restless from '../mixins/restless'
import validations from '../mixins/validations'

const AF_FIELD_MIXINS = { i18n, loc, restless, validations }

export default Ember.Service.extend({
  config: Object.assign({}, FIELD_DEFAULT_CONFIG, {
    prompt: 'Select',
    fieldPlugins: []
  }),
  configure (arg) {
    if (typeof arg === 'function') {
      arg(this.config)
    } else {
      Ember.$.extend(true, this.config, arg)
    }

    let afFieldClass = Ember.getOwner(this).resolveRegistration('component:af-field')
    this.config.fieldPlugins.forEach((plugin) => {
      if (plugin instanceof Ember.Mixin) {
        afFieldClass.reopen(plugin)
      } else if (AF_FIELD_MIXINS[plugin]) {
        afFieldClass.reopen(AF_FIELD_MIXINS[plugin])
      } else {
        Ember.warn(`Not a valid plugin: ${plugin}`)
      }
    })

    return this
  }
})

Load addons into resolver AFField instead of global AFField


Add test for two variables being multiplied who lack a space in between.


$(document).ready(function(){
  $("#advanced_search_toggler").click(function(e){
    $('body').append('<div class="as-form-overlay">')
    $("#advanced_search_toggler").toggleClass('is-opened')
    $("#advanced_search_form").toggle();
    $(".as-form-overlay").click(function(e){
      $(".as-form-overlay").remove();
      $("#advanced_search_form").hide();
      $(".select2-drop").hide();
      return false;
    });
    return false;
  });

  $("#search_button").click(function(e){
    e.preventDefault();

    var queryParams = [];

    Carnival.submitIndexForm();
  });

  $("#clear_button").click(function(e){
    e.preventDefault();
    $($(this).parent().parent().parent()).trigger("reset")
    $("#advanced_search_form input").each(function(){
      var inputValue = $(this).val();
      $(this).val('');
    });

    $("#advanced_search_form select").each(function(){
      var inputValue = $(this).val();
      $(this).val('');
    });
    Carnival.submitIndexForm();
  });
});

$(document).ready(function(){
  $("#advanced_search_toggler").click(function(e){
    $('body').append('<div class="as-form-overlay">')
    $("#advanced_search_toggler").toggleClass('is-opened')
    $("#advanced_search_form").toggle();
    $('#advanced_search_form').find('input').focus();

    $(".as-form-overlay").click(function(e){
      $(".as-form-overlay").remove();
      $("#advanced_search_form").hide();
      $(".select2-drop").hide();
      return false;
    });
    return false;
  });

  $("#search_button").click(function(e){
    e.preventDefault();

    var queryParams = [];

    Carnival.submitIndexForm();
  });

  $("#clear_button").click(function(e){
    e.preventDefault();
    $($(this).parent().parent().parent()).trigger("reset")
    $("#advanced_search_form input").each(function(){
      var inputValue = $(this).val();
      $(this).val('');
    });

    $("#advanced_search_form select").each(function(){
      var inputValue = $(this).val();
      $(this).val('');
    });
    Carnival.submitIndexForm();
  });
});

Set focus on the first advanced search field


var pr = process.env.TRAVIS_PULL_REQUEST;
var souceUser = process.env.SAUCE_USERNAME || "liqd";
var sauceKey = process.env.SAUCE_ACCESS_KEY;
var name = ((pr === "false") ? "" : "#" + pr + " ") + process.env.TRAVIS_COMMIT;

var common = require("./protractorCommon.js");

var local = {
    sauceUser: souceUser,
    sauceKey: sauceKey,

    capabilities: {
        "browserName": "chrome",
        "tunnel-identifier": process.env.TRAVIS_JOB_NUMBER,
        "build": process.env.TRAVIS_BUILD_NUMBER,
        "name": name
    },
    allScriptsTimeout: 21000,
    getPageTimeout: 20000,
    jasmineNodeOpts: {
        showColors: true,
        defaultTimeoutInterval: 120000,
        isVerbose: true,
        includeStackTrace: true
    }
};

exports.config = Object.assign({}, common.config, local);

var pr = process.env.TRAVIS_PULL_REQUEST;
var souceUser = process.env.SAUCE_USERNAME || "liqd";
var sauceKey = process.env.SAUCE_ACCESS_KEY;
var name = ((pr === "false") ? "" : "#" + pr + " ") + process.env.TRAVIS_COMMIT;

var common = require("./protractorCommon.js");

var local = {
    sauceUser: souceUser,
    sauceKey: sauceKey,
        directConnect: false,

    capabilities: {
        "browserName": "chrome",
        "tunnel-identifier": process.env.TRAVIS_JOB_NUMBER,
        "build": process.env.TRAVIS_BUILD_NUMBER,
        "name": name
    },
    allScriptsTimeout: 21000,
    getPageTimeout: 20000,
    jasmineNodeOpts: {
        showColors: true,
        defaultTimeoutInterval: 120000,
        isVerbose: true,
        includeStackTrace: true
    }
};

exports.config = Object.assign({}, common.config, local);

Disable direct connect for sauce labs


import React, {Component, PropTypes} from 'react';
import t from 'tcomb-form';

function renderInput(locals) {

  const onChange = function (event) {
    locals.onChange(event.target.value);
  };

  const triggerRealInputChange = function (event) {
  	locals.onChange(document.getElementById('virtual-keyboard-helper-' + locals.attrs.name).value);
  };

  return <div>
  	<input type="text" label={locals.label} name={locals.attrs.name} value={locals.value} onChange={onChange} style={{display: 'none'}} />
  	<input type="text" id={'virtual-keyboard-helper-' + locals.attrs.name} className="form-control" label={locals.label} onBlur={triggerRealInputChange} />
  </div>;
}

const textboxTemplate = t.form.Form.templates.textbox.clone({ renderInput })

export default class VirtualKeyboardFactory extends t.form.Textbox {

  getTemplate() {
    return textboxTemplate
  }
}

import React, {Component, PropTypes} from 'react';
import t from 'tcomb-form';

function renderInput(locals) {

  const onChange = function (event) {
    locals.onChange(event.target.value);
  };

  const triggerRealInputChange = function (event) {
  	locals.onChange(document.getElementById('virtual-keyboard-helper-' + locals.attrs.name).value);
  };

  return <div>
  	<input type="text" label={locals.label} name={locals.attrs.name} value={locals.value} onChange={onChange} style={{display: 'none'}} />
  	<input type="text" id={'virtual-keyboard-helper-' + locals.attrs.name} value={locals.value} className="form-control" label={locals.label} onBlur={triggerRealInputChange} />
  </div>;
}

const textboxTemplate = t.form.Form.templates.textbox.clone({ renderInput })

export default class VirtualKeyboardFactory extends t.form.Textbox {

  getTemplate() {
    return textboxTemplate
  }
}

Fix bug with moving up and down


(function(global) {
    "use strict";

    var default_i18n = new Jed({
        locale_data: TRANSLATIONS,
        domain: "indico"
    });

    global.i18n = default_i18n;
    global.$T = _.bind(default_i18n.gettext, default_i18n);

    ['gettext', 'ngettext', 'pgettext', 'npgettext', 'translate'].forEach(function(method_name) {
        global.$T[method_name] = _.bind(default_i18n[method_name], default_i18n);
    });

    global.$T.domain = _.memoize(function(domain) {
        return new Jed({
            locale_data: TRANSLATIONS,
            domain: domain
        });
    });
}(window));

(function(global) {
    "use strict";

    var default_i18n = new Jed({
        locale_data: global.TRANSLATIONS,
        domain: "indico"
    });

    global.i18n = default_i18n;
    global.$T = _.bind(default_i18n.gettext, default_i18n);

    ['gettext', 'ngettext', 'pgettext', 'npgettext', 'translate'].forEach(function(method_name) {
        global.$T[method_name] = _.bind(default_i18n[method_name], default_i18n);
    });

    global.$T.domain = _.memoize(function(domain) {
        return new Jed({
            locale_data: global.TRANSLATIONS,
            domain: domain
        });
    });
}(window));

Make it more obvious that TRANSLATIONS is global


'use strict';

describe('Sign up page', function () {

	var watchButton;

	beforeEach(function () {
		watchButton = element('.btn-primary');
	});

	it('should have Watch/Unwatch disabled by default', function() {
		expect(watchButton.attr('disabled')).toBeTruthy();
	});

	it('should enable Watch/Unwatch button', function() {
		input('#email').enter('banan@fluga.com');
		expect(watchButton.attr('disabled')).toBeTruthy();

		input('#url').enter('http://github.com');
		expect(watchButton.attr('disabled')).toBeFalsy();
	});
});

'use strict';

describe('Sign up page', function () {

	var watchButton;

	beforeEach(function () {
		watchButton = element('.btn-primary');
	});

	it('should have Watch enabled by default', function() {
		expect(watchButton.attr('disabled')).toBeFalsy();
	});

	it('should enable Watch/Unwatch button', function() {
		input('#email').enter('banan@fluga.com');
		expect(watchButton.attr('disabled')).toBeTruthy();

		input('#url').enter('http://github.com');
		expect(watchButton.attr('disabled')).toBeFalsy();
	});
});

Update default behavior of Watch button


var users = require('./user-dump'),
	Promise = require('bluebird')

module.exports = {
	get : Get,
	add : Add
};

function Get(request,reply) {

	setTimeout(function() {
		reply.data = users;
		reply.continue();
	},500);
}

function Add(request,reply) {

	setTimeout(function() {
		users.push({name : request.payload.name,id : (users.length + 1)});
		reply.continue();
	},500);
}

var users = require('./stub'),
	Promise = require('bluebird')

module.exports = {
	get : Get,
	add : Add
};

function Get(request,reply) {

	setTimeout(function() {
		reply.data = users;
		reply.continue();
	},500);
}

function Add(request,reply) {

	setTimeout(function() {
		users.push({name : request.payload.name,id : (users.length + 1)});
		reply.continue();
	},500);
}

Use correct file name for db stub


import React, {Component, PropTypes} from 'react'
import classnames from 'classnames'
import {Scrollbars} from 'react-custom-scrollbars'

class FancyScrollbar extends Component {
  constructor(props) {
    super(props)
  }

  static defaultProps = {
    autoHide: true,
    autoHeight: false,
  }

  render() {
    const {autoHide, autoHeight, children, className, maxHeight} = this.props

    return (
      <Scrollbars
        className={classnames('fancy-scroll--container', {
          [className]: className,
        })}
        autoHide={autoHide}
        autoHideTimeout={1000}
        autoHideDuration={250}
        autoHeight={autoHeight}
        autoHeightMax={maxHeight}
        renderTrackHorizontal={props =>
          <div {...props} className="fancy-scroll--track-h" />}
        renderTrackVertical={props =>
          <div {...props} className="fancy-scroll--track-v" />}
        renderThumbHorizontal={props =>
          <div {...props} className="fancy-scroll--thumb-h" />}
        renderThumbVertical={props =>
          <div {...props} className="fancy-scroll--thumb-v" />}
        renderView={props => <div {...props} className="fancy-scroll--view" />}
      >
        {children}
      </Scrollbars>
    )
  }
}

const {bool, node, number, string} = PropTypes

FancyScrollbar.propTypes = {
  children: node.isRequired,
  className: string,
  autoHide: bool,
  autoHeight: bool,
  maxHeight: number,
}

export default FancyScrollbar

import React, {Component, PropTypes} from 'react'
import classnames from 'classnames'
import {Scrollbars} from 'react-custom-scrollbars'

class FancyScrollbar extends Component {
  constructor(props) {
    super(props)
  }

  static defaultProps = {
    autoHide: true,
    autoHeight: false,
  }

  handleMakeDiv = className => props => {
    return <div {...props} className={`fancy-scroll--${className}`} />
  }

  render() {
    const {autoHide, autoHeight, children, className, maxHeight} = this.props

    return (
      <Scrollbars
        className={classnames('fancy-scroll--container', {
          [className]: className,
        })}
        autoHide={autoHide}
        autoHideTimeout={1000}
        autoHideDuration={250}
        autoHeight={autoHeight}
        autoHeightMax={maxHeight}
        renderTrackHorizontal={this.handleMakeDiv('track-h')}
        renderTrackVertical={this.handleMakeDiv('track-v')}
        renderThumbHorizontal={this.handleMakeDiv('thumb-h')}
        renderThumbVertical={this.handleMakeDiv('thumb-v')}
        renderView={this.handleMakeDiv('view')}
      >
        {children}
      </Scrollbars>
    )
  }
}

const {bool, node, number, string} = PropTypes

FancyScrollbar.propTypes = {
  children: node.isRequired,
  className: string,
  autoHide: bool,
  autoHeight: bool,
  maxHeight: number,
}

export default FancyScrollbar

Update FancyScrollBar to use arrow properties


// This is a manifest file that'll be compiled into including all the files listed below.
// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
// be included in the compiled file accessible from http://example.com/assets/application.js
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// the compiled file.
//
//= require jquery
//= require jquery_ujs
//= require_tree .

// This is a manifest file that'll be compiled into including all the files listed below.
// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
// be included in the compiled file accessible from http://example.com/assets/application.js
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// the compiled file.
//
//= require_tree .

Remove jQuery from the assets


"use strict"

const mocha = require('mocha');
const expect = require('chai').expect;
const fs = require('fs');
const mri = require('../src/mri');

describe("Detect CSharp functions", function(){
    it("should detect functions", function(){
        const expected = ["DiscoverTestsToExecute", "GetTestsThatCall", "GetTestsThatCall", "GetCallsInMethod", "GetTestMethodsInAssembly"];

        const code = fs.readFileSync('test/testRepo/aFile.cs').toString();
        const result = mri.getCSharpFunctionNamesFrom(code);

        expect(result).to.have.ordered.deep.members(expected);
    });
});

"use strict"

const mocha = require('mocha');
const expect = require('chai').expect;
const fs = require('fs');
const mri = require('../src/mri');

describe("Detect CSharp functions", function(){
    it("should detect functions", function(){
        this.timeout(5000);

        const expected = ["DiscoverTestsToExecute", "GetTestsThatCall", "GetTestsThatCall", "GetCallsInMethod", "GetTestMethodsInAssembly"];

        const code = fs.readFileSync('test/testRepo/aFile.cs').toString();
        const result = mri.getCSharpFunctionNamesFrom(code);

        expect(result).to.have.ordered.deep.members(expected);
    });
});

Add timeout to mri tests


'use strict';

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

var rule = require('../../../lib/rules/no-const-outside-module-scope');
var RuleTester = require('eslint').RuleTester;

//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------

var ruleTester = new RuleTester({
  parserOptions: {
    ecmaVersion: 6,
    sourceType: 'module'
  }
});

ruleTester.run('no-const-outside-module-scope', rule, {
  valid: [
    'const FOOBAR = 5;',
    'export const FOOBAR = 5;'
  ],

  invalid: [{
    code: '{ const FOOBAR = 5; }',
    output: '{ let FOOBAR = 5; }',
    errors: [{ message: '`const` should only be used in module scope (not inside functions/blocks).' }]
  }, {
    code: 'function foobar() { const FOOBAR = 5; return FOOBAR; }',
    output: 'function foobar() { let FOOBAR = 5; return FOOBAR; }',
    errors: [{ message: '`const` should only be used in module scope (not inside functions/blocks).' }]
  }]
});

Add output returned by the fixer


// (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP

import React, { Component } from 'react';
import Spinning from 'grommet/components/icons/Spinning';
import DocsArticle from '../../components/DocsArticle';
import Code from '../../components/Code';

export default class SpinningDoc extends Component {

  render () {
    return (
      <DocsArticle title='Spinning'>

An indeterminate spinning/busy icon. This should be used sparingly.
          If at all possible, use Meter with % to indicate progress. For content
          loading situations, Meter, Chart, and Distribution already have
          visuals for when the data has not arrived yet. In general,
          there should not be more than one Spinning icon on the screen at a
          time.

          <Spinning />

Usage

          <Code preamble={
            `import Spinning from 'grommet/components/Spinning';`}>
            <Spinning />

      </DocsArticle>
    );
  }
};

// (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP

import React, { Component } from 'react';
import Spinning from 'grommet/components/icons/Spinning';
import DocsArticle from '../../components/DocsArticle';
import Code from '../../components/Code';

export default class SpinningDoc extends Component {

  render () {
    return (
      <DocsArticle title='Spinning'>

An indeterminate spinning/busy icon. This should be used sparingly.
          If at all possible, use Meter with % to indicate progress. For content
          loading situations, Meter, Chart, and Distribution already have
          visuals for when the data has not arrived yet. In general,
          there should not be more than one Spinning icon on the screen at a
          time.

          <Spinning />

Usage

          <Code preamble={
            `import Spinning from 'grommet/components/icons/Spinning';`}>
            <Spinning />

      </DocsArticle>
    );
  }
};

Fix import path of Spinning usage


/**
 * This is the default (fallback) configuration file for the web application.
 */

// Create empty CONFIG object as fallback if it does not exist.
var CONFIG = CONFIG || {};

// Default configuration.
var DEFAULT_CONFIG = {};

/**
 * This is the default (fallback) configuration file for the web application.
 */

// Default configuration.
var DEFAULT_CONFIG = {};

Remove duplicate declaration of CONFIG.


module.exports = function (Como) {
    var models = {},
        // include underscore utility-belt
        _ = require('/lib/Underscore/underscore.min');

    _.each(Como.config.models, function (m) {
        var _m = require('/app/models/' + m.name),
            mName = m.name.toLowerCase();

        models[mName] = new _m(Como);
        if (m.truncate) { models[mName].truncate(); }
    });

    return models;
};

module.exports = function (Como) {
    var models = {},
        // include underscore utility-belt
        _ = require('/lib/Underscore/underscore.min');

    _.each(Como.config.models, function (m) {
        var _m = require('/app/models/' + m.name),
            mName = m.name.toLowerCase();

        models[mName] = new _m(Como);
        //if (m.truncate) { models[mName].truncate(); }
    });

    return models;
};

Truncate during app start causing error, disable it for now.


export default {
    name: 'app.session',

    initialize(container, application) {
        application.inject('controller', 'session', 'service:session');
        application.inject('route', 'session', 'service:session');
    }
};

export default {
    name: 'app.session',

    initialize(application) {
        application.inject('controller', 'session', 'service:session');
        application.inject('route', 'session', 'service:session');
    }
};

Fix a deprecation warning about `initialize` arguments


/**
 * Globals we expose on the window object for manual debugging with the Chrome
 * console.
 *
 * TODO: Consider disabling this for release builds.
 *
 * @flow
 */

import * as actions from './actions'
import store from './store'
import parseExpression from './parseExpression'

/**
 * Parse the given expression text and place the expression on the screen.
 */
const makeExpression = (exprString: string) => {
    const userExpr = parseExpression(exprString);
    const screenExpr = {
        expr: userExpr,
        x: 100,
        y: 100,
    };
    store.dispatch(actions.addExpression(screenExpr));
};

window.DebugGlobals = {
    store,
    actions,
    makeExpression,
};

/**
 * Globals we expose on the window object for manual debugging with the Chrome
 * console.
 *
 * @flow
 */

import {NativeModules} from 'react-native'

import * as actions from './actions'
import store from './store'
import parseExpression from './parseExpression'

/**
 * Parse the given expression text and place the expression on the screen.
 */
const makeExpression = (exprString: string) => {
    const userExpr = parseExpression(exprString);
    const screenExpr = {
        expr: userExpr,
        x: 100,
        y: 100,
    };
    store.dispatch(actions.addExpression(screenExpr));
    refreshNative();
};

// Only run when we're running in Chrome. We detect this by checking if we're in
// the debuggerWorker.js web worker, which defines a messageHandlers global.
if (__DEV__ && window.messageHandlers !== undefined) {
    window.DebugGlobals = {
        store,
        actions,
        makeExpression,
    };

    // Set a callback to be run regularly. This ensures that pending calls to
    // native code are flushed in a relatively timely manner, so we can run
    // things like redux actions from the Chrome console and have them take
    // effect immediately.
    setInterval(() => {}, 100);

    console.log(
        'Created debug globals. Access them on the DebugGlobals object.');
}

Update debug globals code to allow modifications from the Chrome console


function routerConfig ($stateProvider, $urlRouterProvider) {
  'ngInject';
  $stateProvider
    // Holding page
    .state('holding-page', {
      url: '/',
      views: {
        'content': {
          templateUrl: 'app/holding-page/holding-page.html'
        }
      }
    })

    // Home page
    .state('home', {
      url: '/home',
      views: {
        'content': {
          templateUrl: 'app/home/home.html',
          controller: 'HomeController',
          controllerAs: 'home'
        }
      }
    })

    // Projects
    .state('ambr', {
      url: '/ambr',
      views: {
        'content': {
          templateUrl: 'app/projects/ambr/ambr.html'
        }
      }
    })
    .state('layla-moran', {
      url: '/layla-moran',
      views: {
        'content': {
          templateUrl: 'app/projects/layla/layla.html'
        }
      }
    });

  $urlRouterProvider.otherwise('/');
}

export default routerConfig;

function routerConfig ($stateProvider, $urlRouterProvider) {
  'ngInject';
  $stateProvider
    // Holding page
    .state('holding-page', {
      url: '/',
      views: {
        'content': {
          templateUrl: 'app/holding-page/holding-page.html'
        }
      }
    })

    // Home page
    .state('home', {
      url: '/home',
      views: {
        'content': {
          templateUrl: 'app/home/home.html',
          controller: 'HomeController',
          controllerAs: 'home'
        }
      }
    })

    // Projects
    .state('eufn', {
      url: '/eufn',
      views: {
        'content': {
          templateUrl: 'app/projects/eufn/eufn.html'
        }
      }
    })
    .state('lancastrians', {
      url: '/lancastrians',
      views: {
        'content': {
          templateUrl: 'app/projects/lancastrians/lancastrians.html'
        }
      }
    })
    .state('layla-moran', {
      url: '/layla-moran',
      views: {
        'content': {
          templateUrl: 'app/projects/layla/layla.html'
        }
      }
    })
    .state('ambr', {
      url: '/ambr',
      views: {
        'content': {
          templateUrl: 'app/projects/ambr/ambr.html'
        }
      }
    });

  $urlRouterProvider.otherwise('/');
}

export default routerConfig;

Add routing for EUFN and Lancastrians projects


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

module.exports = persist;

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

/**
 * @param String db MongoDB connection string
 * @param Array data
 * @return promise
 * This module makes use of the node-promise API.
 * Operate on the singular result argument passed to a `then` callback, as follows:
 *
 * persist(db, data).then(function (result) {
 *   // Operate on the mongodb documents
 * }, function (error) {
 *   // Handle errors
 * })
 */
module.exports = persist;

Add a more useful comment


'use strict';

module.exports = function(prompts) {
  // This method will only show prompts that haven't been supplied as options. This makes the generator more composable.
  const filteredPrompts = [];
  const props = new Map();

  prompts.forEach(function prompts(prompt) {
    this.option(prompt.name);
    const option = this.options[prompt.name];

    if (option === undefined) {
      // No option supplied, user will be prompted
      filteredPrompts.push(prompt);
    } else {
      // Options supplied, add to props
      props[prompt.name] = option;
    }
  }, this);

  if (filteredPrompts.length) {
    return this.prompt(filteredPrompts).then(function mergeProps(mergeProps) {
      // Merge mergeProps into props/
      Object.assign(props, mergeProps);
      return props;
    });
  }

  // No prompting required call the callback right away.
  return Promise.resolve(props);
};

'use strict';

module.exports = function(prompts) {
  // This method will only show prompts that haven't been supplied as options. This makes the generator more composable.
  const filteredPrompts = [];
  const props = new Map();

  prompts.forEach(function prompts(prompt) {
    const option = this.options[prompt.name];

    if (option === undefined) {
      // No option supplied, user will be prompted
      filteredPrompts.push(prompt);
    } else {
      // Options supplied, add to props
      props[prompt.name] = option;
    }
  }, this);

  if (filteredPrompts.length) {
    return this.prompt(filteredPrompts).then(function mergeProps(mergeProps) {
      // Merge mergeProps into props/
      Object.assign(props, mergeProps);
      return props;
    });
  }

  // No prompting required call the callback right away.
  return Promise.resolve(props);
};

Fix to allow non boolean options