import type Plugin from "./plugin";
import Store from "../store";
import traverse from "comal-traverse";
import File from "./file";

export default class PluginPass extends Store {
  constructor(file: File, plugin: Plugin, options: Object = {}) {
    super();
    this.plugin = plugin;
    this.file   = file;
    this.opts   = options;
  }

  plugin: Plugin;
  file: File;
  opts: Object;

  transform() {
    let file = this.file;
    file.log.debug(`Start transformer ${this.key}`);
    traverse(file.ast, this.plugin.visitor, file.scope, file);
    file.log.debug(`Finish transformer ${this.key}`);
  }

  addHelper(...args) {
    return this.file.addHelper(...args);
  }

  addImport(...args) {
    return this.file.addImport(...args);
  }

  getModuleName(...args) {
    return this.file.getModuleName(...args);
  }

  buildCodeFrameError(...args) {
    return this.file.buildCodeFrameError(...args);
  }
}

import type Plugin from "./plugin";
import Store from "../store";
import traverse from "comal-traverse";
import File from "./file";

export default class PluginPass {
  constructor(file: File, plugin: Plugin, options: Object = {}) {
    this.store = new Store();
    this.plugin = plugin;
    this.file   = file;
    this.opts   = options;
  }

  plugin: Plugin;
  file: File;
  opts: Object;

  transform() {
    let file = this.file;
    file.log.debug(`Start transformer ${this.key}`);
    traverse(file.ast, this.plugin.visitor, file.scope, file);
    file.log.debug(`Finish transformer ${this.key}`);
  }

  addHelper(...args) {
    return this.file.addHelper(...args);
  }

  addImport(...args) {
    return this.file.addImport(...args);
  }

  getModuleName(...args) {
    return this.file.getModuleName(...args);
  }

  buildCodeFrameError(...args) {
    return this.file.buildCodeFrameError(...args);
  }
}

Use composition instead of inheritance for PluginPass


import React from 'react';
import ClipboardJS from 'clipboard';
import 'balloon-css/balloon.css';

export default class CopyButton extends React.PureComponent {
  constructor(props) {
    super(props);
    this.copyBtnRef = React.createRef();
    this.clipboardRef = React.createRef();
  }

  static defaultProps = {
    content: '',
  };
  state = {
    tooltipAttrs: {},
  };

  componentDidMount() {
    this.clipboardRef.current = new ClipboardJS(this.copyBtnRef.current, {
      text: () => this.props.content,
    });
    this.clipboardRef.current.on('success', () => {
      const self = this;
      this.setState({
        tooltipAttrs: {
          'data-balloon': '複製成功！',
          'data-balloon-visible': '',
          'data-balloon-pos': 'up',
        },
      });

      setTimeout(function() {
        self.setState({ tooltipAttrs: {} });
      }, 1000);
    });
  }

  render() {
    const { tooltipAttrs } = this.state;
    return (
      <button
        ref={this.copyBtnRef}
        className="btn-copy"
        {...tooltipAttrs}
      >
        複製到剪貼簿
        <style jsx>{`
          .btn-copy {
            margin-left: 10px;
          }
        `}</style>
      </button>
    );
  }
}

import React from 'react';
import ClipboardJS from 'clipboard';
import 'balloon-css/balloon.css';

export default class CopyButton extends React.PureComponent {
  constructor(props) {
    super(props);
    this.copyBtnRef = React.createRef();
    this.clipboardRef = React.createRef();
  }

  static defaultProps = {
    content: '',
  };
  state = {
    tooltipAttrs: {},
  };

  componentDidMount() {
    this.clipboardRef.current = new ClipboardJS(this.copyBtnRef.current, {
      text: () => this.props.content,
    });
    this.clipboardRef.current.on('success', () => {
      const self = this;
      this.setState({
        tooltipAttrs: {
          'data-balloon': '複製成功！',
          'data-balloon-visible': '',
          'data-balloon-pos': 'up',
        },
      });

      setTimeout(function() {
        self.setState({ tooltipAttrs: {} });
      }, 1000);
    });
  }

  render() {
    const { tooltipAttrs } = this.state;
    return (
      <button ref={this.copyBtnRef} className="btn-copy" {...tooltipAttrs}>
        複製到剪貼簿
        <style jsx>{`
          .btn-copy {
            margin-left: 10px;
          }
        `}</style>
      </button>
    );
  }
}

Modify <button> format to 1 line


'use strict';

const { defaultsDeep, merge } = require('lodash/fp');
const helmet = require('koa-helmet');

const defaults = {
  crossOriginEmbedderPolicy: false,
  crossOriginOpenerPolicy: false,
  crossOriginResourcePolicy: false,
  originAgentCluster: false,
  contentSecurityPolicy: {
    useDefaults: true,
    directives: {
      'connect-src': ["'self'", 'https:'],
      'img-src': ["'self'", 'data:', 'blob:'],
      'media-src': ["'self'", 'data:', 'blob:'],
      upgradeInsecureRequests: null,
    },
  },
  xssFilter: false,
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
  },
  frameguard: {
    action: 'sameorigin',
  },
};

/**
 * @type {import('./').MiddlewareFactory}
 */
module.exports = config => (ctx, next) => {
  let helmetConfig = defaultsDeep(defaults, config);

  if (
    ctx.method === 'GET' &&
    ['/graphql', '/documentation'].some(str => ctx.path.startsWith(str))
  ) {
    helmetConfig = merge(helmetConfig, {
      contentSecurityPolicy: {
        directives: {
          'script-src': ["'self'", "'unsafe-inline'", 'cdn.jsdelivr.net'],
          'img-src': ["'self'", 'data:', 'cdn.jsdelivr.net', 'strapi.io'],
        },
      },
    });
  }

  return helmet(helmetConfig)(ctx, next);
};

'use strict';

const { defaultsDeep, merge } = require('lodash/fp');
const helmet = require('koa-helmet');

const defaults = {
  crossOriginEmbedderPolicy: false,
  crossOriginOpenerPolicy: false,
  crossOriginResourcePolicy: false,
  originAgentCluster: false,
  contentSecurityPolicy: {
    useDefaults: true,
    directives: {
      'connect-src': ["'self'", 'https:'],
      'img-src': ["'self'", 'data:', 'blob:', 'https://dl.airtable.com'],
      'media-src': ["'self'", 'data:', 'blob:'],
      upgradeInsecureRequests: null,
    },
  },
  xssFilter: false,
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
  },
  frameguard: {
    action: 'sameorigin',
  },
};

/**
 * @type {import('./').MiddlewareFactory}
 */
module.exports = config => (ctx, next) => {
  let helmetConfig = defaultsDeep(defaults, config);

  if (
    ctx.method === 'GET' &&
    ['/graphql', '/documentation'].some(str => ctx.path.startsWith(str))
  ) {
    helmetConfig = merge(helmetConfig, {
      contentSecurityPolicy: {
        directives: {
          'script-src': ["'self'", "'unsafe-inline'", 'cdn.jsdelivr.net'],
          'img-src': ["'self'", 'data:', 'cdn.jsdelivr.net', 'strapi.io'],
        },
      },
    });
  }

  return helmet(helmetConfig)(ctx, next);
};

Add airtable in CSP policy


'use strict';

// MODULES //

var keys = require( 'object-keys' );
var isPlainObject = require( '@stdlib/utils/is-plain-object' );
var hasSymbolSupport = require( '@stdlib/utils/detect-symbol-support' );

// MAIN //

/**
* Tests if a value is an empty object.
*
* @param {*} value - value to test
* @returns {boolean} boolean indicating whether value is an empty object
*
* @example
* var bool = isEmptyObject( {} );
* // returns true
* @example
* var bool = isEmptyObject( { 'beep': 'boop' } );
* // returns false
* @example
* var bool = isEmptyObject( [] );
* // returns false
*/
function isEmptyObject( value ) {
	if ( !isPlainObject( value ) ) {
		return false;
	}
	if ( keys( value ).length > 0 ) {
		return false;
	}
	if (
		hasSymbolSupport &&
		Object.getOwnPropertySymbols( value ).length > 0
	) {
		return false;
	}
	return true;
} // end FUNCTION isEmptyObject()

// EXPORTS //

module.exports = isEmptyObject;

'use strict';

// MODULES //

var keys = require( 'object-keys' );
var isPlainObject = require( '@stdlib/utils/is-plain-object' );
var hasSymbolSupport = require( '@stdlib/utils/detect-symbol-support' );

// MAIN //

/**
* Tests if a value is an empty object.
*
* @param {*} value - value to test
* @returns {boolean} boolean indicating whether value is an empty object
*
* @example
* var bool = isEmptyObject( {} );
* // returns true
* @example
* var bool = isEmptyObject( { 'beep': 'boop' } );
* // returns false
* @example
* var bool = isEmptyObject( [] );
* // returns false
*/
function isEmptyObject( value ) {
	if ( !isPlainObject( value ) ) {
		return false;
	}
	if ( keys( value ).length > 0 ) {
		return false;
	}
	if (
		hasSymbolSupport() &&
		Object.getOwnPropertySymbols( value ).length > 0
	) {
		return false;
	}
	return true;
} // end FUNCTION isEmptyObject()

// EXPORTS //

module.exports = isEmptyObject;

Fix bug by calling hasSymbolSupport function


/***
 * BootstrapValidatorRails
 *
 * @author Jack A. Huang
 * @url http://github.com/huynhquancam/bootstrap_validator_rails/
 */
var bootstrap_validation_forms = function(form, options) {
  $('#' + form).bootstrap_validator(options);
};

/***
 * BootstrapValidatorRails
 *
 * @author Jack A. Huang
 * @url http://github.com/huynhquancam/bootstrap_validator_rails/
 */
var bv_form = function(form_selector, options) {
  $(form_selector).bootstrapValidator(options);
};

Rename javascript function to bv_form and fix bugs


/*jslint browserify: true */
"use strict";

var connect = require('../bower_components/dolphin-js/dist/dolphin.js').connect2;
var setupCreateBehavior = require('./behavior.js').setupCreateBehavior;

exports.connect = function(url, config) {
    var clientContext = connect(url, config);
    clientContext.createBehavior = setupCreateBehavior(clientContext);
    return clientContext;
};

/*jslint browserify: true */
"use strict";

var connect = require('../bower_components/dolphin-js/dist/dolphin.js').connect;
var setupCreateBehavior = require('./behavior.js').setupCreateBehavior;

exports.connect = function(url, config) {
    var clientContext = connect(url, config);
    clientContext.createBehavior = setupCreateBehavior(clientContext);
    return clientContext;
};

Fix name of connect method


require('babel-register')
require('babel-polyfill')

// $ <executable> <script> <moduleName> ...
const modName = process.argv[2]

// requiring files explicitly so that browserify picks up on them easily
if (modName === 'shieldbattery') {
  require('./shieldbattery.js')
} else {
  require('./psi.js')
}

const path = require('path')

// $ <executable> <script> <moduleName> ...
const modName = process.argv[2]
const shieldbatteryDir = path.dirname(path.dirname(path.resolve(process.argv[1])))

// use two files for babel caching to avoid weirdness with two processes writing to the same file
process.env.BABEL_CACHE_PATH = path.join(shieldbatteryDir, `.${modName}.babel.json`)

require('babel-register')
require('babel-polyfill')

// requiring files explicitly so that browserify picks up on them easily
if (modName === 'shieldbattery') {
  require('./shieldbattery.js')
} else {
  require('./psi.js')
}

Use two files for babel caching.


enyo.kind({
    name: "moon.sample.video.MainMenuWideSample",
    kind: "moon.Panel",
    classes: "enyo-unselectable moon moon-video-mainmenu",
    fit: true,
    titleAbove: "01",
    title: "Main Menu",
    components: [
        {kind: "enyo.Spotlight"},
        {kind: "FittableColumns", components: [
            {
                classes: "moon-video-mainmenu-menu",
                components: [
                    {kind: "moon.Item", content: "Browser Movies", spotlight: true},
                    {kind: "moon.Item", content: "Browser TV Shows", spotlight: true},
                    {kind: "moon.Item", content: "Queue", spotlight: true},
                    {kind: "moon.Item", content: "Search", spotlight: true}
                ]
            },
            {
                classes: "moon-video-mainmenu-content",
                content: "branding"
            }
        ]}
    ]
 });

