const filesInDirectory = dir => new Promise (resolve =>

    dir.createReader ().readEntries (entries =>

        Promise.all (entries.filter (e => e.name[0] !== '.').map (e =>

            e.isDirectory
                ? filesInDirectory (e)
                : new Promise (resolve => e.file (resolve))
        ))
        .then (files => [].concat (...files))
        .then (resolve)
    )
)

const timestampForFilesInDirectory = dir =>
        filesInDirectory (dir).then (files =>
            files.map (f => f.name + f.lastModifiedDate).join ())

const reload = () => {

    chrome.tabs.query ({ active: true, currentWindow: true }, tabs => { // NB: see https://github.com/xpl/crx-hotreload/issues/5

        if (tabs[0]) { chrome.tabs.reload (tabs[0].id) }
		{
            setTimeout(function () {

                chrome.runtime.reload ()
            }, 500)
        }
    })
}

const watchChanges = (dir, lastTimestamp) => {

    timestampForFilesInDirectory (dir).then (timestamp => {

        if (!lastTimestamp || (lastTimestamp === timestamp)) {

            setTimeout (() => watchChanges (dir, timestamp), 1000) // retry after 1s

        } else {

            reload ()
        }
    })

}

chrome.management.getSelf (self => {

    if (self.installType === 'development') {

        chrome.runtime.getPackageDirectoryEntry (dir => watchChanges (dir))
    }
})

const filesInDirectory = dir => new Promise (resolve =>

    dir.createReader ().readEntries (entries =>

        Promise.all (entries.filter (e => e.name[0] !== '.').map (e =>

            e.isDirectory
                ? filesInDirectory (e)
                : new Promise (resolve => e.file (resolve))
        ))
        .then (files => [].concat (...files))
        .then (resolve)
    )
)

const timestampForFilesInDirectory = dir =>
        filesInDirectory (dir).then (files =>
            files.map (f => f.name + f.lastModifiedDate).join ())

const reload = () => {

    chrome.tabs.query ({ active: true, currentWindow: true }, tabs => { // NB: see https://github.com/xpl/crx-hotreload/issues/5

        if (tabs[0]) {

            chrome.tabs.executeScript(tabs[0].id, {code: 'setTimeout(function () { location.reload(); }, 300)'}, function() {});
        }

        chrome.runtime.reload ()
    })
}

const watchChanges = (dir, lastTimestamp) => {

    timestampForFilesInDirectory (dir).then (timestamp => {

        if (!lastTimestamp || (lastTimestamp === timestamp)) {

            setTimeout (() => watchChanges (dir, timestamp), 1000) // retry after 1s

        } else {

            reload ()
        }
    })

}

chrome.management.getSelf (self => {

    if (self.installType === 'development') {

        chrome.runtime.getPackageDirectoryEntry (dir => watchChanges (dir))
    }
})

Use execute script to reload page rather than chrome.tabs.reload() as chrome.runtime.reload() causes the previous operation to abort.


import React, {Component} from 'react';
import {connect} from 'react-redux';
import {Avatar, Chip} from 'material-ui';

const mapStateToProps = (state, ownProps) => ({
  line: ownProps.line,
  user: state.auth.user,
});

const LineChat = ({line, user}) => {

  const divStyleRight = {
    float: 'rigth',
    color: 'red',
  };

  return (

    { user.id === line.studentid ? (

        <Chip>
          <Avatar src={line.studentavatar} />

              You says

{line.text}

        </Chip>

    ): (

        <Chip>
          <Avatar src={line.studentavatar} />

              {line.studentname} says

{line.text}

        </Chip>

    ) }

  );
};

export default connect(mapStateToProps)(LineChat);

import React, {Component} from 'react';
import {connect} from 'react-redux';
import {Avatar, Chip} from 'material-ui';

const mapStateToProps = (state, ownProps) => ({
  line: ownProps.line,
  user: state.auth.user,
});

const LineChat = ({line, user}) => {

  return (

      {user.id === line.studentid ? (

          <Chip>
            <Avatar src={line.studentavatar} />

              You says

{line.text}

          </Chip>

      ) : (

          <Chip>
            <Avatar src={line.studentavatar} />

              {line.studentname} says

{line.text}

          </Chip>

      ) }

  );
};

export default connect(mapStateToProps)(LineChat);

Fix format in linechat code


/*global describe, beforeEach, it*/
'use strict';

var path    = require('path');
var helpers = require('yeoman-generator').test;

describe('playbook', function () {
  beforeEach(function (done) {
    helpers.testDirectory(path.join(__dirname, 'temp'), function (err) {
      if (err) {
        return done(err);
      }

      this.app = helpers.createGenerator('playbook:app', ['../../app']);

      done();
    }.bind(this));
  });

  it('creates expected files', function (done) {
    var expected = [
      'app/index.html',
      'app/404.html',
      'app/humans.txt',
      'app/robots.txt',
      '.jshintrc',
      '.gitignore',
      '.editorconfig',
      '.csslintrc',
      '.bowerrc',
      'bower.json',
      'Gruntfile.js',
      'package.json'
    ];

    helpers.mockPrompt(this.app, {
      'authorName': 'foo',
      'authorEmail': 'bar',
      'jsPre': 'None',
      'sassComp': 'Ruby',
      'googleAnalytics': false,
      'deploy': false
    });

    this.app.options['skip-install'] = true;

    this.app.run({}, function () {
      helpers.assertFiles(expected);
      done();
    });
  });
});

/*global describe, beforeEach, it*/
'use strict';

var path    = require('path');
var helpers = require('yeoman-generator').test;

describe('playbook', function () {
  beforeEach(function (done) {
    helpers.testDirectory(path.join(__dirname, 'temp'), function (err) {
      if (err) {
        return done(err);
      }

      this.app = helpers.createGenerator('playbook:app', ['../../app']);

      done();
    }.bind(this));
  });

  it('creates expected files', function (done) {
    var expected = [
      'app/index.html',
      'app/404.html',
      'app/humans.txt',
      'app/robots.txt',
      '.jshintrc',
      '.gitignore',
      '.editorconfig',
      '.csslintrc',
      '.bowerrc',
      'bower.json',
      'gulpfile.js',
      'package.json'
    ];

    helpers.mockPrompt(this.app, {
      'authorName': 'foo',
      'authorEmail': 'bar',
      'jsPre': 'None',
      'sassComp': 'Ruby',
      'googleAnalytics': false,
      'deploy': false
    });

    this.app.options['skip-install'] = true;

    this.app.run({}, function () {
      helpers.assertFiles(expected);
      done();
    });
  });
});

Fix creation test for Playbook 2.0


import Ember from 'ember';

const { getOwner, get } = Ember;

/**
 * Unsee a requirejs module if it exists
 * @param {String} module The requirejs module name
 */
function requireUnsee(module) {
  if (requirejs.has(module)) {
    requirejs.unsee(module);
  }
}

export default function clearContainerCache(context, componentName) {
  const owner = getOwner(context);
  const config = owner.resolveRegistration('config:environment');
  const appName = get(owner, 'base.name') || get(owner, 'application.name');
  const modulePrefix = get(config, 'modulePrefix') || appName;
  const podModulePrefix = get(config, 'podModulePrefix');

  // Invalidate regular module
  requireUnsee(`${modulePrefix}/components/${componentName}`);
  requireUnsee(`${modulePrefix}/templates/components/${componentName}`);

  // Invalidate pod modules
  requireUnsee(`${podModulePrefix}/components/${componentName}/component`);
  requireUnsee(`${podModulePrefix}/components/${componentName}/template`);
}

import Ember from 'ember';

const { getOwner, get } = Ember;

// Access requirejs global
const requirejs = window.requirejs;

/**
 * Unsee a requirejs module if it exists
 * @param {String} module The requirejs module name
 */
function requireUnsee(module) {
  if (requirejs.has(module)) {
    requirejs.unsee(module);
  }
}

export default function clearContainerCache(context, componentName) {
  const owner = getOwner(context);
  const config = owner.resolveRegistration('config:environment');
  const appName = get(owner, 'base.name') || get(owner, 'application.name');
  const modulePrefix = get(config, 'modulePrefix') || appName;
  const podModulePrefix = get(config, 'podModulePrefix');

  // Invalidate regular module
  requireUnsee(`${modulePrefix}/components/${componentName}`);
  requireUnsee(`${modulePrefix}/templates/components/${componentName}`);

  // Invalidate pod modules
  requireUnsee(`${podModulePrefix}/components/${componentName}/component`);
  requireUnsee(`${podModulePrefix}/components/${componentName}/template`);
}

Use window to access requirejs


import {stripIndent} from 'common-tags';

export default {
  id: "private-npm-packages",
  title: "Private NPM Packages",
  content: stripIndent`
    If you are using private NPM repos or private Github urls we won't be able to access those repos by default. But there are some ways you could grant permission to us.

    ## Custom .npmrc

    You can set a custom \`.npmrc\` file when we are building storybook. With that, you can provide NPM tokens to access your private packages. For that, simply expose the following environmental variable with your custom .npmrc content:

    ~~~sh
    SB_NPMRC
    ~~~

    We will append the above content to your existing .npmrc file if there's a one.

    ## SSH Private Keys

    You may use private Github repos as NPM packages. Then you can set a private key which is authorized to access those repos. For that, simply expose the following environmental variable:

    ~~~sh
    SB_SSHKEY
    ~~~
  `
};

import {stripIndent} from 'common-tags';

export default {
  id: "private-npm-packages",
  title: "Private NPM Packages",
  content: stripIndent`
    If you are using private NPM repos or private Github urls we won't be able to access those repos by default. But there are some ways you could grant permission to us.

    ## NPM Token

    You could provide a NPM token so, you could use private NPM packages inside storybook. For that, simply create the following environmet variable.

    ~~~sh
    SB_NPM_TOKEN
    ~~~

    ## Custom .npmrc

    You can set a [custom \`.npmrc\` file](http://blog.npmjs.org/post/118393368555/deploying-with-npm-private-modules) when we are building storybook. With that, you can provide NPM tokens to access your private packages. For that, simply expose the following environmental variable with your custom .npmrc content:

    ~~~sh
    SB_NPMRC
    ~~~

    We will append the above content to your existing .npmrc file if there's a one.

    ## SSH Private Keys

    You may use private Github repos as NPM packages. Then you can set a private key which is authorized to access those repos. For that, simply expose the following environmental variable:

    ~~~sh
    SB_SSHKEY
    ~~~
  `
};

Update the doc for using NPM Token.


// Copyright (c) 2019, Frappe Technologies and contributors
// For license information, please see license.txt

frappe.ui.form.on('Website Route Meta', {
	refresh: function(frm) {
		frm.get_field('__newname').set_label('Route');
	}
});

// Copyright (c) 2019, Frappe Technologies and contributors
// For license information, please see license.txt

frappe.ui.form.on('Website Route Meta', {
	refresh: function(frm) {
		frm.get_field('__newname').set_label('Route');
		frm.add_custom_button(__('Visit Web Page'), () => {
			let route = frm.doc.name;
			if (!route.startsWith('/')) {
				route = '/' + route;
			}
			window.open(route, '_blank');
		});
	}
});

Add Visit Web Page button in form


export default class World {
  constructor() {
    // Object that will hold loaded images
    this.images = {};

    // Provides image loading functionalities
    this.image = {pending: 0};
    this.image.load = (name, url) => {
      this.image.pending += 1;
      this.images[name] = new Image();
      this.images[name].onload = function() {
        this.image.pending -= 1;
      }.bind(this);
      this.images[name].src = url;
    };
  }
}

export default class World {
  constructor(options) {
    this.load = options.load;
    this.init = options.init;

    // Object that will hold loaded images
    this.images = {};

    // Provides image loading functionalities
    this.image = {pending: 0};
    this.image.load = (name, url) => {
      this.image.pending += 1;
      this.images[name] = new Image();
      this.images[name].onload = () => {
        this.image.pending -= 1;
        this.tryInit();
      };
      this.images[name].src = url;
    };

    this.load();
    this.tryInit();
  }

