[ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
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 + ' × ' + 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);
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Thu Nov 21 08:20:01 2024 | Cross-referenced by PHPXref |