import { merge } from 'lodash/fp';
import { firestoreActions } from './actions';
import { mapWithFirebaseAndDispatch } from './utils/actions';
import { defaultConfig } from './constants';

/**
 * Create a firebase instance that has helpers attached for dispatching actions
 * @param  {Object} firebase - Firebase instance which to extend
 * @param  {Object} configs - Configuration object
 * @param  {Function} dispatch - Action dispatch function
 * @return {Object} Extended Firebase instance
 */
export default function createFirestoreInstance(firebase, configs, dispatch) {
  // Setup internal variables
  const defaultInternals = {
    // Setup empty listeners object (later used to track listeners)
    listeners: {},
    // Extend default config with provided config
    config: { defaultConfig, ...configs },
  };

  // extend existing firebase internals (using redux-firestore along with redux-firebase)
  firebase._ = merge(defaultInternals, firebase._); // eslint-disable-line no-param-reassign

  // Aliases for methods
  const aliases = [
    { action: firestoreActions.deleteRef, name: 'delete' },
    { action: firestoreActions.setListener, name: 'onSnapshot' },
  ];

  // Create methods with internal firebase object and dispatch passed
  const methods = mapWithFirebaseAndDispatch(
    firebase,
    dispatch,
    firestoreActions,
    aliases,
  );

  return Object.assign(
    firebase.firestore(),
    firebase.firestore,
    { _: firebase._ },
    configs.helpersNamespace
      ? // Attach helpers to specified namespace
        { [configs.helpersNamespace]: methods }
      : methods,
  );
}

import { merge } from 'lodash/fp';
import { firestoreActions } from './actions';
import { mapWithFirebaseAndDispatch } from './utils/actions';
import { defaultConfig } from './constants';

/**
 * Create a firebase instance that has helpers attached for dispatching actions
 * @param  {Object} firebase - Firebase instance which to extend
 * @param  {Object} configs - Configuration object
 * @param  {Function} dispatch - Action dispatch function
 * @return {Object} Extended Firebase instance
 */
export default function createFirestoreInstance(firebase, configs, dispatch) {
  // Setup internal variables
  const defaultInternals = {
    // Setup empty listeners object (later used to track listeners)
    listeners: {},
    // Extend default config with provided config
    config: { defaultConfig, ...configs },
  };

  // extend existing firebase internals (using redux-firestore along with redux-firebase)
  firebase._ = merge(defaultInternals, firebase._); // eslint-disable-line no-param-reassign

  // Aliases for methods
  const aliases = [
    { action: firestoreActions.deleteRef, name: 'delete' },
    { action: firestoreActions.setListener, name: 'onSnapshot' },
  ];

  // Create methods with internal firebase object and dispatch passed
  const methods = mapWithFirebaseAndDispatch(
    firebase,
    dispatch,
    firestoreActions,
    aliases,
  );

  return Object.assign(
    firebase && firebase.firestore ? firebase.firestore() : {},
    firebase.firestore,
    { _: firebase._ },
    configs.helpersNamespace
      ? // Attach helpers to specified namespace
        { [configs.helpersNamespace]: methods }
      : methods,
  );
}

Add existence check for firestore before invoking.


var gulp = require('gulp');
var browserify = require('./browserify');
var vendor = require('./vendor');
var less = require('./less');
var images = require('./images');
var copy = require('./copy');
var migrate = require('./migrate')
gulp.task('prodBuild', ['browserify', 'vendor', 'less', 'images', 'copy', 'migrate']);

Add a space to prodbuild task.


import React, { Component } from 'react';
import { action } from '@kadira/storybook';
import { DraftJSEditor, Renderer, defaultBlocks } from '../../src';

const draftRenderer = new Renderer(defaultBlocks);

class SetStateExample extends Component {
  state = {
    content: {
      entityMap: {},
      blocks: [
        {
          text: '',
          type: 'unstyled',
          depth: 0,
          inlineStyleRanges: [],
          entityRanges: [],
        },
      ],
    },
  };

  onChange = content => {
    action('onChange')(content); // Log to storybook's "action-logger"
    this.setState({ content });
  };

  render() {
    return (

@synapsestudios/draftjs-editor

          A customized WYSIWYG editor utilizing Facebook's Draft.js library

          View this project on
          {' '}
          Github

        <DraftJSEditor
          content={this.state.content || null}
          onChange={this.onChange}
          placeholder="Tell a story..."
          customBlocks={defaultBlocks}
          customBlockControls={Object.keys(defaultBlocks)}
        />
        {this.state.content
          ?

          : null}

    );
  }
}

export default SetStateExample;

import React, { Component } from 'react';
import { action } from '@kadira/storybook';
import { DraftJSEditor, Renderer, defaultBlocks } from '../../src';

const draftRenderer = new Renderer(defaultBlocks);

class SetStateExample extends Component {
  state = {
    content: {
      entityMap: {},
      blocks: [
        {
          text: '',
          type: 'unstyled',
          depth: 0,
          inlineStyleRanges: [],
          entityRanges: [],
        },
      ],
    },
  };

  onChange = content => {
    action('onChange')(content); // Log to storybook's "action-logger"
    this.setState({ content });
  };

  render() {
    return (

@synapsestudios/draftjs-editor

          {`A customized WYSIWYG editor utilizing Facebook's Draft.js library`}

          View this project on
          {' '}
          Github

        <DraftJSEditor
          content={this.state.content || null}
          onChange={this.onChange}
          placeholder="Tell a story..."
          customBlocks={defaultBlocks}
          customBlockControls={Object.keys(defaultBlocks)}
        />
        {this.state.content
          ?

          : null}

    );
  }
}

export default SetStateExample;

Fix red highlighting in GitHub diff


jsPsych.plugins['conditional-run'] = (function () {
	var plugin = {};

	plugin.info = {
		name: 'conditional-run',
		description: 'Only run the specified plugin if a condition is met',
		parameters: {
			'dependentPluginParameters': {
				type: jsPsych.plugins.parameterType.COMPLEX,
				description: 'The parameters to pass to the plugin that will be run, including the type of plugin',
				nested: {
					'type': {
						type: jsPsych.plugins.parameterType.STRING,
						description: 'The type of the plugin to use if the condition is met'
					}
					// + Other parameters relevant to the dependent plugin
				}
			},
			'conditionalFunction': {
				type: jsPsych.plugins.parameterType.FUNCTION,
				description: 'Function that will be executed when this trial is started. If it returns true, the trial is run. Otherwise, it is skipped.'
			}
		}
	}
	plugin.trial = function (display_element, trial) {
		if (typeof trial.conditionalFunction === 'function' && trial.conditionalFunction()) {
			jsPsych.plugins[trial.dependentPluginParameters.type].trial(display_element, trial.dependentPluginParameters);
		} else {
			// skip
			jsPsych.finishTrial();
		}
	}
	return plugin;
})();

jsPsych.plugins['conditional-run'] = (function () {
	var plugin = {};

	plugin.info = {
		name: 'conditional-run',
		description: 'Only run the specified plugin if a condition is met',
		parameters: {
			'dependentPluginParameters': {
				type: jsPsych.plugins.parameterType.COMPLEX,
				description: 'The parameters to pass to the plugin that will be run, including the type of plugin',
				nested: {
					'type': {
						type: jsPsych.plugins.parameterType.STRING,
						description: 'The type of the plugin to use if the condition is met'
					}
					// + Other parameters relevant to the dependent plugin
				}
			},
			'conditionalFunction': {
				type: jsPsych.plugins.parameterType.FUNCTION,
				description: 'Function that will be executed when this trial is started. If it returns true, the trial is run. Otherwise, it is skipped.'
			}
		}
	}
	plugin.trial = function (display_element, trial) {
		if (typeof trial.conditionalFunction === 'function' && trial.conditionalFunction()) {
			// protect functions in the parameters (only does a shallow copy)
			var dependentPluginParams = Object.assign({}, trial.dependentPluginParameters);
			jsPsych.plugins[dependentPluginParams.type].trial(display_element, dependentPluginParams);
		} else {
			// skip
			jsPsych.finishTrial();
		}
	}
	return plugin;
})();

Enable usage with multiple jspsych timeline_variables


/*jslint node: true, continue: true, eqeq: true, forin: true, nomen: true, plusplus: true, todo: true, vars: true, white: true */
'use strict';

