<commit_before>
/**
 * Module dependencies.
 */

var util = require('./util')
  , debug = require('debug')('monk:queries')
  , EventEmitter = require('events').EventEmitter
  , Promise = require('./promise')

/**
 * Module exports
 */

module.exports = Collection;

/**
 * Collection.
 *
 * @api public
 */

function Collection (manager, name) {
  this.manager = manager;
  this.driver = manager.driver;
  this.name = name;
  this.col = this.driver.collection(name);
  this.options = {};
}

/**
 * Inherits from EventEmitter.
 */

Collection.prototype.__proto__ = EventEmitter.prototype;

/**
 * Casts to objectid
 *
 * @param {Mixed} hex id or ObjectId
 * @return {ObjectId}
 * @api public
 */

Collection.prototype.id = function (str) {
  return 'string' == typeof str ? this.col.id(str) : str;
};

/**
 * Opts utility.
 */

Collection.prototype.opts = function (opts) {
  opts = opts || {};
  for (var i in this.manager.options) {
    if (!(i in opts) && !(i in this.options)) {
      opts[i] = this.manager.options[i];
    }
  }
  for (var i in this.options) {
    if (!(i in opts)) {
      opts[i] = this.options[i];
    }
  }
  return util.options(opts);
};

/**
 * Set up indexes.
 *
 * @param {Object|String|Array} fields
 * @param {Object|Function} optional, options or callback
 * @param {Function} optional, callback
 * @return {Promise}
 * @api public
 */

Collection.prototype.index =
Collection.prototype.ensureIndex = function (fields, opts, fn) {
  if ('function' == typeof opts) {
    fn = opts;
    opts = {};
  }

  var fields = util.fields(fields)
    , opts = opts || {}
    , promise = new Promise(this, 'index')

  if (fn) {
    promise.complete(fn);
  }

  // query
  debug('%s ensureIndex "%j" ("%j")', this.name, fields, opts);
  this.col.ensureIndex(fields, opts, promise.fulfill);

  return promise;
};

/**
 * Gets all indexes.
 *
 * @param {Function} callback
 * @return {Promise}
 * @api public
 */

Collection.prototype.indexes = function (fn) {
  var promise = new Promise(this, 'indexes');

  if (fn) {
    promise.complete(fn);
  }

  // query
  debug('%s indexInformation', this.name);
  this.col.indexInformation(promise.fulfill);

  return promise;
};

/**
 * update
 *
 * @param {Object} search query
 * @param {Object} update obj
 * @param {Object|String|Array} optional, options or fields
 * @param {Function} callback
 * @return {Promise}
 * @api public
 */

Collection.prototype.update = function (search, update, opts, fn) {
  if ('function' == typeof opts) {
    fn = opts;
    opts = {};
  }

  var promise = new Promise(this, 'update');

  if (fn) {
    promise.on('complete', fn);
  }

  // cast
  search = this.cast(search);
  update = this.cast(update);

  // query
  debug('%s update %j with %j', this.name, search, update);
  opts = this.opts(opts);

  var callback = opts.safe ? promise.fulfill : function () {
    // node-mongodb-native will send err=undefined and call the fn
    // in the same tick if safe: false
    var args = arguments;
    args[0] = args[0] || null;
    process.nextTick(function () {
      promise.fulfill.apply(promise, args);
    });
  };

  this.col.update(search, update, opts, callback);

  return promise;
};

/**
 * update by id helper
 *
 * @param {String|Object} object id
 * @param {Object} update obj
 * @param {Object|String|Array} optional, options or fields
 * @param {Function} callback
 * @return {Promise}
 * @api public
 */

Collection.prototype.updateById = function (id, obj, opts, fn) {
  return this.update({ _id: id }, obj, opts, fn);
};

/**
 * remove
 *
 * @param {Object} search query
 * @param {Object|Function} optional, options or callback
 * @param {Function} optional, callback
 * @return {Promise}
 */

Collection.prototype.remove = function (search, opts, fn) {
  if ('function' == typeof opts) {
    fn = opts;
    opts = {};
  }

  var promise = new Promise(this, 'remove');

  if (fn) {
    promise.on('complete', fn);
  }

  // cast
  search = this.cast(search);

  // query
  debug('%s remove "%j" with "%j"', this.name, search, update);
  this.col.update(search, this.opts(opts), promise.fulfill);

  return promise;
};

/**
 * findAndModify
 *
 * @param {Object} search query, or { query, update } object
 * @param {Object} optional, update object
 * @param {Object|String|Array} optional, options or fields
 * @param {Function} callback
 * @return {Promise}
 * @api public
 */

Collection.prototype.findAndModify = function (query, update, opts, fn) {
  var promise = new Promise(this, 'findAndModify');

  query = query || {};

  if ('object' != typeof query.query && 'object' != typeof query.update) {
    query = {
        query: query
      , update: update
    };
  } else {
    opts = update;
    fn = opts;
  }

  if ('function' == typeof opts) {
    fn = opts;
    opts = {};
  }

  opts = opts || {};

  if (fn) {
    promise.on('complete', fn);
  }

  // cast
  query.query = this.cast(query.query);
  query.update = this.cast(query.update);

  // query
  debug('%s findAndModify "%j" with "%j"', this.name, query.query, query.update);
  this.col.findAndModify(
      query.query
    , []
    , query.update
    , this.opts(opts)
    , promise.fulfill
  );

  return promise;
};

/**
 * insert
 *
 * @param {Object} data
 * @param {Object|String|Array} optional, options or fields
 * @param {Function} callback
 * @return {Promise}
 * @api public
 */

Collection.prototype.insert = function (data, opts, fn) {
  if ('function' == typeof opts) {
    fn = opts;
    opts = {};
  }

  var promise = new Promise(this, 'insert');

  if (fn) {
    promise.complete(fn);
  }

  // cast
  data = this.cast(data);

  // query
  debug('%s insert "%j"', this.name, data);
  this.col.insert(data, this.opts(opts), function (err, docs) {
    process.nextTick(function () {
      promise.fulfill.call(promise, err, docs ? docs[0] : docs);
    });
  });

  return promise;
};

/**
 * findOne by ID
 *
 * @param {String} hex id
 * @param {Object|String|Array} optional, options or fields
 * @param {Function} completion callback
 * @return {Promise}
 * @api public
 */

Collection.prototype.findById = function (id, opts, fn) {
  return this.findOne({ _id: id }, opts, fn);
};

/**
 * find
 *
 * @param {Object} query
 * @param {Object|String|Array} optional, options or fields
 * @param {Function} completion callback
 * @return {Promise}
 * @api public
 */

Collection.prototype.find = function (query, opts, fn) {
  if ('function' == typeof opts) {
    fn = opts;
    opts = {};
  }

  var promise = new Promise(this, 'find')

  if (fn) {
    promise.complete(fn);
  }

  // cast
  query = this.cast(query);

  // query
  debug('%s find "%j"', this.name, query);
  var cursor = this.col.find(query, this.opts(opts));

  if (null == opts.stream) {
    process.nextTick(function () {
      if (promise.listeners('each').length) {
        stream();
      } else {
        cursor.toArray(promise.fulfill);
      }
    });
  } else if (opts.stream) {
    stream();
  } else {
    cursor.toArray(promise.fulfill);
  }

  function stream () {
    cursor.each(function (err, doc) {
      if (err) {
        promise.emit('error', err);
      } else if (doc) {
        promise.emit('each', doc);
      } else {
        promise.emit('success');
      }
    });
  }

  return promise;
};

/**
 * findOne
 *
 * @param {String|ObjectId|Object} query
 * @param {Object} options
 * @param {Function} completion callback
 * @return {Promise}
 * @api public
 */

Collection.prototype.findOne = function (search, opts, fn) {
  search = search || {};

  if ('string' == typeof search || 'function' == typeof search.toHexString) {
    return this.findById(search, opts, fn);
  }

  if ('function' == typeof opts) {
    fn = opts;
    opts = {};
  }

  var promise = new Promise(this, 'findOne');

  if (fn) {
    promise.complete(fn);
  }

  // cast
  search = this.cast(search);

  // query
  debug('findOne "%j"', search);
  this.col.findOne(search, this.opts(opts), promise.fulfill);

  return promise;
};

/**
 * Applies ObjectId casting to _id fields.
 *
 * @param {Object} optional, query
 * @return {Object} query
 * @api private
 */

Collection.prototype.cast = function (obj) {
  obj = obj || {};

  if (obj._id) {
    obj._id = this.id(obj._id);
  }

  if (obj.$set && obj.$set._id) {
    obj.$set._id = this.id(obj.$set._id);
  }

  return obj;
};

/**
 * Drops the collection.
 *
 * @param {Function} optional, callback
 * @return {Promise}
 * @api public
 */

Collection.prototype.drop = function (fn) {
  var promise = new Promise(this, 'drop');

  if (fn) {
    promise.complete(fn);
  }

  this.col.drop(promise.fulfill);

  return promise;
};
<commit_msg>Enhanced findAndModify default behavior for upserts.<commit_after>
/**
 * Module dependencies.
 */

var util = require('./util')
  , debug = require('debug')('monk:queries')
  , EventEmitter = require('events').EventEmitter
  , Promise = require('./promise')

/**
 * Module exports
 */

module.exports = Collection;

/**
 * Collection.
 *
 * @api public
 */

function Collection (manager, name) {
  this.manager = manager;
  this.driver = manager.driver;
  this.name = name;
  this.col = this.driver.collection(name);
  this.options = {};
}

/**
 * Inherits from EventEmitter.
 */

Collection.prototype.__proto__ = EventEmitter.prototype;

/**
 * Casts to objectid
 *
 * @param {Mixed} hex id or ObjectId
 * @return {ObjectId}
 * @api public
 */

Collection.prototype.id = function (str) {
  return 'string' == typeof str ? this.col.id(str) : str;
};

/**
 * Opts utility.
 */

Collection.prototype.opts = function (opts) {
  opts = opts || {};
  for (var i in this.manager.options) {
    if (!(i in opts) && !(i in this.options)) {
      opts[i] = this.manager.options[i];
    }
  }
  for (var i in this.options) {
    if (!(i in opts)) {
      opts[i] = this.options[i];
    }
  }
  return util.options(opts);
};

/**
 * Set up indexes.
 *
 * @param {Object|String|Array} fields
 * @param {Object|Function} optional, options or callback
 * @param {Function} optional, callback
 * @return {Promise}
 * @api public
 */

Collection.prototype.index =
Collection.prototype.ensureIndex = function (fields, opts, fn) {
  if ('function' == typeof opts) {
    fn = opts;
    opts = {};
  }

  var fields = util.fields(fields)
    , opts = opts || {}
    , promise = new Promise(this, 'index')

  if (fn) {
    promise.complete(fn);
  }

  // query
  debug('%s ensureIndex "%j" ("%j")', this.name, fields, opts);
  this.col.ensureIndex(fields, opts, promise.fulfill);

  return promise;
};

/**
 * Gets all indexes.
 *
 * @param {Function} callback
 * @return {Promise}
 * @api public
 */

Collection.prototype.indexes = function (fn) {
  var promise = new Promise(this, 'indexes');

  if (fn) {
    promise.complete(fn);
  }

  // query
  debug('%s indexInformation', this.name);
  this.col.indexInformation(promise.fulfill);

  return promise;
};

/**
 * update
 *
 * @param {Object} search query
 * @param {Object} update obj
 * @param {Object|String|Array} optional, options or fields
 * @param {Function} callback
 * @return {Promise}
 * @api public
 */

