/**
 * External dependencies
 */
import { render } from '@testing-library/react';
import PropTypes from 'prop-types';

/**
 * WordPress dependencies
 */
import { Fragment } from '@wordpress/element';

const TestProviders = ( { children } ) => {
	return (
		<Fragment>
			{ children }
		</Fragment>
	);
};

TestProviders.defaultProps = {
	children: undefined,
};

TestProviders.propTypes = {
	children: PropTypes.node,
};

// custom render with the test provider component
const customRender = ( ui, options ) => {
	return render( ui, { wrapper: TestProviders, ...options } );
};

// Export our own test utils from this file.
export * from 'tests/js/utils';

// Export @testing-library/react as normal.
export * from '@testing-library/react';

// Override @testing-library/react's render method with our own.
export { customRender as render };

/**
 * External dependencies
 */
import { render } from '@testing-library/react';
import PropTypes from 'prop-types';

/**
 * WordPress dependencies
 */
import { RegistryProvider } from '@wordpress/data';

/**
 * Internal dependencies
 */
import { createTestRegistry } from 'tests/js/utils';

// Override `@testing-library/react`'s render method with one that includes
// our data store.
const customRender = ( children, options = {} ) => {
	const {
		registry = createTestRegistry(),
		...renderOptions
	} = options;
	return {
		...render( (
			<RegistryProvider value={ registry }>
				{ children }
			</RegistryProvider>
		), renderOptions ),
		registry,
	};
};

// Export our own test utils from this file.
export * from 'tests/js/utils';

// Export @testing-library/react as normal.
export * from '@testing-library/react';

// Override @testing-library/react's render method with our own.
export { customRender as render };

Add a Provider/registry to test render method.


var _ = require('lodash'),
    async = require('async'),
    checkForFiling = require('./check'),
    request = require('request'),
    models = require('../../models'),
    parser = require('rss-parser');

var interval = 60000;

function queueFilingsToCheck() {
    console.log('checking RSS');

    parser.parseURL('http://efilingapps.fec.gov/rss/generate?preDefinedFilingType=ALL', function(err, parsed) {
        if (!err && parsed && parsed.feed && parsed.feed.entries) {
            var newFilings = parsed.feed.entries.map(function (filing) {
                return parseInt(filing.link.replace('http://docquery.fec.gov/dcdev/posted/','').replace('.fec',''));
            });

            models.fec_filing.findAll({
                    attributes: ['filing_id'],
                    where: {
                        filing_id: {
                            gte: _.min(newFilings)
                        }
                    }
                })
                .then(function(filings) {
                    filings = filings.map(function(filing) {
                        return filing.filing_id;
                    });

                    async.mapSeries(_.difference(newFilings,filings), checkForFiling, function () {
                        console.log('waiting');
                        setTimeout(queueFilingsToCheck,interval);
                    });

                });

        }
        else {
            console.error(error);

            console.log('waiting');
            setTimeout(queueFilingsToCheck,interval);
        }
    });
}

queueFilingsToCheck();

var _ = require('lodash'),
    async = require('async'),
    checkForFiling = require('./check'),
    request = require('request'),
    models = require('../../models'),
    parser = require('rss-parser');

// var interval = 60000;

function queueFilingsToCheck() {
    console.log('checking RSS');

    parser.parseURL('http://efilingapps.fec.gov/rss/generate?preDefinedFilingType=ALL', function(err, parsed) {
        if (!err && parsed && parsed.feed && parsed.feed.entries) {
            var newFilings = parsed.feed.entries.map(function (filing) {
                return parseInt(filing.link.replace('http://docquery.fec.gov/dcdev/posted/','').replace('.fec',''));
            });

            models.fec_filing.findAll({
                    attributes: ['filing_id'],
                    where: {
                        filing_id: {
                            gte: _.min(newFilings)
                        }
                    }
                })
                .then(function(filings) {
                    filings = filings.map(function(filing) {
                        return filing.filing_id;
                    });

                    async.mapSeries(_.difference(newFilings,filings), checkForFiling, function () {
                        console.log('done');
                        // setTimeout(queueFilingsToCheck,interval);
                    });

                });

        }
        else {
            console.error(error);

            console.log('done');
            // setTimeout(queueFilingsToCheck,interval);
        }
    });
}

queueFilingsToCheck();

Remove Node RSS restart interval in favor of kubernetes cron job


import { Command } from 'substance'

/*
  This command intended to switch view and scroll to the selected node.
  Command state becoming active only for certain type of custom selection,
  e.g. if you want to use it, provide config with selectionType property.
*/
export default class EditEntityCommand extends Command {
  getCommandState (params, context) {
    let sel = params.selection
    let newState = {
      disabled: true
    }

    if (sel.isCustomSelection()) {
      if (sel.customType === this.config.selectionType) {
        newState.disabled = false
        newState.nodeId = sel.nodeId
      }
    }

    return newState
  }

  execute (params, context) {
    const appState = context.appState
    const viewName = appState.get('viewName')
    if (viewName !== 'metadata') {
      const sel = params.selection
      context.editor.send('updateViewName', 'metadata')
      context.articlePanel.refs.content.send('scrollTo', { nodeId: sel.nodeId })
    }
  }
}

import { Command } from 'substance'

/*
  This command intended to switch view and scroll to the selected node.
  Command state becoming active only for certain type of custom selection,
  e.g. if you want to use it, provide config with selectionType property.
*/
export default class EditEntityCommand extends Command {
  getCommandState (params, context) {
    let sel = params.selection
    let newState = {
      disabled: true
    }

    if (sel.isCustomSelection()) {
      if (sel.customType === this.config.selectionType) {
        newState.disabled = false
        newState.nodeId = sel.nodeId
      }
    }

    return newState
  }

  execute (params, context) {
    const appState = context.appState
    const viewName = appState.get('viewName')
    if (viewName !== 'metadata') {
      const sel = params.selection
      const nodeId = sel.nodeId
      context.editor.send('updateViewName', 'metadata')
      // HACK: using the ArticlePanel instance to get to the current editor
      // so that we can dispatch 'executeCommand'
      let editor = context.articlePanel.refs.content
      editor.send('scrollTo', { nodeId })
      // HACK: this is a mess because context.api is a different instance after
      // switching to metadata view
      // TODO: we should extend ArticleAPI to allow for this
      editor.api.selectFirstRequiredPropertyOfMetadataCard(nodeId)
    }
  }
}

Set selection when editing an entity from manuscript view.


function(newDoc, oldDoc, userCtx) {
  if (oldDoc){
    if (userCtx.roles.indexOf("_admin") == -1) {
      throw({forbidden: "Only database admins are allowed to update pastes/comments"});
    }
  } else {
    // verify well-formedness

    var malformed = false;
    if (typeof(newDoc.data) != "string") {
      malformed = true;
    } else if (["paste", "comment"].indexOf(newDoc.type) == -1) {
      malformed = true;
    }

    if (malformed == true) {
      throw({forbidden: "The document is malformed. Not accepting this."});
    }
  }
}

function(newDoc, oldDoc, userCtx) {
  if (oldDoc){
    if (userCtx.roles.indexOf("_admin") == -1) {
      throw({forbidden: "Only database admins are allowed to update pastes/comments"});
    }
  } else {

    // verify well-formedness
    var malformed = false;
    if (typeof(newDoc.data) != "string") {
      malformed = true;
    } else if (["paste", "comment"].indexOf(newDoc.type) == -1) {
      malformed = true;
    }

    if (malformed == true) {
      throw({forbidden: "The document is malformed. Not accepting this."});
    }

    // verify size
    if (newDoc.data.length * 2 >= 2000000) {
      throw({forbidden: "Paste is limited to 2MB of encrypted data"});
    }
  }
}

Verify that paste size is < 2MB


angular.module('app.services')
.provider('formElementProvider', function() {
	'use strict';

	var templates = {
		'String': function(field) {
			if(field && field.validation.allowedHtml) {
				return 'partials/inputs/tinymce-input.html';
			} else if(field && field.validation.oneOf) {
				return 'partials/inputs/enum-input.html';
			} else {
				return 'partials/inputs/default-input.html';
			}
		},
		'Link': function(field) {
			if(field.kind.isArray) {
				return 'partials/inputs/link-multiple-input.html';
			}
			else {
				return 'partials/inputs/link-input.html';
			}
		},
		'Date': 'partials/inputs/date-input.html'
	};

	return {
		$get: function() {
			return {
				getTemplateUrl: function(field) {
					var t = templates[field.kind.name];
					if(_.isFunction(t)) {
						return t(field);
					} else {
						return t || templates['String']();
					}
				}
			};
		}
	};
});

angular.module('app.services')
.provider('formElementProvider', function() {
	'use strict';

	var templates = {
		'String': function(field) {
			if(field && field.validation.allowedHtml) {
				return 'partials/inputs/tinymce-input.html';
			} else if(field && field.validation.oneOf) {
				return 'partials/inputs/enum-input.html';
			} else {
				return 'partials/inputs/default-input.html';
			}
		},
		'Link': function(field) {
			if(field.kind.isArray) {
				return 'partials/inputs/link-multiple-input.html';
			}
			else {
				return 'partials/inputs/link-input.html';
			}
		},
		'Date': 'partials/inputs/date-input.html',
		'Boolean': 'partials/inputs/boolean-input.html'
	};

	return {
		$get: function() {
			return {
				getTemplateUrl: function(field) {
					var t = templates[field.kind.name];
					if(_.isFunction(t)) {
						return t(field);
					} else {
						return t || templates['String']();
					}
				}
			};
		}
	};
});

Add boolean form element provider field


define(['backbone', 'common/utils/padTwo'], function(Backbone, padTwo) {
  'use strict';

  // Convert exam in Unix time to 12-hour date/time format. We add 8 hours to
  // the UTC time, then use the getUTC* methods so that they will correspond to
  // Singapore time regardless of the local time zone.
  var examStr = function(exam) {
    if (exam) {
      var date = new Date(exam + 288e5);
      var hours = date.getUTCHours();
      return padTwo(date.getUTCDate()) +
        '-' + padTwo(date.getUTCMonth() + 1) +
        '-' + date.getUTCFullYear() +
        ' ' + (hours % 12 || 12) +
        ':' + padTwo(date.getUTCMinutes()) +
        ' ' + (hours < 12 ? 'AM' : 'PM');
    }
    return null;
  };

  return Backbone.Model.extend({
    initialize: function() {
      this.set('examStr', examStr(this.get('exam')));
    }
  });
});

define(['backbone', 'common/utils/padTwo'], function(Backbone, padTwo) {
  'use strict';

  // Convert exam in Unix time to 12-hour date/time format. We add 8 hours to
  // the UTC time, then use the getUTC* methods so that they will correspond to
  // Singapore time regardless of the local time zone.
  var examStr = function(exam) {
    if (exam) {
      var date = new Date(exam + 288e5);
      var hours = date.getUTCHours();
      return padTwo(date.getUTCDate()) +
        '-' + padTwo(date.getUTCMonth() + 1) +
        '-' + date.getUTCFullYear() +
        ' ' + (hours % 12 || 12) +
        ':' + padTwo(date.getUTCMinutes()) +
        ' ' + (hours < 12 ? 'AM' : 'PM');
    }
    return null;
  };

  return Backbone.Model.extend({
    idAttribute: 'code',
    initialize: function() {
      this.set('examStr', examStr(this.get('exam')));
    }
  });
});

Use code as id attribute for module model


