import { uuid } from './util';
import * as chartFns from './chart';

const FORMAT_YAML = 'yaml';

let chartScriptFn = () => {};

module.exports = {
    book: {
        assets: './assets'
    },
    hooks: {
        init: function () {
            let pluginConfig = (this.options.pluginsConfig || {}).chart || {};
            let type = pluginConfig.type;
            chartScriptFn = chartFns[type];
        }
    },
    blocks: {
        chart: {
            process: function (blk) {
                let id = uuid();
                let body = {};
                try {
                    // get string in {% chart %}
                    let bodyString = blk.body.trim();
                    if (blk.kwargs.format === FORMAT_YAML) {
                        // load yaml into body:
                        body = require('js-yaml').safeLoad(bodyString);
                    } else {
                        // just think it as json:
                        // TODO: Avoiding `eval`
                        // https://github.com/rollup/rollup/wiki/Troubleshooting#avoiding-eval
                        eval('body=' + bodyString);
                    }
                } catch (e) {
                    console.error(e);
                }
                let scripts = chartScriptFn(id, body);
                return `

`;
            }
        }
    }
};

import { uuid } from './util';
import * as chartFns from './chart';

const FORMAT_YAML = 'yaml';

let chartScriptFn = () => {};

module.exports = {
    book: {
        assets: './assets'
    },
    hooks: {
        init: function () {
            let pluginConfig = this.config.get('pluginsConfig.chart');
            let type = pluginConfig.type;
            chartScriptFn = chartFns[type];
        }
    },
    blocks: {
        chart: {
            process: function (blk) {
                let id = uuid();
                let body = {};
                try {
                    // get string in {% chart %}
                    let bodyString = blk.body.trim();
                    if (blk.kwargs.format === FORMAT_YAML) {
                        // load yaml into body:
                        body = require('js-yaml').safeLoad(bodyString);
                    } else {
                        // just think it as json:
                        // TODO: Avoiding `eval`
                        // https://github.com/rollup/rollup/wiki/Troubleshooting#avoiding-eval
                        eval('body=' + bodyString);
                    }
                } catch (e) {
                    console.error(e);
                }
                let scripts = chartScriptFn(id, body);
                return `

`;
            }
        }
    }
};

Use new GitBook 3 API to get plugin configuration


/* eslint-disable no-console */
console.log('hi');
// Old ES5 way of creating a Class Component
// var HelloWorld = React.createClass({
//   render: function() {
//     return (
//
Hello World!

//     );
//   };
// });

/* eslint-disable no-console */
console.log('hi');
// Old ES5 way of creating a Class Component
// var HelloWorld = React.createClass({
//   render: function() {
//     return (
//
Hello world!

//     );
//   };
// });

Fix example es5 create class


import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom'
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';
import ReactGA from 'react-ga';

import rootReducer from './reducers/root';
import registerServiceWorker from './registerServiceWorker';

import App from './containers/App';

import 'semantic-ui-css/semantic.min.css';
import './index.css';

ReactGA.initialize('UA-106072204-1');

function logPageView() {
  ReactGA.set({ page: window.location.pathname + window.location.search });
  ReactGA.pageview(window.location.pathname + window.location.search);
}

const composeEnhancers = composeWithDevTools({});
const store = createStore(rootReducer, /* preloadedState, */ composeEnhancers(
  applyMiddleware(thunk),
));

ReactDOM.render(
  <Provider store={store}>
    <BrowserRouter onUpdate={logPageView}>
      <App/>
    </BrowserRouter>
  </Provider>, document.getElementById('root')
);

registerServiceWorker();

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom'
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';
import ReactGA from 'react-ga';

import rootReducer from './reducers/root';
import registerServiceWorker from './registerServiceWorker';

import App from './containers/App';

import 'semantic-ui-css/semantic.min.css';
import './index.css';

ReactGA.initialize('UA-106072204-1', { debug: true });

function logPageView() {
  ReactGA.set({ page: window.location.pathname + window.location.search });
  ReactGA.pageview(window.location.pathname + window.location.search);
}

const composeEnhancers = composeWithDevTools({});
const store = createStore(rootReducer, /* preloadedState, */ composeEnhancers(
  applyMiddleware(thunk),
));

ReactDOM.render(
  <Provider store={store}>
    <BrowserRouter onUpdate={logPageView}>
      <App/>
    </BrowserRouter>
  </Provider>, document.getElementById('root')
);

registerServiceWorker();

Set react-ga to debug mode


module.exports = () => {
  return {
    use: [
      'postcss-flexbugs-fixes',
      'autoprefixer',
    ],
    map: {
      inline: false,
      annotation: true,
      sourcesContent: true,
    },
    plugins: {
      autoprefixer: {
        cascade: false,
        // https://github.com/twbs/bootstrap/blob/v4.0.0/package.json#L136-L147
        browsers: [
          'last 1 major version',
          '>= 1%',
          'Chrome >= 45',
          'Firefox >= 38',
          'Edge >= 12',
          'Explorer >= 10',
          'iOS >= 9',
          'Safari >= 9',
          'Android >= 4.4',
          'Opera >= 30',
        ],
      },
    },
  };
};

module.exports = () => {
  return {
    use: [
      'postcss-flexbugs-fixes',
      'autoprefixer',
    ],
    map: {
      inline: false,
      annotation: true,
      sourcesContent: true,
    },
    plugins: {
      autoprefixer: {
        cascade: false,
        // https://github.com/twbs/bootstrap/blob/v4.0.0/package.json#L136-L147
        overrideBrowserslist: [
          'last 1 major version',
          '>= 1%',
          'Chrome >= 45',
          'Firefox >= 38',
          'Edge >= 12',
          'Explorer >= 10',
          'iOS >= 9',
          'Safari >= 9',
          'Android >= 4.4',
          'Opera >= 30',
        ],
      },
    },
  };
};

Change autoprefix browser specific key


export default class Library {
  constructor() {
    this._name = 'Library';
  }
  get name() {
    return this._name;
  }
}

export default class EasyLog {
  constructor(level) {
    // Logging levels:
    this.LOG = {
      INFO: 4,
      DEBUG: 3,
      WARN: 2,
      ERROR: 1,
      DISABLED: 0
    };
    this.setLevel(level);
  }

  getLevel() {
    return this.level;
  }

  setLevel(level = this.LOG.INFO) {
    if (typeof level !== 'number') {
      throw new Error(`SetLevel method expected a Number as argument but got ${typeof level} instead`);
    }
    this.level = level;
  }

  getConsole(method) {
    return console[method] ? console[method] : console.log;
  }

  shouldLog(logLevel) {
    return this.level > 0 && this.level <= logLevel;
  }

  beautify(value) {
    return JSON.stringify(value, null, 2);
  }

  info(message) {
    if (this.shouldLog(this.LOG.INFO)) {
      const console = this.getConsole('info');
      console(`INFO: ${message}`);
    }
  }

  debug(message) {
    if (this.shouldLog(this.LOG.DEBUG)) {
      const console = this.getConsole('log'); // Yeah, stupid AF, but for consistency...
      console(`DEBUG: ${message}`);
    }
  }

  warn(message) {
    if (this.shouldLog(this.LOG.WARN)) {
      const console = this.getConsole('warn');
      console(`WARNING: ${message}`);
    }
  }

  error(message) {
    if (this.shouldLog(this.LOG.ERROR)) {
      const console = this.getConsole('error');
      console(`ERROR: ${message}`);
    }
  }
}

Implement basic version of EasyLog


'use babel'

/* @flow */

export default function promisify(callback: Function): Function {
  return function promisified(){
    const parameters = arguments
    const parametersLength = arguments.length + 1
    return new Promise((resolve, reject) => {
      Array.prototype.push.call(parameters, function(error, data) {
        if (error) {
          reject(error)
        } else resolve(data)
      })
      if (parametersLength === 1) {
        callback.call(this, parameters[0])
      } else if (parametersLength === 2) {
        callback.call(this, parameters[0], parameters[1])
      } else if (parametersLength === 3) {
        callback.call(this, parameters[0], parameters[1], parameters[2])
      } else if (parametersLength === 4) {
        callback.call(this, parameters[0], parameters[1], parameters[2], parameters[3])
      } else {
        callback.apply(this, parameters)
      }
    })
  }
}

'use babel'

/* @flow */

module.exports = function promisify(callback: Function): Function {
  return function promisified(){
    const parameters = arguments
    const parametersLength = arguments.length + 1
    return new Promise((resolve, reject) => {
      Array.prototype.push.call(parameters, function(error, data) {
        if (error) {
          reject(error)
        } else resolve(data)
      })
      if (parametersLength === 1) {
        callback.call(this, parameters[0])
      } else if (parametersLength === 2) {
        callback.call(this, parameters[0], parameters[1])
      } else if (parametersLength === 3) {
        callback.call(this, parameters[0], parameters[1], parameters[2])
      } else if (parametersLength === 4) {
        callback.call(this, parameters[0], parameters[1], parameters[2], parameters[3])
      } else {
        callback.apply(this, parameters)
      }
    })
  }
}

Use common js exports instead of es exports


// ./src/index.js
import React from 'react';
import ReactDOM from 'react-dom';

import { AppContainer } from 'react-hot-loader';
// AppContainer is a necessary wrapper component for HMR

import App from './components/App';

const render = (Component) => {
  ReactDOM.render(
    <AppContainer>
      <Component/>
    </AppContainer>,
    document.getElementById('root')
  );
};

render(App);

// Hot Module Replacement API
if (module.hot) {
  module.hot.accept('./components/App', () => {
    render(App)
  });
}

// ./src/index.js
import React from 'react';
import ReactDOM from 'react-dom';

import { AppContainer } from 'react-hot-loader';
// AppContainer is a necessary wrapper component for HMR

import App from './components/App';

const render = (Component) => {
  ReactDOM.render(
    <AppContainer>
      <Component/>
    </AppContainer>,
    document.getElementById('root')
  );
};

render(App);

// Hot Module Replacement API
if (module.hot) module.hot.accept('./components/App', () => render(App));

Make hot reload line more succinct


var poker = require('./node-poker');

var table = new poker.Table(50,100,2,10,100,1000);

table.AddPlayer('bob',1000)
table.AddPlayer('jane',1000)
table.AddPlayer('dylan',1000)
table.AddPlayer('john',1000)

table.StartGame()

table.players[1].Call();
table.players[2].Call();
table.players[3].Call();
table.players[0].Call();
table.players[1].Call();
table.players[2].Call();
table.players[3].Call();
table.players[0].Call();
table.players[1].Call();
table.players[2].Call();
table.players[3].Call();
table.players[0].Call();
table.players[1].Call();
table.players[2].Call();
table.players[3].Call();
table.players[0].Call();

table.initNewRound()

console.log(table);

var poker = require('./lib/node-poker');

var table = new poker.Table(50,100,2,10,100,1000);

table.AddPlayer('bob',1000)
table.AddPlayer('jane',1000)
table.AddPlayer('dylan',1000)
table.AddPlayer('john',1000)

table.StartGame()

table.players[1].Call();
table.players[2].Call();
table.players[3].Call();
table.players[0].Call();
table.players[1].Call();
table.players[2].Call();
table.players[3].Call();
table.players[0].Call();
table.players[1].Call();
table.players[2].Call();
table.players[3].Call();
table.players[0].Call();
table.players[1].Call();
table.players[2].Call();
table.players[3].Call();
table.players[0].Call();

table.initNewRound()

console.log(table);

