wp/wp-admin/js/image-edit.js
changeset 7 cf61fcea0001
parent 5 5e2f62d02dcd
child 9 177826044cd9
equal deleted inserted replaced
6:490d5cc509ed 7:cf61fcea0001
     1 /* global imageEditL10n, ajaxurl, confirm */
     1 /* global imageEditL10n, ajaxurl, confirm */
       
     2 /**
       
     3  * @summary   The functions necessary for editing images.
       
     4  *
       
     5  * @since     2.9.0
       
     6  */
     2 
     7 
     3 (function($) {
     8 (function($) {
     4 var imageEdit = window.imageEdit = {
     9 
       
    10 	/**
       
    11 	 * Contains all the methods to initialise and control the image editor.
       
    12 	 *
       
    13 	 * @namespace imageEdit
       
    14 	 */
       
    15 	var imageEdit = window.imageEdit = {
     5 	iasapi : {},
    16 	iasapi : {},
     6 	hold : {},
    17 	hold : {},
     7 	postid : '',
    18 	postid : '',
     8 	_view : false,
    19 	_view : false,
     9 
    20 
       
    21 	/**
       
    22 	 * @summary Converts a value to an integer.
       
    23 	 *
       
    24 	 * @memberof imageEdit
       
    25 	 * @since    2.9.0
       
    26 	 *
       
    27 	 * @param {number} f The float value that should be converted.
       
    28 	 *
       
    29 	 * @return {number} The integer representation from the float value.
       
    30 	 */
    10 	intval : function(f) {
    31 	intval : function(f) {
       
    32 		/*
       
    33 		 * Bitwise OR operator: one of the obscure ways to truncate floating point figures,
       
    34 		 * worth reminding JavaScript doesn't have a distinct "integer" type.
       
    35 		 */
    11 		return f | 0;
    36 		return f | 0;
    12 	},
    37 	},
    13 
    38 
    14 	setDisabled : function(el, s) {
    39 	/**
       
    40 	 * @summary Adds the disabled attribute and class to a single form element
       
    41 	 *          or a field set.
       
    42 	 *
       
    43 	 * @memberof imageEdit
       
    44 	 * @since    2.9.0
       
    45 	 *
       
    46 	 * @param {jQuery}         el The element that should be modified.
       
    47 	 * @param {bool|number}    s  The state for the element. If set to true
       
    48 	 *                            the element is disabled,
       
    49 	 *                            otherwise the element is enabled.
       
    50 	 *                            The function is sometimes called with a 0 or 1
       
    51 	 *                            instead of true or false.
       
    52 	 *
       
    53 	 * @returns {void}
       
    54 	 */
       
    55 	setDisabled : function( el, s ) {
       
    56 		/*
       
    57 		 * `el` can be a single form element or a fieldset. Before #28864, the disabled state on
       
    58 		 * some text fields  was handled targeting $('input', el). Now we need to handle the
       
    59 		 * disabled state on buttons too so we can just target `el` regardless if it's a single
       
    60 		 * element or a fieldset because when a fieldset is disabled, its descendants are disabled too.
       
    61 		 */
    15 		if ( s ) {
    62 		if ( s ) {
    16 			el.removeClass('disabled');
    63 			el.removeClass( 'disabled' ).prop( 'disabled', false );
    17 			$('input', el).removeAttr('disabled');
       
    18 		} else {
    64 		} else {
    19 			el.addClass('disabled');
    65 			el.addClass( 'disabled' ).prop( 'disabled', true );
    20 			$('input', el).prop('disabled', true);
    66 		}
    21 		}
    67 	},
    22 	},
    68 
    23 
    69 	/**
       
    70 	 * @summary Initializes the image editor.
       
    71 	 *
       
    72 	 * @memberof imageEdit
       
    73 	 * @since    2.9.0
       
    74 	 *
       
    75 	 * @param {number} postid The post id.
       
    76 	 *
       
    77 	 * @returns {void}
       
    78 	 */
    24 	init : function(postid) {
    79 	init : function(postid) {
    25 		var t = this, old = $('#image-editor-' + t.postid),
    80 		var t = this, old = $('#image-editor-' + t.postid),
    26 			x = t.intval( $('#imgedit-x-' + postid).val() ),
    81 			x = t.intval( $('#imgedit-x-' + postid).val() ),
    27 			y = t.intval( $('#imgedit-y-' + postid).val() );
    82 			y = t.intval( $('#imgedit-y-' + postid).val() );
    28 
    83 
    38 		$('#imgedit-response-' + postid).empty();
    93 		$('#imgedit-response-' + postid).empty();
    39 
    94 
    40 		$('input[type="text"]', '#imgedit-panel-' + postid).keypress(function(e) {
    95 		$('input[type="text"]', '#imgedit-panel-' + postid).keypress(function(e) {
    41 			var k = e.keyCode;
    96 			var k = e.keyCode;
    42 
    97 
       
    98 			// Key codes 37 thru 40 are the arrow keys.
    43 			if ( 36 < k && k < 41 ) {
    99 			if ( 36 < k && k < 41 ) {
    44 				$(this).blur();
   100 				$(this).blur();
    45 			}
   101 			}
    46 
   102 
       
   103 			// The key code 13 is the enter key.
    47 			if ( 13 === k ) {
   104 			if ( 13 === k ) {
    48 				e.preventDefault();
   105 				e.preventDefault();
    49 				e.stopPropagation();
   106 				e.stopPropagation();
    50 				return false;
   107 				return false;
    51 			}
   108 			}
    52 		});
   109 		});
    53 	},
   110 	},
    54 
   111 
       
   112 	/**
       
   113 	 * @summary Toggles the wait/load icon in the editor.
       
   114 	 *
       
   115 	 * @memberof imageEdit
       
   116 	 * @since    2.9.0
       
   117 	 *
       
   118 	 * @param {number} postid The post id.
       
   119 	 * @param {number} toggle Is 0 or 1, fades the icon in then 1 and out when 0.
       
   120 	 *
       
   121 	 * @returns {void}
       
   122 	 */
    55 	toggleEditor : function(postid, toggle) {
   123 	toggleEditor : function(postid, toggle) {
    56 		var wait = $('#imgedit-wait-' + postid);
   124 		var wait = $('#imgedit-wait-' + postid);
    57 
   125 
    58 		if ( toggle ) {
   126 		if ( toggle ) {
    59 			wait.height( $('#imgedit-panel-' + postid).height() ).fadeIn('fast');
   127 			wait.fadeIn( 'fast' );
    60 		} else {
   128 		} else {
    61 			wait.fadeOut('fast');
   129 			wait.fadeOut('fast');
    62 		}
   130 		}
    63 	},
   131 	},
    64 
   132 
       
   133 	/**
       
   134 	 * @summary Shows or hides the image edit help box.
       
   135 	 *
       
   136 	 * @memberof imageEdit
       
   137 	 * @since    2.9.0
       
   138 	 *
       
   139 	 * @param {HTMLElement} el The element to create the help window in.
       
   140 	 *
       
   141 	 * @returns {boolean} Always returns false.
       
   142 	 */
    65 	toggleHelp : function(el) {
   143 	toggleHelp : function(el) {
    66 		$( el ).parents( '.imgedit-group-top' ).toggleClass( 'imgedit-help-toggled' ).find( '.imgedit-help' ).slideToggle( 'fast' );
   144 		var $el = $( el );
       
   145 		$el
       
   146 			.attr( 'aria-expanded', 'false' === $el.attr( 'aria-expanded' ) ? 'true' : 'false' )
       
   147 			.parents( '.imgedit-group-top' ).toggleClass( 'imgedit-help-toggled' ).find( '.imgedit-help' ).slideToggle( 'fast' );
       
   148 
    67 		return false;
   149 		return false;
    68 	},
   150 	},
    69 
   151 
       
   152 	/**
       
   153 	 * @summary Gets the value from the image edit target.
       
   154 	 *
       
   155 	 * The image edit target contains the image sizes where the (possible) changes
       
   156 	 * have to be applied to.
       
   157 	 *
       
   158 	 * @memberof imageEdit
       
   159 	 * @since    2.9.0
       
   160 	 *
       
   161 	 * @param {number} postid The post id.
       
   162 	 *
       
   163 	 * @returns {string} The value from the imagedit-save-target input field when available,
       
   164 	 *                   or 'full' when not available.
       
   165 	 */
    70 	getTarget : function(postid) {
   166 	getTarget : function(postid) {
    71 		return $('input[name="imgedit-target-' + postid + '"]:checked', '#imgedit-save-target-' + postid).val() || 'full';
   167 		return $('input[name="imgedit-target-' + postid + '"]:checked', '#imgedit-save-target-' + postid).val() || 'full';
    72 	},
   168 	},
    73 
   169 
    74 	scaleChanged : function(postid, x) {
   170 	/**
       
   171 	 * @summary Recalculates the height or width and keeps the original aspect ratio.
       
   172 	 *
       
   173 	 * If the original image size is exceeded a red exclamation mark is shown.
       
   174 	 *
       
   175 	 * @memberof imageEdit
       
   176 	 * @since    2.9.0
       
   177 	 *
       
   178 	 * @param {number}         postid The current post id.
       
   179 	 * @param {number}         x      Is 0 when it applies the y-axis
       
   180 	 *                                and 1 when applicable for the x-axis.
       
   181 	 * @param {jQuery}         el     Element.
       
   182 	 *
       
   183 	 * @returns {void}
       
   184 	 */
       
   185 	scaleChanged : function( postid, x, el ) {
    75 		var w = $('#imgedit-scale-width-' + postid), h = $('#imgedit-scale-height-' + postid),
   186 		var w = $('#imgedit-scale-width-' + postid), h = $('#imgedit-scale-height-' + postid),
    76 		warn = $('#imgedit-scale-warn-' + postid), w1 = '', h1 = '';
   187 		warn = $('#imgedit-scale-warn-' + postid), w1 = '', h1 = '';
       
   188 
       
   189 		if ( false === this.validateNumeric( el ) ) {
       
   190 			return;
       
   191 		}
    77 
   192 
    78 		if ( x ) {
   193 		if ( x ) {
    79 			h1 = ( w.val() !== '' ) ? Math.round( w.val() / this.hold.xy_ratio ) : '';
   194 			h1 = ( w.val() !== '' ) ? Math.round( w.val() / this.hold.xy_ratio ) : '';
    80 			h.val( h1 );
   195 			h.val( h1 );
    81 		} else {
   196 		} else {
    88 		} else {
   203 		} else {
    89 			warn.css('visibility', 'hidden');
   204 			warn.css('visibility', 'hidden');
    90 		}
   205 		}
    91 	},
   206 	},
    92 
   207 
       
   208 	/**
       
   209 	 * @summary Gets the selected aspect ratio.
       
   210 	 *
       
   211 	 * @memberof imageEdit
       
   212 	 * @since    2.9.0
       
   213 	 *
       
   214 	 * @param {number} postid The post id.
       
   215 	 *
       
   216 	 * @returns {string} The aspect ratio.
       
   217 	 */
    93 	getSelRatio : function(postid) {
   218 	getSelRatio : function(postid) {
    94 		var x = this.hold.w, y = this.hold.h,
   219 		var x = this.hold.w, y = this.hold.h,
    95 			X = this.intval( $('#imgedit-crop-width-' + postid).val() ),
   220 			X = this.intval( $('#imgedit-crop-width-' + postid).val() ),
    96 			Y = this.intval( $('#imgedit-crop-height-' + postid).val() );
   221 			Y = this.intval( $('#imgedit-crop-height-' + postid).val() );
    97 
   222 
   104 		}
   229 		}
   105 
   230 
   106 		return '1:1';
   231 		return '1:1';
   107 	},
   232 	},
   108 
   233 
       
   234 	/**
       
   235 	 * @summary Removes the last action from the image edit history
       
   236 	 * The history consist of (edit) actions performed on the image.
       
   237 	 *
       
   238 	 * @memberof imageEdit
       
   239 	 * @since    2.9.0
       
   240 	 *
       
   241 	 * @param {number} postid  The post id.
       
   242 	 * @param {number} setSize 0 or 1, when 1 the image resets to its original size.
       
   243 	 *
       
   244 	 * @returns {string} JSON string containing the history or an empty string if no history exists.
       
   245 	 */
   109 	filterHistory : function(postid, setSize) {
   246 	filterHistory : function(postid, setSize) {
   110 		// apply undo state to history
   247 		// Apply undo state to history.
   111 		var history = $('#imgedit-history-' + postid).val(), pop, n, o, i, op = [];
   248 		var history = $('#imgedit-history-' + postid).val(), pop, n, o, i, op = [];
   112 
   249 
   113 		if ( history !== '' ) {
   250 		if ( history !== '' ) {
       
   251 			// Read the JSON string with the image edit history.
   114 			history = JSON.parse(history);
   252 			history = JSON.parse(history);
   115 			pop = this.intval( $('#imgedit-undone-' + postid).val() );
   253 			pop = this.intval( $('#imgedit-undone-' + postid).val() );
   116 			if ( pop > 0 ) {
   254 			if ( pop > 0 ) {
   117 				while ( pop > 0 ) {
   255 				while ( pop > 0 ) {
   118 					history.pop();
   256 					history.pop();
   119 					pop--;
   257 					pop--;
   120 				}
   258 				}
   121 			}
   259 			}
   122 
   260 
       
   261 			// Reset size to it's original state.
   123 			if ( setSize ) {
   262 			if ( setSize ) {
   124 				if ( !history.length ) {
   263 				if ( !history.length ) {
   125 					this.hold.w = this.hold.ow;
   264 					this.hold.w = this.hold.ow;
   126 					this.hold.h = this.hold.oh;
   265 					this.hold.h = this.hold.oh;
   127 					return '';
   266 					return '';
   128 				}
   267 				}
   129 
   268 
   130 				// restore
   269 				// Restore original 'o'.
   131 				o = history[history.length - 1];
   270 				o = history[history.length - 1];
       
   271 
       
   272 				// c = 'crop', r = 'rotate', f = 'flip'
   132 				o = o.c || o.r || o.f || false;
   273 				o = o.c || o.r || o.f || false;
   133 
   274 
   134 				if ( o ) {
   275 				if ( o ) {
       
   276 					// fw = Full image width
   135 					this.hold.w = o.fw;
   277 					this.hold.w = o.fw;
       
   278 					// fh = Full image height
   136 					this.hold.h = o.fh;
   279 					this.hold.h = o.fh;
   137 				}
   280 				}
   138 			}
   281 			}
   139 
   282 
   140 			// filter the values
   283 			// Filter the last step/action from the history.
   141 			for ( n in history ) {
   284 			for ( n in history ) {
   142 				i = history[n];
   285 				i = history[n];
   143 				if ( i.hasOwnProperty('c') ) {
   286 				if ( i.hasOwnProperty('c') ) {
   144 					op[n] = { 'c': { 'x': i.c.x, 'y': i.c.y, 'w': i.c.w, 'h': i.c.h } };
   287 					op[n] = { 'c': { 'x': i.c.x, 'y': i.c.y, 'w': i.c.w, 'h': i.c.h } };
   145 				} else if ( i.hasOwnProperty('r') ) {
   288 				} else if ( i.hasOwnProperty('r') ) {
   150 			}
   293 			}
   151 			return JSON.stringify(op);
   294 			return JSON.stringify(op);
   152 		}
   295 		}
   153 		return '';
   296 		return '';
   154 	},
   297 	},
   155 
   298 	/**
       
   299 	 * @summary Binds the necessary events to the image.
       
   300 	 *
       
   301 	 * When the image source is reloaded the image will be reloaded.
       
   302 	 *
       
   303 	 * @memberof imageEdit
       
   304 	 * @since    2.9.0
       
   305 	 *
       
   306 	 * @param {number}   postid   The post id.
       
   307 	 * @param {string}   nonce    The nonce to verify the request.
       
   308 	 * @param {function} callback Function to execute when the image is loaded.
       
   309 	 *
       
   310 	 * @returns {void}
       
   311 	 */
   156 	refreshEditor : function(postid, nonce, callback) {
   312 	refreshEditor : function(postid, nonce, callback) {
   157 		var t = this, data, img;
   313 		var t = this, data, img;
   158 
   314 
   159 		t.toggleEditor(postid, 1);
   315 		t.toggleEditor(postid, 1);
   160 		data = {
   316 		data = {
   163 			'postid': postid,
   319 			'postid': postid,
   164 			'history': t.filterHistory(postid, 1),
   320 			'history': t.filterHistory(postid, 1),
   165 			'rand': t.intval(Math.random() * 1000000)
   321 			'rand': t.intval(Math.random() * 1000000)
   166 		};
   322 		};
   167 
   323 
   168 		img = $('<img id="image-preview-' + postid + '" />')
   324 		img = $( '<img id="image-preview-' + postid + '" alt="" />' )
   169 			.on('load', function() {
   325 			.on( 'load', { history: data.history }, function( event ) {
   170 				var max1, max2, parent = $('#imgedit-crop-' + postid), t = imageEdit;
   326 				var max1, max2,
       
   327 					parent = $( '#imgedit-crop-' + postid ),
       
   328 					t = imageEdit,
       
   329 					historyObj;
       
   330 
       
   331 				// Checks if there already is some image-edit history.
       
   332 				if ( '' !== event.data.history ) {
       
   333 					historyObj = JSON.parse( event.data.history );
       
   334 					// If last executed action in history is a crop action.
       
   335 					if ( historyObj[historyObj.length - 1].hasOwnProperty( 'c' ) ) {
       
   336 						/*
       
   337 						 * A crop action has completed and the crop button gets disabled
       
   338 						 * ensure the undo button is enabled.
       
   339 						 */
       
   340 						t.setDisabled( $( '#image-undo-' + postid) , true );
       
   341 						// Move focus to the undo button to avoid a focus loss.
       
   342 						$( '#image-undo-' + postid ).focus();
       
   343 					}
       
   344 				}
   171 
   345 
   172 				parent.empty().append(img);
   346 				parent.empty().append(img);
   173 
   347 
   174 				// w, h are the new full size dims
   348 				// w, h are the new full size dims
   175 				max1 = Math.max( t.hold.w, t.hold.h );
   349 				max1 = Math.max( t.hold.w, t.hold.h );
   195 				$('#imgedit-crop-' + postid).empty().append('<div class="error"><p>' + imageEditL10n.error + '</p></div>');
   369 				$('#imgedit-crop-' + postid).empty().append('<div class="error"><p>' + imageEditL10n.error + '</p></div>');
   196 				t.toggleEditor(postid, 0);
   370 				t.toggleEditor(postid, 0);
   197 			})
   371 			})
   198 			.attr('src', ajaxurl + '?' + $.param(data));
   372 			.attr('src', ajaxurl + '?' + $.param(data));
   199 	},
   373 	},
   200 
   374 	/**
       
   375 	 * @summary Performs an image edit action.
       
   376 	 *
       
   377 	 * @memberof imageEdit
       
   378 	 * @since    2.9.0
       
   379 	 *
       
   380 	 * @param  {number}  postid The post id.
       
   381 	 * @param  {string}  nonce  The nonce to verify the request.
       
   382 	 * @param  {string}  action The action to perform on the image.
       
   383 	 *                          The possible actions are: "scale" and "restore".
       
   384 	 *
       
   385 	 * @returns {boolean|void} Executes a post request that refreshes the page
       
   386 	 *                         when the action is performed.
       
   387 	 *                         Returns false if a invalid action is given,
       
   388 	 *                         or when the action cannot be performed.
       
   389 	 */
   201 	action : function(postid, nonce, action) {
   390 	action : function(postid, nonce, action) {
   202 		var t = this, data, w, h, fw, fh;
   391 		var t = this, data, w, h, fw, fh;
   203 
   392 
   204 		if ( t.notsaved(postid) ) {
   393 		if ( t.notsaved(postid) ) {
   205 			return false;
   394 			return false;
   247 				t._view.refresh();
   436 				t._view.refresh();
   248 			}
   437 			}
   249 		});
   438 		});
   250 	},
   439 	},
   251 
   440 
       
   441 	/**
       
   442 	 * @summary Stores the changes that are made to the image.
       
   443 	 *
       
   444 	 * @memberof imageEdit
       
   445 	 * @since    2.9.0
       
   446 	 *
       
   447 	 * @param {number}  postid   The post id to get the image from the database.
       
   448 	 * @param {string}  nonce    The nonce to verify the request.
       
   449 	 *
       
   450 	 * @returns {boolean|void}  If the actions are successfully saved a response message is shown.
       
   451 	 *                          Returns false if there is no image editing history,
       
   452 	 *                          thus there are not edit-actions performed on the image.
       
   453 	 */
   252 	save : function(postid, nonce) {
   454 	save : function(postid, nonce) {
   253 		var data,
   455 		var data,
   254 			target = this.getTarget(postid),
   456 			target = this.getTarget(postid),
   255 			history = this.filterHistory(postid, 0),
   457 			history = this.filterHistory(postid, 0),
   256 			self = this;
   458 			self = this;
   267 			'history': history,
   469 			'history': history,
   268 			'target': target,
   470 			'target': target,
   269 			'context': $('#image-edit-context').length ? $('#image-edit-context').val() : null,
   471 			'context': $('#image-edit-context').length ? $('#image-edit-context').val() : null,
   270 			'do': 'save'
   472 			'do': 'save'
   271 		};
   473 		};
   272 
   474 		// Post the image edit data to the backend.
   273 		$.post(ajaxurl, data, function(r) {
   475 		$.post(ajaxurl, data, function(r) {
       
   476 			// Read the response.
   274 			var ret = JSON.parse(r);
   477 			var ret = JSON.parse(r);
   275 
   478 
       
   479 			// If a response is returned, close the editor and show an error.
   276 			if ( ret.error ) {
   480 			if ( ret.error ) {
   277 				$('#imgedit-response-' + postid).html('<div class="error"><p>' + ret.error + '</p></div>');
   481 				$('#imgedit-response-' + postid).html('<div class="error"><p>' + ret.error + '</p></div>');
   278 				imageEdit.close(postid);
   482 				imageEdit.close(postid);
   279 				return;
   483 				return;
   280 			}
   484 			}
   297 				imageEdit.close(postid);
   501 				imageEdit.close(postid);
   298 			}
   502 			}
   299 		});
   503 		});
   300 	},
   504 	},
   301 
   505 
       
   506 	/**
       
   507 	 * @summary Creates the image edit window.
       
   508 	 *
       
   509 	 * @memberof imageEdit
       
   510 	 * @since    2.9.0
       
   511 	 *
       
   512 	 * @param {number} postid   The post id for the image.
       
   513 	 * @param {string} nonce    The nonce to verify the request.
       
   514 	 * @param {object} view     The image editor view to be used for the editing.
       
   515 	 *
       
   516 	 * @returns {void|promise} Either returns void if the button was already activated
       
   517 	 *                         or returns an instance of the image editor, wrapped in a promise.
       
   518 	 */
   302 	open : function( postid, nonce, view ) {
   519 	open : function( postid, nonce, view ) {
   303 		this._view = view;
   520 		this._view = view;
   304 
   521 
   305 		var dfd, data, elem = $('#image-editor-' + postid), head = $('#media-head-' + postid),
   522 		var dfd, data, elem = $('#image-editor-' + postid), head = $('#media-head-' + postid),
   306 			btn = $('#imgedit-open-btn-' + postid), spin = btn.siblings('.spinner');
   523 			btn = $('#imgedit-open-btn-' + postid), spin = btn.siblings('.spinner');
   307 
   524 
   308 		btn.prop('disabled', true);
   525 		/*
       
   526 		 * Instead of disabling the button, which causes a focus loss and makes screen
       
   527 		 * readers announce "unavailable", return if the button was already clicked.
       
   528 		 */
       
   529 		if ( btn.hasClass( 'button-activated' ) ) {
       
   530 			return;
       
   531 		}
       
   532 
   309 		spin.addClass( 'is-active' );
   533 		spin.addClass( 'is-active' );
   310 
   534 
   311 		data = {
   535 		data = {
   312 			'action': 'image-editor',
   536 			'action': 'image-editor',
   313 			'_ajax_nonce': nonce,
   537 			'_ajax_nonce': nonce,
   316 		};
   540 		};
   317 
   541 
   318 		dfd = $.ajax({
   542 		dfd = $.ajax({
   319 			url:  ajaxurl,
   543 			url:  ajaxurl,
   320 			type: 'post',
   544 			type: 'post',
   321 			data: data
   545 			data: data,
       
   546 			beforeSend: function() {
       
   547 				btn.addClass( 'button-activated' );
       
   548 			}
   322 		}).done(function( html ) {
   549 		}).done(function( html ) {
   323 			elem.html( html );
   550 			elem.html( html );
   324 			head.fadeOut('fast', function(){
   551 			head.fadeOut('fast', function(){
   325 				elem.fadeIn('fast');
   552 				elem.fadeIn('fast');
   326 				btn.removeAttr('disabled');
   553 				btn.removeClass( 'button-activated' );
   327 				spin.removeClass( 'is-active' );
   554 				spin.removeClass( 'is-active' );
   328 			});
   555 			});
       
   556 			// Initialise the Image Editor now that everything is ready.
       
   557 			imageEdit.init( postid );
   329 		});
   558 		});
   330 
   559 
   331 		return dfd;
   560 		return dfd;
   332 	},
   561 	},
   333 
   562 
       
   563 	/**
       
   564 	 * @summary Initializes the cropping tool and sets a default cropping selection.
       
   565 	 *
       
   566 	 * @memberof imageEdit
       
   567 	 * @since    2.9.0
       
   568 	 *
       
   569 	 * @param {number} postid The post id.
       
   570 	 *
       
   571 	 * @returns {void}
       
   572 	 */
   334 	imgLoaded : function(postid) {
   573 	imgLoaded : function(postid) {
   335 		var img = $('#image-preview-' + postid), parent = $('#imgedit-crop-' + postid);
   574 		var img = $('#image-preview-' + postid), parent = $('#imgedit-crop-' + postid);
       
   575 
       
   576 		// Ensure init has run even when directly loaded.
       
   577 		if ( 'undefined' === typeof this.hold.sizer ) {
       
   578 			this.init( postid );
       
   579 		}
   336 
   580 
   337 		this.initCrop(postid, img, parent);
   581 		this.initCrop(postid, img, parent);
   338 		this.setCropSelection(postid, 0);
   582 		this.setCropSelection(postid, 0);
   339 		this.toggleEditor(postid, 0);
   583 		this.toggleEditor(postid, 0);
   340 	},
   584 		// Editor is ready, move focus to the first focusable element.
   341 
   585 		$( '.imgedit-wrap .imgedit-help-toggle' ).eq( 0 ).focus();
       
   586 	},
       
   587 
       
   588 	/**
       
   589 	 * @summary Initializes the cropping tool.
       
   590 	 *
       
   591 	 * @memberof imageEdit
       
   592 	 * @since    2.9.0
       
   593 	 *
       
   594 	 * @param {number}      postid The post id.
       
   595 	 * @param {HTMLElement} image  The preview image.
       
   596 	 * @param {HTMLElement} parent The preview image container.
       
   597 	 *
       
   598 	 * @returns {void}
       
   599 	 */
   342 	initCrop : function(postid, image, parent) {
   600 	initCrop : function(postid, image, parent) {
   343 		var t = this,
   601 		var t = this,
   344 			selW = $('#imgedit-sel-width-' + postid),
   602 			selW = $('#imgedit-sel-width-' + postid),
   345 			selH = $('#imgedit-sel-height-' + postid),
   603 			selH = $('#imgedit-sel-height-' + postid),
   346 			$img;
   604 			$img;
   351 			handles: true,
   609 			handles: true,
   352 			keys: true,
   610 			keys: true,
   353 			minWidth: 3,
   611 			minWidth: 3,
   354 			minHeight: 3,
   612 			minHeight: 3,
   355 
   613 
       
   614 			/**
       
   615 			 * @summary Sets the CSS styles and binds events for locking the aspect ratio.
       
   616 			 *
       
   617 			 * @param {jQuery} img The preview image.
       
   618 			 */
   356 			onInit: function( img ) {
   619 			onInit: function( img ) {
   357 				// Ensure that the imgareaselect wrapper elements are position:absolute
   620 				// Ensure that the imgAreaSelect wrapper elements are position:absolute.
   358 				// (even if we're in a position:fixed modal)
   621 				// (even if we're in a position:fixed modal)
   359 				$img = $( img );
   622 				$img = $( img );
   360 				$img.next().css( 'position', 'absolute' )
   623 				$img.next().css( 'position', 'absolute' )
   361 					.nextAll( '.imgareaselect-outer' ).css( 'position', 'absolute' );
   624 					.nextAll( '.imgareaselect-outer' ).css( 'position', 'absolute' );
   362 
   625 				/**
   363 				parent.children().mousedown(function(e){
   626 				 * @summary Binds mouse down event to the cropping container.
       
   627 				 *
       
   628 				 * @returns {void}
       
   629 				 */
       
   630 				parent.children().on( 'mousedown, touchstart', function(e){
   364 					var ratio = false, sel, defRatio;
   631 					var ratio = false, sel, defRatio;
   365 
   632 
   366 					if ( e.shiftKey ) {
   633 					if ( e.shiftKey ) {
   367 						sel = t.iasapi.getSelection();
   634 						sel = t.iasapi.getSelection();
   368 						defRatio = t.getSelRatio(postid);
   635 						defRatio = t.getSelRatio(postid);
   373 						aspectRatio: ratio
   640 						aspectRatio: ratio
   374 					});
   641 					});
   375 				});
   642 				});
   376 			},
   643 			},
   377 
   644 
       
   645 			/**
       
   646 			 * @summary Event triggered when starting a selection.
       
   647 			 *
       
   648 			 * @returns {void}
       
   649 			 */
   378 			onSelectStart: function() {
   650 			onSelectStart: function() {
   379 				imageEdit.setDisabled($('#imgedit-crop-sel-' + postid), 1);
   651 				imageEdit.setDisabled($('#imgedit-crop-sel-' + postid), 1);
   380 			},
   652 			},
   381 
   653 			/**
       
   654 			 * @summary Event triggered when the selection is ended.
       
   655 			 *
       
   656 			 * @param {object} img jQuery object representing the image.
       
   657 			 * @param {object} c   The selection.
       
   658 			 *
       
   659 			 * @returns {object}
       
   660 			 */
   382 			onSelectEnd: function(img, c) {
   661 			onSelectEnd: function(img, c) {
   383 				imageEdit.setCropSelection(postid, c);
   662 				imageEdit.setCropSelection(postid, c);
   384 			},
   663 			},
   385 
   664 
       
   665 			/**
       
   666 			 * @summary Event triggered when the selection changes.
       
   667 			 *
       
   668 			 * @param {object} img jQuery object representing the image.
       
   669 			 * @param {object} c   The selection.
       
   670 			 *
       
   671 			 * @returns {void}
       
   672 			 */
   386 			onSelectChange: function(img, c) {
   673 			onSelectChange: function(img, c) {
   387 				var sizer = imageEdit.hold.sizer;
   674 				var sizer = imageEdit.hold.sizer;
   388 				selW.val( imageEdit.round(c.width / sizer) );
   675 				selW.val( imageEdit.round(c.width / sizer) );
   389 				selH.val( imageEdit.round(c.height / sizer) );
   676 				selH.val( imageEdit.round(c.height / sizer) );
   390 			}
   677 			}
   391 		});
   678 		});
   392 	},
   679 	},
   393 
   680 
       
   681 	/**
       
   682 	 * @summary Stores the current crop selection.
       
   683 	 *
       
   684 	 * @memberof imageEdit
       
   685 	 * @since    2.9.0
       
   686 	 *
       
   687 	 * @param {number} postid The post id.
       
   688 	 * @param {object} c      The selection.
       
   689 	 *
       
   690 	 * @returns {boolean}
       
   691 	 */
   394 	setCropSelection : function(postid, c) {
   692 	setCropSelection : function(postid, c) {
   395 		var sel;
   693 		var sel;
   396 
   694 
   397 		c = c || 0;
   695 		c = c || 0;
   398 
   696 
   408 		sel = { 'x': c.x1, 'y': c.y1, 'w': c.width, 'h': c.height };
   706 		sel = { 'x': c.x1, 'y': c.y1, 'w': c.width, 'h': c.height };
   409 		this.setDisabled($('.imgedit-crop', '#imgedit-panel-' + postid), 1);
   707 		this.setDisabled($('.imgedit-crop', '#imgedit-panel-' + postid), 1);
   410 		$('#imgedit-selection-' + postid).val( JSON.stringify(sel) );
   708 		$('#imgedit-selection-' + postid).val( JSON.stringify(sel) );
   411 	},
   709 	},
   412 
   710 
       
   711 
       
   712 	/**
       
   713 	 * @summary Closes the image editor.
       
   714 	 *
       
   715 	 * @memberof imageEdit
       
   716 	 * @since    2.9.0
       
   717 	 *
       
   718 	 * @param {number}  postid The post id.
       
   719 	 * @param {bool}    warn   Warning message.
       
   720 	 *
       
   721 	 * @returns {void|bool} Returns false if there is a warning.
       
   722 	 */
   413 	close : function(postid, warn) {
   723 	close : function(postid, warn) {
   414 		warn = warn || false;
   724 		warn = warn || false;
   415 
   725 
   416 		if ( warn && this.notsaved(postid) ) {
   726 		if ( warn && this.notsaved(postid) ) {
   417 			return false;
   727 			return false;
   427 		}
   737 		}
   428 
   738 
   429 		// In case we are not accessing the image editor in the context of a View, close the editor the old-skool way
   739 		// In case we are not accessing the image editor in the context of a View, close the editor the old-skool way
   430 		else {
   740 		else {
   431 			$('#image-editor-' + postid).fadeOut('fast', function() {
   741 			$('#image-editor-' + postid).fadeOut('fast', function() {
   432 				$('#media-head-' + postid).fadeIn('fast');
   742 				$( '#media-head-' + postid ).fadeIn( 'fast', function() {
       
   743 					// Move focus back to the Edit Image button. Runs also when saving.
       
   744 					$( '#imgedit-open-btn-' + postid ).focus();
       
   745 				});
   433 				$(this).empty();
   746 				$(this).empty();
   434 			});
   747 			});
   435 		}
   748 		}
   436 
   749 
   437 
   750 
   438 	},
   751 	},
   439 
   752 
       
   753 	/**
       
   754 	 * @summary Checks if the image edit history is saved.
       
   755 	 *
       
   756 	 * @memberof imageEdit
       
   757 	 * @since    2.9.0
       
   758 	 *
       
   759 	 * @param {number} postid The post id.
       
   760 	 *
       
   761 	 * @returns {boolean} Returns true if the history is not saved.
       
   762 	 */
   440 	notsaved : function(postid) {
   763 	notsaved : function(postid) {
   441 		var h = $('#imgedit-history-' + postid).val(),
   764 		var h = $('#imgedit-history-' + postid).val(),
   442 			history = ( h !== '' ) ? JSON.parse(h) : [],
   765 			history = ( h !== '' ) ? JSON.parse(h) : [],
   443 			pop = this.intval( $('#imgedit-undone-' + postid).val() );
   766 			pop = this.intval( $('#imgedit-undone-' + postid).val() );
   444 
   767 
   449 			return true;
   772 			return true;
   450 		}
   773 		}
   451 		return false;
   774 		return false;
   452 	},
   775 	},
   453 
   776 
       
   777 	/**
       
   778 	 * @summary Adds an image edit action to the history.
       
   779 	 *
       
   780 	 * @memberof imageEdit
       
   781 	 * @since    2.9.0
       
   782 	 *
       
   783 	 * @param {object} op     The original position.
       
   784 	 * @param {number} postid The post id.
       
   785 	 * @param {string} nonce  The nonce.
       
   786 	 *
       
   787 	 * @returns {void}
       
   788 	 */
   454 	addStep : function(op, postid, nonce) {
   789 	addStep : function(op, postid, nonce) {
   455 		var t = this, elem = $('#imgedit-history-' + postid),
   790 		var t = this, elem = $('#imgedit-history-' + postid),
   456 		history = ( elem.val() !== '' ) ? JSON.parse( elem.val() ) : [],
   791 			history = ( elem.val() !== '' ) ? JSON.parse( elem.val() ) : [],
   457 		undone = $('#imgedit-undone-' + postid),
   792 			undone = $( '#imgedit-undone-' + postid ),
   458 		pop = t.intval(undone.val());
   793 			pop = t.intval( undone.val() );
   459 
   794 
   460 		while ( pop > 0 ) {
   795 		while ( pop > 0 ) {
   461 			history.pop();
   796 			history.pop();
   462 			pop--;
   797 			pop--;
   463 		}
   798 		}
   470 			t.setDisabled($('#image-undo-' + postid), true);
   805 			t.setDisabled($('#image-undo-' + postid), true);
   471 			t.setDisabled($('#image-redo-' + postid), false);
   806 			t.setDisabled($('#image-redo-' + postid), false);
   472 		});
   807 		});
   473 	},
   808 	},
   474 
   809 
       
   810 	/**
       
   811 	 * @summary Rotates the image.
       
   812 	 *
       
   813 	 * @memberof imageEdit
       
   814 	 * @since    2.9.0
       
   815 	 *
       
   816 	 * @param {string} angle  The angle the image is rotated with.
       
   817 	 * @param {number} postid The post id.
       
   818 	 * @param {string} nonce  The nonce.
       
   819 	 * @param {object} t      The target element.
       
   820 	 *
       
   821 	 * @returns {boolean}
       
   822 	 */
   475 	rotate : function(angle, postid, nonce, t) {
   823 	rotate : function(angle, postid, nonce, t) {
   476 		if ( $(t).hasClass('disabled') ) {
   824 		if ( $(t).hasClass('disabled') ) {
   477 			return false;
   825 			return false;
   478 		}
   826 		}
   479 
   827 
   480 		this.addStep({ 'r': { 'r': angle, 'fw': this.hold.h, 'fh': this.hold.w }}, postid, nonce);
   828 		this.addStep({ 'r': { 'r': angle, 'fw': this.hold.h, 'fh': this.hold.w }}, postid, nonce);
   481 	},
   829 	},
   482 
   830 
       
   831 	/**
       
   832 	 * @summary Flips the image.
       
   833 	 *
       
   834 	 * @memberof imageEdit
       
   835 	 * @since    2.9.0
       
   836 	 *
       
   837 	 * @param {number} axis   The axle the image is flipped on.
       
   838 	 * @param {number} postid The post id.
       
   839 	 * @param {string} nonce  The nonce.
       
   840 	 * @param {object} t      The target element.
       
   841 	 *
       
   842 	 * @returns {boolean}
       
   843 	 */
   483 	flip : function (axis, postid, nonce, t) {
   844 	flip : function (axis, postid, nonce, t) {
   484 		if ( $(t).hasClass('disabled') ) {
   845 		if ( $(t).hasClass('disabled') ) {
   485 			return false;
   846 			return false;
   486 		}
   847 		}
   487 
   848 
   488 		this.addStep({ 'f': { 'f': axis, 'fw': this.hold.w, 'fh': this.hold.h }}, postid, nonce);
   849 		this.addStep({ 'f': { 'f': axis, 'fw': this.hold.w, 'fh': this.hold.h }}, postid, nonce);
   489 	},
   850 	},
   490 
   851 
       
   852 	/**
       
   853 	 * @summary Crops the image.
       
   854 	 *
       
   855 	 * @memberof imageEdit
       
   856 	 * @since    2.9.0
       
   857 	 *
       
   858 	 * @param {number} postid The post id.
       
   859 	 * @param {string} nonce  The nonce.
       
   860 	 * @param {object} t      The target object.
       
   861 	 *
       
   862 	 * @returns {void|boolean} Returns false if the crop button is disabled.
       
   863 	 */
   491 	crop : function (postid, nonce, t) {
   864 	crop : function (postid, nonce, t) {
   492 		var sel = $('#imgedit-selection-' + postid).val(),
   865 		var sel = $('#imgedit-selection-' + postid).val(),
   493 			w = this.intval( $('#imgedit-sel-width-' + postid).val() ),
   866 			w = this.intval( $('#imgedit-sel-width-' + postid).val() ),
   494 			h = this.intval( $('#imgedit-sel-height-' + postid).val() );
   867 			h = this.intval( $('#imgedit-sel-height-' + postid).val() );
   495 
   868 
   503 			sel.fh = h;
   876 			sel.fh = h;
   504 			this.addStep({ 'c': sel }, postid, nonce);
   877 			this.addStep({ 'c': sel }, postid, nonce);
   505 		}
   878 		}
   506 	},
   879 	},
   507 
   880 
       
   881 	/**
       
   882 	 * @summary Undoes an image edit action.
       
   883 	 *
       
   884 	 * @memberof imageEdit
       
   885 	 * @since    2.9.0
       
   886 	 *
       
   887 	 * @param {number} postid   The post id.
       
   888 	 * @param {string} nonce    The nonce.
       
   889 	 *
       
   890 	 * @returns {void|false} Returns false if the undo button is disabled.
       
   891 	 */
   508 	undo : function (postid, nonce) {
   892 	undo : function (postid, nonce) {
   509 		var t = this, button = $('#image-undo-' + postid), elem = $('#imgedit-undone-' + postid),
   893 		var t = this, button = $('#image-undo-' + postid), elem = $('#imgedit-undone-' + postid),
   510 			pop = t.intval( elem.val() ) + 1;
   894 			pop = t.intval( elem.val() ) + 1;
   511 
   895 
   512 		if ( button.hasClass('disabled') ) {
   896 		if ( button.hasClass('disabled') ) {
   514 		}
   898 		}
   515 
   899 
   516 		elem.val(pop);
   900 		elem.val(pop);
   517 		t.refreshEditor(postid, nonce, function() {
   901 		t.refreshEditor(postid, nonce, function() {
   518 			var elem = $('#imgedit-history-' + postid),
   902 			var elem = $('#imgedit-history-' + postid),
   519 			history = ( elem.val() !== '' ) ? JSON.parse( elem.val() ) : [];
   903 				history = ( elem.val() !== '' ) ? JSON.parse( elem.val() ) : [];
   520 
   904 
   521 			t.setDisabled($('#image-redo-' + postid), true);
   905 			t.setDisabled($('#image-redo-' + postid), true);
   522 			t.setDisabled(button, pop < history.length);
   906 			t.setDisabled(button, pop < history.length);
       
   907 			// When undo gets disabled, move focus to the redo button to avoid a focus loss.
       
   908 			if ( history.length === pop ) {
       
   909 				$( '#image-redo-' + postid ).focus();
       
   910 			}
   523 		});
   911 		});
   524 	},
   912 	},
   525 
   913 
       
   914 	/**
       
   915 	 * Reverts a undo action.
       
   916 	 *
       
   917 	 * @memberof imageEdit
       
   918 	 * @since    2.9.0
       
   919 	 *
       
   920 	 * @param {number} postid The post id.
       
   921 	 * @param {string} nonce  The nonce.
       
   922 	 *
       
   923 	 * @returns {void}
       
   924 	 */
   526 	redo : function(postid, nonce) {
   925 	redo : function(postid, nonce) {
   527 		var t = this, button = $('#image-redo-' + postid), elem = $('#imgedit-undone-' + postid),
   926 		var t = this, button = $('#image-redo-' + postid), elem = $('#imgedit-undone-' + postid),
   528 			pop = t.intval( elem.val() ) - 1;
   927 			pop = t.intval( elem.val() ) - 1;
   529 
   928 
   530 		if ( button.hasClass('disabled') ) {
   929 		if ( button.hasClass('disabled') ) {
   533 
   932 
   534 		elem.val(pop);
   933 		elem.val(pop);
   535 		t.refreshEditor(postid, nonce, function() {
   934 		t.refreshEditor(postid, nonce, function() {
   536 			t.setDisabled($('#image-undo-' + postid), true);
   935 			t.setDisabled($('#image-undo-' + postid), true);
   537 			t.setDisabled(button, pop > 0);
   936 			t.setDisabled(button, pop > 0);
       
   937 			// When redo gets disabled, move focus to the undo button to avoid a focus loss.
       
   938 			if ( 0 === pop ) {
       
   939 				$( '#image-undo-' + postid ).focus();
       
   940 			}
   538 		});
   941 		});
   539 	},
   942 	},
   540 
   943 
   541 	setNumSelection : function(postid) {
   944 	/**
       
   945 	 * @summary Sets the selection for the height and width in pixels.
       
   946 	 *
       
   947 	 * @memberof imageEdit
       
   948 	 * @since    2.9.0
       
   949 	 *
       
   950 	 * @param {number} postid The post id.
       
   951 	 * @param {jQuery} el     The element containing the values.
       
   952 	 *
       
   953 	 * @returns {void|boolean} Returns false when the x or y value is lower than 1,
       
   954 	 *                         void when the value is not numeric or when the operation
       
   955 	 *                         is successful.
       
   956 	 */
       
   957 	setNumSelection : function( postid, el ) {
   542 		var sel, elX = $('#imgedit-sel-width-' + postid), elY = $('#imgedit-sel-height-' + postid),
   958 		var sel, elX = $('#imgedit-sel-width-' + postid), elY = $('#imgedit-sel-height-' + postid),
   543 			x = this.intval( elX.val() ), y = this.intval( elY.val() ),
   959 			x = this.intval( elX.val() ), y = this.intval( elY.val() ),
   544 			img = $('#image-preview-' + postid), imgh = img.height(), imgw = img.width(),
   960 			img = $('#image-preview-' + postid), imgh = img.height(), imgw = img.width(),
   545 			sizer = this.hold.sizer, x1, y1, x2, y2, ias = this.iasapi;
   961 			sizer = this.hold.sizer, x1, y1, x2, y2, ias = this.iasapi;
       
   962 
       
   963 		if ( false === this.validateNumeric( el ) ) {
       
   964 			return;
       
   965 		}
   546 
   966 
   547 		if ( x < 1 ) {
   967 		if ( x < 1 ) {
   548 			elX.val('');
   968 			elX.val('');
   549 			return false;
   969 			return false;
   550 		}
   970 		}
   576 			ias.update();
   996 			ias.update();
   577 			this.setCropSelection(postid, ias.getSelection());
   997 			this.setCropSelection(postid, ias.getSelection());
   578 		}
   998 		}
   579 	},
   999 	},
   580 
  1000 
       
  1001 	/**
       
  1002 	 * Rounds a number to a whole.
       
  1003 	 *
       
  1004 	 * @memberof imageEdit
       
  1005 	 * @since    2.9.0
       
  1006 	 *
       
  1007 	 * @param {number} num The number.
       
  1008 	 *
       
  1009 	 * @returns {number} The number rounded to a whole number.
       
  1010 	 */
   581 	round : function(num) {
  1011 	round : function(num) {
   582 		var s;
  1012 		var s;
   583 		num = Math.round(num);
  1013 		num = Math.round(num);
   584 
  1014 
   585 		if ( this.hold.sizer > 0.6 ) {
  1015 		if ( this.hold.sizer > 0.6 ) {
   595 		}
  1025 		}
   596 
  1026 
   597 		return num;
  1027 		return num;
   598 	},
  1028 	},
   599 
  1029 
       
  1030 	/**
       
  1031 	 * Sets a locked aspect ratio for the selection.
       
  1032 	 *
       
  1033 	 * @memberof imageEdit
       
  1034 	 * @since    2.9.0
       
  1035 	 *
       
  1036 	 * @param {number} postid     The post id.
       
  1037 	 * @param {number} n          The ratio to set.
       
  1038 	 * @param {jQuery} el         The element containing the values.
       
  1039 	 *
       
  1040 	 * @returns {void}
       
  1041 	 */
   600 	setRatioSelection : function(postid, n, el) {
  1042 	setRatioSelection : function(postid, n, el) {
   601 		var sel, r, x = this.intval( $('#imgedit-crop-width-' + postid).val() ),
  1043 		var sel, r, x = this.intval( $('#imgedit-crop-width-' + postid).val() ),
   602 			y = this.intval( $('#imgedit-crop-height-' + postid).val() ),
  1044 			y = this.intval( $('#imgedit-crop-height-' + postid).val() ),
   603 			h = $('#image-preview-' + postid).height();
  1045 			h = $('#image-preview-' + postid).height();
   604 
  1046 
   605 		if ( !this.intval( $(el).val() ) ) {
  1047 		if ( false === this.validateNumeric( el ) ) {
   606 			$(el).val('');
       
   607 			return;
  1048 			return;
   608 		}
  1049 		}
   609 
  1050 
   610 		if ( x && y ) {
  1051 		if ( x && y ) {
   611 			this.iasapi.setOptions({
  1052 			this.iasapi.setOptions({
   626 
  1067 
   627 				this.iasapi.setSelection( sel.x1, sel.y1, sel.x2, r );
  1068 				this.iasapi.setSelection( sel.x1, sel.y1, sel.x2, r );
   628 				this.iasapi.update();
  1069 				this.iasapi.update();
   629 			}
  1070 			}
   630 		}
  1071 		}
       
  1072 	},
       
  1073 
       
  1074 	/**
       
  1075 	 * Validates if a value in a jQuery.HTMLElement is numeric.
       
  1076 	 *
       
  1077 	 * @memberof imageEdit
       
  1078 	 * @since    4.6
       
  1079 	 *
       
  1080 	 * @param {jQuery} el The html element.
       
  1081 	 *
       
  1082 	 * @returns {void|boolean} Returns false if the value is not numeric,
       
  1083 	 *                         void when it is.
       
  1084 	 */
       
  1085 	validateNumeric: function( el ) {
       
  1086 		if ( ! this.intval( $( el ).val() ) ) {
       
  1087 			$( el ).val( '' );
       
  1088 			return false;
       
  1089 		}
   631 	}
  1090 	}
   632 };
  1091 };
   633 })(jQuery);
  1092 })(jQuery);