YUI.add("macaroon", function(Y) {
  var ns = Y.namespace('macaroon');

/*jslint node: true, continue: true, eqeq: true, forin: true, nomen: true, plusplus: true, todo: true, vars: true, white: true */
if (typeof Float64Array === "undefined") {
  Float64Array = Array
}

'use strict';

YUI.add("macaroon", function(Y) {
  var ns = Y.namespace('macaroon');

Fix for phantom support of Float64Array.


/**
 * Created by quasarchimaere on 21.01.2019.
 */
import { get } from "../utils.js";
import {
  isProcessingInitialLoad,
  isProcessingLogin,
  isProcessingLogout,
  isProcessingPublish,
  isProcessingAcceptTermsOfService,
  isProcessingVerifyEmailAddress,
  isProcessingResendVerificationEmail,
  isProcessingSendAnonymousLinkEmail,
  isAnyNeedLoading,
  isAnyConnectionLoading,
  isAnyMessageLoading,
} from "../process-utils.js";

/**
 * Check if anything in the state sub-map of process is currently marked as loading
 * @param state (full redux-state)
 * @returns true if anything is currently loading
 */
export function isLoading(state) {
  const process = get(state, "process");

  return (
    isProcessingInitialLoad(process) ||
    isProcessingLogin(process) ||
    isProcessingLogout(process) ||
    isProcessingPublish(process) ||
    isProcessingAcceptTermsOfService(process) ||
    isProcessingVerifyEmailAddress(process) ||
    isProcessingResendVerificationEmail(process) ||
    isProcessingSendAnonymousLinkEmail(process) ||
    isAnyNeedLoading(process) ||
    isAnyConnectionLoading(process) ||
    isAnyMessageLoading(process)
  );
}

/**
 * Created by quasarchimaere on 21.01.2019.
 */
import { get } from "../utils.js";
import {
  isProcessingInitialLoad,
  isProcessingLogin,
  isProcessingLogout,
  isProcessingPublish,
  isProcessingAcceptTermsOfService,
  isProcessingVerifyEmailAddress,
  isProcessingResendVerificationEmail,
  isProcessingSendAnonymousLinkEmail,
  isAnyNeedLoading,
  isAnyConnectionLoading,
  isAnyMessageLoading,
} from "../process-utils.js";

/**
 * Check if anything in the state sub-map of process is currently marked as loading
 * @param state (full redux-state)
 * @returns true if anything is currently loading
 */
export function isLoading(state) {
  const process = get(state, "process");

  return (
    isProcessingInitialLoad(process) ||
    isProcessingLogin(process) ||
    isProcessingLogout(process) ||
    isProcessingPublish(process) ||
    isProcessingAcceptTermsOfService(process) ||
    isProcessingVerifyEmailAddress(process) ||
    isProcessingResendVerificationEmail(process) ||
    isProcessingSendAnonymousLinkEmail(process) ||
    isAnyNeedLoading(process) ||
    isAnyConnectionLoading(process, true) ||
    isAnyMessageLoading(process)
  );
}

Include includeSubData parameter true, so we show the loading indicator if petriNetData or agreementData is loading as well


import React from 'react';
import { storiesOf } from '@storybook/react';

import YouTube from 'shared/components/youtubeVideo/youtubeVideo';

storiesOf('shared/components/youtubeVideo', module)
  .add('Default', () => (
    <YouTube
      id=""
      height="390"
      width="640"
    />
  ));

import React from 'react';
import { storiesOf } from '@storybook/react';

import YouTube from 'shared/components/youtubeVideo/youtubeVideo';

storiesOf('shared/components/youtubeVideo', module)
  .add('Default', () => (
    <YouTube
      id="edMC6Wc3nsg"
      height="390"
      width="640"
    />
  ));

Add ID for video in story


load('classpath:META-INF/resources/webjars/viz.js/1.5.1/viz.js');

var svg = Viz(dotSrc, {
    format: "svg",
    engine: "dot"
});

var writer = new java.io.FileWriter(outFile);
writer.write(svg);
writer.close();

load('classpath:META-INF/resources/webjars/viz.js/1.5.1/viz.js');

var svg = Viz(dotSrc, {
    format: "svg",
    engine: "dot"
});

var writer = new java.io.FileWriter(outFile);
writer.write(svg.toString());
writer.close();

Use toString for SVG text


var path = require('path');

module.exports = {

  one: function (req, res) {
/* vcl:
vcl 4.0; backend default { .host = "www.vclfiddle.net"; .port = "80"; }

sub vcl_backend_response {
  if (beresp.http.Content-Type ~ "^image/") {
    set beresp.http.Cache-Control = "public, max-age=300";
    set beresp.ttl = 300s;
  }
}
*/
/* curl:
curl "http://www.vclfiddle.net/example/one"
*/
    req.session = null; // don't send Set-Cookie: sails.sid=
    return res
      .header('Link', '<https://www.flickr.com/photos/crazymandi/8165527856/>; rel=alternate')
      .header('Cache-Control', 'public, max-age=0')
      .sendfile(path.join(__dirname, 'flickr-crazymandi-puppy.jpg'));
  }

}

var path = require('path');

module.exports = {

  one: function (req, res) {
/* vcl:
vcl 4.0; backend default { .host = "www.vclfiddle.net"; .port = "80"; }

sub vcl_backend_response {
  if (beresp.http.Content-Type ~ "^image/") {
    set beresp.http.Cache-Control = "public, max-age=300";
    set beresp.ttl = 300s;
  }
}
*/
/* curl:
curl "http://www.vclfiddle.net/example/one"
*/
    req.session = null; // don't send Set-Cookie: sails.sid=
    return res
      .header('Link', '<https://www.flickr.com/photos/crazymandi/8165527856/>; rel=alternate')
      .header('Cache-Control', 'public, max-age=0')
      .sendfile(path.join(__dirname, 'flickr-crazymandi-puppy.jpg'));
  },

  two: function (req, res) {
/* vcl:
vcl 4.0; backend default { .host = "www.vclfiddle.net"; .port = "80"; }

sub vcl_deliver {
  unset resp.http.Server;
  unset resp.http.X-Powered-By;
}
*/
/* curl:
curl "http://www.vclfiddle.net/example/two"
*/
    return res.send('Where are the Server and X-Powered-By response headers?');
  }

}

Add example two: remove information leaking response headers


module.exports = {
  'plugins': {
    'local': {
      'browsers': [ 'chrome' ]
    }
  }
}

module.exports = {
  'plugins': {
    'local': {
      'browsers': [ 'chrome', 'firefox' ]
    }
  }
}

Add firefox to wct tests


import rp from 'request-promise'
import { Observable } from 'rx'

const sendActivityToCoreService = (activities$, requestLib=rp) => {
  return activities$
  .flatMap(activity => {
    const performRequest = requestLib({
      method: 'POST',
      uri: process.env.CORE_ACTIVITY_API_ENDPOINT || 'https://velocitas-core.herokuapp.com/api/commutes/activities',
      body: activity.toJSON,
      json: true
    })
    return Observable.fromPromise(performRequest)
    .catch(e => {
      console.error(`Error performing HTTP request: ${e}`);
      return Observable.just('error');
    })
    .tap(v => console.log(`Performed HTTP request. Response: ${v}`))
    .map(_ => activity)
  });
};

export default sendActivityToCoreService;

import rp from 'request-promise'
import { Observable } from 'rx'

const sendActivityToCoreService = (activities$, requestLib=rp) => {
  return activities$
  .flatMap(activity => {
    const performRequest = requestLib({
      method: 'POST',
      uri: process.env.CORE_ACTIVITY_API_ENDPOINT || 'https://velocitas-core.herokuapp.com/api/v1/commuting/activities',
      body: activity.toJSON,
      json: true
    })
    return Observable.fromPromise(performRequest)
    .catch(e => {
      console.error(`Error performing HTTP request: ${e}`);
      return Observable.just('error');
    })
    .tap(v => console.log(`Performed HTTP request. Response: ${v}`))
    .map(_ => activity)
  });
};

export default sendActivityToCoreService;

Tweak commuting activity POST endpoint.


import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import root from './reducer';
import schedule from './schedule/reducer';
import speakers from './speakers/reducer';
import calendar from './calendar/reducer';

const initialState = { };

const middleware = [
    applyMiddleware(thunk)
];

if (window.devToolsExtension) middleware.push(window.devToolsExtension())

const store = createStore(
    combineReducers({ root, schedule, speakers, calendar }),
    initialState,
    compose(...middleware)
);

export default store;

import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import root from './reducer';
import schedule from './schedule/reducer';
import speakers from './speakers/reducer';

const initialState = { };

const middleware = [
    applyMiddleware(thunk)
];

if (window.devToolsExtension) middleware.push(window.devToolsExtension())

const store = createStore(
    combineReducers({ root, schedule, speakers }),
    initialState,
    compose(...middleware)
);

export default store;

Remove reference to calendar reducer.js.


'use strict';

const debug = require('debug')('sockjs:connection');
const stream = require('stream');
const uuid = require('uuid/v4');

class SockJSConnection extends stream.Duplex {
  constructor(session) {
    super({ decodeStrings: false, encoding: 'utf8' });
    this._session = session;
    this.id = uuid();
    this.headers = {};
    this.prefix = this._session.prefix;
    debug('new connection', this.id, this.prefix);
  }

  toString() {
    return `<SockJSConnection ${this.id}>`;
  }

  _write(chunk, encoding, callback) {
    if (Buffer.isBuffer(chunk)) {
      chunk = chunk.toString();
    }
    this._session.send(chunk);
    callback();
  }

  _read() {
  }

  end(chunk, encoding, callback) {
    super.end(chunk, encoding, callback);
    this.close();
  }

  close(code, reason) {
    debug('close', code, reason);
    return this._session.close(code, reason);
  }

  get readyState() {
    return this._session.readyState;
  }
}

module.exports = SockJSConnection;

'use strict';

const debug = require('debug')('sockjs:connection');
const stream = require('stream');
const uuid = require('uuid/v4');

class SockJSConnection extends stream.Duplex {
  constructor(session) {
    super({ decodeStrings: false, encoding: 'utf8', readableObjectMode: true });
    this._session = session;
    this.id = uuid();
    this.headers = {};
    this.prefix = this._session.prefix;
    debug('new connection', this.id, this.prefix);
  }

  toString() {
    return `<SockJSConnection ${this.id}>`;
  }

  _write(chunk, encoding, callback) {
    if (Buffer.isBuffer(chunk)) {
      chunk = chunk.toString();
    }
    this._session.send(chunk);
    callback();
  }

  _read() {
  }

  end(chunk, encoding, callback) {
    super.end(chunk, encoding, callback);
    this.close();
  }

  close(code, reason) {
    debug('close', code, reason);
    return this._session.close(code, reason);
  }

  get readyState() {
    return this._session.readyState;
  }
}

module.exports = SockJSConnection;

Fix sending of empty string


const _ = require('lodash');

const db = require('../../db.js');

module.exports = async () => {
	const stops = await db.query(`
		SELECT
			stop_id AS id,
			GROUP_CONCAT(type, ':', destination) AS destinations
		FROM stop_times
		JOIN trips AS trip ON trip.id = trip_id
		JOIN routes AS route ON route.id = route_id
		GROUP BY stop_id
	`);

	let query = 'START TRANSACTION;';

	// Get the most common destination for all trips serving a stop with coaches having the least priority.
	for (const stop of stops) {
		if (!stop.destinations) continue;

		let destinations = stop.destinations.split(',');
		if (destinations.reduce((prev, curr) => prev + Number(curr.startsWith('coach')), 0) !== destinations.length) destinations = destinations.filter((destination) => !destination.startsWith('coach'));
		query += `UPDATE stops SET description = '${_(destinations.map((destination) => destination.split(':')[1])).countBy().entries().maxBy('[1]')[0]}' WHERE id = '${stop.id}';`;
	}

	await db.query(query + 'COMMIT;');
};

const _ = require('lodash');

const db = require('../../db.js');

module.exports = async () => {
	const stops = await db.query(`
		SELECT
			stop.id,
		    stop.name,
			GROUP_CONCAT(route.type, ':', destination) AS destinations
		FROM stops AS stop
		JOIN stop_times AS time ON stop_id = stop.id
		JOIN trips AS trip ON trip.id = trip_id
		JOIN routes AS route ON route.id = route_id
		GROUP BY stop_id
	`);

	let query = 'START TRANSACTION;';

	// Get the most common destination for all trips serving a stop with coaches having the least priority.
	for (const stop of stops) {
		if (!stop.destinations) continue;

		let destinations = stop.destinations.split(',');
		if (!destinations.every((destination) => destination.startsWith('coach'))) destinations = destinations.filter((destination) => !destination.startsWith('coach'));
		const variants = _.chain(destinations).map((destination) => destination.split(':')[1]).countBy().map((value, key) => [ key, value ]).sortBy('[1]').reverse().map('[0]').value();
		query += `UPDATE stops SET description = '${variants[Number(variants.length > 1 && stop.name === variants[0])]}' WHERE id = '${stop.id}';`;
	}

	await db.query(query + 'COMMIT;');
};

Fix matching stop name and destination (api)


import React, { Component, PropTypes } from 'react'
import { Ripple } from './ripple'

class RippleButton extends Component {

  static propTypes = {
    children: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.element,
      PropTypes.arrayOf(PropTypes.element),
    ]).isRequired,
    className: PropTypes.string,
    onClick: PropTypes.func.isRequired,
    text: PropTypes.string,
  }

  constructor() {
    super()
    this.state = {
      cursorPos: {}
    }
  }

  handleClick(e) {
    // Get Cursor Position
    let cursorPos = {
      top: e.clientY,
      left: e.clientX,
      // Prevent Component duplicates do ripple effect at the same time
      time: Date.now()
    }
    this.setState({ cursorPos: cursorPos })
  }

  render() {
    const { className } = this.props || 'glare-button'

    return (
      <button
        className={`${className} Ripple-parent`}
        onMouseUp={(e) => {
          this.handleClick(e)
          this.props.onClick()
        }}
        onTouchend={(e) => {
          this.handleClick(e)
          this.props.onClick()
        }}>
        {this.props.children}
        <Ripple cursorPos={this.state.cursorPos} />
      </button>
    )
  }
}

export default RippleButton

import React, { Component, PropTypes } from 'react'
import { Ripple } from './ripple'

class RippleButton extends Component {

  static propTypes = {
    children: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.element,
      PropTypes.arrayOf(PropTypes.element),
    ]).isRequired,
    className: PropTypes.string,
    onClick: PropTypes.func,
    text: PropTypes.string,
  }

  static defaultProps = {
    onClick: () => {},
    className: '',
    text: '',
  }

  constructor() {
    super()
    this.state = {
      cursorPos: {}
    }
  }

  handleClick(e) {
    // Get Cursor Position
    let cursorPos = {
      top: e.clientY,
      left: e.clientX,
      // Prevent Component duplicates do ripple effect at the same time
      time: Date.now()
    }
    this.setState({ cursorPos: cursorPos })
  }

  render() {
    const { className } = this.props || 'glare-button'

    return (
      <button
        className={`${className} Ripple-parent`}
        onMouseUp={(e) => {
          this.handleClick(e)
          this.props.onClick()
        }}
        onTouchend={(e) => {
          this.handleClick(e)
          this.props.onClick()
        }}>
        {this.props.children}
        <Ripple cursorPos={this.state.cursorPos} />
      </button>
    )
  }
}

export default RippleButton

Remove required onClick from Ripple Button


const photos = Application('photos');
const albums = photos.albums();

albums.forEach(album => {
  const name = album.name();
  const items = album.mediaItems();
  const path = Path('./export/' + name);
  items.forEach(item => {
    const filename = item.filename();
    console.log(filename + ' => ' + path + '/'  + filename);
    photos.export([item], {to: path});
  });
});