Collection.prototype.update = function (search, update, opts, fn) {
  if ('function' == typeof opts) {
    fn = opts;
    opts = {};
  }

  var promise = new Promise(this, 'update');

  if (fn) {
    promise.on('complete', fn);
  }

  // cast
  search = this.cast(search);
  update = this.cast(update);

  // query
  debug('%s update %j with %j', this.name, search, update);
  opts = this.opts(opts);

  var callback = opts.safe ? promise.fulfill : function () {
    // node-mongodb-native will send err=undefined and call the fn
    // in the same tick if safe: false
    var args = arguments;
    args[0] = args[0] || null;
    process.nextTick(function () {
      promise.fulfill.apply(promise, args);
    });
  };

  this.col.update(search, update, opts, callback);

  return promise;
};

/**
 * update by id helper
 *
 * @param {String|Object} object id
 * @param {Object} update obj
 * @param {Object|String|Array} optional, options or fields
 * @param {Function} callback
 * @return {Promise}
 * @api public
 */

Collection.prototype.updateById = function (id, obj, opts, fn) {
  return this.update({ _id: id }, obj, opts, fn);
};

/**
 * remove
 *
 * @param {Object} search query
 * @param {Object|Function} optional, options or callback
 * @param {Function} optional, callback
 * @return {Promise}
 */

Collection.prototype.remove = function (search, opts, fn) {
  if ('function' == typeof opts) {
    fn = opts;
    opts = {};
  }

  var promise = new Promise(this, 'remove');

  if (fn) {
    promise.on('complete', fn);
  }

  // cast
  search = this.cast(search);

  // query
  debug('%s remove "%j" with "%j"', this.name, search, update);
  this.col.update(search, this.opts(opts), promise.fulfill);

  return promise;
};

/**
 * findAndModify
 *
 * @param {Object} search query, or { query, update } object
 * @param {Object} optional, update object
 * @param {Object|String|Array} optional, options or fields
 * @param {Function} callback
 * @return {Promise}
 * @api public
 */

Collection.prototype.findAndModify = function (query, update, opts, fn) {
  var promise = new Promise(this, 'findAndModify');

  query = query || {};

  if ('object' != typeof query.query && 'object' != typeof query.update) {
    query = {
        query: query
      , update: update
    };
  } else {
    opts = update;
    fn = opts;
  }

  if ('function' == typeof opts) {
    fn = opts;
    opts = {};
  }

  opts = opts || {};

  // `new` defaults to `true` for upserts
  if (null == opts.new && opts.upsert) {
    opts.new = true;
  }

  if (fn) {
    promise.on('complete', fn);
  }

  // cast
  query.query = this.cast(query.query);
  query.update = this.cast(query.update);

  // query
  debug('%s findAndModify "%j" with "%j"', this.name, query.query, query.update);
  this.col.findAndModify(
      query.query
    , []
    , query.update
    , this.opts(opts)
    , promise.fulfill
  );

  return promise;
};

/**
 * insert
 *
 * @param {Object} data
 * @param {Object|String|Array} optional, options or fields
 * @param {Function} callback
 * @return {Promise}
 * @api public
 */

Collection.prototype.insert = function (data, opts, fn) {
  if ('function' == typeof opts) {
    fn = opts;
    opts = {};
  }

  var promise = new Promise(this, 'insert');

  if (fn) {
    promise.complete(fn);
  }

  // cast
  data = this.cast(data);

  // query
  debug('%s insert "%j"', this.name, data);
  this.col.insert(data, this.opts(opts), function (err, docs) {
    process.nextTick(function () {
      promise.fulfill.call(promise, err, docs ? docs[0] : docs);
    });
  });

  return promise;
};

/**
 * findOne by ID
 *
 * @param {String} hex id
 * @param {Object|String|Array} optional, options or fields
 * @param {Function} completion callback
 * @return {Promise}
 * @api public
 */

Collection.prototype.findById = function (id, opts, fn) {
  return this.findOne({ _id: id }, opts, fn);
};

/**
 * find
 *
 * @param {Object} query
 * @param {Object|String|Array} optional, options or fields
 * @param {Function} completion callback
 * @return {Promise}
 * @api public
 */

Collection.prototype.find = function (query, opts, fn) {
  if ('function' == typeof opts) {
    fn = opts;
    opts = {};
  }

  var promise = new Promise(this, 'find')

  if (fn) {
    promise.complete(fn);
  }

  // cast
  query = this.cast(query);

  // query
  debug('%s find "%j"', this.name, query);
  var cursor = this.col.find(query, this.opts(opts));

  if (null == opts.stream) {
    process.nextTick(function () {
      if (promise.listeners('each').length) {
        stream();
      } else {
        cursor.toArray(promise.fulfill);
      }
    });
  } else if (opts.stream) {
    stream();
  } else {
    cursor.toArray(promise.fulfill);
  }

  function stream () {
    cursor.each(function (err, doc) {
      if (err) {
        promise.emit('error', err);
      } else if (doc) {
        promise.emit('each', doc);
      } else {
        promise.emit('success');
      }
    });
  }

  return promise;
};

/**
 * findOne
 *
 * @param {String|ObjectId|Object} query
 * @param {Object} options
 * @param {Function} completion callback
 * @return {Promise}
 * @api public
 */

Collection.prototype.findOne = function (search, opts, fn) {
  search = search || {};

  if ('string' == typeof search || 'function' == typeof search.toHexString) {
    return this.findById(search, opts, fn);
  }

  if ('function' == typeof opts) {
    fn = opts;
    opts = {};
  }

  var promise = new Promise(this, 'findOne');

  if (fn) {
    promise.complete(fn);
  }

  // cast
  search = this.cast(search);

  // query
  debug('findOne "%j"', search);
  this.col.findOne(search, this.opts(opts), promise.fulfill);

  return promise;
};

/**
 * Applies ObjectId casting to _id fields.
 *
 * @param {Object} optional, query
 * @return {Object} query
 * @api private
 */

Collection.prototype.cast = function (obj) {
  obj = obj || {};

  if (obj._id) {
    obj._id = this.id(obj._id);
  }

  if (obj.$set && obj.$set._id) {
    obj.$set._id = this.id(obj.$set._id);
  }

  return obj;
};

/**
 * Drops the collection.
 *
 * @param {Function} optional, callback
 * @return {Promise}
 * @api public
 */

Collection.prototype.drop = function (fn) {
  var promise = new Promise(this, 'drop');

  if (fn) {
    promise.complete(fn);
  }

  this.col.drop(promise.fulfill);

  return promise;
};
<|endoftext|>


<commit_before>/*global define*/
/*jslint nomen: true*/
define(function (require) {
    'use strict';

    var $ = require('jquery');
    var _ = require('underscore');
    var __ = require('orotranslation/js/translator');
    var mediator = require('oroui/js/mediator');
    var Util = require('oroentity/js/entity-fields-util');
    require('oroentity/js/fields-loader');
    require('jquery.select2');

    /**
     * @export ororeport/js/chart_options
     * @class  ororeport.ChartOptions
     */
    return {

        /**
         * @property {Object}
         */
        options: {
            childTemplate: '[id^=<%= id %>_]',
            optionsTemplate: '<%= field %>(<%= group %>,<%= name %>,<%= type %>)',
            fieldsLoaderSelector: '#oro_report_form_entity',
            fieldsTableIdentifier: 'item-container'
        },

        /**
         * @param {String} id
         * @param {Array} options
         */
        initialize: function (id, options) {
            var self = this;
            this.options = _.extend({}, this.options, options);
            this.items = [];

            mediator.on('items-manager:table:reset:' + self.options.fieldsTableIdentifier, function (collection) {
                self.updateCollection(collection);
                self.validateSelect2(id);
            });

            mediator.once('items-manager:table:reset:' + self.options.fieldsTableIdentifier, function () {
                self.initSelect2(id);
                self.initializeListeners(id);
            });
        },

        initializeListeners: function (id) {
            var self = this;
            mediator.on('items-manager:table:add:' + self.options.fieldsTableIdentifier, function (collection) {
                self.updateCollection(collection);
            });

            mediator.on('items-manager:table:change:' + self.options.fieldsTableIdentifier, function (collection) {
                self.updateCollection(collection);
                self.validateSelect2(id);
            });

            mediator.on('items-manager:table:remove:' + self.options.fieldsTableIdentifier, function (collection) {
                self.updateCollection(collection);
                self.validateSelect2(id);
            });
        },

        updateCollection: function (collection) {
            var entity = $(this.options.fieldsLoaderSelector).fieldsLoader('getEntityName');
            var data = $(this.options.fieldsLoaderSelector).fieldsLoader('getFieldsData');
            this.util = new Util(entity, data);
            this.items = collection.toJSON();
        },

        validateSelect2: function (id) {
            var self = this;
            var childSelector = _.template(this.options.childTemplate, {id: id});
            $('#' + id).find(childSelector).each(function () {
                var value = $(this).val();
                if (value) {
                    var name = _.first(value.split('('));
                    if (!_.findWhere(self.items, {name: name})) {
                        $(this).select2('val', '');
                    }
                }
            });
        },

        initSelect2: function (id) {
            var childSelector = _.template(this.options.childTemplate, {id: id});
            var self = this;
            $('#' + id).find(childSelector).each(function () {
                var exclude = $(this).data('type-filter');
                $(this).select2({
                    collapsibleResults: true,
                    placeholder: __('oro.entity.form.choose_entity_field'),
                    data: function () {
                        return self.data(exclude);
                    },
                    initSelection: function (element, callback) {
                        var value = element.val().split('(');
                        var node = _.last(self.util.pathToEntityChain(value[0]));
                        callback({
                            id: value.join('('),
                            text: node.field.label
                        });
                    }
                });
            });
        },

        data: function (exclude) {
            var data, util, optionsTemplate;
            util = this.util;
            data = {
                more: false,
                results: []
            };

            if (!util) {
                return data;
            }

            optionsTemplate = _.template(this.options.optionsTemplate);

            $.each(this.items, function () {
                var options = this.func;
                var chain = util.pathToEntityChain(this.name).slice(1);
                var field = chain[chain.length - 1].field;
                var items = data.results;
                if (!Util.filterFields([field], exclude).length) {
                    return;
                }
                $.each(chain, function () {
                    var item, id;
                    if (this.entity) {
                        item = _.findWhere(items, {path: this.path});
                        if (!item) {
                            item = {
                                text: this.field.label,
                                path: this.path,
                                children: []
                            };
                            items.push(item);
                        }
                        items = item.children;
                    } else {
                        if (options) {
                            id = optionsTemplate({
                                field: this.path,
                                group: options.name,
                                name: options.group_name,
                                type: options.group_type
                            });
                        } else {
                            id = this.path;
                        }
                        items.push({
                            text: this.field.label,
                            id: id
                        });
                    }
                });
            });

            return data;
        }
    }
});
<commit_msg>BAP-4177: Chart settings for a report do not cleared up when main entity changed BAP-4178: Removed column do not removed from chart settings - add doc blocks<commit_after>/*global define*/
/*jslint nomen: true*/
define(function (require) {
    'use strict';

    var $ = require('jquery');
    var _ = require('underscore');
    var __ = require('orotranslation/js/translator');
    var mediator = require('oroui/js/mediator');
    var Util = require('oroentity/js/entity-fields-util');
    require('oroentity/js/fields-loader');
    require('jquery.select2');

    /**
     * @export ororeport/js/chart_options
     * @class  ororeport.ChartOptions
     */
    return {

        /**
         * @property {Object}
         */
        options: {
            childTemplate: '[id^=<%= id %>_]',
            optionsTemplate: '<%= field %>(<%= group %>,<%= name %>,<%= type %>)',
            fieldsLoaderSelector: '#oro_report_form_entity',
            fieldsTableIdentifier: 'item-container'
        },

        /**
         * @param {String} id
         * @param {Array} options
         */
        initialize: function (id, options) {
            var self = this;
            this.options = _.extend({}, this.options, options);
            this.items = [];

            mediator.on('items-manager:table:reset:' + self.options.fieldsTableIdentifier, function (collection) {
                self.updateCollection(collection);
                self.validateSelect2(id);
            });

            mediator.once('items-manager:table:reset:' + self.options.fieldsTableIdentifier, function () {
                self.initSelect2(id);
                self.initializeListeners(id);
            });
        },

        /**
         * @param {String} id
         */
        initializeListeners: function (id) {
            var self = this;
            mediator.on('items-manager:table:add:' + self.options.fieldsTableIdentifier, function (collection) {
                self.updateCollection(collection);
            });

            mediator.on('items-manager:table:change:' + self.options.fieldsTableIdentifier, function (collection) {
                self.updateCollection(collection);
                self.validateSelect2(id);
            });

            mediator.on('items-manager:table:remove:' + self.options.fieldsTableIdentifier, function (collection) {
                self.updateCollection(collection);
                self.validateSelect2(id);
            });
        },

        /**
         * @param {Array} collection
         */
        updateCollection: function (collection) {
            var entity = $(this.options.fieldsLoaderSelector).fieldsLoader('getEntityName');
            var data = $(this.options.fieldsLoaderSelector).fieldsLoader('getFieldsData');
            this.util = new Util(entity, data);
            this.items = collection.toJSON();
        },

        /**
         * @param {String} id
         */
        validateSelect2: function (id) {
            var self = this;
            var childSelector = _.template(this.options.childTemplate, {id: id});
            $('#' + id).find(childSelector).each(function () {
                var value = $(this).val();
                if (value) {
                    var name = _.first(value.split('('));
                    if (!_.findWhere(self.items, {name: name})) {
                        $(this).select2('val', '');
                    }
                }
            });
        },

        /**
         * @param {String} id
         */
        initSelect2: function (id) {
            var childSelector = _.template(this.options.childTemplate, {id: id});
            var self = this;
            $('#' + id).find(childSelector).each(function () {
                var exclude = $(this).data('type-filter');
                $(this).select2({
                    collapsibleResults: true,
                    placeholder: __('oro.entity.form.choose_entity_field'),
                    data: function () {
                        return self.data(exclude);
                    },
                    initSelection: function (element, callback) {
                        var value = element.val().split('(');
                        var node = _.last(self.util.pathToEntityChain(value[0]));
                        callback({
                            id: value.join('('),
                            text: node.field.label
                        });
                    }
                });
            });
        },

        /**
         * @param {Array} exclude
         */
        data: function (exclude) {
            var data, util, optionsTemplate;
            util = this.util;
            data = {
                more: false,
                results: []
            };

            if (!util) {
                return data;
            }

            optionsTemplate = _.template(this.options.optionsTemplate);

            $.each(this.items, function () {
                var options = this.func;
                var chain = util.pathToEntityChain(this.name).slice(1);
                var field = chain[chain.length - 1].field;
                var items = data.results;
                if (!Util.filterFields([field], exclude).length) {
                    return;
                }
                $.each(chain, function () {
                    var item, id;
                    if (this.entity) {
                        item = _.findWhere(items, {path: this.path});
                        if (!item) {
                            item = {
                                text: this.field.label,
                                path: this.path,
                                children: []
                            };
                            items.push(item);
                        }
                        items = item.children;
                    } else {
                        if (options) {
                            id = optionsTemplate({
                                field: this.path,
                                group: options.name,
                                name: options.group_name,
                                type: options.group_type
                            });
                        } else {
                            id = this.path;
                        }
                        items.push({
                            text: this.field.label,
                            id: id
                        });
                    }
                });
            });

            return data;
        }
    }
});
<|endoftext|>


