<commit_before>/* exported ampInvalidUrlPostEditScreen */

var ampInvalidUrlPostEditScreen = ( function() { // eslint-disable-line no-unused-vars
	var component;

	component = {
		data: {
			l10n: {
				unsaved_changes: '',
				showing_number_errors: '',
				page_heading: '',
				show_all: ''
			}
		}
	};

	/**
	 * The id for the 'Showing x of y errors' notice.
	 *
	 * @var {string}
	 */
	component.idNumberErrors = 'number-errors';

	/**
	 * The id for the 'Show all' button.
	 *
	 * @var {string}
	 */
	component.showAllId = 'show-all-errors';

	/**
	 * Boot.
	 *
	 * @param {Object} data Data.
	 * @param {Object} data.l10n Translations.
	 */
	component.boot = function boot( data ) {
		Object.assign( component.data, data );
		component.handleShowAll();
		component.handleFiltering();
		component.handleSearching();
		component.handleStatusChange();
		component.handleBulkActions();
		component.changeHeading();
		component.watchForUnsavedChanges();
	};

	/**
	 * Add prompt when leaving page due to unsaved changes.
	 */
	component.addBeforeUnloadPrompt = function addBeforeUnloadPrompt() {
		if ( component.beforeUnloadPromptAdded ) {
			return;
		}
		window.addEventListener( 'beforeunload', component.onBeforeUnload );

		// Remove prompt when clicking trash or update.
		document.querySelector( '#major-publishing-actions' ).addEventListener( 'click', function() {
			window.removeEventListener( 'beforeunload', component.onBeforeUnload );
		} );

		component.beforeUnloadPromptAdded = true;
	};

	/**
	 * Watch for unsaved changes.
	 *
	 * Add an beforeunload warning when attempting to leave the page when there are unsaved changes,
	 * unless the user is pressing the trash link or update button.
	 */
	component.watchForUnsavedChanges = function watchForUnsavedChanges() {
		const onChange = function( event ) {
			if ( event.target.matches( 'select' ) ) {
				document.getElementById( 'post' ).removeEventListener( 'change', onChange );
				component.addBeforeUnloadPrompt();
			}
		};
		document.getElementById( 'post' ).addEventListener( 'change', onChange );
	};

	/**
	 * Show message at beforeunload.
	 *
	 * @param {Event} event - The beforeunload event.
	 * @return {string} Message.
	 */
	component.onBeforeUnload = function onBeforeUnload( event ) {
		event.preventDefault();
		event.returnValue = component.data.l10n.unsaved_changes;
		return component.data.l10n.unsaved_changes;
	};

	/**
	 * Updates the <tr> with 'Showing x of y validation errors' at the top of the list table with the current count.
	 * If this does not exist yet, it creates the element.
	 *
	 * @param {number} numberErrorsDisplaying - The number of errors displaying.
	 * @param {number} totalErrors - The total number of errors, displaying or not.
	 */
	component.updateShowingErrorsRow = function updateShowingErrorsRow( numberErrorsDisplaying, totalErrors ) {
		const showAllButton = document.getElementById( component.showAllId );
		let thead, th,
			tr = document.getElementById( component.idNumberErrors );
		const theadQuery = document.getElementsByTagName( 'thead' );

		// Only create the <tr> if it does not exist yet.
		if ( theadQuery[ 0 ] && ! tr ) {
			thead = theadQuery[ 0 ];
			tr = document.createElement( 'tr' );
			th = document.createElement( 'th' );
			th.setAttribute( 'id', component.idNumberErrors );
			th.setAttribute( 'colspan', '6' );
			tr.appendChild( th );
			thead.appendChild( tr );
		}

		// If all of the errors are displaying, hide the 'Show all' button and the count notice.
		if ( showAllButton && numberErrorsDisplaying === totalErrors ) {
			showAllButton.classList.add( 'hidden' );
			tr.classList.add( 'hidden' );
		} else if ( null !== numberErrorsDisplaying ) {
			// Update the number of errors displaying and create a 'Show all' button if it does not exist yet.
			document.getElementById( component.idNumberErrors ).innerText = component.data.l10n.showing_number_errors.replace( '%', numberErrorsDisplaying );
			document.getElementById( component.idNumberErrors ).classList.remove( 'hidden' );
			component.conditionallyCreateShowAllButton();
			if ( document.getElementById( component.showAllId ) ) {
				document.getElementById( component.showAllId ).classList.remove( 'hidden' );
			}
		}
	};

	/**
	 * Conditionally creates and appends a 'Show all' button.
	 */
	component.conditionallyCreateShowAllButton = function conditionallyCreateShowAllButton() {
		const buttonContainer = document.getElementById( 'url-post-filter' );
		let showAllButton = document.getElementById( component.showAllId );

		// There is no 'Show all' <button> yet, but there is a container element for it, create the <button>
		if ( ! showAllButton && buttonContainer ) {
			showAllButton = document.createElement( 'button' );
			showAllButton.id = component.showAllId;
			showAllButton.classList.add( 'button' );
			showAllButton.innerText = component.data.l10n.show_all;
			buttonContainer.appendChild( showAllButton );
		}
	};

	/**
	 * On clicking the 'Show all' <button>, this displays all of the validation errors.
	 * Then, it hides this 'Show all' <button> and the notice for the number of errors showing.
	 */
	component.handleShowAll = function handleShowAll() {
		const onClick = function( event ) {
			const validationErrors = document.querySelectorAll( '[data-error-type]' );
			if ( ! event.target.matches( '#' + component.showAllId ) ) {
				return;
			}
			event.preventDefault();

			// Iterate through all of the errors, and remove the 'hidden' class.
			validationErrors.forEach( function( element ) {
				element.parentElement.parentElement.classList.remove( 'hidden' );
			} );

			/*
			 * Update the notice to indicate that all of the errors are displaying.
			 * Like 'Showing 5 of 5 validation errors'.
 			 */
			component.updateShowingErrorsRow( validationErrors.length, validationErrors.length );

			// Hide this 'Show all' button.
			event.target.classList.add( 'hidden' );

			// Change the value of the error type <select> element to 'All Error Types'.
			document.getElementById( 'amp_validation_error_type' ).value = '-1';
		};

		document.getElementById( 'url-post-filter' ).addEventListener( 'click', onClick );
	};

	/**
	 * Handles filtering by error type, triggered by clicking 'Apply Filter'.
	 *
	 * Gets the value of the error type <select> element.
	 * And hides all <tr> elements that do not have the same type of this value.
	 * If 'All Error Types' is selected, this displays all errors.
	 */
	component.handleFiltering = function handleFiltering() {
		const onChange = function( event ) {
			const showAllButton = document.getElementById( component.showAllId );
			if ( ! event.target.matches( 'select' ) ) {
				return;
			}

			event.preventDefault();

			const isAllErrorTypesSelected = ( '-1' === event.target.value );
			const errorTypeQuery = document.querySelectorAll( '[data-error-type]' );

			// If the user has chosen 'All Error Types' from the <select>, hide the 'Show all' button.
			if ( isAllErrorTypesSelected && showAllButton ) {
				showAllButton.classList.add( 'hidden' );
			}

			/*
			 * Iterate through all of the <tr> elements in the list table.
			 * If the error type does not match the value (selected error type), hide them.
			 */
			let numberErrorsDisplaying = 0;
			errorTypeQuery.forEach( function( element ) {
				const errorType = element.getAttribute( 'data-error-type' );

				// If 'All Error Types' was selected, this should display all errors.
				if ( isAllErrorTypesSelected || ! event.target.value || event.target.value === errorType ) {
					element.parentElement.parentElement.classList.remove( 'hidden' );
					numberErrorsDisplaying++;
				} else {
					element.parentElement.parentElement.classList.add( 'hidden' );
				}
			} );

			component.updateShowingErrorsRow( numberErrorsDisplaying, errorTypeQuery.length );
		};

		document.getElementById( 'amp_validation_error_type' ).addEventListener( 'change', onChange );
	};

	/**
	 * Handles searching for errors via the <input> and the 'Search Errors' <button>.
	 */
	component.handleSearching = function handleSearching() {
		const onClick = function( event ) {
			event.preventDefault();
			if ( ! event.target.matches( 'input' ) ) {
				return;
			}

			const searchQuery = document.getElementById( 'invalid-url-search-search-input' ).value;
			const detailsQuery = document.querySelectorAll( 'tbody .column-details' );

			/*
			 * Iterate through the 'Details' column of each row.
			 * If the search query is not present, hide the row.
			 */
			let numberErrorsDisplaying = 0;
			detailsQuery.forEach( function( element ) {
				let isSearchQueryPresent = false;

				element.querySelectorAll( '.detailed' ).forEach( function( detailed ) {
					if ( -1 !== detailed.innerText.indexOf( searchQuery ) ) {
						isSearchQueryPresent = true;
					}
				} );

				if ( isSearchQueryPresent ) {
					element.parentElement.classList.remove( 'hidden' );
					numberErrorsDisplaying++;
				} else {
					element.parentElement.classList.add( 'hidden' );
				}
			} );

			component.updateShowingErrorsRow( numberErrorsDisplaying, detailsQuery.length );
		};

		document.getElementById( 'search-submit' ).addEventListener( 'click', onClick );
	};

	/**
	 * Update icon for select element.
	 *
	 * @param {HTMLSelectElement} select Select element.
	 */
	component.updateSelectIcon = function updateSelectIcon( select ) {
		const newOption = select.options[ select.selectedIndex ];
		if ( newOption ) {
			const iconSrc = newOption.getAttribute( 'data-status-icon' );
			select.parentNode.querySelector( 'img' ).setAttribute( 'src', iconSrc );
		}
	};

	/**
	 * Handles a change in the error status, like from 'New' to 'Accepted'.
	 *
	 * Gets the data-status-icon value from the newly-selected <option>.
	 * And sets this as the src of the status icon <img>.
	 */
	component.handleStatusChange = function handleStatusChange() {
		const onChange = function( event ) {
			if ( event.target.matches( 'select' ) ) {
				component.updateSelectIcon( event.target );
			}
		};

		document.querySelectorAll( '.amp-validation-error-status' ).forEach( function( element ) {
			element.addEventListener( 'change', onChange );
		} );
	};

	/**
	 * On checking a bulk action checkbox, this ensures that the 'Accept' and 'Reject' buttons are present. Handle clicking on buttons.
	 *
	 * They're hidden until one of these boxes is checked.
	 * Also, on unchecking the last checked box, this hides these buttons.
	 */
	component.handleBulkActions = function handleBulkActions() {
		const acceptButton = document.querySelector( 'button.action.accept' );
		const rejectButton = document.querySelector( 'button.action.reject' );

		const onChange = function( event ) {
			let areThereCheckedBoxes;

			if ( ! event.target.matches( '[type=checkbox]' ) ) {
				return;
			}

			if ( event.target.checked ) {
				// This checkbox was checked, so ensure the buttons display.
				acceptButton.classList.remove( 'hidden' );
				rejectButton.classList.remove( 'hidden' );
			} else {
				/*
				 * This checkbox was unchecked.
				 * So find if there are any other checkboxes that are checked.
				 * If not, hide the 'Accept' and 'Reject' buttons.
				 */
				areThereCheckedBoxes = false;
				document.querySelectorAll( '.check-column [type=checkbox]' ).forEach( function( element ) {
					if ( element.checked ) {
						areThereCheckedBoxes = true;
					}
				} );
				if ( ! areThereCheckedBoxes ) {
					acceptButton.classList.add( 'hidden' );
					rejectButton.classList.add( 'hidden' );
				}
			}
		};

		document.querySelectorAll( '.check-column [type=checkbox]' ).forEach( function( element ) {
			element.addEventListener( 'change', onChange );
		} );

		// Handle click on accept button.
		acceptButton.addEventListener( 'click', function() {
			Array.prototype.forEach.call( document.querySelectorAll( 'select.amp-validation-error-status' ), function( select ) {
				if ( select.closest( 'tr' ).querySelector( '.check-column input[type=checkbox]' ).checked ) {
					select.value = '3';
					component.updateSelectIcon( select );
					component.addBeforeUnloadPrompt();
				}
			} );
		} );

		// Handle click on reject button.
		rejectButton.addEventListener( 'click', function() {
			Array.prototype.forEach.call( document.querySelectorAll( 'select.amp-validation-error-status' ), function( select ) {
				if ( select.closest( 'tr' ).querySelector( '.check-column input[type=checkbox]' ).checked ) {
					select.value = '2';
					component.updateSelectIcon( select );
					component.addBeforeUnloadPrompt();
				}
			} );
		} );
	};

	/**
	 * Changes the page heading and document title, as this doesn't look to be possible with a PHP filter.
	 */
	component.changeHeading = function changeHeading() {
		const headingQuery = document.getElementsByClassName( 'wp-heading-inline' );
		if ( headingQuery[ 0 ] && component.data.l10n.page_heading ) {
			headingQuery[ 0 ].innerText = component.data.l10n.page_heading;
			document.title = component.data.l10n.page_heading + document.title;
		}
	};

	return component;
}() );
<commit_msg>Fix resetting All Error Types dropdown to first option<commit_after>/* exported ampInvalidUrlPostEditScreen */

