import _ from 'lodash';
import webpack from 'webpack';

function addWebpackDevServerScripts(entries, webpackDevServerAddress) {
  let clientScript = `webpack-dev-server/client?${webpackDevServerAddress}`;
  let webpackScripts = ['webpack/hot/dev-server', clientScript];
  return _.mapValues(entries, entry => webpackScripts.concat(entry));
}

export default (config, options) => {
  if (options.development && options.docs) {
    let webpackDevServerAddress = `http://localhost:${options.port}`;
    config = _.extend({}, config, {
      entry: addWebpackDevServerScripts(config.entry, webpackDevServerAddress),
      output: _.extend({}, config.output, {
        publicPath: `${webpackDevServerAddress}/assets/`
      }),
      module: _.extend({}, config.module, {
        loaders: config.module.loaders.map(value => {
          if (/js/.test(value.test.toString())) {
            return _.extend({}, value, {
              loader: 'react-hot!' + value.loader
            });
          }
          else {
            return value;
          }
        })
      }),
      // Remove extract text plugin from dev workflow so hot reload works on css.
      plugins: config.plugins.concat([
        new webpack.NoErrorsPlugin()
      ])
    });

    return config;
  }

  return config;
};

import _ from 'lodash';
import webpack from 'webpack';

function addWebpackDevServerScripts(entries, webpackDevServerAddress) {
  let clientScript = `webpack-dev-server/client?${webpackDevServerAddress}`;
  let webpackScripts = ['webpack/hot/dev-server', clientScript];
  return _.mapValues(entries, entry => webpackScripts.concat(entry));
}

export default (config, options) => {
  if (options.development && options.docs) {
    let webpackDevServerAddress = `http://localhost:${options.port}`;
    config = _.extend({}, config, {
      entry: addWebpackDevServerScripts(config.entry, webpackDevServerAddress),
      output: _.extend({}, config.output, {
        publicPath: `${webpackDevServerAddress}/assets/`
      }),
      module: _.extend({}, config.module, {
        loaders: config.module.loaders.map(value => {
          if (/\.js\/$/.test(value.test.toString())) {
            return _.extend({}, value, {
              loader: 'react-hot!' + value.loader
            });
          }
          else {
            return value;
          }
        })
      }),
      // Remove extract text plugin from dev workflow so hot reload works on css.
      plugins: config.plugins.concat([
        new webpack.NoErrorsPlugin()
      ])
    });

    return config;
  }

  return config;
};

Fix regexp to test regexps in tools.


var _atom = require('atom');
var SelectListView = _atom.SelectListView;
var helpers          = require('atom-helpers');

function PackageListView() {
  SelectListView.__super__.constructor.apply(this, arguments);
}

helpers.extends(PackageListView, SelectListView);
PackageListView.prototype.currentInstall;
PackageListView.prototype.initialize = function() {
  PackageListView.__super__.initialize.apply(this, arguments);
  return this;
};

PackageListView.prototype.destroy = function() {
  this.currentCommand = null;
  return PackageListView.__super__.detach.apply(this, arguments);
};

PackageListView.prototype.viewForItem = function(item) {
  return '
' + item + '
';
};

PackageListView.prototype.confirmed = function(packageListName) {
  var utils = require('./Utils.js');
  utils.loadPackageList(packageListName);
};

PackageListView.prototype.showPackageList = function(item) {
  var items = ['PHP', 'Javascript'];
  this.addClass('overlay from-top');
  this.setItems(items);
  atom.workspaceView.append(this);
  this.focusFilterEditor();
};

module.exports = PackageListView;

var _atom = require('atom');
var SelectListView = _atom.SelectListView;
var helpers          = require('atom-helpers');

function PackageListView() {
  SelectListView.__super__.constructor.apply(this, arguments);
}

helpers.extends(PackageListView, SelectListView);
PackageListView.prototype.currentInstall;
PackageListView.prototype.initialize = function() {
  PackageListView.__super__.initialize.apply(this, arguments);
  return this;
};

PackageListView.prototype.destroy = function() {
  this.currentCommand = null;
  return PackageListView.__super__.detach.apply(this, arguments);
};

PackageListView.prototype.viewForItem = function(item) {
  return '
' + item + '
';
};

PackageListView.prototype.confirmed = function(packageListName) {
  var utils = require('./Utils.js');
  utils.loadPackageList(packageListName);
  this.remove();
};

PackageListView.prototype.showPackageList = function(item) {
  var items = ['PHP', 'Javascript'];
  this.addClass('overlay from-top');
  this.setItems(items);
  atom.workspaceView.append(this);
  this.focusFilterEditor();
};

module.exports = PackageListView;

Remove package list on confirmation


function checkDuplicateUser(playQueue, video, config) {
  if (config.userQueueLimit <= -1) return false;

  var count = 0;
  for (element in playQueue) {
    if (element.userId === video.userId) count++;
  }

  return (count > config.userQueueLimit);
}

function checkDuplicateVideo(playQueue, video, config) {
  for (element in playQueue) {
    if (element.vid === video.vid) return true;
  }

  return false;
}

function checkVideoLength(playQueue, video, config) {
  if (config.maxLength > -1) {
    if (video.length_seconds > config.maxLength) {
      return true;
    }
  }

  return false;
}

var checks = [
  [checkDuplicateVideo, 'That video is already in the queue'],
  [checkDuplicateUser, 'You already have two videos in the queue'],
  [checkVideoLength, 'That video is too long!'],
];

function shouldDisallowQueue(playQueue, video, config) {
  for (check of checks) {
    if (config.checksEnabled[check[0].name]) {
      result = check[0](playQueue, video, config);
      if (result) return check[1];
    }
  }

  return false;
}

module.exports = shouldDisallowQueue;

function checkDuplicateUser(playQueue, video, config) {
  if (config.userQueueLimit <= -1) return false;

  var count = 0;
  for (element of playQueue) {
    if (element.userId === video.userId) count++;
  }

  return (count > config.userQueueLimit);
}

function checkDuplicateVideo(playQueue, video, config) {
  for (element of playQueue) {
    if (element.vid === video.vid) return true;
  }

  return false;
}

function checkVideoLength(playQueue, video, config) {
  if (config.maxLength > -1) {
    if (video.length_seconds > config.maxLength) {
      return true;
    }
  }

  return false;
}

var checks = [
  [checkDuplicateVideo, 'That video is already in the queue'],
  [checkDuplicateUser, 'You already have two videos in the queue'],
  [checkVideoLength, 'That video is too long!'],
];

function shouldDisallowQueue(playQueue, video, config) {
  for (check of checks) {
    if (config.checksEnabled[check[0].name]) {
      result = check[0](playQueue, video, config);
      if (result) return check[1];
    }
  }

  return false;
}

module.exports = shouldDisallowQueue;

Use of instead of in for iteration


(function ($) {
  jQuery.expr[':'].contains = function (obj, index, meta){
    return (obj.textContent || obj.innerText || "").toUpperCase().indexOf(meta[3].toUpperCase()) >= 0;
  };

  var headingHasVisibleCountries = function(headingFirstLetter) {
    return $("section#" + headingFirstLetter.toUpperCase()).find("li:visible").length > 0;
  };

  var input = $("form#country-filter input#country");
  var listItems = $("ul.countries li");
  var countryHeadings = $(".inner section").not(":first").children("h1");

  var filterHeadings = function() {
    countryHeadings.each(function(index, elem) {
      var $elem = $(elem),
          header = $elem.text();
      if(headingHasVisibleCountries(header)) {
        $elem.show();
      } else {
        $elem.hide();
      }
    });
  };

  input.change(function () {
    var filter = $(this).val();

    if (filter && filter.length > 0) {
      listItems.show();
      listItems.filter(":not(:contains(" + filter + "))").hide();
      filterHeadings();
    } else {
      listItems.show();
      countryHeadings.show();
    }

    return false;
  }).keyup(function () {
    $(this).change();
  });
}(jQuery));

(function ($) {
  jQuery.expr[':'].contains = function(obj, index, meta){
    return (obj.textContent || obj.innerText || "").toUpperCase().indexOf(meta[3].toUpperCase()) >= 0;
  };

  var headingHasVisibleCountries = function(headingFirstLetter) {
    return $("section#" + headingFirstLetter.toUpperCase()).find("li:visible").length > 0;
  };

  $(function() {
    var input = $("form#country-filter input#country"),
        listItems = $("ul.countries li"),
        countryHeadings = $(".inner section").not(":first").children("h1");

    var filterHeadings = function() {
      countryHeadings.each(function(index, elem) {
        var $elem = $(elem),
            header = $elem.text();

        if(headingHasVisibleCountries(header)) {
          $elem.show();
        } else {
          $elem.hide();
        }

      });
    };

    input.change(function() {
      var filter = $(this).val();

      if (filter && filter.length > 0) {
        listItems.show();
        listItems.filter(":not(:contains(" + filter + "))").hide();
        filterHeadings();
      } else {
        listItems.show();
        countryHeadings.show();
      }

      return false;

    }).keyup(function() {
      $(this).change();
    });
  });
}(jQuery));

Move code into DOM Ready event


'use strict';

var fs = require('fs')
  , _find = require('lodash.find')
  , prompt = require('inquirer').prompt
  , defaults = require('../conf/cli-defaults')();

/**
 * Initialization questions
 */
var props = [{
  name: 'trelloApiKey',
  message: 'Trello API key',
  default: defaults.trelloApiKey
},{
  name: 'trelloToken',
  message: 'Trello auth token',
  default: defaults.trelloToken
},{
  name: 'trelloBoardUrl',
  message: 'Trello board URL',
  default: defaults.trelloBoardUrl
}];

module.exports = function(opts, cb) {
  var finish = function(result) {
    if(opts.dryRun) {
      console.log(JSON.stringify(result, null, ' '));
      process.exit(0);
    }

    var pkg = require('../package.json');

    result.$$stello = {
      version: pkg.version,
      intialized: Date.now()
    };

    var rcData = JSON.stringify(result, null, '  ')
      , writeErr = require('fs').writeFileSync('.stellorc', rcData);
    (cb || function(){})(writeErr, result);
  };

  // opts might just have defaults... only skip prompts for items that still
  // have a default value
  Object.keys(opts).forEach(function(k) {
    if(opts[k] !== defaults[k]) {
      var prop = _find(props, {name: k});
      if(prop) {
        prop.default = opts[k];
      }
    }
  });

  prompt(props, finish);
};

'use strict';

var fs = require('fs')
  , _find = require('lodash.find')
  , prompt = require('inquirer').prompt
  , stellorc = require('./stellorc')
  , defaults = require('../conf/cli-defaults')();

/**
 * Initialization questions
 */
var props = [{
  name: 'trelloApiKey',
  message: 'Trello API key',
  default: defaults.trelloApiKey
},{
  name: 'trelloToken',
  message: 'Trello auth token',
  default: defaults.trelloToken
},{
  name: 'trelloBoardUrl',
  message: 'Trello board URL',
  default: defaults.trelloBoardUrl
}];

module.exports = function(opts, cb) {
  var finish = function(result) {
    if(opts.dryRun) {
      console.log(JSON.stringify(result, null, ' '));
      process.exit(0);
    }

    var pkg = require('../package.json');

    result.$$stello = {
      version: pkg.version,
      intialized: Date.now()
    };

    stellorc.write(result, cb);
  };

  // opts might just have defaults... only skip prompts for items that still
  // have a default value
  Object.keys(opts).forEach(function(k) {
    if(opts[k] !== defaults[k]) {
      var prop = _find(props, {name: k});
      if(prop) {
        prop.default = opts[k];
      }
    }
  });

  prompt(props, finish);
};

