module.exports = {
  props: {
    chartData: {
      required: true
    }
  },

  watch: {
    'chartData': {
      handler (newData, oldData) {
        if (oldData) {
          let chart = this._chart

          // Get new and old DataSet Labels
          let newDatasetLabels = newData.datasets.map((dataset) => {
            return dataset.label
          })

          let oldDatasetLabels = oldData.datasets.map((dataset) => {
            return dataset.label
          })

          // Stringify 'em for easier compare
          const oldLabels = JSON.stringify(oldDatasetLabels)
          const newLabels = JSON.stringify(newDatasetLabels)

          // Check if Labels are equal and if dataset length is equal
          if (newLabels === oldLabels && oldData.datasets.length === newData.datasets.length) {
            newData.datasets.forEach((dataset, i) => {
              chart.data.datasets[i] = dataset
            })

            chart.data.labels = newData.labels
            chart.update()
          } else {
            chart.destroy()
            this.renderChart(this.chartData, this.options)
          }
        }
      }
    }
  }
}

module.exports = {
  props: {
    chartData: {
      required: true
    }
  },

  watch: {
    'chartData': {
      handler (newData, oldData) {
        if (oldData) {
          let chart = this._chart

          // Get new and old DataSet Labels
          let newDatasetLabels = newData.datasets.map((dataset) => {
            return dataset.label
          })

          let oldDatasetLabels = oldData.datasets.map((dataset) => {
            return dataset.label
          })

          // Stringify 'em for easier compare
          const oldLabels = JSON.stringify(oldDatasetLabels)
          const newLabels = JSON.stringify(newDatasetLabels)

          // Check if Labels are equal and if dataset length is equal
          if (newLabels === oldLabels && oldData.datasets.length === newData.datasets.length) {
            newData.datasets.forEach((dataset, i) => {
              // Get new and old dataset keys
              const oldDatasetKeys = Object.keys(oldData.datasets[i])
              const newDatasetKeys = Object.keys(dataset)

              // Get keys that aren't present in the new data
              const deletionKeys = oldDatasetKeys.filter((key) => {
                return key !== '_meta' && newDatasetKeys.indexOf(key) === -1
              })

              // Remove outdated key-value pairs
              deletionKeys.forEach((deletionKey) => {
                delete chart.data.datasets[i][deletionKey]
              })

              // Update attributes individually to avoid re-rendering the entire chart
              for (const attribute in dataset) {
                if (dataset.hasOwnProperty(attribute)) {
                  chart.data.datasets[i][attribute] = dataset[attribute]
                }
              }
            })

            chart.data.labels = newData.labels
            chart.update()
          } else {
            chart.destroy()
            this.renderChart(this.chartData, this.options)
          }
        }
      }
    }
  }
}

Resolve reactive props animation issue.


'use strict';

angular.module('Teem')
  .factory('NewForm', [
  '$location', '$window', '$rootScope',
  function($location, $window, $rootScope) {
    var scope,
        objectName,
        scopeFn = {
          isNew () {
            return $location.search().form === 'new';
          },
          cancelNew () {
            scope[objectName].delete();

            $location.search('form', undefined);

            $window.history.back();
          },
          confirmNew () {
            $location.search('form', undefined);

            scope.invite.selected.forEach(function(i){
              scope.project.addContributor(i);
            });

            $rootScope.$broadcast('teem.project.join');
          }
        };

    function initialize(s, o) {
      scope = s;
      objectName = o;

      Object.assign(scope, scopeFn);
    }

    return {
      initialize
    };
  }]);

'use strict';

angular.module('Teem')
  .factory('NewForm', [
  '$location', '$window', '$rootScope',
  function($location, $window, $rootScope) {
    var scope,
        objectName,
        scopeFn = {
          isNew () {
            return $location.search().form === 'new';
          },
          cancelNew () {
            scope[objectName].delete();

            $location.search('form', undefined);

            $window.history.back();
          },
          confirmNew () {
            $location.search('form', undefined);

            // TODO fix with community invite
            if (objectName === 'project') {
              scope.invite.selected.forEach(function(i){
                scope.project.addContributor(i);
              });
            }

            $rootScope.$broadcast('teem.' + objectName + '.join');
          }
        };

    function initialize(s, o) {
      scope = s;
      objectName = o;

      Object.assign(scope, scopeFn);
    }

    return {
      initialize
    };
  }]);

Fix new form confirm code


/** ngInject **/
function StationListCtrl($scope, $interval, EventBus, PvlService) {
  let vm = this;

  vm.imgUrls = {};
  vm.nowPlaying = {};
  vm.currentIndex = null;
  vm.setSelected = setSelected;

  var refreshDataBindings = $interval(null, 1000);

  function setSelected(index) {
    vm.currentIndex = index;
    EventBus.emit('pvl:stationSelect', vm.stations[index]);
  }

  PvlService.getStations('audio')
    .then(stations => vm.stations = stations);

  let nowPlayingListener = (event, data) => {
    vm.nowPlaying = data;
  };

  let unsubNowPlaying = EventBus.on('pvl:nowPlaying', nowPlayingListener);
  $scope.$on('$destroy', () => {
	$interval.cancel(refreshDataBindings);
    unsubNowPlaying();
  });
}

function StationListDirective() {
  return {
    restrict: 'E',
    templateUrl: '/stationList.html',
    scope: true,
    controller: StationListCtrl,
    controllerAs: 'stationList',
    bindToController: true
  };
}

angular
  .module('PVL')
  .directive('pvlStationList', StationListDirective);

/** ngInject **/
function StationListCtrl($scope, EventBus, PvlService) {
  let vm = this;

  vm.imgUrls = {};
  vm.nowPlaying = {};
  vm.currentIndex = null;
  vm.setSelected = setSelected;

  function setSelected(index) {
    vm.currentIndex = index;
    EventBus.emit('pvl:stationSelect', vm.stations[index]);
  }

  PvlService.getStations('audio')
    .then(stations => vm.stations = stations);

  let nowPlayingListener = (event, data) => {
    vm.nowPlaying = data;
  };

  let unsubNowPlaying = EventBus.on('pvl:nowPlaying', nowPlayingListener);
  $scope.$on('$destroy', () => {
    unsubNowPlaying();
  });
}

function StationListDirective() {
  return {
    restrict: 'E',
    templateUrl: '/stationList.html',
    scope: true,
    controller: StationListCtrl,
    controllerAs: 'stationList',
    bindToController: true
  };
}

angular
  .module('PVL')
  .directive('pvlStationList', StationListDirective);

Revert "Quick patch to constantly update currently playing song without requiring user interaction"


google.load('search', '1', {style : google.loader.themes.V2_DEFAULT});
google.setOnLoadCallback(function() {
    var customSearchOptions = {};
    var customSearchControl = new google.search.CustomSearchControl(
        '003721718359151553648:qipcq6hq6uy', customSearchOptions);
    customSearchControl.setResultSetSize(google.search.Search.FILTERED_CSE_RESULTSET);
    customSearchControl.setSearchStartingCallback(
        this,
        function(control, searcher) {
            searcher.setRestriction(
                google.search.Search.RESTRICT_EXTENDED_ARGS,
                { "filter" : "0"});
        }
    );
    var options = new google.search.DrawOptions();
    options.enableSearchResultsOnly();
    customSearchControl.draw('cse', options);
    function parseParamsFromUrl() {
        var params = {};
        var parts = window.location.search.substr(1).split('\x26');
        for (var i = 0; i < parts.length; i++) {
            var keyValuePair = parts[i].split('=');
            var key = decodeURIComponent(keyValuePair[0]);
            params[key] = keyValuePair[1] ?
                decodeURIComponent(keyValuePair[1].replace(/\+/g, ' ')) :
                keyValuePair[1];
        }
        return params;
    }

    var urlParams = parseParamsFromUrl();
    var queryParamName = "q";
    if (urlParams[queryParamName]) {
        customSearchControl.execute(urlParams[queryParamName]);
        $('#searchbox').val(urlParams[queryParamName]);
    }
}, true);

google.load('search', '1', {style : google.loader.themes.V2_DEFAULT});
google.setOnLoadCallback(function() {
    var customSearchOptions = {};
    var customSearchControl = new google.search.CustomSearchControl(
        '003721718359151553648:qipcq6hq6uy', customSearchOptions);
    customSearchControl.setResultSetSize(google.search.Search.FILTERED_CSE_RESULTSET);
    var options = new google.search.DrawOptions();
    options.enableSearchResultsOnly();
    customSearchControl.draw('cse', options);
    function parseParamsFromUrl() {
        var params = {};
        var parts = window.location.search.substr(1).split('\x26');
        for (var i = 0; i < parts.length; i++) {
            var keyValuePair = parts[i].split('=');
            var key = decodeURIComponent(keyValuePair[0]);
            params[key] = keyValuePair[1] ?
                decodeURIComponent(keyValuePair[1].replace(/\+/g, ' ')) :
                keyValuePair[1];
        }
        return params;
    }

    var urlParams = parseParamsFromUrl();
    var queryParamName = "q";
    if (urlParams[queryParamName]) {
        customSearchControl.execute(urlParams[queryParamName]);
        $('#searchbox').val(urlParams[queryParamName]);
    }
}, true);

Remove code that did not fix google search pagination issue.


var download = require('./download').download;
var path = require('path');
var fs = require('fs');

var TAR_URL = 'https://github.com/nteract/zmq-prebuilt/releases/download/win-libzmq-4.1.5-v140/libzmq-' + process.arch + '.lib';
var DIR_NAME = path.join(__dirname, '..', 'windows', 'lib');
var FILE_NAME = path.join(DIR_NAME, 'libzmq.lib');

if (!fs.existsSync(DIR_NAME)) {
  fs.mkdirSync(DIR_NAME);
}

download(TAR_URL, FILE_NAME);

var download = require('./download').download;
var path = require('path');
var fs = require('fs');

var TAR_URL = 'https://github.com/nteract/libzmq-win/releases/download/v1.0.0/libzmq-' + process.arch + '.lib';
var DIR_NAME = path.join(__dirname, '..', 'windows', 'lib');
var FILE_NAME = path.join(DIR_NAME, 'libzmq.lib');

if (!fs.existsSync(DIR_NAME)) {
  fs.mkdirSync(DIR_NAME);
}

download(TAR_URL, FILE_NAME);

Store windows lib in different repo


/* OutsideClickable only accepts one top-level child */
import React, { Component, Children, cloneElement } from "react";
import ReactDOM from "react-dom";
import PropTypes from "prop-types";

export default class OutsideClickable extends Component {
  static propTypes = {
    children: PropTypes.node,
    onOutsideClick: PropTypes.func.isRequired,
    className: PropTypes.string,
    toggle: PropTypes.func
  };

  constructor(props) {
    super(props);
  }

  componentDidMount() {
    window.addEventListener("click", this.contains);
  }

  componentWillUnmount() {
    window.removeEventListener("click", this.contains);
  }

  contains = e => {
    const node = this.outsideClickable;
    if (node && node.contains(e.target) === false) {
      this.props.onOutsideClick.call();
    }
  };

  onlyOneChild = () => {
    if (this.props.children.length > 1) {
      throw new Error("Multiple children are not allowed in OutsideClickable");
      return;
    }
    return true;
  };

  render() {
    return (
      this.onlyOneChild() &&
      Children.map(this.props.children, child =>
        cloneElement(child, {
          ref: node => {
            // Keep your own reference
            this.outsideClickable = node;
            // Call the original ref, if any
            const { ref } = child;

            if (typeof ref === "function") {
              ref(node);
            }
          }
        })
      )
    );
  }
}

