<commit_before>/* Jquery HTTP NTP Client */
(function($){

	var _server_offset = 0;

	/** Assert that time is valid */
	function assert_time(t) {
		if(typeof t !== 'number') { throw new TypeError('time (' + t + ') is not number!'); }
		if(t <= 1378656286000) {
			throw new TypeError('time (' + t + ') is invalid!');
		}
	}

	/** Get server offset */
	$.getServerTimeOffset = function() {
		return _server_offset;
	};

	/** Get server time */
	$.getServerTime = function() {
		var time = new Date();
		if(_server_offset === undefined) {
			return time;
		}
		time.setTime( time.getTime() - _server_offset );
		return time;
	};

	/** The time offset on server */
	$.resetServerTime = function(url) {
		var results = [], i, p;
		function iter() {
			return $.testServerTime(url).then(function(r) {
				// FIXME: Check correct implementation from the RFC
				var fix = r.server.start /* t1 */ - (r.client.start /* t0 */ + r.rtd/2);
				console.log('server='+ r.server.start + ' versus client=' + r.client.start + ' offset ' + fix + ' (rtd='+r.rtd+')');
				_server_offset -= fix;
				return r;
			});
		}
		p = iter();
		for(i=0; i<5; i += 1) {
			p = p.then(function() {
				return iter();
			}).fail(function() {
				return iter();
			});
		}
		return p;
	};

	/** Asynchronically get time offset between the client and server */
	$.testServerTime = function(url) {
		if(!url) { throw new TypeError("missing url for .testServerTime()!"); }

		var client_start_time;

		var p = $.ajax({
			url: url + '/time',
			cache: false,
			dataType: 'text',
			beforeSend: function() {
				client_start_time = $.getServerTime().getTime();
			}
		}).then(function(json) {
			var client_end_time = $.getServerTime().getTime();
			assert_time(client_start_time);
			assert_time(client_end_time);
			var id = JSON.parse(json);
			var p2 = $.ajax({
				url: url+'/fetch?id='+encodeURIComponent(id),
				dataType: 'text',
				cache: false
			}).then(function(json) {
				var data = JSON.parse(json);
				assert_time(data.start);
				assert_time(data.end);

				var t0 = client_start_time;
				var t1 = data.start;
				var t2 = data.end;
				var t3 = client_end_time;

				var rtd = (t3-t0) - (t2-t1);
				var offset = ((t1-t0) + (t2-t3)) / 2;

				var res = {
					client:{
						start: client_start_time,
						end: client_end_time
					},
					server:{
						start: data.start,
						end: data.end
					},
					rtd: rtd,
					offset: offset
				};
				return res;
			});
			return p2;
		});

		return p;
	};

})( jQuery );
/* EOF */
<commit_msg>Changed ServerTime implementation<commit_after>/* Jquery HTTP NTP Client */
(function($){

	var _server_offset = 0;

	/** Assert that time is valid */
	function assert_time(t) {
		if(typeof t !== 'number') { throw new TypeError('time (' + t + ') is not number!'); }
		if(t <= 1378656286000) {
			throw new TypeError('time (' + t + ') is invalid!');
		}
	}

	/** Get server offset */
	$.getServerTimeOffset = function() {
		return _server_offset;
	};

	/** Get server time */
	$.getServerTime = function() {
		var time = new Date();
		if(_server_offset === undefined) {
			return time;
		}
		time.setTime( time.getTime() - _server_offset );
		return time;
	};

	/** The time offset on server */
	$.resetServerTime = function(url, times) {
		times = times || 5;
		var results = [], i, p;
		function iter() {
			return $.testServerTime(url).then(function(r) {
				// FIXME: Check correct implementation from the RFC
				var fix = r.server.start /* t1 */ - (r.client.start /* t0 */ + r.rtd/2);
				console.log('server='+ r.server.start + ' versus client=' + r.client.start + ' offset ' + fix + ' (rtd='+r.rtd+')');
				_server_offset -= fix;
				return r;
			});
		}
		p = iter();
		for(i=0; i<times-1; i += 1) {
			p = p.then(function() {
				return iter();
			}).fail(function() {
				return iter();
			});
		}
		return p;
	};

	/** Asynchronically get time offset between the client and server */
	$.testServerTime = function(url) {
		if(!url) { throw new TypeError("missing url for .testServerTime()!"); }

		var client_start_time;

		var p = $.ajax({
			url: url + '/time',
			cache: false,
			dataType: 'text',
			beforeSend: function() {
				client_start_time = $.getServerTime().getTime();
			}
		}).then(function(json) {
			var client_end_time = $.getServerTime().getTime();
			assert_time(client_start_time);
			assert_time(client_end_time);
			var id = JSON.parse(json);
			var p2 = $.ajax({
				url: url+'/fetch?id='+encodeURIComponent(id),
				dataType: 'text',
				cache: false
			}).then(function(json) {
				var data = JSON.parse(json);
				assert_time(data.start);
				assert_time(data.end);

				var t0 = client_start_time;
				var t1 = data.start;
				var t2 = data.end;
				var t3 = client_end_time;

				var rtd = (t3-t0) - (t2-t1);
				var offset = ((t1-t0) + (t2-t3)) / 2;

				var res = {
					client:{
						start: client_start_time,
						end: client_end_time
					},
					server:{
						start: data.start,
						end: data.end
					},
					rtd: rtd,
					offset: offset
				};
				return res;
			});
			return p2;
		});

		return p;
	};

})( jQuery );
/* EOF */
<|endoftext|>


<commit_before>import Ember from 'ember';

export default Ember.Component.extend({

  classNameBindings: ['collapse','in','collapsing'],
  attributeBindings: ['style'],

  /**
   * Collapsed/expanded state
   *
   * @property collapsed
   * @type boolean
   * @default true
   * @public
   */
  collapsed: true,

  /**
   * True if this item is expanded
   *
   * @property active
   * @protected
   */
  active: false,

  collapse: Ember.computed.not('transitioning'),
  collapsing: Ember.computed.alias('transitioning'),
  in: Ember.computed.and('collapse', 'active'),

  /**
   * true if the component is currently transitioning
   *
   * @property transitioning
   * @type boolean
   * @protected
   */
  transitioning: false,

  /**
   * @property collapseSize
   * @type number
   * @protected
   */
  collapseSize: null,

  /**
   * The size of the element when collapsed. Defaults to 0.
   *
   * @property collapsedSize
   * @type number
   * @default 0
   * @public
   */
  collapsedSize: 0,

  /**
   * The size of the element when expanded. When null the value is calculated automatically to fit the containing elements.
   *
   * @property expandedSize
   * @type number
   * @default null
   * @public
   */
  expandedSize: null,

  /**
   * Usually the size (height) of the element is only set while transitioning, and reseted afterwards. Set to true to always set a size.
   *
   * @property resetSizeWhenNotCollapsing
   * @type boolean
   * @default true
   * @private
   */
  resetSizeWhenNotCollapsing: true,

  /**
   * The direction (height/width) of the collapse animation.
   * When setting this to 'width' you should also define custom CSS transitions for the width property, as the Bootstrap
   * CSS does only support collapsible elements for the height direction.
   *
   * @property collapseDimension
   * @type string
   * @default 'height'
   * @public
   */
  collapseDimension: 'height',

  style: Ember.computed('collapseSize', function () {
    var size = this.get('collapseSize'),
      dimension = this.get('collapseDimension');
    if (Ember.isEmpty(size)) {
      return new Ember.Handlebars.SafeString('');
    }
    return new Ember.Handlebars.SafeString(`${dimension}: ${size}px`);
  }),

  /**
   * Triggers the show transition
   *
   * @method show
   * @protected
   */
  show: function () {
    var complete = function () {
        this.set('transitioning', false);
        if (this.get('resetSizeWhenNotCollapsing')) {
          this.set('collapseSize', null);
        }
        this.sendAction('didShow');
      };

    this.sendAction('willShow');

    this.setProperties({
      transitioning: true,
      collapseSize: this.get('collapsedSize'),
      active: true
    });

    if (!Ember.$.support.transition) {
      return complete.call(this);
    }

    this.$()
      .one('bsTransitionEnd', Ember.run.bind(this, complete))
      // @todo: make duration configurable
      .emulateTransitionEnd(350)
    ;

    Ember.run.next(this, function () {
      if (!this.get('isDestroyed')) {
        this.set('collapseSize', this.getExpandedSize('show'));
      }
    });
  },

  /**
   * Get the size of the element when expanded
   *
   * @method getExpandedSize
   * @param $action
   * @returns number
   * @private
   */
  getExpandedSize: function($action) {
    var expandedSize = this.get('expandedSize');
    if (Ember.isPresent(expandedSize)) {
      return expandedSize;
    }

    var collapseElement = this.$(),
      prefix = $action === 'show' ? 'scroll' : 'offset',
      measureProperty = Ember.String.camelize(prefix + '-' + this.get('collapseDimension'));
    return collapseElement[0][measureProperty];
  },

  /**
   * Triggers the hide transition
   *
   * @method hide
   * @protected
   */
  hide: function () {

    var complete = function () {
        this.set('transitioning', false);
        if (this.get('resetSizeWhenNotCollapsing')) {
          this.set('collapseSize', null);
        }
        this.sendAction('didHide');
      };

    this.sendAction('willHide');

    this.setProperties({
      transitioning: true,
      collapseSize: this.getExpandedSize('hide'),
      active: false
    });

    if (!Ember.$.support.transition) {
      return complete.call(this);
    }

    this.$()
      .one('bsTransitionEnd', Ember.run.bind(this, complete))
      // @todo: make duration configurable
      .emulateTransitionEnd(350)
    ;

    Ember.run.next(this, function () {
        if (!this.get('isDestroyed')) {
          this.set('collapseSize', this.get('collapsedSize'));
        }
    });
  },

  _onCollapsedChange: Ember.observer('collapsed', function () {
    var collapsed = this.get('collapsed'),
      active = this.get('active');
    if (collapsed !== active) {
      return;
    }
    if (collapsed === false) {
      this.show();
    }
    else {
      this.hide();
    }
  }),

  didInsertElement: function() {
    this.set('active', !this.get('collapsed'));
  },

  _updateCollapsedSize: Ember.observer('collapsedSize', function() {
    if (!this.get('resetSizeWhenNotCollapsing') && this.get('collapsed') && !this.get('collapsing')) {
      this.set('collapseSize', this.get('collapsedSize'));
    }
  }),

  _updateExpandedSize: Ember.observer('expandedSize', function() {
    if (!this.get('resetSizeWhenNotCollapsing') && !this.get('collapsed') && !this.get('collapsing')) {
      this.set('collapseSize', this.get('expandedSize'));
    }
  })

});
<commit_msg>fix(Collapse): fixed deprecation warning for changing properties in didInsertElement()<commit_after>import Ember from 'ember';