Fix poker lib getting referenced incorrectly


// Karma configuration file, see link for more information
// https://karma-runner.github.io/0.13/config/configuration-file.html

module.exports = function (config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine', '@angular/cli'],
    plugins: [
      require('karma-jasmine'),
      require('karma-chrome-launcher'),
      require('karma-jasmine-html-reporter'),
      require('karma-coverage-istanbul-reporter'),
      require('@angular/cli/plugins/karma')
    ],
    client:{
      clearContext: false // leave Jasmine Spec Runner output visible in browser
    },
    files: [
      { pattern: './src/test.ts', watched: false }
    ],
    preprocessors: {
      './src/test.ts': ['@angular/cli']
    },
    mime: {
      'text/x-typescript': ['ts','tsx']
    },
    coverageIstanbulReporter: {
      reports: [ 'html', 'lcovonly' ],
      fixWebpackSourcePaths: true
    },
    angularCli: {
      environment: 'dev'
    },
    reporters: config.angularCli && config.angularCli.codeCoverage
              ? ['progress', 'coverage-istanbul']
              : ['progress', 'kjhtml'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['Chrome'],
    singleRun: false
  });
};

// Karma configuration file, see link for more information
// https://karma-runner.github.io/0.13/config/configuration-file.html

module.exports = function (config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine', '@angular/cli'],
    plugins: [
      require('karma-jasmine'),
      require('karma-chrome-launcher'),
      require('karma-jasmine-html-reporter'),
      require('karma-coverage-istanbul-reporter'),
      require('@angular/cli/plugins/karma')
    ],
    client:{
      clearContext: false // leave Jasmine Spec Runner output visible in browser
    },
    files: [
      { pattern: './src/test.ts', watched: false }
    ],
    preprocessors: {
      './src/test.ts': ['@angular/cli']
    },
    mime: {
      'text/x-typescript': ['ts','tsx']
    },
    coverageIstanbulReporter: {
      reports: [ 'html', 'lcovonly' ],
      fixWebpackSourcePaths: true
    },
    angularCli: {
      environment: 'dev'
    },
    reporters: config.angularCli && config.angularCli.codeCoverage
              ? ['progress', 'coverage-istanbul']
              : ['progress', 'kjhtml'],
    port: 8081,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['Chrome'],
    singleRun: false
  });
};

Change Karma port to 8081


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

import {processIM, processChannel, processGroup, saveData} from './commons.js';
import PleasantProgress from 'pleasant-progress';

export function slackHistoryExport(args) {
  const progress = new PleasantProgress();
  progress.start('working');
  if(args.type === 'dm') {
    processIM(args.token, args.username).then(history => {
      saveData(history,args,progress,args.filename);
    }).catch(error => {
      console.log(error.stack);
      progress.stop();
      process.exit(1);
    });
  } else if (args.type === 'channel') {
    processChannel(args.token, args.channel).then(history => {
      saveData(history,args, progress,args.channel);
    }).catch((error) => {
      progress.stop();
      console.log(error);
      console.log(error.stack);
    });
  } else if (args.type === 'group') {
    processGroup(args.token, args.group).then(history => {
      saveData(history,args, progress,args.group);
    }).catch((error) => {
      progress.stop();
      console.log(error);
      console.log(error.stack);
    });
  }
}

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

import {processIM, processChannel, processGroup, saveData} from './commons.js';
import PleasantProgress from 'pleasant-progress';

export function slackHistoryExport(args) {
  const progress = new PleasantProgress();
  progress.start('working');
  if(args.type === 'dm') {
    processIM(args.token, args.username).then(history => {
      saveData(history,args,progress,args.username);
    }).catch(error => {
      console.log(error.stack);
      progress.stop();
      process.exit(1);
    });
  } else if (args.type === 'channel') {
    processChannel(args.token, args.channel).then(history => {
      saveData(history,args, progress,args.channel);
    }).catch((error) => {
      progress.stop();
      console.log(error);
      console.log(error.stack);
    });
  } else if (args.type === 'group') {
    processGroup(args.token, args.group).then(history => {
      saveData(history,args, progress,args.group);
    }).catch((error) => {
      progress.stop();
      console.log(error);
      console.log(error.stack);
    });
  }
}

Fix issue with missing username


import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import moment from './moment';

import App from './components/App';
import rootReducer from './reducers';
import { fetchBookings } from './actions';

import './index.css';

const today = moment().startOf("day"); // today at 00:00

const initialState = {
  active: false,
  loading: false,
  error: null,
  date: today,
  diaryDate: today, // holds the date of the current roomDiaries
  hours: 0,
  between: [
    "08:00",
    "14:00"
  ],
  capacity: [],
  roomDiaries: {}
};

const store = createStore(
  rootReducer,
  initialState,
  applyMiddleware(thunk)
);

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

store.dispatch(fetchBookings(today));

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import moment from './moment';

import App from './components/App';
import rootReducer from './reducers';
import { fetchBookings } from './actions';

import './index.css';

const today = moment().startOf("day"); // today at 00:00

const initialState = {
  active: false,
  loading: true,
  error: null,
  date: today,
  diaryDate: today, // holds the date of the current roomDiaries
  hours: 0,
  between: [
    "08:00",
    "14:00"
  ],
  capacity: [],
  roomDiaries: {}
};

const store = createStore(
  rootReducer,
  initialState,
  applyMiddleware(thunk)
);

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

store.dispatch(fetchBookings(today));

Set loading: true on app start


import { GitHub } from './GitHub';
import * as _ from 'lodash';

const fs = require('fs');

let hub = null;
let jsonFile = null;

let keys = [
  'name', 'description', 'updated_at', 'language',
  'stargazers_count', 'forks_count', 'contributor_count'
];

if (process.argv.length > 2) {
  let user = process.argv[2];
  let token = process.argv[3];
  let userAgent = process.argv[4];
  jsonFile = process.argv[5];
  hub = new GitHub(user, token, userAgent, keys);
}

hub.parseUserRepositories()
  .then((userJSON) => {
    hub.parseOrganizationRepositories()
      .then((orgJSON) => {
        let minimal = {};
        minimal['repositories'] = userJSON;
        _.each(orgJSON, (value, key) => {
          minimal[key] = value;
        });
        console.log('Updating github.json');
        fs.writeFile(jsonFile, JSON.stringify(minimal, null, 2), (err) => {
          if (err) {
            console.log(err);
          }
        });
      }).catch((err) => console.log(err));
  }).catch((err) => console.log(err));

import { GitHub } from './GitHub';
import * as _ from 'lodash';

const fs = require('fs');

let hub = null;
let jsonFile = null;

let keys = [
  'name', 'description', 'updated_at', 'html_url', 'language',
  'stargazers_count', 'forks_count', 'contributor_count'
];

if (process.argv.length > 2) {
  let user = process.argv[2];
  let token = process.argv[3];
  let userAgent = process.argv[4];
  jsonFile = process.argv[5];
  hub = new GitHub(user, token, userAgent, keys);
}

hub.parseUserRepositories()
  .then((userJSON) => {
    hub.parseOrganizationRepositories()
      .then((orgJSON) => {
        let minimal = {};
        minimal['repositories'] = userJSON;
        _.each(orgJSON, (value, key) => {
          minimal[key] = value;
        });
        console.log('Updating github.json');
        fs.writeFile(jsonFile, JSON.stringify(minimal, null, 2), (err) => {
          if (err) {
            console.log(err);
          }
        });
      }).catch((err) => console.log(err));
  }).catch((err) => console.log(err));

Add html_url into default keys


module.exports = function(config){
  config.set({

    basePath : './',

    files : [
      'app/bower_components/angular/angular.js',
      'app/bower_components/angular-route/angular-route.js',
      'app/bower_components/angular-mocks/angular-mocks.js',
      'app/components/**/*.js',
      'app/view*/**/*.js'
    ],

    autoWatch : true,

    frameworks: ['jasmine'],

    browsers : ['Chrome'],

    plugins : [
            'karma-chrome-launcher',
            'karma-firefox-launcher',
            'karma-jasmine',
            'karma-junit-reporter'
            ],

    junitReporter : {
      outputFile: 'test_out/unit.xml',
      suite: 'unit'
    }

  });
};

module.exports = function(config){
  config.set({

    basePath : './',

    files : [
      'app/bower_components/angular/angular.js',
      'app/bower_components/angular-route/angular-route.js',
      'app/bower_components/angular-mocks/angular-mocks.js',
      'app/components/version/*.js',
      'app/view*/**/*.js'
    ],

    autoWatch : true,

    frameworks: ['jasmine'],

    browsers : ['Chrome'],

    plugins : [
            'karma-chrome-launcher',
            'karma-firefox-launcher',
            'karma-jasmine',
            'karma-junit-reporter'
            ],

    junitReporter : {
      outputFile: 'test_out/unit.xml',
      suite: 'unit'
    }

  });
};

Exclude cordova files from karma


const watch = require(`${__dirname}/watch.js`);
const datastore = require(`${__dirname}/datastore`);
const express = require('express');
const app = express();

app.use('/', express.static(`${__dirname}/../public`));

app.get('/api/full', function (req, res) {
    res.json(datastore.full);
});
app.get('/api/delta', function (req, res) {
    res.json(datastore.delta);
});
app.get('/api/merc/:id', function (req, res) {
    if (req.params.id && datastore.full.Merchants.list[req.params.id]) {
        res.json(datastore.full.Merchants.list[req.params.id]);
    } else {
        res.status(404).send('Not found.');
    }
});
app.listen(3088, 'localhost', function () {
    console.log('Server started on port 3088');
});

const watch = require(`${__dirname}/watch.js`);
const datastore = require(`${__dirname}/datastore`);
const express = require('express');
const app = express();

app.use('/', express.static(`${__dirname}/../public`));

app.get('/api/full', function (req, res) {
    res.json(datastore.full);
});
app.get('/api/delta', function (req, res) {
    res.json(datastore.delta);
});
app.get('/api/merc/:id', function (req, res) {
    if (req.params.id && datastore.full.Merchants.list[req.params.id]) {
        res.json(datastore.full.Merchants.list[req.params.id]);
    } else {
        res.status(404).json({"error": "Not found."});
    }
});
app.listen(3088, 'localhost', function () {
    console.log('Server started on port 3088');
});

Send the error message as JSON


// Layout
import Layout from './components/Layout'

// Main Header
import MainHeaderComponents from './components/MainHeader'

// Main Sidebar
import MainSidebarComponents from './components/MainSidebar'

// Control Sidebar
import ControlSidebarComponents from './components/ControlSidebar'

export {Layout}

export {MainHeaderComponents}

export {MainSidebarComponents}

export {ControlSidebarComponents}

// Layout
export {default as Layout} from './components/Layout'

// Main Header
export {default as MainHeader} from './components/MainHeader'

// Main Sidebar
export {default as MainSidebar} from './components/MainSidebar'

// Control Sidebar
export {default as ControlSidebar} from './components/ControlSidebar'

Change the way components are exported


import ru from 'moment/locale/ru';
import lazysizes from 'lazysizes';

import Locations from './locations';
import Options from './options';

import LocationChooser from './location-chooser';
import EventsView from './events';

import {show, hide} from './utils';