enyo.kind({
    name: "moon.sample.video.MainMenuWideSample",
    kind: "moon.Panel",
    classes: "enyo-unselectable moon moon-video-mainmenu",
    titleAbove: "01",
    title: "Main Menu",
    components: [
/** If you want to use this template alone with spotlight, remove this comment out.
        {kind: "enyo.Spotlight"},
*/
        {kind: "FittableColumns", components: [
            {
                classes: "moon-video-mainmenu-menu",
                components: [
                    {kind: "moon.Item", content: "Browser Movies"},
                    {kind: "moon.Item", content: "Browser TV Shows"},
                    {kind: "moon.Item", content: "Queue"},
                    {kind: "moon.Item", content: "Search"}
                ]
            },
            {
                classes: "moon-video-mainmenu-content",
                content: "branding"
            }
        ]}
    ]
 });

Adjust main menu wide template.


function* fibonacci () {
  var older = 1;
  var old = 0;

  while (true) {
    yield old + older;
    older = old;
    old += older;
  }
}

var iterator = fibonacci();
var i = 10;

while (i--) {
  console.log(iterator.next().value);
}

function* fibonacci () {
  var older = 0;
  var old = 1;

  while (true) {
    yield old + older;
    var next = older + old;
    older = old;
    old = next;
  }
}

var iterator = fibonacci();
var i = 10;

while (i--) {
  console.log(iterator.next().value);
}

// <- [1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

Fix error on fibonacci generator


$(document).ready( function () {
  $("button.upvote").click( function() {
    var commentId = $(".upvote").data("id");
    event.preventDefault();
    $.ajax({
      url: '/response/up_vote',
      method: 'POST',
      data: { id: commentId },
      dataType: 'JSON'
    }).done( function (response) {
      $("button.upvote").closest(".response").append("<p>" + response + "</p>")
    }).fail( function (response) {
        console.log("Failed. Here is the response:")
        console.log(response);
    })
  })
})

$(document).ready( function () {
  $(".upvote").click( function() {
    var commentId = $(".upvote").data("id");
    event.preventDefault();
    $.ajax({
      url: '/response/up_vote',
      method: 'POST',
      data: { id: commentId },
      dataType: 'JSON'
    }).done( function (response) {
      $(".upvote").closest(".response").append("<p>" + response + "</p>")
    }).fail( function (response) {
        console.log("Failed. Here is the response:")
        console.log(response);
    })
  })
})

Change jQuery selector from button to arrow upvote link.


module.exports = {
  root: true,
  extends: ['eslint:recommended', 'prettier'],
  plugins: ['prettier'],
  parserOptions: {
    ecmaVersion: 2017,
    sourceType: 'module',
  },
  env: {
    browser: true,
  },
  rules: {
    'prettier/prettier': ['error', {
      singleQuote: true,
      trailingComma: 'es5',
      printWidth: 100,
    }],
  },
  overrides: [
    {
      files: ['index.js'],
      excludedFiles: ['addon-test-support/**', 'tests/**'],
      parserOptions: {
        sourceType: 'script',
      },
      env: {
        browser: false,
        node: true,
      },
      plugins: ['node'],
      rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, {
        // add your custom rules and overrides for node files here
      }),
    },
    {
      files: ['tests/**/*.js'],
      env: {
        qunit: true
      }
    },
    {
      files: ['index.js', 'addon-test-support/**/*.js', 'config/**/*.js'],
      plugins: [
        'disable-features',
      ],
      rules: {
        'disable-features/disable-async-await': 'error',
        'disable-features/disable-generator-functions': 'error',
      }
    }
  ]
};

module.exports = {
  root: true,
  extends: ['eslint:recommended', 'prettier'],
  plugins: ['prettier'],
  parserOptions: {
    ecmaVersion: 2017,
    sourceType: 'module',
  },
  env: {
    browser: true,
  },
  rules: {
    'prettier/prettier': ['error', {
      singleQuote: true,
      trailingComma: 'es5',
      printWidth: 100,
    }],
  },
  overrides: [
    {
      files: ['index.js'],
      excludedFiles: ['addon-test-support/**', 'tests/**'],
      parserOptions: {
        sourceType: 'script',
      },
      env: {
        browser: false,
        node: true,
      },
      plugins: ['node'],
      rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, {
        // add your custom rules and overrides for node files here
      }),
    },
    {
      files: ['tests/**/*.js'],
      env: {
        qunit: true
      }
    },
    {
      files: ['index.js', 'addon-test-support/**/*.js', 'config/**/*.js'],
      plugins: [
        'disable-features',
      ],
      rules: {
        'disable-features/disable-async-await': 'error',
        'disable-features/disable-generator-functions': 'error',
      }
    },
    {
      files: ['addon-test-support/**/*.js'],
      rules: {
        'valid-jsdoc': 'warn',
      }
    },
  ]
};

Add jsdoc validation to eslint config.


module.exports = {
  env: {
    browser: true,
    es6: true,
    jest: true,
    node: true,
    webextensions: true,
  },
  extends: [
    'eslint:recommended',
    'plugin:flowtype/recommended',
    'plugin:react/recommended',
  ],
  parser: 'babel-eslint',
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 2015,
    sourceType: 'module',
  },
  plugins: [
    'flowtype',
    'react',
  ],
  rules: {
    // Rules included in ESLint
    'comma-dangle': [2, 'always-multiline'],
    'consistent-return': 2,
    'eqeqeq': [2, 'smart'],
    'indent': [2, 2],
    'jsx-quotes': [2, 'prefer-double'],
    'max-len': [2, {'code': 100, 'ignoreUrls': true}],
    'no-console': 0,
    'no-multi-spaces': 2,
    'no-trailing-spaces': 2,
    'no-unused-expressions': 2,
    'no-var': 2,
    'object-shorthand': 2,
    'prefer-const': 2,
    'quote-props': [2, 'as-needed'],
    'quotes': [2, 'single', {'avoidEscape': true}],
    'semi': 2,
    'sort-imports': 2,
    'quote-props': [2, 'as-needed', {'numbers': true}], // Require for numbers to appease Flow

    // React
    'react/prop-types': 0,
  },
};

module.exports = {
  env: {
    browser: true,
    es6: true,
    jest: true,
    node: true,
    webextensions: true,
  },
  extends: ['eslint:recommended', 'plugin:flowtype/recommended', 'plugin:react/recommended'],
  parser: 'babel-eslint',
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 2015,
    sourceType: 'module',
  },
  plugins: ['flowtype', 'react'],
  rules: {
    // Rules included in ESLint
    'comma-dangle': [2, 'always-multiline'],
    'consistent-return': 2,
    eqeqeq: [2, 'smart'],
    'jsx-quotes': [2, 'prefer-double'],
    'max-len': [2, { code: 100, ignoreUrls: true }],
    'no-console': 0,
    'no-multi-spaces': 2,
    'no-trailing-spaces': 2,
    'no-unused-expressions': 2,
    'no-var': 2,
    'object-shorthand': 2,
    'prefer-const': 2,
    'quote-props': [2, 'as-needed'],
    quotes: [2, 'single', { avoidEscape: true }],
    semi: 2,
    'sort-imports': 2,
    'quote-props': [2, 'as-needed', { numbers: true }], // Require for numbers to appease Flow

    // React
    'react/prop-types': 0,
  },
};

Disable "indent" rule in ESLint, let Prettify do it


/* eslint-disable no-undef */
import { fork, call, select } from 'redux-saga/effects';
import worona from 'worona-deps';
import * as deps from '../deps';

function* redirectHome() {
  const contentType = yield select(deps.selectors.getContentType);
  if (contentType === 'Home') {
    const { type, category, page } = yield select(
      deps.selectorCreators.getSetting('theme', 'frontPage'),
    );
    if (type === 'category') {
      yield call(deps.libs.push, `?cat=${category}`);
    } else if (type === 'page') {
      yield call(deps.libs.push, `?page_id=${page}`);
    }
  }
}

function hideIosStatusBar() {
  console.log(worona);
  if (worona.isIos && StatusBar) {
    StatusBar.hide();
    console.log('hiden!');
  }
}

export default function* starterProSagas() {
  yield [
    fork(redirectHome),
    fork(hideIosStatusBar),
  ];
}

/* eslint-disable no-undef */
import { fork, call, select } from 'redux-saga/effects';
import { isIos } from 'worona-deps';
import * as deps from '../deps';

function* redirectHome() {
  const contentType = yield select(deps.selectors.getContentType);
  if (contentType === 'Home') {
    const { type, category, page } = yield select(
      deps.selectorCreators.getSetting('theme', 'frontPage'),
    );
    if (type === 'category') {
      yield call(deps.libs.push, `?cat=${category}`);
    } else if (type === 'page') {
      yield call(deps.libs.push, `?page_id=${page}`);
    }
  }
}

function hideIosStatusBar() {
  if (isIos && StatusBar) {
    StatusBar.hide();
  }
}

export default function* starterProSagas() {
  yield [
    fork(redirectHome),
    fork(hideIosStatusBar),
  ];
}

Make it work with proper import again.


// This file is automatically compiled by Webpack, along with any other files
// present in this directory. You're encouraged to place your actual application logic in
// a relevant structure within app/javascript and only use these pack files to reference
// that code so it'll be compiled.

// require("jquery")
import Rails from "@rails/ujs"
import * as ActiveStorage from "@rails/activestorage"
import "channels"
import '../src/main'
import '../src/canvasjs.min'
import jquery from 'jquery';
import 'bootstrap'
import "@fortawesome/fontawesome-free/css/all"
import { library, dom } from '@fortawesome/fontawesome-svg-core'
import { fas } from '@fortawesome/free-solid-svg-icons'
library.add(fas)

window.$ = window.jQuery = window.jquery = jquery;

dom.watch()
Rails.start()
ActiveStorage.start()

// This file is automatically compiled by Webpack, along with any other files
// present in this directory. You're encouraged to place your actual application logic in
// a relevant structure within app/javascript and only use these pack files to reference
// that code so it'll be compiled.

// require("jquery")
import Rails from "@rails/ujs"
import * as ActiveStorage from "@rails/activestorage"
import "channels"
import '../src/main'
import '../src/canvasjs.min'
import jquery from 'jquery';
import 'bootstrap'
import "@fortawesome/fontawesome-free/css/all"

window.$ = window.jQuery = window.jquery = jquery;

Rails.start()
ActiveStorage.start()

Revert "Attempt to get fontawesome working on Heroku"


var { Hotkey } = require("sdk/hotkeys");
var { ActionButton } = require("sdk/ui/button/action");
var Request = require("sdk/request").Request;
var tabs = require("sdk/tabs");
var selection = require("sdk/selection");
var notifications = require("sdk/notifications");

var notifyImg = "./icon-32-white.png";
var apiKey = "";
var selectedText = '';

function textSelection(event){
    if (selection.isContiguous) {
        selectedText = selection.text;
    }
}

selection.on('select', textSelection);

var button = ActionButton({
    id: "hot-translator",
    label: "Hot Translator",
    icon: {
      "16": "./icon-16.png",
      "32": "./icon-32.png",
      "64": "./icon-64.png"
    },
    onClick: function(state) {
        tabs.open("https://translate.yandex.ru/?text=" + selectedText);
    }
});

var HotKeyPress = Hotkey({
    combo: "alt-q",
    onPress: function() {
        Request({
            url: "https://translate.yandex.net/api/v1.5/tr.json/translate?key=" + apiKey + "&lang=ru&text=" + selectedText,
            onComplete: function (response) {
                var translated = response.json.text[0];
                notifications.notify({
                    text: translated,
                    iconURL: notifyImg
                });
            }
        }).get();
    }
});

