<commit_before>/* Copyright (c) 2007 Paul Bakaus (paul.bakaus@googlemail.com) and Brandon Aaron (brandon.aaron@gmail.com || http://brandonaaron.net)
 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
 *
 * $LastChangedDate: 2007-06-22 04:38:37 +0200 (Fr, 22 Jun 2007) $
 * $Rev: 2141 $
 *
 * Version: 1.0b2
 */

(function($){

// store a copy of the core height and width methods
var height = $.fn.height,
    width  = $.fn.width;

$.fn.extend({
	/**
	 * If used on document, returns the document's height (innerHeight)
	 * If used on window, returns the viewport's (window) height
	 * See core docs on height() to see what happens when used on an element.
	 *
	 * @example $("#testdiv").height()
	 * @result 200
	 *
	 * @example $(document).height()
	 * @result 800
	 *
	 * @example $(window).height()
	 * @result 400
	 *
	 * @name height
	 * @type Object
	 * @cat Plugins/Dimensions
	 */
	height: function() {
		if ( this[0] == window )
			return self.innerHeight ||
				$.boxModel && document.documentElement.clientHeight ||
				document.body.clientHeight;

		if ( this[0] == document )
			return Math.max( document.body.scrollHeight, document.body.offsetHeight );

		return height.apply(this, arguments);
	},

	/**
	 * If used on document, returns the document's width (innerWidth)
	 * If used on window, returns the viewport's (window) width
	 * See core docs on height() to see what happens when used on an element.
	 *
	 * @example $("#testdiv").width()
	 * @result 200
	 *
	 * @example $(document).width()
	 * @result 800
	 *
	 * @example $(window).width()
	 * @result 400
	 *
	 * @name width
	 * @type Object
	 * @cat Plugins/Dimensions
	 */
	width: function() {
		if ( this[0] == window )
			return self.innerWidth ||
				$.boxModel && document.documentElement.clientWidth ||
				document.body.clientWidth;

		if ( this[0] == document )
			return Math.max( document.body.scrollWidth, document.body.offsetWidth );

		return width.apply(this, arguments);
	},

	/**
	 * Returns the inner height value (without border) for the first matched element.
	 * If used on document, returns the document's height (innerHeight)
	 * If used on window, returns the viewport's (window) height
	 *
	 * @example $("#testdiv").innerHeight()
	 * @result 800
	 *
	 * @name innerHeight
	 * @type Number
	 * @cat Plugins/Dimensions
	 */
	innerHeight: function() {
		return this[0] == window || this[0] == document ?
			this.height() :
			this.is(':visible') ?
				this[0].offsetHeight - num(this, 'borderTopWidth') - num(this, 'borderBottomWidth') :
				this.height() + num(this, 'paddingTop') + num(this, 'paddingBottom');
	},

	/**
	 * Returns the inner width value (without border) for the first matched element.
	 * If used on document, returns the document's Width (innerWidth)
	 * If used on window, returns the viewport's (window) width
	 *
	 * @example $("#testdiv").innerWidth()
	 * @result 1000
	 *
	 * @name innerWidth
	 * @type Number
	 * @cat Plugins/Dimensions
	 */
	innerWidth: function() {
		return this[0] == window || this[0] == document ?
			this.width() :
			this.is(':visible') ?
				this[0].offsetWidth - num(this, 'borderLeftWidth') - num(this, 'borderRightWidth') :
				this.width() + num(this, 'paddingLeft') + num(this, 'paddingRight');
	},

	/**
	 * Returns the outer height value (including border) for the first matched element.
	 * Cannot be used on document or window.
	 *
	 * @example $("#testdiv").outerHeight()
	 * @result 1000
	 *
	 * @name outerHeight
	 * @type Number
	 * @cat Plugins/Dimensions
	 */
	outerHeight: function() {
		return this[0] == window || this[0] == document ?
			this.height() :
			this.is(':visible') ?
				this[0].offsetHeight :
				this.height() + num(this,'borderTopWidth') + num(this, 'borderBottomWidth') + num(this, 'paddingTop') + num(this, 'paddingBottom');
	},

	/**
	 * Returns the outer width value (including border) for the first matched element.
	 * Cannot be used on document or window.
	 *
	 * @example $("#testdiv").outerHeight()
	 * @result 1000
	 *
	 * @name outerHeight
	 * @type Number
	 * @cat Plugins/Dimensions
	 */
	outerWidth: function() {
		return this[0] == window || this[0] == document ?
			this.width() :
			this.is(':visible') ?
				this[0].offsetWidth :
				this.width() + num(this, 'borderLeftWidth') + num(this, 'borderRightWidth') + num(this, 'paddingLeft') + num(this, 'paddingRight');
	},

	/**
	 * Returns how many pixels the user has scrolled to the right (scrollLeft).
	 * Works on containers with overflow: auto and window/document.
	 *
	 * @example $("#testdiv").scrollLeft()
	 * @result 100
	 *
	 * @name scrollLeft
	 * @type Number
	 * @cat Plugins/Dimensions
	 */
	/**
	 * Sets the scrollLeft property and continues the chain.
	 * Works on containers with overflow: auto and window/document.
	 *
	 * @example $("#testdiv").scrollLeft(10).scrollLeft()
	 * @result 10
	 *
	 * @name scrollLeft
	 * @param Number value A positive number representing the desired scrollLeft.
	 * @type jQuery
	 * @cat Plugins/Dimensions
	 */
	scrollLeft: function(val) {
		if ( val != undefined )
			// set the scroll left
			return this.each(function() {
				if (this == window || this == document)
					window.scrollTo( val, $(window).scrollTop() );
				else
					this.scrollLeft = val;
			});

		// return the scroll left offest in pixels
		if ( this[0] == window || this[0] == document )
			return self.pageXOffset ||
				$.boxModel && document.documentElement.scrollLeft ||
				document.body.scrollLeft;

		return this[0].scrollLeft;
	},

	/**
	 * Returns how many pixels the user has scrolled to the bottom (scrollTop).
	 * Works on containers with overflow: auto and window/document.
	 *
	 * @example $("#testdiv").scrollTop()
	 * @result 100
	 *
	 * @name scrollTop
	 * @type Number
	 * @cat Plugins/Dimensions
	 */
	/**
	 * Sets the scrollTop property and continues the chain.
	 * Works on containers with overflow: auto and window/document.
	 *
	 * @example $("#testdiv").scrollTop(10).scrollTop()
	 * @result 10
	 *
	 * @name scrollTop
	 * @param Number value A positive number representing the desired scrollTop.
	 * @type jQuery
	 * @cat Plugins/Dimensions
	 */
	scrollTop: function(val) {
		if ( val != undefined )
			// set the scroll top
			return this.each(function() {
				if (this == window || this == document)
					window.scrollTo( $(window).scrollLeft(), val );
				else
					this.scrollTop = val;
			});

		// return the scroll top offset in pixels
		if ( this[0] == window || this[0] == document )
			return self.pageYOffset ||
				$.boxModel && document.documentElement.scrollTop ||
				document.body.scrollTop;

		return this[0].scrollTop;
	},

	/**
	 * Returns the top and left positioned offset in pixels.
	 * The positioned offset is the offset between a positioned
	 * parent and the element itself.
	 *
	 * @example $("#testdiv").position()
	 * @result { top: 100, left: 100 }
	 *
	 * @name position
	 * @param Map options Optional settings to configure the way the offset is calculated.
	 * @option Boolean margin Should the margin of the element be included in the calculations? False by default.
	 * @option Boolean border Should the border of the element be included in the calculations? False by default.
	 * @option Boolean padding Should the padding of the element be included in the calculations? False by default.
	 * @param Object returnObject An object to store the return value in, so as not to break the chain. If passed in the
	 *                            chain will not be broken and the result will be assigned to this object.
	 * @type Object
	 * @cat Plugins/Dimensions
	 */
	position: function(options, returnObject) {
		var elem = this[0], parent = elem.parentNode, op = elem.offsetParent,
		    options = $.extend({ margin: false, border: false, padding: false, scroll: false }, options || {}),
			x = elem.offsetLeft,
			y = elem.offsetTop,
			sl = elem.scrollLeft,
			st = elem.scrollTop;

		// Mozilla and IE do not add the border
		if ($.browser.mozilla || $.browser.msie) {
			// add borders to offset
			x += num(elem, 'borderLeftWidth');
			y += num(elem, 'borderTopWidth');
		}

		if ($.browser.mozilla) {
			do {
				// Mozilla does not add the border for a parent that has overflow set to anything but visible
				if ($.browser.mozilla && parent != elem && $.css(parent, 'overflow') != 'visible') {
					x += num(parent, 'borderLeftWidth');
					y += num(parent, 'borderTopWidth');
				}

				if (parent == op) break; // break if we are already at the offestParent
			} while ((parent = parent.parentNode) && (parent.tagName.toLowerCase() != 'body' || parent.tagName.toLowerCase() != 'html'));
		}

		var returnValue = handleOffsetReturn(elem, options, x, y, sl, st);

		if (returnObject) { $.extend(returnObject, returnValue); return this; }
		else              { return returnValue; }
	},

	/**
	 * Returns the location of the element in pixels from the top left corner of the viewport.
	 *
	 * For accurate readings make sure to use pixel values for margins, borders and padding.
	 *
	 * Known issues:
	 *  - Issue: A div positioned relative or static without any content before it and its parent will report an offsetTop of 0 in Safari
	 *    Workaround: Place content before the relative div ... and set height and width to 0 and overflow to hidden
	 *
	 * @example $("#testdiv").offset()
	 * @result { top: 100, left: 100, scrollTop: 10, scrollLeft: 10 }
	 *
	 * @example $("#testdiv").offset({ scroll: false })
	 * @result { top: 90, left: 90 }
	 *
	 * @example var offset = {}
	 * $("#testdiv").offset({ scroll: false }, offset)
	 * @result offset = { top: 90, left: 90 }
	 *
	 * @name offset
	 * @param Map options Optional settings to configure the way the offset is calculated.
	 * @option Boolean margin Should the margin of the element be included in the calculations? True by default.
	 * @option Boolean border Should the border of the element be included in the calculations? False by default.
	 * @option Boolean padding Should the padding of the element be included in the calculations? False by default.
	 * @option Boolean scroll Should the scroll offsets of the parent elements be included in the calculations? True by default.
	 *                        When true it adds the totla scroll offets of all parents to the total offset and also adds two properties
	 *                        to the returned object, scrollTop and scrollLeft.
	 * @options Boolean lite Will use offsetLite instead of offset when set to true. False by default.
	 * @param Object returnObject An object to store the return value in, so as not to break the chain. If passed in the
	 *                            chain will not be broken and the result will be assigned to this object.
	 * @type Object
	 * @cat Plugins/Dimensions
	 */
	offset: function(options, returnObject) {
		var x = 0, y = 0, sl = 0, st = 0,
		    elem = this[0], parent = this[0], op, parPos, elemPos = $.css(elem, 'position'),
		    mo = $.browser.mozilla, ie = $.browser.msie, sf = $.browser.safari, oa = $.browser.opera,
		    absparent = false, relparent = false,
		    options = $.extend({ margin: true, border: false, padding: false, scroll: true, lite: false }, options || {});

		// Use offsetLite if lite option is true
		if (options.lite) return this.offsetLite(options, returnObject);

		if (elem.tagName.toLowerCase() == 'body') {
			// Safari is the only one to get offsetLeft and offsetTop properties of the body "correct"
			// Except they all mess up when the body is positioned absolute or relative
			x = elem.offsetLeft;
			y = elem.offsetTop;
			// Mozilla ignores margin and subtracts border from body element
			if (mo) {
				x += num(elem, 'marginLeft') + (num(elem, 'borderLeftWidth')*2);
				y += num(elem, 'marginTop')  + (num(elem, 'borderTopWidth') *2);
			} else
			// Opera ignores margin
			if (oa) {
				x += num(elem, 'marginLeft');
				y += num(elem, 'marginTop');
			} else
			// IE does not add the border in Standards Mode
			if (ie && jQuery.boxModel) {
				x += num(elem, 'borderLeftWidth');
				y += num(elem, 'borderTopWidth');
			}
		} else {
			do {
				parPos = $.css(parent, 'position');

				x += parent.offsetLeft;
				y += parent.offsetTop;

				// Mozilla and IE do not add the border
				if (mo || ie) {
					// add borders to offset
					x += num(parent, 'borderLeftWidth');
					y += num(parent, 'borderTopWidth');

					// Mozilla does not include the border on body if an element isn't positioned absolute and is without an absolute parent
					if (mo && parPos == 'absolute') absparent = true;
					// IE does not include the border on the body if an element is position static and without an absolute or relative parent
					if (ie && parPos == 'relative') relparent = true;
				}

				op = parent.offsetParent;
				if (options.scroll || mo) {
					do {
						if (options.scroll) {
							// get scroll offsets
							sl += parent.scrollLeft;
							st += parent.scrollTop;
						}

						// Mozilla does not add the border for a parent that has overflow set to anything but visible
						if (mo && parent != elem && $.css(parent, 'overflow') != 'visible') {
							x += num(parent, 'borderLeftWidth');
							y += num(parent, 'borderTopWidth');
						}

						parent = parent.parentNode;
					} while (parent != op);
				}
				parent = op;

				if (parent.tagName.toLowerCase() == 'body' || parent.tagName.toLowerCase() == 'html') {
					// Safari and IE Standards Mode doesn't add the body margin for elments positioned with static or relative
					if ((sf || (ie && $.boxModel)) && elemPos != 'absolute' && elemPos != 'fixed') {
						x += num(parent, 'marginLeft');
						y += num(parent, 'marginTop');
					}
					// Mozilla does not include the border on body if an element isn't positioned absolute and is without an absolute parent
					// IE does not include the border on the body if an element is positioned static and without an absolute or relative parent
					if ( (mo && !absparent && elemPos != 'fixed') ||
					     (ie && elemPos == 'static' && !relparent) ) {
						x += num(parent, 'borderLeftWidth');
						y += num(parent, 'borderTopWidth');
					}
					break; // Exit the loop
				}
			} while (parent);
		}

		var returnValue = handleOffsetReturn(elem, options, x, y, sl, st);

		if (returnObject) { $.extend(returnObject, returnValue); return this; }
		else              { return returnValue; }
	},

	/**
	 * Returns the location of the element in pixels from the top left corner of the viewport.
	 * This method is much faster than offset but not as accurate. This method can be invoked
	 * by setting the lite option to true in the offset method.
	 *
	 * @name offsetLite
	 * @param Map options Optional settings to configure the way the offset is calculated.
	 * @option Boolean margin Should the margin of the element be included in the calculations? True by default.
	 * @option Boolean border Should the border of the element be included in the calculations? False by default.
	 * @option Boolean padding Should the padding of the element be included in the calculations? False by default.
	 * @option Boolean scroll Should the scroll offsets of the parent elements be included in the calculations? True by default.
	 *                        When true it adds the totla scroll offets of all parents to the total offset and also adds two properties
	 *                        to the returned object, scrollTop and scrollLeft.
	 * @param Object returnObject An object to store the return value in, so as not to break the chain. If passed in the
	 *                            chain will not be broken and the result will be assigned to this object.
	 * @type Object
	 * @cat Plugins/Dimensions
	 */
	offsetLite: function(options, returnObject) {
		var x = 0, y = 0, sl = 0, st = 0, parent = this[0], op,
		    options = $.extend({ margin: true, border: false, padding: false, scroll: true }, options || {});

		do {
			x += parent.offsetLeft;
			y += parent.offsetTop;

			op = parent.offsetParent;
			if (options.scroll) {
				// get scroll offsets
				do {
					sl += parent.scrollLeft;
					st += parent.scrollTop;
					parent = parent.parentNode;
				} while(parent != op);
			}
			parent = op;
		} while (parent && parent.tagName.toLowerCase() != 'body' && parent.tagName.toLowerCase() != 'html');

		var returnValue = handleOffsetReturn(this[0], options, x, y, sl, st);

		if (returnObject) { $.extend(returnObject, returnValue); return this; }
		else              { return returnValue; }
	}
});

/**
 * Handles converting a CSS Style into an Integer.
 * @private
 */
var num = function(el, prop) {
	return parseInt($.css(el.jquery?el[0]:el,prop))||0;
};

/**
 * Handles the return value of the offset and offsetLite methods.
 * @private
 */
var handleOffsetReturn = function(elem, options, x, y, sl, st) {
	if ( !options.margin ) {
		x -= num(elem, 'marginLeft');
		y -= num(elem, 'marginTop');
	}

	// Safari and Opera do not add the border for the element
	if ( options.border && ($.browser.safari || $.browser.opera) ) {
		x += num(elem, 'borderLeftWidth');
		y += num(elem, 'borderTopWidth');
	} else if ( !options.border && !($.browser.safari || $.browser.opera) ) {
		x -= num(elem, 'borderLeftWidth');
		y -= num(elem, 'borderTopWidth');
	}

	if ( options.padding ) {
		x += num(elem, 'paddingLeft');
		y += num(elem, 'paddingTop');
	}

	// do not include scroll offset on the element
	if ( options.scroll ) {
		sl -= elem.scrollLeft;
		st -= elem.scrollTop;
	}

	return options.scroll ? { top: y - st, left: x - sl, scrollTop:  st, scrollLeft: sl }
	                      : { top: y, left: x };
};

})(jQuery);<commit_msg>clean up for no longer needed jquery dimensions<commit_after><|endoftext|>


