(function () {
    "use strict";
    $(document).ready(function() {
        if ($('select').is('#active_filter')){
            var page_size = parseInt($('select').attr("value"));
            var table = $('table').dataTable(
                {   "pageLength" : page_size,
                    "search": {
                                "caseInsensitive": false
                              },
                    "ordering": false
                });
            $('.dataTables_filter').hide();
            $('#active_filter').prop('selectedIndex',0);
            $('#active_filter').on('change', function() {
                if (this.value !== "All") {
                    table.fnFilter( $(this).val());
                }
                else {
                    table.fnFilter("");
                }
            });
        }
    });
})();

(function () {
    "use strict";
    $(document).ready(function() {
        if ($('select').is('#active_filter')){
            var page_size = parseInt($('select').attr("value"), 10);
            var table = $('table').dataTable(
                {   "pageLength" : page_size,
                    "search": {
                                "caseInsensitive": false
                              },
                    "ordering": false
                });
            $('.dataTables_filter').hide();
            $('#active_filter').prop('selectedIndex',0);
            $('#active_filter').on('change', function() {
                if (this.value !== "All") {
                    table.fnFilter( $(this).val());
                }
                else {
                    table.fnFilter("");
                }
            });
        }
    });
})();

Include radix in parseInt call


import React from 'react'
import ReactDOM from 'react-dom'
import AppContainer from 'react-hot-loader/lib/AppContainer'
import injectTapEventPlugin from 'react-tap-event-plugin'
import Cookie from 'js-cookie'

import createRootAsync from './createRootAsync'
import withIntl from './intl/ismorphicIntlProvider'

injectTapEventPlugin()

async function render(createRoot) {
  const Root = await createRoot()
  ReactDOM.render(
    <AppContainer>
      {
        withIntl(<Root />, Cookie.get('locale'))
      }
    </AppContainer>,
    // eslint-disable-next-line no-undef
    document.getElementById('root'),
  )
}

if (process.env.NODE_ENV === 'development' && module.hot) {
  module.hot.accept('./createRootAsync.js', () => {
    render(require('./createRootAsync').default)
  })
}

render(createRootAsync)

import React from 'react'
import ReactDOM from 'react-dom'
import AppContainer from 'react-hot-loader/lib/AppContainer'
import injectTapEventPlugin from 'react-tap-event-plugin'
import Cookie from 'js-cookie'

import createRootAsync from './createRootAsync'
import withIntl from './intl/ismorphicIntlProvider'

injectTapEventPlugin()

async function render(createRoot) {
  const Root = await createRoot()
  ReactDOM.hydrate(
    <AppContainer>
      {
        withIntl(<Root />, Cookie.get('locale'))
      }
    </AppContainer>,
    // eslint-disable-next-line no-undef
    document.getElementById('root'),
  )
}

if (process.env.NODE_ENV === 'development' && module.hot) {
  module.hot.accept('./createRootAsync.js', () => {
    render(require('./createRootAsync').default)
  })
}

render(createRootAsync)

Use ReactDOM.hydrate instead of render


module.exports = {
	url: 'mongodb://localhost/gokibitz'
};

module.exports = {
	// Your MONGO_URI URL needs to be set in your `.env` file at project root.
	url: process.env.MONGO_URI
};

Enable auth on production DB


var StatsD = require('node-statsd').StatsD;
exports = module.exports = function(settings, logger) {
	var config = settings.get('statsd') || {};
	if (! config.host) {throw new Error('StatsD host not set in config')}
	var host = config.host;
	var port = config.port || 8125;
	var client = new StatsD({host: host, port: port, prefix: config.prefix, suffix: config.suffix });
	client.socket.on('error', function (error) {
		logger.error("StatsD Error " + error);
	})
	return client;
}

exports['@singleton'] = true;
exports['@require'] = ['settings', 'logger'];

var StatsD = require('node-statsd').StatsD;
exports = module.exports = function(settings, logger) {
	var config = settings.get('statsd') || {};
	if (! config.host) {throw new Error('StatsD host not set in config')}
	var host = config.host;
	var port = config.port || 8125;
	var client = new StatsD({host: host, port: port, prefix: config.prefix, suffix: config.suffix, mock: config.mock });
	client.socket.on('error', function (error) {
		logger.error("StatsD Error " + error);
	})
	return client;
}

exports['@singleton'] = true;
exports['@require'] = ['settings', 'logger'];

Add support for mock config flag.


'use strict';

const validator = require('../util/validator');
const serverErrors = require('../util/server-errors');
const sendJson = require('../util/send-json');

module.exports = function({version, resource, controller}) {
  const {validations, plural_form, actions} = resource;

  const notAllowedMiddleware = [(req, res) => {
    res.status(serverErrors.notAllowed.code);
    sendJson(res, serverErrors.notAllowed.body());
  }];

  const postMiddleware = !actions.create ? notAllowedMiddleware : [
    validator(validations.create),
    controller.create
  ];
  const getManyMiddleware = !actions.readMany ? notAllowedMiddleware : [
    validator(validations.readMany),
    controller.read
  ];
  const getOneMiddleware = !actions.readOne ? notAllowedMiddleware : [
    validator(validations.readOne),
    controller.read
  ];
  const patchMiddleware = !actions.update ? notAllowedMiddleware : [
    validator(validations.update),
    controller.update
  ];
  const deleteMiddleware = !actions.delete ? notAllowedMiddleware : [
    validator(validations.delete),
    controller.delete
  ];

  return {
    // The root location of this resource
    location: `/v${version}/${plural_form}`,

    // Four routes for the four CRUD ops
    routes: {
      post: {'/': postMiddleware},
      get: {
        '/': getOneMiddleware,
        '/:id': getManyMiddleware
      },
      patch: {'/:id': patchMiddleware},
      delete: {'/:id': deleteMiddleware}
    }
  };
};

'use strict';

const validator = require('../util/validator');
const serverErrors = require('../util/server-errors');
const sendJson = require('../util/send-json');

module.exports = function({version, resource, controller}) {
  const {validations, plural_form, actions} = resource;

  const notAllowedMiddleware = [(req, res) => {
    res.status(serverErrors.notAllowed.code);
    sendJson(res, serverErrors.notAllowed.body());
  }];

  const postMiddleware = !actions.create ? notAllowedMiddleware : [
    validator(validations.create),
    controller.create
  ];
  const getManyMiddleware = !actions.readMany ? notAllowedMiddleware : [
    validator(validations.readMany),
    controller.read
  ];
  const getOneMiddleware = !actions.readOne ? notAllowedMiddleware : [
    validator(validations.readOne),
    controller.read
  ];
  const patchMiddleware = !actions.update ? notAllowedMiddleware : [
    validator(validations.update),
    controller.update
  ];
  const deleteMiddleware = !actions.delete ? notAllowedMiddleware : [
    validator(validations.delete),
    controller.delete
  ];

  return {
    // The root location of this resource
    location: `/v${version}/${plural_form}`,

    // Four routes for the four CRUD ops
    routes: {
      post: {'/': postMiddleware},
      get: {
        '/': getManyMiddleware,
        '/:id': getOneMiddleware
      },
      patch: {'/:id': patchMiddleware},
      delete: {'/:id': deleteMiddleware}
    }
  };
};

Fix read middleware to align with proper endpoint


import React, { Component } from 'react';
import './App.css';
import Nav from './components/Nav'
import Home from './components/Home'
import HouseholdsContainer from './components/HouseholdsContainer'
import Signup from './components/Signup'
import Login from './components/Login'
import { authenticate, authenticationFailure, logout } from './redux/modules/Auth/actions'
import { connect } from 'react-redux'
import {
  BrowserRouter as Router,
  Route,
  Switch
} from 'react-router-dom'
import { Container } from 'semantic-ui-react'
import NewHousehold from './components/NewHousehold'

class App extends Component {

  componentDidMount() {
    const token = localStorage.getItem('token')

    if (token) {
      this.props.authenticate()
    } else {
      this.props.authenticationFailure()
    }
  }

  render() {
    return (

        <Router>
          <div>
            <Container text>
            <Nav logout={this.props.logout} />
            </Container>
            <Container>
              <Switch>
                <Route exact path="/" component={Home} />
                <Route path="/households/new" component={NewHousehold} />
                <Route path="/households" component={HouseholdsContainer} />
                <Route path="/signup" component={Signup} />
                <Route path="/login" component={Login} />
              </Switch>
            </Container>
          </div>
        </Router>
    );
  }
}

export default connect(null, { authenticate, authenticationFailure, logout })(App);

import React, { Component } from 'react';
import './App.css';
import Nav from './components/Nav'
import Home from './components/Home'
import HouseholdsContainer from './components/HouseholdsContainer'
import Signup from './components/Signup'
import Login from './components/Login'
import { authenticate, authenticationFailure, logout } from './redux/modules/Auth/actions'
import { connect } from 'react-redux'
import {
  BrowserRouter as Router,
  Route,
  Switch
} from 'react-router-dom'
import { Container } from 'semantic-ui-react'
import NewHousehold from './components/NewHousehold'
import NewMeal from './components/NewMeal'

class App extends Component {

  componentDidMount() {
    const token = localStorage.getItem('token')

    if (token) {
      this.props.authenticate()
    } else {
      this.props.authenticationFailure()
    }
  }

  render() {
    return (

        <Router>
          <div>
            <Container text>
            <Nav logout={this.props.logout} />
            </Container>
            <Container>
              <Switch>
                <Route exact path="/" component={Home} />
                <Route path="/households/new" component={NewHousehold} />
                <Route path="/households" component={HouseholdsContainer} />
                <Route path="/meals/new" component={NewMeal} />
                <Route path="/signup" component={Signup} />
                <Route path="/login" component={Login} />
              </Switch>
            </Container>
          </div>
        </Router>
    );
  }
}

export default connect(null, { authenticate, authenticationFailure, logout })(App);

Create route for new meal form


import React, { Component } from 'react'

export default class App extends Component {
  state = {
    listSize: 7776,
    passwordSize: 6
  }

  static bits (size) {
    return Math.floor(Math.log2(size)) + 1
  }

  numberSetter (key) {
    return (event) => this.setState({ [key]: parseInt(event.target.value, 10) })
  }

  render () {
    const size = this.state.listSize ** this.state.passwordSize
    return (

Password Entropy

Diceware

          <input value={this.state.passwordSize} onChange={this.numberSetter('passwordSize')} type="number" min="1" required/>
          words from a set of
          <input value={this.state.listSize} onChange={this.numberSetter('listSize')} type="number" min="1" required/>

Roughly {size.toPrecision(4)} passwords ({this.constructor.bits(size)} bits of entropy)

    )
  }
}

import React, { Component } from 'react'

export default class App extends Component {
  state = {
    length: 6,
    wordlistLength: 7776
  }

  static bits (size) {
    return Math.floor(Math.log2(size)) + 1
  }

  numberSetter (key) {
    return (event) => this.setState({ [key]: parseInt(event.target.value, 10) })
  }

  render () {
    const size = this.state.wordlistLength ** this.state.length
    return (
      <form>

Password Entropy

Length

          <input value={this.state.length} onChange={this.numberSetter('length')} type="number" min="1" required/>

Options

Wordlist Length

          <input value={this.state.wordlistLength} onChange={this.numberSetter('wordlistLength')} type="number" min="1" required/>

Possible Passwords

        Roughly {size.toPrecision(4)} ({this.constructor.bits(size)} bits of entropy)
      </form>
    )
  }
}

Clean up the form structure


/*
 * Copyright (c) 2017 Bonnie Schulkin. All Rights Reserved.
 *
 * This file is part of BoxCharter.
 *
 * BoxCharter is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * BoxCharter is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with BoxCharter. If not, see <http://www.gnu.org/licenses/>.
 *
 */