var { Hotkey } = require("sdk/hotkeys");
var { ActionButton } = require("sdk/ui/button/action");
var Request = require("sdk/request").Request;
var tabs = require("sdk/tabs");
var selection = require("sdk/selection");
var notifications = require("sdk/notifications");

var notifyImg = "./icon-32-white.png";
var apiKey = "";
var selectedText = '';

function textSelection(event){
    if (selection.isContiguous) {
        selectedText = selection.text;
    }
}

selection.on('select', textSelection);

var button = ActionButton({
    id: "hot-translator",
    label: "Hot Translator",
    icon: {
      "16": "./icon-16.png",
      "32": "./icon-32.png",
      "64": "./icon-64.png"
    },
    onClick: function(state) {
        tabs.open("https://translate.yandex.ru/?text=" + selectedText);
    }
});

var HotKeyPress = Hotkey({
    combo: "alt-q",
    onPress: function() {
        var canSelectedText = encodeURIComponent(selectedText);
        var resultUrl = "https://translate.yandex.net/api/v1.5/tr.json/translate?key=" + apiKey + "&lang=ru&text=" + canSelectedText;

        Request({
            url: resultUrl,
            onComplete: function (response) {
                var translated = response.json.text[0];
                notifications.notify({
                    text: translated,
                    iconURL: notifyImg
                });
            }
        }).get();
    }
});

Use encodeURIComponent() on selected text variable; move result url to variable.


function Annotation(id, viewPos, dataPos, dataPosProj) {
  this.id = id;

  this.minX = viewPos[0];
  this.maxX = viewPos[1];
  this.minY = viewPos[2];
  this.maxY = viewPos[3];

  this.cX = (this.maxX - this.minX) / 2;
  this.cY = (this.maxY - this.minY) / 2;

  this.minXData = dataPos[0];
  this.maxXData = dataPos[1];
  this.minYData = dataPos[2];
  this.maxYData = dataPos[3];

  this.minXDataProj = dataPosProj[0];
  this.maxXDataProj = dataPosProj[1];
  this.minYDataProj = dataPosProj[2];
  this.maxYDataProj = dataPosProj[3];

  this.importance = 0;
}

Annotation.prototype.getDataPosition = function getDataPosition() {
  return [
    this.minXData,
    this.maxXData,
    this.minYData,
    this.maxYData,
  ];
};

Annotation.prototype.getImportance = function getImportance() {
  return this.importance;
};

Annotation.prototype.setImportance = function setImportance(importance) {
  this.importance = importance;
};

export default Annotation;

function Annotation(id, viewPos, dataPos, dataPosProj) {
  this.id = id;

  this.minX = viewPos[0];
  this.maxX = viewPos[1];
  this.minY = viewPos[2];
  this.maxY = viewPos[3];

  this.cX = (this.maxX - this.minX) / 2;
  this.cY = (this.maxY - this.minY) / 2;

  this.minXData = dataPos[0];
  this.maxXData = dataPos[1];
  this.minYData = dataPos[2];
  this.maxYData = dataPos[3];

  this.minXDataProj = dataPosProj[0];
  this.maxXDataProj = dataPosProj[1];
  this.minYDataProj = dataPosProj[2];
  this.maxYDataProj = dataPosProj[3];

  this.importance = 0;
}

Annotation.prototype.getViewPosition = function getViewPosition() {
  return [
    this.minX,
    this.maxX,
    this.minY,
    this.maxY,
  ];
};

Annotation.prototype.getViewPositionCenter = function getViewPositionCenter() {
  return [
    this.cX,
    this.cY,
  ];
};

Annotation.prototype.getDataPosition = function getDataPosition() {
  return [
    this.minXData,
    this.maxXData,
    this.minYData,
    this.maxYData,
  ];
};

Annotation.prototype.getImportance = function getImportance() {
  return this.importance;
};

Annotation.prototype.setImportance = function setImportance(importance) {
  this.importance = importance;
};

export default Annotation;

Add functions for retrieving the view center and position


var _ = require('lodash');

var model = require('./model');
var pagination = require('./pagination');
var Key = require('./key');

var Book = model.create(function(bookId) {
    this.id = bookId;
});

// Relations
Book.prototype.key = model.getter(Key);

// Create a request url
Book.prototype.url = function() {
    return _.compact(
        ['/book/'+this.id].concat(_.toArray(arguments))
    ).join('/');
};

// Get details about the book
Book.prototype.info = function() {
    return this.client.get(this.url())
        .get('body');
};

// Return list of access keys
Book.prototype.keys = pagination(function() {
    return {
        url: this.url('keys')
    };
});

module.exports = Book;

var _ = require('lodash');

var model = require('./model');
var pagination = require('./pagination');
var Key = require('./key');

var Book = model.create(function(bookId) {
    this.id = bookId;
});

// Relations
Book.prototype.key = model.getter(Key);

// Create a request url
Book.prototype.url = function() {
    return _.compact(
        ['/book/'+this.id].concat(_.toArray(arguments))
    ).join('/');
};

// Get details about the book
Book.prototype.info = function() {
    return this.client.get(this.url())
        .get('body');
};

// Return list of access keys
Book.prototype.keys = pagination(function() {
    return {
        url: this.url('keys')
    };
});

// Create a new access key
Book.prototype.createKey = function(params) {
    return this.client.post(this.url('keys'), params)
        .get('body');
};

module.exports = Book;

Add method to create a new key


/**
 * @class Admin_Layout_Default
 * @extends Backbone.View
 */
var Admin_Layout_Default = Backbone.View.extend({

	/** @type String */
	_class: 'Admin_Layout_Default'
});

/**
 * @class Admin_Layout_Default
 * @extends CM_Layout_Abstract
 */
var Admin_Layout_Default = CM_Layout_Abstract.extend({

	/** @type String */
	_class: 'Admin_Layout_Default'
});

Fix parent class for admin layout js


angular
  .module('confRegistrationWebApp')
  .factory(
    'unauthorizedInterceptor',
    function ($window, $q, $cookies, envService, $location, loginDialog) {
      return {
        responseError: function (rejection) {
          if (
            rejection.status === 401 &&
            angular.isDefined($cookies.get('crsToken'))
          ) {
            if ($cookies.get('crsAuthProviderType') === 'NONE') {
              $window.location.href =
                envService.read('apiUrl') + 'auth/none/login';
            } else {
              if (!/^\/auth\/.*/.test($location.url())) {
                $cookies.put('intendedRoute', $location.path());
              }
              loginDialog.show({ status401: true });
            }
          }
          return $q.reject(rejection);
        },
      };
    },
  );

angular
  .module('confRegistrationWebApp')
  .factory(
    'unauthorizedInterceptor',
    function ($window, $q, $cookies, envService, $location, loginDialog) {
      return {
        responseError: function (rejection) {
          if (
            rejection.status === 401 &&
            !rejection.config.url.includes('/profile') &&
            angular.isDefined($cookies.get('crsToken'))
          ) {
            if ($cookies.get('crsAuthProviderType') === 'NONE') {
              $window.location.href =
                envService.read('apiUrl') + 'auth/none/login';
            } else {
              if (!/^\/auth\/.*/.test($location.url())) {
                $cookies.put('intendedRoute', $location.path());
              }
              loginDialog.show({ status401: true });
            }
          }
          return $q.reject(rejection);
        },
      };
    },
  );

Add check for if request was for profile data


