var Arrow = require('arrow');

module.exports = Arrow.createModel('base', {

    connector: 'appc.redis',

    fields: { },

    expire: function expire(seconds, callback){
        return this.getConnector().expire(this.getModel(), this, seconds, callback);
    },

    expireAt: function expireAt(date, callback){
        return this.getConnector().expireAt(this.getModel(), this, date, callback);
    },

    keys: function keys(limit, callback){
        return this.getConnector().keys(this.getModel(), limit, callback);
    },

    persist: function persist(callback){
        return this.getConnector().persist(this.getModel(), this, callback);
    },

    ttl: function ttl(callback){
        return this.getConnector().ttl(this.getModel(), this, callback);
    }

});

var Arrow = require('arrow');

var Base = Arrow.Model.extend('base', {

    connector: 'appc.redis',

    fields: { },

    expire: function expire(seconds, callback){
        return this.getConnector().expire(this.getModel(), this, seconds, callback);
    },

    expireAt: function expireAt(date, callback){
        return this.getConnector().expireAt(this.getModel(), this, date, callback);
    },

    keys: function keys(limit, callback){
        return this.getConnector().keys(this.getModel(), limit, callback);
    },

    persist: function persist(callback){
        return this.getConnector().persist(this.getModel(), this, callback);
    },

    ttl: function ttl(callback){
        return this.getConnector().ttl(this.getModel(), this, callback);
    }

});

module.exports = Base;

Switch out 'createModel' for 'extend'

Switch out 'createModel' for 'extend'


import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import css from './LastVotes.css';
import { voteWithNameSelector } from '../selectors/voting';

const LastVotes = ({ votes }) => (

Siste stemmer

    { votes.length > 0 ? votes.map(vote => (

        { vote.voter }: { vote.alternative }

    ))
    : (

Ingen stemmer enda

    )}

);

LastVotes.propTypes = {
  votes: PropTypes.shape({
    voter: PropTypes.string.isRequired,
    alternative: PropTypes.string.isRequired,
  }).isRequired,
};

const mapStateToProps = state => ({
  votes: voteWithNameSelector(state).slice(0, 5),
});

export default connect(mapStateToProps)(LastVotes);

import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import css from './LastVotes.css';
import { voteWithNameSelector } from '../selectors/voting';
import { activeIssueExists, getOwnVote, getIssueKey } from '../selectors/issues';

const LastVotes = ({ votes, hideVotes }) => {
  if (hideVotes) {
    return null;
  }
  return (

Siste stemmer

      { votes.length > 0 ? votes.map(vote => (

          { vote.voter }: { vote.alternative }

      ))
      : (

Ingen stemmer enda

      )}

  );
};

LastVotes.propTypes = {
  votes: PropTypes.shape({
    voter: PropTypes.string.isRequired,
    alternative: PropTypes.string.isRequired,
  }).isRequired,
  hideVotes: PropTypes.bool.isRequired,
};

const mapStateToProps = state => ({
  votes: voteWithNameSelector(state).slice(0, 5),
  hideVotes: (
    !activeIssueExists(state)
    || !getOwnVote(state, state.auth.id)
    || getIssueKey(state, 'showOnlyWinner')
  ),
});

export default connect(mapStateToProps)(LastVotes);

Hide last votes unless there is an active issue with public votes and user has voted


var Mocha = require('mocha'),
    _ = require('underscore'),
    moduleTest = require('./module');

module.exports = function (browser, dashboard, suite, config) {

  suite = Mocha.Suite.create(suite, dashboard.title);

  suite.beforeAll(function () {
    return browser.get(config.baseUrl + dashboard.slug)
      .$('body.ready', 5000)
      // if a dashboard isn't ready after 5s then there's probably a failed module
      // run module tests to help establish which
      .fail(function () {});
  });

  var tests = {
    exists: function exists() {
      return browser
        .title()
          .should.become(dashboard.title + ' - ' + dashboard.strapline + ' - GOV.UK')
        .$('h1').text()
          .should.eventually.equal(dashboard.strapline + '\n' + dashboard.title)
        .$('body.ready')
          .should.eventually.exist;
    }
  };

  _.each(tests, function (test, key) {
    suite.addTest(new Mocha.Test(key, test));
  });

  var modules = Mocha.Suite.create(suite, 'modules');
  dashboard.modules.forEach(function (module) {
    moduleTest(browser, module, modules, config);
  });

};

var Mocha = require('mocha'),
    _ = require('underscore'),
    moduleTest = require('./module');

module.exports = function (browser, dashboard, suite, config) {

  suite = Mocha.Suite.create(suite, dashboard.title);

  suite.beforeAll(function () {
    return browser.get(config.baseUrl + dashboard.slug)
      .setWindowSize(1024, 800)
      .$('body.ready', 5000)
      // if a dashboard isn't ready after 5s then there's probably a failed module
      // run module tests to help establish which
      .fail(function () {});
  });

  var tests = {
    exists: function exists() {
      return browser
        .title()
          .should.become(dashboard.title + ' - ' + dashboard.strapline + ' - GOV.UK')
        .$('h1').text()
          .should.eventually.equal(dashboard.strapline + '\n' + dashboard.title)
        .$('body.ready')
          .should.eventually.exist;
    }
  };

  _.each(tests, function (test, key) {
    suite.addTest(new Mocha.Test(key, test));
  });

  var modules = Mocha.Suite.create(suite, 'modules');
  dashboard.modules.forEach(function (module) {
    moduleTest(browser, module, modules, config);
  });

};

Set window size to desktop display dimensions

Some tests were failing because the default window dimensions cause the site to display in "mobile" format, truncating some elements and causing tests to fail.


PluginsAPI.Dashboard.addNewTaskButton(
	["cloudimport/build/ImportView.js"],
	function(args, ImportView) {
        return React.createElement(ImportView, {
                onNewTaskAdded: args.onNewTaskAdded,
                projectId: args.projectId,
                apiURL: "{{ api_url }}",
        });
	}
);

PluginsAPI.Dashboard.addTaskActionButton(
	["cloudimport/build/TaskView.js"],
	function(args, ImportView) {
		$.ajax({
			url: "{{ api_url }}/projects/" + args.task.project + "/tasks/" + args.task.id + "/checkforurl",
			dataType: 'json',
			async: false,
			success: function(data) {
				if (data.folder_url) {
					return React.createElement(ImportView, {
						folderUrl: data.folder_url,
					});
				}
			}
		});
	}
);

PluginsAPI.Dashboard.addNewTaskButton(
	["cloudimport/build/ImportView.js"],
	function(args, ImportView) {
        return React.createElement(ImportView, {
                onNewTaskAdded: args.onNewTaskAdded,
                projectId: args.projectId,
                apiURL: "{{ api_url }}",
        });
	}
);

PluginsAPI.Dashboard.addTaskActionButton(
	["cloudimport/build/TaskView.js"],
	function(args, TaskView) {
		var reactElement;
		$.ajax({
			url: "{{ api_url }}/projects/" + args.task.project + "/tasks/" + args.task.id + "/checkforurl",
			dataType: 'json',
			async: false,
			success: function(data) {
				if (data.folder_url) {
					reactElement = React.createElement(TaskView, {
						folderUrl: data.folder_url,
					});
				}
			}
		});
		return reactElement;
	}
);

Fix to show task button

Fix to show task button


var express = require('express'),
    mongoose = require('mongoose'),
    app = express()

app.set('port', process.env.PORT || 3000)
app.set('mongodb-url', process.env.MONGODB_URL || 'mongodb://localhost/hangman')

if ('test' === app.get('env')) {
  app.set('port', 9000)
  app.set('mongodb-url', 'mongodb://localhost/hangman-test')
}

mongoose.connect(app.get('mongodb-url'))

app.use(require('morgan')('dev'))
app.use(require('body-parser').json())

app.get('/', function(req, res) {
  res.json({
    'index': '/',
    'me': '/me',
    'prisoners': '/prisoners'
  })
})

if (require.main === module) {
  mongoose.connection.on('connected', function() {
    console.log('Connected to %s', app.get('mongodb-url'))
    app.listen(app.get('port'), function() {
      console.log('Listening on port %d', app.get('port'))
    })
  })
}

var express = require('express'),
    mongoose = require('mongoose'),
    util = require('util'),
    _ = require('lodash'),
    app = express()

app.set('port', process.env.PORT || 3000)
app.set('mongodb-url', process.env.MONGODB_URL || 'mongodb://localhost/hangman')

if ('test' === app.get('env')) {
  app.set('port', 9000)
  app.set('mongodb-url', 'mongodb://localhost/hangman-test')
}

mongoose.connect(app.get('mongodb-url'))

var User = mongoose.model('User', (function() {
  return mongoose.Schema({
    'email_address': {type: String, required: 'Missing required field [{PATH}]'},
    'password': {type: String, required: 'Missing required field [{PATH}]'}
  })
})())

app.use(require('morgan')('dev'))
app.use(require('body-parser').urlencoded({extended: true}))

app.get('/', function(req, res) {
  res.json({
    'index': '/',
    'me': '/me',
    'prisoners': '/prisoners'
  })
})

app.post('/me', function(req, res) {
  new User(req.body).save(function(err, user) {
    if (err) {
      return res.send(400, {
        'status': 'Bad Request',
        'status_code': 400,
        'description': _(err.errors).chain().values().pluck('message').join(', ').value()
      })
    }
    res.location(util.format('/users/%s', user.id))
    res.send(201)
  })
})

if (require.main === module) {
  mongoose.connection.on('connected', function() {
    console.log('Connected to %s', app.get('mongodb-url'))
    app.listen(app.get('port'), function() {
      console.log('Listening on port %d', app.get('port'))
    })
  })
}

Create User document after POST /me

[take-002] Create User document after POST /me


var notifier = new require("node-notifier")({});
var extend   = require("extend");
var path     = require("path");
var CFG      = require("./config.js");
var pkg      = require(path.join("..", "..", CFG.FILE.config.pkg));

module.exports = {
  defaults: {
    title: pkg.name
  },

  showNotification: function (options) {
    extend(options, this.defaults);

    if("undefined" !== typeof(process.env.REMEMBER_CI)) {
      // Running inside a CI environment, do not show notifications
      console.log("[notification]", options.message);
    } else {
      // Running inside a non-CI environment, okay to show notifications
      notifier.notify(options);
    }
  },

  appIcon: {
    sass: __dirname + "/asset/image/sass-logo.png",
    karma: __dirname + "/asset/image/karma-logo.png"
  }
};

var notifier = new require("node-notifier");
var extend   = require("extend");
var path     = require("path");
var CFG      = require("./config.js");
var pkg      = require(path.join("..", "..", CFG.FILE.config.pkg));

module.exports = {
  defaults: {
    title: pkg.name
  },

  showNotification: function (options) {
    extend(options, this.defaults);

    if("undefined" !== typeof(process.env.REMEMBER_CI)) {
      // Running inside a CI environment, do not show notifications
      console.log("[notification]", options.message);
    } else {
      // Running inside a non-CI environment, okay to show notifications
      notifier.notify(options);
    }
  },

  appIcon: {
    sass: __dirname + "/asset/image/sass-logo.png",
    karma: __dirname + "/asset/image/karma-logo.png"
  }
};

Update node-notifier initialization as per the new version.


import React from 'react';

export default class FileDescription extends React.Component {

  render() {
    return

      {this.props.file.name}
      {this.props.file.size}
      {this.props.file.type}

;
  }

}

import React from 'react';

export default class FileDescription extends React.Component {

