<commit_before>// UMD module from From https://github.com/umdjs/umd/blob/master/returnExports.js
// From 'if the module has no dependencies' example.
(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define([], factory);
    } else if (typeof exports === 'object') {
        // Node. Does not work with strict CommonJS, but
        // only CommonJS-like environments that support module.exports,
        // like Node.
        module.exports = factory();
    } else {
        // Browser globals (root is window)
        root.returnExports = factory();
  }
}(this, function () {
// End of UMD module

	// Quick aliases and polyfills if needed
	var query = document.querySelector.bind(document);
	var queryAll = document.querySelectorAll.bind(document);
	var log = console.log.bind(console);

	var KEYCODES = {
		SPACE: 32,
		UP: 38,
		DOWN: 40,
		ENTER: 13
	}

	if ( ! NodeList.prototype.forEach ) {
		NodeList.prototype.forEach = Array.prototype.forEach;
	}
	if ( ! HTMLCollection.prototype.forEach ) {
		HTMLCollection.prototype.forEach = Array.prototype.forEach;
	}
	if ( ! Element.prototype.matches ) {
		// See https://developer.mozilla.org/en-US/docs/Web/API/Element.matches
		Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.webkitMatchesSelector || Element.prototype.oMatchesSelector
	}

	// IE 9-11 CustomEvent polyfill
	// From https://developer.mozilla.org/en/docs/Web/API/CustomEvent
	var CustomEvent = function( eventName, params ) {
		params = params || { bubbles: false, cancelable: false, detail: undefined };
		var event = document.createEvent( 'CustomEvent' );
		event.initCustomEvent( eventName, params.bubbles, params.cancelable, params.detail );
		return event;
	};
	CustomEvent.prototype = window.Event.prototype;
	window.CustomEvent = CustomEvent;

	// IE10 dataset polyfill
	// From https://gist.githubusercontent.com/brettz9/4093766/raw/ba31a05e7ce21af67c6cafee9b3f439c86e95b01/html5-dataset.js
	if (!document.documentElement.dataset &&
			 // FF is empty while IE gives empty object
			(!Object.getOwnPropertyDescriptor(Element.prototype, 'dataset')  ||
			!Object.getOwnPropertyDescriptor(Element.prototype, 'dataset').get)
		) {
		var propDescriptor = {
			enumerable: true,
			get: function () {
				'use strict';
				var i,
					that = this,
					HTML5_DOMStringMap,
					attrVal, attrName, propName,
					attribute,
					attributes = this.attributes,
					attsLength = attributes.length,
					toUpperCase = function (n0) {
						return n0.charAt(1).toUpperCase();
					},
					getter = function () {
						return this;
					},
					setter = function (attrName, value) {
						return (typeof value !== 'undefined') ?
							this.setAttribute(attrName, value) :
							this.removeAttribute(attrName);
					};
				try { // Simulate DOMStringMap w/accessor support
					// Test setting accessor on normal object
					({}).__defineGetter__('test', function () {});
					HTML5_DOMStringMap = {};
				}
				catch (e1) { // Use a DOM object for IE8
					HTML5_DOMStringMap = document.createElement('div');
				}
				for (i = 0; i < attsLength; i++) {
					attribute = attributes[i];
					// Fix: This test really should allow any XML Name without
					//         colons (and non-uppercase for XHTML)
					if (attribute && attribute.name &&
						(/^data-\w[\w\-]*$/).test(attribute.name)) {
						attrVal = attribute.value;
						attrName = attribute.name;
						// Change to CamelCase
						propName = attrName.substr(5).replace(/-./g, toUpperCase);
						try {
							Object.defineProperty(HTML5_DOMStringMap, propName, {
								enumerable: this.enumerable,
								get: getter.bind(attrVal || ''),
								set: setter.bind(that, attrName)
							});
						}
						catch (e2) { // if accessors are not working
							HTML5_DOMStringMap[propName] = attrVal;
						}
					}
				}
				return HTML5_DOMStringMap;
			}
		};
		try {
			// FF enumerates over element's dataset, but not
			//   Element.prototype.dataset; IE9 iterates over both
			Object.defineProperty(Element.prototype, 'dataset', propDescriptor);
		} catch (e) {
			propDescriptor.enumerable = false; // IE8 does not allow setting to true
			Object.defineProperty(Element.prototype, 'dataset', propDescriptor);
		}
	}

	// Return true if any ancestor matches selector
	// Borrowed from ancestorMatches() from agave.js (MIT)
	var isAncestorOf = function(element, selector, includeSelf) {
		var parent = element.parentNode;
		if ( includeSelf && element.matches(selector) ) {
			return true
		}
		// While parents are 'element' type nodes
		// See https://developer.mozilla.org/en-US/docs/DOM/Node.nodeType
		while ( parent && parent.nodeType && parent.nodeType === 1 ) {
			if ( parent.matches(selector) ) {
				return true
			}
			parent = parent.parentNode;
		}
		return false;
	};

	// Used to match select boxes to their style select partners
	var makeUUID = function(){
		var UUID = 'ss-xxxx-xxxx-xxxx-xxxx-xxxx'.replace(/x/g, function(c) {
			var r = Math.random() * 16|0, v = c == 'x'? r : r&0x3|0x8;
			return v.toString(16);
		})
		return UUID
	}

	// The 'styleSelect' main function
	// selector:String - CSS selector for the select box to style
	return function(selector) {

		// Use native selects (which pop up large native UIs to go through the options ) on iOS/Android
		if ( navigator.userAgent.match( /iPad|iPhone|Android/i ) ) {
			return
		}

		var realSelect = query(selector),
			realOptions = realSelect.children,
			selectedIndex = realSelect.selectedIndex,
			uuid = makeUUID(),
			styleSelectHTML = '
';

		// The index of the item that's being highlighted by the mouse or keyboard
		var highlightedOptionIndex;
		var highlightedOptionIndexMax = realOptions.length - 1;

		realSelect.setAttribute('data-ss-uuid', uuid);
		// Even though the element is display: none, a11y users should still see it.
		// According to http://www.w3.org/TR/wai-aria/states_and_properties#aria-hidden
		// some browsers may have bugs with this but future implementation may improve
		realSelect.setAttribute('aria-hidden', "false");

		// Build styled clones of all the real options
		var selectedOptionHTML;
		var optionsHTML = '
';
		realOptions.forEach(function(realOption, index){
			var text = realOption.textContent,
				value = realOption.getAttribute('value') || '' ;

			if (index === selectedIndex) {
				// Mark first item as selected-option - this is where we store state for the styled select box
				// aria-hidden=true so screen readers ignore the styles selext box in favor of the real one (which is visible by default)
				selectedOptionHTML = '
' + text + '
'
			}
			// Continue building optionsHTML
			optionsHTML += '
' + text + '
';
		})
		optionsHTML += '
';
		styleSelectHTML += selectedOptionHTML += optionsHTML += '
';
		// And add out styled select just after the real select
		realSelect.insertAdjacentHTML('afterend', styleSelectHTML);

		var styledSelect = query('.style-select[data-ss-uuid="'+uuid+'"]')
		var styleSelectOptions = styledSelect.querySelectorAll('.ss-option');
		var selectedOption = styledSelect.querySelector('.ss-selected-option')

		var changeRealSelectBox = function(newValue, newLabel) {
			// Close styledSelect
			styledSelect.classList.remove('open');

			// Update styled value
			selectedOption.textContent = newLabel;
			selectedOption.dataset.value = newValue;

			// Update the 'tick' that shows the option with the current value
			styleSelectOptions.forEach(function(styleSelectOption){
				if ( styleSelectOption.dataset.value === newValue) {
					styleSelectOption.classList.add('ticked')
				} else {
					styleSelectOption.classList.remove('ticked')
				}
			})

			// Update real select box
			realSelect.value = newValue;

			// Send 'change' event to real select - to trigger any change event listeners
			var changeEvent = new CustomEvent('change');
			realSelect.dispatchEvent(changeEvent);
		}

		// Change real select box when a styled option is clicked
		styleSelectOptions.forEach(function(unused, index){

			var styleSelectOption = styleSelectOptions.item(index);
			styleSelectOption.addEventListener('click', function(ev) {
				var target = ev.target,
					styledSelectBox = target.parentNode.parentNode,
					uuid = styledSelectBox.getAttribute('data-ss-uuid'),
					newValue = target.getAttribute('data-value'),
					newLabel = target.textContent;

				changeRealSelectBox(newValue, newLabel)

			});

			// Tick and highlight the option that's currently in use
			if ( styleSelectOption.dataset.value === realSelect.value ) {
				highlightedOptionIndex = index;
				styleSelectOption.classList.add('ticked');
				styleSelectOption.classList.add('highlighted')
			}

			// Important: we can't use ':hover' as the keyboard and default value can also set the highlight
			styleSelectOption.addEventListener('mouseover', function(ev){
				styleSelectOption.parentNode.childNodes.forEach(function(sibling, index){
					if ( sibling === ev.target ) {
						sibling.classList.add('highlighted')
						highlightedOptionIndex = index;
					} else {
						sibling.classList.remove('highlighted')
					}
				})
			})
		})

		var closeAllStyleSelects = function(exception){
			queryAll('.style-select').forEach(function(styleSelectEl) {
				if ( styleSelectEl !== exception ) {
					styleSelectEl.classList.remove('open');
				}
			});
		}

		var toggleStyledSelect = function(styledSelectBox){
			if ( ! styledSelectBox.classList.contains('open') ) {
				// If we're closed and about to open, close other style selects on the page
				closeAllStyleSelects(styledSelectBox);
			}
			// Then toggle open/close
			styledSelectBox.classList.toggle('open');
		}

		// When a styled select box is clicked
		var styledSelectedOption = query('.style-select[data-ss-uuid="' + uuid + '"] .ss-selected-option')
		styledSelectedOption.addEventListener('click', function(ev) {
			ev.preventDefault();
			ev.stopPropagation();
			toggleStyledSelect(ev.target.parentNode);
		});

		// Keyboard handling
		styledSelectedOption.addEventListener('keydown', function(ev) {
			var styledSelectBox = ev.target.parentNode

			switch (ev.keyCode) {
				case KEYCODES.SPACE:
					// Space shows and hides styles select boxes
					toggleStyledSelect(styledSelectBox);
					break;
				case KEYCODES.DOWN:
				case KEYCODES.UP:
					// Move the highlight up and down
					if ( ! styledSelectBox.classList.contains('open') ) {
						// If style select is not open, up/down should open it.
						toggleStyledSelect(styledSelectBox);
					} else {
						// If style select is already open, these should change what the highlighted option is
						if ( ev.keyCode === KEYCODES.UP ) {
							// Up arrow moves earlier in list
							if ( highlightedOptionIndex !== 0 ) {
								highlightedOptionIndex = highlightedOptionIndex - 1
							}
						} else {
							// Down arrow moves later in list
							if ( highlightedOptionIndex < highlightedOptionIndexMax ) {
								highlightedOptionIndex = highlightedOptionIndex + 1
							}
						}
						styleSelectOptions.forEach(function(option, index){
							if ( index === highlightedOptionIndex ) {
								option.classList.add('highlighted')
							} else {
								option.classList.remove('highlighted')
							}
						})
					}
					ev.preventDefault();
					ev.stopPropagation();
					break;
				// User has picked an item from the keyboard
				case KEYCODES.ENTER:
					var highlightedOption = styledSelectedOption.parentNode.querySelectorAll('.ss-option')[highlightedOptionIndex],
						newValue = highlightedOption.dataset.value,
						newLabel = highlightedOption.textContent

					changeRealSelectBox(newValue, newLabel)
					ev.preventDefault();
					ev.stopPropagation();
					break;
			}
		});

		// Clicking outside of the styled select box closes any open styled select boxes
		query('body').addEventListener('click', function(ev){

			if ( ! isAncestorOf(ev.target, '.style-select', true) ) {
				closeAllStyleSelects();
			}
		})

	};

// Close UMD module
}));