  tryInit() {
    this.image.pending || this.init();
  }
}

Add hook to fire up when all images are loaded


(function()
{
  angular
    .module('history')
    .controller('HistoryController', HistoryController);

  HistoryController.$inject = ['$scope', '$state', 'localStorage', 'HistoryService', 'SettingsService'];

  function HistoryController($scope, $state, localStorage, HistoryService, SettingsService)
  {
    $scope.history = HistoryService;
    $scope.filter = filter;
    $scope.loadMore = loadMore;
    $scope.refresh = refresh;

    // load on entering view
    $scope.$on('$ionicView.beforeEnter', function()
    {
      // send to login screen if they haven't logged in yet
      if (!SettingsService.loggedIn) SettingsService.reallyLogOut();
      refresh();
    });

    function filter()
    {
      console.log('Filter yo');
    }

    function loadMore()
    {
      HistoryService.loadMore().then(done);
    }

    function refresh()
    {
      HistoryService.refresh().then(done);
    }

    function done()
    {
      $scope.$broadcast('scroll.refreshComplete');
      $scope.$broadcast('scroll.infiniteScrollComplete');
    }
  }
})();

(function()
{
  angular
    .module('history')
    .controller('HistoryController', HistoryController);

  HistoryController.$inject = ['$scope', '$state', 'localStorage', 'HistoryService', 'SettingsService'];

  function HistoryController($scope, $state, localStorage, HistoryService, SettingsService)
  {
    $scope.history = HistoryService;
    $scope.filter = filter;
    $scope.loadMore = loadMore;
    $scope.refresh = refresh;

    // load on entering view
    $scope.$on('$ionicView.beforeEnter', function()
    {
      // send to login screen if they haven't logged in yet
      if (!SettingsService.loggedIn) SettingsService.reallyLogOut();
      refresh();
    });

    function filter()
    {
      console.log('Filter yo');
    }

    function loadMore()
    {
      // this is necessary because the loadMore function may just
      // return if there are no games loaded yet, so we can't expect
      // a promise, so we must check for one.
      var loadMore = HistoryService.loadMore();
      if (loadMore) loadMore.then(done);
    }

    function refresh()
    {
      HistoryService.refresh().then(done);
    }

    function done()
    {
      $scope.$broadcast('scroll.refreshComplete');
      $scope.$broadcast('scroll.infiniteScrollComplete');
    }
  }
})();

Fix console output bug in history


module.exports = {
  sass: {
    files: ["scss/**/*.{scss,sass}"],
    tasks: ["lint:css", "sass:compile", "test:css"]
  },
  js: {
    files: ["js/**/*.js", "tests/**/*.js"],
    tasks: ["lint:js", "copy:js", "test:js"]
  },
  requirejs: {
    files: ["js/config.js", "js/config.dev.js"],
    tasks: ["uglify:dev"]
  },
  assets: {
    files: ["assets/**/*", "bower_components/**/*"],
    tasks: ["copy:assets"]
  }
}

module.exports = {
  sass: {
    files: ["scss/**/*.{scss,sass}"],
    tasks: ["lint:css", "sass:compile", "test:css"]
  },
  js: {
    files: ["js/**/*.js", "tests/**/*.js"],
    tasks: ["lint:js", "copy:js", "test:js"]
  },
  requirejs: {
    files: ["js/main.js", "js/config.dev.js"],
    tasks: ["uglify:dev"]
  },
  assets: {
    files: ["assets/**/*", "bower_components/**/*"],
    tasks: ["copy:assets"]
  }
}

Watch main file for RequireJS configuration changes.


import BreadcrumbSegment from '../../../components/BreadcrumbSegment';
import MesosStateStore from '../../../stores/MesosStateStore';

class TaskDetailBreadcrumb extends BreadcrumbSegment {
  constructor() {
    super(...arguments);

    this.store_listeners = [
      {name: 'state', events: ['success'], listenAlways: false}
    ];
  }

  componentDidMount() {
    super.componentDidMount();
    this.updateCrumbStatus();
  }

  componentWillReceiveProps() {
    super.componentWillReceiveProps(...arguments);
    this.updateCrumbStatus();
  }

  onStateStoreSuccess() {
    this.setState({isLoadingCrumb: false});
  }

  updateCrumbStatus() {
    let taskID = this.getTaskName();

    if (taskID) {
      this.setState({isLoadingCrumb: false});
    }
  }

  getTaskName() {
    let {taskID} = this.props.parentRouter.getCurrentParams();

    if (MesosStateStore.get('lastMesosState').slaves == null) {
      return null;
    }

    return MesosStateStore.getTaskFromTaskID(taskID).getName();
  }

  getCrumbLabel() {
    return this.getTaskName();
  }
};

module.exports = TaskDetailBreadcrumb;

import BreadcrumbSegment from '../../../components/BreadcrumbSegment';
import MesosStateStore from '../../../stores/MesosStateStore';

class TaskDetailBreadcrumb extends BreadcrumbSegment {
  constructor() {
    super(...arguments);

    this.store_listeners = [
      {name: 'state', events: ['success'], listenAlways: false}
    ];
  }

  componentDidMount() {
    this.updateCrumbStatus();
  }

  componentWillReceiveProps() {
    this.updateCrumbStatus();
  }

  onStateStoreSuccess() {
    this.setState({isLoadingCrumb: false});
  }

  updateCrumbStatus() {
    let taskID = this.getTaskName();

    if (taskID) {
      this.setState({isLoadingCrumb: false});
    }
  }

  getTaskName() {
    let {taskID} = this.props.parentRouter.getCurrentParams();

    if (MesosStateStore.get('lastMesosState').slaves == null) {
      return null;
    }

    return MesosStateStore.getTaskFromTaskID(taskID).getName();
  }

  getCrumbLabel() {
    return this.getTaskName();
  }
};

module.exports = TaskDetailBreadcrumb;

Remove calls to absent superclass methods


'use strict';

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

var $ = require('gulp-load-plugins')();

module.exports = function(options) {
  gulp.task('markups', function() {
    function renameToHtml(path) {
      path.extname = '.html';
    }

    return gulp.src(options.src + '/{app,components}/**/*.<%= props.htmlPreprocessor.extension %>')
<% if (props.htmlPreprocessor.key === 'jade') { %>
      .pipe($.consolidate('jade', { basedir: options.src, doctype: 'html', pretty: '  ' }))
<% } else if (props.htmlPreprocessor.key === 'haml') { %>
      .pipe($.consolidate('hamljs'))
<% } else if (props.htmlPreprocessor.key === 'handlebars') { %>
      .pipe($.consolidate('handlebars'))
<% } %>
      .on('error', function handleError(err) {
        console.error(err.toString());
        this.emit('end');
      })
      .pipe($.rename(renameToHtml))
      .pipe(gulp.dest(options.tmp + '/serve/'))
      .pipe(browserSync.reload({ stream: true }));
  });
};

'use strict';

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

var $ = require('gulp-load-plugins')();

module.exports = function(options) {
  gulp.task('markups', function() {
    function renameToHtml(path) {
      path.extname = '.html';
    }

    return gulp.src(options.src + '/{app,components}/**/*.<%= props.htmlPreprocessor.extension %>')
<% if (props.htmlPreprocessor.key === 'jade') { %>
      .pipe($.consolidate('jade', { basedir: options.src, doctype: 'html', pretty: '  ' })).on('error', options.errorHandler('Jade'))
<% } else if (props.htmlPreprocessor.key === 'haml') { %>
      .pipe($.consolidate('hamljs')).on('error', options.errorHandler('Haml'))
<% } else if (props.htmlPreprocessor.key === 'handlebars') { %>
      .pipe($.consolidate('handlebars')).on('error', options.errorHandler('Handlebars'))
<% } %>
      .pipe($.rename(renameToHtml))
      .pipe(gulp.dest(options.tmp + '/serve/'))
      .pipe(browserSync.reload({ stream: true }));
  });
};

Use function errorHandler for htmlPreproc


var _ = require('../../../lib/alloy/underscore')._;

exports.parse = function(node, state) {
	_.extend(state, {
		proxyPropertyDefinition: {
			parents: [
				'Ti.UI.Window'
			]
		}
	});
	return require('./Alloy.Abstract._ProxyProperty').parse(node, state);
};

var _ = require('../../../lib/alloy/underscore')._;

exports.parse = function(node, state) {
	_.extend(state, {
		proxyPropertyDefinition: {
			parents: [
				'Ti.UI.Window',
				'Ti.UI.iPad.Popover'
			]
		}
	});
	return require('./Alloy.Abstract._ProxyProperty').parse(node, state);
};

Add leftNavButton and rightNavButton support in markup for Ti.UI.iPad.Popover. Easy peesy.


//= require_directory ./AbstractDocument
//= require_directory ./BrowserWidget
//= require_directory ./BusyWidget
//= require_directory ./ComponentStateManager
//= require_directory ./Desktop
//= require_directory ./DiagramWidget
//= require_directory ./DocumentEditor
//= require_directory ./FundamentalTypes
//= require_directory ./HierarchicalMenuWidget
//= require_directory ./HtmlDocument
//= require_directory ./Internationalization
//= require_directory ./jStorage
//= require_directory ./LanguageSelectorWidget
//= require_directory ./MochaUI
//= require_directory ./MooEditable
//= require_directory ./NewsReaderWidget
//= require_directory ./PartyEventWidget
//= require_directory ./PhotoGaleryWidget
//= require_directory ./ResourceManager
//= require_directory ./ScrollingBehaviour
//= require_directory ./Singleton
//= require_directory ./SkinSelectorWidget
//= require_directory ./SlideShow
//= require_directory ./SmartDocument
//= require_directory ./SplashForm
//= require_directory ./TabWidget
//= require_directory ./TextAreaEditor
//= require_directory ./ToolBarWidget
//= require_directory ./TreeWidget
//= require_directory ./VideoPlayerWidget
//= require_directory ./WebUIConfiguration
//= require_directory ./WebUIController
//= require_directory ./WebUILogger
//= require_directory ./WebUIMessageBus

//= require_directory ./AbstractDocument
//= require_directory ./BrowserWidget
//= require_directory ./BusyWidget
//= require_directory ./ComponentStateManager
//= require_directory ./Desktop
//= require_directory ./DiagramWidget
//= require_directory ./DocumentEditor
//= require_directory ./FundamentalTypes
//= require_directory ./HierarchicalMenuWidget
//= require_directory ./HtmlDocument
//= require_directory ./Internationalization
//= require_directory ./jStorage
//= require_directory ./LanguageSelectorWidget
//= require_directory ./MochaUI
//= require_directory ./MooEditable
//= require_directory ./NewsReaderWidget
//= require_directory ./PartyEventWidget
//= require_directory ./PhotoGaleryWidget
//= require_directory ./ResourceManager
//= require_directory ./ScrollingBehaviour
//= require_directory ./Singleton
//= require_directory ./SkinSelectorWidget
//= require_directory ./SmartDocument
//= require_directory ./SplashForm
//= require_directory ./TabWidget
//= require_directory ./TextAreaEditor
//= require_directory ./ToolBarWidget
//= require_directory ./TreeWidget
//= require_directory ./VideoPlayerWidget
//= require_directory ./WebUIConfiguration
//= require_directory ./WebUIController
//= require_directory ./WebUILogger
//= require_directory ./WebUIMessageBus

Exclude Slideshow from javascript concatenation.


/**
 * Created by taha on 9/10/17.
 */
import * as types from '../actions/actionTypes';
import initialState from './initialState';

export default function courseReducer(state = initialState.courses, action) {
  switch(action.type) {
    case types.CREATE_COURSE:
      return [...state,
        Object.assign({}, action.course)
      ];
    case types.LOAD_COURSES_SUCCESS:
      return action.courses;

    default:
      return state;
  }
}

/**
 * Created by taha on 9/10/17.
 */
import * as types from '../actions/actionTypes';
import initialState from './initialState';