  render() {
    return

      {this.props.file.name}

;
  }

}

Remove file size and type descriptors.

Remove file size and type descriptors.


/*
 * 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/>.
 *
 */

/**
 * Action creators for loading state.
 * @module
 * loadingActions
 */

import { CANCEL_ALL_LOADING } from './loadingActionTypes';

/**
 * Return CANCEL_ALL_LOADING action.
 * @function clearLoadingEvents
 * @returns {object} - CANCEL_ALL_LOADING action.
 */
const clearLoadingEvents = () => ({ type: CANCEL_ALL_LOADING });

module.exports = {
  clearLoadingEvents,
};

/*
 * 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/>.
 *
 */

/**
 * Action creators for loading state.
 * @module
 * loadingActions
 */

import { CANCEL_ALL_LOADING } from './loadingActionTypes';

// TODO: call this upon page refresh
// (where? get funny routing errors if I make App a connected component...)

/**
 * Return CANCEL_ALL_LOADING action.
 * @function clearLoadingEvents
 * @returns {object} - CANCEL_ALL_LOADING action.
 */
const clearLoadingEvents = () => ({ type: CANCEL_ALL_LOADING });

module.exports = {
  clearLoadingEvents,
};

Add TODO for clear loading upon refresh

Add TODO for clear loading upon refresh


define(['../is', '../array/last', '../functional'], function(is, last, functional) {
    var map = functional.map;
    var filter = functional.filter;
    var forEach = functional.forEach;

    function annotate() {
        var args = Array.prototype.slice.call(arguments);
        var fn = args.shift();

        if(is.string(last(args))) {
            fn._doc = args.pop();
        }

        forEach(function(arg) {
            if(!is.fn(arg)) console.warn('Invariant' + arg + 'is not a function!');
        }, args);

        fn._invariants = args;

        return function() {
            var curArgs = arguments;

            var info = map(function(arg, i) {
                var curArg = curArgs[i];

                if(is.fn(arg)) {
                    if(arg(curArg)) return {
                        state: 'ok',
                        arg: curArg
                    };
                    else return {
                        state: 'error',
                        info: 'Parameter check failed!',
                        arg: curArg
                    };
                }
                else {
                    return {
                        state: 'error',
                        info: 'Parameter checker is not a function!',
                        arg: curArg
                    };
                }
            }, args);
            var isOk = filter(function(k) {
                return k.state == 'ok';
            }, info).length == info.length;

            if(isOk) return fn.apply(undefined, curArgs);
            else console.warn(info);
        };
    }
    return annotate;
});

define(['../is', '../array/last', '../functional'], function(is, last, functional) {
    var map = functional.map;
    var filter = functional.filter;
    var forEach = functional.forEach;

    function annotate() {
        var args = Array.prototype.slice.call(arguments);
        var fn = args.shift();
        var doc;
        var invariants;

        if(is.string(last(args))) {
            doc = args.pop();
        }

        forEach(function(arg) {
            if(!is.fn(arg)) console.warn('Invariant' + arg + 'is not a function!');
        }, args);

        invariants = args;

        var ret = function() {
            var curArgs = arguments;

            var info = map(function(arg, i) {
                var curArg = curArgs[i];

                if(is.fn(arg)) {
                    if(arg(curArg)) return {
                        state: 'ok',
                        arg: curArg
                    };
                    else return {
                        state: 'error',
                        info: 'Parameter check failed!',
                        arg: curArg
                    };
                }
                else {
                    return {
                        state: 'error',
                        info: 'Parameter checker is not a function!',
                        arg: curArg
                    };
                }
            }, args);
            var isOk = filter(function(k) {
                return k.state == 'ok';
            }, info).length == info.length;

            if(isOk) return fn.apply(undefined, curArgs);
            else console.warn(info);
        };
        ret._doc = doc;
        ret._invariants = invariants;

        return ret;
    }
    return annotate;
});

Attach metadata to the right function

Attach metadata to the right function


'use strict';

const createLifecyclesManager = db => {
  let subscribers = [];

  const lifecycleManager = {
    subscribe(subscriber) {
      // TODO: verify subscriber
      subscribers.push(subscriber);

      return () => {
        subscribers.splice(subscribers.indexOf(subscriber), 1);
      };
    },

    createEvent(action, uid, properties) {
      const model = db.metadata.get(uid);

      return {
        action,
        model,
        ...properties,
      };
    },

    async run(action, uid, properties) {
      for (const subscriber of subscribers) {
        if (typeof subscriber === 'function') {
          const event = this.createEvent(action, uid, properties);
          return await subscriber(event);
        }

        const hasAction = action in subscriber;
        const hasModel = !subscriber.models || subscriber.models.includes(uid);

        if (hasAction && hasModel) {
          const event = this.createEvent(action, uid, properties);

          await subscriber[action](event);
        }
      }
    },

    clear() {
      subscribers = [];
    },
  };

  return lifecycleManager;
};

module.exports = {
  createLifecyclesManager,
};

'use strict';

const createLifecyclesManager = db => {
  let subscribers = [];

  const lifecycleManager = {
    subscribe(subscriber) {
      // TODO: verify subscriber
      subscribers.push(subscriber);

      return () => {
        subscribers.splice(subscribers.indexOf(subscriber), 1);
      };
    },

    createEvent(action, uid, properties) {
      const model = db.metadata.get(uid);

      return {
        action,
        model,
        ...properties,
      };
    },

    async run(action, uid, properties) {
      for (const subscriber of subscribers) {
        if (typeof subscriber === 'function') {
          const event = this.createEvent(action, uid, properties);
          await subscriber(event);
          continue;
        }

        const hasAction = action in subscriber;
        const hasModel = !subscriber.models || subscriber.models.includes(uid);

        if (hasAction && hasModel) {
          const event = this.createEvent(action, uid, properties);

          await subscriber[action](event);
        }
      }
    },

    clear() {
      subscribers = [];
    },
  };

  return lifecycleManager;
};

module.exports = {
  createLifecyclesManager,
};

Fix return in for loop instead of continue


import React, { Component } from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import { space, width, height } from 'styled-system';

import { preset } from 'react/styles/functions';

import Text from 'react/components/UI/Text';

const Container = styled.div`
  box-sizing: border-box;
  display: flex;
  flex: 1;
  align-items: center;
  justify-content: center;
  user-select: none;

  ${space}
  ${preset(width, { width: '100%' })}
  ${preset(height, { height: '100%' })}
`;

export default class LoadingIndicator extends Component {
  static propTypes = {
    frames: PropTypes.arrayOf(PropTypes.string),
    interval: PropTypes.number,
  }

  static defaultProps = {
    frames: [
      '·',
      '··',
      '···',
    ],
    interval: 175,
  }

  state = {
    cursor: 0,
  }

  componentDidMount() {
    this.interval = setInterval(() => {
      this.setState(({ cursor }) => ({ cursor: cursor + 1 }));
    }, this.props.interval);
  }

  componentWillUnmount() {
    clearInterval(this.interval);
  }

  render() {
    const { cursor } = this.state;
    const { frames, ...rest } = this.props;

    return (
      <Container {...rest}>
        <Text f={7} color="gray.base">
          {frames[cursor % frames.length]}
        </Text>
      </Container>
    );
  }
}

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import { space, width, height } from 'styled-system';

import { preset } from 'react/styles/functions';

import Text from 'react/components/UI/Text';

const Container = styled.div`
  box-sizing: border-box;
  display: flex;
  flex: 1;
  align-items: center;
  justify-content: center;
  user-select: none;
  ${space}
  ${preset(width, { width: '100%' })}
  ${preset(height, { height: '100%' })}
`;

export default class LoadingIndicator extends Component {
  static propTypes = {
    frames: PropTypes.arrayOf(PropTypes.string),
    interval: PropTypes.number,
    f: PropTypes.number,
    color: PropTypes.string,
  }

  static defaultProps = {
    frames: [
      '·',
      '··',
      '···',
    ],
    interval: 175,
    f: 7,
    color: 'gray.base',
  }

  state = {
    cursor: 0,
  }

  componentDidMount() {
    this.interval = setInterval(() => {
      this.setState(({ cursor }) => ({ cursor: cursor + 1 }));
    }, this.props.interval);
  }

  componentWillUnmount() {
    clearInterval(this.interval);
  }

  render() {
    const { cursor } = this.state;
    const {
      frames, f, color, ...rest
    } = this.props;

    return (
      <Container {...rest}>
        <Text f={f} color={color}>
          {frames[cursor % frames.length]}
        </Text>
      </Container>
    );
  }
}

Support color/font size in loading indicator


/**
 * Internal dependencies
 */
import { addAMPExtraProps } from '../';

describe( 'addAMPExtraProps', () => {
	it( 'does not modify non-child blocks', () => {
		const props = addAMPExtraProps( {}, { name: 'foo/bar' }, {} );

		expect( props ).toStrictEqual( {} );
	} );

	it( 'generates a unique ID', () => {
		const props = addAMPExtraProps( {}, { name: 'amp/amp-story-text' }, {} );

		expect( props ).toHaveProperty( 'id' );
	} );

	it( 'uses the existing anchor attribute as the ID', () => {
		const props = addAMPExtraProps( {}, { name: 'amp/amp-story-text' }, { anchor: 'foo' } );

		expect( props ).toStrictEqual( { id: 'foo' } );
	} );

	it( 'adds a font family attribute', () => {
		const props = addAMPExtraProps( {}, { name: 'amp/amp-story-text' }, { ampFontFamily: 'Roboto' } );

		expect( props ).toMatchObject( { 'data-font-family': 'Roboto' } );
	} );

	it( 'adds inline CSS for rotation', () => {
		const props = addAMPExtraProps( {}, { name: 'amp/amp-story-text' }, { rotationAngle: 90.54321 } );

		expect( props ).toMatchObject( { style: { transform: 'rotate(90deg)' } } );
	} );
} );

/**
 * Internal dependencies
 */
import { addAMPExtraProps } from '../';

describe( 'addAMPExtraProps', () => {
	it( 'does not modify non-child blocks', () => {
		const props = addAMPExtraProps( {}, { name: 'foo/bar' }, {} );

		expect( props ).toStrictEqual( {} );
	} );

	it( 'adds a font family attribute', () => {
		const props = addAMPExtraProps( {}, { name: 'amp/amp-story-text' }, { ampFontFamily: 'Roboto' } );

		expect( props ).toMatchObject( { 'data-font-family': 'Roboto' } );
	} );

	it( 'adds inline CSS for rotation', () => {
		const props = addAMPExtraProps( {}, { name: 'amp/amp-story-text' }, { rotationAngle: 90.54321 } );

		expect( props ).toMatchObject( { style: { transform: 'rotate(90deg)' } } );
	} );
} );

Remove tests that are not relevant anymore.


export default class ChannelHistogramController {
    constructor() {
        'ngInject';
    }

    $onInit() {
        this.histOptions = {
            chart: {
                type: 'lineChart',
                showLegend: false,
                showXAxis: false,
                showYAxis: false,
                margin: {
                    top: 0,
                    right: 0,
                    bottom: 0,
                    left: 0
                },
                xAxis: {
                    showLabel: false
                },
                yAxis: {
                    showLabel: false
                }
            }
        };

        if (this.data) {
            this.histData = Array.from(this.data);
        }
    }

    $onChanges(changesObj) {
        if ('data' in changesObj && changesObj.data.currentValue) {
            this.histData = Array.from(changesObj.data.currentValue);
        }
    }
}