export default Ember.Component.extend({

  classNameBindings: ['collapse','in','collapsing'],
  attributeBindings: ['style'],

  /**
   * Collapsed/expanded state
   *
   * @property collapsed
   * @type boolean
   * @default true
   * @public
   */
  collapsed: true,

  /**
   * True if this item is expanded
   *
   * @property active
   * @protected
   */
  active: false,

  collapse: Ember.computed.not('transitioning'),
  collapsing: Ember.computed.alias('transitioning'),
  in: Ember.computed.and('collapse', 'active'),

  /**
   * true if the component is currently transitioning
   *
   * @property transitioning
   * @type boolean
   * @protected
   */
  transitioning: false,

  /**
   * @property collapseSize
   * @type number
   * @protected
   */
  collapseSize: null,

  /**
   * The size of the element when collapsed. Defaults to 0.
   *
   * @property collapsedSize
   * @type number
   * @default 0
   * @public
   */
  collapsedSize: 0,

  /**
   * The size of the element when expanded. When null the value is calculated automatically to fit the containing elements.
   *
   * @property expandedSize
   * @type number
   * @default null
   * @public
   */
  expandedSize: null,

  /**
   * Usually the size (height) of the element is only set while transitioning, and reseted afterwards. Set to true to always set a size.
   *
   * @property resetSizeWhenNotCollapsing
   * @type boolean
   * @default true
   * @private
   */
  resetSizeWhenNotCollapsing: true,

  /**
   * The direction (height/width) of the collapse animation.
   * When setting this to 'width' you should also define custom CSS transitions for the width property, as the Bootstrap
   * CSS does only support collapsible elements for the height direction.
   *
   * @property collapseDimension
   * @type string
   * @default 'height'
   * @public
   */
  collapseDimension: 'height',

  style: Ember.computed('collapseSize', function () {
    var size = this.get('collapseSize'),
      dimension = this.get('collapseDimension');
    if (Ember.isEmpty(size)) {
      return new Ember.Handlebars.SafeString('');
    }
    return new Ember.Handlebars.SafeString(`${dimension}: ${size}px`);
  }),

  /**
   * Triggers the show transition
   *
   * @method show
   * @protected
   */
  show: function () {
    var complete = function () {
        this.set('transitioning', false);
        if (this.get('resetSizeWhenNotCollapsing')) {
          this.set('collapseSize', null);
        }
        this.sendAction('didShow');
      };

    this.sendAction('willShow');

    this.setProperties({
      transitioning: true,
      collapseSize: this.get('collapsedSize'),
      active: true
    });

    if (!Ember.$.support.transition) {
      return complete.call(this);
    }

    this.$()
      .one('bsTransitionEnd', Ember.run.bind(this, complete))
      // @todo: make duration configurable
      .emulateTransitionEnd(350)
    ;

    Ember.run.next(this, function () {
      if (!this.get('isDestroyed')) {
        this.set('collapseSize', this.getExpandedSize('show'));
      }
    });
  },

  /**
   * Get the size of the element when expanded
   *
   * @method getExpandedSize
   * @param $action
   * @returns number
   * @private
   */
  getExpandedSize: function($action) {
    var expandedSize = this.get('expandedSize');
    if (Ember.isPresent(expandedSize)) {
      return expandedSize;
    }

    var collapseElement = this.$(),
      prefix = $action === 'show' ? 'scroll' : 'offset',
      measureProperty = Ember.String.camelize(prefix + '-' + this.get('collapseDimension'));
    return collapseElement[0][measureProperty];
  },

  /**
   * Triggers the hide transition
   *
   * @method hide
   * @protected
   */
  hide: function () {

    var complete = function () {
        this.set('transitioning', false);
        if (this.get('resetSizeWhenNotCollapsing')) {
          this.set('collapseSize', null);
        }
        this.sendAction('didHide');
      };

    this.sendAction('willHide');

    this.setProperties({
      transitioning: true,
      collapseSize: this.getExpandedSize('hide'),
      active: false
    });

    if (!Ember.$.support.transition) {
      return complete.call(this);
    }

    this.$()
      .one('bsTransitionEnd', Ember.run.bind(this, complete))
      // @todo: make duration configurable
      .emulateTransitionEnd(350)
    ;

    Ember.run.next(this, function () {
        if (!this.get('isDestroyed')) {
          this.set('collapseSize', this.get('collapsedSize'));
        }
    });
  },

  _onCollapsedChange: Ember.observer('collapsed', function () {
    var collapsed = this.get('collapsed'),
      active = this.get('active');
    if (collapsed !== active) {
      return;
    }
    if (collapsed === false) {
      this.show();
    }
    else {
      this.hide();
    }
  }),

  _onInit: Ember.on('init', function () {
    this.set('active', !this.get('collapsed'));
  }),

  _updateCollapsedSize: Ember.observer('collapsedSize', function() {
    if (!this.get('resetSizeWhenNotCollapsing') && this.get('collapsed') && !this.get('collapsing')) {
      this.set('collapseSize', this.get('collapsedSize'));
    }
  }),

  _updateExpandedSize: Ember.observer('expandedSize', function() {
    if (!this.get('resetSizeWhenNotCollapsing') && !this.get('collapsed') && !this.get('collapsing')) {
      this.set('collapseSize', this.get('expandedSize'));
    }
  })

});
<|endoftext|>


