import { compile } from '../../emblem/template-compiler'

QUnit.module("template compiler");

QUnit.test("compiles text node AST", function(assert){
  var ast = {
    type: 'program',
    childNodes: [
      { type: 'text',
        content: 'hello world' }
    ]
  };

  var result = compile(ast);

  assert.equal(result, 'hello world', 'content is output');
});

QUnit.test("compiles element node AST", function(assert){
  var ast = {
    type: 'program',
    childNodes: [
      { type: 'element',
        tagName: 'div',
        attrStaches: [
          { type: 'attribute',
            name: 'class',
            content: 'red' }
        ] }
    ]
  };

  var result = compile(ast);

  assert.equal(result, '

', 'content is output');
});

QUnit.test("compiles block node AST", function(assert){
  var ast = {
    type: 'program',
    childNodes: [
      { type: 'block',
        content: 'each person in people',
        childNodes: [
          { type: 'element',
            tagName: 'div' }
        ] }
    ]
  };

  var result = compile(ast);

  assert.equal(result, '{{#each person in people}}

{{/each}}', 'content is output');
});

QUnit.test("compiles mustache node AST", function(assert){
  var ast = {
    type: 'program',
    childNodes: [
      { type: 'mustache',
        content: 'name' }
    ]
  };

  var result = compile(ast);

  assert.equal(result, '{{name}}', 'content is output');
});

QUnit.test("compiles mustaches in attr content AST", function(assert){
  var ast = {
    type: 'program',
    childNodes: [
      { type: 'element',
        tagName: 'div',
        attrStaches: [
          { type: 'mustache',
            content: 'bind-attr foo=baz' }
          { type: 'mustache',
            content: 'action "whammo"' }
        ] }
    ]
  };

  var result = compile(ast);

  assert.equal(result, '

', 'content is output');
});

Add tests for compiling mustache and block nodes


import angular from 'angular';
import mdfigcaption from 'mdfigcaption';
import deflist from 'markdown-it-deflist';
import attrs from 'markdown-it-attrs';
export default /*@ngInject*/class MarkdownProvider{
  constructor(markdownit){
    this.config = {
      preset: 'default',
      options: {
        html: true,
        typographer: true,
        quotes: '„“‚‘',
        breaks: true
      },
      plugins: [mdfigcaption, deflist,attrs]
    };
    this.markdownit = markdownit;
  }
  get preset(){
    return this.config.preset;
  }
  set preset(val){
    this.config.preset = val;
    return this;
  }
  get options(){
    return this.config.options;
  }
  set options(val){
    angular.extend(this.config.options, val);
    return this;
  }
  get plugins(){
    return this.config.plugins;
  }
  use(val){
    this.config.plugins.push(val);
    return this;
  }
  $get(){
    const md = this.markdownit(this.config.preset,this.config.options);
    angular.forEach(this.config.plugins,(plugin) => {
      md.use(plugin);
    });
    return md;
  }
}

import angular from 'angular';
import mdfigcaption from 'mdfigcaption';
import deflist from 'markdown-it-deflist';
import attrs from 'markdown-it-attrs';
import footnote from 'markdown-it-footnote';
export default /*@ngInject*/class MarkdownProvider{
  constructor(markdownit){
    this.config = {
      preset: 'default',
      options: {
        html: true,
        typographer: true,
        quotes: '„“‚‘',
        breaks: true
      },
      plugins: [mdfigcaption, deflist,attrs,footnote]
    };
    this.markdownit = markdownit;
  }
  get preset(){
    return this.config.preset;
  }
  set preset(val){
    this.config.preset = val;
    return this;
  }
  get options(){
    return this.config.options;
  }
  set options(val){
    angular.extend(this.config.options, val);
    return this;
  }
  get plugins(){
    return this.config.plugins;
  }
  use(val){
    this.config.plugins.push(val);
    return this;
  }
  $get(){
    const md = this.markdownit(this.config.preset,this.config.options);
    angular.forEach(this.config.plugins,(plugin) => {
      md.use(plugin);
    });
    return md;
  }
}

Add footnotes to markdown parser


(function(App) {

  'use strict';

  App.Controller = App.Controller || {};

  App.Controller.About = App.Controller.Page.extend({

    index: function() {
      new App.View.Anchors({});
      new App.View.StaffCategories();
      if(this.isScreen_s) {
        this.initSliders();
      } else {
        _.each($('.masonry-layout'), function(element) {
          if($('.masonry-layout').find('.masonry-column').length === 0) {
            new App.View.Masonry({
              el: element
            });
          }
        });
      }
    },

    initSliders: function() {
      _.each($('.js_slider'), function(element) {
        if($(element).find(".js_slide").length > 0) {
          lory(element, {
            enableMouseEvents: true
          });
        }
      });
    }

  });

})(this.App);

(function(App) {

  'use strict';

  App.Controller = App.Controller || {};

  App.Controller.About = App.Controller.Page.extend({

    index: function() {
      new App.View.Anchors({});
      new App.View.StaffCategories();
      if(this.isScreen_s) {
        this.initSliders();
      } else {
        _.each($('.masonry-layout'), function(element) {
          if($(element).find('.masonry-column').length === 0) {
            new App.View.Masonry({
              el: element
            });
          }
        });
      }
    },

    initSliders: function() {
      _.each($('.js_slider'), function(element) {
        if($(element).find(".js_slide").length > 0) {
          lory(element, {
            enableMouseEvents: true
          });
        }
      });
    }

  });

})(this.App);

Fix masonry bulk loader in the about page


import Ember from 'ember';
import ValidatorsMessages from 'ember-cp-validations/validators/messages';

const { Logger:logger } = Ember;

export default function() {
  ValidatorsMessages.reopen({
    intl: Ember.inject.service(),
    prefix: 'errors',

    getDescriptionFor(attribute, context = {}) {
      const key = `${this.get('prefix')}.description`;
      const intl = this.get('intl');

      if (intl && intl.exists(key)) {
        return intl.t(key, context);
      }

      return this._super(...arguments);
    },

    getMessageFor(type, context = {}) {
      const key = `${this.get('prefix')}.${type}`;
      const intl = this.get('intl');

      if (intl && intl.exists(key)) {
        return this.formatMessage(intl.t(key, context));
      }

      logger.warn(`[ember-intl-cp-validations] Missing translation for validation key: ${key}\nhttp://offirgolan.github.io/ember-cp-validations/docs/messages/index.html`);

      return this._super(...arguments);
    }
  });
}

import Ember from 'ember';
import ValidatorsMessages from 'ember-cp-validations/validators/messages';

const { Logger:logger, get, isEmpty } = Ember;

export default function() {
  ValidatorsMessages.reopen({
    intl: Ember.inject.service(),
    prefix: 'errors',

    getDescriptionFor(attribute, options = {}) {
      const intl = get(this, 'intl');
      let key = `${get(this, 'prefix')}.description`;
      let foundCustom;

      if (!isEmpty(options.descriptionKey)) {
        key = options.descriptionKey;
        foundCustom = true;
      } else if (!isEmpty(options.description)) {
        return options.description;
      }

      if (intl) {
        if (intl.exists(key)) {
          return intl.t(key, options);
        } else if (foundCustom) {
          logger.warn(`Custom descriptionKey '${key}' provided but does not exist in intl translations.`);
        }
      }

      return this._super(...arguments);
    },

    getMessageFor(type, options = {}) {
      const key = `${get(this, 'prefix')}.${type}`;
      const intl = get(this, 'intl');

      if (intl && intl.exists(key)) {
        return this.formatMessage(intl.t(key, options));
      }

      logger.warn(`[ember-intl-cp-validations] Missing translation for validation key: ${key}\nhttp://offirgolan.github.io/ember-cp-validations/docs/messages/index.html`);

      return this._super(...arguments);
    }
  });
}

Add support for providing a custom description key


'use strict';

angular.module('occupied.filters', ['occupied.services'])

    .filter('interpolate', ['$filter', 'basePopulation', function($filter, basePopulation) {
        return function(text, city) {
            text = text.replace(new RegExp('{city}', 'g'), city.name);

            text = text.replace(new RegExp('\\{population\\|([0-9]+)\\}', 'g'), function(match, capture) {
                return $filter('number')(parseInt(capture) * city.population / basePopulation, 0);
            });

            text = text.replace(new RegExp('\\{year\\|([0-9]+)\\}', 'g'), function(match, capture) {
                return parseInt(capture) + (new Date()).getFullYear() - 1946;
            });

            return text;
        };
    }]);

'use strict';

angular.module('occupied.filters', ['occupied.services'])

    /**
     *  Filter to insert adjusted/ filled-in values for {city}, {population|...}, {area|...}, {year|...}.
     *
     *  Population must be an integer with no commas.
     *  Area must be an int or float with no commas.
     *  Area will have unit (km^2) appended to the number.
     */
    .filter('interpolate', ['$filter', 'basePopulation', 'baseArea', function($filter, basePopulation, baseArea) {
        return function(text, city) {
            text = text.replace(new RegExp('{city}', 'g'), city.name);

            text = text.replace(new RegExp('\\{population\\|([0-9]+)\\}', 'g'), function(match, capture) {
                return $filter('number')(parseInt(capture) * city.population / basePopulation, 0);
            });

            text = text.replace(new RegExp('\\{area\\|([0-9.]+)\\}', 'g'), function(match, capture) {
                return $filter('number')(parseFloat(capture) * city.area / baseArea, 1) + ' km²';
            });

            text = text.replace(new RegExp('\\{year\\|([0-9]+)\\}', 'g'), function(match, capture) {
                return parseInt(capture) + (new Date()).getFullYear() - 1946;
            });

            return text;
        };
    }]);

Make interpolate filter support {area|...} tags and briefly document what it does


import React from 'react';

const VideoDetail = (props) => {
	return (

	);
};

export VideoDetail;

Add markup for video detail.


var gulp = require('gulp');
var config = require('../config');

var plumber = require('gulp-plumber');
var jscs = require('gulp-jscs');
var jshint = require('gulp-jshint');
var stylish = require('jshint-stylish');
var handleError = require('../util/handle-errors');

gulp.task('jshint', function() {
  var allJavascript = Array.prototype.concat(config.source, config.gulp, config.tests);

  return gulp.src(allJavascript)
    .pipe(plumber())
    .pipe(jshint())
    .pipe(jshint.reporter(stylish))
    .on('error', handleError);
});

gulp.task('jscs', function () {
  var allJavascript = Array.prototype.concat(config.source, config.gulp, config.tests);

  return gulp.src(allJavascript)
    .pipe(plumber())
    .pipe(jscs())
    .on('error', handleError);
});

gulp.task('lint', ['jshint', 'jscs']);

var gulp = require('gulp'),
    config = require('../config'),

    plumber = require('gulp-plumber'),
    jscs = require('gulp-jscs'),
    jshint = require('gulp-jshint'),
    stylish = require('jshint-stylish'),
    handleError = require('../util/handle-errors');

gulp.task('jshint', function() {
  var allJavascript = Array.prototype.concat(config.source, config.gulp, config.tests);

  return gulp.src(allJavascript)
    .pipe(plumber())
    .pipe(jshint())
    .pipe(jshint.reporter(stylish))
    .on('error', handleError);
});

gulp.task('jscs', function () {
  var allJavascript = Array.prototype.concat(config.source, config.gulp, config.tests);

  return gulp.src(allJavascript)
    .pipe(plumber())
    .pipe(jscs())
    .on('error', handleError);
});

gulp.task('lint', ['jshint', 'jscs']);

Use single var for requires


var cheerio = require('cheerio'),
	request = require('request'),
	fs = require('fs');

var savePath = process.argv[2];

if(typeof savePath === 'undefined') {
	console.log("Error: Save path not defined.");
	return;
}

var year = "2013";
var baseUri = 'http://www.facets.la/';

(function getImg(id) {
	var uri = baseUri + year + '/' + id + '/';

	request({ uri: uri }, function(error, response, body) {
		if(id == 330) year = "2014";

		var $ = cheerio.load(body);
		var imgDiv = $('#facet-image').children()['0'];

		if(typeof imgDiv !== 'undefined') {
			request(imgDiv.attribs.src).pipe(fs.createWriteStream(savePath + id + '.jpg'));
		}

		getImg(id+1);
	});
})(1);

var cheerio = require('cheerio'),
	request = require('request'),
	fs = require('fs');

var savePath = process.argv[2];

if(typeof savePath === 'undefined') {
	console.log("Error: Save path not defined.");
	return;
}

var year = "2013";
var baseUri = 'http://www.facets.la/';

(function getImg(id) {
	var uri = baseUri + year + '/' + id + '/';

	request({ uri: uri }, function(error, response, body) {
		if(id == 330) year = "2014";

		var $ = cheerio.load(body);
		var imgDiv = $('#facet-image').children()['0'];

		if(typeof imgDiv !== 'undefined') {
			request(imgDiv.attribs.src).pipe(fs.createWriteStream(savePath + id + '.jpg'));
		}
	});

	if(id <= 365)
		getImg(id+1);
})(1);

Improve download speed by using non-blocking. Also adding upper limit 365.


$(document).ready(function() {

    function displaySellermaniaCredentials()
    {
        if ($('#sm_import_orders_yes').attr('checked') == 'checked')
            $('#sm_import_orders_credentials').fadeIn();
        else
            $('#sm_import_orders_credentials').fadeOut();
        return true;
    }

    $('#sm_import_orders_yes').click(function() { return displaySellermaniaCredentials(); });
    $('#sm_import_orders_no').click(function() { return displaySellermaniaCredentials(); });
    displaySellermaniaCredentials();
});

$(document).ready(function() {

    function displaySellermaniaCredentials()
    {
        if ($('#sm_import_orders_yes').attr('checked') == 'checked' || $('#sm_import_orders_yes').attr('checked') == true)
            $('#sm_import_orders_credentials').fadeIn();
        else
            $('#sm_import_orders_credentials').fadeOut();
        return true;
    }

    $('#sm_import_orders_yes').click(function() { return displaySellermaniaCredentials(); });
    $('#sm_import_orders_no').click(function() { return displaySellermaniaCredentials(); });
    displaySellermaniaCredentials();
});

Fix JS compatibility for PS 1.4


import { moduleForComponent, test } from 'ember-qunit'
import hbs from 'htmlbars-inline-precompile'

moduleForComponent('svg-icon', 'Integration | Component | svg icon', {
  integration: true
})

const icons = [
  'arrow-left',
  'arrow-right',
  'bubble',
  'check',
  'ellipsis-horz',
  'menu',
  'paper-plane',
  'pencil',
  'reply',
  'thumbs-up',
  'times',
  'cog',
  'envelope',
  'lifebuoy',
  'paper-stack',
  'clipboard-check',
  'lock',
  'lock-solid'
]

const testSVG = icon => {
  test(`'${icon}' renders and has class`, function(assert) {
    assert.expect(2)

    this.set('icon', icon)
    this.render(hbs`{{svg-icon icon=icon}}`)

    assert.ok(this.$('svg path'), '<path> renders')
    assert.ok(this.$('svg').hasClass(`svg-icon-${icon}`), 'has class')
  })
}

const runTests = () => {
  icons.forEach(icon => {
    testSVG(icon)
  })
}

runTests()

import { moduleForComponent, test } from 'ember-qunit'
import hbs from 'htmlbars-inline-precompile'

moduleForComponent('svg-icon', 'Integration | Component | svg icon', {
  integration: true
})

const icons = [
  'arrow-left',
  'arrow-right',
  'bubble',
  'check',
  'checkmark-circle',
  'clipboard-check',
  'cog',
  'ellipsis-horz',
  'envelope',
  'lifebuoy',
  'lock-solid',
  'lock',
  'menu',
  'paper-plane',
  'paper-stack',
  'pencil',
  'reply',
  'sync',
  'thumbs-up',
  'times'
]

const testSVG = icon => {
  test(`'${icon}' renders and has class`, function(assert) {
    assert.expect(2)

    this.set('icon', icon)
    this.render(hbs`{{svg-icon icon=icon}}`)

    assert.ok(this.$('svg path'), '<path> renders')
    assert.ok(this.$('svg').hasClass(`svg-icon-${icon}`), 'has class')
  })
}

const runTests = () => {
  icons.forEach(icon => {
    testSVG(icon)
  })
}

runTests()

Add new icons to tests


'use strict';

const _ = require('lodash');
const { isSortable } = require('./attributes');

const getDefaultMainField = schema => {
  if (schema.modelType == 'group') {
    // find first group attribute that is sortable
    return (
      Object.keys(schema.attributes).find(key => isSortable(schema, key)) ||
      'id'
    );
  }

  return 'id';
};

/**
 * Retunrs a configuration default settings
 */
async function createDefaultSettings(schema) {
  const generalSettings = await strapi.plugins[
    'content-manager'
  ].services.generalsettings.getGeneralSettings();

  let defaultField = getDefaultMainField(schema);

  return {
    ...generalSettings,
    mainField: defaultField,
    defaultSortBy: defaultField,
    defaultSortOrder: 'ASC',
    ..._.pick(_.get(schema, ['config', 'settings'], {}), [
      'searchable',
      'filterable',
      'bulkable',
      'pageSize',
      'mainField',
      'defaultSortBy',
      'defaultSortOrder',
    ]),
  };
}

/** Synchronisation functions */

async function syncSettings(configuration, schema) {
  if (_.isEmpty(configuration.settings)) return createDefaultSettings(schema);

  let defaultField = getDefaultMainField(schema);

  const { mainField = defaultField, defaultSortBy = defaultField } =
    configuration.settings || {};

  return {
    ...configuration.settings,
    mainField: isSortable(schema, mainField) ? mainField : defaultField,
    defaultSortBy: isSortable(schema, defaultSortBy)
      ? defaultSortBy
      : defaultField,
  };
}

module.exports = {
  createDefaultSettings,
  syncSettings,
};

'use strict';

const _ = require('lodash');
const { isSortable } = require('./attributes');

const getDefaultMainField = schema =>
  Object.keys(schema.attributes).find(
    key => schema.attributes[key].type === 'string'
  ) || 'id';

/**
 * Retunrs a configuration default settings
 */
async function createDefaultSettings(schema) {
  const generalSettings = await strapi.plugins[
    'content-manager'
  ].services.generalsettings.getGeneralSettings();

  let defaultField = getDefaultMainField(schema);

  return {
    ...generalSettings,
    mainField: defaultField,
    defaultSortBy: defaultField,
    defaultSortOrder: 'ASC',
    ..._.pick(_.get(schema, ['config', 'settings'], {}), [
      'searchable',
      'filterable',
      'bulkable',
      'pageSize',
      'mainField',
      'defaultSortBy',
      'defaultSortOrder',
    ]),
  };
}

/** Synchronisation functions */

async function syncSettings(configuration, schema) {
  if (_.isEmpty(configuration.settings)) return createDefaultSettings(schema);

  let defaultField = getDefaultMainField(schema);

  const { mainField = defaultField, defaultSortBy = defaultField } =
    configuration.settings || {};

  return {
    ...configuration.settings,
    mainField: isSortable(schema, mainField) ? mainField : defaultField,
    defaultSortBy: isSortable(schema, defaultSortBy)
      ? defaultSortBy
      : defaultField,
  };
}

module.exports = {
  createDefaultSettings,
  syncSettings,
};

Set content type mainField to be a string if possible else id


/* @flow */

import React, { Component, PropTypes } from 'react';
import Connect from '../../../modules/store/Connect';
import Dummy from '../views/Dummy';

const ChatSuggestionsContainerInner = Connect(({ prefix, user }) => ({
	users: {
		key: {
			slice: {
				type: 'user',
				filter: {
					prefix
				},
				order: 'id'
			},
			// REVIEW: verify the range schema works
			range: {
				start: -Infinity,
				before: 5,
				after: 0,
			}
		},
		transform: results => results.filter(item => item.id !== user)
	}
}))(Dummy);

ChatSuggestionsContainerInner.propTypes = {
	prefix: PropTypes.string.isRequired,
	user: PropTypes.string.isRequired
};

export default class ChatSuggestionsContainer extends Component {
	state: { prefix: string } = {
		prefix: '',
	};

	_getMatchingUsers: Function = (prefix: string) => {
		this.setState({
			prefix: prefix.trim()
		});
	};

	render() {
		return (
			<ChatSuggestionsContainerInner
				{...this.props}
				{...this.state}
				getMatchingUsers={this._getMatchingUsers}
			/>
		);
	}
}

Add container for user suggestions in chat


(function($){
	var $bar;
	window.xeNotifyMessage = function(text, count){
		$bar = $('div.message.info');
		if(!$bar.length) {
			$bar = $('
')
				.hide()
				.css({
					'position'   : 'absolute',
					'z-index' : '100',
				})
				.prependTo(document.body);
		}

		text = text.replace('%d', count);
		$bar.html('
'+text+'
').height();
		$bar.show().animate({top:0});
	};

	window.xeNotifyMessageClose = function(){
		setTimeout(function(){
			$bar.slideUp();
		}, 2000);
	};
})(jQuery);

(function($){
	var $bar;
	window.xeNotifyMessage = function(text, count){
		$bar = $('div.message.info');
		if(!$bar.length) {
			$bar = jQuery('

').hide().css({
				'position' : 'absolute',
				'opacity' : 0.7,
				'z-index' : 10000,
			}).appendTo(document.body);
		}

		text = text.replace('%d', count);
		var link = jQuery('');
		link.attr("href", current_url.setQuery('module','').setQuery('act','dispCommunicationNewMessage'));
		//link.attr("onclick", "popopen(this.href, 'popup');xeNotifyMessageClose(); return false;");
		link.text(text);
		var para = jQuery('

');
		para.append(link).appendTo($bar);
		$bar.show().animate({top:0});
	};

	window.xeNotifyMessageClose = function(){
		setTimeout(function(){
			$bar.slideUp();
		}, 2000);
	};
})(jQuery);

Fix styles of new message notification


'use strict';

const should = require('should');

const refResolver = require('./../../lib/ref_resolver');

const schema = require('./schema.json');

describe('RefResolver', () => {

  it('should resolve refs in schema synchronous', () => {
    const resolvedSchema = refResolver(schema);

    should(resolvedSchema.string).eql('string');
  });

});

'use strict';

const should = require('should');

const refResolver = require('./../../lib/ref_resolver');

const schema = require('./schema.json');

describe('RefResolver', () => {

  it('should resolve refs in schema synchronous', () => {
    const resolvedSchema = refResolver(schema);

    should(resolvedSchema.string)
      .eql('string');
  });

});

Add sync version of json ref resolver


import { connect } from 'react-redux';
import Matches from '../components/Matches.jsx';
import { unmountMatch, removeMatch, incrementOffset, addMatches } from '../actions/index.js';
import request from 'then-request';

const mapStateToProps = (state) => (
  {
    matches: state.matches.values,
    offset: state.matches.offset,
    self: state.profile,
  }
);

const mapDispatchToProps = (dispatch) => (
  {
    onConnectClick: (id) => {
      dispatch(unmountMatch(id));
      setTimeout(
        () => {
          dispatch(removeMatch(id));
        },
        120
      );
    },
    onHideClick: (id) => {
      dispatch(unmountMatch(id));
      setTimeout(
        () => {
          dispatch(removeMatch(id));
        },
        120
      );
    },
    onLoadMoreClick: (self, offset) => {
      //use the offset to get additional matches from db, then
      request('GET', '/api/matches', {
        qs: {
          self,
          offset,
        },
      })
      .done((matches) => {

        dispatch(addMatches([{firstName: 'Velvet', lastName: 'Underground', languages: { canTeach: [['English', 'native']], willLearn: [['German', 'advanced']] }, description: 'hi there', photoUrl: 'http://watchmojo.com/uploads/blipthumbs/M-RR-Top10-Velvet-Underground-Songs-480p30_480.jpg'}]));
        dispatch(incrementOffset(20));
      });
    },
  }
);

export default connect(mapStateToProps, mapDispatchToProps)(Matches);

import { connect } from 'react-redux';
import Matches from '../components/Matches.jsx';
import { unmountMatch, removeMatch, incrementOffset, addMatches } from '../actions/index.js';
import request from 'then-request';

const mapStateToProps = (state) => (
  {
    matches: state.matches.values,
    offset: state.matches.offset,
    self: state.profile,
  }
);

const mapDispatchToProps = (dispatch) => (
  {
    onConnectClick: (id) => {
      dispatch(unmountMatch(id));
      setTimeout(
        () => {
          dispatch(removeMatch(id));
        },
        120
      );
    },
    onHideClick: (id) => {
      dispatch(unmountMatch(id));
      setTimeout(
        () => {
          dispatch(removeMatch(id));
        },
        120
      );
    },
    onLoadMoreClick: (self, offset) => {
      //use the offset to get additional matches from db, then
      request('GET', '/api/matches', {
        qs: {
          self,
          offset,
        },
      })
      .done((matches) => {
        if (matches.statusCode === 200) {
          dispatch(addMatches(JSON.parse(matches.body)));
          dispatch(incrementOffset(20));
        } else {
          // TODO: handle error
        }
      });
    },
  }
);

export default connect(mapStateToProps, mapDispatchToProps)(Matches);

Add results from load more to matches prop


function Network() {
    function sendGameRequest(numPlayers) {
        var message = {"NumPlayers": numPlayers}
        sendMessage(GAMEREQUEST, message)
    }

    GAMEREQUEST = "newGame";

    host = window.location.host.split(":")[0];
    port = "8888"
    ws_addr = "ws://" + host + ":" + port + "/ws"
    conn = new WebSocket(ws_addr);
    DELIM = ":"

    // This function expects the message as a javascript object
    function sendMessage(command, messageObj) {
        var jsonified = JSON.stringify(messageObj)
        packet = command + DELIM + jsonified;
        conn.send(packet)
    }

    // When the connection is open, send some data to the server
    conn.onopen = function () {
        // eventually send some login info
    };

    // Log errors
    conn.onerror = function (error) {
        console.log('WebSocket Error ' + error);
    };

    // Log messages from the server
    conn.onmessage = function (e) {
        console.log('Server: ' + e.data);
    };

    this.sendGameRequest = function(numPlayers) {
        sendGameRequest(numPlayers)
    }
}

function Network() {
    function sendGameRequest(numPlayers) {
        var message = {"NumPlayers": numPlayers}
        sendMessage(NEW_GAME_CMD, message)
    }

    function sendClientInfo(playerId) {
        var message = {"Id": playerId}
        sendMessage(CLIENT_INFO_CMD, message);
    }

    CLIENT_INFO_CMD = "clientInfo";
    NEW_GAME_CMD = "newGame";

    host = window.location.host.split(":")[0];
    port = "8888"
    ws_addr = "ws://" + host + ":" + port + "/ws"
    conn = new WebSocket(ws_addr);
    DELIM = ":"

    // This function expects the message as a javascript object
    function sendMessage(command, messageObj) {
        var jsonified = JSON.stringify(messageObj)
        packet = command + DELIM + jsonified;
        conn.send(packet)
    }

    // When the connection is open, send some data to the server
    conn.onopen = function () {
        network.sendClientInfo(getPlayerId());
    };

    // Log errors
    conn.onerror = function (error) {
        console.log('WebSocket Error ' + error);
    };

    // Log messages from the server
    conn.onmessage = function (e) {
        console.log('Server: ' + e.data);
    };

    this.sendGameRequest = function(numPlayers) {
        sendGameRequest(numPlayers);
    }

    this.sendClientInfo = function(playerId) {
        sendClientInfo(playerId);
    }
}

Send player login info on websocket connection


/* eslint-disable prefer-arrow-callback */
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';

import { Affiliations } from '../affiliations.js';

Meteor.publish('affiliations.byParent', function affiliationsbyEvent(id) {
  check(id, String);

  return Affiliations.find({ parentId: id }, {
    fields: Affiliations.publicFields,
    limit: 25,
  });
});

Meteor.publish('affiliations.byProfile', function affiliationsbyProfile(id) {
  check(id, String);

  return Affiliations.find({ 'profile._id': id }, {
    fields: Affiliations.publicFields,
    limit: 25,
  });
});

Meteor.publish('affiliations.anyById', function affiliationsbyProfile(id) {
  check(id, String);

  return Affiliations.find(
    {
      $or: [
        { 'profile._id': id },
        { parentId: id },
      ],
    }, {
      fields: Affiliations.publicFields,
      limit: 25,
    }
  );
});

/* eslint-disable prefer-arrow-callback */
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';

import { Affiliations } from '../affiliations.js';

Meteor.publish('affiliations.byParent', function affiliationsbyEvent(id) {
  check(id, String);

  return Affiliations.find({ parentId: id }, {
    fields: Affiliations.publicFields,
    limit: 100,
  });
});

Meteor.publish('affiliations.byProfile', function affiliationsbyProfile(id) {
  check(id, String);

  return Affiliations.find({ 'profile._id': id }, {
    fields: Affiliations.publicFields,
    limit: 100,
  });
});

Meteor.publish('affiliations.anyById', function affiliationsbyProfile(id) {
  check(id, String);

  return Affiliations.find(
    {
      $or: [
        { 'profile._id': id },
        { parentId: id },
      ],
    }, {
      fields: Affiliations.publicFields,
      limit: 100,
    }
  );
});

Increase limit for display of affiliations.


var BaseCollection = require('./data-observatory-base-collection');
var BaseModel = require('../../components/custom-list/custom-list-item-model');

var REGIONS_QUERY = "SELECT count(*) num_measurements, tag.key region_id, tag.value region_name FROM (SELECT * FROM OBS_GetAvailableNumerators((SELECT ST_SetSRID(ST_Extent(the_geom), 4326) FROM ({{{ query }}}) q)) WHERE jsonb_pretty(numer_tags) LIKE '%subsection/%') numers,  Jsonb_Each(numers.numer_tags) tag WHERE tag.key like 'section%' GROUP BY tag.key, tag.value";

module.exports = BaseCollection.extend({
  buildQuery: function () {
    return REGIONS_QUERY;
  },

  model: function (attrs, opts) {
    // label and val to custom list compatibility
    var o = {};
    o.val = attrs.region_id;
    o.label = attrs.region_name.replace(/["]+/g, '');

    o.renderOptions = {
      measurements: attrs.num_measurements
    };

    return new BaseModel(o);
  }
});

var BaseCollection = require('./data-observatory-base-collection');
var BaseModel = require('../../components/custom-list/custom-list-item-model');

var REGIONS_QUERY = "SELECT count(*) num_measurements, tag.key region_id, tag.value region_name FROM (SELECT * FROM OBS_GetAvailableNumerators() WHERE jsonb_pretty(numer_tags) LIKE '%subsection/%') numers,  Jsonb_Each(numers.numer_tags) tag WHERE tag.key like 'section%' GROUP BY tag.key, tag.value ORDER BY region_name";

module.exports = BaseCollection.extend({
  buildQuery: function () {
    return REGIONS_QUERY;
  },

  model: function (attrs, opts) {
    // label and val to custom list compatibility
    var o = {};
    o.val = attrs.region_id;
    o.label = attrs.region_name.replace(/["]+/g, '');

    o.renderOptions = {
      measurements: attrs.num_measurements
    };

    return new BaseModel(o);
  }
});

Remove the extent in the regions query for DO to get all the available regions without filtering


var $ = django.jQuery;

$(function () {
    var $prettifyBtn,
        $previewBtn,
        $previewRow,
        $queryRow = $('.form-row.field-query'),
        $queryField = $('#id_query');

    // Create a wrapping
 for the button, and add an empty label
    // to align the button witth the text input field.
    $previewRow = $('

');
    $queryRow.append($previewRow);

    // Create a prettify button
    $prettifyBtn = $('<button/>', {
        // Translators: "Prettify" means formatting the JSON, fixing alignment issues.
        text: gettext('Prettify'),
        click: function (e) {
            var query = $queryField.val();
            e.preventDefault();

            if (query) {
                query = JSON.stringify(JSON.parse(query), null, 2);
                $queryField.val(query);
            }
        }
    });

    $previewRow.append($prettifyBtn);

    // Create a preview button
    $previewBtn = $('<button/>', {
        text: gettext('Preview'),
        click: function (e) {
            var url,
                query = $queryField.val();
            e.preventDefault();

            if (query) {
                // URL encode
                query = encodeURIComponent(query);

                url = '/api/v1/courses/?q=' + query;
                window.open(url, 'catalog_preview');
            }
        }
    });

    $previewRow.append($previewBtn);
});

var $ = django.jQuery;

$(function () {
    var $previewBtn,
        $previewRow,
        $queryRow = $('.form-row.field-query'),
        $queryField = $('#id_query');

    // Create a wrapping
 for the button, and add an empty label
    // to align the button witth the text input field.
    $previewRow = $('

');
    $queryRow.append($previewRow);

    // Create a preview button
    $previewBtn = $('<button/>', {
        text: gettext('Preview'),
        click: function (e) {
            var url,
                query = $queryField.val();
            e.preventDefault();

            if (query) {
                // URL encode
                query = encodeURIComponent(query);

                url = '/api/v1/courses/?q=' + query;
                window.open(url, 'catalog_preview');
            }
        }
    });

    $previewRow.append($previewBtn);
});

Remove broken 'prettify' button for catalog queries.


var mongoose     = require('mongoose');
var Table        = require('./Table');
var TableSchema  = Table.schema;
var Schema       = mongoose.Schema;

var handleError = function(err){
    if (err){
      console.log(err);
      return;
    }
}

var TableQueueSchema   = new Schema({
    queue : {type: [TableSchema], default: []}
});

TableQueueSchema.methods.addTable = function(tableID, status) {
  var queue = this.queue;
  var query = Table.findOne({'id': tableID, 'status':status}, 'index', function(err, result){
    if (err){
      console.log(err);
      return;
    }
    if(!result){
      var table = new Table({id:tableID, index: queue.length, status: status})
      table.save(handleError);
      queue.push(table);
    }
  });
}

TableQueueSchema.methods.removeTable = function(tableID) {
  var index;
  var query = Table.findOne({'id': tableID}, 'index', function(err, table){
    if (err){
      console.log(err);
      return;
    }
    index = table.index;
    table.remove();
  });
  this.queue.splice(index, 1);
}

module.exports = mongoose.model('TableQueue', TableQueueSchema);

var mongoose     = require('mongoose');
var Table        = require('./Table');
var TableSchema  = Table.schema;
var Schema       = mongoose.Schema;

var handleError = function(err){
    if (err){
      console.log(err);
      return;
    }
}

var TableQueueSchema   = new Schema({
    queue : {type: [TableSchema], default: []}
});

TableQueueSchema.methods.addTable = function(tableID, status) {
  var queue = this.queue;
  var query = Table.findOne({'id': tableID, 'status':status}, 'index', function(err, result){
    if (err){
      console.log(err);
      return;
    }
    if(!result){
      var table = new Table({id:tableID, index: queue.length, status: status})
      table.save(handleError);
      queue.push(table);
    }
  });
}

TableQueueSchema.methods.removeTable = function(tableID) {
  var index;
  var query = Table.findOne({'id': tableID}, 'index', function(err, table){
    if (err){
      console.log(err);
      return;
    }
    if(table){
      index = table.index;
      table.remove();
    }
  });
  this.queue.splice(index, 1);
}

module.exports = mongoose.model('TableQueue', TableQueueSchema);

Check for existence before removal


$(function(){
  $('.step-section').readmore({
    collapsedHeight: 300,
    heightMargin: 300,
    speed: 250,
    moreLink: '',
    lessLink: '',

    blockCSS: 'display: block;', // override default CSS, which has width settings that conflict with Foundation

    // seems like these classes should be added by the library, but they're not...
    beforeToggle: function(trigger, element, expanded) {
      if (!expanded) { // The "open" link was clicked
        $(element).addClass('readmore-expanded');
        $(element).removeClass('readmore-collapsed');
      } else if (expanded) { // The "close" link was clicked
        $(element).addClass('readmore-collapsed');
        $(element).removeClass('readmore-expanded');
      }
    },

    afterToggle: function(trigger, element, expanded) {
      if(! expanded) { // The "Close" link was clicked
        $('html, body').animate( { scrollTop: element.offset().top }, {duration: 250 } );
      }
    },
  });
});

$(function(){
  $('.step-section').readmore({
    collapsedHeight: 300,
    heightMargin: 300,
    speed: 250,
    moreLink: '',
    lessLink: '',

    blockCSS: 'display: block;', // override default CSS, which has width settings that conflict with Foundation

    // seems like these classes should be added by the library, but they're not...
    beforeToggle: function(trigger, element, expanded) {
      if (!expanded) { // The "open" link was clicked
        $(element).addClass('readmore-expanded');
        $(element).removeClass('readmore-collapsed');
      } else if (expanded) { // The "close" link was clicked
        $(element).addClass('readmore-collapsed');
        $(element).removeClass('readmore-expanded');
      }
    },
  });
});

Remove readmore after-collapse scroll effect


var sinon = require('sinon');
var chai = require('chai');

var should = chai.should();
chai.Assertion.includeStack = true;
chai.use(require('sinon-chai'));

var nc = require('../../..');
nc.requireNative = require;

var requireComponent = nc.requireComponent;

require('sinon-doublist').sinonDoublist(sinon, 'mocha');

describe('nc', function() {
  'use strict';

  describe('NodeConsole', function() {
    beforeEach(function() {
      this.nc = nc.create();
    });

    it('should do something', function() {
      console.log('\x1B[33m<---------- INCOMPLETE'); // TODO
    });
  });
});

var sinon = require('sinon');
var chai = require('chai');

var should = chai.should();
chai.Assertion.includeStack = true;
chai.use(require('sinon-chai'));

var nc = require('../../..');
nc.requireNative = require;

var requireComponent = nc.requireComponent;

require('sinon-doublist')(sinon, 'mocha');

describe('nc', function() {
  'use strict';

  describe('NodeConsole', function() {
    beforeEach(function() {
      this.nc = nc.create();
    });

    it('should do something', function() {
      console.log('\x1B[33m<---------- INCOMPLETE'); // TODO
    });
  });
});

Update use to latest API


'use strict';

/**
 * @module manage-code-project/handler/removeUser
 */

 /**
  * Allows user to remove a member from a code project
  * @param {Request} request - Hapi request
  * @param {Reply} reply - Hapi Reply
  * @returns {Null} responds with HTML page
  */
function chooseUserToRemove (request, reply) {
    reply.view('modules/manage-code-project/view/remove-user');
}

module.exports = chooseUserToRemove;

'use strict';

/**
 * @module manage-code-project/handler/remove-user
 */

 /**
  * Allows user to remove a member from a code project
  * @param {Request} request - Hapi request
  * @param {Reply} reply - Hapi Reply
  * @returns {Null} responds with HTML page
  */
function chooseUserToRemove (request, reply) {
    reply.view('modules/manage-code-project/view/remove-user');
}

module.exports = chooseUserToRemove;

Update remove user for manage code project


import React, {PureComponent} from 'react';
import classNames from 'classnames';
import {isPast, parse} from 'date-fns';

import {formatDate, generateEventJSONLD} from './utils';
import Heading from '../Heading';
import Link from '../Link';
import styles from './ConferenceItem.scss';

export default class ConferenceItem extends PureComponent {
  render() {
    const {
      name,
      url,
      city,
      country,
      startDate,
      endDate,
      twitter,
    } = this.props;

    return (

Refactor how location is shown


import React, {Component} from 'react'

class Login extends Component {
  constructor(props) {
    super();
    this.state = {
      email: '',
      password: ''
    }
    this.handleChange = this.handleChange.bind(this)
    this.handleSubmit = this.handleSubmit.bind(this)
  }

  handleSubmit(e) {
    e.preventDefault();
    console.log("Submitted email: ", this.state.email, ", password: ", this.state.password)
    this.setState({
      email: '',
      password: ''
    })
  }

  handleChange(e) {
    const name = e.target.name
    const value = e.target.value
    this.setState({
      [name]: value
    })
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        Email:
        <input type="text" name="email" value={this.state.email} onChange={this.handleChange} />

        Password:
        <input type="password" name="password" value={this.state.password} onChange={this.handleChange} />

        <input type="Submit" value="Log In" />
      </form>
    )
  }
}

export default Login

import React, {Component} from 'react'
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux'

import * as sessionsActions from '../actions/sessionsActions';

class Login extends Component {
  constructor(props) {
    super();
    this.state = {
      email: '',
      password: ''
    }
    this.handleChange = this.handleChange.bind(this)
    this.handleSubmit = this.handleSubmit.bind(this)
  }

  handleSubmit(e) {
    e.preventDefault();
    this.props.actions.loginUser(this.state)
    this.setState({
      email: '',
      password: ''
    })
  }

  handleChange(e) {
    const name = e.target.name
    const value = e.target.value
    this.setState({
      [name]: value
    })
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        Email:
        <input type="text" name="email" value={this.state.email} onChange={this.handleChange} />

        Password:
        <input type="password" name="password" value={this.state.password} onChange={this.handleChange} />

        <input type="Submit" value="Log In" />
      </form>
    )
  }
}

function mapDispatchToProps(dispatch) {
  return {actions:
    bindActionCreators(sessionsActions, dispatch)
  }
}

export default connect(null, mapDispatchToProps)(Login)

Connect actions to props and add onSubmit function


// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @constructor
 * @suppressGlobalPropertiesCheck
 */
WebInspector.DevToolsApp = function()
{
    this._iframe = document.getElementById("inspector-app-iframe");
    this._inspectorFrontendHostImpl = new WebInspector.InspectorFrontendHostImpl();

    /**
     * @type {!Window}
     */
    this._inspectorWindow = this._iframe.contentWindow;
    this._inspectorWindow.InspectorFrontendHost = this._inspectorFrontendHostImpl;
    DevToolsAPI.setInspectorWindow(this._inspectorWindow);

    this._iframe.focus();
}

WebInspector.DevToolsApp.prototype = {
}

runOnWindowLoad(function() { new WebInspector.DevToolsApp(); });

// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @constructor
 * @suppressGlobalPropertiesCheck
 */
WebInspector.DevToolsApp = function()
{
    this._iframe = document.getElementById("inspector-app-iframe");
    this._inspectorFrontendHostImpl = new WebInspector.InspectorFrontendHostImpl();

    /**
     * @type {!Window}
     */
    this._inspectorWindow = this._iframe.contentWindow;
    this._inspectorWindow.InspectorFrontendHost = this._inspectorFrontendHostImpl;
    DevToolsAPI.setInspectorWindow(this._inspectorWindow);

    this._iframe.focus();
    this._iframe.addEventListener("load", this._onIframeLoad.bind(this), false);
}

WebInspector.DevToolsApp.prototype = {
    _onIframeLoad: function()
    {
        /**
         * @this {CSSStyleDeclaration}
         */
        function getValue(property)
        {
            // Note that |property| comes from another context, so we can't use === here.
            if (property == "padding-left") {
                return {
                    /**
                     * @suppressReceiverCheck
                     * @this {Object}
                     */
                    getFloatValue: function() { return this.__paddingLeft; },
                    __paddingLeft: parseFloat(this.paddingLeft)
                };
            }
            throw new Error("getPropertyCSSValue is undefined");
        }

        // Support for legacy (<M41) frontends. Remove in M45.
        this._iframe.contentWindow.CSSStyleDeclaration.prototype.getPropertyCSSValue = getValue;
        this._iframe.contentWindow.CSSPrimitiveValue = { CSS_PX: "CSS_PX" };
    }
}

runOnWindowLoad(function() { new WebInspector.DevToolsApp(); });

Add getPropertyCSSValue polyfill for old frontends.


module.exports = readJson;

var fs = require("fs"),
  semver = require("./semver");

function readJson (jsonFile, cb) {
  fs.readFile(jsonFile, function (er, jsonString) {
    if (er) return cb(er, jsonString);
    var json;
    try {
      json = JSON.parse(jsonString);
    } catch (ex) {
      return cb(new Error(
        "Failed to parse json file: "+jsonFile+"\n"+ex.message+"\n"+jsonString));
    }
    json.name = json.name.replace(/([^\w-]|_)+/g, '-');
    // allow semvers, but also stuff like
    // 0.1.2beta-L24561-2010-02-25-13-41-32-903 for test/link packages.
    if (!(semver.valid(json.version))) {
      return cb(new Error("Invalid version: "+json.version));
    }
    var key = json.name+"-"+json.version;
    json._npmKey = key;
    cb(null, json);
  });
};

module.exports = readJson;

var fs = require("fs"),
  semver = require("./semver"),
  log = require("../utils").log;

function readJson (jsonFile, cb) {
  log(jsonFile, "readJson");
  fs.readFile(jsonFile, function (er, jsonString) {
    if (er) return cb(er, jsonString);
    var json;
    try {
      json = JSON.parse(jsonString);
    } catch (ex) {
      return cb(new Error(
        "Failed to parse json file: "+jsonFile+"\n"+ex.message+"\n"+jsonString));
    }
    json.name = json.name.replace(/([^\w-]|_)+/g, '-');
    // allow semvers, but also stuff like
    // 0.1.2beta-L24561-2010-02-25-13-41-32-903 for test/link packages.
    if (!(semver.valid(json.version))) {
      return cb(new Error("Invalid version: "+json.version));
    }
    var key = json.name+"-"+json.version;
    json._npmKey = key;
    cb(null, json);
  });
};

Add log statement in readJson


class TimeSync {
  constructor() {
    this.ts = timesync.create({
      server: '/timesync'
    });
  }

  now() {
    return parseInt(this.ts.now().toFixed());
  }

  humanTime(millisec) {
    var seconds = (millisec / 1000).toFixed(0);
    var minutes = Math.floor(seconds / 60);
    var hours = "";
    if (minutes > 59) {
        hours = Math.floor(minutes / 60);
        hours = (hours >= 10) ? hours : "0" + hours;
        minutes = minutes - (hours * 60);
        minutes = (minutes >= 10) ? minutes : "0" + minutes;
    }

    seconds = Math.floor(seconds % 60);
    seconds = (seconds >= 10) ? seconds : "0" + seconds;
    if (hours != "") {
        return hours + ":" + minutes + ":" + seconds;
    }
    return minutes + ":" + seconds;
  }
}

timeSync = new TimeSync();

class TimeSync {
  constructor() {
    this.ts = timesync.create({
      server: '/timesync'
    });
  }

  now() {
    return parseInt(this.ts.now().toFixed());
  }

  humanTime(millisec) {
    var millis = millisec%1000;
    var seconds = (millisec / 1000).toFixed(0);
    var minutes = Math.floor(seconds / 60);
    var hours = "";
    if (minutes > 59) {
        hours = Math.floor(minutes / 60);
        hours = (hours >= 10) ? hours : "0" + hours;
        minutes = minutes - (hours * 60);
        minutes = (minutes >= 10) ? minutes : "0" + minutes;
    }

    seconds = Math.floor(seconds % 60);
    seconds = (seconds >= 10) ? seconds : "0" + seconds;
    if (hours != "") {
        return hours + ":" + minutes + ":" + seconds + '.' + millis;
    }
    return minutes + ":" + seconds + '.' + millis;
  }
}

timeSync = new TimeSync();

Add millis to human readable


define([
  'extensions/collections/collection'
],
function (Collection) {
  return {
    requiresSvg: true,
    collectionClass: Collection,

    collectionOptions: function () {
      var valueAttr = this.model.get('value-attribute') || '_count';
      var options = {
        valueAttr: valueAttr
      };
      options.format = this.model.get('format') ||
        { type: 'integer', magnitude: true, sigfigs: 3, pad: true };

      options.dataSource = this.model.get('data-source');
      options.dataSource['query-params'] = _.extend({flatten:true}, options.dataSource['query-params']);

      options.axes = _.merge({
          x: {
            label: 'Dates',
            key: ['_start_at', 'end_at'],
            format: 'date'
          },
          y: [
            {
              label: 'Number of applications',
              key: valueAttr,
              format: options.format
            }
          ]
        }, this.model.get('axes'));
      return options;
    },

    visualisationOptions: function () {
      return {
        maxBars: this.model.get('max-bars'),
        target: this.model.get('target'),
        pinTo: this.model.get('pin-to')
      };
    }
  };

});

Use the column module and just extend from that.


var merge = require('webpack-merge')
var shell = require('shelljs')
var prodEnv = require('./prod.env')
var hash = shell.cat('HEAD').exec('cut -d " " -f2', {silent: true}).stdout

module.exports = merge(prodEnv, {
  NODE_ENV: '"development"',
  GIT_HASH_VERSION: JSON.stringify(hash.slice(0, 9))
})

var merge = require('webpack-merge')
var shell = require('shelljs')
var prodEnv = require('./prod.env')

var cat = shell.cat('HEAD')
var hash = '0000000'
if (cat.code === 0) {
  var cut = cat.exec('cut -d " " -f2', {silent: true}).stdout
  hash = JSON.stringify(cut.slice(0, 7))
}

module.exports = merge(prodEnv, {
  NODE_ENV: '"development"',
  GIT_HASH_VERSION: hash
})

Deal with HEAD possibly not existing


/**
 * This class implements authentication against google
 * using the OAuth2 authorization flow in a popup window.
 */

import Oauth2 from 'torii/providers/oauth2-code';
import {configurable} from 'torii/configuration';

var GoogleOauth2 = Oauth2.extend({

  name:    'google-oauth2',
  baseUrl: 'https://accounts.google.com/o/oauth2/auth',

  // additional params that this provider requires
  requiredUrlParams: ['state'],
  optionalUrlParams: ['scope', 'request_visible_actions'],

  request_visible_actions: configurable('requestVisibleActions', ''),

  scope: configurable('scope', 'email'),

  state: 'STATE',

  redirectUri: configurable('redirectUri',
                            'http://localhost:8000/oauth2callback')
});

export default GoogleOauth2;

/**
 * This class implements authentication against google
 * using the OAuth2 authorization flow in a popup window.
 */

import Oauth2 from 'torii/providers/oauth2-code';
import {configurable} from 'torii/configuration';

var GoogleOauth2 = Oauth2.extend({

  name:    'google-oauth2',
  baseUrl: 'https://accounts.google.com/o/oauth2/auth',

  // additional params that this provider requires
  requiredUrlParams: ['state'],
  optionalUrlParams: ['scope', 'request_visible_actions'],

  request_visible_actions: configurable('requestVisibleActions', ''),

  scope: configurable('scope', 'email'),

  state: configurable('state', 'STATE'),

  redirectUri: configurable('redirectUri',
                            'http://localhost:8000/oauth2callback')
});

export default GoogleOauth2;

Make Google OAuth2 state parameter configurable


export default function apply(issues, jiraClientAPI) {
  let issueMap = issues.map(issue => {
    return jiraClientAPI.findIssue(issue);
  });

  return Promise.all(issueMap)
   .then(() => true);
}

export default function apply(issues, jiraClientAPI) {
  let issueMap = issues.map(issue => {
    return jiraClientAPI.findIssue(issue);
  });

  //On error, throws: "Error: Issue {key} does not exist."
  return Promise.all(issueMap)
    .then(() => true);
}

Add comment showing what error is thrown


'use strict';

var passport = require('passport'),
    nconf = require('nconf'),
    utils = require('../lib/utils'),
    geboSchema = require('../schemata/gebo'),
    agentSchema = require('../schemata/agent'),
    q = require('q');

module.exports = function(email) {

    // Turn the email into a mongo-friend database name
    var dbName = utils.ensureDbName(email);

    /**
     * Receive a request for consideration
     */
    exports.request = [
        passport.authenticate('bearer', { session: false }),
        function(req, res) {
            console.log('request');
            console.log(req.body);
            console.log(req.user);
            console.log(req.authInfo);
            var action = require('../config/action')(dbName);

            // Convert email address to mongo-friendly DB
            // and collection strings
            req.user.dbName = utils.getMongoDbName(req.user.dbName);
            req.user.collectionName = utils.getMongoCollectionName(req.user.collectionName);

            action[req.body.action](req.user, req.body).
                then(function(data) {
                    res.send(data);
                  }).
                catch(function(err) {
                    console.log(err);
                    res.send(404, err);
                  });

          }
      ];

    return exports;
  };

'use strict';

var passport = require('passport'),
    nconf = require('nconf'),
    utils = require('../lib/utils'),
    geboSchema = require('../schemata/gebo'),
    agentSchema = require('../schemata/agent'),
    extend = require('extend'),
    q = require('q');

module.exports = function(email) {

    // Turn the email into a mongo-friend database name
    var dbName = utils.ensureDbName(email);

    /**
     * Receive a request for consideration
     */
    exports.request = [
        passport.authenticate('bearer', { session: false }),
        function(req, res) {

            // There might be files attached to the request
            var params = req.body;
            extend(true, params, req.files);

            console.log('request');
            console.log(req.body);
            console.log(req.user);
            console.log(req.authInfo);
            console.log('params');
            console.log(params);

            var action = require('../config/action')(dbName);
            //action[req.body.action](req.user, req.body).
            action[req.body.action](req.user, params).
                then(function(data) {
                    res.send(data);
                  }).
                catch(function(err) {
                    console.log(err);
                    res.send(404, err);
                  });
          }
      ];

    return exports;
  };

Add req.files to params object


// Place all the behaviors and hooks related to the matching controller here.
// All this logic will automatically be available in application.js.

$(document).ready(function() {
    $("#owner a.add_fields").
      data("association-insertion-position", 'before').
      data("association-insertion-node", 'this');

    $('#owner').bind('cocoon:after-insert',
         function() {
           $("#owner_from_list").hide();
           $("#owner a.add_fields").hide();
         });
    $('#owner').bind("cocoon:after-remove",
         function() {
           $("#owner_from_list").show();
           $("#owner a.add_fields").show();
         });

    $("#tags a.add_fields").
      data("association-insertion-position", 'before').
      data("association-insertion-node", 'this');

    $('#tags').bind('cocoon:after-insert',
         function(e, tag) {
             $(".project-tag-fields a.add_fields").
                 data("association-insertion-position", 'before').
                 data("association-insertion-node", 'this');
             $('.project-tag-fields').bind('cocoon:after-insert',
                  function() {
                    $(this).children("#tag_from_list").remove();
                    $(this).children("a.add_fields").hide();
                  });
         });

    $('#tasks').bind('cocoon:before-insert', function(e,task_to_be_added) {
        task_to_be_added.fadeIn('slow');
    });

    $('#tasks').bind('cocoon:after-insert', function(e, added_task) {
        //added_task.css("background","red");
    });

    $('#tasks').bind('cocoon:before-remove', function(e, task) {
        $(this).data('remove-timeout', 1000);
        task.fadeOut('slow');
    })

    $('body').tabs();
});

Add some effects for the Owner and Tag associations.


import Relay from 'react-relay';

export default class extends Relay.Mutation {
  getMutation() {
    return Relay.QL`mutation{ ConnectOauth }`;
  }

  getFatQuery() {
    return Relay.QL`
      fragment on ConnectOauthPayload {
        developer {
          connections
        }
      }
    `;
  }

  getConfigs() {
    return [
      {
        type: 'FIELDS_CHANGE',
        fieldIDs: {
          developer: this.props.id,
        },
      },
    ];
  }

  getVariables() {
    return {
      ...this.props,
    };
  }
}

import Relay from 'react-relay';

export default class extends Relay.Mutation {
  getMutation() {
    return Relay.QL`mutation{ ConnectOauth }`;
  }

  getFatQuery() {
    return Relay.QL`
      fragment on ConnectOauthPayload {
        connection {
          id,
          importing,
          expired,
          connected,
        }
      }
    `;
  }

  getConfigs() {
    return [
      {
        type: 'FIELDS_CHANGE',
        fieldIDs: {
          connection: this.props.id,
        },
      },
    ];
  }

  getVariables() {
    return {
      ...this.props,
    };
  }
}

Refactor mutation to only return connection


Meteor.methods({
  addTask: function (text) {
    console.log('add task tset');
    // Make sure the user is logged in before inserting a task
    // if (! Meteor.userId()) {
    //   throw new Meteor.Error("not-authorized");
    // }

    Tasks.insert({
      text: text,
      createdAt: new Date(),
      // owner: Meteor.userId(),
      // username: Meteor.user().username
    });
  },
  deleteTask: function (taskId) {
    Tasks.remove(taskId);
  },
  setChecked: function (taskId, setChecked) {
    Tasks.update(taskId, { $set: { checked: setChecked} });
  }
});

Tasks = new Mongo.Collection("tasks");

Meteor.methods({
  addGiblet: function (text) {
    console.log('add giblet');
    // Make sure the user is logged in before inserting a task
    // if (! Meteor.userId()) {
    //   throw new Meteor.Error("not-authorized");
    // }

    Giblets.insert({
      text: text,
      createdAt: new Date(),
      owner: Meteor.userId(),
      // username: Meteor.user().username
    });
  },
  deleteGiblet: function ( gibletID ) {
    Giblets.remove( gibletID );
  }
  // setChecked: function (taskId, setChecked) {
  //   Giblets.update(taskId, { $set: { checked: setChecked} });
  // }
});

Giblets = new Mongo.Collection("giblets");

Change methods from tasks to giblets


/*
 * Copyright 2015 The Closure Compiler Authors.
 *
 * 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.
 */

/**
 * @fileoverview Extra externs for Jasmine when using Angular.
 *
 * Depends on both the angular-1.x-mocks.js and Jasmine 2.0 externs.
 *
 * @externs
 */

/**
 * Provided by angular-mocks.js.
 * @type {angular.$injector}
 */
jasmine.Spec.prototype.$injector;

/**
 * Provided by angular-mocks.js.
 * @param {...(Function|Array<string|Function>)} var_args
 */
function inject(var_args) {}

/**
 * Provided by angular-mocks.js.
 * @param {...(string|Function|Array<string|Function>)} var_args
 * @suppress {checkTypes}
 */
function module(var_args) {}

/*
 * Copyright 2015 The Closure Compiler Authors.
 *
 * 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.
 */

/**
 * @fileoverview Extra externs for Jasmine when using Angular.
 *
 * Depends on both the angular-1.x-mocks.js and Jasmine 2.0 externs.
 *
 * @externs
 */

/**
 * Provided by angular-mocks.js.
 * @type {angular.$injector}
 */
jasmine.Spec.prototype.$injector;

/**
 * Provided by angular-mocks.js.
 * @param {...(Function|Array<string|Function>)} var_args
 */
function inject(var_args) {}

/**
 * Provided by angular-mocks.js.
 * @param {...(string|Function|Array<string|Function>|Object<string, *>)}
 *     var_args
 * @suppress {checkTypes}
 */
function module(var_args) {}

Update angular+jasmine externs to have the module function accept an Object value map.


import * as Sentry from '@sentry/browser';
import { getSetting } from 'meteor/vulcan:core'

const sentryUrl = getSetting('sentry.url');
const sentryEnvironment = getSetting('sentry.environment');

Sentry.init({
  dsn: sentryUrl,
  beforeBreadcrumb(breadcrumb, hint) {
    if (breadcrumb.level === "error" && breadcrumb.message) {
      Sentry.captureException(breadcrumb.message)
    }
    return breadcrumb
  },
  environment: sentryEnvironment
});
// Initializing sentry on the client browser

import * as Sentry from '@sentry/browser';
import { getSetting, addCallback } from 'meteor/vulcan:core'

const sentryUrl = getSetting('sentry.url');
const sentryEnvironment = getSetting('sentry.environment');

Sentry.init({
  dsn: sentryUrl,
  beforeBreadcrumb(breadcrumb, hint) {
    if (breadcrumb.level === "error" && breadcrumb.message) {
      Sentry.captureException(breadcrumb.message)
    }
    return breadcrumb
  },
  environment: sentryEnvironment
});
// Initializing sentry on the client browser

function identifyUserToSentry(user) {
  // Set user in sentry scope
  Sentry.configureScope((scope) => {
    scope.setUser({id: user._id, email: user.email, username: user.username});
  });
}

addCallback('events.identify', identifyUserToSentry)

function addUserIdToGoogleAnalytics(user) {
  if (window && window.ga) {
    window.ga('set', 'userId', user._id); // Set the user ID using signed-in user_id.
  }
}

addCallback('events.identify', addUserIdToGoogleAnalytics)

Add userId to google Analytics


$(document).ready(function() {
  $("body").on("click", "#signup", function(event){
    event.preventDefault();
    var $target = $(event.target);
    $.ajax({
      type: "GET",
      url: "/signup",
    }).done(function(response){
      $(".container").replaceWith(response);
    });
  });

  $("body").on("click", "#login", function(event){
    event.preventDefault();
    var $target = $(event.target);
    $.ajax({
      type: "GET",
      url: "/",
    }).done(function(response){
      $(".container").replaceWith(response);
    });
  });

  $(".edit_comment_link").click(function(event){
    var $this = $(this);
    event.preventDefault();
    $this.hide();
    $this.siblings(".edit_form_div").css('display','block');
  });

  $(".add_comment_button").click(function(event){
    event.preventDefault();
    var $target = $(event.target);
    $target.hide();
    $(".comment_form").css('display','block');
  });

 $('#comment_block').on('submit', '.comment_form form', function(event) {
    event.preventDefault();
    var $form = $(this);
    console.log($form);
    $.ajax({
      type: "POST",
      url: $form.attr('action'),
      data: $form.serialize()
    }).done(function(response){
      console.log(response);
      $('.userComments').append(response);
    });
  });

});

$(document).ready(function() {
  $("body").on("click", "#signup", function(event){
    event.preventDefault();
    var $target = $(event.target);
    $.ajax({
      type: "GET",
      url: "/signup",
    }).done(function(response){
      $(".container").replaceWith(response);
    });
  });

  $("body").on("click", "#login", function(event){
    event.preventDefault();
    var $target = $(event.target);
    $.ajax({
      type: "GET",
      url: "/",
    }).done(function(response){
      $(".container").replaceWith(response);
    });
  });

  $(".edit_comment_link").click(function(event){
    var $this = $(this);
    event.preventDefault();
    $this.hide();
    $this.siblings(".edit_form_div").css('display','block');
  });

  $(".add_comment_button").click(function(event){
    event.preventDefault();
    var $target = $(event.target);
    $target.hide();
    $(".comment_form").css('display','block');
  });

 $('#comment_block').on('submit', '.comment_form form', function(event) {
    event.preventDefault();
    var $form = $(this);
    $.ajax({
      type: "POST",
      url: $form.attr('action'),
      data: $form.serialize()
    }).done(function(response){
      $('.userComments').add(response, function(){
        $('.userComments').css('display', 'block');
      });
    });
  });

});

Implement AJAX for showing comments.


const checkString = (string) => {
  if (typeof string == 'string') {
    if (isNaN(Date.parse(string)) == false) {
      const unix = Date.parse(string) / 1000;
      const natural = (new Date(string)).toDateString();
      return { unix, natural };
    } else if (isNaN((new Date(parseInt(string))).getHours()) == false) {
      return true;
    }
  }
  return false;
}
module.exports = checkString;

const checkString = (string) => {
  if (typeof string == 'string') {
    if (isNaN(Date.parse(string)) == false) {
      const unix = Date.parse(string) / 1000;
      const natural = (new Date(string)).toDateString();
      return { unix, natural };
    } else if (isNaN((new Date(1000 * parseInt(string))).getHours()) == false) {
      const unix = parseInt(string);
      const natural = (new Date(unix * 1000)).toDateString();
      return { unix, natural };
    }
  }
  return false;
}
module.exports = checkString;

Implement checkString to return correct date object for unix timestamps


//= require jquery
//= require jquery-ui
//= require jquery_ujs
//= require jquery.cookie
//= require faye-browser
//= require prettify
//= require twitter/bootstrap
//= require underscore
//= require init
//= require jquery.activity-indicator-1.0.0
//= require jquery.scrollto
//= require simple_hash
//= require backbone
//= require backbone_rails_sync
//= require backbone_datalink
//= require backbone/water_backbone
//= require_tree ./controllers

//= require jquery
//= require jquery-ui
//= require jquery_ujs
//= require jquery.cookie
//= require faye-browser
//= require twitter/bootstrap
//= require underscore
//= require init
//= require jquery.activity-indicator-1.0.0
//= require jquery.scrollto
//= require simple_hash
//= require backbone
//= require backbone_rails_sync
//= require backbone_datalink
//= require backbone/water_backbone
//= require_tree ./controllers

Remove reference to prettify from js manifest


import React, {Component} from 'react'
import {StyleSheet, Text, TouchableHighlight, View} from 'react-native'

import commonStyles from '../../../styles/common'

export default class ExistingDeviceRender extends Component {
  render () {
    return (
      <View style={[styles.container, {marginTop: 200, padding: 20, alignItems: 'stretch'}]}>
        <Text style={commonStyles.h1}>What type of device would you like to connect this device with?</Text>
        <View style={{flex: 1, flexDirection: 'row', marginTop: 40, justifyContent: 'space-between', alignItems: 'flex-start', paddingLeft: 40, paddingRight: 40}}>
          <TouchableHighlight onPress={() => this.props.onSubmitComputer()}>
            <View style={{flexDirection: 'column', alignItems: 'center'}}>
              <Text>[Desktop icon]</Text>
              <Text>Desktop Device ></Text>
            </View>
          </TouchableHighlight>
          <TouchableHighlight onPress={() => this.props.onSubmitPhone()}>
            <View style={{flexDirection: 'column', alignItems: 'center'}}>
              <Text>[Mobile icon]</Text>
              <Text>Mobile Device ></Text>
            </View>
          </TouchableHighlight>
        </View>
      </View>
    )
  }
}

ExistingDeviceRender.propTypes = {
  onSubmitComputer: React.PropTypes.func.isRequired,
  onSubmitPhone: React.PropTypes.func.isRequired

}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'flex-start'
  }
})

// @flow
import React, {Component} from 'react'
import {StyleSheet, Text, TouchableHighlight, View} from 'react-native'

import commonStyles from '../../styles/common'

export default class ExistingDeviceRender extends Component {
  render () {
    return (
      <View style={[styles.container, {marginTop: 200, padding: 20, alignItems: 'stretch'}]}>
        <Text style={commonStyles.h1}>What type of device would you like to connect this device with?</Text>
        <View style={{flex: 1, flexDirection: 'row', marginTop: 40, justifyContent: 'space-between', alignItems: 'flex-start', paddingLeft: 40, paddingRight: 40}}>
          <TouchableHighlight onPress={() => this.props.onSubmitComputer()}>
            <View style={{flexDirection: 'column', alignItems: 'center'}}>
              <Text>[Desktop icon]</Text>
              <Text>Desktop Device ></Text>
            </View>
          </TouchableHighlight>
          <TouchableHighlight onPress={() => this.props.onSubmitPhone()}>
            <View style={{flexDirection: 'column', alignItems: 'center'}}>
              <Text>[Mobile icon]</Text>
              <Text>Mobile Device ></Text>
            </View>
          </TouchableHighlight>
        </View>
      </View>
    )
  }
}

ExistingDeviceRender.propTypes = {
  onSubmitComputer: React.PropTypes.func.isRequired,
  onSubmitPhone: React.PropTypes.func.isRequired

}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'flex-start'
  }
})

Fix import and type file


import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import { primary } from '@artsy/reaction-force/dist/Assets/Fonts'

const propTypes = {
  title: PropTypes.string,
  href: PropTypes.string,
  image: PropTypes.object
}

const Container = styled.div`
  position: relative;
  width: 95%;
  overflow: hidden;
`

const GeneLink = styled.a`
  position: absolute;
  left: 1em;
  bottom: 1em;
  text-decoration: none;

  color: white;
  text-shadow: 0 0 10px rgba(0, 0, 0, 0.7);

  ${primary.style} font-size: 13px;
  line-height: 1.33em;
  font-weight: bold;
`

const GeneImage = styled.img`width: 100%;`

const FeaturedGene = ({ title, href, image: { url: imageSrc } }) => {
  return (
    <Container>
      <GeneLink href={href}>{title}</GeneLink>
      <GeneImage src={imageSrc} />
    </Container>
  )
}

FeaturedGene.propTypes = propTypes

export default FeaturedGene

import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import { primary } from '@artsy/reaction-force/dist/Assets/Fonts'

const propTypes = {
  title: PropTypes.string,
  href: PropTypes.string,
  image: PropTypes.object
}

const Container = styled.div`
  position: relative;
  width: 95%;
  overflow: hidden;
`

const GeneName = styled.span`
  position: absolute;
  left: 1em;
  bottom: 1em;
  text-decoration: none;

  color: white;
  text-shadow: 0 0 10px rgba(0, 0, 0, 0.7);

  ${primary.style} font-size: 13px;
  line-height: 1.33em;
  font-weight: bold;
`

const GeneImage = styled.img`width: 100%;`

const FeaturedGene = ({ title, href, image: { url: imageSrc } }) => {
  return (
    <a href={href}>
      <Container>
        <GeneName>{title}</GeneName>
        <GeneImage src={imageSrc} />
      </Container>
    </a>
  )
}

FeaturedGene.propTypes = propTypes

export default FeaturedGene

Make whole feat gene thumb clickable


import React from 'react';
import { Navbar, NavItem, Nav, Jumbotron, Button, Input} from 'react-bootstrap';

import {
    BrowserRouter as Router,
    Route,
    Link,
    Redirect,
    Switch,
    NavLink
} from 'react-router-dom';
import IssueReports from './IssueReports.js';

class  IssueTrackerNav extends React.Component {
    constructor(props) {
        super(props);

     this.state = {
      redirectToMenu: false,
    };
  }

render(){

  return (
      <div>
         <Navbar>
        <Nav >
          <NavItem>
            <NavLink activeClassName='active' to='/menu/IssueTracker'>Issue</NavLink>
          </NavItem>
          <NavItem>
            <NavLink activeClassName='active' to='/menu/IssueReports'>Reports</NavLink>
          </NavItem>
          <NavItem>
            <NavLink activeClassName='active' to='/menu/IssueWiki'>Issue Wiki</NavLink>
          </NavItem>
          <NavItem>
            <NavLink activeClassName='active' to='/menu/NewIssue'>Create New Issue</NavLink>
          </NavItem>

        </Nav>
        </Navbar>
    </div>
 );
}
}

export default IssueTrackerNav;

import React from 'react';
import { Navbar, NavItem, Nav, Jumbotron, Button, Input} from 'react-bootstrap';
import { LinkContainer } from 'react-router-bootstrap';

import {
    BrowserRouter as Router,
    Route,
    Link,
    Redirect,
    Switch,
    NavLink
} from 'react-router-dom';
import IssueReports from './IssueReports.js';

class  IssueTrackerNav extends React.Component {
    constructor(props) {
        super(props);

     this.state = {
      redirectToMenu: false,
    };
  }

render(){

  return (
      <div>
         <Navbar>

        <Nav >
            <LinkContainer  to='/menu/IssueTracker'>
                  <NavItem  eventKey={1}>Issue</NavItem>
              </LinkContainer>

                <LinkContainer  to='/menu/IssueReports'>
                  <NavItem  eventKey={2}>Reports</NavItem>
              </LinkContainer>

                  <LinkContainer  to='/menu/NewIssue'>
                  <NavItem  eventKey={4}>Create New Issue</NavItem>
              </LinkContainer>

        </Nav>
        <Nav pullRight>
            <LinkContainer  to="/menu/IssueWiki">
                  <NavItem  eventKey={3}>Issue Wiki</NavItem>
            </LinkContainer>
        </Nav>
        </Navbar>
    </div>
 );
}
}

export default IssueTrackerNav;

Update the Issue Summary navigation bar


const cheerio = require('cheerio')
const normalizeUrl = require('normalize-url')

const request = require('../request')

module.exports = async function findFeed ({ url }) {
  const normalizedUrl = normalizeUrl(url)
  let response = null

  try {
    response = await request(normalizedUrl)
  } catch (error) {
    console.log(error)
    return []
  }

  if (/application\/(rss|atom)/.test(response.contentType)) {
    return [{link: normalizedUrl}]
  }

  const dom = cheerio.load(response.text)
  const $linkTags = dom('link[rel="alternate"][type="application/rss+xml"]')
    .add('link[rel="alternate"][type="application/atom+xml"]')

  return $linkTags.map((index, $linkTag) => {
    const link = normalizeUrl($linkTag.attribs.href)
    return {
      link: /^\//.test(link) ? url + link : link
    }
  }).toArray()
}

const cheerio = require('cheerio')
const normalizeUrl = require('normalize-url')

const request = require('../request')

const normalizeOptions = {removeTrailingSlash: false}

module.exports = async function findFeed ({ url }) {
  const normalizedUrl = normalizeUrl(url, normalizeOptions)
  let response = null

  try {
    response = await request(normalizedUrl)
  } catch (error) {
    console.log(error)
    return []
  }

  if (/application\/(rss|atom)/.test(response.contentType)) {
    return [{link: normalizedUrl}]
  }

  const dom = cheerio.load(response.text)
  const $linkTags = dom('link[rel="alternate"][type="application/rss+xml"]')
    .add('link[rel="alternate"][type="application/atom+xml"]')

  return $linkTags.map((index, $linkTag) => {
    const link = normalizeUrl($linkTag.attribs.href, normalizeOptions)
    return {
      link: /^\//.test(link) ? url + link : link
    }
  }).toArray()
}

Make normalize urls to not remove trailing slash


'use strict';

var path = require('path');
var core = require('thehelp-core');
core.env.merge(path.join(__dirname, '../../env.json'));

var cluster = require('../../src/server/index');
var ld = require('thehelp-last-ditch');

var lastDitch = new ld.LastDitch({
  targets: ['stderr']
});

cluster.Graceful.start({
  messenger: lastDitch.go
});

cluster({
  masterOptions: {
    spinTimeout: 100
  },
  worker: function() {
    require('./end_to_end_server');
  }
});

'use strict';

var path = require('path');
var core = require('thehelp-core');
core.env.merge(path.join(__dirname, '../../env.json'));

var winston = require('winston');
var log = {
  info: function(text) {
    winston.info('PREFIX ' + text);
  },
  warn: function(text) {
    winston.warn('PREFIX ' + text);
  },
  error: function(text) {
    winston.error('PREFIX ' + text);
  }
};

var cluster = require('../../src/server/index');
var ld = require('thehelp-last-ditch');

var lastDitch = new ld.LastDitch({
  targets: ['stderr'],
  log: log
});

cluster.Graceful.start({
  messenger: lastDitch.go,
  log: log
});

cluster({
  log: log,
  masterOptions: {
    spinTimeout: 100,
    log: log
  },
  worker: function() {
    require('./end_to_end_server');
  }
});

Add override of winston logging to end-to-end test


module.exports = context => {
    function inspectHardcodedPassword(node, value) {
        const isHardcoded = value.toLowerCase().includes('passw');

        if (isHardcoded) {
            return context.report({
                node,
                message : 'Do not use hadrcoded password'
            });
        }
    }

    return {
        VariableDeclarator({ id, init }) {
            if (id.name && init.type === 'Literal' ) {
                return inspectHardcodedPassword(id, id.name);
            }
        },

        Property({ key, value }) {
            if (value.type !== 'Literal') return;

            if (key.type === 'Identifier') {
                return inspectHardcodedPassword(key, key.name);
            }

            if (key.type === 'Literal') {
                return inspectHardcodedPassword(key, key.value);
            }

            if (key.type === 'TemplateLiteral') {
                return key.quasis.some(element => inspectHardcodedPassword(key, element.value.raw));
            }
        }
    };
};

module.exports.schema = [];

module.exports = context => {
    function inspectHardcodedPassword(node, value) {
        const isHardcoded = value.toLowerCase().includes('passw');

        if (isHardcoded) {
            return context.report({
                node,
                message : 'Do not use hadrcoded password'
            });
        }
    }

    return {
        VariableDeclarator({ id, init }) {
            if (id && id.name && init && init.type === 'Literal' ) {
                return inspectHardcodedPassword(id, id.name);
            }
        },

        Property({ key, value }) {
            if (!key || !value || value.type !== 'Literal') return;

            if (key.type === 'Identifier') {
                return inspectHardcodedPassword(key, key.name);
            }

            if (key.type === 'Literal') {
                return inspectHardcodedPassword(key, key.value);
            }

            if (key.type === 'TemplateLiteral') {
                return key.quasis.some(element => inspectHardcodedPassword(key, element.value.raw));
            }
        }
    };
};

module.exports.schema = [];

Add additional checks for no-hardrcoded-password rule


'use strict';

// MODULES //

var tape = require( 'tape' );
var ksa = require( './../lib/ksa.js' );

// FIXTURES //

var byte_array = require( './fixtures/ksa.json' );

// TESTS //

tape( 'main export is a function', function test( t ) {
	t.ok( true, __filename );
	t.equal( typeof ksa, 'function', 'main export is a function' );
	t.end();
});

tape( 'the function returns a permuted byte array', function test( t ) {
	var expected;
	var S;

	expected = new Uint8Array( byte_array );

	S = ksa( 'pwd12' );

	t.equal( S instanceof Uint8Array, true, 'returns a Uint8Array' );
	t.equal( S.length, 256, 'length is 256' );
	t.deepEqual( S, expected, 'returns expected byte array' );

	t.end();
});

'use strict';

// MODULES //

var tape = require( 'tape' );
var ksa = require( './../lib/ksa.js' );

// FIXTURES //

var byte_array = require( './fixtures/ksa.json' );

// TESTS //

tape( 'main export is a function', function test( t ) {
	t.ok( true, __filename );
	t.equal( typeof ksa, 'function', 'main export is a function' );
	t.end();
});

tape( 'the function returns a permuted byte array', function test( t ) {
	var expected;
	var S;
	var i;

	expected = new Uint8Array( byte_array );

	S = ksa( 'pwd12' );

	t.equal( S instanceof Uint8Array, true, 'returns a Uint8Array' );
	t.equal( S.length, 256, 'length is 256' );
	for ( i = 0; i < S.length; i++ ) {
		t.equal( S[i], expected[i], 'element '+i+' ('+S[i]+') has expected value '+expected[i] );
	}
	t.end();
});

Modify test to print expected and actual values


export function webpack(config) {
  return {
    ...config,
    module: {
      ...config.module,
      rules: [
        ...config.module.rules,
        {
          test: /\.marko$/,
          loader: require.resolve('marko-loader'),
        },
      ],
    },
    resolve: {
      ...config.resolve,
      extensions: [...config.resolve.extensions, '.marko'],
    },
  };
}

export function webpack(config) {
  return {
    ...config,
    module: {
      ...config.module,
      rules: [
        ...config.module.rules,
        {
          test: /\.marko$/,
          loader: require.resolve('marko-loader'),
          options: {
            compiler: require.resolve('marko/compiler'),
          },
        },
      ],
    },
    resolve: {
      ...config.resolve,
      extensions: [...config.resolve.extensions, '.marko'],
    },
  };
}

ADD compiler to loader as per docs


var PaginationViewInfiniteScrollMixin = Ember.Mixin.create({

    /**
     * Determines if we are past a scroll point where we need to fetch the next page
     * @param {object} event The scroll event
     */
    checkScroll: function (event) {
        var element = event.target,
            triggerPoint = 100,
            controller = this.get('controller'),
            isLoading = controller.get('isLoading');

        // If we haven't passed our threshold or we are already fetching content, exit
        if (isLoading || (element.scrollTop + element.clientHeight + triggerPoint <= element.scrollHeight)) {
            return;
        }

        controller.send('loadNextPage');
    },

    /**
     * Bind to the scroll event once the element is in the DOM
     */
    attachCheckScroll: function () {
        var el = this.$();

        el.on('scroll', Ember.run.bind(this, this.checkScroll));
    }.on('didInsertElement'),

    /**
     * Unbind from the scroll event when the element is no longer in the DOM
     */
    detachCheckScroll: function () {
        var el = this.$();
        el.off('scroll');
    }.on('willDestroyElement')
});

export default PaginationViewInfiniteScrollMixin;

var PaginationViewInfiniteScrollMixin = Ember.Mixin.create({

    /**
     * Determines if we are past a scroll point where we need to fetch the next page
     * @param {object} event The scroll event
     */
    checkScroll: function (event) {
        var element = event.target,
            triggerPoint = 100,
            controller = this.get('controller'),
            isLoading = controller.get('isLoading');

        // If we haven't passed our threshold or we are already fetching content, exit
        if (isLoading || (element.scrollTop + element.clientHeight + triggerPoint <= element.scrollHeight)) {
            return;
        }

        controller.send('loadNextPage');
    },

    /**
     * Bind to the scroll event once the element is in the DOM
     */
    attachCheckScroll: function () {
        var el = this.$(),
            controller = this.get('controller');

        el.on('scroll', Ember.run.bind(this, this.checkScroll));

        if (this.element.scrollHeight <= this.element.clientHeight) {
            controller.send('loadNextPage');
        }
    }.on('didInsertElement'),

    /**
     * Unbind from the scroll event when the element is no longer in the DOM
     */
    detachCheckScroll: function () {
        var el = this.$();
        el.off('scroll');
    }.on('willDestroyElement')
});

export default PaginationViewInfiniteScrollMixin;

Load next page if scrollHeight <= clientHeight


/**
 * Tatoeba Project, free collaborative creation of multilingual corpuses project
 * Copyright (C) 2009-2010  HO Ngoc Phuong Trang <tranglich@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

$(document).ready(function() {

    $(".removeFromListButton").click(function(){
        var sentenceId = $(this).attr("data-sentence-id");
        var listId = $("#sentencesList").attr('data-list-id');
        var rootUrl = get_tatoeba_root_url();

        $("#sentence"+sentenceId).html(
            "

"
        );
        $("#sentence"+sentenceId).load(
            rootUrl
            + "/sentences_lists/remove_sentence_from_list/"
            + sentenceId + "/" + listId
        );

    });

});

/**
 * Tatoeba Project, free collaborative creation of multilingual corpuses project
 * Copyright (C) 2009-2010  HO Ngoc Phuong Trang <tranglich@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

$(document).ready(function() {
    $(document).watch("addrule", function() {
        $(".removeFromListButton").click(function(){
            var sentenceId = $(this).attr("data-sentence-id");
            var listId = $("#sentencesList").attr('data-list-id');
            var rootUrl = get_tatoeba_root_url();

            $("#sentence"+sentenceId).html(
                "

"
            );
            $("#sentence"+sentenceId).load(
                rootUrl
                + "/sentences_lists/remove_sentence_from_list/"
                + sentenceId + "/" + listId
            );
        });
    });
});

Fix removing a sentence from a list


'use strict';

var restify = require('restify');
var js2xmlparser = require('js2xmlparser');
var server = restify.createServer({
    formatters: {
        'application/json; q=5': function format(req, res, body) {
            res.setHeader('Content-type', 'application/hal+json');
            return JSON.stringify(body);
        },
        'application/xml; q=0': function formatFoo(req, res, body) {
            return js2xmlparser('document', body);
        }
    }
});

server
    .use(restify.fullResponse())
    .use(restify.bodyParser())
;
server.pre(restify.pre.userAgentConnection());

require('./routes')(server);

server.listen(8002);

'use strict';

var restify = require('restify');
var js2xmlparser = require('js2xmlparser');
var server = restify.createServer({
    formatters: {
        'application/json; q=5': function format(req, res, body) {
            res.setHeader('Content-type', 'application/hal+json');
            res.send(JSON.stringify(body));
        },
        'application/xml; q=0': function formatFoo(req, res, body) {
            res.send(js2xmlparser('document', body));
        }
    }
});

server
    .use(restify.fullResponse())
    .use(restify.bodyParser())
;
server.pre(restify.pre.userAgentConnection());

require('./routes')(server);

server.listen(8002);

Fix after upgrade node v7


#!node

var SlackClient = require("@slack/client");
var RtmClient = SlackClient.RtmClient;
var RTM_EVENTS = SlackClient.RTM_EVENTS;

var token = process.env.SLACK_API_TOKEN || "";

var rtm = new RtmClient(token, { logLevel: "info"});
rtm.start();

rtm.on(RTM_EVENTS.MESSAGE, function(message) {
    console.log("Message Received: ", message);
});

rtm.on(RTM_EVENTS.REACTION_ADDED, function(reaction) {
    console.log("Reaction Added: ", reaction);
});

rtm.on(RTM_EVENTS.REACTION_REMOVED, function(reaction) {
    console.log("Reaction Removed: ", reaction);
});

Add code from Slack SDK examples


#!/usr/bin/env node

if (!process.argv[2]) return console.error(
  'Usage: npm-me <username>'
)

var columnify = require('columnify')

require('./')(process.argv[2], function(err, downloads) {
  if (err) throw err
  console.log(
    columnify(downloads
    .filter(Boolean)
    .sort(function(a, b) {
      return a.count - b.count
    }))
  )
})

#!/usr/bin/env node

if (!process.argv[2]) return console.error(
  'Usage: npm-me <username>'
)

var columnify = require('columnify')

require('./')(process.argv[2], function(err, downloads) {
  if (err) throw err
  downloads = downloads
  .filter(Boolean)
  .sort(function(a, b) {
    return a.count - b.count
  })

  console.log(columnify(downloads))

  var total = downloads.reduce(function(total, dl) {
    return total + dl.count
  }, 0)

  var max = downloads.reduce(function(max, dl) {
    return Math.max(max, dl.count)
  }, 0)

  var min = downloads.reduce(function(min, dl) {
    return Math.min(min, dl.count)
  }, 0)

  var avg = Math.round((max - min) / downloads.length)
  var stats = columnify([
      {name: 'Total', value: total}
    , {name: 'Max', value: max}
    , {name: 'Min', value: min}
    , {name: 'Avg', value: avg}
  ], {showHeaders: false})

  console.log()
  console.log('SUMMARY')
  console.log(stats)
})

Add summary after downloads list.


(function(){
    'use strict';

    angular
        .module('<%=angularAppName%>')
        .factory('ParseLinks', ParseLinks);

    function ParseLinks () {

        var service = {
            parse : parse
        };

        return service;

        function parse(header) {
            if (header.length === 0) {
                throw new Error('input must not be of zero length');
            }

            // Split parts by comma
            var parts = header.split(',');
            var links = {};
            // Parse each part into a named link
            angular.forEach(parts, function(p) {
                var section = p.split(';');
                if (section.length !== 2) {
                    throw new Error('section could not be split on ";"');
                }
                var url = section[0].replace(/<(.*)>/, '$1').trim();
                var queryString = {};
                url.replace(
                    new RegExp('([^?=&]+)(=([^&]*))?', 'g'),
                    function($0, $1, $2, $3) { queryString[$1] = $3; }
                );
                var page = queryString.page;
                if (angular.isString(page)) {
                    page = parseInt(page);
                }
                var name = section[1].replace(/rel="(.*)"/, '$1').trim();
                links[name] = page;
            });
            return links;
        }
    }
})();

(function(){
    'use strict';

    angular
        .module('<%=angularAppName%>')
        .factory('ParseLinks', ParseLinks);

    function ParseLinks () {

        var service = {
            parse : parse
        };

        return service;

        function parse(header) {
            if (header.length === 0) {
                throw new Error('input must not be of zero length');
            }

            // Split parts by comma
            var parts = header.split(',');
            var links = {};
            // Parse each part into a named link
            angular.forEach(parts, function(p) {
                var section = p.split('>;');
                if (section.length !== 2) {
                    throw new Error('section could not be split on ">;"');
                }
                var url = section[0].replace(/<(.*)/, '$1').trim();
                var queryString = {};
                url.replace(
                    new RegExp('([^?=&]+)(=([^&]*))?', 'g'),
                    function($0, $1, $2, $3) { queryString[$1] = $3; }
                );
                var page = queryString.page;
                if (angular.isString(page)) {
                    page = parseInt(page);
                }
                var name = section[1].replace(/rel="(.*)"/, '$1').trim();
                links[name] = page;
            });
            return links;
        }
    }
})();

Fix broken pagination with Elasticsearch and Undertow


'use strict';

const debug = require('debug')('hmpo:fields:Select');
const InputDate = require('./input-date');
const ErrorGroup = require('./error-group');

class InputDateGroup extends InputDate {
    constructor(content, ctx, options) {
        debug('constructor');
        super(content, ctx, options);

        this.errorGroup = new ErrorGroup({
            key: this.key,
            groupClassName: 'form-date',
            legendClassName: 'form-label-bold'
        }, ctx, options);

        this.errorGroupEnd = new ErrorGroup.End({}, ctx, options);
    }

    render() {
        let parts  = [
            this.errorGroup.render(),
            super.render(),
            this.errorGroupEnd.render(),
        ];
        return parts.join('\n');
    }
}

module.exports = InputDateGroup;

'use strict';

const debug = require('debug')('hmpo:fields:Select');
const InputDate = require('./input-date');
const ErrorGroup = require('./error-group');

class InputDateGroup extends InputDate {
    constructor(content, ctx, options) {
        debug('constructor');
        super(content, ctx, options);

        this.errorGroup = new ErrorGroup(Object.assign({
            groupClassName: 'form-date',
            legendClassName: 'form-label-bold'
        }, this.field), ctx, options);

        this.errorGroupEnd = new ErrorGroup.End({}, ctx, options);
    }

    render() {
        let parts  = [
            this.errorGroup.render(),
            super.render(),
            this.errorGroupEnd.render(),
        ];
        return parts.join('\n');
    }
}

module.exports = InputDateGroup;

Allow overriding of date error fields


Spotbox.Controllers.Player = Ember.Object.create({
  content: null,
  init: function() {
    var self = this;
    Spotbox.socket.on("tracks/current", function(track) {
      self.set("content", Spotbox.Models.Track.create(track));
    });

    Spotbox.socket.on("tracks/current/progress", function(data) {
      var track = self.get("content");
      track.set("progress", data.progress);
    });
  },

  play: function() {
    Spotbox.socket.emit("player", "play");
  },

  stop: function() {
    Spotbox.socket.emit("player", "stop");
  },

  next: function() {
    Spotbox.socket.emit("player", "next");
  }
});

Spotbox.Controllers.Player = Ember.Object.create({
  content: null,
  init: function() {
    var self = this;
    Spotbox.socket.on("tracks/current", function(track) {
      self.set("content", Spotbox.Models.Track.create(track));
    });

    Spotbox.socket.on("tracks/current/progress", function(data) {
      var track = self.get("content");
      track.set("progress", data.progress);
    });
  },

  play: function() {
    Spotbox.socket.emit("player", "play");
  },

  stop: function() {
    Spotbox.socket.emit("player", "stop");
    var track = this.get("content");
    track.set("progress", 0);
  },

  next: function() {
    Spotbox.socket.emit("player", "next");
  }
});

Set progress back to zero when stopping