export default class ChannelHistogramController {
    constructor() {
        'ngInject';
    }

    $onInit() {
        this.histOptions = {
            chart: {
                type: 'lineChart',
                showLegend: false,
                showXAxis: false,
                showYAxis: false,
                interactive: false,
                margin: {
                    top: 0,
                    right: 0,
                    bottom: 0,
                    left: 0
                },
                xAxis: {
                    showLabel: false
                },
                yAxis: {
                    showLabel: false
                }
            }
        };

        if (this.data) {
            this.histData = Array.from(this.data);
        }
    }

    $onChanges(changesObj) {
        if ('data' in changesObj && changesObj.data.currentValue) {
            this.histData = Array.from(changesObj.data.currentValue);
        }
    }
}

Remove interactivity from cc histogram

Remove interactivity from cc histogram


import React, { PropTypes } from 'react';
import { Link, IndexLink } from 'react-router';

import { Login } from 'auth';
import { ErrorHandler } from 'error';
import { rendered } from 'lib/fetchData';

export default class App extends React.Component {
  static propTypes = {
    children: PropTypes.object,
  };

  componentDidMount() {
    rendered();
  }

  render() {
    return (

        <IndexLink to="/">About</IndexLink>
        {' '}
        <Link to="/todos">Todos</Link>

        <Login />

        <ErrorHandler>
          {this.props.children}
        </ErrorHandler>

    );
  }
}

import React, { PropTypes } from 'react';
import { Link, IndexLink } from 'react-router';

import { Login } from 'auth';
import { ErrorHandler } from 'error';
import { rendered } from 'lib/fetchData';

export default class App extends React.Component {
  static propTypes = {
    children: PropTypes.object,
  };

  componentDidMount() {
    /* This is one of the most important lines of the app,
     * it is where we start triggering loading of data
     * on the client-side
     */
    rendered();
  }

  render() {
    return (

        <IndexLink to="/">About</IndexLink>
        {' '}
        <Link to="/todos">Todos</Link>

        <Login />

        <ErrorHandler>
          {this.props.children}
        </ErrorHandler>

    );
  }
}

Add comment as to how important the call to render() is


import bg from 'gulp-bg';
import eslint from 'gulp-eslint';
import gulp from 'gulp';
import runSequence from 'run-sequence';
import webpackBuild from './webpack/build';

const runEslint = () => {
  return gulp.src([
    'gulpfile.babel.js',
    'src/**/*.js',
    'webpack/*.js'
    // '!**/__tests__/*.*'
  ])
  .pipe(eslint())
  .pipe(eslint.format());
};

// Always use Gulp only in development
gulp.task('set-dev-environment', () => {
  process.env.NODE_ENV = 'development'; // eslint-disable-line no-undef
});

gulp.task('build', webpackBuild);

gulp.task('eslint', () => {
  return runEslint();
});

gulp.task('eslint-ci', () => {
  // Exit process with an error code (1) on lint error for CI build.
  return runEslint().pipe(eslint.failAfterError());
});

gulp.task('test', (done) => {
  runSequence('eslint-ci', 'build', done);
});

gulp.task('server-hot', bg('node', './webpack/server'));

gulp.task('server', ['set-dev-environment', 'server-hot'], bg('./node_modules/.bin/nodemon', './src/server'));

gulp.task('default', ['server']);

import bg from 'gulp-bg';
import eslint from 'gulp-eslint';
import gulp from 'gulp';
import runSequence from 'run-sequence';
import webpackBuild from './webpack/build';
import os from 'os';

const runEslint = () => {
  return gulp.src([
    'gulpfile.babel.js',
    'src/**/*.js',
    'webpack/*.js'
    // '!**/__tests__/*.*'
  ])
  .pipe(eslint())
  .pipe(eslint.format());
};

// Always use Gulp only in development
gulp.task('set-dev-environment', () => {
  process.env.NODE_ENV = 'development'; // eslint-disable-line no-undef
});

gulp.task('build', webpackBuild);

gulp.task('eslint', () => {
  return runEslint();
});

gulp.task('eslint-ci', () => {
  // Exit process with an error code (1) on lint error for CI build.
  return runEslint().pipe(eslint.failAfterError());
});

gulp.task('test', (done) => {
  runSequence('eslint-ci', 'build', done);
});

gulp.task('server-hot', bg('node', './webpack/server'));

gulp.task('server', ['set-dev-environment', 'server-hot'], bg(
  os.type() == 'Windows_NT' ? '.\\node_modules\\.bin\\nodemon.cmd' : './node_modules/.bin/nodemon', './src/server'));

gulp.task('default', ['server']);

Fix gulp nodemon commang for windows environments

original fix by @mannro


/**
 * Handles writting the notes to the browser console in synchronization with the reveal.js
 */
var ConsoleNotes = (function() {

    function log(event) {
	    // event.previousSlide, event.currentSlide, event.indexh, event.indexv
	    var notes = event.currentSlide.querySelector(".notes");

		if (notes) {
	        console.info(notes.innerHTML.replace(/\n\s+/g, "\n"));
	    }
    }

    // Fires when reveal.js has loaded all (synchronous) dependencies and is ready to start navigating
    Reveal.addEventListener("ready", log);

    // Fires when slide is changed
    Reveal.addEventListener("slidechanged", log);
})();

/**
 * Handles writting the notes to the browser console in synchronization with the reveal.js
 */
var ConsoleNotes = (function() {

    function log(event) {
	    // event.previousSlide, event.currentSlide, event.indexh, event.indexv
	    var notes = event.currentSlide.querySelector(".notes");

		if (notes) {
	        console.info(notes.innerHTML.replace(/\n\s+/g, "\n"));
	    }
    }

    // Fires when reveal.js has loaded all (synchronous) dependencies and is ready to start navigating
    //Reveal.addEventListener("ready", log);

    // Fires when slide is changed
    Reveal.addEventListener("slidechanged", log);
})();

Remove load event handler for browser console speaker notes plugin


const ENV = {
  // ...
  "ember-validated-form": {
    label: {
      submit: "Go for it!"
    },
    css: {
      // bootstrap classes
      group: "form-group",
      radio: "radio",
      control: "form-control",
      label: "col-form-label",
      help: "small form-text text-danger",
      hint: "small form-text text-muted",
      checkbox: "checkbox",
      button: "btn btn-default",
      submit: "btn btn-primary",
      loading: "loading",
      valid: "is-valid",
      invalid: "is-invalid"
    }
  }
  // ...
};

const ENV = {
  // ...
  "ember-validated-form": {
    label: {
      submit: "Go for it!"
    },
    css: {
      // bootstrap classes
      group: "form-group",
      radio: "radio",
      control: "form-control",
      label: "col-form-label",
      help: "small form-text text-danger",
      hint: "small form-text text-muted",
      checkbox: "checkbox",
      button: "btn btn-default",
      submit: "btn btn-primary",
      loading: "loading",
      valid: "is-valid",
      error: "is-invalid"
    }
  }
  // ...
};

Update bootstrap 4 config error class

Update bootstrap 4 config error class

The error class used in `error`, not `invalid`.


define([
    'jquery',
    'var/eventStorage',
    'prototype/var/EmojioneArea'
],
function($, eventStorage, EmojioneArea) {
    EmojioneArea.prototype.off = function(events, handler) {
        if (events) {
            var id = this.id;
            $.each(events.toLowerCase().replace(/_/g, '.').split(' '), function(i, event) {
                if (eventStorage[id][event] && !/^@/.test(event)) {
                    if (handler) {
                        $.each(eventStorage[id][event], function(j, fn) {
                            if (fn === handler) {
                                eventStorage[id][event] = eventStorage[id][event].splice(j, 1);
                            }
                        });
                    } else {
                        eventStorage[id][event] = [];
                    }
                }
            });
        }
        return this;
    };
});

define([
    'jquery',
    'var/eventStorage',
    'prototype/var/EmojioneArea'
],
function($, eventStorage, EmojioneArea) {
    EmojioneArea.prototype.off = function(events, handler) {
        if (events) {
            var id = this.id;
            $.each(events.toLowerCase().replace(/_/g, '.').split(' '), function(i, event) {
                if (eventStorage[id][event] && !/^@/.test(event)) {
                    if (handler) {
                        $.each(eventStorage[id][event], function(j, fn) {
                            if (fn === handler) {
                                eventStorage[id][event].splice(j, 1);
                            }
                        });
                    } else {
                        eventStorage[id][event] = [];
                    }
                }
            });
        }
        return this;
    };
});

Fix typo which causes memory leak.

Fix typo which causes memory leak.


#!/usr/bin/env node

var argv = require('argv');
var polyjuice = require('./polyjuice');

var options = [
  {
    name: 'jshint',
    type: 'string',
    description: 'Defines the source file for jshint',
    example: '\'polyjuice --jshint .jshintrc\''
  },
  {
    name: 'jscs',
    type: 'string',
    description: 'Defines the source file for jscs',
    example: '\'polyjuice --jscs .jscsrc\''
  }
];

var src = argv.option(options).run();

var output = {};

if (src.options.jshint && src.options.jscs) {
  output = polyjuice.to.eslint(src.options.jshint, src.options.jscs);
} else if (src.options.jshint) {
  output = polyjuice.from.jshint(src.options.jshint);
} else if (src.options.jscs) {
  output = polyjuice.from.jscs(src.options.jscs);
}

console.log(JSON.stringify(output, null, 2));

#!/usr/bin/env node

var argv = require('argv');
var polyjuice = require('./polyjuice');

var options = [
  {
    name: 'jshint',
    type: 'string',
    description: 'Defines the source file for jshint',
    example: '\'polyjuice --jshint=.jshintrc\''
  },
  {
    name: 'jscs',
    type: 'string',
    description: 'Defines the source file for jscs',
    example: '\'polyjuice --jscs=.jscsrc\''
  }
];

var src = argv.option(options).run();

var output = {};

if (src.options.jshint && src.options.jscs) {
  output = polyjuice.to.eslint(src.options.jshint, src.options.jscs);
} else if (src.options.jshint) {
  output = polyjuice.from.jshint(src.options.jshint);
} else if (src.options.jscs) {
  output = polyjuice.from.jscs(src.options.jscs);
}

console.log(JSON.stringify(output, null, 2));

Fix --help examples to include '='

Fix --help examples to include '='

Fixes #9


'use strict';

const _              = require('lodash');
const updateNotifier = require('update-notifier');

/**
 * Expose a CLI with given actions as subcommands
 * @param {Array} actions Array of subclasses of Action
 * @param {Object} pkg The package.json contents
 * @returns {Array}
 */
module.exports = function (actions, pkg) {

    updateNotifier({pkg}).notify();

    const yargs = require('yargs').usage('$0 <cmd> [args]');

    _.each(actions, Action => (new Action()).register(yargs));

    return yargs
        .help('h')
        .version()
        .alias('h', 'help')
        .alias('v', 'version')
        .argv;
};

'use strict';

const _              = require('lodash');
const updateNotifier = require('update-notifier');

/**
 * Expose a CLI with given actions as subcommands
 * @param {Array} actions Array of subclasses of Action
 * @param {Object} pkg The package.json contents
 * @returns {Array}
 */
module.exports = function (actions, pkg) {

    updateNotifier({pkg}).notify();

    const yargs = require('yargs').usage('$0 <cmd> [args]');

    _.each(actions, Action => (new Action()).register(yargs));

    return yargs
        .help('h')
        .version()
        .alias('h', 'help')
        .argv;
};

Fix error caused by aliasing "v" to "version" on yargs


