[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-admin/js/ -> image-edit.js (source)

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


Generated : Tue Apr 16 08:20:01 2024 Cross-referenced by PHPXref