<commit_msg>Issue #5: Add missing statement terminators<commit_after>// UMD module from From https://github.com/umdjs/umd/blob/master/returnExports.js
// From 'if the module has no dependencies' example.
(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define([], factory);
    } else if (typeof exports === 'object') {
        // Node. Does not work with strict CommonJS, but
        // only CommonJS-like environments that support module.exports,
        // like Node.
        module.exports = factory();
    } else {
        // Browser globals (root is window)
        root.returnExports = factory();
  }
}(this, function () {
// End of UMD module

	// Quick aliases and polyfills if needed
	var query = document.querySelector.bind(document);
	var queryAll = document.querySelectorAll.bind(document);
	var log = console.log.bind(console);

	var KEYCODES = {
		SPACE: 32,
		UP: 38,
		DOWN: 40,
		ENTER: 13
	};

	if ( ! NodeList.prototype.forEach ) {
		NodeList.prototype.forEach = Array.prototype.forEach;
	}
	if ( ! HTMLCollection.prototype.forEach ) {
		HTMLCollection.prototype.forEach = Array.prototype.forEach;
	}
	if ( ! Element.prototype.matches ) {
		// See https://developer.mozilla.org/en-US/docs/Web/API/Element.matches
		Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.webkitMatchesSelector || Element.prototype.oMatchesSelector
	}

	// IE 9-11 CustomEvent polyfill
	// From https://developer.mozilla.org/en/docs/Web/API/CustomEvent
	var CustomEvent = function( eventName, params ) {
		params = params || { bubbles: false, cancelable: false, detail: undefined };
		var event = document.createEvent( 'CustomEvent' );
		event.initCustomEvent( eventName, params.bubbles, params.cancelable, params.detail );
		return event;
	};
	CustomEvent.prototype = window.Event.prototype;
	window.CustomEvent = CustomEvent;

	// IE10 dataset polyfill
	// From https://gist.githubusercontent.com/brettz9/4093766/raw/ba31a05e7ce21af67c6cafee9b3f439c86e95b01/html5-dataset.js
	if (!document.documentElement.dataset &&
			 // FF is empty while IE gives empty object
			(!Object.getOwnPropertyDescriptor(Element.prototype, 'dataset')  ||
			!Object.getOwnPropertyDescriptor(Element.prototype, 'dataset').get)
		) {
		var propDescriptor = {
			enumerable: true,
			get: function () {
				'use strict';
				var i,
					that = this,
					HTML5_DOMStringMap,
					attrVal, attrName, propName,
					attribute,
					attributes = this.attributes,
					attsLength = attributes.length,
					toUpperCase = function (n0) {
						return n0.charAt(1).toUpperCase();
					},
					getter = function () {
						return this;
					},
					setter = function (attrName, value) {
						return (typeof value !== 'undefined') ?
							this.setAttribute(attrName, value) :
							this.removeAttribute(attrName);
					};
				try { // Simulate DOMStringMap w/accessor support
					// Test setting accessor on normal object
					({}).__defineGetter__('test', function () {});
					HTML5_DOMStringMap = {};
				}
				catch (e1) { // Use a DOM object for IE8
					HTML5_DOMStringMap = document.createElement('div');
				}
				for (i = 0; i < attsLength; i++) {
					attribute = attributes[i];
					// Fix: This test really should allow any XML Name without
					//         colons (and non-uppercase for XHTML)
					if (attribute && attribute.name &&
						(/^data-\w[\w\-]*$/).test(attribute.name)) {
						attrVal = attribute.value;
						attrName = attribute.name;
						// Change to CamelCase
						propName = attrName.substr(5).replace(/-./g, toUpperCase);
						try {
							Object.defineProperty(HTML5_DOMStringMap, propName, {
								enumerable: this.enumerable,
								get: getter.bind(attrVal || ''),
								set: setter.bind(that, attrName)
							});
						}
						catch (e2) { // if accessors are not working
							HTML5_DOMStringMap[propName] = attrVal;
						}
					}
				}
				return HTML5_DOMStringMap;
			}
		};
		try {
			// FF enumerates over element's dataset, but not
			//   Element.prototype.dataset; IE9 iterates over both
			Object.defineProperty(Element.prototype, 'dataset', propDescriptor);
		} catch (e) {
			propDescriptor.enumerable = false; // IE8 does not allow setting to true
			Object.defineProperty(Element.prototype, 'dataset', propDescriptor);
		}
	}

	// Return true if any ancestor matches selector
	// Borrowed from ancestorMatches() from agave.js (MIT)
	var isAncestorOf = function(element, selector, includeSelf) {
		var parent = element.parentNode;
		if ( includeSelf && element.matches(selector) ) {
			return true
		}
		// While parents are 'element' type nodes
		// See https://developer.mozilla.org/en-US/docs/DOM/Node.nodeType
		while ( parent && parent.nodeType && parent.nodeType === 1 ) {
			if ( parent.matches(selector) ) {
				return true
			}
			parent = parent.parentNode;
		}
		return false;
	};

	// Used to match select boxes to their style select partners
	var makeUUID = function(){
		var UUID = 'ss-xxxx-xxxx-xxxx-xxxx-xxxx'.replace(/x/g, function(c) {
			var r = Math.random() * 16|0, v = c == 'x'? r : r&0x3|0x8;
			return v.toString(16);
		});
		return UUID
	};

	// The 'styleSelect' main function
	// selector:String - CSS selector for the select box to style
	return function(selector) {

		// Use native selects (which pop up large native UIs to go through the options ) on iOS/Android
		if ( navigator.userAgent.match( /iPad|iPhone|Android/i ) ) {
			return
		}

		var realSelect = query(selector),
			realOptions = realSelect.children,
			selectedIndex = realSelect.selectedIndex,
			uuid = makeUUID(),
			styleSelectHTML = '
';

		// The index of the item that's being highlighted by the mouse or keyboard
		var highlightedOptionIndex;
		var highlightedOptionIndexMax = realOptions.length - 1;

		realSelect.setAttribute('data-ss-uuid', uuid);
		// Even though the element is display: none, a11y users should still see it.
		// According to http://www.w3.org/TR/wai-aria/states_and_properties#aria-hidden
		// some browsers may have bugs with this but future implementation may improve
		realSelect.setAttribute('aria-hidden', "false");

		// Build styled clones of all the real options
		var selectedOptionHTML;
		var optionsHTML = '
';
		realOptions.forEach(function(realOption, index){
			var text = realOption.textContent,
				value = realOption.getAttribute('value') || '' ;

			if (index === selectedIndex) {
				// Mark first item as selected-option - this is where we store state for the styled select box
				// aria-hidden=true so screen readers ignore the styles selext box in favor of the real one (which is visible by default)
				selectedOptionHTML = '
' + text + '
'
			}
			// Continue building optionsHTML
			optionsHTML += '
' + text + '
';
		});
		optionsHTML += '
';
		styleSelectHTML += selectedOptionHTML += optionsHTML += '
';
		// And add out styled select just after the real select
		realSelect.insertAdjacentHTML('afterend', styleSelectHTML);

		var styledSelect = query('.style-select[data-ss-uuid="'+uuid+'"]');
		var styleSelectOptions = styledSelect.querySelectorAll('.ss-option');
		var selectedOption = styledSelect.querySelector('.ss-selected-option');

		var changeRealSelectBox = function(newValue, newLabel) {
			// Close styledSelect
			styledSelect.classList.remove('open');

			// Update styled value
			selectedOption.textContent = newLabel;
			selectedOption.dataset.value = newValue;

			// Update the 'tick' that shows the option with the current value
			styleSelectOptions.forEach(function(styleSelectOption){
				if ( styleSelectOption.dataset.value === newValue) {
					styleSelectOption.classList.add('ticked')
				} else {
					styleSelectOption.classList.remove('ticked')
				}
			});

			// Update real select box
			realSelect.value = newValue;

			// Send 'change' event to real select - to trigger any change event listeners
			var changeEvent = new CustomEvent('change');
			realSelect.dispatchEvent(changeEvent);
		};

		// Change real select box when a styled option is clicked
		styleSelectOptions.forEach(function(unused, index){

			var styleSelectOption = styleSelectOptions.item(index);
			styleSelectOption.addEventListener('click', function(ev) {
				var target = ev.target,
					styledSelectBox = target.parentNode.parentNode,
					uuid = styledSelectBox.getAttribute('data-ss-uuid'),
					newValue = target.getAttribute('data-value'),
					newLabel = target.textContent;

				changeRealSelectBox(newValue, newLabel)

			});

			// Tick and highlight the option that's currently in use
			if ( styleSelectOption.dataset.value === realSelect.value ) {
				highlightedOptionIndex = index;
				styleSelectOption.classList.add('ticked');
				styleSelectOption.classList.add('highlighted')
			}

			// Important: we can't use ':hover' as the keyboard and default value can also set the highlight
			styleSelectOption.addEventListener('mouseover', function(ev){
				styleSelectOption.parentNode.childNodes.forEach(function(sibling, index){
					if ( sibling === ev.target ) {
						sibling.classList.add('highlighted');
						highlightedOptionIndex = index;
					} else {
						sibling.classList.remove('highlighted')
					}
				})
			})
		});

		var closeAllStyleSelects = function(exception){
			queryAll('.style-select').forEach(function(styleSelectEl) {
				if ( styleSelectEl !== exception ) {
					styleSelectEl.classList.remove('open');
				}
			});
		};

		var toggleStyledSelect = function(styledSelectBox){
			if ( ! styledSelectBox.classList.contains('open') ) {
				// If we're closed and about to open, close other style selects on the page
				closeAllStyleSelects(styledSelectBox);
			}
			// Then toggle open/close
			styledSelectBox.classList.toggle('open');
		};

		// When a styled select box is clicked
		var styledSelectedOption = query('.style-select[data-ss-uuid="' + uuid + '"] .ss-selected-option');
		styledSelectedOption.addEventListener('click', function(ev) {
			ev.preventDefault();
			ev.stopPropagation();
			toggleStyledSelect(ev.target.parentNode);
		});

		// Keyboard handling
		styledSelectedOption.addEventListener('keydown', function(ev) {
			var styledSelectBox = ev.target.parentNode;

			switch (ev.keyCode) {
				case KEYCODES.SPACE:
					// Space shows and hides styles select boxes
					toggleStyledSelect(styledSelectBox);
					break;
				case KEYCODES.DOWN:
				case KEYCODES.UP:
					// Move the highlight up and down
					if ( ! styledSelectBox.classList.contains('open') ) {
						// If style select is not open, up/down should open it.
						toggleStyledSelect(styledSelectBox);
					} else {
						// If style select is already open, these should change what the highlighted option is
						if ( ev.keyCode === KEYCODES.UP ) {
							// Up arrow moves earlier in list
							if ( highlightedOptionIndex !== 0 ) {
								highlightedOptionIndex = highlightedOptionIndex - 1
							}
						} else {
							// Down arrow moves later in list
							if ( highlightedOptionIndex < highlightedOptionIndexMax ) {
								highlightedOptionIndex = highlightedOptionIndex + 1
							}
						}
						styleSelectOptions.forEach(function(option, index){
							if ( index === highlightedOptionIndex ) {
								option.classList.add('highlighted')
							} else {
								option.classList.remove('highlighted')
							}
						})
					}
					ev.preventDefault();
					ev.stopPropagation();
					break;
				// User has picked an item from the keyboard
				case KEYCODES.ENTER:
					var highlightedOption = styledSelectedOption.parentNode.querySelectorAll('.ss-option')[highlightedOptionIndex],
						newValue = highlightedOption.dataset.value,
						newLabel = highlightedOption.textContent;

					changeRealSelectBox(newValue, newLabel);
					ev.preventDefault();
					ev.stopPropagation();
					break;
			}
		});

		// Clicking outside of the styled select box closes any open styled select boxes
		query('body').addEventListener('click', function(ev){

			if ( ! isAncestorOf(ev.target, '.style-select', true) ) {
				closeAllStyleSelects();
			}
		})

	};

// Close UMD module
}));

