var Gitter = require('node-gitter');

/**
 * Create new instance of GitterBot
 * @param {String} key Your API key
 * @constructor
 */
function GitterBot(key) {
  this.setApiKey(key);
}

GitterBot.prototype = {
  /**
   * Set API key to GitterBot instance
   * @param {String} key New API key
   * @returns {GitterBot}
   */
  setApiKey: function (key) {
    this._apiKey = key;
    return this;
  },

  /**
   * Get current API key
   * @returns {String}
   */
  getApiKey: function () {
    return this._apiKey;
  }
};

module.exports = GitterBot;

var Gitter = require('node-gitter');

/**
 * Create new instance of GitterBot
 * @param {String} key Your API key
 * @constructor
 */
function GitterBot(key) {
  this.setApiKey(key);
}

GitterBot.prototype = {
  /**
   * Set API key to GitterBot instance
   * @param {String} key New API key
   * @returns {GitterBot}
   */
  setApiKey: function (key) {
    this._apiKey = key || process.env['GITTER_API_KEY'];
    return this;
  },

  /**
   * Get current API key
   * @returns {String}
   */
  getApiKey: function () {
    return this._apiKey;
  }
};

module.exports = GitterBot;

Add posibility to read GITTER_API_KEY from environment


"use strict";

/*global nodeca, _*/

// stdlib
var crypto = require('crypto');

// nodeca
var HashTree = require('nlib').Support.HashTree;
var JASON    = require('nlib').Vendor.JASON;

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

var helpers = module.exports = {};

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

// returns asset source
helpers.asset_include = function asset_include(path) {
  var asset = nodeca.runtime.assets.environment.findAsset(path);
  return !asset ? "" : asset.toString();
};

// returns part or whole config
helpers.config = function (part) {
  return !part ? nodeca.config : HashTree.get(nodeca.config, part);
};

// crypto-strong random 128 bit string
helpers.random = function () {
  var rnd = crypto.randomBytes(16);
  return crypto.createHash('md5').update(rnd).digest('hex');
};

// returns link for the api path `name`
helpers.link_to = function (name, params) {
  return nodeca.runtime.router.linkTo(name, params) || '#';
};

// nodeca reference
helpers.nodeca = nodeca;

// JSON alike serializer (but that treats RegExps, Function as they are)
helpers.jason = JASON.stringify;

"use strict";

/*global nodeca, _*/

// stdlib
var crypto = require('crypto');

// nodeca
var HashTree = require('nlib').Support.HashTree;
var JASON    = require('nlib').Vendor.JASON;

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

var helpers = module.exports = {};

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

// returns asset source
helpers.asset_include = function asset_include(path) {
  var asset = nodeca.runtime.assets.environment.findAsset(path);
  return !asset ? "" : asset.toString();
};

// returns part or whole config
helpers.config = function (part) {
  return !part ? nodeca.config : HashTree.get(nodeca.config, part);
};

// crypto-strong random 128 bit string
// TODO: Remove it once REALTIME_ID will be received by AJAX handshake
helpers.random = function () {
  var rnd = crypto.randomBytes(16);
  return crypto.createHash('md5').update(rnd).digest('hex');
};

// returns link for the api path `name`
helpers.link_to = function (name, params) {
  return nodeca.runtime.router.linkTo(name, params) || '#';
};

// nodeca reference
helpers.nodeca = nodeca;

// JSON alike serializer (but that treats RegExps, Function as they are)
helpers.jason = JASON.stringify;

Add deprecation notice about random() helper


import { Action, registerAction } from '../utils';

export default class PreferenceExperimentAction extends Action {
  async execute() {
    this.normandy.log(this.recipe.arguments.message, 'info');
  }
}

registerAction('preference-experiment', PreferenceExperimentAction);

import { Action, registerAction } from '../utils';

export default class PreferenceExperimentAction extends Action {
  async execute() {
    this.normandy.log(this.recipe.arguments.slug, 'info');
  }
}

registerAction('preference-experiment', PreferenceExperimentAction);

Use arguments that actually exist in the preference-experiment action.


export { default as LocationAction } from './actions/location-action'
export { default as LocationStore } from './stores/location-store'
export { default as Link } from './components/link'
export { Router, Switch, Route } from 'react-router'
export * from './location-history'

export { default as LocationAction } from './actions/location-action'
export { default as LocationStore } from './stores/location-store'
export { default as Link } from './components/link'
export { Router, Routes, Route } from 'react-router'
export * from './location-history'

Replace Switch with Routes export


import generateReducer from './generate-reducer';
import generateActionTypes from './generate-action-types';
import generateActionCreators from './generate-action-creators';
import {generateDefaultInitialState, xhrStatuses} from './utils';

const allowAllCrudOperations = {
  create: true,
  readOne: true,
  readMany: true,
  update: true,
  del: true
};

// resourceName: a string representing the name of the resource. For instance,
//  "books". This will be the name of the store slice in Redux.
// options: a list of options to configure the resource. Refer to the docs
//  for the complete list of options
function simpleResource(resourceName, options = {}) {
  const {initialState, idAttribute, customHandlers, pluralForm, allowedOperations} = options;
  const initial = Object.assign({}, generateDefaultInitialState(), initialState);
  const idAttr = idAttribute || 'id';
  const handlers = customHandlers || {};
  const pluralName = pluralForm ? pluralForm : `${resourceName}s`;
  const allowedCrudOperations = {
    ...allowAllCrudOperations,
    ...allowedOperations
  };

  const types = generateActionTypes(resourceName, pluralName, allowedCrudOperations);
  const actionCreators = generateActionCreators(allowedCrudOperations);

  return {
    actionCreators,
    actionTypes: types,
    initialState: initial,
    reducer: generateReducer({
      pluralForm: pluralName,
      allowedOperations: allowedCrudOperations,
      idAttr, initialState, handlers, types, resourceName
    }),
    pluralForm: pluralName
  };
}

export {xhrStatuses};
export default simpleResource;

import generateReducer from './generate-reducer';
import generateActionTypes from './generate-action-types';
import generateActionCreators from './generate-action-creators';
import {generateDefaultInitialState, xhrStatuses} from './utils';

const allowAllCrudOperations = {
  create: true,
  readOne: true,
  readMany: true,
  update: true,
  del: true
};

// resourceName: a string representing the name of the resource. For instance,
//  "books". This will be the name of the store slice in Redux.
// options: a list of options to configure the resource. Refer to the docs
//  for the complete list of options
function simpleResource(resourceName, options = {}) {
  const {initialState, idAttribute, customHandlers, pluralForm, allowedOperations} = options;
  const initial = Object.assign({}, generateDefaultInitialState(), initialState);
  const idAttr = idAttribute || 'id';
  const handlers = customHandlers || {};
  const pluralName = pluralForm ? pluralForm : `${resourceName}s`;
  const allowedCrudOperations = {
    ...allowAllCrudOperations,
    ...allowedOperations
  };

  const types = generateActionTypes(resourceName, pluralName, allowedCrudOperations);
  const actionCreators = generateActionCreators(allowedCrudOperations);

  return {
    actionCreators,
    actionTypes: types,
    initialState: initial,
    reducer: generateReducer({
      pluralForm: pluralName,
      allowedOperations: allowedCrudOperations,
      initialState: initial,
      idAttr, handlers, types, resourceName
    }),
    pluralForm: pluralName
  };
}

export {xhrStatuses};
export default simpleResource;

Fix an issue where initial state was not computed correctly


import React , { Component , PropTypes }from 'react';
import { Provider } from 'react-redux';
import createStore from './store/create';
import NotificationCenter from './component/notification-center';
import {extendConfig} from './config'
//Import sass files
import './component/style';

const store = createStore();

class SmartNotificationCenter extends Component {
    componentWillMount() {
        const {config} = this.props;
        console.log('SMART PROPS',this.props)
        extendConfig(config);
    }
    render() {
        return <Provider store={store}><NotificationCenter hasAddNotif={false} /></Provider>
    }
}
SmartNotificationCenter.displayName = SmartNotificationCenter;
SmartNotificationCenter.propTypes = {
    config: PropTypes.object
};
export default SmartNotificationCenter;

import React , { Component , PropTypes }from 'react';
import { Provider } from 'react-redux';
import createStore from './store/create';
import NotificationCenter from './component/notification-center';
import {extendConfig} from './config';
import metadata from '../package.json';
//Import sass files
import './component/style';

const store = createStore();

class SmartNotificationCenter extends Component {
    componentWillMount() {
        const {config} = this.props;
        console.log('SMART PROPS',this.props)
        extendConfig(config);
    }
    render() {
        return <Provider store={store}><NotificationCenter hasAddNotif={false} /></Provider>
    }
}
SmartNotificationCenter.displayName = SmartNotificationCenter;
SmartNotificationCenter.propTypes = {
    config: PropTypes.object
};
SmartNotificationCenter.VERSION = metadata.version;
export default SmartNotificationCenter;
export const VERSION = metadata.version;

Add the VERSION in the center component.


const suffixToRemove = 'Container';

function addDisplayNameStatement(t, varName) {
    const pos = varName.indexOf(suffixToRemove);
    const displayName = 0 < pos ? varName.substring(0, pos) : varName;

    // <VAR>.displayName = '<VAR>';
    return t.expressionStatement( //
        t.assignmentExpression('=', //
            t.memberExpression(t.identifier(varName), t.identifier('displayName')),  // <VAR>.displayName
            t.stringLiteral(displayName)));
}

export default function build({types: t}) {
    return {
        visitor: {
            VariableDeclaration(path, state) {
                if (1 === path.node.declarations.length) {
                    const declarator = path.node.declarations[0];
                    if (t.isIdentifier(declarator.id)) {
                        const varName = declarator.id.name;
                        const initExpression = declarator.init;
                        const {methodName} = state.opts;
                        if (t.isCallExpression(initExpression)) {
                            let callee = initExpression.callee;
                            if (t.isMemberExpression(initExpression.callee)) {
                                callee = callee.object.callee;
                            }
                            if (callee && methodName === callee.name) {
                                const statementPath = t.isExportNamedDeclaration(path.parentPath) ? path.parentPath : path;
                                statementPath.insertAfter(addDisplayNameStatement(t, varName));
                            }
                        }
                    }
                }
            }
        }
    };
}

const suffixToRemove = 'Container';

function addDisplayNameStatement(t, varName) {
    const pos = varName.indexOf(suffixToRemove);
    const displayName = 0 < pos ? varName.substring(0, pos) : varName;

    // <VAR>.displayName = '<VAR>';
    return t.expressionStatement( //
        t.assignmentExpression('=', //
            t.memberExpression(t.identifier(varName), t.identifier('displayName')),  // <VAR>.displayName
            t.stringLiteral(displayName)));
}

export default function build({types: t}) {
    return {
        visitor: {
            VariableDeclaration(path, state) {
                if (1 === path.node.declarations.length) {
                    const declarator = path.node.declarations[0];
                    if (t.isIdentifier(declarator.id)) {
                        const varName = declarator.id.name;
                        const initExpression = declarator.init;
                        const {methodName = 'reactStamp'} = state.opts;
                        if (methodName && t.isCallExpression(initExpression)) {
                            let callee = initExpression.callee;
                            if (t.isMemberExpression(initExpression.callee)) {
                                callee = callee.object.callee;
                            }
                            if (callee && methodName === callee.name) {
                                const statementPath = t.isExportNamedDeclaration(path.parentPath) ? path.parentPath : path;
                                statementPath.insertAfter(addDisplayNameStatement(t, varName));
                            }
                        }
                    }
                }
            }
        }
    };
}

Fix default value if plugin is not configured


import React, { PureComponent, PropTypes } from 'react'
import SVGInjector from 'svg-injector'