/* OutsideClickable only accepts one top-level child */
import React, { Component, Children, cloneElement } from "react";
import ReactDOM from "react-dom";
import PropTypes from "prop-types";

export default class OutsideClickable extends Component {
  static propTypes = {
    children: PropTypes.node,
    onOutsideClick: PropTypes.func.isRequired,
    className: PropTypes.string,
    toggle: PropTypes.func
  };

  constructor(props) {
    super(props);
  }

  componentDidMount() {
    const event = "ontouchstart" in window ? "touchstart" : "click";
    console.log(event);
    document.body.addEventListener(event, this.contains);
  }

  componentWillUnmount() {
    const event = "ontouchstart" in window ? "touchstart" : "click";
    document.body.removeEventListener(event, this.contains);
  }

  contains = e => {
    const node = this.outsideClickable;

    if (node && node.contains(e.target) === false) {
      this.props.onOutsideClick.call();
    }
  };

  onlyOneChild = () => {
    if (this.props.children.length > 1) {
      throw new Error("Multiple children are not allowed in OutsideClickable");
    }

    return true;
  };

  render() {
    return (
      this.onlyOneChild() &&
      Children.map(this.props.children, child =>
        cloneElement(child, {
          ref: node => {
            // Keep your own reference
            this.outsideClickable = node;
            // Call the original ref, if any
            const { ref } = child;

            if (typeof ref === "function") {
              ref(node);
            }
          }
        })
      )
    );
  }
}

Fix iOS and Android touch events


/* global pref:false */
/* eslint strict: ["error", "never"] */

pref("extensions.rspamd-spamness.header", "x-spamd-result");
pref("extensions.rspamd-spamness.display.column", 0);
pref("extensions.rspamd-spamness.display.messageScore", true);
pref("extensions.rspamd-spamness.display.messageRules", true);
pref("extensions.rspamd-spamness.display.messageGreylist", true);
pref("extensions.rspamd-spamness.headers.show_n_lines_before_more", 2);
pref("extensions.rspamd-spamness.headers.symbols_order", "score");
pref("extensions.rspamd-spamness.isDefaultColumn", true);
pref("extensions.rspamd-spamness.installationGreeting", true);
pref("extensions.rspamd-spamness.trainingButtons.defaultAction", "move");

Add ESLint config to preferences


/*
 * Avoiding Object Mutations.
 *
 * @author: Francisco Maria Calisto
 *
 */

const toggleTodo = (todo) => {

    return {

        id: todo.id,
        text: todo.text,
        completed: !todo.completed

    };

};

const testToggleTodo = () => {

    const todoBefore = {

        id: 0,
        text: 'Learn Redux',
        completed: false

    };

    const todoAfter = {

        id: 0,
        text: 'Learn Redux',
        completed: true

    };

    deepFreeze(todoBefore);

    expect(
        toggleTodo(todoBefore)
    ).toEqual(todoAfter);

};

testToggleTodo();
console.log('All tests passed.');

/*
 * Avoiding Object Mutations.
 *
 * @author: Francisco Maria Calisto
 *
 */

const toggleTodo = (todo) => {

    return Object.assign({}, todo, {

        completed: !todo.completed

    });

};

const testToggleTodo = () => {

    const todoBefore = {

        id: 0,
        text: 'Learn Redux',
        completed: false

    };

    const todoAfter = {

        id: 0,
        text: 'Learn Redux',
        completed: true

    };

    deepFreeze(todoBefore);

    expect(
        toggleTodo(todoBefore)
    ).toEqual(todoAfter);

};

testToggleTodo();
console.log('All tests passed.');

Return Toggle Todo Object Assign


// @flow
import path from 'path';
import PluginRegistory from '../../src/services/plugin-registory'

describe('PluginRegistory', () => {
    it('exporting: PluginFeatures', () => {
        expect(PluginRegistory.PluginFeatures).to.not.eql(null)
        expect(PluginRegistory.PluginFeatures).to.be.an('object')
    })

    it('loading plugins', async () => {
        const r = new PluginRegistory()
        const result = await r.loadPackageDir(path.join(__dirname, '../../src/plugins'))

        expect(result).to.not.empty()
        expect(result).to.have.key('packages')
        expect(result).to.have.key('failed')

        expect(result.packages).to.be.an('object')
        expect(result.failed).to.be.an(Array)
    })
})

// @flow
import path from 'path';
import PluginRegistory from '../../src/services/plugin-registory'

describe('PluginRegistory', () => {
    it('exporting: PluginFeatures', () => {
        expect(PluginRegistory.PluginFeatures).to.not.eql(null)
        expect(PluginRegistory.PluginFeatures).to.be.an('object')
    })

    it('loading plugins', async () => {
        // mock missing method in mocha
        global.require = require

        const r = new PluginRegistory()
        const result = await r.loadPackageDir(path.join(__dirname, '../../src/plugins'))

        expect(result).to.not.empty()
        expect(result).to.have.key('packages')
        expect(result).to.have.key('failed')

        expect(result.packages).to.be.an('object')
        expect(result.failed).to.be.an(Array)

        delete global.require
    })
})

Fix PluginRegistry error (missing global.require)


const fs = require('fs');
const process = require('process');
const assert = require('assert');
const buffer = fs.readFileSync(process.argv[2]);

let m = new WebAssembly.Module(buffer);
let list = WebAssembly.Module.exports(m);
console.log('exports', list);

const my_exports = {};
let nexports = 0;

for (const entry of list) {
  if (entry.kind == 'function'){
    nexports += 1;
  }
  my_exports[entry.name] = true;
}

if (my_exports.foo === undefined)
  throw new Error("`foo` wasn't defined");

if (my_exports.FOO === undefined)
  throw new Error("`FOO` wasn't defined");

if (my_exports.main === undefined) {
  if (nexports != 1)
    throw new Error("should only have one function export");
} else {
  if (nexports != 2)
    throw new Error("should only have two function exports");
}

const fs = require('fs');
const process = require('process');
const assert = require('assert');
const buffer = fs.readFileSync(process.argv[2]);

let m = new WebAssembly.Module(buffer);
let list = WebAssembly.Module.exports(m);
console.log('exports', list);

const my_exports = {};
let nexports = 0;

for (const entry of list) {
  if (entry.kind == 'function'){
    nexports += 1;
  }
  my_exports[entry.name] = entry.kind;
}

if (my_exports.foo != "function")
  throw new Error("`foo` wasn't defined");

if (my_exports.FOO != "global")
  throw new Error("`FOO` wasn't defined");

if (my_exports.main === undefined) {
  if (nexports != 1)
    throw new Error("should only have one function export");
} else {
  if (nexports != 2)
    throw new Error("should only have two function exports");
}

Check for the entry kind


import React, { Fragment, PureComponent } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import theme from './theme.css';

class Link extends PureComponent {
  render() {
    const { children, className, icon, iconPlacement, element, inherit, ...others } = this.props;

    const classNames = cx(
      theme['link'],
      {
        [theme['inherit']]: inherit,
      },
      className,
    );

    const ChildrenWrapper = icon ? 'span' : Fragment;
    const Element = element || 'a';

    return (
      <Element className={classNames} data-teamleader-ui="link" {...others}>
        {icon && iconPlacement === 'left' && icon}
        <ChildrenWrapper>{children}</ChildrenWrapper>
        {icon && iconPlacement === 'right' && icon}
      </Element>
    );
  }
}

Link.propTypes = {
  children: PropTypes.node.isRequired,
  className: PropTypes.string,
  /** The icon displayed inside the button. */
  icon: PropTypes.element,
  /** The position of the icon inside the button. */
  iconPlacement: PropTypes.oneOf(['left', 'right']),
  inherit: PropTypes.bool,
  element: PropTypes.element,
};

Link.defaultProps = {
  className: '',
  inherit: true,
};

export default Link;

import React, { Fragment, PureComponent } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import theme from './theme.css';

class Link extends PureComponent {
  render() {
    const { children, className, icon, iconPlacement, element, inherit, ...others } = this.props;

    const classNames = cx(
      theme['link'],
      {
        [theme['inherit']]: inherit,
      },
      className,
    );

    const ChildrenWrapper = icon ? 'span' : Fragment;
    const Element = element;

    return (
      <Element className={classNames} data-teamleader-ui="link" {...others}>
        {icon && iconPlacement === 'left' && icon}
        <ChildrenWrapper>{children}</ChildrenWrapper>
        {icon && iconPlacement === 'right' && icon}
      </Element>
    );
  }
}

Link.propTypes = {
  children: PropTypes.node.isRequired,
  className: PropTypes.string,
  /** The icon displayed inside the button. */
  icon: PropTypes.element,
  /** The position of the icon inside the button. */
  iconPlacement: PropTypes.oneOf(['left', 'right']),
  inherit: PropTypes.bool,
  element: PropTypes.element,
};

Link.defaultProps = {
  className: '',
  element: 'a',
  inherit: true,
};

export default Link;

Add element to defaultProps and assign 'a' to it


Package.describe({
  name: 'svelte:core',
  version: '1.40.1_1',
  summary: 'Svelte compiler core',
  git: 'https://github.com/meteor-svelte/meteor-svelte.git'
});

Npm.depends({
  htmlparser2: '3.9.2',
  'source-map': '0.5.6',
  svelte: '1.40.1'
});

Package.onUse(function (api) {
  api.versionsFrom('1.4.2.3');

  // Dependencies for `SvelteCompiler`
  api.use([
    'caching-compiler@1.1.9',
    'babel-compiler@6.13.0',
    'ecmascript@0.6.1'
  ]);

  // Dependencies for compiled Svelte components
  api.imply([
    'modules',
    'ecmascript-runtime',
    'babel-runtime',
    'promise'
  ]);

  // Make `SvelteCompiler` available to build plugins.
  api.mainModule('svelte-compiler.js');
  api.export('SvelteCompiler', 'server');
});

Package.describe({
  name: 'svelte:core',
  version: '1.40.1_1',
  summary: 'Svelte compiler core',
  git: 'https://github.com/meteor-svelte/meteor-svelte.git'
});

Npm.depends({
  htmlparser2: '3.9.2',
  'source-map': '0.5.6',
  svelte: '1.40.1'
});

Package.onUse(function (api) {
  api.versionsFrom('1.4.2.3');

  // Dependencies for `SvelteCompiler`
  api.use([
    'caching-compiler@1.1.9',
    'babel-compiler@6.13.0||7.0.0',
    'ecmascript@0.6.1'
  ]);

  // Dependencies for compiled Svelte components
  api.imply([
    'modules',
    'ecmascript-runtime',
    'babel-runtime',
    'promise'
  ]);

  // Make `SvelteCompiler` available to build plugins.
  api.mainModule('svelte-compiler.js');
  api.export('SvelteCompiler', 'server');
});

Use `babel-compiler` 7.x for compatibility with Meteor 1.6.1


'use strict';

const path = require('path');

module.exports = (() => {
    // const akasha = require('../index');
    const Command = require('cmnd').Command;

    class CopyAssetsCommand extends Command {

        constructor() {
            super('copy-assets');
        }

        help() {
            return {
                description: 'Copy assets into output directory',
                args: [ 'config_file' ],
            };
        }

        run(args, flags, vflags, callback) {

            const config = require(path.join(__dirname, args[0]));

            config.copyAssets()
            .then(() => { callback(null); })
            .catch(err => { callback(err); });

        }
    }

    return CopyAssetsCommand;

})();

'use strict';

const path = require('path');