<commit_before>/*global define*/
define([
        './defaultValue',
        './DeveloperError',
        './EquidistantCylindricalProjection',
        './Intersect'
    ], function(
        defaultValue,
        DeveloperError,
        EquidistantCylindricalProjection,
        Intersect) {
    "use strict";

    /**
     * A bounding rectangle given by a corner, width and height.
     * @alias BoundingRectangle
     * @constructor
     *
     * @param {Number} [x=0.0] The x coordinate of the rectangle.
     * @param {Number} [y=0.0] The y coordinate of the rectangle.
     * @param {Number} [width=0.0] The width of the rectangle.
     * @param {Number} [height=0.0] The height of the rectangle.
     *
     * @see BoundingSphere
     */
    var BoundingRectangle = function(x, y, width, height) {
        /**
         * The x coordinate of the rectangle.
         * @type Number
         */
        this.x = defaultValue(x, 0.0);

        /**
         * The y coordinate of the rectangle.
         * @type Number
         */
        this.y = defaultValue(y, 0.0);

        /**
         * The width of the rectangle.
         * @type Number
         */
        this.width = defaultValue(width, 0.0);

        /**
         * The height of the rectangle.
         * @type Number
         */
        this.height = defaultValue(height, 0.0);
    };

    /**
     * Computes a bounding rectangle enclosing the list of 2D points.
     * The rectangle is oriented with the corner at the bottom left.
     * @memberof BoundingRectangle
     *
     * @param {Array} positions List of points that the bounding rectangle will enclose.  Each point must have x and y properties.
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @return {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if none was provided.
     */
    BoundingRectangle.fromPoints = function(positions, result) {
        if (typeof result === 'undefined') {
            result = new BoundingRectangle();
        }

        if (typeof positions === 'undefined' || positions.length === 0) {
            result.x = 0;
            result.y = 0;
            result.width = 0;
            result.height = 0;
            return result;
        }

        var length = positions.length;

        var minimumX = positions[0].x;
        var minimumY = positions[0].y;

        var maximumX = positions[0].x;
        var maximumY = positions[0].y;

        for ( var i = 1; i < length; i++) {
            var p = positions[i];
            var x = p.x;
            var y = p.y;

            minimumX = Math.min(x, minimumX);
            maximumX = Math.max(x, maximumX);
            minimumY = Math.min(y, minimumY);
            maximumY = Math.max(y, maximumY);
        }

        result.x = minimumX;
        result.y = minimumY;
        result.width = maximumX - minimumX;
        result.height = maximumY - minimumY;
        return result;
    };

    var defaultProjection = new EquidistantCylindricalProjection();
    /**
     * Computes a bounding rectangle from an extent.
     * @memberof BoundingRectangle
     *
     * @param {Extent} extent The valid extent used to create a bounding rectangle.
     * @param {Object} [projection=EquidistantCylindricalProjection] The projection used to project the extent into 2D.
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @return {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if none was provided.
     */
    BoundingRectangle.fromExtent = function(extent, projection, result) {
        if (typeof result === 'undefined') {
            result = new BoundingRectangle();
        }

        if (typeof extent === 'undefined') {
            result.x = 0;
            result.y = 0;
            result.width = 0;
            result.height = 0;
            return result;
        }

        projection = (typeof projection !== 'undefined') ? projection : defaultProjection;

        var lowerLeft = projection.project(extent.getSouthwest());
        var upperRight = projection.project(extent.getNortheast());

        upperRight.subtract(lowerLeft, upperRight);

        result.x = lowerLeft.x;
        result.y = lowerLeft.y;
        result.width = upperRight.x;
        result.height = upperRight.y;
        return result;
    };

    /**
     * Duplicates a BoundingRectangle instance.
     * @memberof BoundingRectangle
     *
     * @param {BoundingRectangle} rectangle The bounding rectangle to duplicate.
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @return {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if none was provided.
     *
     * @exception {DeveloperError} rectangle is required.
     */
    BoundingRectangle.clone = function(rectangle, result) {
        if (typeof rectangle === 'undefined') {
            throw new DeveloperError('rectangle is required');
        }

        if (typeof result === 'undefined') {
            return new BoundingRectangle(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
        }

        result.x = rectangle.x;
        result.y = rectangle.y;
        result.width = rectangle.width;
        result.height = rectangle.height;
        return result;
    };

    /**
     * Computes a bounding rectangle that is the union of the left and right bounding rectangles.
     * @memberof BoundingRectangle
     *
     * @param {BoundingRectangle} left A rectangle to enclose in bounding rectangle.
     * @param {BoundingRectangle} right A rectangle to enclose in a bounding rectangle.
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @return {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if none was provided.
     *
     * @exception {DeveloperError} left is required.
     * @exception {DeveloperError} right is required.
     */
    BoundingRectangle.union = function(left, right, result) {
        if (typeof left === 'undefined') {
            throw new DeveloperError('left is required.');
        }

        if (typeof right === 'undefined') {
            throw new DeveloperError('right is required.');
        }

        if (typeof result === 'undefined') {
            result = new BoundingRectangle();
        }

        var lowerLeftX = Math.min(left.x, right.x);
        var lowerLeftY = Math.min(left.y, right.y);
        var upperRightX = Math.max(left.x + left.width, right.x + right.width);
        var upperRightY = Math.max(left.y + left.height, right.y + right.height);

        result.x = lowerLeftX;
        result.y = lowerLeftY;
        result.width = upperRightX - lowerLeftX;
        result.height = upperRightY - lowerLeftY;
        return result;
    };

    /**
     * Computes a bounding rectangle by enlarging the provided rectangle until it contains the provided point.
     * @memberof BoundingRectangle
     *
     * @param {BoundingRectangle} rectangle A rectangle to expand.
     * @param {Cartesian2} point A point to enclose in a bounding rectangle.
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @return {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if none was provided.
     *
     * @exception {DeveloperError} rectangle is required.
     * @exception {DeveloperError} point is required.
     */
    BoundingRectangle.expand = function(rectangle, point, result) {
        if (typeof rectangle === 'undefined') {
            throw new DeveloperError('rectangle is required.');
        }

        if (typeof point === 'undefined') {
            throw new DeveloperError('point is required.');
        }

        result = BoundingRectangle.clone(rectangle, result);

        var width = point.x - result.x;
        var height = point.y - result.y;

        if (width > result.width) {
            result.width = width;
        } else if (width < 0) {
            result.width -= width;
            result.x = point.x;
        }

        if (height > result.height) {
            result.height = height;
        } else if (height < 0) {
            result.height -= height;
            result.y = point.y;
        }

        return result;
    };

    /**
     * Determines if two rectangles intersect.
     * @memberof BoundingRectangle
     *
     * @param {BoundingRectangle} left A rectangle to check for intersection.
     * @param {BoundingRectangle} right The other rectangle to check for intersection.
     * @return {Boolean} true if the rectangles intersect, false otherwise.
     *
     * @exception {DeveloperError} left is required.
     * @exception {DeveloperError} right is required.
     */
    BoundingRectangle.intersect = function(left, right) {
        if (typeof left === 'undefined') {
            throw new DeveloperError('left is required.');
        }

        if (typeof right === 'undefined') {
            throw new DeveloperError('right is required.');
        }

        var leftX = left.x;
        var leftY = left.y;
        var rightX = right.x;
        var rightY = right.y;
        if (!(leftX > rightX + right.width ||
              leftX + left.width < rightX ||
              leftY + left.height < rightY ||
              leftY > rightY + right.height)) {
            return Intersect.INTERSECTING;
        }

        return Intersect.OUTSIDE;
    };

    /**
     * Compares the provided BoundingRectangles componentwise and returns
     * true if they are equal, false otherwise.
     * @memberof BoundingRectangle
     *
     * @param {BoundingRectangle} [left] The first BoundingRectangle.
     * @param {BoundingRectangle} [right] The second BoundingRectangle.
     * @return {Boolean} true if left and right are equal, false otherwise.
     */
    BoundingRectangle.equals = function(left, right) {
        return (left === right) ||
               ((typeof left !== 'undefined') &&
                (typeof right !== 'undefined') &&
                (left.x === right.x) &&
                (left.y === right.y) &&
                (left.width === right.width) &&
                (left.height === right.height));
    };

    /**
     * Duplicates this BoundingRectangle instance.
     * @memberof BoundingRectangle
     *
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @return {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if none was provided.
     */
    BoundingRectangle.prototype.clone = function(result) {
        return BoundingRectangle.clone(this, result);
    };

    /**
     * Computes a bounding rectangle that contains both this bounding rectangle and the argument rectangle.
     * @memberof BoundingRectangle
     *
     * @param {BoundingRectangle} right The rectangle to enclose in this bounding rectangle.
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @return {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if none was provided.
     *
     * @exception {DeveloperError} right is required.
     */
    BoundingRectangle.prototype.union = function(right, result) {
        return BoundingRectangle.union(this, right, result);
    };

    /**
     * Computes a bounding rectangle that is rectangle expanded to contain point.
     * @memberof BoundingRectangle
     *
     * @param {BoundingRectangle} point A point to enclose in a bounding rectangle.
     * @return {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if none was provided.
     *
     * @exception {DeveloperError} point is required.
     */
    BoundingRectangle.prototype.expand = function(point, result) {
        return BoundingRectangle.expand(this, point, result);
    };

    /**
     * Determines if this rectangle intersects with another.
     * @memberof BoundingRectangle
     *
     * @param {BoundingRectangle} right A rectangle to check for intersection.
     * @return {Boolean} true if the rectangles intersect, false otherwise.
     *
     * @exception {DeveloperError} right is required.
     */
    BoundingRectangle.prototype.intersect = function(right) {
        return BoundingRectangle.intersect(this, right);
    };

    /**
     * Compares this BoundingRectangle against the provided BoundingRectangle componentwise and returns
     * true if they are equal, false otherwise.
     * @memberof BoundingRectangle
     *
     * @param {BoundingRectangle} [right] The right hand side BoundingRectangle.
     * @return {Boolean} true if they are equal, false otherwise.
     */
    BoundingRectangle.prototype.equals = function(right) {
        return BoundingRectangle.equals(this, right);
    };

    return BoundingRectangle;
});
<commit_msg>Fix doc.<commit_after>/*global define*/
define([
        './defaultValue',
        './DeveloperError',
        './EquidistantCylindricalProjection',
        './Intersect'
    ], function(
        defaultValue,
        DeveloperError,
        EquidistantCylindricalProjection,
        Intersect) {
    "use strict";

    /**
     * A bounding rectangle given by a corner, width and height.
     * @alias BoundingRectangle
     * @constructor
     *
     * @param {Number} [x=0.0] The x coordinate of the rectangle.
     * @param {Number} [y=0.0] The y coordinate of the rectangle.
     * @param {Number} [width=0.0] The width of the rectangle.
     * @param {Number} [height=0.0] The height of the rectangle.
     *
     * @see BoundingSphere
     */
    var BoundingRectangle = function(x, y, width, height) {
        /**
         * The x coordinate of the rectangle.
         * @type Number
         */
        this.x = defaultValue(x, 0.0);

        /**
         * The y coordinate of the rectangle.
         * @type Number
         */
        this.y = defaultValue(y, 0.0);

        /**
         * The width of the rectangle.
         * @type Number
         */
        this.width = defaultValue(width, 0.0);

        /**
         * The height of the rectangle.
         * @type Number
         */
        this.height = defaultValue(height, 0.0);
    };

    /**
     * Computes a bounding rectangle enclosing the list of 2D points.
     * The rectangle is oriented with the corner at the bottom left.
     * @memberof BoundingRectangle
     *
     * @param {Array} positions List of points that the bounding rectangle will enclose.  Each point must have x and y properties.
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @return {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if none was provided.
     */
    BoundingRectangle.fromPoints = function(positions, result) {
        if (typeof result === 'undefined') {
            result = new BoundingRectangle();
        }

        if (typeof positions === 'undefined' || positions.length === 0) {
            result.x = 0;
            result.y = 0;
            result.width = 0;
            result.height = 0;
            return result;
        }

        var length = positions.length;

        var minimumX = positions[0].x;
        var minimumY = positions[0].y;

        var maximumX = positions[0].x;
        var maximumY = positions[0].y;

        for ( var i = 1; i < length; i++) {
            var p = positions[i];
            var x = p.x;
            var y = p.y;

            minimumX = Math.min(x, minimumX);
            maximumX = Math.max(x, maximumX);
            minimumY = Math.min(y, minimumY);
            maximumY = Math.max(y, maximumY);
        }

        result.x = minimumX;
        result.y = minimumY;
        result.width = maximumX - minimumX;
        result.height = maximumY - minimumY;
        return result;
    };

    var defaultProjection = new EquidistantCylindricalProjection();
    /**
     * Computes a bounding rectangle from an extent.
     * @memberof BoundingRectangle
     *
     * @param {Extent} extent The valid extent used to create a bounding rectangle.
     * @param {Object} [projection=EquidistantCylindricalProjection] The projection used to project the extent into 2D.
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @return {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if none was provided.
     */
    BoundingRectangle.fromExtent = function(extent, projection, result) {
        if (typeof result === 'undefined') {
            result = new BoundingRectangle();
        }

        if (typeof extent === 'undefined') {
            result.x = 0;
            result.y = 0;
            result.width = 0;
            result.height = 0;
            return result;
        }

        projection = (typeof projection !== 'undefined') ? projection : defaultProjection;

        var lowerLeft = projection.project(extent.getSouthwest());
        var upperRight = projection.project(extent.getNortheast());

        upperRight.subtract(lowerLeft, upperRight);

        result.x = lowerLeft.x;
        result.y = lowerLeft.y;
        result.width = upperRight.x;
        result.height = upperRight.y;
        return result;
    };

    /**
     * Duplicates a BoundingRectangle instance.
     * @memberof BoundingRectangle
     *
     * @param {BoundingRectangle} rectangle The bounding rectangle to duplicate.
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @return {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if none was provided.
     *
     * @exception {DeveloperError} rectangle is required.
     */
    BoundingRectangle.clone = function(rectangle, result) {
        if (typeof rectangle === 'undefined') {
            throw new DeveloperError('rectangle is required');
        }

        if (typeof result === 'undefined') {
            return new BoundingRectangle(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
        }

        result.x = rectangle.x;
        result.y = rectangle.y;
        result.width = rectangle.width;
        result.height = rectangle.height;
        return result;
    };

    /**
     * Computes a bounding rectangle that is the union of the left and right bounding rectangles.
     * @memberof BoundingRectangle
     *
     * @param {BoundingRectangle} left A rectangle to enclose in bounding rectangle.
     * @param {BoundingRectangle} right A rectangle to enclose in a bounding rectangle.
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @return {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if none was provided.
     *
     * @exception {DeveloperError} left is required.
     * @exception {DeveloperError} right is required.
     */
    BoundingRectangle.union = function(left, right, result) {
        if (typeof left === 'undefined') {
            throw new DeveloperError('left is required.');
        }

        if (typeof right === 'undefined') {
            throw new DeveloperError('right is required.');
        }

        if (typeof result === 'undefined') {
            result = new BoundingRectangle();
        }

        var lowerLeftX = Math.min(left.x, right.x);
        var lowerLeftY = Math.min(left.y, right.y);
        var upperRightX = Math.max(left.x + left.width, right.x + right.width);
        var upperRightY = Math.max(left.y + left.height, right.y + right.height);

        result.x = lowerLeftX;
        result.y = lowerLeftY;
        result.width = upperRightX - lowerLeftX;
        result.height = upperRightY - lowerLeftY;
        return result;
    };

    /**
     * Computes a bounding rectangle by enlarging the provided rectangle until it contains the provided point.
     * @memberof BoundingRectangle
     *
     * @param {BoundingRectangle} rectangle A rectangle to expand.
     * @param {Cartesian2} point A point to enclose in a bounding rectangle.
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @return {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if none was provided.
     *
     * @exception {DeveloperError} rectangle is required.
     * @exception {DeveloperError} point is required.
     */
    BoundingRectangle.expand = function(rectangle, point, result) {
        if (typeof rectangle === 'undefined') {
            throw new DeveloperError('rectangle is required.');
        }

        if (typeof point === 'undefined') {
            throw new DeveloperError('point is required.');
        }

        result = BoundingRectangle.clone(rectangle, result);

        var width = point.x - result.x;
        var height = point.y - result.y;

        if (width > result.width) {
            result.width = width;
        } else if (width < 0) {
            result.width -= width;
            result.x = point.x;
        }

        if (height > result.height) {
            result.height = height;
        } else if (height < 0) {
            result.height -= height;
            result.y = point.y;
        }

        return result;
    };

    /**
     * Determines if two rectangles intersect.
     * @memberof BoundingRectangle
     *
     * @param {BoundingRectangle} left A rectangle to check for intersection.
     * @param {BoundingRectangle} right The other rectangle to check for intersection.
     * @return {Intersect} Intersect.INTESECTING if the rectangles intersect, Intersect.OUTSIDE otherwise.
     *
     * @exception {DeveloperError} left is required.
     * @exception {DeveloperError} right is required.
     */
    BoundingRectangle.intersect = function(left, right) {
        if (typeof left === 'undefined') {
            throw new DeveloperError('left is required.');
        }

        if (typeof right === 'undefined') {
            throw new DeveloperError('right is required.');
        }

        var leftX = left.x;
        var leftY = left.y;
        var rightX = right.x;
        var rightY = right.y;
        if (!(leftX > rightX + right.width ||
              leftX + left.width < rightX ||
              leftY + left.height < rightY ||
              leftY > rightY + right.height)) {
            return Intersect.INTERSECTING;
        }

        return Intersect.OUTSIDE;
    };

    /**
     * Compares the provided BoundingRectangles componentwise and returns
     * true if they are equal, false otherwise.
     * @memberof BoundingRectangle
     *
     * @param {BoundingRectangle} [left] The first BoundingRectangle.
     * @param {BoundingRectangle} [right] The second BoundingRectangle.
     * @return {Boolean} true if left and right are equal, false otherwise.
     */
    BoundingRectangle.equals = function(left, right) {
        return (left === right) ||
               ((typeof left !== 'undefined') &&
                (typeof right !== 'undefined') &&
                (left.x === right.x) &&
                (left.y === right.y) &&
                (left.width === right.width) &&
                (left.height === right.height));
    };

    /**
     * Duplicates this BoundingRectangle instance.
     * @memberof BoundingRectangle
     *
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @return {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if none was provided.
     */
    BoundingRectangle.prototype.clone = function(result) {
        return BoundingRectangle.clone(this, result);
    };

    /**
     * Computes a bounding rectangle that contains both this bounding rectangle and the argument rectangle.
     * @memberof BoundingRectangle
     *
     * @param {BoundingRectangle} right The rectangle to enclose in this bounding rectangle.
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @return {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if none was provided.
     *
     * @exception {DeveloperError} right is required.
     */
    BoundingRectangle.prototype.union = function(right, result) {
        return BoundingRectangle.union(this, right, result);
    };

    /**
     * Computes a bounding rectangle that is rectangle expanded to contain point.
     * @memberof BoundingRectangle
     *
     * @param {BoundingRectangle} point A point to enclose in a bounding rectangle.
     * @return {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if none was provided.
     *
     * @exception {DeveloperError} point is required.
     */
    BoundingRectangle.prototype.expand = function(point, result) {
        return BoundingRectangle.expand(this, point, result);
    };

    /**
     * Determines if this rectangle intersects with another.
     * @memberof BoundingRectangle
     *
     * @param {BoundingRectangle} right A rectangle to check for intersection.
     * @return {Intersect} Intersect.INTESECTING if the rectangles intersect, Intersect.OUTSIDE otherwise.
     *
     * @exception {DeveloperError} right is required.
     */
    BoundingRectangle.prototype.intersect = function(right) {
        return BoundingRectangle.intersect(this, right);
    };

    /**
     * Compares this BoundingRectangle against the provided BoundingRectangle componentwise and returns
     * true if they are equal, false otherwise.
     * @memberof BoundingRectangle
     *
     * @param {BoundingRectangle} [right] The right hand side BoundingRectangle.
     * @return {Boolean} true if they are equal, false otherwise.
     */
    BoundingRectangle.prototype.equals = function(right) {
        return BoundingRectangle.equals(this, right);
    };

    return BoundingRectangle;
});
<|endoftext|>


<commit_before>/* ==================================================================
	AngularJS Datatype Editor - Rich Text
	A directive to edit a large text blob in place.
	TODO: In the future it will allow rich text formatting

	Usage:

{{data}}

	Config:
	"class" will be added to the input box so you can style it.
	"id" will be used in messages broadcast to the app on state changes.

	Messages:
		name: ADE-start
		data: id from config

		name: ADE-finish
		data: {id from config, old value, new value, exit value}

------------------------------------------------------------------*/

angular.module('ADE').directive('adeRich', ['ADE', '$compile', function(ADE, $compile) {
	return {
		require: '?ngModel', //optional dependency for ngModel
		restrict: 'A', //Attribute declaration eg:

		//The link step (after compile)
		link: function(scope, element, attrs, controller) {
			var options = {};
			var editing = false;
			var txtArea = null;
			var input = null;
			var value = '';
			var oldValue = '';
			var exit = 0; //0=click, 1=tab, -1= shift tab, 2=return, -2=shift return, 3=esc. controls if you exited the field so you can focus the next field if appropriate
			var timeout = null; //the delay when mousing out of the ppopup

			//whenever the model changes, we get called so we can update our value
			if (controller !== null && controller !== undefined) {
				controller.$render = function() {
					oldValue = value = controller.$modelValue;
					if (value === undefined || value === null) value = '';
					return controller.$viewValue;
				};
			}

			//called once the edit is done, so we can save the new data	and remove edit mode
			var saveEdit = function(exited) {
				oldValue = value;
				exit = exited;

				if (exited != 3) { //don't save value on esc
					value = $('#tinyText_ifr').contents().find('#tinymce')[0].innerHTML;
					// check if contents are empty
					if (value === '

') {
						value = '';
					}
					controller.$setViewValue(value);
				}

				input.remove();
				editing = false;

				ADE.done(options, oldValue, value, exit);

				if (exit == 1) {
					element.data('dontclick', true); //tells the focus handler not to click
					element.focus();
					//TODO: would prefer to advance the focus to the next logical element on the page
				} else if (exit == -1) {
					element.data('dontclick', true); //tells the focus handler not to click
					element.focus();
					//TODO: would prefer to advance the focus to the previous logical element on the page
				}

				// we're done, no need to listen to events
				$(document).off('click.ADE');

				scope.$digest();
			};

			//shows a popup with the full text in read mode
			//TODO: handle scrolling of very long text blobs
			var viewRichText = function() {
				scope.ADE_hidePopup();

				var elOffset = element.offset();
				var posLeft = elOffset.left;
				var posTop = elOffset.top + element[0].offsetHeight;
				var content = value.replace ? value.replace(/\n/g, '
') : value; //what is inside the popup

				if (!content) return; //dont show popup if there is nothing to show

				$compile('

' + content + '

')(scope).insertAfter(element);

				editing = false;

				input = element.next('.ade-rich');
				input.bind('mouseenter.ADE', mousein);
				input.bind('mouseleave.ADE', mouseout);
				input.bind('click.ADE', mouseclick);
			};

			//sets the height of the textarea based on the actual height of the contents.
			//min and max are set in css
			var textareaHeight = function(elem) {
				elem.style.height = '1px';
				elem.style.height = (elem.scrollHeight) + 'px';
			};

			// don't blur on initialization
			var ready = false;

			// detect clicks outside tinymce textarea
			var outerBlur = function(e) {
				// check where click occurred
				//   1: inside ade popup
				//   0: outside ade popup
				var outerClick = $('.ade-popup').has(e.target).length === 0;

				// check if modal for link is shown
				var modalShown = $('.mce-floatpanel').css('display') === 'block';

				if (ready && !modalShown && outerClick) {
					// some elements are outside popup but belong to mce
					// these elements start with the text 'mce_' or have a parent/grandparent that starts with the text 'mce_'
					// the latter include texcolor color pickup background element, link ok and cancel buttons

					// check if id starts with 'mce_'
					//   0: true
					//  -1: false
					var parent = e.target;
					var startsMce = false;
					while (parent) {
						if (parent.id.search('mce_') === 0) {
							startsMce = true;
							break;
						}
						parent = parent.parentElement;
					}

					// blur and save changes
					if (!startsMce) {
						mouseout();
						saveEdit(0);
						// reset ready
						ready = false;
					}
				} else {
					// set a timeout so it doesn't trigger during initialization
					setTimeout(function() { ready = true}, 500);
				}
			};

			// handle special keyboard events
			var handleKeyEvents = function(e) {
				switch(e.keyCode) {
					case 27: // esc
						mouseout();
						saveEdit(3); // don't save results
						e.preventDefault();
						break;
					case 9: // tab
						mouseout();
						saveEdit(0); // blur and save
						e.preventDefault();
						break;
					default:
						break;
				}
			};

			//enters edit mode for the text
			var editRichText = function() {
				window.clearTimeout(timeout);
				if(input) input.unbind('.ADE');

				scope.ADE_hidePopup();

				var content = '<textarea id="tinyText" class="' + options.className + '" style="height:30px">' + value + '</textarea>';

				var elOffset = element.offset();
				var posLeft = elOffset.left;
				var posTop = elOffset.top + element[0].offsetHeight;
				var html = '
' + content + '
';
				$compile(html)(scope).insertAfter(element);

				// Initialize tinymce
				// Full example:
				//   http://www.tinymce.com/tryit/full.php

				tinymce.init({
					selector: "#tinyText",
					theme: "modern",
					menubar: "false",
					plugins: ["textcolor", "link"],
					toolbar: "styleselect | bold italic | bullist numlist outdent indent | hr | link | forecolor backcolor",
					// CHANGE: Added to TinyMCE plugin
					handleKeyEvents: handleKeyEvents
				});

				editing = true;

				input = element.next('.ade-rich');

				// Handle blur case
				// save when user blurs out of text editor
				// listen to clicks on all elements in page
				// this will determine when to blur
				$(document).bind('click.ADE', outerBlur);
			};

			//When the mouse enters, show the popup view of the note
			var mousein = function()  {
				window.clearTimeout(timeout);

				//if any other popup is open in edit mode, don't do this view
				if (angular.element('.ade-rich').hasClass('open') && angular.element('.ade-rich').find('textarea').length) return;

				var linkPopup = element.next('.ade-rich');
				if (!linkPopup.length) {
					viewRichText();
				}
			};

			//if the mouse leaves, hide the popup note view if in read mode
			var mouseout = function() {
				var linkPopup = element.next('.' + ADE.popupClass + '');
				if (linkPopup.length && !editing) { //checks for read/edit mode
					timeout = window.setTimeout(function() {
						scope.ADE_hidePopup(element);
					},400);
				}
			};

			//handles clicks on the read version of the data
			var mouseclick = function() {
				if(element) element.unbind('keypress.ADE');
				window.clearTimeout(timeout);
				if (editing) return;
				editing = true;
				exit = 0;

				ADE.begin(options);

				editRichText();
			};

			element.bind('mouseenter.ADE', mousein);
			element.bind('mouseleave.ADE', mouseout);
			element.bind('click.ADE', mouseclick);

			//handles focus events
			element.bind('focus.ADE', function(e) {

				//if this is an organic focus, then do a click to make the popup appear.
				//if this was a focus caused my myself then don't do the click
				if (!element.data('dontclick')) {
					element.click();
					return;
				}
				window.setTimeout(function() { //IE needs this delay because it fires 2 focus events in quick succession.
					element.data('dontclick',false);
				},100);

				//listen for keys pressed while the element is focused but not clicked
				element.bind('keypress.ADE', function(e) {
					if (e.keyCode == 13) { //return
						e.preventDefault();
						e.stopPropagation(); //to prevent return key from going into text box
						element.click();
					} else if (e.keyCode != 9) { //not tab
						//for a key other than tab we want it to go into the text box
						element.click();
					}
				});

			});

			//handles blur events
			element.bind('blur.ADE', function(e) {
				if(element) element.unbind('keypress.ADE');
			});

			// Watches for changes to the element
			// TODO: understand why I have to return the observer and why the observer returns element
			return attrs.$observe('adeRich', function(settings) { //settings is the contents of the ade-rich="" string
				options = ADE.parseSettings(settings, {className: 'input-xlarge'});
				return element;
			});
		}
	};
}]);
<commit_msg>Removed old code<commit_after>/* ==================================================================
	AngularJS Datatype Editor - Rich Text
	A directive to edit a large text blob in place.
	TODO: In the future it will allow rich text formatting

	Usage:

{{data}}

	Config:
	"class" will be added to the input box so you can style it.
	"id" will be used in messages broadcast to the app on state changes.

	Messages:
		name: ADE-start
		data: id from config

		name: ADE-finish
		data: {id from config, old value, new value, exit value}

------------------------------------------------------------------*/

angular.module('ADE').directive('adeRich', ['ADE', '$compile', function(ADE, $compile) {
	return {
		require: '?ngModel', //optional dependency for ngModel
		restrict: 'A', //Attribute declaration eg:

		//The link step (after compile)
		link: function(scope, element, attrs, controller) {
			var options = {};
			var editing = false;
			var txtArea = null;
			var input = null;
			var value = '';
			var oldValue = '';
			var exit = 0; //0=click, 1=tab, -1= shift tab, 2=return, -2=shift return, 3=esc. controls if you exited the field so you can focus the next field if appropriate
			var timeout = null; //the delay when mousing out of the ppopup

			//whenever the model changes, we get called so we can update our value
			if (controller !== null && controller !== undefined) {
				controller.$render = function() {
					oldValue = value = controller.$modelValue;
					if (value === undefined || value === null) value = '';
					return controller.$viewValue;
				};
			}

			//called once the edit is done, so we can save the new data	and remove edit mode
			var saveEdit = function(exited) {
				oldValue = value;
				exit = exited;

				if (exited != 3) { //don't save value on esc
					value = $('#tinyText_ifr').contents().find('#tinymce')[0].innerHTML;
					// check if contents are empty
					if (value === '

') {
						value = '';
					}
					controller.$setViewValue(value);
				}

				input.remove();
				editing = false;

				ADE.done(options, oldValue, value, exit);

				if (exit == 1) {
					element.data('dontclick', true); //tells the focus handler not to click
					element.focus();
					//TODO: would prefer to advance the focus to the next logical element on the page
				} else if (exit == -1) {
					element.data('dontclick', true); //tells the focus handler not to click
					element.focus();
					//TODO: would prefer to advance the focus to the previous logical element on the page
				}

				// we're done, no need to listen to events
				$(document).off('click.ADE');

				scope.$digest();
			};

			//shows a popup with the full text in read mode
			//TODO: handle scrolling of very long text blobs
			var viewRichText = function() {
				scope.ADE_hidePopup();

				var elOffset = element.offset();
				var posLeft = elOffset.left;
				var posTop = elOffset.top + element[0].offsetHeight;
				var content = value.replace ? value.replace(/\n/g, '
') : value; //what is inside the popup

				if (!content) return; //dont show popup if there is nothing to show

				$compile('

' + content + '

')(scope).insertAfter(element);

				editing = false;

				input = element.next('.ade-rich');
				input.bind('mouseenter.ADE', mousein);
				input.bind('mouseleave.ADE', mouseout);
				input.bind('click.ADE', mouseclick);
			};

			//sets the height of the textarea based on the actual height of the contents.
			//min and max are set in css
			var textareaHeight = function(elem) {
				elem.style.height = '1px';
				elem.style.height = (elem.scrollHeight) + 'px';
			};

			// don't blur on initialization
			var ready = false;

			// detect clicks outside tinymce textarea
			var outerBlur = function(e) {
				// check where click occurred
				//   1: inside ade popup
				//   0: outside ade popup
				var outerClick = $('.ade-popup').has(e.target).length === 0;

				// check if modal for link is shown
				var modalShown = $('.mce-floatpanel').css('display') === 'block';

				if (ready && !modalShown && outerClick) {
					// some elements are outside popup but belong to mce
					// these elements start with the text 'mce_' or have a parent/grandparent that starts with the text 'mce_'
					// the latter include texcolor color pickup background element, link ok and cancel buttons

					// check if id starts with 'mce_'
					//   0: true
					//  -1: false
					var parent = e.target;
					var startsMce = false;
					while (parent) {
						if (parent.id.search('mce_') === 0) {
							startsMce = true;
							break;
						}
						parent = parent.parentElement;
					}

					// blur and save changes
					if (!startsMce) {
						mouseout();
						saveEdit(0);
						// reset ready
						ready = false;
					}
				} else {
					// set a timeout so it doesn't trigger during initialization
					setTimeout(function() { ready = true}, 500);
				}
			};

			// handle special keyboard events
			var handleKeyEvents = function(e) {
				switch(e.keyCode) {
					case 27: // esc
						mouseout();
						saveEdit(3); // don't save results
						e.preventDefault();
						break;
					case 9: // tab
						mouseout();
						saveEdit(0); // blur and save
						e.preventDefault();
						break;
					default:
						break;
				}
			};

			//enters edit mode for the text
			var editRichText = function() {
				window.clearTimeout(timeout);
				if(input) input.unbind('.ADE');

				scope.ADE_hidePopup();

				var content = '<textarea id="tinyText" class="' + options.className + '" style="height:30px">' + value + '</textarea>';

				var elOffset = element.offset();
				var posLeft = elOffset.left;
				var posTop = elOffset.top + element[0].offsetHeight;
				var html = '
' + content + '
';
				$compile(html)(scope).insertAfter(element);

				// Initialize tinymce
				// Full example:
				//   http://www.tinymce.com/tryit/full.php

				tinymce.init({
					selector: "#tinyText",
					theme: "modern",
					menubar: "false",
					plugins: ["textcolor", "link"],
					toolbar: "styleselect | bold italic | bullist numlist outdent indent | hr | link | forecolor backcolor",
					// CHANGE: Added to TinyMCE plugin
					handleKeyEvents: handleKeyEvents
				});

				editing = true;

				input = element.next('.ade-rich');

				// Handle blur case
				// save when user blurs out of text editor
				// listen to clicks on all elements in page
				// this will determine when to blur
				$(document).bind('click.ADE', outerBlur);
			};

			//When the mouse enters, show the popup view of the note
			var mousein = function()  {
				window.clearTimeout(timeout);

				//if any other popup is open in edit mode, don't do this view
				if (angular.element('.ade-rich').hasClass('open') && angular.element('.ade-rich').find('textarea').length) return;

				var linkPopup = element.next('.ade-rich');
				if (!linkPopup.length) {
					viewRichText();
				}
			};

			//if the mouse leaves, hide the popup note view if in read mode
			var mouseout = function() {
				var linkPopup = element.next('.' + ADE.popupClass + '');
				if (linkPopup.length && !editing) { //checks for read/edit mode
					timeout = window.setTimeout(function() {
						scope.ADE_hidePopup(element);
					},400);
				}
			};

			//handles clicks on the read version of the data
			var mouseclick = function() {
				if(element) element.unbind('keypress.ADE');
				window.clearTimeout(timeout);
				if (editing) return;
				editing = true;
				exit = 0;

				ADE.begin(options);

				editRichText();
			};

			element.bind('mouseenter.ADE', mousein);
			element.bind('mouseleave.ADE', mouseout);
			element.bind('click.ADE', mouseclick);

			// Watches for changes to the element
			// TODO: understand why I have to return the observer and why the observer returns element
			return attrs.$observe('adeRich', function(settings) { //settings is the contents of the ade-rich="" string
				options = ADE.parseSettings(settings, {className: 'input-xlarge'});
				return element;
			});
		}
	};
}]);
<|endoftext|>


<commit_before>var acg = acg || {};
acg.ext = acg.ext || {};

var random_highbyte = function () {
    return Math.floor(Math.random() * 128 + 128);
};

var init2014 = function () {
    var secsRemaining2014 = Math.floor((new Date(2015, 0, 1) - Date.now()) / 1000);
    acg.put(0, acg.text(
        {x: 0.5, y: 0.75, fontsize: 36,
            text: '2014 剩余的秒数',
            colour: [255, 255, 255]},
        ['+',
            ['delay', 10],
            ['tint-to', 0.2, 0, 0, 0],
            ['delay', secsRemaining2014 - 10.2]
        ]
    ));
    acg.put(0.01, acg.text(
        {x: 0.5, y: 0.5, fontsize: 64,
            text: secsRemaining2014.toString(),
            fontname: 'Courier New',
            colour: [255, 255, 255]},
        ['//',
            ['+',
                ['delay', 10],
                ['tint-to', 0.2, 0, 0, 0]
            ],
            ['+',
                ['go-number', secsRemaining2014 - 60, 60],
                ['ease-cubic-out', ['scale-to', 0.15, 1.1]],
                ['go-number', 30 - 0.15, 30],
                ['ease-cubic-out', ['scale-to', 0.15, 1.2]],
                ['go-number', 20 - 0.15, 10],
                ['ease-cubic-out', ['scale-to', 0.15, 1.3]],
                ['go-number', 10 - 0.15, 0],
                ['call-func', function () { window.location.reload(); }],
                ['delay', 1]    // 防止CallFunc调用直接被跳过
            ]
        ]
    ));
    var d = new Date();
    if (d.getHours() < 23 || d.getMinutes() <= 56) {
        acg.put(0.01, acg.text(
            {x: 0.5, y: 0.25, fontsize: 24,
                text: '记得到23:57左右的时候再来看看～',
                colour: [255, 255, 255]},
            ['+',
                ['delay', 5],
                ['fade-out', 0.2]
            ]
        ));
    }
    acg.begin_offset(secsRemaining2014);
    var t = 200;
    acg.put(-t, acg.text(
        {x: 0.5, y: 0.25, fontsize: 24,
            text: '崭新的旅程即将开启！！\n明年，在那边见～～～',
            colour: [0, 0, 0]},
        ['delay', t]
    ));
    acg.put(-t, acg.text(
        {x: 0.5, y: 0.12, fontsize: 18,
            text: '不不不，不要想歪……只是脑洞又大开了一次而已',
            colour: [0, 0, 0]},
        ['delay', t]
    ));
    acg.end_offset();
    var cur_t = 0;
    while (secsRemaining2014 > cur_t) {
        cur_t += 10;
        acg.ext.background(cur_t, [random_highbyte(), random_highbyte(), random_highbyte()]);
    }
    acg.ext.background_commit();
    acg.commit();
    acg.travel(0);
};

var init2015 = function () {
    acg.begin_offset(2);
    acg.ext.background(-1, [0, 192, 255]);
    acg.put(0, acg.text(
        {x: 0.5, y: 0.75, fontsize: 50,
            text: 'WELCOME TO',
            colour: [0, 192, 255]},
        ['+',
            ['tint-to', 0.2, 0, 0, 0],
            ['delay', 3.8],
            ['tint-to', 0.2, 0, 192, 255]
        ]
    ));
    acg.put(1, acg.text(
        {x: 0.5, y: 1.5, fontsize: 80,
            text: '2015!!!',
            colour: [0, 0, 0]},
        ['+',
            ['ease-elastic-out', ['move-to', 1, acg.p(0.5, 0.4)], 0.4],
            ['delay', 2],
            ['tint-to', 0.2, 0, 192, 255]
        ]
    ));
    acg.end_offset();
    acg.ext.background_commit();
    acg.commit();
    acg.travel(0);
    acg.ext.cp_enable();
};

var initStage = function () {
    if ((new Date()).getFullYear() == 2015) {
        init2014();
    } else {
        init2015();
    }
    // 不管的，直接用Cocos API了！！
    cc.director.setDisplayStats(false);
};

acg.bootstrap('game_canvas', 16 / 9, initStage);
<commit_msg>完成更多，洗澡去先～～<commit_after>var acg = acg || {};
acg.ext = acg.ext || {};

var random_highbyte = function () {
    return Math.floor(Math.random() * 128 + 128);
};

var init2014 = function () {
    var secsRemaining2014 = Math.floor((new Date(2015, 0, 1) - Date.now()) / 1000);
    acg.put(0, acg.text(
        {x: 0.5, y: 0.75, fontsize: 36,
            text: '2014 剩余的秒数',
            colour: [255, 255, 255]},
        ['+',
            ['delay', 10],
            ['tint-to', 0.2, 0, 0, 0],
            ['delay', secsRemaining2014 - 10.2]
        ]
    ));
    acg.put(0.01, acg.text(
        {x: 0.5, y: 0.5, fontsize: 64,
            text: secsRemaining2014.toString(),
            fontname: 'Courier New',
            colour: [255, 255, 255]},
        ['//',
            ['+',
                ['delay', 10],
                ['tint-to', 0.2, 0, 0, 0]
            ],
            ['+',
                ['go-number', secsRemaining2014 - 60, 60],
                ['ease-cubic-out', ['scale-to', 0.15, 1.1]],
                ['go-number', 30 - 0.15, 30],
                ['ease-cubic-out', ['scale-to', 0.15, 1.2]],
                ['go-number', 20 - 0.15, 10],
                ['ease-cubic-out', ['scale-to', 0.15, 1.3]],
                ['go-number', 10 - 0.15, 0],
                ['call-func', function () { window.location.reload(); }],
                ['delay', 1]    // 防止CallFunc调用直接被跳过
            ]
        ]
    ));
    var d = new Date();
    if (d.getHours() < 23 || d.getMinutes() <= 56) {
        acg.put(0.01, acg.text(
            {x: 0.5, y: 0.25, fontsize: 24,
                text: '记得到23:57左右的时候再来看看～',
                colour: [255, 255, 255]},
            ['+',
                ['delay', 5],
                ['fade-out', 0.2]
            ]
        ));
    }
    acg.begin_offset(secsRemaining2014);
    var t = 200;
    acg.put(-t, acg.text(
        {x: 0.5, y: 0.25, fontsize: 24,
            text: '崭新的旅程即将开启！！\n明年，在那边见～～～',
            colour: [0, 0, 0]},
        ['delay', t]
    ));
    acg.put(-t, acg.text(
        {x: 0.5, y: 0.12, fontsize: 18,
            text: '不不不，不要想歪……只是脑洞又大开了一次而已',
            colour: [0, 0, 0]},
        ['delay', t]
    ));
    acg.end_offset();
    var cur_t = 0;
    while (secsRemaining2014 > cur_t) {
        cur_t += 10;
        acg.ext.background(cur_t, [random_highbyte(), random_highbyte(), random_highbyte()]);
    }
    acg.ext.background_commit();
    acg.commit();
    acg.travel(0);
};

var init2015 = function () {
    acg.begin_offset(2);
    acg.ext.background(-1, [0, 192, 255]);
    acg.put(0, acg.text(
        {x: 0.5, y: 0.75, fontsize: 50,
            text: 'WELCOME TO',
            colour: [0, 192, 255]},
        ['+',
            ['tint-to', 0.2, 0, 0, 0],
            ['delay', 3.8],
            ['tint-to', 0.2, 0, 192, 255]
        ]
    ));
    acg.put(1, acg.text(
        {x: 0.5, y: 1.5, fontsize: 80,
            text: '2015!!!',
            colour: [0, 0, 0]},
        ['+',
            ['ease-elastic-out', ['move-to', 1, acg.p(0.5, 0.4)], 0.4],
            ['delay', 2],
            ['tint-to', 0.2, 0, 192, 255]
        ]
    ));
    // -- PLACEHOLDER --
    acg.put(40, acg.text(
        {x: 0.5, y: 1.5, fontsize: 80,
            text: '2015!!!',
            colour: [0, 0, 0]}, ['delay', 2]
    ));
    acg.ext.words(3.7, '好吧……');
    acg.ext.words(4.5, '我觉得刚才这玩意应该没必要放的。');
    acg.ext.words(6.2, '毕竟你们都懂的，现在是2015年了。');
    acg.ext.words(8.7, '哎，对了，');
    acg.ext.words(9.7, '你有没有奇怪，');
    acg.ext.words(10.7, '下面的这个白色条是做甚的？');
    acg.ext.words(12.7, '嗯，你没猜错，');
    acg.ext.words(14.1, '这货就是给我说话用的。');
    acg.ext.words(15.6, '好了，好了，不用再调音量了，');
    acg.ext.words(18.2, '这次是没有声音的。');
    acg.ext.words(20.0, '不过下次，');
    acg.ext.words(20.9, '我应该会拿个机器人讲出声音来。');
    acg.ext.words(22.8, '好了，下面进入正题。');
    acg.ext.words(23.7, '');
    acg.ext.background(24, [0, 64, 128]);
    acg.ext.words(25, '在这辞旧迎新的时刻，');
    acg.ext.words(26.0, '我想向大家解释一下，');
    acg.ext.words(27.0, '学校饮水机上面的数字到底是什么。');
    acg.put(27.8, acg.text(
        {x: 0.5, y: 0.7, fontsize: 32,
            text: '//这两件事有什么关系吗……',
            colour: [255, 255, 255]}, ['delay', 2]
    ));
    acg.end_offset();
    acg.ext.background_commit();
    acg.ext.words_commit();
    acg.commit();
    acg.travel(0);
    acg.ext.cp_enable();
};

var initStage = function () {
    if ((new Date()).getFullYear() == 2015) {
        init2014();
    } else {
        init2015();
    }
    // 不管的，直接用Cocos API了！！
    cc.director.setDisplayStats(false);
};

acg.bootstrap('game_canvas', 16 / 9, initStage);
<|endoftext|>


<commit_before>import Ember from 'ember';
import RlDropdownContainer from './rl-dropdown-container';

export default Ember.Component.extend({
  classNames: ['rl-dropdown'],

  dropdownContainer: Ember.computed(function () {
    return this.nearestOfType(RlDropdownContainer);
  }),

  isExpanded: Ember.computed.alias('dropdownContainer.dropdownExpanded'),

  closeOnChildClick: false,

  propagateClicks: true,

  manageVisibility: Ember.on('didInsertElement', Ember.observer('isExpanded', function () {
    if (this.get('isExpanded')) {
      this.$().css('display', 'block');
    } else {
      this.$().css('display', 'none');
    }
  })),

  click(event) {
    let closeOnChildClick = this.get('closeOnChildClick');
    let propagateClicks = this.get('propagateClicks');
    let $target = Ember.$(event.target);
    let $c = this.$();

    if ($target !== $c) {
      if ((closeOnChildClick === true || closeOnChildClick === "true") && $target.closest($c).length) {
        this.set('isExpanded', false);
      } else if (closeOnChildClick && $target.closest($c.find(closeOnChildClick)).length) {
        this.set('isExpanded', false);
      }
    }

    if (propagateClicks === false || propagateClicks === "false") {
      event.stopPropagation();
    }
  }
});
<commit_msg>Better target matching in click handler (#20)<commit_after>import Ember from 'ember';
import RlDropdownContainer from './rl-dropdown-container';

export default Ember.Component.extend({
  classNames: ['rl-dropdown'],

  dropdownContainer: Ember.computed(function () {
    return this.nearestOfType(RlDropdownContainer);
  }),

  isExpanded: Ember.computed.alias('dropdownContainer.dropdownExpanded'),

  closeOnChildClick: false,

  propagateClicks: true,

  manageVisibility: Ember.on('didInsertElement', Ember.observer('isExpanded', function () {
    if (this.get('isExpanded')) {
      this.$().css('display', 'block');
    } else {
      this.$().css('display', 'none');
    }
  })),

  click(event) {
    let closeOnChildClick = this.get('closeOnChildClick');
    let propagateClicks = this.get('propagateClicks');
    let $target = Ember.$(event.target);
    let $c = this.$();

    if ($target !== $c) {
      if ((closeOnChildClick === true || closeOnChildClick === "true") && $target.closest($c).length) {
        this.set('isExpanded', false);
      } else if (closeOnChildClick && $target.closest(closeOnChildClick, $c).length) {
        this.set('isExpanded', false);
      }
    }

    if (propagateClicks === false || propagateClicks === "false") {
      event.stopPropagation();
    }
  }
});
<|endoftext|>


<commit_before>/*global define*/
define([
        '../ThirdParty/measureText',
        './Color',
        './defaultValue',
        './defined',
        './DeveloperError'
    ], function(
        measureText,
        Color,
        defaultValue,
        defined,
        DeveloperError) {
    "use strict";

    /**
     * Writes the given text into a new canvas.  The canvas will be sized to fit the text.
     * If text is blank, returns undefined.
     *
     * @param {String} text The text to write.
     * @param {Object} [options] Object with the following properties:
     * @param {String} [options.font='10px sans-serif'] The CSS font to use.
     * @param {String} [options.textBaseline='bottom'] The baseline of the text.
     * @param {Boolean} [options.fill=true] Whether to fill the text.
     * @param {Boolean} [options.stroke=false] Whether to stroke the text.
     * @param {Color} [options.fillColor=Color.WHITE] The fill color.
     * @param {Color} [options.strokeColor=Color.BLACK] The stroke color.
     * @param {Color} [options.strokeWidth=1] The stroke width.
     * @returns {Canvas} A new canvas with the given text drawn into it.  The dimensions object
     *                   from measureText will also be added to the returned canvas. If text is
     *                   blank, returns undefined.
     */
    var writeTextToCanvas = function(text, options) {
        //>>includeStart('debug', pragmas.debug);
        if (!defined(text)) {
            throw new DeveloperError('text is required.');
        }
        //>>includeEnd('debug');
        if (text === '') {
            return undefined;
        }

        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var font = defaultValue(options.font, '10px sans-serif');
        var stroke = defaultValue(options.stroke, false);
        var fill = defaultValue(options.fill, true);
        var strokeWidth = defaultValue(options.strokeWidth, 1);

        var canvas = document.createElement('canvas');
        canvas.width = 1;
        canvas.height = 1;
        canvas.style.font = font;

        var context2D = canvas.getContext('2d');
        context2D.font = font;
        context2D.lineJoin = 'round';
        context2D.lineWidth = strokeWidth;

        // textBaseline needs to be set before the measureText call. It won't work otherwise.
        // It's magic.
        context2D.textBaseline = defaultValue(options.textBaseline, 'bottom');

        // in order for measureText to calculate style, the canvas has to be
        // (temporarily) added to the DOM.
        canvas.style.visibility = 'hidden';
        document.body.appendChild(canvas);

        var dimensions = measureText(context2D, text, stroke, fill);
        dimensions.computedWidth = Math.max(dimensions.width, dimensions.bounds.maxx - dimensions.bounds.minx);
        canvas.dimensions = dimensions;

        document.body.removeChild(canvas);
        canvas.style.visibility = '';

        var baseline = dimensions.height - dimensions.ascent;
        canvas.width = dimensions.computedWidth;
        canvas.height = dimensions.height;
        var y = canvas.height - baseline;

        // Properties must be explicitly set again after changing width and height
        context2D.font = font;
        context2D.lineJoin = 'round';
        context2D.lineWidth = strokeWidth;

        if (stroke) {
            var strokeColor = defaultValue(options.strokeColor, Color.BLACK);
            context2D.strokeStyle = strokeColor.toCssColorString();
            context2D.strokeText(text, 0, y);
        }

        if (fill) {
            var fillColor = defaultValue(options.fillColor, Color.WHITE);
            context2D.fillStyle = fillColor.toCssColorString();
            context2D.fillText(text, 0, y);
        }

        return canvas;
    };

    return writeTextToCanvas;
});<commit_msg>Disable image smoothing in writeTextToCanvas.<commit_after>/*global define*/
define([
        '../ThirdParty/measureText',
        './Color',
        './defaultValue',
        './defined',
        './DeveloperError'
    ], function(
        measureText,
        Color,
        defaultValue,
        defined,
        DeveloperError) {
    "use strict";

    var imageSmoothingEnabledName;

    /**
     * Writes the given text into a new canvas.  The canvas will be sized to fit the text.
     * If text is blank, returns undefined.
     *
     * @param {String} text The text to write.
     * @param {Object} [options] Object with the following properties:
     * @param {String} [options.font='10px sans-serif'] The CSS font to use.
     * @param {String} [options.textBaseline='bottom'] The baseline of the text.
     * @param {Boolean} [options.fill=true] Whether to fill the text.
     * @param {Boolean} [options.stroke=false] Whether to stroke the text.
     * @param {Color} [options.fillColor=Color.WHITE] The fill color.
     * @param {Color} [options.strokeColor=Color.BLACK] The stroke color.
     * @param {Color} [options.strokeWidth=1] The stroke width.
     * @returns {Canvas} A new canvas with the given text drawn into it.  The dimensions object
     *                   from measureText will also be added to the returned canvas. If text is
     *                   blank, returns undefined.
     */
    var writeTextToCanvas = function(text, options) {
        //>>includeStart('debug', pragmas.debug);
        if (!defined(text)) {
            throw new DeveloperError('text is required.');
        }
        //>>includeEnd('debug');
        if (text === '') {
            return undefined;
        }

        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var font = defaultValue(options.font, '10px sans-serif');
        var stroke = defaultValue(options.stroke, false);
        var fill = defaultValue(options.fill, true);
        var strokeWidth = defaultValue(options.strokeWidth, 1);

        var canvas = document.createElement('canvas');
        canvas.width = 1;
        canvas.height = 1;
        canvas.style.font = font;

        var context2D = canvas.getContext('2d');

        if (!defined(imageSmoothingEnabledName)) {
            if (defined(context2D.imageSmoothingEnabled)) {
                imageSmoothingEnabledName = 'imageSmoothingEnabled';
            } else if (defined(context2D.mozImageSmoothingEnabled)) {
                imageSmoothingEnabledName = 'mozImageSmoothingEnabled';
            } else if (defined(context2D.webkitImageSmoothingEnabled)) {
                imageSmoothingEnabledName = 'webkitImageSmoothingEnabled';
            } else if (defined(context2D.msImageSmoothingEnabled)) {
                imageSmoothingEnabledName = 'msImageSmoothingEnabled';
            }
        }

        context2D.font = font;
        context2D.lineJoin = 'round';
        context2D.lineWidth = strokeWidth;
        context2D[imageSmoothingEnabledName] = false;

        // textBaseline needs to be set before the measureText call. It won't work otherwise.
        // It's magic.
        context2D.textBaseline = defaultValue(options.textBaseline, 'bottom');

        // in order for measureText to calculate style, the canvas has to be
        // (temporarily) added to the DOM.
        canvas.style.visibility = 'hidden';
        document.body.appendChild(canvas);

        var dimensions = measureText(context2D, text, stroke, fill);
        dimensions.computedWidth = Math.max(dimensions.width, dimensions.bounds.maxx - dimensions.bounds.minx);
        canvas.dimensions = dimensions;

        document.body.removeChild(canvas);
        canvas.style.visibility = '';

        var baseline = dimensions.height - dimensions.ascent;
        canvas.width = dimensions.computedWidth;
        canvas.height = dimensions.height;
        var y = canvas.height - baseline;

        // Properties must be explicitly set again after changing width and height
        context2D.font = font;
        context2D.lineJoin = 'round';
        context2D.lineWidth = strokeWidth;
        context2D[imageSmoothingEnabledName] = false;

        if (stroke) {
            var strokeColor = defaultValue(options.strokeColor, Color.BLACK);
            context2D.strokeStyle = strokeColor.toCssColorString();
            context2D.strokeText(text, 0, y);
        }

        if (fill) {
            var fillColor = defaultValue(options.fillColor, Color.WHITE);
            context2D.fillStyle = fillColor.toCssColorString();
            context2D.fillText(text, 0, y);
        }

        return canvas;
    };

    return writeTextToCanvas;
});<|endoftext|>


<commit_before>import { A, isArray } from '@ember/array';
import { inject as service } from '@ember/service';
import { run } from '@ember/runloop';
import Route from '@ember/routing/route';
import $ from 'jquery';
import Analytics from 'ember-osf/mixins/analytics';
import config from 'ember-get-config';
import permissions from 'ember-osf/const/permissions';
import extractDoiFromString from 'ember-osf/utils/extract-doi-from-string';

import ResetScrollMixin from '../../mixins/reset-scroll';
import SetupSubmitControllerMixin from '../../mixins/setup-submit-controller';

const {
    OSF: { url: osfUrl },
} = config;

// Error handling for API
const handlers = new Map([
    // format: ['Message detail', 'page']
    ['Authentication credentials were not provided.', 'page-not-found'], // 401
    ['You do not have permission to perform this action.', 'page-not-found'], // 403
    ['Not found.', 'page-not-found'], // 404
    ['The requested node is no longer available.', 'resource-deleted'], // 410
]);

/**
 * @module ember-preprints
 * @submodule routes
 */

/**
 * Fetches current preprint. Redirects to preprint provider route if necessary.
 * @class Content Route Handler
 */
export default Route.extend(Analytics, ResetScrollMixin, SetupSubmitControllerMixin, {
    theme: service(),
    headTagsService: service('head-tags'),
    currentUser: service('currentUser'),

    downloadUrl: '',
    preprint: null,
    contributors: A(),

    afterModel(preprint) {
        const { location: { origin } } = window;

        const downloadUrl = [
            origin,
            this.get('theme.isSubRoute') ? `preprints/${this.get('theme.id')}` : null,
            preprint.get('id'),
            'download',
        ]
            .filter(part => !!part)
            .join('/');
        this.set('downloadUrl', downloadUrl);

        this.set('fileDownloadURL', downloadUrl);
        this.set('preprint', preprint);

        return preprint.get('provider')
            .then(this._getProviderDetails.bind(this))
            .then(this._getUserPermissions.bind(this))
            .then(this._setupMetaData.bind(this));
    },

    setupController(controller, model) {
        controller.setProperties({
            activeFile: model.get('primaryFile'),
            node: this.get('node'),
            fileDownloadURL: this.get('fileDownloadURL'),
        });

        run.scheduleOnce('afterRender', this, function() {
            MathJax.Hub.Queue(['Typeset', MathJax.Hub, [$('.abstract')[0], $('#preprintTitle')[0]]]);
        });

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

    actions: {
        error(error) {
            // Handle API Errors
            if (error && error.errors && isArray(error.errors)) {
                const { detail } = error.errors[0];
                const page = handlers.get(detail) || 'page-not-found';

                return this.intermediateTransitionTo(page);
            }
        },
        didTransition() {
            const ev = document.createEvent('HTMLEvents');
            ev.initEvent('ZoteroItemUpdated', true, true);
            document.dispatchEvent(ev);
            return true; // Bubble the didTransition event
        },
    },

    _getProviderDetails(provider) {
        const providerId = provider.get('id');
        const themeId = this.get('theme.id');
        const isOSF = providerId === 'osf';
        const preprint = this.get('preprint');

        // If we're on the proper branded site, stay here.
        if (themeId === providerId) {
            return Promise.all([
                provider,
                preprint.get('node'),
            ]);
        }

        window.location.replace(`${osfUrl}${isOSF ? '' : `preprints/${providerId}/`}${preprint.get('id')}/`);
        return Promise.reject();
    },

    _getUserPermissions([provider, node]) {
        // const contributors = this.get('contributors');
        const preprint = this.get('preprint');

        this.set('node', node);

        if (this.get('editMode')) {
            const userPermissions = this.get('node.currentUserPermissions') || [];

            if (!userPermissions.includes(permissions.ADMIN)) {
                this.replaceWith('forbidden'); // Non-admin trying to access edit form.
            }
        }

        return Promise.all([
            provider,
            node,
            preprint.get('license'),
            // Don't bother putting contributors in <meta> tags to avoid blocking page render
            // loadAll(node, 'contributors', contributors, { filter: { bibliographic: true } }),
        ]);
    },

    _setupMetaData([provider, license]) {
        const { origin } = window.location;
        const preprint = this.get('preprint');
        const title = preprint.get('title');
        const description = preprint.get('description');
        const facebookAppId = provider.get('facebookAppId') || config.FB_APP_ID;
        const mintDoi = extractDoiFromString(preprint.get('preprintDoiUrl'));
        const peerDoi = preprint.get('doi');
        const doi = peerDoi || mintDoi;
        const image = this.get('theme.logoSharing');
        const imageUrl = /^https?:\/\//.test(image.path) ? image.path : origin + image.path;
        const dateCreated = new Date(preprint.get('dateCreated') || null);
        const dateModified = new Date(preprint.get('dateModified') || dateCreated);
        if (!preprint.get('datePublished')) { preprint.set('datePublished', dateCreated); }
        const providerName = provider.get('name') === 'Open Science Framework' ? 'OSF Preprints' : provider.get('name');
        const canonicalUrl = preprint.get('links.html');
        const contributors = this.get('contributors');
        const downloadUrl = this.get('downloadUrl');

        // NOTE: Ordering of meta tags matters for scrapers
        // EX: (Facebook, LinkedIn, Google, etc)

        // Open Graph Protocol
        const openGraph = [
            ['fb:app_id', facebookAppId],
            ['og:title', title],
            ['og:image', imageUrl],
            ['og:image:width', image.width.toString()],
            ['og:image:height', image.height.toString()],
            ['og:image:type', image.type],
            ['og:url', canonicalUrl],
            ['og:description', description],
            ['og:site_name', providerName],
            ['og:type', 'article'],
            ['article:published_time', dateCreated.toISOString()],
            ['article:modified_time', dateModified.toISOString()],
        ];

        // Highwire Press
        const highwirePress = [
            ['citation_title', title],
            ['citation_description', description],
            ['citation_public_url', canonicalUrl],
            ['citation_online_date', `${dateCreated.getFullYear()}/${dateCreated.getMonth() + 1}/${dateCreated.getDate()}`],
        ];
        if (doi) {
            highwirePress.push(['citation_doi', doi]);
        }

        // TODO map Eprints fields
        // Eprints
        const eprints = [];

        // TODO map BE Press fields
        // BE Press
        const bePress = [];

        // TODO map PRISM fields
        // PRISM
        const prism = [];

        // Dublin Core
        const dublinCore = [
            ['dc.title', title],
            ['dc.abstract', description],
            ['dc.identifier', canonicalUrl],
        ];
        if (mintDoi) {
            dublinCore.push(['dc.identifier', `doi:${mintDoi}`]);
        }
        if (peerDoi) {
            dublinCore.push(['dc.relation', `doi:${peerDoi}`]);
        }

        const tags = [
            ...preprint.get('subjects').map(subjectBlock => subjectBlock.map(subject => subject.text)),
            ...preprint.get('tags'),
        ];

        for (const tag of tags) {
            openGraph.push(['article:tag', tag]);
            highwirePress.push(['citation_keywords', tag]);
            dublinCore.push(['dc.subject', tag]);
        }

        for (const contributor of contributors) {
            let givenName = contributor.get('users.givenName');
            let familyName = contributor.get('users.familyName');
            let fullName = contributor.get('users.fullName');

            // If the contributor is unregistered, use the
            // unregistered_contributor field for first/last/middle names
            if (contributor.get('unregisteredContributor')) {
                const unregisteredName = contributor.get('unregisteredContributor').split(' ');
                [givenName] = unregisteredName;
                familyName = unregisteredName.length > 1 ? unregisteredName.pop() : '';
                fullName = contributor.get('unregisteredContributor');
            }

            openGraph.push(
                ['og:type', 'article:author'],
                ['profile:first_name', givenName],
                ['profile:last_name', familyName],
            );
            highwirePress.push(['citation_author', fullName]);
            dublinCore.push(['dc.creator', fullName]);
        }

        highwirePress.push(['citation_publisher', providerName]);
        dublinCore.push(
            ['dc.publisher', providerName],
            ['dc.license', license ? license.get('name') : 'No license'],
        );

        highwirePress.push(['citation_pdf_url', `${downloadUrl}?format=pdf`]);

        const openGraphTags = openGraph
            .map(([property, content]) => ({
                property,
                content,
            }));

        const googleScholarTags = [
            highwirePress,
            eprints,
            bePress,
            prism,
            dublinCore,
        ]
            .reduce((a, b) => a.concat(b), [])
            .map(([name, content]) => ({
                name,
                content,
            }));

        const headTags = [
            ...openGraphTags,
            ...googleScholarTags,
        ]
            .filter(({ content }) => content) // Only show tags with content
            .map(attrs => ({
                type: 'meta',
                attrs,
            }));

        this.set('headTags', headTags);
        this.get('headTagsService').collectHeadTags();
    },
});
<commit_msg>do not wait for metadata if not using prerender, instead wait for node to be set<commit_after>import { A, isArray } from '@ember/array';
import { inject as service } from '@ember/service';
import { run } from '@ember/runloop';
import loadAll from 'ember-osf/utils/load-relationship';
import Route from '@ember/routing/route';
import $ from 'jquery';
import Analytics from 'ember-osf/mixins/analytics';
import config from 'ember-get-config';
import permissions from 'ember-osf/const/permissions';
import extractDoiFromString from 'ember-osf/utils/extract-doi-from-string';

import ResetScrollMixin from '../../mixins/reset-scroll';
import SetupSubmitControllerMixin from '../../mixins/setup-submit-controller';

const {
    OSF: { url: osfUrl },
} = config;

// Error handling for API
const handlers = new Map([
    // format: ['Message detail', 'page']
    ['Authentication credentials were not provided.', 'page-not-found'], // 401
    ['You do not have permission to perform this action.', 'page-not-found'], // 403
    ['Not found.', 'page-not-found'], // 404
    ['The requested node is no longer available.', 'resource-deleted'], // 410
]);

/**
 * @module ember-preprints
 * @submodule routes
 */

/**
 * Fetches current preprint. Redirects to preprint provider route if necessary.
 * @class Content Route Handler
 */
export default Route.extend(Analytics, ResetScrollMixin, SetupSubmitControllerMixin, {
    theme: service(),
    headTagsService: service('head-tags'),
    currentUser: service('currentUser'),

    downloadUrl: '',
    preprint: null,
    waitForMetaData: navigator.userAgent.includes('Prerender'),
    contributors: A(),

    afterModel(preprint) {
        const { location: { origin } } = window;

        const downloadUrl = [
            origin,
            this.get('theme.isSubRoute') ? `preprints/${this.get('theme.id')}` : null,
            preprint.get('id'),
            'download',
        ]
            .filter(part => !!part)
            .join('/');
        this.set('downloadUrl', downloadUrl);

        this.set('fileDownloadURL', downloadUrl);
        this.set('preprint', preprint);

        const setupMetaData = preprint.get('provider')
            .then(this._getProviderDetails.bind(this))
            .then(this._getUserPermissions.bind(this))
            .then(this._setupMetaData.bind(this));

        const setupNode = preprint.get('node').then(this._setupNode.bind(this));

        return this.get('waitForMetaData') ? setupMetaData : setupNode;
    },

    setupController(controller, model) {
        controller.setProperties({
            activeFile: model.get('primaryFile'),
            node: this.get('node'),
            fileDownloadURL: this.get('fileDownloadURL'),
        });

        run.scheduleOnce('afterRender', this, function() {
            MathJax.Hub.Queue(['Typeset', MathJax.Hub, [$('.abstract')[0], $('#preprintTitle')[0]]]);
        });

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

    actions: {
        error(error) {
            // Handle API Errors
            if (error && error.errors && isArray(error.errors)) {
                const { detail } = error.errors[0];
                const page = handlers.get(detail) || 'page-not-found';

                return this.intermediateTransitionTo(page);
            }
        },
        didTransition() {
            const ev = document.createEvent('HTMLEvents');
            ev.initEvent('ZoteroItemUpdated', true, true);
            document.dispatchEvent(ev);
            return true; // Bubble the didTransition event
        },
    },

    _getProviderDetails(provider) {
        const providerId = provider.get('id');
        const themeId = this.get('theme.id');
        const isOSF = providerId === 'osf';
        const preprint = this.get('preprint');

        // If we're on the proper branded site, stay here.
        if (themeId === providerId) {
            return Promise.all([
                provider,
                preprint.get('node'),
            ]);
        }

        window.location.replace(`${osfUrl}${isOSF ? '' : `preprints/${providerId}/`}${preprint.get('id')}/`);
        return Promise.reject();
    },

    _getUserPermissions([provider, node]) {
        const contributors = this.get('contributors');
        const preprint = this.get('preprint');
        this.set('node', node);

        if (this.get('editMode')) {
            const userPermissions = this.get('node.currentUserPermissions') || [];

            if (!userPermissions.includes(permissions.ADMIN)) {
                this.replaceWith('forbidden'); // Non-admin trying to access edit form.
            }
        }

        return Promise.all([
            provider,
            preprint.get('license'),
            loadAll(node, 'contributors', contributors, { filter: { bibliographic: true } }),
        ]);
    },

    _setupMetaData([provider, license]) {
        const { origin } = window.location;
        const preprint = this.get('preprint');
        const title = preprint.get('title');
        const description = preprint.get('description');
        const facebookAppId = provider.get('facebookAppId') || config.FB_APP_ID;
        const mintDoi = extractDoiFromString(preprint.get('preprintDoiUrl'));
        const peerDoi = preprint.get('doi');
        const doi = peerDoi || mintDoi;
        const image = this.get('theme.logoSharing');
        const imageUrl = /^https?:\/\//.test(image.path) ? image.path : origin + image.path;
        const dateCreated = new Date(preprint.get('dateCreated') || null);
        const dateModified = new Date(preprint.get('dateModified') || dateCreated);
        if (!preprint.get('datePublished')) { preprint.set('datePublished', dateCreated); }
        const providerName = provider.get('name') === 'Open Science Framework' ? 'OSF Preprints' : provider.get('name');
        const canonicalUrl = preprint.get('links.html');
        const contributors = this.get('contributors');
        const downloadUrl = this.get('downloadUrl');

        // NOTE: Ordering of meta tags matters for scrapers
        // EX: (Facebook, LinkedIn, Google, etc)

        // Open Graph Protocol
        const openGraph = [
            ['fb:app_id', facebookAppId],
            ['og:title', title],
            ['og:image', imageUrl],
            ['og:image:width', image.width.toString()],
            ['og:image:height', image.height.toString()],
            ['og:image:type', image.type],
            ['og:url', canonicalUrl],
            ['og:description', description],
            ['og:site_name', providerName],
            ['og:type', 'article'],
            ['article:published_time', dateCreated.toISOString()],
            ['article:modified_time', dateModified.toISOString()],
        ];

        // Highwire Press
        const highwirePress = [
            ['citation_title', title],
            ['citation_description', description],
            ['citation_public_url', canonicalUrl],
            ['citation_online_date', `${dateCreated.getFullYear()}/${dateCreated.getMonth() + 1}/${dateCreated.getDate()}`],
        ];
        if (doi) {
            highwirePress.push(['citation_doi', doi]);
        }

        // TODO map Eprints fields
        // Eprints
        const eprints = [];

        // TODO map BE Press fields
        // BE Press
        const bePress = [];

        // TODO map PRISM fields
        // PRISM
        const prism = [];

        // Dublin Core
        const dublinCore = [
            ['dc.title', title],
            ['dc.abstract', description],
            ['dc.identifier', canonicalUrl],
        ];
        if (mintDoi) {
            dublinCore.push(['dc.identifier', `doi:${mintDoi}`]);
        }
        if (peerDoi) {
            dublinCore.push(['dc.relation', `doi:${peerDoi}`]);
        }

        const tags = [
            ...preprint.get('subjects').map(subjectBlock => subjectBlock.map(subject => subject.text)),
            ...preprint.get('tags'),
        ];

        for (const tag of tags) {
            openGraph.push(['article:tag', tag]);
            highwirePress.push(['citation_keywords', tag]);
            dublinCore.push(['dc.subject', tag]);
        }

        for (const contributor of contributors) {
            let givenName = contributor.get('users.givenName');
            let familyName = contributor.get('users.familyName');
            let fullName = contributor.get('users.fullName');

            // If the contributor is unregistered, use the
            // unregistered_contributor field for first/last/middle names
            if (contributor.get('unregisteredContributor')) {
                const unregisteredName = contributor.get('unregisteredContributor').split(' ');
                [givenName] = unregisteredName;
                familyName = unregisteredName.length > 1 ? unregisteredName.pop() : '';
                fullName = contributor.get('unregisteredContributor');
            }

            openGraph.push(
                ['og:type', 'article:author'],
                ['profile:first_name', givenName],
                ['profile:last_name', familyName],
            );
            highwirePress.push(['citation_author', fullName]);
            dublinCore.push(['dc.creator', fullName]);
        }

        highwirePress.push(['citation_publisher', providerName]);
        dublinCore.push(
            ['dc.publisher', providerName],
            ['dc.license', license ? license.get('name') : 'No license'],
        );

        highwirePress.push(['citation_pdf_url', `${downloadUrl}?format=pdf`]);

        const openGraphTags = openGraph
            .map(([property, content]) => ({
                property,
                content,
            }));

        const googleScholarTags = [
            highwirePress,
            eprints,
            bePress,
            prism,
            dublinCore,
        ]
            .reduce((a, b) => a.concat(b), [])
            .map(([name, content]) => ({
                name,
                content,
            }));

        const headTags = [
            ...openGraphTags,
            ...googleScholarTags,
        ]
            .filter(({ content }) => content) // Only show tags with content
            .map(attrs => ({
                type: 'meta',
                attrs,
            }));

        this.set('headTags', headTags);
        this.get('headTagsService').collectHeadTags();
    },

    _setupNode(node) {
        this.set('node', node);
    },
});
<|endoftext|>