document.addEventListener('DOMContentLoaded', function(event) {
  const options = new Options();
  const locations = new Locations();

  Promise.all([
    locations.fetch(),
    options.fetch()
  ]).then(() => {
    const locationContainer = document.querySelector('#city');
    const viewContainer = document.querySelector('#view-container');

    const locationChooser = new LocationChooser(locations, options);
    locationChooser.render();
    locationContainer.appendChild(locationChooser.element);

    const eventsView = new EventsView(options);
    eventsView.render();
    viewContainer.appendChild(eventsView.element);

    show(locationContainer);
  });
});

import ru from 'moment/locale/ru';
import lazysizes from 'lazysizes';

import Locations from './locations';
import Options from './options';

import LocationChooser from './location-chooser';
import EventsView from './events';

import {show, hide} from './utils';

document.addEventListener('DOMContentLoaded', function(event) {
  const options = new Options();
  const locations = new Locations();

  locations.fetch().then(() => {
    const locationContainer = document.querySelector('#city');
    const locationChooser = new LocationChooser(locations, options);
    locationChooser.render();
    locationContainer.appendChild(locationChooser.element);
    show(locationContainer);
  });

  options.fetch().then(() => {
    const viewContainer = document.querySelector('#view-container');

    const eventsView = new EventsView(options);
    eventsView.render();
    viewContainer.appendChild(eventsView.element);
  });
});

Load location chooser separately from options and everything


var Combobox = require('./Combobox');
var InputPopupWrapper = require('./InputPopupWrapper');
var ListPopup = require('./ListPopup');
var ListPopupOption = require('./ListPopupOption');
var TypeaheadInput = require('./TypeaheadInput');

module.exports = {
  Combobox,
  InputPopupWrapper,
  ListPopup,
  ListPopupOption,
  TypeaheadInput
};

Make sure we're exporting TypeaheadInput.


const comanche = require('./comanche')
const api = require('./plugins/api')
const cli = require('./plugins/cli')
const execution = require('./plugins/execution')
const validation = require('./plugins/validation')
const camelCase = require('./plugins/camelCase')

const DEFAULT_PLUGINS = [api, cli, execution, validation, camelCase]

module.exports = function defaultComanche(...args) {
  let plugins = DEFAULT_PLUGINS

  if (Array.isArray(args[args.length - 1])) {
    plugins = plugins.concat(args.pop())
  }

  return comanche(args, plugins)
}

const comanche = require('./comanche')
const api = require('./plugins/api')
const cli = require('./plugins/cli')
const execution = require('./plugins/execution')
const validation = require('./plugins/validation')
const camelCase = require('./plugins/camelCase')

const DEFAULT_PLUGINS = [execution, api, cli, validation, camelCase]

module.exports = function defaultComanche(...args) {
  let plugins = DEFAULT_PLUGINS

  if (Array.isArray(args[args.length - 1])) {
    plugins = plugins.concat(args.pop())
  }

  return comanche(args, plugins)
}

Make execution the first plugin in the queue


const appendUnit = (value, unit) => (
  value ? `${value}${unit}` : '0'
);

const mapStringTemplateToGetter = (value) => {
  if (typeof value === 'string') {
    const [key, unit] = value.split(':');
    return unit
      ? props => appendUnit(props[key], unit)
      : props => props[key];
  }
  return value;
};

const withStyledShortcuts = styled => (
  (strings, ...values) => {
    const newValues = values.map(mapStringTemplateToGetter);
    return styled(strings, ...newValues);
  }
);

const withStyledShortcutsFunction = styled => (...args) => (
  withStyledShortcuts(styled(...args))
);

const wrapStyled = (styled) => {
  const styledShortcuts = withStyledShortcutsFunction(styled); // styled(Component)

  Object.keys(styled).forEach((key) => {
    if (typeof styled[key] === 'function' && styled[key].attrs) { // styled.div
      styledShortcuts[key] = withStyledShortcuts(styled[key]);
      styledShortcuts[key].attrs = withStyledShortcutsFunction(styled[key].attrs);
    } else {
      styledShortcuts[key] = styled[key];
    }
  });

  return styledShortcuts;
};

export default wrapStyled;

const appendUnit = (value, unit) => (
  value ? `${value}${unit}` : '0'
);

const getPropValue = (obj, keys) => {
  const [key, balance] = keys.split(/\.(.+)/);
  const value = obj[key];
  if (balance) {
    return getPropValue(value, balance);
  }
  return value;
};

const mapStringTemplateToGetter = (value) => {
  if (typeof value === 'string') {
    const [key, unit] = value.split(':');
    return unit
      ? props => appendUnit(getPropValue(props, key), unit)
      : props => getPropValue(props, key);
  }
  return value;
};

const withStyledShortcuts = styled => (
  (strings, ...values) => {
    const newValues = values.map(mapStringTemplateToGetter);
    return styled(strings, ...newValues);
  }
);

const withStyledShortcutsFunction = styled => (...args) => (
  withStyledShortcuts(styled(...args))
);

const wrapStyled = (styled) => {
  const styledShortcuts = withStyledShortcutsFunction(styled); // styled(Component)

  Object.keys(styled).forEach((key) => {
    if (typeof styled[key] === 'function' && styled[key].attrs) { // styled.div
      styledShortcuts[key] = withStyledShortcuts(styled[key]);
      styledShortcuts[key].attrs = withStyledShortcutsFunction(styled[key].attrs);
    } else {
      styledShortcuts[key] = styled[key];
    }
  });

  return styledShortcuts;
};

export default wrapStyled;

Add dotted notation key support (ie. themes)


// Return Promise
const imageMerge = (sources = [], options = {format: 'image/png'}) => new Promise(resolve => {
	// Load sources
	const images = sources.map(source => new Promise(resolve => {
		// Convert strings to objects
		if (typeof source === 'string') {
			source = {src: source};
		}

		// Resolve source and img when loaded
		const img = new Image();
		img.onload = () => resolve(Object.assign({}, source, {img}));
		img.src = source.src;
	}));

	// Create canvas
	const canvas = document.createElement('canvas');
	const ctx = canvas.getContext('2d');

	// When sources have loaded
	Promise.all(images)
		.then(images => {
			// Set canvas dimensions
			const getSize = dim => options[dim] || Math.max(...images.map(image => image.img[dim]));
			canvas.width = getSize('width');
			canvas.height = getSize('height');

			// Draw images to canvas
			images.forEach(image => ctx.drawImage(image.img, image.x || 0, image.y || 0));

			// Resolve data uri
			resolve(canvas.toDataURL(options.format));
		});
});

module.exports = imageMerge;

// Return Promise
const imageMerge = (sources = [], options = {format: 'image/png'}) => new Promise(resolve => {
	// Use global window if we don't have one
	const window = options.window || window;

	// Load sources
	const images = sources.map(source => new Promise(resolve => {
		// Convert strings to objects
		if (typeof source === 'string') {
			source = {src: source};
		}

		// Resolve source and img when loaded
		const img = new window.Image();
		img.onload = () => resolve(Object.assign({}, source, {img}));
		img.src = source.src;
	}));

	// Create canvas
	const canvas = window.document.createElement('canvas');
	const ctx = canvas.getContext('2d');

	// When sources have loaded
	Promise.all(images)
		.then(images => {
			// Set canvas dimensions
			const getSize = dim => options[dim] || Math.max(...images.map(image => image.img[dim]));
			canvas.width = getSize('width');
			canvas.height = getSize('height');

			// Draw images to canvas
			images.forEach(image => ctx.drawImage(image.img, image.x || 0, image.y || 0));

			// Resolve data uri
			resolve(canvas.toDataURL(options.format));
		});
});

module.exports = imageMerge;

Allow window object to be passed in


import React, { Component, PropTypes } from 'react';
import { Provider } from 'react-redux';
import { getRoutes } from '../routes';
import { Router, applyRouterMiddleware } from 'react-router';
import { useScroll } from 'react-router-scroll';
import ReactGA from 'react-ga';

export default class Root extends Component { // eslint-disable-line react/prefer-stateless-function
  componentDidMount() {
    ReactGA.initialize('UA-73635552-1', {
      debug: false
    });
  }

  _logPageView() {
    ReactGA.set({ page: window.location.pathname });
    ReactGA.pageview(window.location.pathname);
  }

  render() {
    const { store, history } = this.props;
    return (
      <Provider store={store}>
        <Router
          history={history}
          routes={getRoutes(store)}
          render={applyRouterMiddleware(useScroll())}
          onUpdate={this._logPageView}
        />
      </Provider>
    );
  }
}

Root.propTypes = {
  store: PropTypes.object.isRequired,
  history: PropTypes.object.isRequired
};

import React, { Component, PropTypes } from 'react';
import { Provider } from 'react-redux';
import { getRoutes } from '../routes';
import { Router, applyRouterMiddleware } from 'react-router';
import { useScroll } from 'react-router-scroll';
//import ReactGA from 'react-ga';

export default class Root extends Component { // eslint-disable-line react/prefer-stateless-function
  componentDidMount() {
    // Google Analytics init.
    /*ReactGA.initialize('UA-73635552-1', {
      debug: false
    });*/
  }

  _logPageView() {
    // Logs each page view to your Google Analytics account.
    //ReactGA.set({ page: window.location.pathname });
    //ReactGA.pageview(window.location.pathname);
  }

  render() {
    const { store, history } = this.props;
    return (
      <Provider store={store}>
        <Router
          history={history}
          routes={getRoutes(store)}
          render={applyRouterMiddleware(useScroll())}
          onUpdate={this._logPageView}
        />
      </Provider>
    );
  }
}

Root.propTypes = {
  store: PropTypes.object.isRequired,
  history: PropTypes.object.isRequired
};

Comment out Google Analytics init.


var Logger = function (config) {
  this.config  = config;
  this.backend = this.config.backend || 'stdout'
  this.level   = this.config.level || "LOG_INFO"
  if (this.backend == 'stdout') {
    this.util = require('util');
  } else {
    if (this.backend == 'syslog') {
      this.util = require('node-syslog');
      this.util.init(config.application || 'statsd', this.util.LOG_PID | this.util.LOG_ODELAY, this.util.LOG_LOCAL0);
    } else {
      throw "Logger: Should be 'stdout' or 'syslog'."
    }
  }
}

Logger.prototype = {
  log: function (msg, type) {
    if (this.backend == 'stdout') {
      if (!type) {
        type = 'DEBUG';
      }
      this.util.log(type + ": " + msg);
    } else {
      if (!type) {
        type = this.level
        if (!this.util[type]) {
          throw "Undefined log level: " + type;
        }
      } else if (type == 'debug') {
        type = "LOG_DEBUG";
      }
      this.util.log(this.util[type], msg);
    }
  }
}

exports.Logger = Logger

var Logger = function (config) {
  this.config  = config;
  this.backend = this.config.backend || 'stdout'
  this.level   = this.config.level || "LOG_INFO"
  if (this.backend == 'stdout') {
    this.util = require('util');
  } else {
    if (this.backend == 'syslog') {
      this.util = require('node-syslog');
      this.util.init(config.application || 'statsd', this.util.LOG_PID | this.util.LOG_ODELAY, this.util.LOG_LOCAL0);
    } else {
      throw "Logger: Should be 'stdout' or 'syslog'."
    }
  }
}

Logger.prototype = {
  log: function (msg, type) {
    if (this.backend == 'stdout') {
      if (!type) {
        type = 'DEBUG: ';
      }
      this.util.log(type + msg);
    } else {
      if (!type) {
        type = this.level
        if (!this.util[type]) {
          throw "Undefined log level: " + type;
        }
      } else if (type == 'debug') {
        type = "LOG_DEBUG";
      }
      this.util.log(this.util[type], msg);
    }
  }
}

