<commit_before>(function(global){

  "use strict";

  var fabric = global.fabric || (global.fabric = { }),
      extend = fabric.util.object.extend,
      min = fabric.util.array.min,
      max = fabric.util.array.max,
      invoke = fabric.util.array.invoke;

  if (fabric.Group) {
    return;
  }

  // lock-related properties, for use in fabric.Group#get
  // to enable locking behavior on group
  // when one of its objects has lock-related properties set
  var _lockProperties = {
    lockMovementX:  true,
    lockMovementY:  true,
    lockRotation:   true,
    lockScalingX:   true,
    lockScalingY:   true,
    lockUniScaling: true
  };

  /**
   * Group class
   * @class fabric.Group
   * @extends fabric.Object
   * @mixes fabric.Collection
   */
  fabric.Group = fabric.util.createClass(fabric.Object, fabric.Collection, /** @lends fabric.Group.prototype */ {

    /**
     * Type of an object
     * @type String
     * @default
     */
    type: 'group',

    /**
     * Constructor
     * @param {Object} objects Group objects
     * @param {Object} [options] Options object
     * @return {Object} thisArg
     */
    initialize: function(objects, options) {
      options = options || { };

      this._objects = objects || [];
      for (var i = this._objects.length; i--; ) {
        this._objects[i].group = this;
      }

      this.originalState = { };
      this.callSuper('initialize');

      this._calcBounds();
      this._updateObjectsCoords();

      if (options) {
        extend(this, options);
      }
      this._setOpacityIfSame();

      this.setCoords(true);
      this.saveCoords();
    },

    /**
     * @private
     */
    _updateObjectsCoords: function() {
      var groupDeltaX = this.left,
          groupDeltaY = this.top;

      this.forEachObject(function(object) {

        var objectLeft = object.get('left'),
            objectTop = object.get('top');

        object.set('originalLeft', objectLeft);
        object.set('originalTop', objectTop);

        object.set('left', objectLeft - groupDeltaX);
        object.set('top', objectTop - groupDeltaY);

        object.setCoords();

        // do not display corners of objects enclosed in a group
        object.__origHasControls = object.hasControls;
        object.hasControls = false;
      }, this);
    },

    /**
     * Returns string represenation of a group
     * @return {String}
     */
    toString: function() {
      return '#<fabric.Group: (' + this.complexity() + ')>';
    },

    /**
     * Returns an array of all objects in this group
     * @return {Array} group objects
     */
    getObjects: function() {
      return this._objects;
    },

    /**
     * Adds an object to a group; Then recalculates group's dimension, position.
     * @param {Object} object
     * @return {fabric.Group} thisArg
     * @chainable
     */
    addWithUpdate: function(object) {
      this._restoreObjectsState();
      this._objects.push(object);
      object.group = this;
      // since _restoreObjectsState set objects inactive
      this.forEachObject(function(o){ o.set('active', true); o.group = this; }, this);
      this._calcBounds();
      this._updateObjectsCoords();
      return this;
    },

    /**
     * Removes an object from a group; Then recalculates group's dimension, position.
     * @param {Object} object
     * @return {fabric.Group} thisArg
     * @chainable
     */
    removeWithUpdate: function(object) {
      this._restoreObjectsState();
      // since _restoreObjectsState set objects inactive
      this.forEachObject(function(o){ o.set('active', true); o.group = this; }, this);

      this.remove(object);
      this._calcBounds();
      this._updateObjectsCoords();
      return this;
    },

    /**
     * @private
     */
    _onObjectAdded: function(object) {
      object.group = this;
    },

    /**
     * @private
     */
    _onObjectRemoved: function(object) {
      delete object.group;
      object.set('active', false);
    },

    /**
     * Properties that are delegated to group objects when reading/writing
     * @param {Object} delegatedProperties
     */
    delegatedProperties: {
      fill:             true,
      opacity:          true,
      fontFamily:       true,
      fontWeight:       true,
      fontSize:         true,
      fontStyle:        true,
      lineHeight:       true,
      textDecoration:   true,
      textAlign:        true,
      backgroundColor:  true
    },

    /**
     * @private
     */
    _set: function(key, value) {
      if (key in this.delegatedProperties) {
        var i = this._objects.length;
        this[key] = value;
        while (i--) {
          this._objects[i].set(key, value);
        }
      }
      else {
        this[key] = value;
      }
    },

    /**
     * Returns object representation of an instance
     * @param {Array} propertiesToInclude
     * @return {Object} object representation of an instance
     */
    toObject: function(propertiesToInclude) {
      return extend(this.callSuper('toObject', propertiesToInclude), {
        objects: invoke(this._objects, 'toObject', propertiesToInclude)
      });
    },

    /**
     * Renders instance on a given context
     * @param {CanvasRenderingContext2D} ctx context to render instance on
     * @param {Boolean} [noTransform] When true, context is not transformed
     */
    render: function(ctx, noTransform) {
      // do not render if object is not visible
      if (!this.visible) return;

      ctx.save();
      this.transform(ctx);

      var groupScaleFactor = Math.max(this.scaleX, this.scaleY);

      this.clipTo && fabric.util.clipContext(this, ctx);

      //The array is now sorted in order of highest first, so start from end.
      for (var i = 0, len = this._objects.length; i < len; i++) {

        var object = this._objects[i],
            originalScaleFactor = object.borderScaleFactor,
            originalHasRotatingPoint = object.hasRotatingPoint;

        // do not render if object is not visible
        if (!object.visible) continue;

        object.borderScaleFactor = groupScaleFactor;
        object.hasRotatingPoint = false;

        object.render(ctx);

        object.borderScaleFactor = originalScaleFactor;
        object.hasRotatingPoint = originalHasRotatingPoint;
      }
      this.clipTo && ctx.restore();

      if (!noTransform && this.active) {
        this.drawBorders(ctx);
        this.drawControls(ctx);
      }
      ctx.restore();
      this.setCoords();
    },

    /**
     * Retores original state of each of group objects (original state is that which was before group was created).
     * @private
     * @return {fabric.Group} thisArg
     * @chainable
     */
    _restoreObjectsState: function() {
      this._objects.forEach(this._restoreObjectState, this);
      return this;
    },

    /**
     * Restores original state of a specified object in group
     * @private
     * @param {fabric.Object} object
     * @return {fabric.Group} thisArg
     */
    _restoreObjectState: function(object) {

      var groupLeft = this.get('left'),
          groupTop = this.get('top'),
          groupAngle = this.getAngle() * (Math.PI / 180),
          rotatedTop = Math.cos(groupAngle) * object.get('top') * this.get('scaleY') + Math.sin(groupAngle) * object.get('left') * this.get('scaleX'),
          rotatedLeft = -Math.sin(groupAngle) * object.get('top') * this.get('scaleY') + Math.cos(groupAngle) * object.get('left') * this.get('scaleX');

      object.setAngle(object.getAngle() + this.getAngle());

      object.set('left', groupLeft + rotatedLeft);
      object.set('top', groupTop + rotatedTop);

      object.set('scaleX', object.get('scaleX') * this.get('scaleX'));
      object.set('scaleY', object.get('scaleY') * this.get('scaleY'));

      object.setCoords();
      object.hasControls = object.__origHasControls;
      delete object.__origHasControls;
      object.set('active', false);
      object.setCoords();
      delete object.group;

      return this;
    },

    /**
     * Destroys a group (restoring state of its objects)
     * @return {fabric.Group} thisArg
     * @chainable
     */
    destroy: function() {
      return this._restoreObjectsState();
    },

    /**
     * Saves coordinates of this instance (to be used together with `hasMoved`)
     * @saveCoords
     * @return {fabric.Group} thisArg
     * @chainable
     */
    saveCoords: function() {
      this._originalLeft = this.get('left');
      this._originalTop = this.get('top');
      return this;
    },

    /**
     * Checks whether this group was moved (since `saveCoords` was called last)
     * @return {Boolean} true if an object was moved (since fabric.Group#saveCoords was called)
     */
    hasMoved: function() {
      return this._originalLeft !== this.get('left') ||
             this._originalTop !== this.get('top');
    },

    /**
     * Sets coordinates of all group objects
     * @return {fabric.Group} thisArg
     * @chainable
     */
    setObjectsCoords: function() {
      this.forEachObject(function(object) {
        object.setCoords();
      });
      return this;
    },

    /**
     * @private
     */
    _setOpacityIfSame: function() {
      var objects = this.getObjects(),
          firstValue = objects[0] ? objects[0].get('opacity') : 1;

      var isSameOpacity = objects.every(function(o) {
        return o.get('opacity') === firstValue;
      });

      if (isSameOpacity) {
        this.opacity = firstValue;
      }
    },

    /**
     * @private
     */
    _calcBounds: function() {
      var aX = [],
          aY = [],
          minX, minY, maxX, maxY, o, width, height,
          i = 0,
          len = this._objects.length;

      for (; i < len; ++i) {
        o = this._objects[i];
        o.setCoords();
        for (var prop in o.oCoords) {
          aX.push(o.oCoords[prop].x);
          aY.push(o.oCoords[prop].y);
        }
      }

      minX = min(aX);
      maxX = max(aX);
      minY = min(aY);
      maxY = max(aY);

      width = (maxX - minX) || 0;
      height = (maxY - minY) || 0;

      this.width = width;
      this.height = height;

      this.left = (minX + width / 2) || 0;
      this.top = (minY + height / 2) || 0;
    },

    /* _TO_SVG_START_ */
    /**
     * Returns svg representation of an instance
     * @return {String} svg representation of an instance
     */
    toSVG: function() {
      var objectsMarkup = [ ];
      for (var i = 0, len = this._objects.length; i < len; i++) {
        objectsMarkup.push(this._objects[i].toSVG());
      }

      return (
        '<g transform="' + this.getSvgTransform() + '">' +
          objectsMarkup.join('') +
        '</g>');
    },
    /* _TO_SVG_END_ */

    /**
     * Returns requested property
     * @param {String} prop Property to get
     * @return {Any}
     */
    get: function(prop) {
      if (prop in _lockProperties) {
        if (this[prop]) {
          return this[prop];
        }
        else {
          for (var i = 0, len = this._objects.length; i < len; i++) {
            if (this._objects[i][prop]) {
              return true;
            }
          }
          return false;
        }
      }
      else {
        if (prop in this.delegatedProperties) {
          return this._objects[0] && this._objects[0].get(prop);
        }
        return this[prop];
      }
    }
  });

  /**
   * Returns {@link fabric.Group} instance from an object representation
   * @static
   * @memberOf fabric.Group
   * @param {Object} object Object to create a group from
   * @param {Object} [options] Options object
   * @return {fabric.Group} An instance of fabric.Group
   */
  fabric.Group.fromObject = function(object, callback) {
    fabric.util.enlivenObjects(object.objects, function(enlivenedObjects) {
      delete object.objects;
      callback && callback(new fabric.Group(enlivenedObjects, object));
    });
  };

  /**
   * Indicates that instances of this type are async
   * @static
   * @memberOf fabric.Group
   * @type Boolean
   * @default
   */
  fabric.Group.async = true;

})(typeof exports !== 'undefined' ? exports : this);
<commit_msg>Allow flipping in fabric.Group to change the object positions permanently<commit_after>(function(global){

  "use strict";

  var fabric = global.fabric || (global.fabric = { }),
      extend = fabric.util.object.extend,
      min = fabric.util.array.min,
      max = fabric.util.array.max,
      invoke = fabric.util.array.invoke;

  if (fabric.Group) {
    return;
  }

  // lock-related properties, for use in fabric.Group#get
  // to enable locking behavior on group
  // when one of its objects has lock-related properties set
  var _lockProperties = {
    lockMovementX:  true,
    lockMovementY:  true,
    lockRotation:   true,
    lockScalingX:   true,
    lockScalingY:   true,
    lockUniScaling: true
  };

  /**
   * Group class
   * @class fabric.Group
   * @extends fabric.Object
   * @mixes fabric.Collection
   */
  fabric.Group = fabric.util.createClass(fabric.Object, fabric.Collection, /** @lends fabric.Group.prototype */ {

    /**
     * Type of an object
     * @type String
     * @default
     */
    type: 'group',

    /**
     * Constructor
     * @param {Object} objects Group objects
     * @param {Object} [options] Options object
     * @return {Object} thisArg
     */
    initialize: function(objects, options) {
      options = options || { };

      this._objects = objects || [];
      for (var i = this._objects.length; i--; ) {
        this._objects[i].group = this;
      }

      this.originalState = { };
      this.callSuper('initialize');

      this._calcBounds();
      this._updateObjectsCoords();

      if (options) {
        extend(this, options);
      }
      this._setOpacityIfSame();

      this.setCoords(true);
      this.saveCoords();
    },

    /**
     * @private
     */
    _updateObjectsCoords: function() {
      var groupDeltaX = this.left,
          groupDeltaY = this.top;

      this.forEachObject(function(object) {

        var objectLeft = object.get('left'),
            objectTop = object.get('top');

        object.set('originalLeft', objectLeft);
        object.set('originalTop', objectTop);

        object.set('left', objectLeft - groupDeltaX);
        object.set('top', objectTop - groupDeltaY);

        object.setCoords();

        // do not display corners of objects enclosed in a group
        object.__origHasControls = object.hasControls;
        object.hasControls = false;
      }, this);
    },

    /**
     * Returns string represenation of a group
     * @return {String}
     */
    toString: function() {
      return '#<fabric.Group: (' + this.complexity() + ')>';
    },

    /**
     * Returns an array of all objects in this group
     * @return {Array} group objects
     */
    getObjects: function() {
      return this._objects;
    },

    /**
     * Adds an object to a group; Then recalculates group's dimension, position.
     * @param {Object} object
     * @return {fabric.Group} thisArg
     * @chainable
     */
    addWithUpdate: function(object) {
      this._restoreObjectsState();
      this._objects.push(object);
      object.group = this;
      // since _restoreObjectsState set objects inactive
      this.forEachObject(function(o){ o.set('active', true); o.group = this; }, this);
      this._calcBounds();
      this._updateObjectsCoords();
      return this;
    },

    /**
     * Removes an object from a group; Then recalculates group's dimension, position.
     * @param {Object} object
     * @return {fabric.Group} thisArg
     * @chainable
     */
    removeWithUpdate: function(object) {
      this._restoreObjectsState();
      // since _restoreObjectsState set objects inactive
      this.forEachObject(function(o){ o.set('active', true); o.group = this; }, this);

      this.remove(object);
      this._calcBounds();
      this._updateObjectsCoords();
      return this;
    },

    /**
     * @private
     */
    _onObjectAdded: function(object) {
      object.group = this;
    },

    /**
     * @private
     */
    _onObjectRemoved: function(object) {
      delete object.group;
      object.set('active', false);
    },

    /**
     * Properties that are delegated to group objects when reading/writing
     * @param {Object} delegatedProperties
     */
    delegatedProperties: {
      fill:             true,
      opacity:          true,
      fontFamily:       true,
      fontWeight:       true,
      fontSize:         true,
      fontStyle:        true,
      lineHeight:       true,
      textDecoration:   true,
      textAlign:        true,
      backgroundColor:  true
    },

    /**
     * @private
     */
    _set: function(key, value) {
      if (key in this.delegatedProperties) {
        var i = this._objects.length;
        this[key] = value;
        while (i--) {
          this._objects[i].set(key, value);
        }
      }
      else {
        this[key] = value;
      }
    },

    /**
     * Returns object representation of an instance
     * @param {Array} propertiesToInclude
     * @return {Object} object representation of an instance
     */
    toObject: function(propertiesToInclude) {
      return extend(this.callSuper('toObject', propertiesToInclude), {
        objects: invoke(this._objects, 'toObject', propertiesToInclude)
      });
    },

    /**
     * Renders instance on a given context
     * @param {CanvasRenderingContext2D} ctx context to render instance on
     * @param {Boolean} [noTransform] When true, context is not transformed
     */
    render: function(ctx, noTransform) {
      // do not render if object is not visible
      if (!this.visible) return;

      ctx.save();
      this.transform(ctx);

      var groupScaleFactor = Math.max(this.scaleX, this.scaleY);

      this.clipTo && fabric.util.clipContext(this, ctx);

      //The array is now sorted in order of highest first, so start from end.
      for (var i = 0, len = this._objects.length; i < len; i++) {

        var object = this._objects[i],
            originalScaleFactor = object.borderScaleFactor,
            originalHasRotatingPoint = object.hasRotatingPoint;

        // do not render if object is not visible
        if (!object.visible) continue;

        object.borderScaleFactor = groupScaleFactor;
        object.hasRotatingPoint = false;

        object.render(ctx);

        object.borderScaleFactor = originalScaleFactor;
        object.hasRotatingPoint = originalHasRotatingPoint;
      }
      this.clipTo && ctx.restore();

      if (!noTransform && this.active) {
        this.drawBorders(ctx);
        this.drawControls(ctx);
      }
      ctx.restore();
      this.setCoords();
    },

    /**
     * Retores original state of each of group objects (original state is that which was before group was created).
     * @private
     * @return {fabric.Group} thisArg
     * @chainable
     */
    _restoreObjectsState: function() {
      this._objects.forEach(this._restoreObjectState, this);
      return this;
    },

    /**
     * Restores original state of a specified object in group
     * @private
     * @param {fabric.Object} object
     * @return {fabric.Group} thisArg
     */
    _restoreObjectState: function(object) {

      var center = object.getCenterPoint();
      center.x *= -1;
      center.y *= -1;
      var newOrigin = object.translateToOriginPoint(center, object.get('originX'), object.get('originY'));
      if (this.flipX) {
        object.set('flipX', true);
        object.set('left', newOrigin.x);
      }
      if (this.flipY) {
        object.set('flipY', true);
        object.set('top', newOrigin.y);
      }

      var groupLeft = this.get('left'),
          groupTop = this.get('top'),
          groupAngle = this.getAngle() * (Math.PI / 180),
          rotatedTop = Math.cos(groupAngle) * object.get('top') * this.get('scaleY') + Math.sin(groupAngle) * object.get('left') * this.get('scaleX'),
          rotatedLeft = -Math.sin(groupAngle) * object.get('top') * this.get('scaleY') + Math.cos(groupAngle) * object.get('left') * this.get('scaleX');

      object.setAngle(object.getAngle() + this.getAngle());

      object.set('left', groupLeft + rotatedLeft);
      object.set('top', groupTop + rotatedTop);

      object.set('scaleX', object.get('scaleX') * this.get('scaleX'));
      object.set('scaleY', object.get('scaleY') * this.get('scaleY'));

      object.setCoords();
      object.hasControls = object.__origHasControls;
      delete object.__origHasControls;
      object.set('active', false);
      object.setCoords();
      delete object.group;

      return this;
    },

    /**
     * Destroys a group (restoring state of its objects)
     * @return {fabric.Group} thisArg
     * @chainable
     */
    destroy: function() {
      return this._restoreObjectsState();
    },

    /**
     * Saves coordinates of this instance (to be used together with `hasMoved`)
     * @saveCoords
     * @return {fabric.Group} thisArg
     * @chainable
     */
    saveCoords: function() {
      this._originalLeft = this.get('left');
      this._originalTop = this.get('top');
      return this;
    },

    /**
     * Checks whether this group was moved (since `saveCoords` was called last)
     * @return {Boolean} true if an object was moved (since fabric.Group#saveCoords was called)
     */
    hasMoved: function() {
      return this._originalLeft !== this.get('left') ||
             this._originalTop !== this.get('top');
    },

    /**
     * Sets coordinates of all group objects
     * @return {fabric.Group} thisArg
     * @chainable
     */
    setObjectsCoords: function() {
      this.forEachObject(function(object) {
        object.setCoords();
      });
      return this;
    },

    /**
     * @private
     */
    _setOpacityIfSame: function() {
      var objects = this.getObjects(),
          firstValue = objects[0] ? objects[0].get('opacity') : 1;

      var isSameOpacity = objects.every(function(o) {
        return o.get('opacity') === firstValue;
      });

      if (isSameOpacity) {
        this.opacity = firstValue;
      }
    },

    /**
     * @private
     */
    _calcBounds: function() {
      var aX = [],
          aY = [],
          minX, minY, maxX, maxY, o, width, height,
          i = 0,
          len = this._objects.length;

      for (; i < len; ++i) {
        o = this._objects[i];
        o.setCoords();
        for (var prop in o.oCoords) {
          aX.push(o.oCoords[prop].x);
          aY.push(o.oCoords[prop].y);
        }
      }

      minX = min(aX);
      maxX = max(aX);
      minY = min(aY);
      maxY = max(aY);

      width = (maxX - minX) || 0;
      height = (maxY - minY) || 0;

      this.width = width;
      this.height = height;

      this.left = (minX + width / 2) || 0;
      this.top = (minY + height / 2) || 0;
    },

    /* _TO_SVG_START_ */
    /**
     * Returns svg representation of an instance
     * @return {String} svg representation of an instance
     */
    toSVG: function() {
      var objectsMarkup = [ ];
      for (var i = 0, len = this._objects.length; i < len; i++) {
        objectsMarkup.push(this._objects[i].toSVG());
      }

      return (
        '<g transform="' + this.getSvgTransform() + '">' +
          objectsMarkup.join('') +
        '</g>');
    },
    /* _TO_SVG_END_ */

    /**
     * Returns requested property
     * @param {String} prop Property to get
     * @return {Any}
     */
    get: function(prop) {
      if (prop in _lockProperties) {
        if (this[prop]) {
          return this[prop];
        }
        else {
          for (var i = 0, len = this._objects.length; i < len; i++) {
            if (this._objects[i][prop]) {
              return true;
            }
          }
          return false;
        }
      }
      else {
        if (prop in this.delegatedProperties) {
          return this._objects[0] && this._objects[0].get(prop);
        }
        return this[prop];
      }
    }
  });

  /**
   * Returns {@link fabric.Group} instance from an object representation
   * @static
   * @memberOf fabric.Group
   * @param {Object} object Object to create a group from
   * @param {Object} [options] Options object
   * @return {fabric.Group} An instance of fabric.Group
   */
  fabric.Group.fromObject = function(object, callback) {
    fabric.util.enlivenObjects(object.objects, function(enlivenedObjects) {
      delete object.objects;
      callback && callback(new fabric.Group(enlivenedObjects, object));
    });
  };

  /**
   * Indicates that instances of this type are async
   * @static
   * @memberOf fabric.Group
   * @type Boolean
   * @default
   */
  fabric.Group.async = true;

})(typeof exports !== 'undefined' ? exports : this);
<|endoftext|>