const photos = Application('photos');
const terminal = Application('terminal');
const albums = photos.albums();

const pad = (val) => String(val).length < 2 ? '0' + val : String(val);
const app = Application.currentApplication()
app.includeStandardAdditions = true;

albums.forEach(album => {
  const name = album.name();
  const items = album.mediaItems();
  const path = Path('./export/' + name);

  // Export items one by one to prevent timeout
  // Set creation data becausse photos might not set them for videos
  items.forEach(item => {
    const filename = item.filename();
    const date = item.date();
    const formattedDate = pad(date.getMonth() + 1) + '/' +
      pad(date.getDate()) + '/' +
      date.getFullYear() + ' ' +
      pad(date.getHours()) + ':' +
      pad(date.getMinutes()) + ':' +
      pad(date.getSeconds());

    console.log(filename + ' => ' + path);
    photos.export([item], {to: path});

    const videoPath = (path + '/' + filename).replace(/(.*)\.[0-9A-Za-z]+$/, '$1.m4v');
    const cmd = 'ls "' + videoPath + '" && ' +
      'SetFile -d "' + formattedDate + '" "' + videoPath + '"';

    try {
      app.doShellScript(cmd);
      console.log('Set creation date for video file');
    } catch (e) {
    }
  });
});

Set creation date for videos


/** Executes the main.js file in the context of the user's current webpage. */
function injectAmaranth() {
  chrome.tabs.executeScript(null, {
    file: 'src/main.js',
  });
}

chrome.webNavigation.onCompleted.addListener(injectAmaranth, {
  // Code should only be injected if on grubhub.com/restaurant/...
  url: [{pathPrefix: '/restaurant'}],
});

chrome.webNavigation.onHistoryStateUpdated.addListener(function() {
  // When the user navigates to a different restaurant, a new page is shown
  // for a fraction of a second. The Chrome extension labels this intermediate
  // page but not the real restaurant page when it loads. This timeout
  // attempts to avoid labelling the intermediate page.
  setTimeout(injectAmaranth, 1000);
}, {
  url: [{pathPrefix: '/restaurant'}],
});

/** Executes the main.js file in the context of the user's current webpage. */
function injectAmaranth() {
  chrome.tabs.executeScript(null, {
    file: 'src/main.js',
  });
}

chrome.webNavigation.onCompleted.addListener(injectAmaranth, {
  // Code should only be injected if on grubhub.com/restaurant/...
  url: [{pathPrefix: '/restaurant'}],
});

// Due to Grubhub making use of HTML5's new pushState API, we can't rely on
// the user refreshing the webpage as a reliable trigger for the extension. The
// onHistoryStateUpdated hook listens for when a pushState call happens.
chrome.webNavigation.onHistoryStateUpdated.addListener(function() {
  // When the user navigates to a different restaurant, a new page is shown
  // for a fraction of a second. The Chrome extension labels this intermediate
  // page but not the real restaurant page when it loads. This timeout
  // attempts to avoid labelling the intermediate page.
  setTimeout(injectAmaranth, 1000);
}, {
  url: [{pathPrefix: '/restaurant'}],
});

Add Explanatory comment for onHistoryStateUpdated


import path from 'path';
import cachifyStatic, { cachify } from 'connect-cachify-static';

export const PUBLIC_DIR = path.resolve(__dirname, '..', 'public');

const middleware = cachifyStatic(PUBLIC_DIR);
export { middleware as assetMiddleware, cachify as getAsset };

import path from 'path';
import cachifyStatic, { cachify } from 'connect-cachify-static';

export const PUBLIC_DIR = path.resolve(__dirname, '..', 'public');

const middleware = cachifyStatic(PUBLIC_DIR, {
  match: /\.(js|css|svg|eot|woff|ttf|cur|png|gif|jpg|mp3)$/,
});
export { middleware as assetMiddleware, cachify as getAsset };

Add mp3 to cache list


DP.PaymentForm = function() {
  this.setup();
};

DP.PaymentForm.prototype = {
  setup: function() {
    $("#payment-form").submit(_.bind(function(event) {
      // disable the submit button to prevent repeated clicks
      var $button = $('.submit-button');
      $button.attr("disabled", "disabled").addClass('active');

      Stripe.createToken({
        number: $('.card-number').val(),
        cvc: $('.card-cvc').val(),
        exp_month: $('.card-expiry-month').val(),
        exp_year: $('.card-expiry-year').val()
      }, _.bind(this.stripeResponseHandler, this));

      return false; // submit from callback
    }, this));
  },

  stripeResponseHandler: function(status, response) {
    if (response.error) {
      $('.submit-button').removeAttr("disabled").removeClass('active');
      $(".payment-errors").html(response.error.message);
    } else {
      var form$ = $("#payment-form");
      var token = response['id'];
      form$.append("<input type='hidden' name='stripeToken' value='" + token + "' />");
      this.submitForm();
    }
  },

  submitForm: function() {
    var $form = $("#payment-form");
    var data = $form.serialize();

    $.post('/charge', data, function(data) {
      DP.realtime.reloadDonations(data);
    });
  }
};

DP.PaymentForm = function() {
  this.setup();
};

DP.PaymentForm.prototype = {
  setup: function() {
    $("#payment-form").submit(_.bind(function(event) {
      // disable the submit button to prevent repeated clicks
      var $button = $('.submit-button');
      $button.attr("disabled", "disabled").addClass('active');

      Stripe.createToken({
        number: $('.card-number').val(),
        cvc: $('.card-cvc').val(),
        exp_month: $('.card-expiry-month').val(),
        exp_year: $('.card-expiry-year').val()
      }, _.bind(this.stripeResponseHandler, this));

      return false; // submit from callback
    }, this));
  },

  stripeResponseHandler: function(status, response) {
    if (response.error) {
      $('.submit-button').removeAttr("disabled").removeClass('active');
      $(".payment-errors").html(response.error.message);
    } else {
      var form$ = $("#payment-form");
      var token = response['id'];
      form$.append("<input type='hidden' name='stripe_token' value='" + token + "' />");
      this.submitForm();
    }
  },

  submitForm: function() {
    var $form = $("#payment-form");
    var data = $form.serialize();

    $.post('/charge', data, function(data) {
      DP.realtime.reloadDonations(data);
    });
  }
};

Use new stripe token parameter name


import { fromJS } from 'immutable';
import {
  SEARCH_REQUEST,
  SEARCH_SUCCESS,
  SEARCH_ERROR,
  SELECT_BOOK,
  UNSELECT_BOOK,
} from './constants';

const initialState = fromJS({
  searching: false,
  error: false,
  books: false,
  activeBook: false,
});

function addBookPageReducer(state = initialState, action) {
  switch (action.type) {
    case SEARCH_REQUEST:
      return state
        .set('error', false)
        .set('books', false)
        .set('activeBook', false)
        .set('searching', true);
    case SEARCH_SUCCESS:
      return state
        .set('searching', false)
        .set('books', action.payload.books);
    case SEARCH_ERROR:
      return state
        .set('searching', false)
        .set('error', action.payload.error);
    case SELECT_BOOK:
      return state
        .set('activeBook', action.payload.book);
    case UNSELECT_BOOK:
      return state
        .set('activeBook', false);
    default:
      return state;
  }
}

export default addBookPageReducer;

import { fromJS } from 'immutable';
import {
  SEARCH_REQUEST,
  SEARCH_SUCCESS,
  SEARCH_ERROR,
  SELECT_BOOK,
  UNSELECT_BOOK,
  ADD_BOOK_SUCCESS,
} from './constants';

const initialState = fromJS({
  searching: false,
  error: false,
  books: false,
  activeBook: false,
});

function addBookPageReducer(state = initialState, action) {
  switch (action.type) {
    case SEARCH_REQUEST:
      return state
        .set('error', false)
        .set('books', false)
        .set('activeBook', false)
        .set('searching', true);
    case SEARCH_SUCCESS:
      return state
        .set('searching', false)
        .set('books', action.payload.books);
    case SEARCH_ERROR:
      return state
        .set('searching', false)
        .set('error', action.payload.error);
    case SELECT_BOOK:
      return state
        .set('activeBook', action.payload.book);
    case UNSELECT_BOOK:
      return state
        .set('activeBook', false);
    case ADD_BOOK_SUCCESS:
      return state
        .set('error', false)
        .set('books', false)
        .set('activeBook', false)
        .set('searching', false);
    default:
      return state;
  }
}

export default addBookPageReducer;

Reset state on book added


'use strict';

/* jasmine specs for controllers go here */

describe('RootCtrl', function(){
    beforeEach(module('swiftBrowser.controllers'));

    it('should list containers', inject(function($controller, $httpBackend) {
        var containers = [
            {"count": 10, "bytes": 1234, "name": "foo"},
            {"count": 20, "bytes": 2345, "name": "bar"},
        ];
        var scope = {};
        $httpBackend.whenGET('/v1/AUTH_abc?format=json')
            .respond(200, containers);

        $controller('RootCtrl', {$scope: scope});
        expect(scope.containers).toEqual([]);
        $httpBackend.flush();
        expect(scope.containers).toEqual(containers);
    }));

});

'use strict';

/* jasmine specs for controllers go here */

describe('RootCtrl', function(){
    beforeEach(module('swiftBrowser.controllers'));

    it('should list containers', inject(function($controller, $httpBackend) {
        var containers = [
            {"count": 10, "bytes": 1234, "name": "foo"},
            {"count": 20, "bytes": 2345, "name": "bar"},
        ];
        var scope = {};
        $httpBackend.whenGET('/v1/AUTH_abc?format=json')
            .respond(200, containers);

        $controller('RootCtrl', {$scope: scope});
        expect(scope.containers).toEqual([]);
        $httpBackend.flush();
        expect(scope.containers).toEqual(containers);
    }));

    it('should set sort order', inject(function($controller) {
        var scope = {};
        $controller('RootCtrl', {$scope: scope});
        expect(scope.orderProp).toEqual('name');
    }));

});

Check that RootCtrl sets the default sort order


import _ from 'lodash'
import postcss from 'postcss'
import cloneNodes from '../util/cloneNodes'
import buildMediaQuery from '../util/buildMediaQuery'

export default function(config) {
  return function(css) {
    const screens = config().screens
    const responsiveRules = []
    let finalRules = []

    css.walkAtRules('responsive', atRule => {
      const nodes = atRule.nodes
      responsiveRules.push(...cloneNodes(nodes))
      atRule.before(nodes)
      atRule.remove()
    })

    _.keys(screens).forEach(screen => {
      const mediaQuery = postcss.atRule({
        name: 'media',
        params: buildMediaQuery(screens[screen]),
      })

      mediaQuery.append(
        responsiveRules.map(rule => {
          const cloned = rule.clone()
          cloned.selectors = _.map(rule.selectors, selector => `.${screen}\\:${selector.slice(1)}`)
          return cloned
        })
      )

      finalRules.push(mediaQuery)
    })

    const hasScreenRules = finalRules.some(i => i.nodes.length !== 0)
    if (!hasScreenRules) {
      return
    }

    const includesScreensExplicitly = css.some(
      rule => rule.type === 'atrule' && rule.params === 'screens'
    )

    if (!includesScreensExplicitly) {
      css.append(finalRules)
      return
    }

    css.walkAtRules('tailwind', atRule => {
      if (atRule.params === 'screens') {
        atRule.replaceWith(finalRules)
      }
    })
  }
}

import _ from 'lodash'
import postcss from 'postcss'
import cloneNodes from '../util/cloneNodes'
import buildMediaQuery from '../util/buildMediaQuery'

