'use strict';

(function() {
  angular.module('ncsaas')
    .config(['$translateProvider', function($translateProvider) {
        $translateProvider.useLocalStorage();
        $translateProvider.useStaticFilesLoader({
            prefix: 'static/js/i18n/locale-',
            suffix: '.json'
        });
    }]);

  angular.module('ncsaas')
    .value('LANGUAGE', {
        CHOICES: [
            {code: 'en', label: 'English'}
        ],
        DEFAULT: 'en'
    });
})();

'use strict';

(function() {
  angular.module('ncsaas')
    .config(['$translateProvider', function($translateProvider) {
        $translateProvider.useLocalStorage();
        $translateProvider.useStaticFilesLoader({
            prefix: 'static/js/i18n/locale-',
            suffix: '.json'
        });
        $translateProvider.useSanitizeValueStrategy('escaped');
    }]);

  angular.module('ncsaas')
    .value('LANGUAGE', {
        CHOICES: [
            {code: 'en', label: 'English'}
        ],
        DEFAULT: 'en'
    });
})();

Add sanitization strategy to translation


//See gh-tabs-manager.js for use
var TabPane = Ember.Component.extend({
    classNameBindings: ['active'],

    tabsManager: Ember.computed(function () {
        return this.nearestWithProperty('isTabsManager');
    }),

    tab: Ember.computed('tabsManager.tabs.@each', function () {
        var index = this.get('tabsManager.tabPanes').indexOf(this),
            tabs = this.get('tabsManager.tabs');

        return tabs && tabs.objectAt(index);
    }),

    active: Ember.computed.alias('tab.active'),

    // Register with the tabs manager
    registerWithTabs: function () {
        this.get('tabsManager').registerTabPane(this);
    }.on('didInsertElement'),
    unregisterWithTabs: function () {
        this.get('tabsManager').unregisterTabPane(this);
    }.on('willDestroyElement')
});

export default TabPane;

//See gh-tabs-manager.js for use
var TabPane = Ember.Component.extend({
    classNameBindings: ['active'],

    tabsManager: Ember.computed(function () {
        return this.nearestWithProperty('isTabsManager');
    }),

    tab: Ember.computed('tabsManager.tabs.[]', 'tabsManager.tabPanes.[]',
    function () {
        var index = this.get('tabsManager.tabPanes').indexOf(this),
            tabs = this.get('tabsManager.tabs');

        return tabs && tabs.objectAt(index);
    }),

    active: Ember.computed.alias('tab.active'),

    // Register with the tabs manager
    registerWithTabs: function () {
        this.get('tabsManager').registerTabPane(this);
    }.on('didInsertElement'),
    unregisterWithTabs: function () {
        this.get('tabsManager').unregisterTabPane(this);
    }.on('willDestroyElement')
});

export default TabPane;

Fix GhostTab and GhostTabPane array dependencies


const Profile = require('../structs/db/Profile.js')
const Command = require('../structs/Command.js')

async function setupCommands (disableCommands) {
  await Profile.populatePrefixes()
  await Command.initialize()
  if (disableCommands) {
    Command.disable()
  }
}

module.exports = setupCommands

const Profile = require('../structs/db/Profile.js')
const Command = require('../structs/Command.js')

async function setupCommands (disableCommands) {
  await Profile.populatePrefixes()
  await Command.initialize()
  if (disableCommands) {
    Command.disable()
  } else {
    Command.enable()
  }
}

module.exports = setupCommands

Fix commands always being blocked


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

const SIZES = {
  medium: {
    paddingHorizontal: 2,
    paddingVertical: 1,
  },
  small: {
    paddingHorizontal: 2,
  },
};

class StatusLabel extends PureComponent {
  static propTypes = {
    children: PropTypes.node.isRequired,
    className: PropTypes.string,
    color: PropTypes.oneOf(['neutral', 'mint', 'violet', 'ruby', 'gold', 'aqua']),
    size: PropTypes.oneOf(Object.keys(SIZES)),
  };

  static defaultProps = {
    color: 'neutral',
    size: 'medium',
  };

  render() {
    const { children, className, color, size, ...others } = this.props;

    const classNames = cx(theme['label'], theme[color], theme[size], className);

    return (
      <Box className={classNames} element="span" {...SIZES[size]} {...others} data-teamleader-ui="status-label">
        {children}
      </Box>
    );
  }
}

export default StatusLabel;

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

const SIZES = {
  medium: {
    paddingHorizontal: 2,
    paddingVertical: 1,
  },
  small: {
    paddingHorizontal: 2,
  },
};

class StatusLabel extends PureComponent {
  static propTypes = {
    children: PropTypes.any.isRequired,
    className: PropTypes.string,
    color: PropTypes.oneOf(['neutral', 'mint', 'violet', 'ruby', 'gold', 'aqua']),
    size: PropTypes.oneOf(Object.keys(SIZES)),
  };

  static defaultProps = {
    color: 'neutral',
    size: 'medium',
  };

  render() {
    const { children, className, color, size, ...others } = this.props;

    const classNames = cx(theme['label'], theme[color], theme[size], className);

    return (
      <Box className={classNames} element="span" {...SIZES[size]} {...others} data-teamleader-ui="status-label">
        {children}
      </Box>
    );
  }
}

export default StatusLabel;

Change propTypes because React16 allows us to also pass down arrays


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

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