exports.Logger = Logger

Revert "Tweak log formating to append colon and space to stdout type."


import Item from './item';

export default class CheckItem extends Item {
  constructor() {
    super();

    this._root
      .classed('check', true)
      .styles({
        'cursor': 'pointer'
      });

    this._check = this
      .icon()
      .class('ion-ios-circle-outline')
      .size('1.5em');

    this._check
      .root()
      .styles({
        'width': '1.75em'
      });
  }

  _click() {
    const cancel =
      this._model === null ||
      this._disabled === true;

    if (cancel === true) {
      return;
    }

    const value = this._model.get(this._name);
    const action = typeof value === 'undefined' ||
      value.indexOf(this._value) === -1;

    this._model.add(this._name, this._value, action);
  }

  _set(setEvent) {
    if (setEvent.name !== this._name) {
      return;
    }

    const value = setEvent.value;
    const checked = typeof value !== 'undefined' &&
      value.indexOf(this._value) > -1;

    if (checked === true) {
      this._check.class('ion-ios-circle-outline');
    } else {
      this._check.class('ion-ios-checkmark');
    }
  }
}

import Item from './item';

export default class CheckItem extends Item {
  constructor() {
    super();

    this._root
      .classed('check', true)
      .styles({
        'cursor': 'pointer'
      });

    this._check = this
      .icon()
      .class('ion-ios-circle-outline')
      .size('1.5em');

    this._check
      .root()
      .styles({
        'width': '1.75em'
      });
  }

  _click() {
    const cancel =
      this._model === null ||
      this._disabled === true;

    if (cancel === true) {
      return;
    }

    const value = this._model.get(this._name);
    const action = typeof value === 'undefined' ||
      value.indexOf(this._value) === -1;

    this._model.add(this._name, this._value, action);
  }

  _set(setEvent) {
    if (setEvent.name !== this._name) {
      return;
    }

    const value = setEvent.value;
    const checked = typeof value !== 'undefined' &&
      value.indexOf(this._value) > -1;

    if (checked === true) {
      this._check.class('ion-ios-checkmark');
    } else {
      this._check.class('ion-ios-circle-outline');
    }
  }
}

Fix in/active state of check item


import * as PIXI from 'pixi.js'
import TouchPlugin from './touch-plugin'

describe('TouchPlugin', function () {
  it('should get value when touching', function () {
    let instance = new TouchPlugin({
      refs: {
        p1_1: new Set([
          { getBounds: () => new PIXI.Rectangle(64, 64, 64, 64) }
        ]),
        p1_2: new Set([
          { getBounds: () => new PIXI.Rectangle(128, 64, 64, 64) }
        ])
      },
      input: [{ x: 72, y: 72, id: 'touch1' }, { x: 32, y: 32, id: 'touch2' }]
    })
    let data = instance.get()
    expect(data['p1_1']).to.equal(1)
    expect(data['p1_2']).to.equal(0)
  })
})

import * as PIXI from 'pixi.js'
import TouchPlugin from './touch-plugin'

describe('TouchPlugin', function () {
  it('should get value when touching', function () {
    let instance = new TouchPlugin({
      skinData: { },
      refs: {
        p1_1: new Set([
          { getBounds: () => new PIXI.Rectangle(64, 64, 64, 64) }
        ]),
        p1_2: new Set([
          { getBounds: () => new PIXI.Rectangle(128, 64, 64, 64) }
        ])
      },
      input: [{ x: 72, y: 72, id: 'touch1' }, { x: 32, y: 32, id: 'touch2' }]
    })
    let data = instance.get()
    expect(data['p1_1']).to.equal(1)
    expect(data['p1_2']).to.equal(0)
  })
})

Send blank skinData to TouchPlugin ctor


// Create a not found error object. This should at some point inherit
// from a HTTPError object with a nice .toJSON() method that can be
// used directly by handlers.
function HTTPError(status, message) {
  Error.call(this, message);

  // Remove the Error itself from the stack trace.
  // http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
  Error.captureStackTrace(this, this.constructor);

  this.name    = this.constructor.name;
  this.status  = status;
  this.message = message || '';
}
HTTPError.prototype = Object.create(Error.prototype);
HTTPError.prototype = Object.create(HTTPError.prototype);

function NotFound(message) {
  Error.call(this, 404, message);
}
NotFound.prototype = Object.create(HTTPError.prototype);

module.exports.NotFound = NotFound;

function BadRequest(message) {
  HTTPError.call(this, 400, message);
}
BadRequest.prototype = Object.create(HTTPError.prototype);

module.exports.BadRequest = BadRequest;

// Create a not found error object. This should at some point inherit
// from a HTTPError object with a nice .toJSON() method that can be
// used directly by handlers.
function HTTPError(status, message) {
  Error.call(this, message);

  // Remove the Error itself from the stack trace.
  // http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
  Error.captureStackTrace(this, this.constructor);

  this.name    = this.constructor.name;
  this.status  = status;
  this.message = message || '';
}
HTTPError.prototype = Object.create(Error.prototype);
HTTPError.prototype = Object.create(HTTPError.prototype);

function NotFound(message) {
  Error.call(this, 404, message);
}
NotFound.prototype = Object.create(HTTPError.prototype);

module.exports.NotFound = NotFound;

function NotAuthorized(message) {
  Error.call(this, 401, message);
}
NotAuthorized.prototype = Object.create(HTTPError.prototype);

module.exports.NotFound = NotFound;

function BadRequest(message) {
  HTTPError.call(this, 400, message);
}
BadRequest.prototype = Object.create(HTTPError.prototype);

module.exports.BadRequest = BadRequest;

Add a NotAuthorized error object


/**
 * A dom-delegate helper for Financial Times' FruitMachine
 *
 * @author Matt Andrews <matt@mattandre.ws>
 * @copyright The Financial Times Limited
 */

/**
 * External dependency
 */

var delegate = require('dom-delegate');

/**
 * Link up dom-delegate with fruitmachine
 */

module.exports = function(view) {
  view.on('before initialize', function() {
    this.delegate = delegate();
  });

  view.on('mount', function() {
    this.delegate.root(this.el);
  });

  view.on('unmount', function() {
    this.delegate.root();
  });

  view.on('destroy', function() {
    this.delegate.destroy();
    this.delegate = undefined;
  });
};

/**
 * A dom-delegate helper for Financial Times' FruitMachine
 *
 * @author Matt Andrews <matt@mattandre.ws>
 * @copyright The Financial Times Limited
 */

/**
 * External dependency
 */

var delegate = require('dom-delegate');

/**
 * Link up dom-delegate with fruitmachine
 */

module.exports = function(view) {
  view.on('before initialize', function() {
    // https://github.com/Financial-Times/ftdomdelegate exposes either a Prototype or an instantiated Object
    // Which one depends on whether Bower (bower.json) or Node (package.json) is preferred in the client library's build tool (e.g.: Browserify, Webpack)
    this.delegate = typeof delegate === 'function' ? new delegate() : delegate();
  });

  view.on('mount', function() {
    this.delegate.root(this.el);
  });

  view.on('unmount', function() {
    this.delegate.root();
  });

  view.on('destroy', function() {
    this.delegate.destroy();
    this.delegate = undefined;
  });
};

Make ftdomdelegate work regardless whether it is loaded via Bower or NPM


Plugin.registerCompiler(
  {
    extensions: ["md"],
    isTemplate: true
  },
  () => new MdCompiler
);

export class MdCompiler extends CachingCompiler {
  constructor() {
    super({
      compilerName: 'md',
      defaultCacheSize: 1024*1024*10,
    });
  }
  getCacheKey(inputFile) {
    return inputFile.getSourceHash();
  }
  compileResultSize(compileResult) {
    return compileResult.length;
  }
  compileOneFile(inputFile) {
    const content = inputFile.getContentsAsString().toString('utf8');

    let results;
    try {
      results = markdown_scanner.scan(content);
    } catch (e) {
      if (! (e instanceof markdown_scanner.ParseError)) {
        throw e;
      }

      inputFile.error({
        message: e.message,
        sourcePath: files.inputPath,
        line: e.line
      });
    }

    return results;
  }
  addCompileResult(inputFile, compileResult) {
    if (! (compileResult && compileResult.js)) return;
    inputFile.addJavaScript({
      path: inputFile.getPathInPackage() + '.js',
      data: compileResult.js,
    });
  }
}

Plugin.registerCompiler(
  {
    extensions: ["md"],
    isTemplate: true
  },
  () => new MdCompiler
);

export class MdCompiler extends CachingCompiler {
  constructor() {
    super({
      compilerName: 'md',
      defaultCacheSize: 1024*1024*10,
    });
  }
  getCacheKey(inputFile) {
    return inputFile.getSourceHash();
  }
  compileResultSize(compileResult) {
    return compileResult.length;
  }
  compileOneFile(inputFile) {
    const content = inputFile.getContentsAsString().toString('utf8');

    let results;
    try {
      results = markdown_scanner.scan(content);
    } catch (e) {
      if (! (e instanceof markdown_scanner.ParseError)) {
        throw e;
      }

      inputFile.error({
        message: e.message,
        sourcePath: inputFile.getPathInPackage(),
        line: e.line
      });
    }

    return results;
  }
  addCompileResult(inputFile, compileResult) {
    if (! (compileResult && compileResult.js)) return;
    inputFile.addJavaScript({
      path: inputFile.getPathInPackage() + '.js',
      data: compileResult.js,
    });
  }
}

Fix error in catch source file path.


import jpegThumbnail from './jpeg-thumbnail';

const costumePayload = costume => {
    // TODO is it ok to base64 encode SVGs? What about unicode text inside them?
    const assetDataUrl = costume.asset.encodeDataURI();
    const assetDataFormat = costume.dataFormat;
    const payload = {
        type: 'costume',
        name: costume.name,
        // Params to be filled in below
        mime: '',
        body: '',
        thumbnail: ''
    };

    switch (assetDataFormat) {
    case 'svg':
        payload.mime = 'image/svg+xml';
        payload.body = assetDataUrl.replace('data:image/svg+xml;base64,', '');
        break;
    case 'png':
        payload.mime = 'image/png';
        payload.body = assetDataUrl.replace('data:image/png;base64,', '');
        break;
    default:
        alert(`Cannot serialize for format: ${assetDataFormat}`); // eslint-disable-line
    }

    // TODO we may be able to optimize by not actually generating thumbnails...
    return jpegThumbnail(assetDataUrl).then(thumbnail => {
        payload.thumbnail = thumbnail.replace('data:image/jpeg;base64,', '');
        return payload;
    });
};

export default costumePayload;

import jpegThumbnail from './jpeg-thumbnail';
import getCostumeUrl from '../get-costume-url';

const costumePayload = costume => {
    // TODO is it ok to base64 encode SVGs? What about unicode text inside them?
    const assetDataUrl = costume.asset.encodeDataURI();
    const assetDataFormat = costume.dataFormat;
    const payload = {
        type: 'costume',
        name: costume.name,
        // Params to be filled in below
        mime: '',
        body: '',
        thumbnail: ''
    };

    switch (assetDataFormat) {
    case 'svg':
        payload.mime = 'image/svg+xml';
        payload.body = assetDataUrl.replace('data:image/svg+xml;base64,', '');
        break;
    case 'png':
        payload.mime = 'image/png';
        payload.body = assetDataUrl.replace('data:image/png;base64,', '');
        break;
    default:
        alert(`Cannot serialize for format: ${assetDataFormat}`); // eslint-disable-line
    }

    // Do not generate the thumbnail from the raw asset. Instead use the getCostumeUrl
    // utility which inlines the fonts to make the thumbnail show the right fonts.
    const inlinedFontDataUrl = getCostumeUrl(costume.asset);
    return jpegThumbnail(inlinedFontDataUrl).then(thumbnail => {
        payload.thumbnail = thumbnail.replace('data:image/jpeg;base64,', '');
        return payload;
    });
};