Use stellorc lib for storing prefs


var testPages = [
  'specs/util'

  , 'specs/config'
  , 'specs/module'
  , 'specs/package'
  , 'specs/extensible'
  , 'specs/extensible/plugin-debug'

  , 'specs/misc/data-api'
  , 'specs/misc/ie-cache'
  , 'specs/misc/utf8-in-gbk'
  , 'specs/misc/x-ua-compatible'

  , 'research/derive-uri'
]

var testPages = [
  'specs/util'

  , 'specs/config'
  , 'specs/module'
  , 'specs/package'
  , 'specs/extensible'
  , 'specs/extensible/plugin-debug'

  , 'specs/misc/bootstrap-async'
  , 'specs/misc/data-api'
  , 'specs/misc/ie-cache'
  , 'specs/misc/utf8-in-gbk'
  , 'specs/misc/x-ua-compatible'

  , 'research/derive-uri'
]

Add test specs for async bootstrap


module.exports = function () {
	return function (irc) {
		irc.join = function (channels, fn) {
			if (!Array.isArray(channels)) {
				channels = [channels];
			}

			this.write('JOIN ' + channels.join(','), fn);
		};

		irc.on('data', function (msg) {
			if (msg.command !== 'JOIN') {return;}

			var e = {
				nick: msg.nick,
				host: msg.host,
				target: msg.params.toLowerCase(),
				isSelf: msg.nick === this.nick,
				raw: msg.string
			};

			if (e.isSelf) {
				this.emit('join:self', e);
			}

			irc.emit('join', e);

		});
	};
};

module.exports = function () {
	return function (irc) {
		irc.join = function (channels, fn) {
			if (!Array.isArray(channels)) {
				channels = [channels];
			}

			this.write('JOIN ' + channels.join(','), fn);
		};

		irc.on('data', function (msg) {
			if (msg.command !== 'JOIN') {return;}

			var e = {
				nick: msg.nick,
				host: msg.host,
				target: (msg.params.trim() || msg.trailing.trim()).toLowerCase(),
				isSelf: msg.nick === this.nick,
				raw: msg.string
			};

			if (e.isSelf) {
				this.emit('join:self', e);
			}

			irc.emit('join', e);

		});
	};
};

Join can have the channel name as a param or the trailing.


const packager = require('electron-packager')
const path = require('path')

const options = {
  dir: '.',
  name: 'todu',
  out: 'dist/',
  overwrite: true,
  icon: path.resolve(__dirname, 'src/resource/img/icon.ico'),
  ignore: [
    '^/(?!src|package\\.json|window\\.js)',
    '^/src/(storage|component|plugin)($|/)',
  ],
  win32metadata: {
    OriginalFilename: 'todu.exe',
  }
}

packager(options, appPaths => {
  console.log('Build finished.')
})

const packager = require('electron-packager')
const path = require('path')

const options = {
  dir: '.',
  name: 'todu',
  out: 'dist/',
  overwrite: true,
  icon: path.resolve(__dirname, 'src/resource/img/icon.ico'),
  ignore: [
    '^/(?!src|package\\.json|window\\.js)',
    '^/src/(storage|component|plugin)($|/)',
  ],
  win32metadata: {
    FileDescription: 'TODU',
    OriginalFilename: 'todu.exe',
  }
}

packager(options, appPaths => {
  console.log('Build finished.')
})

Fix case of file description displayed in task bar


import React, { Component } from 'react';
import { Router, Route, Switch } from 'react-router-dom';
import { hot } from 'react-hot-loader';

import SignUpForm from './SignUpForm';

class App extends Component {
    render() {
        return(
            <Router history = { this.props.history }>
                <Switch>
                    <Route path = '/signup' component = { SignUpForm }/>
                </Switch>
            </Router>
        )
    }

}

export default hot(module)(App);

import React, { Component } from 'react';
import { Router, Route, Switch } from 'react-router-dom';
import { hot } from 'react-hot-loader';

import SignUpPage from './SignUp/SignUpPage';

class App extends Component {
    render() {
        return(
            <Router history = { this.props.history }>
                <Switch>
                    <Route path = '/signup' component = { SignUpPage }/>
                </Switch>
            </Router>
        )
    }
}

export default hot(module)(App);

Replace SignUpForm component with SignUpPage container


$(function() {
  // only run if the correct page
  if (!($('body').hasClass('students') && $('body').hasClass('show'))) return;

  // imports
  var createEl = window.shared.ReactHelpers.createEl;
  var PageContainer = window.shared.PageContainer;
  var parseQueryString = window.shared.parseQueryString;

  // entry point, reading static bootstrapped data from the page
  var serializedData = $('#serialized-data').data();
  ReactDOM.render(createEl(PageContainer, {
    nowMomentFn: function() { return moment.utc(); },
    serializedData: serializedData,
    queryParams: parseQueryString(window.location.search)
  }), document.getElementById('main'));
});

$(function() {
  // only run if the correct page
  if (!($('body').hasClass('students') && $('body').hasClass('show'))) return;

  // imports
  var createEl = window.shared.ReactHelpers.createEl;
  var PageContainer = window.shared.PageContainer;
  var parseQueryString = window.shared.parseQueryString;

  // mixpanel analytics
  MixpanelUtils.registerUser(serializedData.currentEducator);
  MixpanelUtils.track('PAGE_VISIT', { page_key: 'STUDENT_PROFILE' });

  // entry point, reading static bootstrapped data from the page
  var serializedData = $('#serialized-data').data();
  ReactDOM.render(createEl(PageContainer, {
    nowMomentFn: function() { return moment.utc(); },
    serializedData: serializedData,
    queryParams: parseQueryString(window.location.search)
  }), document.getElementById('main'));
});

Add MixPanel page view tracking for the student profile page


export const initialState = {
  loading: false,
  loaded: false,
  error: false,
  data: {}
};

const setLoading = (state, loading) => ({ ...state, loading });
const setError = (state, error) => ({ ...state, error });
const setLoaded = (state, loaded) => ({ ...state, loaded });

export default {
  fetchNDCSInit: state => setLoading(state, true),
  fetchNDCSReady: (state, { payload }) =>
    setLoaded(
      setLoading(
        {
          ...state,
          data: {
            categories: payload.categories,
            mapCategories: state.data.mapCategories || {},
            sectors: payload.sectors,
            mapIndicators: state.data.mapIndicators || [],
            indicators: payload.indicators
          }
        },
        false
      ),
      true
    ),
  fetchNDCSMapIndicatorsReady: (state, { payload }) =>
    setLoaded(
      setLoading(
        {
          ...state,
          data: {
            categories: state.data.categories || {},
            mapCategories: payload.categories,
            sectors: payload.sectors,
            indicators: state.data.indicators || [],
            mapIndicators: payload.indicators
          }
        },
        false
      ),
      true
    ),
  fetchNDCSFail: state => setError(state, true)
};

export const initialState = {
  loading: false,
  loaded: false,
  error: false,
  data: {}
};

const setLoading = (state, loading) => ({ ...state, loading });
const setError = (state, error) => ({ ...state, error });
const setLoaded = (state, loaded) => ({ ...state, loaded });

export default {
  fetchNDCSInit: state => setLoading(state, true),
  fetchNDCSReady: (state, { payload }) =>
    setLoaded(
      setLoading(
        {
          ...state,
          data: {
            ...state.data,
            sectors: payload.sectors,
            categories: payload.categories,
            indicators: payload.indicators
          }
        },
        false
      ),
      true
    ),
  fetchNDCSMapIndicatorsReady: (state, { payload }) =>
    setLoaded(
      setLoading(
        {
          ...state,
          data: {
            ...state.data,
            sectors: payload.sectors,
            mapCategories: payload.categories,
            mapIndicators: payload.indicators
          }
        },
        false
      ),
      true
    ),
  fetchNDCSFail: state => setError(state, true)
};

Use object spread instead of duplicating


// @flow
import { BrowserWindow } from 'electron'

import type { BrowserWindowOptions, BrowserWindowEvents, WebContentsEvents } from 'electron'

export default class Window {
  constructor (opts: BrowserWindowOptions) {
    this.window = new BrowserWindow(opts)
    this.window.on('close', this.onClose)
    this.willForceQuit = false
  }

  willForceQuit: boolean
  window: BrowserWindow

  onClose = (e: any) => {
    if (!this.willForceQuit) {
      e.preventDefault()
      this.window.hide()
    }
  }

  get id (): string {
    return this.window.id
  }

  get url (): string {
    throw new Error('Should override url getter')
  }

  forceQuit () {
    this.willForceQuit = true
  }

  create (initialState?: Object) {
    this.window.loadURL(this.url)
    if (initialState != null) {
      this.window.webContents.on('did-finish-load', () => {
        this.send('initialState', initialState)
      })
    }
  }

  show () {
    this.window.show()
  }

  on (channel: BrowserWindowEvents, callback: Function) {
    this.window.on(channel, callback)
  }

  onWebContents (channel: WebContentsEvents, callback: Function) {
    this.window.webContents.on(channel, callback)
  }

  send (channel: string, ...args: Array<any>) {
    this.window.webContents.send(channel, ...args)
  }
}

// @flow
import { BrowserWindow } from 'electron'

import type { BrowserWindowOptions, BrowserWindowEvents, WebContentsEvents } from 'electron'

export default class Window {
  static defaultOptions = {
    show: false,
  }

  constructor (opts: BrowserWindowOptions) {
    this.window = new BrowserWindow(Object.assign({}, Window.defaultOptions, opts))
    this.window.on('close', this.onClose)
    this.window.once('ready-to-show', this.window.show)
    this.willForceQuit = false
  }

  willForceQuit: boolean
  window: BrowserWindow

  onClose = (e: any) => {
    if (!this.willForceQuit) {
      e.preventDefault()
      this.window.hide()
    }
  }

  get id (): string {
    return this.window.id
  }

  get url (): string {
    throw new Error('Should override url getter')
  }

  forceQuit () {
    this.willForceQuit = true
  }

  create (initialState?: Object) {
    this.window.loadURL(this.url)
    if (initialState != null) {
      this.window.webContents.on('did-finish-load', () => {
        this.send('initialState', initialState)
      })
    }
  }

  show () {
    this.window.show()
  }

  on (channel: BrowserWindowEvents, callback: Function) {
    this.window.on(channel, callback)
  }

  onWebContents (channel: WebContentsEvents, callback: Function) {
    this.window.webContents.on(channel, callback)
  }

  send (channel: string, ...args: Array<any>) {
    this.window.webContents.send(channel, ...args)
  }
}

Make window be shown after ready-to-show


// Main Module for searchthedocs demo
define(function (require) {
  var _ = require('underscore'),
    $ = require('jquery'),
    SearchTheDocsView = require('searchthedocs/src/searchthedocs');

  var searchthedocs_main = function() {

    var search_options = {
      default_endpoint: 'sections',
      endpoints: {
        sections: {
          data_type: 'json',
          //default_params: {format: 'jsonp'},
          api_url: 'http://searchtd.dev:8000/api/v2/search/section/',
          record_url:
            'http://{{domain}}.readthedocs.org/en/'
            + '{{version}}/{{path}}.html#{{page_id}}',
          param_map: {
            search: 'q',
            domain: 'project'
          },
          result_format: {
            records: 'results.hits.hits',
            record_format: {
              // Fields used in sidebar display
              domain: 'fields.project',
              title: 'fields.title',
              // Fields used to build record_url
              version: 'fields.version',
              path: 'fields.setup',
              page_id: 'fields.quick-install'
            }
          },
      }
    }
  };

    window.searchtd = new SearchTheDocsView({
      brand: 'searchthedocs',
      brand_href: '#',
      search_options: search_options
    });

   $('#searchthedocs-container').append(searchtd.render().el);

  };

  return searchthedocs_main;

});