export default function courseReducer(state = initialState.courses, action) {
  switch(action.type) {
    case types.LOAD_COURSES_SUCCESS:
      return action.courses;

    case types.CREATE_COURSE_SUCCESS:
      return [
        ...state,
        Object.assign({}, action.course)
      ];

    case types.UPDATE_COURSE_SUCCESS:
      return [
        ...state.filter(course => course.id !== action.course.id),
        Object.assign({}, action.course)
      ];

    default:
      return state;
  }
}

Handle Create and Updates in Reducer


exports = module.exports = function(directory) {

  return function(clientID, secret, cb) {
    directory.query(clientID, function(err, client) {
      if (err) { return cb(err); }
      // TODO: Handle ENOTFOUND or somesuch as a undefined client
      if (!client) {
        return cb(null, false);
      }
      if (client.secret !== secret) {
        return cb(null, false);
      }

      return cb(null, client);
    });
  };
};

exports['@require'] = [ 'http://schemas.modulate.io/js/aaa/clients/Directory' ];

exports = module.exports = function(verifyPassword) {

  return function(clientID, secret, cb) {
    verifyPassword(clientID, secret, 'clients', function(err, client, info) {
      return cb(err, client, info)
    });
  };
};

exports['@require'] = [
  'http://schemas.authnomicon.org/js/security/authentication/password/verifyFn'
];

Verify secret using password and realm.


export function ifNoUserRedirectTo(url, message, type = 'errors') {
  return function(req, res, next) {
    const { path } = req;
    if (req.user) {
      return next();
    }

    req.flash(type, {
      msg: message || `You must be signed in to access ${path}`
    });

    return res.redirect(url);
  };
}

export function ifNoUserSend(sendThis) {
  return function(req, res, next) {
    if (req.user) {
      return next();
    }
    return res.status(200).send(sendThis);
  };
}

export function ifNoUser401(req, res, next) {
  if (req.user) {
    return next();
  }
  return res.status(401).end();
}

export function flashIfNotVerified(req, res, next) {
    const user = req.user;
    if (!user) {
      return next();
    }
    const email = req.user.email;
    const emailVerified = req.user.emailVerified;
    if (!email || !emailVerified) {
      req.flash('info', {
        msg: 'Please verify your email address ' +
        'here.'
      });
    }
    return next();
}

export function ifNoUserRedirectTo(url, message, type = 'errors') {
  return function(req, res, next) {
    const { path } = req;
    if (req.user) {
      return next();
    }

    req.flash(type, {
      msg: message || `You must be signed in to access ${path}`
    });

    return res.redirect(url);
  };
}

export function ifNoUserSend(sendThis) {
  return function(req, res, next) {
    if (req.user) {
      return next();
    }
    return res.status(200).send(sendThis);
  };
}

export function ifNoUser401(req, res, next) {
  if (req.user) {
    return next();
  }
  return res.status(401).end();
}

export function flashIfNotVerified(req, res, next) {
  return next();
  /*
  // disabled until authorized required bug is fixed
  const user = req.user;
  if (!user) {
    return next();
  }
  const email = req.user.email;
  const emailVerified = req.user.emailVerified;
  if (!email || !emailVerified) {
    req.flash('info', {
      msg: 'Please verify your email address ' +
      'here.'
    });
  }
  */
}

Remove flash until author required bug is fixed


import Ember from "ember";

export default Ember.Helper.helper(function(value) {
  value = new Ember.Handlebars.SafeString(value[0] || "").string;
  value = value.replace(/(\r\n|\n|\r)/gm, "<br>");
  return new Ember.String.htmlSafe(value);
});

import Ember from "ember";

export default Ember.Helper.helper(function(value) {
  value = new Ember.Handlebars.SafeString(value[0] || "").string;
  value = value.replace(/(\r\n|\n|\r)/gm, "<br>");
  return new Ember.String.htmlSafe(
    Ember.Handlebars.Utils.escapeExpression(value)
  );
});

Add escapeExpression to the apply line helper


module.exports = {
  test: /\.es6\.js$|\.js$|\.jsx$/,
  exclude: /node_modules/,
  loader: 'babel',
  query: {
    presets: [
      'es2015-native-modules',
      'stage-2',
      'react',
    ],
    plugins: [
      'transform-class-properties',
      'transform-react-constant-elements',
      'transform-react-inline-elements',
      'lodash', // fixes the babel budnling issue
    ],
  }
};

module.exports = {
  test: /\.es6\.js$|\.js$|\.jsx$/,
  exclude: /node_modules/,
  loader: 'babel',
  query: {
    presets: [
      'es2015-native-modules',
      'stage-2',
      'react',
    ].map(function(p) { return require.resolve('babel-preset-' + p) }),
    plugins: [
      'transform-class-properties',
      'transform-react-constant-elements',
      'transform-react-inline-elements',
      'lodash', // fixes the babel budnling issue
    ].map(function(p) { return require.resolve('babel-plugin-' + p) }),
  }
};

Fix module resolution for npm-link'd packages


import BaseSyntax from './base.js';

/**
 * The class to define the TemplateElement syntax
 *
 * @class TemplateElement
 */
export default
class TemplateElement extends BaseSyntax {

  /**
   * Create a template literal
   *
   * @constructor
   */
    constructor() {

    super('TemplateElement');

    this.value = {raw: '', cooked: ''};
    this.tail = false;

  }

  setValue(value) {
    this.value.raw = value;
    this.value.cooked = value;
  }

  setRaw(raw) {
    this.value.raw = raw;
  }

  setCooked(cooked) {
    this.value.cooked = cooked;
  }

}

import BaseSyntax from './base.js';

/**
 * The class to define the TemplateElement syntax
 *
 * @class TemplateElement
 */
export default
class TemplateElement extends BaseSyntax {

  /**
   * Create a template literal
   *
   * @constructor
   */
    constructor() {

    super('TemplateElement');

    this.value = {raw: '', cooked: ''};
    this.tail = false;

  }

  setValue(value) {
    this.setRaw(value);
    this.setCooked(value);
  }

  setRaw(raw) {
    this.value.raw = raw;
  }

  setCooked(cooked) {
    this.value.cooked = cooked;
  }

}

Use methods rather than properties


const injectLivereload = () => {
  const script = document.createElement("script")
  script.setAttribute("src", "http://localhost:35729/livereload.js")
  document.head.appendChild(script)
}

const removeLivereload = () => {
  const elems = document.querySelectorAll("script[src='http://localhost:35729/livereload.js']")
  elems.forEach(s => s.remove())
}

browser.runtime.onMessage.addListener(msg => {
  switch (msg.command) {
    case "inject":
      injectLivereload();
      break;
    case "remove":
      removeLivereload();
      break;
    default:
      break;
  }
})

true

const injectLivereload = () => {
  const script = document.createElement("script")
  script.setAttribute("src", "http://localhost:35729/livereload.js")
  document.head.appendChild(script)
}

browser.runtime.onMessage.addListener(msg => {
  switch (msg.command) {
    case "inject":
      injectLivereload();
      break;
    default:
      break;
  }
})

true

Remove livereload script tag removing


// @flow

import React from 'react';
import cx from 'classnames';
import RandomQuote from '../RandomQuote';

// CSS
import styles from './Footer.css';
import { hiddenOnMobile } from 'app/styles/utilities.css';

const Footer = props => (

README

        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce semper
        nunc at ex consequat ultricies.

        <RandomQuote loggedIn={props.loggedIn} />

Kontakt oss

        abakus@abakus.no

        7491 Trondheim

);

export default Footer;

// @flow

import React from 'react';
import cx from 'classnames';
import RandomQuote from '../RandomQuote';

// CSS
import styles from './Footer.css';
import { hiddenOnMobile } from 'app/styles/utilities.css';

const Footer = props => (

Lego

          Velkommen til LEGO, den nye nettsiden til Abakus. Vi i Webkom har
          arbeidet lenge med denne siden og håper den faller i smak. Spørsmål
          eller rapportering av feil gjøres til{' '}
          webkom@abakus.no.

        <RandomQuote loggedIn={props.loggedIn} />

Kontakt oss

        abakus@abakus.no

Abakus

Sem Sælands vei 7-9

7491 Trondheim

);

export default Footer;

Replace lorem ipsum in footer


define(
    [
        'text!templates/index.html',
        'backbone',
        'underscore'
    ],
    function (template, Backbone, _) {
        "use strict";

        return Backbone.View.extend({
            tagName: 'div',
            className: 'row-fluid',
            template: _.template(template),

            events: {
                'click #watch': 'watch'
            },

            initialize: function (options) {
                this.router = options.router;
            },

            render: function () {
                this.$el.html(this.template());
            },

            watch: function (e) {
                e.preventDefault();

                this.router.navigate('/' + this.$('#username').val(), { trigger: true });
            }
        });
    }
);

define(
    [
        'text!templates/index.html',
        'backbone',
        'underscore',
        'jquery'
    ],
    function (template, Backbone, _, $) {
        "use strict";

        return Backbone.View.extend({
            tagName: 'div',
            className: 'row-fluid',
            template: _.template(template),

            events: {
                'click #watch': 'watch'
            },

            initialize: function (options) {
                this.router = options.router;
            },

            render: function () {
                $('body').removeClass('loading');
                this.$el.html(this.template());
            },

            watch: function (e) {
                e.preventDefault();

                this.router.navigate('/' + this.$('#username').val(), { trigger: true });
            }
        });
    }
);

Fix UI bug with loader


angular.module('proxtop').controller('MainController', ['$scope', 'ipc', '$state', 'notification', '$mdToast', function($scope, ipc, $state, notification, $mdToast) {
    ipc.on('check-login', function(result) {
        if(result) {
            ipc.send('watchlist-update');
            $state.go('profile');
        } else {
            $state.go('login');
        }
    });

    ipc.on('error', function(severity, message) {
        $mdToast.show($mdToast.simple().content(severity + ':' + message));
    });

    var displayNotification = function(type) {
        return function(update) {
            notification.displayNotification('Proxtop', 'Episode ' + update.ep + ' of ' + update.name + ' is now available', '', function() {
                if(type == 'anime') {
                    open.openAnime(update.id, update.ep, update.sub);
                } else {
                    open.openManga(update.id, update.ep, update.sub);
                }
            });
        };
    };

    ipc.on('new-anime-ep', displayNotification('anime'));
    ipc.on('new-manga-ep', displayNotification('manga'));

    ipc.send('check-login');
}]);

angular.module('proxtop').controller('MainController', ['$scope', 'ipc', '$state', 'notification', '$mdToast', function($scope, ipc, $state, notification, $mdToast) {
    ipc.on('check-login', function(result) {
        if(result) {
            ipc.send('watchlist-update');
            $state.go('profile');
        } else {
            $state.go('login');
        }
    });

    ipc.on('error', function(severity, message) {
        $mdToast.show($mdToast.simple().content(severity + ':' + message));
    });

    var displayNotification = function(type) {
        return function(update) {
            notification.displayNotification('Proxtop', 'Episode ' + update.episode + ' of ' + update.name + ' is now available', '', function() {
                if(type == 'anime') {
                    open.openAnime(update.id, update.episode, update.sub);
                } else {
                    open.openManga(update.id, update.episode, update.sub);
                }
            });
        };
    };

    ipc.on('new-anime-ep', displayNotification('anime'));
    ipc.on('new-manga-ep', displayNotification('manga'));

    ipc.send('check-login');
}]);

Fix displaying undefined for episode.


import Ember from 'ember';

export default Ember.Route.extend({
  model(params) {
    return this.store.findRecord('rule-set', params.rule_set_id);
  },

  actions: {
    createSkill() {
      const skill = this.store.createRecord('skill-definition');

      skill.set('ruleSet', this.currentModel);

      this.currentModel.get('skills').addObject(skill);

      this.transitionTo('skills.edit', skill);
    }
  }
});

import Ember from 'ember';

export default Ember.Route.extend({
  model(params) {
    return this.store.findRecord('rule-set', params.rule_set_id);
  },

  actions: {
    createSkill() {
      const skill = this.store.createRecord('skill-definition');

      skill.set('ruleSet', this.currentModel);

      this.currentModel.get('skills').addObject(skill);
      this.currentModel.save();

      this.transitionTo('skills.edit', skill);
    }
  }
});