/**
 * Don't do "x <= y", just do "x < (y + 1)".
 * @param {number} x
 * @param {number} y
 * @see {@link http://redd.it/3rc2rm}
 */
var isXSmallerThanOrEqualsToY = function(x, y) {
    if (x < (y + 1) ? true : false) {
        return true;
    } else {
        return false;
    }
};

/**
 * Readability at its finest.
 * @param {number} x
 * @param {number} y
 * @see {@link isXSmallerThanOrEqualToY} for further information.
 */
var isXBiggerThanOrEqualsToY = function(x, y) {
    if (x > (y + 1) ? true : false) {
        return true;
    } else {
        return false;
    }
};

anything.prototype.isXSmallerThanOrEqualToY = isXSmallerThanOrEqualToY;
anything.prototype.isXBiggerThanOrEqualToY = isXBiggerThanOrEqualToY;

/**
 * Don't do "x <= y", just do "x < (y + 1)".
 * @param {number} x
 * @param {number} y
 * @see {@link http://redd.it/3rc2rm}
 */
var isXSmallerThanOrEqualsToY = function(x, y) {
    if (x < (y + 1) ? true : false) {
        return true;
    } else {
        return false;
    }
};

/**
 * Readability at its finest.
 * @param {number} x
 * @param {number} y
 * @see {@link isXSmallerThanOrEqualToY} for further information.
 */
var isXBiggerThanOrEqualsToY = function(x, y) {
    if (x > (y + 1) ? true : false) {
        return true;
    } else {
        return false;
    }
};

anything.prototype.isXSmallerThanOrEqualsToY = isXSmallerThanOrEqualsToY;
anything.prototype.isXBiggerThanOrEqualsToY = isXBiggerThanOrEqualsToY;

Fix typo with function added to prototype


/*
 * Movable Type DataAPI SDK for JavaScript v1
 * Copyright (c) 2013 Six Apart, Ltd.
 * This program is distributed under the terms of the MIT license.
 *
 * Includes jQuery JavaScript Library to serialize a HTMLFormElement
 * http://jquery.com/
 * Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors
 * Released under the MIT license
 * http://jquery.org/license
 */

;(function(window, undefined) {

"use strict";

/*
 * Movable Type DataAPI SDK for JavaScript v1
 * https://github.com/movabletype/mt-data-api-sdk-js
 * Copyright (c) 2013 Six Apart, Ltd.
 * This program is distributed under the terms of the MIT license.
 *
 * Includes jQuery JavaScript Library to serialize a HTMLFormElement
 * http://jquery.com/
 * Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors
 * Released under the MIT license
 * http://jquery.org/license
 */

;(function(window, undefined) {

"use strict";

Insert the project URL to script header.


import Analytics from '../api/Analytics';
import LocalStorage from '../storage/LocalStorage';
import ApolloClient from '../api/ApolloClient';
import Auth0Api from '../api/Auth0Api';

export default {
  setSession(session) {
    return async (dispatch) => {
      await LocalStorage.saveSessionAsync(session);
      return dispatch({
        type: 'setSession',
        payload: session,
      });
    };
  },

  signOut(options = {}) {
    return async (dispatch) => {
      const { shouldResetApolloStore } = options.shouldResetApolloStore || true;
      const session = await LocalStorage.getSessionAsync();
      if (session) {
        await Auth0Api.signOutAsync(session.sessionSecret);
      }
      await LocalStorage.removeSessionAsync();
      await LocalStorage.clearHistoryAsync();

      Analytics.track(Analytics.events.USER_LOGGED_OUT);
      Analytics.identify(null);
      if (shouldResetApolloStore) {
        ApolloClient.resetStore();
      }

      return dispatch({
        type: 'signOut',
        payload: null,
      });
    };
  },
};

import Analytics from '../api/Analytics';
import LocalStorage from '../storage/LocalStorage';
import ApolloClient from '../api/ApolloClient';
import Auth0Api from '../api/Auth0Api';

export default {
  setSession(session) {
    return async (dispatch) => {
      await LocalStorage.saveSessionAsync(session);
      return dispatch({
        type: 'setSession',
        payload: session,
      });
    };
  },

  signOut(options = {}) {
    return async (dispatch) => {
      const shouldResetApolloStore = options.shouldResetApolloStore || true;
      const session = await LocalStorage.getSessionAsync();
      if (session) {
        await Auth0Api.signOutAsync(session.sessionSecret);
      }
      await LocalStorage.removeSessionAsync();
      await LocalStorage.clearHistoryAsync();

      Analytics.track(Analytics.events.USER_LOGGED_OUT);
      Analytics.identify(null);
      if (shouldResetApolloStore) {
        ApolloClient.resetStore();
      }

      return dispatch({
        type: 'signOut',
        payload: null,
      });
    };
  },
};

Reset apollo store when logging out


const axios = require(`axios`);

export default itemIdentifier => axios.get(`items`, {
  baseURL: `https://track.bpost.be/btr/api/`,
  responseType: `json`,
  params: { itemIdentifier },
}).then(res => res.data.items[0])
.catch(e => e);

import axios from 'axios';

export default itemIdentifier => axios.get(`items`, {
  baseURL: `https://track.bpost.be/btr/api/`,
  responseType: `json`,
  params: { itemIdentifier },
}).then(res => res.data.items[0])
.catch(e => e);

Fix axios import for UMD build


const path = require('path');
const webpack = require('webpack');

const distPath = path.join(__dirname, 'dist');
const srcPath = path.join(__dirname, 'src');
const env = process.env.NODE_ENV || 'development';
const minified = env === 'development' ? '' : '.min';
const exclude = [/node_modules/];

const config = {
  context: srcPath,

  entry: {
    reduxReuse: ['reduxReuse'],
  },

  output: {
    path: distPath,
    filename: `[name]${minified}.js`,
    library: ['reduxReuse', '[name]'],
    libraryTarget: 'umd',
  },

  resolve: {
    root: [srcPath]
  },

  module: {
    preloaders: [
      {
        test: /\.js$/,
        loader: 'eslint',
        exclude
      }
    ],

    loaders: [
      {
        test: /\.js$/,
        loader: 'babel',
        exclude
      }
    ]
  },
};

module.exports = [
  config,
  {
    ...config,
    entry: { reduxReuse: 'reduxReuse' },
    output: {
      ...config.output,
      filename: `reduxReuse${minified}.js`,
      library: 'reduxReuse',
    },
  }
];

const path = require('path');
const webpack = require('webpack');

const distPath = path.join(__dirname, 'dist');
const srcPath = path.join(__dirname, 'src');
const env = process.env.NODE_ENV || 'development';
const minified = env === 'development' ? '' : '.min';
const exclude = [/node_modules/];

const config = {
  context: srcPath,

  entry: { reduxReuse: 'reduxReuse' },

  output: {
    path: distPath,
    filename: `reduxReuse${minified}.js`,
    library: 'reduxReuse',
    libraryTarget: 'umd',
  },

  resolve: {
    root: [srcPath]
  },

  module: {
    preloaders: [
      {
        test: /\.js$/,
        loader: 'eslint',
        exclude
      }
    ],

    loaders: [
      {
        test: /\.js$/,
        loader: 'babel',
        exclude
      }
    ]
  },
};

module.exports = config;

Fix webpack config to ensure it outputs a correct umd bundle


// Webpack configuration to use with the build task.

var webpack = require('webpack');

module.exports = {

  // Create also a "lib" chunk with common libraries, e.g. react.
  entry: {
    lib: ['react'],
    main: './app/client/main'
  },

  output: {
    path: './build/app/public/js',
    publicPath: '/js/',
    filename: 'main.js'
  },

  plugins: [
    new webpack.DefinePlugin({
      "process.env": {
        "NODE_ENV": JSON.stringify("production") // This has effect on the react lib size
      }
    }),
    new webpack.optimize.CommonsChunkPlugin('lib', 'lib.js'),
    new webpack.optimize.DedupePlugin(),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      }
    })
  ],

  resolve: {
    extensions: ['', '.js', '.jsx']
  },

  module: {
    loaders: [{
      test: /\.jsx$/,
      loaders: ['jsx']
    }]
  },

  externals: {
    // Showdown is not is node_modules,
    // so we tell Webpack to resolve it to a global
    // 'showdown': 'window.Showdown'
  }
};

// Webpack configuration to use with the build task.

var webpack = require('webpack');

module.exports = {

  // Create also a "lib" chunk with common libraries, e.g. react.
  entry: {
    lib: ['react', 'react-router'],
    main: './app/client/main'
  },

  output: {
    path: './build/app/public/js',
    publicPath: '/js/',
    filename: 'main.js'
  },

  plugins: [
    new webpack.DefinePlugin({
      "process.env": {
        "NODE_ENV": JSON.stringify("production") // This has effect on the react lib size
      }
    }),
    new webpack.optimize.CommonsChunkPlugin('lib', 'lib.js'),
    new webpack.optimize.DedupePlugin(),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      }
    })
  ],

  resolve: {
    extensions: ['', '.js', '.jsx']
  },

  module: {
    loaders: [{
      test: /\.jsx$/, loaders: ['jsx']
    }]
  },

  externals: {}
};

Add react-router to the library


(function(Modules) {
  "use strict";

  var queues = {};
  var dd = new diffDOM();
  var timer;

  var getRenderer = $component => response => function() {
    var component = $component.get(0);
    var updated = $(response[$component.data('key')]).get(0);
    var diff = dd.diff(component, updated);
    dd.apply(
      component, diff
    );
  };

  var getQueue = resource => (
    queues[resource] = queues[resource] || []
  );

  var flushQueue = function(queue, response) {
    while(queue.length) queue.shift()(response);
  };

  var clearQueue = queue => (queue.length = 0);

  var poll = function(renderer, resource, queue, interval) {

    if (queue.push(renderer) === 1) $.ajax(
      resource
    ).done(
      response => flushQueue(queue, response)
    ).fail(
      () => clearQueue(queue)
    );

    timer = setTimeout(
      () => poll(...arguments), interval
    );

  };

  Modules.UpdateContent = function() {

    this.start = component => poll(
      getRenderer($(component)),
      $(component).data('resource'),
      getQueue($(component).data('resource')),
      ($(component).data('interval-seconds') || 1.5) * 1000
    );

  };

})(window.GOVUK.Modules);

(function(Modules) {
  "use strict";

  var queues = {};
  var dd = new diffDOM();

  var getRenderer = $component => response => dd.apply(
    $component.get(0),
    dd.diff($component.get(0), $(response[$component.data('key')]).get(0))
  );

  var getQueue = resource => (
    queues[resource] = queues[resource] || []
  );

  var flushQueue = function(queue, response) {
    while(queue.length) queue.shift()(response);
  };

  var clearQueue = queue => (queue.length = 0);

  var poll = function(renderer, resource, queue, interval) {

    if (queue.push(renderer) === 1) $.ajax(
      resource
    ).done(
      response => flushQueue(queue, response)
    ).fail(
      () => clearQueue(queue)
    );

    setTimeout(
      () => poll(...arguments), interval
    );

  };

  Modules.UpdateContent = function() {

    this.start = component => poll(
      getRenderer($(component)),
      $(component).data('resource'),
      getQueue($(component).data('resource')),
      ($(component).data('interval-seconds') || 1.5) * 1000
    );

  };

})(window.GOVUK.Modules);

Revert "Stop AJAX updater consuming ever-increasing memory"


/* eslint-disable */
import React from 'react';
import { configure, addDecorator, addParameters } from '@storybook/react';
import { withNotes } from '@storybook/addon-notes';
import { withKnobs } from '@storybook/addon-knobs';
import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport';