<commit_before>'use strict';

const { uniq, difference, get, isUndefined, merge } = require('lodash');

module.exports = async function() {
  // Set if is admin destination for middleware application.
  this.app.use(async (ctx, next) => {
    if (ctx.request.header['origin'] === 'http://localhost:4000') {
      ctx.request.header['x-forwarded-host'] = 'strapi';
    }

    ctx.request.admin = ctx.request.header['x-forwarded-host'] === 'strapi';

    await next();
  });

  /** Utils */

  const middlewareConfig = this.config.middleware;

  // check if a middleware exists
  const middlewareExists = key => {
    return !isUndefined(this.middleware[key]);
  };

  // check if a middleware is enabled
  const middlewareEnabled = key =>
    get(middlewareConfig, ['settings', key, 'enabled'], false) === true;

  // list of enabled middlewares
  const enabledMiddlewares = Object.keys(this.middleware).filter(
    middlewareEnabled
  );

  // Method to initialize middlewares and emit an event.
  const initialize = middlewareKey => {
    const module = this.middleware[middlewareKey].load;

    return new Promise((resolve, reject) => {
      const timeout = setTimeout(
        () =>
          reject(`(middleware: ${middlewareKey}) is taking too long to load.`),
        middlewareConfig.timeout || 1000
      );

      this.middleware[middlewareKey] = merge(
        this.middleware[middlewareKey],
        module
      );

      Promise.resolve()
        .then(() => module.initialize())
        .then(() => {
          clearTimeout(timeout);
          this.middleware[middlewareKey].loaded = true;
          resolve();
        })
        .catch(err => {
          clearTimeout(timeout);

          if (err) {
            return reject(err);
          }
        });
    });
  };

  // run the initialization of an array of middlewares sequentially
  const initMiddlewaresSeq = async middlewareArr => {
    for (let key of uniq(middlewareArr)) {
      await initialize(key);
    }
  };

  const middlewaresBefore = get(middlewareConfig, 'load.before', [])
    .filter(middlewareExists)
    .filter(middlewareEnabled);

  const middlewaresAfter = get(middlewareConfig, 'load.after', [])
    .filter(middlewareExists)
    .filter(middlewareEnabled);

  const middlewaresOrder = get(middlewareConfig, 'load.order', [])
    .filter(middlewareExists)
    .filter(middlewareEnabled);

  const unspecifiedMiddlewares = difference(
    enabledMiddlewares,
    middlewaresBefore,
    middlewaresOrder,
    middlewaresAfter
  );

  /**
   * Run init functions
   */

  // Run beforeInitialize of every middleware
  await Promise.all(
    enabledMiddlewares.map(key => {
      const { beforeInitialize } = this.middleware[key].load;
      if (typeof beforeInitialize === 'function') {
        return beforeInitialize();
      }
    })
  );

  // before
  await initMiddlewaresSeq(middlewaresBefore);

  // ordered // rest of middlewares
  await Promise.all([
    initMiddlewaresSeq(middlewaresOrder),
    Promise.all(unspecifiedMiddlewares.map(initialize)),
  ]);

  // after
  await initMiddlewaresSeq(middlewaresAfter);
};
<commit_msg>Fix beforeInitialize middlewares can modify order or middlewares<commit_after>'use strict';