module.exports = (() => {
    // const akasha = require('../index');
    const Command = require('cmnd').Command;

    class CopyAssetsCommand extends Command {

        constructor() {
            super('copy-assets');
        }

        help() {
            return {
                description: 'Copy assets into output directory',
                args: [ 'config_file' ],
            };
        }

        run(args, flags, vflags, callback) {

            if (!args[0]) callback(new Error("copy-assets: no config file name given"));

            const config = require(path.join(__dirname, args[0]));

            config.copyAssets()
            .then(() => { callback(null); })
            .catch(err => { callback(err); });

        }
    }

    return CopyAssetsCommand;

})();

Check for missing config file name


var testCase = require('nodeunit').testCase,
	parser = require('../language/parser'),
	tokenizer = require('../language/tokenizer')

test('TextLiteral', '"hello world"', static("hello world"))
test('NumberLiteral', '1', static(1))
test('Declaration', 'let greeting = "hello"', {
	type:'DECLARATION',
	name:'greeting',
	value: static("hello")
})

/* UTIL */

function test(name, code, expectedAST) {
	module.exports['test '+name] = function(assert) {
		var ast = parse(code)
		assert.deepEqual(ast, expectedAST)
		assert.done()
	}
}

function static(value) {
	var ast = { type:'STATIC', value:value }
	ast.valueType = typeof value
	return ast
}

function parse(code) {
	tokens = tokenizer.tokenize(code)
	return pruneInfo(parser.parse(tokens))
}

function pruneInfo(ast) {
	for (var key in ast) {
		if (key == 'info') { delete ast[key] }
		else if (typeof ast[key] == 'object') { pruneInfo(ast[key]) }
	}
	return ast
}

var testCase = require('nodeunit').testCase,
	parser = require('../language/parser'),
	tokenizer = require('../language/tokenizer')

/* TESTS */
test('text literal', '"hello world"', static("hello world"))
test('number literal', '1', static(1))
test('declaration', 'let greeting = "hello"', { type:'DECLARATION', name:'greeting', value: static("hello") })

/* UTIL */

function test(name, code, expectedAST) {
	module.exports['test '+name] = function(assert) {
		var ast = parse(code)
		assert.deepEqual(ast, expectedAST)
		assert.done()
	}
}

function static(value) {
	var ast = { type:'STATIC', value:value }
	ast.valueType = typeof value
	return ast
}

function parse(code) {
	tokens = tokenizer.tokenize(code)
	return pruneInfo(parser.parse(tokens))
}

function pruneInfo(ast) {
	for (var key in ast) {
		if (key == 'info') { delete ast[key] }
		else if (typeof ast[key] == 'object') { pruneInfo(ast[key]) }
	}
	return ast
}

Make the tests more reader friendly


// Without these options Less will inject a `body { display: none !important; }`
var less = window.less || (window.less = {});
less.async = true;

module.exports = require("less/dist/less");

Make less use async mode in the browser


import test from 'ava';
import plugin from '../lib/forms';

const formResponse = {
  body: {
    'service-name': 'Foo Service One',
    'service-desciption': 'Foo service lorem ipsum description',
    'service-url': 'foo@bar.com',
    'id': '07b45c32-001d-4c46-a785-0025b04f7f37',
    'created': '2016-05-03 20:25:14',
  },
};

test('Parse data from a form', t => {
  t.pass();
});

test('Retrieve data from a form', t => {
  const data = plugin.getFormData(formResponse);

  t.is(data['service-name'], 'Foo Service One', 'should return name from response');
  t.is(data['service-desciption'], 'Foo service lorem ipsum description', 'should return desc from response');
  t.is(data['service-url'], 'foo@bar.com', 'should return url from response');
  t.is(data.id, '07b45c32-001d-4c46-a785-0025b04f7f37', 'should return id from response');
  t.ok(data.created !== formResponse.body.created, '`created` datetime should be re-created by function');
  t.ok(data.created > formResponse.body.created, 'new `created` datetime variable will be more recent');
});

import test from 'ava';
import plugin from '../lib/forms';

const formResponse = {
  body: {
    'service-name': 'Foo Service One',
    'service-desciption': 'Foo service lorem ipsum description',
    'service-url': 'foo@bar.com',
    'id': '07b45c32-001d-4c46-a785-0025b04f7f37',
    'created': '2016-05-03 20:25:14',
  },
};

test('Parse data from a form', t => {
  t.pass();
});

test('Retrieve data from a form', t => {
  const data = plugin.getFormData(formResponse);

  t.is(data['service-name'], 'Foo Service One', 'should return name from response');
  t.is(data['service-desciption'], 'Foo service lorem ipsum description', 'should return desc from response');
  t.is(data['service-url'], 'foo@bar.com', 'should return url from response');
  t.is(data.id, '07b45c32-001d-4c46-a785-0025b04f7f37', 'should return id from response');
  t.true(data.created !== formResponse.body.created, '`created` datetime should be re-created by function');
  t.true(data.created > formResponse.body.created, 'new `created` datetime variable will be more recent');
});

Clean up old test assertions


var path = require('path');
var fs = require('fs');
var _ = require('underscore');
var debug = require('debug')('carcass:Index');

module.exports = function(obj) {

    // Register every file in a dir plus a namespace.
    obj.register = function(dir, namespace) {
        namespace || (namespace = 'plugins');
        // TODO: validate namespace.
        obj[namespace] || (obj[namespace] = {});

        // .
        dir = path.join(dir, namespace);
        fs.readdirSync(dir).forEach(function(filename) {
            if (!/\.js$/.test(filename)) return;
            var name = path.basename(filename, '.js');
            function load() {
                var component = require(path.join(dir, name));
                component.title || (component.title = name);
                return component;
            }
            obj[namespace].__defineGetter__(name, load);
        });
    };

    // A method that can make something ...
    // TODO: rename to plugin, and build a real mixin.
    obj.mixable = function(target) {
        target.plugin = function(namespace, title, options) {
            var factory = obj[namespace][title];
            if (factory) {
                factory(options)(this);
            }
            return this;
        };
        _.bindAll(target, 'plugin');
    };

};

var path = require('path');
var fs = require('fs');
var _ = require('underscore');
var debug = require('debug')('carcass:Index');

var descriptor = Object.getOwnPropertyDescriptor;
var properties = Object.getOwnPropertyNames;
var defineProp = Object.defineProperty;

module.exports = function(obj) {

    // Register every file in a dir plus a namespace.
    obj.register = function(dir, namespace) {
        namespace || (namespace = 'plugins');
        // TODO: validate namespace.
        obj[namespace] || (obj[namespace] = {});

        // .
        dir = path.join(dir, namespace);
        fs.readdirSync(dir).forEach(function(filename) {
            if (!/\.js$/.test(filename)) return;
            var name = path.basename(filename, '.js');
            function load() {
                var component = require(path.join(dir, name));
                component.title || (component.title = name);
                return component;
            }
            obj[namespace].__defineGetter__(name, load);
        });
    };

    // Add some helpers to a target object.
    obj.mixable = function(target) {
        if (!_.isObject(target)) return;
        // I organize my mixins into plugins. Each a plugin is a factory
        // function. Once invoked, the factory will return the mixin function,
        // and the mixin function can be used to modify an object directly.
        target.plugin = function(namespace, title, options) {
            var factory = obj[namespace][title];
            if (factory) {
                factory(options)(this);
            }
            return this;
        };
        // The common mixin, simply merge properties.
        target.mixin = function(source) {
            var self = this;
            properties(source).forEach(function(key) {
                defineProp(self, key, descriptor(source, key));
            });
            return this;
        };
        _.bindAll(target, 'plugin', 'mixin');
    };

};

Build a common mixin method.


/*
 * literal-test.js: Tests for the nconf literal store.
 *
 * (C) 2011, Charlie Robbins
 *
 */

var vows = require('vows'),
    assert = require('assert'),
    helpers = require('../helpers'),
    nconf = require('../../lib/nconf');

vows.describe('nconf/stores/literal').addBatch({
  "An instance of nconf.Literal": {
    topic: new nconf.Literal({
      store: {
        foo: 'bar',
        one: 2
      }
    }),
    "should have the correct methods defined": function (literal) {
      assert.equal(literal.type, 'literal');
      assert.isFunction(literal.get);
      assert.isFunction(literal.set);
      assert.isFunction(literal.merge);
      assert.isFunction(literal.loadSync);
    },
    "should have the correct values in the store": function (literal) {
      assert.equal(literal.store.foo, 'bar');
      assert.equal(literal.store.one, 2);
    }
  }
}).export(module);

/*
 * literal-test.js: Tests for the nconf literal store.
 *
 * (C) 2011, Charlie Robbins
 *
 */

var vows = require('vows'),
    assert = require('assert'),
    helpers = require('../helpers'),
    nconf = require('../../lib/nconf');

vows.describe('nconf/stores/literal').addBatch({
  "An instance of nconf.Literal": {
    topic: new nconf.Literal({
      foo: 'bar',
      one: 2
    }),
    "should have the correct methods defined": function (literal) {
      assert.equal(literal.type, 'literal');
      assert.isFunction(literal.get);
      assert.isFunction(literal.set);
      assert.isFunction(literal.merge);
      assert.isFunction(literal.loadSync);
    },
    "should have the correct values in the store": function (literal) {
      assert.equal(literal.store.foo, 'bar');
      assert.equal(literal.store.one, 2);
    }
  }
}).export(module);

Update tests to use optional options API


import SocketIO from 'socket.io';
import { createClient } from 'redis';

import { redisUrl } from './config';

const io = SocketIO();

io.use((socket, next) => {
  return next();
});

io.of('/live-chatroom')
  .use((socket, next) => {
    return next();
  })
  .on('connection', (socket) => {
    const redis = createClient(redisUrl);
    let currentRoom = '';

    socket.on('subscribe', (id) => {
      currentRoom = `live:${id}:comments`;
      socket.join(currentRoom);

      io.of('/live-chatroom').in(currentRoom).clients((err, clients) => {
        if (clients.length === 1) {
          redis.subscribe(`${currentRoom}:latest`);
        }
      });

      socket.emit('subscribed');
    });

    redis.on('message', (channel, message) => {
      io.of('/live-chatroom').in(channel.replace(':latest', '')).emit('comment', message);
    });

    socket.on('unsubscribe', (id) => {
      socket.leave(id);

      socket.emit('unsubscribed');
    });

    socket.on('disconnect', () => {
      io.of('/live-chatroom').in(currentRoom).clients((err, clients) => {
        if (clients.length === 0) {
          const redis = createClient(redisUrl);
          redis.unsubscribe(`${currentRoom}:latest`);
          redis.quit();
        }
      });

      redis.quit();
    })
  });

export default io;

import SocketIO from 'socket.io';
import { createClient } from 'redis';

import { redisUrl } from './config';

const io = SocketIO();

io.use((socket, next) => {
  return next();
});

io.of('/live-chatroom')
  .use((socket, next) => {
    return next();
  })
  .on('connection', (socket) => {
    const redis = createClient(redisUrl);
    let currentRoom = '';

    socket.on('subscribe', (id) => {
      currentRoom = `live:${id}:comments`;
      socket.join(currentRoom);

      redis.subscribe(`${currentRoom}:latest`);

      socket.emit('subscribed');
    });

    redis.on('message', (channel, message) => {
      socket.emit('comment', message);
    });

    socket.on('unsubscribe', (id) => {
      socket.leave(id);
      socket.emit('unsubscribed');
    });

    socket.on('disconnect', () => {
      redis.unsubscribe(`${currentRoom}:latest`);
      redis.quit();
    })
  });

export default io;

Fix bug: if the reds subscriber quit, members who still in the room cannot receive the messages.