export default costumePayload;

Fix missing fonts in backpack thumbnails


"use strict";

var $ = require('jquery');

// Tab functions on jquery
require('bootstrap');

module.exports = function (tabList) {
    // Use hash to determine which tab to go to
    var hash = window.location.hash;
    if (hash) {
        $(tabList).find('a[href="' + hash + '"]').tab('show');
    }

    // bootstrap swallows events so the url doesn't change
    // add our own here
    $(tabList).on('click', 'a', function (e) {
        $(this).tab('show');

        var originalScrollPosition = $('body').scrollTop();
        window.location.hash = this.hash;
        $('html,body').scrollTop(originalScrollPosition);
    });
};

"use strict";

var $ = require('jquery');

// Tab functions on jquery
require('bootstrap');

module.exports = function (tabList) {
    function changeTabToHash() {
        // Use hash to determine which tab to go to
        var hash = window.location.hash;
        if (hash) {
            $(tabList).find('a[href="' + hash + '"]').tab('show');
        }
    }
    changeTabToHash();

    // bootstrap swallows events so the url doesn't change
    // add our own here
    $(tabList).on('click', 'a', function (e) {
        $(this).tab('show');

        var originalScrollPosition = $('body').scrollTop();
        window.location.hash = this.hash;
        $('html,body').scrollTop(originalScrollPosition);
    });
    if ("onhashchange" in window) {
        $(window).on("hashchange", changeTabToHash);
    }
};

Change tabs on browser back/forward to match hash


import React, { Component } from 'react'
import { reduxForm, Field } from 'redux-form'
import { Button, TextField } from '../../components'
import redeemTechDomainValidation from './redeemTechDomainValidation'

class RedeemTechDomainForm extends Component {
  buttonState() {
    const {
      submitting,
      invalid,
      status
    } = this.props

    if (invalid) {
      return "disabled"
    } else if (submitting) {
      return "loading"
    } else {
      return status
    }
  }

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

    return (
      <form style={style} onSubmit={handleSubmit}>
        <Field name="name" label="Your Name" placeholder="Arthur Dent" component={TextField} />
        <Field name="email" label="Email" placeholder="arthur@earth.com" component={TextField} />
        <Field name="requested_domain" label="Domain" placeholder="arthurdent.tech" component={TextField} />
        <Field name="secret_code" label="Secret Code" placeholder="Get from your club leader" component={TextField} />
        <Button type="form" state={ this.buttonState() }>
          Get your .TECH domain!
        </Button>
      </form>
    )
  }
}

export default reduxForm({
  validate: redeemTechDomainValidation,
  form: 'redeemTechDomainRedemption'
})(RedeemTechDomainForm)

import React, { Component } from 'react'
import { reduxForm, Field } from 'redux-form'
import { Button, Emoji, TextField } from '../../components'
import redeemTechDomainValidation from './redeemTechDomainValidation'

class RedeemTechDomainForm extends Component {
  buttonState() {
    const {
      submitting,
      invalid,
      status
    } = this.props

    if (invalid) {
      return "disabled"
    } else if (submitting) {
      return "loading"
    } else {
      return status
    }
  }

  buttonText(status) {
    switch (status) {
    case "error":
      return (Something went wrong! <Emoji type="thinking_face" />)
    case "success":
      return (Domain requested! <Emoji type="fisted_hand_sign" />)
    default:
      return "Get your .TECH domain!"
    }

  }

  render() {
    const { handleSubmit, style, status } = this.props

    return (
      <form style={style} onSubmit={handleSubmit}>
        <Field name="name" label="Your Name" placeholder="Arthur Dent" component={TextField} />
        <Field name="email" label="Email" placeholder="arthur@earth.com" component={TextField} />
        <Field name="requested_domain" label="Domain" placeholder="arthurdent.tech" component={TextField} />
        <Field name="secret_code" label="Secret Code" placeholder="Get from your club leader" component={TextField} />
        <Button type="form" state={ this.buttonState() }>
          { this.buttonText(status) }
        </Button>
      </form>
    )
  }
}

export default reduxForm({
  validate: redeemTechDomainValidation,
  form: 'redeemTechDomainRedemption'
})(RedeemTechDomainForm)

Add user friendly success and error responses


/*global require, QUnit */

/*
 *      PUMASCRIPT MAIN ENTRY FILE FOR TEST
 *  @file: file that includes the dependencies and executions fof RequireJS.
 */

require.config({
    paths: {
        'pumascript': '../src/pumascript',
        'esprima': '../thirdparty/esprima/esprima'
    }
});

/**
 * @enum containing the list of test suites to execute
 */
var suites = [
        './suites/base-tests.js',
        './suites/runtime-tests.js',
        './suites/integration-tests.js',
        './suites/Tests-7-ECMA.js',
        './suites/Tests-8-ECMA.js',
        './suites/test-9-ecma.js',
        './suites/test-10-ecma.js'
        './suites/test-12-ecma.js',
        './suites/test-13-ecma.js',
        './suites/Tests-15-ECMA.js'
];

require(suites, function () {
    // Required to make it work under phantomjs environment
    QUnit.start();
});

/*global require, QUnit */

/*
 *      PUMASCRIPT MAIN ENTRY FILE FOR TEST
 *  @file: file that includes the dependencies and executions fof RequireJS.
 */

require.config({
    paths: {
        'pumascript': '../src/pumascript',
        'esprima': '../thirdparty/esprima/esprima'
    }
});

/**
 * @enum containing the list of test suites to execute
 */
var suites = [
        './suites/base-tests.js',
        './suites/runtime-tests.js',
        './suites/integration-tests.js',
        './suites/Tests-7-ECMA.js',
        './suites/Tests-8-ECMA.js',
        './suites/test-9-ecma.js',
        './suites/test-10-ecma.js',
        './suites/test-12-ecma.js',
        './suites/test-13-ecma.js',
        './suites/Tests-15-ECMA.js'
];

require(suites, function () {
    // Required to make it work under phantomjs environment
    QUnit.start();
});

Test suites section 9 and 10


import { IndexRoute } from 'react-router';
import { isArray } from 'lodash/lang';
import { flattenDeep } from 'lodash/array';

/**
 * NOTE: We could likely use createRoutes to our advantage here. It may simplify
 * the code we currently use to recurse over the virtual dom tree:
 *
 * import { createRoutes } from 'react-router';
 * console.log(createRoutes(routes)); =>
 * [ { path: '/',
 *    component: [Function: Layout],
 *    childRoutes: [ [Object], [Object] ] } ]
 *
 * Ex:
 * const routes = (
 *   <Route component={App} path='/'>
 *     <Route component={About} path='/about' />
 *   </Route>
 * );
 *
 * getAllPaths(routes); => ['/', '/about]
 */
export const getNestedPaths = (route, prefix = '') => {
  if (!route) return [];

  if (isArray(route)) return route.map(x => getNestedPaths(x, prefix));

  // Index routes don't represent a distinct path, so we don't count them
  if (route.type === IndexRoute) return [];

  const path = prefix + route.props.path;
  const nextPrefix = path === '/' ? path : path + '/';
  return ([path].concat(getNestedPaths(route.props.children, nextPrefix)));
};

export const getAllPaths = routes => {
  return flattenDeep(getNestedPaths(routes));
};

export default getAllPaths;

import { isArray, isUndefined } from 'lodash/lang';
import { flattenDeep } from 'lodash/array';

/**
 * This is not a very sophisticated checking method. Assuming we already know
 * this is either a Route or an IndexRoute under what cases would this break?
 */
const isIndexRoute = route => isUndefined(route.props.path);

/**
 * NOTE: We could likely use createRoutes to our advantage here. It may simplify
 * the code we currently use to recurse over the virtual dom tree:
 *
 * import { createRoutes } from 'react-router';
 * console.log(createRoutes(routes)); =>
 * [ { path: '/',
 *    component: [Function: Layout],
 *    childRoutes: [ [Object], [Object] ] } ]
 *
 * Ex:
 * const routes = (
 *   <Route component={App} path='/'>
 *     <Route component={About} path='/about' />
 *   </Route>
 * );
 *
 * getAllPaths(routes); => ['/', '/about]
 */
export const getNestedPaths = (route, prefix = '') => {
  if (!route) return [];

  if (isArray(route)) return route.map(x => getNestedPaths(x, prefix));

  // Index routes don't represent a distinct path, so we don't count them
  if (isIndexRoute(route)) return [];

  const path = prefix + route.props.path;
  const nextPrefix = path === '/' ? path : path + '/';
  return ([path].concat(getNestedPaths(route.props.children, nextPrefix)));
};

export const getAllPaths = routes => {
  return flattenDeep(getNestedPaths(routes));
};

export default getAllPaths;

Fix innacurate test for whether or not a route was an IndexRoute


/**
* Build the cartridge store definition.
* @return {object} - The cartridge component.
*/
module.exports = () => {
    return ['summaryComponent', 'barContentLeftComponent', 'barContentRightComponent', 'cartridgeComponent', 'actions', 'mode', 'route', 'confirmConfig']
    .reduce((def, node) => {
        def[node] = node;
        return def;
    }, {});
};

/**
* Build the cartridge store definition.
* @return {object} - The cartridge component.
*/
module.exports = () => {
    return ['summaryComponent', 'barContentLeftComponent', 'barContentRightComponent', 'cartridgeComponent', 'actions', 'mode', 'route', 'confirmConfig', 'headerSize']
    .reduce((def, node) => {
        def[node] = node;
        return def;
    }, {});
};

Add 'headerSize' node to application store.


const ProvinceCard = require('../../provincecard.js');

class TheArtOfPeace extends ProvinceCard {
    setupCardAbilities() {
        this.interrupt({
            title: 'Honor all defenders and dishonor all attackers',
            when: {
                onBreakProvince: (event, context) => event.card === context.source && (
                    event.conflict.attackers.some(card => card.allowGameAction('honor', context)) ||
                    event.conflict.defenders.some(card => card.allowGameAction('dishonor', context))
                )
            },
            handler: context => {
                this.game.addMessage('{0} uses {1} to dishonor all attackers and honor all defenders in this conflict', this.controller, this);
                this.game.applyGameAction(context, { honor: context.event.conflict.defenders, dishonor: context.event.conflict.attackers });
            }
        });
    }
}

TheArtOfPeace.id = 'the-art-of-peace';

module.exports = TheArtOfPeace;

const ProvinceCard = require('../../provincecard.js');

class TheArtOfPeace extends ProvinceCard {
    setupCardAbilities() {
        this.interrupt({
            title: 'Honor all defenders and dishonor all attackers',
            when: {
                onBreakProvince: (event, context) => event.card === context.source && (
                    event.conflict.attackers.some(card => card.allowGameAction('dishonor', context)) ||
                    event.conflict.defenders.some(card => card.allowGameAction('honor', context))
                )
            },
            handler: context => {
                this.game.addMessage('{0} uses {1} to dishonor all attackers and honor all defenders in this conflict', this.controller, this);
                this.game.applyGameAction(context, { honor: context.event.conflict.defenders, dishonor: context.event.conflict.attackers });
            }
        });
    }
}

