Diff: STRATO-apps/wordpress_03/app/wp-admin/js/image-edit.js

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