/**
 * Written with help from Stephen Grider's Advanced React and Redux Udemy Course
 * @module
 * reducer_auth
 */

import { AUTH_USER, UNAUTH_USER, AUTH_ERROR } from '../actions/types'

export default (state = {}, action) => {
  switch(action.type) {
    case AUTH_USER:
      return { ...state, authenticated: true, userId: action.payload.userId }
    case UNAUTH_USER:
      return { ...state, authenticated: false }
    case AUTH_ERROR:
      return { ...state, error: action.payload }
    default:
      return { ...state }
  }
}

/*
 * Copyright (c) 2017 Bonnie Schulkin. All Rights Reserved.
 *
 * This file is part of BoxCharter.
 *
 * BoxCharter is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * BoxCharter is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with BoxCharter. If not, see <http://www.gnu.org/licenses/>.
 *
 */

/**
 * Written with help from Stephen Grider's Advanced React and Redux Udemy Course
 * @module
 * reducer_auth
 */

import { AUTH_USER, UNAUTH_USER, AUTH_ERROR } from '../actions/types'

export default (state = {}, action) => {
  switch(action.type) {
    case AUTH_USER:
      return { ...state, authenticated: true, userId: action.payload.userId, error: null }
    case UNAUTH_USER:
      return { ...state, authenticated: false }
    case AUTH_ERROR:
      return { ...state, error: action.payload }
    default:
      return { ...state }
  }
}

Clear error on successful login


import fetch from 'isomorphic-fetch';
import { checkStatus, parseJSON } from './utils';
require('es6-promise').polyfill();

export const fetchTickets = projectId => {
  const headers = {
    Authorization: `Basic ${process.env.REACT_APP_API_KEY}`,
  };

  return fetch(`${process.env.REACT_APP_API_URL}/v1/tickets/${projectId}`, { headers }).then(checkStatus).then(parseJSON);
}

export const fetchTicketNotes = ticketId => {
  const headers = {
    Authorization: `Basic ${process.env.REACT_APP_API_KEY}`,
  };
  return fetch(`${process.env.REACT_APP_API_URL}/v1/ticket/${ticketId}/notes`, { headers }).then(checkStatus).then(parseJSON);
}

export const dispatchTickets = params => {
  const headers = {
    Authorization: `Basic ${process.env.REACT_APP_API_KEY}`,
    'Content-Type': 'application/json'
  };

  return fetch(`${process.env.REACT_APP_API_URL}/v1/dispatch`, {
    headers,
    method: 'POST',
    body: JSON.stringify(params),
  }).then(checkStatus).then(parseJSON);
}

import fetch from 'isomorphic-fetch';
import { checkStatus, parseJSON } from './utils';
require('es6-promise').polyfill();

export const fetchTickets = projectId => {
  const headers = {
    Authorization: `Basic ${process.env.REACT_APP_API_KEY}`,
  };

  return fetch(`${process.env.REACT_APP_API_URL}/v1/tickets/${projectId}`, { headers }).then(checkStatus).then(parseJSON);
}

export const fetchTicketNotes = ticketId => {
  const headers = {
    Authorization: `Basic ${process.env.REACT_APP_API_KEY}`,
  };
  return fetch(`${process.env.REACT_APP_API_URL}/v1/ticket/${ticketId}/notes`, { headers }).then(checkStatus).then(parseJSON);
}

export const fetchTicketScheduleEntryIds = ticketId => {
  const headers = {
    Authorization: `Basic ${process.env.REACT_APP_API_KEY}`,
  };
  return fetch(`${process.env.REACT_APP_API_URL}/v1/ticket/${ticketId}/scheduleentries`, { headers }).then(checkStatus).then(parseJSON);
}

export const fetchScheduleEntryById = entryId => {
  const headers = {
    Authorization: `Basic ${process.env.REACT_APP_API_KEY}`,
  };
  return fetch(`${process.env.REACT_APP_API_URL}/v1/schedule/entries/${entryId}`, { headers }).then(checkStatus).then(parseJSON);
}

export const dispatchTickets = params => {
  const headers = {
    Authorization: `Basic ${process.env.REACT_APP_API_KEY}`,
    'Content-Type': 'application/json'
  };

  return fetch(`${process.env.REACT_APP_API_URL}/v1/dispatch`, {
    headers,
    method: 'POST',
    body: JSON.stringify(params),
  }).then(checkStatus).then(parseJSON);
}

Add schedule entry http requests


// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file.
//
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery
//= require jquery-ui
//= require jquery_ujs
//= require 'jquery-readyselector'
//= require select2
//= require 'polyfills'
//= require 'lib/requests'
//= require 'lib/ui/content'
//= require 'lib/ui/filter-selectize'
//= require 'lib/ckeditor'
//= require old_tree/fonts
//= require old_tree/ui
//= require export
//= require_tree ./old_tree

// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file.
//
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery
//= require jquery-ui
//= require jquery_ujs
//= require 'jquery-readyselector'
//= require select2
//= require 'polyfills'
//= require 'lib/requests'
//= require 'lib/ui/skip-link-focus-fix'
//= require 'lib/ui/content'
//= require 'lib/ui/filter-selectize'
//= require 'lib/ckeditor'
//= require old_tree/fonts
//= require old_tree/ui
//= require export
//= require_tree ./old_tree

Include skip-link-focus-fix.js in bundled js.


/**
*
* FunctionsList
*
*/

import React, { PropTypes } from 'react';
import LoadingIndicator from 'components/LoadingIndicator';
import FunctionListItem from 'containers/FunctionListItem';
// import styled from 'styled-components';
import { FormattedMessage } from 'react-intl';
import commonMessages from 'messages';

function FunctionsList({ loading, error, items, onRemove }) {
  if (loading) {
    return <LoadingIndicator />;
  }
  if (error !== false) {
    return
{ error }
;
  }
  return (

          <FormattedMessage {...commonMessages.name} />
          <FormattedMessage {...commonMessages.environment} />
          <FormattedMessage {...commonMessages.trigger} />
          <FormattedMessage {...commonMessages.action} />

        {
          items.map((item, index) => (
            <FunctionListItem item={item} key={`function-${index}`} onRemove={() => { onRemove(item); }} />
          ))
        }

  );
}

FunctionsList.propTypes = {
  loading: PropTypes.bool,
  error: PropTypes.any,
  items: PropTypes.oneOfType([
    PropTypes.object,
    PropTypes.array,
  ]),
  onRemove: PropTypes.func,
};

export default FunctionsList;

/**
*
* FunctionsList
*
*/

import React, { PropTypes } from 'react';
import LoadingIndicator from 'components/LoadingIndicator';
import ErrorIndicator from 'components/ErrorIndicator';
import FunctionListItem from 'containers/FunctionListItem';
// import styled from 'styled-components';
import { FormattedMessage } from 'react-intl';
import commonMessages from 'messages';

function FunctionsList({ loading, error, items, onRemove }) {
  if (loading) {
    return <LoadingIndicator />;
  }
  if (error !== false) {
    return <ErrorIndicator error={error} />;
  }
  return (

          <FormattedMessage {...commonMessages.name} />
          <FormattedMessage {...commonMessages.environment} />
          <FormattedMessage {...commonMessages.trigger} />
          <FormattedMessage {...commonMessages.action} />

        {
          items.map((item, index) => (
            <FunctionListItem item={item} key={`function-${index}`} onRemove={() => { onRemove(item); }} />
          ))
        }

  );
}

FunctionsList.propTypes = {
  loading: PropTypes.bool,
  error: PropTypes.any,
  items: PropTypes.oneOfType([
    PropTypes.object,
    PropTypes.array,
  ]),
  onRemove: PropTypes.func,
};

export default FunctionsList;

Fix react multi child error of FunctionsList


var combineTwoStrings = function (str1, str2, str3) {
  var pos1 = 0,
      pos2 = 0;

  if (str1 + str2 === str3) { return true; }

  for (var i = 0; i < str3.length; i++) {
    if (str3[i] === str1[pos1]) {
      pos1++;
    }
    else if (str3[i] === str2[pos2]) {
      pos2++;
    }
    else if (str3.length - i - 1 < str1.length - pos1 + str2.length - pos2) {
      return false;
    }
  }
  return true;
};

var combineTwoStrings = function (str1, str2, combined) {
  // Generate all the posible paths between `str1` and `str2`
  var paths = {};

  // Check the string lengths are the same to begin
  if ((str1 + str2).length !== combined.length) {
    return false;
  }

  // Finding paths is essentially the anagrams solution
  (function findPath (str1, str2, path) {
    if (path.length === combined.length) { return paths[path] = true; }

    // Find the next path from the first character of either strings
    str1 && findPath(str1.substr(1), str2, path + str1.substr(0, 1));
    str2 && findPath(str1, str2.substr(1), path + str2.substr(0, 1));
  })(str1, str2, '');

  return combined in paths;
};

Reimplement combine strings, much easier to precompute paths.


jQuery(function(){

	jQuery('button.link').click(function(){
		window.location.href = $(this).find('span.href').text();
	});

	jQuery('form.destroy_link_to').submit(function(){
		if( !confirm('Seriously?') ){
			return false;
		}
	});

	jQuery('p.flash').click(function(){$(this).remove();});

});

jQuery(function(){

	jQuery('button.link').click(function(){
		window.location.href = $(this).find('span.href').text();
	});

	jQuery('form.destroy_link_to').submit(function(){
		var message = "Destroy?  Seriously?"
		if( this.confirm && this.confirm.value ) {
			message = this.confirm.value
		}
		if( !confirm(message) ){
			return false;
		}
	});

	jQuery('p.flash').click(function(){$(this).remove();});

});

Check form for confirm field and use it if it exists.


'use strict';

/**
 * @ngdoc function
 * @name hmdaPilotApp.controller:selectFile
 * @description
 * # Select File
 * Controller for selecting a HMDA file and Reporting Year for verification.
 */
module.exports = function ($scope, RuleEngine) {
    var fiscalYears = RuleEngine.getFiscalYears();

    // Populate the $scope
    $scope.reportingYears = fiscalYears;

    // Set default values for any form fields
    $scope.hmdaData = {
        year: fiscalYears[1],
        file: ''
    };

    // Process the form submission
    $scope.submit = function(hmdaData) {
        // Nothing to hook up to yet...
    };
};

'use strict';

/**
 * @ngdoc function
 * @name hmdaPilotApp.controller:selectFile
 * @description
 * # Select File
 * Controller for selecting a HMDA file and Reporting Year for verification.
 */
module.exports = function ($scope, RuleEngine) {
    var fiscalYears = RuleEngine.getFiscalYears();

    // Populate the $scope
    $scope.reportingYears = fiscalYears;

    // Set default values for any form fields
    $scope.hmdaData = {
        year: fiscalYears[1],
        file: ''
    };

    // Process the form submission
    $scope.submit = function(hmdaData) {
        console.log('HMDA Data: ', hmdaData);
        // Nothing to hook up to yet...
    };
};

Add a little console.log to use the hmdaData or else jshint complains about the param never being used