export default class ReactSVG extends PureComponent {

  static defaultProps = {
    callback: () => {},
    className: '',
    evalScripts: 'once',
    style: {}
  }

  static propTypes = {
    callback: PropTypes.func,
    className: PropTypes.string,
    evalScripts: PropTypes.oneOf([ 'always', 'once', 'never' ]),
    path: PropTypes.string.isRequired,
    style: PropTypes.object
  }

  injectSVG() {
    const {
      evalScripts,
      callback: each
    } = this.props

    if (this._img) {
      SVGInjector(this._img, {
        evalScripts,
        each
      })
    }
  }

  componentDidMount() {
    this.injectSVG()
  }

  componentDidUpdate() {
    this.injectSVG()
  }

  render() {
    const {
      className,
      path,
      style
    } = this.props

    return (
      <div>
        <img
          ref={img => this._img = img}
          className={className}
          data-src={path}
          style={style}
        />
      </div>
    )

  }

}

import React, { PureComponent, PropTypes } from 'react'
import SVGInjector from 'svg-injector'
import ReactDOM from 'react-dom'

export default class ReactSVG extends PureComponent {

  static defaultProps = {
    callback: () => {},
    className: '',
    evalScripts: 'once',
    style: {}
  }

  static propTypes = {
    callback: PropTypes.func,
    className: PropTypes.string,
    evalScripts: PropTypes.oneOf([ 'always', 'once', 'never' ]),
    path: PropTypes.string.isRequired,
    style: PropTypes.object
  }

  renderSVG(props = this.props) {
    const {
      callback: each,
      className,
      evalScripts,
      path,
      style
    } = props

    this.container = ReactDOM.findDOMNode(this)

    ReactDOM.unstable_renderSubtreeIntoContainer(
      this,
      <img
        ref={(img) => {
          this.img = img
        }}
        className={className}
        data-src={path}
        style={style}
      />,
      this.container,
      () => {
        SVGInjector(this.img, {
          evalScripts,
          each
        })
      }
    )
  }

  componentDidMount() {
    this.renderSVG()
  }

  componentWillReceiveProps(nextProps) {
    this.renderSVG(nextProps)
  }

  componentWillUnmount() {
    ReactDOM.unmountComponentAtNode(this.container)
  }

  render() {
    return React.createElement('div')
  }

}

Refactor so React doesn't get confused when the component updates


import {has, type} from 'ramda';

export function urlBase(config) {
    if (type(config) === "Null" ||
        (type(config) === "Object") &&
        !has('url_base_pathname', config) &&
        !has('requests_pathname_prefix', config)) {
        throw new Error(`
            Trying to make an API request but "url_base_pathname" and
            "requests_pathname_prefix"
            is not in \`config\`. \`config\` is: `, config);
    } else if (has('url_base_pathname', config) &&
        !has('requests_pathname_prefix', config)) {
        return config.url_base_pathname;
    } else if (has('requests_pathname_prefix', config)) {
        return config.requests_pathname_prefix;
    } else {
        throw new Error(
            `Unhandled case trying to get url_base_pathname or
             requests_pathname_prefix from config`, config);
    }
}

import {has, type} from 'ramda';

/*
 * requests_pathname_prefix is the new config parameter introduced in
 * dash==0.18.0. The previous versions just had url_base_pathname
 */
export function urlBase(config) {
    if (type(config) === "Null" ||
        (type(config) === "Object") &&
        !has('url_base_pathname', config) &&
        !has('requests_pathname_prefix', config)) {
        throw new Error(`
            Trying to make an API request but "url_base_pathname" and
            "requests_pathname_prefix"
            is not in \`config\`. \`config\` is: `, config);
    } else if (has('url_base_pathname', config) &&
        !has('requests_pathname_prefix', config)) {
        return config.url_base_pathname;
    } else if (has('requests_pathname_prefix', config)) {
        return config.requests_pathname_prefix;
    } else {
        throw new Error(
            `Unhandled case trying to get url_base_pathname or
             requests_pathname_prefix from config`, config);
    }
}

Add comment describing url prefixes


var _ = require('underscore');
var async = require('async');
var logger = require('./loggingservice.js');
var api = require('./api.js');

var downloader = function(refreshInterval){

  this.refreshInterval = refreshInterval;

  _.bindAll(this, 'start', 'stop', 'processTrades');

};

//---EventEmitter Setup
var Util = require('util');
var EventEmitter = require('events').EventEmitter;
Util.inherits(downloader, EventEmitter);
//---EventEmitter Setup

downloader.prototype.processTrades = function(err, trades) {

  if(!err) {

    this.emit('update', trades);

  }

};

downloader.prototype.start = function() {

  logger.log('Downloader started!');

  api.getTrades(this.processTrades);

  this.downloadInterval = setInterval(function(){
    api.getTrades(this.processTrades);
  }.bind(this),1000 * this.refreshInterval);

};

downloader.prototype.stop = function() {

  clearInterval(this.downloadInterval);

  logger.log('Downloader stopped!');

};

module.exports = downloader;

var _ = require('underscore');
var async = require('async');
var logger = require('./loggingservice.js');
var api = require('./api.js');

var downloader = function(refreshInterval){

  this.refreshInterval = refreshInterval;
  this.noTradesCount = 0;

  _.bindAll(this, 'start', 'stop', 'processTrades');

};

//---EventEmitter Setup
var Util = require('util');
var EventEmitter = require('events').EventEmitter;
Util.inherits(downloader, EventEmitter);
//---EventEmitter Setup

downloader.prototype.processTrades = function(err, trades) {

  if(!err) {

    if(trades.length === 0) {
      this.noTradesCount += 1;
    } else {
      this.noTradesCount = 0;
    }

    if(this.noTradesCount >= 30) {
      logger.error('Haven\'t received data from the API for 30 consecutive attempts, stopping qpplication');
      return process.exit();
    }

    this.emit('update', trades);

  }

};

downloader.prototype.start = function() {

  logger.log('Downloader started!');

  api.getTrades(this.processTrades);

  this.downloadInterval = setInterval(function(){
    api.getTrades(this.processTrades);
  }.bind(this),1000 * this.refreshInterval);

};

downloader.prototype.stop = function() {

  clearInterval(this.downloadInterval);

  logger.log('Downloader stopped!');

};

module.exports = downloader;

Stop bot after 30 consecutive failed API calls


module.exports = {
  description: 'Generates a new presentation slide',

  // locals: function(options) {
  //   // Return custom template variables here.
  //   return {
  //     foo: options.entity.options.foo
  //   };
  // }

  afterInstall: function(options) {
    console.log(options);
    // var file = 'app/presentation.js';
    // var text = "";
    // var afterText = "import resolver from './helpers/resolver';" + EOL;

    // return this.insertIntoFile(file, text, { after: afterText});
  }
};

module.exports = {
  description: 'Generates a new presentation slide',

  normalizeEntityName: function() {},

  afterInstall: function(options) {
    // TODO: Generate slide url in `presentations.js`
  }
};

Add TODO for generating slide url in `presentations.js`


import inatjs from "inaturalistjs";
import {
  loadingDiscussionItem,
  fetchCurrentObservation
} from "./current_observation_actions";

const POST_IDENTIFICATION = "post_identification";

function postIdentification( params ) {
  return function ( ) {
    const body = Object.assign( {}, params );
    body.user_id = 1;
    return inatjs.identifications.create( body );
  };
}

function agreeWithCurrentObservation( ) {
  return function ( dispatch, getState ) {
    const o = getState( ).currentObservation.observation;
    dispatch( loadingDiscussionItem( ) );
    return dispatch(
      postIdentification( { observation_id: o.id, taxon_id: o.taxon.id } )
    ).then( ( ) => {
      dispatch( fetchCurrentObservation( ) );
    } );
  };
}

export {
  postIdentification,
  POST_IDENTIFICATION,
  agreeWithCurrentObservation
};

import inatjs from "inaturalistjs";
import {
  loadingDiscussionItem,
  fetchCurrentObservation
} from "./current_observation_actions";
import { fetchObservationsStats } from "./observations_stats_actions";

const POST_IDENTIFICATION = "post_identification";

function postIdentification( params ) {
  return function ( dispatch ) {
    const body = Object.assign( {}, params );
    body.user_id = 1;
    return inatjs.identifications.create( body )
      .then( response => {
        dispatch( fetchObservationsStats( ) );
        return response;
      } );
  };
}

function agreeWithCurrentObservation( ) {
  return function ( dispatch, getState ) {
    const o = getState( ).currentObservation.observation;
    dispatch( loadingDiscussionItem( ) );
    return dispatch(
      postIdentification( { observation_id: o.id, taxon_id: o.taxon.id } )
    ).then( ( ) => {
      dispatch( fetchCurrentObservation( ) );
    } );
  };
}

export {
  postIdentification,
  POST_IDENTIFICATION,
  agreeWithCurrentObservation
};

Update stats after every identification.


var ResourceService = require("services/ResourceService");
var ItemListService = require("services/ItemListService");

Vue.component("item-list", {

    props: [
        "template"
    ],

    data: function()
    {
        return {
            itemList: {},
            isLoading: false
        };
    },

    created: function()
    {
        this.$options.template = this.template;
    },

    ready: function()
    {
        ResourceService.bind("itemList", this);
        ResourceService.bind("isLoading", this);

        ItemListService.setSearchParams(document.location.search);
    }
});

var ResourceService = require("services/ResourceService");
var ItemListService = require("services/ItemListService");

Vue.component("item-list", {

    props: [
        "template"
    ],

    data: function()
    {
        return {
            itemList: {},
            isLoading: false
        };
    },

    created: function()
    {
        this.$options.template = this.template;
    },

    ready: function()
    {
        ResourceService.bind("itemList", this);
        ResourceService.bind("isLoading", this);

        ItemListService.setSearchParams(document.location.search);
    },

    watch:
    {
        itemList: function()
        {
            if (!$.isEmptyObject(this.itemList))
            {
                document.getElementById("filterCollapse").classList.add("in");
            }
        }
    }
});

UPDATE filter overlay now stay open after click


require('./test_helper');

describe('app', function () {
  before(seeds);

  describe('GET /', function () {
    it('responds with success', function (done) {
      request(app)
        .get('/')
        .expect(200)
        .end(done);
    });
  });

  describe('GET /index.json', function () {
    it('responds with features', function (done) {
      request(app)
        .get('/index.json')
        .expect(200)
        .expect('Content-Type', /json/)
        .expect(function (res) {
          expect(res.body).to.have.length.above(0);
        })
        .end(done);
    });
  });
});

require('./test_helper');

describe('app', function () {
  before(seeds);

  describe('GET /', function () {
    it('responds with success', function (done) {
      request(app)
        .get('/')
        .expect(200)
        .end(done);
    });
  });

  describe('GET /index.json', function () {
    it('responds with features', function (done) {
      request(app)
        .get('/index.json')
        .expect(200)
        .expect('Content-Type', /json/)
        .expect(function (res) {
          expect(res.body).to.have.length.above(0);
        })
        .end(done);
    });
  });

  describe('GET /404', function () {
    it('responds with a 404 error', function (done) {
      request(app)
        .get('/404')
        .expect(404)
        .end(done);
    });
  });
});

Test the 404 error handler


'use strict';

var request = require('supertest');
require('should');

var app = require('../app.js');

describe("GET /init/connect", function () {
  it("should redirect to Google", function (done) {
    request(app).get('/init/connect?code=123')
      .expect(302)
      .expect('Location', /google\.com/)
      .end(done);
  });
});

'use strict';

var request = require('supertest');
require('should');

var app = require('../app.js');