import DataUtil from '../src/utils/DataUtil';

addParameters({
  viewport: {
    viewports: {
      ...INITIAL_VIEWPORTS,
    },
  },
});

addDecorator(withNotes);
addDecorator(withKnobs);

let data;
try {
  // eslint-disable-next-line global-require, import/no-unresolved
  data = require('../local/blip-input.json');
} catch (e) {
  data = [];
}

const patientId = 'abc123';
const dataUtil = new DataUtil();
dataUtil.addData(data, patientId);

const props = {
  dataUtil,
  patientId,
};

addDecorator(storyFn => (
  <div>{storyFn(props)}</div>
));

function loadStories() {
  const context = require.context('../stories', true, /.js$/); // Load .js files in /storybook
  context.keys().forEach(context);
}

configure(loadStories, module);

/* eslint-disable */
import React from 'react';
import { configure, addDecorator, addParameters } from '@storybook/react';
import { withNotes } from '@storybook/addon-notes';
import { withKnobs } from '@storybook/addon-knobs';
import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport';

import DataUtil from '../src/utils/DataUtil';

addParameters({
  viewport: {
    viewports: {
      ...INITIAL_VIEWPORTS,
    },
  },
});

addDecorator(withNotes);
addDecorator(withKnobs);

let data;
try {
  // eslint-disable-next-line global-require, import/no-unresolved
  data = _.flatten(_.map(require('../local/data.json'), v => v.data));
} catch (e) {
  data = { data: [] };
}

const patientId = 'abc123';
const dataUtil = new DataUtil();
dataUtil.addData(data, patientId);

const props = {
  dataUtil,
  patientId,
};

addDecorator(storyFn => (
  <div>{storyFn(props)}</div>
));

function loadStories() {
  const context = require.context('../stories', true, /.js$/); // Load .js files in /storybook
  context.keys().forEach(context);
}

configure(loadStories, module);

Update data stories to use datasets from private export tool


var path = require('path');

module.exports = {
  description: 'Initialize files needed for typescript compilation',

  files: function() {
    return [
      path.join(this.path, 'files', 'tsconfig.json'),
      path.join(this.path, 'files', 'app', 'config', 'environment.d.ts')
      ];
  },

   mapFile: function() {
    var result = this._super.mapFile.apply(this, arguments);
    if (result.indexOf('/tsconfig.json')>-1) {
      return 'tsconfig.json';
    } else if (result.indexOf('/app/')>-1) {
      var pos = result.indexOf('/app/');
      return result.substring(pos+1);
    }
  },

  normalizeEntityName: function() {
    // Entity name is optional right now, creating this hook avoids an error.
  },

  beforeInstall: function() {
    return this.addPackagesToProject([
      { name: 'typescript', target: '^2.1' },
      { name: '@types/ember', target: '^2.7.34' }
    ]);
  }
}

var path = require('path');

module.exports = {
  description: 'Initialize files needed for typescript compilation',

  files: function() {
    return [
      path.join(this.path, 'files', 'tsconfig.json'),
      path.join(this.path, 'files', 'app', 'config', 'environment.d.ts')
      ];
  },

   mapFile: function() {
    var result = this._super.mapFile.apply(this, arguments);
    if (result.indexOf('/tsconfig.json')>-1) {
      return 'tsconfig.json';
    } else if (result.indexOf('/app/')>-1) {
      var pos = result.indexOf('/app/');
      return result.substring(pos+1);
    }
  },

  normalizeEntityName: function() {
    // Entity name is optional right now, creating this hook avoids an error.
  },

  afterInstall: function() {
    return this.addPackagesToProject([
      { name: 'typescript', target: '^2.1' },
      { name: '@types/ember', target: '^2.7.34' }
    ]);
  }
}

Revert afterInstall -> beforeInstall change. afterInstall is fine.


/* globals module */
module.exports = {
  afterInstall: function() {
    var _self = this;
    return this.addBowerPackagesToProject([
      { name: 'localforage', target: '1.3.3' }
    ]).then(function() {
      return _self.addAddonsToProject({
        packages: [
          'https://github.com/Flexberry/ember-localforage-adapter.git',
          { name: 'ember-browserify', target: '1.1.9' }
        ]
      });
    }).then(function () {
      return _self.addPackagesToProject([
        { name: 'dexie', target: '1.3.6' }
      ]);
    });
  },

  normalizeEntityName: function() {}
};

/* globals module */
module.exports = {
  afterInstall: function() {
    var _this = this;
    return this.addBowerPackagesToProject([
      { name: 'localforage', target: '1.3.3' }
    ]).then(function() {
      return _this.addAddonsToProject({
        packages: [
          'https://github.com/Flexberry/ember-localforage-adapter.git',
          { name: 'ember-browserify', target: '1.1.9' }
        ]
      });
    }).then(function () {
      return _this.addPackagesToProject([
        { name: 'dexie', target: '1.3.6' }
      ]);
    });
  },

  normalizeEntityName: function() {}
};

Fix JSCS in default blueprint


var _ = require('lodash');
var Authority = require('../authority');

var defaultOptions = {
	assignField: 'auth'
};

function identify(auth, opts) {
	opts = _.defaults(opts || {}, defaultOptions);

	if (!(auth instanceof Authority))
		throw new TypeError;

	return function(req, res, next) {
		if (req[opts.assignField] !== undefined)
			return next();
		getUserAndRole(auth, req, function(err, authInfo) {
			if (err)
				return next(err);
			req[opts.assignField] = authInfo;
			next();
		});
	};
}

function getUserAndRole(auth, req, cb) {
	auth.getUser(req, function(err, user) {
		if (err || !user)
			return cb(err, null);
		user.getRole(function(err, role) {
			if (err || !role)
				return cb(err, null);
			cb(null, { user: user, role: role });
		});
	});
}

module.exports = exports = identify;
exports.defaultOptions = defaultOptions;

var _ = require('lodash');

var defaultOptions = {
	assignField: 'auth'
};

function validAuthority(authority) {
	return authority.getUser !== undefined;
}

function identify(auth, opts) {
	opts = _.defaults(opts || {}, defaultOptions);

	if (!validAuthority(auth))
		throw new TypeError;

	return function(req, res, next) {
		if (req[opts.assignField] !== undefined)
			return next();
		getUserAndRole(auth, req, function(err, authInfo) {
			if (err)
				return next(err);
			req[opts.assignField] = authInfo;
			next();
		});
	};
}

function getUserAndRole(auth, req, cb) {
	auth.getUser(req, function(err, user) {
		if (err || !user)
			return cb(err, null);
		user.getRole(function(err, role) {
			if (err || !role)
				return cb(err, null);
			cb(null, { user: user, role: role });
		});
	});
}

module.exports = exports = identify;
exports.defaultOptions = defaultOptions;

Use duck-typing instead of strong type checking


// Page modules
var FastClick = require('fastclick')
var nav = require('./nav.js')
var socialShare = require('./social-share')
var analytics = require('./analytics')

nav.init()
analytics.init()
FastClick.attach(document.body)

// Load and process data
require.ensure(['./api', './get-api-data', './template-render', 'spin.js', 'lodash'], function (require) {
  var apiRoutes = require('./api')
  var getApiData = require('./get-api-data')
  var templating = require('./template-render')
  var Spinner = require('spin.js')
  var _ = require('lodash')

  // Spinner
  var spin = document.getElementById('spin')
  var loading = new Spinner().spin(spin)

  // Get API data using promise
  getApiData.data(apiRoutes.needs)
    .then(function (result) {
      var needsFromApi = result.data
      _.each(needsFromApi, function(need) {
        need.link = "mailto:givehelp@streetsupport.net?subject=" + need.serviceProviderId + " need: " + need.description
      })
      var theData = { 'needs': needsFromApi }
      var callback = function () {
        loading.stop()
        socialShare.init()
      }

      templating.renderTemplate('js-need-list-tpl', theData, 'js-need-list-output', callback)
    })
})

// Page modules
var FastClick = require('fastclick')
var nav = require('./nav.js')
var socialShare = require('./social-share')
var analytics = require('./analytics')

nav.init()
analytics.init()
FastClick.attach(document.body)

// Load and process data
require.ensure(['./api', './get-api-data', './template-render', 'spin.js', 'lodash'], function (require) {
  var apiRoutes = require('./api')
  var getApiData = require('./get-api-data')
  var templating = require('./template-render')
  var Spinner = require('spin.js')
  var _ = require('lodash')

  // Spinner
  var spin = document.getElementById('spin')
  var loading = new Spinner().spin(spin)

  // Get API data using promise
  getApiData.data(apiRoutes.needs)
    .then(function (result) {
      var needsFromApi = result.data
      _.each(needsFromApi, function(need) {
        need.link = "give-item-submit-details.html?needId=" + need.id
      })
      var theData = { 'needs': needsFromApi }
      var callback = function () {
        loading.stop()
        socialShare.init()
      }

      templating.renderTemplate('js-need-list-tpl', theData, 'js-need-list-output', callback)
    })
})

Revert "point more info link to mailto"


import initialState from './initialState';
import {
    BOARD_LIST_LOADED,
    BOARD_LIST_REQUESTED,
} from '../constants'

// ADD_FAVOURITE_BOARD
// TODO: Add boardList invalidation + favourites

export default function (state=initialState.boardList, action) {
    switch (action.type) {

        case BOARD_LIST_LOADED:
            return Object.assign({}, state, {
                [action.provider]: action.payload
            })

        default:
            return state
    }
}

import initialState from './initialState';
import {
    BOARD_LIST_LOADED,
    BOARD_LIST_REQUESTED,
    BOARD_LIST_INVALIDATED,
    BOARD_LIST_ADD_FAVOURITE,
    BOARD_LIST_REMOVE_FAVOURITE,
} from '../constants'

export default function (state=initialState.boardList, action) {
    switch (action.type) {

        case BOARD_LIST_REQUESTED:
            return Object.assign({}, state, {
                didInvalidate: false
            })

        case BOARD_LIST_INVALIDATED:
            return Object.assign({}, state, {
                didInvalidate: true
            })

        case BOARD_LIST_LOADED:
            return Object.assign({}, state, {
                [action.provider]: action.payload
            })

        case BOARD_LIST_ADD_FAVOURITE:
            return Object.assign({}, state, {
                favourites: [...state.favourites,
                    action.payload
                ]
            })

        case BOARD_LIST_REMOVE_FAVOURITE:
            return Object.assign({}, state, {
                favourites: state.favourites.filter(
                    board =>
                        board.boardID !== action.payload.boardID &&
                        board.provider === action.payload.provider
                )
            })

        default:
            return state
    }
}

Add reducer for BoardList favourites + request invalidation


import React from 'react';
import PropTypes from 'prop-types';

import ModelObserver from '../models/model-observer';

export default class ObserveModel extends React.Component {
  static propTypes = {
    model: PropTypes.shape({
      onDidUpdate: PropTypes.func.isRequired,
    }),
    fetchData: PropTypes.func.isRequired,
    children: PropTypes.func.isRequired,
  }

  constructor(props, context) {
    super(props, context);

    this.state = {data: null};
    this.modelObserver = new ModelObserver({fetchData: this.fetchData, didUpdate: this.didUpdate});
  }

  componentWillMount() {
    this.mounted = true;
    this.modelObserver.setActiveModel(this.props.model);
  }

  componentWillReceiveProps(nextProps) {
    this.modelObserver.setActiveModel(nextProps.model);
  }