$(function() {

  var form = $('form');
  var inputs = form.find(':input');
  var required = inputs.filter('[required]');
  var submit = inputs.filter('[type=submit]');
  var confirmation = form.find('.confirmation');

  // On touch devices, the links with drop-downs aren't normally hoverable, so
  // we need to disable their action to allow them to expand the drop-down only.
  if ('ontouchstart' in document.documentElement) {
    $('.menu-item:has(.menu-dropdown) > a').click(function() {
      return false;
    });
  }

  // Primitive form validation; inform immediately on missing a required field
  required.blur(function() {
    var input = $(this);
    input.toggleClass('invalid', !input.val());
  });

  // Keep the submit button disabled until everything is valid
  required.keyup(function() {
    var valid = true;
    required.each(function() {
      return valid &= !!$(this).val();
    });
    submit.prop('disabled', !valid);
  });

  // Submit asynchronously and show a confirmation message
  form.submit(function(event) {
    event.preventDefault();

    confirmation.hide();
    inputs.prop('disabled', true);

    var url = form.attr('action');
    $.post(url, form.serialize()).done(function() {
      inputs.prop('disabled', false);
      confirmation.show();
    });
    return false;
  });
});

$(function() {

  var form = $('form');
  var inputs = form.find(':input');
  var required = inputs.filter('[required]');
  var submit = inputs.filter('[type=submit]');
  var confirmation = form.find('.confirmation');

  // On touch devices, the links with drop-downs aren't normally hoverable, so
  // we need to disable their action to allow them to expand the drop-down only.
  if ('ontouchstart' in document.documentElement) {
    $('.menu-item:has(.menu-dropdown) > a').click(function() {
      return false;
    });
  }

  // Primitive form validation; inform immediately on missing a required field
  required.blur(function() {
    var input = $(this);
    input.toggleClass('invalid', !input.val());
  });

  // Keep the submit button disabled until everything is valid
  required.keyup(function() {
    var valid = true;
    required.each(function() {
      return valid &= !!$(this).val();
    });
    submit.prop('disabled', !valid);
  });

  // Submit asynchronously and show a confirmation message
  form.submit(function(event) {
    var url = form.attr('action');
    var data = form.serialize();

    confirmation.hide();
    inputs.prop('disabled', true);

    $.post(url, data).always(function() {
      window.console.log('You can ignore the error above.');
      inputs.prop('disabled', false);
      confirmation.show();
    });

    event.preventDefault();
    return false;
  });
});

Fix form submission to handle CORS errors.


const fs = require('fs')
const path = require('path')

const appDirectory = fs.realpathSync(process.cwd())

function resolveApp(relativePath) {
  return path.resolve(appDirectory, relativePath)
}

function resolveSelf(relativePath) {
  return path.resolve(__dirname, '../', relativePath)
}

module.exports = {
  root: appDirectory,
  dist: resolveApp('dist'),
  static: resolveApp('static'),
  src: resolveApp('src'),
  lib: resolveSelf('lib'),
  entrypoints: {
    scripts: resolveApp('src/assets/js/index.js'),
    static: resolveSelf('lib/static-files-glob.js')
  },
  assetsOutput: resolveApp('dist/assets'),
  userShopifyConfig: resolveApp('shopify.yml'),
  eslintrc: resolveSelf('./.eslintrc')
}

const fs = require('fs')
const path = require('path')

const appDirectory = fs.realpathSync(process.cwd())

function resolveApp(relativePath) {
  return path.resolve(appDirectory, relativePath)
}

function resolveSelf(relativePath) {
  return path.resolve(__dirname, '../', relativePath)
}

function getEslintrc() {
  const appEslintrc = resolveApp('./.eslintrc')

  if (fs.existsSync(appEslintrc)) {
    return appEslintrc
  }

  return resolveSelf('./.eslintrc')
}

module.exports = {
  root: appDirectory,
  dist: resolveApp('dist'),
  static: resolveApp('static'),
  src: resolveApp('src'),
  lib: resolveSelf('lib'),
  entrypoints: {
    scripts: resolveApp('src/assets/js/index.js'),
    static: resolveSelf('lib/static-files-glob.js')
  },
  assetsOutput: resolveApp('dist/assets'),
  userShopifyConfig: resolveApp('shopify.yml'),
  eslintrc: getEslintrc()
}

Return app eslintrc path if the file exists


$(document).on("ready", function() {

  // $("a").click(".snowflake", function(event){
  //   event.preventDefault();
  //   event.stopPropagation();
  //   debugger
  //   // var target = $(event.target).parent().find("#vote_count")
  //   $.ajax({
  //     type: "POST",
  //     url : $(this).attr('href'),
  //     dataType: "json",
  //   }).done(function(response){
  //     debugger
  //     alert("Success!")
  //   }).fail(function(response){
  //     console.log(response);
  //   });
  // });

  $("#post").on("click", function(event){
    event.preventDefault();
    $.ajax({
      url : $(event.target).attr('href'),
    }).done(function(response){
      $(".comment-form").show();
    }).fail(function(response){
      alert("You are unable to post a comment.");
    });
  });

  $('.comment-form').on("submit", 'form', function(event){
    event.preventDefault();
    var id = $('#comment_trail_id').val()
    $.ajax({
      type: $(event.target).attr('method'),
      url : '/trails/' + id + '/comments',
      data: $(this).serialize()
    }).done(function(response){
      debugger;
      $(".river").append(response);
      $(".comment-form").remove();
      $(".comment-form").hide();
    }).fail(function(){
      alert("Your post has not been posted")
    });
  });

});

$(document).on("ready", function() {

  $("#post").on("click", function(event){
    event.preventDefault();
    $.ajax({
      url : $(event.target).attr('href'),
    }).done(function(response){
      $(".comment-form").show();
    }).fail(function(response){
      alert("You are unable to post a comment.");
    });
  });

  // $("a").click(".snowflake", function(event){
  //   event.preventDefault();
  //   event.stopPropagation();
  //   debugger
  //   // var target = $(event.target).parent().find("#vote_count")
  //   $.ajax({
  //     type: "POST",
  //     url : $(this).attr('href'),
  //     dataType: "json",
  //   }).done(function(response){
  //     debugger
  //     alert("Success!")
  //   }).fail(function(response){
  //     console.log(response);
  //   });
  // });

  // $('.comment-form').on("submit", 'form', function(event){
  //   event.preventDefault();
  //   var id = $('#comment_trail_id').val()
  //   // debugger;
  //   $.ajax({
  //     type: $(event.target).attr('method'),
  //     url : '/trails/' + id + '/comments',
  //     data: $(this).serialize()
  //   }).done(function(response){
  //     debugger;
  //     $(".river").toggle().toggle();
  //     $(".comment-form").remove();
  //     $(".comment-form").hide();
  //   }).fail(function(){
  //     alert("Your post has not been posted")
  //   });
  // });

});

Comment out AJAX data calls


function Scoreboard(options) {
  this.containers = {
    best: options.best,
    score: options.score
  };
  this.storage_key = 'personal_best'

  try {
    this.best = localStorage.getItem(this.storage_key) || 0;
  } catch (e) {
    this.best = 0;
  }

  this.score = 0;
  this.render();
};

Scoreboard.prototype.add = function(points) {
  this.score += points;
  if (this.best < this.score) {
    this.best = this.score;

    try {
      localStorage.setItem(this.storage_key, this.best);
    } catch (e) {
      // let's fail silently
    }
  }
  this.render();
};

Scoreboard.prototype.render = function() {
  this.containers.score.innerHTML = this.score;
  this.containers.best.innerHTML = this.best;
}

function Scoreboard(options) {
  this.containers = {
    best: options.best,
    score: options.score
  };
  this.storage_key = 'memchimp.personal_best'

  try {
    this.best = localStorage.getItem(this.storage_key) || 0;
  } catch (e) {
    this.best = 0;
  }

  this.score = 0;
  this.render();
};

Scoreboard.prototype.add = function(points) {
  this.score += points;
  if (this.best < this.score) {
    this.best = this.score;

    try {
      localStorage.setItem(this.storage_key, this.best);
    } catch (e) {
      // let's fail silently
    }
  }
  this.render();
};

Scoreboard.prototype.render = function() {
  this.containers.score.innerHTML = this.score;
  this.containers.best.innerHTML = this.best;
}

Change localStorage key for personal best


import Inferno from 'inferno'
import Component from 'inferno-component'
import styled from 'styled-components'
import { bind, debounce } from 'decko'

import { RaisedButton } from '@slup/buttons'
import { Slider } from '@slup/slider'
import { Logo } from '../components/logo'

const LogoContainer = styled.div`
  display: flex;
  flex: 1;
  height: 100%;
  align-items: center;
  justify-content: center;
  flex-direction: column;
`

export class Home extends Component {
  state = { value: 0, focus: false }

  @bind
  onChange(value) {
    this.setState({ value })
  }

  @bind
  onFocus() {
    this.setState({ focus: true })
  }

  @bind
  onBlur() {
    console.log('called')
    this.setState({ focus: false })
  }

  @bind
  @debounce(300)
  handleClick() {
    this.context.router.push('/components/buttons')
  }

  render() {
    return (
      <LogoContainer>
        {Logo}
        <RaisedButton onClick={this.handleClick} primary>Get started</RaisedButton>
        <Slider
          value={this.state.value}
          focus={this.state.focus}
          keyFocus={this.state.focus}
          max={100}
          onChange={this.onChange}
          onFocus={this.onFocus}
          onBlur={this.onBlur}
          discrete
          steps={10} />
      </LogoContainer>
    )
  }
}

import Inferno from 'inferno'
import Component from 'inferno-component'
import styled from 'styled-components'
import { bind, debounce } from 'decko'

import { RaisedButton } from '@slup/buttons'
import { Slider } from '@slup/slider'
import { Logo } from '../components/logo'

const LogoContainer = styled.div`
  display: flex;
  flex: 1;
  height: 100%;
  align-items: center;
  justify-content: center;
  flex-direction: column;
`

export class Home extends Component {
  @bind
  @debounce(300)
  handleClick() {
    this.context.router.push('/components/buttons')
  }

  render() {
    return (
      <LogoContainer>
        {Logo}
        <RaisedButton onClick={this.handleClick} primary>Get started</RaisedButton>
      </LogoContainer>
    )
  }
}

Revert "Revert ":fire: Removed useless sample code""


/*!
 * debug
 * Copyright(c) 2012 TJ Holowaychuk <tj@vision-media.ca>
 * MIT Licensed
 */

/**
 * Previous debug() call.
 */

var prev = {};

/**
 * Create a debugger with the given `name`.
 *
 * @param {String} name
 * @return {Type}
 * @api public
 */

function debug(name) {
  if (!debug.enable) {
    throw new Error('You must set a function for `debug.enable`');
  }

  var enabled = debug.enable(name);

  if (!enabled) return function(){};

  function plain(fmt) {
    var curr = new Date;
    var ms = curr - (prev[name] || curr);
    prev[name] = curr;

    fmt = name
      + ' +' + ms + 'ms '
      + fmt;

    console.log.apply(console, arguments);
  }

  return plain;
}

/*!
 * debug
 * Copyright(c) 2012 TJ Holowaychuk <tj@vision-media.ca>
 * MIT Licensed
 */

/**
 * Previous debug() call.
 */

var prev = {};

/**
 * Create a debugger with the given `name`.
 *
 * @param {String} name
 * @return {Type}
 * @api public
 */

function debug(name) {
  if (!debug.enable) {
    throw new Error('You must set a function for `debug.enable`');
  }

  var enabled = debug.enable(name);

  if (!enabled) return function(){};

  function plain(fmt) {
    var curr = new Date;
    var ms = curr - (prev[name] || curr);
    prev[name] = curr;

    fmt = name
      + ' +' + ms + 'ms '
      + fmt;

    // This hackery is required for IE8, where `console.log` doesn't have 'apply'
    window.console && console.log &&
      Function.prototype.apply.call(console.log, console, arguments);
  }

  return plain;
}

Make it work in IE8.


import { quat_create, quat_set, quat_normalize, quat_multiply } from './quat';

var quat = quat_create();

