[ Index ]

PHP Cross Reference of WordPress

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


Generated: Mon Oct 26 01:00:02 2020 Cross-referenced by PHPXref 0.7.1