Remove turbolinks from manifest (it doesn't work well with the template)


import test from 'ava';
import AsyncEventEmitter from '../lib/async-event-emitter';
import EventEmitter from 'events';

test("constructor", (t) => {
    const aee = new AsyncEventEmitter();

    t.true(aee instanceof EventEmitter);
});

test.todo("handler are chained based on the promises they return");
test.todo("emit behaves like the original emit");

import test from 'ava';
import AsyncEventEmitter from '../lib/async-event-emitter';
import EventEmitter from 'events';
import sinon from 'sinon';

test("constructor", (t) => {
    const aee = new AsyncEventEmitter();

    t.true(aee instanceof EventEmitter);
});

test("handler are chained based on the promises they return", async (t) => {
    const aee = new AsyncEventEmitter();

    let lastListener;
    const promiseToResolve = new Promise((resolve) => {
        lastListener = sinon.spy(() => {
            resolve();
            return Promise.resolve();
        });
    });

    const listener = sinon.spy(() => Promise.resolve());
    const firstListener = sinon.spy();

    aee.on('test', firstListener);
    aee.on('test', listener);
    aee.on('test', lastListener);

    t.true(aee.emit('test'));

    await promiseToResolve;

    t.true(firstListener.calledOnce);
    t.true(listener.calledOnce);
    t.true(lastListener.calledOnce);
    t.true(firstListener.calledBefore(listener));
    t.true(listener.calledBefore(lastListener));
});

test("emit behaves like the original emit", async (t) => {
    const EVENT = 'test';
    const aee = new AsyncEventEmitter();

    const result = aee.emit(EVENT);
    t.is(typeof result, 'boolean');
    t.false(result);

    let listener;
    const promise = new Promise((resolve) => {
        listener = sinon.spy(() => {
            resolve();
        });
    });
    aee.on(EVENT, listener);

    const resultWith = aee.emit(EVENT);
    t.is(typeof resultWith, 'boolean');
    t.true(resultWith);

    await promise;
    t.true(listener.calledOnce);

    aee.removeListener(EVENT, listener);
});

test("rejecting listener doesn't stop chain", async (t) => {
    const aee = new AsyncEventEmitter();

    let listener;
    const promiseToResolve = new Promise((resolve) => {
        listener = sinon.spy(() => {
            resolve();
            return Promise.resolve();
        });
    });

    const firstListener = sinon.spy(() => Promise.reject());

    aee.on('test', firstListener);
    aee.on('test', listener);

    t.true(aee.emit('test'));

    await promiseToResolve;

    t.true(firstListener.calledOnce);
    t.true(listener.calledOnce);
    t.true(firstListener.calledBefore(listener));
});

Add tests for async event emitter


/**
 * Generates a function that moves the cursor on a Google Chart
 * @private
 * @param {GoogleChart} chart - the in-memory GoogleChart object
 * @returns {VisualCallback} the callback
 */
var googleVisualCallbackMaker = function(chart) {
	return function(series, row) {
		chart.setSelection([
			{
				'row': row,
				'column': series + 1
			}
		])
	}
}

/**
 * Generate a function that can be used to highlight table cells
 * @private
 * @param {HTMLTableElement} table - The in-DOM table element
 * @param {string} className - Name of the CSS highlight class
 * @returns {VisualCallback} The highlighting function
 */
var htmlTableVisualCallbackMaker = function(table, className) {
	return function(series, row) {
		const tds = table.getElementsByTagName('td')
		let cell  // TODO remove
		for (let i = 0; i < tds.length; i++) {
			cell = tds[i]
			cell.classList.remove(className)
		}
		cell = table.getElementsByTagName('td')[row]
		cell.classList.add(className)
	}
}

/**
 * Generates a function that moves the cursor on a C3 Chart
 * @private
 * @param {Object} chart - the in-memory C3 chart object
 * @returns {VisualCallback} the callback
 * @todo define C3 chart type?
 */
var c3VisualCallbackMaker = function(chart) {
	return function(series, row) {
		chart.select(null, [row], true)
	}
}

/**
 * Generates a function that moves the cursor on a Google Chart
 * @private
 * @param {GoogleChart} chart - the in-memory GoogleChart object
 * @returns {VisualCallback} the callback
 */
var googleVisualCallbackMaker = function(chart) {
	return function(series, row) {
		chart.setSelection([
			{
				'row': row,
				'column': series + 1
			}
		])
	}
}

/**
 * Generate a function that can be used to highlight table cells
 * @private
 * @param {HTMLTableElement} table - The in-DOM table element
 * @param {string} className - Name of the CSS highlight class
 * @returns {VisualCallback} The highlighting function
 */
var htmlTableVisualCallbackMaker = function(table, className) {
	return function(series, row) {
		const tds = table.getElementsByTagName('td')
		for (const cell of tds) {
			cell.classList.remove(className)
		}
		tds[row].classList.add(className)
	}
}

/**
 * Generates a function that moves the cursor on a C3 Chart
 * @private
 * @param {Object} chart - the in-memory C3 chart object
 * @returns {VisualCallback} the callback
 * @todo define C3 chart type?
 */
var c3VisualCallbackMaker = function(chart) {
	return function(series, row) {
		chart.select(null, [row], true)
	}
}

Clean up HTML Table visual callback


/* eslint-disable react/forbid-prop-types */
import React, { useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import { useNavigation } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';

const DEFAULT_TAB_CONFIG = { options: { tabBarVisible: false } };
const Tab = createBottomTabNavigator();

/**
 * Simple TabNavigator component managing lazily loaded
 * tabs of components. Slightly different from a regular tab navigator:
 * Managed declaratively through passing props rather than  imperatively
 * using functions on the navigation ref.
 *
 * @prop {Array} tabs             An array of components for each tab.
 * @prop {Number} currentTabIndex The index of the tab to show.
 */
export const TabNavigator = ({ tabs, currentTabIndex }) => {
  const navigatorRef = useRef(useNavigation());

  // When `currentTabIndex` changes, dispatch an action to trigger a switch on the
  // base navigation component to the passed tab index.
  useEffect(() => navigatorRef.current.navigate(String(currentTabIndex)), [currentTabIndex]);

  return (
    <Tab.Navigator>
      {tabs.map((tab, idx) => (
        <Tab.Screen
          {...DEFAULT_TAB_CONFIG}
          name={`${idx}`}
          key={tab.name}
          component={tab.component}
        />
      ))}
    </Tab.Navigator>
  );
};

TabNavigator.propTypes = {
  tabs: PropTypes.array.isRequired,
  currentTabIndex: PropTypes.number.isRequired,
};

/* eslint-disable react/forbid-prop-types */
import React, { useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import { useNavigation } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';

const DEFAULT_TAB_CONFIG = { options: { tabBarVisible: false } };
const Tab = createBottomTabNavigator();

/**
 * Simple TabNavigator component managing lazily loaded
 * tabs of components. Slightly different from a regular tab navigator:
 * Managed declaratively through passing props rather than  imperatively
 * using functions on the navigation ref.
 *
 * @prop {Array} tabs             An array of components for each tab.
 * @prop {Number} currentTabIndex The index of the tab to show.
 */
export const TabNavigator = ({ tabs, currentTabIndex }) => {
  const navigatorRef = useRef(useNavigation());

  // When `currentTabIndex` changes, dispatch an action to trigger a switch on the
  // base navigation component to the passed tab index.
  useEffect(() => navigatorRef.current.navigate(String(currentTabIndex)), [currentTabIndex]);

  return (
    <Tab.Navigator tabBar={() => null}>
      {tabs.map((tab, idx) => (
        <Tab.Screen
          {...DEFAULT_TAB_CONFIG}
          name={`${idx}`}
          key={tab.name}
          component={tab.component}
        />
      ))}
    </Tab.Navigator>
  );
};

TabNavigator.propTypes = {
  tabs: PropTypes.array.isRequired,
  currentTabIndex: PropTypes.number.isRequired,
};

Add default tabbar as null


(function() {
  'use strict';

  angular.module('app', [
    'app.config',
    'app.core',
    'app.feature'
  ]);

  angular.module('app').config([
    '$logProvider', 'LOG', '$locationProvider',
    function($logProvider, log, $locationProvider) {
      // Enable or disable debug logging
      $logProvider.debugEnabled(log.ENABLE_DEBUG);

      $locationProvider.html5Mode(true);
    }
  ]);

  // CONFIG SETTINGS
  angular.module('app.config', [])
    // Log settings
    .constant('LOG', {
      ENABLE_DEBUG: true
    });
})();

(function() {
  'use strict';

  angular.module('app', [
    'app.config',
    'app.core',
    'app.feature'
  ]);

  angular.module('app').config([
    '$logProvider', 'LOG', '$locationProvider',
    function($logProvider, log, $locationProvider) {
      // Enable or disable debug logging
      $logProvider.debugEnabled(log.ENABLE_DEBUG);

      $locationProvider.html5Mode(false);
    }
  ]);

  // CONFIG SETTINGS
  angular.module('app.config', [])
    // Log settings
    .constant('LOG', {
      ENABLE_DEBUG: true
    });
})();

Disable HTML5 mode since our website will consist only of a single page


export default ngModule => {
  ngModule.config(addColorPickerType);

  function addColorPickerType(formlyConfigProvider) {

    var ngModelAttrs = {};

    function camelize(string) {
      string = string.replace(/[\-_\s]+(.)?/g, function(match, chr) {
        return chr ? chr.toUpperCase() : '';
      });
      // Ensure 1st char is always lowercase
      return string.replace(/^([A-Z])/, function(match, chr) {
        return chr ? chr.toLowerCase() : '';
      });
    }

    ngModelAttrs = {};

    // attributes
    angular.forEach([
      'color-picker-format',
      'color-picker-alpha',
      'color-picker-swatch',
      'color-picker-swatch-pos',
      'color-picker-swatch-only',
      'color-picker-pos',
      'color-picker-case'
    ], function(attr) {
      ngModelAttrs[camelize(attr)] = {attribute: attr};
    });

    formlyConfigProvider.setType({
      name: 'colorpicker',
      template: '<color-picker ng-model="model[options.key]"></color-picker>',
      wrapper: ['koappLabel', 'koappHasError'],
      defaultOptions: {
        ngModelAttrs: ngModelAttrs
      }
    });

  }

};

export default ngModule => {
  ngModule.config(addColorPickerType);

  function addColorPickerType(formlyConfigProvider) {

    var ngModelAttrs = {};

    function camelize(string) {
      string = string.replace(/[\-_\s]+(.)?/g, function(match, chr) {
        return chr ? chr.toUpperCase() : '';
      });
      // Ensure 1st char is always lowercase
      return string.replace(/^([A-Z])/, function(match, chr) {
        return chr ? chr.toLowerCase() : '';
      });
    }

    ngModelAttrs = {};

    // attributes
    angular.forEach([
      'color-picker-format',
      'color-picker-alpha',
      'color-picker-swatch',
      'color-picker-swatch-pos',
      'color-picker-swatch-only',
      'color-picker-pos',
      'color-picker-case'
    ], function(attr) {
      ngModelAttrs[camelize(attr)] = {attribute: attr};
    });

    formlyConfigProvider.setType({
      name: 'colorpicker',
      template: '<color-picker class="cpStyles" ng-model="model[options.key]"></color-picker>',
      wrapper: ['koappLabel', 'koappHasError'],
      defaultOptions: {
        ngModelAttrs: ngModelAttrs
      }
    });

  }

};

Add a class to color-picker tag


'use strict';

function _InputManager() {
  EventEmitter.call(this);

  var self = this;
  function addElCapture(name, outname) {
    if (!outname) {
      outname = name;
    }
    $(function() {
      renderer.domElement.addEventListener(name, function(e) {
        self._handleEvent(outname, e);
      }, false );
    });
  }
  function addCapture(name, outname) {
    if (!outname) {
      outname = name;
    }
    document.addEventListener(name, function(e) {
      self._handleEvent(outname, e);
    }, false );
  }

  addElCapture('mousedown');
  addElCapture('mouseup');
  addElCapture('mousemove');
  addCapture('keydown');
  addCapture('keyup');
  addElCapture('touchstart');
  addElCapture('touchend');
  addElCapture('touchmove');
  addElCapture('mousewheel');
  addElCapture('DOMMouseScroll', 'mousewheel');

  document.addEventListener('contextmenu', function(e) {
    e.preventDefault();
  });
}
_InputManager.prototype = new EventEmitter();

_InputManager.prototype._handleEvent = function(name, e) {
  this.emit(name, e);
};

var InputManager = new _InputManager();

'use strict';

function _InputManager() {
  EventEmitter.call(this);

  var self = this;
  function addElCapture(name, outname) {
    if (!outname) {
      outname = name;
    }
    $(function() {
      renderer.domElement.addEventListener(name, function(e) {
        self._handleEvent(outname, e);
      }, false );
    });
  }
  function addCapture(name, outname) {
    if (!outname) {
      outname = name;
    }
    document.addEventListener(name, function(e) {
      self._handleEvent(outname, e);
    }, false );
  }

  addElCapture('mousedown');
  addCapture('mouseup');
  addCapture('mousemove');
  addCapture('keydown');
  addCapture('keyup');
  addElCapture('touchstart');
  addElCapture('touchend');
  addElCapture('touchmove');
  addElCapture('mousewheel');
  addElCapture('DOMMouseScroll', 'mousewheel');

  document.addEventListener('contextmenu', function(e) {
    e.preventDefault();
  });
}
_InputManager.prototype = new EventEmitter();

_InputManager.prototype._handleEvent = function(name, e) {
  this.emit(name, e);
};

var InputManager = new _InputManager();

Make InputManager mouseup and mousemove global events.


const themeProps = [
  'primary',
  'secondary',
  'background',
  'on-primary',
  'on-secondary',
  'primary-bg',
  'secondary-bg',
  'text-primary-on-light',
  'text-secondary-on-light',
  'text-hint-on-light',
  'text-disabled-on-light',
  'text-icon-on-light',
  'text-primary-on-dark',
  'text-secondary-on-dark',
  'text-hint-on-dark',
  'text-disabled-on-dark',
  'text-icon-on-dark'
]

const themeClassMixin = {
  props: {
    theming: {
      type: String,
      default: ''
    }
  },
  mounted () {
    if (themeProps.indexOf(this.theming) > -1) {
      this.$el.classList.add('mdc-theme--' + this.theming)
    }
  }
}
export default themeClassMixin

const themeProps = [
  'primary',
  'secondary',
  'background',
  'surface',
  'on-primary',
  'on-secondary',
  'on-surface',
  'primary-bg',
  'secondary-bg',
  'text-primary-on-light',
  'text-secondary-on-light',
  'text-hint-on-light',
  'text-disabled-on-light',
  'text-icon-on-light',
  'text-primary-on-dark',
  'text-secondary-on-dark',
  'text-hint-on-dark',
  'text-disabled-on-dark',
  'text-icon-on-dark'
]

const themeClassMixin = {
  props: {
    theming: {
      type: String,
      default: ''
    }
  },
  mounted () {
    if (themeProps.indexOf(this.theming) > -1) {
      this.$el.classList.add('mdc-theme--' + this.theming)
    }
  }
}
export default themeClassMixin

Add surface color to theming mixin.


/*
 * Handy little util function that highlights all files in the stack trace not
 * in the node_modules/ folder.
 *
 * @module warepot/util/colorize-stack
 */
'use strict'

// modules > native
const p = require('path')

// modules > 3rd party
const chalk = require('chalk')

/*
 * Colorizes a stack trace.
 *
 * {String} - String
 *
 * @returns A colored string of the stack trace
 */
module.exports = function (stack) {
  if (!stack) return

  stack = stack.replace(/\/[\/\w.-]+/g, function (match) {
    if (match.indexOf('node_modules') > -1)
      return match

    const dir = p.dirname(process.cwd())
    const index = match.indexOf(dir)

    if (index > -1) {
      const endIndex = index + dir.length
      return match.slice(0, endIndex) + chalk.yellow(match.slice(endIndex))
    }

    return chalk.yellow(match)
  })

  return stack
}

/*
 * Handy little util function that highlights all files in the stack trace not
 * in the node_modules/ folder.
 *
 * Copied from warepot/util/colorize-stack (commit 86b66ea37abc0ecba43c39381ada76b2a0bffe67)
 *
 * @module warepot/util/colorize-stack
 */
'use strict'

// modules > native
const p = require('path')

// modules > 3rd party
const chalk = require('chalk')

/*
 * Colorizes a stack trace.
 *
 * {String} - String
 *
 * @returns A colored string of the stack trace
 */
module.exports = function (stack, html) {
  if (!stack) return

  return stack.replace(/\/[\/\w.-]+/g, (match) => {
    if (match.indexOf('node_modules') > -1)
      return match

    const dir = p.dirname(process.cwd())
    const index = match.indexOf(dir)

    if (index > -1) {
      const endIndex = index + dir.length

      if (html)
        return match.slice(0, endIndex) + match.slice(endIndex).bold()

      return match.slice(0, endIndex) + chalk.yellow(match.slice(endIndex))
    }

    if (html)
      return match.bold()

    return chalk.yellow(match)
  })
}

Enable html styling of stack.


import $ from 'jquery'

export default (modal) => {
    $(document).on('click', '.finish.button', function () {
        chrome.runtime.sendMessage({ eventType: 'pop' });
        modal.modal('show');
    });
};

import $ from 'jquery'

export default (modal) => {
    $(document).on('click', '.finish.button', function () {
        setTimeout(function() {
            chrome.runtime.sendMessage({ eventType: 'pop' });
            modal.modal('show');
        }, 0)
    });
};

Set timeout on modal popper event listener


// Copyright 2012 Traceur Authors.
//
// Licensed under the Apache License, Version 2.0 (the 'License');
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an 'AS IS' BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {
  EXPRESSION_STATEMENT,
  LITERAL_EXPRESSION
} from '../syntax/trees/ParseTreeType.js';
import {STRING} from '../syntax/TokenType.js';

function isStringExpressionStatement(tree) {
  return tree.type === EXPRESSION_STATEMENT &&
      tree.expression.type === LITERAL_EXPRESSION &&
      tree.expression.literalToken.type === STRING;
}

/**
 * Prepends |statements| with the |statementsToPrepend| making sure that any
 * leading directives, like 'use strict', are kept at the top of the statements.
 * @param {Array.<ParseTree>} statements
 * @param {ParseTree} statementsToPrepend
 * @return {Array.<ParseTree>}
 */
export function prependStatements(statements, ...statementsToPrepend) {
  if (!statements.length)
    return statementsToPrepend;

  if (!statementsToPrepend.length)
    return statements;

  var transformed  = [];
  var inProlog = true;
  statements.forEach((statement) => {
    if (inProlog && !isStringExpressionStatement(statement)) {
      transformed.push(...statementsToPrepend);
      inProlog = false;
    }
    transformed.push(statement);
  });
  return transformed;
}

// Copyright 2012 Traceur Authors.
//
// Licensed under the Apache License, Version 2.0 (the 'License');
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an 'AS IS' BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * Prepends |statements| with the |statementsToPrepend| making sure that any
 * leading directives, like 'use strict', are kept at the top of the statements.
 * @param {Array.<ParseTree>} statements
 * @param {ParseTree} statementsToPrepend
 * @return {Array.<ParseTree>}
 */
export function prependStatements(statements, ...statementsToPrepend) {
  if (!statements.length)
    return statementsToPrepend;

  if (!statementsToPrepend.length)
    return statements;

  var transformed  = [];
  var inProlog = true;
  statements.forEach((statement) => {
    if (inProlog && !statement.isDirectivePrologue()) {
      transformed.push(...statementsToPrepend);
      inProlog = false;
    }
    transformed.push(statement);
  });
  return transformed;
}

Use ParseTree.isDirectivePrologue() to simplify PrependStatement


const test = require('tape')
const BorrowState = require('borrow-state')

test(`read-only-safety`, (t) => {
  t.plan(2)
  let hasHappened = []
  let myState = new BorrowState()
  myState.block().then((state) => {
    hasHappened.push(1)
    state.foo = Math.PI
    state.unblock()
  })
  myState.block('r').then((state) => {
    hasHappened.push(2)
    state.foo = 5
    state.unblock()
  })
  // The following is not set to read-only so that it can occur *after* the previous
  myState.block().then((state) => {
    hasHappened.push(3)
    t.equal(state.foo, Math.PI, 'The state should still be set to pi')
    t.equal(hasHappened, [1, 2, 3], 'The operations happened in the right order')
    state.unblock()
  })
})

test(`read-only-safety, chained`, (t) => {
  t.plan(2)
  let hasHappened = []
  let myState = new BorrowState()
  myState.block().then((state) => {
    hasHappened.push(1)
    state.foo = Math.PI
    state.unblock()
  })
  myState.block('r').then((state) => {
    hasHappened.push(2)
    state.foo = 5
    return state
  }).then((state) => {
    hasHappened.push(3)
    t.equal(state.foo, Math.PI, 'The state should still be set to pi')
    t.equal(hasHappened, [1, 2, 3], 'The operations happened in the right order')
    state.unblock()
  })
})

Add the same test, but with promise chaining


import Bacon from 'baconjs';
import Immutable from 'immutable';

export function Madera(reducers, actionTypes){
    const action$ = new Bacon.Bus();
    window.action$ = action$;
    action$.actionTypes = actionTypes;

    const state$ = Bacon.update(
        Immutable.Map(),
        // Last defined has precedence
        ...reducers.map(reducer => reducer(action$))
                   .reduce((a, b) => a.concat(b))
    );

    const init = () => action$.push({type: 'INIT'});

    return { action$, state$, init };
}

import Bacon from 'baconjs';
import Immutable from 'immutable';

export function Madera(reducers, actionTypes){
    const action$ = new Bacon.Bus();
    action$.actionTypes = actionTypes;

    const state$ = Bacon.update(
        Immutable.Map(),
        ...reducers.map(reducer => reducer(action$))
                   .reduce((a, b) => a.concat(b))
    );

    const init = () => action$.push({type: 'INIT'});

    return { action$, state$, init };
}

Remove dependency on browser environment


var _complement = require('./internal/_complement');
var _curry2 = require('./internal/_curry2');
var _dispatchable = require('./internal/_dispatchable');
var _xany = require('./internal/_xany');
var any = require('./any');

/**
 * Returns `true` if no elements of the list match the predicate, `false`
 * otherwise.
 *
 * Dispatches to the `any` method of the second argument, if present.
 *
 * @func
 * @memberOf R
 * @since v0.12.0
 * @category List
 * @sig (a -> Boolean) -> [a] -> Boolean
 * @param {Function} fn The predicate function.
 * @param {Array} list The array to consider.
 * @return {Boolean} `true` if the predicate is not satisfied by every element, `false` otherwise.
 * @see R.all, R.any
 * @example
 *
 *      R.none(R.isNaN, [1, 2, 3]); //=> true
 *      R.none(R.isNaN, [1, 2, 3, NaN]); //=> false
 */
module.exports = _curry2(_complement(_dispatchable('any', _xany, any)));

var _complement = require('./internal/_complement');
var _curry2 = require('./internal/_curry2');
var _dispatchable = require('./internal/_dispatchable');
var _xany = require('./internal/_xany');
var any = require('./any');

/**
 * Returns `true` if no elements of the list match the predicate, `false`
 * otherwise.
 *
 * Dispatches to the `any` method of the second argument, if present.
 *
 * @func
 * @memberOf R
 * @since v0.12.0
 * @category List
 * @sig (a -> Boolean) -> [a] -> Boolean
 * @param {Function} fn The predicate function.
 * @param {Array} list The array to consider.
 * @return {Boolean} `true` if the predicate is not satisfied by every element, `false` otherwise.
 * @see R.all, R.any
 * @example
 *
 *      var isEven = n => n % 2 === 0;
 *
 *      R.none(isEven, [1, 3, 5, 7, 9, 11]), //=> true
 *      R.none(isEven, [1, 3, 5, 7, 8, 11]), //=> false
 */
module.exports = _curry2(_complement(_dispatchable('any', _xany, any)));

Remove R.isNaN leftover in documentation


function formatTime () {
  //
}

/**
 * Trims off null bytes and any additional garbage bytes from a buffer.
 * @param {Buffer} buffer Buffer to parse string from
 * @returns {string} string
 */
function trimString (buffer) {
  let index = buffer.length
  for (let i = 0; i < index; i++) {
    if (buffer[i] === 0x00) {
      index = i
      break
    }
  }
  return buffer.toString('ascii', 0, index)
}

module.exports = {
  formatTime,
  trimString
}

function formatTime () {
  //
}

/**
 * Pads string with null bytes.
 * @param {string} str String to pad
 * @param {integer} pad Amount to pad
 * @returns {string} string
 */
function nullpadString (str, pad) {
  // check if ascii or padding shorter than string
  if ((!/^[\x00-\x7F]*$/.test(str)) || (str.length > pad)) return undefined
  return str + Array(pad - str.length + 1).join('\x00')
}

/**
 * Trims off null bytes and any additional garbage bytes from a buffer.
 * @param {Buffer} buffer Buffer to parse string from
 * @returns {string} string
 */
function trimString (buffer) {
  let index = buffer.length
  for (let i = 0; i < index; i++) {
    if (buffer[i] === 0x00) {
      index = i
      break
    }
  }
  return buffer.toString('ascii', 0, index)
}

module.exports = {
  formatTime,
  nullpadString,
  trimString
}

Add simple nullpadString function for testing


'use strict'

require('colors')
const config = require('./config.json')

let io
const datas = {
  stringData: 'foo',
  intData: 5,
  floatData: 0.5,
  boolData: true
}

io = require('socket.io')(config.server.port)
io.on('connection', function (socket) {
  console.log('connection'.bold.green)

  let emitInterval = setInterval(() => {
    socket.emit('server-event', datas)
    socket.emit('ping')
  }, 2000)

  socket
  .on('disconnect', () => {
    console.log('disconnect'.bold.red)
    clearInterval(emitInterval)
  })
  .on('pong', (data) => {
    console.log('pong'.blue, data)
  })
})

'use strict'

require('colors')
const config = require('./config.json')

let io
const datas = {
  stringData: 'foo',
  intData: 5,
  floatData: 0.5,
  boolData: true
}

io = require('socket.io')(config.server.port)
io.on('connection', function (socket) {
  console.log('connection'.bold.green)

  console.log('query:', socket.handshake.query)

  let emitInterval = setInterval(() => {
    socket.emit('server-event', datas)
    socket.emit('ping')
  }, 2000)

  socket
  .on('disconnect', () => {
    console.log('disconnect'.bold.red)
    clearInterval(emitInterval)
  })
  .on('pong', (data) => {
    console.log('pong'.blue, data)
  })
})

Add query log to example server


// @flow

import $ from 'jquery'
import 'bootstrap/dist/css/bootstrap.min.css'
require('bootstrap')

$(document).ready(() => {
  // TODO: Add event handlers
})

// @flow

import $ from 'jquery'
import 'bootstrap/dist/css/bootstrap.min.css'
require('bootstrap')

$(document).ready(() => {
  // TODO: Add event handlers
  $('userForm').submit(() => {
    // TODO: Add code here
  })
})

Add form submission handler draft


import { visit, currentURL } from '@ember/test-helpers';
import { setupApplicationTest } from 'ember-qunit';
import { module, test } from 'qunit';

import { setupPolly } from 'skylines/tests/test-helpers/setup-polly';

module('Acceptance | flight upload', function (hooks) {
  setupApplicationTest(hooks);
  setupPolly(hooks, { recordIfMissing: false });

  module('visiting /flights/upload (unauthenticated)', function (hooks) {
    hooks.beforeEach(async function () {
      await visit('/flights/upload');
    });

    test('redirects to the login page', function (assert) {
      assert.equal(currentURL(), '/login');
    });
  });
});

import { visit, currentURL } from '@ember/test-helpers';
import { setupApplicationTest } from 'ember-qunit';
import { module, test } from 'qunit';

import { setupMirage } from 'ember-cli-mirage/test-support';

module('Acceptance | flight upload', function (hooks) {
  setupApplicationTest(hooks);
  setupMirage(hooks);

  module('visiting /flights/upload (unauthenticated)', function (hooks) {
    hooks.beforeEach(async function () {
      await visit('/flights/upload');
    });

    test('redirects to the login page', function (assert) {
      assert.equal(currentURL(), '/login');
    });
  });
});

Use Mirage.js instead of Polly.js


function insert_version_links() {
    var labels = ['dev', '0.8.0', '0.7.0', '0.6', '0.5', '0.4', '0.3'];

    for (i = 0; i < labels.length; i++){
        open_list = '
'
        if (typeof(DOCUMENTATION_OPTIONS) !== 'undefined') {
            if ((DOCUMENTATION_OPTIONS['VERSION'] == labels[i]) ||
                (DOCUMENTATION_OPTIONS['VERSION'].match(/dev$/) && (i == 0))) {
                open_list = '
'
            }
        }
        document.write(open_list);
        document.write('skimage VERSION
\n'
                        .replace('VERSION', labels[i])
                        .replace('URL', 'http://scikit-image.org/docs/' + labels[i]));
    }
}

var versions = ['dev', '0.8.0', '0.7.0', '0.6', '0.5', '0.4', '0.3'];

function insert_version_links() {
    for (i = 0; i < versions.length; i++){
        open_list = '
'
        if (typeof(DOCUMENTATION_OPTIONS) !== 'undefined') {
            if ((DOCUMENTATION_OPTIONS['VERSION'] == versions[i]) ||
                (DOCUMENTATION_OPTIONS['VERSION'].match(/dev$/) && (i == 0))) {
                open_list = '
'
            }
        }
        document.write(open_list);
        document.write('skimage VERSION
\n'
                        .replace('VERSION', versions[i])
                        .replace('URL', 'http://scikit-image.org/docs/' + versions[i]));
    }
}

function stable_version() {
    return versions[1];
}

Add javascript for locating latest stable version.


var childProcess = require('child_process');

// Exit the process if the command failed and only call the callback if the
// command succeed, output of the command would also be piped.
exports.safeExec = function(command, options, callback) {
  if (!callback) {
    callback = options;
    options = {};
  }
  var child = childProcess.exec(command, options, function(error, stdout, stderr) {
    if (error)
      process.exit(error.code || 1);
    else
      callback(null);
  });
  child.stderr.pipe(process.stderr);
  child.stdout.pipe(process.stdout);
}

// Same with safeExec but call child_process.spawn instead.
exports.safeSpawn = function(command, args, options, callback) {
  if (!callback) {
    callback = options;
    options = {};
  }
  var child = childProcess.spawn(command, args, options);
  child.stderr.pipe(process.stderr);
  child.stdout.pipe(process.stdout);
  child.on('exit', function(code) {
    if (code != 0)
      process.exit(code);
    else
      callback(null);
  });
}

var childProcess = require('child_process');

// Exit the process if the command failed and only call the callback if the
// command succeed, output of the command would also be piped.
exports.safeExec = function(command, options, callback) {
  if (!callback) {
    callback = options;
    options = {};
  }
  if (!options)
    options = {};

  // This needed to be increase for `apm test` runs that generate tons of failures
  // The default is 200KB.
  options.maxBuffer = 1024 * 1024;

  var child = childProcess.exec(command, options, function(error, stdout, stderr) {
    if (error)
      process.exit(error.code || 1);
    else
      callback(null);
  });
  child.stderr.pipe(process.stderr);
  child.stdout.pipe(process.stdout);
}

// Same with safeExec but call child_process.spawn instead.
exports.safeSpawn = function(command, args, options, callback) {
  if (!callback) {
    callback = options;
    options = {};
  }
  var child = childProcess.spawn(command, args, options);
  child.stderr.pipe(process.stderr);
  child.stdout.pipe(process.stdout);
  child.on('exit', function(code) {
    if (code != 0)
      process.exit(code);
    else
      callback(null);
  });
}

Increase max buffer to 1MB


var debug = require('debug')('intents');
var parse = require('co-body');

module.exports.push = function (runtime) {
  return function * (data, next) {
    if (this.method !== 'POST') {
      return yield next;
    }

    var intent = yield parse.json(this, { limit: '10kb' });

    if (!intent.type) {
      this.throw(400, 'intent must have a type!');
    }

    debug('registering intent', intent);

    var intents = runtime.db.get('intents');
    yield intents.insert(intent);
    this.body = 'OK!';
  };
};

var debug = require('debug')('intents');
var parse = require('co-body');

module.exports.push = function (runtime) {
  return function * (data, next) {
    if (this.method !== 'POST') {
      return yield next;
    }

    var intent = yield parse.json(this, { limit: '10kb' });

    if (!intent.type) {
      this.throw(400, 'intent must have a type!');
    }

    debug('registering intent', intent);

    var intents = runtime.db.get('intents');
    yield intents.insert(intent);

    // Return the user record as a response.
    var users = runtime.db.get('users');
    var user = yield users.find({
        userId: intent.userId
      }, {
        userId: true,
        statAdReplaceCount: true
      });
    this.body = user[0];
  };
};

Return user information on intent saving.


import { describe } from 'riteway';

import dsm from '../dsm';

const actionStates = [
  ['initialize', 'signed out',
    ['sign in', 'authenticating'
      // ['report error', 'error',
      //   ['handle error', 'signed out']
      // ],
      // ['sign-in success', 'signed in']
    ]
  ]
];

const { initialize, reducer, signIn } = dsm({
  component: 'user-authentication',
  description: 'authenticate user',
  actionStates
});

const createState = ({
  status = 'signed out',
  payload: { type = 'empty'} = {}
} = {}) => ({ status, payload: { type } });

describe('userAuthenticationReducer', async should => {
  {
    const {assert} = should('use "signed out" as initialized state');

    assert({
      given: '["initialize", "signed out", /*...*/',
      actual: reducer(),
      expected: createState()
    });
  }

  {
    const {assert} = should('transition into authenticating state');
    const initialState = reducer(undefined, initialize());

    assert({
      given: 'signed out initial state & signIn action',
      actual: reducer(initialState, signIn()),
      expected: createState()
    });
  }
});

import { describe } from 'riteway';

import dsm from '../dsm';

const SIGNED_OUT = 'signed_out';
const AUTHENTICATING = 'authenticating';

const actionStates = [
  ['initialize', SIGNED_OUT,
    ['sign in', AUTHENTICATING
      // ['report error', 'error',
      //   ['handle error', 'signed out']
      // ],
      // ['sign-in success', 'signed in']
    ]
  ]
];

const { reducer, actionCreators: { initialize, signIn } } = dsm({
  component: 'user-authentication',
  description: 'authenticate user',
  actionStates
});

const createState = ({
  status = SIGNED_OUT,
  payload = { type: 'empty' }
} = {}) => ({ status, payload });

describe('userAuthenticationReducer', async should => {
  {
    const {assert} = should('use "signed out" as initialized state');

    assert({
      given: '["initialize", "signed out", /*...*/',
      actual: reducer(),
      expected: createState()
    });
  }

  {
    const {assert} = should('transition into authenticating state');
    const initialState = reducer(undefined, initialize());

    assert({
      given: 'signed out initial state & signIn action',
      actual: reducer(initialState, signIn()),
      expected: createState({ status: 'authenticating' })
    });
  }
});

Fix a few issues in the test file


// FeedbackLite Widget
// ===================
var h = document.getElementsByTagName('head')[0];
(function(){
var fc = document.createElement('link'); fc.type = 'text/css'; fc.rel = 'stylesheet';
fc.href = 'https://product.feedbacklite.com/feedbacklite.css'; h.appendChild(fc);
})();
var fbl = {'campaign':{'id':2339, 'type':1, 'size':1, 'position':7, 'tab':5, 'control':1, 'analytics':2}};
(function(){
var fj = document.createElement('script'); fj.type = 'text/javascript';
fj.async = true; fj.src = 'https://product.feedbacklite.com/feedbacklite.js'; h.appendChild(fj);
})();

// FAB Button
// ===================
/*
(function() {
  var fab = '
mail
';
  $('.feedback-wrapper').html(fab);
})();
*/

Use FeedbackLite widget for now


import '../client_helper';

import UserNamePrompter from '../../client/username-prompter';

describe('UserNamePrompter', () => {
  it('is visible if the username is null');

  it('is hidden if the username is present');

  describe('when the button is clicked', () => {
    it('calls the signin call back with the username');
  });

  describe('on hide', () => {
    it('stays open');
  });
});

import '../client_helper';
import React from 'react';
import ReactTestUtils from 'react-addons-test-utils';
import { Modal } from 'react-bootstrap';

import UserNamePrompter from '../../client/username-prompter';

describe('UserNamePrompter', () => {
  const onEnter = jasmine.createSpy('onEnter');
  function createUserNamePrompter(props = {}) {
    return ReactTestUtils.renderIntoDocument(<UserNamePrompter
      user={props.user || null} onEnter={props.onEnter || onEnter} />);
  }
  it('is visible if the username is undefined', () => {
    const component = createUserNamePrompter({user: undefined});
    // TODO test via class name
    const modal = ReactTestUtils.findRenderedComponentWithType(component, Modal);
    expect(modal.props.show).toBe(true);
  });

  it('is hidden if the username is present', () => {
    const component = createUserNamePrompter({user: 'jim'});
    const modal = ReactTestUtils.findRenderedComponentWithType(component, Modal);
    expect(modal.props.show).toBe(false);
  });

  describe('when the button is clicked', () => {
    it('calls the signin call back with the username');
  });

  describe('on hide', () => {
    it('stays open');
  });
});

Test showing/hiding a username modal prompt


var inspect  = require('eyes').inspector({ stream: null });

module.exports = {

  index: function(req, res, next){

    process.database.games.find(function(error, games){
      if(error){res.send(error); return false;}

      games.toArray(function(err, results){
        if(err){res.send(err); return false;}

        console.log(inspect(results));
        res.send(results);
      });

    });

  }

};

var inspect  = require('eyes').inspector({ stream: null });

module.exports = {

  index: function(req, res, next){
    var project_id = req.query.project_id;

    process.database.games.find({project_id: project_id}, function(error, games){
      if(error){res.send(error); return false;}

      games.toArray(function(err, results){
        if(err){res.send(err); return false;}

        res.send(results);
      });

    });

  },

  create: function(req, res, next){

    var game = {
      _id: req.body._id,
      name: req.body.name,
      project_id: req.body.projectId
    };

    process.database.games.save(game, function(error, savedGame){
      if(error){res.send(error); return false;}

      res.send({sucess: true});
    });

  }

};

Create game logic / fetch games form current project


const LogHandler = require('../../utils/logHandler');

class ProcessLogsApi {
  constructor({embark, processName, silent}) {
    this.embark = embark;
    this.processName = processName;
    this.logger = this.embark.logger;
    this.events = this.embark.events;
    this.logHandler = new LogHandler({events: this.events, logger: this.logger, processName: this.processName, silent});

    this.registerAPICalls();
  }

  registerAPICalls() {
    const apiRoute = '/embark-api/process-logs/' + this.processName;
    this.embark.registerAPICall(
      'ws',
      apiRoute,
      (ws, _req) => {
        this.events.on('process-log-' + this.processName, function (log) {
          ws.send(JSON.stringify(log), () => {});
        });
      }
    );
    this.embark.registerAPICall(
      'get',
      '/embark-api/process-logs/' + this.processName,
      (req, res) => {
        let limit = parseInt(req.query.limit, 10);
        if (!Number.isInteger(limit)) limit = 0;
        const result = this.logHandler.logs.map((log, id) => Object.assign(log, {id})).slice(limit);
        res.send(JSON.stringify(result));
      }
    );
  }
}

module.exports = ProcessLogsApi;

const LogHandler = require('../../utils/logHandler');

class ProcessLogsApi {
  constructor({embark, processName, silent}) {
    this.embark = embark;
    this.processName = processName;
    this.logger = this.embark.logger;
    this.events = this.embark.events;
    this.logHandler = new LogHandler({events: this.events, logger: this.logger, processName: this.processName, silent});

    this.registerAPICalls();
  }

  registerAPICalls() {
    const apiRoute = '/embark-api/process-logs/' + this.processName;
    this.embark.registerAPICall(
      'ws',
      apiRoute,
      (ws, _req) => {
        this.events.on('process-log-' + this.processName, function (log) {
          ws.send(JSON.stringify(log), () => {});
        });
      }
    );
    this.embark.registerAPICall(
      'get',
      '/embark-api/process-logs/' + this.processName,
      (req, res) => {
        let limit = parseInt(req.query.limit, 10);
        if (!Number.isInteger(limit)) limit = 0;
        const result = this.logHandler.logs.slice(limit * -1);
        res.send(JSON.stringify(result));
      }
    );
  }
}

module.exports = ProcessLogsApi;

Fix process logs not returning


/* @flow */
import { connect } from 'react-redux';

import React, { PureComponent } from 'react';
import { FlatList } from 'react-native';

import { Popup } from '../common';
import EmojiRow from '../emoji/EmojiRow';
import getFilteredEmojiList from '../emoji/getFilteredEmojiList';
import type { GlobalState, RealmEmojiState } from '../types';
import { getActiveRealmEmojiById } from '../selectors';

type Props = {
  filter: string,
  realmEmoji: RealmEmojiState,
  onAutocomplete: (name: string) => void,
};

const MAX_CHOICES = 30;

class EmojiAutocomplete extends PureComponent<Props> {
  props: Props;

  render() {
    const { filter, realmEmoji, onAutocomplete } = this.props;
    const emojis = getFilteredEmojiList(filter, realmEmoji);

    if (emojis.length === 0) {
      return null;
    }

    return (
      <Popup>
        <FlatList
          keyboardShouldPersistTaps="always"
          initialNumToRender={12}
          data={emojis.slice(0, MAX_CHOICES)}
          keyExtractor={item => item}
          renderItem={({ item }) => <EmojiRow name={item} onPress={() => onAutocomplete(item)} />}
        />
      </Popup>
    );
  }
}

export default connect((state: GlobalState) => ({
  realmEmoji: getActiveRealmEmojiById(state),
}))(EmojiAutocomplete);

/* @flow */
import { connect } from 'react-redux';

import React, { PureComponent } from 'react';
import { FlatList } from 'react-native';

import { Popup } from '../common';
import EmojiRow from '../emoji/EmojiRow';
import getFilteredEmojiList from '../emoji/getFilteredEmojiList';
import type { GlobalState, RealmEmojiState } from '../types';
import { getActiveRealmEmojiById } from '../selectors';

type Props = {
  filter: string,
  realmEmoji: RealmEmojiState,
  onAutocomplete: (name: string) => void,
};

const MAX_CHOICES = 30;

class EmojiAutocomplete extends PureComponent<Props> {
  props: Props;

  onAutocomplete = (name: string): void => {
    this.props.onAutocomplete(name);
  };

  render() {
    const { filter, realmEmoji } = this.props;
    const emojis = getFilteredEmojiList(filter, realmEmoji);

    if (emojis.length === 0) {
      return null;
    }

    return (
      <Popup>
        <FlatList
          keyboardShouldPersistTaps="always"
          initialNumToRender={12}
          data={emojis.slice(0, MAX_CHOICES)}
          keyExtractor={item => item}
          renderItem={({ item }) => <EmojiRow name={item} onPress={this.onAutocomplete} />}
        />
      </Popup>
    );
  }
}

export default connect((state: GlobalState) => ({
  realmEmoji: getActiveRealmEmojiById(state),
}))(EmojiAutocomplete);

Save new function creation for each row.


(function( window, $, cue, undefined ) {
	'use strict';

	$.extend( MediaElementPlayer.prototype, {
		buildcueprevioustrack: function( player, controls ) {
			$( '
' +
					'<button type="button" aria-controls="' + player.id + '" title="' + cue.l10n.previousTrack + '"></button>' +
					'
' )
				.appendTo( controls )
				.on( 'click.cue', function() {
					var state,
						track = player.cueGetCurrentTrack() || {};

					state = $.extend({}, {
						currentTime: media.currentTime,
						duration: media.duration,
						src: media.src
					});

					player.$node.trigger( 'skipBack.cue', [ state, track ] );
					player.cuePlayPreviousTrack();
				});
		},

		// @todo Go to previous playable track.
		cuePlayPreviousTrack: function() {
			var player = this,
				index = player.cueCurrentTrack - 1 < 0 ? player.options.cuePlaylistTracks.length - 1 : player.cueCurrentTrack - 1;

			player.$node.trigger( 'previousTrack.cue', player );
			player.cueSetCurrentTrack( index );
		}
	});

})( this, jQuery, window.cue );

(function( window, $, cue, undefined ) {
	'use strict';

	$.extend( MediaElementPlayer.prototype, {
		buildcueprevioustrack: function( player, controls, layers, media ) {
			$( '
' +
					'<button type="button" aria-controls="' + player.id + '" title="' + cue.l10n.previousTrack + '"></button>' +
					'
' )
				.appendTo( controls )
				.on( 'click.cue', function() {
					var state,
						track = player.cueGetCurrentTrack() || {};

					state = $.extend({}, {
						currentTime: media.currentTime,
						duration: media.duration,
						src: media.src
					});

					player.$node.trigger( 'skipBack.cue', [ state, track ] );
					player.cuePlayPreviousTrack();
				});
		},

		// @todo Go to previous playable track.
		cuePlayPreviousTrack: function() {
			var player = this,
				index = player.cueCurrentTrack - 1 < 0 ? player.options.cuePlaylistTracks.length - 1 : player.cueCurrentTrack - 1;

			player.$node.trigger( 'previousTrack.cue', player );
			player.cueSetCurrentTrack( index );
		}
	});

})( this, jQuery, window.cue );

Declare the media argument in the previous track feature.


pref("extensions.{e4a8a97b-f2ed-450b-b12d-ee082ba24781}.description", "chrome://greasemonkey/locale/greasemonkey.properties");
pref("greasemonkey.fileIsGreaseable", false);
pref("greasemonkey.globalExcludes", '[]');
pref("greasemonkey.unmhtIsGreaseable", false);
pref("greasemonkey.enableScriptRefreshing", true);
pref("greasemonkey.uninstallPreferences", true);
pref("greasemonkey.enableUpdateChecking", true);
pref("greasemonkey.minDaysBetweenUpdateChecks", 7);
pref("greasemonkey.haveInsertedToolbarbutton", false);
pref("greasemonkey.logChrome", false);

pref("extensions.{e4a8a97b-f2ed-450b-b12d-ee082ba24781}.description", "chrome://greasemonkey/locale/greasemonkey.properties");
pref("greasemonkey.fileIsGreaseable", false);
pref("greasemonkey.globalExcludes", '[]');
pref("greasemonkey.unmhtIsGreaseable", false);
pref("greasemonkey.enableScriptRefreshing", true);
pref("greasemonkey.uninstallPreferences", true);
pref("greasemonkey.enableUpdateChecking", false);
pref("greasemonkey.minDaysBetweenUpdateChecks", 7);
pref("greasemonkey.haveInsertedToolbarbutton", false);
pref("greasemonkey.logChrome", false);

Disable update checking by default.


/*
 * ready
 * Watch for when an element becomes available in the DOM
 * @param {String} selector
 * @param {Function} fn
 */

(function(win){
    'use strict';

    var listeners = [],
    doc = win.document,
    MutationObserver = win.MutationObserver || win.WebKitMutationObserver,
    observer;

    function ready(selector, fn){
        // Store the selector and callback to be monitored
        listeners.push({
            selector: selector,
            fn: fn
        });
        if(!observer){
            // Watch for changes in the document
            observer = new MutationObserver(checkListeners);
            observer.observe(doc.documentElement, {
                childList: true,
                subtree: true
            });
        }
        // Check if the element is currently in the DOM
        check(selector, fn);
    }

    function checkListeners(){
        // Check the DOM for elements matching a stored selector
        for(var i = 0, len = listeners.length, listener; i < len; i++){
            listener = listeners[i];
            check(listener.selector, listener.fn);
        }
    }

    function check(selector, fn){
        // Query for elements matching the specified selector
        var elements = doc.querySelectorAll(selector), i = 0, len = elements.length, element;
        for(; i < len; i++){
            element = elements[i];
            // Make sure the callback isn't invoked with the
            // same element more than once
            if(!element.ready){
                element.ready = true;
                // Invoke the callback with the element
                fn.call(element, element);
            }
        }
    }

    // Expose `ready`
    win.ready = ready;

})(this);

/*
 * ready
 * Watch for when an element becomes available in the DOM
 * @param {String} selector
 * @param {Function} fn
 */

(function(win){
    'use strict';

    var listeners = [],
    doc = win.document,
    MutationObserver = win.MutationObserver || win.WebKitMutationObserver,
    observer;

    function ready(selector, fn){
        // Store the selector and callback to be monitored
        listeners.push({
            selector: selector,
            fn: fn
        });
        if(!observer){
            // Watch for changes in the document
            observer = new MutationObserver(checkListeners);
            observer.observe(doc.documentElement, {
                childList: true,
                subtree: true
            });
        }
        // Check if the element is currently in the DOM
        checkSelector(selector, fn);
    }

    function checkListeners(){
        // Check the DOM for elements matching a stored selector
        for(var i = 0, len = listeners.length, listener; i < len; i++){
            listener = listeners[i];
            checkSelector(listener.selector, listener.fn);
        }
    }

    function checkSelector(selector, fn){
        // Query for elements matching the specified selector
        var elements = doc.querySelectorAll(selector), i = 0, len = elements.length, element;
        for(; i < len; i++){
            element = elements[i];
            // Make sure the callback isn't invoked with the
            // same element more than once
            if(!element.ready){
                element.ready = true;
                // Invoke the callback with the element
                fn.call(element, element);
            }
        }
    }

    // Expose `ready`
    win.ready = ready;

})(this);

Make function name more descriptive


import Component from '@ember/component';
import { A } from '@ember/array';
import { computed } from '@ember/object';

export default Component.extend({
  /**
   * Class names for the component
   * @type {Array.string}
   * @default
   */
  classNames: ['tumblr-blog'],

  /**
   * Route to link posts to
   * @type {string}
   * @default
   */
  postsRoute: null,

  /**
   * Determines whether or not the blog can collapse its posts
   * @type {boolean}
   * @default
   */
  collapsible: false,

  /**
   * Default all posts to collapsed
   * @type {boolean}
   * @default
   */
  collapseByDefault: true,

  /**
   * Use date formatting
   * @type {boolean}
   * @default
   */
  formatDates: true,

  /**
   * Display an error message instead of attempting to render posts when none are returned
   * @type {boolean}
   * @default
   */
  showErrors: true,

  /**
   * Error message to display when no posts were found
   */
  errorMessage: "We're sorry, we were unable to retrieve any posts. Please check back later!",

  /**
   * Configurable Sort Options
   * @type {Array.string}
   * @default
   */
  sortBy: [],

  /**
   * Posts to be displayed
   * @type {Array.Tumblr-Post}
   * @default
   */
  posts: A([]),

  /**
   * Sorted array of posts
   * @type {Array.Tumblr-Post}
   * @default
   */
  sortedPosts: computed.sort('posts', 'sortBy')
});

import Component from '@ember/component';
import { A } from '@ember/array';
import { computed } from '@ember/object';

export default Component.extend({
  /**
   * Class names for the component
   * @type {Array.string}
   * @default
   */
  classNames: 'tumblr-blog',

  /**
   * Route to link posts to
   * @type {string}
   * @default
   */
  postsRoute: null,

  /**
   * Determines whether or not the blog can collapse its posts
   * @type {boolean}
   * @default
   */
  collapsible: false,

  /**
   * Default all posts to collapsed
   * @type {boolean}
   * @default
   */
  collapseByDefault: true,

  /**
   * Use date formatting
   * @type {boolean}
   * @default
   */
  formatDates: true,

  /**
   * Display an error message instead of attempting to render posts when none are returned
   * @type {boolean}
   * @default
   */
  showErrors: true,

  /**
   * Error message to display when no posts were found
   */
  errorMessage: "We're sorry, we were unable to retrieve any posts. Please check back later!",

  /**
   * Configurable Sort Options
   * @type {Array.string}
   * @default
   */
  sortBy: [],

  /**
   * Posts to be displayed
   * @type {Array.Tumblr-Post}
   * @default
   */
  posts: A([]),

  /**
   * Sorted array of posts
   * @type {Array.Tumblr-Post}
   * @default
   */
  sortedPosts: computed.sort('posts', 'sortBy')
});

Change classNames from array to string


var nodegit = require("../");
var path = require("path");

/**
 * This shows how to get details from a tree entry or a blob
**/

nodegit.Repository.open(path.resolve(__dirname, "../.git"))
  .then(function(repo) {
    return repo.getTree("e1b0c7ea57bfc5e30ec279402a98168a27838ac9")
    .then(function(tree) {
      var treeEntry = tree.entryByIndex(0);

      // Tree entry doesn't have any data associated with the actual entry
      // To get that we need to get the index entry that this points to
      return repo.refreshIndex().then(function(index) {
        var indexEntry = index.getByPath(treeEntry.path());

        // With the index entry we can now view the details for the tree entry
        console.log("Entry path: " + indexEntry.path());
        console.log("Entry time in seconds: " + indexEntry.mtime().seconds());
        console.log("Entry oid: " + indexEntry.id().toString());
        console.log("Entry size: " + indexEntry.fileSize());
      });
    });
  })
  .done(function() {
    console.log("Done!");
  });

var nodegit = require("../");
var path = require("path");

/**
 * This shows how to get details from a tree entry or a blob
**/

nodegit.Repository.open(path.resolve(__dirname, "../.git"))
  .then(function(repo) {
    return repo.getTree("e1b0c7ea57bfc5e30ec279402a98168a27838ac9")
    .then(function(tree) {
      var treeEntry = tree.entryByIndex(0);

      // Tree entry doesn't have any data associated with the actual entry
      // To get that we need to get the index entry that this points to
      return repo.refreshIndex().then(function(index) {
        var indexEntry = index.getByPath(treeEntry.path());

        // With the index entry we can now view the details for the tree entry
        console.log("Entry path: " + indexEntry.path);
        console.log("Entry time in seconds: " + indexEntry.mtime.seconds());
        console.log("Entry oid: " + indexEntry.id.toString());
        console.log("Entry size: " + indexEntry.fileSize);
      });
    });
  })
  .done(function() {
    console.log("Done!");
  });

Change function calls to field accesses instead


import Ember from 'ember';
import RavenLogger from 'ember-cli-sentry/services/raven';

export default RavenLogger.extend({

  unhandledPromiseErrorMessage: '',

  captureException(/* error */) {
    this._super(...arguments);
  },

  captureMessage(/* message */) {
    return this._super(...arguments);
  },

  enableGlobalErrorCatching() {
    return this._super(...arguments);
  },

  ignoreError() {
    return this._super();
  },

  callRaven(/* methodName, ...optional */) {
    return this._super(...arguments);
  }
});

Remove unused import in blueprint


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

const draftRenderer = new Renderer(defaultBlocks);

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

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

  render() {
    return (

@synapsestudios/draftjs-editor

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

          View this project on
          {' '}
          Github

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

          : null}

    );
  }
}

export default SetStateExample;

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

const draftRenderer = new Renderer(defaultBlocks);

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

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

  render() {
    return (

@synapsestudios/draftjs-editor

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

          View this project on
          {' '}
          Github

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

          : null}

    );
  }
}

export default SetStateExample;

Use method in storybook example that wasn't being used that logs actions


import { gql } from 'react-apollo'

export default gql`
  mutation updateMemory(
    $nodeId: ID!,
    $ownerFirstName: String!,
    $ownerLastName: String,
    $ownerEmail: String!,
    $ownerCountry: String!,
    $victimName: String!,
    $victimBornAt: Int!,
    $victimDeadAt: Int!,
    $victimCity: String!,
    $victimHistory: String!,
    $victimRememberText: String,
    $victimGoodWords: String,
    $victimPhoto: String!,
    $victimSilhouette: String!,
    $authorizedToSite: Boolean!
  ) {
    updateMemory(input: {
      nodeId: $nodeId,
      memoryPatch: {
        ownerFirstName: $ownerFirstName,
        ownerLastName: $ownerLastName,
        ownerEmail: $ownerEmail,
        ownerCountry: $ownerCountry,
        victimName: $victimName,
        victimBornAt: $victimBornAt,
        victimDeadAt: $victimDeadAt,
        victimCity: $victimCity,
        victimHistory: $victimHistory,
        victimRememberText: $victimRememberText,
        victimGoodWords: $victimGoodWords,
        victimPhoto: $victimPhoto,
        victimSilhouette: $victimSilhouette,
        authorizedToSite: $authorizedToSite
      }
    }) {
      memory {
        id,
        memoryAssetsByMemoryId {
          nodes {
            nodeId
          }
        }
      }
    }
  }
`

import { gql } from 'react-apollo'

export default gql`
  mutation updateMemory(
    $nodeId: ID!,
    $ownerFirstName: String!,
    $ownerLastName: String,
    $ownerEmail: String!,
    $ownerCountry: String!,
    $victimName: String!,
    $victimBornAt: Int!,
    $victimDeadAt: Int!,
    $victimCity: String!,
    $victimHistory: String!,
    $victimRememberText: String,
    $victimGoodWords: String,
    $victimPhoto: String!,
    $victimSilhouette: String!,
    $authorizedToSite: Boolean
  ) {
    updateMemory(input: {
      nodeId: $nodeId,
      memoryPatch: {
        ownerFirstName: $ownerFirstName,
        ownerLastName: $ownerLastName,
        ownerEmail: $ownerEmail,
        ownerCountry: $ownerCountry,
        victimName: $victimName,
        victimBornAt: $victimBornAt,
        victimDeadAt: $victimDeadAt,
        victimCity: $victimCity,
        victimHistory: $victimHistory,
        victimRememberText: $victimRememberText,
        victimGoodWords: $victimGoodWords,
        victimPhoto: $victimPhoto,
        victimSilhouette: $victimSilhouette,
        authorizedToSite: $authorizedToSite
      }
    }) {
      memory {
        id,
        memoryAssetsByMemoryId {
          nodes {
            nodeId
          }
        }
      }
    }
  }
`

Remove required autorizedToSite from update memory


var __slice = Array.prototype.slice;

/**
 * Generate a function that accepts a variable number of arguments as the last
 * function argument.
 *
 * @param  {Function} fn
 * @return {Function}
 */
module.exports = function (fn) {
  var count = Math.max(fn.length - 1, 0);

  return function () {
    var args = __slice.call(arguments, 0, count);

    // Enforce the array length, in case we don't have enough array padding.
    args.length = count;
    args.push(__slice.call(arguments, count));

    return fn.apply(this, args);
  };
};

/**
 * Generate a function that accepts a variable number of arguments as the last
 * function argument.
 *
 * @param  {Function} fn
 * @return {Function}
 */
module.exports = function (fn) {
  var count = Math.max(fn.length - 1, 0);

  return function () {
    var args = new Array(count);
    var index = 0;

    // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments
    for (; index < count; index++) {
      args[index] = arguments[index];
    }

    var variadic = args[count] = [];

    for (; index < arguments.length; index++) {
      variadic.push(arguments[index]);
    }

    return fn.apply(this, args);
  };
};

Use for loop over slicing arguments


'use strict';

module.exports = {
	hexToRGB: function( hex ) {
		var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec( hex );
		if ( result ) {
			return {
				r: parseInt( result[1], 16 ),
				g: parseInt( result[2], 16 ),
				b: parseInt( result[3], 16 )
			};
		} else {
			return null;
		}
	},

	hexToTransparentRGB: function( hex, opacity ) {
		var rgb = this.hexToRGB( hex );
		return "rgba(" + rgb.r + ", " + rgb.g + ", " + rgb.b + ", " + opacity + ")";
	}
};

'use strict';

module.exports = {
	hexToRGB: function( hex ) {
		var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec( hex );
		if ( result ) {
			return {
				r: parseInt( result[1], 16 ),
				g: parseInt( result[2], 16 ),
				b: parseInt( result[3], 16 )
			};
		} else {
			return null;
		}
	},

	hexToTransparentRGB: function( hex, opacity ) {
		var rgb = this.hexToRGB( hex );
		return 'rgba(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ', ' + opacity + ')';
	}
};

