var DataStore = Ember.Object.extend({
  init: function () {
    this._super();
    this.set('store', {});
  },

  addType: function (type) {
    this.store[type] = [];
  },

  findById: function (type, id) {
    var type = this.store[type],
    model;

    if (!type) {
      throw new Error('There is no model of that type in the datastore!');
    }

    model = type.filter(function (model) {
      return model.id && model.id === id;
    });

    return model;
  },

  findByKey: function (type, keyName, val) {
    var type = this.store[type],
    model;

    if (!type) {
      throw new Error('There is no model of that type in the datastore!');
    }

    model = type.filter(function (model) {
      return model[key] && model[key] === val;
    });

    return model;
  },
});

export default DataStore;

import parseFields from 'client/utils/parse-fields-metadata';

var DataStore = Ember.Object.extend({
  init: function () {
    this._super();
    this.set('store', Ember.Object.create());
  },

  addType: function (type) {
    this.store[type] = Ember.ArrayProxy.create({ content: Ember.A() });
  },

  load: function (type, payload) {
    if (!this.store[type]) {
      throw new Error('There is no model of type ' + type + ' in the datastore!');
    }

    if (typeof payload !== 'object') {
      throw new Error('Payload for type ' + type + ' was not an object!', payload);
    }

    // turn `fields` metadata field from a string like "[]" into a JSON object
    parseFields(payload);

    if (!this.store[type].length) {
      this.store[type].pushObjects(payload);
      return;
    }

    if (!Array.isArray(payload) && !this.store[type].findBy('id', payload.id)) {
      this.store[type].pushObject(payload);
      return;
    }

    // now we know both payload and the modelType in the store are non-zero-length arrays
    // we need to check for collisions and update those that exist, and insert those that don't

  },

  binarySearch: function (sortedArray, options) {

  },

  all: function (type, key, val) {
    var modelType = this.store[type];

    if (!modelType) {
      throw new Error('There is no model of type ' + type + ' in the datastore!');
    }

    if (!key || !val) {
      return modelType;
    }

    return modelType.filterBy(key, val);
  },

  findById: function (type, id) {
    var modelType = this.store[type];

    if (!modelType) {
      throw new Error('There is no model of type ' + type + ' in the datastore!');
    }

    return modelType.findBy('id', id);
  },

  findByKey: function (type, keyName, val) {
    var modelType = this.store[type];

    if (!modelType) {
      throw new Error('There is no model of type ' + type + ' in the datastore!');
    }

    return modelType.filterBy(keyName, val);
  },
});

export default DataStore;

Build up how DataStore works with and transforms its data types


'use strict';

const testInfo = require('ember-cli-test-info');
const path = require('path');
const stringUtil = require('ember-cli-string-utils');
const useTestFrameworkDetector = require('../test-framework-detector');

module.exports = useTestFrameworkDetector({
  description: 'Generates a route unit test.',

  availableOptions: [
    {
      name: 'reset-namespace',
      type: Boolean
    }
  ],

  fileMapTokens: function() {
    return {
      __test__: function (options) {
        let moduleName = options.locals.moduleName;

        if (options.pod) {
          moduleName = 'route';
        }

        return `${moduleName}-test`;
      },
      __path__: function(options) {
        if (options.pod) {
          return path.join(options.podPath, options.locals.moduleName);
        }
        return 'routes';
      }
    };
  },

  locals: function(options) {
    let moduleName = options.entity.name;

    if (options.resetNamespace) {
      moduleName = moduleName.split('/').pop();
    }

    return {
      friendlyTestDescription: testInfo.description(options.entity.name, 'Unit', 'Route'),
      moduleName: stringUtil.dasherize(moduleName)
    };
  },
});

'use strict';

const path = require('path');
const stringUtil = require('ember-cli-string-utils');
const useTestFrameworkDetector = require('../test-framework-detector');

module.exports = useTestFrameworkDetector({
  description: 'Generates a route unit test.',

  availableOptions: [
    {
      name: 'reset-namespace',
      type: Boolean
    }
  ],

  fileMapTokens: function() {
    return {
      __test__: function (options) {
        let moduleName = options.locals.moduleName;

        if (options.pod) {
          moduleName = 'route';
        }

        return `${moduleName}-test`;
      },
      __path__: function(options) {
        if (options.pod) {
          return path.join(options.podPath, options.locals.moduleName);
        }
        return 'routes';
      }
    };
  },

  locals: function(options) {
    let moduleName = options.entity.name;

    if (options.resetNamespace) {
      moduleName = moduleName.split('/').pop();
    }

    return {
      friendlyTestDescription: ['Unit', 'Route', options.entity.name].join(' | '),
      moduleName: stringUtil.dasherize(moduleName)
    };
  },
});

Use `options.entity-name` for test description


// This file is part of BenchExec, a framework for reliable benchmarking:
// https://github.com/sosy-lab/benchexec
//
// SPDX-FileCopyrightText: 2019-2020 Dirk Beyer <https://www.sosy-lab.org>
//
// SPDX-License-Identifier: Apache-2.0

// enzyme
import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";

// mock uniqid to have consistent names
// https://stackoverflow.com/a/44538270/396730
jest.mock("uniqid", () => (i) => i + "uniqid");

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

// This file is part of BenchExec, a framework for reliable benchmarking:
// https://github.com/sosy-lab/benchexec
//
// SPDX-FileCopyrightText: 2019-2020 Dirk Beyer <https://www.sosy-lab.org>
//
// SPDX-License-Identifier: Apache-2.0

// enzyme
import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";

// mock uniqid to have consistent names
// https://stackoverflow.com/a/44538270/396730
jest.mock("uniqid", () => (i) => i + "uniqid");

/**
 * Worker mock
 */
class Worker {
  constructor(dataUrl) {
    this.url = dataUrl;
    const b = Buffer.from(dataUrl.split(",")[1], "base64");
    let onmessage;
    this.mockedPostMessage = (data) => this.cb({ data });
    eval(b.toString()); //new Function("data", b.toString);
    this.onmessageImpl = onmessage;
  }

  mockedPostMessage(data) {
    this.cb({ data });
  }
  postMessage(msg) {
    this.onmessageImpl({ data: { ...msg } });
  }

  set onmessage(cb) {
    this.cb = cb;
    this.mockedPostMessage = (data) => this.cb({ data });
  }
}
window.Worker = Worker;
configure({ adapter: new Adapter() });

Add worker mock for tests


({
    handleEditBatch: function (component) {

        $A.createComponents([["c:geBatchWizard", {recordId: component.get('v.recordId')}]],
            function (components, status, errorMessage) {
                if (status === "SUCCESS") {
                    const modalBody = components[0];

                    let modalReference = component.find('overlayLib').showCustomModal({
                        body: modalBody,
                    });

                    component.set('v.modal', modalReference);
                } else {
                    console.error(errorMessage);
                }
            }
        );
    }

});

({
    handleEditBatch: function (component) {

        $A.createComponents([["c:geBatchWizard", {recordId: component.get('v.recordId')}]],
            function (components, status, errorMessage) {
                if (status === "SUCCESS") {
                    const modalBody = components[0];

                    let modalReference = component.find('overlayLib').showCustomModal({
                        body: modalBody,
                        cssClass: "slds-modal_large",
                    });

                    component.set('v.modal', modalReference);
                } else {
                    console.error(errorMessage);
                }
            }
        );
    }

});

Apply SLDS style to maximize width of modal


import React from 'react';
import autobind from 'core-decorators/es/autobind';
import { NonIdealState, Button, Intent } from '@blueprintjs/core';
import LoadingSpinner from '../../../components/LoadingSpinner';

@autobind
class HideResults extends React.Component {
	refreshPage() {
		location.reload();
	}

	render() {
		return (

Who will reign victorious?

				<NonIdealState
					description='Results will be out soon. Check this page again in a few minutes!'
					visual={<LoadingSpinner hideText/>}
					action={<Button intent={Intent.PRIMARY} text='Refresh page' onClick={this.refreshPage}/>}/>

		);
	}
}

export default HideResults;

import React from 'react';
import autobind from 'core-decorators/es/autobind';
import { NonIdealState, Button, Intent } from '@blueprintjs/core';
import LoadingSpinner from '../../../components/LoadingSpinner';

@autobind
class HideResults extends React.Component {
	refreshPage() {
		location.reload();
	}

	render() {
		return (

Who will reign victorious?

				<NonIdealState
					title='Results will be out soon'
					description='Check this page again in a few minutes!'
					visual={<LoadingSpinner hideText/>}
					action={<Button intent={Intent.PRIMARY} text='Refresh page' onClick={this.refreshPage}/>}/>

		);
	}
}

export default HideResults;

Improve pending results screen text


'use strict';
var assert = require('assert');
var callbackify = require('./');

function sum (a, b) { return a + b; }

it('should make a sync function pass return to a callback', function () {
	var sumCb = callbackify(sum);
	var answer;
	sumCb(32, 96, function (err, result) { answer = result; });
	assert.equal(answer, 128);
});

it('should throw if not given a function', function () {
	assert.throws(function () { callbackify(''); });
});

it('should throw if wrapped function not given a callback', function () {
	var sumCb = callbackify(sum);
	assert.throws(function () { sumCb(32, 96); });
});

it('should preserve functions that already expect a callback', function () {
	function cbFunc1 (a, b, callback) { callback(null, a + b); }
	function cbFunc2 (foo, cb) { cb(null, foo); }
	function cbFunc3 (callback_) { callback_(); }
	function cbFunc4 (bar, badlyNamedCb) { badlyNamedCb(null, bar); }

	callbackify(cbFunc1)(0, 1, function (err, res) { assert.equal(res, 1); });

	var res2 = callbackify(cbFunc2)(2, function (err, res) {
		assert.equal(res, 2);
	});
	assert.strictEqual(res2, undefined);

	var res3;
	callbackify(cbFunc3)(function () { res3 = true; });
	assert.ok(res3);

	callbackify(cbFunc4)(4, function (err, res) {
		assert.equal(res, undefined);
	});
});

Test handling of callback-wielding functions


import Immutable from 'immutable';
import { COLUMN_MEDIA_RESIZE } from '../actions/column_media';

const initialState = Immutable.Map({
  scale: null,
  single: null,
  wide: null,
});

function resize(state, { columnCount, defaultPage, single: givenSingle, window: givenWindow }) {
  const single = state.get('single') || givenSingle;
  const widthCandidate = (givenWindow.innerWidth - 300) / columnCount;
  const width = single ? givenWindow.innerWidth : Math.max(widthCandidate, 330);
  const wide = !defaultPage || Math.min(width, 400) < givenWindow.innerHeight;
  let scale;

  if (!defaultPage || (!single && widthCandidate < 330)) {
    scale = '230px';
  } else if (!wide) {
    scale = '50vh';
  } else if (single) {
    scale = 'calc(50vw - 100px)';
  } else if (width > 500) {
    scale = '400px';
  } else {
    scale = `calc((100vw - 300px)/${columnCount} - 100px)`;
  }

  return state.merge({ scale, single, wide });
}

export default function columnMedia(state = initialState, action) {
  switch (action.type) {
  case COLUMN_MEDIA_RESIZE:
    return resize(state, action);
  default:
    return state;
  }
}

import Immutable from 'immutable';
import { COLUMN_MEDIA_RESIZE } from '../actions/column_media';

const initialState = Immutable.Map({
  scale: null,
  single: null,
  wide: null,
});