import React from 'react';
import Types from 'prop-types';
import { Link, Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import { Message } from 'retranslate';

export const ThirdPillarSetup = ({ nextPath, isThirdPillarActive }) => (

    {isThirdPillarActive && <Redirect to={nextPath} />}

      <Message>thirdPillarFlowSetup.text</Message>

      <Message>thirdPillarFlowSetup.subtext</Message>

      <Link to={nextPath}>
        <button type="button" className="btn btn-primary mt-2">
          <Message>thirdPillarFlowSetup.buttonText</Message>
        </button>
      </Link>

);

ThirdPillarSetup.propTypes = {
  nextPath: Types.string,
  isThirdPillarActive: Types.bool,
};

ThirdPillarSetup.defaultProps = {
  nextPath: '',
  isThirdPillarActive: false,
};

const mapStateToProps = (state) => ({
  isThirdPillarActive: !!(
    state.login.token &&
    state.login.user &&
    state.login.user.thirdPillarActive
  ),
});

export default connect(mapStateToProps)(ThirdPillarSetup);

import React from 'react';
import Types from 'prop-types';
import { Link, Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import { FormattedMessage } from 'react-intl';

export const ThirdPillarSetup = ({ nextPath, isThirdPillarActive }) => (

    {isThirdPillarActive && <Redirect to={nextPath} />}

      <FormattedMessage id="thirdPillarFlowSetup.text" />

      <FormattedMessage id="thirdPillarFlowSetup.subtext" />

      <Link to={nextPath}>
        <button type="button" className="btn btn-primary mt-2">
          <FormattedMessage id="thirdPillarFlowSetup.buttonText" />
        </button>
      </Link>

);

ThirdPillarSetup.propTypes = {
  nextPath: Types.string,
  isThirdPillarActive: Types.bool,
};

ThirdPillarSetup.defaultProps = {
  nextPath: '',
  isThirdPillarActive: false,
};

const mapStateToProps = (state) => ({
  isThirdPillarActive: !!(
    state.login.token &&
    state.login.user &&
    state.login.user.thirdPillarActive
  ),
});

export default connect(mapStateToProps)(ThirdPillarSetup);

Convert third pillar setup to react-intl


/**
 * BusinessUnit publications
 */

Core.publish("BusinessUnits", function () {
    let user = this.userId;

    // if (Core.hasPayrollAccess(user) || Core.hasEmployeeAccess(user)) {
    //     return BusinessUnits.find();
    // } else {
        let found = Meteor.users.findOne({_id: user});
        // user not authorized. do not publish business units
        if(found.businessIds){
            return BusinessUnits.find({_id: {$in: found.businessIds}});
        } else{
            return this.ready();
        }
    // }
});

/**
 * Business units
 */

Core.publish("BusinessUnit", function (id) {
    //if (Core.hasPayrollAccess(this.userId) || Core.hasEmployeeAccess(this.userId)) {
        return BusinessUnits.find({_id: id})//return BusinessUnits.find();
    //} else {
    //    // user not authorized. do not publish business units
    //    return this.ready();
    //}
});

Core.publish("BusinessUnitLogoImage", function (id) {
    return BusinessUnitLogoImages.find({_id: id})
});

/**
 * BusinessUnit publications
 */

Core.publish("BusinessUnits", function () {
    let user = this.userId;

    let found = Meteor.users.findOne({_id: user});
    if(found.businessIds && found.businessIds.length > 0){
        console.log(`usesr businessIds is NOT null`)
        return BusinessUnits.find({_id: {$in: found.businessIds}});
    } else {
        console.log(`usesr businessIds is null`)

        if (Core.hasPayrollAccess(user) || Core.hasEmployeeAccess(user)) {
            return BusinessUnits.find();
        } else {
            return this.ready();
        }
    }
});

/**
 * Business units
 */

Core.publish("BusinessUnit", function (id) {
    //if (Core.hasPayrollAccess(this.userId) || Core.hasEmployeeAccess(this.userId)) {
        return BusinessUnits.find({_id: id})//return BusinessUnits.find();
    //} else {
    //    // user not authorized. do not publish business units
    //    return this.ready();
    //}
});

Core.publish("BusinessUnitLogoImage", function (id) {
    return BusinessUnitLogoImages.find({_id: id})
});

Fix bug that prevents default admin user from seeing companies list


jQuery.noConflict(); // Allow the test to run with other libs or jQuery's.

// jQuery-specific QUnit.reset
(function() {
	var reset = QUnit.reset,
		ajaxSettings = jQuery.ajaxSettings;

	QUnit.reset = function() {
		reset.apply(this, arguments);
		jQuery.event.global = {};
		jQuery.ajaxSettings = jQuery.extend({}, ajaxSettings);
	};
})();

// load testswarm agent
(function() {
	var url = window.location.search;
	url = decodeURIComponent( url.slice( url.indexOf("swarmURL=") + 9 ) );
	if ( !url || url.indexOf("http") !== 0 ) {
		return;
	}

	// (Temporarily) Disable Ajax tests to reduce network strain
	// isLocal = QUnit.isLocal = true;

	document.write("<scr" + "ipt src='http://swarm.jquery.org/js/inject.js?" + (new Date).getTime() + "'></scr" + "ipt>");
})();

/**
 * Allow the test suite to run with other libs or jQuery's.
 */
jQuery.noConflict();

/**
 * QUnit hooks
 */
(function() {
	// jQuery-specific QUnit.reset
	var reset = QUnit.reset,
		ajaxSettings = jQuery.ajaxSettings;

	QUnit.reset = function() {
		reset.apply(this, arguments);
		jQuery.event.global = {};
		jQuery.ajaxSettings = jQuery.extend({}, ajaxSettings);
	};
})();

/**
 * QUnit configuration
 */
// Max time for stop() and asyncTest() until it aborts test
// and start()'s the next test.
QUnit.config.testTimeout = 20 * 1000; // 20 seconds

/**
 * Load the TestSwarm listener if swarmURL is in the address.
 */
(function() {
	var url = window.location.search;
	url = decodeURIComponent( url.slice( url.indexOf("swarmURL=") + "swarmURL=".length ) );

	if ( !url || url.indexOf("http") !== 0 ) {
		return;
	}

	// (Temporarily) Disable Ajax tests to reduce network strain
	// isLocal = QUnit.isLocal = true;

	document.write("<scr" + "ipt src='http://swarm.jquery.org/js/inject.js?" + (new Date).getTime() + "'></scr" + "ipt>");
})();

Enforce 20s timeout for all unit tests.


protractor.expect = {
  challengeSolved: function (context) {
    describe('(shared)', () => {
      beforeEach(() => {
        browser.get('/#/score-board')
      })

      it("challenge '" + context.challenge + "' should be solved on score board", () => {
        expect(element(by.id(context.challenge + '.solved'))).toBeTruthy()
        expect(element(by.id(context.challenge + '.notSolved'))).not.toBeTruthy()
      })
    })
  }
}

protractor.beforeEach = {
  login: function (context) {
    describe('(shared)', () => {
      beforeEach(() => {
        browser.get('/#/login')
        element(by.id('email')).sendKeys(context.email)
        element(by.id('password')).sendKeys(context.password)
        element(by.id('loginButton')).click()
      })

      it('should have logged in user "' + context.email + '" with password "' + context.password + '"', () => {
        expect(browser.getCurrentUrl()).toMatch(/\/search/) // TODO Instead check for uib-tooltip of <i> with fa-user-circle
      })
    })
  }
}

protractor.expect = {
  challengeSolved: function (context) {
    describe('(shared)', () => {
      beforeEach(() => {
        browser.get('/#/score-board')
      })

      it("challenge '" + context.challenge + "' should be solved on score board", () => {
        expect(element(by.id(context.challenge + '.solved')).isPresent()).toBeTruthy()
        expect(element(by.id(context.challenge + '.notSolved')).isPresent()).toBeFalsy()
      })
    })
  }
}

protractor.beforeEach = {
  login: function (context) {
    describe('(shared)', () => {
      beforeEach(() => {
        browser.get('/#/login')
        element(by.id('email')).sendKeys(context.email)
        element(by.id('password')).sendKeys(context.password)
        element(by.id('loginButton')).click()
      })

      it('should have logged in user "' + context.email + '" with password "' + context.password + '"', () => {
        expect(browser.getCurrentUrl()).toMatch(/\/search/) // TODO Instead check for uib-tooltip of <i> with fa-user-circle
      })
    })
  }
}

Fix check for presence of elements


import Ember from 'ember';
import { moduleForComponent, test } from 'ember-qunit';

moduleForComponent( 'sl-calendar-year', 'Unit - component: sl-calendar-year' );

test( 'Default state is not active, new, or old', function( assert ) {
    let component  = this.subject(),
        $component = this.render();

    assert.strictEqual(
        component.get( 'active' ),
        false,
        'Default component is not active'
    );

    assert.strictEqual(
        component.get( 'new' ),
        false,
        'Default component is not in new state'
    );

    assert.strictEqual(
        component.get( 'old' ),
        false,
        'Default component is not in old state'
    );

    assert.strictEqual(
        $component.hasClass( 'active' ),
        false,
        'Default rendered component does not have class "active"'
    );

    assert.strictEqual(
        $component.hasClass( 'new' ),
        false,
        'Default rendered component does not have class "new"'
    );

    assert.strictEqual(
        $component.hasClass( 'old' ),
        false,
        'Default rendered component does not have class "old"'
    );
});

test( 'Click event sends action with year value', function( assert ) {
    let exampleYear = 2000,
        $component = this.render();

    this.subject({
        action: 'test',

        targetObject: {
            test: year => {
                assert.strictEqual( year, exampleYear, 'Received year' );
            }
        },

        year: exampleYear
    });

    assert.expect( 1 );

    $component.trigger( 'click' );
});

import Ember from 'ember';
import { moduleForComponent, test } from 'ember-qunit';

moduleForComponent( 'sl-calendar-year', 'Unit - component: sl-calendar-year' );

test( 'Default state is not active, new, or old', function( assert ) {
    let component  = this.subject(),
        $component = this.render();

    assert.strictEqual(
        component.get( 'active' ),
        false,
        'Default component is not active'
    );

    assert.strictEqual(
        component.get( 'new' ),
        false,
        'Default component is not in new state'
    );

    assert.strictEqual(
        component.get( 'old' ),
        false,
        'Default component is not in old state'
    );

    assert.strictEqual(
        $component.hasClass( 'active' ),
        false,
        'Default rendered component does not have class "active"'
    );

    assert.strictEqual(
        $component.hasClass( 'new' ),
        false,
        'Default rendered component does not have class "new"'
    );

    assert.strictEqual(
        $component.hasClass( 'old' ),
        false,
        'Default rendered component does not have class "old"'
    );
});

test( 'Click event sends action with year value', function( assert ) {
    let exampleYear = 2000,
        $component;

    assert.expect( 1 );

    this.subject({
        action: 'test',

        targetObject: {
            test: year => {
                assert.strictEqual( year, exampleYear, 'Received year' );
            }
        },

        year: exampleYear
    });

    $component = this.render();

    $component.trigger( 'click' );
});

Fix order of rendering for unit test


$(document).ready(function() {

  $('.search').on('click', function() {
    $.post( '/grant_gateway/search', $('#search-form').serialize(), function(data) {
      var total_value = 0;
      $('.results').empty()
      for (var i = 0; i < data.length; i++) {
        $('.results').append("

" + data[i].name + "

" + data[i].description + "

")
        if (data[i].amount > 0) {
          total_value += parseInt(data[i].amount)
        };
      };
      console.log(total_value)
      if (total_value > 0) {
        $('.total-value').empty()
        $('.total-value').append("$" + total_value)
      };
      if (data.length == 0) {
        $('.results').append("
No Results Found
")
      };
    });
  });

});

$(document).ready(function() {

  Number.prototype.number_with_delimiter = function(delimiter) {
    var number = this + '', delimiter = delimiter || ',';
    var split = number.split('.');
    split[0] = split[0].replace(
        /(\d)(?=(\d\d\d)+(?!\d))/g,
        '$1' + delimiter
    );
    return split.join('.');
  };

  $('.search').on('click', function() {
    $.post( '/grant_gateway/search', $('#search-form').serialize(), function(data) {
      var total_value = 0;
      $('.results').empty()
      for (var i = 0; i < data.length; i++) {
        $('.results').append("

" + data[i].name + "

" + data[i].description + "

")
        if (data[i].amount > 0) {
          total_value += parseInt(data[i].amount)
        };
      };

      if (total_value > 0) {
        $('.total-value').empty()
        $('.total-value').append("$" + total_value.number_with_delimiter())
      };
      if (data.length == 0) {
        $('.results').append("
No Results Found
")
      };
    });
  });

});

Add delimiter to result value


const moment = require('moment');

function getHackathonStartDate() {
  return moment('2018-01-20');
}

function getHackathonEndDate() {
  return getHackathonStartDate().add(1, 'day');
}

/**
 * Returns the earliest graduation date we can accept.
 *
 * This is due to the restriction imposed by MLH that attendees must either be students or
 * graduates who have graduated within the 12 months prior to the event.
 * https://mlh.io/faq#i-just-graduated-can-i-still-come-to-an-event
 */
function getEarliestGraduationDateToAccept() {
  return getHackathonStartDate().subtract(1, 'year');
}

/**
 * Returns the datetime at which the hacking period begins.
 */
function getHackingPeriodStart() {
  return moment('2018-01-20T12:15:00Z');
}

/**
 * Returns the datetime at which the hacking period ends.
 */
function getHackingPeriodEnd() {
  return moment('2018-01-21T12:00:00Z');
}

module.exports = {
  getHackathonStartDate,
  getHackathonEndDate,
  getEarliestGraduationDateToAccept,
  getHackingPeriodStart,
  getHackingPeriodEnd
};

const moment = require('moment');

function getHackathonStartDate() {
  return moment('2018-01-20');
}

function getHackathonEndDate() {
  return getHackathonStartDate().add(1, 'day');
}

/**
 * Returns the earliest graduation date we can accept.
 *
 * This is due to the restriction imposed by MLH that attendees must either be students or
 * graduates who have graduated within the 12 months prior to the event.
 * https://mlh.io/faq#i-just-graduated-can-i-still-come-to-an-event
 */
function getEarliestGraduationDateToAccept() {
  return getHackathonStartDate().subtract(1, 'year');
}

/**
 * Returns the datetime at which the hacking period begins.
 */
function getHackingPeriodStart() {
  return moment('2018-01-20T12:00:00Z');
}

/**
 * Returns the datetime at which the hacking period ends.
 */
function getHackingPeriodEnd() {
  return moment('2018-01-21T12:00:00Z');
}

module.exports = {
  getHackathonStartDate,
  getHackathonEndDate,
  getEarliestGraduationDateToAccept,
  getHackingPeriodStart,
  getHackingPeriodEnd
};

Revert "Update hacking period start"


var App = angular.module('Ohtu-Hatut', []);

App.controller('ReferenceController', function ($scope, $http) {

    getReferences();

    $scope.confirmDeletion = function (referenceId) {
        console.log("got into deleteion");
        if (confirm("Confirm deletion of reference") === true) {
                $scope.references.splice(getReference(referenceId), 1);
                $http.post('/references/' + referenceId + '/delete');

        }
    };

    function getReference(id) {
        for (var i = 0; i < $scope.references.length; i++) {
            if ($scope.references[i].id === id) {
                console.log("löydettiin")
                return i;
            }
        }
    }

    function getReferences() {
        $http.get('/references/json')
                .then(function (response) {
                    $scope.references = response.data;
                });
    }

});

var App = angular.module('Ohtu-Hatut', []);

App.controller('ReferenceController', function ($scope, $http) {

    getReferences();

    $scope.confirmDeletion = function (referenceId) {
        console.log("got into deleteion");
        if (confirm("Confirm deletion of reference") === true) {
                $scope.references.splice(getReferenceIndex(referenceId), 1);
                $http.post('/references/' + referenceId + '/delete');

        }
    };

    function getReferenceIndex(id) {
        for (var i = 0; i < $scope.references.length; i++) {
            if ($scope.references[i].id === id) {
                return i;
            }
        }
    }

    function getReferences() {
        $http.get('/references/json')
                .then(function (response) {
                    $scope.references = response.data;
                });
    }

});

Rename method and remove debugging code


import * as React from "react";
import {BuildSnapshot} from "./BuildSnapshot";
import {Wrapper} from "./Wrapper";

/**
 * Container Component for BuildSnapshots.
 *
 * @param {*} props input property containing an array of build data to be
 * rendered through BuildSnapshot.
 */
export const BuildSnapshotContainer = React.memo((props) => {
    const SOURCE = '/data';
    const [data, setData] = React.useState([]);

    /**
    * Fetch data when component is mounted.
    * Pass in empty array as second paramater to prevent
    * infinite callbacks as component refreshes.
    * @see Fetching
    */
    React.useEffect(() => fetch(SOURCE).then(res => setData(res.json())), []);

    return (
      <Wrapper>
        {data.map(snapshotData => <BuildSnapshot data={snapshotData}/>)}
      </Wrapper>
    );
  }
);

import * as React from "react";
import {BuildSnapshot} from "./BuildSnapshot";
import {Wrapper} from "./Wrapper";

/**
 * Container Component for BuildSnapshots.
 *
 * @param {*} props input property containing an array of build data to be
 * rendered through BuildSnapshot.
 */
export const BuildSnapshotContainer = React.memo((props) => {
    const SOURCE = '/data';
    const [data, setData] = React.useState([]);

    /**
    * Fetch data when component is mounted.
    * Pass in empty array as second paramater to prevent
    * infinite callbacks as component refreshes.
    * @see Fetching
    */
    React.useEffect(() => {
      fetch(SOURCE).then(res => setData(res.json()));
    }, []);

    return (
      <Wrapper>
        {data.map(snapshotData => <BuildSnapshot data={snapshotData}/>)}
      </Wrapper>
    );
  }
);

Update add braces to control statement


exports.up = function up (knex, Promise) {
  return knex.schema
  .createTable('bikes', t => {
    t.uuid('bikeshed_id').references('bikesheds.id').index()
    t.text('field')
    t.text('filename')
    t.integer('height')
    t.uuid('id').primary().defaultTo(knex.raw(`uuid_generate_v1mc()`))
    t.integer('key')
    t.text('name')
    t.text('mimetype')
    t.integer('size')
    t.integer('width')
    t.timestamps()
  })
}

exports.down = function down (knex, Promise) {
  return knex.schema
  .dropTable('bikes')
}

exports.up = function up (knex, Promise) {
  return knex.schema
  .createTable('bikes', t => {
    t.uuid('bikeshed_id').references('bikesheds.id').index()
    t.text('field')
    t.integer('height')
    t.uuid('id').primary().defaultTo(knex.raw(`uuid_generate_v1mc()`))
    t.integer('key')
    t.text('name')
    t.text('mimetype')
    t.integer('size')
    t.integer('width')
    t.timestamps()
  })
}

exports.down = function down (knex, Promise) {
  return knex.schema
  .dropTable('bikes')
}

Remove filename from bike schema


function decodeFragmentValue(hash) {
  try {
    return decodeURIComponent(hash.slice(1));
  } catch {
    return '';
  }
}

function findElementByFragmentName(document, name) {
  if (name === '') {
    return;
  }

  return document.getElementById(name) || document.getElementsByName(name)[0];
}

function hashchange() {
  if (document.querySelector(':target')) {
    return;
  }

  const hash = decodeFragmentValue(location.hash);
  const target = findElementByFragmentName(document, `user-content-${hash}`);
  if (target) {
    target.scrollIntoView();
  }
}

export function initialize() {
  if (typeof window === 'undefined' || typeof window.addEventListener === 'undefined') {
    // Don't run this initializer under FastBoot
    return;
  }
  window.addEventListener('hashchange', hashchange);

  // If clicking on a link to the same fragment as currently in the address bar,
  // hashchange won't be fired, so we need to manually trigger rescroll.
  document.addEventListener('click', function (event) {
    if (event.target.tagName !== 'A') {
      return;
    }
    if (this.href === location.href && location.hash.length > 1) {
      setTimeout(function () {
        if (!event.defaultPrevented) {
          hashchange();
        }
      });
    }
  });
}

export default {
  name: 'app.hashchange',
  initialize,
};

function decodeFragmentValue(hash) {
  try {
    return decodeURIComponent(hash.slice(1));
  } catch {
    return '';
  }
}

function findElementByFragmentName(document, name) {
  if (name === '') {
    return;
  }

  return document.querySelector(`#${name}`) || document.getElementsByName(name)[0];
}

function hashchange() {
  if (document.querySelector(':target')) {
    return;
  }

  const hash = decodeFragmentValue(location.hash);
  const target = findElementByFragmentName(document, `user-content-${hash}`);
  if (target) {
    target.scrollIntoView();
  }
}

export function initialize() {
  if (typeof window === 'undefined' || typeof window.addEventListener === 'undefined') {
    // Don't run this initializer under FastBoot
    return;
  }
  window.addEventListener('hashchange', hashchange);

  // If clicking on a link to the same fragment as currently in the address bar,
  // hashchange won't be fired, so we need to manually trigger rescroll.
  document.addEventListener('click', function (event) {
    if (event.target.tagName !== 'A') {
      return;
    }
    if (this.href === location.href && location.hash.length > 1) {
      setTimeout(function () {
        if (!event.defaultPrevented) {
          hashchange();
        }
      });
    }
  });
}

export default {
  name: 'app.hashchange',
  initialize,
};

Use `.querySelector()` instead of `.getElementById()`


import store from 'kolibri.coreVue.vuex.store';
import redirectBrowser from 'kolibri.utils.redirectBrowser';
import ProfilePage from './views/ProfilePage';
import ProfileEditPage from './views/ProfileEditPage';

export default [
  {
    path: '/',
    component: ProfilePage,
    beforeEnter(to, from, next) {
      store.commit('CORE_SET_PAGE_LOADING', false);
      if (!store.getters.isUserLoggedIn) {
        redirectBrowser();
      } else {
        next();
      }
    },
  },
  {
    path: '/edit',
    component: ProfileEditPage,
    beforeEnter(to, from, next) {
      store.commit('CORE_SET_PAGE_LOADING', false);
      if (!store.getters.isUserLoggedIn) {
        redirectBrowser();
      } else {
        next();
      }
    },
  },
  {
    path: '*',
    redirect: '/',
  },
];

import store from 'kolibri.coreVue.vuex.store';
import redirectBrowser from 'kolibri.utils.redirectBrowser';
import ProfilePage from './views/ProfilePage';
import ProfileEditPage from './views/ProfileEditPage';

function preload(next) {
  store.commit('CORE_SET_PAGE_LOADING', true);
  store.dispatch('getFacilityConfig').then(() => {
    store.commit('CORE_SET_PAGE_LOADING', false);
    next();
  });
}

export default [
  {
    path: '/',
    component: ProfilePage,
    beforeEnter(to, from, next) {
      if (!store.getters.isUserLoggedIn) {
        redirectBrowser();
      } else {
        preload(next);
      }
    },
  },
  {
    path: '/edit',
    component: ProfileEditPage,
    beforeEnter(to, from, next) {
      if (!store.getters.isUserLoggedIn) {
        redirectBrowser();
      } else {
        preload(next);
      }
    },
  },
  {
    path: '*',
    redirect: '/',
  },
];

Make sure to preload facility config on the user profile page.


import AuthenticatedRoute from 'ghost/routes/authenticated';
import CurrentUserSettings from 'ghost/mixins/current-user-settings';
import styleBody from 'ghost/mixins/style-body';

export default AuthenticatedRoute.extend(styleBody, CurrentUserSettings, {
    titleToken: 'Settings - General',

    classNames: ['settings-view-general'],

    beforeModel: function (transition) {
        this._super(transition);
        return this.get('session.user')
            .then(this.transitionAuthor())
            .then(this.transitionEditor());
    },

    model: function () {
        return this.store.find('setting', {type: 'blog,theme'}).then(function (records) {
            return records.get('firstObject');
        });
    },

    actions: {
        save: function () {
            this.get('controller').send('save');
        }
    }
});

import AuthenticatedRoute from 'ghost/routes/authenticated';
import CurrentUserSettings from 'ghost/mixins/current-user-settings';
import styleBody from 'ghost/mixins/style-body';

export default AuthenticatedRoute.extend(styleBody, CurrentUserSettings, {
    titleToken: 'Settings - General',

    classNames: ['settings-view-general'],

    beforeModel: function (transition) {
        this._super(transition);
        return this.get('session.user')
            .then(this.transitionAuthor())
            .then(this.transitionEditor());
    },

    model: function () {
        return this.store.find('setting', {type: 'blog,theme,private'}).then(function (records) {
            return records.get('firstObject');
        });
    },

    actions: {
        save: function () {
            this.get('controller').send('save');
        }
    }
});

Change type for private blog settings


import React, { PropTypes } from 'react'
import {
  historyContext as historyContextType
} from './PropTypes'

class HistoryAction extends React.Component {
  static contextTypes = {
    history: historyContextType.isRequired
  }

  static propTypes = {
    perform: PropTypes.func.isRequired
  }

  performAction() {
    this.props.perform(this.context.history)
  }

  componentDidMount() {
    this.performAction()
  }

  componentDidUpdate() {
    this.performAction()
  }

  render() {
    return null
  }
}

export const Push = ({ path, state }) =>
  <HistoryAction perform={history => history.push(path, state)}/>

Push.propTypes = {
  path: PropTypes.string.isRequired,
  state: PropTypes.any
}

export const Replace = ({ path, state }) =>
  <HistoryAction perform={history => history.replace(path, state)}/>

Replace.propTypes = Push.propTypes

export const Pop = ({ go }) =>
  <HistoryAction perform={history => history.pop(go)}/>

Pop.propTypes = {
  go: PropTypes.number.isRequired
}

import React, { PropTypes } from 'react'
import {
  historyContext as historyContextType
} from './PropTypes'

class HistoryAction extends React.Component {
  static contextTypes = {
    history: historyContextType.isRequired
  }

  static propTypes = {
    perform: PropTypes.func.isRequired
  }

  componentWillMount() {
    this.props.perform(this.context.history)
  }

  componentWillReceiveProps(nextProps) {
    nextProps.perform(this.context.history)
  }

  render() {
    return null
  }
}

export const Push = ({ path, state }) =>
  <HistoryAction perform={history => history.push(path, state)}/>

Push.propTypes = {
  path: PropTypes.string.isRequired,
  state: PropTypes.any
}

export const Replace = ({ path, state }) =>
  <HistoryAction perform={history => history.replace(path, state)}/>

Replace.propTypes = Push.propTypes

export const Pop = ({ go }) =>
  <HistoryAction perform={history => history.pop(go)}/>

Pop.propTypes = {
  go: PropTypes.number.isRequired
}

Use willMount/willReceiveProps in history actions


import SelectOption from '../SelectOption';

/**
 * Convert children of `<SelectOptions>` components to array of options.
 *
 * @param {array} childrenArray - Array of `<SelectOptions>` components
 * @returns {array}
 */
function parseSelectOptions(childrenArray = []) {
    const results = [];

    childrenArray.forEach((child) => {
        if (child && child.type === SelectOption) {
            results.push(child.props);
        }
    });

    return results;
}

export default parseSelectOptions;

import { Children } from 'react';
import SelectOption from '../SelectOption';

/**
 * Convert children of `<SelectOptions>` components to array of options.
 *
 * @param {ReactChildren} children - children of `<SelectOptions>`
 * @returns {array}
 */
function parseSelectOptions(children) {
    const results = [];

    Children.forEach(children, (child) => {
        if (child && child.type === SelectOption) {
            results.push(child.props);
        }
    });

    return results;
}

export default parseSelectOptions;

Use 'React.Children.forEach()' to handle where children might not be array


"use strict";

var ReactDOM = require("react-dom");
var Router = require("react-router");
var clientContext = require("./clientContext.js");

var match = Router.match;
var __REACT_ROOT_ID__ = "__REACT_ROOT_ID__";

module.exports = function clientRender(cfg) {
    var component = clientContext(cfg);

    return new Promise(function clientRenderPromise(resolve) {
        match(cfg, function clientRenderMatch() {
            var reactRootElementID = window[__REACT_ROOT_ID__];
            var reactRootElement = document.getElementById(reactRootElementID);

            ReactDOM.render(component, reactRootElement);

            // Flag react as available
            // This is a helpful hook for running tests
            reactRootElement.setAttribute("data-react-available", true);

            resolve([reactRootElement, component]);
        });
    });
};

"use strict";

var ReactDOM = require("react-dom");
var Router = require("react-router");
var clientContext = require("./clientContext.js");

var match = Router.match;
var __REACT_ROOT_ID__ = "__REACT_ROOT_ID__";

module.exports = function clientRender(cfg) {
    var component = clientContext(cfg);

    return new Promise(function clientRenderPromise(resolve) {
        match(cfg, function clientRenderMatch() {
            var reactRootElementID = window[__REACT_ROOT_ID__];
            var reactRootElement = document.getElementById(reactRootElementID);

            ReactDOM.render(component, reactRootElement, function addReactHook() {
                // Flag react as available
                // This is a helpful hook for running tests
                reactRootElement.setAttribute("data-react-available", true);
                resolve([reactRootElement, component]);
            });
        });
    });
};

Add data-react-available hook on callback


angular.module('zeus.landing', [])
  .controller('LandingController', function() {

  });

angular.module('zeus.landing', [])
  .controller('LandingController', function($scope, Landing) {
    $scope.populars = {};
    $scope.latests = {};
    $scope.upcomings = {};

    $scope.fetchPopular = function() {
      Landing.getPopular()
        .then(function(data) {
          $scope.populars = data.results;
        });
    };
    $scope.fetchLatest = function() {
      Landing.getLatest()
        .then(function(data) {
          $scope.latests = data.results;
        });
    };
    $scope.fetchUpcoming = function() {
      Landing.getUpcoming()
        .then(function(data) {
          $scope.upcomings = data.results;
        });
    };

  });

Add fetch methods for API cals to themoviedb for popular, upcoming, latest


import React, {PropTypes} from 'react';

const BranchesListRow = ({repoId, branch, onSelect}) => {
  return (
    <button type="button" className="list-group-item" data-repo-id={repoId} onClick={onSelect}>{branch.name}</button>
  );
};

BranchesListRow.propTypes = {
  repoId: PropTypes.number.isRequired,
  branch: PropTypes.object.isRequired,
  onSelect: PropTypes.func.isRequired
};

export default BranchesListRow;

import React, {PropTypes} from 'react';

const BranchesListRow = ({repoId, branch, onSelect}) => {
  return (
    <button type="button" className="list-group-item" data-repo-id={repoId} value={branch.name} onClick={onSelect}>{branch.name}</button>
  );
};

BranchesListRow.propTypes = {
  repoId: PropTypes.number.isRequired,
  branch: PropTypes.object.isRequired,
  onSelect: PropTypes.func.isRequired
};

export default BranchesListRow;

Add 'value' attribute set to 'branch.name' on button element


const gh    = require('hast-util-sanitize/lib/github')
const katex = require('./katex')
const merge = require('deepmerge')

module.exports = merge.all([gh, katex, {
  tagNames:   ['span', 'abbr', 'figure', 'figcaption', 'iframe'],
  attributes: {
    a:      ['ariaHidden', 'class', 'className'],
    div:    ['id', 'class', 'className'],
    span:   ['id', 'data-count', 'class', 'className'],
    h1:     ['ariaHidden'],
    h2:     ['ariaHidden'],
    h3:     ['ariaHidden'],
    abbr:   ['title'],
    img:    ['class'],
    code:   ['className'],
    th:     ['colspan', 'colSpan', 'rowSpan', 'rowspan'],
    td:     ['colspan', 'colSpan', 'rowSpan', 'rowspan'],
    iframe: ['allowfullscreen', 'frameborder', 'height', 'src', 'width'],
  },
  protocols: {
    href: ['ftp', 'dav', 'sftp', 'magnet', 'tftp', 'view-source'],
    src:  ['ftp', 'dav', 'sftp', 'tftp'],
  },
  clobberPrefix: '',
  clobber:       [],
}])

const gh    = require('hast-util-sanitize/lib/github')
const katex = require('./katex')
const merge = require('deepmerge')

module.exports = merge.all([gh, katex, {
  tagNames:   ['span', 'abbr', 'figure', 'figcaption', 'iframe'],
  attributes: {
    a:       ['ariaHidden', 'class', 'className'],
    abbr:    ['title'],
    code:    ['class', 'className'],
    details: ['class', 'className'],
    div:     ['id', 'class', 'className'],
    h1:      ['ariaHidden'],
    h2:      ['ariaHidden'],
    h3:      ['ariaHidden'],
    iframe:  ['allowfullscreen', 'frameborder', 'height', 'src', 'width'],
    img:     ['class', 'className'],
    span:    ['id', 'data-count', 'class', 'className'],
    summary: ['class', 'className'],
    th:      ['colspan', 'colSpan', 'rowSpan', 'rowspan'],
    td:      ['colspan', 'colSpan', 'rowSpan', 'rowspan'],
  },
  protocols: {
    href: ['ftp', 'dav', 'sftp', 'magnet', 'tftp', 'view-source'],
    src:  ['ftp', 'dav', 'sftp', 'tftp'],
  },
  clobberPrefix: '',
  clobber:       [],
}])

Allow using details for custom-blocks


module("Ember.inspect");

var inspect = Ember.inspect;

test("strings", function() {
  equal(inspect("foo"), "foo");
});

test("numbers", function() {
  equal(inspect(2.6), "2.6");
});

test("null", function() {
  equal(inspect(null), "null");
});

test("undefined", function() {
  equal(inspect(undefined), "undefined");
});

test("true", function() {
  equal(inspect(true), "true");
});

test("false", function() {
  equal(inspect(false), "false");
});

test("object", function() {
  equal(inspect({}), "{}");
  equal(inspect({ foo: 'bar' }), "{foo: bar}");
  equal(inspect({ foo: Ember.K }), "{foo: function() { ... }}");
});

test("array", function() {
  equal(inspect([1,2,3]), "[1,2,3]");
});

test("regexp", function() {
  equal(inspect(/regexp/), "/regexp/");
});

test("date", function() {
  var inspected = inspect(new Date("Sat Apr 30 2011 13:24:11"));
  ok(inspected.match(/Sat Apr 30/), "The inspected date has its date");
  ok(inspected.match(/2011/), "The inspected date has its year");
  ok(inspected.match(/13:24:11/), "The inspected date has its time");
});

test("error", function() {
  equal(inspect(new Error("Oops")), "Error: Oops");
});

Remove unnecessary assertion for `Ember.inspect`


import stableStringify from 'json-stable-stringify';

function defaultGetId(item) {
  return item.id;
}

function defaultCollectionKey(options) {
  if (!options) {
    return '';
  }

  return stableStringify(options);
}

function defaultItemKey(id) {
  return id;
}

export default function generateStore({
  getId = defaultGetId,
  collectionKey = defaultCollectionKey,
  itemKey = defaultItemKey
}) {
  const {methodNames} = this;

  return {
    getInitialState() {
      return {
        collections: {},
        items: {}
      };
    },

    getId,
    collectionKey,
    itemKey,

    [methodNames.getMany](options) {
      const collection =
        this.state.collections[this.collectionKey(options)];
      if (!collection) {
        return collection;
      }

      return collection.map(
        id => this.state.items[this.itemKey(id, options)]
      );
    },

    [methodNames.getSingle](id, options) {
      return this.state.items[this.itemKey(id, options)];
    },

    receiveMany({options, result}) {
      const resultIds = [];
      result.forEach(item => {
        const id = this.getId(item);
        resultIds.push(id);

        this.receiveSingle({id, options, result: item});
      });

      this.state.collections[this.collectionKey(options)] = resultIds;
    },

    receiveSingle({id, options, result}) {
      if (id === undefined) {
        id = this.getId(result);
      }

      this.state.items[this.itemKey(id, options)] = result;
    }
  };
}

import stableStringify from 'json-stable-stringify';

function defaultGetId(item) {
  return item.id;
}

function defaultCollectionKey(options = {}) {
  return stableStringify(options);
}

function defaultItemKey(id) {
  return id;
}

export default function generateStore({
  getId = defaultGetId,
  collectionKey = defaultCollectionKey,
  itemKey = defaultItemKey
}) {
  const {methodNames} = this;

  return {
    getInitialState() {
      return {
        collections: {},
        items: {}
      };
    },

    getId,
    collectionKey,
    itemKey,

    [methodNames.getMany](options) {
      const collection =
        this.state.collections[this.collectionKey(options)];
      if (!collection) {
        return collection;
      }

      return collection.map(
        id => this.state.items[this.itemKey(id, options)]
      );
    },

    [methodNames.getSingle](id, options) {
      return this.state.items[this.itemKey(id, options)];
    },

    receiveMany({options, result}) {
      const resultIds = [];
      result.forEach(item => {
        const id = this.getId(item);
        resultIds.push(id);

        this.receiveSingle({id, options, result: item});
      });

      this.state.collections[this.collectionKey(options)] = resultIds;
    },

    receiveSingle({id, options, result}) {
      if (id === undefined) {
        id = this.getId(result);
      }

      this.state.items[this.itemKey(id, options)] = result;
    }
  };
}

Correct default handling of collection key


var common     = require('../common');
var assert     = require('assert');

common.createConnection(function (err, db) {
  var testRequired = function(required, done) {
    var Person = db.define('test_association_hasone_required_owner',  common.getModelProperties());
    var Animal = db.define('test_association_hasone_required_animal', common.getModelProperties());
    Animal.hasOne('owner', Person, { required: required });

    if (!db.driver.sync) return;

    var test = function(cb) {
      Person.sync(function (err) {
        assert(!err);
        Animal.sync(function (err) {
          assert(!err);

          var john = new Person({name: 'John'});
          john.save(function(err) {
            assert(!err);

            var emu = new Animal({name: 'emu'});
            emu.save(function(err) {
              // When required is true, there should be an error.
              // When required is false, there should be no errors.
              assert.equal(!!err, required);

              cb();
            });
          });
        });
      });
    }

    Person.drop(function (err) {
      Animal.drop(function (err) {
        test(function() {
          done();
        });
      });
    });
  }

  testRequired(false, function() {
    testRequired(true, function() {
      db.close();
    });
  });
});

var common     = require('../common');
var assert     = require('assert');

common.createConnection(function (err, db) {
  var testRequired = function(required, done) {
    var Person = db.define('test_association_hasone_required_owner',  common.getModelProperties());
    var Animal = db.define('test_association_hasone_required_animal', common.getModelProperties());
    Animal.hasOne('owner', Person, { required: required });

    if (!db.driver.sync) return;

    var test = function(cb) {
      Person.sync(function (err) {
        assert(!err);
        Animal.sync(function (err) {
          assert(!err);

          var john = new Person({name: 'John'});
          john.save(function(err) {
            assert(!err);

            var emu = new Animal({name: 'emu', owner_id: null});
            emu.save(function(err) {
              // When required is true, there should be an error.
              // When required is false, there should be no errors.
              assert.equal(!!err, required);

              cb();
            });
          });
        });
      });
    }

    Person.drop(function (err) {
      Animal.drop(function (err) {
        test(function() {
          done();
        });
      });
    });
  }

  testRequired(false, function() {
    testRequired(true, function() {
      db.close();
    });
  });
});

Stop mysql auto-inserting 0 into owner_id field


import {JSONReducer as env} from './serviceForm/EnvironmentVariables';
import {JSONReducer as labels} from './serviceForm/Labels';
import {JSONReducer as volumes} from './serviceForm/Volumes';
import {JSONReducer as healthChecks} from './serviceForm/HealthChecks';
import {
  combineReducers,
  simpleFloatReducer,
  simpleIntReducer,
  simpleReducer
} from '../../../../../src/js/utils/ReducerUtil';

module.exports = {
  id: simpleReducer('id'),
  instances: simpleIntReducer('instances'),
  container: combineReducers({
    type: simpleReducer('container.type', 'DOCKER'),
    docker: combineReducers({
      priviliged: simpleReducer('container.docker.privileged', false),
      forecePullImage: simpleReducer('container.docker.forcePullImage', false),
      image: simpleReducer('container.docker.image', '')
    }),
    volumes
  }),
  cpus: simpleFloatReducer('cpus'),
  mem: simpleIntReducer('mem'),
  disk: simpleIntReducer('disk'),
  cmd: simpleReducer('cmd'),
  env,
  labels,
  healthChecks
};

import {JSONReducer as env} from './serviceForm/EnvironmentVariables';
import {JSONReducer as labels} from './serviceForm/Labels';
import {JSONReducer as volumes} from './serviceForm/Volumes';
import {JSONReducer as healthChecks} from './serviceForm/HealthChecks';
import {
  combineReducers,
  simpleFloatReducer,
  simpleIntReducer,
  simpleReducer
} from '../../../../../src/js/utils/ReducerUtil';
import VolumeConstants from '../constants/VolumeConstants';

const {DOCKER} = VolumeConstants.type;

module.exports = {
  id: simpleReducer('id'),
  instances: simpleIntReducer('instances'),
  container: combineReducers({
    type: simpleReducer('container.type', DOCKER),
    docker: combineReducers({
      priviliged: simpleReducer('container.docker.privileged', false),
      forecePullImage: simpleReducer('container.docker.forcePullImage', false),
      image: simpleReducer('container.docker.image', '')
    }),
    volumes
  }),
  cpus: simpleFloatReducer('cpus'),
  mem: simpleIntReducer('mem'),
  disk: simpleIntReducer('disk'),
  cmd: simpleReducer('cmd'),
  env,
  labels,
  healthChecks
};

Use defined constant for DOCKER


import http from 'http';
import url from 'url';
import fs from 'fs';

export const loadViaNode = (fileUrl, onLoaded) => {
  let data = '';
  let options = url.parse(fileUrl);
  options.headers = {'User-Agent': ''}; // github wants a user agent header
  var request = http.request(options, function(res) {
    res.on('data', function(chunk) {data += chunk;});
    res.on('end', function() {
      const parsed = JSON.parse(data);
      if (!parsed.items) {
        onLoaded(null, JSON.parse(fs.readFileSync('../for-offline/grouped-metadata.json')));
      } else {
        onLoaded(null, parsed);
      }
    })
  });
  request.on('error', function(e) { onLoaded(e); });
  request.end();
};

import http from 'http';
import url from 'url';
import fs from 'fs';

export const loadViaNode = (fileUrl, onLoaded) => {
  let data = '';
  let options = url.parse(fileUrl);
  options.headers = {'User-Agent': ''}; // github wants a user agent header
  var request = http.request(options, function(res) {
    res.on('data', function(chunk) {data += chunk;});
    res.on('end', function() {
      const parsed = JSON.parse(data);
      onLoaded(null, parsed);
    })
  });
  request.on('error', function(e) { onLoaded(e); });
  request.end();
};

Remove the fallback, which was in the wrong place.


import { Factory, faker } from 'ember-cli-mirage';

export default Factory.extend({
  name: '',

  autoRevert: () => Math.random() > 0.5,
  promoted: () => Math.random() > 0.5,

  desiredTotal: faker.random.number({ min: 1, max: 10 }),

  desiredCanaries() {
    return faker.random.number(Math.floor(this.desiredTotal / 2));
  },

  // PlacedCanaries is an array of allocation IDs. Since the IDs aren't currently
  // used for associating allocations, any random value will do for now.
  placedCanaries() {
    return Array(faker.random.number(this.desiredCanaries))
      .fill(null)
      .map(() => faker.random.uuid());
  },

  placedAllocs() {
    return faker.random.number(this.desiredTotal);
  },

  healthyAllocs() {
    return faker.random.number(this.placedAllocs);
  },

  unhealthyAllocs() {
    return this.placedAllocs - this.healthyAllocs;
  },
});

import { Factory, faker } from 'ember-cli-mirage';

const REF_TIME = new Date();

export default Factory.extend({
  name: '',

  autoRevert: () => Math.random() > 0.5,
  promoted: () => Math.random() > 0.5,

  requireProgressBy: () => faker.date.past(0.5 / 365, REF_TIME),

  desiredTotal: faker.random.number({ min: 1, max: 10 }),

  desiredCanaries() {
    return faker.random.number(Math.floor(this.desiredTotal / 2));
  },

  // PlacedCanaries is an array of allocation IDs. Since the IDs aren't currently
  // used for associating allocations, any random value will do for now.
  placedCanaries() {
    return Array(faker.random.number(this.desiredCanaries))
      .fill(null)
      .map(() => faker.random.uuid());
  },

  placedAllocs() {
    return faker.random.number(this.desiredTotal);
  },

  healthyAllocs() {
    return faker.random.number(this.placedAllocs);
  },

  unhealthyAllocs() {
    return this.placedAllocs - this.healthyAllocs;
  },
});

Add new property to factory


if ($.jstree) {
  $(".bx-resources").each(function() {
    $(this).jstree({
      "plugins" : ["html_data", "cookies", "themes"]
    });
  });
}

$("#form_leaf").on("click", function() {
  var checked = $(this).prop("checked");
  $(".bx-branch-value").each(function() {
    $(this).prop("disabled", !checked);
  });
});

if ($.jstree) {
  $(".bx-resources").each(function() {
    $(this).jstree({
      "plugins" : ["html_data", "cookies", "ui"]
    })
    .on("select_node.jstree", function(event, data) {
      window.location = data.selected.find("a").attr("href");
    });
  });
}

$("#form_leaf").on("click", function() {
  var checked = $(this).prop("checked");
  $(".bx-branch-value").each(function() {
    $(this).prop("disabled", !checked);
  });
});

Enable transit to show page, when resource node is clicked.


import Ember from 'ember';

export default Ember.Controller.extend({
    notifications: Ember.inject.service(),

    userPostCount: Ember.computed('model.id', function () {
        var promise,
            query = {
                author: this.get('model.slug'),
                status: 'all'
            };

        promise = this.store.query('post', query).then(function (results) {
            return results.meta.pagination.total;
        });

        return Ember.Object.extend(Ember.PromiseProxyMixin, {
            count: Ember.computed.alias('content'),

            inflection: Ember.computed('count', function () {
                return this.get('count') > 1 ? 'posts' : 'post';
            })
        }).create({promise: promise});
    }),

    actions: {
        confirmAccept: function () {
            var self = this,
                user = this.get('model');

            user.destroyRecord().then(function () {
                self.get('notifications').closeAlerts('user.delete');
                self.store.unloadAll('post');
                self.transitionToRoute('team');
            }, function () {
                self.get('notifications').showAlert('The user could not be deleted. Please try again.', {type: 'error', key: 'user.delete.failed'});
            });
        },

        confirmReject: function () {
            return false;
        }
    },

    confirm: {
        accept: {
            text: 'Delete User',
            buttonClass: 'btn btn-red'
        },
        reject: {
            text: 'Cancel',
            buttonClass: 'btn btn-default btn-minor'
        }
    }
});

import Ember from 'ember';

export default Ember.Controller.extend({
    notifications: Ember.inject.service(),

    userPostCount: Ember.computed('model.id', function () {
        var promise,
            query = {
                filter: `author:${this.get('model.slug')}`,
                status: 'all'
            };

        promise = this.store.query('post', query).then(function (results) {
            return results.meta.pagination.total;
        });

        return Ember.Object.extend(Ember.PromiseProxyMixin, {
            count: Ember.computed.alias('content'),

            inflection: Ember.computed('count', function () {
                return this.get('count') > 1 ? 'posts' : 'post';
            })
        }).create({promise: promise});
    }),

    actions: {
        confirmAccept: function () {
            var self = this,
                user = this.get('model');

            user.destroyRecord().then(function () {
                self.get('notifications').closeAlerts('user.delete');
                self.store.unloadAll('post');
                self.transitionToRoute('team');
            }, function () {
                self.get('notifications').showAlert('The user could not be deleted. Please try again.', {type: 'error', key: 'user.delete.failed'});
            });
        },

        confirmReject: function () {
            return false;
        }
    },

    confirm: {
        accept: {
            text: 'Delete User',
            buttonClass: 'btn btn-red'
        },
        reject: {
            text: 'Cancel',
            buttonClass: 'btn btn-default btn-minor'
        }
    }
});

Update admin area queries to use new `filter` parameter


/* eslint import/no-extraneous-dependencies: ["error", {"devDependencies": true}] */
import webpack from 'webpack';

export default {
  context: __dirname,
  entry: './index.jsx',
  output: {
    path: `${__dirname}/__build__`,
    filename: 'bundle.js',
  },
  module: {
    loaders: [
      { test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel' },
    ],
  },
  resolve: {
    extensions: ['', '.js', '.jsx'],
  },
  plugins: (() => {
    if (process.argv.indexOf('-p') !== -1) {
      return [
        new webpack.DefinePlugin({
          'process.env': {
            NODE_ENV: JSON.stringify('production'),
          },
        }),
        new webpack.optimize.UglifyJsPlugin({
          output: {
            comments: false,
          },
        }),
      ];
    }
    return [];
  })(),
};

/* eslint import/no-extraneous-dependencies: ["error", {"devDependencies": true}] */
import webpack from 'webpack';

export default {
  context: __dirname,
  entry: './index.jsx',
  output: {
    path: `${__dirname}/__build__`,
    filename: 'bundle.js',
  },
  module: {
    loaders: [
        { test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel' },
        { test: /\.md$/, exclude: /node_modules/, loader: 'raw-loader' },
    ],
  },
  resolve: {
    extensions: ['', '.js', '.jsx'],
  },
  plugins: (() => {
    if (process.argv.indexOf('-p') !== -1) {
      return [
        new webpack.DefinePlugin({
          'process.env': {
            NODE_ENV: JSON.stringify('production'),
          },
        }),
        new webpack.optimize.UglifyJsPlugin({
          output: {
            comments: false,
          },
        }),
      ];
    }
    return [];
  })(),
};

Configure raw file loader for reading texts from files


'use strict';

/**
 * Desktop notifications
 * see http://caniuse.com/#search=notifications
 *
 * If the browser does not support notifications, the message will
 * be logged to the browser console
 */
app.factory('AvvisoService', [

    function() {
        var hasNotifications = window.Notification != undefined;

        if (hasNotifications) {
            Notification.requestPermission();
        }

        var Service = {
            notify: function(title, body) {
                if (hasNotifications) {
                    var notif = new Notification(title, {
                        body: body,
                        // icon: // TODO: add purkinje icon (only displayed in Firefox; not Chrome)
                    });
                } else {
                    console.debug(title + ': ' + body);
                }
            }
        };

        return Service;
    }
]);

'use strict';

/**
 * Desktop notifications
 * see http://caniuse.com/#search=notifications
 *
 * If the browser does not support notifications, the message will
 * be logged to the browser console
 */
app.factory('AvvisoService', ['$window',

    function($window) {
        var hasNotifications = $window.Notification != undefined;

        if (hasNotifications) {
            Notification.requestPermission();
        }

        var Service = {
            notify: function(title, body) {
                if (hasNotifications) {
                    var notif = new Notification(title, {
                        body: body,
                        // icon: // TODO: add purkinje icon (only displayed in Firefox; not Chrome)
                    });
                } else {
                    console.debug(title + ': ' + body);
                }
            }
        };

        return Service;
    }
]);

Use $window service instead of global window object (best practice)


(function($) {

	$( '.modal.fade' ).on( 'keypress', function( e ) {
	    if( e.keyCode === 13 ) {
	        e.preventDefault();
	        $( this ).find('.btn-accept-modal').click();
	    }
	} );

})(django.jQuery);

(function($) {

	$( '.modal.fade' ).on( 'keypress', function( e ) {
	    if( e.keyCode === 13 ) {
	        e.preventDefault();
	        $( this ).find('.btn-accept-modal').click();
	        $( this ).find('.modal-footer input').click();
	    }
	} );

})(django.jQuery);

Allow submit with enter on all modals


/*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */
/*global define, $, brackets, window */

/** Autosaves all files when leaving Brackets, a la PHPStorm */
define(function () {
    "use strict";

    var CommandManager = brackets.getModule('command/CommandManager');

    $(window).on('blur', function () {
        if (CommandManager.get('file.saveAll').getEnabled()) {
            CommandManager.execute("file.saveAll");
        }
    });
});

/*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */
/*global define, $, brackets, window */

/** Autosaves all files when leaving Brackets, a la PHPStorm */
define(function () {
    "use strict";

    var CommandManager = brackets.getModule("command/CommandManager");

    $(window).on("blur", function () {
        if (CommandManager.get("file.saveAll").getEnabled()) {
            CommandManager.execute("file.saveAll");
        }
    });
});

Change all quotes to double for uniformity


import { combineReducers } from 'redux';

import authentication from 'reducers/app/authenticationReducer';
import confirmEmail from 'reducers/app/confirmEmailReducer';
import slideEditor from 'reducers/app/slide-editor';
import feed from 'reducers/app/feedReducer';
import annotations from 'reducers/app/annotations';
import deckManagement from 'reducers/app/deckManagementReducer';
import deckImport from 'reducers/app/deckImportReducer';
import presentationView from 'reducers/app/presentationViewReducer';

const app = combineReducers({
  authentication,
  confirmEmail,
  slideEditor,
  feed,
  annotations,
  deckManagement,
  deckImport,
  presentationView,
});

export default app;

import { combineReducers } from 'redux';

import authentication from 'reducers/app/authenticationReducer';
import confirmEmail from 'reducers/app/confirmEmailReducer';
import slideEditor from 'reducers/app/slide-editor';
import feed from 'reducers/app/feedReducer';
import annotations from 'reducers/app/annotations';
import deckManagement from 'reducers/app/deckManagementReducer';
import userImports from 'reducers/app/userImportsReducer';
import deckImport from 'reducers/app/deckImportReducer';
import presentationView from 'reducers/app/presentationViewReducer';

const app = combineReducers({
  authentication,
  confirmEmail,
  slideEditor,
  feed,
  annotations,
  deckManagement,
  userImports,
  deckImport,
  presentationView,
});

export default app;

Add userimports reducer to root reducer


'use strict';

module.exports = function(grunt) {

    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        jshint: {
            allFiles: ['*.js', 'lib/**/*.js', 'examples/**/*.js', 'test/**/*.js'],
            options: {
                jshintrc: '.jshintrc',
            }
        },
        mochacli: {
            all: ['test/**/*.js'],
            options: {
                reporter: 'spec',
                ui: 'tdd'
            }
        },
        'mocha_istanbul': {
            coveralls: {
                src: 'test',
                options: {
                    coverage: true,
                    legend: true,
                    check: {
                        lines: 68,
                        statements: 66
                    },
                    root: './lib',
                    reportFormats: ['lcov']
                }
            }
        }
    })

    grunt.event.on('coverage', function(lcov, done){
        require('coveralls').handleInput(lcov, function(error) {
            if (error) {
                console.log(error)
                return done(error)
            }
            done()
        })
    })

    // Load the plugins
    grunt.loadNpmTasks('grunt-contrib-jshint')
    grunt.loadNpmTasks('grunt-mocha-cli')
    grunt.loadNpmTasks('grunt-mocha-istanbul')

    // Configure tasks
    grunt.registerTask('coveralls', ['mocha_istanbul:coveralls'])
    grunt.registerTask('default', ['test'])
    grunt.registerTask('test', ['mochacli', 'jshint', 'coveralls'])
}

'use strict';

module.exports = function(grunt) {

    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        jshint: {
            allFiles: ['*.js', 'lib/**/*.js', 'examples/**/*.js', 'test/**/*.js'],
            options: {
                jshintrc: '.jshintrc',
            }
        },
        mochacli: {
            all: ['test/**/*.js'],
            options: {
                reporter: 'spec',
                ui: 'tdd'
            }
        },
        'mocha_istanbul': {
            coveralls: {
                src: 'test',
                options: {
                    coverage: true,
                    legend: true,
                    /* check: {
                        lines: 90,
                        statements: 90
                    }, */
                    root: './lib',
                    reportFormats: ['lcov']
                }
            }
        }
    })

    grunt.event.on('coverage', function(lcov, done){
        require('coveralls').handleInput(lcov, function(error) {
            if (error) {
                console.log(error)
                return done(error)
            }
            done()
        })
    })

    // Load the plugins
    grunt.loadNpmTasks('grunt-contrib-jshint')
    grunt.loadNpmTasks('grunt-mocha-cli')
    grunt.loadNpmTasks('grunt-mocha-istanbul')

    // Configure tasks
    grunt.registerTask('coveralls', ['mocha_istanbul:coveralls'])
    grunt.registerTask('default', ['test'])
    grunt.registerTask('test', ['mochacli', 'jshint', 'coveralls'])
}

Remove minimum requirements for now until we can increase coverage


'use strict';

module.exports = function(grunt) {

    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        jshint: {
            allFiles: ['Gruntfile.js', 'lib/**/*.js', 'test/**/*.js', 'index.js'],
            options: {
                jshintrc: '.jshintrc',
            }
        },
        mochacli: {
            all: ['test/**/*.js'],
            options: {
                reporter: 'spec',
                ui: 'tdd'
            }
        }
    })

    // Load the plugins
    grunt.loadNpmTasks('grunt-contrib-jshint')
    grunt.loadNpmTasks('grunt-mocha-cli')

    // Configure tasks
    grunt.registerTask('default', ['test'])
    grunt.registerTask('test', ['mochacli', 'jshint'])
}

'use strict';

module.exports = function(grunt) {

    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        jshint: {
            allFiles: ['Gruntfile.js', 'lib/**/*.js', 'test/**/*.js', 'index.js'],
            options: {
                jshintrc: '.jshintrc',
            }
        },
        mochacli: {
            all: ['test/**/*.js'],
            options: {
                reporter: 'spec',
                ui: 'tdd'
            }
        },
        'mocha_istanbul': {
            coveralls: {
                src: [ 'test/lib', 'test/lib/utils' ],
                options: {
                    coverage: true,
                    legend: true,
                    check: {
                        lines: 100,
                        statements: 99
                    },
                    root: './lib',
                    reportFormats: ['lcov']
                }
            }
        }
    })

    grunt.event.on('coverage', function(lcov, done){
        require('coveralls').handleInput(lcov, function(error) {
            if (error) {
                console.log(error)
                return done(error)
            }
            done()
        })
    })

    // Load the plugins
    grunt.loadNpmTasks('grunt-contrib-jshint')
    grunt.loadNpmTasks('grunt-mocha-cli')
    grunt.loadNpmTasks('grunt-mocha-istanbul')

    // Configure tasks
    grunt.registerTask('coveralls', ['mocha_istanbul:coveralls'])
    grunt.registerTask('default', ['test'])

    grunt.registerTask('test', ['mochacli', 'jshint', 'coveralls'])
}