Convert double quotes to single quotes to appease jshint


importScripts('/static/vendor/scripts/highlight.pack.js');

var unescape = {
    '&': '&',
    '<': '<',
    '>': '>',
    '"': '"',
    ''': '\'',
};

var regex = new RegExp(Object.keys(unescape).join('|'), 'g');

function unescapeFn(match) {
    return unescape[match];
}

function highlightBlockHTML(block) {
    block = block.replace(regex, unescapeFn);
    return self.hljs.highlight('html', block);
};

function highlightBlockCSS(block) {
    block = block.replace(regex, unescapeFn);
    return self.hljs.highlight('css', block);
};

function highlightBlockJS(block) {
    block = block.replace(regex, unescapeFn);
    return self.hljs.highlight('js', block);
};

onmessage = function(event) {
    let highlightFn;
    switch(event.data.type) {
        case 'html':
            highlightFn = highlightBlockHTML;
            break;
        case 'css':
            highlightFn = highlightBlockCSS;
            break;
        case 'js':
            highlightFn = highlightBlockJS;
            break;
        default:
            throw new Error('unsupported highlight type in web worker: ' + event.data.type);
            break;
    }
    var result = event.data.blocks.map(highlightFn).map(h => h.value);
    postMessage({
        type: event.data.type,
        blocks: result,
    });
};

importScripts('/static/vendor/scripts/highlight.pack.js');

var unescape = {
    '&': '&',
    '<': '<',
    '>': '>',
    '"': '"',
    ''': '\'',
};