const { uniq, difference, get, isUndefined, merge } = require('lodash');

module.exports = async function() {
  // Set if is admin destination for middleware application.
  this.app.use(async (ctx, next) => {
    if (ctx.request.header['origin'] === 'http://localhost:4000') {
      ctx.request.header['x-forwarded-host'] = 'strapi';
    }

    ctx.request.admin = ctx.request.header['x-forwarded-host'] === 'strapi';

    await next();
  });

  /** Utils */

  const middlewareConfig = this.config.middleware;

  // check if a middleware exists
  const middlewareExists = key => {
    return !isUndefined(this.middleware[key]);
  };

  // check if a middleware is enabled
  const middlewareEnabled = key =>
    get(middlewareConfig, ['settings', key, 'enabled'], false) === true;

  // list of enabled middlewares
  const enabledMiddlewares = Object.keys(this.middleware).filter(
    middlewareEnabled
  );

  // Method to initialize middlewares and emit an event.
  const initialize = middlewareKey => {
    const module = this.middleware[middlewareKey].load;

    return new Promise((resolve, reject) => {
      const timeout = setTimeout(
        () =>
          reject(`(middleware: ${middlewareKey}) is taking too long to load.`),
        middlewareConfig.timeout || 1000
      );

      this.middleware[middlewareKey] = merge(
        this.middleware[middlewareKey],
        module
      );

      Promise.resolve()
        .then(() => module.initialize())
        .then(() => {
          clearTimeout(timeout);
          this.middleware[middlewareKey].loaded = true;
          resolve();
        })
        .catch(err => {
          clearTimeout(timeout);

          if (err) {
            return reject(err);
          }
        });
    });
  };

  /**
   * Run init functions
   */

  // Run beforeInitialize of every middleware
  await Promise.all(
    enabledMiddlewares.map(key => {
      const { beforeInitialize } = this.middleware[key].load;
      if (typeof beforeInitialize === 'function') {
        return beforeInitialize();
      }
    })
  );

  // run the initialization of an array of middlewares sequentially
  const initMiddlewaresSeq = async middlewareArr => {
    for (let key of uniq(middlewareArr)) {
      await initialize(key);
    }
  };

  const middlewaresBefore = get(middlewareConfig, 'load.before', [])
    .filter(middlewareExists)
    .filter(middlewareEnabled);

  const middlewaresAfter = get(middlewareConfig, 'load.after', [])
    .filter(middlewareExists)
    .filter(middlewareEnabled);

  const middlewaresOrder = get(middlewareConfig, 'load.order', [])
    .filter(middlewareExists)
    .filter(middlewareEnabled);

  const unspecifiedMiddlewares = difference(
    enabledMiddlewares,
    middlewaresBefore,
    middlewaresOrder,
    middlewaresAfter
  );

  // before
  await initMiddlewaresSeq(middlewaresBefore);

  // ordered // rest of middlewares
  await Promise.all([
    initMiddlewaresSeq(middlewaresOrder),
    Promise.all(unspecifiedMiddlewares.map(initialize)),
  ]);

  // after
  await initMiddlewaresSeq(middlewaresAfter);
};
<|endoftext|>