var ampInvalidUrlPostEditScreen = ( function() { // eslint-disable-line no-unused-vars
	var component;

	component = {
		data: {
			l10n: {
				unsaved_changes: '',
				showing_number_errors: '',
				page_heading: '',
				show_all: ''
			}
		}
	};

	/**
	 * The id for the 'Showing x of y errors' notice.
	 *
	 * @var {string}
	 */
	component.idNumberErrors = 'number-errors';

	/**
	 * The id for the 'Show all' button.
	 *
	 * @var {string}
	 */
	component.showAllId = 'show-all-errors';

	/**
	 * Boot.
	 *
	 * @param {Object} data Data.
	 * @param {Object} data.l10n Translations.
	 */
	component.boot = function boot( data ) {
		Object.assign( component.data, data );
		component.handleShowAll();
		component.handleFiltering();
		component.handleSearching();
		component.handleStatusChange();
		component.handleBulkActions();
		component.changeHeading();
		component.watchForUnsavedChanges();
	};

	/**
	 * Add prompt when leaving page due to unsaved changes.
	 */
	component.addBeforeUnloadPrompt = function addBeforeUnloadPrompt() {
		if ( component.beforeUnloadPromptAdded ) {
			return;
		}
		window.addEventListener( 'beforeunload', component.onBeforeUnload );

		// Remove prompt when clicking trash or update.
		document.querySelector( '#major-publishing-actions' ).addEventListener( 'click', function() {
			window.removeEventListener( 'beforeunload', component.onBeforeUnload );
		} );

		component.beforeUnloadPromptAdded = true;
	};

	/**
	 * Watch for unsaved changes.
	 *
	 * Add an beforeunload warning when attempting to leave the page when there are unsaved changes,
	 * unless the user is pressing the trash link or update button.
	 */
	component.watchForUnsavedChanges = function watchForUnsavedChanges() {
		const onChange = function( event ) {
			if ( event.target.matches( 'select' ) ) {
				document.getElementById( 'post' ).removeEventListener( 'change', onChange );
				component.addBeforeUnloadPrompt();
			}
		};
		document.getElementById( 'post' ).addEventListener( 'change', onChange );
	};

	/**
	 * Show message at beforeunload.
	 *
	 * @param {Event} event - The beforeunload event.
	 * @return {string} Message.
	 */
	component.onBeforeUnload = function onBeforeUnload( event ) {
		event.preventDefault();
		event.returnValue = component.data.l10n.unsaved_changes;
		return component.data.l10n.unsaved_changes;
	};

	/**
	 * Updates the <tr> with 'Showing x of y validation errors' at the top of the list table with the current count.
	 * If this does not exist yet, it creates the element.
	 *
	 * @param {number} numberErrorsDisplaying - The number of errors displaying.
	 * @param {number} totalErrors - The total number of errors, displaying or not.
	 */
	component.updateShowingErrorsRow = function updateShowingErrorsRow( numberErrorsDisplaying, totalErrors ) {
		const showAllButton = document.getElementById( component.showAllId );
		let thead, th,
			tr = document.getElementById( component.idNumberErrors );
		const theadQuery = document.getElementsByTagName( 'thead' );

		// Only create the <tr> if it does not exist yet.
		if ( theadQuery[ 0 ] && ! tr ) {
			thead = theadQuery[ 0 ];
			tr = document.createElement( 'tr' );
			th = document.createElement( 'th' );
			th.setAttribute( 'id', component.idNumberErrors );
			th.setAttribute( 'colspan', '6' );
			tr.appendChild( th );
			thead.appendChild( tr );
		}

		// If all of the errors are displaying, hide the 'Show all' button and the count notice.
		if ( showAllButton && numberErrorsDisplaying === totalErrors ) {
			showAllButton.classList.add( 'hidden' );
			tr.classList.add( 'hidden' );
		} else if ( null !== numberErrorsDisplaying ) {
			// Update the number of errors displaying and create a 'Show all' button if it does not exist yet.
			document.getElementById( component.idNumberErrors ).innerText = component.data.l10n.showing_number_errors.replace( '%', numberErrorsDisplaying );
			document.getElementById( component.idNumberErrors ).classList.remove( 'hidden' );
			component.conditionallyCreateShowAllButton();
			if ( document.getElementById( component.showAllId ) ) {
				document.getElementById( component.showAllId ).classList.remove( 'hidden' );
			}
		}
	};

	/**
	 * Conditionally creates and appends a 'Show all' button.
	 */
	component.conditionallyCreateShowAllButton = function conditionallyCreateShowAllButton() {
		const buttonContainer = document.getElementById( 'url-post-filter' );
		let showAllButton = document.getElementById( component.showAllId );

		// There is no 'Show all' <button> yet, but there is a container element for it, create the <button>
		if ( ! showAllButton && buttonContainer ) {
			showAllButton = document.createElement( 'button' );
			showAllButton.id = component.showAllId;
			showAllButton.classList.add( 'button' );
			showAllButton.innerText = component.data.l10n.show_all;
			buttonContainer.appendChild( showAllButton );
		}
	};

	/**
	 * On clicking the 'Show all' <button>, this displays all of the validation errors.
	 * Then, it hides this 'Show all' <button> and the notice for the number of errors showing.
	 */
	component.handleShowAll = function handleShowAll() {
		const onClick = function( event ) {
			const validationErrors = document.querySelectorAll( '[data-error-type]' );
			if ( ! event.target.matches( '#' + component.showAllId ) ) {
				return;
			}
			event.preventDefault();

			// Iterate through all of the errors, and remove the 'hidden' class.
			validationErrors.forEach( function( element ) {
				element.parentElement.parentElement.classList.remove( 'hidden' );
			} );

			/*
			 * Update the notice to indicate that all of the errors are displaying.
			 * Like 'Showing 5 of 5 validation errors'.
 			 */
			component.updateShowingErrorsRow( validationErrors.length, validationErrors.length );

			// Hide this 'Show all' button.
			event.target.classList.add( 'hidden' );

			// Change the value of the error type <select> element to 'All Error Types'.
			document.getElementById( 'amp_validation_error_type' ).selectedIndex = 0;
		};

		document.getElementById( 'url-post-filter' ).addEventListener( 'click', onClick );
	};

	/**
	 * Handles filtering by error type, triggered by clicking 'Apply Filter'.
	 *
	 * Gets the value of the error type <select> element.
	 * And hides all <tr> elements that do not have the same type of this value.
	 * If 'All Error Types' is selected, this displays all errors.
	 */
	component.handleFiltering = function handleFiltering() {
		const onChange = function( event ) {
			const showAllButton = document.getElementById( component.showAllId );
			if ( ! event.target.matches( 'select' ) ) {
				return;
			}

			event.preventDefault();

			const isAllErrorTypesSelected = ( '-1' === event.target.value );
			const errorTypeQuery = document.querySelectorAll( '[data-error-type]' );

			// If the user has chosen 'All Error Types' from the <select>, hide the 'Show all' button.
			if ( isAllErrorTypesSelected && showAllButton ) {
				showAllButton.classList.add( 'hidden' );
			}

			/*
			 * Iterate through all of the <tr> elements in the list table.
			 * If the error type does not match the value (selected error type), hide them.
			 */
			let numberErrorsDisplaying = 0;
			errorTypeQuery.forEach( function( element ) {
				const errorType = element.getAttribute( 'data-error-type' );

				// If 'All Error Types' was selected, this should display all errors.
				if ( isAllErrorTypesSelected || ! event.target.value || event.target.value === errorType ) {
					element.parentElement.parentElement.classList.remove( 'hidden' );
					numberErrorsDisplaying++;
				} else {
					element.parentElement.parentElement.classList.add( 'hidden' );
				}
			} );

			component.updateShowingErrorsRow( numberErrorsDisplaying, errorTypeQuery.length );
		};

		document.getElementById( 'amp_validation_error_type' ).addEventListener( 'change', onChange );
	};

	/**
	 * Handles searching for errors via the <input> and the 'Search Errors' <button>.
	 */
	component.handleSearching = function handleSearching() {
		const onClick = function( event ) {
			event.preventDefault();
			if ( ! event.target.matches( 'input' ) ) {
				return;
			}

			const searchQuery = document.getElementById( 'invalid-url-search-search-input' ).value;
			const detailsQuery = document.querySelectorAll( 'tbody .column-details' );

			/*
			 * Iterate through the 'Details' column of each row.
			 * If the search query is not present, hide the row.
			 */
			let numberErrorsDisplaying = 0;
			detailsQuery.forEach( function( element ) {
				let isSearchQueryPresent = false;

				element.querySelectorAll( '.detailed' ).forEach( function( detailed ) {
					if ( -1 !== detailed.innerText.indexOf( searchQuery ) ) {
						isSearchQueryPresent = true;
					}
				} );

				if ( isSearchQueryPresent ) {
					element.parentElement.classList.remove( 'hidden' );
					numberErrorsDisplaying++;
				} else {
					element.parentElement.classList.add( 'hidden' );
				}
			} );

			component.updateShowingErrorsRow( numberErrorsDisplaying, detailsQuery.length );
		};

		document.getElementById( 'search-submit' ).addEventListener( 'click', onClick );
	};

	/**
	 * Update icon for select element.
	 *
	 * @param {HTMLSelectElement} select Select element.
	 */
	component.updateSelectIcon = function updateSelectIcon( select ) {
		const newOption = select.options[ select.selectedIndex ];
		if ( newOption ) {
			const iconSrc = newOption.getAttribute( 'data-status-icon' );
			select.parentNode.querySelector( 'img' ).setAttribute( 'src', iconSrc );
		}
	};

	/**
	 * Handles a change in the error status, like from 'New' to 'Accepted'.
	 *
	 * Gets the data-status-icon value from the newly-selected <option>.
	 * And sets this as the src of the status icon <img>.
	 */
	component.handleStatusChange = function handleStatusChange() {
		const onChange = function( event ) {
			if ( event.target.matches( 'select' ) ) {
				component.updateSelectIcon( event.target );
			}
		};

		document.querySelectorAll( '.amp-validation-error-status' ).forEach( function( element ) {
			element.addEventListener( 'change', onChange );
		} );
	};

	/**
	 * On checking a bulk action checkbox, this ensures that the 'Accept' and 'Reject' buttons are present. Handle clicking on buttons.
	 *
	 * They're hidden until one of these boxes is checked.
	 * Also, on unchecking the last checked box, this hides these buttons.
	 */
	component.handleBulkActions = function handleBulkActions() {
		const acceptButton = document.querySelector( 'button.action.accept' );
		const rejectButton = document.querySelector( 'button.action.reject' );

		const onChange = function( event ) {
			let areThereCheckedBoxes;

			if ( ! event.target.matches( '[type=checkbox]' ) ) {
				return;
			}

			if ( event.target.checked ) {
				// This checkbox was checked, so ensure the buttons display.
				acceptButton.classList.remove( 'hidden' );
				rejectButton.classList.remove( 'hidden' );
			} else {
				/*
				 * This checkbox was unchecked.
				 * So find if there are any other checkboxes that are checked.
				 * If not, hide the 'Accept' and 'Reject' buttons.
				 */
				areThereCheckedBoxes = false;
				document.querySelectorAll( '.check-column [type=checkbox]' ).forEach( function( element ) {
					if ( element.checked ) {
						areThereCheckedBoxes = true;
					}
				} );
				if ( ! areThereCheckedBoxes ) {
					acceptButton.classList.add( 'hidden' );
					rejectButton.classList.add( 'hidden' );
				}
			}
		};

		document.querySelectorAll( '.check-column [type=checkbox]' ).forEach( function( element ) {
			element.addEventListener( 'change', onChange );
		} );

		// Handle click on accept button.
		acceptButton.addEventListener( 'click', function() {
			Array.prototype.forEach.call( document.querySelectorAll( 'select.amp-validation-error-status' ), function( select ) {
				if ( select.closest( 'tr' ).querySelector( '.check-column input[type=checkbox]' ).checked ) {
					select.value = '3';
					component.updateSelectIcon( select );
					component.addBeforeUnloadPrompt();
				}
			} );
		} );

		// Handle click on reject button.
		rejectButton.addEventListener( 'click', function() {
			Array.prototype.forEach.call( document.querySelectorAll( 'select.amp-validation-error-status' ), function( select ) {
				if ( select.closest( 'tr' ).querySelector( '.check-column input[type=checkbox]' ).checked ) {
					select.value = '2';
					component.updateSelectIcon( select );
					component.addBeforeUnloadPrompt();
				}
			} );
		} );
	};

	/**
	 * Changes the page heading and document title, as this doesn't look to be possible with a PHP filter.
	 */
	component.changeHeading = function changeHeading() {
		const headingQuery = document.getElementsByClassName( 'wp-heading-inline' );
		if ( headingQuery[ 0 ] && component.data.l10n.page_heading ) {
			headingQuery[ 0 ].innerText = component.data.l10n.page_heading;
			document.title = component.data.l10n.page_heading + document.title;
		}
	};

	return component;
}() );
<|endoftext|>