var regex = new RegExp(Object.keys(unescape).join('|'), 'g');

function unescapeFn(match) {
    return unescape[match];
}

function highlightBlockHTML(block) {
    block = block.replace(regex, unescapeFn);
    return self.hljs.highlight('html', block);
};

function highlightBlockCSS(block) {
    block = block.replace(regex, unescapeFn);
    return self.hljs.highlight('css', block);
};

function highlightBlockJS(block) {
    block = block.replace(regex, unescapeFn);
    return self.hljs.highlight('js', block);
};

onmessage = function(event) {
    var highlightFn;
    switch(event.data.type) {
        case 'html':
            highlightFn = highlightBlockHTML;
            break;
        case 'css':
            highlightFn = highlightBlockCSS;
            break;
        case 'js':
            highlightFn = highlightBlockJS;
            break;
        default:
            throw new Error('unsupported highlight type in web worker: ' + event.data.type);
            break;
    }
    var result = event.data.blocks.map(highlightFn).map(function(h) { return h.value; });
    postMessage({
        type: event.data.type,
        blocks: result,
    });
};

Fix webworker ES6 syntax to ES5


import {
  moduleForComponent,
  test
} from 'ember-qunit';
import { arrayContains } from '../../helpers/common';

var component;

moduleForComponent('t-grid', {
  beforeEach: function() {
    component = this.subject();
  }
});