export function controls_create(object) {
  var controls = {
    object: object,
    speed: 1,
    turnRate: Math.PI / 4,
    sensitivity: 0.002,
    enabled: false,

    onMouseMove: function(event) {
      if (!controls.enabled) {
        return;
      }

      var movementX = event.movementX || event.mozMovementX || 0;
      var movementY = event.movementY || event.mozMovementY || 0;

      var pitch = -movementY * controls.sensitivity;
      var yaw = -movementX * controls.sensitivity;

      quat_normalize(quat_set(quat, pitch, yaw, 0, 1));
      quat_multiply(object.quaternion, quat);
    },
  };

  document.addEventListener('mousemove', controls.onMouseMove);

  return controls;
}

export function controls_dispose(controls) {
  document.removeEventListener('mousemove', controls.onMouseMove);
}

import { quat_create, quat_copy, quat_set, quat_normalize, quat_multiply } from './quat';

var pitchQuat = quat_create();
var yawQuat = quat_create();

export function controls_create(object) {
  var controls = {
    object: object,
    speed: 1,
    turnRate: Math.PI / 4,
    sensitivity: 0.002,
    enabled: false,

    onMouseMove: function(event) {
      if (!controls.enabled) {
        return;
      }

      var movementX = event.movementX || event.mozMovementX || 0;
      var movementY = event.movementY || event.mozMovementY || 0;

      var pitch = -movementY * controls.sensitivity;
      var yaw = -movementX * controls.sensitivity;

      quat_normalize(quat_set(pitchQuat, pitch, 0, 0, 1));
      quat_normalize(quat_set(yawQuat, 0, yaw, 0, 1));

      // pitch * object * yaw
      quat_multiply(object.quaternion, pitchQuat);
      quat_multiply(yawQuat, object.quaternion);
      quat_copy(object.quaternion, yawQuat);
    },
  };

  document.addEventListener('mousemove', controls.onMouseMove);

  return controls;
}

export function controls_dispose(controls) {
  document.removeEventListener('mousemove', controls.onMouseMove);
}

Fix decomposition of camera quaternions