<commit_before>/**
 * @fileoverview Soundworks client side time syncronization module
 * @author Sebastien.Robaszkiewicz@ircam.fr, Norbert.Schnell@ircam.fr
 */
'use strict';

var ClientModule = require('./ClientModule');
var Sync = require('sync/client');
var client = require('./client');
var audioContext = require('audio-context');

class ClientSync extends ClientModule {
  constructor(options = {}) {
    super('sync', true, options.color || 'black');

    this.ready = false;

    this.sync = new Sync(
      () => audioContext.currentTime, (cmd, ...args) => client.send(cmd, ...args), (cmd, callback) => client.receive(cmd, callback), () => {
        if (!this.ready) {
          this.ready = true;
          this.done();
        }
      }
    );

    this.setViewText('Clock syncing, stand by…', 'soft-blink');
  }

  start() {
    super.start();
    this.sync.start();
  }

  getLocalTime(syncTime) {
    return this.sync.getLocalTime(syncTime);
  }

  getSyncTime(localTime) {
    return this.sync.getSyncTime(localTime);
  }
}

module.exports = ClientSync;<commit_msg>ClientSync: sendFunction, receiveFunction, callback in the .start() method<commit_after>/**
 * @fileoverview Soundworks client side time syncronization module
 * @author Sebastien.Robaszkiewicz@ircam.fr, Norbert.Schnell@ircam.fr
 */
'use strict';

var ClientModule = require('./ClientModule');
var Sync = require('sync/client');
var client = require('./client');
var audioContext = require('audio-context');

class ClientSync extends ClientModule {
  constructor(options = {}) {
    super('sync', true, options.color || 'black');

    this.ready = false;

    this.sync = new Sync(() => audioContext.currentTime);

    this.setViewText('Clock syncing, stand by…', 'soft-blink');
  }

  start() {
    super.start();
    this.sync.start(
      (cmd, ...args) => client.send(cmd, ...args),
      (cmd, callback) => client.receive(cmd, callback),
      () => {
        if (!this.ready) {
          this.ready = true;
          this.done();
        }
      }
    );
  }

  getLocalTime(syncTime) {
    return this.sync.getLocalTime(syncTime);
  }

  getSyncTime(localTime) {
    return this.sync.getSyncTime(localTime);
  }
}

module.exports = ClientSync;<|endoftext|>


<commit_before>/*
 * (c) Copyright Ascensio System SIA 2010-2019
 *
 * This program is a free software product. You can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License (AGPL)
 * version 3 as published by the Free Software Foundation. In accordance with
 * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
 * that Ascensio System SIA expressly excludes the warranty of non-infringement
 * of any third-party rights.
 *
 * This program is distributed WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR  PURPOSE. For
 * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
 *
 * You can contact Ascensio System SIA at 20A-12 Ernesta Birznieka-Upisha
 * street, Riga, Latvia, EU, LV-1050.
 *
 * The  interactive user interfaces in modified source and object code versions
 * of the Program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU AGPL version 3.
 *
 * Pursuant to Section 7(b) of the License you must retain the original Product
 * logo when distributing the program. Pursuant to Section 7(e) we decline to
 * grant you any rights under trademark law for use of our trademarks.
 *
 * All the Product's GUI elements, including illustrations and icon sets, as
 * well as technical writing content are licensed under the terms of the
 * Creative Commons Attribution-ShareAlike 4.0 International. See the License
 * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
 *
 */

'use strict';
const crypto = require('crypto');
var multiparty = require('multiparty');
var co = require('co');
var jwt = require('jsonwebtoken');
var taskResult = require('./taskresult');
var docsCoServer = require('./DocsCoServer');
var utils = require('./../../Common/sources/utils');
var constants = require('./../../Common/sources/constants');
var storageBase = require('./../../Common/sources/storage-base');
var formatChecker = require('./../../Common/sources/formatchecker');
var logger = require('./../../Common/sources/logger');
const commonDefines = require('./../../Common/sources/commondefines');

var config = require('config');
var configServer = config.get('services.CoAuthoring.server');
var configUtils = config.get('services.CoAuthoring.utils');

var cfgImageSize = configServer.get('limits_image_size');
var cfgTypesUpload = configUtils.get('limits_image_types_upload');
var cfgTokenEnableBrowser = config.get('services.CoAuthoring.token.enable.browser');

const PATTERN_ENCRYPTED = 'ENCRYPTED;';