describe("GET /init/connect", function () {
  it("should redirect to Google", function (done) {
    request(app).get('/init/connect?code=123')
      .expect(302)
      .expect('Location', /google\.com/)
      .end(done);
  });

  it("should redirect to Google with callback URL", function (done) {
    request(app).get('/init/connect?code=123')
      .expect(302)
      .expect('Location', /localhost%3A8000%2Finit%2Fcallback%3Fcode%3D/)
      .end(done);
  });
});

Add a test about the callbackUrl


import {createRunner} from 'atom-mocha-test-runner';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';

chai.use(chaiAsPromised);
global.assert = chai.assert;

module.exports = createRunner({
  reporter: 'spec',
  overrideTestPaths: [/spec$/, /test/],
});

import {createRunner} from 'atom-mocha-test-runner';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';

import until from 'test-until';

chai.use(chaiAsPromised);
global.assert = chai.assert;

// Give tests that rely on filesystem event delivery lots of breathing room.
until.setDefaultTimeout(5000);

module.exports = createRunner({
  reporter: 'spec',
  overrideTestPaths: [/spec$/, /test/],
});

Increase default test-until timeout to 5000ms


jQuery(document).ready(function() {
    jQuery('#features textarea').each(function() {
        jQuery(this).css('height', jQuery(this).get(0).scrollHeight);
    });

    jQuery('#features').accordion();

    jQuery('#analyze button').button().click(function() {
        jQuery('#analyze .resultsholder').empty();
        eval(jQuery('#analyze textarea').val());
    });
});

jQuery(document).ready(function() {
    jQuery('#features textarea').each(function() {
        jQuery(this).css('height', jQuery(this).get(0).scrollHeight);
    });

    jQuery('#features').accordion();

    jQuery('#features > div').each(function() {
        var feature = jQuery(this).attr('id');
        if (!feature) {
            return true;
        }

        jQuery('#' + feature + ' button').button().click(function() {
            jQuery('#' + feature + ' .resultsholder').empty();
            eval(jQuery('#' + feature + ' textarea').val());
        });
    });
});

Make feature example running generic


module.exports = {
    "env": {
        "browser": true
    },
    "extends": "eslint:recommended",
    "rules": {
        "indent": [
            "error",
            4
        ],
        "linebreak-style": [
            "error",
            "unix"
        ],
        "quotes": [
            "error",
            "double"
        ],
        "semi": [
            "error",
            "always"
        ]
    },
    "globals": {
        "$": true,
        "it": true,
        "describe": true,
        "beforeEach": true,
        "chai": true,
        "jQuery": true,
        "chrome": true,
        "modal": true,
        "modalTimeout": true,
        "modalHTML": true,
        "Modal": true,
        "flashInterval": true
    }
};

module.exports = {
    "env": {
        "browser": true
    },
    "extends": "eslint:recommended",
    "rules": {
        "indent": [
            "error",
            4
        ],
        "linebreak-style": [
            "error",
            "unix"
        ],
        "quotes": [
            "error",
            "double"
        ],
        "semi": [
            "error",
            "always"
        ]
    },
    "globals": {
        "$": true,
        "it": true,
        "describe": true,
        "beforeEach": true,
        "chai": true,
        "jQuery": true,
        "chrome": true,
        "modal": true,
        "modalTimeout": true,
        "modalHTML": true,
        "Modal": true,
        "flashInterval": true,
        "currentScript": true
    }
};

Add currentScript to eslint globals


/*
 * Copyright (C) 2017-present Arctic Ice Studio <development@arcticicestudio.com>
 * Copyright (C) 2017-present Sven Greb <development@svengreb.de>
 *
 * Project:    Nord Visual Studio Code
 * Repository: https://github.com/arcticicestudio/nord-visual-studio-code
 * License:    MIT
 */

/**
 * @file The ESLint configuration.
 * @author Arctic Ice Studio <development@arcticicestudio.com>
 * @author Sven Greb <development@svengreb.de>
 * @see https://github.com/babel/eslint-plugin-babel#rules
 * @see https://github.com/tc39/proposal-optional-chaining
 * @see https://eslint.org/docs/user-guide/configuring#specifying-environments
 */

module.exports = {
  extends: "arcticicestudio-base",
  plugins: ["babel", "json", "prettier"],
  parser: "babel-eslint",
  env: {
    node: true,
    browser: true
  },
  rules: {
    /* Prioritize format errors found by Prettier. */
    "prettier/prettier": "error"
  }
};

/*
 * Copyright (C) 2017-present Arctic Ice Studio <development@arcticicestudio.com>
 * Copyright (C) 2017-present Sven Greb <development@svengreb.de>
 *
 * Project:    Nord Visual Studio Code
 * Repository: https://github.com/arcticicestudio/nord-visual-studio-code
 * License:    MIT
 */

/**
 * @file The ESLint configuration.
 * @author Arctic Ice Studio <development@arcticicestudio.com>
 * @author Sven Greb <development@svengreb.de>
 * @see https://github.com/babel/eslint-plugin-babel#rules
 * @see https://github.com/tc39/proposal-optional-chaining
 * @see https://eslint.org/docs/user-guide/configuring#specifying-environments
 */

module.exports = {
  extends: "arcticicestudio-base",
  plugins: ["json", "prettier"],
  parser: "babel-eslint",
  env: {
    node: true,
    browser: true
  },
  rules: {
    /* Prioritize format errors found by Prettier. */
    "prettier/prettier": "error"
  }
};

Remove unused "eslint-plugin-babel" from ESLint configuration


import { takeEvery } from 'redux-saga';
import { call, put } from 'redux-saga/effects';

const defaultOptions = {
  throwErrors: false,
};

export function sagasBuilder(request, REQUEST, actions, options = {}) {
  const {
    throwErrors,
  } = {
    ...defaultOptions,
    ...options,
  };

  function* makeRequest({ payload, meta }) {
    let data;
    let error;

    try {
      yield put(actions.pending(payload, meta));
      data = yield call(request, payload, meta);
      yield put(actions.success(data, meta));
    } catch (err) {
      error = err;
      yield put(actions.failure(error, meta));
      if (throwErrors) {
        throw error;
      }
    }

    return {
      data,
      error,
    };
  }

  function* watchForRequest() {
    yield takeEvery(REQUEST, makeRequest);
  }

  return {
    makeRequest,
    watchForRequest,
  };
}

import 'regenerator-runtime/runtime';

import { call, put, takeEvery } from 'redux-saga/effects';

const defaultOptions = {
  throwErrors: false,
};

export function sagasBuilder(request, REQUEST, actions, options = {}) {
  const {
    throwErrors,
  } = {
    ...defaultOptions,
    ...options,
  };

  function* makeRequest({ payload, meta }) {
    let data;
    let error;

    try {
      yield put(actions.pending(payload, meta));
      data = yield call(request, payload, meta);
      yield put(actions.success(data, meta));
    } catch (err) {
      error = err;
      yield put(actions.failure(error, meta));
      if (throwErrors) {
        throw error;
      }
    }

    return {
      data,
      error,
    };
  }

  function* watchForRequest() {
    yield takeEvery(REQUEST, makeRequest);
  }

  return {
    makeRequest,
    watchForRequest,
  };
}

Update redux-saga and add regenerator


/**
 * Module dependencies.
 */

require('should');

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

/**
 * Tests
 */

describe('fixturefiles', function() {
  describe('function', function() {
    before(function() {
      fixtures.reload();
    });

    it('should return json', function() {
      fixtures.one.should.eql({ hello: 'world' });
      fixtures.three.four.six.should.eql({ six: 6 });
    });

    it('should return text', function() {
      fixtures.three.four.five.should.eql('555');
      fixtures.eight.should.eql('nine: 10');
    });
  });

  describe('function', function() {
    before(function() {
      fixtures.reload({ type: 'function' });
    });

    it('should return json', function() {
      fixtures.one().should.eql({ hello: 'world' });
      fixtures.one('text').should.eql('{"hello":"world"}');
      fixtures.three.four.six().should.eql({ six: 6 });
    });

    it('should return text', function() {
      fixtures.three.four.five().should.eql('555');
      fixtures.eight().should.eql('nine: 10');
    });

    it('should return buffer', function() {
      fixtures.one('buffer').should.be.instanceof(Buffer);
      fixtures.one('buffer').length.should.equal(18);
      fixtures.two('buffer').should.be.instanceof(Buffer);
    });
  });
});

/**
 * Module dependencies.
 */

require('should');

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

/**
 * Tests
 */

describe('fixturefiles', function() {
  describe('getter', function() {
    before(function() {
      fixtures.reload();
    });

    it('should return json', function() {
      fixtures.one.should.eql({ hello: 'world' });
      fixtures.three.four.six.should.eql({ six: 6 });
    });

    it('should return text', function() {
      fixtures.three.four.five.should.eql('555');
      fixtures.eight.should.eql('nine: 10');
    });
  });

  describe('function', function() {
    before(function() {
      fixtures.reload({ type: 'function' });
    });

    it('should return json', function() {
      fixtures.one().should.eql({ hello: 'world' });
      fixtures.one('text').should.eql('{"hello":"world"}');
      fixtures.three.four.six().should.eql({ six: 6 });
    });

    it('should return text', function() {
      fixtures.three.four.five().should.eql('555');
      fixtures.eight().should.eql('nine: 10');
    });

    it('should return buffer', function() {
      fixtures.one('buffer').should.be.instanceof(Buffer);
      fixtures.one('buffer').length.should.equal(18);
      fixtures.two('buffer').should.be.instanceof(Buffer);
    });
  });
});

Fix describe name for getter tests


import { expect } from 'chai';

import { ADD_ITEM, COMPLETE_ITEM } from '../src/shared/actions/ItemActions';
import itemReducer from '../src/shared/reducers/itemReducer';

describe('reducer', () => {
  describe('addItem', () => {
    it('Returns new correct state', () => {
      const initialState = [
        { text: 'Learn Redux', completed: false }
      ];
      const action = { type: ADD_ITEM, value: 'Learn Node' };
      const newState = itemReducer(initialState, action);

      expect(newState.length).to.equal(2);
      expect(newState[1].text).to.equal('Learn Node');
      expect(newState[1].completed).to.equal(false);
      expect(newState).to.not.equal(initialState);
    });
  });
});

import { expect } from 'chai';

import { ADD_ITEM, COMPLETE_ITEM } from '../src/shared/actions/ItemActions';
import itemReducer from '../src/shared/reducers/itemReducer';

describe('reducer', () => {
  describe('addItem', () => {
    it('Returns new correct state', () => {
      const initialState = [
        { text: 'Learn Redux', completed: false }
      ];
      const action = { type: ADD_ITEM, value: 'Learn Node' };
      const newState = itemReducer(initialState, action);

      expect(newState.length).to.equal(2);
      expect(newState[1].text).to.equal('Learn Node');
      expect(newState[1].completed).to.equal(false);
      expect(newState).to.not.equal(initialState);
    });

    it('Initializes state correctly and Returns new correct state', () => {
      const action = { type: ADD_ITEM, value: 'Learn Node' };
      const newState = itemReducer(undefined, action);

      expect(newState.length).to.equal(1);
      expect(newState[0].text).to.equal('Learn Node');
      expect(newState[0].completed).to.equal(false);
    });
  });

  describe('completeItem', () => {
    it('Correctly sets item as complete', () => {
      const initialState = [
        { text: 'Learn Redux', completed: false }
      ];
      const action = { type: COMPLETE_ITEM, id: 0 };
      const newState = itemReducer(initialState, action);

      expect(newState.length).to.equal(1);
      expect(newState[0].completed).to.equal(true);
      expect(newState[0].text).to.equal('Learn Redux');
    });
  });

  describe('Invalid Action', () => {
    it('Returns initial state if action is not valid', () => {
      const initialState = [
        { text: 'Learn Redux', completed: false }
      ];
      const action = { type: "NOT_REAL_ACTION", id: 0 };
      const newState = itemReducer(initialState, action);

      expect(newState.length).to.equal(1);
      expect(newState).to.equal(initialState);
    });
  });
});