<commit_before>/**
 * HUD
 */
Garnish.HUD = Garnish.Base.extend({

	$trigger: null,
	$fixedTriggerParent: null,
	$hud: null,
	$tip: null,
	$body: null,
	$header: null,
	$footer: null,
	$mainContainer: null,
	$main: null,
	$shade: null,

	showing: false,
	orientation: null,

	/**
	 * Constructor
	 */
	init: function(trigger, bodyContents, settings) {

		this.$trigger = $(trigger);

		this.setSettings(settings, Garnish.HUD.defaults);
		this.on('show', this.settings.onShow);
		this.on('hide', this.settings.onHide);
		this.on('submit', this.settings.onSubmit);

		if (typeof Garnish.HUD.activeHUDs == "undefined")
		{
			Garnish.HUD.activeHUDs = {};
		}

		this.$shade = $('<div/>', {'class': this.settings.shadeClass});
		this.$hud = $('<div/>', {'class': this.settings.hudClass});
		this.$tip = $('<div/>', {'class': this.settings.tipClass}).appendTo(this.$hud);
		this.$body = $('<form/>', {'class': this.settings.bodyClass}).appendTo(this.$hud);
		this.$mainContainer = $('<div/>', {'class': this.settings.mainContainerClass}).appendTo(this.$body);
		this.$main = $('<div/>', {'class': this.settings.mainClass}).appendTo(this.$mainContainer);

		this.updateBody(bodyContents);

		// See if the trigger is fixed
		var $parent = this.$trigger;

		do {
			if ($parent.css('position') == 'fixed')
			{
				this.$fixedTriggerParent = $parent;
				break;
			}

			$parent = $parent.offsetParent();
		}
		while ($parent.length && $parent.prop('nodeName') != 'HTML');

		if (this.$fixedTriggerParent)
		{
			this.$hud.css('position', 'fixed');
		}
		else
		{
			this.$hud.css('position', 'absolute');
		}

		this.show();

		this.addListener(this.$body, 'submit', '_handleSubmit');
		this.addListener(this.$shade, 'click', 'hide');
		this.addListener(Garnish.$win, 'resize', 'updateSizeAndPosition');

		if (this.settings.closeBtn)
		{
			this.addListener(this.settings.closeBtn, 'activate', 'hide');
		}
	},

	/**
	 * Update the body contents
	 */
	updateBody: function(bodyContents)
	{
		this.removeListener(this.$main, 'resize');

		// Cleanup
		this.$main.html('');

		if (this.$header)
		{
			this.$hud.removeClass('has-header');
			this.$header.remove();
			this.$header = null;
		}

		if (this.$footer)
		{
			this.$hud.removeClass('has-footer');
			this.$footer.remove();
			this.$footer = null;
		}

		// Append the new body contents
		this.$main.append(bodyContents);

		// Look for a header and footer
		var $header = this.$main.find('.'+this.settings.headerClass+':first'),
			$footer = this.$main.find('.'+this.settings.footerClass+':first');

		if ($header.length)
		{
			this.$header = $header.insertBefore(this.$mainContainer);
			this.$hud.removeClass('has-header');
		}

		if ($footer.length)
		{
			this.$footer = $footer.insertAfter(this.$mainContainer);
			this.$hud.removeClass('has-footer');
		}

		this.updateSizeAndPosition();
	},

	/**
	 * Show
	 */
	show: function(ev)
	{
		if (ev && ev.stopPropagation)
		{
			ev.stopPropagation();
		}

		if (this.showing)
		{
			return;
		}

		if (this.settings.closeOtherHUDs)
		{
			for (var hudID in Garnish.HUD.activeHUDs)
			{
				Garnish.HUD.activeHUDs[hudID].hide();
			}
		}

		// Prevent the browser from jumping
		this.$hud.css('top', Garnish.$win.scrollTop());

		// Move it to the end of <body> so it gets the highest sub-z-index
		this.$shade.appendTo(Garnish.$bod);
		this.$hud.appendTo(Garnish.$bod);

		this.$hud.show();
		this.updateSizeAndPosition();

		this.$shade.show();

		this.showing = true;
		Garnish.HUD.activeHUDs[this._namespace] = this;

		Garnish.escManager.register(this, 'hide');

		this.onShow();

		// Reposition one last time just in case
		Garnish.requestAnimationFrame($.proxy(this, 'updateSizeAndPosition'));
	},

	onShow: function()
	{
		this.trigger('show');
	},

	updateSizeAndPosition: function()
	{
		this.removeListener(this.$main, 'resize');

		var windowWidth,
			windowHeight,
			triggerWidth,
			triggerHeight,
			triggerOffset,
			windowScrollLeft,
			windowScrollTop,
			hudBodyWidth,
			hudBodyHeight;

		// Get the window sizes and trigger offset
		// ---------------------------------------------------------------------

		windowWidth = Garnish.$win.width();
		windowHeight = Garnish.$win.height();
		windowScrollLeft = Garnish.$win.scrollLeft();
		windowScrollTop = Garnish.$win.scrollTop();

		// Get the trigger's dimensions
		triggerWidth = this.$trigger.outerWidth();
		triggerHeight = this.$trigger.outerHeight();

		// Get the offsets for each side of the trigger element
		triggerOffset = this.$trigger.offset();

		if (this.$fixedTriggerParent)
		{
			triggerOffset.left -= windowScrollLeft;
			triggerOffset.top -= windowScrollTop;

			windowScrollLeft = 0;
			windowScrollTop = 0;
		}

		triggerOffset.right = triggerOffset.left + triggerWidth;
		triggerOffset.bottom = triggerOffset.top + triggerHeight;

		// Get the HUD dimensions
		this.$hud.css({
			width: ''
		});

		this.$mainContainer.css({
			height: '',
			'overflow-x': '',
			'overflow-y': ''
		});

		hudBodyWidth = this.$body.width();
		hudBodyHeight = this.$body.height();

		// Determine the best orientation for the HUD
		// ---------------------------------------------------------------------

		// Find the actual available top/right/bottom/left clearances
		var clearances = {
			bottom: windowHeight + windowScrollTop - triggerOffset.bottom,
			top:    triggerOffset.top - windowScrollTop,
			right:  windowWidth + windowScrollLeft - triggerOffset.right,
			left:   triggerOffset.left - windowScrollLeft
		};

		// Find the first position that has enough room
		this.orientation = null;

		for (var i = 0; i < this.settings.orientations.length; i++)
		{
			var orientation = this.settings.orientations[i],
				relevantSize = (orientation == 'top' || orientation == 'bottom' ? hudBodyHeight : hudBodyWidth);

			if (clearances[orientation] - (this.settings.windowSpacing + this.settings.triggerSpacing) >= relevantSize)
			{
				// This is the first orientation that has enough room in order of preference, so we'll go with this
				this.orientation = orientation;
				break;
			}

			if (!this.orientation || clearances[orientation] > clearances[this.orientation])
			{
				// Use this as a fallback as it's the orientation with the most clearance so far
				this.orientation = orientation;
			}
		}

		// Just in case...
		if (!this.orientation || $.inArray(this.orientation, ['bottom', 'top', 'right', 'left']) == -1)
		{
			this.orientation = 'bottom'
		}

		// Update the tip class
		if (this.tipClass)
		{
			this.$tip.removeClass(this.tipClass);
		}

		this.tipClass = this.settings.tipClass+'-'+Garnish.HUD.tipClasses[this.orientation];
		this.$tip.addClass(this.tipClass);

		// Make sure the HUD body is within the allowed size
		// ---------------------------------------------------------------------

		var maxHudBodyWidth,
			maxHudBodyHeight;

		if (this.orientation == 'top' || this.orientation == 'bottom')
		{
			maxHudBodyWidth = windowWidth - this.settings.windowSpacing * 2;
			maxHudBodyHeight = clearances[this.orientation];
		}
		else
		{
			maxHudBodyWidth = clearances[this.orientation];
			maxHudBodyHeight = windowHeight - this.settings.windowSpacing * 2;
		}

		if (maxHudBodyWidth < this.settings.minBodyWidth)
		{
			maxHudBodyWidth = this.settings.minBodyWidth;
		}

		if (maxHudBodyHeight < this.settings.minBodyHeight)
		{
			maxHudBodyHeight = this.settings.minBodyHeight;
		}

		if (hudBodyWidth > maxHudBodyWidth)
		{
			this.$hud.width(maxHudBodyWidth);
			hudBodyWidth = maxHudBodyWidth;

			// Is there any overflow now?
			if (this.$main.outerWidth() > maxHudBodyWidth)
			{
				this.$mainContainer.css('overflow-x', 'scroll');
			}

			// The height may have just changed
			hudBodyHeight = this.$body.height();
		}

		if (hudBodyHeight > maxHudBodyHeight)
		{
			var mainHeight = maxHudBodyHeight;

			if (this.$header)
			{
				mainHeight -= this.$header.outerHeight();
			}

			if (this.$footer)
			{
				mainHeight -= this.$footer.outerHeight();
			}

			this.$mainContainer.height(mainHeight);
			hudBodyHeight = maxHudBodyHeight;

			// Is there any overflow now?
			if (this.$main.outerHeight() > mainHeight)
			{
				this.$mainContainer.css('overflow-y', 'scroll');
			}
		}

		// Set the HUD/tip positions
		// ---------------------------------------------------------------------

		if (this.orientation == 'top' || this.orientation == 'bottom')
		{
			// Center the HUD horizontally
			var maxLeft = (windowWidth + windowScrollLeft) - (hudBodyWidth + this.settings.windowSpacing),
				minLeft = (windowScrollLeft + this.settings.windowSpacing),
				triggerCenter = triggerOffset.left + Math.round(triggerWidth / 2),
				left = triggerCenter - Math.round(hudBodyWidth / 2);

			if (left > maxLeft) left = maxLeft;
			if (left < minLeft) left = minLeft;

			this.$hud.css('left', left);

			var tipLeft = (triggerCenter - left) - (this.settings.tipWidth / 2);
			this.$tip.css({ left: tipLeft, top: '' });

			if (this.orientation == 'top')
			{
				var top = triggerOffset.top - (hudBodyHeight + this.settings.triggerSpacing);
				this.$hud.css('top', top);
			}
			else
			{
				var top = triggerOffset.bottom + this.settings.triggerSpacing;
				this.$hud.css('top', top);
			}
		}
		else
		{
			// Center the HUD vertically
			var maxTop = (windowHeight + windowScrollTop) - (hudBodyHeight + this.settings.windowSpacing),
				minTop = (windowScrollTop + this.settings.windowSpacing),
				triggerCenter = triggerOffset.top + Math.round(triggerHeight / 2),
				top = triggerCenter - Math.round(hudBodyHeight / 2);

			if (top > maxTop) top = maxTop;
			if (top < minTop) top = minTop;

			this.$hud.css('top', top);

			var tipTop = (triggerCenter - top) - (this.settings.tipWidth / 2);
			this.$tip.css({ top: tipTop, left: '' });

			if (this.orientation == 'left')
			{
				var left = triggerOffset.left - (hudBodyWidth + this.settings.triggerSpacing);
				this.$hud.css('left', left);
			}
			else
			{
				var left = triggerOffset.right + this.settings.triggerSpacing;
				this.$hud.css('left', left);
			}
		}

		this.addListener(this.$main, 'resize', 'updateSizeAndPosition');
	},

	/**
	 * Hide
	 */
	hide: function()
	{
		this.$hud.hide();
		this.$shade.hide();
		this.showing = false;

		delete Garnish.HUD.activeHUDs[this._namespace];

		Garnish.escManager.unregister(this);

		this.onHide();
	},

	onHide: function()
	{
		this.trigger('hide');
	},

	toggle: function()
	{
		if (this.showing)
		{
			this.hide();
		}
		else
		{
			this.show();
		}
	},

	submit: function()
	{
		this.onSubmit();
	},

	onSubmit: function()
	{
		this.trigger('submit');
	},

	_handleSubmit: function(ev)
	{
		ev.preventDefault();
		this.submit();
	}
},
{
	tipClasses: { bottom: 'top', top: 'bottom', right: 'left', left: 'right'},

	defaults: {
		shadeClass: 'hud-shade',
		hudClass: 'hud',
		tipClass: 'tip',
		bodyClass: 'body',
		headerClass: 'header',
		footerClass: 'footer',
		mainContainerClass: 'main-container',
		mainClass: 'main',
		orientations: ['bottom', 'top', 'right', 'left'],
		triggerSpacing: 10,
		windowSpacing: 10,
		tipWidth: 30,
		minBodyWidth: 200,
		minBodyHeight: 200,
		onShow: $.noop,
		onHide: $.noop,
		onSubmit: $.noop,
		closeBtn: null,
		closeOtherHUDs: true
	}
});
<commit_msg>Enforce the HUD min body width/height settings when it's going to be smaller<commit_after>/**
 * HUD
 */