<commit_before>import _ from 'lodash';
import React from 'react';
import Settings from '@folio/stripes-components/lib/Settings';

import PermissionSets from './permissions/PermissionSets';
import PatronGroupsSettings from './PatronGroupsSettings';

const pages = [
  {
    route: 'perms',
    label: 'Permission sets',
    component: PermissionSets,
    perm: 'perms.permissions.get',
  },
  {
    route: 'groups',
    label: 'Patron groups',
    component: PatronGroupsSettings,
    // No perm needed yet
  },
];

export default props => <Settings {...props} pages={_.sortBy(pages, ['label'])} />;
<commit_msg>Specify module name in settings second column. Part of STRPCOMP-1<commit_after>import _ from 'lodash';
import React from 'react';
import Settings from '@folio/stripes-components/lib/Settings';

import PermissionSets from './permissions/PermissionSets';
import PatronGroupsSettings from './PatronGroupsSettings';

const pages = [
  {
    route: 'perms',
    label: 'Permission sets',
    component: PermissionSets,
    perm: 'perms.permissions.get',
  },
  {
    route: 'groups',
    label: 'Patron groups',
    component: PatronGroupsSettings,
    // No perm needed yet
  },
];

export default props => <Settings {...props} pages={_.sortBy(pages, ['label'])} paneTitle="Users" />;
<|endoftext|>


<commit_before>import ITEMS from 'common/ITEMS';
import SPELLS from 'common/SPELLS';
import CoreAbilities from 'Parser/Core/Modules/Abilities';
import Enemies from 'Parser/Core/Modules/Enemies';

import Ability from './Ability';

// The amount of time after a proc has occurred when casting a filler is no longer acceptable
const REACTION_TIME_THRESHOLD = 500;

const hastedCooldown = (baseCD, haste) => (baseCD / (1 + haste));

class Abilities extends CoreAbilities {
  static ABILITY_CLASS = Ability;
  static dependencies = {
    ...CoreAbilities.dependencies,
    enemies: Enemies,
  };

  spellbook() {
    const combatant = this.selectedCombatant;
    return [
      // Rotational Spells
      {
        spell: SPELLS.MANGLE_BEAR,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: (haste, selectedCombatant) => {
          if (selectedCombatant.hasBuff(SPELLS.INCARNATION_GUARDIAN_OF_URSOC_TALENT.id)) {
            return hastedCooldown(1.5, haste);
          }
          return hastedCooldown(6, haste);
        },
        gcd: {
          base: 1500,
        },
        antiFillerSpam: {
          isHighPriority: true,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.7,
          averageIssueEfficiency: 0.6,
          majorIssueEfficiency: 0.5,
        },
        timelineSortIndex: 1,
      },
      {
        spell: SPELLS.THRASH_BEAR,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: (haste, selectedCombatant) => {
          if (selectedCombatant.hasBuff(SPELLS.INCARNATION_GUARDIAN_OF_URSOC_TALENT.id)) {
            return hastedCooldown(1.5, haste);
          }
          return hastedCooldown(6, haste);
        },
        gcd: {
          base: 1500,
        },
        antiFillerSpam: {
          isHighPriority: true,
        },
        castEfficiency: {
          suggestion: true,
        },
        timelineSortIndex: 2,
      },
      {
        spell: SPELLS.MOONFIRE,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        gcd: {
          base: 1500,
        },
        antiFillerSpam: {
          isFiller: (event, selectedCombatant, targets) => {
            if (combatant.hasTalent(SPELLS.GALACTIC_GUARDIAN_TALENT.id) && selectedCombatant.hasBuff(SPELLS.GALACTIC_GUARDIAN.id)) {
              return false;
            }
            // Check if moonfire is present on the current target
            // Note that if the current target has no enemy data we can't track whether the dot
            // is ticking or not, in that case we consider it non-filler as a concession.
            if (!this.enemies.getEntity(event) || !this.enemies.getEntity(event).hasBuff(SPELLS.MOONFIRE_BEAR.id, event.timestamp)) {
              return false;
            }
            return true;
          },
          isHighPriority: ({ timestamp }, selectedCombatant) => {
            // Account for reaction time; the player must have had the proc for at least this long
            return selectedCombatant.hasBuff(SPELLS.GALACTIC_GUARDIAN.id, timestamp - REACTION_TIME_THRESHOLD);
          },
        },
        timelineSortIndex: 3,
      },
      {
        spell: SPELLS.SWIPE_BEAR,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        gcd: {
          base: 1500,
        },
        antiFillerSpam: {
          isFiller: (event, selectedCombatant, targets) => targets.length < 4,
        },
        timelineSortIndex: 4,
      },
      {
        spell: SPELLS.MAUL,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        gcd: {
          base: 1500,
        },
        timelineSortIndex: 5,
      },
      // Cooldowns
      {
        spell: SPELLS.BARKSKIN,
        buffSpellId: SPELLS.BARKSKIN.id,
        category: Abilities.SPELL_CATEGORIES.DEFENSIVE,
        cooldown: haste => combatant.hasTalent(SPELLS.SURVIVAL_OF_THE_FITTEST_TALENT.id) ? 90 - (90 / 3) : 90,
        timelineSortIndex: 9,
      },
      {
        spell: SPELLS.SURVIVAL_INSTINCTS,
        buffSpellId: SPELLS.SURVIVAL_INSTINCTS.id,
        category: Abilities.SPELL_CATEGORIES.DEFENSIVE,
        cooldown: (haste, selectedCombatant) => {
          const baseCd = combatant.hasTalent(SPELLS.SURVIVAL_OF_THE_FITTEST_TALENT.id) ? 240 - (240 / 3) : 240;
          return combatant.hasFinger(ITEMS.DUAL_DETERMINATION.id) ? baseCd - (baseCd * 0.85) : baseCd;
        },
        charges: combatant.hasFinger(ITEMS.DUAL_DETERMINATION.id) ? 3 : 2,
        timelineSortIndex: 9,
      },
      {
        spell: SPELLS.INCARNATION_GUARDIAN_OF_URSOC_TALENT,
        category: Abilities.SPELL_CATEGORIES.SEMI_DEFENSIVE,
        gcd: {
          base: 1500,
        },
        cooldown: 180,
        enabled: combatant.hasTalent(SPELLS.INCARNATION_GUARDIAN_OF_URSOC_TALENT.id),
        timelineSortIndex: 9,
      },
      {
        spell: SPELLS.BRISTLING_FUR_TALENT,
        buffSpellId: SPELLS.BRISTLING_FUR_TALENT.id,
        isDefensive: true,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        gcd: {
          base: 1500,
        },
        cooldown: 40,
        enabled: combatant.hasTalent(SPELLS.BRISTLING_FUR_TALENT.id),
        timelineSortIndex: 9,
      },
      {
        spell: SPELLS.IRONFUR,
        buffSpellId: SPELLS.IRONFUR.id,
        category: Abilities.SPELL_CATEGORIES.DEFENSIVE,
        timelineSortIndex: 7,
      },
      {
        spell: SPELLS.FRENZIED_REGENERATION,
        buffSpellId: SPELLS.FRENZIED_REGENERATION.id,
        category: Abilities.SPELL_CATEGORIES.DEFENSIVE,
        gcd: {
          base: 1500,
        },
        cooldown: haste => hastedCooldown(36, haste),
        charges: 2,
        timelineSortIndex: 8,
      },
      {
        spell: SPELLS.PULVERIZE_TALENT,
        category: Abilities.SPELL_CATEGORIES.DEFENSIVE,
        enabled: combatant.hasTalent(SPELLS.PULVERIZE_TALENT.id),
        gcd: {
          base: 1500,
        },
        antiFillerSpam: {
          // A spell must meet these conditions to be castable
          isHighPriority: ({ timestamp, targetID }, selectedCombatant, targets) => {
            const pulverizeTalented = combatant.hasTalent(SPELLS.PULVERIZE_TALENT.id);
            const target = targets.find(t => t.id === targetID);
            if (!target) {
              return false;
            }
            const targetHasThrashStacks = target.hasBuff(SPELLS.THRASH_BEAR_DOT.id, timestamp).stacks >= 2;
            return pulverizeTalented && targetHasThrashStacks;
          },
        },
        timelineSortIndex: 6,
      },
      // Raid utility
      {
        spell: SPELLS.STAMPEDING_ROAR_BEAR,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 120,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.GROWL,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
      },
      {
        spell: SPELLS.SKULL_BASH,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
      },
      {
        spell: SPELLS.BEAR_FORM,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.CAT_FORM,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.TRAVEL_FORM,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.MOONKIN_FORM,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.REBIRTH,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.INCAPACITATING_ROAR,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        enabled: !combatant.hasTalent(SPELLS.INTIMIDATING_ROAR_TALENT.id),
        cooldown: 30,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.INTIMIDATING_ROAR_TALENT,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        enabled: combatant.hasTalent(SPELLS.INTIMIDATING_ROAR_TALENT.id),
        cooldown: 30,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.TYPHOON,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        enabled: combatant.hasTalent(SPELLS.TYPHOON_TALENT.id),
        cooldown: 30,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.MASS_ENTANGLEMENT_TALENT,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        enabled: combatant.hasTalent(SPELLS.MASS_ENTANGLEMENT_TALENT.id),
        cooldown: 30,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.MIGHTY_BASH_TALENT,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        enabled: combatant.hasTalent(SPELLS.MIGHTY_BASH_TALENT.id),
        cooldown: 50,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: [SPELLS.WILD_CHARGE_TALENT, SPELLS.WILD_CHARGE_MOONKIN, SPELLS.WILD_CHARGE_CAT, SPELLS.WILD_CHARGE_BEAR, SPELLS.WILD_CHARGE_TRAVEL],
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 15,
        gcd: {
          static: 500,
        },
        enabled: combatant.hasTalent(SPELLS.WILD_CHARGE_TALENT.id),
      },
      {
        spell: SPELLS.DASH,
        buffSpellId: SPELLS.DASH.id,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        enabled: !combatant.hasTalent(SPELLS.TIGER_DASH_TALENT.id),
        cooldown: 180,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.TIGER_DASH_TALENT,
        buffSpellId: SPELLS.TIGER_DASH_TALENT.id,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 45,
        enabled: combatant.hasTalent(SPELLS.TIGER_DASH_TALENT.id),
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.HIBERNATE,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.SOOTHE,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 10,
        gcd: {
          base: 1500,
        },
      },
    ];
  }
}