Add more tests for reducer


//Bootstrap and jquery included via CodePen. Much js functionality is in minified Bootstrap.js and configured through data attributes.

$(document).ready(function() {
    function sendMail() {
        var name = $('#contactName').val();
        var email = $('#contactEmail').val();
        var message = $('#contactMessage').val();
        // message += "\n\n" + name;
        var msgString = `mailto:jonathanghoffman@gmail.com?subject=new message from ${email}&body=${message}%0A${name}`;
        window.open(msgString);
    }

    $('#contactSubmit').click(function () {
        sendMail();
    });

    // Fix scrolling with a fixed navbar.
    var navOffset = $('.navbar').height();

    $('.navbar li a').click(function(event) {
        var href = $(this).attr('href');

        // Don't let the browser scroll, but still update the current address
        // in the browser.
        event.preventDefault();
        window.location.hash = href;

        // Explicitly scroll to where the browser thinks the element
        // is, but apply the offset.
        $(href)[0].scrollIntoView();
        window.scrollBy(0, -navOffset);
    });
});

//Bootstrap and jquery included via CodePen. Much js functionality is in minified Bootstrap.js and configured through data attributes.

$(document).ready(function() {
    function sendMail() {
        var name = $('#contactName').val();
        var email = $('#contactEmail').val();
        var message = $('#contactMessage').val();
        // message += "\n\n" + name;
        var msgString = `mailto:jonathanghoffman@gmail.com?subject=new message from ${email}&body=${message}%0A${name}`;
        window.open(msgString);
    }

    $('#contactSubmit').click(function () {
        sendMail();
    });

    // Fix scrolling with a fixed navbar.
    var navOffset = $('.navbar').height();

    $('.navbar li a').click(function(event) {
        var href = $(this).attr('href');

        // Don't let the browser scroll, but still update the current address
        // in the browser.
        event.preventDefault();
        window.location.hash = href;

        // Explicitly scroll to where the browser thinks the element
        // is, but apply the offset.
        $(href)[0].scrollIntoView();
        // Skip the offset for the last element.
        if (href != "#contact-me") {
            window.scrollBy(0, -navOffset);
        }
    });
});

Fix scrolling bug with contact me link


'use strict';

// Setting up route
angular.module('articles').config(['$stateProvider',
  function ($stateProvider) {
    // Articles state routing
    $stateProvider
      .state('articles', {
        abstract: true,
        url: '/articles',
        template: '<ui-view/>',
        data: {
          roles: ['user', 'admin']
        }
      })
      .state('articles.list', {
        url: '',
        templateUrl: 'modules/articles/views/list-articles.client.view.html'
      })
      .state('articles.create', {
        url: '/create',
        templateUrl: 'modules/articles/views/create-article.client.view.html'
      })
      .state('articles.view', {
        url: '/:articleId',
        templateUrl: 'modules/articles/views/view-article.client.view.html'
      })
      .state('articles.edit', {
        url: '/:articleId/edit',
        templateUrl: 'modules/articles/views/edit-article.client.view.html'
      });
  }
]);

'use strict';

// Setting up route
angular.module('articles').config(['$stateProvider',
  function ($stateProvider) {
    // Articles state routing
    $stateProvider
      .state('articles', {
        abstract: true,
        url: '/articles',
        template: '<ui-view/>'
      })
      .state('articles.list', {
        url: '',
        templateUrl: 'modules/articles/views/list-articles.client.view.html'
      })
      .state('articles.create', {
        url: '/create',
        templateUrl: 'modules/articles/views/create-article.client.view.html',
        data: {
          roles: ['user', 'admin']
        }
      })
      .state('articles.view', {
        url: '/:articleId',
        templateUrl: 'modules/articles/views/view-article.client.view.html'
      })
      .state('articles.edit', {
        url: '/:articleId/edit',
        templateUrl: 'modules/articles/views/edit-article.client.view.html',
        data: {
          roles: ['user', 'admin']
        }
      });
  }
]);

Allow Guests to view Articles


Nbsp = {};

Nbsp.updateColor = function() {
  var red = Session.get('red'),
      green = Session.get('green'),
      blue = Session.get('blue');
  $('body').css({
    'background-color': 'rgb(' + red + ', ' + green + ', ' + blue + ')'
  });
}

if (Meteor.isClient) {

  Session.setDefault('red', 0);
  Session.setDefault('green', 0);
  Session.setDefault('blue', 0);

  Template.form.onCreated(function() {
    Nbsp.updateColor();
  });

  Template.form.helpers({
    counter: function () {
      return Session.get('counter');
    }
  });

  Template.form.events({
    'change input': function (event) {
      Session.set(event.target.id, event.target.value)

      Nbsp.updateColor();

      $('')
    }
  });
}

if (Meteor.isServer) {
  Meteor.startup(function () {
    // code to run on server at startup
  });
}

Nbsp = {};

Nbsp.updateColor = function() {
  var red = Session.get('red'),
      green = Session.get('green'),
      blue = Session.get('blue');
  $('body').css({
    'background-color': 'rgb(' + red + ', ' + green + ', ' + blue + ')'
  });
}

if (Meteor.isClient) {

  Session.setDefault('red', 0);
  Session.setDefault('green', 0);
  Session.setDefault('blue', 0);

  Template.form.onCreated(function() {
    Nbsp.updateColor();
  });

  Template.form.helpers({
    counter: function () {
      return Session.get('counter');
    }
  });

  Template.form.events({
    'change input': function (event) {
      Session.set(event.target.id, event.target.value)

      Nbsp.updateColor();
    }
  });
}

if (Meteor.isServer) {
  Meteor.startup(function () {
    let hueSettings = Meteor.settings.hue || Meteor.settings.public.hue;
    if (!hueSettings) {
      throw new Meteor.Error(500, "Hue credentials not available.");
    }
    Nbsp.hue = new HueBridge(hueSettings.server, hueSettings.username);
  });
}

Initialize Hue service at startup


function FindProxyForURL(url, host) {
  return "PROXY success:80";
}

// We end the script with a comment (and no trailing newline).
// This used to cause problems, because internally ProxyResolverV8
// would append some functions to the script; the first line of
// those extra functions was being considered part of the comment.

Set the mime-type on a test data file to binary.


module.exports = {
  parser: "babel-eslint",
  root: true,
  parserOptions: {
    ecmaVersion: 2017,
    sourceType: 'module'
  },
  extends: "airbnb",
  env: {
    browser: true
  },
  rules: {
    'func-names': 0,
    'no-underscore-dangle': 0,
    'prefer-rest-params': 0,
    'prefer-arrow-callback': 0,
    'no-extraneous-dependencies': 0
  },
  globals: {
   $: true,
   d3: true,
  }
};

module.exports = {
  parser: "babel-eslint",
  root: true,
  parserOptions: {
    ecmaVersion: 2017,
    sourceType: 'module'
  },
  extends: "airbnb",
  env: {
    browser: true
  },
  rules: {
    'func-names': 0,
    'no-underscore-dangle': 0,
    'prefer-rest-params': 0,
    'prefer-arrow-callback': 0,
    'no-extraneous-dependencies': 0,
    'space-before-function-paren': 0,
  },
  globals: {
   $: true,
   d3: true,
  }
};

Update ESLint to permit no-space between fun paren


// http://eslint.org/docs/user-guide/configuring

module.exports = {
  root: true,
  parser: 'babel-eslint',
  parserOptions: {
    sourceType: 'module'
  },
  env: {
    browser: true,
  },
  // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
  extends: 'eslint:recommended',
  // required to lint *.vue files
  plugins: [
    'html'
  ],
  // add your custom rules here
  'rules': {
    'indent': ["error", 4],
    'no-undef': 0,
    'no-unused-vars': 0,
    'no-console': 0,

    // allow paren-less arrow functions
    'arrow-parens': 0,
    // allow async-await
    'generator-star-spacing': 0,
    // allow debugger during development
    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
  }
}

// http://eslint.org/docs/user-guide/configuring

module.exports = {
  root: true,
  parser: 'babel-eslint',
  parserOptions: {
    sourceType: 'module'
  },
  env: {
    browser: true,
  },
  // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
  extends: 'eslint:recommended',
  // required to lint *.vue files
  plugins: [
    'html'
  ],
  // add your custom rules here
  'rules': {
    'indent': ["error", 4, {"SwitchCase": 1}],
    'no-undef': 0,
    'no-unused-vars': 0,
    'no-console': 0,

    // allow paren-less arrow functions
    'arrow-parens': 0,
    // allow async-await
    'generator-star-spacing': 0,
    // allow debugger during development
    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
  }
}

Update eslint for switch/case statements


var pingPong = function(userNumber) {
  return true;
}

var pingPong = function(userNumber) {
  if ((userNumber % 3 === 0) || (userNumber % 5 === 0)) {
    return true;
  } else {
    return false;
  }
}

Add code to pass most recent specs.js commit


import PlantList from './plantList';
import PlantListAddBox from './plantListAddBox';
import PlantView from './plantView';
import React from 'react';

export default class PlantsViewPage extends React.Component {
    static get propTypes() {
        return {
            editing: React.PropTypes.bool.isRequired,
            filter: React.PropTypes.func.isRequired,
            onAddPlant: React.PropTypes.func.isRequired,
            onEdit: React.PropTypes.func.isRequired,
            plants: React.PropTypes.object.isRequired,
            selectedPlantName: React.PropTypes.string,
            sort: React.PropTypes.func.isRequired
        };
    }

    render() {
        let plantView;
        if (this.props.selectedPlantName) {
            plantView = (
                <PlantView
                    plantName={this.props.selectedPlantName}
                    editing={this.props.editing}
                    onEdit={this.props.onEdit} />
            );
        }

        return (
            <div id="content" className="plantsViewPage">
                <PlantListAddBox
                    onAdd={this.props.onAddPlant} />
                <PlantList
                    plants={this.props.plants}
                    filterString={this.props.filterString}
                    sortType={this.props.sortType} />
                {plantView}
            </div>
        );
    }
}

import PlantList from './plantList';
import PlantListAddBox from './plantListAddBox';
import PlantView from './plantView';
import React from 'react';

const plantsViewPage = props => {
    let plantView;
    if (props.selectedPlantName) {
        plantView = (
            <PlantView
                plantName={props.selectedPlantName}
                editing={props.editing}
                onEdit={props.onEdit} />
        );
    }

    return (
        <div id="content" className="plantsViewPage">
            <PlantListAddBox
                onAdd={props.onAddPlant} />
            <PlantList
                plants={props.plants}
                filterString={props.filterString}
                sortType={props.sortType} />
            {plantView}
        </div>
    );
};

plantsViewPage.propTypes = {
    editing: React.PropTypes.bool.isRequired,
    filter: React.PropTypes.func.isRequired,
    onAddPlant: React.PropTypes.func.isRequired,
    onEdit: React.PropTypes.func.isRequired,
    plants: React.PropTypes.object.isRequired,
    selectedPlantName: React.PropTypes.string,
    sort: React.PropTypes.func.isRequired
};

export default plantsViewPage;

Convert `PlantsViewPage` to an a stateless component


/* vim:set ts=2 sw=2 sts=2 expandtab */
/*jshint asi: true undef: true es5: true node: true browser: true devel: true
         forin: true latedef: false globalstrict: true */
'use strict';