"use strict";
function createDialog(pageMantle, dialogHtml, dialogCss) {

	var dialog = {};

	// write dialog HTML to document
	var dialogDiv = jQuery('

');
	dialogDiv.appendTo(document.body);

	// create iframe
	var iframe = jQuery('');
	iframe.appendTo(dialogDiv);
	iframe.load(function() {

		var dialogDocument = iframe[0].contentWindow.document;
		dialogDocument.write(dialogHtml); // document.write() has the advantage to actually use the doctype from the given string.
		dialogDocument.close();

		// write dialog content CSS to dialog
		var dialogCssElement = jQuery('');
		dialogCssElement.appendTo(dialogDocument.head);

		var dialogContent = createDialogContent(dialogDocument, pageMantle);

		var keepPositionFixed = function(event) {
			$(event.target).parent().css('position', 'fixed');
		};
		$(dialogDiv).dialog({
			dialogClass : 'ftc_dialogDiv',
			/*TODO fix the vertical position. This seems especially difficult for pages without html5 doctype.
			 position : ['center', 'bottom'], //this works with the html5 doctype, but not in quirks mode
			 */
			width : 750,
			height : 270,
			create : keepPositionFixed,
			resize : keepPositionFixed,
			close : pageMantle.destroy
		});

	});

	return dialog;
}

"use strict";
function createDialog(pageMantle, dialogHtml, dialogCss) {

	var dialog = {};

	// write dialog HTML to document
	var dialogDiv = jQuery('

');
	dialogDiv.appendTo(document.body);

	// create iframe
	var iframe = jQuery('');
	iframe.appendTo(dialogDiv);
	iframe.load(function() {

		var dialogDocument = iframe[0].contentWindow.document;
		// document.write() has the advantage to actually use the doctype from the given string.
		dialogDocument.write(dialogHtml);
		dialogDocument.close();

		// write dialog content CSS to dialog
		var dialogCssElement = jQuery('');
		dialogCssElement.appendTo(dialogDocument.head);

		var dialogContent = createDialogContent(dialogDocument, pageMantle);

		var keepPositionFixed = function(event) {
			$(event.target).parent().css('position', 'fixed');
		};
		$(dialogDiv).dialog({
			dialogClass : 'ftc_dialogDiv',
			position : ['center', 'bottom'], // FIXME in quirks mode the vertical position is unpredictable and sometimes even invisible
			width : 750,
			height : 270,
			create : keepPositionFixed,
			resize : keepPositionFixed,
			close : pageMantle.destroy
		});

	});

	return dialog;
}

Change the vertical position to work correctly in standards mode.


// @flow
import React from 'react'

const Box = (props: any) => (
  <div {...props} />
)
export default Box

// @flow
import React, {Component} from 'react'

export default class Box extends Component {
  render () {
    return (
      <div {...this.props} />
    )
  }
}

Revert Box to a stateful component to fix refs usage


module.exports = {
    activeUserCount: require('./activeUserCount.js'),
    activeUsers: require('./activeUsers.js'),
    slackPostMessage: require('./slackPostMessage.js'),
    createDir: require('./createDir.js'),
    createDirQVD: require('./createDirQVD.js'),
    getDiskSpace: require('./getDiskSpace.js'),
    mqttPublishMessage: require('./mqttPublishMessage.js'),
    senseStartTask: require('./senseStartTask.js'),
    senseQRSPing: require('./senseQRSPing.js'),
    senseAppDump: require('./senseAppDump.js'),
    senseListApps: require('./senseListApps.js'),
    butlerPing: require('./butlerPing.js'),
    base62ToBase16: require('./baseConversion.js'),
    base16ToBase62: require('./baseConversion.js')
};

module.exports = {
    activeUserCount: require('./activeUserCount.js'),
    activeUsers: require('./activeUsers.js'),
    slackPostMessage: require('./slackPostMessage.js'),
    createDir: require('./createDir.js'),
    createDirQVD: require('./createDirQVD.js'),
    mqttPublishMessage: require('./mqttPublishMessage.js'),
    senseStartTask: require('./senseStartTask.js'),
    senseQRSPing: require('./senseQRSPing.js'),
    senseAppDump: require('./senseAppDump.js'),
    senseListApps: require('./senseListApps.js'),
    butlerPing: require('./butlerPing.js'),
    base62ToBase16: require('./baseConversion.js'),
    base16ToBase62: require('./baseConversion.js')
};
//getDiskSpace: require('./getDiskSpace.js'),

Remove disk space REST endpoint


$(document).ready( function () {
  $(".upvote").click( function() {
    var commentId = $(".upvote").data("id");
    event.preventDefault();
    $.ajax({
      url: '/response/up_vote',
      method: 'POST',
      data: { id: commentId },
      dataType: 'JSON'
    }).done( function (voteCount) {

    }).fail( function (voteCount) {
        console.log("Failed. Here is the voteCount:");
        console.log(voteCount);
    })
  })
})

$(document).ready( function () {
  $(".upvote").click( function() {
    var commentId = $(".upvote").data("id");
    event.preventDefault();
    $.ajax({
      url: '/response/up_vote',
      method: 'POST',
      data: { id: commentId },
      dataType: 'JSON'
    }).done( function (voteCount) {
      if (voteCount == 1) {
        $("span[data-id=" + commentId + "]").html(voteCount + " vote");
      } else {
        $("span[data-id=" + commentId + "]").html(voteCount + " vote");
      }

    }).fail( function (voteCount) {
        console.log("Failed. Here is the voteCount:");
        console.log(voteCount);
    })
  })
})

Add inflection logic upon successful ajax upvote call.


angular.module('siyfion.sfTypeahead', [])
  .directive('sfTypeahead', function () {
    return {
      restrict: 'ACE',
      scope: {
        datasets: '=',
        ngModel: '='
      },
      link: function (scope, element) {
        element.typeahead(scope.datasets);

        // Updates the ngModel binding when a value is manually selected from the dropdown.
        // ToDo: Think about how the value could be updated on user entry...
        element.bind('typeahead:selected', function (object, datum) {
          scope.$apply(function() {
            scope.ngModel = datum;
          });
        });

        // Updates the ngModel binding when a query is autocompleted.
        element.bind('typeahead:autocompleted', function (object, datum) {
          scope.$apply(function() {
            scope.ngModel = datum;
          });
        });

        // Updates typeahead when ngModel changed.
        scope.$watch('ngModel', function (newVal) {
            var valueKey = scope.datasets.valueKey;
            if (newVal && valueKey && newVal.hasOwnProperty(valueKey)) {
                newVal = newVal[valueKey];
            }
            element.typeahead('setQuery', newVal || '');
        });
      }
    };
  });

angular.module('siyfion.sfTypeahead', [])
  .directive('sfTypeahead', function () {
    return {
      restrict: 'ACE',
      scope: {
        datasets: '=',
        ngModel: '='
      },
      link: function (scope, element) {
        element.typeahead(scope.datasets);

        // Updates the ngModel binding when a value is manually selected from the dropdown.
        // ToDo: Think about how the value could be updated on user entry...
        element.bind('typeahead:selected', function (object, datum, dataset) {
          scope.$apply(function() {
            scope.ngModel = datum;
            scope.selectedDataset = dataset
          });
        });

        // Updates the ngModel binding when a query is autocompleted.
        element.bind('typeahead:autocompleted', function (object, datum) {
          scope.$apply(function() {
            scope.ngModel = datum;
          });
        });

        // Updates typeahead when ngModel changed.
        scope.$watch('ngModel', function (newVal) {
            if ($.isArray(scope.datasets)) {
                for (var i=0;i<scope.datasets.length;i++) {
                    if (scope.datasets[i].name ==  scope.selectedDataset) {
                        var valueKey = scope.datasets[i].valueKey;
                        break;
                    }
                }

            } else {
                var valueKey = scope.datasets.valueKey;
            }

            if (newVal && valueKey && newVal.hasOwnProperty(valueKey)) {
                newVal = newVal[valueKey];
            }
            element.typeahead('setQuery', newVal || '');
        });
      }
    };
  });

Add support for multiple datasets


'use strict'

module.exports = {
  presets: [
    require('babel-preset-es2015'),
    require('babel-preset-stage-2')
  ],
  plugins: [
    require('babel-plugin-transform-decorators-legacy'),
    [require('babel-plugin-transform-react-jsx'), {
      pragma: 'createElement'
    }]
  ],
  env: {
    production: {
      plugins: [
        require('babel-plugin-transform-react-constant-elements'),
        require('babel-plugin-transform-react-inline-elements')
      ]
    }
  }
}

'use strict'

module.exports = {
  presets: [
    require('babel-preset-es2015'),
    require('babel-preset-stage-2')
  ],
  plugins: [
    require('babel-plugin-transform-decorators-legacy').default,
    [require('babel-plugin-transform-react-jsx'), {
      pragma: 'createElement'
    }]
  ],
  env: {
    production: {
      plugins: [
        require('babel-plugin-transform-react-constant-elements'),
        require('babel-plugin-transform-react-inline-elements')
      ]
    }
  }
}

Fix bug when requiring babel-plugin-transform-decorators-legacy


#!/usr/bin/env node

var program = require('commander');
var prompt = require('inquirer');
var path = require('path');

var pkg = require(path.resolve(__dirname, '..', 'package.json'));

program.version(pkg.version)

program
    .command('new <name>')
    .description('Create a new project with the provided name')
    .action(function(name) {
        console.log('hits new command');
        console.log('project name is ->', name);
    });

program.parse(process.argv);

#!/usr/bin/env node

var program = require('commander');
var prompt = require('inquirer');
var path = require('path');
var chalk = require('chalk');

var pkg = require(path.resolve(__dirname, '..', 'package.json'));

program.version(pkg.version)

program
    .command('new <name>')
    .description('Create a new project with the provided name')
    .action(function(name) {
        console.log('Creating a new project: %s', chalk.underline(name));
        console.log('@TODO add this in!');
    });

program.parse(process.argv);

Add user message outputting given project name


var FakeRedis = require('fakeredis');
var RedisCache = require('../../cache/RedisCache');
var Promise = require('bluebird');

var expect = require('expect');
var updateAddChunkPrefixes = require('../updateAddChunkPrefixes');
var sinon = require('sinon');

describe('expireChunkRanges', function() {
  var cache = new RedisCache(FakeRedis.createClient());
  var listName = 'list';

  it('should call methods for all specified chunks', function() {
    var ranges = [[1, 3], 5];
    var prefixes = ['a'];
    var mockCache = sinon.mock(cache);
    mockCache.expects('getChunkByID')
      .exactly(4).returns(Promise.resolve(['a']));
    mockCache.expects('dropPrefixes')
      .exactly(4).returns(Promise.resolve());
    mockCache.expects('dropPendingSubChunksByChunkID')
      .exactly(4).returns(Promise.resolve());
    mockCache.expects('dropChunkByID')
      .exactly(4).returns(Promise.resolve());
  });
});

var FakeRedis = require('fakeredis');
var RedisCache = require('../../cache/RedisCache');
var ChunkTypes = require('../ChunkTypes');
var Promise = require('bluebird');

var expect = require('expect');
var expireChunkRanges = require('../expireChunkRanges');
var sinon = require('sinon');

describe('expireChunkRanges', function() {
  var cache = new RedisCache(FakeRedis.createClient());
  var listName = 'list';

  it('should call methods for all specified chunks', function() {
    var ranges = [[1, 3], 5];
    var prefixes = ['a'];

    var mockCache = sinon.mock(cache);
    mockCache.expects('getChunkByID')
      .exactly(4).returns(Promise.resolve(['a']));
    mockCache.expects('dropPrefixes')
      .exactly(4).returns(Promise.resolve());
    mockCache.expects('dropPendingSubChunksByChunkID')
      .exactly(4).returns(Promise.resolve());
    mockCache.expects('dropChunkByID')
      .exactly(4).returns(Promise.resolve());

    return expireChunkRanges(cache, listName, ChunkTypes.ADD, ranges)
      .then(function() {
        mockCache.verify();
        mockCache.restore();
      });
  });
});

Fix expireChunkRanges test to actually work.


const config = {
  common: {
    outputDir: 'public',
    styles: [],
    scripts: [],
  },
  template: {
    common: {
      path: 'templates', // Path where the templates are
    },
    pug: {
      pretty: true,
    },
  },
  file: {
    html: {},
    json: {},
    markdown: {},
    yaml: {},
  },
  asset: {
    active: [],
    common: {
      path: 'assets', // path to assets
    },
    styles: {
      postcss: {
        plugins: {
          prod: [{ name: 'cssnano' }],
          dev: [],
        },
      },
    },
    scripts: {},
    fonts: {},
    images: {
      minify: {},
    },
  },
  content: {
    active: [],
    common: {
      path: 'content', // path to content
      urlWithExtension: false,
      htmlmin: {
        removeAttributeQuotes: true,
        collapseWhitespace: true,
      },
    },
    pages: {},
    posts: {
      urlFormat: '_blog/${year}/${month}/${date}/', // eslint-disable-line
    },
  },
  decorator: {
    active: [],
    common: {},
    sitemap: {},
    tags: {},
    pagination: {},
  },
};

module.exports = config;

const config = {
  common: {
    outputDir: 'public',
    styles: [],
    scripts: [],
    urlWithExtension: false,
  },
  template: {
    common: {
      path: 'templates', // Path where the templates are
    },
    pug: {
      pretty: true,
    },
  },
  file: {
    html: {},
    json: {},
    markdown: {},
    yaml: {},
  },
  asset: {
    active: [],
    common: {
      path: 'assets', // path to assets
    },
    styles: {
      postcss: {
        plugins: {
          prod: [{ name: 'cssnano' }],
          dev: [],
        },
      },
    },
    scripts: {},
    fonts: {},
    images: {
      minify: {},
    },
  },
  content: {
    active: [],
    common: {
      path: 'content', // path to content
      htmlmin: {
        removeAttributeQuotes: true,
        collapseWhitespace: true,
      },
    },
    pages: {},
    posts: {
      urlFormat: '_blog/${year}/${month}/${date}/', // eslint-disable-line
    },
  },
  decorator: {
    active: [],
    common: {},
    sitemap: {},
    tags: {},
    pagination: {},
  },
};

module.exports = config;

Move urlWithExtension to the global common


#!/usr/bin/env node
'use strict';

const Path = require('path');
const server = require('../utils/test-metadata-server');

global.Config = require('nconf')
    .argv()
    .env()
    .file(Path.resolve(__dirname, '../data/config.json'))
    .defaults(require('../../config/defaults.json'));
global.Log = require('../../lib/logger').attach(global.Config);

server.start();

#!/usr/bin/env node
'use strict';

const Path = require('path');
const server = require('../utils/test-metadata-server');

global.Config = require('nconf')
    .argv()
    .env()
    .file(Path.resolve(__dirname, '../data/config.json'))
    .defaults(require('../../config/defaults.json'));
global.Log = require('../../lib/logger').attach(Config.get('log:level'));

server.start();

Allow the test metadata server to start by giving it a log level.


import Client from './state-client';

// Wrapper around global Twitch extension object.
class Ext {
  static onAuthorized(cb) {
    if (window.Twitch) {
      window.Twitch.ext.onAuthorized(cb);
    } else {
      Client.fetchTestAuth(this.testAppID, this.testChannelID)
        .then((auth) => {
          cb(auth);
        })
        .catch(() => {
          cb();
        });
    }
  }

  static onContext(cb) {
    if (window.Twitch) {
      window.Twitch.ext.onContext(cb);
    }
  }
}

export default Ext;

import Client from './state-client';

// Wrapper around global Twitch extension object.
class Ext {
  static fetchTestAuth(cb) {
    Client.fetchTestAuth(this.testAppID, this.testChannelID)
      .then((auth) => {
        cb(auth);
      })
      .catch(() => {
        cb();
      });
  }

  static onAuthorized(cb) {
    if (window.Twitch) {
      window.Twitch.ext.onAuthorized(cb);
    } else {
      Ext.fetchTestAuth(cb);
      setInterval(Ext.fetchTestAuth, 1000 * 60 * 25, cb);
    }
  }

  static onContext(cb) {
    if (window.Twitch) {
      window.Twitch.ext.onContext(cb);
    }
  }
}

export default Ext;

Refresh the test token every 25 minutes


import React from 'react';
import {render} from 'react-dom';
import Rx from 'rx';
import {actionIs} from './utils.js';

/*
update is an Array of trios: [actionName, actionHandler, effect]
*/

export default function(model, update, View, container) {
  let eventStream = new Rx.Subject();

  let dispatcherArray = update.map(([actionName, actionHandler]) =>
    eventStream.filter(actionIs(actionName)).map(actionHandler))

  return Rx.Observable
    .merge(...dispatcherArray)
    .scan(((state, action) => action(state)), model)
    .startWith([model])
    .do(([state, _]) => render(<View {...state} eventStream={eventStream} />, container))
    .flatMap(([state, effect]) => effect ? effect(state, eventStream) : Rx.Observable.empty() )
    .subscribe(
      function(){},
      console.error.bind(console)
    );
}

import React from 'react';
import {render} from 'react-dom';
import Rx from 'rx';
import {actionIs} from './utils.js';

/*
update is an Array of trios: [actionName, actionHandler, effect]
*/

export default function(model, update, View, container) {
  let eventStream = new Rx.Subject();

  let dispatcherArray = update.map(([actionName, actionHandler]) =>
    eventStream.filter(actionIs(actionName)).map(actionHandler))

  return Rx.Observable
    .merge(...dispatcherArray)
    .scan((([state, _], action) => action(state)), [model])
    .startWith([model])
    .do(([state, _]) => render(<View {...state} eventStream={eventStream} />, container))
    .flatMap(([state, effect]) => effect ? effect(state, eventStream) : Rx.Observable.empty() )
    .subscribe(
      function(){},
      console.error.bind(console)
    );
}

Correct passing of state to state mutators


import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'

import Text from 'v2/components/UI/Text'

const Copy = styled(Text)`
  a {
    font-weight: bold;
  }
  a:hover {
    color: ${x => x.theme.colors.gray.base};
  }
`

export default class SearchEmptyMessage extends PureComponent {
  static propTypes = {
    term: PropTypes.string.isRequired,
  }

  render() {
    const { term } = this.props

    return (
      <Copy f={6} my={8} color="gray.medium" lineHeight={2}>
        {`No results for '${term}'`}
      </Copy>
    )
  }
}

import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'

import Text from 'v2/components/UI/Text'

const Copy = styled(Text)`
  a {
    font-weight: bold;
  }
  a:hover {
    color: ${x => x.theme.colors.gray.base};
  }
`

export default class SearchEmptyMessage extends PureComponent {
  static propTypes = {
    term: PropTypes.string.isRequired,
  }

  render() {
    const { term } = this.props

    return (
      <Copy f={6} my={8} color="gray.medium" lineHeight={2}>
        {term
          ? `No results for '${term}'`
          : `No results found, try changing your search filters.`}
      </Copy>
    )
  }
}

Fix no results message when there is no term


/*
 * Swagger UI and Specs Servers
*/
var path = require('path')
var createServer = require('http-server').createServer;

var dist = path.join(__dirname, '..', '..', 'dist');
var specs = path.join(__dirname, '..', '..', 'test', 'specs');
var DOCS_PORT = 8080;
var SPEC_SERVER_PORT = 8081;

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

var headers = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept'
};

var swaggerUI;
var specServer;

module.exports.start = function (specsLocation, done) {
  swaggerUI = createServer({ root: dist, headers: headers });
  specServer = createServer({ root: specs, headers: headers });

  swaggerUI.listen(DOCS_PORT);
  specServer.listen(SPEC_SERVER_PORT);

  var swaggerSpecLocation = encodeURIComponent('http://localhost:' + SPEC_SERVER_PORT + specsLocation)
  var url = 'http://localhost:' + DOCS_PORT + '/index.html?url=' + swaggerSpecLocation;

  setTimeout(function(){
    driver.get(url);
    done();
  }, 3000);
};

module.exports.close = function(){
  swaggerUI.close();
  specServer.close();
}

/*
 * Swagger UI and Specs Servers
*/
var path = require('path')
var createServer = require('http-server').createServer;

var dist = path.join(__dirname, '..', '..', 'dist');
var specs = path.join(__dirname, '..', '..', 'test', 'specs');
var DOCS_PORT = 8080;
var SPEC_SERVER_PORT = 8081;

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

var headers = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept'
};

var swaggerUI;
var specServer;

module.exports.start = function (specsLocation, done) {
  swaggerUI = createServer({ root: dist, headers: headers });
  specServer = createServer({ root: specs, headers: headers });

  swaggerUI.listen(DOCS_PORT);
  specServer.listen(SPEC_SERVER_PORT);

  var swaggerSpecLocation = encodeURIComponent('http://localhost:' + SPEC_SERVER_PORT + specsLocation)
  var url = 'http://localhost:' + DOCS_PORT + '/index.html?url=' + swaggerSpecLocation;

  setTimeout(function(){
    driver.get(url);
    done();
  }, process.env.TRAVIS ? 6000 : 3000);
};

module.exports.close = function(){
  swaggerUI.close();
  specServer.close();
}