export default function(config) {
  return function(css) {
    const screens = config().screens
    const responsiveRules = []
    let finalRules = []

    css.walkAtRules('responsive', atRule => {
      const nodes = atRule.nodes
      responsiveRules.push(...cloneNodes(nodes))
      atRule.before(nodes)
      atRule.remove()
    })

    _.keys(screens).forEach(screen => {
      const mediaQuery = postcss.atRule({
        name: 'media',
        params: buildMediaQuery(screens[screen]),
      })

      mediaQuery.append(
        responsiveRules.map(rule => {
          const cloned = rule.clone()
          cloned.selectors = _.map(rule.selectors, selector => `.${screen}\\:${selector.slice(1)}`)
          return cloned
        })
      )

      finalRules.push(mediaQuery)
    })

    const hasScreenRules = finalRules.some(i => i.nodes.length !== 0)

    if (!hasScreenRules) {
      return
    }

    let includesScreensExplicitly = false

    css.walkAtRules('tailwind', atRule => {
      if (atRule.params === 'screens') {
        atRule.replaceWith(finalRules)
        includesScreensExplicitly = true
      }
    })

    if (!includesScreensExplicitly) {
      css.append(finalRules)
      return
    }
  }
}

Allow `@tailwind screens` directive inside an at-rule


/**
 * Copyright 2013-2018 the original author or authors from the JHipster project.
 *
 * This file is part of the JHipster project, see http://www.jhipster.tech/
 * for more information.
 *
 * 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.
 */

const AWS_SSM_VERSION = '1.11.247';
const SPRING_CLOUD_CTX_VERSION = '1.3.0.RELEASE';

const configuration = {
    aws: 'aws'
};

const constants = {
    conf: configuration,
    AWS_SSM_VERSION,
    SPRING_CLOUD_CTX_VERSION
};

module.exports = constants;

/**
 * Copyright 2013-2018 the original author or authors from the JHipster project.
 *
 * This file is part of the JHipster project, see http://www.jhipster.tech/
 * for more information.
 *
 * 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.
 */

const AWS_SSM_VERSION = '1.11.247';

const configuration = {
    aws: 'aws'
};

const constants = {
    conf: configuration,
    AWS_SSM_VERSION
};

module.exports = constants;

Use the Spring Cloud Context version from jhipster-dependencies


const React = require('react');

const Utils = require('../utils');

const AuthentificationPage = React.createClass({
  getInitialState() { return { loading: false }; },
  openAuthWindow() {
    this.setState({ loading: true });
    Utils.Socket.emit('youtube/auth');
  },
  render() {
    if (this.state.loading) {
      return (

YouWatch

Please fulfill the informations on the other window

              <button className="btn btn-primary btn-lg disabled">Logging in...</button>

      );
    }

    return (

YouWatch

{'Let\'s connect to your YouTube Account'}

            <button className="btn btn-primary btn-lg" onClick={this.openAuthWindow}>Log in</button>

    );
  },
});

module.exports = AuthentificationPage;

const React = require('react');

const Utils = require('../utils');

const AuthentificationPage = React.createClass({
  getInitialState() { return { loading: false }; },
  openAuthWindow() {
    this.setState({ loading: true });
    Utils.Socket.emit('youtube/auth');
  },
  render() {
    if (this.state.loading) {
      return (

YouWatch

Please fulfill the informations on the other window

              <button className="btn btn-primary btn-lg disabled"> Logging in...</button>

      );
    }

    return (

YouWatch

{'Let\'s connect to your YouTube Account'}

            <button className="btn btn-primary btn-lg" onClick={this.openAuthWindow}>Log in</button>

    );
  },
});

module.exports = AuthentificationPage;

Add a loading indicator on the auth page


/*
 * generator-init
 * https://github.com/use-init/generator-init
 *
 */

'use strict';

module.exports = function (grunt) {
  // Load all grunt tasks matching the `grunt-*` pattern.
  require('load-grunt-tasks')(grunt);

  // Project configuration.
  grunt.initConfig({
    jshint: {
      all: [
        'Gruntfile.js',
        'app/index.js',
        'lib/*.js',
        'page/index.js',
        'module/index.js',
        'jqueryplugin/index.js',
        'page/index.js',
        '<%= mochaTest.test.src%>'
      ],
      options: {
        jshintrc: '.jshintrc'
      }
    },

    // Before generating any new files, remove any previously-created files.
    clean: {
      tests: ['tests/temp']
    },

    // Unit tests.
    mochaTest: {
      test: {
        options: {
          reporter: 'spec'
        },
        src: ['test/*.js']
      }
    }

  });

  // Whenever the "test" task is run, first clean the "temp" dir, then run this
  // plugin's task(s), then test the result.
  grunt.registerTask('test', ['clean', 'mochaTest']);

  // By default, lint and run all tests.
  grunt.registerTask('default', ['clean', 'jshint', 'mochaTest']);
};

/*
 * generator-init
 * https://github.com/use-init/generator-init
 *
 */

'use strict';

module.exports = function (grunt) {
  // Load all grunt tasks matching the `grunt-*` pattern.
  require('load-grunt-tasks')(grunt);

  // Project configuration.
  grunt.initConfig({
    jshint: {
      all: [
        'Gruntfile.js',
        'app/index.js',
        'lib/*.js',
        'page/index.js',
        'module/index.js',
        'jqueryplugin/index.js',
        'page/index.js',
        '<%= mochaTest.test.src%>'
      ],
      options: {
        jshintrc: '.jshintrc'
      }
    },

    // Before generating any new files, remove any previously-created files.
    clean: {
      test: ['test/temp']
    },

    // Unit tests.
    mochaTest: {
      test: {
        options: {
          reporter: 'spec'
        },
        src: ['test/*.js']
      }
    }

  });

  // Whenever the "test" task is run, first clean the "temp" dir, then run this
  // plugin's task(s), then test the result.
  grunt.registerTask('test', ['clean', 'mochaTest']);

  // By default, lint and run all tests.
  grunt.registerTask('default', ['clean', 'jshint', 'mochaTest']);
};

Rename clean folder to test/tmp


*
 * grunt-cdndeps
 * https://github.com/cambridge-healthcare/grunt-cdndeps
 *
 * Copyright (c) 2014 Cambridge Healthcare
 * Licensed under the MIT license.
 */

"use strict";

module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    cdndeps: {
      main: {
        files: {
          "deps": ["example_deps.json"]
        }
      }
    }
  });

  grunt.loadTasks("tasks");
  grunt.loadNpmTasks("grunt-contrib-clean");

  grunt.registerTask("default", ["cdndeps"]);

};

/*
 * grunt-cdndeps
 * https://github.com/cambridge-healthcare/grunt-cdndeps
 *
 * Copyright (c) 2014 Cambridge Healthcare
 * Licensed under the MIT license.
 */

"use strict";

module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    cdndeps: {
      main: {
        files: {
          "deps": ["example_deps.json"]
        }
      }
    }
  });

  grunt.loadTasks("tasks");

  grunt.registerTask("default", ["cdndeps"]);

};

Clean plugin is not used


module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
	gitclone: {
		docs: {
			options: {
				directory: "site",
				repository: "https://github.com/SockDrawer/SockBot.git",
				branch: "gh-pages"
			}
		}
	},
	mkdocs: {
		dist: {
		  src: '.',
		  options: {
			clean: true
		  }
		}
	},
	jsdoc : {
        dist : {
            src: ['*.js', 'sock-modules/**/*.js'],
            options: {
                destination: 'site/docs'
            }
        }
    },
	'gh-pages': {
		options: {
			// The default commit message for the gh-pages branch
			base: 'site',
			message: 'push documentation automatically',
			repo: 'https://' + process.env.GH_TOKEN + '@github.com/SockDrawer/SockBot'
		},
		src: "**"
	}
  });

  // Load the plugins
    grunt.loadNpmTasks('grunt-mkdocs');
  grunt.loadNpmTasks('grunt-git');
  grunt.loadNpmTasks('grunt-jsdoc');
  grunt.loadNpmTasks('grunt-gh-pages');

  // Default task(s).
  grunt.registerTask('generate-docs', ['mkdocs', 'jsdoc', 'gh-pages']);
  grunt.registerTask('default', ['mkdocs', 'jsdoc', 'gh-pages']);

};

module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
	gitclone: {
		docs: {
			options: {
				directory: "site",
				repository: "https://github.com/SockDrawer/SockBot.git",
				branch: "gh-pages"
			}
		}
	},
	mkdocs: {
		dist: {
		  src: '.',
		  options: {
			clean: true
		  }
		}
	},
	jsdoc : {
        dist : {
            src: ['*.js', 'sock_modules/*.js', 'sock_modules/**/*.js'],
            options: {
                destination: 'site/docs'
            }
        }
    },
	'gh-pages': {
		options: {
			// The default commit message for the gh-pages branch
			base: 'site',
			message: 'push documentation automatically',
			repo: 'https://' + process.env.GH_TOKEN + '@github.com/SockDrawer/SockBot'
		},
		src: "**"
	}
  });

  // Load the plugins
  grunt.loadNpmTasks('grunt-mkdocs');
  grunt.loadNpmTasks('grunt-git');
  grunt.loadNpmTasks('grunt-jsdoc');
  grunt.loadNpmTasks('grunt-gh-pages');

  // Default task(s).
  grunt.registerTask('generate-docs', ['mkdocs', 'jsdoc', 'gh-pages']);
  grunt.registerTask('default', ['mkdocs', 'jsdoc', 'gh-pages']);

};

Fix for it not detecting the sock_modules folder


/**
 * A Backbone view that renders breadcrumbs-type tiered navigation.
 *
 * Initialize the view by passing in the following attributes:
 *
 *~~~ javascript
 * var view = new DropdownMenuView({
 *     el: $('selector for element that will contain breadcrumbs'),
 *     model: new BreadcrumbsModel({
 *         breadcrumbs: [{url: '/', title: 'Overview'}]
 *     }),
 *     events: {
 *         'click nav.breadcrumbs a.nav-item': function (event) {
 *             event.preventDefault();
 *             window.location = $(event.currentTarget).attr('href');
 *         }
 *     }
 * });
 *~~~
 * @module BreadcrumbsView
 */
;(function (define) {
    'use strict';
    define(['backbone', 'edx-ui-toolkit/js/utils/html-utils', 'text!./breadcrumbs.underscore'],
        function (Backbone, HtmlUtils, breadcrumbsTemplate) {
            var BreadcrumbsView = Backbone.View.extend({
                initialize: function (options) {
                    this.template = HtmlUtils.template(breadcrumbsTemplate);
                    this.listenTo(this.model, 'change', this.render);
                    this.render();
                },

                render: function () {
                    var json = this.model.attributes;
                    HtmlUtils.setHtml(this.$el, this.template(json));
                    return this;
                }
            });

            return BreadcrumbsView;
        });
}).call(
    this,
    // Use the default 'define' function if available, else use 'RequireJS.define'
    typeof define === 'function' && define.amd ? define : RequireJS.define
);

/**
 * A Backbone view that renders breadcrumbs-type tiered navigation.
 *
 * Initialize the view by passing in the following attributes:
 *
 *~~~ javascript
 * var view = new BreadcrumbsView({
 *     el: $('selector for element that will contain breadcrumbs'),
 *     model: new BreadcrumbsModel({
 *         breadcrumbs: [{url: '/', title: 'Overview'}]
 *     }),
 *     events: {
 *         'click nav.breadcrumbs a.nav-item': function (event) {
 *             event.preventDefault();
 *             window.location = $(event.currentTarget).attr('href');
 *         }
 *     }
 * });
 *~~~
 * @module BreadcrumbsView
 */
;(function (define) {
    'use strict';
    define(['backbone', 'edx-ui-toolkit/js/utils/html-utils', 'text!./breadcrumbs.underscore'],
        function (Backbone, HtmlUtils, breadcrumbsTemplate) {
            var BreadcrumbsView = Backbone.View.extend({
                initialize: function (options) {
                    this.template = HtmlUtils.template(breadcrumbsTemplate);
                    this.listenTo(this.model, 'change', this.render);
                    this.render();
                },

                render: function () {
                    var json = this.model.attributes;
                    HtmlUtils.setHtml(this.$el, this.template(json));
                    return this;
                }
            });

            return BreadcrumbsView;
        });
}).call(
    this,
    // Use the default 'define' function if available, else use 'RequireJS.define'
    typeof define === 'function' && define.amd ? define : RequireJS.define
);