  fetchData = model => this.props.fetchData(model);

  didUpdate = () => {
    if (this.mounted) {
      const data = this.modelObserver.getActiveModelData();
      this.setState({data});
    }
  }

  render() {
    return this.props.children(this.state.data);
  }

  componentWillUnmount() {
    this.mounted = false;
    this.modelObserver.destroy();
  }
}

import React from 'react';
import PropTypes from 'prop-types';

import ModelObserver from '../models/model-observer';

export default class ObserveModel extends React.Component {
  static propTypes = {
    model: PropTypes.shape({
      onDidUpdate: PropTypes.func.isRequired,
    }),
    fetchData: PropTypes.func.isRequired,
    children: PropTypes.func.isRequired,
  }

  constructor(props, context) {
    super(props, context);

    this.state = {data: null};
    this.modelObserver = new ModelObserver({fetchData: this.fetchData, didUpdate: this.didUpdate});
  }

  componentDidMount() {
    this.mounted = true;
    this.modelObserver.setActiveModel(this.props.model);
  }

  componentDidUpdate() {
    this.modelObserver.setActiveModel(this.props.model);
  }

  fetchData = model => this.props.fetchData(model);

  didUpdate = () => {
    if (this.mounted) {
      const data = this.modelObserver.getActiveModelData();
      this.setState({data});
    }
  }

  render() {
    return this.props.children(this.state.data);
  }

  componentWillUnmount() {
    this.mounted = false;
    this.modelObserver.destroy();
  }
}

Remove deprecated lifecycle methods from ObserveModel


var chai = require('chai')
var mathModule = require('./math-module')

var expect = chai.expect

describe('math module', function () {
  describe('add', function () {
    it('should correctly add positive numbers', function () {
      var result = mathModule.add(3, 2)

      expect(result).to.equal(5)
    })
  })
})

var chai = require('chai')
var mathModule = require('./math-module')

var expect = chai.expect

describe('math module', function () {
  describe('add', function () {
    it('should correctly add positive numbers', function () {
      var result = mathModule.add(3, 2)

      expect(result).to.equal(5)
    })

    it('should correctly add negative numbers', function () {
      var result = mathModule.add(-3, -5)

      expect(result).to.equal(-8)
    })
  })
})

Add additional test for add method


import DS from 'ember-data';

export default DS.RESTAdapter.extend({
  defaultSerializer: 'endpoints',
  buildURL: function(type, id, record) {
    var url = this._super(type, id, record);
    var includes = [];
    var model = this.container.lookupFactory('model:' + type);
    model.eachRelationship(function(name, descriptor) {
      if (!descriptor.options.async) {
        includes.push(name);
      }
    });

    if (includes.length) {
      return url + '?include=' + includes.join(',');
    }

    return url;
  }
});

import DS from 'ember-data';

export default DS.RESTAdapter.extend({
  defaultSerializer: 'endpoints',
  buildURL: function(type, id, record) {
    var url = this._super(type, id, record);
    var includes = [];
    var model = this.container.lookupFactory('model:' + type);
    model.eachRelationship(function(name, descriptor) {
      if (!descriptor.options.async) {
        includes.push(name);
      }
    });

    if (includes.length) {
      return url + '?include=' + includes.join(',');
    }

    return url;
  },
  // Workaround where REST URLs were getting camelCased
  // https://github.com/ember-cli/ember-cli/issues/2906
  pathForType: function(type) {
    var dasherized = Ember.String.dasherize(type);
    return Ember.String.pluralize(dasherized);
  }
});

Use dasherized urls for path names


var Q = require("q");
var fs = require("fs");
var fsExtra = require("fs-extra");
var Ignore = require("fstream-ignore");

var getFiles = function(path) {
    var d = Q.defer();

    // Our list of files
    var files = [];

    var ig = Ignore({
        path: path,
        ignoreFiles: ['.ignore', '.gitignore']
    });

    // Add extra rules to ignore common folders
    ig.addIgnoreRules([
        '.git/'
    ], '__custom_stuff');

    // Push each file to our list
    ig.on('child', function (c) {
        files.push(
            c.path.substr(c.root.path.length + 1) + (c.props.Directory === true ? '/' : '')
        );
    });

    ig.on('end', function() {
        d.resolve(files);
    });

    ig.on('error', d.reject);

    return d.promise;
}

module.exports = {
    list: getFiles,
    readFile: Q.denodeify(fs.readFile),
    writeFile: Q.denodeify(fs.writeFile),
    mkdirp: Q.denodeify(fsExtra.mkdirp),
    copy: Q.denodeify(fsExtra.copy),
    remove: Q.denodeify(fsExtra.remove),
    symlink: Q.denodeify(fsExtra.symlink),
};

var Q = require("q");
var fs = require("fs");
var fsExtra = require("fs-extra");
var Ignore = require("fstream-ignore");

var getFiles = function(path) {
    var d = Q.defer();

    // Our list of files
    var files = [];

    var ig = Ignore({
        path: path,
        ignoreFiles: ['.ignore', '.gitignore']
    });

    // Add extra rules to ignore common folders
    ig.addIgnoreRules([
        '.git/'
    ], '__custom_stuff');

    // Push each file to our list
    ig.on('child', function (c) {
        files.push(
            c.path.substr(c.root.path.length + 1) + (c.props.Directory === true ? '/' : '')
        );
    });

    ig.on('end', function() {
        // Normalize paths on Windows
        if(process.platform === 'win32') {
            return d.resolve(files.map(function(file) {
                return file.replace(/\\/g, '/');
            }));
        }

        // Simply return paths otherwise
        return d.resolve(files);
    });

    ig.on('error', d.reject);

    return d.promise;
};

module.exports = {
    list: getFiles,
    readFile: Q.denodeify(fs.readFile),
    writeFile: Q.denodeify(fs.writeFile),
    mkdirp: Q.denodeify(fsExtra.mkdirp),
    copy: Q.denodeify(fsExtra.copy),
    remove: Q.denodeify(fsExtra.remove),
    symlink: Q.denodeify(fsExtra.symlink),
};

Normalize paths on Windows to Unix convention


(function (env) {
    "use strict";
    env.ddg_spice_<: $lia_name :> = function(api_result){

        // Validate the response (customize for your Spice)
        if (api_result.error) {
            return Spice.failed('<: $lia_name :>');
        }

        // Render the response
        Spice.add({
            id: "<: $lia_name :>",

            // Customize these properties
            name: "AnswerBar title",
            data: api_result,
            meta: {
                sourceName: "Example.com",
                sourceUrl: 'http://example.com/url/to/details/' + api_result.name
            },
            templates: {
                group: 'your-template-group',
                options: {
                    content: Spice.<: $lia_name :>.content,
                    moreAt: true
                }
            }
        });
    };
}(this));

(function (env) {
    "use strict";
    env.ddg_spice_<: $lia_name :> = function(api_result){

        // Validate the response (customize for your Spice)
        if (!api_result || api_result.error) {
            return Spice.failed('<: $lia_name :>');
        }

        // Render the response
        Spice.add({
            id: "<: $lia_name :>",

            // Customize these properties
            name: "AnswerBar title",
            data: api_result,
            meta: {
                sourceName: "Example.com",
                sourceUrl: 'http://example.com/url/to/details/' + api_result.name
            },
            templates: {
                group: 'your-template-group',
                options: {
                    content: Spice.<: $lia_name :>.content,
                    moreAt: true
                }
            }
        });
    };
}(this));

Add error checking of api_result


import React from 'react'
import { TabNavigator, addNavigationHelpers } from 'react-navigation'
import { connect } from 'react-redux'
import Challenges from './components/Challenges'
import Friends from './components/Friends'
import CameraScreen from './components/CameraScreen'
import PostChallengeScreen from './components/PostChallengeScreen'
import CompleteChallengeScreen from './components/CompleteChallengeScreen'
import LoginScreen from './components/LoginScreen'

export const Navigator = TabNavigator({
  Home: {
    screen: Friends,
  },
  Challenges: {
    screen: Challenges,
  },
  CameraScreen: {
    screen: CameraScreen,
  },
  PostChallenge: {
    screen: PostChallengeScreen,
  },
  CompleteChallenge: {
    screen: CompleteChallengeScreen
  }
}, {
  tabBarComponent: () => null,
  tabBarPosition: 'bottom',
  animationEnabled: true,
  swipeEnabled: true,
  tabBarOptions: {
    activeTintColor: '#e91e63',
  },
});

class App extends React.Component {
  render () {
    const {dispatch, nav} = this.props
    if (this.props.user) {
      return (
        <Navigator
          navigation={addNavigationHelpers({
            dispatch,
            state: nav,
          })}
        />
      )
    } else {
      return (
        <LoginScreen/>
      )
    }
  }
}

function mapStateToProps (state) {
  return {
    nav: state.nav,
    user: state.user,
  }
}

export default connect(
  mapStateToProps,
  null
)(App)

import React from 'react'
import { TabNavigator, addNavigationHelpers } from 'react-navigation'
import { connect } from 'react-redux'
import Challenges from './components/Challenges'
import Friends from './components/Friends'
import CameraScreen from './components/CameraScreen'
import PostChallengeScreen from './components/PostChallengeScreen'
import CompleteChallengeScreen from './components/CompleteChallengeScreen'
import LoginScreen from './components/LoginScreen'

export const Navigator = TabNavigator({
  Home: {
    screen: Friends,
  },
  Challenges: {
    screen: Challenges,
  },
  CameraScreen: {
    screen: CameraScreen,
  },
  PostChallenge: {
    screen: PostChallengeScreen,
  },
  CompleteChallenge: {
    screen: CompleteChallengeScreen
  }
}, {
  tabBarComponent: () => null,
  tabBarPosition: 'bottom',
  animationEnabled: true,
  swipeEnabled: true,
  tabBarOptions: {
    activeTintColor: '#e91e63',
  },
  initialRouteName: 'Challenges',
});

class App extends React.Component {
  render () {
    const {dispatch, nav} = this.props
    if (this.props.user) {
      return (
        <Navigator
          navigation={addNavigationHelpers({
            dispatch,
            state: nav,
          })}
        />
      )
    } else {
      return (
        <LoginScreen/>
      )
    }
  }
}

function mapStateToProps (state) {
  return {
    nav: state.nav,
    user: state.user,
  }
}

export default connect(
  mapStateToProps,
  null
)(App)

Make challenges the initial route


let React = require("react")
let {errorList, sizeClassNames, formGroupCx, label} = require("../util.js")
let {div, textarea} = React.DOM
let cx = require("classnames")

export default class extends React.Component {

  static displayName = "Frig.friggingBootstrap.Text"

  static defaultProps = Object.assign(require("../default_props.js"))

  constructor(props) {
    super(props)

    this.state = {
      errors: undefined,
      edited: false,
    }
  }

  _cx() {
    return cx({
      "form-group": true,
      "has-error": this.state.errors != null,
      "has-success": this.state.edited && this.state.errors == null,
    })
  }

  _errorList() {
    if (this.state.errors == null) return ""
    return errorList(this.state.errors)
  }

  _label() {
    if (this.props.label == null) return ""
    return label(this.props.labelHtml, this.props.label)
  }

  render() {
    return div({className: cx(sizeClassNames(this.props))},
      div({className: this._cx()},
        this._label(),
        div({className: "controls"},
          textarea(this.props.inputHtml),
        ),
        this._errorList(),
      ),
    )
  }

}