TheArtOfPeace.id = 'the-art-of-peace';

module.exports = TheArtOfPeace;

Fix for backwards trigger check on Art of Peace


'use strict';

angular
    .module('flyNg.server', ['services', 'ngCookies'])

    .controller('ServerController', ['$scope', '$rootScope', '$location', '$cookies', '$log', 'management', function ($scope, $rootScope, $location, $cookies, $log, management) {
        $scope.management = management;
        var KEY = 'server-url';
        if (!angular.isUndefined($cookies[KEY])) {
            var url = decodeURI($cookies[KEY]);
        }
        if (angular.isUndefined(url)) {
            management.server = {"url": "localhost"};
        } else {
            management.server = {"url": url};
        }
        loadServerData();

        $scope.updateServerUrl = function() {
            $cookies[KEY] = encodeURI(management.server.url);
            loadServerData();
        };

        function loadServerData() {
            management.load([]).then(
                function(response) {
                    var serverState = response.result['server-state'];
                    if (serverState == 'reload-required') {
                        management.server.state = 'running';
                    } else {
                        management.server.state = serverState;
                    }
                    return management.list([], "subsystem");
            }).then(
                function (data) {
                    management.resources = data.result;
                },function() {
                    management.server.state = 'not connected';
                    $location.path('/');
                }
            );
        }

    }]);

'use strict';

angular
    .module('flyNg.server', ['services', 'ngCookies'])

    .controller('ServerController', ['$scope', '$rootScope', '$location', '$cookies', '$log', 'management', function ($scope, $rootScope, $location, $cookies, $log, management) {
        $scope.management = management;
        var KEY = 'server-url';
        if (!angular.isUndefined($cookies[KEY])) {
            var url = decodeURI($cookies[KEY]);
        }
        if (angular.isUndefined(url)) {
            management.server = {"url": "localhost"};
        } else {
            management.server = {"url": url};
        }
        loadServerData();

        $scope.updateServerUrl = function() {
            $cookies[KEY] = encodeURI(management.server.url);
            loadServerData();
        };

        function loadServerData() {
            management.load([]).then(
                function(response) {
                    var serverState = response.result['server-state'];
                    if (serverState == 'reload-required') {
                        management.server.state = 'running';
                    } else {
                        management.server.state = serverState;
                    }
                    return management.list([], "subsystem");
            }).then(
                function (data) {
                    management.resources = data.result;
                },function() {
                    management.server.state = 'not connected';
                    $location.path('/');
                    $location.search('name', null);
                }
            );
        }

    }]);

Reset search on connection error


import { Mixin } from './ce';

import { ShadowDOM } from './shadow-dom';

const templates = new Map();
function resolveTemplate(self) {
	const name = self.localName;
	let template = templates.get(name);
	if(template) return template;

	template = document.createElement('template');
	template.innerHTML = self.constructor.templateHTML;
	templates.set(name, template);
	return template;
}

export const Template = Mixin(SuperClass => class extends SuperClass.with(ShadowDOM) {
	static get templateHTML() {
		return '';
	}

	createdCallback() {
		super.createdCallback();

		const template = resolveTemplate(this);
		const instance = template.content.cloneNode(true);
		this.shadowRoot.appendChild(instance);
	}
});

import { Mixin } from './ce';

import { ShadowDOM } from './shadow-dom';

/*
 * Cache and resolution of templates based on the element name.
 */
const templates = new Map();
function resolveTemplate(self) {
	const name = self.localName;
	let template = templates.get(name);
	if(template) return template;

	template = document.createElement('template');
	template.innerHTML = self.constructor.templateHTML;
	templates.set(name, template);

	if(window.ShadyCSS) {
		window.ShadyCSS.prepareTemplate(template, name);
	}
	return template;
}

/**
 * Mixin that create a template by calling the static function `templateHTML`.
 * The template is applied to the shadow root whenever an element is created.
 */
export const Template = Mixin(SuperClass => class extends SuperClass.with(ShadowDOM) {
	static get templateHTML() {
		return '';
	}

	createdCallback() {
		super.createdCallback();

		const template = resolveTemplate(this);
		const instance = template.content.cloneNode(true);
		this.shadowRoot.appendChild(instance);

		if(window.ShadyCSS) {
			window.ShadyCSS.styleElement(this);
		}
	}
});

Integrate with ShadyCSS if in use


//initialize settings
HN.settings.initSettings();

var currentUrl = window.location.href;

if(currentUrl.match(/\/comments\/?.*$/i)){
	if(currentUrl.match(/ask\/comments\/?.*$/i)){
		var commentsType = 'ask';
	}
	else if(currentUrl.match(/show\/comments\/?.*$/i)){
		var commentsType = 'show';
	}
	else{
		var commentsType = 'general';
	}
	//add listener for if user clicks on comments link while on comments page
	window.addEventListener('hashchange', function(){HN.clearComments(); HN.displayComments(commentsType);}, false);
	HN.displayComments(commentsType);
}
else if(currentUrl.match(/\/settings\/?$/i)){
	HN.settings.initSettingsPage();
}
else{
	if(currentUrl.match(/ask\/?$/i)){
		var pageSettings = HN.pages.ask;
	}
	else if(currentUrl.match(/show\/?$/i)){
		var pageSettings = HN.pages.show;
	}
	else{
		var pageSettings = HN.pages.home;
	}
	HN.displayStories(pageSettings);
}

//initialize settings
HN.settings.initSettings();

var currentUrl = window.location.href;

if(currentUrl.match(/\/comments\/?.*$/i)){
	var commentsType = 'general';
	if(currentUrl.match(/ask\/comments\/?.*$/i)){
		commentsType = 'ask';
	}
	else if(currentUrl.match(/show\/comments\/?.*$/i)){
		commentsType = 'show';
	}
	//add listener for if user clicks on comments link while on comments page
	window.addEventListener('hashchange', function(){HN.clearComments(); HN.displayComments(commentsType);}, false);
	HN.displayComments(commentsType);
}
else if(currentUrl.match(/\/settings\/?$/i)){
	HN.settings.initSettingsPage();
}
else{
	var pageSettings = HN.pages.home;
	if(currentUrl.match(/ask\/?$/i)){
		pageSettings = HN.pages.ask;
	}
	else if(currentUrl.match(/show\/?$/i)){
		pageSettings = HN.pages.show;
	}
	HN.displayStories(pageSettings);
}

Refactor to simplify if else statements


/*
 * grunt-external-daemon
 * https://github.com/jlindsey/grunt-external-daemon
 *
 * Copyright (c) 2013 Joshua Lindsey
 * Licensed under the MIT license.
 */

'use strict';

var grunt = require('grunt'),
    exec  = require('child_process').exec;

exports.missing_cmd = function(test) {
  exec('grunt external_daemon:server_4', function(err, stdout, stderr) {
    var check = stdout.indexOf('Warning: You must specify "cmd" for task server_4');
    test.ok((check > -1), 'correct error');
    test.done();
  });
};

exports.wrong_args = function(test) {
  exec('grunt external_daemon:server_4a', function(err, stdout, stderr) {
    var check = stdout.indexOf('Warning: You must specify "args" as an array for task server_4a');
    test.ok((check > -1), 'correct error');
    test.done();
  });
};

exports.bad_start_check = function(test) {
  exec('grunt external_daemon:server_4b', function(err, stdout, stderr) {
    var check = stdout.indexOf('Warning: You must specify "startCheck" as a function for task server_4b');
    test.ok((check > -1), 'correct error');
    test.done();
  });
};

/*
 * grunt-external-daemon
 * https://github.com/jlindsey/grunt-external-daemon
 *
 * Copyright (c) 2013 Joshua Lindsey
 * Licensed under the MIT license.
 */

'use strict';

var grunt = require('grunt'),
    exec  = require('child_process').exec;

exports.missing_cmd = function(test) {
  test.expect(1);

  exec('grunt external_daemon:server_4', function(err, stdout, stderr) {
    var check = stdout.indexOf('Warning: You must specify "cmd" for task server_4');
    test.ok((check > -1), 'correct error');
    test.done();
  });
};

exports.wrong_args = function(test) {
  test.expect(1);

  exec('grunt external_daemon:server_4a', function(err, stdout, stderr) {
    var check = stdout.indexOf('Warning: You must specify "args" as an array for task server_4a');
    test.ok((check > -1), 'correct error');
    test.done();
  });
};

exports.bad_start_check = function(test) {
  test.expect(1);

  exec('grunt external_daemon:server_4b', function(err, stdout, stderr) {
    var check = stdout.indexOf('Warning: You must specify "startCheck" as a function for task server_4b');
    test.ok((check > -1), 'correct error');
    test.done();
  });
};

Add test expectations to error test


var express =       require('express')
    , http =        require('http')
    , passport =    require('passport')
    , path =        require('path')
    , User =        require('./models/User.js');

var app = express();

app.set('views', __dirname + '/app');
app.set('view engine', 'jade');
app.use(express.logger('dev'))
app.use(express.cookieParser());
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.static(path.join(__dirname, 'app')));
app.use(express.session(
    {
        secret: "Superdupersecret",
        cookie: { maxAge: 3600000 * 24 * 365 }
    }));
app.use(passport.initialize());
app.use(passport.session());

passport.use(User.localStrategy);
passport.serializeUser(User.serializeUser);
passport.deserializeUser(User.deserializeUser);

require('./routes.js')(app);

http.createServer(app).listen(process.env.port || 8080, function(){
    console.log("Express server listening on port " + app.get('port'));
});

var express =       require('express')
    , http =        require('http')
    , passport =    require('passport')
    , path =        require('path')
    , User =        require('./models/User.js');

var app = express();

app.set('views', __dirname + '/app');
app.set('view engine', 'jade');
app.use(express.logger('dev'))
app.use(express.cookieParser());
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.static(path.join(__dirname, 'app')));
app.use(express.session(
    {
        secret: "Superdupersecret",
        cookie: { maxAge: 3600000 * 24 * 365 }
    }));
app.use(passport.initialize());
app.use(passport.session());

passport.use(User.localStrategy);
passport.serializeUser(User.serializeUser);
passport.deserializeUser(User.deserializeUser);

require('./routes.js')(app);

app.set('port', process.env.PORT || 8000);
http.createServer(app).listen(app.get('port'), function(){
    console.log("Express server listening on port " + app.get('port'));
});

Set port on express app explicitly


/**
 * @see http://expressjs.com/4x/api.html
 */

var express = require('express'),
	serveStatic = require('serve-static'),
	errorHandler = require('errorhandler'),
	path = require('path');

var app = express();

// For openshift.
app.set('port', process.env.PORT || 7000);

// Static file server.
app.use(serveStatic(path.join(__dirname, 'public')));

// Required to support JSON-encoded bodies
//app.use(express.json());

// TODO Check for process.env.NODE_ENV === 'development'
app.use(errorHandler());

// Load routes, passing app
var indexRoute = require('./routes/index');

// Bind routes
app.route('/').get(indexRoute.index);

// @see http://stackoverflow.com/questions/17696801/express-js-app-listen-vs-server-listen
app.listen(app.get('port'), function () {
	console.log('HTTP server listening on: http://localhost:' + app.get('port'));
});

/**
 * @see http://expressjs.com/4x/api.html
 */

var express = require('express'),
	serveStatic = require('serve-static'),
	errorHandler = require('errorhandler'),
	path = require('path');

var app = express();

// For openshift.
app.set('port', process.env.PORT || 7001);