<|endoftext|>


<commit_before>module("Basic Tests");

function appendTestInputElem(name, value) {
    input = document.createElement("input");
    input.name = name;
    input.value = value;
    document.body.appendChild(input);
}

test("inputToUri", function() {
    appendTestInputElem('website', 'http');
    equal(inputToUri("website"), "website=http");
});
<commit_msg>js: fix test element creation<commit_after>module("Basic Tests");

function appendTestInputElem(name, value) {
    var fixture = document.getElementById("qunit-fixture");
    input = document.createElement("input");
    input.name = name;
    input.value = value;
    fixture.appendChild(input);
}

test("inputToUri", function() {
    appendTestInputElem('website', 'http');
    equal(inputToUri("website"), "website=http");
});
<|endoftext|>


<commit_before><commit_msg>Update validations.js<commit_after><|endoftext|>


<commit_before>import d3 from 'd3';
import { isEmpty } from 'ramda';
import map2tree from 'map2tree';
import deepmerge from 'deepmerge';
import { getTooltipString, toggleChildren, visit, getNodeGroupByDepthCount } from './utils';
import d3tooltip from 'd3tooltip';

const defaultOptions = {
  id: 'd3svg',
  style: {
    node: {
      colors: {
        'default': '#ccc',
        collapsed: 'lightsteelblue',
        parent: 'white'
      },
      radius: 5
    },
    text: {
      colors: {
        'default': 'black',
        hover: 'skyblue'
      }
    },
    link: {
      stroke: '#000',
      fill: 'none'
    }
  },
  size: 500,
  aspectRatio: 1.0,
  margin: {
    top: 10,
    right: 10,
    bottom: 10,
    left: 50
  },
  isSorted: false,
  heightBetweenNodesCoeff: 2,
  widthBetweenNodesCoeff: 1,
  transitionDuration: 750,
  state: undefined,
  rootKeyName: 'state',
  pushMethod: 'push',
  tree: undefined,
  tooltipOptions: {
    disabled: false,
    left: undefined,
    right: undefined,
    offset: {
      left: 0,
      top: 0
    },
    style: undefined
  }
};

export default function(DOMNode, options = {}) {
  const {
    id,
    style,
    size,
    aspectRatio,
    margin,
    isSorted,
    widthBetweenNodesCoeff,
    heightBetweenNodesCoeff,
    transitionDuration,
    state,
    rootKeyName,
    pushMethod,
    tree,
    tooltipOptions
    } = deepmerge(defaultOptions, options);

  const width = size - margin.left - margin.right;
  const height = size * aspectRatio - margin.top - margin.bottom;
  const fullWidth = size;
  const fullHeight = size * aspectRatio;

  const attr = {
    id,
    viewBox: `0 0 ${fullWidth} ${fullHeight}`,
    preserveAspectRatio: 'xMinYMin slice'
  };

  if (!style.width) {
    attr.width = fullWidth;
  }

  const root = d3.select(DOMNode);
  const vis = root
    .append('svg')
    .attr(attr)
    .style(style)
    .append('g')
    .attr({
      transform: `translate(${margin.left + style.node.radius}, ${margin.top})`
    });

  let layout = d3.layout.tree().size([width, height]);
  let data;

  if (isSorted) {
    layout.sort((a, b) => b.name.toLowerCase() < a.name.toLowerCase() ? 1 : -1);
  }

  return function renderChart(nextState = tree || state) {
    data = !tree ? map2tree(nextState, {key: rootKeyName, pushMethod}) : nextState;

    if (isEmpty(data) || !data.name) {
      data = { name: 'error', message: 'Please provide a state map or a tree structure'};
    }

    let nodeIndex = 0;
    let maxLabelLength = 0;

    visit(data,
        node => maxLabelLength = Math.max(node.name.length, maxLabelLength),
        node => node.children && node.children.length > 0 ? node.children : null
    );

    data.x0 = height / 2;
    data.y0 = 0;
    /*eslint-disable*/
    update(data);
    /*eslint-enable*/

    function update(source) {
      // path generator for links
      const diagonal = d3.svg.diagonal().projection(d => [d.y, d.x]);
      // set tree dimensions and spacing between branches and nodes
      const maxNodeCountByLevel = Math.max(...getNodeGroupByDepthCount(data));

      layout = layout.size([maxNodeCountByLevel * 25 * heightBetweenNodesCoeff, width]);

      let nodes = layout.nodes(data);
      let links = layout.links(nodes);

      nodes.forEach(node => node.y = node.depth * (maxLabelLength * 7 * widthBetweenNodesCoeff));

      // process the node selection
      let node = vis.selectAll('g.node').data(nodes, d => d.id || (d.id = ++nodeIndex));

      let nodeEnter = node.enter().append('g')
        .attr({
          'class': 'node',
          transform: d => `translate(${source.y0},${source.x0})`
        })
        .style({
          fill: style.text.colors.default,
          cursor: 'pointer'
        })
        .on({
          click: clickedNode => {
            if (d3.event.defaultPrevented) return;
            update(toggleChildren(clickedNode));
          },
          mouseover: function mouseover(d, i) {
            d3.select(this).style({
              fill: style.text.colors.hover
            });
          },
          mouseout: function mouseout(d, i) {
            d3.select(this).style({
              fill: style.text.colors.default
            });
          }
        });

      if (!tooltipOptions.disabled) {
        nodeEnter.call(d3tooltip(d3, 'tooltip', {...tooltipOptions, root})
          .text((d, i) => getTooltipString(d, i, tooltipOptions))
          .style(tooltipOptions.style)
        );
      }

      nodeEnter.append('circle')
        .attr({
          'class': 'nodeCircle'
        });

      nodeEnter.append('text')
        .attr({
          'class': 'nodeText',
          dy: '.35em'
        })
        .style({
          'fill-opacity': 0
        })
        .text(d => d.name);

      // update the text to reflect whether node has children or not
      node.select('text')
        .attr({
          x: d => d.children || d._children ? -(style.node.radius + 10) : style.node.radius + 10,
          'text-anchor': d => d.children || d._children ? 'end' : 'start'
        })
        .text(d => d.name);

      // change the circle fill depending on whether it has children and is collapsed
      node.select('circle.nodeCircle')
        .attr({
          r: style.node.radius
        })
        .style({
          stroke: 'black',
          'stroke-width': '1.5px',
          fill: d => d._children ? style.node.colors.collapsed : (d.children ? style.node.colors.parent : style.node.colors.default)
        });

      // transition nodes to their new position
      let nodeUpdate = node.transition()
        .duration(transitionDuration)
        .attr({
          transform: d => `translate(${d.y},${d.x})`
        });

      // fade the text in
      nodeUpdate.select('text')
        .style('fill-opacity', 1);

      // transition exiting nodes to the parent's new position
      let nodeExit = node.exit().transition()
        .duration(transitionDuration)
        .attr({
          transform: d => `translate(${source.y},${source.x})`
        })
        .remove();

      nodeExit.select('circle')
        .attr('r', 0);

      nodeExit.select('text')
        .style('fill-opacity', 0);

      // update the links
      let link = vis.selectAll('path.link')
        .data(links, d => d.target.id);

      // enter any new links at the parent's previous position
      link.enter().insert('path', 'g')
        .attr({
          'class': 'link',
          d: d => {
            let o = {
              x: source.x0,
              y: source.y0
            };
            return diagonal({
              source: o,
              target: o
            });
          }
        })
        .style(style.link);

      // transition links to their new position
      link.transition()
        .duration(transitionDuration)
        .attr({
          d: diagonal
        });

      // transition exiting nodes to the parent's new position
      link.exit().transition()
        .duration(transitionDuration)
        .attr({
          d: d => {
            let o = {
              x: source.x,
              y: source.y
            };
            return diagonal({
              source: o,
              target: o
            });
          }
        })
        .remove();

      // stash the old positions for transition
      nodes.forEach(d => {
        d.x0 = d.x;
        d.y0 = d.y;
      });
    }
  };
}
<commit_msg>Click on circle to collapse Add onClickText hook<commit_after>import d3 from 'd3';
import { isEmpty } from 'ramda';
import map2tree from 'map2tree';
import deepmerge from 'deepmerge';
import { getTooltipString, toggleChildren, visit, getNodeGroupByDepthCount } from './utils';
import d3tooltip from 'd3tooltip';