Save the rule set after adding a skill


function toggleMenu()
{
    var menu = document.getElementById('main_menu');
    if (menu.style.display == "none") {
        menu.style.display = "block";
    } else {
        menu.style.display = "none";
    }
}

function toggleMenu()
{
    var menu = document.getElementById('main_menu');
    if (menu.style.display == "none" || menu.style.display == "") {
        menu.style.display = "block";
    } else {
        menu.style.display = "none";
    }
}

Fix foldable menu only shown on second click glitch.


module.exports = function(config) {
  config.set({
    frameworks: [ 'hydro' ],
    singleRun: true,
    browsers: [
      'PhantomJS',
      'Chrome',
      'Firefox',
      'Safari'
    ],
    files: [
      'test/*.js'
    ],
    hydro: {
      before: [
        'build/build.js',
      ]
    },
    client: {
      hydro: {
        setup: true,
        suite: 'hydro-require',
        plugins: [ 'hydro-require' ],
        proxies: {
          test: 'addTest'
        },
        require: {
          'assert': 'simple-assert'
        },
      }
    },
  });
};

module.exports = function(config) {
  var browsers = process.env.TRAVIS
    ? [ 'PhantomJS' ]
    : [ 'PhantomJS', 'Chrome', 'Firefox', 'Safari' ];

  config.set({
    frameworks: [ 'hydro' ],
    singleRun: true,
    browsers: browsers,
    files: [
      'test/*.js'
    ],
    hydro: {
      before: [
        'build/build.js',
      ]
    },
    client: {
      hydro: {
        setup: true,
        suite: 'hydro-require',
        plugins: [ 'hydro-require' ],
        proxies: {
          test: 'addTest'
        },
        require: {
          'assert': 'simple-assert'
        },
      }
    },
  });
};

Make karma pick up only PhantomJS when on Travis CI


module.exports = function(config) {
  config.set({
    basePath: '',
    frameworks: ['mocha', 'chai-jquery', 'jquery-2.1.0', 'chai', 'sinon-chai', 'fixture'],
    files: [
      'public/javascripts/vendor/d3.v3.min.js',
      'public/javascripts/traffic.js',
      'tests/**/*Spec.js',
      'tests/fixtures/**/*'
    ],
    exclude: [
      '**/*.swp'
    ],
    preprocessors: {
      'tests/**/*.json'   : ['json_fixtures']
    },
    jsonFixturesPreprocessor: {
      variableName: '__json__'
    },
    reporters: ['progress', 'coverage'],
    coverageReporter: {
      reporters: [
        { type: 'html', subdir: 'report-html' },
        { type: 'lcov', subdir: 'lcov-report' },
      ],
    },
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['PhantomJS'],
    singleRun: false
  });
};

module.exports = function(config) {
  config.set({
    basePath: '',
    frameworks: ['mocha', 'chai-jquery', 'jquery-2.1.0', 'chai', 'sinon-chai', 'fixture'],
    files: [
      'public/javascripts/vendor/d3.v3.min.js',
      'public/javascripts/traffic.js',
      'tests/**/*Spec.js',
      'tests/fixtures/**/*'
    ],
    exclude: [
      '**/*.swp'
    ],
    preprocessors: {
      'public/javascripts/*.js': ['coverage'],
      'tests/**/*.json'   : ['json_fixtures']
    },
    jsonFixturesPreprocessor: {
      variableName: '__json__'
    },
    reporters: ['progress', 'coverage'],
    coverageReporter: {
      reporters: [
        { type: 'html', subdir: 'report-html' },
        { type: 'lcov', subdir: 'lcov-report' },
      ],
    },
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['PhantomJS'],
    singleRun: false
  });
};

Add code coverage to karma


/*jshint node:true*/
"use strict";

var exec = require("child_process").exec;

function runCommand(command, args, done) {
    if(typeof args === "function") {
        done = args;
        args = "";
    }

    exec("p4 " + command + " " + (args || ""), function(err, stdOut, stdErr) {
        if(err) return done(err);
        if(stdErr) return done(new Error(stdErr));

        done(null, stdOut);
    });
}

function edit(path, done) {
    runCommand("edit", path, done);
}

function add(path, done) {
    runCommand("add", path, done);
}

function smartEdit(path, done) {
    edit(path, function(err) {
        if(!err) return done();

        add(path, done);
    });
}

exports.edit = edit;
exports.add = add;
exports.smartEdit = smartEdit;
exports.run = runCommand;

/*jshint node:true*/
"use strict";

var exec = require("child_process").exec;

function runCommand(command, args, done) {
    if(typeof args === "function") {
        done = args;
        args = "";
    }

    exec("p4 " + command + " " + (args || ""), function(err, stdOut, stdErr) {
        if(err) return done(err);
        if(stdErr) return done(new Error(stdErr));

        done(null, stdOut);
    });
}

function edit(path, done) {
    runCommand("edit", path, done);
}

function add(path, done) {
    runCommand("add", path, done);
}

function smartEdit(path, done) {
    edit(path, function(err) {
        if(!err) return done();

        add(path, done);
    });
}

function revertUnchanged(path, done) {
    runCommand("revert", "-a", done);
}

exports.edit = edit;
exports.add = add;
exports.smartEdit = smartEdit;
exports.run = runCommand;
exports.revertUnchanged = revertUnchanged;

Add revertUnchanged command, because it's not uncommon.


import App from "./App";

export default class Action {
    dispatchAction(...args) {
        return App.dispatchAction(...args);
    }

    get logger() {
        return App.logger(this.ns);
    }

    get events() {
        return App.events;
    }

    publish(ev, ...args) {
        return App.events.publish(`${this.ns}.${ev}`, ...args);
    }

    get ns() {
        return this._ns || "app";
    }
    set ns(namespace) {
        this._ns = namespace;
    }
}

import App from "./App";

export default class Action {
    dispatchAction(...args) {
        return App.dispatchAction(...args);
    }

    get logger() {
        return App.logger(this.ns);
    }

    get events() {
        return App.events;
    }

    publish(ev, ...args) {
        return App.events.publish(`${this.ns}.${ev}`, ...args);
    }

    get ns() {
        return this._ns || "app";
    }
    set ns(namespace) {
        this._ns = namespace;
    }

    static ns(key) { // Decorator for easily adding namespace.
        return (ActionWithNS) => ActionWithNS.prototype.ns = key;
    }
}

Add static decorator for action namespace.


module.exports = function(app) {
  var mongoose = require('mongoose'); // require mongoose (for mongodb integration
  var fs = require('fs'); // necessary to read from files

  fs.readFile(".dbconfig", 'utf8', function(err,data){
     if (err) {
          console.log(err);
     } else {
          var dbAccess = JSON.parse(data); // if read successful, parse JSON into object
          var userAuthString = dbAccess.user + ':' + dbAccess.password; // get username and password
          mongoose.connect('mongodb://' + userAuthString + '@ds147975.mlab.com:47975/classratdbtest'); // connect to database
     }
  });

  app.use(express.static("public")); // serve files within the "public" folder

  app.get("/api/hello", function(request, response) { // provide RESTful GET API at /hello
      response.send("Hello, World!"); // respond with string
  });

  app.listen(process.env.PORT, process.env.IP); // begin app listening on Cloud9 port/IP

  console.log("API running!");
}

module.exports = function(app) {
  var mongoose = require('mongoose'); // require mongoose (for mongodb integration
  var fs = require('fs'); // necessary to read from files

  fs.readFile(".dbconfig", 'utf8', function(err,data){
     if (err) {
          console.log(err);
     } else {
          var dbAccess = JSON.parse(data); // if read successful, parse JSON into object
          var userAuthString = dbAccess.user + ':' + dbAccess.password; // get username and password
          mongoose.connect('mongodb://' + userAuthString + '@ds147975.mlab.com:47975/classratdbtest'); // connect to database
     }
  });

  app.get("/api/hello", function(request, response) { // provide RESTful GET API at /hello
      response.send("Hello, World!"); // respond with string
  });

  app.listen(process.env.PORT, process.env.IP); // begin app listening on Cloud9 port/IP

  console.log("API running!");
}

Remove old express listen line that causes a server error.


const express = require('express');
const app = express();
const portNumber = 3000;

const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser')
if (process.env.NODE_ENV !== 'production') {
  require('dotenv').config();
}
const path = require('path');

const users = require('./routes/users');
const token = require('./routes/token');
const resorts = require('./routes/resorts');
const trails = require('./routes/trails');
const ratings = require('./routes/ratings');
const favorites = require('./routes/favorites');

app.use(bodyParser.json());
app.use(cookieParser());

app.use('/apidoc', express.static(path.join(__dirname, 'apidoc')));

app.use(users);
app.use(token);
app.use(resorts);
app.use(trails);
app.use(ratings);
app.use(favorites);

app.use((req, res) => {
  res.sendStatus(404);
});

app.listen(portNumber, () => {
  console.log(`Listening on port ${portNumber}`);
});

module.exports = app;

const express = require('express');
const app = express();
const portNumber = process.env.PORT || 3000;

const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser')
if (process.env.NODE_ENV !== 'production') {
  require('dotenv').config();
}
const path = require('path');

const users = require('./routes/users');
const token = require('./routes/token');
const resorts = require('./routes/resorts');
const trails = require('./routes/trails');
const ratings = require('./routes/ratings');
const favorites = require('./routes/favorites');

app.use(bodyParser.json());
app.use(cookieParser());

app.use('/apidoc', express.static(path.join(__dirname, 'apidoc')));

app.use(users);
app.use(token);
app.use(resorts);
app.use(trails);
app.use(ratings);
app.use(favorites);

app.use((req, res) => {
  res.sendStatus(404);
});

app.listen(portNumber, () => {
  console.log(`Listening on port ${portNumber}`);
});

module.exports = app;

Set up dynamic port number


/* eslint-disable */
import '@cypress/code-coverage/support'

require('./commands')

beforeEach(() => {
  cy.resetFeatureFlags()
})

after(() => {
  cy.task('generateReport')
})

/* eslint-disable */
import '@cypress/code-coverage/support'

require('./commands')

after(() => {
  cy.task('generateReport')
})

Revert the resetFeatureFlag command as tests were breaking


/**
 * enemy.js
 *
 * Manages creation and properties for our enemy object
 */

// Alias our enemy object
var enemy = game.objs.enemy;

/**
 * Enemy object factory
 *
 * @returns {object}
 */
var createEnemy = function() {
    var svg = document.getElementById('game-board');
    var rect = document.createElementNS(SVG_SPEC, 'rect');
    var gameBoard = game.objs.board;

    var xPos = 100;
    var yPos = 100;
    var width = 10;
    var height = 10;

    var init = function() {
        rect.setAttribute('x', xPos);
        rect.setAttribute('y', yPos);
        rect.setAttribute('width', width);
        rect.setAttribute('height', height);
        rect.id = 'enemy';

        svg.appendChild(rect);
    };

    var destroy = function() {
        var gameBoard = document.getElementById('game-board');
        var enemy = document.getElementById('enemy');

        if (enemy) {
            gameBoard.removeChild(enemy);
        }

        game.destroy('enemy');
    };

    init();

    return {
        xPos: xPos,
        yPos: yPos,
        width: width,
        height: height,
        area: width * height,
        destroy: destroy
    };
};

// Set objects and methods
game.create.enemy = createEnemy;
game.objs.enemy = game.create.enemy();

/**
 * enemy.js
 *
 * Manages creation and properties for our enemy object
 */

// Alias our enemy object
var enemy = game.objs.enemy;

/**
 * Enemy object factory
 *
 * @returns {object}
 */