Garnish.HUD = Garnish.Base.extend({

	$trigger: null,
	$fixedTriggerParent: null,
	$hud: null,
	$tip: null,
	$body: null,
	$header: null,
	$footer: null,
	$mainContainer: null,
	$main: null,
	$shade: null,

	showing: false,
	orientation: null,

	/**
	 * Constructor
	 */
	init: function(trigger, bodyContents, settings) {

		this.$trigger = $(trigger);

		this.setSettings(settings, Garnish.HUD.defaults);
		this.on('show', this.settings.onShow);
		this.on('hide', this.settings.onHide);
		this.on('submit', this.settings.onSubmit);

		if (typeof Garnish.HUD.activeHUDs == "undefined")
		{
			Garnish.HUD.activeHUDs = {};
		}

		this.$shade = $('<div/>', {'class': this.settings.shadeClass});
		this.$hud = $('<div/>', {'class': this.settings.hudClass});
		this.$tip = $('<div/>', {'class': this.settings.tipClass}).appendTo(this.$hud);
		this.$body = $('<form/>', {'class': this.settings.bodyClass}).appendTo(this.$hud);
		this.$mainContainer = $('<div/>', {'class': this.settings.mainContainerClass}).appendTo(this.$body);
		this.$main = $('<div/>', {'class': this.settings.mainClass}).appendTo(this.$mainContainer);

		this.updateBody(bodyContents);

		// See if the trigger is fixed
		var $parent = this.$trigger;

		do {
			if ($parent.css('position') == 'fixed')
			{
				this.$fixedTriggerParent = $parent;
				break;
			}

			$parent = $parent.offsetParent();
		}
		while ($parent.length && $parent.prop('nodeName') != 'HTML');

		if (this.$fixedTriggerParent)
		{
			this.$hud.css('position', 'fixed');
		}
		else
		{
			this.$hud.css('position', 'absolute');
		}

		this.show();

		this.addListener(this.$body, 'submit', '_handleSubmit');
		this.addListener(this.$shade, 'click', 'hide');
		this.addListener(Garnish.$win, 'resize', 'updateSizeAndPosition');

		if (this.settings.closeBtn)
		{
			this.addListener(this.settings.closeBtn, 'activate', 'hide');
		}
	},

	/**
	 * Update the body contents
	 */
	updateBody: function(bodyContents)
	{
		this.removeListener(this.$main, 'resize');

		// Cleanup
		this.$main.html('');

		if (this.$header)
		{
			this.$hud.removeClass('has-header');
			this.$header.remove();
			this.$header = null;
		}

		if (this.$footer)
		{
			this.$hud.removeClass('has-footer');
			this.$footer.remove();
			this.$footer = null;
		}

		// Append the new body contents
		this.$main.append(bodyContents);

		// Look for a header and footer
		var $header = this.$main.find('.'+this.settings.headerClass+':first'),
			$footer = this.$main.find('.'+this.settings.footerClass+':first');

		if ($header.length)
		{
			this.$header = $header.insertBefore(this.$mainContainer);
			this.$hud.removeClass('has-header');
		}

		if ($footer.length)
		{
			this.$footer = $footer.insertAfter(this.$mainContainer);
			this.$hud.removeClass('has-footer');
		}

		this.updateSizeAndPosition();
	},

	/**
	 * Show
	 */
	show: function(ev)
	{
		if (ev && ev.stopPropagation)
		{
			ev.stopPropagation();
		}

		if (this.showing)
		{
			return;
		}

		if (this.settings.closeOtherHUDs)
		{
			for (var hudID in Garnish.HUD.activeHUDs)
			{
				Garnish.HUD.activeHUDs[hudID].hide();
			}
		}

		// Prevent the browser from jumping
		this.$hud.css('top', Garnish.$win.scrollTop());

		// Move it to the end of <body> so it gets the highest sub-z-index
		this.$shade.appendTo(Garnish.$bod);
		this.$hud.appendTo(Garnish.$bod);

		this.$hud.show();
		this.updateSizeAndPosition();

		this.$shade.show();

		this.showing = true;
		Garnish.HUD.activeHUDs[this._namespace] = this;

		Garnish.escManager.register(this, 'hide');

		this.onShow();

		// Reposition one last time just in case
		Garnish.requestAnimationFrame($.proxy(this, 'updateSizeAndPosition'));
	},

	onShow: function()
	{
		this.trigger('show');
	},

	updateSizeAndPosition: function()
	{
		this.removeListener(this.$main, 'resize');

		var windowWidth,
			windowHeight,
			triggerWidth,
			triggerHeight,
			triggerOffset,
			windowScrollLeft,
			windowScrollTop,
			hudBodyWidth,
			hudBodyHeight;

		// Get the window sizes and trigger offset
		// ---------------------------------------------------------------------

		windowWidth = Garnish.$win.width();
		windowHeight = Garnish.$win.height();
		windowScrollLeft = Garnish.$win.scrollLeft();
		windowScrollTop = Garnish.$win.scrollTop();

		// Get the trigger's dimensions
		triggerWidth = this.$trigger.outerWidth();
		triggerHeight = this.$trigger.outerHeight();

		// Get the offsets for each side of the trigger element
		triggerOffset = this.$trigger.offset();

		if (this.$fixedTriggerParent)
		{
			triggerOffset.left -= windowScrollLeft;
			triggerOffset.top -= windowScrollTop;

			windowScrollLeft = 0;
			windowScrollTop = 0;
		}

		triggerOffset.right = triggerOffset.left + triggerWidth;
		triggerOffset.bottom = triggerOffset.top + triggerHeight;

		// Get the HUD dimensions
		this.$hud.css({
			width: ''
		});

		this.$mainContainer.css({
			height: '',
			'overflow-x': '',
			'overflow-y': ''
		});

		hudBodyWidth = this.$body.width();
		hudBodyHeight = this.$body.height();

		// Determine the best orientation for the HUD
		// ---------------------------------------------------------------------

		// Find the actual available top/right/bottom/left clearances
		var clearances = {
			bottom: windowHeight + windowScrollTop - triggerOffset.bottom,
			top:    triggerOffset.top - windowScrollTop,
			right:  windowWidth + windowScrollLeft - triggerOffset.right,
			left:   triggerOffset.left - windowScrollLeft
		};

		// Find the first position that has enough room
		this.orientation = null;

		for (var i = 0; i < this.settings.orientations.length; i++)
		{
			var orientation = this.settings.orientations[i],
				relevantSize = (orientation == 'top' || orientation == 'bottom' ? hudBodyHeight : hudBodyWidth);

			if (clearances[orientation] - (this.settings.windowSpacing + this.settings.triggerSpacing) >= relevantSize)
			{
				// This is the first orientation that has enough room in order of preference, so we'll go with this
				this.orientation = orientation;
				break;
			}

			if (!this.orientation || clearances[orientation] > clearances[this.orientation])
			{
				// Use this as a fallback as it's the orientation with the most clearance so far
				this.orientation = orientation;
			}
		}

		// Just in case...
		if (!this.orientation || $.inArray(this.orientation, ['bottom', 'top', 'right', 'left']) == -1)
		{
			this.orientation = 'bottom'
		}

		// Update the tip class
		if (this.tipClass)
		{
			this.$tip.removeClass(this.tipClass);
		}

		this.tipClass = this.settings.tipClass+'-'+Garnish.HUD.tipClasses[this.orientation];
		this.$tip.addClass(this.tipClass);

		// Make sure the HUD body is within the allowed size
		// ---------------------------------------------------------------------

		var maxHudBodyWidth,
			maxHudBodyHeight;

		if (this.orientation == 'top' || this.orientation == 'bottom')
		{
			maxHudBodyWidth = windowWidth - this.settings.windowSpacing * 2;
			maxHudBodyHeight = clearances[this.orientation];
		}
		else
		{
			maxHudBodyWidth = clearances[this.orientation];
			maxHudBodyHeight = windowHeight - this.settings.windowSpacing * 2;
		}

		if (maxHudBodyWidth < this.settings.minBodyWidth)
		{
			maxHudBodyWidth = this.settings.minBodyWidth;
		}

		if (maxHudBodyHeight < this.settings.minBodyHeight)
		{
			maxHudBodyHeight = this.settings.minBodyHeight;
		}

		if (hudBodyWidth > maxHudBodyWidth || hudBodyWidth < this.settings.minBodyWidth)
		{
			if (hudBodyWidth > maxHudBodyWidth)
			{
				hudBodyWidth = maxHudBodyWidth;
			}
			else
			{
				hudBodyWidth = this.settings.minBodyWidth;
			}

			this.$hud.width(hudBodyWidth);

			// Is there any overflow now?
			if (this.$main.outerWidth() > maxHudBodyWidth)
			{
				this.$mainContainer.css('overflow-x', 'scroll');
			}

			// The height may have just changed
			hudBodyHeight = this.$body.height();
		}

		if (hudBodyHeight > maxHudBodyHeight || hudBodyHeight < this.settings.minBodyHeight)
		{
			if (hudBodyHeight > maxHudBodyHeight)
			{
				hudBodyHeight = maxHudBodyHeight;
			}
			else
			{
				hudBodyHeight = this.settings.minBodyHeight;
			}

			var mainHeight = hudBodyHeight;

			if (this.$header)
			{
				mainHeight -= this.$header.outerHeight();
			}

			if (this.$footer)
			{
				mainHeight -= this.$footer.outerHeight();
			}

			this.$mainContainer.height(mainHeight);

			// Is there any overflow now?
			if (this.$main.outerHeight() > mainHeight)
			{
				this.$mainContainer.css('overflow-y', 'scroll');
			}
		}

		// Set the HUD/tip positions
		// ---------------------------------------------------------------------

		if (this.orientation == 'top' || this.orientation == 'bottom')
		{
			// Center the HUD horizontally
			var maxLeft = (windowWidth + windowScrollLeft) - (hudBodyWidth + this.settings.windowSpacing),
				minLeft = (windowScrollLeft + this.settings.windowSpacing),
				triggerCenter = triggerOffset.left + Math.round(triggerWidth / 2),
				left = triggerCenter - Math.round(hudBodyWidth / 2);

			if (left > maxLeft) left = maxLeft;
			if (left < minLeft) left = minLeft;

			this.$hud.css('left', left);

			var tipLeft = (triggerCenter - left) - (this.settings.tipWidth / 2);
			this.$tip.css({ left: tipLeft, top: '' });

			if (this.orientation == 'top')
			{
				var top = triggerOffset.top - (hudBodyHeight + this.settings.triggerSpacing);
				this.$hud.css('top', top);
			}
			else
			{
				var top = triggerOffset.bottom + this.settings.triggerSpacing;
				this.$hud.css('top', top);
			}
		}
		else
		{
			// Center the HUD vertically
			var maxTop = (windowHeight + windowScrollTop) - (hudBodyHeight + this.settings.windowSpacing),
				minTop = (windowScrollTop + this.settings.windowSpacing),
				triggerCenter = triggerOffset.top + Math.round(triggerHeight / 2),
				top = triggerCenter - Math.round(hudBodyHeight / 2);

			if (top > maxTop) top = maxTop;
			if (top < minTop) top = minTop;

			this.$hud.css('top', top);

			var tipTop = (triggerCenter - top) - (this.settings.tipWidth / 2);
			this.$tip.css({ top: tipTop, left: '' });

			if (this.orientation == 'left')
			{
				var left = triggerOffset.left - (hudBodyWidth + this.settings.triggerSpacing);
				this.$hud.css('left', left);
			}
			else
			{
				var left = triggerOffset.right + this.settings.triggerSpacing;
				this.$hud.css('left', left);
			}
		}

		this.addListener(this.$main, 'resize', 'updateSizeAndPosition');
	},

	/**
	 * Hide
	 */
	hide: function()
	{
		this.$hud.hide();
		this.$shade.hide();
		this.showing = false;

		delete Garnish.HUD.activeHUDs[this._namespace];

		Garnish.escManager.unregister(this);

		this.onHide();
	},

	onHide: function()
	{
		this.trigger('hide');
	},

	toggle: function()
	{
		if (this.showing)
		{
			this.hide();
		}
		else
		{
			this.show();
		}
	},

	submit: function()
	{
		this.onSubmit();
	},

	onSubmit: function()
	{
		this.trigger('submit');
	},

	_handleSubmit: function(ev)
	{
		ev.preventDefault();
		this.submit();
	}
},
{
	tipClasses: { bottom: 'top', top: 'bottom', right: 'left', left: 'right'},

	defaults: {
		shadeClass: 'hud-shade',
		hudClass: 'hud',
		tipClass: 'tip',
		bodyClass: 'body',
		headerClass: 'header',
		footerClass: 'footer',
		mainContainerClass: 'main-container',
		mainClass: 'main',
		orientations: ['bottom', 'top', 'right', 'left'],
		triggerSpacing: 10,
		windowSpacing: 10,
		tipWidth: 30,
		minBodyWidth: 200,
		minBodyHeight: 200,
		onShow: $.noop,
		onHide: $.noop,
		onSubmit: $.noop,
		closeBtn: null,
		closeOtherHUDs: true
	}
});
<|endoftext|>