// Main Module for searchthedocs demo
define(function (require) {
  var _ = require('underscore'),
    $ = require('jquery'),
    SearchTheDocsView = require('searchthedocs/src/searchthedocs');

  var searchthedocs_main = function() {

    var search_options = {
      default_endpoint: 'sections',
      endpoints: {
        sections: {
          data_type: 'json',
          //default_params: {format: 'jsonp'},
          api_url: 'http://searchtd.dev:8000/api/v2/search/section/',
          record_url:
            'http://{{domain}}.readthedocs.org/en/'
            + '{{version}}/{{path}}.html#{{page_id}}',
          param_map: {
            search: 'q',
            domain: 'project'
          },
          result_format: {
            records_path: 'results.hits.hits',
            record_format: {
              // Fields used in sidebar display
              domain: 'fields.project',
              title: 'fields.title',
              // Fields used to build record_url
              version: 'fields.version',
              path: 'fields.path',
              page_id: 'fields.page_id'
            }
          },
      }
    }
  };

    window.searchtd = new SearchTheDocsView({
      brand: 'searchthedocs',
      brand_href: '#',
      search_options: search_options
    });

   $('#searchthedocs-container').append(searchtd.render().el);

  };

  return searchthedocs_main;

});

Fix typos in record_format hash


'use strict'

import FS from 'fs'
import promisify from 'sb-promisify'
import open from 'open'
import chalk from 'chalk'
import { exec } from 'sb-exec'
import { CLI } from './cli'
import { run as runStartup, build } from './startup'
import server from './server'
import builder from './builder'

const realPath = promisify(FS.realpath)

// print - so we can easily weed out console.logs
global.print = console.log.bind(console)

async function run(options) {
  const cli = new CLI()
  cli.activate()
  await runStartup(options, cli)

  // Startup messages
  cli.log(chalk.green('Server running at') + ' ' + chalk.yellow('http://' + server.url()))

  cli.addCommand('open', 'Open this project in Browser', async function() {
    open('http://' + server.url())
  })
  cli.addCommand('editor', 'Open this project in Atom', async function() {
    const directory = await realPath(process.cwd())
    await exec('atom', [directory])
  })
  cli.addCommand('build', 'Build dist files of your motion app', async function() {
    await builder.build()
  })
  return cli
}

// TODO: This is for backward compatibility only, remove this in the upcoming future
export default { build, run }

'use strict'

import FS from 'fs'
import promisify from 'sb-promisify'
import open from 'open'
import chalk from 'chalk'
import { exec } from 'sb-exec'
import { CLI } from './cli'
import { run as runStartup, build } from './startup'
import server from './server'
import builder from './builder'

const realPath = promisify(FS.realpath)

// print - so we can easily weed out console.logs
global.print = console.log.bind(console)

async function run(options) {
  const cli = new CLI()
  cli.activate()
  // Make printing go to our shiny UI
  global.print = function() {
    cli.log(...arguments)
  }
  await runStartup(options, cli)

  // Startup messages
  cli.log(chalk.green('Server running at') + ' ' + chalk.yellow('http://' + server.url()))

  cli.addCommand('open', 'Open this project in Browser', async function() {
    open('http://' + server.url())
  })
  cli.addCommand('editor', 'Open this project in Atom', async function() {
    const directory = await realPath(process.cwd())
    await exec('atom', [directory])
  })
  cli.addCommand('build', 'Build dist files of your motion app', async function() {
    await builder.build()
  })
  return cli
}

// TODO: This is for backward compatibility only, remove this in the upcoming future
export default { build, run }

Print through the new console UI


/* jshint node: true */
var EmberApp = require('ember-cli/lib/broccoli/ember-app');
var mergeTrees = require('broccoli-merge-trees');
var pickFiles = require('broccoli-static-compiler');

var velocity = pickFiles('node_modules/velocity-animate', {
  srcDir: '/',
  destDir: 'velocity'
});

var appTree = mergeTrees(['app-addon', 'app'], { overwrite: true });
var templateTree = mergeTrees(['app-addon/templates', 'app/templates'], { overwrite: true });
var vendorTree = mergeTrees([velocity, 'vendor-addon', 'vendor']);

var app = new EmberApp({
  trees: {
    app: appTree,
    vendor: vendorTree,
    templates: templateTree
  },
  snippetSearchPaths: ['app', 'app-addon', 'vendor-addon']
});

app.import("vendor/velocity/jquery.velocity.js");
app.import("vendor/moment/moment.js");
app.import("vendor/liquid-fire/liquid-fire.css");

var fonts = pickFiles('vendor/bootstrap-sass-official/assets/fonts/bootstrap', {
  srcDir:'/',
  destDir: '/fonts/bootstrap'
});

module.exports = mergeTrees([app.toTree(), fonts]);

/* jshint node: true */
var EmberApp = require('ember-cli/lib/broccoli/ember-app');
var mergeTrees = require('broccoli-merge-trees');
var pickFiles = require('broccoli-static-compiler');

var velocity = pickFiles('node_modules/velocity-animate', {
  srcDir: '/',
  destDir: 'velocity'
});

var appTree = mergeTrees(['app-addon', 'app'], { overwrite: true });
var templateTree = mergeTrees(['app-addon/templates', 'app/templates'], { overwrite: true });
var vendorTree = mergeTrees([velocity, 'vendor-addon', 'vendor']);

var app = new EmberApp({
  trees: {
    app: appTree,
    vendor: vendorTree,
    templates: templateTree
  },
  snippetSearchPaths: ['app', 'app-addon', 'vendor-addon']
});

app.import("vendor/velocity/jquery.velocity.js");
app.import("vendor/moment/moment.js");
app.import("vendor/liquid-fire/liquid-fire.css");

var fonts = pickFiles('vendor/bootstrap-sass-official/assets/fonts', {
  srcDir:'/',
  destDir: 'assets'
});

module.exports = mergeTrees([app.toTree(), fonts]);

Revert "fix bootstrap glyphicon path"


process.env.TZ = "America/Los_Angeles";

var smash = require("smash"),
    jsdom = require("jsdom"),
    version = require("../package.json").version;

require("./XMLHttpRequest");

module.exports = function() {
  var files = [].slice.call(arguments).map(function(d) { return "src/" + d; }),
      expression = "d3",
      sandbox = {VERSION: version};

  files.unshift("src/start");
  files.push("src/end");

  function topic() {
    smash.load(files, expression, sandbox, this.callback);
  }

  topic.expression = function(_) {
    expression = _;
    return topic;
  };

  topic.document = function(_) {
    var document = jsdom.jsdom("");

    // Monkey-patch createRange support to JSDOM.
    document.createRange = function() {
      return {
        selectNode: function() {},
        createContextualFragment: jsdom.jsdom
      };
    };

    sandbox = {
      VERSION: version,
      console: console,
      XMLHttpRequest: XMLHttpRequest,
      document: document,
      window: document.createWindow(),
      setTimeout: setTimeout,
      clearTimeout: clearTimeout,
      Date: Date // so we can override Date.now in tests
    };

    return topic;
  };

  return topic;
};

process.on("uncaughtException", function(e) {
  console.trace(e.stack);
});

process.env.TZ = "America/Los_Angeles";

var smash = require("smash"),
    jsdom = require("jsdom");

require("./XMLHttpRequest");

module.exports = function() {
  var files = [].slice.call(arguments).map(function(d) { return "src/" + d; }),
      expression = "d3",
      sandbox = null;

  files.unshift("src/start");
  files.push("src/end");

  function topic() {
    smash.load(files, expression, sandbox, this.callback);
  }

  topic.expression = function(_) {
    expression = _;
    return topic;
  };

  topic.document = function(_) {
    var document = jsdom.jsdom("");

    // Monkey-patch createRange support to JSDOM.
    document.createRange = function() {
      return {
        selectNode: function() {},
        createContextualFragment: jsdom.jsdom
      };
    };

    sandbox = {
      console: console,
      XMLHttpRequest: XMLHttpRequest,
      document: document,
      window: document.createWindow(),
      setTimeout: setTimeout,
      clearTimeout: clearTimeout,
      Date: Date // so we can override Date.now in tests
    };

    return topic;
  };

  return topic;
};

process.on("uncaughtException", function(e) {
  console.trace(e.stack);
});

Remove redundant VERSION from test sandbox.


import test from 'ava';
import Keyv from '../';

test('Keyv is a function', t => {
	t.is(typeof Keyv, 'function');
});

test('Keyv cannot be invoked without \'new\'', t => {
	t.throws(() => Keyv()); // eslint-disable-line new-cap
	t.notThrows(() => new Keyv());
});

test('.set(key, value) returns a Promise', t => {
	const store = new Keyv();
	t.true(store.set('foo', 'bar') instanceof Promise);
});

Test .set(key, value) returns a Promise


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

moduleForComponent('validated-button', 'Integration | Component | validated button', {
  integration: true
});

test('it renders a button with a label', function(assert) {
  this.render(hbs`{{validated-button label="Test"}}`);
  assert.equal(this.$('button').text().trim(), 'Test');
});

test('it renders a button with block style', function(assert) {
  this.render(hbs`{{#validated-button}}Test{{/validate-button}}`);
  assert.equal(this.$('button').text().trim(), 'Test');
});

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

moduleForComponent('validated-button', 'Integration | Component | validated button', {
  integration: true
});

test('it renders a button with a label', function(assert) {
  this.render(hbs`{{validated-button label="Test"}}`);
  assert.equal(this.$('button').text().trim(), 'Test');
});

test('it renders a button with block style', function(assert) {
  this.render(hbs`{{#validated-button}}Test{{/validated-button}}`);
  assert.equal(this.$('button').text().trim(), 'Test');
});

Fix tests for block style buttons


describe('Praad App', function() {
  'use strict';

  describe('Offer list view', function() {
    var offers, clickPromise;

    beforeEach(function() {
      browser.manage().deleteAllCookies();
      browser.get('/');
      clickPromise = element(by.css('.popup')).element(by.css('label')).click();
      offers = element.all(by.repeater('offer in restaurant.offers'));
    });

    it('should initially have 3 offers', function() {
      clickPromise.then(function() {
        expect(offers.count()).toBe(37);
      });
    });

    it('should filter the offer list as user types into the search box', function() {
      clickPromise.then(function() {
        element(by.model('search.query')).sendKeys('kana');

        expect(offers.count()).toBe(7);
        var title = offers.first().element(by.binding('offer.title')).getText();
        expect(title).toBe('Kana-riisisupp');
      });
    });
  });
});

describe('Praad App', function() {
  'use strict';

  describe('Offer list view', function() {
    var offers, regionSelectedPromise;

    beforeEach(function() {
      browser.manage().deleteAllCookies();
      browser.get('/');
      regionSelectedPromise = element(by.css('.icon-22')).click().then(function() {
        var EC = protractor.ExpectedConditions;
        var tartuButton = element(by.cssContainingText('.dropdown label', 'Tartu'));
        browser.wait(EC.elementToBeClickable(tartuButton), 500);
        return tartuButton.click();
      });
      offers = element.all(by.repeater('offer in restaurant.offers'));
    });

    it('should initially have 3 offers', function() {
      regionSelectedPromise.then(function() {
        expect(offers.count()).toBe(37);
      });
    });

    it('should filter the offer list as user types into the search box', function() {
      regionSelectedPromise.then(function() {
        element(by.model('search.query')).sendKeys('kana');

        expect(offers.count()).toBe(7);
        var title = offers.first().element(by.binding('offer.title')).getText();
        expect(title).toBe('Kana-riisisupp');
      });
    });
  });
});