const defaultOptions = {
  id: 'd3svg',
  style: {
    node: {
      colors: {
        'default': '#ccc',
        collapsed: 'lightsteelblue',
        parent: 'white'
      },
      radius: 5
    },
    text: {
      colors: {
        'default': 'black',
        hover: 'skyblue'
      }
    },
    link: {
      stroke: '#000',
      fill: 'none'
    }
  },
  size: 500,
  aspectRatio: 1.0,
  margin: {
    top: 10,
    right: 10,
    bottom: 10,
    left: 50
  },
  isSorted: false,
  heightBetweenNodesCoeff: 2,
  widthBetweenNodesCoeff: 1,
  transitionDuration: 750,
  state: undefined,
  rootKeyName: 'state',
  pushMethod: 'push',
  tree: undefined,
  tooltipOptions: {
    disabled: false,
    left: undefined,
    right: undefined,
    offset: {
      left: 0,
      top: 0
    },
    style: undefined
  },
  onClickText: () => {}
};

export default function(DOMNode, options = {}) {
  const {
    id,
    style,
    size,
    aspectRatio,
    margin,
    isSorted,
    widthBetweenNodesCoeff,
    heightBetweenNodesCoeff,
    transitionDuration,
    state,
    rootKeyName,
    pushMethod,
    tree,
    tooltipOptions,
    onClickText
    } = deepmerge(defaultOptions, options);

  const width = size - margin.left - margin.right;
  const height = size * aspectRatio - margin.top - margin.bottom;
  const fullWidth = size;
  const fullHeight = size * aspectRatio;

  const attr = {
    id,
    viewBox: `0 0 ${fullWidth} ${fullHeight}`,
    preserveAspectRatio: 'xMinYMin slice'
  };

  if (!style.width) {
    attr.width = fullWidth;
  }

  const root = d3.select(DOMNode);
  const vis = root
    .append('svg')
    .attr(attr)
    .style(style)
    .append('g')
    .attr({
      transform: `translate(${margin.left + style.node.radius}, ${margin.top})`
    });

  let layout = d3.layout.tree().size([width, height]);
  let data;

  if (isSorted) {
    layout.sort((a, b) => b.name.toLowerCase() < a.name.toLowerCase() ? 1 : -1);
  }

  return function renderChart(nextState = tree || state) {
    data = !tree ? map2tree(nextState, {key: rootKeyName, pushMethod}) : nextState;

    if (isEmpty(data) || !data.name) {
      data = { name: 'error', message: 'Please provide a state map or a tree structure'};
    }

    let nodeIndex = 0;
    let maxLabelLength = 0;

    visit(data,
        node => maxLabelLength = Math.max(node.name.length, maxLabelLength),
        node => node.children && node.children.length > 0 ? node.children : null
    );

    data.x0 = height / 2;
    data.y0 = 0;
    /*eslint-disable*/
    update(data);
    /*eslint-enable*/

    function update(source) {
      // path generator for links
      const diagonal = d3.svg.diagonal().projection(d => [d.y, d.x]);
      // set tree dimensions and spacing between branches and nodes
      const maxNodeCountByLevel = Math.max(...getNodeGroupByDepthCount(data));

      layout = layout.size([maxNodeCountByLevel * 25 * heightBetweenNodesCoeff, width]);

      let nodes = layout.nodes(data);
      let links = layout.links(nodes);

      nodes.forEach(node => node.y = node.depth * (maxLabelLength * 7 * widthBetweenNodesCoeff));

      // process the node selection
      let node = vis.selectAll('g.node').data(nodes, d => d.id || (d.id = ++nodeIndex));

      let nodeEnter = node.enter().append('g')
        .attr({
          'class': 'node',
          transform: d => `translate(${source.y0},${source.x0})`
        })
        .style({
          fill: style.text.colors.default,
          cursor: 'pointer'
        })
        .on({
          mouseover: function mouseover(d, i) {
            d3.select(this).style({
              fill: style.text.colors.hover
            });
          },
          mouseout: function mouseout(d, i) {
            d3.select(this).style({
              fill: style.text.colors.default
            });
          }
        });

      if (!tooltipOptions.disabled) {
        nodeEnter.call(d3tooltip(d3, 'tooltip', {...tooltipOptions, root})
          .text((d, i) => getTooltipString(d, i, tooltipOptions))
          .style(tooltipOptions.style)
        );
      }

      nodeEnter.append('circle')
        .attr({
          'class': 'nodeCircle'
        })
        .on({
          click: clickedNode => {
            if (d3.event.defaultPrevented) return;
            update(toggleChildren(clickedNode));
          }
        });

      nodeEnter.append('text')
        .attr({
          'class': 'nodeText',
          dy: '.35em'
        })
        .style({
          'fill-opacity': 0
        })
        .text(d => d.name)
        .on({
          click: onClickText
        });

      // update the text to reflect whether node has children or not
      node.select('text')
        .attr({
          x: d => d.children || d._children ? -(style.node.radius + 10) : style.node.radius + 10,
          'text-anchor': d => d.children || d._children ? 'end' : 'start'
        })
        .text(d => d.name);

      // change the circle fill depending on whether it has children and is collapsed
      node.select('circle.nodeCircle')
        .attr({
          r: style.node.radius
        })
        .style({
          stroke: 'black',
          'stroke-width': '1.5px',
          fill: d => d._children ? style.node.colors.collapsed : (d.children ? style.node.colors.parent : style.node.colors.default)
        });

      // transition nodes to their new position
      let nodeUpdate = node.transition()
        .duration(transitionDuration)
        .attr({
          transform: d => `translate(${d.y},${d.x})`
        });

      // fade the text in
      nodeUpdate.select('text')
        .style('fill-opacity', 1);

      // transition exiting nodes to the parent's new position
      let nodeExit = node.exit().transition()
        .duration(transitionDuration)
        .attr({
          transform: d => `translate(${source.y},${source.x})`
        })
        .remove();

      nodeExit.select('circle')
        .attr('r', 0);

      nodeExit.select('text')
        .style('fill-opacity', 0);

      // update the links
      let link = vis.selectAll('path.link')
        .data(links, d => d.target.id);

      // enter any new links at the parent's previous position
      link.enter().insert('path', 'g')
        .attr({
          'class': 'link',
          d: d => {
            let o = {
              x: source.x0,
              y: source.y0
            };
            return diagonal({
              source: o,
              target: o
            });
          }
        })
        .style(style.link);

      // transition links to their new position
      link.transition()
        .duration(transitionDuration)
        .attr({
          d: diagonal
        });

      // transition exiting nodes to the parent's new position
      link.exit().transition()
        .duration(transitionDuration)
        .attr({
          d: d => {
            let o = {
              x: source.x,
              y: source.y
            };
            return diagonal({
              source: o,
              target: o
            });
          }
        })
        .remove();

      // stash the old positions for transition
      nodes.forEach(d => {
        d.x0 = d.x;
        d.y0 = d.y;
      });
    }
  };
}
<|endoftext|>


<commit_before>'use strict';

/**
 * MenuItem Class
 * @class
 * @example

  tinymce.activeEditor.addMenuItem(new MenuItem('myAction',{
    icon: 'text',
    text: 'My Action',
    onclick: function(){
     window.alert('overiden default onclick action');
    }
  }));

 */
function MenuItem(name,options){
  this.name = name;
  if (!this.id) {
    this.id =  'mce-plugin-headersfooters-' + name.camel2Dash();
  }
  for (var key in options) {
    this[key] = options[key];
  }
}

/**
 * Converts a camel cased string to a dashed string
 * @method
 * @extends String
 * @example
 *
    var s = 'camelCase';
    s.camel2Dash()
    // -> camel-case

 */
String.prototype.camel2Dash = function(){
  return this.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
};

/**
 * Returns the menu item UI control as a jquery object
 * @method
 * @returns {Array} the jquery object wrapped in jquery array of lenght 1
 * @example

    var menuElement = ui.menuItems.insertHeader.getUIControl();
    menuElement.css('color','red');

 */
MenuItem.prototype.getUIControl = function(){
  return $('#'+this.id);
};

/**
 * By default on click, the menu item logs on console it has been clicked and returns it to allow chainable behavior.
 * This method should be overriden after instanciation.
 * Be caution when ovveriding, please returns the menu item in the method to not break the chainable behavior allowed by the default implementation.
 * @method
 * @returns void
 */
MenuItem.prototype.onclick = function(){
  console.info('%s menu item has been clicked',this.name);
  return this;
};

/**
 * Show the menu item UI control and returns it to allow chainable behavior.
 * @method
 * @returns {MenuItem} the menu item
 */
MenuItem.prototype.show = function(){
  this.getUIControl().show();
  return this;
};

/**
 * Hide the menu item UI control and returns it to allow chainable behavior.
 * @method
 * @returns {MenuItem} the menu item
 */
MenuItem.prototype.hide = function(){
  this.getUIControl().hide();
  return this;
};

/**
 * Disable the menu item and returns it to allow chainable behavior.
 * @method
 * @returns {MenuItem} the menu item.
 */
MenuItem.prototype.disable = function(){
  this.getUIControl().addClass('mce-disabled');
  return this;
};

/**
 * Enable the menu item and returns it to allow chainable behavior.
 * @method
 * @returns {MenuItem} the menu item
 */
MenuItem.prototype.enable = function(){
  this.getUIControl().removeClass('mce-disabled');
  return this;
};