<commit_before>// Magic scroll things.
// This binds cool animations to the scroll level in such a way that the
// animations' progress can be controlled by scrolling. This does what
// ScrollMagic does except without the bugs.

const elem = {
  body: document.body,

  headerWrapper: document.getElementById('header-wrapper'),
  header: document.getElementsByTagName('header')[0],
  headerTitle: document.getElementsByTagName('h1')[0],
  downIndicator: document.getElementsByClassName('down-indicator')[0],

  laptop: document.getElementsByClassName('laptop3d')[0].parentNode,
  // Won't be initialized by the time elem is evaluated so we use a getter
  get laptopContent() { return this.laptop.getElementsByClassName('screen')[0]; },

  laptopsContainer: document.getElementsByClassName('laptops-container')[0],
  laptopsStretcher: document.getElementsByClassName('laptops-stretcher')[0],
};

let headerIsInLaptop = false;

// HEADER TRANSITION LOGIC =========================================================================

function setHeaderElementsPinned(fixed) {
  // Switch between fixed and absolute, pinning elements in the appropriate place
  [elem.headerTitle, elem.laptop].forEach((element) => {
    /* eslint-disable no-param-reassign */
    element.style.position = fixed ? 'fixed' : 'absolute';
    element.style.top = fixed ? '50vh' : `${window.innerHeight}px`;
    /* eslint-enable no-param-reassign */
  });
}

function updateHeaderElements(progress) {
  // Blends two values based on "progress" made between them.
  function tween(a, b) {
    return a + ((b - a) * progress);
  }
  // Returns the center of a passed ClientRect object (or similar)
  function getCenter(rect) {
    return { x: (rect.left + rect.right) / 2, y: (rect.top + rect.bottom) / 2 };
  }

  // FIT MAIN HEADER TO LAPTOP SCREEN

  if (progress < 1) {
    if (headerIsInLaptop) {
      elem.header.style.position = 'fixed';
      elem.header.style.top = elem.header.style.left = 0;
      elem.header.style.width = '100vw'; elem.header.style.height = '100vh';
      elem.headerWrapper.appendChild(elem.header);
      headerIsInLaptop = false;
    }
    // Beginning and end positions
    const w = window.innerWidth; const h = window.innerHeight;
    const posA = { left: 0, top: 0, right: w, bottom: h, width: w, height: h };
    const posB = elem.laptopContent.getBoundingClientRect();
    // Calculate translation
    // Center is used for calculating translation because element is scaled about the center)
    const centerA = getCenter(posA); const centerB = getCenter(posB);
    const translationNeeded = { x: centerB.x - centerA.x, y: centerB.y - centerA.y };
    // Calculate scale
    const scaleNeeded = { x: posB.width / posA.width, y: posB.height / posA.height };
    // Apply transformations
    elem.header.style.transform = [
      // Translate
      `translateX(${tween(0, translationNeeded.x)}px)`,
      `translateY(${tween(0, translationNeeded.y)}px)`,
      // Scale
      `scaleX(${tween(1, scaleNeeded.x)})`,
      `scaleY(${tween(1, scaleNeeded.y)})`,
    ].join(' ');
  } else if (!headerIsInLaptop) {
    elem.header.style.position = 'absolute';
    elem.header.style.transform = '';
    // This should be easy but Blink insists on rendering .front behind .back if I give
    // position: relative to .screen, so .header has to be manually positioned relative to .front
    const screenBezel = window.sassHeightVariable * 0.85;
    elem.header.style.top = elem.header.style.left = `${screenBezel}vw`;
    elem.header.style.width = `${window.sassLengthVariable - (2 * screenBezel) - 0.6}vw`; // 0.6 is border width
    elem.header.style.height = `${window.sassWidthVariable - (2.5 * screenBezel) - 0.6}vw`; // 2.5 because bottom bezel is bigger
    elem.laptopContent.appendChild(elem.header);
    headerIsInLaptop = true;
  }

  // MISCELLANEOUS

  // Update opacity of down indicator
  elem.downIndicator.style.opacity = 1 - progress;
  // Reduce font size of my name
  const centerA = { x: window.innerWidth / 2, y: window.innerHeight / 2 };
  const centerB = getCenter(elem.laptopContent.getBoundingClientRect());
  const translationNeeded = { x: centerB.x - centerA.x, y: centerB.y - centerA.y };
  elem.headerTitle.style.transform = [
    'translate(-50%, -50%)',
    `translateX(${tween(0, translationNeeded.x)}px)`,
    `translateY(${tween(0, translationNeeded.y)}px)`,
    `scale(${tween(1, 0.5)})`,
  ].join(' ');

  // ADJUST CANVAS SETTINGS

  const canvasRect = window.gol.elem.getBoundingClientRect();
  // How much canvas has been downscaled overall? Averages X and Y scale factors to get a holistic
  // scale factor
  const horizontalScaleFactor = (canvasRect.width / window.innerWidth);
  const verticalScaleFactor = (canvasRect.height / window.innerHeight);
  const averageScaleFactor = (horizontalScaleFactor + verticalScaleFactor) / 2;
  window.gol.idealCellSize = 20 * averageScaleFactor;
  window.gol.needsSizeUpdate = true;
}

// BACKGROUND TRANSITION LOGIC =====================================================================

function updateBackgroundColor(progress) {
  const grad1A = window.parseColor('#5EFCE8');
  const grad1B = window.parseColor('#736EFE');

  const grad2A = window.parseColor('#16222A');
  const grad2B = window.parseColor('#4A5070');

  const interpolate = (a, b, amount) => ({
    r: Math.floor((a.r * (1 - progress)) + (b.r * amount)),
    g: Math.floor((a.g * (1 - progress)) + (b.g * amount)),
    b: Math.floor((a.b * (1 - progress)) + (b.b * amount)),
  });

  const getCssGradString = (a, b, deg = 135) =>
    `linear-gradient(${deg}deg, rgb(${a.r}, ${a.g}, ${a.b}), rgb(${b.r}, ${b.g}, ${b.b}))`;

  const calcA = interpolate(grad1A, grad2A, progress);
  const calcB = interpolate(grad1B, grad2B, progress);
  elem.body.style.backgroundImage = getCssGradString(calcA, calcB);
}

// SCROLL LISTENER =================================================================================