function resize(state, { columnCount, defaultPage, single: givenSingle, window: givenWindow }) {
  const single = state.get('single') || givenSingle;
  let scale;
  let wide;

  if (single) {
    wide = givenWindow.innerWidth - 30 < givenWindow.innerHeight / 2;

    if (wide) {
      scale = 'calc(100vw - 100px)';
    } else {
      scale = 'calc(50vh - 70px)';
    }
  } else {
    const widthCandidate = (givenWindow.innerWidth - 300) / columnCount;
    const width = Math.max(widthCandidate, 330);
    wide = !defaultPage || Math.min(width, 400) < givenWindow.innerHeight;

    if (!defaultPage || widthCandidate < 330) {
      scale = '230px';
    } else if (!wide) {
      scale = '50vh';
    } else if (width > 500) {
      scale = '400px';
    } else {
      scale = `calc((100vw - 300px)/${columnCount} - 100px)`;
    }
  }

  return state.merge({ scale, single, wide });
}

export default function columnMedia(state = initialState, action) {
  switch (action.type) {
  case COLUMN_MEDIA_RESIZE:
    return resize(state, action);
  default:
    return state;
  }
}

Adjust media scaling in narrow window


var ActionCheckbox = Ember.Checkbox.extend({
  transmitAction: function () {
    var action = this.get('action'),
        model = this.get('model');

    if (action && model) {
      this.get('controller').send(action, model);
    }
  }.observes('checked')
});

export default ActionCheckbox;

var ActionCheckbox = Ember.Checkbox.extend({
  transmitAction: function () {
    Ember.run.schedule('sync', this, function () {
      var action = this.get('action'),
      model = this.get('model');

      if (action && model) {
        this.get('controller').send(action, model);
      }
    });
  }.observes('checked')
});

export default ActionCheckbox;

Fix scheduling of firing an ActionCheckbox action


export { listener };

/**
 * Feature detection for passive event listeners as per:
 * https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md#feature-detection
 */

let supportsPassive = false;

try {
  const opts = Object.defineProperty({}, 'passive', {
    get: function() {
      supportsPassive = true;
    }
  });

  window.addEventListener('test', null, opts);
} catch (event) {}

// Stores the third argument for `addEventListener` for both cases
const listener = {
  active: supportsPassive ? { passive: false } : false,
  passive: supportsPassive ? { passive: true } : false
};

export { listener };

/**
 * Feature detection for passive event listeners as per:
 * https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md#feature-detection
 */

let supportsPassive = false;

try {
  const opts = Object.defineProperty({}, 'passive', {
    get: function() {
      supportsPassive = true;
    }
  });

  window.addEventListener('testPassive', null, opts);
  window.removeEventListener('testPassive', null, opts);
} catch (e) {}

/**
 * @typedef {object} AddEventListenerOptionsObject
 * @property {boolean|AddEventListenerOptions} active
 * @property {boolean|AddEventListenerOptions} passive
 */

/**
 * Stores the third argument for `addEventListener` for both cases
 * @type {AddEventListenerOptionsObject}
 */
const listener = {
  active: supportsPassive ? { passive: false } : false,
  passive: supportsPassive ? { passive: true } : false
};

Update passive event listener feature detection


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

/**
 * External dependencies
 */
import PropTypes from 'prop-types';

/**
 * Internal dependencies
 */
import Link from '../../../components/Link';

const WidgetHeaderCTA = ( { href, label, external } ) => (
	<div className="googlesitekit-widget__header--cta">
		<Link
			href={ href }
			external={ external }
		>
			{ label }
		</Link>
	</div>
);

WidgetHeaderCTA.propTypes = {
	href: PropTypes.string,
	label: PropTypes.string.isRequired,
	external: PropTypes.bool,
};

WidgetHeaderCTA.defaultProps = {
	href: '#',
	external: true,
};

export default WidgetHeaderCTA;

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

/**
 * External dependencies
 */
import PropTypes from 'prop-types';

/**
 * Internal dependencies
 */
import Link from '../../../components/Link';

const WidgetHeaderCTA = ( { href, label, external } ) => (
	<div className="googlesitekit-widget__header--cta">
		<Link
			href={ href }
			external={ external }
		>
			{ label }
		</Link>
	</div>
);

WidgetHeaderCTA.propTypes = {
	href: PropTypes.string,
	label: PropTypes.string.isRequired,
	external: PropTypes.bool,
};

WidgetHeaderCTA.defaultProps = {
	href: '',
	external: true,
};

export default WidgetHeaderCTA;

Update the default value for the header cta link to be an empty string instead of a hash.


/**
 * Copyright 2017-present, Callstack.
 * All rights reserved.
 *
 * webpackConfigNotFound.js
 *
 * @flow
 */
const dedent = require("dedent");

module.exports = ({ path }: { path: string }) => dedent`
   We tried loading configuration file at ${path}.
`;

/**
 * Copyright 2017-present, Callstack.
 * All rights reserved.
 *
 * webpackConfigNotFound.js
 *
 * @flow
 */
const dedent = require("dedent");
const chalk = require("chalk");

module.exports = ({ path }: { path: string }) => dedent`
   Webpack config wasn't found at ${path}.

   Make sure you have appropriate webpack.config.js.

   You can copy the following simplified version to set it up now:
   ${chalk.gray(`
     module.exports = {
       entry: './index.js',
     };
   `)}
`;

Add proper missing config text


var fs = require('fs');

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

function Err(msg, err){
  var err_str = 'build error\n'
    + (msg ? msg + '\n' : '')
    + (err ? err.message + '\n' : '');

  throw new Error(err_str);
}

function build(dir){

  var commands = 'cd ' + dir + ' && npm install && node-gyp configure && node-gyp build';

  exec(commands, function (err, stdout, stderr) {
    if(err)
      console.log('--- exec error ---\n' + err + '--- end exec error ---');

    console.log('--- stdout ---\n' + stdout + '--- end stdout ---');
    console.log('--- stderr ---\n' + stderr + '--- end stderr ---');
  });
}

var lib_dir = __dirname + '/lib';
fs.readdir(lib_dir, function (err, dirs){
  if(err) return Err('Could not read ' + lib_dir, err);

  dirs.forEach(function (dir){
    var module_dir = lib_dir + '/' + dir

    fs.readdir(module_dir, function (err, dirs){
      if(err) return Err('Could not read ', err);

      dirs.forEach(function (dir){
        if(dir == 'cpp')
          build(module_dir + '/' + dir);
      });
    });
  });
});

var fs = require('fs');

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

function Err(msg, err){
  var err_str = 'build error\n'
    + (msg ? msg + '\n' : '')
    + (err ? err.message + '\n' : '');

  throw new Error(err_str);
}

function build(dir){

  var commands = 'cd ' + dir + ' && npm install && node-gyp configure && node-gyp build';

  console.log(commands);

  exec(commands, function (err, stdout, stderr) {
    if(err)
      console.log('--- exec error ---\n' + err + '--- end exec error ---');

    console.log('--- stdout ---\n' + stdout + '--- end stdout ---');
    console.log('--- stderr ---\n' + stderr + '--- end stderr ---');
  });
}

var lib_dir = __dirname + '/lib';
fs.readdir(lib_dir, function (err, dirs){
  if(err) return Err('Could not read ' + lib_dir, err);

  dirs.forEach(function (dir){
    var module_dir = lib_dir + '/' + dir

    fs.readdir(module_dir, function (err, dirs){
      if(err) return Err('Could not read ', err);

      dirs.forEach(function (dir){
        if(dir == 'cpp')
          build(module_dir + '/' + dir);
      });
    });
  });
});

Test child process command logging


var request = new XMLHttpRequest();
request.onload = function() {
	var data = JSON.parse(this.response);
	document.getElementById('position').innerText = data.curPos;
};

setInterval(function() {
	// request.open('GET', '../queueinfo.json');
	request.open('GET', '../test_queueinfo.php');
	request.send();
}, 5000);

if (window.getComputedStyle(document.body).marginTop.indexOf('-') == 0)
	document.body.style.marginTop = 0;

var request = new XMLHttpRequest();
request.onload = function() {
	var data = JSON.parse(this.response);
	if(document.getElementById('position').innerText != data.curPos)
		document.getElementById('position').innerText = data.curPos;
};

setInterval(function() {
	// request.open('GET', '../queueinfo.json');
	request.open('GET', '../test_queueinfo.php');
	request.send();
}, 5000);

Optimize Screen Page's Operation Code


angular.module('accumulatorApp', [])
  .controller('AccumulatorController', ['$scope', '$http', function ($scope, $http) {

    $scope.accumulator = [];
    $scope.item = '';
    $scope.enabled = false;

    $scope.add = function () {
      $scope.enabled = false;
      $http.post('/api', {"message": $scope.item})
        .then(function success(response) {
          $scope.accumulator.unshift(response.data);
          $scope.item = '';
        });
    }

    $http.get('/api')
      .then(function success(response) {
        $scope.accumulator = response.data;
        $scope.enabled = true;
      });

  }]);

angular.module('accumulatorApp', [])
  .controller('AccumulatorController', ['$scope', '$http', function ($scope, $http) {

    $scope.accumulator = [];
    $scope.item = '';
    $scope.enabled = false;

    $scope.add = function () {
      $scope.enabled = false;
      $http.post('/api', {"message": $scope.item})
        .then(function success(response) {
          $scope.accumulator.unshift(response.data);
          $scope.item = '';
          $scope.enabled = true;
        });
    }

    $http.get('/api')
      .then(function success(response) {
        $scope.accumulator = response.data;
        $scope.enabled = true;
      });

  }]);

Enable add button after post


define([
    'rangy',
    'rangy-text'
], function(
    rangy,
    rangyText
) {

    return {
        expandRangeByWords: function(range, numberWords, splitBeforeAfterOutput) {
            if (!rangy.initialized) rangy.init();

            var e = rangy.createRange();
            e.setStart(range.startContainer, range.startOffset);
            e.setEnd(range.endContainer, range.endOffset);

            // Move range start to include n more of words
            e.moveStart('word', -numberWords);

            // Move range end to include n more words
            e.moveEnd('word', numberWords);

            // Calculate what we just included and send that back
            if (splitBeforeAfterOutput) {
                var output = rangy.createRange();
                output.setStart(e.startContainer, e.startOffset);
                output.setEnd(range.startContainer, range.startOffset);
                splitBeforeAfterOutput.before = output.text();

                output.setStart(range.endContainer, range.endOffset);
                output.setEnd(e.endContainer, e.endOffset);
                splitBeforeAfterOutput.after = output.text();
            }

            return e;
        }
    };
});

define([
    'rangy',
    'rangy-text'
], function(
    rangy,
    rangyText
) {

    if (!rangy.initialized) rangy.init();

    return {
        expandRangeByWords: function(range, numberWords, splitBeforeAfterOutput) {

            var e = rangy.createRange();
            e.setStart(range.startContainer, range.startOffset);
            e.setEnd(range.endContainer, range.endOffset);

            // Move range start to include n more of words
            e.moveStart('word', -numberWords);

            // Move range end to include n more words
            e.moveEnd('word', numberWords);

            // Calculate what we just included and send that back
            if (splitBeforeAfterOutput) {
                var output = rangy.createRange();
                output.setStart(e.startContainer, e.startOffset);
                output.setEnd(range.startContainer, range.startOffset);
                splitBeforeAfterOutput.before = output.text();

                output.setStart(range.endContainer, range.endOffset);
                output.setEnd(e.endContainer, e.endOffset);
                splitBeforeAfterOutput.after = output.text();
            }

            return e;
        }
    };
});