Fix BreadcrumbsView docs that still said DropdownMenuView


const fs = require('fs');
const request = require('request');

const nuget = './nuget.exe';

console.info(`Downloading 'nuget.exe'...`);
console.info();

request
    .get('http://nuget.org/nuget.exe')
    .on('error', function (err) {
        console.error(err);
    })
    .on('response', function (response) {
        if (response.statusCode == 200) {
            console.info(`Successfully downloaded 'nuget.exe'.`);
        } else {
            console.error(`Downloading 'nuget.exe' failed. statusCode '${response.statusCode}'`);
        }
    })
    .pipe(fs.createWriteStream(nuget));

const fs = require('fs');
const request = require('request');

const nuget = './nuget.exe';

console.info(`Downloading 'nuget.exe'...`);
console.info();

request
    .get('https://dist.nuget.org/win-x86-commandline/latest/nuget.exe')
    .on('error', function (err) {
        console.error(err);
    })
    .on('response', function (response) {
        if (response.statusCode == 200) {
            console.info(`Successfully downloaded 'nuget.exe'.`);
        } else {
            console.error(`Downloading 'nuget.exe' failed. statusCode '${response.statusCode}'`);
        }
    })
    .pipe(fs.createWriteStream(nuget));

Fix download location for 'nuget.exe'


'use strict';
const db = require('../utils/database');

/**
 * Fetches a user ID from the database.
 * @constructor
 * @param {integer} userId - The user's ID
 * @return {integer} The number of Hearts a user has.
 */
const getHeartsForUser = (userId) => {
  return db.get({
    TableName: 'Users',
    Key: {
      UserId: userId,
    },
  })
  .then( data => data.Item.VcCurrent);
};

const handler = (event) => {
  if (!event.params.id) {
    return Promise.resolve({
      statusCode: 400,
      body: JSON.stringify({ message: 'The id query param must be set to a valid user id' }),
    });
  }
  let userId = parseInt(event.params.id, 10) || 0;
  return getHeartsForUser(userId)
    .then(
      (hearts) => {
        return {
          statusCode: 200,
          body: JSON.stringify({ message: `User ${userId} has ${hearts} hearts!` }),
        };
      },
      (err) => {
        // console.log('Error:', err);
        return {
          statusCode: 404,
          body: JSON.stringify({ message: `User ID ${userId} not found ` }),
        };
      }
    );
};

const serverlessHandler = (event, context, callback) => {
  handler(event)
    .then( response => callback(null, response) );
}

module.exports = {
  handler: handler,
  serverlessHandler: serverlessHandler,
}

'use strict';
const db = require('../utils/database');

/**
 * Fetches a user ID from the database.
 * @constructor
 * @param {integer} userId - The user's ID
 * @return {integer} The number of Hearts a user has.
 */
const getHeartsForUser = (userId) => {
  return db.get({
    TableName: 'Users',
    Key: {
      id: userId,
    },
  })
  .then( data => data.Item.vcCurrent);
};

const handler = (event) => {
  if (!event.params.id) {
    return Promise.resolve({
      statusCode: 400,
      body: JSON.stringify({ message: 'The id query param must be set to a valid user id' }),
    });
  }
  let userId = event.params.id;
  return getHeartsForUser(userId)
    .then(
      (hearts) => {
        return {
          statusCode: 200,
          body: JSON.stringify({ message: `User ${userId} has ${hearts} hearts!` }),
        };
      },
      (err) => {
        // console.log('Error:', err);
        return {
          statusCode: 404,
          body: JSON.stringify({ message: `User ID ${userId} not found ` }),
        };
      }
    );
};

const serverlessHandler = (event, context, callback) => {
  handler(event)
    .then( response => callback(null, response) );
}

module.exports = {
  handler: handler,
  serverlessHandler: serverlessHandler,
}

Update Hearts lambda function to new DB schema.


import {ipcRenderer} from 'electron';

import piwik from 'renderer/services/piwik';
import webView from 'renderer/webview';

/**
 * Forward a message to the webview.
 */
ipcRenderer.on('fwd-webview', function (event, channel, ...args) {
  if (webView.isLoading && (typeof webView.isLoading === 'function') && !webView.isLoading()) {
    webView.send(channel, ...args);
  } else {
    const onLoaded = function () {
      webView.send(channel, ...args);
      webView.removeEventListener('dom-ready', onLoaded);
    };

    webView.addEventListener('dom-ready', onLoaded);
  }
});

/**
 * Call a method of the webview.
 */
ipcRenderer.on('call-webview-method', function (event, method, ...args) {
  webView[method](...args);
});

/**
 * Track an analytics event.
 */
ipcRenderer.on('track-analytics', function (event, name, args) {
  const tracker = piwik.getTracker();
  if (tracker) {
    const trackerFn = tracker[name];
    trackerFn(...args);
  }
});

import {ipcRenderer} from 'electron';

import * as piwik from 'renderer/services/piwik';
import webView from 'renderer/webview';

/**
 * Forward a message to the webview.
 */
ipcRenderer.on('fwd-webview', function (event, channel, ...args) {
  if (webView.isLoading && (typeof webView.isLoading === 'function') && !webView.isLoading()) {
    webView.send(channel, ...args);
  } else {
    const onLoaded = function () {
      webView.send(channel, ...args);
      webView.removeEventListener('dom-ready', onLoaded);
    };

    webView.addEventListener('dom-ready', onLoaded);
  }
});

/**
 * Call a method of the webview.
 */
ipcRenderer.on('call-webview-method', function (event, method, ...args) {
  webView[method](...args);
});

/**
 * Track an analytics event.
 */
ipcRenderer.on('track-analytics', function (event, name, args) {
  const tracker = piwik.getTracker();
  if (tracker) {
    const trackerFn = tracker[name];
    trackerFn(...args);
  }
});

Fix usage of Piwik in renderer


module.exports = function Server(expressInstance, sessionStore) {
	var parseCookie = require('connect').utils.parseCookie;
	var io = require('socket.io').listen(expressInstance);

	io.configure(function () {
		io.set('log level', 0);
	});

	io.set('authorization', function(handshakeData, ack) {
		var cookies = parseCookie(handshakeData.headers.cookie);
		sessionStore.get(cookies['connect.sid'], function(err, sessionData) {
			handshakeData.session = sessionData || {};
			handshakeData.sid = cookies['connect.sid']|| null;
			ack(err, err ? false : true);
		});
	});

	io.sockets.on('connection', function(client) {
    client.emit('woo', 'Hello!');

		client.on('disconnect', function() { console.log('disconnect'); });

    client.on('scoreSubmit', function(scoreData) {
      console.log('Score submitted: ' + scoreData.score);
    });
	});

	io.sockets.on('error', function(){ console.log(arguments); });

	return io;
};

module.exports = function Server(expressInstance, sessionStore) {
var redis = require('redis'), redisClient = redis.createClient();
	var parseCookie = require('connect').utils.parseCookie;
	var io = require('socket.io').listen(expressInstance);

	io.configure(function () {
		io.set('log level', 0);
	});

	io.set('authorization', function(handshakeData, ack) {
		var cookies = parseCookie(handshakeData.headers.cookie);
		sessionStore.get(cookies['connect.sid'], function(err, sessionData) {
			handshakeData.session = sessionData || {};
			handshakeData.sid = cookies['connect.sid']|| null;
			ack(err, err ? false : true);
		});
	});

	io.sockets.on('connection', function(client) {
    client.emit('woo', 'Hello!');

		client.on('disconnect', function() { console.log('disconnect'); });

    client.on('scoreSubmit', function(scoreData) {
      // TODO - identify player
      redisClient.zadd('HIGHSCORES',scoreData.score, 'playername');
    });
	});

	io.sockets.on('error', function(){ console.log(arguments); });

	return io;
};

Store high scores to redis set


/* eslint-disable no-var */

var webpack = require('webpack')
var Config = require('webpack-config').default

module.exports = new Config().extend('webpack/config/base.config.js').merge({
  devtool: 'cheap-module-eval-source-map',
  devServer: {
    port: process.env.PORT || 8080,
  },
  output: {
    publicPath: '/',
  },
  module: {
    preLoaders: [
      {
        test: /\.jsx?$/,
        loader: 'eslint',
        exclude: /node_modules/,
      },
    ],
    loaders: [
      {
        test: /\.s(c|a)ss$/,
        loaders: ['style', 'css', 'sass?sourceMap=true'],
      },
    ],
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('development'),
    }),
  ],
})

/* eslint-disable no-var */

var webpack = require('webpack')
var Config = require('webpack-config').default

module.exports = new Config().extend('webpack/config/base.config.js').merge({
  devtool: 'cheap-module-eval-source-map',
  devServer: {
    port: process.env.PORT || 8080,
    historyApiFallback: true,
  },
  output: {
    publicPath: '/',
  },
  module: {
    preLoaders: [
      {
        test: /\.jsx?$/,
        loader: 'eslint',
        exclude: /node_modules/,
      },
    ],
    loaders: [
      {
        test: /\.s(c|a)ss$/,
        loaders: ['style', 'css', 'sass?sourceMap=true'],
      },
    ],
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('development'),
    }),
  ],
})

Use history fallback on development


class Element {
  mount(el) {
    if (typeof el === "string") {
      el = document.querySelector(el);
    }

    el.innerHTML = `
      <input id="stripe-cardnumber" placeholder="cardnumber" size="16" type="text">
      <input placeholder="exp-date" size="6" type="text">
      <input placeholder="cvc" size="3" type="text">
    `;
  }
}

window.Stripe = () => {
  const fetchLastFour = () => {
    return document.getElementById("stripe-cardnumber").value.substr(-4, 4);
  };

  return {
    elements: () => {
      return {
        create: (type, options) => new Element()
      };
    },

    createToken: card => {
      return new Promise(resolve => {
        resolve({ token: { id: "tok_123", card: { last4: fetchLastFour() } } });
      });
    }
  };
};

class Element {
  mount(el) {
    if (typeof el === "string") {
      el = document.querySelector(el);
    }

    el.innerHTML = `
      <input id="stripe-cardnumber" name="cardnumber" placeholder="Card number" size="16" type="text">
      <input name="exp-date" placeholder="MM / YY" size="6" type="text">
      <input name="cvc" placeholder="CVC" size="3" type="text">
    `;
  }
}

window.Stripe = () => {
  const fetchLastFour = () => {
    return document.getElementById("stripe-cardnumber").value.substr(-4, 4);
  };

  return {
    elements: () => {
      return {
        create: (type, options) => new Element()
      };
    },

    createToken: card => {
      return new Promise(resolve => {
        resolve({ token: { id: "tok_123", card: { last4: fetchLastFour() } } });
      });
    }
  };
};

Update form elements to more accurately reflect actual Stripe form


#!/usr/bin/env node

'use strict';

var Slack = require('slack-client');
var token = process.argv[2];

if (!token) {
  console.error([
    'Usage: feurbot <token>',
    null,
    'To generate a token, create a bot at http://my.slack.com/services/new/bot'
  ].join('\n'));
  process.exit(1);
}

var slack = new Slack(token);

slack.on('message', function (message) {
  if (message.type !== 'message') {
    return;
  }

  if (!/quoi\s*[?!]*$/.test(message.text) && Math.random() < 0.99) {
    return;
  }

  var channel = slack.getChannelGroupOrDMByID(message.channel);
  var before  = 'FEEEEEUUUUUUUR !';
  var after   = Math.random() < .5 ? '(bim !)' : '(boum !)';
  var msg     = channel.send(before);

  setTimeout(function() {
    msg.updateMessage(before + ' ' + after);
  }, 700);
});

slack.on('error', function (err) {
  if (err === 'invalid_auth') {
    console.error('Invalid token, go grab a valid one!')
    process.exit(1);
  }

  console.error(err);
});

slack.login();

#!/usr/bin/env node

'use strict';

var Slack   = require('slack-client');
var token   = process.argv[2];
var trigger = /(q+u+o+i+|k+o+i+|k+o+a+|c+o+m+e+n+t+)\s*[?!]*$/;