const spawn = require("./spawn");

module.exports = function(workDir, isDebug){
    function git() {
        var len = arguments.length;
        var args = new Array(len);

        for (var i = 0; i < len; i++) {
            args[i] = arguments[i];
        }

        return spawn('git', args, {
            cwd: workDir,
            verbose: isDebug
        });
    }

    return function pullFetchRebasePush(forkedRepoUrl, sourceRepoUrl, forkedDefaultBranch, sourceDefaultBranch) {
        return git('init')
            .then(() => git('remote', 'add', 'origin', forkedRepoUrl))
            .then(() => git('pull', 'origin', forkedDefaultBranch))
            .then(() => git('remote', 'add', 'upstream', sourceRepoUrl))
            .then(() => git('fetch', 'upstream'))
            .then(() => git('checkout', 'origin/' + forkedDefaultBranch))
            .then(() => git('merge', 'upstream/' + sourceDefaultBranch))
            .then(() => git('push', 'origin', 'HEAD:' + forkedDefaultBranch, '--force'))
            .catch((err) => console.log(err));
    }
}

const spawn = require("./spawn");

module.exports = function(workDir, isDebug){
    function git() {
        var len = arguments.length;
        var args = new Array(len);

        for (var i = 0; i < len; i++) {
            args[i] = arguments[i];
        }

        return spawn('git', args, {
            cwd: workDir,
            verbose: isDebug
        });
    }

    return function pullFetchRebasePush(forkedRepoUrl, sourceRepoUrl, forkedDefaultBranch, sourceDefaultBranch) {
        return git('init')
            .then(() => git('remote', 'add', 'origin', forkedRepoUrl))
            .then(() => git('pull', 'origin', forkedDefaultBranch))
            .then(() => git('remote', 'add', 'upstream', sourceRepoUrl))
            .then(() => git('fetch', 'upstream'))
            .then(() => git('checkout', 'origin/' + forkedDefaultBranch))
            .then(() => git('merge', 'upstream/' + sourceDefaultBranch))
            .then(() => git('push', 'origin', 'HEAD:' + forkedDefaultBranch))
            .catch((err) => console.log(err));
    }
}

Remove force as it may overwrite somthing unwanted.


Router.hooks = {
  dataNotFound: function (pause) {
    var data = this.data();
    var tmpl;

    if (data === null || typeof data === 'undefined') {
      tmpl = this.lookupProperty('notFoundTemplate');

      if (tmpl) {
        this.render(tmpl);
        this.renderRegions();
        pause();
      }
    }
  },

  loading: function (pause) {
    var self = this;
    var tmpl;

    if (!this.ready()) {
      tmpl = this.lookupProperty('loadingTemplate');

      if (tmpl) {
        this.render(tmpl);
        this.renderRegions();
        pause();
      }
    }
  }
};

Router.hooks = {
  dataNotFound: function (pause) {
    var data = this.data();
    var tmpl;

    if (!this.ready())
      return;

    if (data === null || typeof data === 'undefined') {
      tmpl = this.lookupProperty('notFoundTemplate');

      if (tmpl) {
        this.render(tmpl);
        this.renderRegions();
        pause();
      }
    }
  },

  loading: function (pause) {
    var self = this;
    var tmpl;

    if (!this.ready()) {
      tmpl = this.lookupProperty('loadingTemplate');

      if (tmpl) {
        this.render(tmpl);
        this.renderRegions();
        pause();
      }
    }
  }
};

Make dataNotFoundHook return if data not ready.


var Client = require('../index').Client;
var Transport = require('./mock/Transport');
var OutgoingFrameStream = require('./mock/OutgoingFrameStream');
var assert = require('assert');

describe('Client.connect', function() {

  var client, transport, framesOut, framesIn;

  beforeEach(function() {

    transport = new Transport();
    framesOut = new OutgoingFrameStream();

    client = new Client(transport, {
      outgoingFrameStream: framesOut
    });
  });

  it('should send a CONNECT frame and include accept-version header', function(done) {

    client.connect();

    assert(framesOut._frames[0]);

    var frame = framesOut._frames[0];

    assert.equal(frame.command, 'CONNECT');
    assert('accept-version' in frame.headers);

    assert.equal(frame._finished, true);
    assert.equal(frame._body.length, 0);

    done();
  });

});

var Client = require('../index').Client;
var Transport = require('./mock/Transport');
var OutgoingFrameStream = require('./mock/OutgoingFrameStream');
var assert = require('assert');

describe('Client.connect', function() {

  var client, transport, framesOut, framesIn;

  beforeEach(function() {

    transport = new Transport();
    framesOut = new OutgoingFrameStream();

    client = new Client(transport, {
      outgoingFrameStream: framesOut
    });
  });

  it('should send a CONNECT frame and include accept-version header', function(done) {

    client.connect();

    assert(framesOut._frames[0]);

    var frame = framesOut._frames[0];

    assert.equal(frame.command, 'CONNECT');
    assert('accept-version' in frame.headers);

    assert.equal(frame._finished, true);
    assert.equal(frame._body.length, 0);

    done();
  });

  it('should parse the heart-beat header and call setHeartbeat', function(done) {

    client.connect({
      'heart-beat': '1,2'
    });

    var heartbeat = client.getHeartbeat();

    assert.equal(heartbeat[0], 1);
    assert.equal(heartbeat[1], 2);

    assert.equal(framesOut._frames[0].headers['heart-beat'], '1,2');

    done();
  });
});

Test for heart-beat header handling

Test for heart-beat header handling


import Ember from 'ember';

export default Ember.Helper.helper(function ([prefix, ...classNames]) {
  var classString = '';

  classNames.forEach(function(name) {
    if (!name) return;

    var trimmedName = name.replace(/\s/g, '');
    if (trimmedName === '') return;

    if (trimmedName === ':component') {
      classString += `${prefix} `;
    } else {
      classString += `${prefix}${trimmedName} `;
    }
  });

  return classString;
});

import Ember from 'ember';

const FONT_SIZE_PATTERN = /font-size/;

export default Ember.Helper.helper(function ([prefix, ...classNames]) {
  return classNames.reduce(function(string, name) {
    if (!name) return string;

    let trimmedName = name.replace(/\s/g, '');
    if (trimmedName === '') return string;

    switch (true) {
      case (trimmedName === ':component'):
        return string += `${prefix} `;
      case (FONT_SIZE_PATTERN.test(trimmedName)):
        return string += `${trimmedName} `;
      default:
        return string += `${prefix}${trimmedName} `;
    }
  }, '');
});

Whitelist font-size classes from prefixing


module.exports = profile => {
	return {
		uid: profile.username || profile.id,
		mail: profile.emails && profile.emails[0] && profile.emails[0].value,
		cn: profile.displayName,
		displayName: profile.displayName,
		givenName: profile.name.familyName,
		sn: profile.name.givenName
	}
}

module.exports = profile => {
	return {
		uid: profile.username || profile.id,
		mail: profile.emails && profile.emails[0] && profile.emails[0].value,
		cn: profile.displayName,
		displayName: profile.displayName,
		givenName: profile.name.givenName,
		sn: profile.name.familyName
	}
}

Fix typo in windows live mapping

Fix typo in windows live mapping


// Obtain ALL anchors on the page.
var links = document.links;

// The previous/next urls if they exist.
var prev = findHref("prev");
var next = findHref("next");

/**
 * Find the href for a given name.
 * @param {String} The name of the anchor to search for.
 * @return {String} The href for a given tag, otherwise an empty string.
 */
function findHref(name) {
  for (var index = 0; index < links.length; ++index) {
    // The complete anchor HTML element (<a>).
    var anchor = links[index];
    // Not all anchors have text, rels or classes defined.
    var rel = (anchor.rel !== undefined) ? anchor.rel : '';
    var text = (anchor.text !== undefined) ? anchor.text.toLowerCase() : '';
    var class_name = (anchor.className !== undefined) ? anchor.className : '';
    if (rel.indexOf(name) > -1 || text.indexOf(name) > -1 || class_name.indexOf(name) > -1) {
      return anchor.href;
    }
  }
}

// Go to the next/previous pages using the arrow keys.
document.addEventListener('keydown', function(event) {
  if(event.keyCode == 37) {
    if (prev) chrome.extension.sendMessage({redirect: prev});
  }
  else if(event.keyCode == 39) {
    if (next) chrome.extension.sendMessage({redirect: next});
  }
});

// Obtain ALL anchors on the page.
var links = document.links;

// The previous/next urls if they exist.
var prev = findHref("prev");
var next = findHref("next");

/**
 * Find the href for a given name.
 * @param {String} name - The name of the anchor to search for.
 * @return {String} The href for a given tag, otherwise an empty string.
 */
function findHref(name) {
  for (var index = 0; index < links.length; ++index) {
    // The complete anchor HTML element (<a>).
    var anchor = links[index];
    // Does the name exist in the anchor?
    if (isNameInAnchor(name, anchor.rel)  ||
        isNameInAnchor(name, anchor.text.toLowerCase()) ||
        isNameInAnchor(name, anchor.className))
    {
      return anchor.href
    }
  }
}

/**
 * Does the word exist in a given element?
 * @param {String} name - The name to search for.
 * @param {String} element - The element to search in.
 * @return {Boolean} True if the name exists in the element, otherwise false.
 */
function isNameInAnchor(name, element) {
  if (element !== undefined && element.indexOf(name) > -1) return true;
  else return false
}

// Go to the next/previous pages using the arrow keys.
document.addEventListener('keydown', function(event) {
  if(event.keyCode == 37) {
    if (prev) chrome.extension.sendMessage({redirect: prev});
  }
  else if(event.keyCode == 39) {
    if (next) chrome.extension.sendMessage({redirect: next});
  }
});

Refactor repeated checking to a helper method.


app.module("ContentModule", function(ContentModule, app){
  ContentModule.listTemplate =  "
List
";
  ContentModule.showTemplate =  "
Show <%= id %>
";

  ContentModule.ListView = Marionette.ItemView.extend({
    template: _.template(ContentModule.listTemplate),
    className: 'content list'
  });

  ContentModule.ShowView = Marionette.ItemView.extend({
    template: _.template(ContentModule.showTemplate),
    className: 'content show'
  });

  ContentModule.Controller = Marionette.Controller.extend({
    displayList: function() {
      app.content.show(new ContentModule.ListView());
    },

    displayShow: function(id) {
      app.content.show(new ContentModule.ShowView({
        model: new Backbone.Model({id: id})
      }));
    }
  });

  ContentModule.addInitializer(function() {
    ContentModule.controller = new ContentModule.Controller();
  });

  ContentModule.Router = Marionette.AppRouter.extend({
    appRoutes: {
      "list": "displayList",
      "show/:id": "displayShow"
    }
  });

  ContentModule.addInitializer(function() {
    new ContentModule.Router({
      controller: ContentModule.controller
    })
  });
});

