text: /**
   * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
   * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
   */
  
  /**
   * @fileOverview The "Notification Aggregator" plugin.
   *
   */
  
  ( function() {
  	'use strict';
  
  	CKEDITOR.plugins.add( 'notificationaggregator', {
  		requires: 'notification'
  	} );
  
  	/**
  	 * An aggregator of multiple tasks (progresses) which should be displayed using one
  	 * {@link CKEDITOR.plugins.notification notification}.
  	 *
  	 * Once all the tasks are done, it means that the whole process is finished and the {@link #finished}
  	 * event will be fired.
  	 *
  	 * New tasks can be created after the previous set of tasks is finished. This will continue the process and
  	 * fire the {@link #finished} event again when it is done.
  	 *
  	 * Simple usage example:
  	 *
  	 *		// Declare one aggregator that will be used for all tasks.
  	 *		var aggregator;
  	 *
  	 *		// ...
  	 *
  	 *		// Create a new aggregator if the previous one finished all tasks.
  	 *		if ( !aggregator || aggregator.isFinished() ) {
  	 *			// Create a new notification aggregator instance.
  	 *			aggregator = new CKEDITOR.plugins.notificationAggregator( editor, 'Loading process, step {current} of {max}...' );
  	 *
  	 *			// Change the notification type to 'success' on finish.
  	 *			aggregator.on( 'finished', function() {
  	 *				aggregator.notification.update( { message: 'Done', type: 'success' } );
  	 *			} );
  	 *		}
  	 *
  	 *		// Create 3 tasks.
  	 *		var taskA = aggregator.createTask(),
  	 *			taskB = aggregator.createTask(),
  	 *			taskC = aggregator.createTask();
  	 *
  	 *		// At this point the notification contains a message: "Loading process, step 0 of 3...".
  	 *
  	 *		// Let's close the first one immediately.
  	 *		taskA.done(); // "Loading process, step 1 of 3...".
  	 *
  	 *		// One second later the message will be: "Loading process, step 2 of 3...".
  	 *		setTimeout( function() {
  	 *			taskB.done();
  	 *		}, 1000 );
  	 *
  	 *		// Two seconds after the previous message the last task will be completed, meaning that
  	 *		// the notification will be closed.
  	 *		setTimeout( function() {
  	 *			taskC.done();
  	 *		}, 3000 );
  	 *
  	 * @since 4.5
  	 * @class CKEDITOR.plugins.notificationAggregator
  	 * @mixins CKEDITOR.event
  	 * @constructor Creates a notification aggregator instance.
  	 * @param {CKEDITOR.editor} editor
  	 * @param {String} message The template for the message to be displayed in the notification. The template can use
  	 * the following variables:
  	 *
  	 * * `{current}` &ndash; The number of completed tasks.
  	 * * `{max}` &ndash; The number of tasks.
  	 * * `{percentage}` &ndash; The progress (number 0-100).
  	 *
  	 * @param {String/null} [singularMessage=null] An optional template for the message to be displayed in the notification
  	 * when there is only one task remaining.  This template can use the same variables as the `message` template.
  	 * If `null`, then the `message` template will be used.
  	 */
  	function Aggregator( editor, message, singularMessage ) {
  		/**
  		 * @readonly
  		 * @property {CKEDITOR.editor} editor
  		 */
  		this.editor = editor;
  
  		/**
  		 * Notification created by the aggregator.
  		 *
  		 * The notification object is modified as aggregator tasks are being closed.
  		 *
  		 * @readonly
  		 * @property {CKEDITOR.plugins.notification/null}
  		 */
  		this.notification = null;
  
  		/**
  		 * A template for the notification message.
  		 *
  		 * The template can use the following variables:
  		 *
  		 * * `{current}` &ndash; The number of completed tasks.
  		 * * `{max}` &ndash; The number of tasks.
  		 * * `{percentage}` &ndash; The progress (number 0-100).
  		 *
  		 * @private
  		 * @property {CKEDITOR.template}
  		 */
  		this._message = new CKEDITOR.template( message );
  
  		/**
  		 * A template for the notification message used when only one task is loading.
  		 *
  		 * Sometimes there might be a need to specify a special message when there
  		 * is only one task loading, and to display standard messages in other cases.
  		 *
  		 * For example, you might want to show a message "Translating a widget" rather than
  		 * "Translating widgets (1 of 1)", but still you would want to have a message
  		 * "Translating widgets (2 of 3)" if more widgets are being translated at the same
  		 * time.
  		 *
  		 * Template variables are the same as in {@link #_message}.
  		 *
  		 * @private
  		 * @property {CKEDITOR.template/null}
  		 */
  		this._singularMessage = singularMessage ? new CKEDITOR.template( singularMessage ) : null;
  
  		// Set the _tasks, _totalWeights, _doneWeights, _doneTasks properties.
  		this._tasks = [];
  		this._totalWeights = 0;
  		this._doneWeights = 0;
  		this._doneTasks = 0;
  
  		/**
  		 * Array of tasks tracked by the aggregator.
  		 *
  		 * @private
  		 * @property {CKEDITOR.plugins.notificationAggregator.task[]} _tasks
  		 */
  
  		/**
  		 * Stores the sum of declared weights for all contained tasks.
  		 *
  		 * @private
  		 * @property {Number} _totalWeights
  		 */
  
  		/**
  		 * Stores the sum of done weights for all contained tasks.
  		 *
  		 * @private
  		 * @property {Number} _doneWeights
  		 */
  
  		/**
  		 * Stores the count of tasks done.
  		 *
  		 * @private
  		 * @property {Number} _doneTasks
  		 */
  	}
  
  	Aggregator.prototype = {
  		/**
  		 * Creates a new task that can be updated to indicate the progress.
  		 *
  		 * @param [options] Options object for the task creation.
  		 * @param [options.weight] For more information about weight, see the
  		 * {@link CKEDITOR.plugins.notificationAggregator.task} overview.
  		 * @returns {CKEDITOR.plugins.notificationAggregator.task} An object that represents the task state, and allows
  		 * for its manipulation.
  		 */
  		createTask: function( options ) {
  			options = options || {};
  
  			var initialTask = !this.notification,
  				task;
  
  			if ( initialTask ) {
  				// It's a first call.
  				this.notification = this._createNotification();
  			}
  
  			task = this._addTask( options );
  
  			task.on( 'updated', this._onTaskUpdate, this );
  			task.on( 'done', this._onTaskDone, this );
  			task.on( 'canceled', function() {
  				this._removeTask( task );
  			}, this );
  
  			// Update the aggregator.
  			this.update();
  
  			if ( initialTask ) {
  				this.notification.show();
  			}
  
  			return task;
  		},
  
  		/**
  		 * Triggers an update on the aggregator, meaning that its UI will be refreshed.
  		 *
  		 * When all the tasks are done, the {@link #finished} event is fired.
  		 */
  		update: function() {
  			this._updateNotification();
  
  			if ( this.isFinished() ) {
  				this.fire( 'finished' );
  			}
  		},
  
  		/**
  		 * Returns a number from `0` to `1` representing the done weights to total weights ratio
  		 * (showing how many of the tasks are done).
  		 *
  		 * Note: For an empty aggregator (without any tasks created) it will return `1`.
  		 *
  		 * @returns {Number} Returns the percentage of tasks done as a number ranging from `0` to `1`.
  		 */
  		getPercentage: function() {
  			// In case there are no weights at all we'll return 1.
  			if ( this.getTaskCount() === 0 ) {
  				return 1;
  			}
  
  			return this._doneWeights / this._totalWeights;
  		},
  
  		/**
  		 * @returns {Boolean} Returns `true` if all notification tasks are done
  		 * (or there are no tasks at all).
  		 */
  		isFinished: function() {
  			return this.getDoneTaskCount() === this.getTaskCount();
  		},
  
  		/**
  		 * @returns {Number} Returns a total tasks count.
  		 */
  		getTaskCount: function() {
  			return this._tasks.length;
  		},
  
  		/**
  		 * @returns {Number} Returns the number of tasks done.
  		 */
  		getDoneTaskCount: function() {
  			return this._doneTasks;
  		},
  
  		/**
  		 * Updates the notification content.
  		 *
  		 * @private
  		 */
  		_updateNotification: function() {
  			this.notification.update( {
  				message: this._getNotificationMessage(),
  				progress: this.getPercentage()
  			} );
  		},
  
  		/**
  		 * Returns a message used in the notification.
  		 *
  		 * @private
  		 * @returns {String}
  		 */
  		_getNotificationMessage: function() {
  			var tasksCount = this.getTaskCount(),
  				doneTasks = this.getDoneTaskCount(),
  				templateParams = {
  					current: doneTasks,
  					max: tasksCount,
  					percentage: Math.round( this.getPercentage() * 100 )
  				},
  				template;
  
  			// If there's only one remaining task and we have a singular message, we should use it.
  			if ( tasksCount == 1 && this._singularMessage ) {
  				template = this._singularMessage;
  			} else {
  				template = this._message;
  			}
  
  			return template.output( templateParams );
  		},
  
  		/**
  		 * Creates a notification object.
  		 *
  		 * @private
  		 * @returns {CKEDITOR.plugins.notification}
  		 */
  		_createNotification: function() {
  			return new CKEDITOR.plugins.notification( this.editor, {
  				type: 'progress'
  			} );
  		},
  
  		/**
  		 * Creates a {@link CKEDITOR.plugins.notificationAggregator.task} instance based
  		 * on `options`, and adds it to the task list.
  		 *
  		 * @private
  		 * @param options Options object coming from the {@link #createTask} method.
  		 * @returns {CKEDITOR.plugins.notificationAggregator.task}
  		 */
  		_addTask: function( options ) {
  			var task = new Task( options.weight );
  			this._tasks.push( task );
  			this._totalWeights += task._weight;
  			return task;
  		},
  
  		/**
  		 * Removes a given task from the {@link #_tasks} array and updates the UI.
  		 *
  		 * @private
  		 * @param {CKEDITOR.plugins.notificationAggregator.task} task Task to be removed.
  		 */
  		_removeTask: function( task ) {
  			var index = CKEDITOR.tools.indexOf( this._tasks, task );
  
  			if ( index !== -1 ) {
  				// If task was already updated with some weight, we need to remove
  				// this weight from our cache.
  				if ( task._doneWeight ) {
  					this._doneWeights -= task._doneWeight;
  				}
  
  				this._totalWeights -= task._weight;
  
  				this._tasks.splice( index, 1 );
  				// And we also should inform the UI about this change.
  				this.update();
  			}
  		},
  
  		/**
  		 * A listener called on the {@link CKEDITOR.plugins.notificationAggregator.task#update} event.
  		 *
  		 * @private
  		 * @param {CKEDITOR.eventInfo} evt Event object of the {@link CKEDITOR.plugins.notificationAggregator.task#update} event.
  		 */
  		_onTaskUpdate: function( evt ) {
  			this._doneWeights += evt.data;
  			this.update();
  		},
  
  		/**
  		 * A listener called on the {@link CKEDITOR.plugins.notificationAggregator.task#event-done} event.
  		 *
  		 * @private
  		 * @param {CKEDITOR.eventInfo} evt Event object of the {@link CKEDITOR.plugins.notificationAggregator.task#event-done} event.
  		 */
  		_onTaskDone: function() {
  			this._doneTasks += 1;
  			this.update();
  		}
  	};
  
  	CKEDITOR.event.implementOn( Aggregator.prototype );
  
  	/**
  	 * # Overview
  	 *
  	 * This type represents a single task in the aggregator, and exposes methods to manipulate its state.
  	 *
  	 * ## Weights
  	 *
  	 * Task progess is based on its **weight**.
  	 *
  	 * As you create a task, you need to declare its weight. As you want the update to inform about the
  	 * progress, you will need to {@link #update} the task, telling how much of this weight is done.
  	 *
  	 * For example, if you declare that your task has a weight that equals `50` and then call `update` with `10`,
  	 * you will end up with telling that the task is done in 20%.
  	 *
  	 * ### Example Usage of Weights
  	 *
  	 * Let us say that you use tasks for file uploading.
  	 *
  	 * A single task is associated with a single file upload. You can use the file size in bytes as a weight,
  	 * and then as the file upload progresses you just call the `update` method with the number of bytes actually
  	 * downloaded.
  	 *
  	 * @since 4.5
  	 * @class CKEDITOR.plugins.notificationAggregator.task
  	 * @mixins CKEDITOR.event
  	 * @constructor Creates a task instance for notification aggregator.
  	 * @param {Number} [weight=1]
  	 */
  	function Task( weight ) {
  		/**
  		 * Total weight of the task.
  		 *
  		 * @private
  		 * @property {Number}
  		 */
  		this._weight = weight || 1;
  
  		/**
  		 * Done weight of the task.
  		 *
  		 * @private
  		 * @property {Number}
  		 */
  		this._doneWeight = 0;
  
  		/**
  		 * Indicates when the task is canceled.
  		 *
  		 * @private
  		 * @property {Boolean}
  		 */
  		this._isCanceled = false;
  	}
  
  	Task.prototype = {
  		/**
  		 * Marks the task as done.
  		 */
  		done: function() {
  			this.update( this._weight );
  		},
  
  		/**
  		 * Updates the done weight of a task.
  		 *
  		 * @param {Number} weight Number indicating how much of the total task {@link #_weight} is done.
  		 */
  		update: function( weight ) {
  			// If task is already done or canceled there is no need to update it, and we don't expect
  			// progress to be reversed.
  			if ( this.isDone() || this.isCanceled() ) {
  				return;
  			}
  
  			// Note that newWeight can't be higher than _doneWeight.
  			var newWeight = Math.min( this._weight, weight ),
  				weightChange = newWeight - this._doneWeight;
  
  			this._doneWeight = newWeight;
  
  			// Fire updated event even if task is done in order to correctly trigger updating the
  			// notification's message. If we wouldn't do this, then the last weight change would be ignored.
  			this.fire( 'updated', weightChange );
  
  			if ( this.isDone() ) {
  				this.fire( 'done' );
  			}
  		},
  
  		/**
  		 * Cancels the task (the task will be removed from the aggregator).
  		 */
  		cancel: function() {
  			// If task is already done or canceled.
  			if ( this.isDone() || this.isCanceled() ) {
  				return;
  			}
  
  			// Mark task as canceled.
  			this._isCanceled = true;
  
  			// We'll fire cancel event it's up to aggregator to listen for this event,
  			// and remove the task.
  			this.fire( 'canceled' );
  		},
  
  		/**
  		 * Checks if the task is done.
  		 *
  		 * @returns {Boolean}
  		 */
  		isDone: function() {
  			return this._weight === this._doneWeight;
  		},
  
  		/**
  		 * Checks if the task is canceled.
  		 *
  		 * @returns {Boolean}
  		 */
  		isCanceled: function() {
  			return this._isCanceled;
  		}
  	};
  
  	CKEDITOR.event.implementOn( Task.prototype );
  
  	/**
  	 * Fired when all tasks are done. When this event occurs, the notification may change its type to `success` or be hidden:
  	 *
  	 *		aggregator.on( 'finished', function() {
  	 *			if ( aggregator.getTaskCount() == 0 ) {
  	 *				aggregator.notification.hide();
  	 *			} else {
  	 *				aggregator.notification.update( { message: 'Done', type: 'success' } );
  	 *			}
  	 *		} );
  	 *
  	 * @event finished
  	 * @member CKEDITOR.plugins.notificationAggregator
  	 */
  
  	/**
  	 * Fired upon each weight update of the task.
  	 *
  	 *		var myTask = new Task( 100 );
  	 *		myTask.update( 30 );
  	 *		// Fires updated event with evt.data = 30.
  	 *		myTask.update( 40 );
  	 *		// Fires updated event with evt.data = 10.
  	 *		myTask.update( 20 );
  	 *		// Fires updated event with evt.data = -20.
  	 *
  	 * @event updated
  	 * @param {Number} data The difference between the new weight and the previous one.
  	 * @member CKEDITOR.plugins.notificationAggregator.task
  	 */
  
  	/**
  	 * Fired when the task is done.
  	 *
  	 * @event done
  	 * @member CKEDITOR.plugins.notificationAggregator.task
  	 */
  
  	/**
  	 * Fired when the task is canceled.
  	 *
  	 * @event canceled
  	 * @member CKEDITOR.plugins.notificationAggregator.task
  	 */
  
  	// Expose Aggregator type.
  	CKEDITOR.plugins.notificationAggregator = Aggregator;
  	CKEDITOR.plugins.notificationAggregator.task = Task;
  } )();
  