<commit_before>define(
    ['ufojs', 'ufojs/errors', 'ufojs/tools/io/main' ,'ufojs/plistLib/main'],
    function(main, errors, io, plistLib)
{
    var goodPlistPath = './testdata/good.plist'
      , goodPlistData = {
            age: 28.92,
            'birth-date': new Date(Date.UTC(1983, 0, 21, 22, 23, 23)),
            glibberish: new plistLib.types.Data('hjkljh678fghj7654refghjudertghu7654redcvghuj765t'),
            names: {
                'given-name': 'John',
                surname: 'Dow'
            },
            five: new plistLib.types.Int(5),
            pets: [
                'Jonny',
                'Bello',
                {
                    'given-name': 'John',
                    type: 'snail'
                }
            ],
            yes: true,
            no: false
        }
        ;

    doh.register("plistLib.main", [
    /**
     * This is to document what the _comparePlists method is supposed to do
     */
    function Test__compareObjects(){
        var a = {a: 'a', date: new Date(), b: [1,2,3,4], c: 0.1234, d:{e:'f', g:'h', i:{j:'k'}}},
            b = {a: 'a', date: new Date(a.date.getTime()), b: [1,2,3,4], c: 0.1234, d:{e:'f', g:'h', i:{j:'k'}}};
        //they are not the same object
        doh.assertFalse(a === b);
        //but look the same
        doh.assertTrue(plistLib._comparePlists(a, b));

        //the same object compares to true, too of course
        doh.assertTrue(plistLib._comparePlists(a, a));

        //now they don't look the same anymore
        b.l = undefined;
        doh.assertFalse(plistLib._comparePlists(a, b));

        //'synchronous recursion' this is allowed
        b.l = a;
        a.l = a;
        doh.assertTrue(plistLib._comparePlists(a, b));

        //'asynchronous recursion' this is very forbidden
        // => should it just return false?
        b.l = a;
        a.l = b;
        doh.assertError(
            errors.Value,
            plistLib, '_comparePlists',
            [a, b],
            'a type of recursion where we stop comparing'
        );
    },
    /**
     * To see whether we read everything, here is our expected result
     * compared against a parsed plist file
     */
    {
        name: "Test_readPlist",
        setUp: function(){
            //Setup to do before runTest.
        },
        runTest: function() {
            //we expect the parsed file to look like this
            var deferred = new doh.Deferred()
              , loadHandler = function(err, data) {
                if(err) {
                    deferred.errback(err);
                    return;
                }
                try {
                    //parse the string to an object
                    var p = plistLib.readPlistFromString(data);
                    //compare
                    doh.assertTrue(plistLib._comparePlists(p, goodPlistData, true));
                    deferred.callback(true);
                } catch(e) {
                    deferred.errback(e);
                }
            };
            io.readFile(goodPlistPath, loadHandler);
            return deferred;
        },
        tearDown: function(){
            //cleanup to do after runTest.
        },
        timeout: 3000 // 3 second timeout is enough for anybody
    },
    /**
     * This checks whether we still have the same result after serialization.
     * That means: reading from XML => writing an XML string => reading
     * from the new XML again => compare
     */
    {
        name: "Test_readWriteReadPlist",
        setUp: function() {
            //Setup to do before runTest.
        },
        runTest: function(){
            var deferred = new doh.Deferred();
            var loadHandler = function(err, data) {
                if(err) {
                    deferred.errback(err);
                    return;
                }
                try {
                    //parse the string to an object
                    var p = plistLib.readPlistFromString(data),
                        // serialize the object again
                        s = plistLib.createPlistString(p),
                        // parse the serialization into an object again
                        p2 = plistLib.readPlistFromString(s);
                        // compare
                    doh.assertTrue(plistLib._comparePlists(p, p2, true));
                    deferred.callback(true);
                } catch(e) {
                    deferred.errback(e);
                }
            };
            io.readFile(goodPlistPath, loadHandler);
            return deferred;
        },
        tearDown: function(){
            //cleanup to do after runTest.
        },
        timeout: 3000
    },
    {
        name: "Test_readPlistFromFile",
        setUp: function() {},
        runTest: function(){
            var deferred = new doh.Deferred()
              , promise
              , data
              ;
            // sync api
            data = plistLib.readPlistFromFile(false, goodPlistPath);
            doh.assertTrue(plistLib._comparePlists(data, goodPlistData, true));

            // async api
            plistLib.readPlistFromFile(true, goodPlistPath)
                .then(function(data){
                    try {
                        doh.assertTrue(plistLib._comparePlists(data, goodPlistData, true));
                    }
                    catch(e) {
                        deferred.errback(e);
                        return;
                    }
                    deferred.callback(true);
                }, deferred.errback.bind(deferred));
            return deferred;
        },
        tearDown: function(){},
        timeout: 3000
    }

    ])
});
<commit_msg>removed test for readPlistFromFile and converted dependency to io/main into io/static<commit_after>define([
    'ufojs'
  , 'ufojs/errors'
  , 'ufojs/tools/io/static'
  , 'ufojs/plistLib/main'
], function(
    main
  , errors
  , staticIO
  , plistLib
) {
    var goodPlistPath = './testdata/good.plist'
      , goodPlistData = {
            age: 28.92,
            'birth-date': new Date(Date.UTC(1983, 0, 21, 22, 23, 23)),
            glibberish: new plistLib.types.Data('hjkljh678fghj7654refghjudertghu7654redcvghuj765t'),
            names: {
                'given-name': 'John',
                surname: 'Dow'
            },
            five: new plistLib.types.Int(5),
            pets: [
                'Jonny',
                'Bello',
                {
                    'given-name': 'John',
                    type: 'snail'
                }
            ],
            yes: true,
            no: false
        }
        ;

    doh.register("plistLib.main", [
    /**
     * This is to document what the _comparePlists method is supposed to do
     */
    function Test__compareObjects(){
        var a = {a: 'a', date: new Date(), b: [1,2,3,4], c: 0.1234, d:{e:'f', g:'h', i:{j:'k'}}},
            b = {a: 'a', date: new Date(a.date.getTime()), b: [1,2,3,4], c: 0.1234, d:{e:'f', g:'h', i:{j:'k'}}};
        //they are not the same object
        doh.assertFalse(a === b);
        //but look the same
        doh.assertTrue(plistLib._comparePlists(a, b));

        //the same object compares to true, too of course
        doh.assertTrue(plistLib._comparePlists(a, a));

        //now they don't look the same anymore
        b.l = undefined;
        doh.assertFalse(plistLib._comparePlists(a, b));

        //'synchronous recursion' this is allowed
        b.l = a;
        a.l = a;
        doh.assertTrue(plistLib._comparePlists(a, b));

        //'asynchronous recursion' this is very forbidden
        // => should it just return false?
        b.l = a;
        a.l = b;
        doh.assertError(
            errors.Value,
            plistLib, '_comparePlists',
            [a, b],
            'a type of recursion where we stop comparing'
        );
    },
    /**
     * To see whether we read everything, here is our expected result
     * compared against a parsed plist file
     */
    {
        name: "Test_readPlist",
        setUp: function(){
            //Setup to do before runTest.
        },
        runTest: function() {
            //we expect the parsed file to look like this
            var deferred = new doh.Deferred()
              , loadHandler = function(err, data) {
                if(err) {
                    deferred.errback(err);
                    return;
                }
                try {
                    //parse the string to an object
                    var p = plistLib.readPlistFromString(data);
                    //compare
                    doh.assertTrue(plistLib._comparePlists(p, goodPlistData, true));
                    deferred.callback(true);
                } catch(e) {
                    deferred.errback(e);
                }
            };
            staticIO.readFile({unified: loadHandler}, goodPlistPath);
            return deferred;
        },
        tearDown: function(){
            //cleanup to do after runTest.
        },
        timeout: 3000 // 3 second timeout is enough for anybody
    },
    /**
     * This checks whether we still have the same result after serialization.
     * That means: reading from XML => writing an XML string => reading
     * from the new XML again => compare
     */
    {
        name: "Test_readWriteReadPlist",
        setUp: function() {
            //Setup to do before runTest.
        },
        runTest: function(){
            var deferred = new doh.Deferred();
            var loadHandler = function(err, data) {
                if(err) {
                    deferred.errback(err);
                    return;
                }
                try {
                    //parse the string to an object
                    var p = plistLib.readPlistFromString(data),
                        // serialize the object again
                        s = plistLib.createPlistString(p),
                        // parse the serialization into an object again
                        p2 = plistLib.readPlistFromString(s);
                        // compare
                    doh.assertTrue(plistLib._comparePlists(p, p2, true));
                    deferred.callback(true);
                } catch(e) {
                    deferred.errback(e);
                }
            };
            staticIO.readFile({unified: loadHandler}, goodPlistPath);
            return deferred;
        },
        tearDown: function(){
            //cleanup to do after runTest.
        },
        timeout: 3000
    }
    ])
});
<|endoftext|>


<commit_before>const colors = require("colors");
const util = require("util");

const { CLIDebugger } = require("./cli");
const execute = require("truffle-contract/lib/execute");
const { DebugPrinter } = require("./printer");

class CLIDebugHook {
  constructor(config, runner) {
    this.config = config;
    this.runner = runner; // mocha runner (**not** lib/test/testrunner)
  }

  async debug(operation) {
    // turn off timeouts for the current runnable
    // HACK we don't turn it back on because it doesn't work...
    // tests that take a long time _after_ debug break just won't timeout
    this.disableTimeout();

    const {
      txHash,
      result,
      method: {
        abi: { name: methodName },
        args,
        contract: { contractName }
      }
    } = await this.invoke(operation);

    this.printStartTestHook({ contractName, methodName, args });

    const interpreter = await new CLIDebugger(this.config).run(txHash);
    await interpreter.start();

    this.printStopTestHook(operation.method);

    return result;
  }

  disableTimeout() {
    this.runner.currentRunnable.timeout(0);
  }

  async invoke(operation) {
    const method = await this.detectMethod(operation);
    const { action } = method;

    switch (action) {
      case "send": {
        const result = await operation;

        return { txHash: result.tx, method, result };
      }
      case "call": {
        const { contract, fn, abi, args, address } = method;

        // get the result of the call
        const result = await operation;

        // and replay it as a transaction so we can debug
        const { tx: txHash } = await execute.send.call(
          contract,
          fn,
          abi,
          address
        )(...args);

        return { txHash, method, result };
      }
      default: {
        throw new Error(`Unsupported action for debugging: ${action}`);
      }
    }
  }

  detectMethod(promiEvent) {
    return new Promise(accept => {
      for (let action of ["send", "call"]) {
        promiEvent.on(
          `execute:${action}:method`,
          ({ fn, abi, args, contract, address }) => {
            accept({
              fn,
              abi,
              args,
              address,
              contract,
              action
            });
          }
        );
      }
    });
  }

  printStartTestHook({ contractName, methodName, args }) {
    const formatOperation = (contractName, methodName, args) =>
      colors.bold(
        `${contractName}.${methodName}(${args.map(util.inspect).join(", ")})`
      );

    this.config.logger.log("");
    this.config.logger.log("  ...");
    this.config.logger.log(
      colors.cyan(
        ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
      )
    );
    this.config.logger.log("  Test run interrupted.");
    this.config.logger.log(
      `  Debugging ${formatOperation(contractName, methodName, args)}`
    );
    this.config.logger.log(
      colors.cyan(
        ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
      )
    );
    this.config.logger.log("");
  }

  printStopTestHook() {
    this.config.logger.log("");
    this.config.logger.log("");
    this.config.logger.log(
      colors.cyan(
        "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
      )
    );
    this.config.logger.log("  Debugger stopped. Test resuming");
    this.config.logger.log(
      colors.cyan(
        "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
      )
    );
    this.config.logger.log("  ...");
    this.config.logger.log("");
  }
}

module.exports = {
  CLIDebugHook
};
<commit_msg>Allow providing compilation result to debugger<commit_after>const colors = require("colors");
const util = require("util");

const { CLIDebugger } = require("./cli");
const execute = require("truffle-contract/lib/execute");
const { DebugPrinter } = require("./printer");

class CLIDebugHook {
  constructor(config, compilation, runner) {
    this.config = config;
    this.compilation = compilation;
    this.runner = runner; // mocha runner (**not** lib/test/testrunner)
  }

  async debug(operation) {
    // turn off timeouts for the current runnable
    // HACK we don't turn it back on because it doesn't work...
    // tests that take a long time _after_ debug break just won't timeout
    this.disableTimeout();

    const {
      txHash,
      result,
      method: {
        abi: { name: methodName },
        args,
        contract: { contractName }
      }
    } = await this.invoke(operation);

    this.printStartTestHook({ contractName, methodName, args });

    const interpreter = await new CLIDebugger(this.config, {
      compilation: this.compilation
    }).run(txHash);
    await interpreter.start();

    this.printStopTestHook(operation.method);

    return result;
  }

  async start() {}

  disableTimeout() {
    this.runner.currentRunnable.timeout(0);
  }

  async invoke(operation) {
    const method = await this.detectMethod(operation);
    const { action } = method;

    switch (action) {
      case "send": {
        const result = await operation;

        return { txHash: result.tx, method, result };
      }
      case "call": {
        const { contract, fn, abi, args, address } = method;

        // get the result of the call
        const result = await operation;

        // and replay it as a transaction so we can debug
        const { tx: txHash } = await execute.send.call(
          contract,
          fn,
          abi,
          address
        )(...args);

        return { txHash, method, result };
      }
      default: {
        throw new Error(`Unsupported action for debugging: ${action}`);
      }
    }
  }

  detectMethod(promiEvent) {
    return new Promise(accept => {
      for (let action of ["send", "call"]) {
        promiEvent.on(
          `execute:${action}:method`,
          ({ fn, abi, args, contract, address }) => {
            accept({
              fn,
              abi,
              args,
              address,
              contract,
              action
            });
          }
        );
      }
    });
  }

  printStartTestHook({ contractName, methodName, args }) {
    const formatOperation = (contractName, methodName, args) =>
      colors.bold(
        `${contractName}.${methodName}(${args.map(util.inspect).join(", ")})`
      );

    this.config.logger.log("");
    this.config.logger.log("  ...");
    this.config.logger.log(
      colors.cyan(
        ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
      )
    );
    this.config.logger.log("  Test run interrupted.");
    this.config.logger.log(
      `  Debugging ${formatOperation(contractName, methodName, args)}`
    );
    this.config.logger.log(
      colors.cyan(
        ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
      )
    );
    this.config.logger.log("");
  }

