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