test('it renders', function(assert) {
  assert.expect(2);

  // creates the component instance
  assert.equal(component._state, 'preRender');

  // renders the component to the page
  this.render();
  assert.equal(component._state, 'inDOM');
});

test('it renders a grid transformicon with defaults', function(assert) {
  assert.expect(4);

  this.render();

  assert.equal(component.get('is-open'), false);
  assert.equal(component.get('animation'), 'rearrange');
  assert.equal(component.get('animationType'), 'tcon-grid--rearrange');
  assert.ok(arrayContains('tcon-grid', component.get('classNames')));
});

import {
  moduleForComponent,
  test
} from 'ember-qunit';
import { arrayContains } from '../../helpers/common';

moduleForComponent('t-grid', {});

test('it renders', function(assert) {
  assert.expect(2);

  // creates the component instance
  var component = this.subject();
  assert.equal(component._state, 'preRender');

  // renders the component to the page
  this.render();
  assert.equal(component._state, 'inDOM');
});

test('it creates a grid transformicon with defaults', function(assert) {
  assert.expect(5);

  /**
    {{t-grid}}
  */
  var component = this.subject();
  var transformClass = component.get('transformClass');
  this.render();
  assert.equal(component.get('is-open'), false);
  assert.equal(component.get('animation'), 'rearrange');
  assert.equal(component.get('animationType'), 'tcon-grid--rearrange');
  assert.ok(arrayContains('tcon-grid', component.get('classNames')));
  assert.ok(!arrayContains(transformClass, component.get('classNames')));
});