Fix issue where first selection in detail pane after refresh would not be possible


'use strict';

require('../node_modules/babel-core/register');

var src = [
  'tests/lib/*.js',
  'tests/lib/parser/properties/*.js',
  'tests/lib/parser/l20n/*.js',
  'tests/lib/resolver/*.js',
  'tests/lib/context/*.js',
  'tests/lib/env/*.js',
];

module.exports = {
  dot: {
    options: {
      reporter: 'dot',
    },
    src: src,
  }
};

'use strict';

require('babel-register')({
  presets: ['es2015']
});

var src = [
  'tests/lib/*.js',
  'tests/lib/parser/properties/*.js',
  'tests/lib/parser/l20n/*.js',
  'tests/lib/resolver/*.js',
  'tests/lib/context/*.js',
  'tests/lib/env/*.js',
];

module.exports = {
  dot: {
    options: {
      reporter: 'dot',
    },
    src: src,
  }
};

Make mocha work with babel6


import React from 'react';
import { DocsContainer } from '@storybook/addon-docs/blocks';
import markdown from './markdown.stories.mdx';

export default {
  title: 'Addons/Docs/mdx-in-story',
  decorators: [(storyFn) => <DocsContainer context={{}}>{storyFn()}</DocsContainer>],
  parameters: {
    layout: 'fullscreen',
  },
};

// This renders the contents of the docs panel into story content
export const Typography = () => {
  const Docs = markdown.parameters.docs.page;
  return <Docs />;
};

import React from 'react';
import { DocsContainer } from '@storybook/addon-docs/blocks';
import { themes } from '@storybook/theming';
import markdown from './markdown.stories.mdx';

export default {
  title: 'Addons/Docs/mdx-in-story',
  decorators: [(storyFn) => <DocsContainer context={{}}>{storyFn()}</DocsContainer>],
  parameters: {
    layout: 'fullscreen',
  },
};

// This renders the contents of the docs panel into story content
export const Typography = () => {
  const Docs = markdown.parameters.docs.page;
  return <Docs />;
};

export const DarkModeDocs = () => {
  const Docs = markdown.parameters.docs.page;
  return <Docs />;
};

DarkModeDocs.decorators = [
  (storyFn) => (
    <DocsContainer context={{ parameters: { docs: { theme: themes.dark } } }}>
      {storyFn()}
    </DocsContainer>
  ),
];

Add story for MDX docs using dark theme


import { log } from '@/store/utils'

export function success (commit, data) {
  commit('set', data)
  commit('setRefreshing', false)
}

export function error (commit, msg) {
  log('Error while updating anime news.', msg)
  commit('refreshing', false)
}

import { log } from '@/store/utils'

export function success (commit, data) {
  commit('set', data)
  commit('setRefreshing', false)
}

export function error (commit, msg) {
  log('Error while updating anime news.', msg)
  commit('setRefreshing', false)
}

Fix news typo on error


import React from 'react';
import PropTypes from 'prop-types';
import yubikiri from 'yubikiri';

import {autobind} from '../helpers';
import ObserveModel from '../views/observe-model';
import FilePatchController from '../controllers/file-patch-controller';

export default class FilePatchContainer extends React.Component {
  static propTypes = {
    repository: PropTypes.object.isRequired,
    stagingStatus: PropTypes.oneOf(['staged', 'unstaged']),
    relPath: PropTypes.string.isRequired,

    tooltips: PropTypes.object.isRequired,
  }

  constructor(props) {
    super(props);

    autobind(this, 'fetchData', 'renderWithData');
  }

  fetchData(repository) {
    return yubikiri({
      filePatch: repository.getFilePatchForPath(this.props.relPath, {staged: this.props.stagingStatus === 'staged'}),
      isPartiallyStaged: repository.isPartiallyStaged(this.props.relPath),
    });
  }

  render() {
    return (
      <ObserveModel model={this.props.repository} fetchData={this.fetchData}>
        {this.renderWithData}
      </ObserveModel>
    );
  }

  renderWithData(data) {
    if (data === null) {
      return null;
    }

    return (
      <FilePatchController
        isPartiallyStaged={data.isPartiallyStaged}
        filePatch={data.filePatch}
        stagingStatus={this.props.stagingStatus}
        tooltips={this.props.tooltips}
      />
    );
  }
}

import React from 'react';
import PropTypes from 'prop-types';
import yubikiri from 'yubikiri';

import {autobind} from '../helpers';
import ObserveModel from '../views/observe-model';
import FilePatchController from '../controllers/file-patch-controller';

export default class FilePatchContainer extends React.Component {
  static propTypes = {
    repository: PropTypes.object.isRequired,
    stagingStatus: PropTypes.oneOf(['staged', 'unstaged']),
    relPath: PropTypes.string.isRequired,

    tooltips: PropTypes.object.isRequired,
  }

  constructor(props) {
    super(props);

    autobind(this, 'fetchData', 'renderWithData');
  }

  fetchData(repository) {
    return yubikiri({
      filePatch: repository.getFilePatchForPath(this.props.relPath, {staged: this.props.stagingStatus === 'staged'}),
      isPartiallyStaged: repository.isPartiallyStaged(this.props.relPath),
    });
  }

  render() {
    return (
      <ObserveModel model={this.props.repository} fetchData={this.fetchData}>
        {this.renderWithData}
      </ObserveModel>
    );
  }

  renderWithData(data) {
    if (data === null) {
      return null;
    }

    if (data.filePatch === null) {
      return null;
    }

    return (
      <FilePatchController
        isPartiallyStaged={data.isPartiallyStaged}
        filePatch={data.filePatch}
        stagingStatus={this.props.stagingStatus}
        tooltips={this.props.tooltips}
      />
    );
  }
}

Return null for an empty patch for now


/*jshint esversion: 6 */
import reducers from './reducers';
import App from './components/App.jsx';

const store = Redux.createStore(reducers);
const rootEl = document.getElementById('root');

const render = () => ReactDOM.render(
  <App />,
  rootEl
);

render();
store.subscribe(render);

Add webpack to bundle and transpile, Add redux


var path = require("path"),
    childProcess = require('child_process'),
    phantomjs = require('phantomjs'),
    fs = require("fs");

module.exports = function(options, html, id, cb) {
    var htmlFilePath = path.resolve(path.join(options.tmpDir, id + ".html"));

    fs.writeFile(htmlFilePath, html, function (err) {
        if (err)
            return cb(err);

        var childArgs = [
            path.join(__dirname, "scripts", "standaloneScript.js"),
            htmlFilePath,
            '--ignore-ssl-errors=yes',
            '--web-security=false',
            '--ssl-protocol=any'
        ];

        var isDone = false;

        var output = "";

        var child = childProcess.execFile(phantomjs.path, childArgs, { maxBuffer: 1024 * 50000 }, function (err, stdout, stderr) {
            if (isDone)
                return;

            isDone = true;
            if (err) {
                return cb(err);
            }

            cb(null, JSON.parse(output));
        });
        child.stdout.on("data", function(data) {
            output += data;
        });

        setTimeout(function() {
            if (isDone)
                return;

            isDone = true;
            cb(new Error("Timeout when executing in phantom"));
        }, options.timeout);
    });
};

var path = require("path"),
    childProcess = require('child_process'),
    phantomjs = require('phantomjs'),
    fs = require("fs");

module.exports = function(options, html, id, cb) {
    var htmlFilePath = path.resolve(path.join(options.tmpDir, id + ".html"));

    fs.writeFile(htmlFilePath, html, function (err) {
        if (err)
            return cb(err);

        var childArgs = [
            path.join(__dirname, "scripts", "standaloneScript.js"),
            htmlFilePath,
            '--ignore-ssl-errors=yes',
            '--web-security=false',
            '--ssl-protocol=any'
        ];

        var isDone = false;

        var output = "";
        var path2PhantomJS = options.phantomJSPath || phantomjs.path;
        var child = childProcess.execFile(path2PhantomJS, childArgs, { maxBuffer: 1024 * 50000 }, function (err, stdout, stderr) {
            if (isDone)
                return;

            isDone = true;
            if (err) {
                return cb(err);
            }

            cb(null, JSON.parse(output));
        });
        child.stdout.on("data", function(data) {
            output += data;
        });

        setTimeout(function() {
            if (isDone)
                return;

            isDone = true;
            cb(new Error("Timeout when executing in phantom"));
        }, options.timeout);
    });
};

Add reference for phantomJSPath option


'use strict';

/**
 * Get unique error field name
 */
var getUniqueErrorMessage = function(err) {
	var output;

	try {
		var fieldName = err.err.substring(err.err.lastIndexOf('.$') + 2, err.err.lastIndexOf('_1'));
		output = fieldName.charAt(0).toUpperCase() + fieldName.slice(1) + ' already exists';

	} catch(ex) {
		output = 'Unique field already exists';
	}

	return output;
};

/**
 * Get the error message from error object
 */
exports.getErrorMessage = function(err) {
	var message = '';

	if (err.code) {
		switch (err.code) {
			case 11000:
			case 11001:
				message = getUniqueErrorMessage(err);
				break;
			default:
				message = 'Something went wrong';
		}
	} else {
		for (var errName in err.errors) {
			if (err.errors[errName].message) message = err.errors[errName].message;
		}
	}

	return message;
};

'use strict';

/**
 * Get unique error field name
 */
var getUniqueErrorMessage = function(err) {
	var output;

	try {
		var fieldName = err.errmsg.substring(err.errmsg.lastIndexOf('.$') + 2, err.errmsg.lastIndexOf('_1'));
		output = fieldName.charAt(0).toUpperCase() + fieldName.slice(1) + ' already exists';

	} catch(ex) {
		output = 'Unique field already exists';
	}

	return output;
};

/**
 * Get the error message from error object
 */
exports.getErrorMessage = function(err) {
	var message = '';

	if (err.code) {
		switch (err.code) {
			case 11000:
			case 11001:
				message = getUniqueErrorMessage(err);
				break;
			default:
				message = 'Something went wrong';
		}
	} else {
		for (var errName in err.errors) {
			if (err.errors[errName].message) message = err.errors[errName].message;
		}
	}

	return message;
};

Fix message catching the field for which a duplicate key error ocurred


/*jshint node:true*/
module.exports = {
  useVersionCompatibility: true,
  scenarios: [
    {
      name: 'ember-beta',
      command: 'echo DISABLED'
    },
    {
      name: 'ember-canary',
      command: 'echo DISABLED'
    }
  ]
};

/*jshint node:true*/
module.exports = {
  useVersionCompatibility: true
};

Enable beta and canary channels.


import template from './categories.html';
import controller from './categories.controller';
import './categories.styl';

// use ES6 shorthand syntax for object initializers
const CategoriesComponent = {
    template,
    controller,
    controllerAs: 'categoriesListCtrl'
}

export default CategoriesComponent;

import template from './categories.html';
import controller from './categories.controller';
import './categories.styl';

const categoriesComponent = {
  template,
  controller,
  controllerAs: 'categoriesListCtrl'
};

export default categoriesComponent;

Change categories.controller to original tutorial code


'use strict'

var isPresent = require('is-present')
var githubRepos = require('github-repositories')
var Promise = require('pinkie-promise')