exports.uploadTempFile = function(req, res) {
  return co(function* () {
    var docId = 'uploadTempFile';
    try {
      let params;
      let authRes = docsCoServer.getRequestParams(docId, req, true);
      if(authRes.code === constants.NO_ERROR){
        params = authRes.params;
      } else {
        utils.fillResponse(req, res, undefined, authRes.code, false);
        return;
      }
      docId = params.key;
      logger.debug('Start uploadTempFile: docId = %s', docId);
      if (docId && req.body && Buffer.isBuffer(req.body)) {
        var task = yield* taskResult.addRandomKeyTask(docId);
        var strPath = task.key + '/' + docId + '.tmp';
        yield storageBase.putObject(strPath, req.body, req.body.length);
        var url = yield storageBase.getSignedUrl(utils.getBaseUrlByRequest(req), strPath,
                                                 commonDefines.c_oAscUrlTypes.Temporary);
        utils.fillResponse(req, res, url, constants.NO_ERROR, false);
      } else {
        utils.fillResponse(req, res, undefined, constants.UNKNOWN, false);
      }
      logger.debug('End uploadTempFile: docId = %s', docId);
    }
    catch (e) {
      logger.error('Error uploadTempFile: docId = %s\r\n%s', docId, e.stack);
      utils.fillResponse(req, res, undefined, constants.UNKNOWN, false);
    }
  });
};
function checkJwtUpload(docId, errorName, token){
  let checkJwtRes = docsCoServer.checkJwt(docId, token, commonDefines.c_oAscSecretType.Session);
  return checkJwtUploadTransformRes(docId, errorName, checkJwtRes);
}
function checkJwtUploadTransformRes(docId, errorName, checkJwtRes){
  var res = {err: true, docId: null, userid: null, encrypted: null};
  if (checkJwtRes.decoded) {
    var doc = checkJwtRes.decoded.document;
    var edit = checkJwtRes.decoded.editorConfig;
    if (!edit.ds_view && !edit.ds_isCloseCoAuthoring) {
      res.err = false;
      res.docId = doc.key;
      res.encrypted = doc.ds_encrypted;
      if (edit.user) {
        res.userid = edit.user.id;
      }
    } else {
      logger.warn('Error %s jwt: docId = %s\r\n%s', errorName, docId, 'access deny');
    }
  } else {
    logger.warn('Error %s jwt: docId = %s\r\n%s', errorName, docId, checkJwtRes.description);
  }
  return res;
}
exports.uploadImageFileOld = function(req, res) {
  var docId = req.params.docid;
  logger.debug('Start uploadImageFileOld: docId = %s', docId);
  var userid = req.params.userid;
  if (cfgTokenEnableBrowser) {
    var checkJwtRes = checkJwtUpload(docId, 'uploadImageFileOld', req.query['token']);
    if(!checkJwtRes.err){
      docId = checkJwtRes.docId || docId;
      userid = checkJwtRes.userid || userid;
    } else {
      res.sendStatus(403);
      return;
    }
  }
  var listImages = [];
  //todo userid
  if (docId) {
    var isError = false;
    var form = new multiparty.Form();
    form.on('error', function(err) {
      logger.error('Error parsing form: docId = %s\r\n%s', docId, err.toString());
      res.sendStatus(400);
    });
    form.on('part', function(part) {
      if (!part.filename) {
        // ignore field's content
        part.resume();
      }
      if (part.filename) {
        if (part.byteCount > cfgImageSize) {
          isError = true;
        }
        if (isError) {
          part.resume();
        } else {
          //в начале пишется хеш, чтобы избежать ошибок при параллельном upload в совместном редактировании
          var strImageName = crypto.randomBytes(16).toString("hex");
          var strPath = docId + '/media/' + strImageName + '.jpg';
          listImages.push(strPath);
          utils.stream2Buffer(part).then(function(buffer) {
            return storageBase.putObject(strPath, buffer, buffer.length);
          }).then(function() {
            part.resume();
          }).catch(function(err) {
            logger.error('Upload putObject: docId = %s\r\n%s', docId, err.stack);
            isError = true;
            part.resume();
          });
        }
      }
      part.on('error', function(err) {
        logger.error('Error parsing form part: docId = %s\r\n%s', docId, err.toString());
      });
    });
    form.on('close', function() {
      if (isError) {
        res.sendStatus(400);
      } else {
        storageBase.getSignedUrlsByArray(utils.getBaseUrlByRequest(req), listImages, docId,
                                         commonDefines.c_oAscUrlTypes.Session).then(function(urls) {
          var outputData = {'type': 0, 'error': constants.NO_ERROR, 'urls': urls, 'input': req.query};
            var output = '';

            //res.setHeader('Access-Control-Allow-Origin', '*');
            res.setHeader('Content-Type', 'text/html');
            res.send(output);
            logger.debug('End uploadImageFileOld: docId = %s %s', docId, output);
          }
        ).catch(function(err) {
            res.sendStatus(400);
            logger.error('upload getSignedUrlsByArray: docId = %s\r\n%s', docId, err.stack);
          });
      }
    });
    form.parse(req);
  } else {
    logger.debug('Error params uploadImageFileOld: docId = %s', docId);
    res.sendStatus(400);
  }
};
exports.uploadImageFile = function(req, res) {
  return co(function* () {
    var isError = true;
    var docId = 'null';
    let output = {};
    let isValidJwt = true;
    try {
      docId = req.params.docid;
      let encrypted = false;
      logger.debug('Start uploadImageFile: docId = %s', docId);

      if (cfgTokenEnableBrowser) {
        let checkJwtRes = docsCoServer.checkJwtHeader(docId, req, 'Authorization', 'Bearer ', commonDefines.c_oAscSecretType.Session);
        if (!checkJwtRes) {
          //todo remove compatibility with previous versions
          checkJwtRes = docsCoServer.checkJwt(docId, req.query['token'], commonDefines.c_oAscSecretType.Session);
        }
        let transformedRes = checkJwtUploadTransformRes(docId, 'uploadImageFile', checkJwtRes);
        if (!transformedRes.err) {
          docId = transformedRes.docId || docId;
          encrypted = transformedRes.encrypted;
        } else {
          isValidJwt = false;
        }
      }

      if (isValidJwt && docId && req.body && Buffer.isBuffer(req.body)) {
        let buffer = req.body;
        if (buffer.length <= cfgImageSize) {
          var format = formatChecker.getImageFormat(buffer, undefined);
          var formatStr = formatChecker.getStringFromFormat(format);
          var supportedFormats = cfgTypesUpload || 'jpg';
          let formatLimit = formatStr && -1 !== supportedFormats.indexOf(formatStr);
          if (!formatLimit && encrypted && PATTERN_ENCRYPTED == buffer.toString('utf8', 0, PATTERN_ENCRYPTED.length)) {
            formatLimit = true;
            formatStr = buffer.toString('utf8', PATTERN_ENCRYPTED.length, buffer.indexOf(';', PATTERN_ENCRYPTED.length));
          }
          if (formatLimit) {
            //в начале пишется хеш, чтобы избежать ошибок при параллельном upload в совместном редактировании
            var strImageName = crypto.randomBytes(16).toString("hex");
            var strPathRel = 'media/' + strImageName + '.' + formatStr;
            var strPath = docId + '/' + strPathRel;
            yield storageBase.putObject(strPath, buffer, buffer.length);
            output[strPathRel] = yield storageBase.getSignedUrl(utils.getBaseUrlByRequest(req), strPath,
                                                                commonDefines.c_oAscUrlTypes.Session);
            isError = false;
          } else {
            logger.debug('uploadImageFile format is not supported: docId = %s', docId);
          }
        } else {
          logger.debug('uploadImageFile size limit exceeded: buffer.length = %d docId = %s', buffer.length, docId);
        }
      }
    } catch (e) {
      isError = true;
      logger.error('Error uploadImageFile: docId = %s\r\n%s', docId, e.stack);
    } finally {
      try {
        if (!isError) {
          res.setHeader('Content-Type', 'application/json');
          res.send(JSON.stringify(output));
        } else {
          res.sendStatus(isValidJwt ? 400 : 403);
        }
        logger.debug('End uploadImageFile: isError = %s docId = %s', isError, docId);
      } catch (e) {
        logger.error('Error uploadImageFile: docId = %s\r\n%s', docId, e.stack);
      }
    }
  });
};
<commit_msg>[bug] Fix Path Traversal vulnerability via image upload params<commit_after>/*
 * (c) Copyright Ascensio System SIA 2010-2019
 *
 * This program is a free software product. You can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License (AGPL)
 * version 3 as published by the Free Software Foundation. In accordance with
 * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
 * that Ascensio System SIA expressly excludes the warranty of non-infringement
 * of any third-party rights.
 *
 * This program is distributed WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR  PURPOSE. For
 * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
 *
 * You can contact Ascensio System SIA at 20A-12 Ernesta Birznieka-Upisha
 * street, Riga, Latvia, EU, LV-1050.
 *
 * The  interactive user interfaces in modified source and object code versions
 * of the Program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU AGPL version 3.
 *
 * Pursuant to Section 7(b) of the License you must retain the original Product
 * logo when distributing the program. Pursuant to Section 7(e) we decline to
 * grant you any rights under trademark law for use of our trademarks.
 *
 * All the Product's GUI elements, including illustrations and icon sets, as
 * well as technical writing content are licensed under the terms of the
 * Creative Commons Attribution-ShareAlike 4.0 International. See the License
 * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
 *
 */

'use strict';
const crypto = require('crypto');
var multiparty = require('multiparty');
var co = require('co');
var jwt = require('jsonwebtoken');
var taskResult = require('./taskresult');
var docsCoServer = require('./DocsCoServer');
var utils = require('./../../Common/sources/utils');
var constants = require('./../../Common/sources/constants');
var storageBase = require('./../../Common/sources/storage-base');
var formatChecker = require('./../../Common/sources/formatchecker');
var logger = require('./../../Common/sources/logger');
const commonDefines = require('./../../Common/sources/commondefines');

var config = require('config');
var configServer = config.get('services.CoAuthoring.server');
var configUtils = config.get('services.CoAuthoring.utils');

var cfgImageSize = configServer.get('limits_image_size');
var cfgTypesUpload = configUtils.get('limits_image_types_upload');
var cfgTokenEnableBrowser = config.get('services.CoAuthoring.token.enable.browser');

const PATTERN_ENCRYPTED = 'ENCRYPTED;';

exports.uploadTempFile = function(req, res) {
  return co(function* () {
    var docId = 'uploadTempFile';
    try {
      let params;
      let authRes = docsCoServer.getRequestParams(docId, req, true);
      if(authRes.code === constants.NO_ERROR){
        params = authRes.params;
      } else {
        utils.fillResponse(req, res, undefined, authRes.code, false);
        return;
      }
      docId = params.key;
      logger.debug('Start uploadTempFile: docId = %s', docId);
      if (docId && req.body && Buffer.isBuffer(req.body)) {
        var task = yield* taskResult.addRandomKeyTask(docId);
        var strPath = task.key + '/' + docId + '.tmp';
        yield storageBase.putObject(strPath, req.body, req.body.length);
        var url = yield storageBase.getSignedUrl(utils.getBaseUrlByRequest(req), strPath,
                                                 commonDefines.c_oAscUrlTypes.Temporary);
        utils.fillResponse(req, res, url, constants.NO_ERROR, false);
      } else {
        utils.fillResponse(req, res, undefined, constants.UNKNOWN, false);
      }
      logger.debug('End uploadTempFile: docId = %s', docId);
    }
    catch (e) {
      logger.error('Error uploadTempFile: docId = %s\r\n%s', docId, e.stack);
      utils.fillResponse(req, res, undefined, constants.UNKNOWN, false);
    }
  });
};
function checkJwtUpload(docId, errorName, token){
  let checkJwtRes = docsCoServer.checkJwt(docId, token, commonDefines.c_oAscSecretType.Session);
  return checkJwtUploadTransformRes(docId, errorName, checkJwtRes);
}
function checkJwtUploadTransformRes(docId, errorName, checkJwtRes){
  var res = {err: true, docId: null, userid: null, encrypted: null};
  if (checkJwtRes.decoded) {
    var doc = checkJwtRes.decoded.document;
    var edit = checkJwtRes.decoded.editorConfig;
    if (!edit.ds_view && !edit.ds_isCloseCoAuthoring) {
      res.err = false;
      res.docId = doc.key;
      res.encrypted = doc.ds_encrypted;
      if (edit.user) {
        res.userid = edit.user.id;
      }
    } else {
      logger.warn('Error %s jwt: docId = %s\r\n%s', errorName, docId, 'access deny');
    }
  } else {
    logger.warn('Error %s jwt: docId = %s\r\n%s', errorName, docId, checkJwtRes.description);
  }
  return res;
}
exports.uploadImageFileOld = function(req, res) {
  var docId = req.params.docid;
  logger.debug('Start uploadImageFileOld: docId = %s', docId);
  var userid = req.params.userid;
  if (cfgTokenEnableBrowser) {
    var checkJwtRes = checkJwtUpload(docId, 'uploadImageFileOld', req.query['token']);
    if(!checkJwtRes.err){
      docId = checkJwtRes.docId || docId;
      userid = checkJwtRes.userid || userid;
    } else {
      res.sendStatus(403);
      return;
    }
  }
  var listImages = [];
  //todo userid
  if (docId) {
    var isError = false;
    var form = new multiparty.Form();
    form.on('error', function(err) {
      logger.error('Error parsing form: docId = %s\r\n%s', docId, err.toString());
      res.sendStatus(400);
    });
    form.on('part', function(part) {
      if (!part.filename) {
        // ignore field's content
        part.resume();
      }
      if (part.filename) {
        if (part.byteCount > cfgImageSize) {
          isError = true;
        }
        if (isError) {
          part.resume();
        } else {
          //в начале пишется хеш, чтобы избежать ошибок при параллельном upload в совместном редактировании
          var strImageName = crypto.randomBytes(16).toString("hex");
          var strPath = docId + '/media/' + strImageName + '.jpg';
          listImages.push(strPath);
          utils.stream2Buffer(part).then(function(buffer) {
            return storageBase.putObject(strPath, buffer, buffer.length);
          }).then(function() {
            part.resume();
          }).catch(function(err) {
            logger.error('Upload putObject: docId = %s\r\n%s', docId, err.stack);
            isError = true;
            part.resume();
          });
        }
      }
      part.on('error', function(err) {
        logger.error('Error parsing form part: docId = %s\r\n%s', docId, err.toString());
      });
    });
    form.on('close', function() {
      if (isError) {
        res.sendStatus(400);
      } else {
        storageBase.getSignedUrlsByArray(utils.getBaseUrlByRequest(req), listImages, docId,
                                         commonDefines.c_oAscUrlTypes.Session).then(function(urls) {
          var outputData = {'type': 0, 'error': constants.NO_ERROR, 'urls': urls, 'input': req.query};
            var output = '';

            //res.setHeader('Access-Control-Allow-Origin', '*');
            res.setHeader('Content-Type', 'text/html');
            res.send(output);
            logger.debug('End uploadImageFileOld: docId = %s %s', docId, output);
          }
        ).catch(function(err) {
            res.sendStatus(400);
            logger.error('upload getSignedUrlsByArray: docId = %s\r\n%s', docId, err.stack);
          });
      }
    });
    form.parse(req);
  } else {
    logger.debug('Error params uploadImageFileOld: docId = %s', docId);
    res.sendStatus(400);
  }
};
exports.uploadImageFile = function(req, res) {
  return co(function* () {
    var isError = true;
    var docId = 'null';
    let output = {};
    let isValidJwt = true;
    try {
      docId = req.params.docid;
      let encrypted = false;
      logger.debug('Start uploadImageFile: docId = %s', docId);

      if (cfgTokenEnableBrowser) {
        let checkJwtRes = docsCoServer.checkJwtHeader(docId, req, 'Authorization', 'Bearer ', commonDefines.c_oAscSecretType.Session);
        if (!checkJwtRes) {
          //todo remove compatibility with previous versions
          checkJwtRes = docsCoServer.checkJwt(docId, req.query['token'], commonDefines.c_oAscSecretType.Session);
        }
        let transformedRes = checkJwtUploadTransformRes(docId, 'uploadImageFile', checkJwtRes);
        if (!transformedRes.err) {
          docId = transformedRes.docId || docId;
          encrypted = transformedRes.encrypted;
        } else {
          isValidJwt = false;
        }
      }

      if (isValidJwt && docId && req.body && Buffer.isBuffer(req.body)) {
        let buffer = req.body;
        if (buffer.length <= cfgImageSize) {
          var format = formatChecker.getImageFormat(buffer, undefined);
          var formatStr = formatChecker.getStringFromFormat(format);
          if (encrypted && PATTERN_ENCRYPTED === buffer.toString('utf8', 0, PATTERN_ENCRYPTED.length)) {
            formatStr = buffer.toString('utf8', PATTERN_ENCRYPTED.length, buffer.indexOf(';', PATTERN_ENCRYPTED.length));
          }
          var supportedFormats = cfgTypesUpload || 'jpg';
          let formatLimit = formatStr && -1 !== supportedFormats.indexOf(formatStr);
          if (formatLimit) {
            //в начале пишется хеш, чтобы избежать ошибок при параллельном upload в совместном редактировании
            var strImageName = crypto.randomBytes(16).toString("hex");
            var strPathRel = 'media/' + strImageName + '.' + formatStr;
            var strPath = docId + '/' + strPathRel;
            yield storageBase.putObject(strPath, buffer, buffer.length);
            output[strPathRel] = yield storageBase.getSignedUrl(utils.getBaseUrlByRequest(req), strPath,
                                                                commonDefines.c_oAscUrlTypes.Session);
            isError = false;
          } else {
            logger.debug('uploadImageFile format is not supported: docId = %s', docId);
          }
        } else {
          logger.debug('uploadImageFile size limit exceeded: buffer.length = %d docId = %s', buffer.length, docId);
        }
      }
    } catch (e) {
      isError = true;
      logger.error('Error uploadImageFile: docId = %s\r\n%s', docId, e.stack);
    } finally {
      try {
        if (!isError) {
          res.setHeader('Content-Type', 'application/json');
          res.send(JSON.stringify(output));
        } else {
          res.sendStatus(isValidJwt ? 400 : 403);
        }
        logger.debug('End uploadImageFile: isError = %s docId = %s', isError, docId);
      } catch (e) {
        logger.error('Error uploadImageFile: docId = %s\r\n%s', docId, e.stack);
      }
    }
  });
};
<|endoftext|>