app.module("ContentModule", function(ContentModule, app){
  ContentModule.itemTemplate =  "Item";
  ContentModule.listTemplate =  "
List

";
  ContentModule.showTemplate =  "
Show <%= id %>
";

  ContentModule.ItemView = Marionette.ItemView.extend({
    template: _.template(ContentModule.itemTemplate),
    tagName: 'li',
  });

  ContentModule.ListView = Marionette.CompositeView.extend({
    template: _.template(ContentModule.listTemplate),
    className: 'content list',
    itemView: ContentModule.ItemView,
    itemViewContainer: 'ul'
  });

  ContentModule.ShowView = Marionette.ItemView.extend({
    template: _.template(ContentModule.showTemplate),
    className: 'content show'
  });

  ContentModule.Controller = Marionette.Controller.extend({
    displayList: function() {
      app.content.show(new ContentModule.ListView({
        collection: new Backbone.Collection([{1:1},{2:2},{3:3}])
      }));
    },

    displayShow: function(id) {
      app.content.show(new ContentModule.ShowView({
        model: new Backbone.Model({id: id})
      }));
    }
  });

  ContentModule.addInitializer(function() {
    ContentModule.controller = new ContentModule.Controller();
  });

  ContentModule.Router = Marionette.AppRouter.extend({
    appRoutes: {
      "list": "displayList",
      "show/:id": "displayShow"
    }
  });

  ContentModule.addInitializer(function() {
    new ContentModule.Router({
      controller: ContentModule.controller
    })
  });
});

Change list view to be a composite view

Change list view to be a composite view


'use strict';

function randomIntFromInterval(min,max){return Math.floor(Math.random()*(max-min+1)+min);};

var markovGenerator = {
	order: 2,
	table:{},
	load:function(text){
		text = text.toLowerCase();
		//text.replace(/[^a-z ]/, '');

		for (var i = this.settings.order; i < text.length; i++){
			var entry = text.slice(i-this.order, i);
			if(Array.isArray(this.table[entry])){
				this.table[entry].push(text.charAt(i));
			}else{
				this.table[entry] = [text.charAt(i)];
			}
		}
	},
	generate:function(length, start){
		if(length == undefined){
			throw 'tooFewArguments';
		}
		if(start == undefined){
			var possibleKeys = Object.keys(this.table);
			start = possibleKeys[randomIntFromInterval(0, possibleKeys.length)];
		}

		var text = start;
		while(text.length < length){
			var key = text.slice(text.length-this.order, text.length);
			var possibilities = this.table[key];
			var letter = possibilities[randomIntFromInterval(0, possibilities.length-1)];

			text += letter;
		}

		return text;
	}
}

'use strict';

function randomIntFromInterval(min,max){return Math.floor(Math.random()*(max-min+1)+min);};

var markovGenerator = {
	order: 2,
	table:{},
	load:function(text){
		text = text.toLowerCase();
		//text.replace(/[^a-z ]/, '');

		for (var i = this.order; i < text.length; i++){
			var entry = text.slice(i-this.order, i);
			if(Array.isArray(this.table[entry])){
				this.table[entry].push(text.charAt(i));
			}else{
				this.table[entry] = [text.charAt(i)];
			}
		}
	},
	generate:function(length, start){
		if(length == undefined){
			throw 'tooFewArguments';
		}
		if(start == undefined){
			var possibleKeys = Object.keys(this.table);
			start = possibleKeys[randomIntFromInterval(0, possibleKeys.length)];
		}

		var text = start;
		while(text.length < length){
			var key = text.slice(text.length-this.order, text.length);
			var possibilities = this.table[key];
			var letter = possibilities[randomIntFromInterval(0, possibilities.length-1)];

			text += letter;
		}

		return text;
	}
}

Fix for a wrongly named variable introduced when cleaning up