function onscroll() {
  const scroll = window.scrollY;

  // Header

  switch (true) {
    case (scroll < 5): {
      setHeaderElementsPinned(true);
      requestAnimationFrame(() => updateHeaderElements(0));
      break;
    }

    case (scroll < window.innerHeight / 2): {
      setHeaderElementsPinned(true);
      const headerProgress = scroll / (window.innerHeight / 2);
      requestAnimationFrame(() => updateHeaderElements(headerProgress));
      break;
    }

    default: {
      setHeaderElementsPinned(false);
      requestAnimationFrame(() => updateHeaderElements(1));
    }
  }

  // Background

  switch (true) {
    case (scroll < window.innerHeight * 0.75): {
      updateBackgroundColor(0);
      break;
    }

    case (scroll > window.innerHeight * 1.25): {
      updateBackgroundColor(1);
      break;
    }

    default: {
      const bgProgress = (scroll - (window.innerHeight * 0.75)) / (window.innerHeight * 0.5);
      updateBackgroundColor(bgProgress);
    }
  }

  // Carousel

  const largerScreenDimension = Math.max(window.innerWidth, window.innerHeight);

  // Adjust carousel position
  const start = elem.laptopsStretcher.offsetTop;
  const end = start + (largerScreenDimension * 1.5);

  const carouselProgress = (scroll - start) / (end - start);
  requestAnimationFrame(() => {
    window.carousel.position = carouselProgress * window.carousel.maxIndex;
  });
}

window.onscroll = onscroll;

document.addEventListener('scroll', onscroll);
window.addEventListener('resize', onscroll);
<commit_msg>Simplify pinning<commit_after>// Magic scroll things.
// This binds cool animations to the scroll level in such a way that the
// animations' progress can be controlled by scrolling. This does what
// ScrollMagic does except without the bugs.

const elem = {
  body: document.body,

  headerWrapper: document.getElementById('header-wrapper'),
  header: document.getElementsByTagName('header')[0],
  headerTitle: document.getElementsByTagName('h1')[0],
  downIndicator: document.getElementsByClassName('down-indicator')[0],

  laptop: document.getElementsByClassName('laptop3d')[0].parentNode,
  // Won't be initialized by the time elem is evaluated so we use a getter
  get laptopContent() { return this.laptop.getElementsByClassName('screen')[0]; },

  laptopsContainer: document.getElementsByClassName('laptops-container')[0],
  laptopsStretcher: document.getElementsByClassName('laptops-stretcher')[0],
};

let headerIsInLaptop = false;

// HEADER TRANSITION LOGIC =========================================================================

function updateHeaderElements(progress) {
  // Blends two values based on "progress" made between them.
  function tween(a, b) {
    return a + ((b - a) * progress);
  }
  // Returns the center of a passed ClientRect object (or similar)
  function getCenter(rect) {
    return { x: (rect.left + rect.right) / 2, y: (rect.top + rect.bottom) / 2 };
  }

  // FIT MAIN HEADER TO LAPTOP SCREEN

  if (progress < 1) {
    if (headerIsInLaptop) {
      elem.header.style.position = 'fixed';
      elem.header.style.top = elem.header.style.left = 0;
      elem.header.style.width = '100vw'; elem.header.style.height = '100vh';
      elem.headerWrapper.appendChild(elem.header);
      headerIsInLaptop = false;
    }
    // Beginning and end positions
    const w = window.innerWidth; const h = window.innerHeight;
    const posA = { left: 0, top: 0, right: w, bottom: h, width: w, height: h };
    const posB = elem.laptopContent.getBoundingClientRect();
    // Calculate translation
    // Center is used for calculating translation because element is scaled about the center)
    const centerA = getCenter(posA); const centerB = getCenter(posB);
    const translationNeeded = { x: centerB.x - centerA.x, y: centerB.y - centerA.y };
    // Calculate scale
    const scaleNeeded = { x: posB.width / posA.width, y: posB.height / posA.height };
    // Apply transformations
    elem.header.style.transform = [
      // Translate
      `translateX(${tween(0, translationNeeded.x)}px)`,
      `translateY(${tween(0, translationNeeded.y)}px)`,
      // Scale
      `scaleX(${tween(1, scaleNeeded.x)})`,
      `scaleY(${tween(1, scaleNeeded.y)})`,
    ].join(' ');
  } else if (!headerIsInLaptop) {
    elem.header.style.position = 'absolute';
    elem.header.style.transform = '';
    // This should be easy but Blink insists on rendering .front behind .back if I give
    // position: relative to .screen, so .header has to be manually positioned relative to .front
    const screenBezel = window.sassHeightVariable * 0.85;
    elem.header.style.top = elem.header.style.left = `${screenBezel}vw`;
    elem.header.style.width = `${window.sassLengthVariable - (2 * screenBezel) - 0.6}vw`; // 0.6 is border width
    elem.header.style.height = `${window.sassWidthVariable - (2.5 * screenBezel) - 0.6}vw`; // 2.5 because bottom bezel is bigger
    elem.laptopContent.appendChild(elem.header);
    headerIsInLaptop = true;
  }

  // FIT NAME TO LAPTOP SCREEN

  const centerA = { x: window.innerWidth / 2, y: window.innerHeight / 2 };
  const centerB = getCenter(elem.laptopContent.getBoundingClientRect());
  const translationNeeded = { x: centerB.x - centerA.x, y: centerB.y - centerA.y };
  elem.headerTitle.style.transform = [
    'translate(-50%, -50%)',
    `translateX(${tween(0, translationNeeded.x)}px)`,
    `translateY(${tween(0, translationNeeded.y)}px)`,
    `scale(${tween(1, 0.5)})`,
  ].join(' ');

  // MISCELLANEOUS

  // Pin / unpin laptop
  elem.laptop.style.position = progress < 1 ? 'fixed' : 'absolute';
  elem.laptop.style.top = progress < 1 ? '50vh' : `${window.innerHeight}px`;
  // Update opacity of down indicator
  elem.downIndicator.style.opacity = 1 - progress;

  // ADJUST CANVAS SETTINGS

  const canvasRect = window.gol.elem.getBoundingClientRect();
  // How much canvas has been downscaled overall? Averages X and Y scale factors to get a holistic
  // scale factor
  const horizontalScaleFactor = (canvasRect.width / window.innerWidth);
  const verticalScaleFactor = (canvasRect.height / window.innerHeight);
  const averageScaleFactor = (horizontalScaleFactor + verticalScaleFactor) / 2;
  window.gol.idealCellSize = 20 * averageScaleFactor;
  window.gol.needsSizeUpdate = true;
}

// BACKGROUND TRANSITION LOGIC =====================================================================

function updateBackgroundColor(progress) {
  const grad1A = window.parseColor('#5EFCE8');
  const grad1B = window.parseColor('#736EFE');

  const grad2A = window.parseColor('#16222A');
  const grad2B = window.parseColor('#4A5070');

  const interpolate = (a, b, amount) => ({
    r: Math.floor((a.r * (1 - progress)) + (b.r * amount)),
    g: Math.floor((a.g * (1 - progress)) + (b.g * amount)),
    b: Math.floor((a.b * (1 - progress)) + (b.b * amount)),
  });

  const getCssGradString = (a, b, deg = 135) =>
    `linear-gradient(${deg}deg, rgb(${a.r}, ${a.g}, ${a.b}), rgb(${b.r}, ${b.g}, ${b.b}))`;

  const calcA = interpolate(grad1A, grad2A, progress);
  const calcB = interpolate(grad1B, grad2B, progress);
  elem.body.style.backgroundImage = getCssGradString(calcA, calcB);
}

// SCROLL LISTENER =================================================================================

function onscroll() {
  const scroll = window.scrollY;

  // Header

  const headerProgress = scroll / (window.innerHeight / 2);
  if (scroll < 5) requestAnimationFrame(() => updateHeaderElements(0));
  else if (scroll > window.innerHeight / 2) requestAnimationFrame(() => updateHeaderElements(1));
  else requestAnimationFrame(() => updateHeaderElements(headerProgress));

  // Background

  switch (true) {
    case (scroll < window.innerHeight * 0.75): {
      updateBackgroundColor(0);
      break;
    }

    case (scroll > window.innerHeight * 1.25): {
      updateBackgroundColor(1);
      break;
    }

    default: {
      const bgProgress = (scroll - (window.innerHeight * 0.75)) / (window.innerHeight * 0.5);
      updateBackgroundColor(bgProgress);
    }
  }

  // Carousel

  const largerScreenDimension = Math.max(window.innerWidth, window.innerHeight);

  // Adjust carousel position
  const start = elem.laptopsStretcher.offsetTop;
  const end = start + (largerScreenDimension * 1.5);

  const carouselProgress = (scroll - start) / (end - start);
  requestAnimationFrame(() => {
    window.carousel.position = carouselProgress * window.carousel.maxIndex;
  });
}

window.onscroll = onscroll;

document.addEventListener('scroll', onscroll);
window.addEventListener('resize', onscroll);
<|endoftext|>