<commit_before>/*
GreedyNav.js - https://github.com/lukejacksonn/GreedyNav
Licensed under the MIT license - http://opensource.org/licenses/MIT
Copyright (c) 2015 Luke Jackson
*/

$(document).ready(function() {
  var $btn = $("nav.greedy-nav .greedy-nav__toggle");
  var $vlinks = $("nav.greedy-nav .visible-links");
  var $hlinks = $("nav.greedy-nav .hidden-links");

  var numOfItems = 0;
  var totalSpace = 0;
  var closingTime = 1000;
  var breakWidths = [];

  // Get initial state
  $vlinks.children().outerWidth(function(i, w) {
    totalSpace += w;
    numOfItems += 1;
    breakWidths.push(totalSpace);
  });

  var availableSpace, numOfVisibleItems, requiredSpace, timer;

  function check() {
    // Get instant state
    availableSpace = $vlinks.width() - $btn.width();
    numOfVisibleItems = $vlinks.children().length;
    requiredSpace = breakWidths[numOfVisibleItems - 1];

    // There is not enough space
    if (requiredSpace > availableSpace) {
      $vlinks
        .children()
        .last()
        .prependTo($hlinks);
      numOfVisibleItems -= 1;
      check();
      // There is more than enough space
    } else if (availableSpace > breakWidths[numOfVisibleItems]) {
      $hlinks
        .children()
        .first()
        .appendTo($vlinks);
      numOfVisibleItems += 1;
      check();
    }
    // Update the button accordingly
    $btn.attr("count", numOfItems - numOfVisibleItems);
    if (numOfVisibleItems === numOfItems) {
      $btn.addClass("hidden");
    } else {
      $btn.removeClass("hidden");
    }
  }

  // Window listeners
  $(window).resize(function() {
    check();
  });

  $btn.on("click", function() {
    $hlinks.toggleClass("hidden");
    $(this).toggleClass("close");
    // clearTimeout(timer);
  });

  // $hlinks
  //   .on("mouseleave", function() {
  //     // Mouse has left, start the timer
  //     timer = setTimeout(function() {
  //       $hlinks.addClass("hidden");
  //       $btn.toggleClass("close");
  //     }, closingTime);
  //   })
  //   .on("mouseenter", function() {
  //     // Mouse is back, cancel the timer
  //     clearTimeout(timer);
  //   });

  check();
});
<commit_msg>Remove unused variables<commit_after>/*
GreedyNav.js - https://github.com/lukejacksonn/GreedyNav
Licensed under the MIT license - http://opensource.org/licenses/MIT
Copyright (c) 2015 Luke Jackson
*/

$(document).ready(function() {
  var $btn = $("nav.greedy-nav .greedy-nav__toggle");
  var $vlinks = $("nav.greedy-nav .visible-links");
  var $hlinks = $("nav.greedy-nav .hidden-links");

  var numOfItems = 0;
  var totalSpace = 0;
  var breakWidths = [];

  // Get initial state
  $vlinks.children().outerWidth(function(i, w) {
    totalSpace += w;
    numOfItems += 1;
    breakWidths.push(totalSpace);
  });

  var availableSpace, numOfVisibleItems, requiredSpace;

  function check() {
    // Get instant state
    availableSpace = $vlinks.width() - $btn.width();
    numOfVisibleItems = $vlinks.children().length;
    requiredSpace = breakWidths[numOfVisibleItems - 1];

    // There is not enough space
    if (requiredSpace > availableSpace) {
      $vlinks
        .children()
        .last()
        .prependTo($hlinks);
      numOfVisibleItems -= 1;
      check();
      // There is more than enough space
    } else if (availableSpace > breakWidths[numOfVisibleItems]) {
      $hlinks
        .children()
        .first()
        .appendTo($vlinks);
      numOfVisibleItems += 1;
      check();
    }
    // Update the button accordingly
    $btn.attr("count", numOfItems - numOfVisibleItems);
    if (numOfVisibleItems === numOfItems) {
      $btn.addClass("hidden");
    } else {
      $btn.removeClass("hidden");
    }
  }

  // Window listeners
  $(window).resize(function() {
    check();
  });

  $btn.on("click", function() {
    $hlinks.toggleClass("hidden");
    $(this).toggleClass("close");
  });

  check();
});
<|endoftext|>


<commit_before>import $ from 'jquery';
import * as featureHighlightHelper from '~/feature_highlight/feature_highlight_helper';
import * as featureHighlight from '~/feature_highlight/feature_highlight';
import axios from '~/lib/utils/axios_utils';
import MockAdapter from 'axios-mock-adapter';

describe('feature highlight', () => {
  beforeEach(() => {
    setFixtures(`

          Trigger

        Content

          Dismiss

    `);
  });

  describe('setupFeatureHighlightPopover', () => {
    let mock;
    const selector = '.js-feature-highlight[data-highlight=test]';

    beforeEach(() => {
      mock = new MockAdapter(axios);
      mock.onGet('/test').reply(200);
      spyOn(window, 'addEventListener');
      spyOn(window, 'removeEventListener');
      featureHighlight.setupFeatureHighlightPopover('test', 0);
    });

    afterEach(() => {
      mock.restore();
    });

    it('setup popover content', () => {
      const $popoverContent = $('.feature-highlight-popover-content');
      const outerHTML = $popoverContent.prop('outerHTML');

      expect($(selector).data('content')).toEqual(outerHTML);
    });

    it('setup mouseenter', () => {
      const toggleSpy = spyOn(featureHighlightHelper.togglePopover, 'call');
      $(selector).trigger('mouseenter');

      expect(toggleSpy).toHaveBeenCalledWith(jasmine.any(Object), true);
    });

    it('setup debounced mouseleave', (done) => {
      const toggleSpy = spyOn(featureHighlightHelper.togglePopover, 'call');
      $(selector).trigger('mouseleave');

      // Even though we've set the debounce to 0ms, setTimeout is needed for the debounce
      setTimeout(() => {
        expect(toggleSpy).toHaveBeenCalledWith(jasmine.any(Object), false);
        done();
      }, 0);
    });

    it('setup show.bs.popover', () => {
      $(selector).trigger('show.bs.popover');
      expect(window.addEventListener).toHaveBeenCalledWith('scroll', jasmine.any(Function));
    });

    it('setup hide.bs.popover', () => {
      $(selector).trigger('hide.bs.popover');
      expect(window.removeEventListener).toHaveBeenCalledWith('scroll', jasmine.any(Function));
    });

    it('removes disabled attribute', () => {
      expect($('.js-feature-highlight').is(':disabled')).toEqual(false);
    });

    it('displays popover', () => {
      expect(document.querySelector(selector).getAttribute('aria-describedby')).toBeFalsy();
      $(selector).trigger('mouseenter');
      expect(document.querySelector(selector).getAttribute('aria-describedby')).toBeTruthy();
    });

    it('toggles when clicked', () => {
      $(selector).trigger('mouseenter');
      const popoverId = $(selector).attr('aria-describedby');
      const toggleSpy = spyOn(featureHighlightHelper.togglePopover, 'call');

      $(`#${popoverId} .dismiss-feature-highlight`).click();

      expect(toggleSpy).toHaveBeenCalled();
    });
  });

  describe('findHighestPriorityFeature', () => {
    beforeEach(() => {
      setFixtures(`

      `);
    });

    it('should pick the highest priority feature highlight', () => {
      setFixtures(`

      `);

      expect($('.js-feature-highlight').length).toBeGreaterThan(1);
      expect(featureHighlight.findHighestPriorityFeature()).toEqual('test-high-priority');
    });

    it('should work when no priority is set', () => {
      setFixtures(`

      `);

      expect(featureHighlight.findHighestPriorityFeature()).toEqual('test');
    });

    it('should pick the highest priority feature highlight when some have no priority set', () => {
      setFixtures(`

      `);

      expect($('.js-feature-highlight').length).toBeGreaterThan(1);
      expect(featureHighlight.findHighestPriorityFeature()).toEqual('test-high-priority');
    });
  });

  describe('highlightFeatures', () => {
    it('calls setupFeatureHighlightPopover', () => {
      expect(featureHighlight.highlightFeatures()).toEqual('test');
    });
  });
});
<commit_msg>Fix spec<commit_after>import $ from 'jquery';
import * as featureHighlight from '~/feature_highlight/feature_highlight';
import * as popover from '~/shared/popover';
import axios from '~/lib/utils/axios_utils';
import MockAdapter from 'axios-mock-adapter';