Increase timeout for server launch in TravisCI


define([], function () {
  return {
    hasLoggedInUser: function() {
        return !!(this.profile && this.profile.get('selected_identity'));
    },
    profile: null
  }
});

import { hasLoggedInUser } from 'profilePredicate';

export default {
    hasLoggedInUser: function() {
        return hasLoggedInUser(this.profile);
    },
    profile: null
}

Use utility predicate in logged in user check


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

const pluginsLoader = require('../../lib/plugins');

// Caching fixture plugin, but redefining its properties for testing plugin load.
const pluginName = 'express-gateway-plugin-test';
const pluginDirectory = path.join(__dirname, '../fixtures', pluginName);
const testPlugin = require(pluginDirectory);
testPlugin.schema = {
  '$id': `http://express-gateway.io/schemas/plugin/${pluginName}.json`,
  'required': ['schema-test-param']
};
testPlugin.init = (pluginContext) => {
  pluginContext.registerPolicy('schema-test-policy');
};

describe('Plugin schema validation on load', () => {
  it('fails loading when parameter undefined', () => {
    const missingParameterConfig = {
      'config': {
        'systemConfig': {
          'plugins': {
            'test': {
              'package': pluginDirectory
            }
          }
        }
      }
    };

    const loadedPlugins = pluginsLoader.load(missingParameterConfig);
    should(loadedPlugins).a.property('policies').be.empty();
  });

  it('loads plugin and registers policy successfully', () => {
    const config = {
      'config': {
        'systemConfig': {
          'plugins': {
            'test': {
              'package': pluginDirectory,
              'schema-test-param': 'defined'
            }
          }
        }
      }
    };

    const loadedPlugins = pluginsLoader.load(config);
    should(loadedPlugins).have.a.property('policies', ['schema-test-policy']);
  });
});

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

const pluginsLoader = require('../../lib/plugins');

// Caching fixture plugin, but redefining its properties for testing plugin load.
const pluginName = 'express-gateway-plugin-test';
const pluginDirectory = path.join(__dirname, '../fixtures', pluginName);
const testPlugin = require(pluginDirectory);

testPlugin.schema = {
  '$id': `http://express-gateway.io/schemas/plugin/${pluginName}.json`,
  'required': ['schema-test-param']
};

testPlugin.init = (pluginContext) => {
  pluginContext.registerPolicy('schema-test-policy');
};

describe('Plugin schema validation on load', () => {
  it('fails loading when parameter undefined', () => {
    const missingParameterConfig = {
      'config': {
        'systemConfig': {
          'plugins': {
            'test': {
              'package': pluginDirectory
            }
          }
        }
      }
    };

    const loadedPlugins = pluginsLoader.load(missingParameterConfig);
    should(loadedPlugins).have.property('policies').empty();
  });

  it('loads plugin and registers policy successfully', () => {
    const config = {
      'config': {
        'systemConfig': {
          'plugins': {
            'test': {
              'package': pluginDirectory,
              'schema-test-param': 'defined'
            }
          }
        }
      }
    };

    const loadedPlugins = pluginsLoader.load(config);
    should(loadedPlugins).have.property('policies', ['schema-test-policy']);
  });
});

Put new lines here and there


(function(previousMethods){

if (typeof previousMethods === 'undefined') {
	// Defining previously that object allows you to use that plugin even if you have overridden L.map
	previousMethods = {
		getTiledPixelBounds: L.GridLayer.prototype._getTiledPixelBounds
	};
}

L.GridLayer.include({

  _getTiledPixelBounds : function(center, zoom, tileZoom) {
    var pixelBounds = previousMethods.getTiledPixelBounds.call(this, center, zoom, tileZoom);

    if (this.options.edgeBufferTiles > 0) {
      var pixelEdgeBuffer = this.options.edgeBufferTiles * this._getTileSize();
      pixelBounds = new L.Bounds(pixelBounds.min.subtract([pixelEdgeBuffer, pixelEdgeBuffer]), pixelBounds.max.add([pixelEdgeBuffer, pixelEdgeBuffer]));
    }

    return pixelBounds;
  }
});

})(window.leafletEdgeBufferPreviousMethods);

(function(previousMethods){

if (typeof previousMethods === 'undefined') {
	// Defining previously that object allows you to use that plugin even if you have overridden L.map
	previousMethods = {
		getTiledPixelBounds: L.GridLayer.prototype._getTiledPixelBounds
	};
}

L.GridLayer.include({

  _getTiledPixelBounds : function(center, zoom, tileZoom) {
    var pixelBounds = previousMethods.getTiledPixelBounds.call(this, center, zoom, tileZoom);

    if (this.options.edgeBufferTiles > 0) {
      var pixelEdgeBuffer = this.options.edgeBufferTiles * this.options.tileSize;
      pixelBounds = new L.Bounds(pixelBounds.min.subtract([pixelEdgeBuffer, pixelEdgeBuffer]), pixelBounds.max.add([pixelEdgeBuffer, pixelEdgeBuffer]));
    }

    return pixelBounds;
  }
});

})(window.leafletEdgeBufferPreviousMethods);

Use tileSize direct from options


// eslint-disable-next-line no-unused-vars
function dega_bed_parser(line, index) {
    // Here is where we convert the content of a specific BED file format to the desired JSON structure

    // In Parul's file, there are two entities- a cis regulatory element (the position column
    //  used for tabix queries) and a promoter that affects a gene (a string column with an ID)
    var fields = line.split('\t');
    var chrom = fields[0].replace('chr', '');

    // assume format chr1:123-456_GENE or chr1:123-456 (chr prefix optional)
    var second_item = fields[3].match(/^(?:chr)?(\d+):(\d+)-(\d+)(?:_)?(\w+)?$/);
    var score = fields[4];
    score = (score === '.') ? null : +score;
    return {
        // Note: The query is based on position of element 1, not element 2. It's possible for a loop to be in the
        //   region, but the tabix query to not find it.
        chrom: chrom,
        start1: +fields[1],
        end1: +fields[2],
        start2: +second_item[2],
        end2: +second_item[3],
        // Optional field: chromatin interaction files have it; coaccessibility files do not
        target: second_item[4] || null,
        score: score,
        id: index,  // Dummy ID: position in the list of lines TODO: create a better unique ID
    };
}

// eslint-disable-next-line no-unused-vars
function dega_bed_parser(line, index) {
    // Here is where we convert the content of a specific BED file format to the desired JSON structure

    // In Parul's file, there are two entities- a cis regulatory element (the position column
    //  used for tabix queries) and a promoter that affects a gene (a string column with an ID)
    var fields = line.split('\t');
    var chrom = fields[0].replace('chr', '');

    // assume format chr1:123-456_GENE or chr1:123-456 (chr prefix optional)
    var second_item = fields[3].match(/^(?:chr)?(\d+):(\d+)-(\d+)(?:_)?(\S+)?$/);
    var score = fields[4];
    score = (score === '.') ? null : +score;
    return {
        // Note: The query is based on position of element 1, not element 2. It's possible for a loop to be in the
        //   region, but the tabix query to not find it.
        chrom: chrom,
        start1: +fields[1],
        end1: +fields[2],
        start2: +second_item[2],
        end2: +second_item[3],
        // Optional field: chromatin interaction files have it; coaccessibility files do not
        target: second_item[4] || null,
        score: score,
        id: index,  // Dummy ID: position in the list of lines TODO: create a better unique ID
    };
}

Fix gene regex per parul suggestion


const moment = require('moment');
const weather = require('./weather');

const actions = {
  getWeather: (data) => new Promise((resolve, reject) => {
    const context = data.context;
    const entities = data.entities;

    const missingLocation = entities.location === undefined;
    const location = entities.location ? entities.location[0].value : null;
    const datetime = entities.datetime ? entities.datetime[0].value : null;

    if (missingLocation) {
      const contextData = Object.assign({}, context, { missingLocation });
      resolve(contextData);
    } else if (datetime) {
      weather.forecastByLocationName(location, datetime)
        .then((weatherData) => {
          const contextData = Object.assign({}, context, weatherData);
          if (datetime) {
            Object.assign(contextData, { datetime: moment(datetime).calendar().toLowerCase() });
          }
          resolve(contextData);
        })
        .catch(reject);
    } else {
      weather.weatherByLocationName(location)
        .then((weatherData) => {
          const contextData = Object.assign({}, context, weatherData);
          if (datetime) {
            Object.assign(contextData, { datetime: moment(datetime).calendar().toLowerCase() });
          }
          resolve(contextData);
        })
        .catch(reject);
    }
  })
}

module.exports = actions;

const moment = require('moment');
const weather = require('./weather');

const actions = {
  getWeather: (data) => new Promise((resolve, reject) => {
    const context = data.context;
    const entities = data.entities;

    const missingLocation = entities.location === undefined;
    const location = entities.location ? entities.location[0].value : null;
    const datetime = entities.datetime ? entities.datetime[0].value : null;

    if (missingLocation) {
      const contextData = Object.assign({}, context, { missingLocation });
      resolve(contextData);
    } else if (datetime) {
      weather.forecastByLocationName(location, datetime)
        .then((weatherData) => {
          const contextData = Object.assign({}, context, weatherData);
          if (datetime) {
            Object.assign(contextData, { datetime: moment(datetime).calendar().toLowerCase() });
          }
          resolve(contextData);
        })
        .catch(reject);
    } else {
      weather.weatherByLocationName(location)
        .then((weatherData) => {
          const contextData = Object.assign({}, context, weatherData);
          if (datetime) {
            Object.assign(contextData, { datetime: moment(datetime).calendar().toLowerCase() });
          }
          resolve(contextData);
        })
        .catch(reject);
    }
  }),
  getTime: (data) => new Promise((resolve, reject) => {
    const context = data.context;
    const contextData = {};
    const datetime = new Date();

    Object.assign(contextData, { datetime: moment(datetime).calendar().toLowerCase() });
    resolve(contextData);
  })
}

module.exports = actions;

Add getTime as default wit action in boilerplate


function normalizeName(name) {
  if (name.charAt(0) === '-') {
    name = name.substr(1)
  }

  if (name.charAt(0) === '-') {
    name = name.substr(1)
  }

  return name
}

class Option {
  constructor(name, parent) {
    if (!parent) {
      throw new Error('An option must have a parent command')
    }

    if (!name || typeof name !== 'string') {
      throw new Error('The first argument of the Option constructor must be a its name (a non-empty string)')
    }

    name = normalizeName(name)
    this.parent = parent
    this.config = {
      name,
      id: `${parent.config.id}#${name}`,
    }
  }

  aliases(...aliases) {
    this.config.aliases = aliases.map((a) => normalizeName(a))
    return this
  }

  shared() {
    this.parent.sharedOptions.push(this.config.id)
    return this
  }

  command(...args) {
    return this.parent.command(...args)
  }

  option(...args) {
    return this.parent.option(...args)
  }

  handle(...args) {
    return this.parent.handle(...args)
  }

  end() {
    return this.parent
  }
}

module.exports = Option

function normalizeName(name) {
  if (name.charAt(0) === '-') {
    name = name.substr(1)
  }

  if (name.charAt(0) === '-') {
    name = name.substr(1)
  }

  return name
}

class Option {
  constructor(name, parent) {
    if (!parent) {
      throw new Error('An option must have a parent command')
    }

    if (!name || typeof name !== 'string') {
      throw new Error('The first argument of the Option constructor must be its name (a non-empty string)')
    }

    name = normalizeName(name)
    this.parent = parent
    this.config = {
      name,
      id: `${parent.config.id}#${name}`,
    }
  }

  aliases(...aliases) {
    this.config.aliases = aliases.map((a) => normalizeName(a))
    return this
  }