  printStopTestHook() {
    this.config.logger.log("");
    this.config.logger.log("");
    this.config.logger.log(
      colors.cyan(
        "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
      )
    );
    this.config.logger.log("  Debugger stopped. Test resuming");
    this.config.logger.log(
      colors.cyan(
        "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
      )
    );
    this.config.logger.log("  ...");
    this.config.logger.log("");
  }
}

module.exports = {
  CLIDebugHook
};
<|endoftext|>


<commit_before>(function(){
  /* globals SpaceDog:false */
  'use strict';

  var TYPE = 'tsygan@spacedog-schema';
  var LOG_PREFIX = 'com.tsygan::normalizer->schema::';

  (function(){

    this.schema = function(response){
      throw new Error('Unhandled normalization on schemas for content', response);
    };

    // Private helper to normalize schema fields
    var schemafield = function(name, hash, schemaId) {
      console.debug(LOG_PREFIX + 'schemafield <<', name, hash, schemaId);

      // XXX SpaceDog https://github.com/spacedog-io/services/issues/50
      // Currently, password field are mangled by the log sanitizer, restore a fake password field for the sake of the user model
      // Spoof in a skeleton password field here
      if(name === 'password')
        hash = {
          _type: 'string'
        };

      // If this is a pure SpaceDog object, insert our extra section
      if(!hash._extra)
        hash._extra = {};
      if(!hash._extra['com.tsygan::1.0::'])
        hash._extra['com.tsygan::1.0::'] = {};

      // Start with that
      var json = hash._extra['com.tsygan::1.0::'];

      // Preserve the SpaceDog exact type in there
      json.attributes.spacetypehard = hash._type;

      // If the object is incomplete, or if it's not a tsygan object, use SpaceDog properties to populate it
      // These properties are "as-is"
      ['lt', 'gt', 'lte', 'gte', 'required', 'array', 'examples', 'pattern'].forEach(function(key){
        if(!(key in json.attributes))
          json.attributes[key] = hash['_' + key];
      });

      // Values / enum-set mapping
      if(!('enum-set' in json.attributes))
        json.attributes['enum-set'] = hash['_values'];

      // If it's not there, build type from spacedog (left) to ours (right)
      if(!json.attributes.type){
        var mapit = {
          boolean: 'tsygan@boolean',
          enum: 'tsygan@enum',
          geopoint: 'tsygan@geocoordinates',
          stash: 'tsygan@json',
          text: 'tsygan@string',
          integer: 'tsygan@integer',
          long: 'tsygan@integer',
          float: 'tsygan@number',
          double: 'tsygan@number',
          string: 'tsygan@identifier',
          date: 'tsygan@date',// XXX not sure the service will accept timestamps on that
          time: 'tsygan@date',// XXX not sure the service will accept timestamps on that
          timestamp: 'tsygan@date',
          ref: 'belongsTo'
        };

        // If SpaceDog introduced a type we don't understand, that's pretty bad, but we survive
        if(!(hash._type in mapit)){
          console.warn('ALERT: unknown SpaceDog type: ', hash._type, ' - falling back on string to leave the data as-is.');
          hash._type = 'string';
        }
        json.attributes.type = mapit[hash._type];
        // If we have a ref + array, then it's a "many" type
        if(json.attributes.array && json.attributes.type === 'belongsTo')
          json.attributes.type = 'hasMany';
      }

      // No id? Generate one
      if(!json.id)
        json.id = SpaceDog.md5.crypt(JSON.stringify(hash));

      // No name? Insert it
      if(!json.attributes.name)
        json.attributes.name = name;

      // Type is always a schemafield obviously
      if(!json.type)
        json.type = 'tsygan@spacedog-schemafield';

      // Insert the relation ship if need be
      if(!json.relationships)
        json.relationships = {
          'parent-model': {
            data: {
              type: TYPE,
              id: schemaId
            }
          }
        };
      console.debug(LOG_PREFIX + 'schemafield >>', json);
      return json;
    };

    this.schema.delete = function(response, url){
      console.debug(LOG_PREFIX + 'delete <<', response);
      var json = {
        data: {
          // XXX this is dangerous, and necessary because of % (for eg) being allowed in urls
          id: decodeURIComponent(url.split('/').pop()),
          type: TYPE
        }
      };
      console.debug(LOG_PREFIX + 'delete >>', json);
      return json;
    };

    this.schema.put = function(response){
      console.debug(LOG_PREFIX + 'put <<', response);
      var payload = {meta: response};
      payload.data = {
        id: response.id,
        type: TYPE // XXX can't trust payload.meta.type because...
      };
      delete response.id;
      delete response.type;
      console.debug(LOG_PREFIX + 'put >>', payload);
      return payload;
    };

    // This will handle responses for all requests to the "/1/schema" endpoint, unless there is a more specialized one
    this.schema.get = function(response, url){
      console.debug(LOG_PREFIX + 'get <<', JSON.stringify(response, null, 2));

      var singular = url.split('/').length > 5;

      var output = {
        data: [],
        meta: {}
      };

      // Denormalized fields for all schemas will leave here
      var attrs = output.included = [];

      // For each schema
      Object.keys(response).forEach(function(schemaId, index) {
        // We will have relations to fields
        var ids = [];
        // And embedded fields as well
        output.data[index] = {
          // Ember id of the schema is actually the name of the schema
          id: schemaId,
          // Type is schema
          type: TYPE,
          // Add the fields will be linked here
          relationships: {
            fields: {
              data: ids
            }
          }
        };

        // Remove useless/spurious attributes
        // Ignore these two entirely from SpaceDog
        delete response[schemaId]._id;
        delete response[schemaId]._type;
        // We don't need a definition for id fields, since we use our own internally
        delete response[schemaId].id;

        // Now, the remainder is actual fields
       Object.keys(response[schemaId]).forEach(function(attrName){
         // Insert the field into the denormalized map, and link it
          var field = schemafield(attrName, response[schemaId][attrName], schemaId);

          ids.push({
            id: field.id,
            type: field.type
          });

          attrs.push(field);
        });
      });

      // Just one object? Pop it
      if(singular)
        output.data = output.data.pop();
      console.debug(LOG_PREFIX + 'get >>', JSON.stringify(output, null, 2));
      return output;
    };

  }).apply(this.normalize || (this.normalize = {}));
}).apply(this.SpaceDog || (this.SpaceDog = {}));

<commit_msg>Mute debug slightly<commit_after>(function(){
  /* globals SpaceDog:false */
  'use strict';

  var TYPE = 'tsygan@spacedog-schema';
  var LOG_PREFIX = 'com.tsygan::normalizer->schema::';

  (function(){

    this.schema = function(response){
      throw new Error('Unhandled normalization on schemas for content', response);
    };

    // Private helper to normalize schema fields
    var schemafield = function(name, hash, schemaId) {
      console.debug(LOG_PREFIX + 'schemafield <<', name, hash, schemaId);

      // XXX SpaceDog https://github.com/spacedog-io/services/issues/50
      // Currently, password field are mangled by the log sanitizer, restore a fake password field for the sake of the user model
      // Spoof in a skeleton password field here
      if(name === 'password')
        hash = {
          _type: 'string'
        };

      // If this is a pure SpaceDog object, insert our extra section
      if(!hash._extra)
        hash._extra = {};
      if(!hash._extra['com.tsygan::1.0::'])
        hash._extra['com.tsygan::1.0::'] = {};

      // Start with that
      var json = hash._extra['com.tsygan::1.0::'];

      // Preserve the SpaceDog exact type in there
      json.attributes.spacetypehard = hash._type;

      // If the object is incomplete, or if it's not a tsygan object, use SpaceDog properties to populate it
      // These properties are "as-is"
      ['lt', 'gt', 'lte', 'gte', 'required', 'array', 'examples', 'pattern'].forEach(function(key){
        if(!(key in json.attributes))
          json.attributes[key] = hash['_' + key];
      });

      // Values / enum-set mapping
      if(!('enum-set' in json.attributes))
        json.attributes['enum-set'] = hash['_values'];

      // If it's not there, build type from spacedog (left) to ours (right)
      if(!json.attributes.type){
        var mapit = {
          boolean: 'tsygan@boolean',
          enum: 'tsygan@enum',
          geopoint: 'tsygan@geocoordinates',
          stash: 'tsygan@json',
          text: 'tsygan@string',
          integer: 'tsygan@integer',
          long: 'tsygan@integer',
          float: 'tsygan@number',
          double: 'tsygan@number',
          string: 'tsygan@identifier',
          date: 'tsygan@date',// XXX not sure the service will accept timestamps on that
          time: 'tsygan@date',// XXX not sure the service will accept timestamps on that
          timestamp: 'tsygan@date',
          ref: 'belongsTo'
        };

        // If SpaceDog introduced a type we don't understand, that's pretty bad, but we survive
        if(!(hash._type in mapit)){
          console.warn('ALERT: unknown SpaceDog type: ', hash._type, ' - falling back on string to leave the data as-is.');
          hash._type = 'string';
        }
        json.attributes.type = mapit[hash._type];
        // If we have a ref + array, then it's a "many" type
        if(json.attributes.array && json.attributes.type === 'belongsTo')
          json.attributes.type = 'hasMany';
      }

      // No id? Generate one
      if(!json.id)
        json.id = SpaceDog.md5.crypt(JSON.stringify(hash));

      // No name? Insert it
      if(!json.attributes.name)
        json.attributes.name = name;

      // Type is always a schemafield obviously
      if(!json.type)
        json.type = 'tsygan@spacedog-schemafield';

      // Insert the relation ship if need be
      if(!json.relationships)
        json.relationships = {
          'parent-model': {
            data: {
              type: TYPE,
              id: schemaId
            }
          }
        };
      console.debug(LOG_PREFIX + 'schemafield >>', json);
      return json;
    };

    this.schema.delete = function(response, url){
      console.debug(LOG_PREFIX + 'delete <<', response);
      var json = {
        data: {
          // XXX this is dangerous, and necessary because of % (for eg) being allowed in urls
          id: decodeURIComponent(url.split('/').pop()),
          type: TYPE
        }
      };
      console.debug(LOG_PREFIX + 'delete >>', json);
      return json;
    };

    this.schema.put = function(response){
      console.debug(LOG_PREFIX + 'put <<', response);
      var payload = {meta: response};
      payload.data = {
        id: response.id,
        type: TYPE // XXX can't trust payload.meta.type because...
      };
      delete response.id;
      delete response.type;
      console.debug(LOG_PREFIX + 'put >>', payload);
      return payload;
    };

    // This will handle responses for all requests to the "/1/schema" endpoint, unless there is a more specialized one
    this.schema.get = function(response, url){
      console.debug(LOG_PREFIX + 'get <<', response);

      var singular = url.split('/').length > 5;

      var output = {
        data: [],
        meta: {}
      };

      // Denormalized fields for all schemas will leave here
      var attrs = output.included = [];

      // For each schema
      Object.keys(response).forEach(function(schemaId, index) {
        // We will have relations to fields
        var ids = [];
        // And embedded fields as well
        output.data[index] = {
          // Ember id of the schema is actually the name of the schema
          id: schemaId,
          // Type is schema
          type: TYPE,
          // Add the fields will be linked here
          relationships: {
            fields: {
              data: ids
            }
          }
        };

        // Remove useless/spurious attributes
        // Ignore these two entirely from SpaceDog
        delete response[schemaId]._id;
        delete response[schemaId]._type;
        // We don't need a definition for id fields, since we use our own internally
        delete response[schemaId].id;

        // Now, the remainder is actual fields
       Object.keys(response[schemaId]).forEach(function(attrName){
         // Insert the field into the denormalized map, and link it
          var field = schemafield(attrName, response[schemaId][attrName], schemaId);

          ids.push({
            id: field.id,
            type: field.type
          });

          attrs.push(field);
        });
      });

      // Just one object? Pop it
      if(singular)
        output.data = output.data.pop();
      console.debug(LOG_PREFIX + 'get >>', output);
      return output;
    };

  }).apply(this.normalize || (this.normalize = {}));
}).apply(this.SpaceDog || (this.SpaceDog = {}));