describe('feature highlight', () => {
  beforeEach(() => {
    setFixtures(`

          Trigger

        Content

          Dismiss

    `);
  });

  describe('setupFeatureHighlightPopover', () => {
    let mock;
    const selector = '.js-feature-highlight[data-highlight=test]';

    beforeEach(() => {
      mock = new MockAdapter(axios);
      mock.onGet('/test').reply(200);
      spyOn(window, 'addEventListener');
      spyOn(window, 'removeEventListener');
      featureHighlight.setupFeatureHighlightPopover('test', 0);
    });

    afterEach(() => {
      mock.restore();
    });

    it('setup popover content', () => {
      const $popoverContent = $('.feature-highlight-popover-content');
      const outerHTML = $popoverContent.prop('outerHTML');

      expect($(selector).data('content')).toEqual(outerHTML);
    });

    fit('setup mouseenter', () => {
      const toggleSpy = spyOn(popover.togglePopover, 'call');
      $(selector).trigger('mouseenter');

      expect(toggleSpy).toHaveBeenCalledWith(jasmine.any(Object), true);
    });

    it('setup debounced mouseleave', (done) => {
      const toggleSpy = spyOn(popover.togglePopover, 'call');
      $(selector).trigger('mouseleave');

      // Even though we've set the debounce to 0ms, setTimeout is needed for the debounce
      setTimeout(() => {
        expect(toggleSpy).toHaveBeenCalledWith(jasmine.any(Object), false);
        done();
      }, 0);
    });

    it('setup show.bs.popover', () => {
      $(selector).trigger('show.bs.popover');
      expect(window.addEventListener).toHaveBeenCalledWith('scroll', jasmine.any(Function));
    });

    it('setup hide.bs.popover', () => {
      $(selector).trigger('hide.bs.popover');
      expect(window.removeEventListener).toHaveBeenCalledWith('scroll', jasmine.any(Function));
    });

    it('removes disabled attribute', () => {
      expect($('.js-feature-highlight').is(':disabled')).toEqual(false);
    });

    it('displays popover', () => {
      expect(document.querySelector(selector).getAttribute('aria-describedby')).toBeFalsy();
      $(selector).trigger('mouseenter');
      expect(document.querySelector(selector).getAttribute('aria-describedby')).toBeTruthy();
    });

    it('toggles when clicked', () => {
      $(selector).trigger('mouseenter');
      const popoverId = $(selector).attr('aria-describedby');
      const toggleSpy = spyOn(popover.togglePopover, 'call');

      $(`#${popoverId} .dismiss-feature-highlight`).click();

      expect(toggleSpy).toHaveBeenCalled();
    });
  });

  describe('findHighestPriorityFeature', () => {
    beforeEach(() => {
      setFixtures(`

      `);
    });

    it('should pick the highest priority feature highlight', () => {
      setFixtures(`

      `);

      expect($('.js-feature-highlight').length).toBeGreaterThan(1);
      expect(featureHighlight.findHighestPriorityFeature()).toEqual('test-high-priority');
    });

    it('should work when no priority is set', () => {
      setFixtures(`

      `);

      expect(featureHighlight.findHighestPriorityFeature()).toEqual('test');
    });

    it('should pick the highest priority feature highlight when some have no priority set', () => {
      setFixtures(`

      `);

      expect($('.js-feature-highlight').length).toBeGreaterThan(1);
      expect(featureHighlight.findHighestPriorityFeature()).toEqual('test-high-priority');
    });
  });

  describe('highlightFeatures', () => {
    it('calls setupFeatureHighlightPopover', () => {
      expect(featureHighlight.highlightFeatures()).toEqual('test');
    });
  });
});
<|endoftext|>


<commit_before>var ObjectID = require('mongodb').ObjectID;
var questions = require('./sampleData').questions;

module.exports = {
    createQuestion:       createQuestion,
    deleteQuestion:       deleteQuestion,
    getQuestions:         getQuestions,
    getQuestionById:      getQuestionById,
    getQuestionSummaries: getQuestionSummaries,
    ready:                ready,
    voteForQuestion:      voteForQuestion
};

///////////////////////////////////////////

function ready(cb){ cb(); } // ready immediately

function getQuestions(req, res) {
    res.send( sortSkipTake(req) );
}

function getQuestionById(req, res, next) {
    var question = findQuestionById(req);
    if (question){
        res.send(question);
    } else {
        questionNotFound(next);
    }
}

function createQuestion(req, res, next) {

    // get the new data from the POST body
    var body = req.body || {};

    // Error checking
    if (!body.text){
        var err = new Error('New question must have text');
        err.status=400;
        next(err);
        return;
    }

    else if ( questionExists(body.text) ){
        err = new Error('That question has been asked already');
        err.status=403; // forbidden
        next(err);
        return;
    }

    // Build the question and save it
    var question = {
        _id: ObjectID(),
        created: Date.now(),
        text: body.text.trim(),
        category: body.category || 'unknown',
        name: body.name || '',
        voteCount: 0,
        votes: []
    };

    questions.push(question);

    res.send(question);

    ////////
    function questionExists(text){
        return questions.filter(function(q){
            return q.text.toLowerCase() === text.toLowerCase().trim()}
        ).length > 0;
    }
}

function voteForQuestion(req, res, next) {
    var question = findQuestionById(req);
    if (question){
        addVote(question, req);
        res.send(question);
    } else {
        questionNotFound(next);
    }

    function addVote(question, req){

        var votes = question.votes;

        // Get new votes from the body (or default to +1)
        var vote = parseInt(req.body && req.body.vote, 10);
        vote = isNaN(vote) ? 1 : vote;

        // Add a "vote event" to the votes array
        votes.push({ vote: vote, time: Date.now() });

        // Recalculate total vote count
        question.voteCount = votes.reduce(function(count, v){
                return count += v.vote || 0;}
            , 0);
    }
}

function getQuestionSummaries(req, res) {
    res.send( summarize( sortSkipTake(req) ) );
}

function deleteQuestion(req, res, next) {
    var question = findQuestionById(req);
    if (question){

        // delete it from the in-memory array with "splice"
        questions.splice(questions.indexOf(question), 1);

        // respond with status and no content.
        res.status(204).end();

    } else {
        questionNotFound(next);
    }
}

////////////////////

function notImplemented(req, res, next) {
    next(new Error(
            req.method + ' ' +
            req.originalUrl + ' is not implemented'
    ));
}

function findQuestionById(req){
    var id = req.params.id;

    if (id) {
        return questions.filter(function(q){
            return q._id.toString() === id;
        })[0];
    }
}

function questionNotFound(next){
    // Create an error and let downstream middleware handle it.
    var err = new Error('Question not found');
    err.status = 404;
    next(err);
}

function sortSkipTake(req){
    // copy the Questions collection so we can sort the copy
    var quests  = questions.slice();
    var sort = req.query.sort;

    if (sort === 'votes'){
        quests.sort(function(a, b){
            return a.voteCount < b.voteCount ? 1 : -1;
        });
    }

    else if (sort === 'time') {
        quests.sort(function(a, b){
            return a.created < b.created ? -1 : 1;
        });
    }

    var skip = parseInt(req.query.offset, 10);
    skip = isNaN(skip) ? 0 : skip;

    var take = parseInt(req.query.limit, 10);
    take = isNaN(take) ? quests.length : take;

    return quests.slice(skip, skip + take);
}

function summarize(quests){
    return quests.map(function(q){ return {
        _id: q._id,
        category: q.category,
        text: q.text,
        created: q.created,
        voteCount : q.voteCount
    };})
}
<commit_msg>Added notifier logic to memQuestionsService<commit_after>var ObjectID = require('mongodb').ObjectID;
var notifier = require('./notifier');
var questions = require('./sampleData').questions;

module.exports = {
    createQuestion:       createQuestion,
    deleteQuestion:       deleteQuestion,
    getQuestions:         getQuestions,
    getQuestionById:      getQuestionById,
    getQuestionSummaries: getQuestionSummaries,
    ready:                ready,
    voteForQuestion:      voteForQuestion
};

///////////////////////////////////////////

function ready(cb){ cb(); } // ready immediately

function getQuestions(req, res) {
    res.send( sortSkipTake(req) );
}

function getQuestionById(req, res, next) {
    var question = findQuestionById(req);
    if (question){
        res.send(question);
    } else {
        questionNotFound(next);
    }
}

function createQuestion(req, res, next) {

    // get the new data from the POST body
    var body = req.body || {};

    // Error checking
    if (!body.text){
        var err = new Error('New question must have text');
        err.status=400;
        next(err);
        return;
    }

    else if ( questionExists(body.text) ){
        err = new Error('That question has been asked already');
        err.status=403; // forbidden
        next(err);
        return;
    }

    // Build the question and save it
    var question = {
        _id: ObjectID(),
        created: Date.now(),
        text: body.text.trim(),
        category: body.category || 'unknown',
        name: body.name || '',
        voteCount: 0,
        votes: []
    };

    questions.push(question);

    notifier.questionAdded(question);

    res.send(question);

    ////////
    function questionExists(text){
        return questions.filter(function(q){
            return q.text.toLowerCase() === text.toLowerCase().trim()}
        ).length > 0;
    }
}

function voteForQuestion(req, res, next) {
    var question = findQuestionById(req);
    if (question){
        addVote(question, req);
        notifier.voteAdded(question);
        res.send(question);
    } else {
        questionNotFound(next);
    }

    function addVote(question, req){

        var votes = question.votes;

        // Get new votes from the body (or default to +1)
        var vote = parseInt(req.body && req.body.vote, 10);
        vote = isNaN(vote) ? 1 : vote;

        // Add a "vote event" to the votes array
        votes.push({ vote: vote, time: Date.now() });

        // Recalculate total vote count
        question.voteCount = votes.reduce(function(count, v){
                return count += v.vote || 0;}
            , 0);
    }
}

function getQuestionSummaries(req, res) {
    res.send( summarize( sortSkipTake(req) ) );
}

function deleteQuestion(req, res, next) {
    var question = findQuestionById(req);
    if (question){

        // delete it from the in-memory array with "splice"
        questions.splice(questions.indexOf(question), 1);

        notifier.questionDeleted(question);

        // respond with status and no content.
        res.status(204).end();

    } else {
        questionNotFound(next);
    }
}

////////////////////

function notImplemented(req, res, next) {
    next(new Error(
            req.method + ' ' +
            req.originalUrl + ' is not implemented'
    ));
}

function findQuestionById(req){
    var id = req.params.id;

    if (id) {
        return questions.filter(function(q){
            return q._id.toString() === id;
        })[0];
    }
}