var sequence = require('./sequence'),
    isEmpty = sequence.isEmpty, count = sequence.count,
    first = sequence.first, rest = sequence.rest,
    cons = sequence.cons, make = sequence.make, empty = sequence.empty

function List() {}
List.prototype.length = 0
List.prototype.toString = function() {
  var value = '', tail = this;
  while (!isEmpty(tail)) {
    value = value + ' ' + first(tail)
    tail = rest(tail)
  }

  return '(' + value.substr(1) + ')'
}

count.define(List, function(list) { return list.length })
first.define(List, function(list) { return list.head })
rest.define(List, function(list) { return list.tail })
make.define(List, function(tail, head) { return new List(head, tail) })

function list() {
  var items = arguments, count = items.length, tail = empty
  while (count--) tail = cons(items[count], tail)
  return tail
}
exports.list = list

/* vim:set ts=2 sw=2 sts=2 expandtab */
/*jshint asi: true undef: true es5: true node: true browser: true devel: true
         forin: true latedef: false globalstrict: true */
'use strict';

var sequence = require('./sequence'),
    isEmpty = sequence.isEmpty, count = sequence.count,
    first = sequence.first, rest = sequence.rest,
    cons = sequence.cons, make = sequence.make, empty = sequence.empty

function List(head, tail) {
  this.head = head
  this.tail = tail || empty
  this.length = count(tail) + 1
}
List.prototype.length = 0
List.prototype.toString = function() {
  var value = '', tail = this;
  while (!isEmpty(tail)) {
    value = value + ' ' + first(tail)
    tail = rest(tail)
  }

  return '(' + value.substr(1) + ')'
}
exports.List = List

count.define(List, function(list) { return list.length })
first.define(List, function(list) { return list.head })
rest.define(List, function(list) { return list.tail })
make.define(List, function(tail, head) { return new List(head, tail) })

function list() {
  var items = arguments, count = items.length, tail = empty
  while (count--) tail = cons(items[count], tail)
  return tail
}
exports.list = list

Change `List` so that it sets it's head, tail and `length` properties & export it from module.


var m = require('mithril');
var utils = require('./utils');
var MESSAGES = JSON.parse(require('raw!./messages.json'));

var Footer = {};

Footer.view = function(ctrl) {
    return m('', [
        m(
            'ul.provider-footer.col-xs-12.col-lg-10.col-lg-offset-2',
            {
                style: {
                    'list-style-type': 'none'
                }
            },
            ctrl.renderProviders()
        ),
        m('.row', m('.col-md-12', {style: 'padding-top: 30px;'}, m('span', m.trust(MESSAGES.ABOUTSHARE))))
    ]);
};

Footer.controller = function(vm) {
    var self = this;
    self.vm = vm;

    self.renderProvider = function(result, index) {

        return m('li.provider-filter', {
                onclick: function(cb){
                    self.vm.query(self.vm.query() === '' ? '*' : self.vm.query());
                    self.vm.showFooter = false;
                    utils.updateFilter(self.vm, 'source:' + result.short_name);
                }
            },
            m('', [
                m('img', {
                    src: result.favicon,
                    style: {
                        width: '16px',
                        height: '16px'
                    }
                }), ' ',
                result.long_name]
             )
        );
    };

    self.renderProviders = function() {
        return self.vm.showFooter ? $.map(self.vm.sortProviders(), self.renderProvider) : null;
    };
};

module.exports = Footer;

var m = require('mithril');
var utils = require('./utils');
var MESSAGES = JSON.parse(require('raw!./messages.json'));

var Footer = {};

Footer.view = function(ctrl) {
    return m('', [
        m(
            'ul.provider-footer.col-xs-12.col-lg-10.col-lg-offset-1',
            {
                style: {
                    'list-style-type': 'none'
                }
            },
            ctrl.renderProviders()
        ),
        m('.row', m('.col-md-12', {style: 'padding-top: 30px;'}, m('span', m.trust(MESSAGES.ABOUTSHARE))))
    ]);
};

Footer.controller = function(vm) {
    var self = this;
    self.vm = vm;

    self.renderProvider = function(result, index) {

        return m('li.provider-filter', {
                onclick: function(cb){
                    self.vm.query(self.vm.query() === '' ? '*' : self.vm.query());
                    self.vm.showFooter = false;
                    utils.updateFilter(self.vm, 'source:' + result.short_name);
                }
            },
            m('', [
                m('img', {
                    src: result.favicon,
                    style: {
                        width: '16px',
                        height: '16px'
                    }
                }), ' ',
                result.long_name]
             )
        );
    };

    self.renderProviders = function() {
        return self.vm.showFooter ? $.map(self.vm.sortProviders(), self.renderProvider) : null;
    };
};

module.exports = Footer;

Fix centering of provider list


module.exports = {
    "env": {
        "node": true
    },
    "extends": "eslint:recommended",
    "rules": {
        "indent": [
            "error",
            2
        ],
        "quotes": [
            "error",
            "single"
        ],
        "semi": [
            "error",
            "always"
        ],
        "no-unused-vars": [
             "error",
             {
               "vars": "all",
               "args": "none"
             }
        ]
    }
};

module.exports = {
    "env": {
        "node": true
    },
    "extends": "eslint:recommended",
    "rules": {
        "indent": [
            "error",
            2
        ],
        "quotes": [
            "error",
            "single"
        ],
        "semi": [
            "error",
            "always"
        ],
        "no-unused-vars": [
             "error",
             {
               "vars": "all",
               "args": "none"
             }
        ],
        "no-empty": [
             "error",
             { "allowEmptyCatch": true }
        ]
    }
};

Change eslint rule no-empty: allowEmptyCatch


module.exports = function karma(config) {
  config.set({
    frameworks: ['mocha'],
    files: [
      { pattern: 'src/**/*.js', included: false },
      'tests/**/*.js',
    ],
    preprocessors: {
      'tests/**/*.js': ['webpack', 'sourcemap'],
    },
    webpack: {
      module: {
        loaders: [
          {
            test: /\.js$/,
            exclude: /node_modules/,
            loader: 'babel-loader',
            query: {
              presets: [
                ['env', {
                  targets: {
                    edge: 12,
                    firefox: 42,
                    chrome: 49,
                    safari: 10,
                    node: 6,
                  },
                  modules: false,
                }],
              ],
              plugins: ['transform-object-rest-spread'],
            },
          },
        ],
      },
      devtool: 'inline-source-map',
    },
    webpackMiddleware: {
      noInfo: 'true',
    },
    reporters: ['mocha'],
    logLevel: config.LOG_WARN,
    browsers: [process.env.TRAVIS ? 'Chrome_travis_ci' : 'Chrome', 'Firefox'],
    customLaunchers: {
      Chrome_travis_ci: {
        base: 'Chrome',
        flags: ['--no-sandbox'],
      },
    },
  });
};

module.exports = function karma(config) {
  config.set({
    frameworks: ['mocha'],
    files: [
      { pattern: 'src/**/*.js', included: false },
      'tests/**/*.js',
    ],
    preprocessors: {
      'tests/**/*.js': ['webpack', 'sourcemap'],
    },
    webpack: {
      module: {
        loaders: [
          {
            test: /\.js$/,
            exclude: /node_modules/,
            loader: 'babel-loader',
            query: {
              presets: [
                ['env', {
                  targets: {
                    edge: 12,
                    firefox: 42,
                    chrome: 49,
                    safari: 10,
                    node: 6,
                  },
                  modules: false,
                }],
              ],
              plugins: ['transform-object-rest-spread'],
            },
          },
        ],
      },
      devtool: 'inline-source-map',
    },
    webpackMiddleware: {
      noInfo: 'true',
    },
    reporters: ['mocha'],
    logLevel: config.LOG_WARN,
    browsers: [process.env.TRAVIS ? 'Chrome_travis_ci' : 'Chrome', 'Firefox'],
    customLaunchers: {
      Chrome_travis_ci: {
        base: 'Chrome',
        flags: ['--no-sandbox'],
      },
    },
    singleRun: process.env.TRAVIS,
  });
};

Change the singleRun Karma option to true for Travis builds


import React from "react";

class Button extends React.Component {

  buttonWithWrapper () {
    return (
      <ButtonWrapper>
        <button onClick={this.props.onClick}
            style={buttonStyle}
        >
          {this.props.text}
        </button>
      </ButtonWrapper>
    );
  }

  buttonWithoutWrapper () {
    return (
      <button onClick={this.props.onClick}
          style={buttonStyle}
      >
        {this.props.text}
      </button>
    );
  }

  render () {
    return (
        this.props.useWrapper ?
        this.buttonWithWrapper() :
        this.buttonWithoutWrapper()
      );
  }
}

Button.propTypes = {
  onClick: React.PropTypes.func.isRequired,
  text: React.PropTypes.string,
  useWrapper: React.PropTypes.string
};

let buttonStyle = {
  fontSize: "1.5rem",
  boxShadow: "0.2rem 0.2rem 0.2rem rgba(0,0,0,0.8)"
};

const ButtonWrapper = (props) => {
  let wrapperStyle = {
    margin: "1rem auto"
  }

  return (
    <div style={wrapperStyle}>
      {props.children}
    </div>
  );
}

export default Button;

import React from "react";

class Button extends React.Component {

  buttonWithWrapper () {
    return (
      <ButtonWrapper>
        <button onClick={this.props.onClick}
            style={buttonStyle}
        >
          {this.props.text}
        </button>
      </ButtonWrapper>
    );
  }

  buttonWithoutWrapper () {
    return (
      <button onClick={this.props.onClick}
          style={buttonStyle}
      >
        {this.props.text}
      </button>
    );
  }

  render () {
    return (
        this.props.useWrapper ?
        this.buttonWithWrapper() :
        this.buttonWithoutWrapper()
      );
  }
}

Button.propTypes = {
  onClick: React.PropTypes.func.isRequired,
  text: React.PropTypes.string,
  useWrapper: React.PropTypes.bool
};

let buttonStyle = {
  fontSize: "1.5rem",
  boxShadow: "0.2rem 0.2rem 0.2rem rgba(0,0,0,0.8)"
};

const ButtonWrapper = (props) => {
  let wrapperStyle = {
    margin: "1rem auto"
  }

  return (
    <div style={wrapperStyle}>
      {props.children}
    </div>
  );
}

export default Button;

Fix wrong prop type in prop validation


import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import Box from '../box';
import WidgetBody from './WidgetBody';
import WidgetFooter from './WidgetFooter';
import WidgetHeader from './WidgetHeader';
import cx from 'classnames';
import theme from './theme.css';

const SIZES = {
  small: 3,
  medium: 4,
  large: 5,
};

class Widget extends PureComponent {
  render() {
    const { children, size, ...others } = this.props;

    return (
      <Box className={cx(theme['widget'])} {...others}>
        {React.Children.map(children, child => {
          return React.cloneElement(child, {
            padding: SIZES[size],
            ...child.props,
          });
        })}
      </Box>
    );
  }
}

Widget.propTypes = {
  /** The content to display inside the widget. */
  children: PropTypes.node,
  size: PropTypes.oneOf(Object.keys(SIZES)),
};

Widget.defaultProps = {
  size: 'medium',
};

Widget.WidgetBody = WidgetBody;
Widget.WidgetFooter = WidgetFooter;
Widget.WidgetHeader = WidgetHeader;

export default Widget;

import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import Box from '../box';
import WidgetBody from './WidgetBody';
import WidgetFooter from './WidgetFooter';
import WidgetHeader from './WidgetHeader';
import cx from 'classnames';
import theme from './theme.css';

const PADDINGS = {
  small: 3,
  medium: 4,
  large: 5,
};