let React = require("react")
let {errorList, sizeClassNames, formGroupCx, label} = require("../util.js")
let {div, textarea} = React.DOM
let cx = require("classnames")

export default class extends React.Component {

  static displayName = "Frig.friggingBootstrap.Text"

  static defaultProps = Object.assign(require("../default_props.js"))

  constructor(props) {
    super(props)

    this.state = {
      errors: undefined,
      edited: false,
    }
  }

  _inputHtml() {
  	return Object.assign({}, this.props.inputHtml, {
  		className: `${this.props.className || ""} form-control`.trim(),
  	})
  }

  _cx() {
    return cx({
      "form-group": true,
      "has-error": this.state.errors != null,
      "has-success": this.state.edited && this.state.errors == null,
    })
  }

  _errorList() {
    if (this.state.errors == null) return ""
    return errorList(this.state.errors)
  }

  _label() {
    if (this.props.label == null) return ""
    return label(this.props.labelHtml, this.props.label)
  }

  render() {
    return div({className: cx(sizeClassNames(this.props))},
      div({className: this._cx()},
        this._label(),
        div({className: "controls"},
          textarea(this._inputHtml()),
        ),
        this._errorList(),
      ),
    )
  }

}

Add User Class Props To Default Textarea Props


var http = require('http'),
    assert = require('assert'),
    cb = require('assert-called'),
    jsonquest = require('../');

var server = http.createServer(cb(function (req, res) {
  var data = '';

  assert.equal(req.method, 'PUT');
  assert.equal(req.url, '/hello');
  assert.equal(req.headers['content-type'], 'application/json');

  req.on('readable', function () {
    var chunk = req.read();
    if (chunk) {
      data += chunk;
    }
  });

  req.on('end', function () {
    assert.deepEqual(JSON.parse(data), { hello: 'world' });

    res.writeHead(201, { 'content-type': 'application/json' });
    res.write(JSON.stringify({ world: 'hello' }));
    res.end();
  });
})).listen(9999, cb(function () {
  jsonquest({
    host: 'localhost',
    port: 9999,
    path: '/hello',
    body: { hello: 'world' },
    method: 'PUT',
    protocol: 'http'
  }, cb(function (err, res, body) {
    assert.equal(res.statusCode, 201);
    assert.deepEqual(body, { world: 'hello' });
    assert.equal(res.headers['content-type'], 'application/json');

    server.close();
  }));
}));

var http = require('http'),
    assert = require('assert'),
    cb = require('assert-called'),
    jsonquest = require('../');

var server = http.createServer(cb(function (req, res) {
  var data = '';

  assert.equal(req.method, 'PUT');
  assert.equal(req.url, '/hello');
  assert.equal(req.headers['content-type'], 'application/json');

  req.on('data', function (chunk) {
    data += chunk;
  });

  req.on('end', function () {
    assert.deepEqual(JSON.parse(data), { hello: 'world' });

    res.writeHead(201, { 'content-type': 'application/json' });
    res.write(JSON.stringify({ world: 'hello' }));
    res.end();
  });
})).listen(9999, cb(function () {
  jsonquest({
    host: 'localhost',
    port: 9999,
    path: '/hello',
    body: { hello: 'world' },
    method: 'PUT',
    protocol: 'http'
  }, cb(function (err, res, body) {
    assert.equal(res.statusCode, 201);
    assert.deepEqual(body, { world: 'hello' });
    assert.equal(res.headers['content-type'], 'application/json');

    server.close();
  }));
}));

Fix tests under node 0.8


var tape = require('tape'),
    comboBreaker = require('..'),
    redundant,
    broken
;

redundant = 'I really really really really love pizza.';

tape('combo breaking', function (t) {
    broken = comboBreaker(redundant, 'really ');
    t.equal(broken, 'I really love pizza.');
    t.end();
});

tape('`options.keep`', function (t) {
    broken = comboBreaker(redundant, 'really ', { keep: 2 });
    t.equal(broken, 'I really really love pizza.');
    t.end();
});

tape('escaping of regex characters', function (t) {
    t.test('├─ `$` character', function (t) {
        broken = comboBreaker('a million dollar$$$$$$$$$$$$$$$', '$');
        t.equal(broken, 'a million dollar$', '$ character');
        t.end();
    });

    t.test('├─ `*` character', function (t) {
        broken = comboBreaker('i can tell no lies***********', '*');
        t.equal(broken, 'i can tell no lies*');
        t.end();
    });

    t.test('├─ `)` character', function (t) {
        broken = comboBreaker('())))))))))))))', ')');
        t.equal(broken, '()');
        t.end();
    });

    t.test('├─ `|` character', function (t) {
        broken = comboBreaker('pipe pipe pipe ||||| pipe', '|');
        t.equal(broken, 'pipe pipe pipe | pipe');
        t.end();
    });

    t.test('├─ `.` character', function (t) {
        broken = comboBreaker('dot dot dot ........ dot dot', '.');
        t.equal(broken, 'dot dot dot . dot dot');
        t.end();
    });

    t.test('├─ `?` character', function (t) {
        broken = comboBreaker('why???????', '?');
        t.equal(broken, 'why?');
        t.end();
    });

    t.test('└─ `\\` character', function (t) {
        broken = comboBreaker('back slash slash \\\\\\\\ slash', '\\');
        t.equal(broken, 'back slash slash \\ slash');
        t.end();
    });
});

Add failing tests for regex meta characters


import { alerts } from '../stores/alerts'

// Handle axios errors from backend API so that they can be shown
export function errorHandler (error) {
    if (error.response) {
        const response = error.response

        // The request was made and the server responded with a status code
        // that falls out of the range of 2xx
        console.error(response.data)
        console.log(response.status)
        console.log(response.headers)

        alerts.error(response.data.errors.join('\n'))

        throw new Error(error.response.data.errors)
    } else if (error.request) {
        // The request was made but no response was received
        // `error.request` is an instance of XMLHttpRequest in the browser and an instance of
        // http.ClientRequest in node.js
        console.log(error.request)
    } else {
        // Something happened in setting up the request that triggered an Error
        console.log('Error', error.message)
    }

    console.log(error.config)
    throw error
}

import { alerts } from '../stores/alerts'

// Handle axios errors from backend API so that they can be shown
export function errorHandler (error) {
    if (error.response) {
        const response = error.response

        // The request was made and the server responded with a status code
        // that falls out of the range of 2xx

        // console.error(response.data)
        // console.log(response.status)
        // console.log(response.headers)

        alerts.error(response.data.errors.join('\n'))

        throw new Error(error.response.data.errors)
    } else if (error.request) {
        // The request was made but no response was received
        // `error.request` is an instance of XMLHttpRequest in the browser and an instance of
        // http.ClientRequest in node.js
        console.log(error.request)
    } else {
        // Something happened in setting up the request that triggered an Error
        console.log('Error', error.message)
    }

    // console.log(error.config)
    throw error
}

Comment out extra logging on errorHandler