function questionNotFound(next){
    // Create an error and let downstream middleware handle it.
    var err = new Error('Question not found');
    err.status = 404;
    next(err);
}

function sortSkipTake(req){
    // copy the Questions collection so we can sort the copy
    var quests  = questions.slice();
    var sort = req.query.sort;

    if (sort === 'votes'){
        quests.sort(function(a, b){
            return a.voteCount < b.voteCount ? 1 : -1;
        });
    }

    else if (sort === 'time') {
        quests.sort(function(a, b){
            return a.created < b.created ? -1 : 1;
        });
    }

    var skip = parseInt(req.query.offset, 10);
    skip = isNaN(skip) ? 0 : skip;

    var take = parseInt(req.query.limit, 10);
    take = isNaN(take) ? quests.length : take;

    return quests.slice(skip, skip + take);
}

function summarize(quests){
    return quests.map(function(q){ return {
        _id: q._id,
        category: q.category,
        text: q.text,
        created: q.created,
        voteCount : q.voteCount
    };})
}
<|endoftext|>


<commit_before>var express = require('express');
var team = require('../../models/team');
var router = express.Router();

router.get('', (request, response) =>
    team.listAll(request.session.user_id)
        .then(results => response.jsonResponse({
            teams: results
        })).catch(error => response.jsonError(error, 500)));

router.put('', (request, response) =>
    team.create(request.body, request.session.user_id)
        .then(results => response.jsonResponse({
            team: results
        })).catch(error => response.jsonError(error, 500)));

router.get('/:teamid', (request, response) =>
    team.fetchInfo(request.params.teamid, request.body, request.session.user_id)
        .then(results => response.jsonResponse({
            team: results
        })).catch(error => response.jsonError(error, 500)));

router.put('/:teamid', (request, response) =>
    team.addMember(request.params.teamid, request.body, request.session.user_id)
        .then(results => response.jsonResponse({
            message: "Member added to team successfully!"
        })).catch(error => response.jsonError(error, 500)));

router.post('/:teamid', (request, response) =>
    team.update(request.params.teamid, request.body, request.session.user_id)
        .then(results => response.jsonResponse({
            message: "Team updated successfully!"
        })).catch(error => response.jsonError(error, 500)));

module.exports = router;<commit_msg>Minor fix<commit_after>var express = require('express');
var team = require('../../models/team');
var router = express.Router();

router.get('', (request, response) =>
    team.listAll(request.session.user_id)
        .then(results => response.jsonResponse({
            teams: results
        })).catch(error => response.jsonError(error, 500)));

router.put('', (request, response) =>
    team.create(request.body, request.session.user_id)
        .then(results => response.jsonResponse({
            team: results
        })).catch(error => response.jsonError(error, 500)));

router.get('/:teamid', (request, response) =>
    team.fetchInfo(request.params.teamid, request.session.user_id)
        .then(results => response.jsonResponse({
            team: results
        })).catch(error => response.jsonError(error, 500)));

router.put('/:teamid', (request, response) =>
    team.addMember(request.params.teamid, request.body, request.session.user_id)
        .then(results => response.jsonResponse({
            message: "Member added to team successfully!"
        })).catch(error => response.jsonError(error, 500)));

router.post('/:teamid', (request, response) =>
    team.update(request.params.teamid, request.body, request.session.user_id)
        .then(results => response.jsonResponse({
            message: "Team updated successfully!"
        })).catch(error => response.jsonError(error, 500)));

module.exports = router;<|endoftext|>


<commit_before>define(['js/svg/Svg'], function(Svg) {

    return Svg.inherit("sprd.view.svg.BendingTextConfigurationUploadRendererClass", {

        defaults: {
            configuration: null,
            width: "{width()}mm"
        },

        ctor: function() {
            this.callBase();
            var config = this.$.configuration;
            config.uploadRenderer = this;
        },

        _initializationComplete: function() {
            this.removeChild(this.$.hidden);
            this.removeChild(this.$.defs);
        },

        getText: function() {
            var config = this.$.configuration;
            if (config) {
                var text = config.$.text.replace(/\n/g, " ");
                return text.trim();
            }
        },

        $classAttributes: ['textPath', 'path', 'configuration', 'x', 'y', 'text', 'viewBox'],

        width: function() {
            var config = this.$.configuration;
            return config ? Math.round(config.widthInMM()) : 0;
        }.on('configuration.widthInMM()'),

        getElement: function() {
            var textBbox = this.$.text.$el.getBBox(),
                svgNamespace = 'http://www.w3.org/2000/svg',
                xlinkNS = 'http://www.w3.org/1999/xlink',
                elem = this.$el;

            if (!elem) {
                return null;
            }

            elem.setAttribute("xmlns", svgNamespace);
            elem.setAttribute("xmlns:xlink", xlinkNS);

            this.setViewBox(textBbox.x, textBbox.y, textBbox.width, textBbox.height);
            return elem;
        },

        getElementAsString: function() {
            var elem = this.getElement(),
                docString = '<!DOCTYPE svg [ <!ENTITY nbsp "  ">] >',
                svgContent = elem.outerHTML;

            svgContent = svgContent.replace(/\b[a-z0-9]+:href/ig, "xlink:href");
            return docString + svgContent;
        }
    });
});
<commit_msg>Sending viewBox over for bendingText upload<commit_after>define(['js/svg/Svg'], function(Svg) {

    return Svg.inherit("sprd.view.svg.BendingTextConfigurationUploadRendererClass", {

        defaults: {
            configuration: null,
            width: "{width()}mm"
        },

        ctor: function() {
            this.callBase();
            var config = this.$.configuration;
            config.uploadRenderer = this;
        },

        _initializationComplete: function() {
            this.removeChild(this.$.hidden);
            this.removeChild(this.$.defs);
        },

        getText: function() {
            var config = this.$.configuration;
            if (config) {
                var text = config.$.text.replace(/\n/g, " ");
                return text.trim();
            }
        },

        $classAttributes: ['textPath', 'path', 'configuration', 'x', 'y', 'text'],

        width: function() {
            var config = this.$.configuration;
            return config ? Math.round(config.widthInMM()) : 0;
        }.on('configuration.widthInMM()'),

        getElement: function() {
            var textBbox = this.$.text.$el.getBBox(),
                svgNamespace = 'http://www.w3.org/2000/svg',
                xlinkNS = 'http://www.w3.org/1999/xlink',
                elem = this.$el;

            if (!elem) {
                return null;
            }

            elem.setAttribute("xmlns", svgNamespace);
            elem.setAttribute("xmlns:xlink", xlinkNS);

            this.setViewBox(textBbox.x, textBbox.y, textBbox.width, textBbox.height);
            return elem;
        },

        getElementAsString: function() {
            var elem = this.getElement(),
                docString = '<!DOCTYPE svg [ <!ENTITY nbsp "  ">] >',
                svgContent = elem.outerHTML;

            svgContent = svgContent.replace(/\b[a-z0-9]+:href/ig, "xlink:href");
            return docString + svgContent;
        }
    });
});
<|endoftext|>


<commit_before>angular.module('GeoAngular').controller('AppCtrl', function($scope, $rootScope, $routeParams, Route) {
  console.log('AppCtrl');

  // weird bug where redirect peels out '://{s' when ':' is there
  // $routeParams.layers We just dont have the : in main.js so that
  // part of the path does not go away...
  $routeParams.layers = $routeParams.layers.replace('http//', 'http://');

  window.RouteParams = $routeParams;
  $scope.routeParams = window.RouteParams;

  RouteParams.landing = false;

  // Update the route when the model is updated.
  $scope.$watchCollection('routeParams', function(newParams, oldParams) {
    console.log('app.js $watchCollection(routeParams).');
    $rootScope.$broadcast('route-update');
    Route.updateLocation();
  });

});
<commit_msg>Adding a resize function for the map. Not sure where to put it.<commit_after>angular.module('GeoAngular').controller('AppCtrl', function($scope, $rootScope, $routeParams, Route) {
  console.log('AppCtrl');

  // weird bug where redirect peels out '://{s' when ':' is there
  // $routeParams.layers We just dont have the : in main.js so that
  // part of the path does not go away...
  $routeParams.layers = $routeParams.layers.replace('http//', 'http://');

  window.RouteParams = $routeParams;
  $scope.routeParams = window.RouteParams;

  RouteParams.landing = false;

  // Update the route when the model is updated.
  $scope.$watchCollection('routeParams', function(newParams, oldParams) {
    console.log('app.js $watchCollection(routeParams).');
    $rootScope.$broadcast('route-update');
    Route.updateLocation();
  });

  //RW - Need to figure out where to put this:
  /*var resizeTracker;
    window.resize =function () {
        map.invalidateSize();

        clearTimeout(resizeTracker);
        resizeTracker = setTimeout(function () {
            map.invalidateSize();
        }, 100);
    };*/

});
<|endoftext|>


<commit_before>import { createWidget } from 'widget'
import * as reducers from './reducers';
import App from './components/application';

createWidget(__dirname, {html: {class: 'flex-body'}}).then((widget) => {
  widget.configureAjaxHelper(
    {
      baseURL: widget.config.scriptParams.url
    }
  )
  widget.setPolicy()
  widget.createStore(reducers)
  widget.render(App)
})
<commit_msg>cloudops: do not reset baseURL on widget init<commit_after>import { createWidget } from 'widget'
import * as reducers from './reducers';
import App from './components/application';

createWidget(__dirname, {html: {class: 'flex-body'}}).then((widget) => {
  widget.configureAjaxHelper()
  widget.setPolicy()
  widget.createStore(reducers)
  widget.render(App)
})
<|endoftext|>


<commit_before>function get(state, key) {
  return state.routes.find(route => route.key === key) || null;
}

function indexOf(state, key) {
  return state.routes.map(route => route.key).indexOf(key);
}

function has(state, key) {
  return !!state.routes.some(route => route.key === key);
}

function push(state, route) {
  if (indexOf(state, route.key) === -1) {
    throw new Error('should not push route with duplicated key ' + route.key);
  }

  const routes = [
    ...state.routes,
    route,
  ];

  return {
    ...state,
    index: routes.length - 1,
    routes,
  };
}

function pop(state) {
  if (state.index <= 0) {
    return state;
  }
  const routes = state.routes.slice(0, -1);
  return {
    ...state,
    index: routes.length - 1,
    routes,
  };
}

function jumpToIndex(state, index: number) {
  if (index === state.index) {
    return state;
  }

  if (!state.routes[index]) {
    throw new Error('invalid index ' + index + ' to jump to');
  }

  return {
    ...state,
    index,
  };
}

function jumpTo(state, key) {
  const index = indexOf(state, key);
  return jumpToIndex(state, index);
}

function replaceAtIndex(state, index, route) {
  if (!state.routes[index]) {
    throw new Error('invalid index ' + index + ' for replacing route ' + route.key);
  }

  if (state.routes[index] === route) {
    return state;
  }

  const routes = state.routes.slice();
  routes[index] = route;

  return {
    ...state,
    index,
    routes,
  };
}

function replaceAt(state, key, route) {
  const index = indexOf(state, key);
  return replaceAtIndex(state, index, route);
}

function reset(state, routes, index = null) {
  if (!routes.length && Array.isArray(routes)) {
    throw new Error('invalid routes to replace');
  }

  const nextIndex = index === undefined ? routes.length - 1 : index;

  if (state.routes.length === routes.length && state.index === nextIndex) {
    const compare = (route, ii) => routes[ii] === route;
    if (state.routes.every(compare)) {
      return state;
    }
  }

  if (!routes[nextIndex]) {
    throw new Error('invalid index ' + nextIndex + ' to reset');
  }

  return {
    ...state,
    index: nextIndex,
    routes,
  };
}