var createEnemy = function() {
    var svg = document.getElementById('game-board');
    var rect = document.createElementNS(SVG_SPEC, 'rect');
    var gameBoard = game.objs.board;

    var getRandomInt = function(min, max) {
        return Math.floor(Math.random() * (max - min)) + min;
    };

    var width = getRandomInt(10, 40);
    var height = getRandomInt(10, 40);
    var xPos = getRandomInt(100, gameBoard.width - width);
    var yPos = getRandomInt(100, gameBoard.height - height);

    var init = function() {
        rect.setAttribute('x', xPos);
        rect.setAttribute('y', yPos);
        rect.setAttribute('width', width);
        rect.setAttribute('height', height);
        rect.id = 'enemy';

        svg.appendChild(rect);
    };

    var destroy = function() {
        var gameBoard = document.getElementById('game-board');
        var enemy = document.getElementById('enemy');

        if (enemy) {
            gameBoard.removeChild(enemy);
        }

        game.destroy('enemy');
    };

    init();

    return {
        xPos: xPos,
        yPos: yPos,
        width: width,
        height: height,
        area: width * height,
        destroy: destroy
    };
};

// Set objects and methods
game.create.enemy = createEnemy;
game.objs.enemy = game.create.enemy();

Allow randomized size and position for enemies


var phantom = require('phantom')

import Element from './element';

class GhostJS {
  constructor () {
  }

  async open (url) {
  	return new Promise(resolve => {
  		phantom.create(ph => {
        this.phantom = ph
        ph.createPage((page) => {
          this.page = page;
          page.open(url, (status) => {
            resolve(status);
          })
        })
      })
    })
  }

  screenshot (filename, folder='screenshots') {
    filename = filename || 'screenshot-' + Date.now()
    this.page.render(`${folder}/${filename}.png`)
  }

  async pageTitle () {
    return new Promise(resolve => {
      this.page.evaluate(() => { return document.title }, result => {
        resolve(result)
      })
    })
  }

  findElement (selector) {
    return new Element(this.page, selector);
  }

  async waitFor (func, pollMs=100) {
    return new Promise(resolve => {
      var poll = () => {
        var result = func()
        if (result) {
          resolve(result)
        } else {
          setTimeout(poll, pollMs)
        }
      }
      poll()
    })
  }
}

var ghost = new GhostJS()
export default ghost

var phantom = require('phantom')

import Element from './element';

class GhostJS {
  constructor () {
  }

  async open (url) {
  	return new Promise(resolve => {
  		phantom.create(ph => {
        this.phantom = ph
        ph.createPage((page) => {
          this.page = page;
          page.open(url, (status) => {
            resolve(status);
          })
        })
      },
      {
        // The dnode `weak` dependency is failing to install on travis.
        // Disable this for now until someone needs it.
        dnodeOpts: {
          weak: false
        }
      })
    })
  }

  screenshot (filename, folder='screenshots') {
    filename = filename || 'screenshot-' + Date.now()
    this.page.render(`${folder}/${filename}.png`)
  }

  async pageTitle () {
    return new Promise(resolve => {
      this.page.evaluate(() => { return document.title }, result => {
        resolve(result)
      })
    })
  }

  findElement (selector) {
    return new Element(this.page, selector);
  }

  async waitFor (func, pollMs=100) {
    return new Promise(resolve => {
      var poll = () => {
        var result = func()
        if (result) {
          resolve(result)
        } else {
          setTimeout(poll, pollMs)
        }
      }
      poll()
    })
  }
}

var ghost = new GhostJS()
export default ghost

Disable dnode/weak for failing to install on travis.


'use strict';

var minify = require('html-minifier').minify;

function minifyHTML(opts) {
    function minifier(req, res, next) {
        if (opts.override !== true) {
            res.renderMin = function (view, renderOpts) {
                this.render(view, renderOpts, function (err, html) {
                    if (err) throw err;
                    html = minify(html, opts.htmlMinifier);
                    res.send(html);
                });
            }
        } else {
            res.oldRender = res.render;
            res.render = function (view, renderOpts) {
                this.oldRender(view, renderOpts, function (err, html) {
                    if (err) throw err;
                    html = minify(html, opts.htmlMinifier);
                    res.send(html);
                });
            };
        }
        return next();
    }
    return (minifier);
}

module.exports = minifyHTML;

'use strict';

var minify = require('html-minifier').minify;

function minifyHTML(opts) {
    if (!opts) opts = {};
    function minifier(req, res, next) {
        if (opts.override !== true) {
            res.renderMin = function (view, renderOpts) {
                this.render(view, renderOpts, function (err, html) {
                    if (err) throw err;
                    html = minify(html, opts.htmlMinifier);
                    res.send(html);
                });
            }
        } else {
            res.oldRender = res.render;
            res.render = function (view, renderOpts) {
                this.oldRender(view, renderOpts, function (err, html) {
                    if (err) throw err;
                    html = minify(html, opts.htmlMinifier);
                    res.send(html);
                });
            };
        }
        return next();
    }
    return (minifier);
}

module.exports = minifyHTML;

Fix crash on no options


'use strict'

exports.config = {
  directConnect: true,

  allScriptsTimeout: 80000,

  specs: [
    'test/e2e/*.js'
  ],

  capabilities: {
    'browserName': 'chrome'
  },

  baseUrl: 'http://localhost:3000',

  framework: 'jasmine2',

  jasmineNodeOpts: {
    showColors: true,
    defaultTimeoutInterval: 80000
  },

  onPrepare: function () {
    var jasmineReporters = require('jasmine-reporters')
    jasmine.getEnv().addReporter(new jasmineReporters.JUnitXmlReporter({
      consolidateAll: true,
      savePath: 'build/reports/e2e_results'
    }))

    // Get cookie consent popup out of the way
    browser.get('/#')
    browser.manage().addCookie({ name: 'cookieconsent_status', value: 'dismiss' })
  }
}

'use strict'

exports.config = {
  directConnect: true,

  allScriptsTimeout: 80000,

  specs: [
    'test/e2e/*.js'
  ],

  capabilities: {
    browserName: 'chrome',
    chromeOptions: {
      args: [ "--headless", "--disable-gpu", "--window-size=800,600" ]
    }
  },

  baseUrl: 'http://localhost:3000',

  framework: 'jasmine2',

  jasmineNodeOpts: {
    showColors: true,
    defaultTimeoutInterval: 80000
  },

  onPrepare: function () {
    var jasmineReporters = require('jasmine-reporters')
    jasmine.getEnv().addReporter(new jasmineReporters.JUnitXmlReporter({
      consolidateAll: true,
      savePath: 'build/reports/e2e_results'
    }))

    // Get cookie consent popup out of the way
    browser.get('/#')
    browser.manage().addCookie({ name: 'cookieconsent_status', value: 'dismiss' })
  }
}

Use headless Chrome for Protractor tests


var events = require('events')
  , util = require('util');

function Mapper() {
  events.EventEmitter.call(this);
}

util.inherits(Mapper, events.EventEmitter);

Mapper.prototype.map = function(server) {
  var self = this
    , sitemaps = [];

  server.on('request', function(page) {
    // TOOD: Make sure this works if page properties are set up async
    if (page.isSitemap) {
      sitemaps.push(page);
    }
  });

  this.wait = true;

  server.once('finish', function() {
    self.request('/robots.txt', function(page) {
      sitemaps = sitemaps.filter(function(p) { return !p.isInSitemap; });
      page.locals = page.locals || Object.create(null);
      page.locals.sitemaps = sitemaps;
    });

    self.emit('finish');
  });
};

module.exports = Mapper;

var events = require('events')
  , util = require('util');

function Mapper() {
  events.EventEmitter.call(this);
}

util.inherits(Mapper, events.EventEmitter);

Mapper.prototype.map = function(server) {
  var self = this
    , sitemaps = [];

  server.on('request', function(page) {
    // TOOD: Make sure this works if page properties are set up async
    if (page.isSitemap) {
      sitemaps.push(page);
    }
  });

  this.wait = true;

  server.once('finish', function() {
    self.request('/robots.txt', function(page) {
      sitemaps = sitemaps.filter(function(p) { return !p.isInSitemap; });
      page.locals = page.locals || Object.create(null);
      page.locals.sitemaps = sitemaps;
    });

    self.end();
  });
};

module.exports = Mapper;

Call end when done mapping.


var Layer = require('./Layer');
var audioNotes = require('audio-notes');
var oscillatorParams = require('./mixins/oscillatorParams');

var Oscillator = Layer.extend(oscillatorParams, {

  type: 'oscillator',

  _source: function (params) {
    var oscillator = this.context.createOscillator();
    var frequency = params.frequency;

    if (params.note) {
      frequency = audioNotes[params.note] || frequency;
    }
    oscillator.frequency.value = frequency || 0;

    var detune = (1200 / 100) * params.pitch;
    oscillator.detune.value = detune;

    oscillator.type = params.shape;
    return oscillator;
  }
});

module.exports = Oscillator;

var Layer = require('./Layer');
var audioNotes = require('audio-notes');
var oscillatorParams = require('./mixins/oscillatorParams');

var Oscillator = Layer.extend(oscillatorParams, {

  type: 'oscillator',

  _source: function (params) {
    var oscillator = this.context.createOscillator();

    var frequency = params.frequency;
    if (params.note) {
      frequency = audioNotes[params.note] || frequency;
    }
    oscillator.frequency.value = frequency || 0;

    oscillator.detune.value = params.pitch * 100;
    oscillator.type = params.shape;

    return oscillator;
  }
});

module.exports = Oscillator;

Change oscillator pitch to be represented as semitones


const events = require('events');

const handleEvent = require('./handle-event');

module.exports = class Plugin extends events.EventEmitter {
  constructor() {
    super();
    this.robot = null;
  }

  load(robot) {
    this.robot = robot;
    this.robot.on('pull_request', this.acceptEvent.bind(this));
  }

  async acceptEvent(event, context) {
    if (this.robot === null) {
      this.emit('error', new Error('Cannot accept event before load'));
      return;
    }

    try {
      await handleEvent(this.robot, event, context);
      this.emit('finished');
    } catch (err) {
      this.emit('error', err);
    }
  }
};

const events = require('events');

const handleEvent = require('./handle-event');

module.exports = class Plugin extends events.EventEmitter {
  constructor() {
    super();
    this.robot = null;
  }

  load(robot) {
    if (!(this instanceof Plugin)) {
      throw new TypeError('Unexpected execution context for method call');
    }

    if (this.robot !== null) {
      this.emit('error', new Error('Plugin instance has already been loaded'));
    }

    this.robot = robot;
    this.robot.on('pull_request', async (event, context) => this.acceptEvent(event, context));
  }

  async acceptEvent(event, context) {
    if (!(this instanceof Plugin)) {
      throw new TypeError('Unexpected execution context for method call');
    }

    if (this.robot === null) {
      this.emit('error', new Error('Cannot accept event before load'));
      return;
    }

    try {
      await handleEvent(this.robot, event, context);
      this.emit('finished');
    } catch (err) {
      this.emit('error', err);
    }
  }
};

Add more checks to `Plugin`


import React from 'react';

export default class SparklinesLine extends React.Component {

    static propTypes = {
        color: React.PropTypes.string,
        style: React.PropTypes.object
    };

    static defaultProps = {
        style: {}
    };

    render() {
        const { points, width, height, margin, color, style } = this.props;

        const linePoints = points
            .map((p) => [p.x, p.y])
            .reduce((a, b) => a.concat(b));
        const closePolyPoints = [
            points[points.length - 1].x, height - margin,
            margin, height - margin,
            margin, points[0].y
        ];
        const fillPoints = linePoints.concat(closePolyPoints);

        const lineStyle = {
            stroke: color || style.stroke || 'slategray',
            strokeWidth: style.strokeWidth || '1',
            strokeLinejoin: style.strokeLinejoin || 'round',
            strokeLinecap: style.strokeLinecap || 'round',
            fill: 'none'
        };
        const fillStyle = {
            stroke: style.stroke || 'none',
            strokeWidth: '0',
            fillOpacity: style.fillOpacity || '.1',
            fill: color || style.fill || 'slategray'
        };

        return (
            <g>
                <polyline points={fillPoints.join(' ')} style={fillStyle} />
                <polyline points={linePoints.join(' ')} style={lineStyle} />
            </g>
        )
    }
}