class Widget extends PureComponent {
  render() {
    const { children, size, ...others } = this.props;

    return (
      <Box className={cx(theme['widget'])} {...others}>
        {React.Children.map(children, child => {
          return React.cloneElement(child, {
            padding: PADDINGS[size],
            ...child.props,
          });
        })}
      </Box>
    );
  }
}

Widget.propTypes = {
  /** The content to display inside the widget. */
  children: PropTypes.node,
  /** The size wich controls the paddings passed down to our children. */
  size: PropTypes.oneOf(Object.keys(PADDINGS)),
};

Widget.defaultProps = {
  size: 'medium',
};

Widget.WidgetBody = WidgetBody;
Widget.WidgetFooter = WidgetFooter;
Widget.WidgetHeader = WidgetHeader;

export default Widget;

Update PropType description and change to a better name


let backtrace = document.querySelector(".Flow-Debug-Exception-Backtrace-Code");
let localStorageItemIdentifier = 'Flow-Debug-Exception-Backtrace-Code-Open';

if (window.localStorage.getItem(localStorageItemIdentifier) === "true") {
    backtrace.open = true;
}

backtrace.addEventListener("click", (event) => {
    if (backtrace.open === true) {
        window.localStorage.removeItem(localStorageItemIdentifier);
    } else {
        window.localStorage.setItem(localStorageItemIdentifier, true);
    }
});

const backtrace = document.querySelector(".Flow-Debug-Exception-Backtrace-Code");
const localStorageItemIdentifier = 'Flow-Debug-Exception-Backtrace-Code-Open';

if (window.localStorage.getItem(localStorageItemIdentifier) === "true") {
    backtrace.open = true;
}

backtrace.addEventListener("click", (event) => {
    if (backtrace.open === true) {
        window.localStorage.removeItem(localStorageItemIdentifier);
    } else {
        window.localStorage.setItem(localStorageItemIdentifier, true);
    }
});

Change JS variables to constants


import React, { Component } from 'react';
import {
  List,
  ListItem,
} from 'react-native-elements';

export default class RepoList extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    const { navigate } = this.props.navigation;
    return (
      <List>
        {
          this.props.list.map((item, i) => (
            <ListItem
              key={i}
              title={item.name}
              onPress={() => navigate('RepoView', {
                reponame: item.name,
                url: item.url
              })} />
          ))
        }
      </List>
    );
  }
}

import React, { Component } from 'react';
import {
  List,
  ListItem,
} from 'react-native-elements';

export default class RepoList extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    const { navigate } = this.props.navigation;
    return (
      <List>
        {
          this.props.list.map((item, i) => (
            <ListItem
              key={i}
              title={item.name}
              url={item.url}
              onPress={() => navigate('RepoView', {
                reponame: item.name
              })} />
          ))
        }
      </List>
    );
  }
}

Add repo url as prop


'use strict';

var Q = require('q'),
    scorer = require('./scorer'),
    print = require('./board/print');

module.exports = {
    play: function(board, player_x, player_o) {
        var self = this;
        return Q.promise(function(resolve) {
            self
                .get_play(board, player_x, player_o)
                .then(function(choice) {
                    board.set(choice, scorer.turn(board));
                    if (scorer.is_over(board)) {
                        resolve();
                    } else {
                        resolve(self.play(board, player_x, player_o));
                    }
                });
        });
    },

    get_play: function(board, player_x, player_o) {
        var current_player;

        print.rows(board.horizontal_rows()); // TODO this doesn't belong in get_play

        if (scorer.turn(board) === 'x') {
            current_player = player_x;
        } else {
            current_player = player_o;
        }

        return current_player.play(board.empty_cells());
    }
};

'use strict';

var Q = require('q'),
    scorer = require('./scorer'),
    print = require('./board/print');

module.exports = {
    play: function(board, player_x, player_o) {
        var self = this;
        return Q.promise(function(resolve) {
            self
                .get_play(board, player_x, player_o)
                .then(function(choice) {
                    board.set(choice, scorer.turn(board));
                    if (scorer.is_over(board)) {
                        resolve();
                    } else {
                        resolve(self.play(board, player_x, player_o));
                    }
                });
        });
    },

    get_play: function(board, player_x, player_o) {
        var current_player;

        if (scorer.turn(board) === 'x') {
            current_player = player_x;
        } else {
            current_player = player_o;
        }

        if (current_player.type === 'human') {
            print.rows(board.horizontal_rows());
        }

        return current_player.play(board.empty_cells());
    }
};

Clean up how UI works


/* global angular */
var wizardServices = angular.module('wizardServices', ['ngResource']);

wizardServices.factory('Transaction', ['$resource',
    function ($resource) {
        return $resource('transactionData.json');
    }]);

wizardServices.factory('Item', ['$resource',
    function ($resource) {
        return $resource('itemData.json');
    }]);

wizardServices.factory('Customer', ['$resource',
    function ($resource) {
        return $resource('appData.json');
    }]);

wizardServices.factory('State', function () {
    var _state = 'index';

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

        set: function (state) {
            if (state) {
                _state = state;
            }

            return _state;
        }
    };
});

/* global angular */
var wizardServices = angular.module('wizardServices', ['ngResource']);

wizardServices.factory('Transaction', ['$resource',
    function ($resource) {
        return $resource('transactionData.json', null, {
            'query':  { method: 'GET', isArray: true, cache: true }
        });
    }]);

wizardServices.factory('Item', ['$resource',
    function ($resource) {
        return $resource('itemData.json', null, {
            'query':  { method: 'GET', isArray: true, cache: true }
        });
    }]);

wizardServices.factory('Customer', ['$resource',
    function ($resource) {
        return $resource('appData.json', null, {
            'query':  { method: 'GET', isArray: true, cache: true }
        });
    }]);

wizardServices.factory('State', function () {
    var _state = 'index';

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

        set: function (state) {
            if (state) {
                _state = state;
            }

            return _state;
        }
    };
});

Set service query method to cache data requests.


/* global $ */

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

    var LiveEditorToolbar = function LiveEditorToolbar (params) {
        this.init(params);
    };

    LiveEditorToolbar.prototype.init = function (params) {
        this.$appendTo = params.$appendTo;
        this.create();
    };

    LiveEditorToolbar.prototype.create = function () {
        this.$modeSelect = $('<select class="form-control">')
                                .append('<option value="edit">Edit mode</option>')
                                .append('<option value="view">View mode</option>');
        this.$buttonAddOption = $('<button class="add-option" type="button">+ add option</button>');
        this.$undoButton = $('<button type="button" class="btn btn-default btn-undo">Undo</button>');
        this.$codePanelButton = $('<button class="btn btn-primary btn-sm code-panel-button" type="button" data-toggle="collapse" data-target="#code-panel">').text('< edit code >');
        this.$toolbar = $('<ul class="toolbar">');

        this.$toolbar.append($('<li>').append(this.$buttonAddOption));
        this.$toolbar.append($('<li>').append(this.$modeSelect));
        this.$toolbar.append($('<li>').append(this.$undoButton));
        this.$toolbar.append($('<li>').append(this.$codePanelButton));

        this.$appendTo.append(this.$toolbar);
    }

    window.LiveEditorToolbar = LiveEditorToolbar;
})(window, document, $);

/* global $ */

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

    var LiveEditorToolbar = function LiveEditorToolbar (params) {
        this.init(params);
    };

    LiveEditorToolbar.prototype.init = function (params) {
        this.$appendTo = params.$appendTo;
        this.create();
    };

    LiveEditorToolbar.prototype.create = function () {
        this.$modeSelect = $('<select class="form-control">')
                                .append('<option value="edit">Edit mode</option>')
                                .append('<option value="view">View mode</option>');
        this.$buttonAddOption = $('<button type="button" class="btn btn-default add-option">+ add option</button>');
        this.$undoButton = $('<button type="button" class="btn btn-default btn-undo">Undo</button>');
        this.$codePanelButton = $('<button class="btn btn-primary btn-sm code-panel-button" type="button" data-toggle="collapse" data-target="#code-panel">').text('< edit code >');
        this.$toolbar = $('<ul class="toolbar">');

        this.$toolbar.append($('<li>').append(this.$buttonAddOption));
        this.$toolbar.append($('<li>').append(this.$modeSelect));
        this.$toolbar.append($('<li>').append(this.$undoButton));
        this.$toolbar.append($('<li>').append(this.$codePanelButton));

        this.$appendTo.append(this.$toolbar);
    };

    window.LiveEditorToolbar = LiveEditorToolbar;
})(window, document, $);

Add css to add-option button


const chai = require('chai')
const assert = chai.assert;
const sinon = require('sinon');
const Bumper = require("../lib/bumper")

describe("Bumper", function(){
  context("with assigned attributes", function(){
    var bumper = new Bumper(0,0,10,10)

    it("should have an x min position", function(){
      assert.equal(bumper.minX, 0)
    })

    it("should have a y min position", function(){
      assert.equal(bumper.minY, 0)
    })

    it("should have an x max position", function(){
      assert.equal(bumper.maxX, 10)
    })

    it("should have a y max position", function(){
      assert.equal(bumper.maxY, 10)
    })

    it("should have a default color of white", function(){
      assert.equal(bumper.color, "white")
    });
  });
});

const chai = require('chai')
const assert = chai.assert;
const sinon = require('sinon');
const Bumper = require("../lib/bumper")

describe("Bumper", function(){
  context("with assigned attributes", function(){
    var bumper = new Bumper({minX: 0, minY:0, maxX:10, maxY:10})

    it("should have an x min position", function(){
      assert.equal(bumper.minX, 0)
    })

    it("should have a y min position", function(){
      assert.equal(bumper.minY, 0)
    })

    it("should have an x max position", function(){
      assert.equal(bumper.maxX, 10)
    })

    it("should have a y max position", function(){
      assert.equal(bumper.maxY, 10)
    })

    it("should have a default color of white", function(){
      assert.equal(bumper.color, "white")
    });
  });
});

Change test to reflect changes to coords


export const selectPage = (state) => state.app ? state.app.page : "";
export const selectPageExtra = (state) => state.app ? state.app.pageExtra : "";
export const selectManagers = (state) => state.list ? state.list.managers : [];
export const selectGameTypeFilter = (state) => state.list ? state.list.gameTypeFilter : "";
export const selectParticipatingActiveGames = (state) => state.list ? state.list.participatingActiveGames : [];
export const selectParticipatingFinishedGames = (state) => state.list ? state.list.participatingFinishedGames : [];
export const selectVisibleActiveGames = (state) => state.list ? state.list.visibleActiveGames : [];
export const selectVisibleJoinableGames = (state) => state.list ? state.list.visibleJoinableGames : [];
export const selectAllGames = (state) => state.list ? state.list.allGames : [];
export const selectGameRoute = (state) => state.game && state.game.id ? {id: state.game.id, name: state.game.name} : null;

import { createSelector } from 'reselect';

export const selectPage = (state) => state.app ? state.app.page : "";
export const selectPageExtra = (state) => state.app ? state.app.pageExtra : "";
export const selectManagers = (state) => state.list ? state.list.managers : [];
export const selectGameTypeFilter = (state) => state.list ? state.list.gameTypeFilter : "";
export const selectParticipatingActiveGames = (state) => state.list ? state.list.participatingActiveGames : [];
export const selectParticipatingFinishedGames = (state) => state.list ? state.list.participatingFinishedGames : [];
export const selectVisibleActiveGames = (state) => state.list ? state.list.visibleActiveGames : [];
export const selectVisibleJoinableGames = (state) => state.list ? state.list.visibleJoinableGames : [];
export const selectAllGames = (state) => state.list ? state.list.allGames : [];

const selectGameID = (state) => state.game ? state.game.id : '';
const selectGameName = (state) => state.game ? state.game.name : '';