if (!token) {
  console.error([
    'Usage: feurbot <token>',
    null,
    'To generate a token, create a bot at http://my.slack.com/services/new/bot'
  ].join('\n'));
  process.exit(1);
}

var slack = new Slack(token);

slack.on('message', function (message) {
  if (message.type !== 'message') {
    return;
  }

  if (!trigger.test(message.text) && Math.random() < 0.99) {
    return;
  }

  var channel = slack.getChannelGroupOrDMByID(message.channel);
  var before  = 'FEEEEEUUUUUUUR !';
  var after   = Math.random() < .5 ? '(bim !)' : '(boum !)';
  var msg     = channel.send(before);

  setTimeout(function() {
    msg.updateMessage(before + ' ' + after);
  }, 700);
});

slack.on('error', function (err) {
  if (err === 'invalid_auth') {
    console.error('Invalid token, go grab a valid one!')
    process.exit(1);
  }

  console.error(err);
});

slack.login();

Add some more trigger messages


var tar = require("../tar.js")
  , fs = require("fs")

fs.createReadStream(__dirname + "/../test/fixtures/c.tar")
  .pipe(tar.Reader())
  .on("entry", function (e) {
    console.error("entry", e.props)
    e.on("data", function (c) {
      console.error("  >>>" + c.toString().replace(/\n/g, "\\n"))
    })
    e.on("end", function () {
      console.error("  <<<EOF")
    })
  })

var tar = require("../tar.js")
  , fs = require("fs")

fs.createReadStream(__dirname + "/../test/fixtures/c.tar")
  .pipe(tar.Reader())
  .on("extendedHeader", function (e) {
    console.error("extended pax header", e.props)
    e.on("end", function () {
      console.error("extended pax fields:", e.fields)
    })
  })
  .on("entry", function (e) {
    console.error("entry", e.props)
    e.on("data", function (c) {
      console.error("  >>>" + c.toString().replace(/\n/g, "\\n"))
    })
    e.on("end", function () {
      console.error("  <<<EOF")
    })
  })

Print data about extended headers


module.exports = function (grunt) {

	grunt.initConfig({

		pkg: grunt.file.readJSON('package.json'),

		clean: {
			build: {
				src: "build"
			}
		},

		copy: {
			build: {
				files: [
					{
						expand: true,
						cwd: 'source/',
						src: ['demo/**/*.html', 'demo/**/*.css', 'demo/**/*.js', 'clever-video.js'],
						dest: 'build/'
					}
				]
			}
		}
	});

	grunt.loadNpmTasks('grunt-contrib-clean');
	grunt.loadNpmTasks('grunt-contrib-copy');

	grunt.registerTask('build', ['clean', 'copy']);

	grunt.registerTask('default', ['build']);
};

module.exports = function (grunt) {

	grunt.initConfig({

		pkg: grunt.file.readJSON('package.json'),

		clean: {
			build: {
				src: "build"
			}
		},

		copy: {
			build: {
				files: [
					{
						expand: true,
						cwd: 'source/',
						src: ['demo/**/*.html', 'demo/**/*.css', 'demo/**/*.js', 'clever-video.js'],
						dest: 'build/'
					}
				]
			}
		},

		uglify: {
			build: {
				files: {
					'build/clever-video.min.js': ['source/clever-video.js']
				}
			}
		}
	});

	grunt.loadNpmTasks('grunt-contrib-clean');
	grunt.loadNpmTasks('grunt-contrib-copy');
	grunt.loadNpmTasks('grunt-contrib-uglify');

	grunt.registerTask('build', ['clean', 'copy']);

	grunt.registerTask('default', ['build']);
};

Add a Grunt task to produce a minified version of clever-video.js.


'use strict';

module.exports = function (grunt) {

	grunt.initConfig({
		pkg: {
			name: 'grunt-jekyll'
		},

		jekyll: {
			actual: {
				options: {
					dest: './test/actual'
				}
			},
			expected : {
				options: {
					dest: './test/expected'
				}
			},
			options: {
				src: './test/app'
			}
		},

		mochaTest: {
			options: {
				reporter: 'list'
			},
			src: 'test/test.js'
		}
	});

	grunt.loadTasks('tasks');
	grunt.loadNpmTasks('grunt-mocha-test');

	grunt.registerTask('test', [
		'jekyll:expected',
		'mochaTest'
	]);

	grunt.registerTask('default', 'test');
};

'use strict';

module.exports = function (grunt) {

	grunt.initConfig({
		pkg: {
			name: 'grunt-jekyll'
		},

		jekyll: {
			options: {
				src: './test/app'
			},
			actual: {
				options: {
					dest: './test/actual'
				}
			},
			expected : {
				options: {
					dest: './test/expected'
				}
			}
		},

		mochaTest: {
			options: {
				reporter: 'list'
			},
			src: 'test/test.js'
		}
	});

	grunt.loadTasks('tasks');
	grunt.loadNpmTasks('grunt-mocha-test');

	grunt.registerTask('test', [
		'jekyll:expected',
		'mochaTest'
	]);

	grunt.registerTask('default', 'test');
};

Move global task options object above task targets


module.exports = function(grunt) {
  require('load-grunt-tasks')(grunt);

  grunt.initConfig({
    secret: grunt.file.readJSON('secret.json'),

    // TODO: This won't rm removed resources automatically!
    sftp: {
      options: {
        path: '<%= secret.path %>',
        host: '<%= secret.host %>',
        username: '<%= secret.username %>',
        agent: process.env.SSH_AUTH_SOCK,
        showProgress: true,
        srcBasePath: '_site/',
        createDirectories: true
      },

      all: {
        files: {
          './': ['_site/**']
        }
      }
    },

    exec: {
      build: 'jekyll b'
    }

  });

  grunt.registerTask('deploy', ['exec:build', 'sftp']);
};

module.exports = function(grunt) {
  require('load-grunt-tasks')(grunt);

  grunt.initConfig({
    secret: grunt.file.readJSON('secret.json'),

    // TODO: This won't rm removed resources automatically!
    sftp: {
      options: {
        path: '<%= secret.path %>',
        host: '<%= secret.host %>',
        username: '<%= secret.username %>',
        agent: process.env.SSH_AUTH_SOCK,
        showProgress: true,
        srcBasePath: '_site/',
        createDirectories: true
      },

      all: {
        files: {
          './': ['_site/**']
        }
      }
    },

    exec: {
      build: 'bundle exec jekyll build'
    }

  });

  grunt.registerTask('deploy', ['exec:build', 'sftp']);
};

Update Grunt deploy to use bundle


module.exports = function(grunt) {
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    concat: {
      options: {
        separator: ';'
      },
      dist: {
        src: 'src/*.js',
        dest: 'dist/m.jquery.js'
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-concat');

  // Run tests?
  grunt.registerTask('default', ['concat']);

  grunt.registerTask('build', 'Build the plugin files.', ['concat']);
};

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

    // Concat task.
    concat: {
      dist: {
        src: 'src/*.js',
        dest: 'dist/m.jquery-patched.js'
      }
    },

    // Copy task.
    copy: {
      dist: {
        files: {
          'dist/m.jquery.js': 'src/m.jquery.js'
        }
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-copy');

  // Run tests?
  grunt.registerTask('default', ['concat']);

  grunt.registerTask('build', 'Build the plugin files.', ['concat', 'copy']);
};

Set up a copy task.


// Generated on 2014-07-07 using generator-nodejs 2.0.0
module.exports = function (grunt) {
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        jshint: {
            all: [
                'Gruntfile.js',
                'test/**/*.js'
            ],
            options: {
                jshintrc: '.jshintrc'
            }
        },
        jison: {
            all : {
                files: { 'lib/parser.js': 'data/grypher.jison' }
            }
        },
        mochacli: {
            all: ['test/**/*.js'],
            options: {
                reporter: 'spec',
                ui: 'tdd'
            }
        },
        watch: {
            js: {
                files: ['**/*.js', 'data/*.jison', '!node_modules/**/*.js'],
                tasks: ['default'],
                options: {
                    nospawn: true
                }
            }
        }
    });

    grunt.loadNpmTasks('grunt-jison');
    grunt.loadNpmTasks('grunt-contrib-jshint');
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-mocha-cli');
    grunt.registerTask('test', ['jshint', 'jison', 'mochacli', 'watch']);
    grunt.registerTask('ci', ['jshint', 'jison', 'mochacli']);
    grunt.registerTask('default', ['test']);
};

// Generated on 2014-07-07 using generator-nodejs 2.0.0
module.exports = function (grunt) {
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        jshint: {
            all: [
                'Gruntfile.js',
                'test/**/*.js'
            ],
            options: {
                jshintrc: '.jshintrc'
            }
        },
        jison: {
            all : {
                files: { 'lib/parser.js': 'data/grypher.jison' }
            }
        },
        mochacli: {
            all: ['test/**/*.js'],
            options: {
                reporter: 'spec',
                ui: 'tdd'
            }
        },
        watch: {
            js: {
                files: ['**/*.js', 'data/*.jison', '!node_modules/**/*.js'],
                tasks: ['default'],
                options: {
                    nospawn: true
                }
            }
        }
    });

    grunt.loadNpmTasks('grunt-jison');
    grunt.loadNpmTasks('grunt-contrib-jshint');
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-mocha-cli');
    grunt.loadNpmTasks('grunt-release');
    grunt.registerTask('test', ['jshint', 'jison', 'mochacli', 'watch']);
    grunt.registerTask('ci', ['jshint', 'jison', 'mochacli']);
    grunt.registerTask('default', ['test']);
    grunt.registerTask('publish', ['ci', 'release']);
};

Add grunt-release and rename NPM module to grypher.


$(document).ready(function () {
  var offset
  var viewMessageId = $('#content').data().viewMessageId
  if (viewMessageId) {
    offset = $('#message-' + viewMessageId).offset()
  }
  if (!window.location.hash && offset) {
    $('html, body').animate({
      scrollTop: offset.top - 77
    }, 350, 'swing')
  }
})

$(document).ready(function () {
  var offset
  var viewMessageId = $('#content').data('view-message-id')
  if (viewMessageId) {
    offset = $('#message-' + viewMessageId).offset()
  }
  if (!window.location.hash && offset) {
    $('html, body').animate({
      scrollTop: offset.top - 77
    }, 350, 'swing')
  }
})

Fix JS on profile page


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

    grunt.initConfig({
        pkg: grunt.file.readJSON("package.json"),
        watch: {
            php: {
                files: ["src/**/*.php", "tests/**/*.php"],
                tasks: ["testphp"]
            }
        },
        phpunit: {
            unit: {
                dir: "tests"
            },
            options: {
                bin: "vendor/bin/phpunit --coverage-text --coverage-html ./report",
                colors: true,
                testdox: false
            }
        },
        phplint: {
            options: {
                swapPath: "/tmp"
            },
            all: ["src/**/*.php", "tests/**/*.php"]
        },
        phpcs: {
            application: {
                src: ["src/**/*.php", "tests/**/*.php"]
            },
            options: {
                bin: "vendor/bin/phpcs",
                standard: "PSR2"
            }
        }
    });

    require("load-grunt-tasks")(grunt);

    grunt.registerTask("testphp", ["phplint", "phpcs", "phpunit"]);
    grunt.registerTask("default", ["testphp"]);
};

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

    grunt.initConfig({
        pkg: grunt.file.readJSON("package.json"),
        watch: {
            php: {
                files: ["src/**/*.php", "tests/*.php"],
                tasks: ["testphp"]
            }
        },
        phpunit: {
            unit: {
                dir: "tests"
            },
            options: {
                bin: "vendor/bin/phpunit --coverage-text --coverage-html ./report",
                colors: true,
                testdox: false
            }
        },
        phplint: {
            options: {
                swapPath: "/tmp"
            },
            all: ["src/**/*.php", "tests/**/*.php"]
        },
        phpcs: {
            application: {
                src: ["src/**/*.php", "tests/**/*.php"]
            },
            options: {
                bin: "vendor/bin/phpcs",
                standard: "PSR2"
            }
        }
    });

    require("load-grunt-tasks")(grunt);

    grunt.registerTask("testphp", ["phplint", "phpcs", "phpunit"]);
    grunt.registerTask("default", ["testphp"]);
};