import React from 'react';

export default class SparklinesLine extends React.Component {

    static propTypes = {
        color: React.PropTypes.string,
        style: React.PropTypes.object
    };

    static defaultProps = {
        style: {}
    };

    render() {
        const { points, width, height, margin, color, style } = this.props;

        const linePoints = points
            .map((p) => [p.x, p.y])
            .reduce((a, b) => a.concat(b));
        const closePolyPoints = [
            points[points.length - 1].x, height - margin,
            margin, height - margin,
            margin, points[0].y
        ];
        const fillPoints = linePoints.concat(closePolyPoints);

        const lineStyle = {
            stroke: color || style.stroke || 'slategray',
            strokeWidth: style.strokeWidth || '1',
            strokeLinejoin: style.strokeLinejoin || 'round',
            strokeLinecap: style.strokeLinecap || 'round',
            fill: 'none'
        };
        const fillStyle = {
            stroke: style.stroke || 'none',
            strokeWidth: '0',
            fillOpacity: style.fillOpacity || '.1',
            fill: style.fill || color || 'slategray'
        };

        return (
            <g>
                <polyline points={fillPoints.join(' ')} style={fillStyle} />
                <polyline points={linePoints.join(' ')} style={lineStyle} />
            </g>
        )
    }
}

Change precedence of fillStyle's parameters


var express = require('express');
var bodyParser = require('body-parser');
var elementExplorerClient = require('./elementExplorerClient');
var cliHelper = require('./cliHelper');

var startExpress = function() {
  var app = express();
  app.use(bodyParser.json());

  app.get('/testSelector', function(req, res) {
    var query = req.query;

    if (query.popupInput) {
      return handlePopupRequest(query.popupInput, res);
    }

    console.log('Got', query);
    res.send('Hello World!');
  });

  function handlePopupRequest(input, res) {
    console.log('Popup input [%s]', input);

    elementExplorerClient.runCommand(input).then(function(response) {
      res.send(response);
    });
  }

  var server = app.listen(13000, function() {
    console.log('Elementor is listening on http://localhost:%s',
        server.address().port);
  });
};

var startProtractor = function() {
  return cliHelper.startProtractor();
};

var startServer = function() {
  startProtractor().then(function() {
    console.log('Done starting protractor');
    startExpress();
  });
};

module.exports = {
  start: startServer
};

var express = require('express');
var bodyParser = require('body-parser');
var elementExplorerClient = require('./elementExplorerClient');
var cliHelper = require('./cliHelper');

var startExpress = function() {
  var app = express();
  app.use(bodyParser.json());

  app.get('/testSelector', function(req, res) {
    var query = req.query;

    if (query.popupInput) {
      return handlePopupRequest(query.popupInput, res);
    }

    console.log('Got', query);
    res.send('Hello World!');
  });

  function handlePopupRequest(input, res) {
    console.log('Popup input [%s]', input);

    elementExplorerClient.runCommand(input).then(function(response) {
      var results = {};
      results[input] = response;

      res.send(JSON.stringify({
        results: results
      }));
    });
  }

  var server = app.listen(13000, function() {
    console.log('Elementor is listening on http://localhost:%s',
        server.address().port);
  });
};

var startProtractor = function() {
  return cliHelper.startProtractor();
};

var startServer = function() {
  startProtractor().then(function() {
    console.log('Done starting protractor');
    startExpress();
  });
};

module.exports = {
  start: startServer
};

Send JSON response instead of string


var React = require("react");
var _ = require("underscore");
var extend = require("./extend");
var elementFactory = require("./elementFactory");

function PageObject(props) {
  if (isReactInstance(props)) {
    this.element = props;
  } else {
    this.element = getElement(this);
  }

  this.dispose = dispose;

  _.each(this.element.refs, function (ref, name) {
    this[name] = elementFactory(ref, name, this.elements);
  }, this);

  Object.defineProperty(this, "state", {
    get: function () {
      return this.element.state;
    }
  });

  function getElement(po) {
    var utils = require("react/addons").addons.TestUtils;
    var component = po.getComponent(props);

    if (!component) {
      throw new Error("component required");
    }

    return utils.renderIntoDocument(component);
  }

  function dispose() {
    React.unmountComponentAtNode(this.element.getDOMNode().parentNode);
  }
}

function isReactInstance(obj) {
  return _.has(obj, "_owner");
}

PageObject.prototype = {
  getComponent: function () {
    console.warn("Implement PageObject#getComponent");
  }
};

PageObject.extend = extend;

module.exports = PageObject;

var React = require("react");
var _ = require("underscore");
var extend = require("./extend");
var elementFactory = require("./elementFactory");

function PageObject(props) {
  if (isReactInstance(props)) {
    this.element = props;
  } else {
    this.element = getElement(this);
  }

  this.dispose = dispose;

  wrapComponentDidUpdate(this.element, this);

  updateRefs(this);

  Object.defineProperty(this, "state", {
    get: function () {
      return this.element.state;
    }
  });

  function getElement(po) {
    var utils = require("react/addons").addons.TestUtils;
    var component = po.getComponent(props);

    if (!component) {
      throw new Error("component required");
    }

    return utils.renderIntoDocument(component);
  }

  function updateRefs(po) {
    _.each(po.element.refs, function (ref, name) {
      this[name] = elementFactory(ref, name, po.elements);
    }, po);
  }

  function wrapComponentDidUpdate(component, po) {
    var oldComponentDidUpdate = component.componentDidUpdate;
    component.componentDidUpdate = function () {
      updateRefs(po);
      oldComponentDidUpdate && oldComponentDidUpdate.apply(this, arguments);
    };
  }

  function dispose() {
    React.unmountComponentAtNode(this.element.getDOMNode().parentNode);
  }
}

function isReactInstance(obj) {
  return _.has(obj, "_owner");
}

PageObject.prototype = {
  getComponent: function () {
    console.warn("Implement PageObject#getComponent");
  }
};

PageObject.extend = extend;

module.exports = PageObject;

Update refs when the virtual dom is updated


'use strict';

module.exports = {

	entry: './src/index.js',

	output: {
		path: 'dist',
		filename: 'index.js'
	},

	target: 'node',

	module: {
		loaders: [
			{test: /\.coffee$/, loader: 'coffee-loader'},
			{
				test: /\.js/,
				loader: 'babel',
				exclude: /node_modules/,
				query: {
					presets: ['es2015']
				}
			}
		]
	}

};

'use strict';

module.exports = {

	entry: './src/index.js',

	output: {
		path: 'dist',
		filename: 'index.js',
		libraryTarget: 'commonjs2'
	},

	target: 'node',

	module: {
		loaders: [
			{test: /\.coffee$/, loader: 'coffee-loader'},
			{
				test: /\.js/,
				loader: 'babel',
				exclude: /node_modules/,
				query: {
					presets: ['es2015']
				}
			}
		]
	}

};

Update webpack to output libraryTarget commonjs2


var path = require('path');

module.exports = {

  entry: './src/index.js',

  output: {
    path: path.join(__dirname, 'dist'),
    filename: 'react-atellier.js',
    library: 'ReactAtellier',
    libraryTarget: 'umd',
  },
  externals: {
      // Use external version of React
      "react": "React"
  },
  module: {
    loaders: [
      {
       test: /\.less$/,
       loader: 'style!css!less?outputStyle=expanded&' +
         'includePaths[]=' + (path.resolve(__dirname, './bower_components')) + '&' +
         'includePaths[]=' + (path.resolve(__dirname, './node_modules'))
      },
      {
        test: /\.less$/,
        loader: 'autoprefixer'
      },
      {
        test: /\.(otf|ttf)$/,
        loader: 'url?limit=100000'
      },
      {
        test: /\.(png|jpg)$/,
        loader: 'url?limit=45000'
      },
      {
        test: /(\.js)$/,
        exclude: [path.resolve(__dirname, 'node_modules')],
        loader: 'babel',
        include: path.join(__dirname, 'src')
      },
    ],
  },

  devServer: {
    contentBase: './example',
    host: 'localhost',
    inline: true,
    info: false,
  },
};

var path = require('path');

module.exports = {

  entry: './src/index.js',

  output: {
    path: path.join(__dirname, 'dist'),
    filename: 'react-atellier.js',
    library: 'ReactAtellier',
    libraryTarget: 'umd',
  },
  externals: {
      // Use external version of React
      'react': 'react',
      'react-dom': 'react-dom'
  },
  module: {
    loaders: [
      {
       test: /\.less$/,
       loader: 'style!css!less?outputStyle=expanded&' +
         'includePaths[]=' + (path.resolve(__dirname, './bower_components')) + '&' +
         'includePaths[]=' + (path.resolve(__dirname, './node_modules'))
      },
      {
        test: /\.less$/,
        loader: 'autoprefixer'
      },
      {
        test: /\.(otf|ttf)$/,
        loader: 'url?limit=100000'
      },
      {
        test: /\.(png|jpg)$/,
        loader: 'url?limit=45000'
      },
      {
        test: /(\.js)$/,
        exclude: [path.resolve(__dirname, 'node_modules')],
        loader: 'babel',
        include: path.join(__dirname, 'src')
      },
    ],
  },

  devServer: {
    contentBase: './example',
    host: 'localhost',
    inline: true,
    info: false,
  },
};

Use external `react` and `react-dom`


const path = require('path')

module.exports = (env, argv) => ({
    entry: './src/components/App.js',

    output: {
        filename: 'bundle.js',
        path: __dirname
    },

    devtool: argv.mode === 'production' ? false : 'cheap-module-eval-source-map',
    target: 'electron-renderer',

    node: {
        __dirname: false
    },

    resolve: {
        alias: {
            'react': path.join(__dirname, 'node_modules/preact/dist/preact.min'),
            'preact': path.join(__dirname, 'node_modules/preact/dist/preact.min'),
            'prop-types': path.join(__dirname, 'src/modules/shims/prop-types')
        }
    }
})

const path = require('path')

module.exports = (env, argv) => ({
    entry: './src/components/App.js',

    output: {
        filename: 'bundle.js',
        path: __dirname
    },

    devtool: argv.mode === 'production' ? false : 'cheap-module-eval-source-map',
    target: 'electron-renderer',

    node: {
        __dirname: false
    },

    resolve: {
        alias: {
            'react': path.join(__dirname, 'node_modules/preact/dist/preact.min'),
            'preact': path.join(__dirname, 'node_modules/preact/dist/preact.min'),
            'prop-types': path.join(__dirname, 'src/modules/shims/prop-types')
        }
    },

    externals: {
        'moment': 'null'
    }
})

Mark moment as external again


const path = require('path');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

module.exports = {
    entry: {
        'dolphin-platform.bundle': './src/clientContextFactory.js',
        'dolphin-platform.bundle.min': './src/clientContextFactory.js'
    },
    devtool: 'source-map',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js',
        library: 'dolphin',
        libraryTarget: 'umd',
        //umdNamedDefine: true
    },
    plugins: [
        new UglifyJsPlugin({
            include: /\.min\.js$/
        })
    ]
};

const path = require('path');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

module.exports = {
    entry: {
        'dolphin-platform': './src/clientContextFactory.js',
        'dolphin-platform.min': './src/clientContextFactory.js'
    },
    devtool: 'source-map',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js',
        library: 'dolphin',
        libraryTarget: 'umd',
        //umdNamedDefine: true
    },
    plugins: [
        new UglifyJsPlugin({
            include: /\.min\.js$/
        })
    ]
};

Use old filename to ensure backward compatibility


var path = require('path')

module.exports = {
  entry: [
    './src/index.js'
  ],
  output: {
    path: path.join(__dirname, '/dist/'),
    filename: 'oidc.rp.min.js',
    library: 'OIDC',
    libraryTarget: 'var'
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        exclude: /(node_modules)/,
        loader: 'babel-loader',
        query: {
          presets: ['es2015']
        }
      }
    ]
  },
  externals: {
    'node-fetch': 'fetch',
    'text-encoding': 'TextEncoder',
    'whatwg-url': 'URL',
    '@trust/webcrypto': 'crypto'
  },
  devtool: 'source-map'
}