<|endoftext|>


<commit_before>/*
   Behaviour v1.1 by Ben Nolan, June 2005. Based largely on the work
   of Simon Willison (see comments by Simon below).

   Description:

   	Uses css selectors to apply javascript behaviours to enable
   	unobtrusive javascript in html documents.

   Usage:

        Behaviour.specify('b.someclass', 'myrules.alert', 10, function(element) {
            element.onclick = function() {
                alert(this.innerHTML);
            }
        });
        Behaviour.specify('#someid u', 'myrules.blah', 0, function(element) {
            element.onmouseover = function() {
                this.innerHTML = "BLAH!";
            }
        });

	// Call Behaviour.apply() to re-apply the rules (if you
	// update the dom, etc).

   License:

   	This file is entirely BSD licensed.

   More information:

   	http://ripcord.co.nz/behaviour/

*/

var Behaviour = (function() {
    var storage = [{selector: '', id: '_deprecated', priority: 0}];
    return {

    /**
     * Specifies something to do when an element matching a CSS selector is encountered.
     * @param {String} selector a CSS selector triggering your behavior
     * @param {String} id combined with selector, uniquely identifies this behavior; prevents duplicate registrations
     * @param {Number} priority relative position of this behavior in case multiple apply to a given element; lower numbers applied first (sorted by id then selector in case of tie); choose 0 if you do not care
     * @param {Function} behavior callback function taking one parameter, a (DOM) {@link Element}, and returning void
     */
    specify : function(selector, id, priority, behavior) {
        for (var i = 0; i < storage.length; i++) {
            if (storage[i].selector == selector && storage[i].id == id) {
                storage.splice(i, 1);
                break;
            }
        }
        storage.push({selector: selector, id: id, priority: priority, behavior: behavior});
        storage.sort(function(a, b) {
            var location = a.priority - b.priority;
            return location != 0 ? location : a.id < b.id ? -1 : a.id > b.id ? 1 : a.selector < b.selector ? -1 : a.selector > b.selector ? 1 : 0;
        });
    },

        /** @deprecated For backward compatibility only; use {@link specify} instead. */
	list : new Array,

        /** @deprecated For backward compatibility only; use {@link specify} instead. */
	register : function(sheet){
		Behaviour.list.push(sheet);
	},

	start : function(){
		Behaviour.addLoadEvent(function(){
			Behaviour.apply();
		});
	},

	apply : function(){
        this.applySubtree(document);
    },

    /**
     * Applies behaviour rules to a subtree/subforest.
     *
     * @param {HTMLElement|HTMLElement[]} startNode
     *      Subtree/forest to apply rules.
     *
     *      Within a single subtree, rules are the outer loop and the nodes in the tree are the inner loop,
     *      and sometimes the behaviour rules rely on this ordering to work correctly. When you pass a forest,
     *      this semantics is preserved.
     */
    applySubtree : function(startNode,includeSelf) {
        if (!(startNode instanceof Array)) {
            startNode = [startNode];
        }
        storage._each(function (registration) {
            if (registration.id == '_deprecated') {
                Behaviour.list._each(function(sheet) {
                    for (var selector in sheet){
                        startNode._each(function (n) {
                            var list = findElementsBySelector(n, selector, includeSelf);
                            if (list.length > 0) { // just to simplify setting of a breakpoint.
                                //console.log('deprecated:' + selector + ' on ' + list.length + ' elements');
                                list._each(sheet[selector]);
                            }
                        });
                    }
                });
            } else {
                startNode._each(function (node) {
                    var list = findElementsBySelector(node, registration.selector, includeSelf);
                    if (list.length > 0) {
                        //console.log(registration.id + ':' + registration.selector + ' @' + registration.priority + ' on ' + list.length + ' elements');
                        list._each(registration.behavior);
                    }
                });
            }
        });
    },

    addLoadEvent : function(func){
		var oldonload = window.onload;

		if (typeof window.onload != 'function') {
			window.onload = func;
		} else {
			window.onload = function() {
				oldonload();
				func();
			}
		}
	}
}})();

Behaviour.start();

/*
   The following code is Copyright (C) Simon Willison 2004.

   document.getElementsBySelector(selector)
   - returns an array of element objects from the current document
     matching the CSS selector. Selectors can contain element names,
     class names and ids and can be nested. For example:

       elements = document.getElementsBySelect('div#main p a.external')

     Will return an array of all 'a' elements with 'external' in their
     class attribute that are contained inside 'p' elements that are
     contained inside the 'div' element which has id="main"

   New in version 0.4: Support for CSS2 and CSS3 attribute selectors:
   See http://www.w3.org/TR/css3-selectors/#attribute-selectors

   Version 0.4 - Simon Willison, March 25th 2003
   -- Works in Phoenix 0.5, Mozilla 1.3, Opera 7, Internet Explorer 6, Internet Explorer 5 on Windows
   -- Opera 7 fails
*/

function findElementsBySelector(startNode,selector,includeSelf) {
    if(includeSelf) {
        function isSelfOrChild(c) {
          while(true) {
              if(startNode == c) return true;
              if(c == null) return false;
              c = c.parentNode;
          }
        }
        return Prototype.Selector.select(selector, startNode.parentNode).filter(isSelfOrChild);
    } else {
        return Prototype.Selector.select(selector, startNode);
    }
}

document.getElementsBySelector = function(selector) {
    return findElementsBySelector(document,selector);
}

/* That revolting regular expression explained
/^(\w+)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/
  \---/  \---/\-------------/    \-------/
    |      |         |               |
    |      |         |           The value
    |      |    ~,|,^,$,* or =
    |   Attribute
   Tag
*/
<commit_msg>addLoadEvent failed to pass on the Event object. Does not matter for any handlers used in Jenkins, but can break third-party frameworks. In particular mixpanel-2.1.min.js seems to detect when this mistake is made and somehow disables some Jenkins handlers, causing e.g. “LOADING” to stay on job config pages and breaking behavior.js (but only on Firefox).<commit_after>/*
   Behaviour v1.1 by Ben Nolan, June 2005. Based largely on the work
   of Simon Willison (see comments by Simon below).

   Description:

   	Uses css selectors to apply javascript behaviours to enable
   	unobtrusive javascript in html documents.

   Usage:

        Behaviour.specify('b.someclass', 'myrules.alert', 10, function(element) {
            element.onclick = function() {
                alert(this.innerHTML);
            }
        });
        Behaviour.specify('#someid u', 'myrules.blah', 0, function(element) {
            element.onmouseover = function() {
                this.innerHTML = "BLAH!";
            }
        });

	// Call Behaviour.apply() to re-apply the rules (if you
	// update the dom, etc).

   License:

   	This file is entirely BSD licensed.

   More information:

   	http://ripcord.co.nz/behaviour/

*/

var Behaviour = (function() {
    var storage = [{selector: '', id: '_deprecated', priority: 0}];
    return {

    /**
     * Specifies something to do when an element matching a CSS selector is encountered.
     * @param {String} selector a CSS selector triggering your behavior
     * @param {String} id combined with selector, uniquely identifies this behavior; prevents duplicate registrations
     * @param {Number} priority relative position of this behavior in case multiple apply to a given element; lower numbers applied first (sorted by id then selector in case of tie); choose 0 if you do not care
     * @param {Function} behavior callback function taking one parameter, a (DOM) {@link Element}, and returning void
     */
    specify : function(selector, id, priority, behavior) {
        for (var i = 0; i < storage.length; i++) {
            if (storage[i].selector == selector && storage[i].id == id) {
                storage.splice(i, 1);
                break;
            }
        }
        storage.push({selector: selector, id: id, priority: priority, behavior: behavior});
        storage.sort(function(a, b) {
            var location = a.priority - b.priority;
            return location != 0 ? location : a.id < b.id ? -1 : a.id > b.id ? 1 : a.selector < b.selector ? -1 : a.selector > b.selector ? 1 : 0;
        });
    },

        /** @deprecated For backward compatibility only; use {@link specify} instead. */
	list : new Array,

        /** @deprecated For backward compatibility only; use {@link specify} instead. */
	register : function(sheet){
		Behaviour.list.push(sheet);
	},

	start : function(){
		Behaviour.addLoadEvent(function(){
			Behaviour.apply();
		});
	},

	apply : function(){
        this.applySubtree(document);
    },

    /**
     * Applies behaviour rules to a subtree/subforest.
     *
     * @param {HTMLElement|HTMLElement[]} startNode
     *      Subtree/forest to apply rules.
     *
     *      Within a single subtree, rules are the outer loop and the nodes in the tree are the inner loop,
     *      and sometimes the behaviour rules rely on this ordering to work correctly. When you pass a forest,
     *      this semantics is preserved.
     */
    applySubtree : function(startNode,includeSelf) {
        if (!(startNode instanceof Array)) {
            startNode = [startNode];
        }
        storage._each(function (registration) {
            if (registration.id == '_deprecated') {
                Behaviour.list._each(function(sheet) {
                    for (var selector in sheet){
                        startNode._each(function (n) {
                            var list = findElementsBySelector(n, selector, includeSelf);
                            if (list.length > 0) { // just to simplify setting of a breakpoint.
                                //console.log('deprecated:' + selector + ' on ' + list.length + ' elements');
                                list._each(sheet[selector]);
                            }
                        });
                    }
                });
            } else {
                startNode._each(function (node) {
                    var list = findElementsBySelector(node, registration.selector, includeSelf);
                    if (list.length > 0) {
                        //console.log(registration.id + ':' + registration.selector + ' @' + registration.priority + ' on ' + list.length + ' elements');
                        list._each(registration.behavior);
                    }
                });
            }
        });
    },

    addLoadEvent : function(func){
		var oldonload = window.onload;

		if (typeof window.onload != 'function') {
			window.onload = func;
		} else {
			window.onload = function(e) {
				oldonload(e);
				func(e);
			}
		}
	}
}})();