var reposToIgnore = [
  'tachyons',
  'tachyons-cli',
  'tachyons-modules'
]

module.exports = function tachyonsModules () {
  return githubRepos('tachyons-css').then(function (repos) {
    if (isPresent(repos)) {
      return repos.filter(isCssModule)
    } else {
      return Promise.reject(new Error('No results found for tachyons-css'))
    }
  })
}

function isCssModule (repo) {
  return reposToIgnore.indexOf(repo.name) === -1
}

'use strict'

var isPresent = require('is-present')
var githubRepos = require('github-repositories')
var Promise = require('pinkie-promise')

var reposToIgnore = [
  'tachyons',
  'tachyons-cli',
  'tachyons-modules',
  'tachyons-css.github.io',
  'tachyons-sass',
  'tachyons-skins',
  'tachyons-styles'
]

module.exports = function tachyonsModules () {
  return githubRepos('tachyons-css').then(function (repos) {
    if (isPresent(repos)) {
      return repos.filter(isCssModule)
    } else {
      return Promise.reject(new Error('No results found for tachyons-css'))
    }
  })
}

function isCssModule (repo) {
  return reposToIgnore.indexOf(repo.name) === -1
}

Add a few more modules to ignore


'use strict'

const ndjson = require('ndjson'),
      request = require('request'),
      zlib = require('zlib')

module.exports = (readableObjectStream, eventWriteKey) => {

  const options = {
          url: `https://event-sink.appuri.net/e?jsonConnectorApiKey=${eventWriteKey}`,
          headers: {
            'Content-Type': 'application/x-ldjson',
            'Content-Encoding': 'gzip'
          }
        },
        serialize = ndjson.serialize(),
        compress = zlib.createGzip(),
        post = request.post(options)

  readableObjectStream
    .pipe(serialize)
    .pipe(compress)
    .pipe(post)

  return new Promise((resolve, reject) => {

    let resp

    readableObjectStream.on('error', reject)
    serialize.on('error', reject)
    compress.on('error', reject)
    post.on('error', reject)
    post.on('response', response => {
      resp = response
      if (response.statusCode >= 400) {
        var body = ''
        response.setEncoding('utf8')
        response.on('data', d => body += d)
        response.on('error', () => reject({ statusCode: response.statusCode }))
        response.on('end', () => {
          try { body = JSON.parse(body) } catch(e){}
          reject({
            statusCode: response.statusCode,
            responseBody: body
          })
        })
      }
    })

    post.on('end', () => resolve(resp))
  })
}

'use strict'

const ndjson = require('ndjson'),
      request = require('request'),
      zlib = require('zlib')

module.exports = (readableObjectStream, eventWriteKey, eventSinkHost) => {

  const options = {
          url: `https://${eventSinkHost || 'event-sink.appuri.net'}/e?jsonConnectorApiKey=${eventWriteKey}`,
          headers: {
            'Content-Type': 'application/x-ldjson',
            'Content-Encoding': 'gzip'
          }
        },
        serialize = ndjson.serialize(),
        compress = zlib.createGzip(),
        post = request.post(options)

  readableObjectStream
    .pipe(serialize)
    .pipe(compress)
    .pipe(post)

  return new Promise((resolve, reject) => {

    let resp

    readableObjectStream.on('error', reject)
    serialize.on('error', reject)
    compress.on('error', reject)
    post.on('error', reject)
    post.on('response', response => {
      resp = response
      if (response.statusCode >= 400) {
        var body = ''
        response.setEncoding('utf8')
        response.on('data', d => body += d)
        response.on('error', () => reject({ statusCode: response.statusCode }))
        response.on('end', () => {
          try { body = JSON.parse(body) } catch(e){}
          reject({
            statusCode: response.statusCode,
            responseBody: body
          })
        })
      }
    })

    post.on('end', () => resolve(resp))
  })
}

Allow the event sink host to be configured


var exec = require('./exec').exec
var chalk = require('chalk')

module.exports = function WebpackShellPlugin (options) {
  return {
    apply: function (compiler) {
      compiler.plugin('after-emit', function (compilation, callback) {
        if (options.script) {
          exec(options.script, { shell: '/bin/bash' }).then(function (res) {
            if (res.stderr) {
              console.error(chalk.red('error') + ' Error while running the command after build')
              console.error(res.stderr)
            }
            res.stdout.split('\\n').map(function (line) { console.log(line) })
          })
          .then(callback)
          .catch(function (err) {
            console.error(chalk.red('error') + ' Error while running the command after build')
            console.error(err)
            callback()
          })
        } else {
          callback()
        }
      })
    }
  }
}

var exec = require('./exec').exec
var chalk = require('chalk')

module.exports = function WebpackShellPlugin (options) {
  return {
    apply: function (compiler) {
      compiler.plugin('after-emit', function (compilation, callback) {
        if (options.script) {
          exec(options.script, { shell: '/bin/bash' }).then(function (res) {
            if (res.stderr) {
              console.error(chalk.red('error') + ' Error while running the command after build')
              console.error(res.stderr)
            }
            if (res.stdout.trim().length > 0) {
              res.stdout.trim().split('\n').map(function (line) { console.log(line) })
            }
          })
          .then(callback)
          .catch(function (err) {
            console.error(chalk.red('error') + ' Error while running the command after build')
            console.error(err)
            callback()
          })
        } else {
          callback()
        }
      })
    }
  }
}

Fix result logging of shell plugin


'use strict';

var path = require('path');
var isglob = require('is-glob');

module.exports = function globParent(str) {
	while (isglob(str)) str = path.dirname(str);
	return str;
};

'use strict';

var path = require('path');
var isglob = require('is-glob');

module.exports = function globParent(str) {
	str += 'a'; // preserves full path in case of trailing path separator
	do {str = path.dirname(str)} while (isglob(str));
	return str;
};

Move up to dirname regardless of glob characters


import compression from 'compression';
import express from 'express';
import fs from 'fs';

const app = express();
const port = process.env.PORT || 8080;
app.use(compression());
app.enable('trust proxy');

app.use((req, res, next) => {
  if (req.secure || req.headers.host === `localhost:${port}`) {
    next();
  } else {
    res.redirect(`https://${req.headers.host}${req.url}`);
  }
});

app.use(express.static('./build'));

app.listen(port, (err) => {
  if (err) {
    console.log(err); // eslint-disable-line no-console
    return;
  }
  console.log(`App and API is live at http://localhost:${port}`); // eslint-disable-line no-console
});

import compression from 'compression';
import express from 'express';
import fs from 'fs';

const app = express();
const port = process.env.PORT || 8080;
app.use(compression());
app.enable('trust proxy');

app.use((req, res, next) => {
  if (req.secure || req.headers.host === `localhost:${port}`) {
    next();
  } else {
    res.redirect(`https://${req.headers.host}${req.url}`);
  }
});

app.use(express.static('./build'));

app.get('*', (req, res) => {
  res.sendFile(path.resolve(__dirname, 'build', 'index.html'));
});

app.listen(port, err => {
  if (err) {
    console.log(err); // eslint-disable-line no-console
    return;
  }
  console.log(`App and API is live at http://localhost:${port}`); // eslint-disable-line no-console
});

Add wildcard GET request to avoid 404 error when going to directly to a page or refreshing on a page.


module.exports = function (max) {
  var size = 0, cache = {}, _cache = {}

  function update (key, value) {
    cache[key] = value
    size ++
    if(size >= max) {
      size = 0
      _cache = cache
      cache = {}
    }
  }

  return {
    get: function (key) {
      var v = cache[key]
      if(v) return v
      if(v = _cache[key]) {
        update(key, v)
        return v
      }
    },
    set: function (key, value) {
      if(cache[key]) cache[key] = value
      else update(key, value)
    }
  }
}

module.exports = function (max) {
  var size = 0, cache = Object.create(null), _cache = Object.create(null)

  function update (key, value) {
    cache[key] = value
    size ++
    if(size >= max) {
      size = 0
      _cache = cache
      cache = Object.create(null)
    }
  }

  return {
    get: function (key) {
      var v = cache[key]
      if(v) return v
      if(v = _cache[key]) {
        update(key, v)
        return v
      }
    },
    set: function (key, value) {
      if(cache[key]) cache[key] = value
      else update(key, value)
    }
  }
}

Create cache objects without prototype


'use strict';

var co = require('co');

module.exports = co;
module.exports.series = function(arr) {
    return co(function* () {
        var result = [];
        for (let promise of arr) {
            let res = yield promise;
            result.push(res);
        }

        return result;
    });
};

'use strict';

var co = require('co');

module.exports = co;
module.exports.series = function(arr) {
    var promiseFn = function(fn) {
        return function(resolve, reject) {
            let p2 = fn(reject, resolve);
            if (p2 && p2.then && p2.catch) {
                p2.then(resolve).catch(reject);
            }
        };
    };

    return co(function* () {
        var result = [];
        for (let promise of arr) {
            let res;
            if (typeof promise === 'function') {
                res = yield new Promise(promiseFn(promise));

            }
            else {
                res = yield promise;
            }

            result.push(res);
        }

        return result;
    });
};

Support functions who returning promises


/*!
 * to-clipboard <https://github.com/jonschlinkert/to-clipboard>
 *
 * Copyright (c) 2015, Jon Schlinkert.
 * Licensed under the MIT License.
 */

'use strict';

var cp = require('child_process');

var program = {
	darwin: {
		name: 'pbcopy',
		args: []
	},
	win32: {
		name: 'clip',
		args: [],
	},
	linux: {
		name: 'xclip',
		args: ['-selection', 'clipboard']
	}
};

function toClipboard(args, cb) {
  var proc = cp.spawn(program.name, program.args, {
    stdio: ["pipe", "ignore", "ignore"]
  });
  if (cb) {
    proc.on("error", cb);
    proc.on("exit", function() {
      cb(null);
    });
  }
  proc.stdin.write(args);
  proc.stdin.end();
}

toClipboard.sync = function toClipboardSync(args) {
  return cp.execSync(program.name, program.args, {
    input: args
  });
};

/**
 * Expose `toClipboard`
 */

module.exports = toClipboard;

/*!
 * to-clipboard <https://github.com/jonschlinkert/to-clipboard>
 *
 * Copyright (c) 2015, Jon Schlinkert.
 * Licensed under the MIT License.
 */

'use strict';

var cp = require('child_process');

var program = {
	darwin: {
		name: 'pbcopy',
		args: []
	},
	win32: {
		name: 'clip',
		args: [],
	},
	linux: {
		name: 'xclip',
		args: ['-selection', 'clipboard']
	}
};

function toClipboard(args, cb) {
  var proc = cp.spawn(program.name, program.args, {
    stdio: ["pipe", "ignore", "ignore"]
  });
  if (cb) {
    proc.on("error", cb);
    proc.on("exit", function() {
      cb(null);
    });
  }
  proc.stdin.write(args);
  proc.stdin.end();
}

toClipboard.sync = function toClipboardSync(args) {
  return cp.execSync(program.name + ' ' + program.args.join(' '), {
    input: args
  });
};

/**
 * Expose `toClipboard`
 */

module.exports = toClipboard;

Fix mistake - exec needs to be a single string


var querystring = require("querystring");
var list = /L_([A-Za-z]+)(\d+)/;