Update grunt file to perform coverage task


module.exports = function(grunt) {
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),

        bump: {
            options: {
                files: ['package.json'],
                updateConfigs: ['pkg'],
                commit: true,
                commitMessage: 'Release v%VERSION%',
                createTag: true,
                tagName: 'v%VERSION%',
                tagMessage: 'Version %VERSION%',
                push: true,
                pushTo: 'origin',
                gitDescribeOptions: '--tags --always --abbrev=1 --dirty=-d'
            }
        },

        jshint: {
            options: {
                reporter: require('jshint-stylish')
            },

            build: ['Gruntfile.js']
        }
    });

    // LOAD GRUNT PACKAGES
    grunt.loadNpmTasks('grunt-bump');
    grunt.loadNpmTasks('grunt-contrib-jshint');
};

module.exports = function(grunt) {
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),

        bump: {
            options: {
                files: ['package.json'],
                updateConfigs: ['pkg'],
                commit: true,
                commitMessage: 'Release v%VERSION%',
                createTag: true,
                tagName: 'v%VERSION%',
                tagMessage: 'Version %VERSION%',
                push: true,
                pushTo: 'origin',
                gitDescribeOptions: '--tags --always --abbrev=1 --dirty=-d'
            }
        },

        jshint: {
            options: {
                esversion: 6,
                reporter: require('jshint-stylish')
            },

            build: ['*.js', 'public/**/*.js', '!public/lib/**/*.js']
        }
    });

    // LOAD GRUNT PACKAGES
    grunt.loadNpmTasks('grunt-bump');
    grunt.loadNpmTasks('grunt-contrib-jshint');
};