Behaviour.start();

/*
   The following code is Copyright (C) Simon Willison 2004.

   document.getElementsBySelector(selector)
   - returns an array of element objects from the current document
     matching the CSS selector. Selectors can contain element names,
     class names and ids and can be nested. For example:

       elements = document.getElementsBySelect('div#main p a.external')

     Will return an array of all 'a' elements with 'external' in their
     class attribute that are contained inside 'p' elements that are
     contained inside the 'div' element which has id="main"

   New in version 0.4: Support for CSS2 and CSS3 attribute selectors:
   See http://www.w3.org/TR/css3-selectors/#attribute-selectors

   Version 0.4 - Simon Willison, March 25th 2003
   -- Works in Phoenix 0.5, Mozilla 1.3, Opera 7, Internet Explorer 6, Internet Explorer 5 on Windows
   -- Opera 7 fails
*/

function findElementsBySelector(startNode,selector,includeSelf) {
    if(includeSelf) {
        function isSelfOrChild(c) {
          while(true) {
              if(startNode == c) return true;
              if(c == null) return false;
              c = c.parentNode;
          }
        }
        return Prototype.Selector.select(selector, startNode.parentNode).filter(isSelfOrChild);
    } else {
        return Prototype.Selector.select(selector, startNode);
    }
}

document.getElementsBySelector = function(selector) {
    return findElementsBySelector(document,selector);
}

/* That revolting regular expression explained
/^(\w+)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/
  \---/  \---/\-------------/    \-------/
    |      |         |               |
    |      |         |           The value
    |      |    ~,|,^,$,* or =
    |   Attribute
   Tag
*/
<|endoftext|>


<commit_before>/*
The MIT Licence, for code from kryogenix.org

Code downloaded from the Browser Experiments section
of kryogenix.org is licenced under the so-called MIT
licence. The licence is below.

Copyright (c) 1997-date Stuart Langridge

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the
Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
Usage
=====

Add the "sortable" CSS class to a table to make it sortable.
The first column must be always table header, and the rest must be table data.
(the script seems to support rows to be fixed at the bottom, but haven't figured out how to use it.)

If the table data is sorted to begin with, you can add 'initialSortDir="up|down"' to the
corresponding column in the header row to display the direction icon from the beginning.
This is recommended to provide a visual cue that the table can be sorted.

The script guesses the table data, and try to use the right sorting algorithm.
But you can override this behavior by having 'data="..."' attribute on each row,
in which case the sort will be done on that field.
*/

var Sortable = (function() {

    function Sortable(table) {
        this.table = table;
        this.arrows = [];

        var firstRow = this.getFirstRow();
        if (!firstRow) return;

        // We have a first row: assume it's the header, and make its contents clickable links
        firstRow.each(function (cell){
            cell.innerHTML = ''+this.getInnerText(cell)+'';
            this.arrows.push(cell.firstChild.lastChild);

            var self = this;
            cell.firstChild.onclick = function () {
                self.onClicked(this);
                return false;
            };
        }.bind(this));

        // figure out the initial sort preference
        this.pref = this.getStoredPreference();
        if (this.pref == null) {
            firstRow.each(function (cell,i){
                var initialSortDir = cell.getAttribute("initialSortDir");
                if (initialSortDir != null) {
                    this.pref = {column:i, direction:arrowTable[initialSortDir]};
                }
            }.bind(this));
        }

        this.refresh();
    }

    Sortable.prototype = {
        /**
         * SPAN tags that we use to render directional arrows, for each columns.
         */
        arrows : null /*Array*/,

        /**
         * Current sort preference.
         */
        pref : null /* { column:int, direction:arrow } */,

        getFirstRow : function() {
            if (this.table.rows && this.table.rows.length > 0) {
                return $A(this.table.rows[0].cells);
            }
            return null;
        },

        getDataRows : function() {
            var newRows = [];
            var rows = this.table.rows;
            for (var j = 1; j < rows.length; j++) {
                newRows.push($(rows[j]));
            }
            return newRows;
        },

        /**
         * If there's a persisted sort direction setting, retrieve it
         */
        getStoredPreference : function() {
            var key = this.getStorageKey();
            if(storage.hasKey(key)){
                var val = storage.getItem(key);
                if(val){
                    var vals = val.split(":");
                    if(vals.length == 2) {
                        return {column:parseInt(vals[0]), direction:arrowTable[vals[1]]};
                    }
                }
            }
            return null;
        },

        getStorageKey : function() {
            var uri = document.location;
            var tableIndex = this.getIndexOfSortableTable();
            return "ts_direction::" + uri + "::" + tableIndex;
        },

        savePreference : function() {
            var key = this.getStorageKey();
            storage.setItem(key, this.pref.column + ":" + this.pref.direction.id);
        },

        /**
         * Determine the sort function for the specified column
         */
        getSorter : function(column) {
            var rows = this.table.rows;
            if (rows.length <= 1)   return sorter.fallback;

            var itm = this.extractData(rows[1].cells[column]).trim();
            return sorter.determine(itm);
        },

        /**
         * Called when the column header gets clicked.
         */
        onClicked : function(lnk) {
            var arrow = lnk.lastChild;
            var th = lnk.parentNode;

            var column = th.cellIndex;
            if (column==(this.pref||{}).column) {
                // direction change on the same row
                this.pref.direction = this.pref.direction.next;
            } else {
                this.pref = {column:column, direction: arrow.sortdir||arrowTable.up};
            }

            arrow.sortdir = this.pref.direction; // remember the last sort direction on this column

            this.refresh();
            this.savePreference();
        },

        /**
         * Call when data has changed. Reapply the current sort setting to the existing data rows.
         * @since 1.484
         */
        refresh : function() {
            if (this.pref==null)     return; // not sorting

            var column = this.pref.column;
            var dir = this.pref.direction;

            var s = this.getSorter(column);
            if(dir === arrowTable.up) {// ascending
                s = sorter.reverse(s);
            }

            // we allow some rows to stick to the top and bottom, so that is our first sort criteria
            // regardless of the sort function
            function rowPos(r) {
                if (r.hasClassName("sorttop"))      return 0;
                if (r.hasClassName("sortbottom"))   return 2;
                return 1;
            }

            var rows = this.getDataRows();
            rows.sort(function(a,b) {
                var x = rowPos(a)-rowPos(b);
                if (x!=0)   return x;

                return s(
                    this.extractData(a.cells[column]),
                    this.extractData(b.cells[column]));
            }.bind(this));

            rows.each(function (e) {
                this.table.tBodies[0].appendChild(e);
            }.bind(this));

            // update arrow rendering
            this.arrows.each(function(e,i){
                e.innerHTML = ((i==column) ? dir : arrowTable.none).text;
            });
        },

        getIndexOfSortableTable : function(){
            return $(document.body).select("TABLE.sortable").indexOf(this.table);
        },

        getInnerText : function(el) {
        	if (typeof el == "string") return el;
        	if (typeof el == "undefined") { return el }
        	if (el.innerText) return el.innerText;	//Not needed but it is faster
        	var str = "";

        	var cs = el.childNodes;
        	var l = cs.length;
        	for (var i = 0; i < l; i++) {
        		switch (cs[i].nodeType) {
        			case 1: //ELEMENT_NODE
        				str += this.getInnerText(cs[i]);
        				break;
        			case 3:	//TEXT_NODE
        				str += cs[i].nodeValue;
        				break;
        		}
        	}
        	return str;
        },

        // extract data for sorting from a cell
        extractData : function(x) {
          if(x==null) return '';
          var data = x.getAttribute("data");
          if(data!=null)
            return data;
          return this.getInnerText(x);
        }
    };

    var arrowTable = {
        up: {
            id: "up",
            text: "  ↑"
        },
        down: {
            id: "down",
            text: "  ↓"
        },
        none: {
            id: "none",
            text: "   "
        },
        lnkRef: null
    };

    arrowTable.up.next = arrowTable.down;
    arrowTable.down.next = arrowTable.up;

    // available sort functions
    var sorter = {
        date : function(a,b) {
            function toDt(x) {
              // y2k notes: two digit years less than 50 are treated as 20XX, greater than 50 are treated as 19XX
              if (x.length == 10) {
                  return x.substr(6,4)+x.substr(3,2)+x.substr(0,2);
              } else {
                  var yr = x.substr(6,2);
                  if (parseInt(yr) < 50) { yr = '20'+yr; } else { yr = '19'+yr; }
                  return yr+x.substr(3,2)+x.substr(0,2);
              }
            }

            var dt1 = toDt(a);
            var dt2 = toDt(b);

            if (dt1==dt2) return 0;
            if (dt1<dt2) return -1;
            return 1;
        },

        currency : function(a,b) {
            a = a.replace(/[^0-9.]/g,'');
            b = b.replace(/[^0-9.]/g,'');
            return parseFloat(a) - parseFloat(b);
        },

        percent : function(a,b) {
            a = a.replace(/[^0-9.<>]/g,'');
            b = b.replace(/[^0-9.<>]/g,'');
            if (a == "<100") a = "99.9";
            else if (a == ">0") a = "0.1";
            if (b == "<100") b = "99.9";
            else if (b == ">0") b = "0.1";
            a = a.replace(/[^0-9.]/g,'');
            b = b.replace(/[^0-9.]/g,'');
            return parseFloat(a) - parseFloat(b);
        },

        numeric : function(a,b) {
            a = parseFloat(a);
            if (isNaN(a)) a = 0;
            b = parseFloat(b);
            if (isNaN(b)) b = 0;
            return a-b;
        },

        caseInsensitive : function(a,b) {
            return sorter.fallback(a.toLowerCase(), b.toLowerCase());
        },

        fallback : function(a,b) {
            if (a==b) return 0;
            if (a<b) return -1;
            return 1;
        },

        /**
         * return the sorter to be used for the given value
         * @param {String} itm
         *      Text
         */
        determine : function(itm) {
            var sortfn = this.caseInsensitive;
            if (itm.match(/^\d\d[\/-]\d\d[\/-]\d\d\d\d$/)) sortfn = this.date;
            if (itm.match(/^\d\d[\/-]\d\d[\/-]\d\d$/)) sortfn = this.date;
            if (itm.match(/^[�$]/)) sortfn = this.currency;
            if (itm.match(/\%$/)) sortfn = this.percent;
            if (itm.match(/^-?[\d\.]+$/)) sortfn = this.numeric;
            return sortfn;
        },

        reverse : function(f) {
            return function (a, b) {
                return -f(a, b)
            };
        }
    };

    var storage;
    try {
        storage = YAHOO.util.StorageManager.get(
            YAHOO.util.StorageEngineHTML5.ENGINE_NAME,
            YAHOO.util.StorageManager.LOCATION_SESSION,
            {
                order: [
                    YAHOO.util.StorageEngineGears
                ]
            }
        );
    } catch(e) {
        // no storage available
        storage = {
            setItem : function() {},
            getItem : function() { return null; },
            hasKey : function() { return false; }
        };
    }

    return {
        Sortable : Sortable,
        sorter : sorter
    };
})();