$(document).ready(function () {

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

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

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

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

functions = {
    setButtonNav : function () {
        var windowWidth = $(window).width(),
            buttonNav = $('.side-nav__button');

        buttonNav.addClass('on-mobile');
    }
};

$(document).ready(function () {
    functions.setButtonNav();

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

    $(window).scroll(function () {
        var logotype = $('.logotype'),
            buttonNav = $('.side-nav__button'),
            buttonNavMobile = buttonNav.filter('.on-mobile'),
            scrollTop = $(window).scrollTop(),
            windowHeight = $(window).height(),
            kudoSide = $('.kudo'),
            kudoBottom = $('.instapaper'), // $('.kudo-bottom')
            kudoBottomPosition = false;

            if (kudoBottom.length > 0) {
                kudoBottomPosition = kudoBottom.offset().top; // instapaper for now while adding kudo-bottom elem
            }

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

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

    $(window).resize(function () {
        functions.setButtonNav();
    });
});

Add animation for button nav on IPhone


import Component from '@ember/component';
import { sort, filterBy } from '@ember/object/computed';
import { inject as service } from '@ember/service';
import { task, timeout } from 'ember-concurrency';

export default Component.extend({
  flashMessages: service(),
  sortedContestsProperties: [
    'num',
  ],
  sortedContestantsProperties: [
    'competitorTotPoints:desc',
  ],
  filteredChampionships: filterBy(
    'model.session.contests',
    'notQualifier',
  ),
  filteredContests: filterBy(
    'filteredChampionships',
    'status',
    'Included',
  ),
  filteredContestants: filterBy(
    'model.contest.contestants',
    'status',
    'Included',
  ),
  sortedOutcomes: sort(
    'model.round.outcomes',
    'sortedContestsProperties'
  ),
  sortedContestants: sort(
    'filteredContestants',
    'sortedContestantsProperties'
  ),
  autosave: task(function* (property){
    yield timeout(200);
    try {
      yield property.save();
    } catch(e) {
      e.errors.forEach((error) => {
        this.flashMessages.danger(error.detail);
      })
    }
  }).restartable(),
  actions: {
    saveModel(model){
      model.save();
      this.flashMessages.success("Saved!");
    },
  }
});

import Component from '@ember/component';
import { sort, filterBy } from '@ember/object/computed';
import { inject as service } from '@ember/service';
import { task, timeout } from 'ember-concurrency';

export default Component.extend({
  flashMessages: service(),
  sortedContestsProperties: [
    'num',
  ],
  sortedContestantsProperties: [
    'competitorTotPoints:desc',
  ],
  filteredChampionships: filterBy(
    'model.session.contests',
    'notQualifier',
  ),
  filteredContests: filterBy(
    'filteredChampionships',
    'status',
    'Included',
  ),
  filteredContestants: filterBy(
    'model.contest.contestants',
    'status',
    'Included',
  ),
  sortedOutcomes: sort(
    'model.round.outcomes',
    'sortedContestsProperties'
  ),
  sortedContestants: sort(
    'filteredContestants',
    'sortedContestantsProperties'
  ),
  autosave: task(function* (property){
    yield timeout(200);
    try {
      yield property.save();
      this.flashMessages.success("Saved!");
    } catch(e) {
      e.errors.forEach((error) => {
        this.flashMessages.danger(error.detail);
      })
    }
  }).restartable(),
  actions: {
    saveModel(model){
      model.save();
      this.flashMessages.success("Saved!");
    },
  }
});

Add saved flash on outcome save


'use strict';

/* This was necessary to priorize Star Wars: The Clone Wars (2008) over Star Wars: Clone Wars (2003).
  I left this object because it could be useful for other movies/shows */
var fullTitles = {
  'Star Wars: The Clone Wars': '"Star Wars: The Clone Wars"',
  'The Office (U.S.)': 'The Office (US)',
  'The Blind Side': '"The Blind Side"',
  'The Avengers': '"The Avengers"',
  'The Seven Deadly Sins': '"The Seven Deadly Sins"',
  'Young and Hungry': '"Young and Hungry"',
  'The 100': '"The 100"',
  'The Ranch': 'The Ranch football player'
}

function Item(options) {
  this.title = fullTitles[options.title] || options.title;
  this.type = options.type;

  if (this.type === 'show') {
    this.epTitle = options.epTitle;
    this.season = options.season;
    this.episode = options.episode;
  }
}

module.exports = Item;

'use strict';

/* This was necessary to priorize Star Wars: The Clone Wars (2008) over Star Wars: Clone Wars (2003).
  I left this object because it could be useful for other movies/shows */
var fullTitles = {
  'Star Wars: The Clone Wars': '"Star Wars: The Clone Wars"',
  'The Office (U.S.)': 'The Office (US)',
  'The Blind Side': '"The Blind Side"',
  'The Avengers': '"The Avengers"',
  'The Seven Deadly Sins': '"The Seven Deadly Sins"',
  'Young and Hungry': '"Young and Hungry"',
  'The 100': '"The 100"'
}

function Item(options) {
  this.title = fullTitles[options.title] || options.title;
  this.type = options.type;

  if (this.type === 'show') {
    this.epTitle = options.epTitle;
    this.season = options.season;
    this.episode = options.episode;
  }
}

module.exports = Item;

Remove "The Ranch" full title; Trakt search shows it first now


SpreeStore.module('Products.Show',function(Show, SpreeStore, Backbone,Marionette,$,_){
  Show.Product = Backbone.Marionette.ItemView.extend({
    tagName: 'div',
    className: 'product',
    template: "#product-template",

    events: {
      "click button": "addToCart"
    },

    addToCart: function (e) {
      var quantity = $(this.el).find("input").val()
      this.trigger("product:addToCart", this.model.id, quantity)
    },

    templateHelpers: {
      displayImage: function(type) {
        if (this.variants[0].images[0]) {
          return this.variants[0].images[0][type + '_url'];
        } else {
          return "/assets/noimage/" + type + ".png";
        }
      },

      thumbnails: function() {
        return _.flatten(_.map(this.variants, function(variant) {
          return _.map(variant.images, function(image) {
            return image.mini_url;
          })
        }))
      }
    }
  });
})

SpreeStore.module('Products.Show',function(Show, SpreeStore, Backbone,Marionette,$,_){
  Show.Product = Backbone.Marionette.ItemView.extend({
    tagName: 'div',
    className: 'product',
    template: "#product-template",

    events: {
      "click button": "addToCart"
    },

    addToCart: function (e) {
      var quantity = $(this.el).find("input").val()
      var variant_id = this.model.variants.first().id
      this.trigger("product:addToCart", variant_id, quantity)
    },

    templateHelpers: {
      displayImage: function(type) {
        if (this.variants[0].images[0]) {
          return this.variants[0].images[0][type + '_url'];
        } else {
          return "/assets/noimage/" + type + ".png";
        }
      },

      thumbnails: function() {
        return _.flatten(_.map(this.variants, function(variant) {
          return _.map(variant.images, function(image) {
            return image.mini_url;
          })
        }))
      }
    }
  });
})

Use correct ID when adding to cart


'use strict';

const getDictionaries = require('./hunspell-dictionaries').getDictionaries;
const getSpellChecker = require('./hunspell-checker');
const webframe = require('web-frame');

function createAndRegisterSpellchecker(language) {
  getDictionaries()
    .then(dictionaries => {
      return dictionaries[language];
    })
    .then(getSpellChecker)
    .then(checker => {
      registerSpellChecker(language, checker);
    })
    .catch(exception => {
      console.log(exception);
    })
  ;
}

function registerSpellChecker(language, checker) {
  webframe.setSpellCheckProvider(language, true, {
    spellCheck: word => {
      return checker.check(word);
    }
  });
}

exports.initialise = createAndRegisterSpellchecker;

'use strict';

const getDictionaries = require('./hunspell-dictionaries').getDictionaries;
const getSpellChecker = require('./hunspell-checker');
const webframe = require('web-frame');

function createAndRegisterSpellchecker(language) {
  getDictionaries()
    .then(dictionaries => {
      return dictionaries[language];
    })
    .then(getSpellChecker)
    .then(checker => {
      registerSpellChecker(language, checker);
    })
    .catch(exception => {
      console.log(exception);
    })
  ;
}

function registerSpellChecker(language, checker) {
  webframe.setSpellCheckProvider(language, false, {
    spellCheck: word => {
      return checker.check(word);
    }
  });
}

exports.initialise = createAndRegisterSpellchecker;

Disable (really annoying) spelling auto correct.


const nest = require('depnest')
const { h, Value, when }  = require('mutant')

exports.gives = nest('app.html.menu')

exports.needs = nest({
  'app.html.menuItem': 'map',
  'app.sync.goTo': 'first',
  'sbot.obs.connection': 'first'
})

exports.create = function (api) {
  var _menu

  return nest('app.html.menu', function menu () {
    if (_menu) return _menu

    const hoverClass = Value('')
    const connectionClass = when(api.sbot.obs.connection, '', '-disconnected')

    const menuItems = api.app.html.menuItem(api.app.sync.goTo)
    const sortedMenuItems = Object.values(menuItems).sort((a, b) =>
      a.text.localeCompare(b.text)
    )

    // TODO: move goTo out into each menuItem
    _menu = h('Menu', {
      classList: [ hoverClass, connectionClass ],
      'ev-mouseover': () => hoverClass.set('-open'),
      'ev-mouseout': () => hoverClass.set('')
    }, [
      h('div', sortedMenuItems)
    ])

    return _menu
  })
}

const nest = require('depnest')
const { h, Value, when } = require('mutant')

exports.gives = nest('app.html.menu')

exports.needs = nest({
  'app.html.menuItem': 'map',
  'app.sync.goTo': 'first',
  'sbot.obs.connection': 'first'
})

exports.create = function (api) {
  var _menu

  return nest('app.html.menu', function menu () {
    if (_menu) return _menu

    const hoverClass = Value('')
    const connectionClass = when(api.sbot.obs.connection, '', '-disconnected')

    const menuItems = api.app.html.menuItem(api.app.sync.goTo).map(item => {
      // Remove custom order from dependencies that give app.html.menuItem
      item.style.order = null
      return item
    })
    const sortedMenuItems = Object.values(menuItems).sort((a, b) =>
      a.text.localeCompare(b.text)
    )

    // TODO: move goTo out into each menuItem
    _menu = h('Menu', {
      classList: [ hoverClass, connectionClass ],
      'ev-mouseover': () => hoverClass.set('-open'),
      'ev-mouseout': () => hoverClass.set('')
    }, [
      h('div', sortedMenuItems)
    ])

    return _menu
  })
}

Remove custom `order` properties from dependencies


(function() {
    var app = angular.module('itineraryApp', ['ngAutocomplete']); // initialize app

    app.controller('autocompCtrl', function ($scope) {
        // see http://plnkr.co/edit/il2J8qOI2Dr7Ik1KHRm8?p=preview
        // for good examples
        $scope.result = '';
        $scope.options = {};
        $scope.details = '';
        $scope.watchForm = function () {
            return $scope.form
        };
        $scope.$watch($scope.watchForm, function () {
            $scope.checkForm()
        }, true);

        $scope.checkForm = function() {
            $scope.options = {};
        };
    });
})();

(function() {
    var app = angular.module('itineraryApp', ['ngAutocomplete']); // initialize app

    app.controller('autocompCtrl', function ($scope, $http) {
        // see http://plnkr.co/edit/il2J8qOI2Dr7Ik1KHRm8?p=preview
        // for good examples
        $scope.options = {};
        var zip = document.cookie;
        $http.get('http://nominatim.openstreetmap.org/search?q=' + zip +'&format=json&polygon=1&addressdetails=1').success(function(data) {
            //console.log("NGNGNGNG")
            //console.log(data);
            var bb = data[0]['boundingbox'];
            console.log(bb);
            var SW = new google.maps.LatLng(bb[1], bb[3]);
            var NE = new google.maps.LatLng(bb[0], bb[2]);
            var bounds = new google.maps.LatLngBounds(SW, NE);
            $scope.options.bounds = bounds;
            console.log('Added bounds to autocomplete');
            console.log(bounds);
        });
        $scope.result = '';
        $scope.details = '';
        $scope.watchForm = function () {
            return $scope.form
        };
        $scope.$watch($scope.watchForm, function () {
            $scope.checkForm()
        }, true);

        $scope.checkForm = function() {
            //$scope.options = {};
        };
    });
})();

Use bounding on autocomplete results


var util = require('util');

var jshint = require('jshint').JSHINT;

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

var Validator = function() {
};

util.inherits(Validator, DefaultValidator);

Validator.prototype.validate = function(options, cb) {
	var err = [];

	jshint(options.content, options.config, options.config.globals);
	var errors = jshint.data().errors;
	if (errors) {
		errors.forEach(function(error) {
			err.push({
				message: error.reason,
				line: error.line,
				column: error.character,
				code: 'jshint:' + error.code
			});
		});
	}
	cb(null, err);
};

module.exports = Validator;

var util = require('util');

var jshint = require('jshint').JSHINT;

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

var Validator = function() {
};

util.inherits(Validator, DefaultValidator);

Validator.prototype.validate = function(options, cb) {
	var err = [];

	options.config.indent = 1;

	jshint(options.content, options.config, options.config.globals);
	var errors = jshint.data().errors;
	if (errors) {
		errors.forEach(function(error) {
			err.push({
				message: error.reason,
				line: error.line,
				column: error.character,
				code: 'jshint:' + error.code
			});
		});
	}
	cb(null, err);
};

module.exports = Validator;

Fix column number in report


define([
  // Libs
  "jquery",
  "use!underscore",
  "use!backbone"
],

function($, _, Backbone) {
  // Put application wide code here

  return {
    // This is useful when developing if you don't want to use a
    // build process every time you change a template.
    //
    // Delete if you are using a different template loading method.
    fetchTemplate: function(path, done) {
      var JST = window.JST = window.JST || {};

      // Should be an instant synchronous way of getting the template, if it
      // exists in the JST object.
      if (JST[path]) {
        return done(JST[path]);
      }

      // Fetch it asynchronously if not available from JST
      return $.get(path, function(contents) {
        var tmpl = _.template(contents);

        // Set the global JST cache and return the template
        done(JST[path] = tmpl);
      });
    },

    // Create a custom object with a nested Views object
    module: function(additionalProps) {
      return _.extend({ Views: {} }, additionalProps);
    },

    // Keep active application instances namespaced under an app object.
    app: _.extend({}, Backbone.Events)
  };
});

define([
  // Libs
  "jquery",
  "use!underscore",
  "use!backbone"
],

function($, _, Backbone) {
  // Put application wide code here

  return {
    // This is useful when developing if you don't want to use a
    // build process every time you change a template.
    //
    // Delete if you are using a different template loading method.
    fetchTemplate: function(path, done) {
      var JST = window.JST = window.JST || {};

      // Should be an instant synchronous way of getting the template, if it
      // exists in the JST object.
      if (JST[path]) {
        return done(JST[path]);
      }

      // Fetch it asynchronously if not available from JST
      return $.get(path, function(contents) {
        var tmpl = _.template(contents);

        // Set the global JST cache and return the template
        done(JST[path] = tmpl);
      }, "text");
    },

    // Create a custom object with a nested Views object
    module: function(additionalProps) {
      return _.extend({ Views: {} }, additionalProps);
    },

    // Keep active application instances namespaced under an app object.
    app: _.extend({}, Backbone.Events)
  };
});

Set dataType of fetchTemplate to "text."


import { StyleSheet, Dimensions } from 'react-native';

export default StyleSheet.create({
  mainContainer: {
    // flex: 3,
    width: Dimensions.get('window').width * .97,
    height: Dimensions.get('window').height * .95,
    marginLeft: Dimensions.get('window').width / 60,
    marginTop: Dimensions.get('window').width / 20,
  },
  startingActorView: {
    flex: 1.6,
    flexDirection: 'column',
    justifyContent: 'center',
    alignItems: 'center',
    // borderWidth: 1,
  },
  endingActorView: {
    flex: 1.6,
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    // borderWidth: 1,
  },
  pathsView: {
    flex: 3,
    justifyContent: 'center',
    // borderWidth: 1,
    // left: 0,
  },
  path: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    justifyContent: 'space-around',
    padding: 5,
    // alignItems: 'flex-start',
  },
  buttonView: {
    flex: .11,
    flexDirection: 'row',
    justifyContent: 'space-between',
  },
})

import { StyleSheet, Dimensions } from 'react-native';

export default StyleSheet.create({
  mainContainer: {
    // flex: 3,
    width: Dimensions.get('window').width * .97,
    height: Dimensions.get('window').height * .95,
    marginLeft: Dimensions.get('window').width / 60,
    marginTop: Dimensions.get('window').width / 20,
  },
  startingActorView: {
    flex: 1.6,
    flexDirection: 'column',
    justifyContent: 'center',
    alignItems: 'center',
    // borderWidth: 1,
  },
  endingActorView: {
    flex: 1.6,
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    // borderWidth: 1,
  },
  pathsView: {
    flex: 3,
    justifyContent: 'center',
    // borderWidth: 1,
    // left: 0,
  },
  path: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    justifyContent: 'space-around',
    padding: 5,
    // alignItems: 'flex-start',
  },
  buttonView: {
    flex: .11,
    flexDirection: 'row',
    justifyContent: 'space-between',
  },
  loadingView: {
    marginTop: Dimensions.get('window').height / 1.03
  },
  loadingText: {
    fontFamily: 'Faster One'
  }
})

Add styles for loading text view/text


import React from 'react'

import { connect } from 'react-redux'

class Message extends React.Component {
  render() {
    return (

        {this.props.author}: {this.props.message}

    )
  }
}

class MessageList extends React.Component {
  render() {
    return (

        {this.props.messages.map(
          msg => <Message key={msg.id}
                          author={msg.author} message={msg.message} />
        )}

    )
  }
}

const VisibleMessageList = connect(
  (state) => {
    return {
      messages: state.messages
    }
  }
)(MessageList)

class Input extends React.Component {

  constructor(props) {
      super(props);
      this.state = {
        value: ""
      }
  }

  render() {
    return (
      <input type="text" value={this.state.value}
             onChange={this.handleChange} />
    )
  }
}

export class App extends React.Component {
  render() {
    return (

        <Input/>
        <VisibleMessageList />

    )
  }
}

import React from 'react'

import { connect } from 'react-redux'

class Message extends React.Component {
  render() {
    return (

        {this.props.author}: {this.props.message}

    )
  }
}

class MessageList extends React.Component {
  render() {
    return (

        {this.props.messages.map(
          msg => <Message key={msg.id}
                          author={msg.author} message={msg.message} />
        )}

    )
  }
}

const VisibleMessageList = connect(
  (state) => {
    return {
      messages: state.messages
    }
  }
)(MessageList)

class OneLineInputForm extends React.Component {

  constructor(props) {
      super(props);
      this.state = {
        value: ""
      }
  }

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

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    if (this.props.onSubmit != null) {
      this.props.onSubmit(this.state.value);
    }

    this.setState({value: ""});
    event.preventDefault();
  }
}

export class App extends React.Component {
  render() {
    return (

        <OneLineInputForm />
        <VisibleMessageList />

    )
  }
}

Make OneLineInput form capable of hanlding submit events


/* global define */
define(['app'], function (app) {
  'use strict';

  app.config(function (fileManagerConfigProvider) {
    var defaults = fileManagerConfigProvider.$get();

    fileManagerConfigProvider.set({
      appName: undefined,
      defaultLang: 'de',
      hidePermissions: true,
      useBinarySizePrefixes: true,
      allowedActions: angular.extend(defaults.allowedActions, {
        upload: true,
        remove: true,
        edit: false,
        changePermissions: false,
        compress: false,
        compressChooseName: false,
        extract: false,
        preview: false
      }),
      listUrl: '/api/files/list',
      downloadFileUrl: '/api/files/download',
      uploadUrl: '/api/files/upload',
      removeUrl: '/api/files/remove',
      createFolderUrl: '/api/files/createFolder'
    });
  });
});

/* global define */
define(['app', angular], function (app, ng) {
  'use strict';

  app.config(function (fileManagerConfigProvider) {
    var defaults = fileManagerConfigProvider.$get();

    fileManagerConfigProvider.set({
      appName: undefined,
      defaultLang: 'de',
      hidePermissions: true,
      useBinarySizePrefixes: true,
      allowedActions: ng.extend(defaults.allowedActions, {
        upload: true,
        remove: true,
        edit: false,
        changePermissions: false,
        compress: false,
        compressChooseName: false,
        extract: false,
        preview: false
      }),
      listUrl: '/api/files/list',
      downloadFileUrl: '/api/files/download',
      uploadUrl: '/api/files/upload',
      removeUrl: '/api/files/remove',
      createFolderUrl: '/api/files/createFolder',
      renameUrl: '/api/files/rename',
      copyUrl: '/api/files/copy',
    });
  });
});

Set rename and copy url


'use strict';

function RunSelectorStats() {

    var result = {};
    var unused_selectors = [];
    result.unused_selectors = unused_selectors;
    var selector_times = [];
    result.selector_times = selector_times;
    var sheets = document.styleSheets;
    var sheetsLength = sheets.length;
    for (var i = 0; i < sheetsLength; i++) {
        var sheetRules = sheets[i].rules;
        if (sheetRules != null) {
            var sheetRulesLength = sheetRules.length;
            for (var j = 0; j < sheetRulesLength; j++) {
                var sheetRule = sheetRules[j];
                var selectorText = sheetRule.selectorText;
                var startTime = new Date;
                var selectorResults = document.querySelectorAll(selectorText);
                if (selectorResults.length == 0) {
                    unused_selectors.push(selectorText);
                }
                var time = new Date - startTime;
                selector_times.push({selector_text: selectorText, selector_time: time});
            }
        }
    }
    return result;
}

'use strict';

function RunSelectorStats() {

    var result = {};
    var unused_selectors = [];
    result.unused_selectors = unused_selectors;
    var selector_times = [];
    result.selector_times = selector_times;
    var sheets = document.styleSheets;
    var sheetsLength = sheets.length;
    for (var i = 0; i < sheetsLength; i++) {
        var sheetRules = sheets[i].rules;
        if (sheetRules != null) {
            var sheetRulesLength = sheetRules.length;
            for (var j = 0; j < sheetRulesLength; j++) {
                var sheetRule = sheetRules[j];
                var selectorText = sheetRule.selectorText;
                var startTime = new Date;
                var selectorResults = document.querySelectorAll(selectorText);
                if (selectorResults.length == 0) {
                    unused_selectors.push(selectorText);
                }
                var time = new Date - startTime;
                selector_times.push({selector_text: selectorText, selector_time: time});
            }
        }
    }
    selector_times.sort(function (a, b) {
        return b.selector_time - a.selector_time;
    });
    return result;
}

Sort selector times, highest time first


const way = require('senseway');
const mcbsp = require('mcbsp');
const renderWay = require('./renderWay');

module.exports = (model, dispatch) => {
  const container = document.createElement('div');
  const channelMeans = way.mean(model.history);
  const channelGain = way.map(channelMeans, mcbsp.gain);

  container.appendChild(renderWay(channelMeans, 'a priori'));
  container.appendChild(renderWay(channelGain, 'infogain'));

  return container;
};

const way = require('senseway');
const mcbsp = require('mcbsp');
const renderWay = require('./renderWay');

module.exports = (model, dispatch) => {
  const container = document.createElement('div');
  const channelMeans = way.mean(model.history);
  const channelGain = way.map(channelMeans, mcbsp.gain);

  container.appendChild(renderWay(channelMeans, 'a priori'));
  container.appendChild(renderWay(channelGain, 'entropy'));

  return container;
};

Rename infogain label to entropy


'use strict';

let path = require('path');
let cors = require('cors');

module.exports = function(app) {
    app.use(cors());
    // app.use('/', function (req, res) { res.json({message:'ok'})});
    app.use('/api/v1/auth', require('./auth'));
};

Build gc-orders-server from commit 855bd30 on branch master


if (Rooms.find().count() === 0) {
    Meteor.call('createRoom');
}

Meteor.publish('rooms', function() {
    return Rooms.find();
});

Rooms.allow({
  insert: ()=> false,
  update: ()=> false,
  remove: ()=> false
});

Meteor.publish(null, function() {
  return Connections.find();
});

// server code: heartbeat method
Meteor.methods({
  keepalive: function (data) {
    if (!data.name) return
    if (!Connections.findOne({user_id: data.id})) {
      console.log("User "+data.name+" enters");

      Connections.insert({user_id: data.id, name: data.name, color: data.color});
    }
    Connections.update({user_id: data.id}, {$set: {last_seen: (new Date()).getTime()}});
  }
});

// server code: clean up dead clients after 10 seconds
Meteor.setInterval(function () {
  var now = (new Date()).getTime();
  Connections.find({last_seen: {$lt: (now - 10 * 1000)}}).forEach(function (user) {
    console.log("User "+user.user_id+" has disconnected");
    Connections.remove(user);
  });
});

if (Rooms.find().count() === 0) {
    Meteor.call('createRoom');
}

Meteor.publish('rooms', function() {
    return Rooms.find();
});

Rooms.allow({
  insert: ()=> false,
  update: ()=> false,
  remove: ()=> false
});

Meteor.publish(null, function() {
  return Connections.find();
});

// server code: heartbeat method
Meteor.methods({
  keepalive: function (data) {
    if (!data.name) return;
    if (!Connections.findOne({user_id: data.id})) {
      console.log("User "+data.name+" enters");

      Connections.insert({user_id: data.id, name: data.name, color: data.color});
    }
    Connections.update({user_id: data.id}, {$set: {last_seen: (new Date()).getTime()}});
  }
});

// server code: clean up dead clients after 10 seconds
Meteor.setInterval(function () {
  var now = (new Date()).getTime();
  Connections.find({last_seen: {$lt: (now - 10 * 1000)}}).forEach(function (user) {
    console.log("User "+user.user_id+" has disconnected");
    Connections.remove(user);
  });
}, 1000);

Fix user timeout setInterval interval value


import Index from 'appkit/routes/index';

var route;
module("Unit - IndexRoute", {
  setup: function(){
    var container = isolatedContainer([
      'route:index'
    ]);

    route = container.lookup('route:index');
  }
});

test("it exists", function(){
  ok(route);
  ok(route instanceof Ember.Route);
});

test("#model", function(){
  deepEqual(route.model(), ['red', 'yellow', 'blue']);
});

import Index from 'appkit/routes/index';

var route;
module("Unit - IndexRoute", {
  setup: function(){
    var container = isolatedContainer([
      'route:index'
    ]);

    route = container.lookup('route:index');
  }
});

test("it exists", function(){
  ok(route);
  ok(route instanceof Index);
});

test("#model", function(){
  deepEqual(route.model(), ['red', 'yellow', 'blue']);
});

Test that an instance of the index is looked up


window.addEventListener("click", notifyExtension);

function notifyExtension(e) {
  console.log("igcs.js: click on ${e}");
  if (e.target.classList.contains("coreSpriteHeartFull")) {
    var parser = document.createElement('a');
    parser.href = e.target.closest("article").querySelector("header a").href;

    var bio = document.getElementsByClassName("_bugdy");
    if (bio.length > 0) {
      bio = bio[0].textContent;
    } else {
      bio = '';
    }

    var pic_url = '';
    var username = '';
    var post = '';
    var timestamp = '';
    var location = '';

    try {
      pic_url = e.target.closest("article").querySelector("img._2di5p").src;
    } catch (e) {}
    try {
      username = parser.pathname.replace("/","").replace("/","");
    } catch (e) {}
    try {
      post = e.target.closest("article > div").querySelector("div > ul > li > span").textContent;
    } catch (e) {}
    try {
      timestamp = e.target.closest("article").querySelector("time").attributes['datetime'].value;
    } catch (e) {}
    try {
      location = e.target.closest('article').querySelector('header div div a').textContent;
    } catch (e) {}

    browser.runtime.sendMessage({
      "url": pic_url,
      "user": username,
      "post": post,
      "bio": bio,
      "timestamp": timestamp,
      "location": location
    });
  }
}

window.addEventListener("click", notifyExtension);

function notifyExtension(e) {
  console.log("igcs.js: click on ${e}");
  if (e.target.classList.contains("coreSpriteHeartFull")) {
    var parser = document.createElement('a');
    parser.href = e.target.closest("article").querySelector("header a").href;

    var bio = document.getElementsByClassName("_bugdy");
    if (bio.length > 0) {
      bio = bio[0].textContent;
    } else {
      bio = '';
    }

    var pic_url = '';
    var username = '';
    var post = '';
    var timestamp = '';
    var location = '';

    try {
      pic_url = e.target.closest("article").querySelector("img._2di5p").src;
    } catch (e) {}
    try {
      username = parser.pathname.replace("/","").replace("/","");
    } catch (e) {}
    try {
      post = e.target.closest("article > div").querySelector("div > ul > li > span").textContent;
    } catch (e) {}
    try {
      timestamp = e.target.closest("article").querySelector("time").attributes["datetime"].value;
    } catch (e) {}
    try {
      location = e.target.closest("article").querySelector("header div._60iqg a").textContent;
    } catch (e) {}

    browser.runtime.sendMessage({
      "url": pic_url,
      "user": username,
      "post": post,
      "bio": bio,
      "timestamp": timestamp,
      "location": location
    });
  }
}

Adjust query selector for location of image


import {Meteor} from 'meteor/meteor';
import {check} from 'meteor/check';

export default function (methodName, ClassName) {
  Meteor.methods({
    [`${methodName}.create`](toInsert) {
      check(toInsert, Object);

      const instance = new ClassName();
      instance.create(toInsert);
    },

    [`${methodName}.edit`](Id, updates) {
      check(Id, String);
      check(updates, Object);

      const instance = ClassName.findOne(Id);
      instance.edit(updates);
    },

    [`${methodName}.delete`](Id) {
      check(Id, String);

      const instance = ClassName.findOne(Id);
      instance.remove();
    }
  });
}

import {Meteor} from 'meteor/meteor';
import {check} from 'meteor/check';

export default function (methodName, ClassName) {
  Meteor.methods({
    [`${methodName}.create`](toInsert) {
      check(toInsert, Object);

      const instance = new ClassName();
      instance.create(toInsert);
      return instance._id;
    },

    [`${methodName}.edit`](Id, updates) {
      check(Id, String);
      check(updates, Object);

      const instance = ClassName.findOne(Id);
      instance.edit(updates);
    },

    [`${methodName}.delete`](Id) {
      check(Id, String);

      const instance = ClassName.findOne(Id);
      instance.remove();
    }
  });
}

Update default create method to return instance id


// (c) 2011 Richard Carter
// This code is licensed under the MIT license; see LICENSE.txt for details.

// This script handles the following functions:
//     When someone says something that starts with "say", repeats what was to be said. Example:
//         <person> say it again
//         <nodebot> it again

listen(/PRIVMSG [^ ]+ :say (.+)$/i, function(match, data, replyTo) {
	irc.privmsg(replyTo, match[1]);
});

// (c) 2011 Richard Carter
// This code is licensed under the MIT license; see LICENSE.txt for details.

// This script handles the following functions:
//     When someone says something that starts with "say", repeats what was to be said. Example:
//         <person> say it again
//         <nodebot> it again

listen(/PRIVMSG [^ ]+ :~?say (.+)$/i, function(match, data, replyTo) {
	irc.privmsg(replyTo, match[1]);
});

Allow for ~say in addition to just say


'use strict';

var express = require('express');
var request = require('supertest');
var logger = require('log4js').getLogger('Unit-Test');

exports['Test send message success'] = function(test){

	var sendObject = {
		message:{
			receiver:'@tester';
			text:'Hello World!';
	}

	time:new Date().getTime();

}

'use strict';

var express = require('express');
var request = require('supertest');
var logger = require('log4js').getLogger('Unit-Test');
var nconf = require('nconf');
var crypto = require('cyprto');

exports['Test send message success'] = function(test){

	var time = new Date().getTime();

	var data = {
		receiver:'@tester';
		text:'Hello World!';
	}

	var salt = secret;

	var signature = sha1sum

	var sendObject = {
		message:message,
		time:time,
		signature:signature
}

Add require and fix the test case of send message success


/**
 * @license
 * Copyright The Closure Library Authors.
 * SPDX-License-Identifier: Apache-2.0
 */

goog.provide('goog.promise.Resolver');

goog.requireType('goog.Promise');

/**
 * Resolver interface for promises. The resolver is a convenience interface that
 * bundles the promise and its associated resolve and reject functions together,
 * for cases where the resolver needs to be persisted internally.
 *
 * @interface
 * @template TYPE
 */
goog.promise.Resolver = function() {};

/**
 * The promise that created this resolver.
 * @type {!goog.Promise<TYPE>}
 */
goog.promise.Resolver.prototype.promise;

/**
 * Resolves this resolver with the specified value.
 * @type {function((TYPE|goog.Promise<TYPE>|Thenable)=)}
 */
goog.promise.Resolver.prototype.resolve;

/**
 * Rejects this resolver with the specified reason.
 * @type {function(*=): void}
 */
goog.promise.Resolver.prototype.reject;

/**
 * @license
 * Copyright The Closure Library Authors.
 * SPDX-License-Identifier: Apache-2.0
 */
goog.module('goog.promise.Resolver');
goog.module.declareLegacyNamespace();

const GoogPromise = goog.requireType('goog.Promise');
const Thenable = goog.requireType('goog.Thenable');

/**
 * Resolver interface for promises. The resolver is a convenience interface that
 * bundles the promise and its associated resolve and reject functions together,
 * for cases where the resolver needs to be persisted internally.
 * @template TYPE
 * @interface
 */
class Resolver {
  constructor() {
    /**
     * The promise that created this resolver.
     * @type {!GoogPromise<TYPE>}
     */
    this.promise;
    /**
     * Resolves this resolver with the specified value.
     * @type {function((TYPE|GoogPromise<TYPE>|Promise<TYPE>|IThenable|Thenable)=)}
     */
    this.resolve;
    /**
     * Rejects this resolver with the specified reason.
     * @type {function(*=): void}
     */
    this.reject;
  }
}

exports = Resolver;

Convert `goog.promise.Resolver` to `goog.module` and to ES6. RELNOTES: n/a


import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import Box from '../box';
import theme from './theme.css';
import Island from './Island';
import { elementIsDark } from '../utils/utils';

class IslandGroup extends PureComponent {
  render() {
    const { children, className, color, dark, size } = this.props;

    const isDark = elementIsDark(color, dark);

    const classNames = cx(theme['segmented'], theme['island'], theme[color], { [theme['dark']]: isDark }, className);

    return (
      <Box className={classNames} padding={0}>
        {React.Children.map(children, child => {
          return (
            <Island {...child.props} color={color} dark={isDark} size={size}>
              {child.props.children}
            </Island>
          );
        })}
      </Box>
    );
  }
}

IslandGroup.propTypes = {
  children: PropTypes.any,
  className: PropTypes.string,
  color: PropTypes.oneOf(['neutral', 'mint', 'violet', 'ruby', 'gold', 'aqua', 'white']),
  dark: PropTypes.bool,
  size: PropTypes.oneOf(['small', 'medium', 'large']),
};

IslandGroup.defaultProps = {
  color: 'white',
};

export default IslandGroup;

import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import Box, { pickBoxProps } from '../box';
import theme from './theme.css';
import Island from './Island';
import { elementIsDark } from '../utils/utils';

class IslandGroup extends PureComponent {
  render() {
    const { children, className, color, dark, size, ...otherProps } = this.props;

    const boxProps = pickBoxProps(otherProps);
    const isDark = elementIsDark(color, dark);

    const classNames = cx(theme['segmented'], theme['island'], theme[color], { [theme['dark']]: isDark }, className);

    return (
      <Box {...boxProps} className={classNames} padding={0}>
        {React.Children.map(children, child => {
          return (
            <Island {...child.props} color={color} dark={isDark} size={size}>
              {child.props.children}
            </Island>
          );
        })}
      </Box>
    );
  }
}

IslandGroup.propTypes = {
  children: PropTypes.any,
  className: PropTypes.string,
  color: PropTypes.oneOf(['neutral', 'mint', 'violet', 'ruby', 'gold', 'aqua', 'white']),
  dark: PropTypes.bool,
  size: PropTypes.oneOf(['small', 'medium', 'large']),
};

IslandGroup.defaultProps = {
  color: 'white',
};

export default IslandGroup;

Apply box props to the Box wrapper


import supertest from 'supertest';
import app from '../index';

const request = supertest(app);

describe('PostIt Tests', () => {
  describe('Authentication routes test', () => {
    it('ensures proper response for successful user sign in', () => {

    });
    it('ensures proper response for incorrect auth details', () => {

    });
    it('ensures proper response for successful user sign up', () => {

    });
    it('ensures proper response for sign up with incorrect data', () => {

    });
  });
  describe('Group CRUD operation routes', () => {
    it('ensure proper response when group is created successfully', () => {

    });
    it('ensures proper response when messages are posted successfully', () => {

    });
    it('ensures all messages for a particular group can be read successfully', () => {

    });
    it('ensures all members of a particular group can be read successfully', () => {

    });
  });
});

import supertest from 'supertest';
import app from '../index';

describe('PostIt Tests', () => {
  describe('Authentication routes test', () => {
    it('ensures proper response for successful user sign in', (done) => {

    });
    it('ensures proper response for incorrect auth details', (done) => {

    });
    it('ensures proper response for successful user sign up', (done) => {

    });
    it('ensures proper response for sign up with incorrect data', (done) => {

    });
  });
  describe('Group CRUD operation routes', () => {
    it('ensure proper response when group is created successfully', (done) => {

    });
    it('ensures proper response when messages are posted successfully', (done) => {

    });
    it('ensures all messages for a particular group can be read successfully', (done) => {

    });
    it('ensures all members of a particular group can be read successfully', (done) => {

    });
  });
});

Remove buggy code from test suite


'use strict';

/* global Connector */

Connector.playerSelector = '.lecteur';

Connector.artistSelector = '.artiste .inside_call';

Connector.getTrack = function () {
	var text = $('.info-text .name').text().trim();
	return text || null;
};

Connector.isPlaying = function () {
	return $('.playing').length;
};

'use strict';

/* global Connector */

Connector.playerSelector = '.lecteur';

Connector.artistSelector = '.artiste .inside_call';

Connector.getTrack = function () {
	var text = $('.info-text .name').text().trim();
	return text || null;
};

Connector.isPlaying = function () {
	return $('.playing').length > 0;
};

Return boolean value in `isPlaying` function