<commit_before>angular.module('app.services').factory('follows', function ($http,serverConfig, $q, $rootScope) {

  var FUser = function(userData, role){
    this.setData = function(userData){
      this.first_name = userData.first_name
      this.last_name = userData.last_name
      this.avatar_file_name = userData.avatar_file_name
      this.user_id = userData.id
      this.username = userData.username
      this.date_approval = userData.date_approval
      this.full_name = userData.full_name
    }

    this.roles = [role]
    this.setData(userData)

    this.isFollowingCurrentUser = function(){
      return(_.include(this.roles, 'isFollowingCurrentUser'))
    }

    this.isFollowedByCurrentUser = function(){
      return(_.include(this.roles, 'isFollowedByCurrentUser'))
    }

    this.isApprovedByCurrentUser = this.isFollowingCurrentUser

    this.addRole = function(role){
      this.roles.push(role)
      this.roles = _.uniq(this.roles)
    }

    this.removeRole = function(role){
      this.roles = _.without(this.roles, role)
    }

    this.approve = function(){
      this.date_approval = new Date()
      return $http.patch(serverConfig.url + '/api/v2/user/followers/'+this.user_id)
    }

    this.unApprove = function(){
      this.date_approval = null
      return $http.delete(serverConfig.url + '/api/v2/user/followers/'+this.user_id)
    }

    this.followByCurrentUser = function(){
      this.addRole('isFollowedByCurrentUser')
      return $http.put(serverConfig.url + '/api/v2/user/followings/'+this.user_id)
    }

    this.unFollowByCurrentUser = function(){
      this.removeRole('isFollowedByCurrentUser')
      return $http.delete(serverConfig.url + '/api/v2/user/followings/'+this.user_id)
    }

    this.isApproved = function(){
      return(this.date_approval != null)
    }
  }

  var service = {}
  service.loaded = false

  service.users = []

  service.load = function(){
    service.users = []

    var p1 = $http.get(serverConfig.url + '/api/v2/user/followings').then(function(response){
      response.data.payload.forEach(function(userData){
        var uID = userData.id
        var u = service.getUser(uID)
        if(u){
          u.addRole('isFollowedByCurrentUser')
          u.setData(userData)
        }

        else{
          u = new FUser(userData, 'isFollowedByCurrentUser')
          service.users.push(u)
        }
      })
    })

    var p2 = $http.get(serverConfig.url + '/api/v2/user/followers').then(function(response){
      response.data.payload.forEach(function(userData){
        var uID = userData.id
        var u = service.getUser(uID)
        if(u){
          u.addRole('isFollowingCurrentUser')
          u.setData(userData)
        }
        else{
          u = new FUser(userData, 'isFollowingCurrentUser')
          service.users.push(u)
        }
      })
    })

    var deferred = $q.defer();
    $q.all([p1, p2]).then(function(){
      service.loaded = true
      $rootScope.$broadcast('follows-loaded');
      deferred.resolve()
    })

    return deferred.promise
  }

  service.getUsersFollowingCurrentUser = function(){
    return this.users.filter(function(u){return u.isFollowingCurrentUser()})
  }

  service.getUsersFollowedByCurrentUser = function(){
    return this.users.filter(function(u){return u.isFollowedByCurrentUser()})
  }

  service.loadSuggested = function (friends) {
      return $http.post(serverConfig.url + '/api/profile/facebook-friends', friends).then(function (response) {
        return response.data;
      });
  }

  service.getFollowing = service.getUsersFollowedByCurrentUser

  service.getUser = function(uID){
    return _.find(service.users, function(user){
      return user.user_id == uID
    })
  }

  service.containsMockUsers = function(){
    var containsMock = false
    this.users.forEach(function(u){
      if(user.roles.length == 0)
        containsMock = true
    })

    return containsMock
  }

  service.getOrCreateUser = function(uID){
    var u = service.getUser(uID)

    if(u == null){
      u = new FUser({id: uID})
      service.users.push(u)
    }

    return u
  }

  service.size = function(){
    return(service.users.length)
  }

  service.searchForUsersFollowableByCurrentUser = function(queryText, page, max_count){
      var params = {
        unfollowing: 1,
        q: queryText,
        page: page || 1,
        max_count: max_count || 50
      };

      return $http.get(serverConfig.url + '/api/users/?' + angular.element.param(params)).then(function (response) {
        return _(response.data).map(function (userData) {
          return new FUser(userData);
        });
      });
  }

  return service
});
<commit_msg>minor:<commit_after>angular.module('app.services').factory('follows', function ($http,serverConfig, $q, $rootScope) {

  var FUser = function(userData, role){
    this.setData = function(userData){
      this.first_name = userData.first_name
      this.last_name = userData.last_name
      this.avatar_file_name = userData.avatar_file_name
      this.user_id = userData.id
      this.username = userData.username
      this.date_approval = userData.date_approval
      this.full_name = userData.full_name
    }

    this.roles = [role]
    this.setData(userData)

    this.isFollowingCurrentUser = function(){
      return(_.include(this.roles, 'isFollowingCurrentUser'))
    }

    this.isFollowedByCurrentUser = function(){
      return(_.include(this.roles, 'isFollowedByCurrentUser'))
    }

    this.addRole = function(role){
      this.roles.push(role)
      this.roles = _.uniq(this.roles)
    }

    this.removeRole = function(role){
      this.roles = _.without(this.roles, role)
    }

    this.approve = function(){
      this.date_approval = new Date()
      return $http.patch(serverConfig.url + '/api/v2/user/followers/'+this.user_id)
    }

    this.unApprove = function(){
      this.date_approval = null
      return $http.delete(serverConfig.url + '/api/v2/user/followers/'+this.user_id)
    }

    this.followByCurrentUser = function(){
      this.addRole('isFollowedByCurrentUser')
      return $http.put(serverConfig.url + '/api/v2/user/followings/'+this.user_id)
    }

    this.unFollowByCurrentUser = function(){
      this.removeRole('isFollowedByCurrentUser')
      return $http.delete(serverConfig.url + '/api/v2/user/followings/'+this.user_id)
    }

    this.isApproved = function(){
      return(this.date_approval != null)
    }

    this.isApprovedByCurrentUser = this.isApproved
  }

  var service = {}
  service.loaded = false

  service.users = []

  service.load = function(){
    service.users = []

    var p1 = $http.get(serverConfig.url + '/api/v2/user/followings').then(function(response){
      response.data.payload.forEach(function(userData){
        var uID = userData.id
        var u = service.getUser(uID)
        if(u){
          u.addRole('isFollowedByCurrentUser')
          u.setData(userData)
        }

        else{
          u = new FUser(userData, 'isFollowedByCurrentUser')
          service.users.push(u)
        }
      })
    })

    var p2 = $http.get(serverConfig.url + '/api/v2/user/followers').then(function(response){
      response.data.payload.forEach(function(userData){
        var uID = userData.id
        var u = service.getUser(uID)
        if(u){
          u.addRole('isFollowingCurrentUser')
          u.setData(userData)
        }
        else{
          u = new FUser(userData, 'isFollowingCurrentUser')
          service.users.push(u)
        }
      })
    })

    var deferred = $q.defer();
    $q.all([p1, p2]).then(function(){
      service.loaded = true
      $rootScope.$broadcast('follows-loaded');
      deferred.resolve()
    })

    return deferred.promise
  }

  service.getUsersFollowingCurrentUser = function(){
    return this.users.filter(function(u){return u.isFollowingCurrentUser()})
  }

  service.getUsersFollowedByCurrentUser = function(){
    return this.users.filter(function(u){return u.isFollowedByCurrentUser()})
  }

  service.loadSuggested = function (friends) {
      return $http.post(serverConfig.url + '/api/profile/facebook-friends', friends).then(function (response) {
        return response.data;
      });
  }

  service.getFollowing = service.getUsersFollowedByCurrentUser

  service.getUser = function(uID){
    return _.find(service.users, function(user){
      return user.user_id == uID
    })
  }

  service.containsMockUsers = function(){
    var containsMock = false
    this.users.forEach(function(u){
      if(user.roles.length == 0)
        containsMock = true
    })

    return containsMock
  }

  service.getOrCreateUser = function(uID){
    var u = service.getUser(uID)

    if(u == null){
      u = new FUser({id: uID})
      service.users.push(u)
    }

    return u
  }

  service.size = function(){
    return(service.users.length)
  }

  service.searchForUsersFollowableByCurrentUser = function(queryText, page, max_count){
      var params = {
        unfollowing: 1,
        q: queryText,
        page: page || 1,
        max_count: max_count || 50
      };

      return $http.get(serverConfig.url + '/api/users/?' + angular.element.param(params)).then(function (response) {
        return _(response.data).map(function (userData) {
          return new FUser(userData);
        });
      });
  }

  return service
});
<|endoftext|>


<commit_before>import path from 'path';
import test from 'ava';
import sinon from 'sinon';
import Filter from '../../src/filters/filter';

const cwd = process.cwd();
const root = path.resolve('/');
const base = path.resolve('/base');

const relPathToFile = 'path/to/file.js';
const absPathToFile = path.resolve('/path/to/file.js');

// basedir configuration
test('process.cwd() is the default baseDir', t => {
  const filter = new Filter();
  t.is(filter.getBaseDir(), process.cwd());
});

test('has a configurable basedir', t => {
  const baseDir = path.join(cwd, 'testcwd');
  const filter = new Filter({cwd: baseDir});
  t.is(filter.getBaseDir(), baseDir);
});

test('resolves a basedir with a relative path to an abslute path, relative to process.cwd()', t => {
  const relativePath = new Filter({cwd: 'testcwd'});
  t.is(relativePath.getBaseDir(), path.join(cwd, 'testcwd'));
});

// default behavior
test('ignores files by default', t => {
  const filter = new Filter();
  t.false(filter.apply(relPathToFile));
});

test('throws exception when apply is called with an empty path', t => {
  const filter = new Filter();
  const customError = 'Expected filePath argument of type string';

  t.throws(() => filter.apply(), customError);
  t.throws(() => filter.apply(''), customError);
  t.throws(() => filter.apply('   '), customError);
  t.throws(() => filter.apply('\n'), customError);
  t.throws(() => filter.apply(null), customError);
  t.throws(() => filter.apply(undefined), customError);
  t.throws(() => filter.apply(false), customError);
});

// filter calling
test('calls filter with an absolute path, and a path relative to the baseDir', t => {
  const filter = new Filter({cwd: base});
  const spy = sinon.spy(filter, 'filter');

  filter.apply('file.js');
  t.true(spy.calledOnce);
  t.true(spy.calledWithExactly(path.join(base, 'file.js'), 'file.js'));
  spy.reset();

  filter.apply('path/to/file.js');
  t.true(spy.calledOnce);
  t.true(spy.calledWithExactly(path.join(base, relPathToFile), relPathToFile));
  spy.reset();

  // outside of baseDir
  filter.apply('/file.js');
  t.true(spy.calledWithExactly('/file.js', '../file.js'));
  spy.reset();

  filter.apply('/path/to/file.js');
  t.true(spy.calledOnce);
  t.true(spy.calledWithExactly(absPathToFile, '../path/to/file.js'));
  spy.reset();
});

// file tracking
test('tracks filtered files by default', t => {
  const filter = new Filter({cwd: root});
  sinon.stub(filter, 'filter').returns(true);

  filter.apply(relPathToFile);
  t.true(filter.tracking(absPathToFile));
});

test('tracks only filtered files', t => {
  const filter = new Filter({cwd: root});
  sinon.stub(filter, 'filter')
    .withArgs(absPathToFile, relPathToFile)
    .returns(true);

  filter.apply(relPathToFile);
  filter.apply('path/to/files2.js');

  t.true(filter.tracking(absPathToFile));
  t.false(filter.tracking(path.resolve('/path/to/files2.js')));
});

test('does not track filtered files when options.trackFiltered is false', t => {
  const filter = new Filter({cwd: root, trackFiltered: false});
  sinon.stub(filter, 'filter').returns(true);

  filter.apply(relPathToFile);
  t.false(filter.tracking(absPathToFile));

  filter.apply('path/to/files2.js');
  t.false(filter.tracking(absPathToFile));
});
<commit_msg>Ensure absolute test path is in expected form<commit_after>import path from 'path';
import pathUtil from 'path-util';
import test from 'ava';
import sinon from 'sinon';
import Filter from '../../src/filters/filter';

const cwd = process.cwd();
const root = path.resolve('/');
const base = path.resolve('/base');

const relPathToFile = 'path/to/file.js';
const absPathToFile = pathUtil.toAbsolute('path/to/file.js', root);

// basedir configuration
test('process.cwd() is the default baseDir', t => {
  const filter = new Filter();
  t.is(filter.getBaseDir(), process.cwd());
});

test('has a configurable basedir', t => {
  const baseDir = path.join(cwd, 'testcwd');
  const filter = new Filter({cwd: baseDir});
  t.is(filter.getBaseDir(), baseDir);
});

test('resolves a basedir with a relative path to an abslute path, relative to process.cwd()', t => {
  const relativePath = new Filter({cwd: 'testcwd'});
  t.is(relativePath.getBaseDir(), path.join(cwd, 'testcwd'));
});

// default behavior
test('ignores files by default', t => {
  const filter = new Filter();
  t.false(filter.apply(relPathToFile));
});

test('throws exception when apply is called with an empty path', t => {
  const filter = new Filter();
  const customError = 'Expected filePath argument of type string';

  t.throws(() => filter.apply(), customError);
  t.throws(() => filter.apply(''), customError);
  t.throws(() => filter.apply('   '), customError);
  t.throws(() => filter.apply('\n'), customError);
  t.throws(() => filter.apply(null), customError);
  t.throws(() => filter.apply(undefined), customError);
  t.throws(() => filter.apply(false), customError);
});

// filter calling
test('calls filter with an absolute path, and a path relative to the baseDir', t => {
  const filter = new Filter({cwd: base});
  const spy = sinon.spy(filter, 'filter');

  filter.apply('file.js');
  t.true(spy.calledOnce);
  t.true(spy.calledWithExactly(pathUtil.toAbsolute('file.js', base), 'file.js'));
  spy.reset();

  filter.apply('path/to/file.js');
  t.true(spy.calledOnce);
  t.true(spy.calledWithExactly(pathUtil.toAbsolute(relPathToFile, base), relPathToFile));
  spy.reset();

  // outside of baseDir
  filter.apply('/file.js');
  t.true(spy.calledWithExactly('/file.js', '../file.js'));
  spy.reset();

  filter.apply('/path/to/file.js');
  t.true(spy.calledOnce);
  t.true(spy.calledWithExactly(absPathToFile, '../path/to/file.js'));
  spy.reset();
});

// file tracking
test('tracks filtered files by default', t => {
  const filter = new Filter({cwd: root});
  sinon.stub(filter, 'filter').returns(true);

  filter.apply(relPathToFile);
  t.true(filter.tracking(absPathToFile));
});

test('tracks only filtered files', t => {
  const filter = new Filter({cwd: root});
  sinon.stub(filter, 'filter')
    .withArgs(absPathToFile, relPathToFile)
    .returns(true);

  filter.apply(relPathToFile);
  filter.apply('path/to/files2.js');

  t.true(filter.tracking(absPathToFile));
  t.false(filter.tracking(path.resolve('/path/to/files2.js')));
});

test('does not track filtered files when options.trackFiltered is false', t => {
  const filter = new Filter({cwd: root, trackFiltered: false});
  sinon.stub(filter, 'filter').returns(true);

  filter.apply(relPathToFile);
  t.false(filter.tracking(absPathToFile));

  filter.apply('path/to/files2.js');
  t.false(filter.tracking(absPathToFile));
});
<|endoftext|>


<commit_before>import assign from 'lodash.assign'
import string from 'string'
import Token from 'markdown-it/lib/token'

const slugify = s =>
  string(s).slugify().toString()

const space = () =>
  assign(new Token('text', '', 0), { content: ' ' })