Fix e2e tests after default region change


export default {
  entry: 'lib/src/index.js',
  dest: 'lib/apollo.umd.js',
  format: 'umd',
  sourceMap: true,
  moduleName: 'apollo',
  globals: {
    'lodash': '_'
  }
};

function globals(mod) {
  if (mod.indexOf('lodash/') === 0) return '_';
}

export default {
  entry: 'lib/src/index.js',
  dest: 'lib/apollo.umd.js',
  format: 'umd',
  sourceMap: true,
  moduleName: 'apollo',
  globals
};

Add lodash to globals in rollup


const babel = require('rollup-plugin-babel');
const commonjs = require('rollup-plugin-commonjs');
const nodeResolve = require('rollup-plugin-node-resolve');

module.exports = {
  entry: 'src/js/index.js',
  targets: [
    {
      dest: 'dist/cropper.js',
    },
    {
      dest: 'docs/js/cropper.js',
    },
  ],
  format: 'umd',
  moduleName: 'Cropper',
  external: ['jquery'],
  globals: {
    jquery: '$',
  },
  plugins: [
    babel({
      exclude: '/node_modules/**',
    }),
    commonjs(),
    nodeResolve({
      jsnext: true,
    }),
  ],
};

const babel = require('rollup-plugin-babel');
const commonjs = require('rollup-plugin-commonjs');
const nodeResolve = require('rollup-plugin-node-resolve');

module.exports = {
  entry: 'src/js/index.js',
  targets: [
    {
      dest: 'dist/cropper.js',
    },
    {
      dest: 'docs/js/cropper.js',
    },
  ],
  format: 'umd',
  moduleName: 'Cropper',
  external: ['jquery'],
  globals: {
    jquery: 'jQuery',
  },
  plugins: [
    babel({
      exclude: '/node_modules/**',
    }),
    commonjs(),
    nodeResolve({
      jsnext: true,
    }),
  ],
};

Use window.jQuery instead of window.$ in browser


/* global SceneScroller, mocha, it, describe, assert, expect, beforeEach */

'use strict';

describe('Entity', () => {

  // Constructor
  describe('Constructor', () => {

    it('Creates a new Entity object', () => {
      var e = new SceneScroller.Entity()
      assert(e instanceof SceneScroller.Entity)
    })

    it('Sets .created to Date object of creation time', () => {
      var t1 = new Date()
      var e = new SceneScroller.Entity()
      var t2 = new Date()
      expect(e.created).to.be.least(t1).and.most(t2)
    })

  })
})

/* global SceneScroller, mocha, it, describe, assert, expect, beforeEach */

'use strict';

describe('Entity', () => {

  var Entity = SceneScroller.Entity

  // Constructor
  describe('constructor', () => {

    var entity

    beforeEach(() => {
      entity = new Entity()
    })

    it('creates a new Entity object', () => {
      assert(entity instanceof Entity)
    })

    it('inherits from EventEmitter', () => {
      assert(entity instanceof SceneScroller.EventEmitter)
    })

    it('sets its created property to the time the object was instantiated', () => {
      var t1 = new Date()
      var e  = new SceneScroller.Entity()
      var t2 = new Date()
      expect(e.created).to.be.least(t1).and.most(t2)
      assert(e.created instanceof Date)
    })

  })

})

Add test for Entity extending EventEmitter


var express = require('express');
var favicon = require('serve-favicon');
var path = require('path');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var passport = require('passport');

function configure() {
    var app = express();

    app.set('views', path.join(__dirname, '..', 'views'));
    app.set('view engine', 'jade');
    app.set('port', 8080);

    app.use(favicon(path.join(__dirname, '..', 'public/images', 'favicon.ico')));
    app.use(logger('dev'));
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: false }));
    app.use(cookieParser());
    app.use(express.static(path.join(__dirname, '..', 'public')));
    app.use(passport.initialize());
    app.use(passport.session());

    if (app.get('env') === 'development') {
        app.locals.pretty = true;
    }

    return app;
}

module.exports = {
    configure: configure
};

var express = require('express');
var favicon = require('serve-favicon');
var path = require('path');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var passport = require('passport');

function configure() {
    var app = express();

    app.set('views', path.join(__dirname, '..', 'views'));
    app.set('view engine', 'jade');
    app.set('port', process.env.PORT | 8080);

    app.use(favicon(path.join(__dirname, '..', 'public/images', 'favicon.ico')));
    app.use(logger('dev'));
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: false }));
    app.use(cookieParser());
    app.use(express.static(path.join(__dirname, '..', 'public')));
    app.use(passport.initialize());
    app.use(passport.session());

    if (app.get('env') === 'development') {
        app.locals.pretty = true;
    }

    return app;
}

module.exports = {
    configure: configure
};

Set port to env variable or 8080.


var GitHubApi = require("github");
var ENV = require('./environments.js');

var github = new GitHubApi({
  version: "3.0.0",
  debug: true
});

github.authenticate({
  type: "oauth",
  token: ENV['GITHUB_KEY']
});

module.exports = github;

var GitHubApi = require("github");
try {
  var ENV = require('./environments.js');
} catch (e) {
  console.log("Local environment vars module not found, using system environment vars instead");
}

var github = new GitHubApi({
  version: "3.0.0",
  debug: true
});

github.authenticate({
  type: "oauth",
  token: ENV['GITHUB_KEY']
});

module.exports = github;

Check for local vars and use them if they exist


WebApp.connectHandlers.use("/package", function (request, response) {
  var pkgName = request.url.split('/')[1];

  Meteor.call('getPackageParams', pkgName, {graph: true}, function (err, params) {
    if (err) {
      console.log('Error occurred while getting package params', err);
      response.end(err.message);
    }

    Meteor.call('incrementRequestCount', pkgName);

    SSR.compileTemplate('icon', Assets.getText('icon.svg'));
    response.writeHead(200, {"Content-Type": "image/svg+xml"});
    response.end(SSR.render('icon', params));
  });
});

WebApp.connectHandlers.use("/package", function (request, response) {
  var pkgName = request.url.split('/')[1];

  Meteor.call('getPackageParams', pkgName, {graph: true}, function (err, params) {
    if (err) {
      console.log('Error occurred while getting package params', err);
      return response.end(err.message);
    }

    Meteor.call('incrementRequestCount', pkgName);

    SSR.compileTemplate('icon', Assets.getText('icon.svg'));
    response.writeHead(200, {"Content-Type": "image/svg+xml"});
    response.end(SSR.render('icon', params));
  });
});

Stop further execution when error occurs


/*
Copyright 2017 The BioBricks Foundation

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
 */

var spawn = require('child_process').spawn
var path = require('path')

var CHROMEDRIVER = path.join(
  __dirname, '..', 'node_modules', '.bin', 'chromedriver'
)

var chromedriver = spawn(CHROMEDRIVER, ['--url-base=/wd/hub'])

var webdriver = module.exports = require('webdriverio')
  .remote({
    host: 'localhost',
    port: 9515,
    desiredCapabilities: {
      browserName: 'chrome'
    }
  })
  .init()
  .timeouts('script', 1000)
  .timeouts('implicit', 1000)

require('tape').onFinish(cleanup)
process
  .on('SIGTERM', cleanup)
  .on('SIGQUIT', cleanup)
  .on('SIGINT', cleanup)
  .on('uncaughtException', cleanup)

function cleanup () {
  webdriver.end()
  chromedriver.kill('SIGKILL')
}

/*
Copyright 2017 The BioBricks Foundation

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
 */

var spawn = require('child_process').spawn
var path = require('path')

var CHROMEDRIVER = path.join(
  __dirname, '..', 'node_modules', '.bin', 'chromedriver'
)

var chromedriver = spawn(CHROMEDRIVER, ['--url-base=/wd/hub'])

var webdriver = module.exports = require('webdriverio')
  .remote({
    host: 'localhost',
    port: 9515,
    desiredCapabilities: {
      browserName: 'chrome',
      chromeOptions: process.env.DISABLE_HEADLESS
        ? undefined
        : {args: ['headless', '--disable-gpu']}
    }
  })
  .init()
  .timeouts('script', 1000)
  .timeouts('implicit', 1000)

require('tape').onFinish(cleanup)
process
  .on('SIGTERM', cleanup)
  .on('SIGQUIT', cleanup)
  .on('SIGINT', cleanup)
  .on('uncaughtException', cleanup)

function cleanup () {
  webdriver.end()
  chromedriver.kill('SIGKILL')
}

Test with headless Chrome by default


exports.settings = {
  twilio: {
    user: '', // Twilio Account key
    key: '', // Twilio auth token
    from: '', // Twilio phone number
    to: [''] // List of phone numbers twilio is able to interact with
  },

  /*
   * A list of what notifications to run when a webhook is received.
   * Currently the supported notification types are:
   *  - twiliosms
   */
  notifications: ['twiliosms']
};

exports.settings = {
  twilio: {
    user: '', // Twilio Account key
    key: '', // Twilio auth token
    from: '', // Twilio phone number
    to: [''] // List of phone numbers twilio is able to interact with
  },

  pagerduty: {
    serviceKey: '' // Pagerduty service key
  },

  /*
   * A list of what notifications to run when a webhook is received.
   * Currently the supported notification types are:
   *  - twiliosms
   *  - pagerduty
   */
  notifications: ['twiliosms']
};

Add default settings for pagerduty.


import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import 'raf/polyfill';

configure({ adapter: new Adapter() });

jest.mock('webAudioWrapper');

// noop console.log when WebAudioFont calls it because it's annoying
const originalLog = console.log

window.console.log = function(msg) {
  if (msg.includes('WebAudioFont')) {
    return null;
  }
  return originalLog(msg);
}

Make webaudiofont quiet in test output


(function () {
  var ns = $.namespace('pskl.tools.transform');

  ns.AbstractTransformTool = function () {};

  pskl.utils.inherit(ns.AbstractTransformTool, pskl.tools.Tool);

  ns.AbstractTransformTool.prototype.applyTransformation = function (evt) {
    var allFrames = evt.shiftKey;
    var allLayers = evt.ctrlKey;

    this.applyTool_(evt.altKey, allFrames, allLayers);

    $.publish(Events.PISKEL_RESET);

    this.raiseSaveStateEvent({
      altKey : evt.altKey,
      allFrames : allFrames,
      allLayers : allLayers
    });
  };

  ns.AbstractTransformTool.prototype.applyTool_ = function (altKey, allFrames, allLayers) {
    var currentFrameIndex = pskl.app.piskelController.getCurrentFrameIndex();
    var layers = allLayers ? pskl.app.piskelController.getLayers() : [pskl.app.piskelController.getCurrentLayer()];
    layers.forEach(function (layer) {
      var frames = allFrames ? layer.getFrames() : [layer.getFrameAt(currentFrameIndex)];
      frames.forEach(function (frame) {
        this.applyToolOnFrame_(frame, altKey);
      }.bind(this));
    }.bind(this));
  };

  ns.AbstractTransformTool.prototype.replay = function (frame, replayData) {
    this.applyTool_(replayData.altKey, replayData.allFrames, replayData.allLayers);
  };

})();