export default Abilities;
<commit_msg>Removed Legion artifact calc<commit_after>import ITEMS from 'common/ITEMS';
import SPELLS from 'common/SPELLS';
import CoreAbilities from 'Parser/Core/Modules/Abilities';
import Enemies from 'Parser/Core/Modules/Enemies';

import Ability from './Ability';

// The amount of time after a proc has occurred when casting a filler is no longer acceptable
const REACTION_TIME_THRESHOLD = 500;

const hastedCooldown = (baseCD, haste) => (baseCD / (1 + haste));

class Abilities extends CoreAbilities {
  static ABILITY_CLASS = Ability;
  static dependencies = {
    ...CoreAbilities.dependencies,
    enemies: Enemies,
  };

  spellbook() {
    const combatant = this.selectedCombatant;
    return [
      // Rotational Spells
      {
        spell: SPELLS.MANGLE_BEAR,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: (haste, selectedCombatant) => {
          if (selectedCombatant.hasBuff(SPELLS.INCARNATION_GUARDIAN_OF_URSOC_TALENT.id)) {
            return hastedCooldown(1.5, haste);
          }
          return hastedCooldown(6, haste);
        },
        gcd: {
          base: 1500,
        },
        antiFillerSpam: {
          isHighPriority: true,
        },
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.7,
          averageIssueEfficiency: 0.6,
          majorIssueEfficiency: 0.5,
        },
        timelineSortIndex: 1,
      },
      {
        spell: SPELLS.THRASH_BEAR,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: (haste, selectedCombatant) => {
          if (selectedCombatant.hasBuff(SPELLS.INCARNATION_GUARDIAN_OF_URSOC_TALENT.id)) {
            return hastedCooldown(1.5, haste);
          }
          return hastedCooldown(6, haste);
        },
        gcd: {
          base: 1500,
        },
        antiFillerSpam: {
          isHighPriority: true,
        },
        castEfficiency: {
          suggestion: true,
        },
        timelineSortIndex: 2,
      },
      {
        spell: SPELLS.MOONFIRE,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        gcd: {
          base: 1500,
        },
        antiFillerSpam: {
          isFiller: (event, selectedCombatant, targets) => {
            if (combatant.hasTalent(SPELLS.GALACTIC_GUARDIAN_TALENT.id) && selectedCombatant.hasBuff(SPELLS.GALACTIC_GUARDIAN.id)) {
              return false;
            }
            // Check if moonfire is present on the current target
            // Note that if the current target has no enemy data we can't track whether the dot
            // is ticking or not, in that case we consider it non-filler as a concession.
            if (!this.enemies.getEntity(event) || !this.enemies.getEntity(event).hasBuff(SPELLS.MOONFIRE_BEAR.id, event.timestamp)) {
              return false;
            }
            return true;
          },
          isHighPriority: ({ timestamp }, selectedCombatant) => {
            // Account for reaction time; the player must have had the proc for at least this long
            return selectedCombatant.hasBuff(SPELLS.GALACTIC_GUARDIAN.id, timestamp - REACTION_TIME_THRESHOLD);
          },
        },
        timelineSortIndex: 3,
      },
      {
        spell: SPELLS.SWIPE_BEAR,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        gcd: {
          base: 1500,
        },
        antiFillerSpam: {
          isFiller: (event, selectedCombatant, targets) => targets.length < 4,
        },
        timelineSortIndex: 4,
      },
      {
        spell: SPELLS.MAUL,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        gcd: {
          base: 1500,
        },
        timelineSortIndex: 5,
      },
      // Cooldowns
      {
        spell: SPELLS.BARKSKIN,
        buffSpellId: SPELLS.BARKSKIN.id,
        category: Abilities.SPELL_CATEGORIES.DEFENSIVE,
        cooldown: haste => combatant.hasTalent(SPELLS.SURVIVAL_OF_THE_FITTEST_TALENT.id) ? 90 - (90 / 3) : 90,
        timelineSortIndex: 9,
      },
      {
        spell: SPELLS.SURVIVAL_INSTINCTS,
        buffSpellId: SPELLS.SURVIVAL_INSTINCTS.id,
        category: Abilities.SPELL_CATEGORIES.DEFENSIVE,
        cooldown: (haste, selectedCombatant) => {
          return combatant.hasTalent(SPELLS.SURVIVAL_OF_THE_FITTEST_TALENT.id) ? 240 - (240 / 3) : 240;
        },
        charges: 2,
        timelineSortIndex: 9,
      },
      {
        spell: SPELLS.INCARNATION_GUARDIAN_OF_URSOC_TALENT,
        category: Abilities.SPELL_CATEGORIES.SEMI_DEFENSIVE,
        gcd: {
          base: 1500,
        },
        cooldown: 180,
        enabled: combatant.hasTalent(SPELLS.INCARNATION_GUARDIAN_OF_URSOC_TALENT.id),
        timelineSortIndex: 9,
      },
      {
        spell: SPELLS.BRISTLING_FUR_TALENT,
        buffSpellId: SPELLS.BRISTLING_FUR_TALENT.id,
        isDefensive: true,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        gcd: {
          base: 1500,
        },
        cooldown: 40,
        enabled: combatant.hasTalent(SPELLS.BRISTLING_FUR_TALENT.id),
        timelineSortIndex: 9,
      },
      {
        spell: SPELLS.IRONFUR,
        buffSpellId: SPELLS.IRONFUR.id,
        category: Abilities.SPELL_CATEGORIES.DEFENSIVE,
        timelineSortIndex: 7,
      },
      {
        spell: SPELLS.FRENZIED_REGENERATION,
        buffSpellId: SPELLS.FRENZIED_REGENERATION.id,
        category: Abilities.SPELL_CATEGORIES.DEFENSIVE,
        gcd: {
          base: 1500,
        },
        cooldown: haste => hastedCooldown(36, haste),
        charges: 2,
        timelineSortIndex: 8,
      },
      {
        spell: SPELLS.PULVERIZE_TALENT,
        category: Abilities.SPELL_CATEGORIES.DEFENSIVE,
        enabled: combatant.hasTalent(SPELLS.PULVERIZE_TALENT.id),
        gcd: {
          base: 1500,
        },
        antiFillerSpam: {
          // A spell must meet these conditions to be castable
          isHighPriority: ({ timestamp, targetID }, selectedCombatant, targets) => {
            const pulverizeTalented = combatant.hasTalent(SPELLS.PULVERIZE_TALENT.id);
            const target = targets.find(t => t.id === targetID);
            if (!target) {
              return false;
            }
            const targetHasThrashStacks = target.hasBuff(SPELLS.THRASH_BEAR_DOT.id, timestamp).stacks >= 2;
            return pulverizeTalented && targetHasThrashStacks;
          },
        },
        timelineSortIndex: 6,
      },
      // Raid utility
      {
        spell: SPELLS.STAMPEDING_ROAR_BEAR,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 120,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.GROWL,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
      },
      {
        spell: SPELLS.SKULL_BASH,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
      },
      {
        spell: SPELLS.BEAR_FORM,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.CAT_FORM,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.TRAVEL_FORM,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.MOONKIN_FORM,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.REBIRTH,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.INCAPACITATING_ROAR,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        enabled: !combatant.hasTalent(SPELLS.INTIMIDATING_ROAR_TALENT.id),
        cooldown: 30,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.INTIMIDATING_ROAR_TALENT,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        enabled: combatant.hasTalent(SPELLS.INTIMIDATING_ROAR_TALENT.id),
        cooldown: 30,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.TYPHOON,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        enabled: combatant.hasTalent(SPELLS.TYPHOON_TALENT.id),
        cooldown: 30,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.MASS_ENTANGLEMENT_TALENT,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        enabled: combatant.hasTalent(SPELLS.MASS_ENTANGLEMENT_TALENT.id),
        cooldown: 30,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.MIGHTY_BASH_TALENT,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        enabled: combatant.hasTalent(SPELLS.MIGHTY_BASH_TALENT.id),
        cooldown: 50,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: [SPELLS.WILD_CHARGE_TALENT, SPELLS.WILD_CHARGE_MOONKIN, SPELLS.WILD_CHARGE_CAT, SPELLS.WILD_CHARGE_BEAR, SPELLS.WILD_CHARGE_TRAVEL],
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 15,
        gcd: {
          static: 500,
        },
        enabled: combatant.hasTalent(SPELLS.WILD_CHARGE_TALENT.id),
      },
      {
        spell: SPELLS.DASH,
        buffSpellId: SPELLS.DASH.id,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        enabled: !combatant.hasTalent(SPELLS.TIGER_DASH_TALENT.id),
        cooldown: 180,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.TIGER_DASH_TALENT,
        buffSpellId: SPELLS.TIGER_DASH_TALENT.id,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 45,
        enabled: combatant.hasTalent(SPELLS.TIGER_DASH_TALENT.id),
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.HIBERNATE,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        gcd: {
          base: 1500,
        },
      },
      {
        spell: SPELLS.SOOTHE,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 10,
        gcd: {
          base: 1500,
        },
      },
    ];
  }
}

export default Abilities;
<|endoftext|>


<commit_before>import CoreHaste from 'Parser/Core/Modules/Haste';
import SPELLS from 'common/SPELLS';

const debug = false;

const VOIDFORM_HASTE_PER_STACK = 0.01;
const LINGERING_INSANITY_HASTE_PER_STACK = 0.01;

class Haste extends CoreHaste {

  _highestVoidformStack = 0;
  _highestLingeringStack = 0;