const position = {
  false: 'push',
  true: 'unshift'
}

const renderPermalink = (slug, opts, tokens, idx) => {
  const linkTokens = [
    assign(new Token('link_open', 'a', 1), {
      attrs: [
        ['class', opts.permalinkClass],
        ['href', `#${slug}`],
        ['aria-hidden', 'true']
      ]
    }),
    assign(new Token('text', '', 0), { content: opts.permalinkSymbol }),
    new Token('link_close', 'a', -1)
  ]

  // `push` or `unshift` according to position option.
  // Space is at the opposite side.
  linkTokens[position[!opts.permalinkBefore]](space())
  tokens[idx + 1].children[position[opts.permalinkBefore]](...linkTokens)
}

const uniqueSlug = (slug, env) => {
  // Add slug storage to environment if it doesn't already exist.
  env.slugs = env.slugs || {}

  // Mark this slug as used in the environment.
  env.slugs[slug] = (env.slugs[slug] || 0) + 1

  // First slug, return as is.
  if (env.slugs[slug] === 1) {
    return slug
  }

  // Duplicate slug, add a `-2`, `-3`, etc. to keep ID unique.
  return slug + '-' + env.slugs[slug]
}

const anchor = (md, opts) => {
  opts = assign({}, anchor.defaults, opts)

  const originalHeadingOpen = md.renderer.rules.heading_open

  md.renderer.rules.heading_open = function (...args) {
    const [ tokens, idx, , env, self ] = args

    if (tokens[idx].tag.substr(1) >= opts.level) {
      const title = tokens[idx + 1].children
        .reduce((acc, t) => acc + t.content, '')

      const slug = uniqueSlug(opts.slugify(title), env)

      ;(tokens[idx].attrs = tokens[idx].attrs || []).push(['id', slug])

      if (opts.permalink) {
        opts.renderPermalink(slug, opts, ...args)
      }
    }

    if (originalHeadingOpen) {
      return originalHeadingOpen.apply(this, args)
    } else {
      return self.renderToken(...args)
    }
  }
}

anchor.defaults = {
  level: 1,
  slugify,
  permalink: false,
  renderPermalink,
  permalinkClass: 'header-anchor',
  permalinkSymbol: '¶',
  permalinkBefore: false
}

export default anchor
<commit_msg>Refactor to use 'md.core.ruler' for adding attributes<commit_after>import assign from 'lodash.assign'
import string from 'string'
import Token from 'markdown-it/lib/token'

const slugify = s =>
  string(s).slugify().toString()

const space = () =>
  assign(new Token('text', '', 0), { content: ' ' })

const position = {
  false: 'push',
  true: 'unshift'
}

const getAttr = (attrs, name) => {
  for (let i = 0; i < attrs.length; i++) {
    if (attrs[i][0] === name) return attrs[i][1]
  }
}

const renderPermalink = (opts, tokens, idx) => {
  const slug = getAttr(tokens[idx].attrs, 'id')
  const linkTokens = [
    assign(new Token('link_open', 'a', 1), {
      attrs: [
        ['class', opts.permalinkClass],
        ['href', `#${slug}`],
        ['aria-hidden', 'true']
      ]
    }),
    assign(new Token('text', '', 0), { content: opts.permalinkSymbol }),
    new Token('link_close', 'a', -1)
  ]

  // `push` or `unshift` according to position option.
  // Space is at the opposite side.
  linkTokens[position[!opts.permalinkBefore]](space())
  tokens[idx + 1].children[position[opts.permalinkBefore]](...linkTokens)
}

const uniqueSlug = (slug, env) => {
  // Add slug storage to environment if it doesn't already exist.
  env.slugs = env.slugs || {}

  // Mark this slug as used in the environment.
  env.slugs[slug] = (env.slugs[slug] || 0) + 1

  // First slug, return as is.
  if (env.slugs[slug] === 1) {
    return slug
  }

  // Duplicate slug, add a `-2`, `-3`, etc. to keep ID unique.
  return slug + '-' + env.slugs[slug]
}

const anchor = (md, opts) => {
  opts = assign({}, anchor.defaults, opts)

  const addAnchors = state => {

    let env = {}
    let tokens = state.tokens

    for (let idx = 0, l = tokens.length; idx < l; idx++) {
      let token = tokens[idx]

      if (token.type !== 'heading_open') continue
      if (token.tag.substr(1) < opts.level) continue

      const title = tokens[idx + 1].children
        .reduce((acc, t) => acc + t.content, '')

      const slug = uniqueSlug(opts.slugify(title), env)

      token.attrPush(['id', slug])
    }
  }

  md.core.ruler.push('anchor', addAnchors)

  const originalHeadingOpen = md.renderer.rules.heading_open

  md.renderer.rules.heading_open = function (...args) {

    if (opts.permalink) {
      opts.renderPermalink(opts, ...args)
    }

    if (originalHeadingOpen) {
      return originalHeadingOpen.apply(this, args)
    } else {
      return md.renderer.renderToken(...args)
    }
  }
}

anchor.defaults = {
  level: 1,
  slugify,
  permalink: false,
  renderPermalink,
  permalinkClass: 'header-anchor',
  permalinkSymbol: '¶',
  permalinkBefore: false
}

export default anchor
<|endoftext|>


<commit_before>/* @flow */

function isSimplePositionValue(value: any) {
  return typeof value === 'number' && !isNaN(value)
}

const alignmentValues = ['center', 'end', 'start', 'stretch']

const displayValues = {
  'inline-grid': ['-ms-inline-grid', 'inline-grid'],
  grid: ['-ms-grid', 'grid'],
}

const propertyConverters = {
  alignSelf: (value: any, style: Object) => {
    if (alignmentValues.indexOf(value) > -1) {
      style.msGridRowAlign = value
    }
  },

  gridColumn: (value: any, style: Object) => {
    if (isSimplePositionValue(value)) {
      style.msGridColumn = value
    } else {
      const [start, end] = value.split('/').map(position => +position)
      propertyConverters.gridColumnStart(start, style)
      propertyConverters.gridColumnEnd(end, style)
    }
  },

  gridColumnEnd: (value: any, style: Object) => {
    const { msGridColumn } = style
    if (isSimplePositionValue(value) && isSimplePositionValue(msGridColumn)) {
      style.msGridColumnSpan = value - msGridColumn
    }
  },

  gridColumnStart: (value: any, style: Object) => {
    if (isSimplePositionValue(value)) {
      style.msGridColumn = value
    }
  },

  gridRow: (value: any, style: Object) => {
    if (isSimplePositionValue(value)) {
      style.msGridRow = value
    } else {
      const [start, end] = value.split('/').map(position => +position)
      propertyConverters.gridRowStart(start, style)
      propertyConverters.gridRowEnd(end, style)
    }
  },

  gridRowEnd: (value: any, style: Object) => {
    const { msGridRow } = style
    if (isSimplePositionValue(value) && isSimplePositionValue(msGridRow)) {
      style.msGridRowSpan = value - msGridRow
    }
  },

  gridRowStart: (value: any, style: Object) => {
    if (isSimplePositionValue(value)) {
      style.msGridRow = value
    }
  },

  gridTemplateColumns: (value: any, style: Object) => {
    style.msGridColumns = value
  },

  gridTemplateRows: (value: any, style: Object) => {
    style.msGridRows = value
  },

  justifySelf: (value: any, style: Object) => {
    if (alignmentValues.indexOf(value) > -1) {
      style.msGridColumnAlign = value
    }
  },
}

export default function grid(
  property: string,
  value: any,
  style: Object
): ?Array<string> {
  if (property === 'display' && value in displayValues) {
    return displayValues[value]
  }

  if (property in propertyConverters) {
    const propertyConverter = propertyConverters[property]
    propertyConverter(value, style)
  }
}
<commit_msg>Support shorthand values with span<commit_after>/* @flow */

function isSimplePositionValue(value: any) {
  return typeof value === 'number' && !isNaN(value)
}

const alignmentValues = ['center', 'end', 'start', 'stretch']

const displayValues = {
  'inline-grid': ['-ms-inline-grid', 'inline-grid'],
  grid: ['-ms-grid', 'grid'],
}

const propertyConverters = {
  alignSelf: (value: any, style: Object) => {
    if (alignmentValues.indexOf(value) > -1) {
      style.msGridRowAlign = value
    }
  },

  gridColumn: (value: any, style: Object) => {
    if (isSimplePositionValue(value)) {
      style.msGridColumn = value
    } else {
      const [start, end] = value.split('/')
      propertyConverters.gridColumnStart(+start, style)

      const [maybeSpan, maybeNumber] = end.split(/ ?span /)
      if (maybeSpan === '') {
        propertyConverters.gridColumnEnd(+start + +maybeNumber, style)
      } else {
        propertyConverters.gridColumnEnd(+end, style)
      }
    }
  },

  gridColumnEnd: (value: any, style: Object) => {
    const { msGridColumn } = style
    if (isSimplePositionValue(value) && isSimplePositionValue(msGridColumn)) {
      style.msGridColumnSpan = value - msGridColumn
    }
  },

  gridColumnStart: (value: any, style: Object) => {
    if (isSimplePositionValue(value)) {
      style.msGridColumn = value
    }
  },

  gridRow: (value: any, style: Object) => {
    if (isSimplePositionValue(value)) {
      style.msGridRow = value
    } else {
      const [start, end] = value.split('/')
      propertyConverters.gridRowStart(+start, style)

      const [maybeSpan, maybeNumber] = end.split(/ ?span /)
      if (maybeSpan === '') {
        propertyConverters.gridRowEnd(+start + +maybeNumber, style)
      } else {
        propertyConverters.gridRowEnd(+end, style)
      }
    }
  },

  gridRowEnd: (value: any, style: Object) => {
    const { msGridRow } = style
    if (isSimplePositionValue(value) && isSimplePositionValue(msGridRow)) {
      style.msGridRowSpan = value - msGridRow
    }
  },

  gridRowStart: (value: any, style: Object) => {
    if (isSimplePositionValue(value)) {
      style.msGridRow = value
    }
  },

  gridTemplateColumns: (value: any, style: Object) => {
    style.msGridColumns = value
  },

  gridTemplateRows: (value: any, style: Object) => {
    style.msGridRows = value
  },

  justifySelf: (value: any, style: Object) => {
    if (alignmentValues.indexOf(value) > -1) {
      style.msGridColumnAlign = value
    }
  },
}

export default function grid(
  property: string,
  value: any,
  style: Object
): ?Array<string> {
  if (property === 'display' && value in displayValues) {
    return displayValues[value]
  }

  if (property in propertyConverters) {
    const propertyConverter = propertyConverters[property]
    propertyConverter(value, style)
  }
}
<|endoftext|>


<commit_before>var _ = require('lodash');

let numThumbnails = 5;

module.exports = ['thumbnails', thumbnails => {
  return {
    restrict: 'E',
    template: require('./template.html'),
    link: ($scope, element, attributes) => {
      setTimeout(() => {
        let thumbnails = $scope.thumbnails,
            canvases = element.find('canvas');

        _.each(_.zip([thumbnails, canvases]), pair => {
          let thumbnail = pair[0],
              canvas = pair[1],
              context = canvas.getContext('2d');

          canvas.width = canvas.parentNode.clientWidth;
          canvas.height = canvas.width * 3 / 4;

          thumbnail.canvas = canvas;
          thumbnail.context = context;

          console.log(thumbnail.context);
        });

        let videoEl = $scope.videoEl;

        videoEl.addEventListener('pause', event => {
          drawThumbnails();
        });

        videoEl.addEventListener('timeupdate', event => {
          if (videoEl.paused) drawThumbnails();
          else drawThumbnails();
        });

        $scope.gotoThumbnail = thumbnail => {
          videoEl.currentTime = thumbnail.time;
        };

        function drawThumbnails() {
          _.each(thumbnails, thumbnail => {
            let canvas = thumbnail.canvas,
                context = thumbnail.context;

            let vw = videoEl.videoWidth,
                vh = videoEl.videoHeight,
                ratio = vw / vh;

            if (ratio > 1) {
              canvas.width = canvas.clientWidth;
              canvas.height = canvas.width / ratio;
            }
            else {
              canvas.height = canvas.clientHeight;
              canvas.width = canvas.height * ratio;
            }

            thumbnail.time = videoEl.currentTime;

            context.drawImage(videoEl, 0, 0, canvas.width, canvas.height);
          });
        }
      }, 0);
    },
    controller: ['$scope', ($scope) => {
      $scope.thumbnails = thumbnails(numThumbnails);

    }]
  };
}];<commit_msg>Changed default aspect ratio. Added parentNode check.<commit_after>var _ = require('lodash');