export const selectGameRoute = createSelector(
    selectGameID,
    selectGameName,
    (id, name) => id ? {id, name} : null
);

Make selectGameRoute be a reselect selector, so the actual object itself won't change if id and name haven't changed.


const fs = require('fs');

const parseInput = (input, config) => new Promise((resolve, reject) => {

    let parser = () => null;

    if (!config.parser) {

        parser = require('doxdox-parser-dox');

    }

    fs.stat(input, (err, stats) => {

        if (err) {

            return reject(err);

        }

        if (stats.isFile()) {

            fs.readFile(input, 'utf8', (err, data) => {

                if (err) {

                    return reject(err);

                }

                return resolve({
                    'contents': data,
                    'file': input,
                    'methods': parser(data, {'file': input})
                });

            });

        }

        return false;

    });

});

const parseInputs = (inputs, config) => new Promise(resolve => {

    let layout = () => null;

    if (!config.layout || config.layout === 'markdown') {

        layout = require('doxdox-plugin-markdown');

    }

    if (inputs.length) {

        Promise.all(inputs.map(input => parseInput(input, config)))
            .then(files => layout(Object.assign({
                files
            }, config)))
            .then(resolve);

    }

});

module.exports = {
    parseInputs
};

const fs = require('fs');

const parseInput = (input, config) => new Promise((resolve, reject) => {

    let parser = () => null;

    if (!config.parser) {

        parser = require('doxdox-parser-dox');

    }

    fs.stat(input, (err, stats) => {

        if (err) {

            return reject(err);

        }

        if (stats.isFile()) {

            fs.readFile(input, 'utf8', (err, data) => {

                if (err) {

                    return reject(err);

                }

                return resolve({
                    'name': input.replace(process.cwd(), ''),
                    'methods': parser(data, {'file': input})
                });

            });

        }

        return false;

    });

});

const parseInputs = (inputs, config) => new Promise(resolve => {

    let layout = () => null;

    if (!config.layout || config.layout === 'markdown') {

        layout = require('doxdox-plugin-markdown');

    }

    if (inputs.length) {

        Promise.all(inputs.map(input => parseInput(input, config)))
            .then(files => layout(Object.assign({
                files
            }, config)))
            .then(resolve);

    }

});

module.exports = {
    parseInputs
};

Remove cwd from file name.


/*global describe, beforeEach, it*/
'use strict';

var path    = require('path');
var helpers = require('yeoman-generator').test;

describe('bespoke generator', function () {
  beforeEach(function (done) {
    helpers.testDirectory(path.join(__dirname, 'temp'), function (err) {
      if (err) {
        return done(err);
      }

      this.app = helpers.createGenerator('bespoke:app', [
        '../../app'
      ]);
      done();
    }.bind(this));
  });

  it('creates expected files', function (done) {
    var expected = [
      // add files you expect to exist here.
      'package.json',
      'bower.json',
      'Gruntfile.js',
      'README.md',
      '.gitignore',
      '.jshintrc',
      '.bowerrc',
      'src/index.jade',
      'src/scripts/main.js',
      'src/styles/main.styl'
    ];

    helpers.mockPrompt(this.app, {
      'title': 'Foo Bar',
      'bullets': 'Y',
      'hash': 'Y'
    });

    this.app.run({}, function () {
      helpers.assertFiles(expected);
      done();
    });
  });
});

/*global describe, beforeEach, it*/
'use strict';

var path    = require('path');
var helpers = require('yeoman-generator').test;

describe('bespoke generator', function () {
  beforeEach(function (done) {
    helpers.testDirectory(path.join(__dirname, 'temp'), function (err) {
      if (err) {
        return done(err);
      }

      this.app = helpers.createGenerator('bespoke:app', [
        '../../app'
      ]);
      done();
    }.bind(this));
  });

  it('creates expected files', function (done) {
    var expected = [
      '.bowerrc',
      '.editorconfig',
      '.gitignore',
      '.jshintrc',
      'bower.json',
      'Gruntfile.js',
      'package.json',
      'README.md',
      'src/index.jade',
      'src/scripts/main.js',
      'src/styles/main.styl'
    ];

    helpers.mockPrompt(this.app, {
      'title': 'Foo Bar',
      'bullets': 'Y',
      'hash': 'Y',
      'state': 'Y',
      'prism': 'Y'
    });

    this.app.run({}, function () {
      helpers.assertFiles(expected);
      done();
    });
  });
});

Clean up test, add missing expected files


//= require active_admin/base
//= require vendor/select2
//= require vendor/redactor
// require jquery.ui.tabs

$(document).ready(function(){
  if($("select.select2").length) {
    $("select.select2").select2();
  }

  // In one line for easy removal using sed
  $('#entry_body').redactor({ focus: false, buttons: ['bold', 'italic', 'link', 'unorderedlist'], lang: 'pl' })
});

//= require jquery.ui.tabs
//= require vendor/select2
//= require vendor/redactor

$(document).ready(function(){
  if($("select.select2").length) {
    $("select.select2").select2();
  }

  // In one line for easy removal using sed
  $('#entry_body').redactor({ focus: false, buttons: ['bold', 'italic', 'link', 'unorderedlist'], lang: 'pl' })
});

Remove active admin assets as they cause probems with jquery tabs


global.WRTC = require('wrtc')
global.WEBTORRENT_ANNOUNCE = [ 'wss://tracker.webtorrent.io/' ]

global.WRTC = require('wrtc')
global.WEBTORRENT_ANNOUNCE = [ 'wss://tracker.webtorrent.io' ]

Revert "Fix error "connection error to wss://tracker.webtorrent.io?1fe16837ed""


import Ember from 'ember';

export default Ember.Component.extend({
  charClass: 'hidden-emoji-char',

  copyText(text) {
    let copied;
    let input = Ember.$('<input>');
    this.$().append(input);
    try {
      input.val(text);
      input.select();
      copied = document.execCommand('copy');
    } catch (err) {
      copied = false;
    } finally {
      input.remove();
    }
    return copied;
  },

  click() {
    if (!Modernizr.clipboard) {
      return;
    }
    const flashMessages = Ember.get(this, 'flashMessages');
    const char = this.get('emoji.char');
    const charHTML = `<span class="emoji-text">${char}</span>`;
    let copied = this.copyText(char);
    flashMessages.clearMessages();
    if (copied) {
      flashMessages.success(`Copied ${charHTML}`);
    } else {
      flashMessages.danger(`Couldn't copy ${charHTML}`);
    }
  },

});

import Ember from 'ember';

export default Ember.Component.extend({
  charClass: 'hidden-emoji-char',

  copyText(text) {
    let copied;
    let input = Ember.$('<input>');
    this.$().append(input);
    try {
      input.val(text);
      input.select();
      copied = document.execCommand('copy');
    } catch (err) {
      copied = false;
    } finally {
      input.remove();
    }
    return copied;
  },

  selectText(element) {
    if (document.body.createTextRange) {
      let range = document.body.createTextRange();
      range.moveToElementText(element);
      range.select();
    } else if (window.getSelection) {
      let selection = window.getSelection();
      let range = document.createRange();
      range.selectNodeContents(element);
      selection.removeAllRanges();
      selection.addRange(range);
    }
  },

  click() {
    if (!Modernizr.clipboard) {
      this.selectText(this.$().find('.emoji-char')[0]);
      return;
    }
    const flashMessages = Ember.get(this, 'flashMessages');
    const char = this.get('emoji.char');
    const charHTML = `<span class="emoji-text">${char}</span>`;
    let copied = this.copyText(char);
    flashMessages.clearMessages();
    if (copied) {
      flashMessages.success(`Copied ${charHTML}`);
    } else {
      flashMessages.danger(`Couldn't copy ${charHTML}`);
    }
  },

});

Select emoji character when copy not supported


'use babel'

import { dirname, basename } from 'path'

function getPath (path) {
  let [projectPath, filePath] = atom.project.relativizePath(path)

  if (!projectPath) {
    projectPath = dirname(filePath)
    filePath = basename(filePath)
  }

  return { projectPath, filePath }
}

function getActiveFile () {
  let editor = atom.workspace.getActivePaneItem()
    if (editor.file) {
      return editor.file.path
    } else if (editor.buffer && editor.buffer.file) {
      return editor.buffer.file.path
    }
}

function getUserConfig (projectPath) {
  try {
    return require(projectPath + '/bs-config.js')
  } catch(err) {
    return {}
  }
}

function onlineConfig (value) {
  switch (value) {
    case 0:
      return false
      break;
    case 1:
      return undefined
      break;
    case 2:
      return true
      break;
  }
}

export { getPath, getActiveFile, getUserConfig, onlineConfig }

'use babel'

import { dirname, basename } from 'path'

function getPath (path) {
  let [projectPath, filePath] = atom.project.relativizePath(path)

  if (!projectPath) {
    projectPath = dirname(filePath)
    filePath = basename(filePath)
  }

  return { projectPath, filePath }
}

function getActiveFile () {
  let editor = atom.workspace.getActivePaneItem()
    if (editor) {
      if (editor.file) {
        return editor.file.path
      } else if (editor.buffer && editor.buffer.file) {
        return editor.buffer.file.path
      }
    }
}

function getUserConfig (projectPath) {
  try {
    return require(projectPath + '/bs-config.js')
  } catch(err) {
    return {}
  }
}

function onlineConfig (value) {
  switch (value) {
    case 0:
      return false
      break;
    case 1:
      return undefined
      break;
    case 2:
      return true
      break;
  }
}

export { getPath, getActiveFile, getUserConfig, onlineConfig }

Fix error when activating on empty editor


/**
 * @param {Object}
 * @param {Array.<String>}
 */
module.exports = function(_class, _instance, mixined) {

	_instance.mixined = {};
	(mixined||[]).forEach(function(name) {
		_instance.mixined[name] = [];
	});

	_instance.mixins = function(mixins)
	{
		if (typeof arguments[0] === 'string') {
			mixins = [{}];
			mixins[0][arguments[0]] = arguments[1];

		} else {
			mixins = Array.prototype.slice.call(arguments);
		}

		mixins.forEach(function(mixin) {
			Object.keys(mixin).forEach(function(name) {
				if (name in _instance.mixined) {
					_instance.mixined[name].push(mixin[name]);

				} else if (_class.prototype[name] !== undefined) {
					throw new Error("Mixin '" + name + "' could not override function.");

				} else if (_instance[name] && typeof _instance[name] !== 'function') {
					throw new Error("Mixin '" + name + "' could not override internal property.");

				} else {
					_instance[name] = mixin[name];
				}
			}, _instance);
		}, _instance);

		return _instance;
	};
};

/**
 * @param {Object}
 * @param {Array.<String>}
 */
module.exports = function(_class, _instance, mixined) {

	_instance.mixined = {};
	(mixined||[]).forEach(function(name) {
		_instance.mixined[name] = [];
	});

	_instance.mixins = function(mixins)
	{
		var args = [].slice.call(arguments);

		if (typeof args[0] === 'string') {
			mixins = [{}];
			mixins[0][args[0]] = args[1];

		} else {
			mixins = args;
		}

		mixins.forEach(function(mixin) {
			Object.keys(mixin).forEach(function(name) {
				if (name in _instance.mixined) {
					_instance.mixined[name].push(mixin[name]);

				} else if (_class.prototype[name] !== undefined) {
					throw new Error("Mixin '" + name + "' could not override function.");

				} else if (_instance[name] && typeof _instance[name] !== 'function') {
					throw new Error("Mixin '" + name + "' could not override internal property.");

				} else {
					_instance[name] = mixin[name];
				}
			}, _instance);
		}, _instance);

		return _instance;
	};
};

Fix JavaScript bug with arguments.