module.exports = function(grunt) {

  grunt.initConfig({
    connect: {
      server: {
        options: {
          port: 8000,
          useAvailablePort: true,
          hostname: '*',
          keepalive: true
        }
      }
    },
    uglify: {
      dist: {
        files: {
          'dist/angular-local-storage.min.min.js': ['src/angular-local-storage.min.js']
        }
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-connect');
  grunt.loadNpmTasks('grunt-contrib-uglify');

  grunt.registerTask('start_server', ['connect:server']);
  grunt.registerTask('dist', ['uglify:dist']);

};

module.exports = function(grunt) {

  grunt.initConfig({
    connect: {
      server: {
        options: {
          port: 8000,
          useAvailablePort: true,
          hostname: '*',
          keepalive: true
        }
      }
    },
    uglify: {
      dist: {
        files: {
          'dist/angular-local-storage.min.js': ['src/angular-local-storage.js']
        }
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-connect');
  grunt.loadNpmTasks('grunt-contrib-uglify');

  grunt.registerTask('start_server', ['connect:server']);
  grunt.registerTask('dist', ['uglify:dist']);

};

Change config uglify task grunt

Change config uglify task grunt


import React from 'react'
import {Link} from 'react-router-dom'

function setArticleUrl(title) {
  var sanitizedTitle = title.replace(/%/g, "[percent]")
  debugger
  return encodeURIComponent(sanitizedTitle)
}

const ArticleCard = ({channel, article}) => {
  return (
    <Link to={`/newsfeed/${channel.source_id}/${setArticleUrl(article.title)}`} className="article-link">

{article.title}

    </Link>
  )
}

export default ArticleCard

import React from 'react'
import {Link} from 'react-router-dom'

const ArticleCard = ({channel, article, setArticleUrl}) => {
  return (
    <Link to={`/newsfeed/${channel.source_id}/${setArticleUrl(article.title)}`} className="article-link">

{article.title}

    </Link>
  )
}

export default ArticleCard

Move setUrl functoin to parent

Move setUrl functoin to parent


'use strict';

angular.module('gc.ngHttpGetParamInterceptor', [
  'gc.utils'
]).factory('httpGetParamInterceptor', [
  'utils',
  function httpGetParamInterceptor(utils) {
    return {
      request: function(config) {
        // Manually build query params using custom logic because Angular and
        // Rails do not handle nested query parameters in the same way
        if (config.params) {
          config.url = config.url + '?' + utils.param(config.params);
          delete config.params;
        }
        return config;
      }
    };
  }
]);

'use strict';

angular.module('gc.ngHttpGetParamInterceptor', [
  'gc.utils'
]).factory('httpGetParamInterceptor', [
  'utils',
  function httpGetParamInterceptor(utils) {

    function deleteUndefinedValues(obj) {
      _.keys(obj).forEach(function(key) {
        if (_.isUndefined(obj[key])) {
          delete obj[key];
        } else if (_.isObject(obj[key])) {
          deleteUndefinedValues(obj[key]);
        }
      });
    }

    return {
      request: function(config) {
        // Manually build query params using custom logic because Angular and
        // Rails do not handle nested query parameters in the same way
        if (config.params) {
          deleteUndefinedValues(config.params);
          config.url = config.url + '?' + utils.param(config.params);
          delete config.params;
        }
        return config;
      }
    };
  }
]);

Revert "Revert "delete undefined properties from http config params""

This reverts commit f2875d45257f892607df8a460b253cd35fb0900a.


"use strict";

var exampleTests = require('../example-tests');

function Controls(mochaRunner) {
  this._mochaRunner = mochaRunner;
}

Controls.prototype = {
  connectButtons: function(passingButtonId, failingButtonId, manyButtonId) {
    function connect(id, fn) {
      document.getElementById(id).addEventListener('click', fn);
    }
    connect(passingButtonId, this._postPassingTest.bind(this));
    connect(failingButtonId, this._postFailingTest.bind(this));
    connect(manyButtonId, this._postManyTests.bind(this));
  },
  _postPassingTest: function() {
    this._postTestSourceCode(exampleTests.simplePassingTestCode);
  },
  _postFailingTest: function() {
    this._postTestSourceCode(exampleTests.simplePassingTestCode);
  },
  _postManyTests: function() {
    this._postTestSourceCode([
      exampleTests.simplePassingTestCode,
      exampleTests.simplePassingTestCode,
      exampleTests.simplePassingTestCode,
      exampleTests.simpleFailingTestCode,
      exampleTests.simpleFailingTestCode,
      exampleTests.simplePassingTestCode
    ].join('\n'));
  },
  _postTestSourceCode: function(sourceCode) {
    this._mochaRunner.send(sourceCode);
  }
};

module.exports = Controls;

"use strict";

var exampleTests = require('../example-tests');

function Controls(mochaRunner) {
  this._mochaRunner = mochaRunner;
}

Controls.prototype = {
  connectButtons: function(passingButtonId, failingButtonId, manyButtonId) {
    function connect(id, fn) {
      document.getElementById(id).addEventListener('click', fn);
    }
    connect(passingButtonId, this._postPassingTest.bind(this));
    connect(failingButtonId, this._postFailingTest.bind(this));
    connect(manyButtonId, this._postManyTests.bind(this));
  },
  _postPassingTest: function() {
    this._postTestSourceCode(exampleTests.simplePassingTestCode);
  },
  _postFailingTest: function() {
    this._postTestSourceCode(exampleTests.simpleFailingTestCode);
  },
  _postManyTests: function() {
    this._postTestSourceCode([
      exampleTests.simplePassingTestCode,
      exampleTests.simplePassingTestCode,
      exampleTests.simplePassingTestCode,
      exampleTests.simpleFailingTestCode,
      exampleTests.simpleFailingTestCode,
      exampleTests.simplePassingTestCode
    ].join('\n'));
  },
  _postTestSourceCode: function(sourceCode) {
    this._mochaRunner.send(sourceCode);
  }
};

module.exports = Controls;

Use the proper test code.

Use the proper test code.


import * as React from "react";
import {BuildSnapshot} from "./BuildSnapshot";

/**
 * Container Component for BuildSnapshots.
 *
 * @param {*} props input property containing an array of build data to be
 * rendered through BuildSnapshot.
 */
export const BuildSnapshotContainer = React.memo((props) =>
  {
    const SOURCE = '/builders';
    const [data, setData] = React.useState([]);

    /**
     * Fetch data when component is mounted.
     * Pass in empty array as second paramater to prevent
     * infinite callbacks as component refreshes.
     * @see Fetching
     */
    React.useEffect(() => {
      fetch(SOURCE)
        .then(res => res.json())
        .then(json => setData(json))
        .catch(setData([]));
    }, []);

    return (

        {data.map(snapshotData => <BuildSnapshot buildData={snapshotData}/>)}

    );
  }
);

import * as React from "react";
import {BuildSnapshot} from "./BuildSnapshot";

/**
 * Container Component for BuildSnapshots.
 *
 * @param {*} props input property containing an array of build data to be
 * rendered through BuildSnapshot.
 */
export const BuildSnapshotContainer = React.memo((props) =>
  {
    const NUMBER = 2;
    const OFFSET = 3;

    const SOURCE = `/builders/number=${NUMBER}/offset=${OFFSET}`;

    const [data, setData] = React.useState([]);

    /**
     * Fetch data when component is mounted.
     * Pass in empty array as second paramater to prevent
     * infinite callbacks as component refreshes.
     * @see Fetching
     */
    React.useEffect(() => {
      fetch(SOURCE)
        .then(res => res.json())
        .then(json => setData(json))
        .catch(setData([]));
    }, []);

    return (

        {data.map(snapshotData => <BuildSnapshot buildData={snapshotData}/>)}

    );
  }
);

Update fetch source, add parameters

Update fetch source, add parameters


import React, { useEffect, useState } from 'react';
import { OpenfinApiHelpers } from 'stockflux-core';
import Components from 'stockflux-components';
import './AppShortcuts.css';

export default () => {
  const [apps, setApps] = useState([]);

  useEffect(() => {
    const options = {
      method: 'GET'
    };

    OpenfinApiHelpers.getCurrentWindow()
      .then(window => window.getOptions())
      .then(options => options.customData.apiBaseUrl)
      .then(baseUrl => fetch(`${baseUrl}/apps/v1`, options))
      .then(response => response.json())
      .then(results => {
        console.log('results', results);
        setApps(results);
      })
      .catch(console.error);
  }, []);

  return (
    <div className="app-shortcuts">
      {apps
        .filter(
          app =>
            app.customConfig !== undefined && app.customConfig.showInLauncher
        )
        .map(app => {
          /* Get the appropriate shortcut button by taking a part of stocklux appId
             (e.g. "news", "watchlist", "chart"), capitalizing the first letter and
             appending "Shortcut" at the end
          */
          const AppShortcut =
            Components[
              app.appId
                .split('stockflux-')[1]
                .charAt(0)
                .toUpperCase() +
                app.appId.slice(11) +
                'Shortcut'
            ];
          return <AppShortcut key={app.appId} app={app} />;
        })}
    </div>
  );
};

import React, { useEffect, useState } from 'react';
import { OpenfinApiHelpers } from 'stockflux-core';
import Components from 'stockflux-components';
import './AppShortcuts.css';

export default () => {
  const [apps, setApps] = useState([]);

  useEffect(() => {
    const options = {
      method: 'GET'
    };

    OpenfinApiHelpers.getCurrentWindow()
      .then(window => window.getOptions())
      .then(options => options.customData.apiBaseUrl)
      .then(baseUrl => fetch(`${baseUrl}/apps/v1`, options))
      .then(response => response.json())
      .then(results => setApps(results))
      .catch(console.error);
  }, []);

  return (
    <div className="app-shortcuts">
      {apps
        .filter(
          app =>
            app.customConfig !== undefined && app.customConfig.showInLauncher
        )
        .map(app => {
          /* Get the appropriate shortcut button by taking a part of stocklux appId
             (e.g. "news", "watchlist", "chart"), capitalizing the first letter and
             appending "Shortcut" at the end
          */
          const AppShortcut =
            Components[
              app.appId
                .split('stockflux-')[1]
                .charAt(0)
                .toUpperCase() +
                app.appId.slice(11) +
                'Shortcut'
            ];
          return <AppShortcut key={app.appId} app={app} />;
        })}
    </div>
  );
};

Rename stockflux-search to stockflux-launcher in package.json and remove console.log


import CartBlogEditorCtrl from './base/cart-blog-editor.js';

class CartBlogCreateCtrl extends CartBlogEditorCtrl {
  constructor(...args) {
    super(...args);
    this.logInit('CartBlogCreateCtrl');

    this.init();
  }

  init() {
    this.apiService.postDefaultCategory().then(
      (category) => {
        this.category = category;
        this.post.category = category.uuid;
      },
      (error) => this.msgService.error(error)
    );
    this.tags = [];
    this.attachments = [];
    this.post = {
      driveId: null,
      title: '',
      created: new Date(),
      updated: new Date(),
      category: null,
      tags: [],
      attachments: [],
      isPublic: this.apiService.postDefaultPrivacy()
    }
  }

  //noinspection ES6Validation
  async save() {
    try {
      //noinspection ES6Validation
      let post = await this.apiService.postUpsert(this.post);
      this.msgService.info('Post saved: ', post);
    } catch (e) {
      this.msgService.error('Error when creating post: ', e.data.error.message);
    }
  }
}

CartBlogCreateCtrl.$inject = [...CartBlogEditorCtrl.$inject];

export default CartBlogCreateCtrl;

import CartBlogEditorCtrl from './base/cart-blog-editor.js';

class CartBlogCreateCtrl extends CartBlogEditorCtrl {
  constructor(...args) {
    super(...args);
    this.logInit('CartBlogCreateCtrl');

    this.init();
  }

  init() {
    this.apiService.postDefaultCategory().then(
      (category) => {
        this.category = category;
        this.post.category = category.uuid;
      },
      (error) => this.msgService.error(error)
    );
    this.tags = [];
    this.attachments = [];
    this.post = {
      uuid: undefined, // have to be "undefined" to make stongloop server recognize this property shall apply defaultFn
      driveId: null,
      title: '',
      created: new Date(),
      updated: new Date(),
      category: null,
      tags: [],
      attachments: [],
      isPublic: this.apiService.postDefaultPrivacy()
    }
  }

  //noinspection ES6Validation
  async save() {
    try {
      //noinspection ES6Validation
      let post = await this.apiService.postUpsert(this.post);
      this.msgService.info('Post saved: ', post);
    } catch (e) {
      this.msgService.error('Error when creating post: ', e.data.error.message);
    }
  }
}

CartBlogCreateCtrl.$inject = [...CartBlogEditorCtrl.$inject];

export default CartBlogCreateCtrl;

Remove uuid in constructor, since strong loop server will see null as value provided, there won't be any defaultFn called. Otherwise you can give uuid "undefined", it's OK.


var baseUrl = process.env.BASE_URI || "localdocker:4000";

module.exports = {
  approveFeed: function(message, recipient, group) {
    return "
" + message["user"]["userName"] + " has approved the feed for their " + message["election"].date.substring(0,10) + " " +
      message["election"].election_type + " election for publication. It was approved by" + message["user"]["userName"] + " (" +
           message["user"]["email"] + ").
" +
           "
Go to the Data Dashboard
";
  },
  processedFeed: function(message, recipient, group) {
    return "
" + recipient.givenName + ",
" +
           "
The data you provided for " + group.description + "'s election is available for you to review on the VIP Data Dashboard.
" +
           "
Please click the link below to review your feed.
" +
           "
Go to the Data Dashboard
" +
           "
If you have any questions, please contact vip@democracy.works.
" +
           "
Thank you!
";
  },
  errorDuringProcessing: function(message) {
    return 'It looks like a feed failed during processing. Here\'s the information we got: \
            \nMessage we got: ' + JSON.stringify(message);
  }
}

var baseUrl = process.env.BASE_URI || "localdocker:4000";

module.exports = {
  approveFeed: function(message, recipient, group) {
    return "
" + message["user"]["userName"] + " has approved the feed for their " + message["election"].date.substring(0,10) + " " +
      message["election"].election_type + " election for publication. It was approved by " + message["user"]["userName"] + " (" +
           message["user"]["email"] + ").
" +
           "
Go to the Data Dashboard
";
  },
  processedFeed: function(message, recipient, group) {
    return "
" + recipient.givenName + ",
" +
           "
The data you provided for " + group.description + "'s election is available for you to review on the VIP Data Dashboard.
" +
           "
Please click the link below to review your feed.
" +
           "
Go to the Data Dashboard
" +
           "
If you have any questions, please contact vip@democracy.works.
" +
           "
Thank you!
";
  },
  errorDuringProcessing: function(message) {
    return 'It looks like a feed failed during processing. Here\'s the information we got: \
            \nMessage we got: ' + JSON.stringify(message);
  }
}

Add missing space in email template.

[CS] Add missing space in email template.


/*
 * grunt-autoprefixer
 *
 *
 * Copyright (c) 2013 Dmitry Nikitenko
 * Licensed under the MIT license.
 */

'use strict';

module.exports = function (grunt) {

    var autoprefixer = require('autoprefixer');

    grunt.registerMultiTask('autoprefixer', 'Parse CSS and add prefixed properties and values by Can I Use database for actual browsers.', function () {

        var options = this.options(),
            compiler = autoprefixer(options.browsers);

        // Iterate over all specified file groups.
        this.files.forEach(function (f) {

            // Concat specified files.
            var src = f.src.filter(function (filepath) {

                // Warn on and remove invalid source files (if nonull was set).
                if (!grunt.file.exists(filepath)) {
                    grunt.log.warn('Source file "' + filepath + '" not found.');
                    return false;
                } else {
                    return true;
                }

            }).map(function (filepath) {

                    // Read file source.
                    return grunt.file.read(filepath);
                }
            ).join('');

            // Write the destination file.
            grunt.file.write(f.dest, compiler.compile(src));

            // Print a success message.
            grunt.log.writeln('File "' + f.dest + '" created.');
        });
    });

};

/*
 * grunt-autoprefixer
 *
 *
 * Copyright (c) 2013 Dmitry Nikitenko
 * Licensed under the MIT license.
 */

'use strict';

module.exports = function (grunt) {

    var autoprefixer = require('autoprefixer');

    grunt.registerMultiTask('autoprefixer', 'Parse CSS and add vendor prefixes to CSS rules using values from the Can I Use website.', function () {

        var options = this.options(),
            compiler = autoprefixer(options.browsers);

        // Iterate over all specified file groups.
        this.files.forEach(function (f) {

            // Concat specified files.
            var src = f.src.filter(function (filepath) {

                // Warn on and remove invalid source files (if nonull was set).
                if (!grunt.file.exists(filepath)) {
                    grunt.log.warn('Source file "' + filepath + '" not found.');
                    return false;
                } else {
                    return true;
                }

            }).map(function (filepath) {

                    // Read file source.
                    return grunt.file.read(filepath);
                }
            ).join('');

            // Write the destination file.
            grunt.file.write(f.dest, compiler.compile(src));

            // Print a success message.
            grunt.log.writeln('File "' + f.dest + '" created.');
        });
    });

};

Update the description of the task.

Update the description of the task.


'use strict';

let telegram = require( './telegram.js' );
let urlchecker = require( './urlchecker.js' );
let github = require( './github.js' );
let rss = require( './rss.js' );
let dagensmix = require( './dagensmix.js' );
let pushbullet = require( './pushbullet.js' );
let httpcat = require( './httpcat.js' );

module.exports.telegram = telegram;
module.exports.urlchecker = urlchecker;
module.exports.github = github;
module.exports.rss = rss;
module.exports.dagensmix = dagensmix;
module.exports.pushbullet = pushbullet;
module.exports.httpcat = httpcat;

'use strict';

let telegram = require( './telegram.js' );
let urlchecker = require( './urlchecker.js' );
let github = require( './github.js' );
let rss = require( './rss.js' );
let dagensmix = require( './dagensmix.js' );
// let pushbullet = require( './pushbullet.js' );
let httpcat = require( './httpcat.js' );

module.exports.telegram = telegram;
module.exports.urlchecker = urlchecker;
module.exports.github = github;
module.exports.rss = rss;
module.exports.dagensmix = dagensmix;
// module.exports.pushbullet = pushbullet;
module.exports.httpcat = httpcat;

Disable pushbullet because we don't use it anymore :D


import React, { Component } from 'react';

import { Route, BrowserRouter as Router } from 'react-router-dom';

import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import injectTapEventPlugin from 'react-tap-event-plugin';

import { blue500 } from 'material-ui/styles/colors';

import Sidebar from './Sidebar/Sidebar';
import FAB from './FAB/FAB';
import TaskList from './Task/TaskList';
import IdeaList from './Idea/IdeaList';
import Settings from './Settings/Settings';
import About from './About/About';

import './App.css';

injectTapEventPlugin();

class App extends Component {
  render() {
    const muiTheme = getMuiTheme({
      palette: {
        primary1Color: blue500,
      },
    });
    return (
      <Router>
        <MuiThemeProvider muiTheme={muiTheme}>
          <div className="App">
            <Sidebar />
            <FAB />
            <Route path="/tasks" component={TaskList} />
            <Route path="/ideas" component={IdeaList} />
            <Route path="/settings" component={Settings} />
            <Route path="/about" component={About} />
          </div>
        </MuiThemeProvider>
      </Router>
    );
  }
}

export default App;

import React, { Component } from 'react';

import { Redirect, Route, BrowserRouter as Router } from 'react-router-dom';

import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import injectTapEventPlugin from 'react-tap-event-plugin';

import { blue500 } from 'material-ui/styles/colors';

import Sidebar from './Sidebar/Sidebar';
import FAB from './FAB/FAB';
import TaskList from './Task/TaskList';
import IdeaList from './Idea/IdeaList';
import Settings from './Settings/Settings';
import About from './About/About';

import './App.css';

injectTapEventPlugin();

class App extends Component {
  render() {
    const muiTheme = getMuiTheme({
      palette: {
        primary1Color: blue500,
      },
    });
    return (
      <Router>
        <MuiThemeProvider muiTheme={muiTheme}>
          <div className="App">
            <Sidebar />
            <FAB />
            <Redirect from="/" to="tasks" />
            <Route path="/tasks" component={TaskList} />
            <Route path="/ideas" component={IdeaList} />
            <Route path="/settings" component={Settings} />
            <Route path="/about" component={About} />
          </div>
        </MuiThemeProvider>
      </Router>
    );
  }
}

export default App;

Add default route from / to /tasks

Add default route from / to /tasks


import React from 'react';
import ReactDOM from 'react-dom';

const ItemTarget = {
  hover(props, monitor, component) {
    const dragIndex = monitor.getItem().index;
    const hoverIndex = props.index;

    // Don't replace items with themselves
    if (dragIndex === hoverIndex) {
      console.log(1);
      return;
    }

    // Determine rectangle on screen
    const hoverBoundingRect = ReactDOM.findDOMNode(component).getBoundingClientRect();

    // Get vertical middle
    const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;

    // Determine mouse position
    const clientOffset = monitor.getClientOffset();

    // Get pixels to the top
    const hoverClientY = clientOffset.y - hoverBoundingRect.top;

    // Only perform the move when the mouse has crossed half of the items height
    // When dragging downwards, only move when the cursor is below 50%
    // When dragging upwards, only move when the cursor is above 50%

    // Dragging downwards
    if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
      console.log(2);
      return;
    }

    // Dragging upwards
    if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
      console.log(3);
      return;
    }

    // Time to actually perform the action
    props.moveItem(dragIndex, hoverIndex);
    // Note: we're mutating the monitor item here!
    // Generally it's better to avoid mutations,
    // but it's good here for the sake of performance
    // to avoid expensive index searches.
    monitor.getItem().index = hoverIndex;
  }
};

export default ItemTarget;

import ReactDOM from 'react-dom';

const ItemTarget = {
  hover(props, monitor, component) {
    const dragIndex = monitor.getItem().index;
    const hoverIndex = props.index;

    // Don't replace items with themselves
    if (dragIndex === hoverIndex) {
      return;
    }

    // Determine rectangle on screen
    const hoverBoundingRect = ReactDOM.findDOMNode(component).getBoundingClientRect();

    // Get vertical middle
    const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;

    // Determine mouse position
    const clientOffset = monitor.getClientOffset();

    // Get pixels to the top
    const hoverClientY = clientOffset.y - hoverBoundingRect.top;

    // Only perform the move when the mouse has crossed half of the items height
    // When dragging downwards, only move when the cursor is below 50%
    // When dragging upwards, only move when the cursor is above 50%

    // Dragging downwards
    if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
      return;
    }

    // Dragging upwards
    if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
      return;
    }

    // Time to actually perform the action
    props.moveItem(dragIndex, hoverIndex);
    // Note: we're mutating the monitor item here!
    // Generally it's better to avoid mutations,
    // but it's good here for the sake of performance
    // to avoid expensive index searches.
    monitor.getItem().index = hoverIndex;
  }
};

export default ItemTarget;

Remove unused var and console statements


/**
* @license Apache-2.0
*
* Copyright (c) 2018 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

// MAIN //

/**
* Returns an array containing the ratio of each pair of consecutive elements in order: `X[ i+1 ] / X[ i ]`.
*
* @private
* @param {NumberArray} X - input array
* @returns {NumberArray} ratio array
*
* @example
* var R = ratioArray( [ 1, 2, 5 ] );
* // returns [ 0.5, 0.4 ]
*/
function ratioArray( X ) {
	var R = new Array( X.length-1 );
	var i;
	for ( i = 0; i < R.length; i++ ) {
		R[ i ] = X[ i+1 ] / X[ i ];
	}
	return R;
}

// EXPORTS //

module.exports = ratioArray;

/**
* @license Apache-2.0
*
* Copyright (c) 2018 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

// MAIN //

/**
* Returns an array containing the ratio of each pair of consecutive elements in order: `X[ i+1 ] / X[ i ]`.
*
* @private
* @param {NumberArray} X - input array
* @returns {NumberArray} ratio array
*
* @example
* var R = ratioArray( [ 1.0, 2.0, 5.0 ] );
* // returns [ 2.0, 2.5 ]
*/
function ratioArray( X ) {
	var R;
	var i;

	R = [];
	for ( i = 0; i < X.length-1; i++ ) {
		R.push( X[ i+1 ] / X[ i ] );
	}
	return R;
}

// EXPORTS //

module.exports = ratioArray;

Fix example and dynamically resize array


$(".up-button").mousedown(function(){
  $.get("/movecam/moveUp");
}).mouseup(function(){
  $.get("/movecam/moveStop");
});

$(".left-button").mousedown(function(){
  $.get("/movecam/moveLeft");
}).mouseup(function(){
  $.get("/movecam/moveStop");
});

$(".right-button").mousedown(function(){
  $.get("/movecam/moveRight");
}).mouseup(function(){
  $.get("/movecam/moveStop");
});

$(".down-button").mousedown(function(){
  $.get("/movecam/moveDown");
}).mouseup(function(){
  $.get("/movecam/moveStop");
});

$(".zoom-tele").mousedown(function(){
  $.get("/movecam/zoomTeleStd");
}).mouseup(function(){
  $.get("/movecam/zoomStop");
});

$(".zoom-wide").mousedown(function(){
  $.get("/movecam/zoomWideStd");
}).mouseup(function(){
  $.get("/movecam/zoomStop");
});

$(".up-button").bind("mousedown touchstart", function(){
  $.get("/movecam/moveUp");
}).bind("mouseup touchend", function(){
  $.get("/movecam/moveStop");
});

$(".left-button").bind("mousedown touchstart", function(){
  $.get("/movecam/moveLeft");
}).bind("mouseup touchend", function(){
  $.get("/movecam/moveStop");
});

$(".right-button").bind("mousedown touchstart", function(){
  $.get("/movecam/moveRight");
}).bind("mouseup touchend", function(){
  $.get("/movecam/moveStop");
});

$(".down-button").bind("mousedown touchstart", function(){
  $.get("/movecam/moveDown");
}).bind("mouseup touchend", function(){
  $.get("/movecam/moveStop");
});

$(".zoom-tele").bind("mousedown touchstart", function(){
  $.get("/movecam/zoomTeleStd");
}).bind("mouseup touchend", function(){
  $.get("/movecam/zoomStop");
});

$(".zoom-wide").bind("mousedown touchstart", function(){
  $.get("/movecam/zoomWideStd");
}).bind("mouseup touchend", function(){
  $.get("/movecam/zoomStop");
});

Enable touch use with jQuery element.

Enable touch use with jQuery element.


var assert = require('nanoassert')
var randombytes = (function () {
  var QUOTA = 65536 // limit for QuotaExceededException
  var crypto = typeof window !== 'undefined' ? (window.crypto || window.msCrypto) : null

  function windowBytes (out, n) {
    for (var i = 0; i < n; i += QUOTA) {
      crypto.getRandomValues(out.subarray(i, i + Math.min(n - i, QUOTA)))
    }
  }

  function nodeBytes (out, n) {
    out.set(crypto.randomBytes(n))
  }

  function noImpl () {
    throw new Error('No secure random number generator available')
  }

  if (crypto && crypto.getRandomValues) {
    return windowBytes
  } else if (typeof require !== 'undefined') {
    // Node.js.
    crypto = require('cry' + 'pto');
    if (crypto && crypto.randomBytes) {
      return nodeBytes
    }
  }

  return noImpl
})()

Object.defineProperty(module.exports, 'randombytes', {
  value: randombytes
})

module.exports.randombytes_buf = function (out) {
  assert(out, 'out must be given')
  randombytes(out, out.length)
}

var assert = require('nanoassert')
var randombytes = (function () {
  var QUOTA = 65536 // limit for QuotaExceededException
  var crypto = typeof global !== 'undefined' ? crypto = (global.crypto || global.msCrypto) : null

  function browserBytes (out, n) {
    for (var i = 0; i < n; i += QUOTA) {
      crypto.getRandomValues(out.subarray(i, i + Math.min(n - i, QUOTA)))
    }
  }

  function nodeBytes (out, n) {
    out.set(crypto.randomBytes(n))
  }

  function noImpl () {
    throw new Error('No secure random number generator available')
  }

  if (crypto && crypto.getRandomValues) {
    return browserBytes
  } else if (typeof require !== 'undefined') {
    // Node.js.
    crypto = require('cry' + 'pto');
    if (crypto && crypto.randomBytes) {
      return nodeBytes
    }
  }

  return noImpl
})()

Object.defineProperty(module.exports, 'randombytes', {
  value: randombytes
})

module.exports.randombytes_buf = function (out) {
  assert(out, 'out must be given')
  randombytes(out, out.length)
}

Fix bug with undefined window in web workers

Fixes #8


'use strict';

angular.module('konehuone.competitionInfos')

.controller('CompetitionInfosCtrl', function ($scope, lodash, apiService) {
    var _ = lodash;

    // # Variables
    $scope.uiClosed = {
        jj1: true,
        jj2: true,
        rc: true
    };
    $scope.compData = {};
    $scope.igImages = [];

    // # Functions
    $scope.init = function() {
        apiService.getCompetitionData().then(
            function success(compData) {
                _.extend($scope.compData, compData);
            }
        );

        apiService.getLatestIgImages().then(
            function success(igImages) {
                $scope.igImages = igImages;
            }
        );
    };
});

'use strict';

angular.module('konehuone.competitionInfos')

.controller('CompetitionInfosCtrl', function ($scope, lodash, apiService) {
    var _ = lodash;

    // # Variables
    $scope.uiClosed = {
        jj1: false,
        jj2: false,
        rc: false
    };
    $scope.compData = {};
    $scope.igImages = [];

    // # Functions
    $scope.init = function() {
        apiService.getCompetitionData().then(
            function success(compData) {
                _.extend($scope.compData, compData);
            }
        );

        apiService.getLatestIgImages().then(
            function success(igImages) {
                $scope.igImages = igImages;
            }
        );
    };
});

Set comp info boxes opened as default

Set comp info boxes opened as default


/*
* Title module displaying title tag based on current view
*/
var EngineJS_titleManager = {
    content: {},
    titleDefault: "",
    titleSuffix: "",
    init: function(content, titleDefault, titleSuffix) {
       this.content = content;
       this.titleDefault = titleDefault;
       this.titleSuffix = titleSuffix;
    },
    change: function(view) {
        var contentPageKey = view.replace(/\//g, '-');
        var title = this.content[contentPageKey];

        if(title === undefined) {
            title = this.titleDefault;
        } else {
            title + ' - ' + this.titleSuffix;
        }

        document.title = title;
    }
};

/*
* Title module displaying title tag based on current view
*/
var EngineJS_titleManager = {
    content: {},
    titleDefault: "",
    titleSuffix: "",
    init: function(content, titleDefault, titleSuffix) {
       this.content = content;
       this.titleDefault = titleDefault;
       this.titleSuffix = titleSuffix;
    },
    change: function(view) {
        var contentPageKey = view.replace(/\//g, '-');
        var title = this.content[contentPageKey];

        if(title === undefined) {
            title = this.titleDefault;
        } else {
            title += ' - ' + this.titleSuffix;
        }

        document.title = title;
    }
};

Fix typo from previous commit

Fix typo from previous commit


Package.describe({
  summary: "Ravelry OAuth flow",
  name: "amschrader:ravelry",
  git: "git@github.com:amschrader/meteor-ravelry.git",
  version: "0.1.0"
});

Package.onTest(function(api) {
  api.use('tinytest');
  api.use('amschrader:ravelry');
  api.addFiles('ravelry-tests.js');
});

Package.on_use(function(api) {
  api.use('http@1.0.8', ['client', 'server']);
  api.use('templating@1.0.9', 'client');
  api.use('oauth1@1.1.2', ['client', 'server']);
  api.use('oauth@1.1.2', ['client', 'server']);
  api.use('random@1.0.1', 'client');
  api.use('underscore@1.0.1', 'server');
  api.use('service-configuration@1.0.2', ['client', 'server']);

  api.export && api.export('Ravelry');

  api.add_files([
    'ravelry-configure.html',
    'ravelry-configure.js'
  ],'client');

  api.add_files('ravelry-server.js', 'server');
  api.add_files('ravelry-client.js', 'client');
});

Package.describe({
  summary: "Ravelry OAuth flow",
  name: "amschrader:ravelry",
  git: "https://github.com/amschrader/meteor-ravelry.git",
  version: "0.1.2"
});

Package.onTest(function(api) {
  api.use('tinytest');
  api.use('amschrader:ravelry');
  api.addFiles('ravelry-tests.js');
});

Package.onUse(function(api) {
  api.use('http@1.0.8', ['client', 'server']);
  api.use('templating@1.0.9', 'client');
  api.use('oauth1@1.1.2', ['client', 'server']);
  api.use('oauth@1.1.2', ['client', 'server']);
  api.use('random@1.0.1', 'client');
  api.use('underscore@1.0.1', 'server');
  api.use('service-configuration@1.0.2', ['client', 'server']);

  api.export && api.export('Ravelry');

  api.add_files([
    'ravelry-configure.html',
    'ravelry-configure.js'
  ],'client');

  api.addFiles('ravelry-server.js', 'server');
  api.addFiles('ravelry-client.js', 'client');
});

Update git url, formatting nits

Update git url, formatting nits


module.exports = {
  validateAnchorText: validateAnchorText,
  click: click,
};

/**
 *
 * @param  {WebdriverIO} browser Instance of web driver
 * @param  {String} anchorText   The anchor text to target by
 */
function click (browser, anchorText) {
  return browser.click('a=' + anchorText);
}

/**
 * Sets the client's location to the provided URL
 * @param  {String} anchorText The visible text to select
 */
function validateAnchorText (anchorText) {
  if (!anchorText) {
    throw new Error('Missing anchor text');
  }
}

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

module.exports = {
  validateAnchorText: validateAnchorText,
  click: click,
};

/**
 * Clicks an anchor element with the given anchor text
 * @param  {WebdriverIO} browser Instance of web driver
 * @param  {String} anchorText   The anchor text to target by
 */
function click (browser, anchorText) {
  return interaction.clickElement(browser, 'a=' + anchorText);
}

/**
 * Sets the client's location to the provided URL
 * @param  {String} anchorText The visible text to select
 */
function validateAnchorText (anchorText) {
  if (!anchorText) {
    throw new Error('Missing anchor text');
  }
}

Refactor click link to use click element method


/**
 * Created by curtis on 3/28/16.
 */
angular.module('clusterApp', [])
    .controller('MainCtrl', [
        '$scope',
        '$http',
        function($scope, $http) {
            var completed = 0,
                timesToQuery = 100,
                pids = [];
            $scope.cluster = [];
            $scope.busy = false;

            $scope.getMyPIDs = function() {
                $scope.busy = true;
                for (var i = 0; i < timesToQuery; i++) {
                    $http.get('/pid').success(function(data) {
                        //console.log("getAll", data);
                        pids.push(data);
                        completed++;
                        if (completed % timesToQuery == 0) {
                            $scope.cluster = angular.copy(pids);
                            $scope.busy = false;
                        }
                    });
                }
            };
            $scope.clearPIDs = function() {
                completed = 0;
                pids = [];
                $scope.cluster = [];
            };
        }
    ]);

/**
 * Created by curtis on 3/28/16.
 */
angular.module('clusterApp', [])
    .controller('MainCtrl', [
        '$scope',
        '$http',
        function($scope, $http) {
            var completed = 0,
                timesToQuery = 100;
            $scope.cluster = [];
            $scope.busy = false;

            $scope.getMyPIDs = function() {
                $scope.busy = true;
                for (var i = 0; i < timesToQuery; i++) {
                    $http.get('/pid').success(function(data) {
                        //console.log("getAll", data);
                        completed++;
                        $scope.cluster.push(data);
                        if (completed % timesToQuery == 0) {
                            $scope.busy = false;
                        }
                    });
                }
            };
            $scope.clearPIDs = function() {
                completed = 0;
                $scope.cluster = [];
            };
        }
    ]);

Clean up the lab10 code a bit.

Clean up the lab10 code a bit.


class GetCharityData {
	getData(){
		var CHARITY_DATA = [
			{amount: '41,234', month: 'January', year: '2015', name: 'Charity:Water', description: 'Cool description that was so hard to come up with. Hence I ended up writing some random text that dosent make much text' ,imageSource: 'https://s3.amazonaws.com/moveit-multunus/charity_resources/March2015/GiveIndia_education_march_2015.png'},
			{amount: '41,234', month: 'February',  year: '2015', name: 'Charity:Water', description: 'Cooler description hat was so hard to come up with. Hence I ended up writing some random text that dosent make much text',imageSource: 'https://s3.amazonaws.com/moveit-multunus/charity_resources/April2015/charity_water+Apr+2015.png'},
			{amount: '21,234', month: 'March',  year: '2015', name: 'Charity:Water', description: 'Coolest description hat was so hard to come up with. Hence I ended up writing some random text that dosent make much text',imageSource: 'https://s3.amazonaws.com/moveit-multunus/charity_resources/April2015/charity_water+Apr+2015.png'}
		];
		return CHARITY_DATA ;
	}
}

export default new GetCharityData();

class GetCharityData {
  getData(){
    var CHARITY_DATA_ARRAY = [];
    var spreadsheetID = "1f1tFr7y62QS7IRWOC2nNR5HWsTO99Bna3d0Xv1PTPiY";
    var url = "https://spreadsheets.google.com/feeds/list/" + spreadsheetID + "/od6/public/values?alt=json";
    fetch(url).then((response) => response.json())
      .then((responseText) => {
        entry = responseText.feed.entry;
        entry.forEach(function(element){
          CHARITY_DATA_ARRAY.push({amount: element.gsx$charityamount.$t, month: element.gsx$month.$t, year: element.gsx$year.$t, name: element.gsx$charityname.$t, description: element.gsx$charitydesc.$t, imageSource: element.gsx$charityimage.$t });
        });
      })
      .catch((error) => {
        console.warn(error);
      });
    return CHARITY_DATA_ARRAY;
  }
}
export default new GetCharityData();

Implement service to fetch charity data from spreadsheet

https://trello.com/c/wjthoHP0/28-user-sbat-view-the-charity-contributions-done-per-month-by-the-company


const Request = require('sdk/request').Request;
const qs = require('sdk/querystring');

const baseURL = 'http://www.youtube.com/get_video_info';

module.exports = getYouTubeUrl;

function getYouTubeUrl(videoId, cb) {
  Request({
    url: baseURL + '?' + qs.stringify({video_id: videoId}),
    onComplete: function (resp) {
      const streamParams = qs.parse(resp.text)['url_encoded_fmt_stream_map'].split(',')[0];
      cb(null, qs.parse(streamParams).url);
    }
  }).get();
}

const Request = require('sdk/request').Request;
const qs = require('sdk/querystring');

const baseURL = 'https://www.youtube.com/get_video_info';

module.exports = getYouTubeUrl;

function getYouTubeUrl(videoId, cb) {
  Request({
    url: baseURL + '?' + qs.stringify({video_id: videoId}),
    onComplete: function (resp) {
      const streamParams = qs.parse(resp.text)['url_encoded_fmt_stream_map'].split(',')[0];
      cb(null, qs.parse(streamParams).url);
    }
  }).get();
}

Convert YouTube link to HTTPS

Convert YouTube link to HTTPS


const session = require('express-session');
const RedisStore = require('connect-redis')(session);
const redisClient = require('redis').createClient();

module.exports.verify = (req, res, next) => {
  if (req.isAuthenticated()) {
    return next();
  }
  res.redirect('/login');
};

module.exports.profileRedirect = (req, res, next) => {
  if ( req.isAuthenticated()) {
    res.redirect('/profile');
  }
  return next();
};

module.exports.session = session({
  store: new RedisStore({
    client: redisClient,
    host: 'localhost',
    port: 6379
  }),
  secret: 'more laughter, more love, more life',
  resave: false,
  saveUninitialized: false
});

const session = require('express-session');
const RedisStore = require('connect-redis')(session);
const redisClient = require('redis').createClient(process.env.REDIS_URL) || require('redis').createClient;

module.exports.verify = (req, res, next) => {
  if (req.isAuthenticated()) {
    return next();
  }
  res.redirect('/login');
};

module.exports.profileRedirect = (req, res, next) => {
  if ( req.isAuthenticated()) {
    res.redirect('/profile');
  }
  return next();
};

module.exports.session = session({
  store: new RedisStore({
    client: redisClient,
    host: 'localhost',
    port: 6379
  }),
  secret: 'more laughter, more love, more life',
  resave: false,
  saveUninitialized: false
});

Add REDIS_URL to createClient as an argument


import Dispatcher from "dispatchers/Dispatcher";
import BaseStore from "stores/BaseStore";
import HelpLinkCollection from "collections/HelpLinkCollection";

var HelpLinkStore = BaseStore.extend({
    collection: HelpLinkCollection,

    queryParams: {
        page_size: 100
    },

});

var store = new HelpLinkStore();

Dispatcher.register(function(dispatch) {
    var options = dispatch.action.options || options;

    if (!options.silent) {
        store.emitChange();
    }

    return true;
});

export default store;

import _ from "underscore";
import Dispatcher from "dispatchers/Dispatcher";
import BaseStore from "stores/BaseStore";
import HelpLinkCollection from "collections/HelpLinkCollection";

var HelpLinkStore = BaseStore.extend({
    collection: HelpLinkCollection,

    queryParams: {
        page_size: 100
    },

});

var store = new HelpLinkStore();

Dispatcher.register(function(dispatch) {
    var options = dispatch.action.options || options;

    if (_.has(options, "silent") && !options.silent) {
        store.emitChange();
    }

    return true;
});

export default store;

Fix error when updateImageAttributes dispatched

An uncaught error has been occurring when an image creator saves any
changes to image attributes:

```
Uncaught TypeError: Cannot read property 'silent' of undefined
```

It turns out that the HelpLinkStore is performing check for an option,
`option.silent`, to determine if an `emitChange()` call should be made.

Unfortunately, this "check" is done in a manner that generates the
above uncaught type error.

The fix was to use the `_.has(obj, "propName")` call. This could be
replaced with an expression like `obj && obj.hasOwnProperty("prop")`,
but here we opt for `underscore` since it is already available - we
could also move to `lodash`.