let numThumbnails = 5;

module.exports = ['thumbnails', thumbnails => {
  return {
    restrict: 'E',
    template: require('./template.html'),
    link: ($scope, element, attributes) => {
      setTimeout(() => {
        let thumbnails = $scope.thumbnails,
            canvases = element.find('canvas');

        _.each(_.zip([thumbnails, canvases]), pair => {
          let thumbnail = pair[0],
              canvas = pair[1],
              context = canvas.getContext('2d');

          canvas.width = canvas.parentNode.clientWidth;
          canvas.height = canvas.width * 9 / 16;

          thumbnail.canvas = canvas;
          thumbnail.context = context;

          console.log(thumbnail.context);
        });

        let videoEl = $scope.videoEl;

        videoEl.addEventListener('pause', event => {
          drawThumbnails();
        });

        videoEl.addEventListener('timeupdate', event => {
          if (videoEl.paused) drawThumbnails();
          else drawThumbnails();
        });

        $scope.gotoThumbnail = thumbnail => {
          videoEl.currentTime = thumbnail.time;
        };

        function drawThumbnails() {
          _.each(thumbnails, thumbnail => {
            let canvas = thumbnail.canvas,
                context = thumbnail.context;

            let vw = videoEl.videoWidth,
                vh = videoEl.videoHeight,
                ratio = vw / vh;

            if (ratio > 1) {
              canvas.width = canvas.parentNode.clientWidth;
              canvas.height = canvas.width / ratio;
            }
            else {
              canvas.height = canvas.parentNode.clientHeight;
              canvas.width = canvas.height * ratio;
            }

            thumbnail.time = videoEl.currentTime;

            context.drawImage(videoEl, 0, 0, canvas.width, canvas.height);
          });
        }
      }, 0);
    },
    controller: ['$scope', ($scope) => {
      $scope.thumbnails = thumbnails(numThumbnails);

    }]
  };
}];<|endoftext|>


<commit_before>import React from 'react'
import ReactDOM from 'react-dom'

import { createStore, combineReducers, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import thunkMiddleware from 'redux-thunk'
import pipelines from 'sajari-react/api/reducers/pipeline'

import App from './App'

function sjsi(config) {
  if (!config) {
    console.error('global value "_sjsi" not found, it is needed for Sajari Search Interface')
    return
  }

  const store = createStore(
    combineReducers({ pipelines }),
    process.env.NODE_ENV === 'development' && window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
    applyMiddleware(thunkMiddleware)
  )

  ReactDOM.render(
    <Provider store={store}>
      <App config={config}/>
    </Provider>,
    config.attachTarget
  )
}

sjsi(window._sjsi)
<commit_msg>Add search middleware and GA to site search integration example<commit_after>import React from 'react'
import ReactDOM from 'react-dom'

import { createStore, combineReducers, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import thunkMiddleware from 'redux-thunk'
import pipelines from 'sajari-react/api/reducers/pipeline'
import search from 'sajari-react/ui/reducers/Search'
import { resetPipelinePageMiddleware, searchPipelineMiddleware } from 'sajari-react/ui/middleware/Search'
import GoogleAnalytics from 'sajari-react/ui/Analytics/ga'
import AnalyticsEvents from 'sajari-react/ui/middleware/Events'

import App from './App'

function sjsi(config) {
  if (!config) {
    console.error('global value "_sjsi" not found, it is needed for Sajari Search Interface')
    return
  }

  const store = createStore(
    combineReducers({ pipelines, search }),
    process.env.NODE_ENV === 'development' && window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
    applyMiddleware(thunkMiddleware, resetPipelinePageMiddleware, searchPipelineMiddleware, AnalyticsEvents(new GoogleAnalytics()))
  )

  ReactDOM.render(
    <Provider store={store}>
      <App config={config}/>
    </Provider>,
    config.attachTarget
  )
}

sjsi(window._sjsi)
<|endoftext|>


<commit_before>'use strict';

const component = require( './parseComponentName' );
const deepmerge = require( 'deepmerge' );
const fs = require( 'fs' );
const gulp = require( 'gulp' );
const gulpData = require( 'gulp-data' );
const gulpForeach = require( 'gulp-foreach' );
const gulpJsonFormat = require( 'gulp-json-format' );
const gulpRename = require( 'gulp-rename' );

let baseManifest = JSON.parse(fs.readFileSync('./package.json', 'utf8'));

gulp.task( 'copy:components:boilerplate', () => {
  gulp.src(['./src/' + (component || '*'), '!./src/*.js', '!./src/*.less'])
    .pipe(gulpForeach(function(stream, file) {
      var component = file.path.split('/').pop();
      gulp.src( './scripts/templates/component-boilerplate/*' )
          .pipe( gulp.dest('./tmp/' + component) );
      return stream;
    }))
} );

gulp.task( 'copy:components:source', () => {
  gulp.src(['./src/' + (component || '*'), '!./src/*.js', '!./src/*.less'])
    .pipe(gulpForeach(function(stream, file) {
      var component = file.path.split('/').pop(),
          src = [
                  file.path + '/**',
                  '!' + file.path + '/package.json',
                  '!' + file.path + '/node_modules',
                  '!' + file.path + '/node_modules/**',
                  '!' + file.path + '/npm-*'
                ];
      gulp.src( src )
          .pipe( gulp.dest('./tmp/' + component) );
      return stream;
    }))
} );

gulp.task( 'copy:components:manifest', () => {
  gulp.src('./src/' + (component || '*') + '/package.json')
    .pipe(gulpData(function(file) {
      // Remove any dependencies from CF's package.json,
      // we don't want components to have them.
      delete baseManifest.dependencies;
      var manifest = deepmerge(baseManifest, JSON.parse(String(file.contents)));
      // After the merge, remove any scripts and dev deps.
      delete manifest.scripts;
      delete manifest.devDependencies;
      file.contents = new Buffer(JSON.stringify(manifest));
    }))
    .pipe(gulpRename(function(path) {
      path.dirname = component || path.dirname;
    }))
    .pipe(gulpJsonFormat(2))
    .pipe(gulp.dest('./tmp'));
} );
<commit_msg>Add named functions for copy task<commit_after>'use strict';

const component = require( './parseComponentName' );
const deepmerge = require( 'deepmerge' );
const fs = require( 'fs' );
const gulp = require( 'gulp' );
const gulpData = require( 'gulp-data' );
const gulpForeach = require( 'gulp-foreach' );
const gulpJSBeautifier = require( 'gulp-jsbeautifier' );
const gulpRename = require( 'gulp-rename' );

let baseManifest = JSON.parse(fs.readFileSync('./package.json', 'utf8'));

/**
 * TODO: Add description of what this task does.
 * @returns {Object} An output stream from gulp.
 */
function copyComponentsBoilerplate(){
  gulp.src(['./src/' + (component || '*'), '!./src/*.js', '!./src/*.less'])
    .pipe(gulpForeach(function(stream, file) {
      var component = file.path.split('/').pop();
      gulp.src( './scripts/templates/component-boilerplate/*' )
          .pipe( gulp.dest('./tmp/' + component) );
      return stream;
    }))
} );

/**
 * TODO: Add description of what this task does.
 * @returns {Object} An output stream from gulp.
 */
function copyComponentsSource() {
  gulp.src(['./src/' + (component || '*'), '!./src/*.js', '!./src/*.less'])
    .pipe(gulpForeach(function(stream, file) {
      var component = file.path.split('/').pop(),
          src = [
                  file.path + '/**',
                  '!' + file.path + '/package.json',
                  '!' + file.path + '/node_modules',
                  '!' + file.path + '/node_modules/**',
                  '!' + file.path + '/npm-*'
                ];
      gulp.src( src )
          .pipe( gulp.dest('./tmp/' + component) );
      return stream;
    }))
} );

/**
 * TODO: Add description of what this task does.
 * @returns {Object} An output stream from gulp.
 */
function copyComponentsManifest() {
  gulp.src('./src/' + (component || '*') + '/package.json')
    .pipe(gulpData(function(file) {
      // Remove any dependencies from CF's package.json,
      // we don't want components to have them.
      delete baseManifest.dependencies;
      var manifest = deepmerge(baseManifest, JSON.parse(String(file.contents)));
      // After the merge, remove any scripts and dev deps.
      delete manifest.scripts;
      delete manifest.devDependencies;
      file.contents = new Buffer(JSON.stringify(manifest));
    }))
    .pipe(gulpRename(function(path) {
      path.dirname = component || path.dirname;
    }))
    .pipe( gulpJSBeautifier( {
       // eslint-disable-next-line camelcase
       indent_size: 2
    } ) )
    .pipe(gulp.dest('./tmp'));
} );

gulp.task( 'copy:components:boilerplate', copyComponentsBoilerplate );
gulp.task( 'copy:components:source', copyComponentsSource );
gulp.task( 'copy:components:manifest', copyComponentsManifest );
<|endoftext|>


<commit_before>var fs = require('fs'),
  _ = require('lodash');

function readFile(path) {
  var filePath = 'app/support/stagecraft_stub/responses/' + path + '.json';
  if (fs.existsSync(filePath)) {
    return fs.readFileSync(filePath);
  } else {
    return false;
  }
}

function parseParent(content, slug) {
  var dashboardData = JSON.parse(content);
  var module = _.find(dashboardData.modules, function (module) {
    return module.slug === slug;
  });

  if (!module) {
    return false;
  }

  module = _.extend(module, {
    'page-type': 'module',
    'dashboard-title': dashboardData.title,
    'dashboard-strapline': dashboardData.strapline,
    'dashboard-slug': dashboardData.slug
  });

  if (dashboardData.department) {
    module = _.extend(module, {
      'department': dashboardData.department
    });
  }

  if (dashboardData.agency) {
    module = _.extend(module, {
      'agency': dashboardData.agency
    });
  }

  delete module.classes; // We don't care about the classes property on the page-per-thing pages

  return module;

}

function send404(res, filePath) {
  res.status(404);
  res.send({error: "No such stub exists: " + filePath});
}

module.exports =  function (req, res) {
  var paramPath = req.params[0];
  var content = readFile(paramPath);
  if (content) {
    res.send(JSON.parse(content));
  } else {
    paramPath = paramPath.split('/');
    var slug = paramPath.pop();
    var parentPath = paramPath.join('/');
    var parentContent = readFile(parentPath);
    if (parentContent) {
      var moduleData = parseParent(parentContent, slug);
      if (moduleData) {
        res.send(moduleData);
      } else {
        send404(res, paramPath);
      }
    } else {
      send404(res, paramPath);
    }
  }
};
<commit_msg>Swap fs sync methods for async versions<commit_after>var fs = require('graceful-fs'),
  _ = require('lodash');

function readFile(path, callback) {
  var filePath = 'app/support/stagecraft_stub/responses/' + path + '.json';
  fs.readFile(filePath, callback);
}

function parseParent(content, slug) {
  var dashboardData = JSON.parse(content);
  var module = _.find(dashboardData.modules, function (module) {
    return module.slug === slug;
  });

  if (!module) {
    return false;
  }

  module = _.extend(module, {
    'page-type': 'module',
    'dashboard-title': dashboardData.title,
    'dashboard-strapline': dashboardData.strapline,
    'dashboard-slug': dashboardData.slug
  });

  if (dashboardData.department) {
    module = _.extend(module, {
      'department': dashboardData.department
    });
  }

  if (dashboardData.agency) {
    module = _.extend(module, {
      'agency': dashboardData.agency
    });
  }

  delete module.classes; // We don't care about the classes property on the page-per-thing pages

  return module;

}

function send404(res, filePath) {
  res.status(404);
  res.send({error: "No such stub exists: " + filePath});
}

module.exports =  function (req, res) {
  var paramPath = req.params[0];
  var content = readFile(paramPath, function (err, content) {
    if (err) {
      paramPath = paramPath.split('/');
      var slug = paramPath.pop();
      var parentPath = paramPath.join('/');
      readFile(parentPath, function (err, parentContent) {
        if (err) {
          send404(res, paramPath);
        } else {
          var moduleData = parseParent(parentContent, slug);
          if (moduleData) {
            res.send(moduleData);
          } else {
            send404(res, paramPath);
          }
        }
      });
    } else {
      res.send(JSON.parse(content));
    }
  });
};
<|endoftext|>