module.exports = MenuItem;
<commit_msg>add support for 'visible' and 'disabled' MenuItem constructor arguments<commit_after>'use strict';

/**
 * MenuItem Class
 * @class
 * @example

  tinymce.activeEditor.addMenuItem(new MenuItem('myAction',{
    icon: 'text',
    text: 'My Action',
    visible: true,
    disabled: true,
    onclick: function(){
     window.alert('overiden default onclick action');
    }
  }));

 */
function MenuItem(name,options){
  this.name = name;
  for (var key in options) {
    if (key !== 'visible' && key !== 'disabled') {
      this[key] = options[key];
    }
  }
  if (!options.id) {
    this.id =  'mce-plugin-headersfooters-' + name.camel2Dash();
  }
  if (options.visible === false) this.hide();
  if (options.disabled) this.disable();
}

/**
 * Converts a camel cased string to a dashed string
 * @method
 * @extends String
 * @example
 *
    var s = 'camelCase';
    s.camel2Dash()
    // -> camel-case

 */
String.prototype.camel2Dash = function(){
  return this.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
};

/**
 * Returns the menu item UI control as a jquery object
 * @method
 * @returns {Array} the jquery object wrapped in jquery array of lenght 1
 * @example

    var menuElement = ui.menuItems.insertHeader.getUIControl();
    menuElement.css('color','red');

 */
MenuItem.prototype.getUIControl = function(){
  return $('#'+this.id);
};

/**
 * By default on click, the menu item logs on console it has been clicked and returns it to allow chainable behavior.
 * This method should be overriden after instanciation.
 * Be caution when ovveriding, please returns the menu item in the method to not break the chainable behavior allowed by the default implementation.
 * @method
 * @returns void
 */
MenuItem.prototype.onclick = function(){
  console.info('%s menu item has been clicked',this.name);
  return this;
};

/**
 * Show the menu item UI control and returns it to allow chainable behavior.
 * @method
 * @returns {MenuItem} the menu item
 */
MenuItem.prototype.show = function(){
  this.getUIControl().show();
  return this;
};

/**
 * Hide the menu item UI control and returns it to allow chainable behavior.
 * @method
 * @returns {MenuItem} the menu item
 */
MenuItem.prototype.hide = function(){
  this.getUIControl().hide();
  return this;
};

/**
 * Disable the menu item and returns it to allow chainable behavior.
 * @method
 * @returns {MenuItem} the menu item.
 */
MenuItem.prototype.disable = function(){
  this.getUIControl().addClass('mce-disabled');
  return this;
};

/**
 * Enable the menu item and returns it to allow chainable behavior.
 * @method
 * @returns {MenuItem} the menu item
 */
MenuItem.prototype.enable = function(){
  this.getUIControl().removeClass('mce-disabled');
  return this;
};

module.exports = MenuItem;
<|endoftext|>


<commit_before>// This [jQuery](http://jquery.com/) plugin implements an `<iframe>`
// [transport](http://api.jquery.com/extending-ajax/#Transports) so that
// `$.ajax()` calls support the uploading of files using standard HTML file
// input fields. This is done by switching the exchange from `XMLHttpRequest` to
// a hidden `iframe` element containing a form that is submitted.

// The [source for the plugin](http://github.com/cmlenz/jquery-iframe-transport)
// is available on [Github](http://github.com/) and dual licensed under the MIT
// or GPL Version 2 licenses.

// ## Usage

// To use this plugin, you simply add a `iframe` option with the value `true`
// to the Ajax settings an `$.ajax()` call, and specify the file fields to
// include in the submssion using the `files` option, which can be a selector,
// jQuery object, or a list of DOM elements containing one or more
// `<input type="file">` elements:

//     $("#myform").submit(function() {
//         $.ajax(this.action, {
//             files: $(":file", this),
//             iframe: true
//         }).complete(function(data) {
//             console.log(data);
//         });
//     });

// The plugin will construct a hidden `<iframe>` element containing a copy of
// the form the file field belongs to, will disable any form fields not
// explicitly included, submit that form, and process the response.

// If you want to include other form fields in the form submission, include them
// in the `data` option, and set the `processData` option to `false`:

//     $("#myform").submit(function() {
//         $.ajax(this.action, {
//             data: $(":text", this).serializeArray(),
//             files: $(":file", this),
//             iframe: true,
//             processData: false
//         }).complete(function(data) {
//             console.log(data);
//         });
//     });

// ### The Server Side

// If the response is not HTML or XML, you (unfortunately) need to apply some
// trickery on the server side. To send back a JSON payload, send back an HTML
// `<textarea>` element with a `data-type` attribute that contains the MIME
// type, and put the actual payload in the textarea:

//     <textarea data-type="application/json">
//       {"ok": true, "message": "Thanks so much"}
//     </textarea>

// The iframe transport plugin will detect this and attempt to apply the same
// conversions that jQuery applies to regular responses. That means for the
// example above you should get a Javascript object as the `data` parameter of
// the `complete` callback, with the properties `ok: true` and
// `message: "Thanks so much"`.

// ### Compatibility

// This plugin has primarily been tested on Safari 5, Firefox 4, and Internet
// Explorer all the way back to version 6. While I haven't found any issues with
// it so far, I'm fairly sure it still doesn't work around all the quirks in all
// different browsers. But the code is still pretty simple overall, so you
// should be able to fix it and contribute a patch :)

// ## Annotated Source

(function($, undefined) {

  // Register a prefilter that checks whether the `iframe` option is set, and
  // switches to the iframe transport if it is `true`.
  $.ajaxPrefilter(function(options, origOptions, jqXHR) {
    if (options.iframe) {
      return "iframe";
    }
  });

  // Register an iframe transport, independent of requested data type. It will
  // only activate when the "files" option has been set to a non-empty list of
  // enabled file inputs.
  $.ajaxTransport("iframe", function(options, origOptions, jqXHR) {
    var form = null,
        iframe = null,
        origAction = null,
        origTarget = null,
        origEnctype = null,
        addedFields = [],
        disabledFields = [],
        files = $(options.files).filter(":file:enabled");

    // This function gets called after a successful submission or an abortion
    // and should revert all changes made to the page to enable the
    // submission via this transport.
    function cleanUp() {
      $(addedFields).remove();
      $(disabledFields).each(function() {
        this.disabled = false;
      });
      form.attr("action", origAction || "")
          .attr("target", origTarget || "")
          .attr("enctype", origEnctype || "");
      iframe.attr("src", "javascript:false;").remove();
    }

    // Remove "iframe" from the data types list so that further processing is
    // based on the content type returned by the server, without attempting an
    // (unsupported) conversion from "iframe" to the actual type.
    options.dataTypes.shift();

    if (files.length) {
      // Determine the form the file fields belong to, and make sure they all
      // actually belong to the same form.
      files.each(function() {
        if (form !== null && this.form !== form) {
          jQuery.error("All file fields must belong to the same form");
        }
        form = this.form;
      });
      form = $(form);

      // Store the original form attributes that we'll be replacing temporarily.
      origAction = form.attr("action");
      origTarget = form.attr("target");
      origEnctype = form.attr("enctype");

      // We need to disable all other inputs in the form so that they don't get
      // included in the submitted data unexpectedly.
      form.find(":input:not(:submit)").each(function() {
        if (!this.disabled && (this.type != "file" || files.index(this) < 0)) {
          this.disabled = true;
          disabledFields.push(this);
        }
      });

      // If there is any additional data specified via the `data` option,
      // we add it as hidden fields to the form. This (currently) requires
      // the `processData` option to be set to false so that the data doesn't
      // get serialized to a string.
      if (typeof(options.data) === "string" && options.data.length > 0) {
        jQuery.error("data must not be serialized");
      }
      $.each(options.data || {}, function(name, value) {
        if ($.isPlainObject(value)) {
          name = value.name;
          value = value.value;
        }
        addedFields.push($("<input type='hidden'>").attr("name", name)
          .attr("value", value).appendTo(form)[0]);
      });

      // Add a hidden `X-Requested-With` field with the value `IFrame` to the
      // field, to help server-side code to determine that the upload happened
      // through this transport.
      addedFields.push($("<input type='hidden' name='X-Requested-With'>")
        .attr("value", "IFrame").appendTo(form)[0]);

      return {

        // The `send` function is called by jQuery when the request should be
        // sent.
        send: function(headers, completeCallback) {
          iframe = $("");

          // The first load event gets fired after the iframe has been injected
          // into the DOM, and is used to prepare the actual submission.
          iframe.bind("load", function() {

            // The second load event gets fired when the response to the form
            // submission is received. The implementation detects whether the
            // actual payload is embedded in a `<textarea>` element, and
            // prepares the required conversions to be made in that case.
            iframe.unbind("load").bind("load", function() {
              var doc = this.contentWindow ? this.contentWindow.document :
                (this.contentDocument ? this.contentDocument : this.document),
                root = doc.documentElement ? doc.documentElement : doc.body,
                textarea = root.getElementsByTagName("textarea")[0],
                type = textarea ? textarea.getAttribute("data-type") : null;
              completeCallback(200, "OK", {
                text: type ? textarea.value : root ? root.innerHTML : null
              }, "Content-Type: " + (type || "text/html"));
              setTimeout(cleanUp, 50);
            });

            // Now that the load handler has been set up, reconfigure and
            // submit the form.
            form.attr("action", options.url)
              .attr("target", iframe.attr("name"))
              .attr("enctype", "multipart/form-data")
              .get(0).submit();
          });

          // After everything has been set up correctly, the iframe gets
          // injected into the DOM so that the submission can be initiated.
          iframe.insertAfter(form);
        },

        // The `abort` function is called by jQuery when the request should be
        // aborted.
        abort: function() {
          if (iframe !== null) {
            iframe.unbind("load").attr("src", "javascript:false;");
            cleanUp();
          }
        }

      };
    }
  });

})(jQuery);
<commit_msg>Fixed warnings of JavaScript Lint<commit_after>// This [jQuery](http://jquery.com/) plugin implements an `<iframe>`
// [transport](http://api.jquery.com/extending-ajax/#Transports) so that
// `$.ajax()` calls support the uploading of files using standard HTML file
// input fields. This is done by switching the exchange from `XMLHttpRequest` to
// a hidden `iframe` element containing a form that is submitted.

// The [source for the plugin](http://github.com/cmlenz/jquery-iframe-transport)
// is available on [Github](http://github.com/) and dual licensed under the MIT
// or GPL Version 2 licenses.