module.exports = function(input) {
  var data;
  var output = {};

  if (typeof input === "string") {
    data = querystring.parse(input);
  } else {
    data = input;
  }

  Object.keys(data).forEach(function(key) {
    var list_match = key.match(list);

    if (!list_match) {
      return output[key] = data[key];
    }

    if (!Array.isArray(output.L)) {
      output.L = [];
    }

    if (!output.L[list_match[2]]) {
      output.L[list_match[2]] = {};
    }

    output.L[list_match[2]][list_match[1]] = data[key];
  });

  return output;
};

var querystring = require("querystring");
var list = /L_([A-Za-z]+)(\d+)/;

module.exports = function(input) {
  var data;
  var output = {};

  if (typeof input === "string") {
    // Parse without limits on the maximum number of keys, see:
    // https://nodejs.org/api/querystring.html#querystring_querystring_parse_str_sep_eq_options
    data = querystring.parse(input, null, null, { maxKeys: 0 });
  } else {
    data = input;
  }

  Object.keys(data).forEach(function(key) {
    var list_match = key.match(list);

    if (!list_match) {
      return output[key] = data[key];
    }

    if (!Array.isArray(output.L)) {
      output.L = [];
    }

    if (!output.L[list_match[2]]) {
      output.L[list_match[2]] = {};
    }

    output.L[list_match[2]][list_match[1]] = data[key];
  });

  return output;
};

Remove the default maximum parsing limit of 1,000 keys


import Route from '@ember/routing/route';
import { collect } from '@ember/object/computed';
import { watchRecord, watchRelationship, watchAll } from 'nomad-ui/utils/properties/watch';
import WithWatchers from 'nomad-ui/mixins/with-watchers';

export default class IndexRoute extends Route.extend(WithWatchers) {
  startWatchers(controller, model) {
    if (!model) {
      return;
    }
    controller.set('watchers', {
      model: this.watch.perform(model),
      summary: this.watchSummary.perform(model.get('summary')),
      allocations: this.watchAllocations.perform(model),
      evaluations: this.watchEvaluations.perform(model),
      latestDeployment:
        model.get('supportsDeployments') && this.watchLatestDeployment.perform(model),
      list: model.get('hasChildren') && this.watchAll.perform(),
    });
  }

  @watchRecord('job') watch;
  @watchAll('job') watchAll;
  @watchRecord('job-summary') watchSummary;
  @watchRelationship('allocations') watchAllocations;
  @watchRelationship('evaluations') watchEvaluations;
  @watchRelationship('latestDeployment') watchLatestDeployment;

  @collect(
    'watch',
    'watchAll',
    'watchSummary',
    'watchAllocations',
    'watchEvaluations',
    'watchLatestDeployment'
  )
  watchers;
}

import Route from '@ember/routing/route';
import { collect } from '@ember/object/computed';
import { watchRecord, watchRelationship, watchAll } from 'nomad-ui/utils/properties/watch';
import WithWatchers from 'nomad-ui/mixins/with-watchers';

export default class IndexRoute extends Route.extend(WithWatchers) {
  startWatchers(controller, model) {
    if (!model) {
      return;
    }
    controller.set('watchers', {
      model: this.watch.perform(model),
      summary: this.watchSummary.perform(model.get('summary')),
      allocations: this.watchAllocations.perform(model),
      evaluations: this.watchEvaluations.perform(model),
      latestDeployment:
        model.get('supportsDeployments') && this.watchLatestDeployment.perform(model),
      list: model.get('hasChildren') && this.watchAll.perform(),
    });
  }

  setupController(controller, model) {
    // Parameterized and periodic detail pages, which list children jobs,
    // should sort by submit time.
    if (model && ['periodic', 'parameterized'].includes(model.templateType)) {
      controller.setProperties({
        sortProperty: 'submitTime',
        sortDescending: true,
      });
    }
    return super.setupController(...arguments);
  }

  @watchRecord('job') watch;
  @watchAll('job') watchAll;
  @watchRecord('job-summary') watchSummary;
  @watchRelationship('allocations') watchAllocations;
  @watchRelationship('evaluations') watchEvaluations;
  @watchRelationship('latestDeployment') watchLatestDeployment;

  @collect(
    'watch',
    'watchAll',
    'watchSummary',
    'watchAllocations',
    'watchEvaluations',
    'watchLatestDeployment'
  )
  watchers;
}

Sort periodic and parameterized job detail pages by most recently submitted


const nodemon = require("nodemon")

var server

module.exports = function () {
  this.nodemon = function (opts) {
    return new Promise((resolve, reject) => {
      if (!server) {
        server = nodemon(opts)
          .on("start", function () {
            this.log("nodemon started.")
          }.bind(this))
          .on("crash", function () {
            this.error("script crashed for some reason")
            reject()
          }.bind(this))
      } else {
        server.emit("restart")
      }
      resolve()
    })
  }
}

const nodemon = require("nodemon")

var server
var started = false

module.exports = function () {
  this.nodemon = function (opts) {
    return new Promise((resolve, reject) => {
      if (started) { server.emit("restart") }
      else {
        server = nodemon(opts)
          .on("start", function () {
            this.log("nodemon started.")
            resolve()
          }.bind(this))
          .on("crash", function () {
            this.error("script crashed for some reason")
            reject()
          }.bind(this))
      }
    })
  }
}

Add started flag and move resolve into event :art:


/**
 * Properties to prefix.
 */
var props = [
	"hyphens",
	"line-break",
	"text-align-last",
	"text-emphasis",
	"text-emphasis-color",
	"text-emphasis-style",
	"word-break"
];

/**
 * PostCSS plugin to prefix ePub3 properties.
 */
module.exports = function plugin() {
	return function(style) {
		style.eachDecl(function(decl) {
			if (decl.value) {
				try {
					if (props.indexOf(decl.prop) >= 0) {
						decl.insertBefore(decl.clone({prop: "-epub-" + decl.prop}));
					}
				} catch (err) {
					err.message = gnuMessage(err.message, decl.source);
					throw err;
				}
			}
		});
	};
};

/**
 * return GNU style message
 *
 * @param {String} message
 * @param {Object} source
 */
function gnuMessage(message, source) {
	return (source ? (source.file ? source.file : "<css input>") + ":" + source.start.line + ":" + source.start.column : "") + " " + message;
}

"use strict";

/**
 * Properties to prefix.
 */
var postcss = require("postcss"),
		props = [
			"hyphens",
			"line-break",
			"text-align-last",
			"text-emphasis",
			"text-emphasis-color",
			"text-emphasis-style",
			"word-break"
		];

/**
 * PostCSS plugin to prefix ePub3 properties.
 * @param {Object} style
 */
function plugin(style) {
	style.eachDecl(function(decl) {
		if (decl.value) {
			try {
				if (props.indexOf(decl.prop) >= 0) {
					decl.parent.insertBefore(decl, decl.clone({prop: "-epub-" + decl.prop}));
				}
			} catch (err) {
				err.message = gnuMessage(err.message, decl.source);
				throw err;
			}
		}
	});
}

/**
 * return GNU style message
 *
 * @param {String} message
 * @param {Object} source
 */
function gnuMessage(message, source) {
	return (source ? (source.file ? source.file : "<css input>") + ":" + source.start.line + ":" + source.start.column : "") + " " + message;
}

/*
 * ...and export for use...
 */
module.exports = {
	postcss: plugin,
	process: function(css, opts) {
		return postcss(plugin).process(css, opts).css;
	}
};

Fix so it works in grunt etc


// @flow

import type {
  DocumentPath,
  DotNotationString,
} from 'mongolike-operations'

/**
 * @public
 * Parse DocumentPath into an array of property names.
 */