Watch only tests root folder


module.exports = function(grunt) {
    grunt.initConfig({
        bower: {
            main: {
                rjsConfig: 'js/main.js'
            }
        },
        jshint: {
            files: ['Gruntfile.js', 'js/**/*.js', 'specs/**/*.js'],
            options: {
                ignores: ['js/vendor/*.js']
            }
        },
        jasmine: {
            src: ['js/**/*.js'],
            options: {
                specs: ['specs/**/*.spec.js'],
                template: require('grunt-template-jasmine-requirejs'),
                templateOptions: {
                    requireConfigFile: 'js/main.js',
                    requireConfig: {
                        baseUrl: 'js'
                    }
                }
            }
        },
        watch: {
            files: ['Gruntfile.js', 'js/**/*.js', 'specs/**/*.js'],
            tasks: ['jshint', 'jasmine']
        }
    });

    grunt.loadNpmTasks('grunt-bower-requirejs');
    grunt.loadNpmTasks('grunt-contrib-jshint');
    grunt.loadNpmTasks('grunt-contrib-jasmine');
    grunt.loadNpmTasks('grunt-contrib-watch');

	grunt.registerTask('travis', ['jshint', 'jasmine']);
	grunt.registerTask('default', ['jshint', 'jasmine', 'watch']);
};

Fix up some tabs & spaces inconsistencies


module.exports = function (grunt) {
  grunt.initConfig({
    karma: {
      options: {
        basePath: '',
        frameworks: ['mocha', 'sinon-chai'],
        files: [
          { pattern: 'spec/fixtures/*.html',
            included: true },

          'bower_components/jquery/jquery.js',
          'bower_components/modula/lib/modula.js',
          'bower_components/sugar/release/sugar-full.development.js',
          'bower_components/underscore/underscore.js',
          'bower_components/backbone/backbone.js',

          'src/vtree_src/nodes_cache.coffee',
          'src/vtree_src/view_hooks.coffee',
          'src/vtree_src/view_node.coffee',
          'src/vtree_src/view_wrapper.coffee',
          'src/vtree_src/tree_manager.coffee',
          'src/vtree.coffee',

          'spec/**/*_spec.coffee'
        ],
        exclude: [],
        reporters: ['progress'],
        port: 9876,
        colors: true,
        autoWatch: true,
        browsers: ['PhantomJS'],
        captureTimeout: 60000,
        singleRun: false,
        coffeePreprocessor: {
          options: {
            bare: false
          }
        }
      },
      dev: {
        reporters: ['dots']
      },
      release: {
        singleRun: true
      },
      ci: {
        singleRun: true,
        browsers: ['PhantomJS']
      }
    }
  });

  grunt.loadNpmTasks('grunt-karma');
};

module.exports = function (grunt) {
  grunt.initConfig({
    karma: {
      options: {
        basePath: '',
        frameworks: ['mocha', 'sinon-chai'],
        files: [
          { pattern: 'spec/fixtures/*.html',
            included: true },

          'bower_components/jquery/dist/jquery.js',
          'bower_components/modula/lib/modula.js',
          'bower_components/sugar/release/sugar-full.development.js',
          'bower_components/underscore/underscore.js',
          'bower_components/backbone/backbone.js',

          'src/vtree_src/nodes_cache.coffee',
          'src/vtree_src/view_hooks.coffee',
          'src/vtree_src/view_node.coffee',
          'src/vtree_src/view_wrapper.coffee',
          'src/vtree_src/tree_manager.coffee',
          'src/vtree.coffee',

          'spec/**/*_spec.coffee'
        ],
        exclude: [],
        reporters: ['progress'],
        port: 9876,
        colors: true,
        autoWatch: true,
        browsers: ['PhantomJS'],
        captureTimeout: 60000,
        singleRun: false,
        coffeePreprocessor: {
          options: {
            bare: false
          }
        }
      },
      dev: {
        reporters: ['dots']
      },
      release: {
        singleRun: true
      },
      ci: {
        singleRun: true,
        browsers: ['PhantomJS']
      }
    }
  });

  grunt.loadNpmTasks('grunt-karma');
};

Fix for broken last version jquery destination


'use strict';

(function() {
  angular.module('ncsaas')
    .service('resourcesService', ['RawResource', 'RawProject', 'currentStateService', '$q', resourcesService]);

  function resourcesService(RawResource, RawProject, currentStateService, $q) {
    /*jshint validthis: true */
    var vm = this;
    vm.getResourcesList = getResourcesList;
    vm.getRawResourcesList = getRawResourcesList;

    function getRawResourcesList() {
      return RawResource.query();
    }
    function getResourcesList() {
      var deferred = $q.defer();
      currentStateService.getCustomer().then(function(response) {
        var customerName = response.name,
        /*jshint camelcase: false */
            resources = RawResource.query({customer_name: customerName}, init);

            function init(resources) {
              for (var i = 0; i < resources.length; i++) {
                initResourceProject(resources[i]);
              }
            }

            function initResourceProject(resource) {
              return RawProject.query(init);
              function init(projects) {
                for (var i = 0; i < projects.length; i++) {
                  if (projects[i].name === resource.project_name) {
                    var new_project = projects[i];
                  }
                }
                resource.project = new_project;
              }
            }

        deferred.resolve(resources);
      }, function(err) {
        deferred.reject(err);
      });
      return deferred.promise;
    }

  }
})();

(function() {
  angular.module('ncsaas')
    .factory('RawResource', ['ENV', '$resource', RawResource]);

  function RawResource(ENV, $resource) {
    return $resource(
      ENV.apiEndpoint + 'api/resources/', {},
      {
      }
    );
  }
})();

'use strict';

(function() {
  angular.module('ncsaas')
    .service('resourcesService', ['RawResource', 'RawProject', 'currentStateService', '$q', resourcesService]);

  function resourcesService(RawResource, RawProject, currentStateService, $q) {
    /*jshint validthis: true */
    var vm = this;
    vm.getResourcesList = getResourcesList;
    vm.getRawResourcesList = getRawResourcesList;

    function getRawResourcesList() {
      return RawResource.query();
    }
    function getResourcesList() {
      var deferred = $q.defer();
      currentStateService.getCustomer().then(function(response) {
        var customerName = response.name,
        /*jshint camelcase: false */
        resources = RawResource.query({customer_name: customerName});
        deferred.resolve(resources);
      }, function(err) {
        deferred.reject(err);
      });
      return deferred.promise;
    }

  }
})();

(function() {
  angular.module('ncsaas')
    .factory('RawResource', ['ENV', '$resource', RawResource]);

  function RawResource(ENV, $resource) {
    return $resource(
      ENV.apiEndpoint + 'api/resources/', {},
      {
      }
    );
  }
})();

Remove init function from resource service, project uuid gives API.


angular.module('bolt.auth', [])

.controller('AuthController', function ($scope, $window, $location, Auth) {
  $scope.user = {};

  $scope.signin = function () {
    Auth.signin($scope.user)
      .then(function (session) {
        $window.localStorage.setItem('com.bolt', session.token);
        $window.localStorage.setItem('username', session.username);
        $window.localStorage.setItem('firstName', session.firstName);
        $window.localStorage.setItem('runs', session.runs);
        $window.localStorage.setItem('achievements', session.achievements);
        $location.path('/');
      })
      .catch(function (error) {
        $scope.errorDetected = true;
        $scope.signinError = "Hmm... we can't seem to find that username in our DB. Could it be another?";
      });
  };

  $scope.signup = function () {
    Auth.signup($scope.user)
      .then(function (token) {
        $window.localStorage.setItem('com.bolt', token);
        $location.path('/createProfile');
      })
      .catch(function (error) {
        $scope.errorDetected = true;
        $scope.signupError = "Please enter both a username and a password";
      });
  };
});

angular.module('bolt.auth', [])

.controller('AuthController', function ($scope, $window, $location, Auth) {
  $scope.user = {};

  $scope.signin = function () {
    Auth.signin($scope.user)
      .then(function (session) {
        $window.localStorage.setItem('com.bolt', session.token);
        $window.localStorage.setItem('username', session.username);
        $window.localStorage.setItem('firstName', session.firstName);
        $window.localStorage.setItem('lastName', session.lastName);
        $window.localStorage.setItem('phone', session.phone);
        $window.localStorage.setItem('email', session.email);
        $window.localStorage.setItem('preferredDistance', session.preferredDistance);
        $window.localStorage.setItem('runs', session.runs);
        $window.localStorage.setItem('achievements', session.achievements);
        $location.path('/');
      })
      .catch(function (error) {
        $scope.errorDetected = true;
        $scope.signinError = "Hmm... we can't seem to find that username in our DB. Could it be another?";
      });
  };

  $scope.signup = function () {
    Auth.signup($scope.user)
      .then(function (token) {
        $window.localStorage.setItem('com.bolt', token);
        $location.path('/createProfile');
      })
      .catch(function (error) {
        $scope.errorDetected = true;
        $scope.signupError = "Please enter both a username and a password";
      });
  };
});

Add complete user data to session


/**
 * Compile:documentation task.
 * Create the extension documentation/styleguide.
 */

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

	grunt.registerTask("compile:docs", function() {
		// Create the living CSS styleguide.
		grunt.task.run("styleguide");

		// Prefix the docs stylesheet.
		grunt.task.run("autoprefixer:styleguide");

		// Compile the new stylesheets.
		grunt.task.run("compile:css:dev");
	});
};

/**
 * Compile:documentation task.
 * Create the extension documentation/styleguide.
 */

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

	grunt.registerTask("compile:docs", function() {
		// Create the living CSS styleguide.
		grunt.task.run("styleguide");

		// Prefix the docs stylesheet.
		grunt.task.run("autoprefixer:docs");

		// Compile the new stylesheets.
		grunt.task.run("compile:css:dev");
	});
};

Set the correct new name for the docs autoprefixer sub-task


ui.CharacterStatusDialog = function(template, character) {
  ui.Dialog.call(this, template);

  this.name = ui.label(this, '.label.name');
  this.hp = ui.progressbar(this, '.progressbar.health');
  this.mp = ui.progressbar(this, '.progressbar.mana');
  this.xp = ui.progressbar(this, '.progressbar.exp');

  if (character) {
    this.setCharacter(character);
  }
}

ui.CharacterStatusDialog.prototype = Object.create(ui.Dialog.prototype);
ui.CharacterStatusDialog.prototype._character = null;

ui.CharacterStatusDialog.prototype.setCharacter = function(character) {
  this._character = character;
  this._update();
};

ui.CharacterStatusDialog.prototype._update = function() {
  this.name.text(this._character.name);

  // TODO: real hp values
  this.hp.min(0);
  this.hp.max(this._character.stats.getMaxHp());
  this.hp.value(this._character.hp);

  // TODO real mp values
  this.mp.min(0);
  this.mp.max(this._character.stats.getMaxMp());
  this.mp.value(this._character.mp);

  // TODO real xp values
  this.xp.min(0);
  this.xp.max(100);
  this.xp.value(10);
};

ui.characterStatusDialog = function(character) {
  return new ui.CharacterStatusDialog('#dlgCharacterStatus', character);
};

ui.CharacterStatusDialog = function(template, character) {
  ui.Dialog.call(this, template);

  this.name = ui.label(this, '.label.name');
  this.hp = ui.progressbar(this, '.progressbar.health');
  this.mp = ui.progressbar(this, '.progressbar.mana');
  this.xp = ui.progressbar(this, '.progressbar.exp');

  if (character) {
    this.setCharacter(character);
  }
}

ui.CharacterStatusDialog.prototype = Object.create(ui.Dialog.prototype);
ui.CharacterStatusDialog.prototype._character = null;

ui.CharacterStatusDialog.prototype.setCharacter = function(character) {
  this._character = character;
  this._update();
};