export async function onDocumentReady() {

  //[START rtdb_emulator_connect]
  let firebaseConfig = {
    // Point to the RTDB emulator running on localhost.
    // Here we supply database namespace 'foo'.
    databaseURL: "http://localhost:9000?ns=foo"
  }

  var myApp = firebase.initializeApp(firebaseConfig);
  const db = myApp.database();
  // [END rtdb_emulator_connect]
}

export async function flushRealtimeDatabase(anRTDBReference) {

  //[START rtdb_emulator_flush]
  anRTDBReference.child("/").set(null);
  // [END rtdb_emulator_connect]
}

Add RTDB flush method using platform SDK methods.


var fs    = require('fs');
var path  = require('path');
var cjson = require('cjson');
var _     = require('lodash');

var data = null;

var OPTS_FILE = 'supersamples.opts';

var DEFAULTS = {
  output: './tmp',
  renderer: {
    name: 'html',
    options: {
      title: 'API Documentation',
      baseUrl: 'http://localhost',
      intro:  null,
      styles: []
    }
  }
};

exports.read = function() {
  if (fs.exists(OPTS_FILE) === false) {
    return DEFAULTS;
  }
  try {
    var opts = cjson.load(OPTS_FILE);
    return _.defaults(opts, DEFAULTS);
  } catch (ex) {
    console.error('Invalid config file: ' + path.resolve('supersamples.opts'));
    console.error(ex);
    process.exit(1);
  }
};

exports.get = function() {
  if (!data) data = exports.read();
  return data;
};

var fs    = require('fs');
var path  = require('path');
var cjson = require('cjson');
var _     = require('lodash');

var data = null;

var OPTS_FILE = 'supersamples.opts';

var DEFAULTS = {
  output: './tmp',
  renderer: {
    name: 'html',
    options: {
      title: 'API Documentation',
      baseUrl: 'http://localhost',
      intro:  null,
      styles: []
    }
  }
};

exports.read = function() {
  if (fs.existsSync(OPTS_FILE) === false) {
    return DEFAULTS;
  }
  try {
    var opts = cjson.load(OPTS_FILE);
    return _.defaults(opts, DEFAULTS);
  } catch (ex) {
    console.error('Invalid config file: ' + path.resolve('supersamples.opts'));
    console.error(ex);
    process.exit(1);
  }
};

exports.get = function() {
  if (!data) data = exports.read();
  return data;
};

Fix bug where supersamples would not run without a supersamples.opt file


// @flow
import getWindow from './getWindow';

/*:: declare function isElement(node: mixed): boolean %checks(node instanceof
  Element); */

function isElement(node) {
  const OwnElement = getWindow(node).Element;
  return node instanceof OwnElement;
}

/*:: declare function isHTMLElement(node: mixed): boolean %checks(node instanceof
  HTMLElement); */

function isHTMLElement(node) {
  const OwnElement = getWindow(node).HTMLElement;
  return node instanceof OwnElement;
}

export { isElement, isHTMLElement };

// @flow
import getWindow from './getWindow';

/*:: declare function isElement(node: mixed): boolean %checks(node instanceof
  Element); */

function isElement(node) {
  const OwnElement = getWindow(node).Element;
  return node instanceof OwnElement || node instanceof Element;
}

/*:: declare function isHTMLElement(node: mixed): boolean %checks(node instanceof
  HTMLElement); */

function isHTMLElement(node) {
  const OwnElement = getWindow(node).HTMLElement;
  return node instanceof OwnElement || node instanceof HTMLElement;
}

export { isElement, isHTMLElement };

Fix error in some situations with iframes


// app.menuCategoriesController.js

(function() {
  "use strict";

  angular.module("MenuCategoriesApp")
    .controller("MenuCategoriesController", MenuCategoriesController);

  MenuCategoriesController.$inject = ["MenuCategoriesService"];

  function MenuCategoriesController(MenuCategoriesService) {
    let vm = this;

    let promise = MenuCategoriesService.getMenuCategories();
  }
})();

// app.menuCategoriesController.js

(function() {
  "use strict";

  angular.module("MenuCategoriesApp")
    .controller("MenuCategoriesController", MenuCategoriesController);

  MenuCategoriesController.$inject = ["GitHubDataService"];

  function MenuCategoriesController(GitHubDataService) {
    let vm = this;

    vm.getRepos = getRepos;

    function getRepos(userName) {
      vm.userName = GitHubDataService.getUserName(userName);

      if (!vm.userName) {
        return;
      }

      let promise = GitHubDataService.getRepos(vm.userName);

      promise
        .then((response) => {
          vm.categories = response.data;

          console.log("response.data: ", response.data);
        })
        .catch((error) => {
          console.error("Somethig went terrible wrong", error);
        });
    }
  }
})();

Use new service. Implement getRepos


'use strict';
module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface.createTable('DiscordInteractions', {
      id: {
        primaryKey: true,
        type: Sequelize.INTEGER,
        autoIncrement: true,
        allowNull: false,
      },
      command: {
        type: Sequelize.STRING,
      },
      alias: {
        type: Sequelize.STRING,
      },
      properties: {
        type: Sequelize.JSONB,
      },
      createdAt: {
        type: Sequelize.DATE,
        allowNull: false,
      },
      updatedAt: {
        type: Sequelize.DATE,
        allowNull: false,
      },
      UserId: {
        type: Sequelize.INTEGER,
        allowNull: false,
        references: {
          model: 'DiscordUsers',
          key: 'id',
        },
        onUpdate: 'no action',
        onDelete: 'no action',
      },
      ChannelId: {
        type: Sequelize.INTEGER,
        allowNull: true,
        references: {
          model: 'DiscordChannels',
          key: 'id',
        },
        onUpdate: 'no action',
        onDelete: 'no action',
      },
      GuildId: {
        type: Sequelize.INTEGER,
        allowNull: false,
        references: {
          model: 'DiscordGuilds',
          key: 'id',
        },
        onUpdate: 'no action',
        onDelete: 'no action',
      },
    });
  },
  down: (queryInterface, Sequelize) => {
    return queryInterface.dropTable('DiscordInteractions');
  },
};

'use strict';
module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface.createTable('DiscordInteractions', {
      id: {
        primaryKey: true,
        type: Sequelize.INTEGER,
        autoIncrement: true,
        allowNull: false,
      },
      command: {
        type: Sequelize.STRING,
      },
      alias: {
        type: Sequelize.STRING,
      },
      properties: {
        type: Sequelize.JSONB,
      },
      createdAt: {
        type: Sequelize.DATE,
        allowNull: false,
      },
      updatedAt: {
        type: Sequelize.DATE,
        allowNull: false,
      },
      UserId: {
        type: Sequelize.INTEGER,
        allowNull: false,
        references: {
          model: 'DiscordUsers',
          key: 'id',
        },
        onUpdate: 'no action',
        onDelete: 'no action',
      },
      ChannelId: {
        type: Sequelize.INTEGER,
        allowNull: false,
        references: {
          model: 'DiscordChannels',
          key: 'id',
        },
        onUpdate: 'no action',
        onDelete: 'no action',
      },
      GuildId: {
        type: Sequelize.INTEGER,
        allowNull: true,
        references: {
          model: 'DiscordGuilds',
          key: 'id',
        },
        onUpdate: 'no action',
        onDelete: 'no action',
      },
    });
  },
  down: (queryInterface, Sequelize) => {
    return queryInterface.dropTable('DiscordInteractions');
  },
};

Fix FK nullability on interaction table


function hallwayType1(length) {
  var space = new THREE.Group();
  space.add(makeArrowHelper(0,0,0,0,1,0));
  // Lighting
  for (var i = -length/2; i <= length/2; i+=10) {
    space.add(makePointLight(i,0,2.5, space));
  }
  // Floor
  space.add(makePlane(5  , length, 0, 0   , 0, null, null, Math.PI/2, floorMat));
  // Left Wall
  space.add(makePlane(2.5, length, 0,  2.5, 1.25, Math.PI/2, null, Math.PI/2, paintMat));
  // Right Wall
  space.add(makePlane(2.5, length, 0, -2.5, 1.25, -Math.PI/2, null, Math.PI/2, paintMat));
  return space;
}

function hallwayType1(length) {
  var space = new THREE.Group();
  space.add(makeArrowHelper(0,0,0,0,1,0));
  // Lighting
  for (var i = -length/2; i <= length/2; i+=10) {
    space.add(makePointLight(i,0,2.5, space));
  }
  // Floor
  space.add(makePlane(5  , length, 0, 0   , 0, null, null, Math.PI/2, plainLightGray));
  // Left Wall
  space.add(makePlane(2.5, length, 0,  2.5, 1.25, Math.PI/2, null, Math.PI/2, plainDarkGray));
  // Right Wall
  space.add(makePlane(2.5, length, 0, -2.5, 1.25, -Math.PI/2, null, Math.PI/2, plainDarkGray));
  return space;
}

Make things gray and black so it doesn't kill my browser


'use strict';

var FusebillResource = require('../FusebillResource');
var utils = require('../utils');
var fusebillMethod = FusebillResource.method;

module.exports = FusebillResource.extend({

  path: 'customers',
  includeBasic: [
    'create', 'list', 'retrieve', 'update', 'del',
  ],

  /**
   * Customer: Subscription methods
   */

  listSubscriptions: fusebillMethod({
    method: 'GET',
    path: '/{customerId}/subscriptions',
    urlParams: ['customerId'],
  }),

  /**
   * Customer: Email Preference methods
   */

  listEmailPreferences: fusebillMethod({
    method: 'GET',
    path: '/{customerId}/CustomerEmailPreferences',
    urlParams: ['customerId'],
  }),

  /**
   * Customer: Invoice methods
   */

  listInvoices: fusebillMethod({
    method: 'GET',
    path: '/{customerId}/invoices',
    urlParams: ['customerId'],
  }),
  listDraftInvoices: fusebillMethod({
    method: 'GET',
    path: '/{customerId}/DraftInvoices',
    urlParams: ['customerId'],
  }),

});

'use strict';

var FusebillResource = require('../FusebillResource');
var utils = require('../utils');
var fusebillMethod = FusebillResource.method;

module.exports = FusebillResource.extend({

  path: 'customers',
  includeBasic: [
    'create', 'list', 'retrieve', 'update', 'del',
  ],

  /**
   * Customer: Subscription methods
   */

  listSubscriptions: fusebillMethod({
    method: 'GET',
    path: '/{customerId}/subscriptions',
    urlParams: ['customerId'],
  }),

  retrieveSubscription: fusebillMethod({
    method: 'GET',
    path: '/{customerId}/subscriptions/{subscriptionId}',
    urlParams: ['customerId', 'subscriptionId'],
  }),

  /**
   * Customer: Email Preference methods
   */

  listEmailPreferences: fusebillMethod({
    method: 'GET',
    path: '/{customerId}/CustomerEmailPreferences',
    urlParams: ['customerId'],
  }),

  /**
   * Customer: Invoice methods
   */

  listInvoices: fusebillMethod({
    method: 'GET',
    path: '/{customerId}/invoices',
    urlParams: ['customerId'],
  }),

  listDraftInvoices: fusebillMethod({
    method: 'GET',
    path: '/{customerId}/DraftInvoices',
    urlParams: ['customerId'],
  }),

  listPaymentMethods: fusebillMethod({
    method: 'GET',
    path: '/{customerId}/paymentmethods',
    urlParams: ['customerId'],
  }),

});

Add back customer list payment methods


"use strict";

var TextView = require('../text/text_view');

// Substance.Heading.View
// ==========================================================================