// ## Usage

// To use this plugin, you simply add a `iframe` option with the value `true`
// to the Ajax settings an `$.ajax()` call, and specify the file fields to
// include in the submssion using the `files` option, which can be a selector,
// jQuery object, or a list of DOM elements containing one or more
// `<input type="file">` elements:

//     $("#myform").submit(function() {
//         $.ajax(this.action, {
//             files: $(":file", this),
//             iframe: true
//         }).complete(function(data) {
//             console.log(data);
//         });
//     });

// The plugin will construct a hidden `<iframe>` element containing a copy of
// the form the file field belongs to, will disable any form fields not
// explicitly included, submit that form, and process the response.

// If you want to include other form fields in the form submission, include them
// in the `data` option, and set the `processData` option to `false`:

//     $("#myform").submit(function() {
//         $.ajax(this.action, {
//             data: $(":text", this).serializeArray(),
//             files: $(":file", this),
//             iframe: true,
//             processData: false
//         }).complete(function(data) {
//             console.log(data);
//         });
//     });

// ### The Server Side

// If the response is not HTML or XML, you (unfortunately) need to apply some
// trickery on the server side. To send back a JSON payload, send back an HTML
// `<textarea>` element with a `data-type` attribute that contains the MIME
// type, and put the actual payload in the textarea:

//     <textarea data-type="application/json">
//       {"ok": true, "message": "Thanks so much"}
//     </textarea>

// The iframe transport plugin will detect this and attempt to apply the same
// conversions that jQuery applies to regular responses. That means for the
// example above you should get a Javascript object as the `data` parameter of
// the `complete` callback, with the properties `ok: true` and
// `message: "Thanks so much"`.

// ### Compatibility

// This plugin has primarily been tested on Safari 5, Firefox 4, and Internet
// Explorer all the way back to version 6. While I haven't found any issues with
// it so far, I'm fairly sure it still doesn't work around all the quirks in all
// different browsers. But the code is still pretty simple overall, so you
// should be able to fix it and contribute a patch :)

// ## Annotated Source

(function($, undefined) {

  // Register a prefilter that checks whether the `iframe` option is set, and
  // switches to the iframe transport if it is `true`.
  $.ajaxPrefilter(function(options, origOptions, jqXHR) {
    if (options.iframe) {
      return "iframe";
    }
  });

  // Register an iframe transport, independent of requested data type. It will
  // only activate when the "files" option has been set to a non-empty list of
  // enabled file inputs.
  $.ajaxTransport("iframe", function(options, origOptions, jqXHR) {
    var form = null,
        iframe = null,
        origAction = null,
        origTarget = null,
        origEnctype = null,
        addedFields = [],
        disabledFields = [],
        files = $(options.files).filter(":file:enabled");

    // This function gets called after a successful submission or an abortion
    // and should revert all changes made to the page to enable the
    // submission via this transport.
    function cleanUp() {
      $(addedFields).remove();
      $(disabledFields).each(function() {
        this.disabled = false;
      });
      form.attr({
        "action":  origAction  || "",
        "target":  origTarget  || "",
        "enctype": origEnctype || ""
      });
      iframe.attr("src", "javascript:false;").remove();
    }

    // Remove "iframe" from the data types list so that further processing is
    // based on the content type returned by the server, without attempting an
    // (unsupported) conversion from "iframe" to the actual type.
    options.dataTypes.shift();

    if (files.length) {
      // Determine the form the file fields belong to, and make sure they all
      // actually belong to the same form.
      files.each(function() {
        if (form !== null && this.form !== form) {
          jQuery.error("All file fields must belong to the same form");
        }
        form = this.form;
      });
      form = $(form);

      // Store the original form attributes that we'll be replacing temporarily.
      origAction = form.attr("action");
      origTarget = form.attr("target");
      origEnctype = form.attr("enctype");

      // We need to disable all other inputs in the form so that they don't get
      // included in the submitted data unexpectedly.
      form.find(":input:not(:submit)").each(function() {
        if (!this.disabled && (this.type != "file" || files.index(this) < 0)) {
          this.disabled = true;
          disabledFields.push(this);
        }
      });

      // If there is any additional data specified via the `data` option,
      // we add it as hidden fields to the form. This (currently) requires
      // the `processData` option to be set to false so that the data doesn't
      // get serialized to a string.
      if (typeof(options.data) === "string" && options.data.length > 0) {
        jQuery.error("data must not be serialized");
      }
      $.each(options.data || {}, function(name, value) {
        if ($.isPlainObject(value)) {
          name = value.name;
          value = value.value;
        }
        addedFields.push($("<input type='hidden'>").attr({
          "name":  name,
          "value": value
        }).appendTo(form)[0]);
      });

      // Add a hidden `X-Requested-With` field with the value `IFrame` to the
      // field, to help server-side code to determine that the upload happened
      // through this transport.
      addedFields.push($("<input type='hidden' name='X-Requested-With'>").
        attr("value", "IFrame").appendTo(form)[0]);

      return {

        // The `send` function is called by jQuery when the request should be
        // sent.
        send: function(headers, completeCallback) {
          iframe = $("");

          // The first load event gets fired after the iframe has been injected
          // into the DOM, and is used to prepare the actual submission.
          iframe.bind("load", function() {

            // The second load event gets fired when the response to the form
            // submission is received. The implementation detects whether the
            // actual payload is embedded in a `<textarea>` element, and
            // prepares the required conversions to be made in that case.
            iframe.unbind("load").bind("load", function() {
              var doc = this.contentWindow ? this.contentWindow.document :
                (this.contentDocument ? this.contentDocument : this.document),
                root = doc.documentElement ? doc.documentElement : doc.body,
                textarea = root.getElementsByTagName("textarea")[0],
                type = textarea ? textarea.getAttribute("data-type") : null;
              completeCallback(200, "OK", {
                text: type ? textarea.value : root ? root.innerHTML : null
              }, "Content-Type: " + (type || "text/html"));
              setTimeout(cleanUp, 50);
            });

            // Now that the load handler has been set up, reconfigure and
            // submit the form.
            form.attr({
              "action":  options.url,
              "target":  iframe.attr("name"),
              "enctype": "multipart/form-data"
            }).get(0).submit();
          });

          // After everything has been set up correctly, the iframe gets
          // injected into the DOM so that the submission can be initiated.
          iframe.insertAfter(form);
        },

        // The `abort` function is called by jQuery when the request should be
        // aborted.
        abort: function() {
          if (iframe !== null) {
            iframe.unbind("load").attr("src", "javascript:false;");
            cleanUp();
          }
        }

      };
    }
  });

})(jQuery);
<|endoftext|>


<commit_before>/* global localStorage document screen window */
const SIZE_REFERENCE = 60;

class Converter {
  constructor (screenSize) {
    this.screenSize = screenSize;
    this.scalingFactor = getScalingFactor(screenSize);
  }

  toDevicePixel (x) {
    // It's Very important to round the result!!!
    return Math.floor(x / this.scalingFactor);
  }

  toAbsPixel (x) {
    // It's Very important to round the result!!!
    return Math.floor(x * this.scalingFactor);
  }

  convertClickPos (transform, evt) {
    const event = {
      position: {
        x: this.toAbsPixel(evt.clientX) + transform.x,
        y: this.toAbsPixel(evt.clientY) + transform.y,
      },
      originalEvent: evt,
    };

    return event;
  }

  convertTouchPos (transform, evt) {
    const event = {
      position: [],
      originalEvent: evt,
    };

    for (let i = 0; i < evt.changedTouches.length; i++) {
      const currTouched = evt.changedTouches[i];

      event.position.push({
        x: this.toAbsPixel(currTouched.clientX) + transform.x,
        y: this.toAbsPixel(currTouched.clientY) + transform.y,
      });
    }

    return event;
  }
}

function getScalingFactor (screenSize) {
  const diagonalPixel = Math.sqrt(Math.pow(screen.height, 2) + Math.pow(screen.width, 2));
  const diagonalScreenCM = screenSize * 2.54;
  const pixelPerCentimeter = diagonalPixel / diagonalScreenCM;

  return SIZE_REFERENCE / pixelPerCentimeter;
}

export default Converter;
<commit_msg>Remove rounding<commit_after>/* global localStorage document screen window */
const SIZE_REFERENCE = 60;

class Converter {
  constructor (screenSize) {
    this.screenSize = screenSize;
    this.scalingFactor = getScalingFactor(screenSize);
  }

  toDevicePixel (x) {
    return x / this.scalingFactor;
  }

  toAbsPixel (x) {
    return x * this.scalingFactor;
  }

  convertClickPos (transform, evt) {
    const event = {
      position: {
        x: this.toAbsPixel(evt.clientX) + transform.x,
        y: this.toAbsPixel(evt.clientY) + transform.y,
      },
      originalEvent: evt,
    };

    return event;
  }

  convertTouchPos (transform, evt) {
    const event = {
      position: [],
      originalEvent: evt,
    };

    for (let i = 0; i < evt.changedTouches.length; i++) {
      const currTouched = evt.changedTouches[i];

      event.position.push({
        x: this.toAbsPixel(currTouched.clientX) + transform.x,
        y: this.toAbsPixel(currTouched.clientY) + transform.y,
      });
    }

    return event;
  }
}

function getScalingFactor (screenSize) {
  const diagonalPixel = Math.sqrt(Math.pow(screen.height, 2) + Math.pow(screen.width, 2));
  const diagonalScreenCM = screenSize * 2.54;
  const pixelPerCentimeter = diagonalPixel / diagonalScreenCM;

  return SIZE_REFERENCE / pixelPerCentimeter;
}

export default Converter;
<|endoftext|>


<commit_before>/**
 * jQuery serializeObject
 * @copyright 2014, macek <paulmacek@gmail.com>
 * @link https://github.com/macek/jquery-serialize-object
 * @license BSD
 * @version 2.0.1
 */
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
var FormSerializer = module.exports = function FormSerializer(helper) {
  this._helper    = helper;
  this._object    = {};
  this._pushes    = {};
  this._patterns  = {
    validate: /^[a-z][a-z0-9_]*(?:\[(?:\d*|[a-z0-9_]+)\])*$/i,
    key:      /[a-z0-9_]+|(?=\[\])/gi,
    push:     /^$/,
    fixed:    /^\d+$/,
    named:    /^[a-z0-9_]+$/i
  };
};