  shared() {
    this.parent.sharedOptions.push(this.config.id)
    return this
  }

  command(...args) {
    return this.parent.command(...args)
  }

  option(...args) {
    return this.parent.option(...args)
  }

  handle(...args) {
    return this.parent.handle(...args)
  }

  end() {
    return this.parent
  }
}

module.exports = Option

Fix a typo in an error


function prepareCSVLink()
{
    var pokemonCells = document.getElementsByClassName('pokemonCell');

    var csvString = 'Number, Name, Caught\n'

    for(var i = 0; i < pokemonCells.length; i++)
    {
        var pokemonCell = pokemonCells[i];
        var number = pokemonCell.getElementsByClassName("pokemonCellNumber")[0].innerHTML;
        var name = pokemonCell.getElementsByClassName("pokemonCellName")[0].innerHTML;
        var caughtState = pokemonCell.className.split(/\s+/)[1];

        csvString+=number;
        csvString+=', ';
        csvString+=name;
        csvString+=', ';
        csvString+=caughtState;
        csvString+='\n';

        break;
    }

    var downloadLink = document.createElement("a");
    var csvURI = 'data:text/csv;charset=utf-8,' + csvString;
    downloadLink.href = csvString;
    downloadLink.download = 'dex.csv';
    downloadLink.innerHTML = "Download as CSV (experimental)";

    var container = document.getElementsByClassName("csvDownloadLinkContainer")[0];
    container.appendChild(downloadLink);
}

function prepareCSVLink()
{
    var pokemonCells = document.getElementsByClassName('pokemonCell');

    var csvString = 'Number, Name, Caught\n'

    for(var i = 0; i < pokemonCells.length; i++)
    {
        var pokemonCell = pokemonCells[i];
        var number = pokemonCell.getElementsByClassName("pokemonCellNumber")[0].innerHTML;
        var name = pokemonCell.getElementsByClassName("pokemonCellName")[0].innerHTML;
        var caughtState = pokemonCell.className.split(/\s+/)[1];

        csvString+=number;
        csvString+=', ';
        csvString+=name;
        csvString+=', ';
        csvString+=caughtState;
        csvString+='\n';

        break;
    }

    var downloadLink = document.createElement("a");
    var csvURI = 'data:text/csv;charset=utf-8,' + csvString;
    downloadLink.href = csvURI;
    downloadLink.download = 'dex.csv';
    downloadLink.innerHTML = "Download as CSV (experimental)";

    var container = document.getElementsByClassName("csvDownloadLinkContainer")[0];
    container.appendChild(downloadLink);
}

Fix CSV download link to include content type


import React, { Component } from 'react'
import { connect } from 'react-redux'
import { addPattern } from '../actions'
import TextField from 'material-ui/TextField'
import RaisedButton from 'material-ui/RaisedButton'
import Subheader from 'material-ui/Subheader'
import Paper from 'material-ui/Paper'
import * as styles from './styles'

const mapStateToProps = function(state) {
  return {
    patternList: state.patternList
  }
}

class AddPattern extends Component {
  render() {
    return (
      <div>
        <Subheader style={styles.headingStyle}>Add Pattern</Subheader>
        <Paper style={styles.paperStyle} zDepth={2} rounded={true}>
          <TextField ref="name" style={styles.inputStyle} hintText="Name" />
          <TextField ref="pattern" style={styles.inputStyle} hintText="Pattern" fullWidth={true} />
          <RaisedButton label="Add Pattern" primary={true} style={styles.buttonStyle} onClick={ () => this.props.addPattern(this.refs.name.input.value, this.refs.pattern.state.searchText) } />
        </Paper>
      </div>
    )
  }
}

export default connect(mapStateToProps,
  (dispatch) => {
    return {
      addPattern: (name, pattern) => {
        dispatch(addPattern(name, pattern))
      }
    }
  })(AddPattern)

import React, { Component } from 'react'
import { connect } from 'react-redux'
import { addPattern } from '../actions'
import TextField from 'material-ui/TextField'
import RaisedButton from 'material-ui/RaisedButton'
import Subheader from 'material-ui/Subheader'
import Paper from 'material-ui/Paper'
import * as styles from './styles'

const mapStateToProps = function(state) {
  return {
    patternList: state.patternList
  }
}

class AddPattern extends Component {
  render() {
    return (
      <div>
        <Subheader style={styles.headingStyle}>Add Pattern</Subheader>
        <Paper style={styles.paperStyle} zDepth={2} rounded={true}>
          <TextField ref="name" style={styles.inputStyle} hintText="Name" />
          <TextField ref="pattern" style={styles.inputStyle} hintText="Pattern" fullWidth={true} />
          <RaisedButton label="Add Pattern" primary={true} style={styles.buttonStyle} onClick={ () => this.props.addPattern(this.refs.name.input.value, this.refs.pattern.input.value) } />
        </Paper>
      </div>
    )
  }
}

export default connect(mapStateToProps,
  (dispatch) => {
    return {
      addPattern: (name, pattern) => {
        dispatch(addPattern(name, pattern))
      }
    }
  })(AddPattern)

Fix adding pattern to actually use the pattern text.


function quotemeta(str) {
  // From: http://phpjs.org/functions
  // +   original by: Paulo Freitas
  // *     example 1: quotemeta(". + * ? ^ ( $ )");
  // *     returns 1: '\. \+ \* \? \^ \( \$ \)'
  return (str + '').replace(/([\.\\\+\*\?\[\^\]\$\(\)])/g, '\\$1');
}

Fix example (to give proper response and as expected by Travis)


"use strict";

// Program that sorts a stack such that smallest items are on top

// create Stack class
function Stack() {
	this.top = null;
}

// push value into stack
Stack.prototype.push = function(val) {
	this.top = {
		data: val,
		next: this.top
	};
};

// pop value from stack
Stack.prototype.pop = function() {
	var top = this.top;
	if(top) {
		var popData = top.data;
		top = top.next;
		return popData;
	}
	return;
};

// sort stack in ascending order (main function in exercise)
Stack.prototype.sort = function() {
	// create output stack
	var stackTwo = new Stack();

	while(this.top) {
		var placeHolder = this.pop();

		while(stackTwo.top && stackTwo.top.data > placeHolder) {
			// push the top element in output stack (larger value) into original stack
			stackOne.push(stackTwo.pop());
		}
		// push element in placeholder into output stack
		stackTwo.push(placeHolder);
	}
	console.log(stackTwo);
};

// test cases
var testStack = new Stack();
testStack.push(1);
testStack.push(2);
testStack.push(3);
testStack.push(4);
testStack.push(5);
// expect 5 to be at top of stack
console.log(testStack);
testStack.sort();

Add test case for sort stack program


(function () {
    function showMessage(message, type) {
        $('#check-update-info a.close').trigger('click.fndtn.alert');
        var alertBox = '
' + message +
            '× ' + '
';
            $(".large-10").prepend(alertBox).foundation();
    }

    function showLoading() {
        var loading = '

';
        $(".large-10").prepend(loading);
    }

    function hideLoading() {
        $('#loading').remove();
    }

    $("#checkupdate").bind('click', function (event) {
        $.ajax({
            url: "/api/checkupdate",
            type: "get",
            dataType: "json",
            beforeSend: function (xhr) {
                $('#check-update-info a.close').trigger('click.fndtn.alert');
                showLoading();
            },
            success: function (res, status, xhr) {
                console.log(res);
                var message = res.message + '点此查看升级方法。'
                showMessage(message, 'success');
            },
            complete: function (xhr, status) {
                hideLoading();
            },
            error: function (xhr, status, thrown) {
                var err = xhr.responseJSON;
                console.log(err);
                showMessage(err.message, 'alert');
            }
        });
    });
})();

(function () {
    function showMessage(message, type) {
        $('#check-update-info a.close').trigger('click.fndtn.alert');
        var alertBox = '
' + message +
            '× ' + '
';
            $(".large-10").prepend(alertBox).foundation();
    }

    function showLoading() {
        var loading = '

';
        $(".large-10").prepend(loading);
    }

    function hideLoading() {
        $('#loading').remove();
    }

    $("#checkupdate").bind('click', function (event) {
        $.ajax({
            url: "/api/checkupdate",
            type: "get",
            dataType: "json",
            beforeSend: function (xhr) {
                $('#check-update-info a.close').trigger('click.fndtn.alert');
                showLoading();
            },
            success: function (res, status, xhr) {
                console.log(res);
                var message = res.message + '点此查看升级方法。'
                showMessage(message, 'success');
            },
            complete: function (xhr, status) {
                hideLoading();
            },
            error: function (xhr, status, thrown) {
                var err = xhr.responseJSON;
                console.log(err);
                showMessage(err ? err.message : '检测更新失败！', 'alert');
            }
        });
    });
})();

Update check update error handler.


Template.latestActivitiesByType.created = function () {
  // Create 'instance' variable for use througout template logic
  var instance = this;

  // Create variable to hold activity type selection
  instance.activityTypeSelection = new ReactiveVar();

  // Create a reactive var to hold data containing latest activities by type
  instance.latestActivityIdsByTypeData = new ReactiveVar();

  Meteor.call('getAllResidentsLatestActivityIdsByType', function (error, latestResidentActivityIdsByType) {
    // Set latest activities by type
    instance.latestActivityIdsByTypeData.set(latestResidentActivityIdsByType);
  });
};

Template.latestActivitiesByType.helpers({
  'latestActivitiesByType': function () {
    // Get a reference to template instance
    var instance = Template.instance();

    var latestResidentActivityIdsByType = instance.latestActivityIdsByTypeData.get();

    //console.log(latestResidentActivitiesByType);
    if (latestResidentActivityIdsByType) {
      console.log(latestResidentActivityIdsByType);
      return latestResidentActivityIdsByType;
    } else {
      return [];
    }
  }
});

Template.latestActivitiesByType.created = function () {
  // Create 'instance' variable for use througout template logic
  var instance = this;

  // Create variable to hold activity type selection
  instance.activityTypeSelection = new ReactiveVar();

  // Create a reactive var to hold data containing latest activities by type
  instance.latestActivityIdsByTypeData = new ReactiveVar();

  Meteor.call('getAllResidentsLatestActivityIdsByType', function (error, latestResidentActivityIdsByType) {
    // Set latest activities by type
    instance.latestActivityIdsByTypeData.set(latestResidentActivityIdsByType);
  });
};

Template.latestActivitiesByType.helpers({
  'latestActivitiesByType': function () {
    // Get a reference to template instance
    var instance = Template.instance();

    var latestResidentActivityIdsByType = instance.latestActivityIdsByTypeData.get();

    // Create an array of activities
    var activities = _.map(latestResidentActivityIdsByType, function (activityId) {
      // Find a single activity by ID
      var activity = Activities.findOne(activityId);

      return activity;
    });

    // If activities exist
    if (activities) {
      // Return the activities to template
      return activities;
    } else {
      // Return an empty array
      return [];
    }
  }
});

Return activity objects to template


import { isFunction } from 'lodash';
import { setHeader } from 'focus-core/application';

module.exports = {

    /**
     * Updates the cartridge using the cartridgeConfiguration.
     */
    _registerCartridge(props = this.props) {
        const cartridgeConfiguration = this.cartridgeConfiguration || props.cartridgeConfiguration;
        const hasCartridge = isFunction(cartridgeConfiguration);
        if (!hasCartridge) {
            console.warn(`
                Your detail page does not have any cartrige configuration, this is not mandarory but recommended.
                It should be a component attribute return by a function.
                function cartridgeConfiguration(){
                    var cartridgeConfiguration = {
                    summary: {component: "A React Component", props: {id: this.props.id}},
                    cartridge: {component: "A React Component"},
                    actions: {components: "react actions"}
                    };
                    return cartridgeConfiguration;
                }
            `);
        }
        const config = hasCartridge ? cartridgeConfiguration() : {};
        setHeader(config);
    },

    /**
     * Registers the cartridge upon mounting.
     */
    componentWillMount() {
        this._registerCartridge();
    },

    componentWillReceiveProps(nextProps) {
        this._registerCartridge(nextProps);
    }
};