(function () {
  var ns = $.namespace('pskl.tools.transform');

  ns.AbstractTransformTool = function () {};

  pskl.utils.inherit(ns.AbstractTransformTool, pskl.tools.Tool);

  ns.AbstractTransformTool.prototype.applyTransformation = function (evt) {
    var allFrames = evt.shiftKey;
    var allLayers = pskl.utils.UserAgent.isMac ?  evt.metaKey : evt.ctrlKey;

    this.applyTool_(evt.altKey, allFrames, allLayers);

    $.publish(Events.PISKEL_RESET);

    this.raiseSaveStateEvent({
      altKey : evt.altKey,
      allFrames : allFrames,
      allLayers : allLayers
    });
  };

  ns.AbstractTransformTool.prototype.applyTool_ = function (altKey, allFrames, allLayers) {
    var currentFrameIndex = pskl.app.piskelController.getCurrentFrameIndex();
    var layers = allLayers ? pskl.app.piskelController.getLayers() : [pskl.app.piskelController.getCurrentLayer()];
    layers.forEach(function (layer) {
      var frames = allFrames ? layer.getFrames() : [layer.getFrameAt(currentFrameIndex)];
      frames.forEach(function (frame) {
        this.applyToolOnFrame_(frame, altKey);
      }.bind(this));
    }.bind(this));
  };

  ns.AbstractTransformTool.prototype.replay = function (frame, replayData) {
    this.applyTool_(replayData.altKey, replayData.allFrames, replayData.allLayers);
  };

})();

Fix Transform tool CMD modifier on OSX


/* jshint unused: false */
import ajax from 'ghost/utils/ajax';
import ValidationEngine from 'ghost/mixins/validation-engine';

var ForgottenController = Ember.Controller.extend(ValidationEngine, {
    email: '',
    submitting: false,

    // ValidationEngine settings
    validationType: 'forgotten',

    actions: {
        submit: function () {
            var self = this,
                data = self.getProperties('email');

            this.toggleProperty('submitting');
            this.validate({ format: false }).then(function () {
                ajax({
                    url: self.get('ghostPaths.url').api('authentication', 'passwordreset'),
                    type: 'POST',
                    data: {
                        passwordreset: [{
                            email: data.email
                        }]
                    }
                }).then(function (resp) {
                    self.toggleProperty('submitting');
                    self.notifications.showSuccess('Please check your email for instructions.', {delayed: true});
                    self.transitionToRoute('signin');
                }).catch(function (resp) {
                    self.toggleProperty('submitting');
                    self.notifications.showAPIError(resp, { defaultErrorText: 'There was a problem logging in, please try again.' });
                });
            }).catch(function (errors) {
                self.toggleProperty('submitting');
                self.notifications.showErrors(errors);
            });
        }
    }
});

export default ForgottenController;

/* jshint unused: false */
import ajax from 'ghost/utils/ajax';
import ValidationEngine from 'ghost/mixins/validation-engine';

var ForgottenController = Ember.Controller.extend(ValidationEngine, {
    email: '',
    submitting: false,

    // ValidationEngine settings
    validationType: 'forgotten',

    actions: {
        submit: function () {
            var self = this,
                data = self.getProperties('email');

            this.toggleProperty('submitting');
            this.validate({ format: false }).then(function () {
                ajax({
                    url: self.get('ghostPaths.url').api('authentication', 'passwordreset'),
                    type: 'POST',
                    data: {
                        passwordreset: [{
                            email: data.email
                        }]
                    }
                }).then(function (resp) {
                    self.toggleProperty('submitting');
                    self.notifications.showSuccess('Please check your email for instructions.', {delayed: true});
                    self.set('email', '');
                    self.transitionToRoute('signin');
                }).catch(function (resp) {
                    self.toggleProperty('submitting');
                    self.notifications.showAPIError(resp, { defaultErrorText: 'There was a problem logging in, please try again.' });
                });
            }).catch(function (errors) {
                self.toggleProperty('submitting');
                self.notifications.showErrors(errors);
            });
        }
    }
});

export default ForgottenController;

Reset email form value on success


/*jslint indent: 2 */
/*global $: false, document: false, togglbutton: false*/
'use strict';

var list = $("#gqItemList").children;
console.log(list.length);
var ids = []
for (var i=0; i<list.length; i++) {
  ids.push(list[i].id)
}

for (i=0; i<ids.length; i++) {
  if (ids[i] === "gqItemQueueEmpty") {
    console.log('No items in gq')
  }
  else {
    var wrapper = '#' + ids[i];
    console.log(wrapper);
    togglbutton.render(wrapper + ':not(.toggl)', {observe: true}, function (elem) {
      console.log('About to add Link');

      var link,
        description = $(wrapper + ' .gq-i-description', elem).text;
      console.log(description);

      link = togglbutton.createTimerLink({
        className: 'gqueues',
        description: description,
        projectName: 'gqueues-'
      });

      elem.appendChild(link);
    });
  }
}

/*jslint indent: 2 */
/*global $: false, document: false, togglbutton: false*/
'use strict';

// GQueues uses 1.5.2 jQuery
window.onload = function() {
  // retrieve ids of items
  // var items = document.getElementById('gqItemList').getElementsByClassName('gq-item-row');

  var items = $("#gqItemList").children;
  console.log(items);
  var ids = []
  for (var i=0; i<items.length; i++) {
    ids.push(items[i].id)
  }
  console.log(ids);

  // add timer to each item
  for (var i=0; i<ids.length; i++) {
    if (ids[i] === "gqItemQueueEmpty") {
      console.log('No items in gq')
    }
    else {
      var wrapper = '#' + ids[i];
      console.log(wrapper);
      togglbutton.render(wrapper + ':not(.toggl)', {observe: true}, function (elem) {
        console.log('About to add Link');

        var link, description;
        description = document.getElementById(elem.id).getElementsByClassName('gq-i-description')[0].innerHTML;
        console.log(description);

        link = togglbutton.createTimerLink({
          className: 'gqueues',
          description: 'gqueues-' + description,
          projectName: 'gqueues-' + description
        });

        elem.appendChild(link);
      });
    }
  }
}

Add window.onload; correctly detect description


var fs = require('fs'),
    path = require('path'),
    settings = require('../settings'),
    mongoose = require('mongoose');

exports.index = function(req, res){
    var id = req.query.id,
        Track = mongoose.model('track');
    Track.findOne({ _id : id }, function (err, track) {
        if (err) {
            console.error(err);
        } else if (track) {
            fs.exists(track.path, function(exists){
                var relativepath = path.relative(settings.scanner.path, track.path);
                if (exists && relativepath.indexOf('..') !== 0){
                    res.sendFile(relativepath, {root: settings.scanner.path});
                }else{
                    console.warn('Path "'+track.path+'" invalid for track '+id);
                    res.sendStatus(404);
                }
            });
        } else {
            res.sendStatus(404);
        }
    });
};

var fs = require('fs'),
    path = require('path'),
    settings = require('../settings'),
    mongoose = require('mongoose');

exports.index = function(req, res){
    var id = req.query.id?req.query.id:req.params.id,
        Track = mongoose.model('track');
    Track.findOne({ _id : id }, function (err, track) {
        if (err) {
            console.error(err);
        } else if (track) {
            fs.exists(track.path, function(exists){
                var relativepath = path.relative(settings.scanner.path, track.path);
                if (exists && relativepath.indexOf('..') !== 0){
                    res.sendFile(relativepath, {root: settings.scanner.path});
                }else{
                    console.warn('Path "'+track.path+'" invalid for track '+id);
                    res.sendStatus(404);
                }
            });
        } else {
            res.sendStatus(404);
        }
    });
};

Add a fallback to params if no query


// ==UserScript==
// @name        Rightmove Enhancement Suite
// @namespace   https://github.com/chigley/
// @description Keyboard shortcuts
// @include     http://www.rightmove.co.uk/*
// @version     1
// @grant       GM_addStyle
// @grant       GM_getResourceText
// @resource    style style.css
// ==/UserScript==

var $ = unsafeWindow.jQuery;

GM_addStyle(GM_getResourceText("style"));

var listItems = $("[name=summary-list-item]");
var currentlySelected;

// Remove grey background on premium listings. Otherwise, my active item doesn't
// stand out against such listings
$(".premium").css("background", "white");

function selectItem(item) {
    listItems.removeClass("RES-active-list-item");
    item.addClass("RES-active-list-item");
    currentlySelected = item;
}

listItems.click(function () {
    selectItem($(this));
});

$('body').bind('keyup', function(e) {
    var code = e.keyCode || e.which;
    if (e.keyCode == 74) {
        // j
        var next = currentlySelected.next("[name=summary-list-item]");
        if (next.length == 1)
            selectItem(next);
    }
});

// ==UserScript==
// @name        Rightmove Enhancement Suite
// @namespace   https://github.com/chigley/
// @description Keyboard shortcuts
// @include     http://www.rightmove.co.uk/*
// @version     1
// @grant       GM_addStyle
// @grant       GM_getResourceText
// @resource    style style.css
// ==/UserScript==

var $ = unsafeWindow.jQuery;

GM_addStyle(GM_getResourceText("style"));

var listItems = $("[name=summary-list-item]");
var currentlySelected;

// Remove grey background on premium listings. Otherwise, my active item doesn't
// stand out against such listings
$(".premium").css("background", "white");

function selectItem(item) {
    listItems.removeClass("RES-active-list-item");
    item.addClass("RES-active-list-item");
    currentlySelected = item;
}

listItems.click(function () {
    selectItem($(this));
});

$('body').bind('keyup', function(e) {
    var code = e.keyCode || e.which;
    if (e.keyCode == 74) {
        // j
        var next = currentlySelected.next("[name=summary-list-item]");
        if (next.length == 1)
            selectItem(next);
    } else if (e.keyCode == 75) {
        // k
        var prev = currentlySelected.prev("[name=summary-list-item]");
        if (prev.length == 1)
            selectItem(prev);
    }
});

Select previous item with k key


var random = require('./randoms');

describe('dot_compiler', function() {

  var fixt = {
    word: random.word(),
    qwer: '{"a":1}',
    dotted: '{{= "asdf" }}',
    dotted_w_params: '{{= it.title }}',
    nested_include: '{{= it.include("asdf") }}'
  };

  var dot = new (require('../lib/dot_compiler'))({
    it: {title: "asdf"},
    template_settings: {}
  });

  it('compiles dot templates', function() {
    var content = '{{= "' + fixt.word + '" }}';
    expect(dot.compile(content)).toEqual(fixt.word);
  });
});

var random = require('./randoms');

describe('dot_compiler', function() {

  var fixt = {
    word: random.word(),
    qwer: '{"a":1}',
    dotted: '{{= "asdf" }}',
    dotted_w_params: '{{= it.title }}',
    nested_include: '{{= it.include("asdf") }}'
  };

  var dot = new (require('../lib/dot_compiler'))({
    it: {title: 'asdf'},
    template_settings: {}
  });

  beforeEach(function() {
    dot.it = {title: 'asdf'};
  })

  it('compiles dot templates', function() {
    var content = '{{= "' + fixt.word + '" }}';
    expect(dot.compile(content)).toEqual(fixt.word);
  });

  describe('extend_it', function() {
    it('can add parameters to the it object', function() {
      dot.extend_it({qwer: 22});
      expect(dot.it).toEqual({title: 'asdf', qwer: 22});
    });

    it('can override values in the it object', function() {
      dot.extend_it({asdf: 1}, {title: 2});
      expect(dot.it).toEqual({title: 2, asdf: 1});
    });
  });
});

Add tests for it extender


/*jslint browser:true */