FormSerializer.prototype._build = function _build(base, key, value) {
  base[key] = value;
  return base;
};

FormSerializer.prototype._makeObject = function _nest(root, value) {

  var keys = root.match(this._patterns.key), k;

  // nest, nest, ..., nest
  while ((k = keys.pop()) !== undefined) {
    // foo[]
    if (this._patterns.push.test(k)) {
      var idx = this._incrementPush(root.replace(/\[\]$/, ''));
      value = this._build([], idx, value);
    }

    // foo[n]
    else if (this._patterns.fixed.test(k)) {
      value = this._build([], k, value);
    }

    // foo; foo[bar]
    else if (this._patterns.named.test(k)) {
      value = this._build({}, k, value);
    }
  }

  return value;
};

FormSerializer.prototype._incrementPush = function _incrementPush(key) {
  if (this._pushes[key] === undefined) {
    this._pushes[key] = 0;
  }
  return this._pushes[key]++;
};

FormSerializer.prototype.addPair = function addPair(pair) {
  if (!this._patterns.validate.test(pair.name)) return this;
  var obj = this._makeObject(pair.name, pair.value);
  this._object = this._helper.extend(true, this._object, obj);
  return this;
};

FormSerializer.prototype.addPairs = function addPairs(pairs) {
  if (!this._helper.isArray(pairs)) {
    throw new Error("formSerializer.addPairs expects an Array");
  }
  for (var i=0, len=pairs.length; i<len; i++) {
    this.addPair(pairs[i]);
  }
  return this;
};

FormSerializer.prototype.serialize = function serialize() {
  return this._object
};

FormSerializer.prototype.serializeJSON = function serializeJSON() {
  return JSON.stringify(this.serialize());
};

},{}],2:[function(require,module,exports){
var Helper = module.exports = function Helper(jQuery) {

  // jQuery.extend requirement
  if (typeof jQuery.extend === 'function') {
    this.extend = jQuery.extend;
  }
  else {
    throw new Error("jQuery is required to use jquery-serialize-object");
  }

  // Array.isArray polyfill
  if(typeof Array.isArray === 'function') {
    this.isArray = Array.isArray;
  }
  else {
    this.isArray = function isArray(input) {
      return Object.prototype.toString.call(input) === "[object Array]";
    };
  }

};

},{}],3:[function(require,module,exports){
var FormSerializer = require("./form-serializer"),
    Helper         = require("./helper");

(function($) {
  var helper = new Helper($ || {});

  $.fn.serializeObject = function() {

    var form = $(this);

    if (form.length > 1) {
      return new Error("jquery-serialize-object can only serialize one form at a time");
    }

    return new FormSerializer(helper).
      addPairs(form.serializeArray()).
      serialize();
  };

})(jQuery);

},{"./form-serializer":1,"./helper":2}]},{},[3])<commit_msg>Restructure plugin using Backbone-style require<commit_after>/**
 * jQuery serializeObject
 * @copyright 2014, macek <paulmacek@gmail.com>
 * @link https://github.com/macek/jquery-serialize-object
 * @license BSD
 * @version 2.0.1
 */