  on_toPlayer_applybuff(event) {
    const spellId = event.ability.guid;
    if (spellId === SPELLS.LINGERING_INSANITY.id) {
      this._highestLingeringStack = this._highestVoidformStack;
      this._highestVoidformStack  = 0;
      return;
    }

    if (spellId === SPELLS.VOIDFORM_BUFF.id){
      return;
    }

    if (spellId === SPELLS.VOID_TORRENT.id) {
      return;
    }

    super.on_toPlayer_applybuff && super.on_toPlayer_applybuff(event);
  }

  on_toPlayer_applybuffstack(event) {
    const spellId = event.ability.guid;
    if (spellId === SPELLS.VOIDFORM_BUFF.id) {
      this._applyHasteLoss(event, this._highestVoidformStack * VOIDFORM_HASTE_PER_STACK);
      this._highestVoidformStack = event.stack;
      this._applyHasteGain(event, this._highestVoidformStack * VOIDFORM_HASTE_PER_STACK);

      debug && console.log(`ABC: Current haste: ${this.current} (gained ${VOIDFORM_HASTE_PER_STACK * this._highestVoidformStack} from VOIDFORM_BUFF)`, event.timestamp);
      return;
    }

    super.on_toPlayer_applybuffstack && super.on_toPlayer_applybuffstack(event);
  }

  on_toPlayer_removebuff(event) {
    const spellId = event.ability.guid;
    if (spellId === SPELLS.LINGERING_INSANITY.id || spellId === SPELLS.VOIDFORM_BUFF.id) return;

    super.on_toPlayer_removebuff && super.on_toPlayer_removebuff(event);
  }

  on_toPlayer_removebuffstack(event) {
    const spellId = event.ability.guid;
    if (spellId === SPELLS.LINGERING_INSANITY.id) {
      this._applyHasteLoss(event, this._highestLingeringStack * LINGERING_INSANITY_HASTE_PER_STACK);
      this._highestLingeringStack = event.stack;
      this._applyHasteGain(event, this._highestLingeringStack * LINGERING_INSANITY_HASTE_PER_STACK);

      debug && console.log(`ABC: Current haste: ${this.current} (lost 0.02 from LINGERING_INSANITY)`);
      return;
    }

    super.on_toPlayer_removebuffstack && super.on_toPlayer_removebuffstack(event);
  }

  _changeBuffStack(event) {
    const spellId = event.ability.guid;
    if (spellId === SPELLS.VOIDFORM_BUFF.id || spellId === SPELLS.LINGERING_INSANITY.id) {
      return;
    }

    super._changeBuffStack && super._changeBuffStack(event);
  }

}

export default Haste;
<commit_msg>Removed constant as it was deemed unnecesarry.<commit_after>import CoreHaste from 'Parser/Core/Modules/Haste';
import SPELLS from 'common/SPELLS';

const debug = false;

const VOIDFORM_HASTE_PER_STACK = 0.01;

class Haste extends CoreHaste {

  _highestVoidformStack = 0;
  _highestLingeringStack = 0;

  on_toPlayer_applybuff(event) {
    const spellId = event.ability.guid;
    if (spellId === SPELLS.LINGERING_INSANITY.id) {
      this._highestLingeringStack = this._highestVoidformStack;
      this._highestVoidformStack  = 0;
      return;
    }

    if (spellId === SPELLS.VOIDFORM_BUFF.id){
      return;
    }

    if (spellId === SPELLS.VOID_TORRENT.id) {
      return;
    }

    super.on_toPlayer_applybuff && super.on_toPlayer_applybuff(event);
  }

  on_toPlayer_applybuffstack(event) {
    const spellId = event.ability.guid;
    if (spellId === SPELLS.VOIDFORM_BUFF.id) {
      this._applyHasteLoss(event, this._highestVoidformStack * VOIDFORM_HASTE_PER_STACK);
      this._highestVoidformStack = event.stack;
      this._applyHasteGain(event, this._highestVoidformStack * VOIDFORM_HASTE_PER_STACK);

      debug && console.log(`ABC: Current haste: ${this.current} (gained ${VOIDFORM_HASTE_PER_STACK * this._highestVoidformStack} from VOIDFORM_BUFF)`, event.timestamp);
      return;
    }

    super.on_toPlayer_applybuffstack && super.on_toPlayer_applybuffstack(event);
  }

  on_toPlayer_removebuff(event) {
    const spellId = event.ability.guid;
    if (spellId === SPELLS.LINGERING_INSANITY.id || spellId === SPELLS.VOIDFORM_BUFF.id) return;

    super.on_toPlayer_removebuff && super.on_toPlayer_removebuff(event);
  }

  on_toPlayer_removebuffstack(event) {
    const spellId = event.ability.guid;
    if (spellId === SPELLS.LINGERING_INSANITY.id) {
      this._applyHasteLoss(event, this._highestLingeringStack * VOIDFORM_HASTE_PER_STACK);
      this._highestLingeringStack = event.stack;
      this._applyHasteGain(event, this._highestLingeringStack * VOIDFORM_HASTE_PER_STACK);

      debug && console.log(`ABC: Current haste: ${this.current} (lost 0.02 from LINGERING_INSANITY)`);
      return;
    }

    super.on_toPlayer_removebuffstack && super.on_toPlayer_removebuffstack(event);
  }

  _changeBuffStack(event) {
    const spellId = event.ability.guid;
    if (spellId === SPELLS.VOIDFORM_BUFF.id || spellId === SPELLS.LINGERING_INSANITY.id) {
      return;
    }

    super._changeBuffStack && super._changeBuffStack(event);
  }

}

export default Haste;
<|endoftext|>


<commit_before>define([
  'angular',
  'app',
  'angular-mocks',
  '/views/header/breadcrumbs.js'
], function(angular, app, mocks, bcCtrl) {

  describe('Breadcrumbs Controller', function() {

    var controller, $rootScope, $scope, $location;
    beforeEach(function() {
      mocks.module('app');

      mocks.inject(function(_$rootScope_, _$location_, $controller, _breadcrumbs_) {
        $rootScope = _$rootScope_;
        $location = _$location_;

        $scope = $rootScope.$new();
        controller = $controller('BreadcrumbsCtrl', {
          $rootScope: $rootScope,
          $scope: $scope,
          $location: _$location_,
          breadcrumbs: _breadcrumbs_
        });

      });
    });

    it('should not show any breadcrumbs on the homepage', function() {
      $location.path = sinon.stub($location.path).returns('/');
      contorller.computeCrumbs();
      expect($scope.showCrumbs).to.be.false;
    });
  });
});<commit_msg>Add more unit tests to breadcrumbs controller<commit_after>define([
  'angular',
  'app',
  'angular-mocks',
  '/views/header/breadcrumbs.js'
], function(angular, app, mocks, bcCtrl) {

  describe('Breadcrumbs Controller', function() {

    var controller, $rootScope, scope, $location, ngBreadcrumbs,
      mockCrumbs = [{label: 'Home', path: '/'}, {label: 'Meetings', path: '/meetings'}];

    beforeEach(function() {
      mocks.module('app');

      mocks.inject(function(_$rootScope_, _$location_, $controller, _breadcrumbs_) {
        $rootScope = _$rootScope_;
        $location = _$location_,
        ngBreadcrumbs = _breadcrumbs_;

        scope = $rootScope.$new();
        controller = $controller('BreadcrumbsCtrl', {
          $rootScope: $rootScope,
          $scope: scope,
          $location: _$location_,
          breadcrumbs: _breadcrumbs_
        });

      });
    });

    it('should not show any breadcrumbs on the homepage', function() {
      $location.path = sinon.stub().returns('/');
      controller.computeCrumbs();
      expect(scope.showCrumbs).to.be.false;
    });

    it('should add a dynamic crumb to the trail when the breadcrumbs:add event is fired', function() {

      ngBreadcrumbs.get = sinon.stub().returns(mockCrumbs);

      $location.path = sinon.stub().returns('/meetings');
      controller.computeCrumbs();
      controller.addCrumb = sinon.spy(controller.addCrumb);

      $rootScope.$emit('breadcrumbs:add', 'The title', '#path');

      expect(controller.addCrumb).to.have.been.called;
      expect(scope.showCrumbs).to.be.true;
      expect(scope.breadcrumbs).to.include.something.that.deep.equals({
        label: 'The title',
        path: '#path',
        dynamic: true
      });
    });

    it('should replace the last dynamic crumb with a new one', function() {
      ngBreadcrumbs.get = sinon.stub().returns(mockCrumbs);

      $location.path = sinon.stub().returns('/meetings');
      controller.computeCrumbs();
      controller.addCrumb = sinon.spy(controller.addCrumb);

      $rootScope.$emit('breadcrumbs:add', 'The title', '#path');
      $rootScope.$emit('breadcrumbs:add', 'New title', '#new_path');

      expect(scope.breadcrumbs).to.not.include.something.that.deep.equals({
        label: 'The title',
        path: '#path',
        dynamic: true
      });

      expect(scope.breadcrumbs).to.include.something.that.deep.equals({
        label: 'New title',
        path: '#new_path',
        dynamic: true
      });
    });

  });
});<|endoftext|>