function ts_makeSortable(table) { // backward compatibility
    return new Sortable.Sortable(table);
}

/** Calls table.sortable.refresh() in case the sortable has been initialized; otherwise does nothing. */
function ts_refresh(table) {
    var s = table.sortable;
    if (s != null) {
        s.refresh();
    }
}
<commit_msg>Minor sortable.js documentation improvement (#2326)<commit_after>/*
The MIT Licence, for code from kryogenix.org

Code downloaded from the Browser Experiments section
of kryogenix.org is licenced under the so-called MIT
licence. The licence is below.

Copyright (c) 1997-date Stuart Langridge

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the
Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
Usage
=====

Add the "sortable" CSS class to a table to make it sortable.
The first row must be always table header, and the rest must be table data.
(the script seems to support rows to be fixed at the bottom, but haven't figured out how to use it.)

If the table data is sorted to begin with, you can add 'initialSortDir="up|down"' to the
corresponding cell in the header row to display the direction icon from the beginning.
This is recommended to provide a visual clue that the table can be sorted.

The script guesses the table data, and try to use the right sorting algorithm.
But you can override this behavior by having 'data="..."' attribute on each row,
in which case the sort will be done on that field.
*/

var Sortable = (function() {

    function Sortable(table) {
        this.table = table;
        this.arrows = [];

        var firstRow = this.getFirstRow();
        if (!firstRow) return;

        // We have a first row: assume it's the header, and make its contents clickable links
        firstRow.each(function (cell){
            cell.innerHTML = ''+this.getInnerText(cell)+'';
            this.arrows.push(cell.firstChild.lastChild);

            var self = this;
            cell.firstChild.onclick = function () {
                self.onClicked(this);
                return false;
            };
        }.bind(this));

        // figure out the initial sort preference
        this.pref = this.getStoredPreference();
        if (this.pref == null) {
            firstRow.each(function (cell,i){
                var initialSortDir = cell.getAttribute("initialSortDir");
                if (initialSortDir != null) {
                    this.pref = {column:i, direction:arrowTable[initialSortDir]};
                }
            }.bind(this));
        }

        this.refresh();
    }

    Sortable.prototype = {
        /**
         * SPAN tags that we use to render directional arrows, for each columns.
         */
        arrows : null /*Array*/,

        /**
         * Current sort preference.
         */
        pref : null /* { column:int, direction:arrow } */,

        getFirstRow : function() {
            if (this.table.rows && this.table.rows.length > 0) {
                return $A(this.table.rows[0].cells);
            }
            return null;
        },

        getDataRows : function() {
            var newRows = [];
            var rows = this.table.rows;
            for (var j = 1; j < rows.length; j++) {
                newRows.push($(rows[j]));
            }
            return newRows;
        },

        /**
         * If there's a persisted sort direction setting, retrieve it
         */
        getStoredPreference : function() {
            var key = this.getStorageKey();
            if(storage.hasKey(key)){
                var val = storage.getItem(key);
                if(val){
                    var vals = val.split(":");
                    if(vals.length == 2) {
                        return {column:parseInt(vals[0]), direction:arrowTable[vals[1]]};
                    }
                }
            }
            return null;
        },

        getStorageKey : function() {
            var uri = document.location;
            var tableIndex = this.getIndexOfSortableTable();
            return "ts_direction::" + uri + "::" + tableIndex;
        },

        savePreference : function() {
            var key = this.getStorageKey();
            storage.setItem(key, this.pref.column + ":" + this.pref.direction.id);
        },

        /**
         * Determine the sort function for the specified column
         */
        getSorter : function(column) {
            var rows = this.table.rows;
            if (rows.length <= 1)   return sorter.fallback;

            var itm = this.extractData(rows[1].cells[column]).trim();
            return sorter.determine(itm);
        },

        /**
         * Called when the column header gets clicked.
         */
        onClicked : function(lnk) {
            var arrow = lnk.lastChild;
            var th = lnk.parentNode;

            var column = th.cellIndex;
            if (column==(this.pref||{}).column) {
                // direction change on the same row
                this.pref.direction = this.pref.direction.next;
            } else {
                this.pref = {column:column, direction: arrow.sortdir||arrowTable.up};
            }

            arrow.sortdir = this.pref.direction; // remember the last sort direction on this column

            this.refresh();
            this.savePreference();
        },

        /**
         * Call when data has changed. Reapply the current sort setting to the existing data rows.
         * @since 1.484
         */
        refresh : function() {
            if (this.pref==null)     return; // not sorting

            var column = this.pref.column;
            var dir = this.pref.direction;

            var s = this.getSorter(column);
            if(dir === arrowTable.up) {// ascending
                s = sorter.reverse(s);
            }

            // we allow some rows to stick to the top and bottom, so that is our first sort criteria
            // regardless of the sort function
            function rowPos(r) {
                if (r.hasClassName("sorttop"))      return 0;
                if (r.hasClassName("sortbottom"))   return 2;
                return 1;
            }

            var rows = this.getDataRows();
            rows.sort(function(a,b) {
                var x = rowPos(a)-rowPos(b);
                if (x!=0)   return x;

                return s(
                    this.extractData(a.cells[column]),
                    this.extractData(b.cells[column]));
            }.bind(this));

            rows.each(function (e) {
                this.table.tBodies[0].appendChild(e);
            }.bind(this));

            // update arrow rendering
            this.arrows.each(function(e,i){
                e.innerHTML = ((i==column) ? dir : arrowTable.none).text;
            });
        },

        getIndexOfSortableTable : function(){
            return $(document.body).select("TABLE.sortable").indexOf(this.table);
        },

        getInnerText : function(el) {
        	if (typeof el == "string") return el;
        	if (typeof el == "undefined") { return el }
        	if (el.innerText) return el.innerText;	//Not needed but it is faster
        	var str = "";

        	var cs = el.childNodes;
        	var l = cs.length;
        	for (var i = 0; i < l; i++) {
        		switch (cs[i].nodeType) {
        			case 1: //ELEMENT_NODE
        				str += this.getInnerText(cs[i]);
        				break;
        			case 3:	//TEXT_NODE
        				str += cs[i].nodeValue;
        				break;
        		}
        	}
        	return str;
        },

        // extract data for sorting from a cell
        extractData : function(x) {
          if(x==null) return '';
          var data = x.getAttribute("data");
          if(data!=null)
            return data;
          return this.getInnerText(x);
        }
    };

    var arrowTable = {
        up: {
            id: "up",
            text: "  ↑"
        },
        down: {
            id: "down",
            text: "  ↓"
        },
        none: {
            id: "none",
            text: "   "
        },
        lnkRef: null
    };

    arrowTable.up.next = arrowTable.down;
    arrowTable.down.next = arrowTable.up;

    // available sort functions
    var sorter = {
        date : function(a,b) {
            function toDt(x) {
              // y2k notes: two digit years less than 50 are treated as 20XX, greater than 50 are treated as 19XX
              if (x.length == 10) {
                  return x.substr(6,4)+x.substr(3,2)+x.substr(0,2);
              } else {
                  var yr = x.substr(6,2);
                  if (parseInt(yr) < 50) { yr = '20'+yr; } else { yr = '19'+yr; }
                  return yr+x.substr(3,2)+x.substr(0,2);
              }
            }

            var dt1 = toDt(a);
            var dt2 = toDt(b);

            if (dt1==dt2) return 0;
            if (dt1<dt2) return -1;
            return 1;
        },

        currency : function(a,b) {
            a = a.replace(/[^0-9.]/g,'');
            b = b.replace(/[^0-9.]/g,'');
            return parseFloat(a) - parseFloat(b);
        },

        percent : function(a,b) {
            a = a.replace(/[^0-9.<>]/g,'');
            b = b.replace(/[^0-9.<>]/g,'');
            if (a == "<100") a = "99.9";
            else if (a == ">0") a = "0.1";
            if (b == "<100") b = "99.9";
            else if (b == ">0") b = "0.1";
            a = a.replace(/[^0-9.]/g,'');
            b = b.replace(/[^0-9.]/g,'');
            return parseFloat(a) - parseFloat(b);
        },

        numeric : function(a,b) {
            a = parseFloat(a);
            if (isNaN(a)) a = 0;
            b = parseFloat(b);
            if (isNaN(b)) b = 0;
            return a-b;
        },

        caseInsensitive : function(a,b) {
            return sorter.fallback(a.toLowerCase(), b.toLowerCase());
        },

        fallback : function(a,b) {
            if (a==b) return 0;
            if (a<b) return -1;
            return 1;
        },

        /**
         * return the sorter to be used for the given value
         * @param {String} itm
         *      Text
         */
        determine : function(itm) {
            var sortfn = this.caseInsensitive;
            if (itm.match(/^\d\d[\/-]\d\d[\/-]\d\d\d\d$/)) sortfn = this.date;
            if (itm.match(/^\d\d[\/-]\d\d[\/-]\d\d$/)) sortfn = this.date;
            if (itm.match(/^[�$]/)) sortfn = this.currency;
            if (itm.match(/\%$/)) sortfn = this.percent;
            if (itm.match(/^-?[\d\.]+$/)) sortfn = this.numeric;
            return sortfn;
        },

        reverse : function(f) {
            return function (a, b) {
                return -f(a, b)
            };
        }
    };

    var storage;
    try {
        storage = YAHOO.util.StorageManager.get(
            YAHOO.util.StorageEngineHTML5.ENGINE_NAME,
            YAHOO.util.StorageManager.LOCATION_SESSION,
            {
                order: [
                    YAHOO.util.StorageEngineGears
                ]
            }
        );
    } catch(e) {
        // no storage available
        storage = {
            setItem : function() {},
            getItem : function() { return null; },
            hasKey : function() { return false; }
        };
    }

    return {
        Sortable : Sortable,
        sorter : sorter
    };
})();

function ts_makeSortable(table) { // backward compatibility
    return new Sortable.Sortable(table);
}

/** Calls table.sortable.refresh() in case the sortable has been initialized; otherwise does nothing. */
function ts_refresh(table) {
    var s = table.sortable;
    if (s != null) {
        s.refresh();
    }
}
<|endoftext|>