test('it creates a grid transformicon with `is-open=true`', function(assert) {
  assert.expect(2);

  /**
    {{t-grid is-open=true}}
  */
  var component = this.subject({ 'is-open': true });
  var transformClass = component.get('transformClass');
  this.render();
  assert.equal(component.get('is-open'), true);
  assert.ok(arrayContains(transformClass, component.get('classNames')));
});

test('it creates a grid transformicon with a non-default animation `a=collapse`', function(assert) {
  assert.expect(4);

  /**
    {{t-grid a='collapse'}}
  */
  var component = this.subject({ a: 'collapse' });
  var transformClass = component.get('transformClass');
  this.render();
  assert.equal(component.get('is-open'), false);
  assert.equal(component.get('animation'), 'collapse');
  assert.equal(component.get('animationType'), 'tcon-grid--collapse');
  assert.ok(!arrayContains(transformClass, component.get('classNames')));
});

Fix and update {{t-grid}} unit tests


const TwitterPostBase = require('./TwitterPostBase');
const { captureSalmonRunScreenshot } = require('../screenshots');
const { readData } = require('../utilities');

class SalmonRunTweet extends TwitterPostBase {
    getKey() { return 'salmonrun'; }
    getName() { return 'Salmon Run'; }

    getSalmonRunSchedules() {
        let results = {};
        let coopSchedules = readData('coop-schedules.json');

        for (let schedule of coopSchedules.schedules)
            results[schedule.start_time] = schedule;

        for (let schedule of coopSchedules.details)
            results[schedule.start_time] = schedule;

        return Object.values(results);
    }