var HeadingView = function(node) {
  TextView.call(this, node);

  this.$el.addClass('heading');
};

HeadingView.Prototype = function() {};

HeadingView.Prototype.prototype = TextView.prototype;
HeadingView.prototype = new HeadingView.Prototype();

module.exports = HeadingView;

"use strict";

var TextView = require('../text/text_view');

// Substance.Heading.View
// ==========================================================================

var HeadingView = function(node) {
  TextView.call(this, node);

  this.$el.addClass('heading');
  this.$el.addClass('level-'+this.node.level);

};

HeadingView.Prototype = function() {};

HeadingView.Prototype.prototype = TextView.prototype;
HeadingView.prototype = new HeadingView.Prototype();

module.exports = HeadingView;

Add css classes for heading levels.


"use strict";

require.config({
  shim: {
    backbone: {
      deps: ['underscore', 'jquery'],
      exports: 'Backbone'
    },
    underscore: {
      exports: '_'
    },
    handlebars: {
      exports: 'Handlebars'
    }
  },
  paths: {
    bootstrap: 'lib/bootstrap',
    jquery: 'lib/jquery-1.7.2',
    underscore: 'lib/underscore',
    handlebars: 'lib/handlebars-1.0.0.beta.6',
    backbone: 'lib/backbone',
    text: 'lib/text',
    async: 'lib/async',
    templates: '../templates'
  }
});

require(['jquery', 'underscore', 'backbone', 'router', 'bootstrap'], function ($, _, Backbone, Router) {

  window.Reitti = {};

  Reitti.Event = _.extend({}, Backbone.Events);

  $(function () {
    window.Router = new Router();
    Backbone.history.start();

    if (navigator.geolocation) {
      navigator.geolocation.watchPosition(function (position) {
        Reitti.Event.trigger('position:updated', position);
      }, function () {
      }, {enableHighAccuracy: true});
    }
  });
});

"use strict";

require.config({
  shim: {
    backbone: {
      deps: ['underscore', 'jquery'],
      exports: 'Backbone'
    },
    underscore: {
      exports: '_'
    },
    handlebars: {
      exports: 'Handlebars'
    }
  },
  paths: {
    bootstrap: 'lib/bootstrap',
    jquery: 'lib/jquery-1.7.2',
    underscore: 'lib/underscore',
    handlebars: 'lib/handlebars-1.0.0.beta.6',
    backbone: 'lib/backbone',
    text: 'lib/text',
    async: 'lib/async',
    templates: '../templates'
  }
});

require(['jquery', 'underscore', 'backbone', 'router', 'bootstrap'], function ($, _, Backbone, Router) {

  window.Reitti = {};
  Reitti.Event = _.extend({}, Backbone.Events);

  $(function () {
    Reitti.Router = new Router();
    Backbone.history.start();

    if (navigator.geolocation) {
      navigator.geolocation.watchPosition(function (position) {
        Reitti.Event.trigger('position:updated', position);
      }, function () {
      }, {enableHighAccuracy: true});
    }
  });
});

Move our router object to the Reitti namespace.


exports.files = {
  javascripts: {
    joinTo: {
      'fateOfAllFools.js': /^(?!test)/
    }
  },
  stylesheets: {
    joinTo: 'fateOfAllFools.css'
  }
};

// Tests are handled by Karma
exports.conventions = {
  ignored: /^test/
};

exports.files = {
  javascripts: {
    joinTo: {
      'fateOfAllFools.js': /^(?!test)/
    }
  },
  stylesheets: {
    joinTo: 'fateOfAllFools.css'
  }
};

/*
  1. Tests are handled by Karma. This is to silence a warning that Brunch
     reports (which is helpful actually in most cases!) because it sees JS files
     outside the scope of its purview.
  2. We exclude them because we don't want them bundled so that the Karma
     watcher only re-runs the tests we've changed during active development.
*/
exports.conventions = {
  ignored: /^test/
};

Add detail to exclusion reasoning


/**
 * model/fancy/location.js
 */

function changePathGen (method) {
    return function changePath(path) {
        root.history[method + 'State'](EMPTY_OBJECT, '', path);
        $(root).trigger(method + 'state');
    };
}

models.location = baseModel.extend({
    /**
     * Example:
     * var loc = tbone.models.location.make();
     * T(function () {
     *     console.log('the hash is ' + loc('hash'));
     * });
     * loc('hash', '#this-is-the-new-hash');
     */
    initialize: function initialize() {
        var self = this;
        var recentlyChanged;
        function update (ev) {
            var changed = self('hash') !== location.hash ||
                          self('search') !== location.search ||
                          self('pathname') !== location.pathname;
            if (changed) {
                self('hash', location.hash);
                self('pathname', location.pathname);
                self('search', location.search);
                recentlyChanged = true;
            }
        }
        $(root).bind('hashchange popstate pushstate replacestate', update);
        update();

        autorun(function initializeAutorun() {
            var pathname = self('pathname');
            var search = self('search');
            var hash = self('hash');
            if (!recentlyChanged) {
                self.pushPath(pathname + (search || '') + (hash ? '#' + hash : ''));
            }
            recentlyChanged = false;
        });
    },

    pushPath: changePathGen('push'),
    replacePath: changePathGen('replace')
});

/**
 * model/fancy/location.js
 */

function changePathGen (method) {
    return function changePath(path) {
        root.history[method + 'State'](EMPTY_OBJECT, '', path);
        window.dispatchEvent(new root.Event(method + 'state'));
    };
}

models.location = baseModel.extend({
    /**
     * Example:
     * var loc = tbone.models.location.make();
     * T(function () {
     *     console.log('the hash is ' + loc('hash'));
     * });
     * loc('hash', '#this-is-the-new-hash');
     */
    initialize: function initialize() {
        var self = this;
        var recentlyChanged;
        function update (ev) {
            var changed = self('hash') !== location.hash ||
                          self('search') !== location.search ||
                          self('pathname') !== location.pathname;
            if (changed) {
                self('hash', location.hash);
                self('pathname', location.pathname);
                self('search', location.search);
                recentlyChanged = true;
            }
        }
        window.addEventListener('hashchange', update);
        window.addEventListener('popstate', update);
        window.addEventListener('pushstate', update);
        window.addEventListener('replacestate', update);
        update();

        autorun(function initializeAutorun() {
            var pathname = self('pathname');
            var search = self('search');
            var hash = self('hash');
            if (!recentlyChanged) {
                self.pushPath(pathname + (search || '') + (hash ? '#' + hash : ''));
            }
            recentlyChanged = false;
        });
    },

    pushPath: changePathGen('push'),
    replacePath: changePathGen('replace')
});

Remove dependency on JQuery for binding/triggering hashchange/pushstate events


app