// Static file server.
app.use(serveStatic(path.join(__dirname, 'public')));

// Required to support JSON-encoded bodies
//app.use(express.json());

// TODO Check for process.env.NODE_ENV === 'development'
app.use(errorHandler());

// Load routes, passing app
var indexRoute = require('./routes/index');

// Bind routes
app.route('/').get(indexRoute.index);

// @see http://stackoverflow.com/questions/17696801/express-js-app-listen-vs-server-listen
app.listen(app.get('port'), function () {
	console.log('HTTP server listening on: http://localhost:' + app.get('port'));
});

Change default port to 7001.


var bar = $('.sr-list');
var subreddits = ['nba', 'madmen', 'gameofthrones', 'programming', 'python', 'starcraft', 'malefashionadvice',
                  'wow', 'arduino', 'videos'];
bar.empty();
bar.append('
'+
           '
all
'+
           '
-random
'+
           '
');
bar.append(' | ');
bar.append('

');
var myreddit = $('ul#myreddit');
var i, len;
for (i = 0, len = subreddits.length; i < len; ++i) {
  if (i != 0) {
    myreddit.append('
-'+
                    ''+subreddits[i]+'
');
  } else {
    myreddit.append('
'+subreddits[i]+'
');
  }
}

var $bar = $('.sr-list');
var $customBar = $('.flat-list.sr-bar.hover:eq(1)');
var subreddits = ['nba', 'madmen', 'gameofthrones', 'programming', 'python', 'starcraft', 'malefashionadvice',
                  'wow', 'arduino', 'videos'];
var i, len;
$customBar.empty();
$bar.children('span.separator:eq(1)').remove();
$bar.children('.flat-list.sr-bar.hover:eq(2)').remove();
for (i = 0, len = subreddits.length; i < len; ++i) {
  if (i != 0) {
    $customBar.append('
-'+
                    ''+subreddits[i]+'
');
  } else {
    $customBar.append('
'+subreddits[i]+'
');
  }
}

Use eq() to select correct elements


'use strict';

import React from 'react';
import connect from '../../bicycle/connect';

@connect(props => ({channels: {id: props.params.channel}}))
export default class ChannelSelector {
  render() {
    if (this.props.loading) {
      return
Loading...
;
    }
    return (

{this.props.channels[0].name}

    );
  }
};

'use strict';

import React from 'react';
import connect from '../../bicycle/connect';

@connect(props => ({channels: {id: props.params.channel}}))
export default class ChannelSelector {
  render() {
    if (!this.props.loaded) {
      return
Loading...
;
    }
    return (

{this.props.channels[0].name}

    );
  }
};

Fix typo in property name


import Ember from 'ember';

export default Ember.Route.extend({
  actions: {
    submit: function () {
      var content = this.controller.get('firstSnippet');

      var snippet = this.store.createRecord('snippet', {
        content: content,
      });

      snippet.save().then(function () {
        var draft = this.store.createRecord('draft', {
          firstLine: snippet.slice(0, 30),
          contributionStatus: 'cooldown',
          canAdmin: true,
          snippetCount: 1,
          lastSnippet: snippet,
        });

        return draft.save();
      }).then(function () {
        this.transitionTo('index');
      });
    }
  }
});

import Ember from 'ember';

export default Ember.Route.extend({
  actions: {
    submit: function () {
      var
        self = this,
        content = this.controller.get('firstSnippet'),
        snippet = this.store.createRecord('snippet', {
          content: content,
      });

      snippet.save().then(function () {
        var draft = self.store.createRecord('draft', {
          firstLine: content.slice(0, 30),
          contributionStatus: 'cooldown',
          canAdmin: true,
          snippetCount: 1,
          lastSnippet: snippet,
        });

        return draft.save();
      }).then(function () {
        return self.transitionTo('index');
      });
    }
  }
});

Create the snippet, then the draft.


var gs = require('glob-stream')
var miss = require('mississippi')
module.exports = (pattern) => miss.pipeline.obj(gs(pattern, {
  nodir: true
}), miss.through.obj((chunk, enc, cb) => cb(null, {
  file: chunk.path,
  status: 'PRESENT'
})))

var gs = require('glob-stream')
var miss = require('mississippi')
module.exports = (pattern) => miss.pipeline.obj(gs(pattern, {
  nodir: true,
  allowEmpty: true
}), miss.through.obj((chunk, enc, cb) => cb(null, {
  file: chunk.path,
  status: 'PRESENT'
})))

Disable failure on empty glob result


/* jshint node: true */
'use strict';
module.exports = function(sequelize, DataTypes) {
  var Exercise = require('./exercise')(sequelize, DataTypes);

  var Set = sequelize.define('Set', {
    weight: DataTypes.STRING,
    reps: DataTypes.INTEGER
  }, {
    classMethods: {
      associate: function(models) {
        // associations can be defined here
      }
    }
  });

  Set.belongsTo(Exercise);
  return Set;
};

/* jshint node: true */
'use strict';
module.exports = function(sequelize, DataTypes) {

  var Set = sequelize.define('Set', {
    weight: DataTypes.STRING,
    reps: DataTypes.INTEGER
  }, {
    classMethods: {
      associate: function(models) {
        // associations can be defined here
        var Exercise = require('./exercise')(sequelize, DataTypes);
        Set.belongsTo(Exercise);
      }
    }
  });

  return Set;
};

Move import inside the runtime call


// top-level container
import React from 'react'
import {connect} from 'react-redux'

import {
  NAME as ROBOT_NAME,
  actions as robotActions,
  selectors as robotSelectors
} from '../robot'

import App from '../components/app'

const mapStateToProps = (state) => {
  return {
    isRunning: state.robot.isRunning,
    isConnected: state.robot.isConnected,
    connectionStatus: robotSelectors.getConnectionStatus(state[ROBOT_NAME]),

    // TODO(mc): remove development hardcoded values
    isPaused: false,
    errors: [],
    runCommands: [
      {
        timestamp: '2:01:43 PM',
        command_description: 'Picking up tip from <Deck><Slot E3><Container p10tiprack><Well A1>'
      },
      {
        timestamp: '2:01:56 PM',
        command_description: 'Aspirating 5 at <Deck><Slot D3><Container template><Well A1>'
      },
      {
        timestamp: '2:02:43 PM',
        command_description: 'Picking up tip from <Deck><Slot E3><Container p10tiprack><Well A1>'
      }
    ]
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    onRunButtonClick: () => dispatch(robotActions.run())
  }
}

function Root (props) {
  return (
    <App {...props} />
  )
}

export default connect(mapStateToProps, mapDispatchToProps)(Root)

// top-level container
import React from 'react'
import {connect} from 'react-redux'

import {
  NAME as ROBOT_NAME,
  actions as robotActions,
  selectors as robotSelectors
} from '../robot'

import App from '../components/app'

const mapStateToProps = (state) => {
  return {
    isRunning: state.robot.isRunning,
    isConnected: state.robot.isConnected,
    connectionStatus: robotSelectors.getConnectionStatus(state[ROBOT_NAME]),

    // TODO(mc): remove development hardcoded values
    isPaused: false,
    errors: [],
    runCommands: [
      {
        timestamp: '2:01:43 PM',
        command_description: 'Picking up tip from <Deck><Slot E3><Container p10tiprack><Well A1>'
      },
      {
        timestamp: '2:01:56 PM',
        command_description: 'Aspirating 5 at <Deck><Slot D3><Container template><Well A1>'
      },
      {
        timestamp: '2:02:43 PM',
        command_description: 'Picking up tip from <Deck><Slot E3><Container p10tiprack><Well A1>'
      }
    ]
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    onRunButtonClick: () => dispatch(robotActions.run()),
    // TODO(mc): revisit when robot discovery / multiple robots is addressed
    onConnectButtonClick: () => dispatch(robotActions.connect())
  }
}

function Root (props) {
  return (
    <App {...props} />
  )
}

export default connect(mapStateToProps, mapDispatchToProps)(Root)

Add simple connect button handler to connect to default robot


import { routerReducer as routing } from 'react-router-redux';
import { reducer as form } from 'redux-form';
import crud from 'redux-crud'
import { combineReducers } from 'redux';

// TODO reducer registry in core via store.replaceReducer() and handle
// module state with a reducer instance for each module/key
const moduleStateReducer = (state = {}, action) => {
  if (!(action.type.startsWith('MODULESTATE'))) return state;
  const newState = Object.assign({}, state);
  const module = action.meta.module;
  const key = action.meta.key;
  switch (action.type) {
    case 'MODULESTATE_UPDATE':
      if (!(module in newState)) newState[module] = {};
      if (!(key in newState[module])) newState[module][key] = {};
      newState[module][key] = Object.assign({}, newState[module], action.payload);
      break;
    case 'MODULESTATE_REPLACE':
      if (!('module' in newState)) newState[module] = {};
      newState[module][key] = Object.assign({}, action.payload);
      break;
  };
  return newState;
};

const rootReducer = combineReducers({
  modules: moduleStateReducer,
  routing,
  form
});

export default rootReducer;

import { routerReducer as routing } from 'react-router-redux';
import { reducer as form } from 'redux-form';
import crud from 'redux-crud'
import { combineReducers } from 'redux';

// TODO reducer registry in core via store.replaceReducer() and handle
// module state with a reducer instance for each module/key
const moduleStateReducer = (state = {}, action) => {
  if (!(action.type.startsWith('MODULESTATE'))) return state;
  const newState = Object.assign({}, state);
  const module = action.meta.module;
  const key = action.meta.key;
  switch (action.type) {
    case 'MODULESTATE_UPDATE':
      if (!(module in newState)) newState[module] = {};
      if (!(key in newState[module])) newState[module][key] = {};
      newState[module][key] = Object.assign({}, newState[module], action.payload);
      break;
    case 'MODULESTATE_REPLACE':
      if (!(module in newState)) newState[module] = {};
      newState[module][key] = Object.assign({}, action.payload);
      break;
  };
  return newState;
};

const rootReducer = combineReducers({
  modules: moduleStateReducer,
  routing,
  form
});

export default rootReducer;

Fix MODULESTATE_REPLACE bug: checked for a member literally called 'module', rather than for a member named according to the content of module.


"use strict";

angular.module('arethusa.relation').directive('nestedMenuCollection', function() {
  return {
    restrict: 'A',
    replace: 'true',
    scope: {
      current: '=',
      all: '=',
      property: '=',
      ancestors: '=',
      emptyVal: '@',
      labelAs: "=",
      change: "&"
    },
    link: function(scope, element, attrs) {
      scope.emptyLabel = "";
      scope.emptyObj = {};
      scope.$watch('current[property]', function(newVal, oldVal) {
        if (newVal !== oldVal) {
          scope.change();
        }
      });

      scope.labelView = function(labelObj) {
        if (scope.labelAs) {
          return labelObj[scope.labelAs];
        } else {
          return labelObj.short;
        }
      };
    },
    template: '\
      <ul>\
        <li ng-if="emptyVal"\
          nested-menu\
          property="property"\
          rel-obj="current"\
          ancestors="ancestors"\
          label="emptyLabel"\
          label-obj="emptyObj">\
        </li>\
        <li\
          ng-repeat="(label, labelObj) in all"\
          nested-menu\
          property="property"\
          rel-obj="current"\
          ancestors="ancestors"\
          label="labelView(labelObj)"\
          label-as="labelAs"\
          label-obj="labelObj">\
        </li>\
      </ul>\
    '
  };
});

"use strict";

angular.module('arethusa.relation').directive('nestedMenuCollection', function() {
  return {
    restrict: 'A',
    replace: 'true',
    scope: {
      current: '=',
      all: '=',
      property: '=',
      ancestors: '=',
      emptyVal: '@',
      labelAs: "=",
      change: "&"
    },
    link: function(scope, element, attrs) {
      scope.emptyLabel = "";
      scope.emptyObj = {};
      scope.$watch('current[property]', function(newVal, oldVal) {
        if (newVal !== oldVal) {
          scope.change();
        }
      });

      scope.labelView = function(labelObj) {
        if (scope.labelAs) {
          return labelObj[scope.labelAs];
        } else {
          return labelObj.short;
        }
      };
    },
    template: '\
      <ul>\
        <li ng-if="emptyVal"\
          nested-menu\
          property="property"\
          rel-obj="current"\
          ancestors="ancestors"\
          label="emptyLabel"\
          label-obj="emptyObj">\
        </li>\
        <li\
          ng-repeat="label in all | keys"\
          nested-menu\
          property="property"\
          rel-obj="current"\
          ancestors="ancestors"\
          label="labelView(all[label])"\
          label-as="labelAs"\
          label-obj="all[label]">\
        </li>\
      </ul>\
    '
  };
});

Use keys filter in nestedMenuCollection


var _ = require('lodash-node');

var Action = function(name, two, three, four) {
  var options, run, helpers;

  // Syntax .extend('action#method', function() { ... }, {})
  if ( typeof two == 'function' ) {
    options = {};
    run = two;
    helpers = three;
  }

  // Syntax .extend('action#method', {}, function() { ... }, {})
  if ( typeof three == 'function' ) {
    options = two;
    run = three;
    helpers = four;
  }

  var presets = {

    name: name,

    description: name + ' description',

    inputs: {
      required: [],
      optional: []
    },

    blockedConnectionTypes: [],

    outputExample: {},

    run: function(api, conn, next){

      this.api = api;
      this.connection = conn;
      this.next = function(success) {
        next(conn, success);
      };
      this.json = conn.response;
      this.params = conn.params;

      run.apply(this, arguments);
    }
  };

  return _.merge(presets, options, helpers);
};

module.exports = Action;

var _ = require('lodash-node');

var Action = function(name, two, three, four) {
  var options, run, helpers;

  var run = two;
  var options = three;

  var presets = {

    name: name,

    description: name + ' description',

    inputs: {
      required: [],
      optional: []
    },

    blockedConnectionTypes: [],

    outputExample: {},

    run: function(api, conn, next){

      this.api = api;
      this.connection = conn;
      this.json = this.connection.response;
      this.next = function(success) {
        next(conn, success);
      };
      this.params = conn.params;

      run.apply(this, arguments);
    }
  };

  return _.merge(presets, options);
};

module.exports = Action;

Remove unnecessary logic in Action helper


#!/usr/bin/env node
//
// NodeJS script that implements an authentication daemon for eJabberd.
//
'use strict';

var assert  = require('assert'),
    etc     = require('etc'),
    yml     = require('etc-yaml'),
    conf    = etc().use(yml).etc().add({
        "ejabberd-auth": {
            method: 'ldap',
            ldap: {
                uri: 'ldap://127.0.0.1',
                filter: '(objectClass=*)'
            }
        }
    }),
    ldapConf = conf.get('ejabberd-auth:ldap');
assert.equal(conf.get('ejabberd-auth:method'), 'ldap', "LDAP is currently the only supported method.");

require('../lib/auth-ldap').start(ldapConf);

#!/usr/bin/env node
//
// NodeJS script that implements an authentication daemon for eJabberd.
//
'use strict';

var assert  = require('assert'),
    etc     = require('etc'),
    yml     = require('etc-yaml'),
    conf    = etc().use(yml).etc().add({
        "ejabberd-auth": {
            method: 'ldap',
            ldap: {
                uri: 'ldap://127.0.0.1',
                filter: '(userPassword=*)',
                uuidAttr: 'uuid'
            }
        }
    }),
    ldapConf = conf.get('ejabberd-auth:ldap');
assert.equal(conf.get('ejabberd-auth:method'), 'ldap', "LDAP is currently the only supported method.");

require('../lib/auth-ldap').start(ldapConf);

Fix the default LDAP properties to match the configuration documentation.


#!/usr/bin/env node

/* eslint-disable strict */
/* eslint-disable no-console */
/* eslint-disable prefer-template */

'use strict';

// A simple check that node + npm versions
// meet the expected minimums.

const exec = require('shelljs').exec;
const chalk = require('chalk');
const semver = require('semver');

const MIN_NODE_VERSION = 4;
const MIN_NPM_VERSION = 3;

const NODE_VERSION = process.versions.node;
const NPM_VERSION = exec('npm --version', {silent: true}).stdout;

let versionCheckPassed = true;

if (semver.major(NODE_VERSION) < MIN_NODE_VERSION) {
  console.log(chalk.red('Node version must be at least: ' + MIN_NODE_VERSION));
  versionCheckPassed = false;
}

if (semver.major(NPM_VERSION) < MIN_NPM_VERSION) {
  console.log(chalk.red('NPM version must be at least: ' + MIN_NPM_VERSION));
  versionCheckPassed = false;
}

if (versionCheckPassed === false) {
  process.exit(1);
}

#!/usr/bin/env node

// eslint messages suppressed to allow this to work
// in older node versions.

/* eslint-disable strict */
/* eslint-disable no-var */
/* eslint-disable no-console */
/* eslint-disable prefer-template */

'use strict';

// A simple check that node + npm versions
// meet the expected minimums.

var exec = require('shelljs').exec;
var chalk = require('chalk');
var semver = require('semver');

var MIN_NODE_VERSION = 4;
var MIN_NPM_VERSION = 3;

var NODE_VERSION = process.versions.node;
var NPM_VERSION = exec('npm --version', {silent: true}).stdout;

var versionCheckPassed = true;

if (semver.major(NODE_VERSION) < MIN_NODE_VERSION) {
  console.log(chalk.red('Node version must be at least: ' + MIN_NODE_VERSION));
  versionCheckPassed = false;
}

if (semver.major(NPM_VERSION) < MIN_NPM_VERSION) {
  console.log(chalk.red('NPM version must be at least: ' + MIN_NPM_VERSION));
  versionCheckPassed = false;
}

if (versionCheckPassed === false) {
  process.exit(1);
}

Allow version check to actually run on older node versions


/*
  A bot that welcomes new guild members when they join
*/

// Import the discord.js module
const Discord = require('discord.js');

// Create an instance of a Discord Client
const client = new Discord.Client();

// The token of your bot - https://discordapp.com/developers/applications/me
const token = 'your bot token here';

// The ready event is vital, it means that your bot will only start reacting to information
// from Discord _after_ ready is emitted
client.on('ready', () => {
  console.log('I am ready!');
});

// Create an event listener for new guild members
client.on('guildMemberAdd', member => {
  // Send the message, mentioning the member
  member.guild.defaultChannel.send(`Welcome to the server, ${member}!`);

  // If you want to send the message to a designated channel on a server instead
  // you can do the following:
  const channel = member.guild.channels.find('name', 'member-log');
  // Do nothing if the channel wasn't found on this server
  if (!channel) return;
  channel.send(`Welcome to the server, ${member}`);
});

// Log our bot in
client.login(token);

/*
  A bot that welcomes new guild members when they join
*/

// Import the discord.js module
const Discord = require('discord.js');

// Create an instance of a Discord Client
const client = new Discord.Client();

// The token of your bot - https://discordapp.com/developers/applications/me
const token = 'your bot token here';

// The ready event is vital, it means that your bot will only start reacting to information
// from Discord _after_ ready is emitted
client.on('ready', () => {
  console.log('I am ready!');
});

// Create an event listener for new guild members
client.on('guildMemberAdd', member => {
  // Send the message, mentioning the member to the guilds default channel (usually #general)
  member.guild.defaultChannel.send(`Welcome to the server, ${member}!`);

  // If you want to send the message to a designated channel on a server instead
  // you can do the following:
  const channel = member.guild.channels.find('name', 'member-log');
  // Do nothing if the channel wasn't found on this server
  if (!channel) return;
  // Send the message, mentioning the member
  channel.send(`Welcome to the server, ${member}`);
});

// Log our bot in
client.login(token);

Add some more comments to the example


import { mainLizmap, mainEventDispatcher } from '../modules/Globals.js';
import olMap from 'ol/Map';
import View from 'ol/View';

/** Class initializing Openlayers Map. */
export default class Map {

    constructor() {
        this._olMap = new olMap({
            view: new View({
                center: [0, 0],
                zoom: 2,
                constrainResolution: true
            }),
            target: 'newOlMap'
        });

        // Init view
        this.syncViews();

        // Listen to old map events to dispatch new ones
        mainLizmap.lizmap3.map.events.on({
            moveend: () => {
                this.syncViews();

                mainEventDispatcher.dispatch('map.moveend');
            },
            zoomend: () => {
                this.syncViews();

                mainEventDispatcher.dispatch('map.zoomend');
            }
        });
    }

    /**
     * Synchronize new OL view with old one
     * @memberof Map
     */
    syncViews(){
        this._olMap.getView().setResolution(mainLizmap.lizmap3.map.getResolution());

        const lizmap3Center = mainLizmap.lizmap3.map.getCenter();
        this._olMap.getView().setCenter([lizmap3Center.lon, lizmap3Center.lat]);
    }
}

import { mainLizmap, mainEventDispatcher } from '../modules/Globals.js';
import olMap from 'ol/Map';
import View from 'ol/View';

/** Class initializing Openlayers Map. */
export default class Map {

    constructor() {
        this._olMap = new olMap({
            view: new View({
                center: [0, 0],
                zoom: 2,
                projection: mainLizmap.projection,
                constrainResolution: true
            }),
            target: 'newOlMap'
        });

        // Init view
        this.syncViews();

        // Listen to old map events to dispatch new ones
        mainLizmap.lizmap3.map.events.on({
            moveend: () => {
                this.syncViews();

                mainEventDispatcher.dispatch('map.moveend');
            },
            zoomend: () => {
                this.syncViews();

                mainEventDispatcher.dispatch('map.zoomend');
            }
        });
    }

    /**
     * Synchronize new OL view with old one
     * @memberof Map
     */
    syncViews(){
        this._olMap.getView().setResolution(mainLizmap.lizmap3.map.getResolution());

        const lizmap3Center = mainLizmap.lizmap3.map.getCenter();
        this._olMap.getView().setCenter([lizmap3Center.lon, lizmap3Center.lat]);
    }
}

Initialize OL6 view projection with OL2 one


$('#send').submit(function(e) {
	$('.flash').text("");
	var name = $('#name').val();
	var email = $('#email').val();
	var text = $('#text').val();

	if (name && email && text) {
		console.log('Conditions met.');
		emailjs.send("gmail", "standard",
			{
				"name": name,
				"email": email,
				"message": text
			}).then(function(response) {
				console.log(response.status + response.text);
				$('.flash').text("Message sent!");
			}, function(err) {
				console.log(err);
				$('.flash').text("Unable to send message. Try again later!");
			});
		$('#name').val("");
		$('#email').val("");
		$('#text').val("");
	} else {
		$('.flash').text("All fields required.");
	}
	e.preventDefault();
});

$('#send').on('reset', function() {
	$('.flash').text("");
});

Clear flashed messages on reset