var path = require('path')

module.exports = {
  entry: [
    './src/index.js'
  ],
  output: {
    path: path.join(__dirname, '/dist/'),
    filename: 'oidc.rp.min.js',
    library: 'OIDC',
    libraryTarget: 'var'
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        exclude: /(node_modules)/,
        loader: 'babel-loader',
        query: {
          presets: ['es2015']
        }
      }
    ]
  },
  externals: {
    'node-fetch': 'fetch',
    'text-encoding': 'TextEncoder',
    'whatwg-url': 'window',
    '@trust/webcrypto': 'crypto'
  },
  devtool: 'source-map'
}

Fix whatwg-url URL externals entry


const path = require('path');
const VueLoaderPlugin = require('vue-loader/lib/plugin');

module.exports = {
  // webpack folder's entry js - excluded from jekll's build process.
  entry: './webpack/entry.js',
  output: {
    // we're going to put the generated file in the assets folder so jekyll will grab it.
    path: path.resolve(__dirname, 'assets/js/'),
    filename: 'bundle.js',
  },
  stats: {
    assets: true,
    children: false,
    chunks: false,
    errors: true,
    errorDetails: true,
    modules: false,
    timings: true,
    colors: true,
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          loaders: {
            js: 'babel?presets[]=es2015',
          },
        },
      },
      {
        test: /\.js?$/,
        exclude: /(node_modules)/,
        loader: 'babel-loader',
      },
    ],
  },
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
    },
    extensions: ['*', '.js', '.vue', '.json'],
  },
  plugins: [
    new VueLoaderPlugin(),
  ],
};

const path = require('path');
const VueLoaderPlugin = require('vue-loader/lib/plugin');

module.exports = {
  // webpack folder's entry js - excluded from jekll's build process.
  entry: './webpack/entry.js',
  output: {
    // we're going to put the generated file in the assets folder so jekyll will grab it.
    path: path.resolve(__dirname, './assets/js/'),
    filename: 'bundle.js',
  },
  stats: {
    assets: true,
    children: false,
    chunks: false,
    errors: true,
    errorDetails: true,
    modules: false,
    timings: true,
    colors: true,
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          loaders: {
            js: 'babel?presets[]=es2015',
          },
        },
      },
      {
        test: /\.js?$/,
        exclude: /(node_modules)/,
        loader: 'babel-loader',
      },
    ],
  },
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
    },
    extensions: ['*', '.js', '.vue', '.json'],
  },
  plugins: [
    new VueLoaderPlugin(),
  ],
};

Fix for webpack output location on CI server


const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const externals = require('webpack-node-externals')
const path = require('path')

const devMode = process.env.NODE_ENV !== 'production'

module.exports = [
  {
    mode: devMode ? 'development' : 'production',
    entry: './client',
    output: {
      path: path.resolve('dist'),
      filename: 'client.js'
    },
    devtool: 'cheap-source-map',
    module: {
      rules: [
        {
          test: /\.less$/,
          use: [
            devMode ? 'style-loader' : MiniCssExtractPlugin.loader,
            'css-loader',
            'less-loader'
          ]
        }
      ]
    },
    plugins: [
      new MiniCssExtractPlugin()
    ]
  },
  {
    mode: devMode ? 'development' : 'production',
    entry: {
      server: './server',
      loader: './server/loader'
    },
    output: {
      path: path.resolve('dist'),
      filename: '[name].js'
    },
    devtool: 'cheap-source-map',
    target: 'node',
    externals: externals(),
    node: false
  }
]

const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const externals = require('webpack-node-externals')
const path = require('path')

const devMode = process.env.NODE_ENV !== 'production'

module.exports = [
  {
    mode: devMode ? 'development' : 'production',
    entry: './client',
    output: {
      path: path.resolve('dist'),
      filename: 'client.js'
    },
    devtool: 'cheap-source-map',
    module: {
      rules: [
        {
          test: /\.less$/,
          use: [
            devMode ? 'style-loader' : MiniCssExtractPlugin.loader,
            'css-loader',
            'less-loader'
          ]
        }
      ]
    },
    plugins: [
      new MiniCssExtractPlugin({
        filename: 'client.css'
      })
    ]
  },
  {
    mode: devMode ? 'development' : 'production',
    entry: {
      server: './server',
      loader: './server/loader'
    },
    output: {
      path: path.resolve('dist'),
      filename: '[name].js'
    },
    devtool: 'cheap-source-map',
    target: 'node',
    externals: externals(),
    node: false
  }
]

Fix name of production CSS file


var webpack = require('webpack');
module.exports = {
  entry: [
    "./src/components/app.js"
  ],
  output: {
    path: __dirname + '/static',
    filename: "bundle.js"
  },
  module: {
    loaders: [
      {
        test: /\.js?$/,
        loader: 'babel-loader',
        query: {
          presets: ['es2015', 'react']
        },
        exclude: /node_modules/
      }
    ]
  },
  plugins: [
  ]
};