<commit_before>$(function() {
  var pageId = $('#content-main').data('page-id');
  var pagePath= $('#content-main').data('path');

  //require('inline-attachment/src/inline-attachment');
  //require('jquery.selection');
  //require('bootstrap-sass');

  // show/hide
  function FetchPagesUpdatePostAndInsert(path) {
    $.get('/_api/pages.updatePost', {path: path}, function(res) {
      if (res.ok) {
        var $slackChannels = $('#page-form-slack-channel');
        $slackChannels.val(res.updatePost.join(','));
      }
    });
  }

  var slackConfigured = $('#page-form-setting').data('slack-configured');

  // for new page
  if (!pageId) {
    if (!pageId && pagePath.match(/(20\d{4}|20\d{6}|20\d{2}_\d{1,2}|20\d{2}_\d{1,2}_\d{1,2})/)) {
      $('#page-warning-modal').modal('show');
    }

    if (slackConfigured) {
      FetchPagesUpdatePostAndInsert(pagePath);
    }
  }

  $('a[data-toggle="tab"][href="#edit-form"]').on('show.bs.tab', function() {
    $('.content-main').addClass('on-edit');

    if (slackConfigured) {
      var $slackChannels = $('#page-form-slack-channel');
      var slackChannels = $slackChannels.val();
      // if slackChannels is empty, then fetch default (admin setting)
      // if not empty, it means someone specified this setting for the page.
      if (slackChannels === '') {
        FetchPagesUpdatePostAndInsert(pagePath);
      }
    }
  });

  $('a[data-toggle="tab"][href="#edit-form"]').on('hide.bs.tab', function() {
    $('.content-main').removeClass('on-edit');
  });

  // preview watch
  var originalContent = $('#form-body').val();

  // restore draft
  // とりあえず、originalContent がない場合のみ復元する。(それ以外の場合は後で考える)
  var draft = crowi.findDraft(pagePath);
  var originalRevision = $('#page-form [name="pageForm[currentRevision]"]').val();
  if (!originalRevision && draft) {
    // TODO
    $('#form-body').val(draft)
  }

  var prevContent = originalContent;

  function renderPreview() {
    var content = $('#form-body').val();
    var parsedHTML = crowiRenderer.render(content);
    $('#preview-body').html(parsedHTML);
  }

  // for initialize preview
  renderPreview();
  var watchTimer = setInterval(function() {
    var content = $('#form-body').val();
    if (prevContent != content) {

      renderPreview();
      prevContent = content;
    }
  }, 500);

  // edit detection
  var isFormChanged = false;
  $(window).on('beforeunload', function(e) {
    if (isFormChanged) {
      return '編集中の内容があります。内容を破棄してページを移動しますか?';
    }
  });
  $('#form-body').on('keyup change', function(e) {
    var content = $('#form-body').val();
    if (originalContent != content) {
      isFormChanged = true;
      crowi.saveDraft(pagePath, content);
    } else {
      isFormChanged = false;
      crowi.clearDraft(pagePath);
    }
  });
  $('#page-form').on('submit', function(e) {
    // avoid message
    isFormChanged = false;
    crowi.clearDraft(pagePath);
  });

  var getCurrentLine = function(event) {
    var $target = $(event.target);

    var text = $target.val();
    var pos = $target.selection('getPos');
    if (text === null || pos.start !== pos.end) {
      return null;
    }

    var startPos = text.lastIndexOf("\n", pos.start - 1) + 1;
    var endPos = text.indexOf("\n", pos.start);
    if (endPos === -1) {
      endPos = text.length;
    }

    return {
      text: text.slice(startPos, endPos),
      start: startPos,
      end: endPos,
      caret: pos.start,
      endOfLine: !$.trim(text.slice(pos.start, endPos))
    };
  };

  var getPrevLine = function(event) {
    var $target = $(event.target);
    var currentLine = getCurrentLine(event);
    var text = $target.val().slice(0, currentLine.start);
    var startPos = text.lastIndexOf("\n", currentLine.start - 2) + 1;
    var endPos = currentLine.start;

    return {
      text: text.slice(startPos, endPos),
      start: startPos,
      end: endPos
    };
  };

  var handleTabKey = function(event) {
    event.preventDefault();

    var $target = $(event.target);
    var currentLine = getCurrentLine(event);
    var text = $target.val();
    var pos = $target.selection('getPos');

    if (currentLine) {
      $target.selection('setPos', {start: currentLine.start, end: (currentLine.end - 1)});
    }

    if (event.shiftKey === true) {
      if (currentLine && currentLine.text.charAt(0) === '|') {
        // prev cell in table
        var newPos = text.lastIndexOf('|', pos.start - 1);
        if (newPos > 0) {
          $target.selection('setPos', {start: newPos - 1, end: newPos - 1});
        }
      } else {
        // re indent
        var reindentedText = $target.selection().replace(/^ {1,4}/gm, '');
        var reindentedCount = $target.selection().length - reindentedText.length;
        $target.selection('replace', {text: reindentedText, mode: 'before'});
        if (currentLine) {
          $target.selection('setPos', {start: pos.start - reindentedCount, end: pos.start - reindentedCount});
        }
      }
    } else {
      if (currentLine && currentLine.text.charAt(0) === '|') {
        // next cell in table
        var newPos = text.indexOf('|', pos.start + 1);
        if (newPos < 0 || newPos === text.lastIndexOf('|', currentLine.end - 1)) {
          $target.selection('setPos', {start: currentLine.end, end: currentLine.end});
        } else {
          $target.selection('setPos', {start: newPos + 2, end: newPos + 2});
        }
      } else {
        // indent
        $target.selection('replace', {
          text: '    ' + $target.selection().split("\n").join("\n    "),
          mode: 'before'
        });
        if (currentLine) {
          $target.selection('setPos', {start: pos.start + 4, end: pos.start + 4});
        }
      }
    }

    $target.trigger('input');
  };

  var handleEnterKey = function(event) {
    if (event.metaKey || event.ctrlKey || event.shiftKey) {
      return;
    }

    var currentLine = getCurrentLine(event);
    if (!currentLine || currentLine.start === currentLine.caret) {
      return;
    }

    var $target = $(event.target);
    var match = currentLine.text.match(/^(\s*(?:-|\+|\*|\d+\.) (?:\[(?:x| )\] )?)\s*\S/);
    if (match) {
      // smart indent with list
      if (currentLine.text.match(/^(\s*(?:-|\+|\*|\d+\.) (?:\[(?:x| )\] ))\s*$/)) {
        // empty task list
        $target.selection('setPos', {start: currentLine.start, end: (currentLine.end - 1)});
        return;
      }
      event.preventDefault();
      var listMark = match[1].replace(/\[x\]/, '[ ]');
      var listMarkMatch = listMark.match(/^(\s*)(\d+)\./);
      if (listMarkMatch) {
        var indent = listMarkMatch[1];
        var num = parseInt(listMarkMatch[2]);
        if (num !== 1) {
          listMark = listMark.replace(/\s*\d+/, indent + (num +1));
        }
      }
      $target.selection('insert', {text: "\n" + listMark, mode: 'before'});
    } else if (currentLine.text.match(/^(\s*(?:-|\+|\*|\d+\.) )/)) {
      // remove list
      $target.selection('setPos', {start: currentLine.start, end: currentLine.end});
    } else if (currentLine.text.match(/^.*\|\s*$/)) {
      // new row for table
      if (currentLine.text.match(/^[\|\s]+$/)) {
        $target.selection('setPos', {start: currentLine.start, end: currentLine.end});
        return;
      }
      if (!currentLine.endOfLine) {
        return;
      }
      event.preventDefault();
      var row = [];
      var cellbarMatch = currentLine.text.match(/\|/g);
      for (var i = 0; i < cellbarMatch.length; i++) {
        row.push('|');
      }
      var prevLine = getPrevLine(event);
      if (!prevLine || (!currentLine.text.match(/---/) && !prevLine.text.match(/\|/g))) {
        $target.selection('insert', {text: "\n" + row.join(' --- ') + "\n" + row.join('  '), mode: 'before'});
        $target.selection('setPos', {start: currentLine.caret + 6 * row.length - 1, end: currentLine.caret + 6 * row.length - 1});
      } else {
        $target.selection('insert', {text: "\n" + row.join('  '), mode: 'before'});
        $target.selection('setPos', {start: currentLine.caret + 3, end: currentLine.caret + 3});
      }
    }

    $target.trigger('input');
  };

  var handleEscapeKey = function(event) {
    event.preventDefault();
    var $target = $(event.target);
    $target.blur();
  };

  var handleSpaceKey = function(event) {
    // keybind: alt + shift + space
    if (!(event.shiftKey && event.altKey)) {
      return;
    }
    var currentLine = getCurrentLine(event);
    if (!currentLine) {
      return;
    }

    var $target = $(event.target);
    var match = currentLine.text.match(/^(\s*)(-|\+|\*|\d+\.) (?:\[(x| )\] )(.*)/);
    if (match) {
      event.preventDefault();
      var checkMark = (match[3] == ' ') ? 'x' : ' ';
      var replaceTo = match[1] + match[2] + ' [' + checkMark + '] ' + match[4];
      $target.selection('setPos', {start: currentLine.start, end: currentLine.end});
      $target.selection('replace', {text: replaceTo, mode: 'keep'});
      $target.selection('setPos', {start: currentLine.caret, end: currentLine.caret});
      $target.trigger('input');
    }
  };

  // markdown helper inspired by 'esarea'.
  // see: https://github.com/fukayatsu/esarea
  $('textarea#form-body').on('keydown', function(event) {
    switch (event.which || event.keyCode) {
      case 9:
        handleTabKey(event);
        break;
      case 13:
        handleEnterKey(event);
        break;
      case 27:
        handleEscapeKey(event);
        break;
      case 32:
        handleSpaceKey(event);
        break;
      default:
    }
  });

  var handlePasteEvent = function(event) {
    var currentLine = getCurrentLine(event);

    if (!currentLine) {
      return false;
    }
    var $target = $(event.target);
    var pasteText = event.clipboardData.getData('text');

    var match = currentLine.text.match(/^(\s*(?:>|\-|\+|\*|\d+\.) (?:\[(?:x| )\] )?)/);
    if (match) {
      if (pasteText.match(/(?:\r\n|\r|\n)/)) {
        pasteText = pasteText.replace(/(\r\n|\r|\n)/g, "$1" + match[1]);
      }
    }

    $target.selection('insert', {text: pasteText, mode: 'after'});

    var newPos = currentLine.end + pasteText.length;
    $target.selection('setPos', {start: newPos, end: newPos});

    return true;
  };

  document.getElementById('form-body').addEventListener('paste', function(event) {
    if (handlePasteEvent(event)) {
      event.preventDefault();
    }
  });

  var unbindInlineAttachment = function($form) {
    $form.unbind('.inlineattach');
  };
  var bindInlineAttachment = function($form, attachmentOption) {
    var $this = $form;
    var editor = createEditorInstance($form);
    var inlineattach = new inlineAttachment(attachmentOption, editor);
    $form.bind({
      'paste.inlineattach': function(e) {
        inlineattach.onPaste(e.originalEvent);
      },
      'drop.inlineattach': function(e) {
        e.stopPropagation();
        e.preventDefault();
        inlineattach.onDrop(e.originalEvent);
      },
      'dragenter.inlineattach dragover.inlineattach': function(e) {
        e.stopPropagation();
        e.preventDefault();
      }
    });
  };
  var createEditorInstance = function($form) {
    var $this = $form;

    return {
      getValue: function() {
        return $this.val();
      },
      insertValue: function(val) {
        inlineAttachment.util.insertTextAtCursor($this[0], val);
      },
      setValue: function(val) {
        $this.val(val);
      }
    };
  };

  var $inputForm = $('form.uploadable textarea#form-body');
  if ($inputForm.length > 0) {
    var pageId = $('#content-main').data('page-id') || 0;
    var attachmentOption = {
      uploadUrl: '/_api/attachment/page/' + pageId,
      extraParams: {
        path: location.pathname
      },
      progressText: '(Uploading file...)',
      urlText: "\n![file]({filename})\n"
    };

    attachmentOption.onFileUploadResponse = function(res) {
      var result = JSON.parse(res.response);

      if (result.status && result.pageCreated) {
        var page = result.page,
            pageId = page._id;

        $('#content-main').data('page-id', page._id);
        $('#page-form [name="pageForm[currentRevision]"]').val(page.revision._id)

        unbindInlineAttachment($inputForm);

        attachmentOption.uploadUrl = '/_api/attachment/page/' + pageId,
        bindInlineAttachment($inputForm, attachmentOption);
      }
      return true;
    };

    bindInlineAttachment($inputForm, attachmentOption);

    $('textarea#form-body').on('dragenter dragover', function() {
      $(this).addClass('dragover');
    });
    $('textarea#form-body').on('drop dragleave dragend', function() {
      $(this).removeClass('dragover');
    });
  }

  var enableScrollSync = function() {
    var getMaxScrollTop = function(dom) {
      var rect = dom.getBoundingClientRect();

      return dom.scrollHeight - rect.height;
    };

    var getScrollRate = function(dom) {
      var maxScrollTop = getMaxScrollTop(dom);
      var rate = dom.scrollTop / maxScrollTop;

      return rate;
    };

    var getScrollTop = function(dom, rate) {
      var maxScrollTop = getMaxScrollTop(dom);
      var top = maxScrollTop * rate;

      return top;
    };

    var editor = document.querySelector('#form-body');
    var preview = document.querySelector('#preview-body');

    editor.addEventListener('scroll', function(event) {
      var rate = getScrollRate(this);
      var top = getScrollTop(preview, rate);

      preview.scrollTop = top;
    });
  };
  enableScrollSync();
});
<commit_msg>Fixed (but temporary) undo behavior<commit_after>$(function() {
  var pageId = $('#content-main').data('page-id');
  var pagePath= $('#content-main').data('path');

  //require('inline-attachment/src/inline-attachment');
  //require('jquery.selection');
  //require('bootstrap-sass');

  // show/hide
  function FetchPagesUpdatePostAndInsert(path) {
    $.get('/_api/pages.updatePost', {path: path}, function(res) {
      if (res.ok) {
        var $slackChannels = $('#page-form-slack-channel');
        $slackChannels.val(res.updatePost.join(','));
      }
    });
  }

  var slackConfigured = $('#page-form-setting').data('slack-configured');

  // for new page
  if (!pageId) {
    if (!pageId && pagePath.match(/(20\d{4}|20\d{6}|20\d{2}_\d{1,2}|20\d{2}_\d{1,2}_\d{1,2})/)) {
      $('#page-warning-modal').modal('show');
    }

    if (slackConfigured) {
      FetchPagesUpdatePostAndInsert(pagePath);
    }
  }

  $('a[data-toggle="tab"][href="#edit-form"]').on('show.bs.tab', function() {
    $('.content-main').addClass('on-edit');

    if (slackConfigured) {
      var $slackChannels = $('#page-form-slack-channel');
      var slackChannels = $slackChannels.val();
      // if slackChannels is empty, then fetch default (admin setting)
      // if not empty, it means someone specified this setting for the page.
      if (slackChannels === '') {
        FetchPagesUpdatePostAndInsert(pagePath);
      }
    }
  });

  $('a[data-toggle="tab"][href="#edit-form"]').on('hide.bs.tab', function() {
    $('.content-main').removeClass('on-edit');
  });

  // preview watch
  var originalContent = $('#form-body').val();

  // restore draft
  // とりあえず、originalContent がない場合のみ復元する。(それ以外の場合は後で考える)
  var draft = crowi.findDraft(pagePath);
  var originalRevision = $('#page-form [name="pageForm[currentRevision]"]').val();
  if (!originalRevision && draft) {
    // TODO
    $('#form-body').val(draft)
  }

  var prevContent = originalContent;

  function renderPreview() {
    var content = $('#form-body').val();
    var parsedHTML = crowiRenderer.render(content);
    $('#preview-body').html(parsedHTML);
  }

  // for initialize preview
  renderPreview();
  var watchTimer = setInterval(function() {
    var content = $('#form-body').val();
    if (prevContent != content) {

      renderPreview();
      prevContent = content;
    }
  }, 500);

  // edit detection
  var isFormChanged = false;
  $(window).on('beforeunload', function(e) {
    if (isFormChanged) {
      return '編集中の内容があります。内容を破棄してページを移動しますか?';
    }
  });
  $('#form-body').on('keyup change', function(e) {
    var content = $('#form-body').val();
    if (originalContent != content) {
      isFormChanged = true;
      crowi.saveDraft(pagePath, content);
    } else {
      isFormChanged = false;
      crowi.clearDraft(pagePath);
    }
  });
  $('#page-form').on('submit', function(e) {
    // avoid message
    isFormChanged = false;
    crowi.clearDraft(pagePath);
  });

  // This is a temporary implementation until porting to React.
  var insertText = function(start, end, newText, mode) {
    var editor = document.querySelector('#form-body');
    mode = mode || 'after';

    switch (mode) {
    case 'before':
      editor.setSelectionRange(start, start);
      break;
    case 'replace':
      editor.setSelectionRange(start, end);
      break;
    case 'after':
    default:
      editor.setSelectionRange(end, end);
    }

    editor.focus();

    var inserted = false;
    try {
      // Chrome, Safari
      inserted = document.execCommand('insertText', false, newText);
    } catch (e) {
      inserted = false;
    }

    if (!inserted) {
      // Firefox
      editor.value = editor.value.substr(0, start) + newText + editor.value.substr(end);
    }
  };

  var getCurrentLine = function(event) {
    var $target = $(event.target);

    var text = $target.val();
    var pos = $target.selection('getPos');
    if (text === null || pos.start !== pos.end) {
      return null;
    }

    var startPos = text.lastIndexOf("\n", pos.start - 1) + 1;
    var endPos = text.indexOf("\n", pos.start);
    if (endPos === -1) {
      endPos = text.length;
    }

    return {
      text: text.slice(startPos, endPos),
      start: startPos,
      end: endPos,
      caret: pos.start,
      endOfLine: !$.trim(text.slice(pos.start, endPos))
    };
  };

  var getPrevLine = function(event) {
    var $target = $(event.target);
    var currentLine = getCurrentLine(event);
    var text = $target.val().slice(0, currentLine.start);
    var startPos = text.lastIndexOf("\n", currentLine.start - 2) + 1;
    var endPos = currentLine.start;

    return {
      text: text.slice(startPos, endPos),
      start: startPos,
      end: endPos
    };
  };

  var handleTabKey = function(event) {
    event.preventDefault();

    var $target = $(event.target);
    var currentLine = getCurrentLine(event);
    var text = $target.val();
    var pos = $target.selection('getPos');

    if (currentLine) {
      $target.selection('setPos', {start: currentLine.start, end: (currentLine.end - 1)});
    }

    if (event.shiftKey === true) {
      if (currentLine && currentLine.text.charAt(0) === '|') {
        // prev cell in table
        var newPos = text.lastIndexOf('|', pos.start - 1);
        if (newPos > 0) {
          $target.selection('setPos', {start: newPos - 1, end: newPos - 1});
        }
      } else {
        // re indent
        var reindentedText = $target.selection().replace(/^ {1,4}/gm, '');
        var reindentedCount = $target.selection().length - reindentedText.length;
        $target.selection('replace', {text: reindentedText, mode: 'before'});
        if (currentLine) {
          $target.selection('setPos', {start: pos.start - reindentedCount, end: pos.start - reindentedCount});
        }
      }
    } else {
      if (currentLine && currentLine.text.charAt(0) === '|') {
        // next cell in table
        var newPos = text.indexOf('|', pos.start + 1);
        if (newPos < 0 || newPos === text.lastIndexOf('|', currentLine.end - 1)) {
          $target.selection('setPos', {start: currentLine.end, end: currentLine.end});
        } else {
          $target.selection('setPos', {start: newPos + 2, end: newPos + 2});
        }
      } else {
        // indent
        $target.selection('replace', {
          text: '    ' + $target.selection().split("\n").join("\n    "),
          mode: 'before'
        });
        if (currentLine) {
          $target.selection('setPos', {start: pos.start + 4, end: pos.start + 4});
        }
      }
    }

    $target.trigger('input');
  };

  var handleEnterKey = function(event) {
    if (event.metaKey || event.ctrlKey || event.shiftKey) {
      return;
    }

    var currentLine = getCurrentLine(event);
    if (!currentLine || currentLine.start === currentLine.caret) {
      return;
    }

    var $target = $(event.target);
    var match = currentLine.text.match(/^(\s*(?:-|\+|\*|\d+\.) (?:\[(?:x| )\] )?)\s*\S/);
    if (match) {
      // smart indent with list
      if (currentLine.text.match(/^(\s*(?:-|\+|\*|\d+\.) (?:\[(?:x| )\] ))\s*$/)) {
        // empty task list
        $target.selection('setPos', {start: currentLine.start, end: (currentLine.end - 1)});
        return;
      }
      event.preventDefault();
      var listMark = match[1].replace(/\[x\]/, '[ ]');
      var listMarkMatch = listMark.match(/^(\s*)(\d+)\./);
      if (listMarkMatch) {
        var indent = listMarkMatch[1];
        var num = parseInt(listMarkMatch[2]);
        if (num !== 1) {
          listMark = listMark.replace(/\s*\d+/, indent + (num +1));
        }
      }
      //$target.selection('insert', {text: "\n" + listMark, mode: 'before'});
      insertText(currentLine.start, currentLine.end, "\n" + listMark, 'after');
    } else if (currentLine.text.match(/^(\s*(?:-|\+|\*|\d+\.) )/)) {
      // remove list
      $target.selection('setPos', {start: currentLine.start, end: currentLine.end});
    } else if (currentLine.text.match(/^.*\|\s*$/)) {
      // new row for table
      if (currentLine.text.match(/^[\|\s]+$/)) {
        $target.selection('setPos', {start: currentLine.start, end: currentLine.end});
        return;
      }
      if (!currentLine.endOfLine) {
        return;
      }
      event.preventDefault();
      var row = [];
      var cellbarMatch = currentLine.text.match(/\|/g);
      for (var i = 0; i < cellbarMatch.length; i++) {
        row.push('|');
      }
      var prevLine = getPrevLine(event);
      if (!prevLine || (!currentLine.text.match(/---/) && !prevLine.text.match(/\|/g))) {
        //$target.selection('insert', {text: "\n" + row.join(' --- ') + "\n" + row.join('  '), mode: 'before'});
        insertText(currentLine.start, currentLine.end, "\n" + row.join(' --- ') + "\n" + row.join('  '), 'after');
        $target.selection('setPos', {start: currentLine.caret + 6 * row.length - 1, end: currentLine.caret + 6 * row.length - 1});
      } else {
        //$target.selection('insert', {text: "\n" + row.join('  '), mode: 'before'});
        insertText(currentLine.start, currentLine.end, "\n" + row.join('  '), 'after');
        $target.selection('setPos', {start: currentLine.caret + 3, end: currentLine.caret + 3});
      }
    }

    $target.trigger('input');
  };

  var handleEscapeKey = function(event) {
    event.preventDefault();
    var $target = $(event.target);
    $target.blur();
  };

  var handleSpaceKey = function(event) {
    // keybind: alt + shift + space
    if (!(event.shiftKey && event.altKey)) {
      return;
    }
    var currentLine = getCurrentLine(event);
    if (!currentLine) {
      return;
    }

    var $target = $(event.target);
    var match = currentLine.text.match(/^(\s*)(-|\+|\*|\d+\.) (?:\[(x| )\] )(.*)/);
    if (match) {
      event.preventDefault();
      var checkMark = (match[3] == ' ') ? 'x' : ' ';
      var replaceTo = match[1] + match[2] + ' [' + checkMark + '] ' + match[4];
      $target.selection('setPos', {start: currentLine.start, end: currentLine.end});
      //$target.selection('replace', {text: replaceTo, mode: 'keep'});
      insertText(currentLine.start, currentLine.end, replaceTo, 'replace');
      $target.selection('setPos', {start: currentLine.caret, end: currentLine.caret});
      $target.trigger('input');
    }
  };

  // markdown helper inspired by 'esarea'.
  // see: https://github.com/fukayatsu/esarea
  $('textarea#form-body').on('keydown', function(event) {
    switch (event.which || event.keyCode) {
      case 9:
        handleTabKey(event);
        break;
      case 13:
        handleEnterKey(event);
        break;
      case 27:
        handleEscapeKey(event);
        break;
      case 32:
        handleSpaceKey(event);
        break;
      default:
    }
  });

  var handlePasteEvent = function(event) {
    var currentLine = getCurrentLine(event);

    if (!currentLine) {
      return false;
    }
    var $target = $(event.target);
    var pasteText = event.clipboardData.getData('text');

    var match = currentLine.text.match(/^(\s*(?:>|\-|\+|\*|\d+\.) (?:\[(?:x| )\] )?)/);
    if (match) {
      if (pasteText.match(/(?:\r\n|\r|\n)/)) {
        pasteText = pasteText.replace(/(\r\n|\r|\n)/g, "$1" + match[1]);
      }
    }

    //$target.selection('insert', {text: pasteText, mode: 'after'});
    insertText(currentLine.start, currentLine.end, pasteText);

    var newPos = currentLine.end + pasteText.length;
    $target.selection('setPos', {start: newPos, end: newPos});

    return true;
  };

  document.getElementById('form-body').addEventListener('paste', function(event) {
    if (handlePasteEvent(event)) {
      event.preventDefault();
    }
  });

  var unbindInlineAttachment = function($form) {
    $form.unbind('.inlineattach');
  };
  var bindInlineAttachment = function($form, attachmentOption) {
    var $this = $form;
    var editor = createEditorInstance($form);
    var inlineattach = new inlineAttachment(attachmentOption, editor);
    $form.bind({
      'paste.inlineattach': function(e) {
        inlineattach.onPaste(e.originalEvent);
      },
      'drop.inlineattach': function(e) {
        e.stopPropagation();
        e.preventDefault();
        inlineattach.onDrop(e.originalEvent);
      },
      'dragenter.inlineattach dragover.inlineattach': function(e) {
        e.stopPropagation();
        e.preventDefault();
      }
    });
  };
  var createEditorInstance = function($form) {
    var $this = $form;

    return {
      getValue: function() {
        return $this.val();
      },
      insertValue: function(val) {
        inlineAttachment.util.insertTextAtCursor($this[0], val);
      },
      setValue: function(val) {
        $this.val(val);
      }
    };
  };

  var $inputForm = $('form.uploadable textarea#form-body');
  if ($inputForm.length > 0) {
    var pageId = $('#content-main').data('page-id') || 0;
    var attachmentOption = {
      uploadUrl: '/_api/attachment/page/' + pageId,
      extraParams: {
        path: location.pathname
      },
      progressText: '(Uploading file...)',
      urlText: "\n![file]({filename})\n"
    };

    attachmentOption.onFileUploadResponse = function(res) {
      var result = JSON.parse(res.response);

      if (result.status && result.pageCreated) {
        var page = result.page,
            pageId = page._id;

        $('#content-main').data('page-id', page._id);
        $('#page-form [name="pageForm[currentRevision]"]').val(page.revision._id)

        unbindInlineAttachment($inputForm);

        attachmentOption.uploadUrl = '/_api/attachment/page/' + pageId,
        bindInlineAttachment($inputForm, attachmentOption);
      }
      return true;
    };

    bindInlineAttachment($inputForm, attachmentOption);

    $('textarea#form-body').on('dragenter dragover', function() {
      $(this).addClass('dragover');
    });
    $('textarea#form-body').on('drop dragleave dragend', function() {
      $(this).removeClass('dragover');
    });
  }

  var enableScrollSync = function() {
    var getMaxScrollTop = function(dom) {
      var rect = dom.getBoundingClientRect();

      return dom.scrollHeight - rect.height;
    };

    var getScrollRate = function(dom) {
      var maxScrollTop = getMaxScrollTop(dom);
      var rate = dom.scrollTop / maxScrollTop;

      return rate;
    };

    var getScrollTop = function(dom, rate) {
      var maxScrollTop = getMaxScrollTop(dom);
      var top = maxScrollTop * rate;

      return top;
    };

    var editor = document.querySelector('#form-body');
    var preview = document.querySelector('#preview-body');

    editor.addEventListener('scroll', function(event) {
      var rate = getScrollRate(this);
      var top = getScrollTop(preview, rate);

      preview.scrollTop = top;
    });
  };
  enableScrollSync();
});
<|endoftext|>