function decorate() {
	"use strict";
	var ad, nav, content, header, body,
		navWidth, contentWidth,
		cssNode,
		MutationObserver, observer;

	body = document.body;
	MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
	observer = new MutationObserver(function (mutationRecord, observer) {
		mutationRecord.forEach(function (mutation) {
			if (body.classList.contains('ready')) {
				ad = document.getElementById('reader-ad-container');
				nav = document.getElementsByTagName('nav');
				content = document.getElementById('reader-container');
				header = document.getElementById('feed-details');
				nav = nav[0];
				navWidth = nav.offsetWidth;

				// Hide ads
				ad.style.width = 0;
				ad.style.display = 'none';

				contentWidth = window.innerWidth - navWidth;
				cssNode = document.createElement('style');
				cssNode.innerHTML = '#reader-container{width:' + contentWidth.toString() + 'px;}' +
					'#feed-details{width:' + contentWidth.toString() + 'px;}';
				document.body.appendChild(cssNode);
				observer.disconnect();
			}
		});
	});
	observer.observe(body, {attributes: true});
}

window.onload = decorate;

/*jslint browser:true */

function decorate() {
	"use strict";
	var ad, nav, content, header, body,
		navWidth, contentWidth,
		cssNode,
		MutationObserver, observer;

	body = document.body;
	MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
	observer = new MutationObserver(function (mutationRecord, observer) {
		mutationRecord.forEach(function (mutation) {
			if (body.classList.contains('ready')) {
				ad = document.getElementById('reader-ad-container');
				nav = document.getElementsByTagName('nav');
				content = document.getElementById('reader-container');
				header = document.getElementById('feed-details');
				nav = nav[0];
				navWidth = nav.offsetWidth;

				// Hide ads
				ad.style.width = 0;
				ad.style.display = 'none';

				contentWidth = window.innerWidth - navWidth;
				cssNode = document.createElement('style');
				cssNode.innerHTML = '#reader-container{width:' + contentWidth.toString() + 'px;}' +
					'#feed-details{width:' + contentWidth.toString() + 'px;}' +
					'.article-item-full .article-content{width:95%;}';
				document.body.appendChild(cssNode);
				observer.disconnect();
			}
		});
	});
	observer.observe(body, {attributes: true});
}

window.onload = decorate;

Expand the article content in full view mode


'use strict';

angular.module('pie')
.controller('ProfileController', function ($scope, $location, $resource, authService, $http, apiBaseUrl) {
	authService.ensureLogin()
	.success(function(data) {
		$scope.user = data;
	});

	$scope.logout = function() {
		authService.logout()
		.success(function() {
			$location.path("/");
		});
	};

	$scope.deleteDocument = function(document) {
		$http({method: "DELETE", url: apiBaseUrl + "/documents/"  + document.docId, withCredentials: true})
		.success(function(data) {
			// Refresh the user
			$http({method: "GET", url: apiBaseUrl + "/user", withCredentials: true})
			.success(function(data) {
				$scope.user = data;
			});
		});
	};
});

'use strict';

angular.module('pie')
.controller('ProfileController', function ($scope, $location, $resource, authService, $http, apiBaseUrl) {
	authService.ensureLogin()
	.success(function(data) {
		$scope.user = data;
	});

	$scope.logout = function() {
		authService.logout()
		.success(function() {
			$location.path("/");
		});
	};

	$scope.deleteDocument = function(document) {
		$http({method: "DELETE", url: apiBaseUrl + "/documents/"  + document.docId, withCredentials: true})
		.success(function() {
			var index = _.indexOf($scope.user.documents.owner, document);
			$scope.user.documents.owner.splice(index, 1);
		});
	};
});

Document deletion UI behavior fixed


const path = require('path')
const express = require('express')
const webpack = require('webpack')
const config = require('../webpack.config')
const history = require('connect-history-api-fallback')

require('dotenv').config()

const app = express()
app.disable('x-powered-by')

const dev = process.env.NODE_ENV === 'development'

if (dev) {
  const compiler = webpack(config)

  app.use(history())

  app.use(require('webpack-dev-middleware')(compiler, {
    noInfo: true,
    publicPath: config.output.publicPath
  }))

  app.use(require('webpack-hot-middleware')(compiler))
}

else {
  app.use(express.static(path.resolve(__dirname, '../public')))

  app.get('*', function(req, res) {
    res.sendFile(path.resolve(__dirname, '../public/index.html'))
  })
}

module.exports = app

const path = require('path')
const express = require('express')

const app = express()
app.disable('x-powered-by')

const dev = process.env.NODE_ENV === 'development'

if (dev) {
  const webpack = require('webpack')
  const config = require('../webpack.config')
  const compiler = webpack(config)
  const history = require('connect-history-api-fallback')

  require('dotenv').config()

  app.use(history())

  app.use(require('webpack-dev-middleware')(compiler, {
    noInfo: true,
    publicPath: config.output.publicPath
  }))

  app.use(require('webpack-hot-middleware')(compiler))
}

else {
  app.use(express.static(path.resolve(__dirname, '../public')))

  app.get('*', function(req, res) {
    res.sendFile(path.resolve(__dirname, '../public/index.html'))
  })
}

module.exports = app

Stop Requiring Dev Packages in Production


define(
  'ephox.dragster.detect.Blocker',

  [
    'ephox.dragster.style.Styles',
    'ephox.highway.Merger',
    'ephox.sugar.api.Class',
    'ephox.sugar.api.Css',
    'ephox.sugar.api.Element',
    'ephox.sugar.api.Remove'
  ],

  function (Styles, Merger, Class, Css, Element, Remove) {
    return function (options) {
      var settings = Merger.merge({
        'layerClass': Styles.resolve('blocker')
      }, options);

      var div = Element.fromTag('div');
      Css.setAll(div, {
        position: 'fixed',
        left: 0,
        top: 0,
        width: '100%',
        height: '100%'
      });

      Class.add(div, Styles.resolve('blocker'));
      Class.add(div, settings.layerClass);

      var element = function () {
        return div;
      };

      var destroy = function () {
        Remove.remove(div);
      };

      return {
        element: element,
        destroy: destroy
      };
    };
  }
);

define(
  'ephox.dragster.detect.Blocker',

  [
    'ephox.dragster.style.Styles',
    'ephox.highway.Merger',
    'ephox.sugar.api.Class',
    'ephox.sugar.api.Css',
    'ephox.sugar.api.Element',
    'ephox.sugar.api.Remove'
  ],

  function (Styles, Merger, Class, Css, Element, Remove) {
    return function (options) {
      var settings = Merger.merge({
        'layerClass': Styles.resolve('blocker')
      }, options);

      var div = Element.fromTag('div');
      Css.setAll(div, {
        position: 'fixed',
        left: '0px',
        top: '0px',
        width: '100%',
        height: '100%'
      });

      Class.add(div, Styles.resolve('blocker'));
      Class.add(div, settings.layerClass);

      var element = function () {
        return div;
      };

      var destroy = function () {
        Remove.remove(div);
      };

      return {
        element: element,
        destroy: destroy
      };
    };
  }
);

Use 0px string instead of number 0 for CSS


import React from 'react';
import ReactDOM from 'react-dom';
import {
    Well, InputGroup, FormControl, Button, Glyphicon
} from 'react-bootstrap';

class SearchPostsWell extends React.Component {
    constructor(props) {
        super(props);
        this.handleSubmit = this.handleSubmit.bind(this);
    }

    handleSubmit(e) {
        e.preventDefault();
        if (this.props.onSubmit) {
            const q = ReactDOM.findDOMNode(this.refs.q).value.trim();

            this.props.onSubmit(q);
        }
    }

    render() {
        const {q, pending} = this.props;

        return (
            <Well>

Blog Search

                <form autoComplete="off" onSubmit={this.handleSubmit}>
                    <InputGroup>
                        <FormControl ref="q" defaultValue={q} />
                        <InputGroup.Button>
                            <Button disabled={pending}>
                                <Glyphicon glyph="search" />
                            </Button>
                        </InputGroup.Button>
                    </InputGroup>
                </form>
            </Well>
        );
    }
}

SearchPostsWell.propTypes = {
    q: React.PropTypes.string,
    pending: React.PropTypes.bool,
    onSubmit: React.PropTypes.func
};

export default SearchPostsWell;

import React from 'react';
import ReactDOM from 'react-dom';
import {
    Well, InputGroup, FormControl, Button, Glyphicon
} from 'react-bootstrap';

class SearchPostsWell extends React.Component {
    constructor(props) {
        super(props);
        this.handleSubmit = this.handleSubmit.bind(this);
    }

    handleSubmit(e) {
        e.preventDefault();
        if (this.props.onSubmit) {
            const q = ReactDOM.findDOMNode(this.refs.q).value.trim();

            if (q) {
                this.props.onSubmit(q);
            }
        }
    }

    render() {
        const {q, pending} = this.props;

        return (
            <Well>

Blog Search

                <form autoComplete="off" onSubmit={this.handleSubmit}>
                    <InputGroup>
                        <FormControl ref="q" defaultValue={q} />
                        <InputGroup.Button>
                            <Button disabled={pending}>
                                <Glyphicon glyph="search" />
                            </Button>
                        </InputGroup.Button>
                    </InputGroup>
                </form>
            </Well>
        );
    }
}

SearchPostsWell.propTypes = {
    q: React.PropTypes.string,
    pending: React.PropTypes.bool,
    onSubmit: React.PropTypes.func
};

export default SearchPostsWell;

Check search text before submitting post search event.


class Job extends React.Component {
  constructor() {
    super();
  }

  render() {
    let data = this.props.jobData;

    return (

            <picture>
              <source srcset={data.company.image.lg} media="(max-width: 992px)" />

            </picture>

            {data.company.name} - {data.title}

{data.ingress}

Type: {data.employment.name}

Sted: {data.location.map((location) => location.name).join(', ')}

Frist: {data.deadline || 'Ikke spesifisert'}

    );
  }
}

export default Job;

class Job extends React.Component {
  constructor() {
    super();
  }

  render() {
    let data = this.props.jobData;

    let locations = data.location.map((location) => location.name);

    if (locations.length >= 2) {
      locations = `${locations.slice(0, -1).join(', ')} and ${locations[locations.length - 1]}`;
    }

    return (

            <picture>
              <source srcset={data.company.image.lg} media="(max-width: 992px)" />

            </picture>

            {data.company.name} - {data.title}

{data.ingress}

Type: {data.employment.name}

Sted: {locations}

Frist: {data.deadline || 'Ikke spesifisert'}

    );
  }
}

export default Job;

Add 'og' before last element in location list.


HB.AnimeController = Ember.ObjectController.extend(HB.HasCurrentUser, {
  coverImageStyle: function() {
    return "background-image: url('" + this.get('model.coverImage') + "'); background-position: 50% -" + this.get('model.coverImageTopOffset') + "px;";
  }.property('model.coverImage', 'model.coverImageTopOffset'),

  randomQuote: function() {
    var quoteCount = this.get('model.featuredQuotes.length');
    if (quoteCount === 0) { return null; }
    var index = Math.floor(Math.random() * quoteCount);
    return this.get('model.featuredQuotes.content')[index];
  }.property('model.featuredQuotes'),

  actions: {
    switchTo: function (newTab) {
      this.set('activeTab', newTab);
      if (newTab === "Franchise") {
        return this.get('model.franchise');
      }
    },
    deleteAnime: function () {
      return this.get('model').destroyRecord();
    }
  }
});

HB.AnimeController = Ember.ObjectController.extend(HB.HasCurrentUser, {
  coverImageStyle: function() {
    var coverImage = this.get('model.coverImage');
    if (!coverImage) { return false; }
    var coverImageTopOffset = this.get('model.coverImageTopOffset');
    return "background-image: url('" + coverImage + "'); background-position: 50% -" + coverImageTopOffset + "px;";
  }.property('model.coverImage', 'model.coverImageTopOffset'),

  randomQuote: function() {
    var quoteCount = this.get('model.featuredQuotes.length');
    if (quoteCount === 0) { return null; }
    var index = Math.floor(Math.random() * quoteCount);
    return this.get('model.featuredQuotes.content')[index];
  }.property('model.featuredQuotes'),

  actions: {
    switchTo: function (newTab) {
      this.set('activeTab', newTab);
      if (newTab === "Franchise") {
        return this.get('model.franchise');
      }
    },
    deleteAnime: function () {
      return this.get('model').destroyRecord();
    }
  }
});