    getData() {
        return this.getSalmonRunSchedules().find(s => s.start_time == this.getDataTime());
    }

    getTestData() {
        return this.getSalmonRunSchedules()[0];
    }

    getImage(data) {
        return captureSalmonRunScreenshot(data.start_time);
    }

    getText(data) {
        let hasMysteryWeapon = data.weapons.some(w => w === null);

        if (hasMysteryWeapon)
            return `Salmon Run is now open on ${data.stage.name} with MYSTERY WEAPONS! #salmonrun #splatoon2`;

        return `Salmon Run is now open on ${data.stage.name}! #salmonrun #splatoon2`;
    }
}

module.exports = SalmonRunTweet;

const TwitterPostBase = require('./TwitterPostBase');
const { captureSalmonRunScreenshot } = require('../screenshots');
const { readData } = require('../utilities');

class SalmonRunTweet extends TwitterPostBase {
    getKey() { return 'salmonrun'; }
    getName() { return 'Salmon Run'; }

    getSalmonRunSchedules() {
        let results = {};
        let coopSchedules = readData('coop-schedules.json');

        for (let schedule of coopSchedules.schedules)
            results[schedule.start_time] = schedule;

        for (let schedule of coopSchedules.details)
            results[schedule.start_time] = schedule;

        return Object.values(results);
    }

    getData() {
        return this.getSalmonRunSchedules().find(s => s.start_time == this.getDataTime());
    }

    getTestData() {
        return this.getSalmonRunSchedules()[0];
    }

    getImage(data) {
        return captureSalmonRunScreenshot(data.start_time);
    }

    getText(data) {
        let hasMysteryWeapon = data.weapons.some(w => w === null || w.coop_special_weapon);

        if (hasMysteryWeapon)
            return `Salmon Run is now open on ${data.stage.name} with MYSTERY WEAPONS! #salmonrun #splatoon2`;

        return `Salmon Run is now open on ${data.stage.name}! #salmonrun #splatoon2`;
    }
}

module.exports = SalmonRunTweet;

Fix Salmon Run tweet mystery weapon detection


import { visit, click } from '@ember/test-helpers';
import { setupApplicationTest } from 'ember-qunit';
import { module, test } from 'qunit';

import { setupPolly } from 'skylines/tests/test-helpers/setup-polly';

module('Acceptance | login', function (hooks) {
  setupApplicationTest(hooks);
  setupPolly(hooks, { recordIfMissing: false });

  const LOGIN_DROPDOWN = '[data-test-login-dropdown]';
  const LOGIN_DROPDOWN_TOGGLE = `${LOGIN_DROPDOWN} a`;
  const LOGIN_EMAIL = '[data-test-input="login-email"]';

  test('login dropdown form stays visible when fields are focused', async function (assert) {
    await visit('/');

    await click(LOGIN_DROPDOWN_TOGGLE);
    assert.dom(LOGIN_DROPDOWN).hasClass('open');

    await click(LOGIN_EMAIL);
    assert.dom(LOGIN_DROPDOWN).hasClass('open');
  });
});

import { visit, click } from '@ember/test-helpers';
import { setupApplicationTest } from 'ember-qunit';
import { module, test } from 'qunit';

import { setupMirage } from 'ember-cli-mirage/test-support';

module('Acceptance | login', function (hooks) {
  setupApplicationTest(hooks);
  setupMirage(hooks);

  const LOGIN_DROPDOWN = '[data-test-login-dropdown]';
  const LOGIN_DROPDOWN_TOGGLE = `${LOGIN_DROPDOWN} a`;
  const LOGIN_EMAIL = '[data-test-input="login-email"]';

  test('login dropdown form stays visible when fields are focused', async function (assert) {
    await visit('/');

    await click(LOGIN_DROPDOWN_TOGGLE);
    assert.dom(LOGIN_DROPDOWN).hasClass('open');

    await click(LOGIN_EMAIL);
    assert.dom(LOGIN_DROPDOWN).hasClass('open');
  });
});

Use Mirage.js instead of Polly.js


import { get, set, computed } from '@ember/object';
import Component from '@ember/component';
import NodeDriver from 'shared/mixins/node-driver';
import layout from './template';

export default Component.extend(NodeDriver, {
  layout,
  // Set by Driver
  driverName: null,

  typeDocumentations: null,
  config: null,

  bootstrap() {
    if (get(this, 'driverName')) {
      const configName = `${get(this, 'driverName')}Config`;
      let config = get(this, 'globalStore').createRecord({
        type: configName,
      });
      const model = get(this, 'model');
      set(model, configName, config);
      set(this, 'config', config)
    }
  },

  validate() {
    let errors = [];

    if (!get(this, 'model.name')) {
      errors.push('Name is required');
    }

    set(this, 'errors', errors);
    return errors.length === 0;
  },

  fieldNames: computed('driverName', 'schema', function () {
    if (get(this, 'schema')) {
      return Object.keys(get(this, 'schema').get('resourceFields'));
    }
  }),

  schema: computed('driverName', function () {
    const configName = `${get(this, 'driverName')}Config`;
    return get(this, 'globalStore').getById('schema', configName.toLowerCase());
  }),
});

import { get, set, computed } from '@ember/object';
import Component from '@ember/component';
import NodeDriver from 'shared/mixins/node-driver';
import layout from './template';

export default Component.extend(NodeDriver, {
  layout,
  // Set by Driver
  driverName: null,

  typeDocumentations: null,
  config: null,

  bootstrap() {
    if (get(this, 'driverName')) {
      const configName = `${get(this, 'driverName')}Config`;
      let config = get(this, 'globalStore').createRecord({
        type: configName,
      });
      const model = get(this, 'model');
      set(model, configName, config);
      set(this, 'config', config)
    }
  },

  init() {
    this._super(...arguments);

    if ( get(this, 'editing') && get(this, 'driverName') ) {
      const config = get(this, `model.${get(this, 'driverName')}Config`);
      set(this, 'config', config);
    }
  },

  validate() {
    let errors = [];

    if (!get(this, 'model.name')) {
      errors.push('Name is required');
    }

    set(this, 'errors', errors);
    return errors.length === 0;
  },

  fieldNames: computed('driverName', 'schema', function () {
    if (get(this, 'schema')) {
      return Object.keys(get(this, 'schema').get('resourceFields'));
    }
  }),

  schema: computed('driverName', function () {
    const configName = `${get(this, 'driverName')}Config`;
    return get(this, 'globalStore').getById('schema', configName.toLowerCase());
  }),
});

Fix node driver edit issue


(function($) {
    $.entwine('kapost', function($) {
        $(window).resize(function() {
            $('#Form_ConvertObjectForm').layout();
        });

        $('#Form_ConvertObjectForm').entwine({
            onadd: function(e) {
                $(this).layout();
            }
        });

        $('#Form_ConvertObjectForm_ConvertMode').entwine({
            onadd: function(e) {
                this.updateVisibleFields();
            },

            updateVisibleFields: function() {
                var selectedVal=$('#Form_ConvertObjectForm_ConvertMode input.radio:checked').val();

                if(selectedVal=='ReplacePage') {
                    $('#Form_ConvertObjectForm #ParentPageID').hide();
                    $('#Form_ConvertObjectForm #ReplacePageID').show();
                }else if(selectedVal=='NewPage') {
                    $('#Form_ConvertObjectForm #ParentPageID').show();
                    $('#Form_ConvertObjectForm #ReplacePageID').hide();
                }
            }
        });

        $('#Form_ConvertObjectForm_ConvertMode input.radio').entwine({
            onchange: function(e) {
                $(this).closest('ul').updateVisibleFields();
            }
        });
    });
})(jQuery);

(function($) {
    $.entwine('kapost', function($) {
        $(window).resize(function() {
            $('#Form_ConvertObjectForm').layout();
        });

        $('#Form_ConvertObjectForm').entwine({
            onadd: function(e) {
                $(this).layout();
            }
        });

        $('#Form_ConvertObjectForm_ConvertMode').entwine({
            onadd: function(e) {
                this.updateVisibleFields();
            },

            updateVisibleFields: function() {
                var selectedVal=$('#Form_ConvertObjectForm_ConvertMode input.radio:checked').val();

                if(selectedVal=='ReplacePage') {
                    $('#Form_ConvertObjectForm #ParentPageID').hide();
                    $('#Form_ConvertObjectForm #ReplacePageID').show();
                }else if(selectedVal=='NewPage') {
                    $('#Form_ConvertObjectForm #ParentPageID').show();
                    $('#Form_ConvertObjectForm #ReplacePageID').hide();
                }else {
                    $('#Form_ConvertObjectForm #ParentPageID, #Form_ConvertObjectForm #ReplacePageID').hide();
                }
            }
        });

        $('#Form_ConvertObjectForm_ConvertMode input.radio').entwine({
            onchange: function(e) {
                $(this).closest('ul').updateVisibleFields();
            }
        });
    });
})(jQuery);

Hide the tree dropdowns if the admin has selected a unknown type


'use strict';

var value         = require('es5-ext/object/valid-value')
  , unserializeId = require('dbjs/_setup/unserialize/id')
  , object        = require('dbjs/valid-dbjs-object')
  , nested        = require('dbjs/valid-dbjs-nested-object');

module.exports = function (obj, id) {
	var names = [];
	object(obj);
	names = [];
	unserializeId(value(id)).forEach(function (token, index) {
		if (index % 2) {
			if (token !== '/') throw new TypeError(id + " is not property id");
			return;
		}
		names.push(token);
	});
	while (names.length > 1) obj = nested(obj[names.shift()]);
	return obj._get(names[0]);
};