import { isFunction } from 'lodash';
import { setHeader } from 'focus-core/application';

const cartridgeBehaviour = {

    /**
     * Updates the cartridge using the cartridgeConfiguration.
     */
    _registerCartridge(props = this.props) {
        const cartridgeConfiguration = this.cartridgeConfiguration || props.cartridgeConfiguration;
        const hasCartridge = isFunction(cartridgeConfiguration);
        if (!hasCartridge) {
            console.warn(`
                Your detail page does not have any cartrige configuration, this is not mandarory but recommended.
                It should be a component attribute return by a function.
                function cartridgeConfiguration(){
                    var cartridgeConfiguration = {
                    summary: {component: "A React Component", props: {id: this.props.id}},
                    cartridge: {component: "A React Component"},
                    actions: {components: "react actions"}
                    };
                    return cartridgeConfiguration;
                }
            `);
        }
        const config = hasCartridge ? cartridgeConfiguration() : {};
        setHeader(config);
    },

    /**
     * Registers the cartridge upon mounting.
     */
    componentWillMount() {
        this._registerCartridge();
    },

    componentWillReceiveProps(nextProps) {
        this._registerCartridge(nextProps);
    }
};

export default cartridgeBehaviour;

Change module.exports to export default


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

moduleForComponent('ics-feed', 'Integration | Component | ics feed', {
  integration: true
});

test('it show instructions', function(assert) {
  assert.expect(1);
  let instructions = 'SOME TEST INS';
  this.set('instructions', instructions);

  this.render(hbs`{{ics-feed instructions=instructions}}`);

  assert.equal(this.$().text().trim(), instructions);
});

test('it show url', function(assert) {
  assert.expect(1);
  let url = 'http://example.com/url';
  this.set('url', url);

  this.render(hbs`{{ics-feed url=url}}`);

  assert.equal(this.$('input').val().trim(), url);
});

test('refresh calls action', function(assert) {
  assert.expect(1);
  let url = 'http://example.com/url';
  this.set('url', url);
  // Set any properties with this.set('myProperty', 'value');
  // Handle any actions with this.on('myAction', function(val) { ... });
  this.on('refresh', function(){
    assert.ok(true);
  });
  this.render(hbs`{{ics-feed refresh='refresh'}}`);

  this.$('button:eq(1)').click();
});

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

moduleForComponent('ics-feed', 'Integration | Component | ics feed', {
  integration: true
});

test('it show instructions', function(assert) {
  assert.expect(1);
  let instructions = 'SOME TEST INS';
  this.set('instructions', instructions);

  this.render(hbs`{{ics-feed instructions=instructions}}`);

  assert.equal(this.$().text().trim(), instructions);
});

test('it show url', function(assert) {
  assert.expect(1);
  let url = 'http://example.com/url';
  this.set('url', url);

  this.render(hbs`{{ics-feed url=url}}`);

  assert.equal(this.$('input').val().trim(), url);
});

// test('refresh calls action', function(assert) {
//   assert.expect(1);
//   let url = 'http://example.com/url';
//   this.set('url', url);
//   // Set any properties with this.set('myProperty', 'value');
//   // Handle any actions with this.on('myAction', function(val) { ... });
//   this.on('refresh', function(){
//     assert.ok(true);
//   });
//   this.render(hbs`{{ics-feed refresh='refresh'}}`);
//
//   this.$('button:eq(1)').click();
// });

Remove test for refresh button that is currently not enabled


!function(PIXI, exports) {
  'use strict';

  var Point = exports.Point,
      Rect = exports.Rect;

  function Frame(position, url, options) {
    this.pos = position;
    this.dir = options.direction || new Point(1, 1);
    this.offset = options.offset || new Point(0, 0);

    var base = PIXI.BaseTexture.fromImage(url),
        slice = options.crop || new Rect(0, 0, base.width, base.height),
        tex = new PIXI.Texture(base, slice);

    this.sprite = new PIXI.Sprite(tex);

    this.render();
  }

  Frame.prototype.render = function() {
    var gPos = this.sprite.position,
        gScale = this.sprite.scale;

    gPos.x = Math.round(this.pos.x + this.offset.x);
    gPos.y = Math.round(this.pos.y + this.offset.y);

    gScale.x = this.dir.x;
    gScale.y = this.dir.y;

    /*
     * Flipping will result in the sprite appearing to jump (flips on the 0,
     * rather than mid-sprite), so subtract the sprite's size from its position
     * if it's flipped.
     */

    if(gScale.x < 0) gPos.x -= this.sprite.width;
    if(gScale.y < 0) gPos.y -= this.sprite.height;
  };

  exports.Frame = Frame;

}(PIXI, demo);

!function(PIXI, exports) {
  'use strict';

  var Point = exports.Point,
      Rect = exports.Rect;

  function Frame(position, url, options) {
    this.pos = position;
    this.dir = options.direction || new Point(1, 1);
    this.offset = options.offset || new Point(0, 0);

    var base = PIXI.BaseTexture.fromImage(url, false, PIXI.scaleModes.NEAREST),
        slice = options.crop || new Rect(0, 0, base.width, base.height),
        tex = new PIXI.Texture(base, slice);

    this.sprite = new PIXI.Sprite(tex);

    this.render();
  }

  Frame.prototype.render = function() {
    var gPos = this.sprite.position,
        gScale = this.sprite.scale;

    gPos.x = Math.round(this.pos.x + this.offset.x);
    gPos.y = Math.round(this.pos.y + this.offset.y);

    gScale.x = this.dir.x;
    gScale.y = this.dir.y;

    /*
     * Flipping will result in the sprite appearing to jump (flips on the 0,
     * rather than mid-sprite), so subtract the sprite's size from its position
     * if it's flipped.
     */

    if(gScale.x < 0) gPos.x -= this.sprite.width;
    if(gScale.y < 0) gPos.y -= this.sprite.height;
  };

  exports.Frame = Frame;

}(PIXI, demo);

Use nearest neighbor scaling in demo renderer.


Meteor.methods({
  addContactUsMessage: function(message) {

    try {
      // synchronous (to the server) insert
      ContactUsMessages.insert({
        name: message.name,
        email: message.email,
        zipcode: message.zipcode,
        text: message.text,
        createdAt: new Date(),
        authorId: Meteor.userId(), // can be null, meaning a message from someone not logged in
      });

      // send an email to ourselves containing the message
      var text = 'name: ' + message.name +
        '\nemail: ' + message.email +
        '\nzipcode: ' + message.zipcode +
        '\ntext: ' + message.text;
      var email = {
        to: "GrowSocial Pilot Coordinator <growsocial.org@gmail.com>",
        from: "GrowSocial Pilot Website <email@growsocial.org>",
        subject: "GrowSocial pilot message - Contact Us",
        text: text,
      };
      // no need to do anything with email errors here, so pass empty callback
      Meteor.call('sendEmail', email, function() {});
      return; // undefined, i.e. a positive response to client/caller

    } catch (err) {
      console.log('caught err from insert, will add to error log, then rethrow to server & client');
      Meteor.call('addErrorLog', {
        tag: 'ContactUsMessages.insert',
        message: err.message + ', message: ' + EJSON.stringify(message).substring(1,300),
      });
      throw(err);
    }
  },
});

Meteor.methods({
  addContactUsMessage: function(message) {

    try {
      // synchronous (to the server) insert
      ContactUsMessages.insert({
        name: message.name,
        email: message.email,
        zipcode: message.zipcode,
        text: message.text,
        createdAt: new Date(),
        authorId: Meteor.userId(), // can be null, meaning a message from someone not logged in
      });

      // send an email to ourselves containing the message
      var text = 'name: ' + message.name +
        '\nemail: ' + message.email +
        '\nzipcode: ' + message.zipcode +
        '\ntext: ' + message.text;
      var email = {
        to: "GrowSocial Pilot Coordinator <growsocial.org@gmail.com>",
        from: "GrowSocial Pilot Website <growsocial.org@gmail.com>",
        subject: "GrowSocial pilot message - Contact Us",
        text: text,
      };
      // no need to do anything with email errors here, so pass empty callback
      Meteor.call('sendEmail', email, function() {});
      return; // undefined, i.e. a positive response to client/caller

    } catch (err) {
      console.log('caught err from insert, will add to error log, then rethrow to server & client');
      Meteor.call('addErrorLog', {
        tag: 'ContactUsMessages.insert',
        message: err.message + ', message: ' + EJSON.stringify(message).substring(1,300),
      });
      throw(err);
    }
  },
});

Use a real email in contactUs email from-field


module.exports = function(question, callback) {

	var apiai = require('apiai');
	var app = apiai("76ccb7c7acea4a6884834f6687475222", "8b3e68f16ac6430cb8c40d49c315aa7a");

		var request = app.textRequest(question);

		request.on('response', function(response) {
		    var kantSucks = require('./deontologyYo')(response);
		    if(kantSucks == 0)
		    	if(response.result.metadata.speech)
		    		callback(response.result.metadata.speech)
		    	else
		    		callback("Unfortunately, I'm not smart enough to answer that question yet.")
		    else if(kantSucks == 1)
		    	if(response.result.metadata.speech)
		    	callback("I'll check with that after I pass through my utility ethic function. Heres what I would say otherwise: " + response.result.metadata.speech);
		    else
		    	callback(kantSucks)
		});

		request.on('error', function(error) {
		    callback(error);
		});

		request.end()
};

module.exports = function(question, callback) {

	var apiai = require('apiai');
	var app = apiai("76ccb7c7acea4a6884834f6687475222", "8b3e68f16ac6430cb8c40d49c315aa7a");

		var request = app.textRequest(question);

		request.on('response', function(response) {
		    var kantSucks = require('./deontologyYo')(response);
		    if(kantSucks == 0)
		    	if(response.result.metadata.speech)
		    		callback(response.result.metadata.speech)
		    	else
		    		callback("Unfortunately, I'm not smart enough to answer that question yet.")
		    else if(kantSucks == 1)
		    	if(response.result.metadata.speech && response.result.metadata.speech != "undefined")
		    	callback("I'll check with that after I pass through my utility ethic function. Heres what I would say otherwise: " + response.result.metadata.speech);
		    else
		    	callback(kantSucks)
		});

		request.on('error', function(error) {
		    callback(error);
		});

		request.end()
};

Solve issue of blank/undefined answers.


// jshint camelcase: false
(function () {
	'use strict';

	// Make async
	window.__karma__.loaded = function () {};

	require.config({

		// Set baseUrl for Karma
		baseUrl: 'http://localhost:9876/base/js/',

		deps: ['config']
	});

	// Load the example spec, add your own specifications here
	require([
		'../test/specs/example.spec'
	], window.__karma__.start);
}());

// jshint camelcase: false
(function () {
	'use strict';

	// Make async
	window.__karma__.loaded = function () {};

	// Set the flag for test environment
	window.__test = true;

	// RequireJS Configuration
	require.config({

		// Set baseUrl for Karma
		baseUrl: 'http://localhost:9876/base/js/',

		deps: ['config']
	});

	// Load the example spec, add your own specifications here
	require([
		'../test/specs/example.spec'
	], window.__karma__.start);
}());

Set a `__test` flag when running tests with karma