module.exports = {
  get,
  has,
  indexOf,
  jumpTo,
  jumpToIndex,
  pop,
  push,
  replaceAt,
  replaceAtIndex,
  reset,
};
<commit_msg>NavigatorExperimental: State should not have the new route<commit_after>function get(state, key) {
  return state.routes.find(route => route.key === key) || null;
}

function indexOf(state, key) {
  return state.routes.map(route => route.key).indexOf(key);
}

function has(state, key) {
  return !!state.routes.some(route => route.key === key);
}

function push(state, route) {
  if (indexOf(state, route.key) !== -1) {
    throw new Error('should not push route with duplicated key ' + route.key);
  }

  const routes = [
    ...state.routes,
    route,
  ];

  return {
    ...state,
    index: routes.length - 1,
    routes,
  };
}

function pop(state) {
  if (state.index <= 0) {
    return state;
  }
  const routes = state.routes.slice(0, -1);
  return {
    ...state,
    index: routes.length - 1,
    routes,
  };
}

function jumpToIndex(state, index: number) {
  if (index === state.index) {
    return state;
  }

  if (!state.routes[index]) {
    throw new Error('invalid index ' + index + ' to jump to');
  }

  return {
    ...state,
    index,
  };
}

function jumpTo(state, key) {
  const index = indexOf(state, key);
  return jumpToIndex(state, index);
}

function replaceAtIndex(state, index, route) {
  if (!state.routes[index]) {
    throw new Error('invalid index ' + index + ' for replacing route ' + route.key);
  }

  if (state.routes[index] === route) {
    return state;
  }

  const routes = state.routes.slice();
  routes[index] = route;

  return {
    ...state,
    index,
    routes,
  };
}

function replaceAt(state, key, route) {
  const index = indexOf(state, key);
  return replaceAtIndex(state, index, route);
}

function reset(state, routes, index = null) {
  if (!routes.length && Array.isArray(routes)) {
    throw new Error('invalid routes to replace');
  }

  const nextIndex = index === undefined ? routes.length - 1 : index;

  if (state.routes.length === routes.length && state.index === nextIndex) {
    const compare = (route, ii) => routes[ii] === route;
    if (state.routes.every(compare)) {
      return state;
    }
  }

  if (!routes[nextIndex]) {
    throw new Error('invalid index ' + nextIndex + ' to reset');
  }

  return {
    ...state,
    index: nextIndex,
    routes,
  };
}

module.exports = {
  get,
  has,
  indexOf,
  jumpTo,
  jumpToIndex,
  pop,
  push,
  replaceAt,
  replaceAtIndex,
  reset,
};
<|endoftext|>


<commit_before>import React from 'react';

import { formatPercentage } from 'common/format';
import { STATISTIC_ORDER } from 'Main/StatisticBox';

import CoreAlwaysBeCasting from 'Parser/Core/Modules/AlwaysBeCasting';
import SPELLS from 'common/SPELLS';

import Haste from '../Core/Haste';

const ONE_FILLER_GCD_HASTE_THRESHOLD = 1.4;

class AlwaysBeCasting extends CoreAlwaysBeCasting {
  static dependencies = {
    haste: Haste,
  };

  static ABILITIES_ON_GCD = [
    // handled in _removebuff
    SPELLS.VOID_TORRENT.id,
    SPELLS.MIND_FLAY.id,
    // SPELLS.DISPERSION.id,

    // rotational:
    SPELLS.VOID_BOLT.id,
    SPELLS.VOID_ERUPTION.id,
    SPELLS.MIND_BLAST.id,
    SPELLS.VAMPIRIC_TOUCH.id,
    SPELLS.SHADOW_WORD_DEATH.id,
    SPELLS.SHADOW_WORD_PAIN.id,
    SPELLS.SHADOWFIEND.id,
    SPELLS.SHADOWFIEND_WITH_GLYPH_OF_THE_SHA.id,

    // talents:
    SPELLS.MINDBENDER_TALENT_SHADOW.id,
    SPELLS.POWER_INFUSION_TALENT.id,
    SPELLS.SHADOW_CRASH_TALENT.id,
    SPELLS.SHADOW_WORD_VOID_TALENT.id,
    SPELLS.MIND_BOMB_TALENT.id,

    // utility:
    SPELLS.SHADOWFORM.id,
    SPELLS.MIND_VISION.id,
    SPELLS.POWER_WORD_SHIELD.id,
    SPELLS.SHADOW_MEND.id,
    SPELLS.DISPEL_MAGIC.id,
    SPELLS.MASS_DISPEL.id,
    SPELLS.LEVITATE.id,
    SPELLS.SHACKLE_UNDEAD.id,
    SPELLS.PURIFY_DISEASE.id,

  ];

  _castsSinceLastVoidBolt = 0;
  _skippableCastsBetweenVoidbolts = 0;

  get skippableCastsBetweenVoidbolts() {
    return this._skippableCastsBetweenVoidbolts;
  }

  on_byPlayer_cast(event) {
    const spellId = event.ability.guid;
    if (this.haste.current >= ONE_FILLER_GCD_HASTE_THRESHOLD) {
      if (spellId === SPELLS.VOID_BOLT.id) {
        this._castsSinceLastVoidBolt = 0;
      } else if (this.isOnGlobalCooldown(spellId)) {
        this._castsSinceLastVoidBolt += 1;
        if (this._castsSinceLastVoidBolt > 1) {
          this._skippableCastsBetweenVoidbolts += 1;
        }
      }
    }

    super.on_byPlayer_cast(event);
  }

  on_toPlayer_removebuff(event) {
    const spellId = event.ability.guid;
    if (
      spellId === SPELLS.MIND_FLAY.id ||
      // spellId === SPELLS.DISPERSION.id ||
      spellId === SPELLS.VOID_TORRENT.id
    ) {
      this._lastCastFinishedTimestamp = event.timestamp;
    }
  }

  get suggestionThresholds() {
    return {
      actual: this.downtimePercentage,
      isGreaterThan: {
        minor: 0.1,
        average: 0.15,
        major: 0.2,
      },
      style: 'percentage',
    };
  }

  suggestions(when) {
    const {
      actual,
      isGreaterThan: {
        minor,
        average,
        major,
      },
    } = this.suggestionThresholds;

    when(actual).isGreaterThan(minor)
      .addSuggestion((suggest, actual, recommended) => {
        return suggest(Your downtime can be improved. Try to Always Be Casting (ABC), try to reduce the delay between casting spells. Even if you have to move, try casting something instant - maybe refresh your dots.)
          .icon('spell_mage_altertime')
          .actual(`${formatPercentage(actual)}% downtime`)
          .recommended(`<${formatPercentage(recommended)}% is recommended`)
          .regular(average).major(major);
      });
  }

  statisticOrder = STATISTIC_ORDER.CORE(6);
}

export default AlwaysBeCasting;
<commit_msg>Fix crash in Shadow Priest caused by the `isOnGCD` changes<commit_after>import React from 'react';

import { formatPercentage } from 'common/format';
import { STATISTIC_ORDER } from 'Main/StatisticBox';

import CoreAlwaysBeCasting from 'Parser/Core/Modules/AlwaysBeCasting';
import SPELLS from 'common/SPELLS';

import Haste from '../Core/Haste';

const ONE_FILLER_GCD_HASTE_THRESHOLD = 1.4;

class AlwaysBeCasting extends CoreAlwaysBeCasting {
  static dependencies = {
    ...CoreAlwaysBeCasting.dependencies,
    haste: Haste,
  };

  static ABILITIES_ON_GCD = [
    // handled in _removebuff
    SPELLS.VOID_TORRENT.id,
    SPELLS.MIND_FLAY.id,
    // SPELLS.DISPERSION.id,

    // rotational:
    SPELLS.VOID_BOLT.id,
    SPELLS.VOID_ERUPTION.id,
    SPELLS.MIND_BLAST.id,
    SPELLS.VAMPIRIC_TOUCH.id,
    SPELLS.SHADOW_WORD_DEATH.id,
    SPELLS.SHADOW_WORD_PAIN.id,
    SPELLS.SHADOWFIEND.id,
    SPELLS.SHADOWFIEND_WITH_GLYPH_OF_THE_SHA.id,

    // talents:
    SPELLS.MINDBENDER_TALENT_SHADOW.id,
    SPELLS.POWER_INFUSION_TALENT.id,
    SPELLS.SHADOW_CRASH_TALENT.id,
    SPELLS.SHADOW_WORD_VOID_TALENT.id,
    SPELLS.MIND_BOMB_TALENT.id,

    // utility:
    SPELLS.SHADOWFORM.id,
    SPELLS.MIND_VISION.id,
    SPELLS.POWER_WORD_SHIELD.id,
    SPELLS.SHADOW_MEND.id,
    SPELLS.DISPEL_MAGIC.id,
    SPELLS.MASS_DISPEL.id,
    SPELLS.LEVITATE.id,
    SPELLS.SHACKLE_UNDEAD.id,
    SPELLS.PURIFY_DISEASE.id,

  ];

  _castsSinceLastVoidBolt = 0;
  _skippableCastsBetweenVoidbolts = 0;

  get skippableCastsBetweenVoidbolts() {
    return this._skippableCastsBetweenVoidbolts;
  }

  on_byPlayer_cast(event) {
    const spellId = event.ability.guid;
    if (this.haste.current >= ONE_FILLER_GCD_HASTE_THRESHOLD) {
      if (spellId === SPELLS.VOID_BOLT.id) {
        this._castsSinceLastVoidBolt = 0;
      } else if (this.isOnGlobalCooldown(spellId)) {
        this._castsSinceLastVoidBolt += 1;
        if (this._castsSinceLastVoidBolt > 1) {
          this._skippableCastsBetweenVoidbolts += 1;
        }
      }
    }

    super.on_byPlayer_cast(event);
  }

  on_toPlayer_removebuff(event) {
    const spellId = event.ability.guid;
    if (
      spellId === SPELLS.MIND_FLAY.id ||
      // spellId === SPELLS.DISPERSION.id ||
      spellId === SPELLS.VOID_TORRENT.id
    ) {
      this._lastCastFinishedTimestamp = event.timestamp;
    }
  }

  get suggestionThresholds() {
    return {
      actual: this.downtimePercentage,
      isGreaterThan: {
        minor: 0.1,
        average: 0.15,
        major: 0.2,
      },
      style: 'percentage',
    };
  }

  suggestions(when) {
    const {
      actual,
      isGreaterThan: {
        minor,
        average,
        major,
      },
    } = this.suggestionThresholds;

    when(actual).isGreaterThan(minor)
      .addSuggestion((suggest, actual, recommended) => {
        return suggest(Your downtime can be improved. Try to Always Be Casting (ABC), try to reduce the delay between casting spells. Even if you have to move, try casting something instant - maybe refresh your dots.)
          .icon('spell_mage_altertime')
          .actual(`${formatPercentage(actual)}% downtime`)
          .recommended(`<${formatPercentage(recommended)}% is recommended`)
          .regular(average).major(major);
      });
  }

  statisticOrder = STATISTIC_ORDER.CORE(6);
}

export default AlwaysBeCasting;
<|endoftext|>