export function parseDocumentPath(docPath: DocumentPath): Array<string | number> {
  return docPath.split(/(?<!\\)[.[]/).map(
    attribute => attribute.charAt(attribute.length - 1) === ']' ? parseInt(attribute.slice(0, -1)) : unescapePathDelimiter(attribute)
  )
}

/**
 * Create DocumentPath from arguments.
 */
export function createDocumentPath(...attributes: Array<string | number>): DocumentPath {
  const joined = attributes.reduce((docPath, attr) =>
    typeof attr === 'string' ? `${docPath}.${escapePathDelimiter(attr)}` : `${docPath}[${attr.toString()}]`, '')
  return (joined.charAt(0) === '.') ? joined.slice(1) : joined
}

/**
 *
 */
export function convertToDotNotationString(docPath: DocumentPath): DotNotationString {
  return docPath.replace(/\[(\d{1,})\]/g, '.$1')
}

function escapePathDelimiter(attr: string | number): string | number {
  return typeof attr === 'number' ? attr : attr.replace(/\./g, '\\.')
}

function unescapePathDelimiter(attr: string | number): string | number {
  return typeof attr === 'number' ? attr : attr.replace(/\\\./, '.')
}

// @flow

import type {
  DocumentPath,
  DotNotationString,
} from 'mongolike-operations'

/**
 * @public
 * Parse DocumentPath into an array of property names.
 */
export function parseDocumentPath(docPath: DocumentPath): Array<string | number> {
  // Many JS runtime cannot use look behind.
  return docPath.split(/\\./).join('$$$').split(/[.[]/).map(
    attribute => attribute.charAt(attribute.length - 1) === ']' ? parseInt(attribute.slice(0, -1)) : unescapePathDelimiter(attribute.split('$$$').join('\\.'))
  )
}

/**
 * Create DocumentPath from arguments.
 */
export function createDocumentPath(...attributes: Array<string | number>): DocumentPath {
  const joined = attributes.reduce((docPath, attr) =>
    typeof attr === 'string' ? `${docPath}.${escapePathDelimiter(attr)}` : `${docPath}[${attr.toString()}]`, '')
  return (joined.charAt(0) === '.') ? joined.slice(1) : joined
}

/**
 *
 */
export function convertToDotNotationString(docPath: DocumentPath): DotNotationString {
  return docPath.replace(/\[(\d{1,})\]/g, '.$1')
}

function escapePathDelimiter(attr: string | number): string | number {
  return typeof attr === 'number' ? attr : attr.replace(/\./g, '\\.')
}

function unescapePathDelimiter(attr: string | number): string | number {
  return typeof attr === 'number' ? attr : attr.replace(/\\\./, '.')
}

Use split+join instead of ES2018 feature(look behind)


/**
 * @module ember-flexberry
 */

import Ember from 'ember';
import ColsconfigDialogController from '../mixins/colsconfig-dialog-controller';
import { translationMacro as t } from 'ember-i18n';

export default Ember.Controller.extend(ColsconfigDialogController, {
  /**
   * Current opened modal window.
   *
   * @property _openedModalDialog
   * @type JQuery
   * @default undefined
   */
  _openedModalDialog: undefined,
  title: t('components.colsconfig-dialog-content.title'),
  actions: {
    /**
     * Handles create modal window action.
     * It saves created window to have opportunity to close it later.
     *
     * @method createdModalDialog
     * @param {JQuery} modalDialog Created modal window.
     */
    createdModalDialog: function(modalDialog) {
      this.set('_openedModalDialog', modalDialog);
    }
  },

  /**
   * Close current modal window if it exists.
   *
   * @method closeModalDialog
   */
  closeModalDialog: function () {
    let openedDialog = this.get('_openedModalDialog');
    if (openedDialog) {
      openedDialog.modal('hide');
      this.set('_openedModalDialog', undefined);
    }
  },

  clear: function() {
    this.set('_openedModalDialog', undefined);
    return this;
  }

});

/**
 * @module ember-flexberry
 */

import Ember from 'ember';
import ColsconfigDialogController from '../mixins/colsconfig-dialog-controller';
import { translationMacro as t } from 'ember-i18n';

export default Ember.Controller.extend(ColsconfigDialogController, {
  /**
   * Current opened modal window.
   *
   * @property _openedModalDialog
   * @type JQuery
   * @default undefined
   */
  _openedModalDialog: undefined,
  title: t('components.colsconfig-dialog-content.title'),
  actions: {
    /**
     * Handles create modal window action.
     * It saves created window to have opportunity to close it later.
     *
     * @method createdModalDialog
     * @param {JQuery} modalDialog Created modal window.
     */
    createdModalDialog: function(modalDialog) {
      this.set('message.visible', false);
      this.set('_openedModalDialog', modalDialog);
    }
  },

  /**
   * Close current modal window if it exists.
   *
   * @method closeModalDialog
   */
  closeModalDialog: function () {
    let openedDialog = this.get('_openedModalDialog');
    if (openedDialog) {
      openedDialog.modal('hide');
      this.set('_openedModalDialog', undefined);
    }
  },

  clear: function() {
    this.set('_openedModalDialog', undefined);
    return this;
  }

});

Add autohide to cols-config messages


(function() {
  'use strict';

  describe("<%= pluginFullName %>", function() {

    var slides, deck,

      createDeck = function() {
        slides = [];

        var parent = document.createElement('article');
        for (var i = 0; i < 10; i++) {
          slides.push(document.createElement('section'));
          parent.appendChild(slides[i]);
        }

        deck = bespoke.from(parent, {
          <%= pluginNameCamelized %>: true
        });
      };

    beforeEach(createDeck);

    describe("deck.slide", function() {

      beforeEach(function() {
        deck.slide(0);
      });

      it("should not add a useless 'foobar' class to the slide", function() {
        expect(slides[0].classList.contains('foobar')).toBe(false);
      });

    });

  });

}());

(function() {
  'use strict';

  describe("<%= pluginFullName %>", function() {

    var deck,

      createDeck = function() {
        slides = [];

        var parent = document.createElement('article');
        for (var i = 0; i < 10; i++) {
          parent.appendChild(document.createElement('section'));
        }

        deck = bespoke.from(parent, {
          <%= pluginNameCamelized %>: true
        });
      };

    beforeEach(createDeck);

    describe("deck.slide", function() {

      beforeEach(function() {
        deck.slide(0);
      });

      it("should not add a useless 'foobar' class to the slide", function() {
        expect(deck.slides[0].classList.contains('foobar')).toBe(false);
      });

    });

  });

}());

Remove redundant 'slides' var from test


import _ from 'underscore';
import BaseStore from 'stores/BaseStore';
import Dispatcher from 'dispatchers/Dispatcher';
import Store from 'stores/Store';
import AllocationConstants from 'constants/ResourceRequestConstants';
import AllocationCollection from 'collections/AllocationCollection';
import stores from 'stores';

let AllocationStore = BaseStore.extend({
    collection: AllocationCollection
});
let store = new AllocationStore();

Dispatcher.register(function(dispatch) {
    var actionType = dispatch.action.actionType;
    var payload = dispatch.action.payload;
    var options = dispatch.action.options || options;

    switch (actionType) {
        case AllocationConstants.CREATE_ALLOCATION:
            store.add(payload.allocation);
            break;
        default:
            return true;
    }

    if (!options.silent) {
        store.emitChange();
    }

    return true;
});

export default store;

import _ from 'underscore';
import BaseStore from 'stores/BaseStore';
import Dispatcher from 'dispatchers/Dispatcher';
import Store from 'stores/Store';
import AllocationConstants from 'constants/AllocationConstants';
import AllocationCollection from 'collections/AllocationCollection';
import stores from 'stores';

let AllocationStore = BaseStore.extend({
    collection: AllocationCollection
});
let store = new AllocationStore();

Dispatcher.register(function(dispatch) {
    var actionType = dispatch.action.actionType;
    var payload = dispatch.action.payload;
    var options = dispatch.action.options || options;

    switch (actionType) {
        case AllocationConstants.CREATE_ALLOCATION:
            store.add(payload.allocation);
            break;
        default:
            return true;
    }

    if (!options.silent) {
        store.emitChange();
    }

    return true;
});

export default store;

Fix store not updating, because incorrect event string


import React from 'react';
import SnackbarItem from './SnackbarItem';
import './Snackbar.css';
import { useSnackbarContext } from '../../utils/SnackbarProvider';

const SnackbarContainer = () => {
  const { snackbarItems, hide } = useSnackbarContext();

  const renderItem = item => {
    return <SnackbarItem key={item.itemId} options={item} onClose={hide} />;
  };

  if (!snackbarItems) {
    return null;
  }

  const renderItems = () => {
    const items = {
      topLeft: [],
      topCenter: [],
      topRight: [],
      bottomLeft: [],
      bottomCenter: [],
      bottomRight: [],
    };

    snackbarItems.map(item => {
      items[item.position].push(item);
    });

    return (

        {Object.keys(items).map(pos => {
          if (!items[pos].length) {
            return null;
          }

          return (
            <div key={pos} className={`sb-container sb-${pos}`}>
              {items[pos].map(item => (

{renderItem(item)}

              ))}

          );
        })}

    );
  };

  return <>{renderItems()}</>;
};

export default SnackbarContainer;

import React from 'react';
import SnackbarItem from './SnackbarItem';
import './Snackbar.css';
import { useSnackbarContext } from '../../utils/SnackbarProvider';

const SnackbarContainer = () => {
  const { snackbarItems, hide } = useSnackbarContext();

  const renderItem = item => {
    return <SnackbarItem key={item.itemId} options={item} onClose={hide} />;
  };

  if (!snackbarItems) {
    return null;
  }

  const renderItems = () => {
    const items = {
      topLeft: [],
      topCenter: [],
      topRight: [],
      bottomLeft: [],
      bottomCenter: [],
      bottomRight: [],
    };

    snackbarItems.map(item => {
      items[item.position].push(item);
    });

    return (

        {Object.keys(items).map(pos => {
          if (!items[pos].length) {
            return null;
          }

          return (
            <div key={pos} className={`sb-container sb-${pos}`}>
              {items[pos].map((item, index) => (

{renderItem(item)}

              ))}

          );
        })}

    );
  };

  return <>{renderItems()}</>;
};

export default SnackbarContainer;

Fix key warning of snackbar elements


describe("InterventionPlotBand", function() {

  describe(".toHighChartsDate", function() {
    describe("good input", function() {
      it("returns a correct JavaScript UTC date for HighCharts", function() {
        var input = { year: 2015, month: 8, day: 14 };
        var high_charts_date = InterventionPlotBand.toHighChartsDate(input);
        expect(high_charts_date).toEqual(1439510400000);
        expect(new Date(high_charts_date).toGMTString()).toEqual('Fri, 14 Aug 2015 00:00:00 GMT');
      });
    });
  });
  describe("#toHighCharts", function() {
    describe("good input", function() {
      it("returns a correct HighCharts configuration", function() {
        var attributes = { name: "Extra math help",
          start_date: { year: 2015, month: 8, day: 14 },
          end_date: { year: 2015, month: 8, day: 15 }
        };
        var intervention_plot_band = new InterventionPlotBand(attributes);
        var to_highcharts = intervention_plot_band.toHighCharts();
        expect(to_highcharts.label.text).toEqual('Extra math help');
        expect(to_highcharts.from).toEqual(1439510400000);
        expect(to_highcharts.to).toEqual(1439596800000);
      });
    });
  });
});

describe("InterventionPlotBand", function() {

  describe(".toHighChartsDate", function() {
    describe("good input", function() {
      it("returns a correct JavaScript UTC date for HighCharts", function() {
        var input = { year: 2015, month: 8, day: 14 };
        var high_charts_date = InterventionPlotBand.toHighChartsDate(input);
        expect(high_charts_date).toEqual(1439510400000);
        expect(new Date(high_charts_date).toGMTString()).toEqual('Fri, 14 Aug 2015 00:00:00 GMT');
      });
    });
  });
  describe("#toHighCharts", function() {
    describe("good input", function() {
      it("returns a correct HighCharts configuration", function() {
        var attributes = { name: "Extra math help",
          start_date: { year: 2015, month: 8, day: 14 },
          end_date: { year: 2015, month: 8, day: 15 }
        };
        var intervention_plot_band = new InterventionPlotBand(attributes);
        var to_highcharts = intervention_plot_band.toHighCharts();
        expect(to_highcharts.from).toEqual(1439510400000);
        expect(to_highcharts.to).toEqual(1439596800000);
      });
    });
  });
});

Update spec to no longer expect label text on intervention plot bands


/**
 * This is an artificial model that takes all sorts of abstracts from the document model
 * so that they can be represented as individual cards
 */
export default class AbstractsSectionModel {
  constructor (api) {
    this._api = api
    this._path = ['article', 'customAbstracts']
  }

  get id () {
    return '@abstracts'
  }

  get type () {
    return '@abstracts'
  }

  get isCollection () {
    return true
  }

  getPath () {
    return this._path
  }

  getItems () {
    let doc = this._api.getDocument()
    return [doc.get('abstract')].concat(
      doc.resolve(['article', 'customAbstracts'])
    )
  }

  get length () {
    return this.getItems().length
  }
}

/**
 * This is an artificial model that takes all sorts of abstracts from the document model
 * so that they can be represented as individual cards
 */
export default class AbstractsSectionModel {
  constructor (api) {
    this._api = api
    this._path = ['article', 'customAbstracts']
  }

  get id () {
    return '@abstracts'
  }

  get type () {
    return '@abstracts'
  }

  get isCollection () {
    return true
  }

  getPath () {
    return this._path
  }

  getItems () {
    let doc = this._api.getDocument()
    return doc.resolve(['article', 'customAbstracts'])
  }

  get length () {
    return this.getItems().length
  }
}

Remove main abstract from metadata.


var mongoose = require("mongoose");
var historySchema = new mongoose.Schema(
    {
        collectionName: {type: String},
        collectionId: {type: mongoose.Schema.Types.ObjectId},
        diff: {},
        user: {},
        reason: {type: String},
        version: {type: Number, min: 0}
    },
    {
        timestamps: true
    });

var History = mongoose.model("History", historySchema);
module.exports = History;

const mongoose = require('mongoose');
const { Schema } = mongoose;

const historySchema = new Schema(
  {
    collectionName: String,
    collectionId: Schema.Types.ObjectId,
    diff: {},
    user: {},
    reason: String,
    version: { type: Number, min: 0 }
  },
  {
    timestamps: true
  }
);

module.exports = mongoose.model('History', historySchema);

Clean up history model: ES6, DRY, simpler schema.


import angular from "angular";
import uiRouter from "angular-ui-router";
import storeDetailComponent from "./storeDetail.component";

let storeDetailModule = angular.module("storeDetail", [
  uiRouter
])

.component("storeDetail", storeDetailComponent)

.config(($stateProvider, hookProvider) => {
  "ngInject";
  $stateProvider
    .state("storeDetail", {
      parent: "main",
      url: "/store/:id",
      component: "storeDetail",
      resolve: {
        storedata: (Store, $stateParams) => {
          return Store.get($stateParams.id);
        }
      }
    });
  hookProvider.setup("storeDetail", { authenticated: true, anonymous: "login" });
})

.name;

export default storeDetailModule;

import angular from "angular";
import uiRouter from "angular-ui-router";
import storeDetailComponent from "./storeDetail.component";

let storeDetailModule = angular.module("storeDetail", [
  uiRouter
])

.component("storeDetail", storeDetailComponent)

.config(($stateProvider, hookProvider) => {
  "ngInject";
  $stateProvider
    .state("storeDetail", {
      parent: "main",
      url: "/store/:id",
      component: "storeDetail",
      resolve: {

        storedata: (Store, $stateParams) => {
          return Store.get($stateParams.id);
        },

        /**
          Loads the group for this store, and sets it as the current group
        */
        groupdata: (storedata, Group, CurrentGroup) => {
          "ngInject";
          return Group.get(storedata.group).then((group) => {
            CurrentGroup.set(group);
            return group;
          });
        }

      }
    });
  hookProvider.setup("storeDetail", { authenticated: true, anonymous: "login" });
})

.name;

export default storeDetailModule;

Load the group when we load the store page


'use strict'

const { throwError } = require('../../errors')

// Check output, for the errors that should not happen,
// i.e. server-side (e.g. 500)
// In short: response should be an array of objects
const responseValidation = function ({ response: { data, metadata } }) {
  if (!data) {
    const message = '\'response.data\' should be defined'
    throwError(message, { reason: 'ENGINE' })
  }

  if (!Array.isArray(data)) {
    const message = `'response.data' should be an array, not '${data}'`
    throwError(message, { reason: 'ENGINE' })
  }

  if (!metadata) {
    const message = '\'response.metadata\' should be defined'
    throwError(message, { reason: 'ENGINE' })
  }
}

module.exports = {
  responseValidation,
}

'use strict'

const { throwPb } = require('../../errors')

// Check output, for the errors that should not happen,
// i.e. server-side (e.g. 500)
// In short: response should be an array of objects
const responseValidation = function ({ response: { data, metadata } }) {
  if (!data) {
    const message = '\'response.data\' should be defined'
    throwPb({ message, reason: 'ENGINE' })
  }

  if (!Array.isArray(data)) {
    const message = `'response.data' should be an array, not '${data}'`
    throwPb({ message, reason: 'ENGINE' })
  }

  if (!metadata) {
    const message = '\'response.metadata\' should be defined'
    throwPb({ message, reason: 'ENGINE' })
  }
}

module.exports = {
  responseValidation,
}

Improve response validation error handling


var seeds = require('./features.json');
var Feature = require('./feature');

module.exports = function (callback) {
  // Drop the features collection.
  Feature.remove(function () {
    // Seed the features collection.
    Feature.create(seeds, function () {
      callback();
    });
  });
};

var seeds = require('./features.json');
var Feature = require('./feature');

module.exports = function (callback) {
  console.log('Seeding the database...')
  // Drop the features collection.
  Feature.remove(function () {
    // Seed the features collection.
    Feature.create(seeds, function () {
      console.log('Seeding complete.')
      callback();
    });
  });
};

Add log messages about database seeding progress


/*
 * Internal module that is used by the default client, http client, and
 * the session fetching apparatus. Made as a separate module to avoid
 * circular dependencies and repeated code.
 */
import axios from 'axios';
import qs from 'qs';

export default function clientFactory(options) {
  const client = axios.create({
    xsrfCookieName: 'kolibri_csrftoken',
    xsrfHeaderName: 'X-CSRFToken',
    paramsSerializer: function(params) {
      // Do custom querystring stingifying to comma separate array params
      return qs.stringify(params, { arrayFormat: 'comma' });
    },
    ...options,
  });
  client.interceptors.response.use(
    response => response,
    function(error) {
      if (!error.response) {
        error.response = {
          status: 0,
        };
      }
      return Promise.reject(error);
    }
  );
  return client;
}

/*
 * Internal module that is used by the default client, http client, and
 * the session fetching apparatus. Made as a separate module to avoid
 * circular dependencies and repeated code.
 */
import axios from 'axios';
import qs from 'qs';

export default function clientFactory(options) {
  const client = axios.create({
    xsrfCookieName: 'kolibri_csrftoken',
    xsrfHeaderName: 'X-CSRFToken',
    paramsSerializer: function(params) {
      // Do custom querystring stingifying to comma separate array params
      return qs.stringify(params, { arrayFormat: 'comma' });
    },
    ...options,
  });
  client.interceptors.response.use(
    response => response,
    function(error) {
      if (!error) {
        error = {};
      }
      if (!error.response) {
        error.response = {
          status: 0,
        };
      }
      return Promise.reject(error);
    }
  );
  return client;
}

Define an error object if it is undefined to prevent errors on failed requests.


import React from 'react'
import sinon from 'sinon'
import { expect } from 'chai'
import { shallow, mount, render } from 'enzyme'
import ColorSchemer from './ColorSchemer.jsx'

describe('ColorSchemer', () => {
  let wrapper
  let props = {
    onChange() {}
  }

  beforeEach(() => {
    wrapper = mount(<ColorSchemer { ...props } />)
  })

  describe('#render', () => {
    it('should render react color picker panel', () => {
      expect(wrapper.find('.react-colorpicker-panel').length).to.equal(1)
    })
  })
})

import React from 'react'
import { expect } from 'chai'
import { mount } from 'enzyme'
import ColorSchemer from './ColorSchemer.jsx'

describe('ColorSchemer', () => {
  let wrapper
  let props = {
    onChange() {}
  }

  beforeEach(() => {
    wrapper = mount(<ColorSchemer { ...props } />)
  })

  describe('#render', () => {
    it('should render react color picker panel', () => {
      expect(wrapper.find('.react-colorpicker-panel').length).to.equal(1)
    })
  })
})

Remove imports where not used.


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

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import settingsPanel from './settings-panel';
import settings from './settings';
import modules from './modules';
import { createErrorStore } from '../../data/create-error-store';
import { CORE_MODULES } from './constants';

const store = Data.combineStores(
	Data.commonStore,
	modules,
	createErrorStore(),
	settingsPanel,
	settings
);

export const initialState = store.initialState;
export const actions = store.actions;
export const controls = store.controls;
export const reducer = store.reducer;
export const resolvers = store.resolvers;
export const selectors = store.selectors;

export const registerStore = ( registry ) => {
	registry.registerStore( CORE_MODULES, store );
};

export default store;

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

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import settingsPanel from './settings-panel';
import settings from './settings';
import modules from './modules';
import sharingSettings from './sharing-settings';
import { createErrorStore } from '../../data/create-error-store';
import { CORE_MODULES } from './constants';

const store = Data.combineStores(
	Data.commonStore,
	modules,
	createErrorStore(),
	settingsPanel,
	settings,
	sharingSettings
);

export const initialState = store.initialState;
export const actions = store.actions;
export const controls = store.controls;
export const reducer = store.reducer;
export const resolvers = store.resolvers;
export const selectors = store.selectors;

export const registerStore = ( registry ) => {
	registry.registerStore( CORE_MODULES, store );
};

export default store;

Add sharingSettings to the modules datastore.


'use strict'

const Logger = use('Logger');

const SynonymService = use('App/Services/SynonymService');

class SynonymController {
  async synonymize({request}) {
    let synonymService = new SynonymService;
    synonymService.setFlags(request.post().flags);
    return await synonymService.synonymize(request.post().text);
  }
}

module.exports = SynonymController

'use strict'

const Logger = use('Logger');

const SynonymService = use('App/Services/SynonymService');
const WordStorageService = use('App/Services/WordStorageService');

class SynonymController {
  async synonymize({request}) {
    // Add new words (if any)
    let text = request.post().text;
    await (new WordStorageService).addNewWords(text);

    // Synonymize
    let synonymService = new SynonymService;

    synonymService.setFlags(JSON.parse(request.post().flags));
    return await synonymService.synonymize(request.post().text);
  }
}

module.exports = SynonymController

Add word addition on synonymize call


migrate(function () {

  // TODO Grep language from existing scenarios

  db.scenarii.createIndex(
    {
      "info.name": "text",
      "comment": "text",
      "steps.info.name": "text",
      "steps.info.arguments.value": "text",
      "background.steps.info.name": "text",
      "background.steps.info.arguments.value": "text",
    },
    {
      name: "searchIndex",
      background: true,
      language_override: "language",
      weights: {
        "info.name": 10,
        "comment": 5,
        "steps.info.name": 5,
        "steps.info.arguments.value": 4,
        "background.steps.info.name": 3,
        "background.steps.info.arguments.value": 2,
      }
    }
  );

});

migrate(function () {

  // Update feature languages

  var defaultLanguage = 'fr'; // Use french as default language on existing docs

  db.features.update({ language: { $exists: false } }, { $set: { language: defaultLanguage } }, { multi: true });
  db.scenarii.update({ language: { $exists: false } }, { $set: { language: defaultLanguage } }, { multi: true });

  // Create search index

  db.scenarii.createIndex(
    {
      "info.name": "text",
      "comment": "text",
      "steps.info.name": "text",
      "steps.info.arguments.value": "text",
      "background.steps.info.name": "text",
      "background.steps.info.arguments.value": "text",
    },
    {
      name: "searchIndex",
      background: true,
      language_override: "language",
      weights: {
        "info.name": 10,
        "comment": 5,
        "steps.info.name": 5,
        "steps.info.arguments.value": 4,
        "background.steps.info.name": 3,
        "background.steps.info.arguments.value": 2,
      }
    }
  );

});

Add french as default language


'use strict';

/*!
 * angular-bootstrap-grid-list-toggle
 * https://github.com/seblucas/angular-bootstrap-grid-list-toggle
 * Copyright (c) 2015 ; Licensed MIT
 */
angular.module('seblucas.slGridListToggle', [])
  .directive('slGridListToggle', function() {
    return {
      restrict: 'E',
      require: '^ngModel',
      scope: {
        templatePrefix: '='
      },
      template:
'
 \
  <button ng-repeat="item in toggles" type="button" class="btn btn-default" ng-class="{active: isTemplateActive(item)}" ng-click="toggleTemplate(item)"> \
     \
  </button> \

',
      link: function(scope, element, attrs, ngModel) {
        scope.toggles = ['th', 'list'];
        ngModel.$render = function() {
            scope.currentTemplate = ngModel.$modelValue;
        };

        scope.toggleTemplate = function(value) {
          scope.currentTemplate = scope.templatePrefix + value +'.html';
          ngModel.$setViewValue(scope.currentTemplate);
        };

        scope.isTemplateActive = function(value) {
          return scope.currentTemplate.indexOf ('.' + value + '.html') > 0;
        };
      }
    };
  });

'use strict';

/*!
 * angular-bootstrap-grid-list-toggle
 * https://github.com/seblucas/angular-bootstrap-grid-list-toggle
 * Copyright (c) 2015 ; Licensed MIT
 */
angular.module('seblucas.slGridListToggle', [])
  .directive('slGridListToggle', function() {
    return {
      restrict: 'E',
      require: '^ngModel',
      scope: {
        templatePrefix: '=',
        defaultTemplate: '='
      },
      template:
'
 \
  <button ng-repeat="item in toggles" type="button" class="btn btn-default" ng-class="{active: isTemplateActive(item)}" ng-click="toggleTemplate(item)"> \
     \
  </button> \

',
      link: function(scope, element, attrs, ngModel) {
        scope.toggles = ['th', 'list'];
        ngModel.$render = function() {
            scope.currentTemplate = ngModel.$modelValue;
        };

        scope.toggleTemplate = function(value) {
          scope.currentTemplate = scope.templatePrefix + value +'.html';
          ngModel.$setViewValue(scope.currentTemplate);
        };

        scope.isTemplateActive = function(value) {
          return scope.currentTemplate.indexOf ('.' + value + '.html') > 0;
        };

        if (attrs.defaultTemplate) {
          scope.toggleTemplate(scope.defaultTemplate);
        }
      }
    };
  });

Add a default template attribute


window.GOVUK = window.GOVUK || {}
window.GOVUK.Modules = window.GOVUK.Modules || {};

(function (Modules) {
  function LocaleSwitcher (module) {
    this.module = module
    this.rightToLeftLocales = module.dataset.rtlLocales.split(' ')
  }

  LocaleSwitcher.prototype.init = function () {
    this.setupLocaleSwitching()
  }

  LocaleSwitcher.prototype.setupLocaleSwitching = function () {
    var form = this.module
    var rightToLeftLocales = this.rightToLeftLocales
    var title = form.querySelector('.attachment-form__title')
    var body = form.querySelector('.attachment-form__body')

    form.querySelector('#attachment_locale').addEventListener('change', function () {
      if (rightToLeftLocales.indexOf(this.value) > -1) {
        title.classList.add('right-to-left')
        body.classList.add('right-to-left')
      } else {
        title.classList.remove('right-to-left')
        body.classList.remove('right-to-left')
      }
    })
  }

  Modules.LocaleSwitcher = LocaleSwitcher
})(window.GOVUK.Modules)

window.GOVUK = window.GOVUK || {}
window.GOVUK.Modules = window.GOVUK.Modules || {};

(function (Modules) {
  function LocaleSwitcher (module) {
    this.module = module
    this.rightToLeftLocales = module.dataset.rtlLocales.split(' ')
  }

  LocaleSwitcher.prototype.init = function () {
    this.setupLocaleSwitching()
  }

  LocaleSwitcher.prototype.setupLocaleSwitching = function () {
    var form = this.module
    var rightToLeftLocales = this.rightToLeftLocales

    var select = form.querySelector('#attachment_locale')
    if (!select) {
      return
    }

    var title = form.querySelector('.attachment-form__title')
    var body = form.querySelector('.attachment-form__body')

    select.addEventListener('change', function () {
      if (rightToLeftLocales.indexOf(this.value) > -1) {
        title.classList.add('right-to-left')
        body.classList.add('right-to-left')
      } else {
        title.classList.remove('right-to-left')
        body.classList.remove('right-to-left')
      }
    })
  }

  Modules.LocaleSwitcher = LocaleSwitcher
})(window.GOVUK.Modules)

Abort locale switcher set up if the local switcher is not present


import { TASK } from '@redux-saga/symbols'

export const cloneableGenerator = generatorFunc => (...args) => {
  const history = []
  const gen = generatorFunc(...args)
  return {
    next: arg => {
      history.push(arg)
      return gen.next(arg)
    },
    clone: () => {
      const clonedGen = cloneableGenerator(generatorFunc)(...args)
      history.forEach(arg => clonedGen.next(arg))
      return clonedGen
    },
    return: value => gen.return(value),
    throw: exception => gen.throw(exception),
  }
}

export function createMockTask() {
  let _isRunning = true
  let _result
  let _error

  return {
    [TASK]: true,
    isRunning: () => _isRunning,
    result: () => _result,
    error: () => _error,

    setRunning: b => (_isRunning = b),
    setResult: r => (_result = r),
    setError: e => (_error = e),
  }
}

import { TASK } from '@redux-saga/symbols'

export const cloneableGenerator = generatorFunc => (...args) => {
  const history = []
  const gen = generatorFunc(...args)
  return {
    next: arg => {
      history.push(arg)
      return gen.next(arg)
    },
    clone: () => {
      const clonedGen = cloneableGenerator(generatorFunc)(...args)
      history.forEach(arg => clonedGen.next(arg))
      return clonedGen
    },
    return: value => gen.return(value),
    throw: exception => gen.throw(exception),
  }
}

export function createMockTask() {
  let _isRunning = true
  let _result
  let _error

  return {
    [TASK]: true,
    isRunning: () => _isRunning,
    result: () => _result,
    error: () => _error,
    cancel: () => {},

    setRunning: b => (_isRunning = b),
    setResult: r => (_result = r),
    setError: e => (_error = e),
  }
}

Add cancel function to the object returned by createMockTask


'use strict';

/**
 * @ngdoc function
 * @name docker-registry-frontend.controller:DeleteRepositoryController
 * @description
 * # DeleteRepositoryController
 * Controller of the docker-registry-frontend
 */
angular.module('delete-repository-controller', ['registry-services'])
  .controller('DeleteRepositoryController', ['$scope', '$route', '$modalInstance', '$window', 'Repository', 'items', 'information',
  function($scope, $route, $modalInstance, $window, Repository, items, information){
    $scope.items = items;
    $scope.information = information;

    // Callback that triggers deletion of tags and reloading of page
    $scope.ok = function () {
      angular.forEach($scope.items, function(value, key) {
        var repoStr = value;
        var repoUser = value.split("/")[0];
        var repoName = value.split("/")[1];

        var repo = {
          repoUser: repoUser,
          repoName: repoName
        };

        var done = function() {
          // Go to the repositories page
          $window.location.href = '#/repositories';
          $route.reload();
        }

        Repository.delete(repo,
          // success
          function(value, responseHeaders) {
            toastr.success('Deleted repository: ' + repoStr);
            done();
          },
          // error
          function(httpResponse) {
            toastr.error('Failed to delete repository: ' + repoStr + ' Response: ' + httpResponse.statusText);
            done();
          }
        );
      });

      $modalInstance.close();

    };

    $scope.cancel = function () {
      $modalInstance.dismiss('cancel');
    };

  }]);

'use strict';

/**
 * @ngdoc function
 * @name docker-registry-frontend.controller:DeleteRepositoryController
 * @description
 * # DeleteRepositoryController
 * Controller of the docker-registry-frontend
 */
angular.module('delete-repository-controller', ['registry-services'])
  .controller('DeleteRepositoryController', ['$scope', '$route', '$modalInstance', '$window', 'Repository', 'items', 'information',
  function($scope, $route, $modalInstance, $window, Repository, items, information){
    $scope.items = items;
    $scope.information = information;

    // Callback that triggers deletion of tags and reloading of page
    $scope.ok = function () {
      angular.forEach($scope.items, function(value, key) {
        var repoStr = value;
        var repoUser = value.split("/")[0];
        var repoName = value.split("/")[1];

        var repo = {
          repoUser: repoUser,
          repoName: repoName
        };

        Repository.delete(repo,
          // success
          function(value, responseHeaders) {
            toastr.success('Deleted repository: ' + repoStr);
          },
          // error
          function(httpResponse) {
            toastr.error('Failed to delete repository: ' + repoStr + ' Response: ' + httpResponse.statusText);
          }
        );
      });

      $modalInstance.close();

      // Go to the repositories page
      $window.location.href = '#/repositories';
      $route.reload();
    };

    $scope.cancel = function () {
      $modalInstance.dismiss('cancel');
    };

  }]);

Revert "fixed deleting repositories doenst always work"


// window on load
window.onload = function () {

    // get run code button
    document.getElementById("run-code").addEventListener("click", function () {
        // evaluate the code to run
        eval(document.getElementById("js-code-to-run").innerText);
    });
};

// window on load
window.onload = function () {

    // get run code button
    document.getElementById("run-code").addEventListener("click", function () {
        // evaluate the code to run
        eval(document.getElementById("js-code-to-run").innerHTML);
    });
};

Use innerHTML to be firefox compatible


define([], function () {
  'use strict';

  var GameRenderer = function (canvas, entities) {
    this.canvas = canvas;
    this.entities = entities;
    this.gc = this.canvas.getContext('2d');
  };

  GameRenderer.prototype.paint = function paint() {
    var len = this.entities.length;
    var i;
    var entity;

    this.gc.fillStyle = '#002b36';
    this.gc.fillRect(0, 0, this.canvas.width, this.canvas.height);
    for (i = 0; i < len; i++) {
      entity = this.entities[i];
      this.gc.save();
      this.gc.translate(Math.round(entity.position.x), Math.round(entity.position.y));
      entity.componentGraphic.paint(this.gc);
      this.gc.restore();
    }
  };

  return GameRenderer;

});

define([], function () {
  'use strict';

  var GameRenderer = function (canvas, entities) {
    this.canvas = canvas;
    this.entities = entities;
    this.gc = this.canvas.getContext('2d');
  };

  GameRenderer.prototype.paint = function paint() {
    this.gc.fillStyle = '#002b36';
    this.gc.fillRect(0, 0, this.canvas.width, this.canvas.height);
    const entities = this.entities.sort(function depthSort(entityA, entityB) {
      return entityA.position.y - entityB.position.y;
    })
    const len =  entities.length;
    for (let i = 0; i < len; i++) {
      let entity = entities[i];
      this.gc.save();
      this.gc.translate(Math.round(entity.position.x), Math.round(entity.position.y));
      entity.componentGraphic.paint(this.gc);
      this.gc.restore();
    }
  };

  return GameRenderer;

});

Sort entities by depth before display


function(doc){
    if((doc.doc_type == 'Application' || doc.doc_type == 'RemoteApp') && doc.copy_of != null) {
        emit([doc.domain, doc.copy_of, doc.version], {
            doc_type: doc.doc_type,
            short_url: doc.short_url,
            version: doc.version,
            _id: doc._id,
            name: doc.name,
            build_spec: doc.build_spec,
            copy_of: doc.copy_of,
            domain: doc.domain,
            built_on: doc.built_on,
            built_with: doc.built_with,
            build_comment: doc.build_comment,
            is_released: doc.is_released
        });
    }
}

function(doc){
    if((doc.doc_type == 'Application' || doc.doc_type == 'RemoteApp') && doc.copy_of != null) {
        emit([doc.domain, doc.copy_of, doc.version], {
            doc_type: doc.doc_type,
            short_url: doc.short_url,
            version: doc.version,
            _id: doc._id,
            name: doc.name,
            build_spec: doc.build_spec,
            copy_of: doc.copy_of,
            domain: doc.domain,
            built_on: doc.built_on,
            built_with: doc.built_with,
            build_comment: doc.build_comment,
            is_released: doc.is_released,
            case_sharing: doc.case_sharing
        });
    }
}

Add case_sharing to saved_app view so that the case sharing warning will work


import client from 'part:@sanity/base/client'

export default {
  getProviders: () => client.request({
    uri: '/auth/providers',
    withCredentials: true
  }).then(res => res.providers),

  getCurrentUser: () => client.request({
    uri: '/users/me',
    withCredentials: true
  }).then(user => {
    return user && user.id ? user : null
  }),

  logout: () => client.request({
    uri: '/auth/logout',
    withCredentials: true
  })
}

import client from 'part:@sanity/base/client'

export default {
  getProviders: () => client.request({
    uri: '/auth/providers',
    withCredentials: true
  }).then(res => res.providers),

  getCurrentUser: () => client.request({
    uri: '/users/me',
    withCredentials: true
  }).then(user => {
    return user && user.id ? user : null
  }),

  logout: () => client.auth.logout()
}

Fix bug where logout would use incorrect URL