'use strict';

var resolve = require('dbjs/_setup/utils/resolve-property-path');

module.exports = function (obj, id) {
	var result = resolve(obj, id);
	return result.object.getObservable(result.key);
};

Update up to changes in dbjs


module.exports = [
  require('./cap3020'),
  require('./cap3027'),
  require('./cap3032')
];

module.exports = [
  require('./cap3020'),
  require('./cap3027'),
  require('./cap3032'),
  require('./cap3033'),
  require('./cap3034')
];

Add CAP3033 and CAP3034 to array of computer science courses


'use strict'

module.exports = function () {
  let config = {
    postcss: this.options.build.postcss,
    loaders: {
      'js': 'babel-loader?presets[]=es2015&presets[]=stage-2',
      'postcss': 'vue-style-loader!css-loader',
      'less': 'vue-style-loader!css-loader!less-loader',
      'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax',
      'scss': 'vue-style-loader!css-loader!sass-loader',
      'stylus': 'vue-style-loader!css-loader!stylus-loader',
      'styl': 'vue-style-loader!css-loader!stylus-loader'
    }
  }

  if (!this.dev) {
    // Use ExtractTextPlugin to extract CSS into a single file
    const ExtractTextPlugin = require('extract-text-webpack-plugin')
    config.loaders.css = ExtractTextPlugin.extract({ loader: 'css-loader' })
    config.loaders.scss = ExtractTextPlugin.extract({ loader: 'css-loader!sass-loader', fallbackLoader: 'vue-style-loader' })
    config.loaders.sass = ExtractTextPlugin.extract({ loader: 'css-loader!sass-loader?indentedSyntax', fallbackLoader: 'vue-style-loader' })
    config.loaders.stylus = ExtractTextPlugin.extract({ loader: 'css-loader!stylus-loader', fallbackLoader: 'vue-style-loader' })
    config.loaders.less = ExtractTextPlugin.extract({ loader: 'css-loader!less-loader', fallbackLoader: 'vue-style-loader' })
  }

  // Return the config
  return config
}

'use strict'

module.exports = function () {
  let config = {
    postcss: this.options.build.postcss,
    loaders: {
      'less': 'vue-style-loader!css-loader!less-loader',
      'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax',
      'scss': 'vue-style-loader!css-loader!sass-loader',
      'stylus': 'vue-style-loader!css-loader!stylus-loader',
      'styl': 'vue-style-loader!css-loader!stylus-loader'
    }
  }

  if (!this.dev) {
    // Use ExtractTextPlugin to extract CSS into a single file
    const ExtractTextPlugin = require('extract-text-webpack-plugin')
    config.loaders.css = ExtractTextPlugin.extract({ loader: 'css-loader' })
    config.loaders.scss = ExtractTextPlugin.extract({ loader: 'css-loader!sass-loader', fallbackLoader: 'vue-style-loader' })
    config.loaders.sass = ExtractTextPlugin.extract({ loader: 'css-loader!sass-loader?indentedSyntax', fallbackLoader: 'vue-style-loader' })
    config.loaders.stylus = ExtractTextPlugin.extract({ loader: 'css-loader!stylus-loader', fallbackLoader: 'vue-style-loader' })
    config.loaders.less = ExtractTextPlugin.extract({ loader: 'css-loader!less-loader', fallbackLoader: 'vue-style-loader' })
  }

  // Return the config
  return config
}

Disable babel query in vue-loaders


"use strict";

const Bucket = require("./Bucket");

class ChainedBucket extends Bucket {
  constructor(size, duration, name, parent) {
    super(size, duration);
    this.parent = parent || null;
    this.name = name || null;
  }
  refillByName(name) {
    if (this.parent) this.parent.refillByName(name);
    if (this.name && this.name.indexOf(name) === 0) this.refill();
  }
  get waitingBucket() {
    if (this.parent && this.parent.waitTime > 0)
      return this.parent;
    if (this.waitTime > 0)
      return this;
    return null;
  }
  get waitTime() {
    const waitTimeParent = this.parent && this.parent.waitTime;
    const waitTimeSelf = super.waitTime;
    if (this.parent && waitTimeParent < waitTimeSelf)
      return waitTimeParent;
    return waitTimeSelf;
  }
  get available() {
    // this getter triggers refill
    const availableParent = this.parent && this.parent.available;
    const availableSelf = super.available;
    if (this.parent && availableParent <= 0)
      return availableParent;
    return availableSelf;
  }
  consume(n) {
    // this triggers 'available' getter which triggers refill
    if (this.parent) {
      return super.consume(n) && this.parent.consume(n);
    }
    return super.consume(n);
  }
}

module.exports = ChainedBucket;

"use strict";

const Bucket = require("./Bucket");

class ChainedBucket extends Bucket {
  constructor(size, duration, name, parent) {
    super(size, duration);
    this.parent = parent || null;
    this.name = name || null;
  }
  refillByName(name) {
    if (this.parent) this.parent.refillByName(name);
    if (this.name && this.name.indexOf(name) === 0) this.refill();
  }
  get waitingBucket() {
    if (this.parent && this.parent.available <= 0)
      return this.parent;
    if (this.available <= 0)
      return this;
    return null;
  }
  get available() {
    // this getter triggers refill
    const availableParent = this.parent && this.parent.available;
    const availableSelf = super.available;
    if (this.parent && availableParent <= 0)
      return availableParent;
    return availableSelf;
  }
  consume(n) {
    // this triggers 'available' getter which triggers refill
    if (this.parent) {
      return super.consume(n) && this.parent.consume(n);
    }
    return super.consume(n);
  }
}

module.exports = ChainedBucket;

Allow buckets to be out of sync


'use strict';

const Queries = require('../../helpers/queries');
const Parser = require('../../helpers/paydroid_parser');
const Boom = require('boom');

exports.getData = {
    auth: {
        mode: 'try',
        strategy: 'standard'
    },
    plugins: {
        'hapi-auth-cookie': {
            redirectTo: false
        }
    },
    handler: function(request, reply) {
        if (!request.auth.isAuthenticated) {
            return reply(Boom.forbidden('You are not logged in'));
        }

        var sequelize = request.server.plugins.sequelize.db.sequelize;

        var userId = request.auth.credentials.id;
        var role = request.auth.credentials.role;
        var version = request.pre.apiVersion;
        var name = request.auth.credentials.firstname + ' ' + request.auth.credentials.lastname;

        var queryString = Queries.paydroid(userId, role, version);

        // API CODE
        sequelize
            .query(queryString, {
                type: sequelize.QueryTypes.SELECT
            })
            .then(function(rows) {
                var data;

                if (version === 1) {
                    data = Parser.v1(rows, userId, name);
                } else if (version === 2) {
                    data = Parser.v2(rows, role, userId, name);
                }

                reply(data);
            });
    }
};

'use strict';

const Queries = require('../../helpers/queries');
const Parser = require('../../helpers/paydroid_parser');
const Boom = require('boom');
const D3 = require('d3');

exports.getData = {
    auth: {
        mode: 'try',
        strategy: 'standard'
    },
    plugins: {
        'hapi-auth-cookie': {
            redirectTo: false
        }
    },
    handler: function(request, reply) {
        if (!request.auth.isAuthenticated) {
            return reply(Boom.forbidden('You are not logged in'));
        }

        var sequelize = request.server.plugins.sequelize.db.sequelize;

        var userId = request.auth.credentials.id;
        var role = request.auth.credentials.role;
        var version = request.pre.apiVersion;
        var name = request.auth.credentials.firstname + ' ' + request.auth.credentials.lastname;

        var queryString = Queries.paydroid(userId, role, version);

        // API CODE
        sequelize
            .query(queryString, {
                type: sequelize.QueryTypes.SELECT
            })
            .then(function(rows) {
                var data;

                if (version === 1) {
                    data = Parser.v1(rows, userId, name);
                } else if (version === 2) {

                    if (role==='block' && D3.values(rows[4])[0]===undefined) {
                        console.log("Couldn't find state code in db for request:\n" + request);
                        return reply(Boom.badRequest('Bad request'));
                    }

                    data = Parser.v2(rows, role, userId, name);

                }

                reply(data);
            });
    }
};

Add logging for state_code error


'use strict';

module.exports = function(app) {
  app.controller('GamesController', ['$route', '$scope', 'GameService', 'UserService', function($route, $scope, gameService, userService) {

    let ctrl = this;

    $scope.gameService = gameService;
    $scope.gamesData = gameService.data.allGames;
    ctrl.user = userService.data.user;
    ctrl.creating = false;

    ctrl.getGamesData = function() {
      gameService.getAllById(ctrl.user.gameIds)
        .catch((err) => {
          console.log('Error getting games.');
        });
    };

    ctrl.reloadPage = function() {
      $route.reload();
    }

    ctrl.init = function() {
      ctrl.getGamesData();
    };

    ctrl.init();

  }]);
};

'use strict';

module.exports = function(app) {
  app.controller('GamesController', ['$route', '$scope', 'GameService', 'UserService', function($route, $scope, gameService, userService) {

    const ctrl = this;
    $scope.gameService = gameService;
    $scope.gamesData = gameService.data.allGames;
    ctrl.user = userService.data.user;
    ctrl.creating = false;

    ctrl.getGamesData = function() {
      gameService.getAllById(ctrl.user.gameIds)
        .catch((err) => {
          console.log('Error getting games.');
        });
    };

    ctrl.reloadPage = function() {
      $route.reload();
    }

    ctrl.init = function() {
      ctrl.getGamesData();
    };

    ctrl.init();

  }]);
};

Use const instead of let.


var AppRouter = Backbone.Router.extend({
  routes: {
    'topics/:topicId': 'showResults',

    '*actions': 'defaultAction'
  },

  showResults: function(topicId) {
    var resultsView = new ResultsView();
    resultsView.render();
    var topicCharts = new TopicCharts([], { topicId: topicId });
    topicCharts.fetch({
      success: function(response) {
        var topicChartsView = new TopicChartsView({ collection: response.models });
        topicChartsView.render();
      }
    });
  },

  defaultAction: function() {
    var homeView = new HomeView();
    homeView.render();
    $('#topics-container').on('change', '#topics-dropdown', function(event) {
      this.navigate("topics/" + $(event.target).val(), { trigger: true });
    }.bind(this));
  }

});

var AppRouter = Backbone.Router.extend({
  routes: {
    'topics/:topicId': 'showResults',

    '*actions': 'defaultAction'
  },

  showResults: function(topicId) {
    if ($('#topics-container').length === 0) {
      this.defaultAction();
    }
    var resultsView = new ResultsView();
    resultsView.render();
    var topicCharts = new TopicCharts([], { topicId: topicId });
    topicCharts.fetch({
      success: function(response) {
        var topicChartsView = new TopicChartsView({ collection: response.models });
        topicChartsView.render();
        // Ensure that the correct option is displayed as 'selected' in the dropdown.
        $('option:nth-child(' + (parseInt(topicId)+1) + ')').attr('selected', true);
      }
    });
  },

  defaultAction: function() {
    var homeView = new HomeView();
    homeView.render();
    $('#topics-container').on('change', '#topics-dropdown', function(event) {
      var targetVal = event.target.value;
      if (targetVal.length > 0) {
        this.navigate("topics/" + targetVal, { trigger: true });
      }
    }.bind(this));
  }

});

Update routing so something actually shows up when a Backbone URL is accessed externally


// This file is for all Javascript that should load at the bottom of the page
// i.e. before the closing </body> tag

//= require bootstrap
//= require bootstrap-datepicker/core

Use only core datepicker (no multi-lingual, for now)