var webpack = require('webpack');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
  entry: [
    "./src/components/app.js"
  ],
  output: {
    path: __dirname + '/static',
    filename: "bundle.js"
  },
  module: {
    loaders: [
      {
        test: /\.js?$/,
        loader: 'babel-loader',
        query: {
          presets: ['es2015', 'react']
        },
        exclude: /node_modules/
      },
      {
        test: /\.css/,
        loader: ExtractTextPlugin.extract('style-loader', 'css-loader')
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin('styles.css', {
      allChunks: true
    })
  ]
};

Configure webpack to use the extract text plugin


var webpack = require('webpack')

module.exports = {
  contentBase: './dist/',
  cache: true,
  inline: true,
  entry: {
    app: ['webpack/hot/dev-server', './js/entry.js']
  },
  devtool: '#source-map',
  output: {
    path: './public/built',
    filename: 'bundle.js',
    publicPath: 'http://localhost:8080/built/',
    sourceMapFilename: '[file].map',
    stats: { colors: true }
  },
  module: {
    noParse: [],
    loaders: [
      {
        test: /\.js?$/,
        exclude: [
          /node_modules/,
          /bower_components/,
          /\.min.js$/
        ],
        loader: 'babel',
        query: {
          presets: ['react', 'es2015']
        }
      },
      {
        test: /\.less$/,
        loader: 'style-loader!css-loader!less-loader'
      },
      {
        test: /\.css$/,
        loader: 'style-loader!css-loader'
      }
    ]
  },
  resolve: {
    alias: {
      // The with-addons is only needed for perseus :(
      // example: 'react': path.resolve('node_modules/react/dist/react-with-addons.js'),
    },
    extensions: ['', '.js', '.jsx']
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new webpack.IgnorePlugin(new RegExp('^(fs|ipc)$'))
  ],
  devServer: {
    contentBase: './public',
    publicPath: 'http://localhost:8080/built/'
  }
}

var webpack = require('webpack')

module.exports = {
  contentBase: './dist/',
  cache: true,
  inline: true,
  target: 'atom',
  entry: {
    app: ['webpack/hot/dev-server', './js/entry.js']
  },
  devtool: '#source-map',
  output: {
    path: './public/built',
    filename: 'bundle.js',
    publicPath: 'http://localhost:8080/built/',
    sourceMapFilename: '[file].map',
    stats: { colors: true }
  },
  module: {
    noParse: [],
    loaders: [
      {
        test: /\.js?$/,
        exclude: [
          /node_modules/,
          /bower_components/,
          /\.min.js$/
        ],
        loader: 'babel',
        query: {
          presets: ['react', 'es2015']
        }
      },
      {
        test: /\.less$/,
        loader: 'style-loader!css-loader!less-loader'
      },
      {
        test: /\.css$/,
        loader: 'style-loader!css-loader'
      }
    ]
  },
  resolve: {
    alias: {
      // The with-addons is only needed for perseus :(
      // example: 'react': path.resolve('node_modules/react/dist/react-with-addons.js'),
    },
    extensions: ['', '.js', '.jsx']
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin()
  ],
  devServer: {
    contentBase: './public',
    publicPath: 'http://localhost:8080/built/'
  }
}

Set atom target for webpack


var path = require('path');

module.exports = {
  context: __dirname + "/src",
  entry: "./jsx/main.jsx",
  output: {
    path: path.join(__dirname, 'build'),
    filename: 'js/app.js'
  },
  resolve: {
    extensions: ['', '.js', '.jsx'],
    modulesDirectories: ["src/jsx", "node_modules"]
  },
  module: {
    loaders: [
      {
        exclude: /node_modules/,
        test: /\.jsx?$/,
        loader: "babel-loader",
        query: {
          cacheDirectory: true,
          presets: ['es2015', 'react']
        }
      },
      {
        test: /\.jpe?g$|\.gif$|\.png$|\.svg$|\.woff$|\.ttf$|\.wav$|\.mp3$/,
        loader: "file"
      },
      {
        test: /\.html$/,
        loader: "file?name=[name].[ext]",
      },
      {
        test: /\.sass$/,
        loaders: ["style", "css", "sass?indentedSyntax"]
      }
    ]
  },
  // context: path.join(__dirname, 'build'),
  plugins: [
  ]
};

var path = require('path');
var webpack = require('webpack');

module.exports = {
  context: __dirname + "/src",
  entry: "./jsx/main.jsx",
  output: {
    path: path.join(__dirname, 'build'),
    filename: 'js/app.js'
  },
  resolve: {
    extensions: ['', '.js', '.jsx'],
    modulesDirectories: ["src/jsx", "node_modules"]
  },
  module: {
    loaders: [
      {
        exclude: /node_modules/,
        test: /\.jsx?$/,
        loader: "babel-loader",
        query: {
          cacheDirectory: true,
          presets: ['es2015', 'react']
        }
      },
      {
        test: /\.jpe?g$|\.gif$|\.png$|\.svg$|\.woff$|\.ttf$|\.wav$|\.mp3$/,
        loader: "file"
      },
      {
        test: /\.html$/,
        loader: "file?name=[name].[ext]",
      },
      {
        test: /\.sass$/,
        loaders: ["style", "css", "sass?indentedSyntax"]
      }
    ]
  },
  // context: path.join(__dirname, 'build'),
  plugins: [
    new webpack.EnvironmentPlugin('NODE_ENV')
  ]
};

Optimize production builds by passing in NODE_ENV


---
library: jquery-2.1.3.min.js
---
// If using a JS framework, include it here
{% include_relative _lib/{{page.library}} %}

// Application code goes here

---
library: jquery-2.1.3.min.js
---
// If using a JS framework, include it here
// {% include_relative _lib/{{page.library}} %}

// Application code goes here

// Vanilla JS equivalent of $(document).ready
document.addEventListener('DOMContentLoaded', function(){

});

Stop loading jQuery – can we get by without it?


/**
@module ember
@submodule ember-views
*/

// BEGIN IMPORTS
import Ember from 'ember-runtime';
import jQuery from 'ember-views/system/jquery';
import {
  isSimpleClick,
  getViewClientRects,
  getViewBoundingClientRect
} from 'ember-views/system/utils';
import 'ember-views/system/ext';  // for the side effect of extending Ember.run.queues

import { Renderer } from 'ember-htmlbars/renderer';
import Component from 'ember-htmlbars/component';

import EventDispatcher from 'ember-views/system/event_dispatcher';
import ViewTargetActionSupport from 'ember-views/mixins/view_target_action_support';
import ComponentLookup from 'ember-views/component_lookup';
import Checkbox from 'ember-htmlbars/components/checkbox';
import TextSupport from 'ember-views/mixins/text_support';
import TextField from 'ember-htmlbars/components/text_field';
import TextArea from 'ember-htmlbars/components/text_area';

// END IMPORTS

/**
  Alias for jQuery

  @method $
  @for Ember
 @public
*/

// BEGIN EXPORTS
Ember.$ = jQuery;

Ember.ViewTargetActionSupport = ViewTargetActionSupport;

const ViewUtils = Ember.ViewUtils = {};
ViewUtils.isSimpleClick = isSimpleClick;
ViewUtils.getViewClientRects = getViewClientRects;
ViewUtils.getViewBoundingClientRect = getViewBoundingClientRect;

Ember._Renderer = Renderer;

Ember.Checkbox = Checkbox;
Ember.TextField = TextField;
Ember.TextArea = TextArea;

Ember.TextSupport = TextSupport;
Ember.ComponentLookup = ComponentLookup;
Ember.Component = Component;
Ember.EventDispatcher = EventDispatcher;

// END EXPORTS

export default Ember;

/**
@module ember
@submodule ember-views
*/

// BEGIN IMPORTS
import Ember from 'ember-runtime';
import jQuery from 'ember-views/system/jquery';
import {
  isSimpleClick,
  getViewClientRects,
  getViewBoundingClientRect
} from 'ember-views/system/utils';
import 'ember-views/system/ext';  // for the side effect of extending Ember.run.queues

import EventDispatcher from 'ember-views/system/event_dispatcher';
import ViewTargetActionSupport from 'ember-views/mixins/view_target_action_support';
import ComponentLookup from 'ember-views/component_lookup';
import TextSupport from 'ember-views/mixins/text_support';

// END IMPORTS

/**
  Alias for jQuery

  @method $
  @for Ember
 @public
*/

// BEGIN EXPORTS
Ember.$ = jQuery;

Ember.ViewTargetActionSupport = ViewTargetActionSupport;

const ViewUtils = Ember.ViewUtils = {};
ViewUtils.isSimpleClick = isSimpleClick;
ViewUtils.getViewClientRects = getViewClientRects;
ViewUtils.getViewBoundingClientRect = getViewBoundingClientRect;

Ember.TextSupport = TextSupport;
Ember.ComponentLookup = ComponentLookup;
Ember.EventDispatcher = EventDispatcher;

// END EXPORTS

export default Ember;

Remove engine specific global exports from ember-views.


/* global Game */
(function () {
  'use strict';

  Game.init();

  var then = Date.now();
  setInterval(function() {
    var now = Date.now(),
        delta = now - then;

    Game.update(delta / 1000);
    Game.render();

    then = now;
  }, 1000 / 60);
}());

/* global Game */
window.requestAnimFrame = (function () {
  'use strict';
  return window.requestAnimationFrame       ||
         window.webkitRequestAnimationFrame ||
         window.mozRequestAnimationFrame    ||
         function (callback) {
           window.setTimeout(callback, 1000 / 60);
         };
})();

(function () {
  'use strict';

  Game.init();

  var before = Date.now();
  window.requestAnimFrame(function update() {
    var now = Date.now(),
        delta = now - before;

    Game.update(delta / 1000);
    Game.render();

    before = now;
    window.requestAnimFrame(update);
  });
}());

Use requestAnimationFrame instead of setInterval


/*
 *
 * LanguageToggle
 *
 */

import React from 'react';
import { connect } from 'react-redux';
import { selectLocale } from '../LanguageProvider/selectors';
import { changeLocale } from '../LanguageProvider/actions';
import { languages } from '../../i18n';
import { createSelector } from 'reselect';
import styles from './styles.scss';
import Toggle from 'components/Toggle';

export class LocaleToggle extends React.Component { // eslint-disable-line
  render() {
    const messages = {};
    languages.forEach(locale => { messages[locale] = locale.toUpperCase(); });

    return (
      <div className={styles.localeToggle}>
        <Toggle values={languages} messages={messages} onToggle={this.props.onLocaleToggle} />
      </div>
    );
  }
}

LocaleToggle.propTypes = {
  onLocaleToggle: React.PropTypes.func,
};

const mapStateToProps = createSelector(
  selectLocale(),
  (locale) => ({ locale })
);

export function mapDispatchToProps(dispatch) {
  return {
    onLocaleToggle: (evt) => dispatch(changeLocale(evt.target.value)),
    dispatch,
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(LocaleToggle);

/*
 *
 * LanguageToggle
 *
 */

import React from 'react';
import { connect } from 'react-redux';
import { selectLocale } from '../LanguageProvider/selectors';
import { changeLocale } from '../LanguageProvider/actions';
import { languages } from '../../i18n';
import { createSelector } from 'reselect';
import styles from './styles.scss';
import Toggle from 'components/Toggle';

export class LocaleToggle extends React.Component { // eslint-disable-line
  render() {
    const messages = languages.reduce((result, locale) => {
      const resultsObj = result;
      resultsObj[locale] = locale.toUpperCase();
      return resultsObj;
    }, {});

    return (
      <div className={styles.localeToggle}>
        <Toggle values={languages} messages={messages} onToggle={this.props.onLocaleToggle} />
      </div>
    );
  }
}

LocaleToggle.propTypes = {
  onLocaleToggle: React.PropTypes.func,
};

const mapStateToProps = createSelector(
  selectLocale(),
  (locale) => ({ locale })
);

export function mapDispatchToProps(dispatch) {
  return {
    onLocaleToggle: (evt) => dispatch(changeLocale(evt.target.value)),
    dispatch,
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(LocaleToggle);

Improve LocaleToggle messages definition syntax


'use strict';
var util = require('util');
var yeoman = require('yeoman-generator');
var fs = require('fs');

var ComponentGenerator = module.exports = function ComponentGenerator(args, options, config) {
  // By calling `NamedBase` here, we get the argument to the subgenerator call
  // as `this.name`.
  yeoman.generators.NamedBase.apply(this, arguments);
};

util.inherits(ComponentGenerator, yeoman.generators.NamedBase);

ComponentGenerator.prototype.askFor = function askFor() {

  var cb = this.async();

  var prompts = [{
    type: 'input',
    name: 'description',
    message: 'Please provide a description for your component'
  }];

  this.prompt(prompts, function (props) {
    this.description = props.description;
    this.componentName = this.name.toLowerCase().replace(/\s/g, '-');
    cb();
  }.bind(this));
};

ComponentGenerator.prototype.create = function create() {

  this.write('_' + this.componentName + '.sass', [
    '/**',
    ' * COMPONENTS',
    ' * ' + this.name,
    ' * ' + this.description,
    ' */'
  ].join('\n'));

};

ComponentGenerator.prototype.updateComponents = function updateComponents() {

  var componentsPartialPath = '../_components.sass';

  fs.appendFile(componentsPartialPath, '\n@import components/' + this.componentName, function (err) {
    if (err) {
      throw err;
    }
    console.log('Components updated');
  });

};

'use strict';
var util = require('util');
var yeoman = require('yeoman-generator');
var fs = require('fs');

var ComponentGenerator = module.exports = function ComponentGenerator(args, options, config) {
  // By calling `NamedBase` here, we get the argument to the subgenerator call
  // as `this.name`.
  yeoman.generators.NamedBase.apply(this, arguments);
};

util.inherits(ComponentGenerator, yeoman.generators.NamedBase);

ComponentGenerator.prototype.askFor = function askFor() {

  var cb = this.async();

  var prompts = [{
    type: 'input',
    name: 'description',
    message: 'Please provide a description for your component'
  }];

  this.prompt(prompts, function (props) {
    this.description = props.description;
    this.componentName = this.name.toLowerCase().replace(/\s/g, '-');
    cb();
  }.bind(this));
};

ComponentGenerator.prototype.create = function create() {

  this.write('_' + this.componentName + '.sass', [
    '/**',
    ' * COMPONENTS',
    ' * ' + this.name,
    ' * ' + this.description,
    ' */',
    '',
    '$name: ' + this.name.toLowerCase().replace(/\s/g, '-')
  ].join('\n'));

};

ComponentGenerator.prototype.updateComponents = function updateComponents() {

  var componentsPartialPath = '../_components.sass';

  fs.appendFile(componentsPartialPath, '\n@import components/' + this.componentName, function (err) {
    if (err) {
      throw err;
    }
    console.log('Components updated');
  });

};

Add component name variable declaration to generated components


var fs = require( "fs" );

module.exports = function( grunt ) {
	grunt.registerTask( "qunit_fixture", function() {
		var dest = "./test/data/qunit-fixture.js";
		fs.writeFileSync(
			dest,
			"// Generated by build/tasks/qunit_fixture.js\n" +
			"QUnit.config.fixture = " +
			JSON.stringify(
				fs.readFileSync(
					"./test/data/qunit-fixture.html",
					"utf8"
				).toString()
			) +
			";\n" +
			"// Compat with QUnit 1.x:\n" +
			"document.getElementById( \"qunit-fixture\" ).innerHTML = QUnit.config.fixture;\n"
		);
		grunt.log.ok( "Updated " + dest + "." );
	} );
};

var fs = require( "fs" );

module.exports = function( grunt ) {
	grunt.registerTask( "qunit_fixture", function() {
		var dest = "./test/data/qunit-fixture.js";
		fs.writeFileSync(
			dest,
			"// Generated by build/tasks/qunit_fixture.js\n" +
			"QUnit.config.fixture = " +
			JSON.stringify(
				fs.readFileSync(
					"./test/data/qunit-fixture.html",
					"utf8"
				).toString().replace( /\r\n/g, "\n" )
			) +
			";\n" +
			"// Compat with QUnit 1.x:\n" +
			"document.getElementById( \"qunit-fixture\" ).innerHTML = QUnit.config.fixture;\n"
		);
		grunt.log.ok( "Updated " + dest + "." );
	} );
};

Remove CRLF line endings to fix builds on Windows


/* global BoundDocument:false - from dispatch:bound-document */

Configuration.subscription = Meteor.subscribe('__entity_configuration');

/**
 * Get the configuration object for an entity, extended with all inherited values.
 *
 * @param {String} type The type of entity, e.g., 'user'
 * @param {String} id The entity ID (Currently no support for ObjectId)
 * @param {Options} options Additional options
 * @param {Boolean} options.inherit Set to false to retrieve configuration for this entity without going through
 *                                  inheritance
 * @param {Boolean} options.bind Set to true to return a bound document which will automatically update the database
 *                               when modified
 * @param {Function} callback A callback function that receives error, config. If options.bind == true,
 *   non-object properties within it will set into the database using the dispatch:bound-document package
 */
Configuration.getForEntity = Meteor.wrapAsync(function(type, id, options, callback) {
  if (typeof options === 'function') {
    callback = options;
    options = null;
  }
  options = options || {};

  Meteor.call('dispatch:configuration/getForEntity', type, id, _.omit(options, 'bind'), function (error, configDoc) {
    if (error || !configDoc) {
      callback(error, configDoc);
      return;
    }

    if (options.bind === true) configDoc = new BoundDocument(Configuration.Collection, configDoc);

    callback(null, configDoc.config);
  });
});

/* global BoundDocument:false - from dispatch:bound-document */

Configuration.subscription = Meteor.subscribe('__entity_configuration');

/**
 * Get the configuration object for an entity, extended with all inherited values.
 *
 * @param {String} type The type of entity, e.g., 'user'
 * @param {String} id The entity ID (Currently no support for ObjectId)
 * @param {Options} options Additional options
 * @param {Boolean} options.inherit Set to false to retrieve configuration for this entity without going through
 *                                  inheritance
 * @param {Boolean} options.bind Set to true to return a bound document which will automatically update the database
 *                               when modified
 * @param {Function} callback A callback function that receives error, config. If options.bind == true,
 *   non-object properties within it will set into the database using the dispatch:bound-document package
 */
Configuration.getForEntity = Meteor.wrapAsync(function(type, id, options, callback) {
  if (typeof options === 'function') {
    callback = options;
    options = null;
  }
  options = options || {};

  Meteor.call('dispatch:configuration/getForEntity', type, id, _.omit(options, 'bind'), function (error, configDoc) {
    if (error || !configDoc) {
      if(typeof callback === 'function') callback(error, configDoc);
      return;
    }

    if (options.bind === true) configDoc = new BoundDocument(Configuration.Collection, configDoc);

    if(typeof callback === 'function') callback(null, configDoc.config);

  });
});

Support no callback in getForEntity