Add other files to jshint task


angular.module('materialscommons').component('mcExperimentNotes', {
    templateUrl: 'app/project/experiments/experiment/components/notes/mc-experiment-notes.html',
    bindings: {
        experiment: '='
    }
});

angular.module('materialscommons').component('mcExperimentNotes', {
    templateUrl: 'app/project/experiments/experiment/components/notes/mc-experiment-notes.html',
    controller: MCExperimentNotesComponentController,
    bindings: {
        experiment: '='
    }
});

class MCExperimentNotesComponentController {
    /*@ngInject*/
    constructor($scope) {
        $scope.editorOptions = {};
    }

    addNote() {

    }
}

Add controller (to be filled out).


const smash = require("../../smash.js");
const logger = smash.logger("DynamodbModel");
const DYNAMODB_TABLE_SUFFIX = "dynamodb.tableSuffix";
const UNDERSCORE = "_";

class DynamodbModel {
    constructor(table, env = null) {
        if (this.constructor === DynamodbModel) {
            throw new Error("DynamodbModel is an abstract class, you must extend it from another class like class Foobar extends DynamodbModel {}");
        }
        if (typeof table !== 'string' || table.length === 0) {
            throw new Error("First parameter of constructor(table) must be a string, " + logger.typeOf(table));
        }
        this._table = table;
        this._env = env;
    }