Fix header style for empty cover images


Package.describe({
  summary: "Update the client when new client code is available",
  version: '1.2.2-plugins.0'
});

Cordova.depends({
    'cordova-plugin-file': '2.0.0',
    'cordova-plugin-file-transfer': '1.0.0'
});

Package.onUse(function (api) {
  api.use([
    'webapp',
    'check'
  ], 'server');

  api.use([
    'tracker',
    'retry'
  ], 'client');

  api.use([
    'ddp',
    'mongo',
    'underscore'
  ], ['client', 'server']);

  api.use('reload', 'client', {weak: true});

  api.use(['http', 'random'], 'web.cordova');

  api.addFiles('autoupdate_server.js', 'server');
  api.addFiles('autoupdate_client.js', 'web.browser');
  api.addFiles('autoupdate_cordova.js', 'web.cordova');

  api.export('Autoupdate');
});

Package.describe({
  summary: "Update the client when new client code is available",
  version: '1.2.2-plugins.0'
});

Cordova.depends({
    'cordova-plugin-file': '2.0.0',
    'cordova-plugin-file-transfer': '1.0.0'
});

Package.onUse(function (api) {
  api.use([
    'webapp',
    'check'
  ], 'server');

  api.use([
    'tracker',
    'retry'
  ], 'client');

  api.use([
    'ddp',
    'mongo',
    'underscore'
  ], ['client', 'server']);

  api.use('reload', 'client');

  api.use(['http', 'random'], 'web.cordova');

  api.addFiles('autoupdate_server.js', 'server');
  api.addFiles('autoupdate_client.js', 'web.browser');
  api.addFiles('autoupdate_cordova.js', 'web.cordova');

  api.export('Autoupdate');
});

Make autoupdate depend on reload, which makes sense


define(function(require) {

// For legacy reasons, Perseus assumes that some modules (such as knumber) are
// executed immediately when the exercise content bundle is loaded, so we load
// them all here.

//require("./ke/utils/answer-types.js");
//require("./ke/utils/graphie.js");
//require("./ke/utils/interactive.js");
require("./ke/utils/kline.js");
require("./ke/utils/knumber.js");
require("./ke/utils/kpoint.js");
require("./ke/utils/kray.js");
require("./ke/utils/kvector.js");
//require("./ke/utils/math.js");

});

define(function(require) {

// For legacy reasons, Perseus assumes that some modules (such as knumber) are
// executed immediately when the exercise content bundle is loaded, so we load
// them all here.

require("./ke/utils/answer-types.js");
require("./ke/utils/graphie.js");
require("./ke/utils/interactive.js");
require("./ke/utils/kline.js");
require("./ke/utils/knumber.js");
require("./ke/utils/kpoint.js");
require("./ke/utils/kray.js");
require("./ke/utils/kvector.js");
require("./ke/utils/math.js");

});

Add some javascript for perseus-editors


var gulp = require('gulp');
var rename = require('gulp-rename');
var webpack = require('webpack');
var webpackStream = require('webpack-stream');
var uglify = require('gulp-uglify');

gulp.task('default', ['build']);
gulp.task('build', function() {
  return gulp.src('src/index.js')
    .pipe(webpackStream({
      module: {
        loaders: [
          {
            exclude: /node_modules/,
            test: /\.js$/,
            loader: 'babel',
            query: {
              presets: ['es2015', 'stage-0']
            }
          },
          {
            test: /\.json$/,
            loader: 'json'
          }
        ]
      },
      node: {
        // Mock Node.js modules that Babel require()s but that we don't
        // particularly care about.
        fs: 'empty',
        module: 'empty',
        net: 'empty'
      },
      output: {
        filename: 'babel.js',
        library: 'Babel',
        libraryTarget: 'umd',
      },
      plugins: [
        new webpack.DefinePlugin({
          'process.env.NODE_ENV': '"production"'
        }),
        new webpack.optimize.OccurenceOrderPlugin(),
        new webpack.optimize.DedupePlugin()
      ]
    }))
    // Output unminified version
    .pipe(gulp.dest('.'))

    // Output minified version
    .pipe(uglify())
    .pipe(rename({ extname: '.min.js' }))
    .pipe(gulp.dest('.'));
});

var gulp = require('gulp');
var rename = require('gulp-rename');
var webpack = require('webpack');
var webpackStream = require('webpack-stream');
var uglify = require('gulp-uglify');

gulp.task('default', ['build']);
gulp.task('build', function() {
  return gulp.src('src/index.js')
    .pipe(webpackStream({
      module: {
        loaders: [
          {
            exclude: /node_modules/,
            test: /\.js$/,
            loader: 'babel',
            query: {
              presets: ['es2015', 'stage-0']
            }
          },
          {
            test: /\.json$/,
            loader: 'json'
          }
        ]
      },
      node: {
        // Mock Node.js modules that Babel require()s but that we don't
        // particularly care about.
        fs: 'empty',
        module: 'empty',
        net: 'empty'
      },
      output: {
        filename: 'babel.js',
        library: 'Babel',
        libraryTarget: 'umd',
        pathinfo: true,
      },
      plugins: [
        new webpack.DefinePlugin({
          'process.env.NODE_ENV': '"production"'
        }),
        new webpack.optimize.OccurenceOrderPlugin(),
        new webpack.optimize.DedupePlugin()
      ]
    }))
    // Output unminified version
    .pipe(gulp.dest('.'))

    // Output minified version
    .pipe(uglify())
    .pipe(rename({ extname: '.min.js' }))
    .pipe(gulp.dest('.'));
});

Include Webpack path info in unminified build


var gulp        = require('gulp');
var browserSync = require('browser-sync').create();

// BrowserSync static server
gulp.task('browsersync', function() {
    browserSync.init({
        server: {
            baseDir: "_site"
        }
    });
});

const gulp = require('gulp');
const child = require('child_process');
const gutil = require('gulp-util');
const browserSync = require('browser-sync').create();

const siteRoot = '_site';

// Jekyll Build
gulp.task('jekyll', () => {
  const jekyll = child.spawn('jekyll', ['build',
    '--watch',
    '--incremental',
    '--drafts'
  ]);

  const jekyllLogger = (buffer) => {
    buffer.toString()
      .split(/\n/)
      .forEach((message) => gutil.log('Jekyll: ' + message));
  };

  jekyll.stdout.on('data', jekyllLogger);
  jekyll.stderr.on('data', jekyllLogger);
});

// BrowserSync static server
gulp.task('browsersync', function() {
  browserSync.init({
    server: {
      baseDir: siteRoot
    },
    files: [siteRoot + '/**']
  });
});

gulp.task('default', ['jekyll', 'browsersync']);

Use gulp for Jekyll build and for BrowserSync


//     telegram.link
//     Copyright 2014 Enrico Stara 'enrico.stara@gmail.com'
//     Released under the MIT License
//     http://telegram.link

// Import dependencies
var tl = require('telegram-tl-node');

// API TL schema as provided by Telegram
var apiTlSchema = require('./api-tlschema.json');

// Declare the `type` module
var type = {_id: 'api.type'};
// List the `api` constructors
var constructors = ['Bool', 'Error', 'Null', 'auth.SentCode'];
// Build  the constructors
tl.TypeBuilder.buildTypes(apiTlSchema.constructors, constructors, type);
// Export the 'type' module
exports.type = type;

// Declare the `service` module
var service = { _id: 'api.service'};
// List the `api' methods
var methods = ['auth.sendCode'];
// Build registered methods
tl.TypeBuilder.buildTypes(apiTlSchema.methods, methods, service, true);
// Export the 'service' module
exports.service = service;

//     telegram.link
//     Copyright 2014 Enrico Stara 'enrico.stara@gmail.com'
//     Released under the MIT License
//     http://telegram.link

// Import dependencies
var tl = require('telegram-tl-node');

// API TL schema as provided by Telegram
var apiTlSchema = require('./api-tlschema.json');

// Declare the `type` module
var type = {_id: 'api.type'};
// Build  the constructors
tl.TypeBuilder.buildTypes(apiTlSchema.constructors, null, type);
// Export the 'type' module
exports.type = type;

// Declare the `service` module
var service = { _id: 'api.service'};
// List the `api' methods
var methods = ['auth.sendCode', 'help.getNearestDc', 'help.getConfig'];
// Build registered methods
tl.TypeBuilder.buildTypes(apiTlSchema.methods, methods, service, true);
// Export the 'service' module
exports.service = service;

Include all (!!) the API Type constructors.. Add some API methods


'use strict';

var findup = require('findup-sync');
var spawnSync = require('child_process').spawnSync;
var gruntPath = findup('node_modules/{.,}bin/grunt', {cwd: __dirname});

process.title = 'grunth';

var harmonyFlags = [
    '--harmony_scoping',
//    '--harmony_modules', // We have `require` and ES6 modules are still in flux
    '--harmony_symbols',
    '--harmony_proxies',
//    '--harmony_collections', // `new Set([2, 3])` doesn't work
    '--harmony_generators',
//    '--harmony_iteration', // no `for-of`, the description must be wrong
    '--harmony_numeric_literals',
    '--harmony_strings',
    '--harmony_arrays',
    '--harmony_maths',
];

module.exports = function cli(params) {
    spawnSync('node',
        harmonyFlags.concat([gruntPath]).concat(params),
        {
            cwd: process.cwd(),
            stdio: 'inherit',
        }
    );
};

'use strict';

var findup = require('findup-sync');
var spawnSync = require('child_process').spawnSync;
var gruntPath = findup('node_modules/{.,}bin/grunt', {cwd: __dirname});

process.title = 'grunth';

var harmonyFlags = [
    '--harmony_scoping',
//    '--harmony_modules', // We have `require` and ES6 modules are still in flux
//    '--harmony_symbols', // `Symbol('s')` throws
//    '--harmony_proxies', // `new Proxy({}, {})` throws
//    '--harmony_collections', // `new Set([2, 3])` doesn't work
    '--harmony_generators',
//    '--harmony_iteration', // no `for-of`, the description must be wrong
    '--harmony_numeric_literals',
    '--harmony_strings',
    '--harmony_arrays',
    '--harmony_maths',
];

module.exports = function cli(params) {
    spawnSync('node',
        harmonyFlags.concat([gruntPath]).concat(params),
        {
            cwd: process.cwd(),
            stdio: 'inherit',
        }
    );
};

Disable Harmony Symbols & Proxies, they're completely broken


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

exports.exists = function (path) {
  try {
    fs.statSync(path)
    return true
  } catch (err) {
    return false
  }
}

exports.deleteDir = function (filePath, leaveDist, callback) {
  const me = this;

  fs.readdirSync(filePath).forEach(function (file, index) {
    var curPath = filePath + '/' + file
    const details = path.parse(curPath)

    if (fs.statSync(curPath).isDirectory()) {
      if (details.name !== '.git') {
        me.deleteDir(curPath)
      }
    } else if (details.base !== '.gitignore') {
      fs.unlinkSync(curPath)
    }
  })

  if (!leaveDist) {
    fs.rmdirSync(filePath)
  }

  if (typeof(callback) == 'function') {
    callback()
  }
}