<commit_before>// @flow
import moment from 'moment';
import faker from 'faker';

let transactionsCount = 0;

type transactionType = {
  id: string,
  type: string,
  title: string,
  amount: number,
  currency: string,
  date: Date,
  description: string,
  exchange: ?string,
  conversionRate: ?string,
  transactionId: ?string,
};

const generateTransaction = (data: Object): transactionType => {
  transactionsCount += 1;
  let date = data.date;
  if (data.date == null) {
    date = moment().subtract(Math.floor(Math.random() * 10), 'days').toDate();
  }
  return Object.assign({}, {
    id: `t-id-${transactionsCount}`,
    description: faker.lorem.sentence(),
  }, data, { date });
};

const cardTransaction = (date: ?Date) => {
  const amount = -1 * ((Math.random() * 1000) + 1);
  return generateTransaction({
    amount,
    date,
    type: 'card',
    currency: '$',
    title: `Invoice to ${faker.company.companyName()}`,
    exchange: null,
    conversionRate: null,
    transactionId: null,
  });
};

const adaTransaction = (data: {
  amount: number,
  type: string,
  title: string,
  date: ?Date
}) => {
  const exchangeRate = (Math.random() * 1000) + 10;
  const { amount, type, title, date } = data;
  return generateTransaction({
    type,
    amount,
    title,
    date,
    currency: 'ada',
    exchange: `${amount.toFixed(2)} ADA for ${(amount / exchangeRate).toFixed(2)} USD`,
    conversionRate: `1 USD = ${exchangeRate.toFixed(2)} ADA`,
    transactionId: faker.finance.bitcoinAddress(),
  });
};