.factory("gitlab",
        ["$http", "$q", "Config",
        function($http, $q, Config) {

    function gitlab() {}

    gitlab.prototype.imageUrl = function(path) {
        return Config.gitlabUrl + path;
    };

    gitlab.prototype.callapi = function(method, path) {
        var deferred = $q.defer();
        var url = Config.gitlabUrl.replace(/\/*$/, "") + '/api/v3' + path;

        $http({
            url: url,
            method: method,
            headers: {
                'SUDO': 'root',
                'PRIVATE-TOKEN': Config.gitlabToken
            }
        })
        .success(function(data, status, headers, config) {
            deferred.resolve({
                data: data,
                status: status,
                headers: headers,
                config: config
            });
        })
        .error(function(data, status, headers, config) {
            deferred.reject({
                data: data,
                status: status,
                headers: headers,
                config: config
            });
        });

        return deferred.promise;
    };

    return new gitlab();
}])

;

app

.factory("gitlab",
        ["$http", "$q", "Config",
        function($http, $q, Config) {

    function gitlab() {}

    gitlab.prototype.imageUrl = function(path) {
        return Config.gitlabUrl.replace(/\/*$/, "") + path;
    };

    gitlab.prototype.callapi = function(method, path) {
        var deferred = $q.defer();
        var url = Config.gitlabUrl.replace(/\/*$/, "") + '/api/v3' + path;

        $http({
            url: url,
            method: method,
            headers: {
                'SUDO': 'root',
                'PRIVATE-TOKEN': Config.gitlabToken
            }
        })
        .success(function(data, status, headers, config) {
            deferred.resolve({
                data: data,
                status: status,
                headers: headers,
                config: config
            });
        })
        .error(function(data, status, headers, config) {
            deferred.reject({
                data: data,
                status: status,
                headers: headers,
                config: config
            });
        });

        return deferred.promise;
    };

    return new gitlab();
}])

;

Remove trailing slash in GitLab url


import IdProxy from '../utils/idproxy';
import ProjectedModelRoute from '../routes/base/projected-model-route';

export default ProjectedModelRoute.extend({
  model: function(params, transition) {
    this._super.apply(this, arguments);

    // :id param defined in router.js
    var id = IdProxy.mutate(params.id, this.get('modelProjection'));

    // TODO: optionally: fetch or find.
    return this.store.fetchById(this.get('modelName'), id);
  },

  resetController: function(controller, isExisting, transition) {
    this._super.apply(this, arguments);

    controller.send('dismissErrorMessages');
    var model = controller.get('model');
    if (model && model.get('isDirty')) {
      model.rollback();
    }
  }
});

import IdProxy from '../utils/idproxy';
import ProjectedModelRoute from '../routes/base/projected-model-route';

export default ProjectedModelRoute.extend({
  model: function(params, transition) {
    this._super.apply(this, arguments);

    // :id param defined in router.js
    var id = IdProxy.mutate(params.id, this.get('modelProjection'));

    // TODO: optionally: fetch or find.
    return this.store.findRecord(this.get('modelName'), id, { reload: true });
  },

  resetController: function(controller, isExisting, transition) {
    this._super.apply(this, arguments);

    controller.send('dismissErrorMessages');
    var model = controller.get('model');
    if (model && model.get('isDirty')) {
      model.rollback();
    }
  }
});

Fix ember data "fetchById" deprecation


var vCard = require( '..' )
var fs = require( 'fs' )
var assert = require( 'assert' )

suite( 'vCard', function() {

  suite( 'Character Sets', function() {

    test( 'charset should not be part of value', function() {
      var data = fs.readFileSync( __dirname + '/data/xing.vcf' )
      var card = new vCard( data )
      assert.strictEqual( card.fn.indexOf( 'CHARSET' ), -1 )
    })

  })

  suite( 'Photo URL', function() {

    test( 'URL parameters should not become object properties', function() {
      var data = fs.readFileSync( __dirname + '/data/xing.vcf' )
      var card = new vCard( data )
      assert.equal( card.photo.txtsize, null )
    })

  })

})

var vCard = require( '..' )
var fs = require( 'fs' )
var assert = require( 'assert' )

suite( 'vCard', function() {

  suite( 'Character Sets', function() {

    test( 'charset should not be part of value', function() {
      var data = fs.readFileSync( __dirname + '/data/xing.vcf' )
      var card = new vCard( data )
      assert.strictEqual( typeof card.fn, 'object' )
      assert.strictEqual( card.fn.data.indexOf( 'CHARSET' ), -1 )
    })

  })

  suite( 'Photo URL', function() {

    test( 'URL parameters should not become object properties', function() {
      var data = fs.readFileSync( __dirname + '/data/xing.vcf' )
      var card = new vCard( data )
      assert.equal( card.photo.txtsize, null )
    })

  })

})

Update test: Adjust assertions to removal of cardinality checks


const minecraftItems = require('../')
const tap = require('tap')

const testItem = (test, item, name) => {
  test.type(item, 'object')
  test.equal(item.name, name)
  test.end()
}

tap.test('should be able to get an item by numeric type', t => {
  testItem(t, minecraftItems.get(1), 'Stone')
})

tap.test('should be able to get an item by string type', t => {
  testItem(t, minecraftItems.get('1'), 'Stone')
})

tap.test('should be able to get an item by string type & subType', t => {
  testItem(t, minecraftItems.get('1:0'), 'Stone')
})

tap.test('should be able to get an item by name', t => {
  testItem(t, minecraftItems.get('Stone'), 'Stone')
})

tap.test('should be able to get an item by name with case insensitivity', t => {
  testItem(t, minecraftItems.get('stoNe'), 'Stone')
})

const minecraftItems = require('../')
const tap = require('tap')

const testItem = (test, item, name) => {
  test.type(item, 'object')
  test.equal(item.name, name)
  test.notEqual(typeof item.id, 'undefined', 'items should have an id property')
  test.notEqual(typeof item.type, 'undefined', 'items should have a type property')
  test.notEqual(typeof item.meta, 'undefined', 'items should have a meta property')
  test.notEqual(typeof item.icon, 'undefined', 'items should have a icon property')
  test.end()
}

tap.test('should be able to get an item by numeric type', t => {
  testItem(t, minecraftItems.get(1), 'Stone')
})

tap.test('should be able to get an item by string type', t => {
  testItem(t, minecraftItems.get('1'), 'Stone')
})

tap.test('should be able to get an item by string type & subType', t => {
  testItem(t, minecraftItems.get('1:0'), 'Stone')
})

tap.test('should be able to get an item by name', t => {
  testItem(t, minecraftItems.get('Stone'), 'Stone')
})

tap.test('should be able to get an item by name with case insensitivity', t => {
  testItem(t, minecraftItems.get('stoNe'), 'Stone')
})

Test for inclusion of icon data.


// import { getDocument } from './lib/couchdb';
// getDocument('3cfc9a96341c0e24')
//   .then(d => console.log(d))
//   .catch(d => console.log(d))

export default (bot) => {
  return async (payload, reply) => {
    let text = payload.postback.payload;

    try {
      const profile = await bot.getProfile(payload.sender.id);
      await reply({
        attachment: {
          type: 'template',
          payload: {
            template_type: 'button',
            text: 'EBudgie needs to log you',
            buttons: [{
              type: 'account_link',
              url: process.env.LOGIN_URL
            }]
          }
        }
      });
      console.log(`Postback to ${profile.first_name} ${profile.last_name}: ${text}`);
    } catch (e) {
      console.log('ERROR in bot.on(\'postback\')', e);
    }
  };
};

// import { getDocument } from './lib/couchdb';
// getDocument('3cfc9a96341c0e24')
//   .then(d => console.log(d))
//   .catch(d => console.log(d))

export default (bot) => {
  return async (payload, reply) => {
    let text = payload.postback.payload;

    try {
      const profile = await bot.getProfile(payload.sender.id);
      await reply({
        attachment: {
          type: 'template',
          payload: {
            template_type: 'generic',
            elements: [{
              title: 'EBudgie needs to log you',
              image_url: 'https://petersfancybrownhats.com/company_image.png',
              subtitle: 'To see what is going on with your account you need to login first.',
              buttons: [{
                type: 'account_link',
                url: process.env.LOGIN_URL
              }]
            }]
          }
        }
      });
      console.log(`Postback to ${profile.first_name} ${profile.last_name}: ${text}`);
    } catch (e) {
      console.log('ERROR in bot.on(\'postback\')', e);
    }
  };
};

Change the template for account linking


define([
  'backbone', 'handlebars', 'moment',
  'connect/collections/Subscriptions',
  'connect/views/SubscriptionListItemView',
  'text!connect/templates/subscriptionList.handlebars'
], function(Backbone, Handlebars, moment, Subscriptions, SubscriptionListItemView, tpl) {

  'use strict';

  var SubscriptionListView = Backbone.View.extend({
    template: Handlebars.compile(tpl),

    initialize: function() {
      this.subscriptions = new Subscriptions();
      this.listenTo(this.subscriptions, 'sync', this.render);
      this.subscriptions.fetch();

      this.render();
    },

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

      var $tableBody = this.$('#user-subscriptions-table-body');
      this.subscriptions.each(function(subscription) {
        var view = new SubscriptionListItemView({
          subscription: subscription});
        $tableBody.append(view.el);
      });
    }
  });

  return SubscriptionListView;

});

define([
  'backbone', 'handlebars', 'moment',
  'connect/collections/Subscriptions',
  'connect/views/SubscriptionListItemView',
  'text!connect/templates/subscriptionList.handlebars'
], function(Backbone, Handlebars, moment, Subscriptions, SubscriptionListItemView, tpl) {

  'use strict';

  var SubscriptionListView = Backbone.View.extend({
    template: Handlebars.compile(tpl),

    initialize: function() {
      this.subscriptions = new Subscriptions();
      this.listenTo(this.subscriptions, 'sync', this.render);
      this.subscriptions.fetch();

      this.render();
    },

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

      var sortedSubscriptions = new Subscriptions(
        this.subscriptions.sortBy(function(subscription) {
          return -moment(subscription.get('created')).unix();
        })
      );

      var $tableBody = this.$('#user-subscriptions-table-body');
      sortedSubscriptions.each(function(subscription) {
        var view = new SubscriptionListItemView({
          subscription: subscription});
        $tableBody.append(view.el);
      });
    }
  });

  return SubscriptionListView;

});

Sort subscription list by date


import transformCss from '../..'

it('textShadow with all values', () => {
  expect(transformCss([['text-shadow', '10px 20px 30px red']])).toEqual({
    textShadowOffset: { width: 10, height: 20 },
    textShadowRadius: 30,
    textShadowColor: 'red',
  })
})

it('textShadow omitting blur', () => {
  expect(transformCss([['text-shadow', '10px 20px red']])).toEqual({
    textShadowOffset: { width: 10, height: 20 },
    textShadowRadius: 0,
    textShadowColor: 'red',
  })
})

it('textShadow omitting color', () => {
  expect(transformCss([['text-shadow', '10px 20px black']])).toEqual({
    textShadowOffset: { width: 10, height: 20 },
    textShadowRadius: 0,
    textShadowColor: 'black',
  })
})

it('textShadow omitting blur, offset-y', () => {
  expect(transformCss([['text-shadow', '10px 20px red']])).toEqual({
    textShadowOffset: { width: 10, height: 20 },
    textShadowRadius: 0,
    textShadowColor: 'red',
  })
})

it('textShadow enforces offset-x and offset-y', () => {
  expect(() => transformCss([['text-shadow', 'red']])).toThrow()
  expect(() => transformCss([['text-shadow', '10px red']])).toThrow()
})

import transformCss from '../..'

it('textShadow with all values', () => {
  expect(transformCss([['text-shadow', '10px 20px 30px red']])).toEqual({
    textShadowOffset: { width: 10, height: 20 },
    textShadowRadius: 30,
    textShadowColor: 'red',
  })
})

it('textShadow omitting blur', () => {
  expect(transformCss([['text-shadow', '10px 20px red']])).toEqual({
    textShadowOffset: { width: 10, height: 20 },
    textShadowRadius: 0,
    textShadowColor: 'red',
  })
})

it('textShadow omitting color', () => {
  expect(transformCss([['text-shadow', '10px 20px']])).toEqual({
    textShadowOffset: { width: 10, height: 20 },
    textShadowRadius: 0,
    textShadowColor: 'black',
  })
})

it('textShadow enforces offset-x and offset-y', () => {
  expect(() => transformCss([['text-shadow', 'red']])).toThrow()
  expect(() => transformCss([['text-shadow', '10px red']])).toThrow()
})

Remove duplicate test, fix duplicate test


// @flow

import { expect } from "chai";

import {
	Game,
	GameDescriptor,
	DefaultDeck
} from "stormtide-core";

import type {
	PlayerDescriptor
} from "stormtide-core";

describe("Scenario: Game Start", () => {
	const settings = new GameDescriptor();

	const player1: PlayerDescriptor = {
		deck: DefaultDeck
	};
	const player2: PlayerDescriptor = {
		deck: DefaultDeck
	};

	settings.players = [player1, player2];

	const game = new Game(settings);
	game.start();

	const playerState1 = game.state.playerTurnOrder
		.map(v => game.state.players[v])
		.filter(v => v != null)
		.find(player => player && player.descriptor === player1);

	it("should have the right players", () => {
		expect(Object.keys(game.state.players)).to.have.lengthOf(2);
	});

	it("should give turn and priority to player 1", () => {
		expect(playerState1).to.be.ok;

		if (!playerState1) {
			return;
		}

		expect(game.state.turn).to.equal(playerState1.id);
		expect(game.state.priority).to.equal(playerState1.id);
	});
});

// @flow

import { expect } from "chai";

import {
	Game,
	GameDescriptor,
	DefaultDeck
} from "stormtide-core";

import type {
	PlayerDescriptor
} from "stormtide-core";

describe("Scenario: Game Start", () => {
	const settings = new GameDescriptor();

	const player1: PlayerDescriptor = {
		deck: DefaultDeck
	};
	const player2: PlayerDescriptor = {
		deck: DefaultDeck
	};

	settings.players = [player1, player2];

	const game = new Game(settings);
	game.start();

	const playerState1 = game.state.playerTurnOrder
		.map(v => game.state.players[v])
		.filter(v => v != null)
		.find(player => player && player.descriptor === player1);

	it("should have the right players", () => {
		expect(Object.keys(game.state.players)).to.have.lengthOf(2);
	});

	// This will change with the addition of pre-game actions
	it("should give turn and priority to player 1", () => {
		expect(playerState1).to.be.ok;

		if (!playerState1) {
			return;
		}

		expect(game.state.turn).to.equal(playerState1.id);
		expect(game.state.priority).to.equal(playerState1.id);
	});
});

Add minor note about future changes


require('ember-runtime/system/object_proxy');
require('ember-runtime/controllers/controller');

/**
@module ember
@submodule ember-runtime
*/

/**
  `Ember.ObjectController` is part of Ember's Controller layer. A single shared
  instance of each `Ember.ObjectController` subclass in your application's
  namespace will be created at application initialization and be stored on your
  application's `Ember.Router` instance.

  `Ember.ObjectController` derives its functionality from its superclass
  `Ember.ObjectProxy` and the `Ember.ControllerMixin` mixin.

  @class ObjectController
  @namespace Ember
  @extends Ember.ObjectProxy
  @uses Ember.ControllerMixin
**/
Ember.ObjectController = Ember.ObjectProxy.extend(Ember.ControllerMixin);

require('ember-runtime/system/object_proxy');
require('ember-runtime/controllers/controller');

/**
@module ember
@submodule ember-runtime
*/

/**
  `Ember.ObjectController` is part of Ember's Controller layer.

  `Ember.ObjectController` derives its functionality from its superclass
  `Ember.ObjectProxy` and the `Ember.ControllerMixin` mixin.

  @class ObjectController
  @namespace Ember
  @extends Ember.ObjectProxy
  @uses Ember.ControllerMixin
**/
Ember.ObjectController = Ember.ObjectProxy.extend(Ember.ControllerMixin);

Remove obsolete paragraph from ObjectController comments


import Phaser from 'phaser';

import frames from '../../sprite-frames';

import update from './update';

export default class extends Phaser.Sprite {
  constructor({ game, x, y }) {
    super(game, x, y, 'guy', frames.GUY.STAND_DOWN);

    this.anchor.setTo(0.5, 0.5);

    this.faceDirection = 'DOWN';
    this.faceObject = null;

    this.inventory = {
      water: 0
    };

    this.update = update.bind(this);
  }
}

import Phaser from 'phaser';

import frames from '../../sprite-frames';

import update from './update';
import Inventory from './inventory';

export default class extends Phaser.Sprite {
  constructor({ game, x, y }) {
    super(game, x, y, 'guy', frames.GUY.STAND_DOWN);

    this.anchor.setTo(0.5, 0.5);

    this.faceDirection = 'DOWN';
    this.faceObject = null;

    this.inventory = new Inventory(this.game);

    this.update = update.bind(this);
  }
}

Use inventory class instead of plain object


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

import styles from './navigation.module.styl'

export default ({ sports }) => (

    <ol className={`${styles['sports-list']}`}>
      {
        sports.map(({ node }, idx) => (
          <li key={idx} className={`${styles['sport-item']}`}>

              <NavLink
                to={`/${node.name}`}
                activeClassName={styles.active}
                className={styles['nav-link']}>
                { node.name }
              </NavLink>

            <ol className={`${styles['countries-list']}`}>
              {
                node.countries.map((country, idx) => (
                  <li key={idx} className={`${styles['country-item']}`}>

                      <NavLink
                        to={`/${node.name}/${country.name}`}
                        activeClassName={styles.active}
                        className={styles['nav-link']}>
                        { country.name }
                      </NavLink>

                ))
              }

        ))
      }

)

import React from 'react'
import NavLink from 'gatsby-link'

import styles from './navigation.module.styl'

export default ({ sports }) => (

    <ol className={`${styles['sports-list']}`}>
      {
        sports.map(({ node }, idx) => (
          <li key={idx} className={`${styles['sport-item']}`}>

              <NavLink
                to={`/${node.name}`}
                activeClassName={styles.active}
                className={styles['nav-link']}>
                { node.name }
              </NavLink>

            <ol className={`${styles['countries-list']}`}>
              {
                node.countries.map((country, idx) => (
                  <li key={idx} className={`${styles['country-item']}`}>

                      <NavLink
                        to={`/${node.name}/${country.name}`}
                        activeClassName={styles.active}
                        className={styles['nav-link']}>
                        { country.name }
                      </NavLink>

                ))
              }

        ))
      }

)

Update NavLinks: use gatsby-link to avoid code chunks necessary for pages not being loaded.


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

module.exports = {
  'bare-strings': true,
  'block-indentation': 2,
  'html-comments': true,
  'nested-interactive': true,
  'lint-self-closing-void-elements': true,
  'triple-curlies': true,
  'deprecated-each-syntax': true
};

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

module.exports = {
  extend: 'recommended'
};

Use the recommended config from ember-template-lint.


module.exports = WebSocket || MozWebSocket || window.WebSocket || window.MozWebSocket

var ws = null

if (typeof WebSocket !== 'undefined') {
  ws = WebSocket
} else if (typeof MozWebSocket !== 'undefined') {
  ws = MozWebSocket
} else {
  ws = window.WebSocket || window.MozWebSocket
}

module.exports = ws

Use typeof for checking globals.


var KeyImporterController = function() {

	this.sendArmor = function() {
		armorContainer = $('#import-armor');
		data = "armor="+armorContainer.val();

		$.post(
		    '/node/api/keyring/post/import',
		    data,
		    function(data){
		       obj = $.parseJSON(data);
		       if(obj.result != 'ok') {
		    	   $('#import-ok').text("Imported Failed!")
		    	   return;
		       }
		       $('#import-ok').text("Imported key for `"+obj.name+"`")
		    }
		);
	}
}

var KeyImporter = new KeyImporterController();

var KeyImporterController = function() {

	this.sendArmor = function() {
		armorContainer = $('#import-armor');
		data = "armor="+encodeURIComponent(armorContainer.val());

		$.post(
		    '/node/api/keyring/post/import',
		    data,
		    function(data){
		       obj = $.parseJSON(data);
		       if(obj.result != 'ok') {
		    	   $('#import-ok').text("Imported Failed!")
		    	   return;
		       }
		       $('#import-ok').text("Imported key for `"+obj.name+"`")
		    }
		);
	}
}

var KeyImporter = new KeyImporterController();

Correct encoding for key import


import DS from 'ember-data';

export default DS.Model.extend({
  title: DS.attr('string'),
  sessionTypeCssClass: DS.attr('string'),
  assessment: DS.attr('boolean'),
  assessmentOption: DS.belongsTo('assessment-option', {async: true}),
  school: DS.belongsTo('school', {async: true}),
  aamcMethods: DS.hasMany('aamc-method', {async: true}),
  sessions: DS.hasMany('session', {async: true}),
});

import DS from 'ember-data';

export default DS.Model.extend({
  title: DS.attr('string'),
  sessionTypeCssClass: DS.attr('string'),
  assessment: DS.attr('boolean'),
  assessmentOption: DS.belongsTo('assessment-option', {async: true}),
  school: DS.belongsTo('school', {async: true}),
  aamcMethods: DS.hasMany('aamc-method', {async: true}),
});

Remove sessions from seasionType model


require('./check-versions')();

var config = require('../config');
if (!process.env.NODE_ENV) {
  process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV);
}

var webpack = require('webpack');
var webpackConfig = require('./webpack.dev.conf');

const compiler = webpack(webpackConfig);

const watching = compiler.watch({}, (err, stats) => {
  if (err) throw err;
  process.stdout.write(stats.toString({
    colors: true,
    modules: false,
    children: false,
    chunks: false,
    chunkModules: false
  }) + '\n\n');
});

module.exports = {
  close: () => {
    watching.close(() => {
      console.log('Watching ended');
    });
  }
};

require('./check-versions')();

let config = require('../config');
if (!process.env.NODE_ENV) {
  process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV);
}

let webpack = require('webpack');
let webpackConfig = require('./webpack.dev.conf');

const compiler = webpack(webpackConfig);

const watching = compiler.watch({}, (err, stats) => {
  if (err) throw err;
  process.stdout.write(stats.toString({
    colors: true,
    modules: false,
    children: false,
    chunks: false,
    chunkModules: false,

    assets: false,
    hash: false,
    version: false,
    timings: false,
  }) + '\n\n');
});

module.exports = {
  close: () => {
    watching.close();
  },
};

Improve output message and lint


"use strict";

// Serves the imprint for the application

module.exports = 'controllers/imprint';
var dependencies = [];

angular.module(module.exports, dependencies)
    .controller('ImprintCtrl', function ($scope, $http) {
        $scope.config = config;
        if (config.server) {
            $http.get(config.server + '/')
                .success(function (data) {
                    $scope.server = data;
                });
        }
    });

"use strict";

// Serves the imprint for the application

module.exports = 'controllers/imprint';
var dependencies = [];

angular.module(module.exports, dependencies)
    .controller('ImprintCtrl', function ($scope, $http) {
        $scope.config = config;
        $http
            .get(config.server + '/')
            .success(function (data) {
                $scope.server = data;
            });
    });

Remove unneeded check if there is a server


var XSSI_PREFIX = ")]}'";

function parseJson(s) {
  return JSON.parse(s.replace(XSSI_PREFIX, ""));
}

$(function(){
  $('div[data-delete-url]').each(function(index, element){
    var groupName = $(element).data('group-name');
    var button = $('<button id="delete-' + groupName + '" ' +
        'class="gcb-list__icon gcb-delete-student-group ' +
        'delete-button gcb-list__icon--rowhover material-icons">' +
        'delete</button>')
    var deleteUrl = $(element).data('delete-url');
    button.click(function(){
      if (confirm('You are about to delete this group.  Really proceed?')) {
        $.ajax({
          url: deleteUrl,
          method: 'DELETE',
          error: function(response) {
            // Here, we are re-using the OEditor style delete handler, so
            // we need to cope with a CB-encoded response style.
            if (response.status == 200 && response.statusText == "OK") {
              var payload = parseJson(response.responseText);
              if (payload.status == 200 && payload.message == "Deleted.") {
                location.reload();
              } else {
                cbShowMsgAutoHide('Deletion failed: ' + payload.message);
              }
            } else {
              cbShowMsgAutoHide('Deletion failed: ' + response.responseText)
            }
          },
          success: function() {
            location.reload();
          }
        });
      }
    });
    $(element).append(button)
  });
});

var XSSI_PREFIX = ")]}'";

function parseJson(s) {
  return JSON.parse(s.replace(XSSI_PREFIX, ""));
}

$(function(){
  $('div[data-delete-url]').each(function(index, element){
    var groupName = $(element).data('group-name');
    var button = $('<button id="delete-' + groupName + '" ' +
        'class="gcb-list__icon gcb-delete-student-group ' +
        'delete-button gcb-list__icon--rowhover material-icons">' +
        'delete</button>')
    var deleteUrl = $(element).data('delete-url');
    button.click(function(){
      if (confirm('You are about to delete this group.  Really proceed?')) {
        $.ajax({
          url: deleteUrl,
          method: 'DELETE',
          error: function(response) {
            // Here, we are re-using the OEditor style delete handler, so
            // we need to cope with a CB-encoded response style.
            if (response.status == 200) {
              var payload = parseJson(response.responseText);
              if (payload.status == 200 && payload.message == "Deleted.") {
                location.reload();
              } else {
                cbShowMsgAutoHide('Deletion failed: ' + payload.message);
              }
            } else {
              cbShowMsgAutoHide('Deletion failed: ' + response.responseText)
            }
          },
          success: function() {
            location.reload();
          }
        });
      }
    });
    $(element).append(button)
  });
});

Change JS parser to only look for 200 success code not the actual "OK".


module.exports = {
  stories: ['../stories/**/*.stories.js'],
  addons: ['@storybook/addon-actions', '@storybook/addon-links'],
};

module.exports = {
  stories: ['../stories/**/*.stories.(ts|tsx|js|jsx)'],
  addons: ['@storybook/addon-actions', '@storybook/addon-links'],
};

Support Both js and jsx or ts and tsx


// Parse CloudCode
Parse.Cloud.beforeSave(Parse.Installation, function(request, response) {
  Parse.Cloud.useMasterKey();

  var androidId = request.object.get("androidId");
  if (androidId == null || androidId == "") {
    console.warn("No androidId found, exit");
    response.success();
  }

  var query = new Parse.Query(Parse.Installation);
  query.equalTo("deviceType", "android");
  query.equalTo("androidId", androidId);
  query.addAscending("createdAt");
  query.find().then(function(results) {
    for (var i = 0; i < results.length; ++i) {
      if (results[i].get("installationId") != request.object.get("installationId")) {
        console.warn("App id " + results[i].get("installationId") + ", delete!");
        results[i].destroy().then(function() {
            console.warn("Delete success");
          },
          function() {
            console.warn("Delete error");
          }
        );
      } else {
        console.warn("Current App id " + results[i].get("installationId") + ", dont delete");
      }
    }
    response.success();
    },
    function(error) {
      response.error("Can't find Installation objects");
    }
  );
});

// Parse CloudCode
Parse.Cloud.beforeSave(Parse.Installation, function(request, response) {
    Parse.Cloud.useMasterKey();

    var androidId = request.object.get("androidId");
    if (androidId == null || androidId == "") {
        console.warn("No androidId found, exit");
        return response.success();
    }

    var query = new Parse.Query(Parse.Installation);
    query.equalTo("deviceType", "android");
    query.equalTo("androidId", androidId);
    query.addAscending("createdAt");
    query.find().then(function(results) {
        for (var i = 0; i < results.length; ++i) {
            if (results[i].get("installationId") != request.object.get("installationId")) {
                console.warn("App id " + results[i].get("installationId") + ", delete!");
                results[i].destroy().then(function() {
                    console.warn("Delete success");
                }, function() {
                    console.warn("Delete error");
                });
            } else {
                console.warn("Current App id " + results[i].get("installationId") + ", dont delete");
            }
        }
        response.success();
    }, function(error) {
        response.error("Can't find Installation objects");
    });
});

Fix typo on error handling conditional


"use strict";
(function (cornerstoneWADOImageLoader) {

  function decodeJPEGLossless(imageFrame, pixelData) {
    // check to make sure codec is loaded
    if(typeof jpeg === 'undefined' ||
      typeof jpeg.lossless === 'undefined' ||
      typeof jpeg.lossless.Decoder === 'undefined') {
      throw 'No JPEG Lossless decoder loaded';
    }

    var byteOutput = imageFrame.bitsAllocated <= 8 ? 1 : 2;
    //console.time('jpeglossless');
    var buffer = pixelData.buffer;
    var decoder = new jpeg.lossless.Decoder();
    var decompressedData = decoder.decode(buffer, buffer.byteOffset, buffer.length, byteOutput);
    //console.timeEnd('jpeglossless');
    if (imageFrame.pixelRepresentation === 0) {
      if (imageFrame.bitsAllocated === 16) {
        imageFrame.pixelData = new Uint16Array(decompressedData.buffer);
        return imageFrame;
      } else {
        // untested!
        imageFrame.pixelData = new Uint8Array(decompressedData.buffer);
        return imageFrame;
      }
    } else {
      imageFrame.pixelData = new Int16Array(decompressedData.buffer);
      return imageFrame;
    }
  }
  // module exports
  cornerstoneWADOImageLoader.decodeJPEGLossless = decodeJPEGLossless;

}(cornerstoneWADOImageLoader));

"use strict";
(function (cornerstoneWADOImageLoader) {

  function decodeJPEGLossless(imageFrame, pixelData) {
    // check to make sure codec is loaded
    if(typeof jpeg === 'undefined' ||
      typeof jpeg.lossless === 'undefined' ||
      typeof jpeg.lossless.Decoder === 'undefined') {
      throw 'No JPEG Lossless decoder loaded';
    }

    var byteOutput = imageFrame.bitsAllocated <= 8 ? 1 : 2;
    //console.time('jpeglossless');
    var buffer = pixelData.buffer;
    var decoder = new jpeg.lossless.Decoder();
    var decompressedData = decoder.decode(buffer, pixelData.byteOffset, pixelData.length, byteOutput);
    //console.timeEnd('jpeglossless');
    if (imageFrame.pixelRepresentation === 0) {
      if (imageFrame.bitsAllocated === 16) {
        imageFrame.pixelData = new Uint16Array(decompressedData.buffer);
        return imageFrame;
      } else {
        // untested!
        imageFrame.pixelData = new Uint8Array(decompressedData.buffer);
        return imageFrame;
      }
    } else {
      imageFrame.pixelData = new Int16Array(decompressedData.buffer);
      return imageFrame;
    }
  }
  // module exports
  cornerstoneWADOImageLoader.decodeJPEGLossless = decodeJPEGLossless;

}(cornerstoneWADOImageLoader));

Fix a bug which causes that JPEG lossless images cannot be decompressed in Safari


var TreeSitter = function() {
  var initPromise;
  class Parser {
    constructor() {
      this.initialize();
    }

    initialize() {
      throw new Error("cannot construct a Parser before calling `init()`");
    }

    static init(moduleOptions) {
      if (initPromise) return initPromise;
      Module = Object.assign({ }, Module, moduleOptions);
      return initPromise = new Promise((resolveInitPromise) => {

var TreeSitter = function() {
  var initPromise;
  var document = typeof window == 'object'
    ? {currentScript: window.document.currentScript}
    : null;

  class Parser {
    constructor() {
      this.initialize();
    }

    initialize() {
      throw new Error("cannot construct a Parser before calling `init()`");
    }

    static init(moduleOptions) {
      if (initPromise) return initPromise;
      Module = Object.assign({}, Module, moduleOptions);
      return initPromise = new Promise((resolveInitPromise) => {

Fix script directory that's passed to locateFile


'use strict';

/*global Wodo*/

angular.module('manticoreApp')
.controller('TitleEditorCtrl', function ($scope, $timeout) {
    function handleTitleChanged(changes) {
        var title = changes.setProperties['dc:title'];
        if (title !== undefined && title !== $scope.title) {
            $timeout(function () {
                $scope.title = title;
            });
        }
    }

    $scope.changeTitle = function () {
        if ($scope.title !== $scope.editor.getMetadata('dc:title')) {
            $scope.editor.setMetadata({
                'dc:title': $scope.title
            });
        }
    };

    $scope.handleEnterKey = function ($event) {
        if ($event.keyCode === 13) {
            $event.target.blur();
        }
    };

    $scope.$watch('joined', function (online) {
        if (online === undefined) { return; }
        if (online) {
            $scope.editor.addEventListener(Wodo.EVENT_METADATACHANGED, handleTitleChanged);
            $scope.title = $scope.editor.getMetadata('dc:title');
        } else {
            $scope.editor.removeEventListener(Wodo.EVENT_METADATACHANGED, handleTitleChanged);
        }
    });

    function init() {
        $scope.title = $scope.document.title;
    }

    init();
});

'use strict';

/*global Wodo*/

angular.module('manticoreApp')
.controller('TitleEditorCtrl', function ($scope, $timeout) {
    function handleTitleChanged(changes) {
        var title = changes.setProperties['dc:title'];
        if (title !== undefined && title !== $scope.title) {
            $timeout(function () {
                $scope.title = title;
            });
        }
    }

    $scope.changeTitle = function () {
        if ($scope.title !== $scope.editor.getMetadata('dc:title')) {
            $scope.editor.setMetadata({
                'dc:title': $scope.title
            });
        }
    };

    $scope.handleEnterKey = function ($event) {
        if ($event.keyCode === 13) {
            $event.target.blur();
        }
    };

    $scope.$watch('joined', function (online) {
        if (online === undefined) { return; }
        if (online) {
            $scope.editor.addEventListener(Wodo.EVENT_METADATACHANGED, handleTitleChanged);
        } else {
            $scope.editor.removeEventListener(Wodo.EVENT_METADATACHANGED, handleTitleChanged);
        }
    });

    function init() {
        $scope.title = $scope.document.title;
    }

    init();
});

Set metadata as UI title only when someone changes it


function extsToRegExp (exts) {
	          return new RegExp('\\.(' + exts.map(function (ext) {
		          return ext.replace(/\./g, '\\.')
	}).join('|') + ')(\\?.*)?$')
}

module.exports = function loadersByExtension (obj) {
	          var loaders = []
	          Object.keys(obj).forEach(function (key) {
		          var exts = key.split('|')
		          var value = obj[key]
		          var entry = {
			          extensions: exts,
			          test: extsToRegExp(exts)
		}
		          if (Array.isArray(value)) {
			          entry.loaders = value
		} else if (typeof value === 'string') {
			          entry.loader = value
		} else {
			          Object.keys(value).forEach(function (valueKey) {
				          entry[valueKey] = value[valueKey]
			})
		}
		          loaders.push(entry)
	})
	          return loaders
}

Update coding styles in webpack loaders


"use strict";

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

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

"use strict";

angular.module('arethusa.relation').directive('nestedMenuCollection', function() {
  return {
    restrict: 'A',
    replace: 'true',
    scope: {
      current: '=',
      all: '=',
      property: '=',
      ancestors: '=',
      emptyVal: '@',
      labelAs: "=",
    },
    link: function(scope, element, attrs) {
      scope.emptyLabel = "";
      scope.emptyObj = {};

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

Remove change fn from nestedMenuCollection


import template from './issue-comments-form.html';

class IssueCommentsFormController {
  // @ngInject
  constructor($rootScope, issueCommentsService) {
    this.$rootScope = $rootScope;
    this.issueCommentsService = issueCommentsService;
  }

  submit() {
    var form = this.issueCommentsService.$create(this.issue.url + 'comment/');
    form.description = this.description;
    this.submitting = true;
    return form.$save().then(() => {
      this.description = '';
      this.$rootScope.$emit('refreshCommentsList');
    }).finally(() => {
      this.submitting = false;
    });
  }
}

const issueCommentsForm = {
  template,
  controller: IssueCommentsFormController,
  bindings: {
    issue: '<',
  },
};

export default issueCommentsForm;

import template from './issue-comments-form.html';

class IssueCommentsFormController {
  // @ngInject
  constructor($rootScope, issueCommentsService, ncUtilsFlash) {
    this.$rootScope = $rootScope;
    this.issueCommentsService = issueCommentsService;
    this.ncUtilsFlash = ncUtilsFlash;
  }

  submit() {
    var form = this.issueCommentsService.$create(this.issue.url + 'comment/');
    form.description = this.description;
    this.submitting = true;
    return form.$save().then(() => {
      this.description = '';
      this.$rootScope.$emit('refreshCommentsList');
      this.ncUtilsFlash.success(gettext('Comment has been added.'));
    })
    .catch(() => {
      this.ncUtilsFlash.error(gettext('Unable to add comment.'));
    })
    .finally(() => {
      this.submitting = false;
    });
  }
}

const issueCommentsForm = {
  template,
  controller: IssueCommentsFormController,
  bindings: {
    issue: '<',
  },
};

export default issueCommentsForm;

Add notifications for comment create processing [WAL-1036].


var m = require('mithril');

var Fangorn = require('fangorn');

// Although this is empty it should stay here. Fangorn is going to look for figshare in it's config file.
Fangorn.config.figshare = {

};

var m = require('mithril');

var Fangorn = require('fangorn');

// Define Fangorn Button Actions
function _fangornActionColumn (item, col) {
    var self = this;
    var buttons = [];

    if (item.kind === 'folder') {
        buttons.push(
            {
                'name' : '',
                'tooltip' : 'Upload files',
                'icon' : 'icon-upload-alt',
                'css' : 'fangorn-clickable btn btn-default btn-xs',
                'onclick' : Fangorn.ButtonEvents._uploadEvent
            }
        );
    }

    if (item.kind === 'file' && item.data.extra && item.data.extra.status === 'public') {
        buttons.push({
            'name' : '',
            'tooltip' : 'Download file',
            'icon' : 'icon-download-alt',
            'css' : 'btn btn-info btn-xs',
            'onclick' : Fangorn.ButtonEvents._downloadEvent
        });
    }

    if (item.kind === 'file' && item.data.extra && item.data.extra.status !== 'public') {
        buttons.push({
            'name' : '',
            'icon' : 'icon-remove',
            'tooltip' : 'Delete',
            'css' : 'm-l-lg text-danger fg-hover-hide',
            'style' : 'display:none',
            'onclick' : Fangorn.ButtonEvents._removeEvent
        });
    }

    return buttons.map(function(btn) {
        return m('span', { 'data-col' : item.id }, [ m('i',{
        	'class' : btn.css,
        	style : btn.style,
            'data-toggle' : 'tooltip', title : btn.tooltip, 'data-placement': 'bottom',
        	'onclick' : function(event){ btn.onclick.call(self, event, item, col); }
        },
            [ m('span', { 'class' : btn.icon}, btn.name) ])
        ]);
    });
}

Fangorn.config.figshare = {
    // Fangorn options are called if functions, so return a thunk that returns the column builder
    resolveActionColumn: function() {return _fangornActionColumn;}
};

Repair removed figshare checks for action buttons