exports.isSite = function () {
  const pkgPath = process.cwd() + '/package.json'

  if (!exports.exists(pkgPath)) {
    return false
  }

  const package = require(pkgPath)

  if (!package.scripts || !package.scripts.start) {
    return false
  }

  const startScript = package.scripts.start
  return startScript.includes('cory')
}

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

exports.exists = function (path) {
  try {
    fs.statSync(path)
    return true
  } catch (err) {
    return false
  }
}

exports.deleteDir = function (filePath, leaveDist, callback) {
  const me = this;

  fs.readdirSync(filePath).forEach(function (file, index) {
    var curPath = filePath + '/' + file
    const details = path.parse(curPath)

    if (fs.statSync(curPath).isDirectory()) {
      if (details.name !== '.git') {
        me.deleteDir(curPath)
      }
    } else if (details.base !== '.gitignore') {
      fs.unlinkSync(curPath)
    }
  })

  if (!leaveDist) {
    fs.unlinkSync(filePath + '/.gitignore')
    fs.rmdirSync(filePath)
  }

  if (typeof(callback) == 'function') {
    callback()
  }
}

exports.isSite = function () {
  const pkgPath = process.cwd() + '/package.json'

  if (!exports.exists(pkgPath)) {
    return false
  }

  const package = require(pkgPath)

  if (!package.scripts || !package.scripts.start) {
    return false
  }

  const startScript = package.scripts.start
  return startScript.includes('cory')
}

Make "clean" command work again


var express = require("express");
var app = express();
app.use(express.logger());

app.get('/', function(request, response) {
  response.send('Hello World!');
});

var port = process.env.PORT || 5000;
app.listen(port, function() {
  console.log("Listening on " + port);
});

var express = require("express");
var pg = require('pg');

var app = express();
app.use(express.logger());

pg.connect(process.env.DATABASE_URL, function(err, client) {
	if (err) {
		app.get('/', function(request, response) {
			response.send(err);
		});
		return;
	}

	var query = client.query("SELECT * FROM subscriptions");
	query.on('row', function(row) {
    	response.send(JSON.stringify(row));
	});
});

var app = express();
app.use(express.logger());

var port = process.env.PORT || 5000;
app.listen(port, function() {
  console.log("Listening on " + port);
});

Test page connecting to DB


import packageJson from '../../package.json';

export default {
  packageJson,
  defaultConfigName: 'create-react-app',
  frameworkPresets: [
    require.resolve('./framework-preset-react.js'),
    require.resolve('./framework-preset-react-docgen.js'),
    require.resolve('./framework-preset-cra.js'),
  ],
};

import packageJson from '../../package.json';

export default {
  packageJson,
  defaultConfigName: 'create-react-app',
  frameworkPresets: [
    require.resolve('./framework-preset-react.js'),
    require.resolve('./framework-preset-cra.js'),
    require.resolve('./framework-preset-react-docgen.js'),
  ],
};

Set docgen to the end


var userController = require('../users/userController.js');
var helpers = require('./helpers.js'); // our custom middleware

module.exports = function (app, express) {
  app.get('/api/users/profile', userController.getUser);
  app.put('/api/users/profile', userController.updateUser);
  // app.post('/api/users/signin', userController.signin);
  app.post('/api/users/signup', userController.signup);
  // app.get('/api/users/signedin', userController.checkAuth);
  //
  // // authentication middleware used to decode token and made available on the request
  // // app.use('/api/links', helpers.decode);

  // If a request is sent somewhere other than the routes above,
  // send it through our custom error handler
  app.use(helpers.errorLogger);
  app.use(helpers.errorHandler);
};

var userController = require('../users/userController.js');
var helpers = require('./helpers.js'); // our custom middleware

module.exports = function (app, express) {
  app.get('/api/users/profile', userController.getUser);
  app.get('/api/users/signedin', userController.checkAuth);

  app.post('/api/users/signin', userController.signin);
  app.post('/api/users/signup', userController.signup);

  app.put('/api/users/profile', userController.updateUser);

  // If a request is sent somewhere other than the routes above,
  // send it through our custom error handler
  app.use(helpers.errorLogger);
  app.use(helpers.errorHandler);
};

Add signin route on server


var _ = require('underscore'),
  fs = require('fs'),
  path = require('path'),
  Mustache = require('mustache'),
  moment = require('moment'),
  appConfig = require('../config');

function Messages(options) {
  var defaults = {
    dateFormat : 'DD MMMM YYYY'
  };

  this.config = _.extend({}, defaults, options);
  this.templates = {
    reminder: fs.readFileSync(
      path.resolve(__dirname, './templates/reminder.mus')
    ).toString('utf8'),
    reminderSubject: 'NOTIFICATION: "{{dataSet.name}}" is OUT OF DATE.'
  };
}

Messages.prototype.dataSetReminder = function (dataSet, dashboards, options) {
  var config = _.extend({}, this.config, options),
      lastUpdatedRaw = moment(dataSet['last-updated']).utc();

  return Mustache.render(this.templates.reminder, {
    dataSet: dataSet,
    dashboards: dashboards,
    lastUpdated: lastUpdatedRaw.format(config.dateFormat),
    outOfDate: lastUpdatedRaw.add(dataSet['max-age-expected'], 's').fromNow(true),
    adminAppUrl: appConfig.adminAppUrl,
    notificationsEmail: appConfig.notificationsEmail
  });
};

Messages.prototype.dataSetReminderSubject = function (dataSet) {
  return Mustache.render(this.templates.reminderSubject, {
    dataSet: dataSet
  });
};

module.exports = Messages;

var _ = require('underscore'),
  fs = require('fs'),
  path = require('path'),
  Mustache = require('mustache'),
  moment = require('moment'),
  appConfig = require('../config');

function readTemplateSync (templateName) {
  return fs.readFileSync(
    path.resolve(__dirname, './templates/' + templateName)
  ).toString('utf8');
}

function Messages(options) {
  var defaults = {
    dateFormat : 'DD MMMM YYYY'
  };

  this.config = _.extend({}, defaults, options);
  this.templates = {
    reminder: readTemplateSync('reminder.mus'),
    reminderSubject: 'NOTIFICATION: "{{dataSet.name}}" is OUT OF DATE.'
  };
}

Messages.prototype.dataSetReminder = function (dataSet, dashboards, options) {
  var config = _.extend({}, this.config, options),
      lastUpdatedRaw = moment(dataSet['last-updated']).utc();

  return Mustache.render(this.templates.reminder, {
    dataSet: dataSet,
    dashboards: dashboards,
    lastUpdated: lastUpdatedRaw.format(config.dateFormat),
    outOfDate: lastUpdatedRaw.add(dataSet['max-age-expected'], 's').fromNow(true),
    adminAppUrl: appConfig.adminAppUrl,
    notificationsEmail: appConfig.notificationsEmail
  });
};

Messages.prototype.dataSetReminderSubject = function (dataSet) {
  return Mustache.render(this.templates.reminderSubject, {
    dataSet: dataSet
  });
};

module.exports = Messages;

Add readTemplateSync() to open a mustache template


(function(global) {
    'use strict';

    global.setupSortableList = function setupSortableList($wrapper) {
        /* Works with the sortable_lists and sortable_list macros defined in
         * web/templates/_sortable_list.html
         */

        // Render the lists sortable
        if ($wrapper.data('disable-dragging') === undefined) {
            var $lists = $wrapper.find('ul');
            $lists.sortable({
                connectWith: $lists,
                placeholder: 'i-label sortable-item placeholder',
                containment: $wrapper,
                forcePlaceholderSize: true
            });
        }

        // Move an item from the enabled list to the disabled one (or vice versa).
        function toggleEnabled($li) {
            var $list = $li.closest('ul');
            var targetClass = $list.hasClass('enabled') ? '.disabled' : '.enabled';
            var $destination = $list.closest('.js-sortable-list-widget').find('ul' + targetClass);
            $li.detach().appendTo($destination);
        }

        $wrapper.find('ul li .toggle-enabled').on('click', function() {
            toggleEnabled($(this).closest('li'));
        });

        // Prevents dragging the row when the action buttons are clicked.
        $wrapper.find('.actions').on('mousedown', function(evt) {
            evt.stopPropagation();
        });
    };
})(window);

(function(global) {
    'use strict';

    global.setupSortableList = function setupSortableList($wrapper) {
        /* Works with the sortable_lists and sortable_list macros defined in
         * web/templates/_sortable_list.html
         */

        // Render the lists sortable
        if ($wrapper.data('disable-dragging') === undefined) {
            var $lists = $wrapper.find('ul');
            $lists.sortable({
                connectWith: $lists,
                placeholder: 'i-label sortable-item placeholder',
                containment: $wrapper,
                tolerance: 'pointer',
                forcePlaceholderSize: true
            });
        }

        // Move an item from the enabled list to the disabled one (or vice versa).
        function toggleEnabled($li) {
            var $list = $li.closest('ul');
            var targetClass = $list.hasClass('enabled') ? '.disabled' : '.enabled';
            var $destination = $list.closest('.js-sortable-list-widget').find('ul' + targetClass);
            $li.detach().appendTo($destination);
        }

        $wrapper.find('ul li .toggle-enabled').on('click', function() {
            toggleEnabled($(this).closest('li'));
        });

        // Prevents dragging the row when the action buttons are clicked.
        $wrapper.find('.actions').on('mousedown', function(evt) {
            evt.stopPropagation();
        });
    };
})(window);

Improve behaviour of sortable lists


// Map centered in NowSecure HQ by default
var map = L.map('map').setView([40.2001925,-89.0876265], 3);

L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
    attribution: '© OpenStreetMap contributors'
}).addTo(map);

console.log('Adding markers for ' + rows.length + ' rows');
rows.forEach(function(row) {
    var coordinates = [row.latitude, row.longitude];
    L.marker(coordinates).addTo(map);
});

// Map centered in NowSecure HQ by default
var map = L.map('map').setView([40.2001925,-89.0876265], 3);

L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
    attribution: '© OpenStreetMap contributors'
}).addTo(map);

console.log('Adding markers for ' + rows.length + ' rows');
rows.forEach(function(row) {
    var coordinates = [row.latitude, row.longitude];
    L.marker(coordinates).addTo(map)
        .bindPopup(
            'Filename: ' + row.filename + '
' +
            'GPS datetime: ' + row.datetime
        )
    ;
});

Add popup to markers with filename and timestamp


let Game = require('./game');

describe('Game', function () {
    let game;

    beforeEach(function () {
        game = new Game();
    });

    it('should instantiate', function () {
        expect(game).toBeDefined();
    });

    describe('when firing a bullet', function () {

        beforeEach(function () {
            game.fireBullet();
        });

        it('should add a bullet to the game state', function () {
            expect(game.state.bullets.data[0]).toBeDefined();
        });
    });
});

let Game = require('./game');

let GameObjects = require('./game-objects/game-objects');

describe('Game', function () {
    let game;

    beforeEach(function () {
        game = new Game();
    });

    it('should instantiate', function () {
        expect(game).toBeDefined();
    });

    describe('when firing a bullet', function () {
        beforeEach(function () {
            game.fireBullet();
        });

        it('should add a bullet to the game state', function () {
            expect(game.state.bullets.data[0]).toBeDefined();
        });
    });

    describe('when moving game objects', function () {
        let spies;

        beforeEach(function () {
            spies = [];
            let gameObjects = new GameObjects();
            for (let i=0; i<3; i++) {
                let spy = jasmine.createSpy();
                gameObjects.add({move: spy});
                spies.push(spy);
            }
            game.moveGameObjects(gameObjects);
        });

        it('should invoke the "move" method on each game object', function () {
            spies.forEach(function (spy) {
                expect(spy).toHaveBeenCalled();
            });
        });
    });
});

Add test for moveGameObjects method