(function(root, factory) {

  // AMD
  if (typeof define === "function" && define.amd) {
    define(["jquery", "exports"], function($, exports) {
      factory(root, exports, $);
    });
  }

  // CommonJS
  else if (typeof exports !== "undefined") {
    var $ = require("jquery");
    factory(root, exports, $);
  }

  // Browser
  else {
    root.FormSerializer = factory(root, {}, (root.jQuery || root.Zepto || root.ender || root.$));
  }

}(this, function(root, exports, $) {

  var FormSerializer = exports.FormSerializer = function FormSerializer(helper) {
    this._helper    = helper;
    this._object    = {};
    this._pushes    = {};
    this._patterns  = {
      validate: /^[a-z][a-z0-9_]*(?:\[(?:\d*|[a-z0-9_]+)\])*$/i,
      key:      /[a-z0-9_]+|(?=\[\])/gi,
      push:     /^$/,
      fixed:    /^\d+$/,
      named:    /^[a-z0-9_]+$/i
    };
  };

  FormSerializer.prototype._build = function _build(base, key, value) {
    base[key] = value;
    return base;
  };

  FormSerializer.prototype._makeObject = function _nest(root, value) {

    var keys = root.match(this._patterns.key), k;

    // nest, nest, ..., nest
    while ((k = keys.pop()) !== undefined) {
      // foo[]
      if (this._patterns.push.test(k)) {
        var idx = this._incrementPush(root.replace(/\[\]$/, ''));
        value = this._build([], idx, value);
      }

      // foo[n]
      else if (this._patterns.fixed.test(k)) {
        value = this._build([], k, value);
      }

      // foo; foo[bar]
      else if (this._patterns.named.test(k)) {
        value = this._build({}, k, value);
      }
    }

    return value;
  };

  FormSerializer.prototype._incrementPush = function _incrementPush(key) {
    if (this._pushes[key] === undefined) {
      this._pushes[key] = 0;
    }
    return this._pushes[key]++;
  };

  FormSerializer.prototype.addPair = function addPair(pair) {
    if (!this._patterns.validate.test(pair.name)) return this;
    var obj = this._makeObject(pair.name, pair.value);
    this._object = this._helper.extend(true, this._object, obj);
    return this;
  };

  FormSerializer.prototype.addPairs = function addPairs(pairs) {
    if (!this._helper.isArray(pairs)) {
      throw new Error("formSerializer.addPairs expects an Array");
    }
    for (var i=0, len=pairs.length; i<len; i++) {
      this.addPair(pairs[i]);
    }
    return this;
  };

  FormSerializer.prototype.serialize = function serialize() {
    return this._object
  };

  FormSerializer.prototype.serializeJSON = function serializeJSON() {
    return JSON.stringify(this.serialize());
  };

  FormSerializer.serializeObject = function serializeObject() {
    if (this.length > 1) {
      return new Error("jquery-serialize-object can only serialize one form at a time");
    }

    return new FormSerializer($).
      addPairs(this.serializeArray()).
      serialize();
  };

  if (typeof $.fn !== "undefined") {
    $.fn.serializeObject = FormSerializer.serializeObject;
  }

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


<commit_before>'use strict';

const AWS = require('aws-sdk');
const im = require('imagemagick');
const fs = require('fs');
const S3 = new AWS.S3();

const TMP_INPUT_FILE_PATH = '/tmp/inputFile';
const TMP_OUTPUT_FILE_PATH = '/tmp/converted_tmpfile';

const buildS3Params = (filename) => {
  const originPrefix = process.env.ORIGIN_PREFIX.replace(/^\//, '').replace(/\/$/, '') + '/';
  return {
    Bucket: process.env.BUCKET_NAME,
    Key: originPrefix + filename
  };
};

const parseParams = (params, convertParams) => {
  let size, crop, gravity = false;
  for (var i = 0; i < params.length; i++) {
    if (!size) {
      const match = params[i].match(/^\d*%?x?\d*%?[\^!<>@]?$/i);
      if (match) {
        convertParams.push('-resize');
        convertParams.push(match[0]);
        size = true;
        continue;
      }
    }
    if (!crop) {
      const match = params[i].match(/^crop(\d+x\d+\+\d+\+\d+)$/i);
      if (match) {
        convertParams.push('-crop');
        convertParams.push(match[1]);
        convertParams.push('+repage');
        crop = true;
        continue;
      }
    }
    if (!gravity) {
      const match = params[i].match(/^(?:NorthWest|North|NorthEast|West|Center|East|SouthWest|South|SouthEast)$/i);
      if (match) {
        convertParams.push('-gravity');
        convertParams.push(match[0]);
        gravity = true;
        continue;
      }
    }
  }
  return convertParams;
};

const buildConvertParams = (event) => {
  let convertParams = [];

  convertParams.push(TMP_INPUT_FILE_PATH);
  convertParams.push('-auto-orient');
  const params = decodeURIComponent(event.pathParameters.parameter).split('-');
  convertParams = parseParams(params, convertParams);
  convertParams.push(TMP_OUTPUT_FILE_PATH);

  console.log(convertParams);

  return convertParams;
};

const convert = (event, callback) => {
  const S3Params = buildS3Params(event.pathParameters.filename);
  S3.getObject(S3Params, (err, data) => {
    if (err) { callback(err, err.stack); }

    fs.writeFile(TMP_INPUT_FILE_PATH, new Buffer(data.Body, 'binary'), (err) => {
      if (err) { callback(err, err.stack); }

      const convertParams = buildConvertParams(event);
      const proc = im.convert(convertParams, (err, stdout, stderr) => {
        if (err) { callback(err, err.stack); }

        fs.readFile(TMP_OUTPUT_FILE_PATH, (err, convertedData) => {
          if (err) { callback(err, convertedData); }

          callback(null, {
            "isBase64Encoded": true,
            "statusCode": 200,
            "headers": { "Content-Type": data.ContentType },
            "body": new Buffer(convertedData, 'binary').toString('base64')
          });
        });
      });
    });
  });
};

exports.handler = (event, context, callback) => {
  convert(event, callback);
};
<commit_msg>Remove unused const<commit_after>'use strict';

const AWS = require('aws-sdk');
const im = require('imagemagick');
const fs = require('fs');
const S3 = new AWS.S3();

const TMP_INPUT_FILE_PATH = '/tmp/inputFile';
const TMP_OUTPUT_FILE_PATH = '/tmp/converted_tmpfile';

const buildS3Params = (filename) => {
  const originPrefix = process.env.ORIGIN_PREFIX.replace(/^\//, '').replace(/\/$/, '') + '/';
  return {
    Bucket: process.env.BUCKET_NAME,
    Key: originPrefix + filename
  };
};

const parseParams = (params, convertParams) => {
  let size, crop, gravity = false;
  for (var i = 0; i < params.length; i++) {
    if (!size) {
      const match = params[i].match(/^\d*%?x?\d*%?[\^!<>@]?$/i);
      if (match) {
        convertParams.push('-resize');
        convertParams.push(match[0]);
        size = true;
        continue;
      }
    }
    if (!crop) {
      const match = params[i].match(/^crop(\d+x\d+\+\d+\+\d+)$/i);
      if (match) {
        convertParams.push('-crop');
        convertParams.push(match[1]);
        convertParams.push('+repage');
        crop = true;
        continue;
      }
    }
    if (!gravity) {
      const match = params[i].match(/^(?:NorthWest|North|NorthEast|West|Center|East|SouthWest|South|SouthEast)$/i);
      if (match) {
        convertParams.push('-gravity');
        convertParams.push(match[0]);
        gravity = true;
        continue;
      }
    }
  }
  return convertParams;
};

const buildConvertParams = (event) => {
  let convertParams = [];

  convertParams.push(TMP_INPUT_FILE_PATH);
  convertParams.push('-auto-orient');
  const params = decodeURIComponent(event.pathParameters.parameter).split('-');
  convertParams = parseParams(params, convertParams);
  convertParams.push(TMP_OUTPUT_FILE_PATH);

  console.log(convertParams);

  return convertParams;
};

const convert = (event, callback) => {
  const S3Params = buildS3Params(event.pathParameters.filename);
  S3.getObject(S3Params, (err, data) => {
    if (err) { callback(err, err.stack); }

    fs.writeFile(TMP_INPUT_FILE_PATH, new Buffer(data.Body, 'binary'), (err) => {
      if (err) { callback(err, err.stack); }

      const convertParams = buildConvertParams(event);
      im.convert(convertParams, (err, stdout, stderr) => {
        if (err) { callback(err, err.stack); }

        fs.readFile(TMP_OUTPUT_FILE_PATH, (err, convertedData) => {
          if (err) { callback(err, convertedData); }

          callback(null, {
            "isBase64Encoded": true,
            "statusCode": 200,
            "headers": { "Content-Type": data.ContentType },
            "body": new Buffer(convertedData, 'binary').toString('base64')
          });
        });
      });
    });
  });
};

exports.handler = (event, context, callback) => {
  convert(event, callback);
};
<|endoftext|>


<commit_before>const {Emitter} = require('event-kit')
const Point = require('./point')
const NOOP = function () {}

module.exports =
class MarkerTextDecorationLayer {
  constructor (markerLayer, {boundariesPerQuery, classNameForMarkerId, inlineStyleForMarkerId}) {
    this.markerLayer = markerLayer
    this.markerLayer.registerMarkerTextDecorationLayer(this)
    this.emitter = new Emitter()
    this.invalidatedRanges = []
    this.boundariesPerQuery = boundariesPerQuery || 50
    this.classNameForScopeId = classNameForMarkerId || NOOP
    this.inlineStyleForScopeId = inlineStyleForMarkerId || NOOP
  }

  destroy () {
    this.markerLayer.unregisterMarkerTextDecorationLayer(this)
  }

  buildIterator () {
    return new MarkerTextDecorationLayerIterator(this)
  }

  didInvalidatedMarkersInRanges (ranges) {
    this.invalidatedRanges.push(...ranges)
  }

  clearInvalidatedRanges () {
    this.invalidatedRanges = []
  }

  getInvalidatedRanges () {
    return this.invalidatedRanges
  }

  onDidInvalidateRange (fn) {
    return this.emitter.on('did-invalidate-range', fn)
  }

  didCreateMarker (range) {
    this.emitter.emit('did-invalidate-range', range)
  }

  didMoveMarker (oldRange, newRange) {
    this.didDestroyMarker(oldRange)
    this.didCreateMarker(newRange)
  }

  didDestroyMarker (range) {
    this.emitter.emit('did-invalidate-range', range)
  }
}

class MarkerTextDecorationLayerIterator {
  constructor (layer) {
    this.layer = layer
    this.openScopeIds = []
    this.closeScopeIds = []
  }

  seek (start) {
    const {containingStart, boundaries} = this.layer.markerLayer.index.findBoundariesAfter(start, this.layer.boundariesPerQuery)
    this.boundaries = boundaries
    for (let i = 0; i < containingStart.length; i++) {
      const marker = this.layer.markerLayer.getMarker(containingStart[i])
      if (!marker.isValid() || !this.hasInlineStyleOrClassName(marker.id)) {
        containingStart.splice(i, 1)
        i--
      }
    }

    this.boundaryIndex = 0
    this.computeScopeIds()
    if (this.openScopeIds.length === 0 && this.closeScopeIds.length === 0) {
      this.moveToSuccessor()
    }
    return containingStart
  }

  moveToSuccessor () {
    if (this.boundaryIndex === this.boundaries.length) return

    // Fetch more marker boundaries if needed
    if (this.boundaryIndex === this.boundaries.length - 1) {
      const {row, column} = this.getPosition()
      const {boundaries} = this.layer.markerLayer.index.findBoundariesAfter(Point(row, column + 1), this.layer.boundariesPerQuery)
      this.boundaries.push(...boundaries)
    }

    do {
      this.boundaryIndex++
      this.computeScopeIds()
    } while (this.openScopeIds.length === 0 && this.closeScopeIds.length === 0 && this.boundaryIndex < this.boundaries.length)
  }

  getPosition () {
    const boundary = this.boundaries[this.boundaryIndex]
    return boundary ? Point.fromObject(boundary.position) : Point.INFINITY
  }

  getCloseScopeIds () {
    return this.closeScopeIds
  }

  getOpenScopeIds () {
    return this.openScopeIds
  }

  computeScopeIds () {
    const boundary = this.boundaries[this.boundaryIndex]
    this.openScopeIds.length = 0
    this.closeScopeIds.length = 0

    if (boundary) {
      boundary.starting.forEach((markerId) => {
        const marker = this.layer.markerLayer.getMarker(markerId)
        if (!boundary.ending.has(markerId) && marker.isValid() && this.hasInlineStyleOrClassName(markerId)) {
          this.openScopeIds.push(markerId)
        }
      })

      boundary.ending.forEach((markerId) => {
        const marker = this.layer.markerLayer.getMarker(markerId)
        if (!boundary.starting.has(markerId) && marker.isValid() && this.hasInlineStyleOrClassName(markerId)) {
          this.closeScopeIds.push(markerId)
        }
      })
    }
  }

  hasInlineStyleOrClassName (scopeId) {
    return (
      this.layer.classNameForScopeId(scopeId) ||
      this.layer.inlineStyleForScopeId(scopeId)
    )
  }
}
<commit_msg>Clear previous boundaries when buffering a new marker window<commit_after>const {Emitter} = require('event-kit')
const Point = require('./point')
const NOOP = function () {}

module.exports =
class MarkerTextDecorationLayer {
  constructor (markerLayer, {boundariesPerQuery, classNameForMarkerId, inlineStyleForMarkerId}) {
    this.markerLayer = markerLayer
    this.markerLayer.registerMarkerTextDecorationLayer(this)
    this.emitter = new Emitter()
    this.invalidatedRanges = []
    this.boundariesPerQuery = boundariesPerQuery || 50
    this.classNameForScopeId = classNameForMarkerId || NOOP
    this.inlineStyleForScopeId = inlineStyleForMarkerId || NOOP
  }

  destroy () {
    this.markerLayer.unregisterMarkerTextDecorationLayer(this)
  }

  buildIterator () {
    return new MarkerTextDecorationLayerIterator(this)
  }

  didInvalidatedMarkersInRanges (ranges) {
    this.invalidatedRanges.push(...ranges)
  }

  clearInvalidatedRanges () {
    this.invalidatedRanges = []
  }

  getInvalidatedRanges () {
    return this.invalidatedRanges
  }

  onDidInvalidateRange (fn) {
    return this.emitter.on('did-invalidate-range', fn)
  }

  didCreateMarker (range) {
    this.emitter.emit('did-invalidate-range', range)
  }

  didMoveMarker (oldRange, newRange) {
    this.didDestroyMarker(oldRange)
    this.didCreateMarker(newRange)
  }

  didDestroyMarker (range) {
    this.emitter.emit('did-invalidate-range', range)
  }
}

class MarkerTextDecorationLayerIterator {
  constructor (layer) {
    this.layer = layer
    this.openScopeIds = []
    this.closeScopeIds = []
  }

  seek (start) {
    const {containingStart, boundaries} = this.layer.markerLayer.index.findBoundariesAfter(start, this.layer.boundariesPerQuery)
    this.boundaries = boundaries
    for (let i = 0; i < containingStart.length; i++) {
      const marker = this.layer.markerLayer.getMarker(containingStart[i])
      if (!marker.isValid() || !this.hasInlineStyleOrClassName(marker.id)) {
        containingStart.splice(i, 1)
        i--
      }
    }

    this.boundaryIndex = 0
    this.computeScopeIds()
    if (this.openScopeIds.length === 0 && this.closeScopeIds.length === 0) {
      this.moveToSuccessor()
    }
    return containingStart
  }

  moveToSuccessor () {
    if (this.boundaryIndex === this.boundaries.length) return

    // Fetch more marker boundaries if needed
    if (this.boundaryIndex === this.boundaries.length - 1) {
      const {row, column} = this.getPosition()
      const {boundaries} = this.layer.markerLayer.index.findBoundariesAfter(
        Point(row, column + 1),
        this.layer.boundariesPerQuery
      )
      this.boundaries = boundaries
      this.boundaryIndex = -1
    }

    do {
      this.boundaryIndex++
      this.computeScopeIds()
    } while (this.openScopeIds.length === 0 && this.closeScopeIds.length === 0 && this.boundaryIndex < this.boundaries.length)
  }

  getPosition () {
    const boundary = this.boundaries[this.boundaryIndex]
    return boundary ? Point.fromObject(boundary.position) : Point.INFINITY
  }

  getCloseScopeIds () {
    return this.closeScopeIds
  }

  getOpenScopeIds () {
    return this.openScopeIds
  }

  computeScopeIds () {
    const boundary = this.boundaries[this.boundaryIndex]
    this.openScopeIds.length = 0
    this.closeScopeIds.length = 0

    if (boundary) {
      boundary.starting.forEach((markerId) => {
        const marker = this.layer.markerLayer.getMarker(markerId)
        if (!boundary.ending.has(markerId) && marker.isValid() && this.hasInlineStyleOrClassName(markerId)) {
          this.openScopeIds.push(markerId)
        }
      })

      boundary.ending.forEach((markerId) => {
        const marker = this.layer.markerLayer.getMarker(markerId)
        if (!boundary.starting.has(markerId) && marker.isValid() && this.hasInlineStyleOrClassName(markerId)) {
          this.closeScopeIds.push(markerId)
        }
      })
    }
  }

  hasInlineStyleOrClassName (scopeId) {
    return (
      this.layer.classNameForScopeId(scopeId) ||
      this.layer.inlineStyleForScopeId(scopeId)
    )
  }
}
<|endoftext|>