export const GITHUB_URL = 'https://api.github.com/search/code?q=repo%3Atddbin%2Fkatas+language%3AJavaScript+path%3Akatas%2Fes6%2Flanguage';

const itemsPerPage = 100;
export const GITHUB_URL = `https://api.github.com/search/code?per_page=${itemsPerPage}&q=repo%3Atddbin%2Fkatas+language%3AJavaScript+path%3Akatas%2Fes6%2Flanguage`;

Use 100 items per page, so we get all the katas.


// nS - No Space
// lC - Lowercase

export function foldersAndFiles(currentAppName, newName) {
  const nS_CurrentAppName = currentAppName.replace(/\s/g, '');
  const nS_NewName = newName.replace(/\s/g, '');

  return [
    `ios/${nS_CurrentAppName}`,
    `ios/${nS_CurrentAppName}-tvOS`,
    `ios/${nS_CurrentAppName}-tvOSTests`,
    `ios/${nS_CurrentAppName}.xcodeproj`,
    `ios/${nS_NewName}.xcodeproj/xcshareddata/xcschemes/${nS_CurrentAppName}-tvOS.xcscheme`,
    `ios/${nS_NewName}.xcodeproj/xcshareddata/xcschemes/${nS_CurrentAppName}.xcscheme`,
    `ios/${nS_CurrentAppName}Tests`,
    `ios/${nS_NewName}Tests/${nS_CurrentAppName}Tests.m`,
    `ios/${nS_CurrentAppName}.xcworkspace`,
    `ios/${nS_NewName}/${nS_CurrentAppName}.entitlements`,
  ];
}

// nS - No Space
// lC - Lowercase

export function foldersAndFiles(currentAppName, newName) {
  const nS_CurrentAppName = currentAppName.replace(/\s/g, '');
  const nS_NewName = newName.replace(/\s/g, '');

  return [
    `ios/${nS_CurrentAppName}`,
    `ios/${nS_CurrentAppName}-tvOS`,
    `ios/${nS_CurrentAppName}-tvOSTests`,
    `ios/${nS_CurrentAppName}.xcodeproj`,
    `ios/${nS_NewName}.xcodeproj/xcshareddata/xcschemes/${nS_CurrentAppName}-tvOS.xcscheme`,
    `ios/${nS_NewName}.xcodeproj/xcshareddata/xcschemes/${nS_CurrentAppName}.xcscheme`,
    `ios/${nS_CurrentAppName}Tests`,
    `ios/${nS_NewName}Tests/${nS_CurrentAppName}Tests.m`,
    `ios/${nS_CurrentAppName}.xcworkspace`,
    `ios/${nS_NewName}/${nS_CurrentAppName}.entitlements`,
    `ios/${nS_CurrentAppName}-Bridging-Header.h`,
  ];
}

Update name of Bridging-Header file


import preInit from '../common/pre-init';

process.on('unhandledRejection', (reason, p) => {
  console.error('Unhandled Rejections:');
  console.error(reason);
  console.error('Caused by:');
  console.error(p);

  process.exit(1);
});

preInit().then(() => {
  require('./init'); // eslint-disable-line global-require
});

import preInit from '../common/pre-init';

process.on('unhandledRejection', reason => {
  console.error('Unhandled Rejections:');
  console.error(reason);

  process.exit(1);
});

preInit().then(() => {
  require('./init'); // eslint-disable-line global-require
});

Remove “caused by“ from unhandled rejection messages, it merely repeats the same information


'use strict';
/* global require */
/* global process */

var fs = require ('fs');
var path = require ('path');

var mkdirSync = function (path) {
  try {
    fs.mkdirSync (path);
  } catch (e) {
    if (e.code !== 'EEXIST') {
      throw e;
    }
  }
};

var cwd = process.cwd ().replace (/\\/g, '/');
var suffix = '/node_modules/mai-chai';

if (cwd.endsWith (suffix)) {
  var root = cwd.substr (0, cwd.length - suffix.length);
  var test = path.join (root, 'test');
  var files = fs.readdirSync (path.join (cwd, 'templates'));

  mkdirSync (test);
  mkdirSync (path.join (root, 'src'));
  mkdirSync (path.join (root, 'src.test'));

  var regex = /\r\n/g;
  var newline = '\n';
  if (process.platform === 'win32') {
    regex = /[^\r]\n/g;
    newline = '\r\n';
  }

  files.forEach (function (file) {
    var data = fs.readFileSync (path.join (cwd, 'templates', file));
    if (file.substr (0, 2) === '--') {
      var name = file.substr (2, file.length - 2);
      fs.writeFileSync (path.join (root, name), data.replace (regex, newline));
    } else {
      fs.writeFileSync (path.join (test, file), data.replace (regex, newline));
    }
  });
}

'use strict';
/* global require */
/* global process */

var fs = require ('fs');
var path = require ('path');

var mkdirSync = function (path) {
  try {
    fs.mkdirSync (path);
  } catch (e) {
    if (e.code !== 'EEXIST') {
      throw e;
    }
  }
};

var cwd = process.cwd ().replace (/\\/g, '/');
var suffix = '/node_modules/mai-chai';

if (cwd.endsWith (suffix)) {
  var root = cwd.substr (0, cwd.length - suffix.length);
  var test = path.join (root, 'test');
  var files = fs.readdirSync (path.join (cwd, 'templates'));

  mkdirSync (test);
  mkdirSync (path.join (root, 'src'));
  mkdirSync (path.join (root, 'src.test'));

  var regex = /\r\n/g;
  var newline = '\n';
  if (process.platform === 'win32') {
    regex = /[^\r]\n/g;
    newline = '\r\n';
  }

  files.forEach (function (file) {
    var data = fs.readFileSync (path.join (cwd, 'templates', file));
    if (file.substr (0, 2) === '--') {
      var name = file.substr (2, file.length - 2);
      fs.writeFileSync (path.join (root, name), data.toString ().replace (regex, newline));
    } else {
      fs.writeFileSync (path.join (test, file), data.toString ().replace (regex, newline));
    }
  });
}

Convert data to a string then apply the regex


(function(){

  window.onerror = function(msg, file, line) {
    var xhr = new XMLHttpRequest();
    xhr.open("POST", '/log', true);
    xhr.setRequestHeader('Content-Type', 'application/json');
    xhr.send(JSON.stringify({
      msg: navigator.userAgent + '\n' + location.href + '\n' + file + ':' + line + '\n' + msg
    }));
  }

})(window);

(function(){

  window.onerror = function(msg, file, line) {
    var xhr = new XMLHttpRequest();
    xhr.open("POST", '/log', true);
    xhr.setRequestHeader('Content-Type', 'application/json');
    xhr.send(JSON.stringify({
      msg: navigator.userAgent + '\n' + location.href + '\n' + file + ':' + line + '\n' + msg
    }));
    alert('抱歉，您的游览器出现异常，如需继续，请尝试其他游览器。');
  }

})(window);

Add alert when error occurs.


function styleButton(button,checked){
    if(checked){
        button.style.border='5px solid black';
    }
    else{
        button.style.border='0px solid black';
    }
}

function addCategoryButton(buttonsDiv, type){
    var btn = document.createElement("BUTTON");
    btn.className = 'button';
    btn.innerText=type.name;
    btn.id=type.type;
    btn.addEventListener("click", function(){setCategory(type.type)},false);
    var btnNext = document.getElementById('btnNext');
    buttonsDiv.insertBefore(btn,btnNext);
}

function addCategoryButtons(categories, types){
    var buttonsDiv = document.getElementById("buttons");

    for (var i = 0; i < categories.length; i++) {
        for (var j= 0; j < types.length; j++)
        {
            if (types[j].type === categories[i]){
                addCategoryButton(buttonsDiv, types[j]);
            }
        }
        //TODO fallback projectType not found...
    }
}

function styleCategoryButtons(categories,category){
    for (var i = 0; i < categories.length; i++) {
        var btn = document.getElementById(categories[i]);
        styleButton(btn, categories[i] === category);
    }
}

function styleButton(button,checked){
    if(checked){
        button.style.border='5px solid black';
    }
    else{
        button.style.border='0px solid black';
    }
}

function addCategoryButton(buttonsDiv, type){
    var btn = document.createElement("BUTTON");
    btn.className = 'button';
    btn.innerText = type.name;
    btn.id = type.type;
    btn.style.backgroundImage = "url('images/" + type.icon +"')";
    btn.style.backgroundRepeat =  "no-repeat";
    btn.style.backgroundSize = "20px";
    btn.style.color = "#0000ff";
    btn.addEventListener("click", function(){setCategory(type.type)},false);
    var btnNext = document.getElementById('btnNext');
    buttonsDiv.insertBefore(btn,btnNext);
}

function addCategoryButtons(categories, types){
    var buttonsDiv = document.getElementById("buttons");

    for (var i = 0; i < categories.length; i++) {
        for (var j= 0; j < types.length; j++)
        {
            if (types[j].type === categories[i]){
                addCategoryButton(buttonsDiv, types[j]);
            }
        }
        //TODO fallback projectType not found...
    }
}

function styleCategoryButtons(categories,category){
    for (var i = 0; i < categories.length; i++) {
        var btn = document.getElementById(categories[i]);
        styleButton(btn, categories[i] === category);
    }
}

Add icon dinamically to the button, based on the projecttype


Package.describe({
  name: 'angular-typescript-compiler',
  version: '0.3.1_2',
  summary: 'Angular TypeScript Compiler Package',
  git: 'https://github.com/Urigo/angular-meteor/tree/master/atmosphere-packages/angular-typescript-compiler',
  documentation: null
});

Npm.depends({
  'meteor-typescript': '0.8.10',
  'rollup': '0.41.4',
  'rollup-plugin-node-resolve': '3.0.0',
  'rollup-plugin-hypothetical': '1.2.1',
  'rollup-plugin-commonjs': '8.2.0'
});

Package.onUse(function(api) {
  api.versionsFrom('1.6.1');
  api.use([
    'ecmascript',
    'babel-compiler@7.0.0',
    'angular-html-compiler@0.3.1_2',
    'angular-scss-compiler@0.3.1_2'
  ], 'server');
  api.mainModule('index.js', 'server');
});

Package.describe({
  name: 'angular-typescript-compiler',
  version: '0.3.1_2',
  summary: 'Angular TypeScript Compiler Package',
  git: 'https://github.com/Urigo/angular-meteor/tree/master/atmosphere-packages/angular-typescript-compiler',
  documentation: null
});

Npm.depends({
  'meteor-typescript': '0.9.0',
  'rollup': '0.41.4',
  'rollup-plugin-node-resolve': '3.0.0',
  'rollup-plugin-hypothetical': '1.2.1',
  'rollup-plugin-commonjs': '8.2.0'
});

Package.onUse(function(api) {
  api.versionsFrom('1.6.1');
  api.use([
    'ecmascript',
    'babel-compiler@7.0.0',
    'angular-html-compiler@0.3.1_2',
    'angular-scss-compiler@0.3.1_2'
  ], 'server');
  api.mainModule('index.js', 'server');
});

Update dependency meteor-typescript to v0.9.0


var Flattenize = require ("../index")
  , myImage = new Flattenize ()
  ;

myImage.convert("http://www.steegle.com/images/chrome.jpg");

var Flattenize = require ("../index")
  , myImage = new Flattenize ()
  ;

myImage._flatColors.splice(0, myImage._flatColors.length);
myImage._flatColors.push.apply(myImage._flatColors, [
    [255, 255, 255] // white
  , [55,  129, 194] // blue
  , [77,  183, 73]  // green
  , [229, 67,  60]  // red
  , [252, 211, 9]   // yellow
]);

myImage.convert("http://www.steegle.com/images/chrome.jpg");

Set my own colors instead