const adaExpend = (date: ?Date) => {
  const amount = -1 * ((Math.random() * 1000) + 1);
  return adaTransaction({
    amount,
    date,
    type: 'adaExpend',
    title: `Money to ${faker.name.firstName()}`
  });
};

const adaIncome = (date: ?Date) => {
  const amount = (Math.random() * 1000) + 1;
  return adaTransaction({
    amount,
    date,
    type: 'adaIncome',
    title: `Money from ${faker.name.firstName()}`,
  });
};

const exchange = (date: ?Date) => {
  const amount = (Math.random() * 1000) + 1;
  const exchangeRate = (Math.random() * 1000) + 10;
  return generateTransaction({
    amount,
    date,
    title: 'ADA to ETH',
    type: 'exchange',
    currency: 'ada',
    exchange: `${amount.toFixed(2)} ADA for ${(amount / exchangeRate).toFixed(2)} ETH`,
    conversionRate: `1 ETH = ${exchangeRate.toFixed(2)} ADA`,
    transactionId: faker.finance.bitcoinAddress(),
  });
};

const transactionTypes = [cardTransaction, adaExpend, adaIncome, exchange];

const generateRandomTransactions = (count: number) => {
  const transactions = [];
  for (let i = 0; i < count; i += 1) {
    const typeFactory = Math.floor(Math.random() * (transactionTypes.length - 1));
    transactions.push(transactionTypes[typeFactory]());
  }
  return transactions;
};

const walletTransactions = {
  '13GvjwDkz8s8ZmGQjwVLNUXrNXdSmQa72x': [
    cardTransaction(new Date()),
    adaExpend(new Date()),
    adaIncome(moment().subtract(1, 'days').toDate())
  ].concat(generateRandomTransactions(30)),
  '1GrT8upXQCR4mzPpAeMfxqwZh2LF7PXsyn': [
    cardTransaction(moment().subtract(1, 'days').toDate()),
    adaExpend(new Date()),
    adaIncome()
  ],
  '1LYYdFD4RGLsYJT5tVQoLQEvfr7hJXHRuv': [
    cardTransaction(moment().subtract(1, 'days').toDate()),
    exchange()
  ],
  '1MKY9oAriyXvUKRBRsbrrT2JDHA8S9T2sf': [
    cardTransaction(new Date()),
  ],
  fd17fda6410ac905447840e33c1851a5: [
    exchange(new Date())
  ]
};

export const loadWalletTransactions = (data: {
  address: string,
  searchTerm: string,
  limit: number
}) => new Promise((resolve) => {
  setTimeout(() => {
    const { address, searchTerm } = data;
    const regexp = new RegExp(searchTerm, 'i');
    const transactions = walletTransactions[address] ? walletTransactions[address] : [];
    resolve({
      total: transactions.length,
      transactions: transactions
        .filter((t) => regexp.test(t.title)) // Filter by title search
        .sort((a, b) => { // Sort by date
          const aIsSmallerOrEqual = a.date < b.date ? 1 : 0;
          return a.date > b.date ? -1 : aIsSmallerOrEqual;
        })
        .slice(0, data.limit), // Limit number of results
    });
  }, 1000);
});
<commit_msg>calculate the total results based on the filtered transactions<commit_after>// @flow
import moment from 'moment';
import faker from 'faker';

let transactionsCount = 0;

type transactionType = {
  id: string,
  type: string,
  title: string,
  amount: number,
  currency: string,
  date: Date,
  description: string,
  exchange: ?string,
  conversionRate: ?string,
  transactionId: ?string,
};

const generateTransaction = (data: Object): transactionType => {
  transactionsCount += 1;
  let date = data.date;
  if (data.date == null) {
    date = moment().subtract(Math.floor(Math.random() * 10), 'days').toDate();
  }
  return Object.assign({}, {
    id: `t-id-${transactionsCount}`,
    description: faker.lorem.sentence(),
  }, data, { date });
};

const cardTransaction = (date: ?Date) => {
  const amount = -1 * ((Math.random() * 1000) + 1);
  return generateTransaction({
    amount,
    date,
    type: 'card',
    currency: '$',
    title: `Invoice to ${faker.company.companyName()}`,
    exchange: null,
    conversionRate: null,
    transactionId: null,
  });
};

const adaTransaction = (data: {
  amount: number,
  type: string,
  title: string,
  date: ?Date
}) => {
  const exchangeRate = (Math.random() * 1000) + 10;
  const { amount, type, title, date } = data;
  return generateTransaction({
    type,
    amount,
    title,
    date,
    currency: 'ada',
    exchange: `${amount.toFixed(2)} ADA for ${(amount / exchangeRate).toFixed(2)} USD`,
    conversionRate: `1 USD = ${exchangeRate.toFixed(2)} ADA`,
    transactionId: faker.finance.bitcoinAddress(),
  });
};

const adaExpend = (date: ?Date) => {
  const amount = -1 * ((Math.random() * 1000) + 1);
  return adaTransaction({
    amount,
    date,
    type: 'adaExpend',
    title: `Money to ${faker.name.firstName()}`
  });
};

const adaIncome = (date: ?Date) => {
  const amount = (Math.random() * 1000) + 1;
  return adaTransaction({
    amount,
    date,
    type: 'adaIncome',
    title: `Money from ${faker.name.firstName()}`,
  });
};

const exchange = (date: ?Date) => {
  const amount = (Math.random() * 1000) + 1;
  const exchangeRate = (Math.random() * 1000) + 10;
  return generateTransaction({
    amount,
    date,
    title: 'ADA to ETH',
    type: 'exchange',
    currency: 'ada',
    exchange: `${amount.toFixed(2)} ADA for ${(amount / exchangeRate).toFixed(2)} ETH`,
    conversionRate: `1 ETH = ${exchangeRate.toFixed(2)} ADA`,
    transactionId: faker.finance.bitcoinAddress(),
  });
};

const transactionTypes = [cardTransaction, adaExpend, adaIncome, exchange];

const generateRandomTransactions = (count: number) => {
  const transactions = [];
  for (let i = 0; i < count; i += 1) {
    const typeFactory = Math.floor(Math.random() * (transactionTypes.length - 1));
    transactions.push(transactionTypes[typeFactory]());
  }
  return transactions;
};

const walletTransactions = {
  '13GvjwDkz8s8ZmGQjwVLNUXrNXdSmQa72x': [
    cardTransaction(new Date()),
    adaExpend(new Date()),
    adaIncome(moment().subtract(1, 'days').toDate())
  ].concat(generateRandomTransactions(30)),
  '1GrT8upXQCR4mzPpAeMfxqwZh2LF7PXsyn': [
    cardTransaction(moment().subtract(1, 'days').toDate()),
    adaExpend(new Date()),
    adaIncome()
  ],
  '1LYYdFD4RGLsYJT5tVQoLQEvfr7hJXHRuv': [
    cardTransaction(moment().subtract(1, 'days').toDate()),
    exchange()
  ],
  '1MKY9oAriyXvUKRBRsbrrT2JDHA8S9T2sf': [
    cardTransaction(new Date()),
  ],
  fd17fda6410ac905447840e33c1851a5: [
    exchange(new Date())
  ]
};

export const loadWalletTransactions = (data: {
  address: string,
  searchTerm: string,
  limit: number
}) => new Promise((resolve) => {
  setTimeout(() => {
    const { address, searchTerm } = data;
    const regexp = new RegExp(searchTerm, 'i');
    let transactions = [];
    if (walletTransactions[address]) {
      transactions = (
        walletTransactions[address]
        .filter((t) => regexp.test(t.title)) // Filter by title search
        .sort((a, b) => { // Sort by date
          const aIsSmallerOrEqual = a.date < b.date ? 1 : 0;
          return a.date > b.date ? -1 : aIsSmallerOrEqual;
        })
        .slice(0, data.limit) // Limit number of results
      );
    }
    resolve({
      transactions,
      total: transactions.length
    });
  }, 1000);
});
<|endoftext|>