ui.CharacterStatusDialog.prototype._update = function() {
  this.name.text(this._character.name);

  this.hp.max(this._character.stats.getMaxHp());
  this.hp.value(this._character.hp);

  this.mp.max(this._character.stats.getMaxMp());
  this.mp.value(this._character.mp);

  this.xp.max(1000); // TODO: Real max xp
  this.xp.value(this._character.xp);
};

ui.characterStatusDialog = function(character) {
  return new ui.CharacterStatusDialog('#dlgCharacterStatus', character);
};

Add real XP value to ChracterStatusDialog.


/**
 * The application initalizer.
 */
function app() {
  alert('test');
}

export default app;

import $ from 'jquery';
import themes from './themes';

/**
 * The application initalizer.
 */
function app() {
  $(function load() {
    themes();
  });
}

export default app;

Load themes when DOM is ready


Package.describe({
  summary: "Given the set of the constraints, picks a satisfying configuration",
  version: "1.0.15"
});

Npm.depends({
  'mori': '0.2.6'
});

Package.on_use(function (api) {
  api.export('ConstraintSolver');
  api.use(['underscore', 'ejson', 'check', 'package-version-parser',
           'binary-heap', 'random'], 'server');
  api.add_files(['constraint-solver.js', 'resolver.js', 'constraints-list.js',
                 'resolver-state.js', 'priority-queue.js'], ['server']);
});

Package.on_test(function (api) {
  api.use('constraint-solver', ['server']);
  api.use(['tinytest', 'minimongo', 'package-version-parser']);
  // data for big benchmarky tests
  api.add_files('test-data.js', ['server', 'client']);
  api.add_files('constraint-solver-tests.js', ['server']);
  api.add_files('resolver-tests.js', ['server']);
  api.use('underscore');
});

Package.describe({
  summary: "Given the set of the constraints, picks a satisfying configuration",
  version: "1.0.15"
});

Npm.depends({
  'mori': '0.2.6'
});

Package.on_use(function (api) {
  api.export('ConstraintSolver');
  api.use(['underscore', 'ejson', 'check', 'package-version-parser',
           'binary-heap', 'random'], 'server');
  api.add_files(['constraint-solver.js', 'resolver.js', 'constraints-list.js',
                 'resolver-state.js', 'priority-queue.js'], ['server']);
});

Package.on_test(function (api) {
  api.use('constraint-solver', ['server']);
  api.use(['tinytest', 'minimongo', 'package-version-parser']);
  // data for big benchmarky tests
  api.add_files('test-data.js', ['server']);
  api.add_files('constraint-solver-tests.js', ['server']);
  api.add_files('resolver-tests.js', ['server']);
  api.use('underscore');
});

Revert accidental committal of client test-data


import {PropTypes} from 'react'

function lazy(fn) {
  let cachedFn
  return (...args) => (cachedFn || (cachedFn = fn()))(...args)
}

const field = PropTypes.shape({
  name: PropTypes.string,
  title: PropTypes.string,
  description: PropTypes.string,
  placeholder: PropTypes.string,
  type: PropTypes.string,
  to: lazy(() => PropTypes.arrayOf(field)),
  of: lazy(() => PropTypes.arrayOf(field))
})

const type = PropTypes.shape({
  fields: PropTypes.arrayOf(field),
  alias: PropTypes.string,
  isPrimitive: PropTypes.bool
})

const validation = {
  fields: PropTypes.objectOf(lazy(() => validation)),
  messages: PropTypes.arrayOf(PropTypes.shape({
    type: PropTypes.oneOf(['error', 'warning']),
    id: PropTypes.string,
    message: PropTypes.string
  }))
}

const schema = PropTypes.objectOf(type)

export default {
  type,
  schema,
  field,
  validation
}

import {PropTypes} from 'react'

function lazy(fn) {
  let cachedFn
  return (...args) => (cachedFn || (cachedFn = fn()))(...args)
}

const field = PropTypes.shape({
  name: PropTypes.string,
  title: PropTypes.string,
  description: PropTypes.string,
  placeholder: PropTypes.string,
  type: PropTypes.string,
  to: lazy(() => PropTypes.arrayOf(field)),
  of: lazy(() => PropTypes.arrayOf(field))
})

const type = PropTypes.shape({
  fields: PropTypes.arrayOf(field),
  alias: PropTypes.string,
  isPrimitive: PropTypes.bool
})

const validation = {
  fields: PropTypes.objectOf(lazy(() => PropTypes.shape(validation))),
  messages: PropTypes.arrayOf(PropTypes.shape({
    type: PropTypes.oneOf(['error', 'warning']),
    id: PropTypes.string,
    message: PropTypes.string
  }))
}

const schema = PropTypes.objectOf(type)

export default {
  type,
  schema,
  field,
  validation
}

Fix proptypes error related to validation


import Motion from '../motion';
import Tween from '../tween';
import { rAF } from '../concurrency-helpers';

export default class Opacity extends Motion {
  constructor(sprite, opts) {
    super(sprite, opts);
    this.prior = null;
    this.opacityTween = null;
    this.opacityFrom = opts && opts.initialOpacity || 0;
    this.opacityTo = opts && opts.initialOpacity || 1;
    if (opts && opts.duration != null) {
      this.duration = opts.duration;
    }
  }

  interrupted(motions) {
    this.prior = motions.find(m => m instanceof this.constructor);
  }

  * animate() {
    let { sprite, duration } = this;
    this.opacityTween = new Tween(this.opacityFrom, this.opacityTo, duration);

    while (true) {
      yield rAF();
      sprite.applyStyles({
        opacity: this.opacityTween.currentValue
      });
      if (this.opacityTween.done) {
        break;
      }
    }
  }
}

import Motion from '../motion';
import Tween from '../tween';
import { rAF } from '../concurrency-helpers';

export default class Opacity extends Motion {
  constructor(sprite, opts) {
    super(sprite, opts);
    this.prior = null;
    this.opacityTween = null;
    this.opacityFrom = 0;
    this.opacityTo = 1;
    if (opts && opts.duration != null) {
      this.duration = opts.duration;
    }
    if (opts && opts.initialOpacity != null) {
      this.opacityFrom = opts.initialOpacity;
    }
    if (opts && opts.finalOpacity != null) {
      this.opacityTo = opts.finalOpacity;
    }
  }

  interrupted(motions) {
    this.prior = motions.find(m => m instanceof this.constructor);
  }

  * animate() {
    let { sprite, duration } = this;
    this.opacityTween = new Tween(this.opacityFrom, this.opacityTo, duration);

    while (true) {
      yield rAF();
      sprite.applyStyles({
        opacity: this.opacityTween.currentValue
      });
      if (this.opacityTween.done) {
        break;
      }
    }
  }
}

Fix finalOpacity unable to be 0


basePath = '..';

files = [
  JASMINE,
  JASMINE_ADAPTER,
  'components/jquery/jquery.js',
  'components/jquery-ui/ui/jquery-ui.custom.js',
  'components/angular/angular.js',
  'components/angular-mocks/angular-mocks.js',
  'src/bootstrap-growl.js',
  'test/*.spec.js'
];

singleRun = true;

browsers = [ 'Chrome' ];

basePath = '..';

files = [
  JASMINE,
  JASMINE_ADAPTER,
  'components/jquery/jquery.js',
  'components/jquery-ui/ui/jquery-ui.js',
  'components/angular/angular.js',
  'components/angular-mocks/angular-mocks.js',
  'src/bootstrap-growl.js',
  'test/*.spec.js'
];

preprocessors = {
  'src/bootstrap-growl.js': 'coverage'
};

singleRun = true;

browsers = [ 'Chrome' ];

Add coverage, fix jquery-ui component line


import React from 'react';
import { TALENTS_PAGES } from '../constants';

export default class SecondaryToolbar extends React.Component {

  render() {
    const { page, navigateTo } = this.props;

    return (

 { navigateTo(TALENTS_PAGES.POPULARITY); } }>POPULARITY

 { navigateTo(TALENTS_PAGES.WINRATE); } }>WIN RATE

    );
  }
};

import React from 'react';
import { TALENTS_PAGES } from '../constants';

class SecondaryToolbar extends React.Component {

  constructor() {
    super();

    this.navigatePopularity = this.navigatePopularity.bind(this);
    this.navigateWinrate = this.navigateWinrate.bind(this);
  }

  navigatePopularity() {
    this.props.navigateTo(TALENTS_PAGES.POPULARITY);
  }

  navigateWinrate() {
    this.props.navigateTo(TALENTS_PAGES.WINRATE);
  }

  render() {
    const { page } = this.props;

    return (

POPULARITY

WIN RATE

    );
  }
}

SecondaryToolbar.propTypes = {
  navigateTo: React.PropTypes.func,
  page: React.PropTypes.string,
};

export default SecondaryToolbar;

Remove jsx binds from secondary toolbar


// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file. JavaScript code in this file should be added after the last require_* statement.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery
//= require jquery_ujs
//= require_tree .
//= require decidim

// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file. JavaScript code in this file should be added after the last require_* statement.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require_tree .
//= require decidim

Fix a problem with rails-ujs and Decidim 0.15.2


import Ember  from 'ember';
import config from '../config/environment';
import { getContext, generateError } from 'ember-cli-bugsnag/utils/errors';
import { getMetaData } from '../utils/bugsnag';
import Bugsnag from 'bugsnag';

export default {
  name: 'bugsnag-error-service',

  initialize: function(instance) {
    var content = '';
    var bugsnagConfig;
    var envArray;

    if (!config.bugsnag) {
      console.warn('`config.bugsnag` is not defined, using environment variables instead.');

      var envApiKey = process.env['BUGSNAG_API_KEY'];
      var envReleases = process.env['BUGSNAG_NOTIFY_RELEASE'];
      var envEndpoint = process.env['BUGSNAG_ENDPOINT'];

      if (!envApiKey || !envReleases) {
        console.error('Environment variables `BUGSNAG_API_KEY` and `BUGSNAG_NOTIFY_RELEASE` are not specified. Bugsnag will not be injected.');

        return '';
      }

      bugsnagConfig = {
        apiKey: envApiKey,
        notifyReleaseStages: envReleases.split(','),
        releaseStage: process.env['BUGSNAG_RELEASE_STAGE'],
        endpoint: envEndpoint
      };
    } else {
      bugsnagConfig = config.bugsnag;
    }

    if (!bugsnagConfig.notifyReleaseStages) {
      bugsnagConfig.notifyReleaseStages = ["production"]
    }

    Object.keys(bugsnagConfig).forEach(function(key) {
      Bugsnag[key] = bugsnagConfig[key];
    });
  }
};

import Ember  from 'ember';
import config from '../config/environment';
import Bugsnag from 'bugsnag';

export default {
  name: 'ember-cli-bugsnag',

  initialize: function(instance) {
    let bugsnagConfig;

    if (!config.bugsnag) {
      console.info('[ember-cli-bugsnag] `config.bugsnag` is not defined, using environment variables instead.');

      bugsnagConfig = {
        apiKey: process.env['BUGSNAG_API_KEY'],
        notifyReleaseStages: process.env['BUGSNAG_NOTIFY_RELEASE'].split(','),
        releaseStage: process.env['BUGSNAG_RELEASE_STAGE'],
        endpoint: process.env['BUGSNAG_ENDPOINT']
      };
    } else {
      bugsnagConfig = config.bugsnag;
    }

    if (!bugsnagConfig.apiKey) {
      console.error('[ember-cli-bugsnag] Bugsnag reporting could not be started because `apiKey` was not configred. Please add it to `bugsnag.apiKey` in `config/environment` or define environment variable `BUGSNAG_API_KEY`');
      return;
    }

    if (!bugsnagConfig.notifyReleaseStages) {
      console.warn('[ember-cli-bugsnag] Notify release stages not defined in configuration, defaulting to `["production"]`.');
      bugsnagConfig.notifyReleaseStages = ["production"]
    }

    Object.keys(bugsnagConfig).forEach(function(key) {
      Bugsnag[key] = bugsnagConfig[key];
    });
  }
};

Tidy up initialization code and error messages