    set env(env) {
        this._env = env;
        return this;
    }

    get env() {
        if (!this._env) {
            this._env = smash.getEnv("ENV");
            if (!this._env) {
                logger.info("Missing ENV variable in environment");
            }
        }
        return this._env;
    }

    get table() {
        const env = this.env;
        const tables = smash.config.get(DYNAMODB_TABLE_SUFFIX);
        if (env && tables && tables[env]) {
            return this._table + tables[env];
        } else if (env) {
            return this._table + UNDERSCORE + env;
        } else {
            return this._table;
        }
    }

}

module.exports = DynamodbModel;

const smash = require("../../smash.js");
const Console = require("./console.js");
const logger = smash.logger("DynamodbModel");
const DYNAMODB_TABLE_SUFFIX = "dynamodb.tableSuffix";
const UNDERSCORE = "_";

class DynamodbModel extends Console {// FIX ME remove extends console in next major, only for compatibility
    constructor(table, env = null) {
        super();
        if (this.constructor === DynamodbModel) {
            throw new Error("DynamodbModel is an abstract class, you must extend it from another class like class Foobar extends DynamodbModel {}");
        }
        if (typeof table !== 'string' || table.length === 0) {
            throw new Error("First parameter of constructor(table) must be a string, " + logger.typeOf(table));
        }
        this._table = table;
        this._env = env;
        this.namespace("Table " + this.table);
    }

    set env(env) {
        this._env = env;
        return this;
    }

    get env() {
        if (!this._env) {
            this._env = smash.getEnv("ENV");
            if (!this._env) {
                logger.info("Missing ENV variable in environment");
            }
        }
        return this._env;
    }

    get table() {
        const env = this.env;
        const tables = smash.config.get(DYNAMODB_TABLE_SUFFIX);
        if (env && tables && tables[env]) {
            return this._table + tables[env];
        } else if (env) {
            return this._table + UNDERSCORE + env;
        } else {
            return this._table;
        }
    }

}

module.exports = DynamodbModel;

Fix retro compatibility in dynamodb model