[ Index ]

WordPress Cross Reference

title

Body

[close]

/wp-admin/js/ -> revisions.js (source)

   1  /* global _wpRevisionsSettings, isRtl */
   2  window.wp = window.wp || {};
   3  
   4  (function($) {
   5      var revisions;
   6  
   7      revisions = wp.revisions = { model: {}, view: {}, controller: {} };
   8  
   9      // Link settings.
  10      revisions.settings = _.isUndefined( _wpRevisionsSettings ) ? {} : _wpRevisionsSettings;
  11  
  12      // For debugging
  13      revisions.debug = false;
  14  
  15      revisions.log = function() {
  16          if ( window.console && revisions.debug ) {
  17              window.console.log.apply( window.console, arguments );
  18          }
  19      };
  20  
  21      // Handy functions to help with positioning
  22      $.fn.allOffsets = function() {
  23          var offset = this.offset() || {top: 0, left: 0}, win = $(window);
  24          return _.extend( offset, {
  25              right:  win.width()  - offset.left - this.outerWidth(),
  26              bottom: win.height() - offset.top  - this.outerHeight()
  27          });
  28      };
  29  
  30      $.fn.allPositions = function() {
  31          var position = this.position() || {top: 0, left: 0}, parent = this.parent();
  32          return _.extend( position, {
  33              right:  parent.outerWidth()  - position.left - this.outerWidth(),
  34              bottom: parent.outerHeight() - position.top  - this.outerHeight()
  35          });
  36      };
  37  
  38      // wp_localize_script transforms top-level numbers into strings. Undo that.
  39      if ( revisions.settings.to ) {
  40          revisions.settings.to = parseInt( revisions.settings.to, 10 );
  41      }
  42      if ( revisions.settings.from ) {
  43          revisions.settings.from = parseInt( revisions.settings.from, 10 );
  44      }
  45  
  46      // wp_localize_script does not allow for top-level booleans. Fix that.
  47      if ( revisions.settings.compareTwoMode ) {
  48          revisions.settings.compareTwoMode = revisions.settings.compareTwoMode === '1';
  49      }
  50  
  51      /**
  52       * ========================================================================
  53       * MODELS
  54       * ========================================================================
  55       */
  56      revisions.model.Slider = Backbone.Model.extend({
  57          defaults: {
  58              value: null,
  59              values: null,
  60              min: 0,
  61              max: 1,
  62              step: 1,
  63              range: false,
  64              compareTwoMode: false
  65          },
  66  
  67          initialize: function( options ) {
  68              this.frame = options.frame;
  69              this.revisions = options.revisions;
  70  
  71              // Listen for changes to the revisions or mode from outside
  72              this.listenTo( this.frame, 'update:revisions', this.receiveRevisions );
  73              this.listenTo( this.frame, 'change:compareTwoMode', this.updateMode );
  74  
  75              // Listen for internal changes
  76              this.listenTo( this, 'change:from', this.handleLocalChanges );
  77              this.listenTo( this, 'change:to', this.handleLocalChanges );
  78              this.listenTo( this, 'change:compareTwoMode', this.updateSliderSettings );
  79              this.listenTo( this, 'update:revisions', this.updateSliderSettings );
  80  
  81              // Listen for changes to the hovered revision
  82              this.listenTo( this, 'change:hoveredRevision', this.hoverRevision );
  83  
  84              this.set({
  85                  max:   this.revisions.length - 1,
  86                  compareTwoMode: this.frame.get('compareTwoMode'),
  87                  from: this.frame.get('from'),
  88                  to: this.frame.get('to')
  89              });
  90              this.updateSliderSettings();
  91          },
  92  
  93          getSliderValue: function( a, b ) {
  94              return isRtl ? this.revisions.length - this.revisions.indexOf( this.get(a) ) - 1 : this.revisions.indexOf( this.get(b) );
  95          },
  96  
  97          updateSliderSettings: function() {
  98              if ( this.get('compareTwoMode') ) {
  99                  this.set({
 100                      values: [
 101                          this.getSliderValue( 'to', 'from' ),
 102                          this.getSliderValue( 'from', 'to' )
 103                      ],
 104                      value: null,
 105                      range: true // ensures handles cannot cross
 106                  });
 107              } else {
 108                  this.set({
 109                      value: this.getSliderValue( 'to', 'to' ),
 110                      values: null,
 111                      range: false
 112                  });
 113              }
 114              this.trigger( 'update:slider' );
 115          },
 116  
 117          // Called when a revision is hovered
 118          hoverRevision: function( model, value ) {
 119              this.trigger( 'hovered:revision', value );
 120          },
 121  
 122          // Called when `compareTwoMode` changes
 123          updateMode: function( model, value ) {
 124              this.set({ compareTwoMode: value });
 125          },
 126  
 127          // Called when `from` or `to` changes in the local model
 128          handleLocalChanges: function() {
 129              this.frame.set({
 130                  from: this.get('from'),
 131                  to: this.get('to')
 132              });
 133          },
 134  
 135          // Receives revisions changes from outside the model
 136          receiveRevisions: function( from, to ) {
 137              // Bail if nothing changed
 138              if ( this.get('from') === from && this.get('to') === to ) {
 139                  return;
 140              }
 141  
 142              this.set({ from: from, to: to }, { silent: true });
 143              this.trigger( 'update:revisions', from, to );
 144          }
 145  
 146      });
 147  
 148      revisions.model.Tooltip = Backbone.Model.extend({
 149          defaults: {
 150              revision: null,
 151              offset: {},
 152              hovering: false, // Whether the mouse is hovering
 153              scrubbing: false // Whether the mouse is scrubbing
 154          },
 155  
 156          initialize: function( options ) {
 157              this.frame = options.frame;
 158              this.revisions = options.revisions;
 159              this.slider = options.slider;
 160  
 161              this.listenTo( this.slider, 'hovered:revision', this.updateRevision );
 162              this.listenTo( this.slider, 'change:hovering', this.setHovering );
 163              this.listenTo( this.slider, 'change:scrubbing', this.setScrubbing );
 164          },
 165  
 166  
 167          updateRevision: function( revision ) {
 168              this.set({ revision: revision });
 169          },
 170  
 171          setHovering: function( model, value ) {
 172              this.set({ hovering: value });
 173          },
 174  
 175          setScrubbing: function( model, value ) {
 176              this.set({ scrubbing: value });
 177          }
 178      });
 179  
 180      revisions.model.Revision = Backbone.Model.extend({});
 181  
 182      revisions.model.Revisions = Backbone.Collection.extend({
 183          model: revisions.model.Revision,
 184  
 185          initialize: function() {
 186              _.bindAll( this, 'next', 'prev' );
 187          },
 188  
 189          next: function( revision ) {
 190              var index = this.indexOf( revision );
 191  
 192              if ( index !== -1 && index !== this.length - 1 ) {
 193                  return this.at( index + 1 );
 194              }
 195          },
 196  
 197          prev: function( revision ) {
 198              var index = this.indexOf( revision );
 199  
 200              if ( index !== -1 && index !== 0 ) {
 201                  return this.at( index - 1 );
 202              }
 203          }
 204      });
 205  
 206      revisions.model.Field = Backbone.Model.extend({});
 207  
 208      revisions.model.Fields = Backbone.Collection.extend({
 209          model: revisions.model.Field
 210      });
 211  
 212      revisions.model.Diff = Backbone.Model.extend({
 213          initialize: function() {
 214              var fields = this.get('fields');
 215              this.unset('fields');
 216  
 217              this.fields = new revisions.model.Fields( fields );
 218          }
 219      });
 220  
 221      revisions.model.Diffs = Backbone.Collection.extend({
 222          initialize: function( models, options ) {
 223              _.bindAll( this, 'getClosestUnloaded' );
 224              this.loadAll = _.once( this._loadAll );
 225              this.revisions = options.revisions;
 226              this.requests  = {};
 227          },
 228  
 229          model: revisions.model.Diff,
 230  
 231          ensure: function( id, context ) {
 232              var diff     = this.get( id ),
 233                  request  = this.requests[ id ],
 234                  deferred = $.Deferred(),
 235                  ids      = {},
 236                  from     = id.split(':')[0],
 237                  to       = id.split(':')[1];
 238              ids[id] = true;
 239  
 240              wp.revisions.log( 'ensure', id );
 241  
 242              this.trigger( 'ensure', ids, from, to, deferred.promise() );
 243  
 244              if ( diff ) {
 245                  deferred.resolveWith( context, [ diff ] );
 246              } else {
 247                  this.trigger( 'ensure:load', ids, from, to, deferred.promise() );
 248                  _.each( ids, _.bind( function( id ) {
 249                      // Remove anything that has an ongoing request
 250                      if ( this.requests[ id ] ) {
 251                          delete ids[ id ];
 252                      }
 253                      // Remove anything we already have
 254                      if ( this.get( id ) ) {
 255                          delete ids[ id ];
 256                      }
 257                  }, this ) );
 258                  if ( ! request ) {
 259                      // Always include the ID that started this ensure
 260                      ids[ id ] = true;
 261                      request   = this.load( _.keys( ids ) );
 262                  }
 263  
 264                  request.done( _.bind( function() {
 265                      deferred.resolveWith( context, [ this.get( id ) ] );
 266                  }, this ) ).fail( _.bind( function() {
 267                      deferred.reject();
 268                  }) );
 269              }
 270  
 271              return deferred.promise();
 272          },
 273  
 274          // Returns an array of proximal diffs
 275          getClosestUnloaded: function( ids, centerId ) {
 276              var self = this;
 277              return _.chain([0].concat( ids )).initial().zip( ids ).sortBy( function( pair ) {
 278                  return Math.abs( centerId - pair[1] );
 279              }).map( function( pair ) {
 280                  return pair.join(':');
 281              }).filter( function( diffId ) {
 282                  return _.isUndefined( self.get( diffId ) ) && ! self.requests[ diffId ];
 283              }).value();
 284          },
 285  
 286          _loadAll: function( allRevisionIds, centerId, num ) {
 287              var self = this, deferred = $.Deferred(),
 288                  diffs = _.first( this.getClosestUnloaded( allRevisionIds, centerId ), num );
 289              if ( _.size( diffs ) > 0 ) {
 290                  this.load( diffs ).done( function() {
 291                      self._loadAll( allRevisionIds, centerId, num ).done( function() {
 292                          deferred.resolve();
 293                      });
 294                  }).fail( function() {
 295                      if ( 1 === num ) { // Already tried 1. This just isn't working. Give up.
 296                          deferred.reject();
 297                      } else { // Request fewer diffs this time
 298                          self._loadAll( allRevisionIds, centerId, Math.ceil( num / 2 ) ).done( function() {
 299                              deferred.resolve();
 300                          });
 301                      }
 302                  });
 303              } else {
 304                  deferred.resolve();
 305              }
 306              return deferred;
 307          },
 308  
 309          load: function( comparisons ) {
 310              wp.revisions.log( 'load', comparisons );
 311              // Our collection should only ever grow, never shrink, so remove: false
 312              return this.fetch({ data: { compare: comparisons }, remove: false }).done( function() {
 313                  wp.revisions.log( 'load:complete', comparisons );
 314              });
 315          },
 316  
 317          sync: function( method, model, options ) {
 318              if ( 'read' === method ) {
 319                  options = options || {};
 320                  options.context = this;
 321                  options.data = _.extend( options.data || {}, {
 322                      action: 'get-revision-diffs',
 323                      post_id: revisions.settings.postId
 324                  });
 325  
 326                  var deferred = wp.ajax.send( options ),
 327                      requests = this.requests;
 328  
 329                  // Record that we're requesting each diff.
 330                  if ( options.data.compare ) {
 331                      _.each( options.data.compare, function( id ) {
 332                          requests[ id ] = deferred;
 333                      });
 334                  }
 335  
 336                  // When the request completes, clear the stored request.
 337                  deferred.always( function() {
 338                      if ( options.data.compare ) {
 339                          _.each( options.data.compare, function( id ) {
 340                              delete requests[ id ];
 341                          });
 342                      }
 343                  });
 344  
 345                  return deferred;
 346  
 347              // Otherwise, fall back to `Backbone.sync()`.
 348              } else {
 349                  return Backbone.Model.prototype.sync.apply( this, arguments );
 350              }
 351          }
 352      });
 353  
 354  
 355      revisions.model.FrameState = Backbone.Model.extend({
 356          defaults: {
 357              loading: false,
 358              error: false,
 359              compareTwoMode: false
 360          },
 361  
 362          initialize: function( attributes, options ) {
 363              var properties = {};
 364  
 365              _.bindAll( this, 'receiveDiff' );
 366              this._debouncedEnsureDiff = _.debounce( this._ensureDiff, 200 );
 367  
 368              this.revisions = options.revisions;
 369              this.diffs = new revisions.model.Diffs( [], { revisions: this.revisions });
 370  
 371              // Set the initial diffs collection provided through the settings
 372              this.diffs.set( revisions.settings.diffData );
 373  
 374              // Set up internal listeners
 375              this.listenTo( this, 'change:from', this.changeRevisionHandler );
 376              this.listenTo( this, 'change:to', this.changeRevisionHandler );
 377              this.listenTo( this, 'change:compareTwoMode', this.changeMode );
 378              this.listenTo( this, 'update:revisions', this.updatedRevisions );
 379              this.listenTo( this.diffs, 'ensure:load', this.updateLoadingStatus );
 380              this.listenTo( this, 'update:diff', this.updateLoadingStatus );
 381  
 382              // Set the initial revisions, baseUrl, and mode as provided through settings
 383              properties.to = this.revisions.get( revisions.settings.to );
 384              properties.from = this.revisions.get( revisions.settings.from );
 385              properties.compareTwoMode = revisions.settings.compareTwoMode;
 386              properties.baseUrl = revisions.settings.baseUrl;
 387              this.set( properties );
 388  
 389              // Start the router if browser supports History API
 390              if ( window.history && window.history.pushState ) {
 391                  this.router = new revisions.Router({ model: this });
 392                  Backbone.history.start({ pushState: true });
 393              }
 394          },
 395  
 396          updateLoadingStatus: function() {
 397              this.set( 'error', false );
 398              this.set( 'loading', ! this.diff() );
 399          },
 400  
 401          changeMode: function( model, value ) {
 402              // If we were on the first revision before switching, we have to bump them over one
 403              if ( value && 0 === this.revisions.indexOf( this.get('to') ) ) {
 404                  this.set({
 405                      from: this.revisions.at(0),
 406                      to: this.revisions.at(1)
 407                  });
 408              }
 409          },
 410  
 411          updatedRevisions: function( from, to ) {
 412              if ( this.get( 'compareTwoMode' ) ) {
 413                  // TODO: compare-two loading strategy
 414              } else {
 415                  this.diffs.loadAll( this.revisions.pluck('id'), to.id, 40 );
 416              }
 417          },
 418  
 419          // Fetch the currently loaded diff.
 420          diff: function() {
 421              return this.diffs.get( this._diffId );
 422          },
 423  
 424          // So long as `from` and `to` are changed at the same time, the diff
 425          // will only be updated once. This is because Backbone updates all of
 426          // the changed attributes in `set`, and then fires the `change` events.
 427          updateDiff: function( options ) {
 428              var from, to, diffId, diff;
 429  
 430              options = options || {};
 431              from = this.get('from');
 432              to = this.get('to');
 433              diffId = ( from ? from.id : 0 ) + ':' + to.id;
 434  
 435              // Check if we're actually changing the diff id.
 436              if ( this._diffId === diffId ) {
 437                  return $.Deferred().reject().promise();
 438              }
 439  
 440              this._diffId = diffId;
 441              this.trigger( 'update:revisions', from, to );
 442  
 443              diff = this.diffs.get( diffId );
 444  
 445              // If we already have the diff, then immediately trigger the update.
 446              if ( diff ) {
 447                  this.receiveDiff( diff );
 448                  return $.Deferred().resolve().promise();
 449              // Otherwise, fetch the diff.
 450              } else {
 451                  if ( options.immediate ) {
 452                      return this._ensureDiff();
 453                  } else {
 454                      this._debouncedEnsureDiff();
 455                      return $.Deferred().reject().promise();
 456                  }
 457              }
 458          },
 459  
 460          // A simple wrapper around `updateDiff` to prevent the change event's
 461          // parameters from being passed through.
 462          changeRevisionHandler: function() {
 463              this.updateDiff();
 464          },
 465  
 466          receiveDiff: function( diff ) {
 467              // Did we actually get a diff?
 468              if ( _.isUndefined( diff ) || _.isUndefined( diff.id ) ) {
 469                  this.set({
 470                      loading: false,
 471                      error: true
 472                  });
 473              } else if ( this._diffId === diff.id ) { // Make sure the current diff didn't change
 474                  this.trigger( 'update:diff', diff );
 475              }
 476          },
 477  
 478          _ensureDiff: function() {
 479              return this.diffs.ensure( this._diffId, this ).always( this.receiveDiff );
 480          }
 481      });
 482  
 483  
 484      /**
 485       * ========================================================================
 486       * VIEWS
 487       * ========================================================================
 488       */
 489  
 490      // The frame view. This contains the entire page.
 491      revisions.view.Frame = wp.Backbone.View.extend({
 492          className: 'revisions',
 493          template: wp.template('revisions-frame'),
 494  
 495          initialize: function() {
 496              this.listenTo( this.model, 'update:diff', this.renderDiff );
 497              this.listenTo( this.model, 'change:compareTwoMode', this.updateCompareTwoMode );
 498              this.listenTo( this.model, 'change:loading', this.updateLoadingStatus );
 499              this.listenTo( this.model, 'change:error', this.updateErrorStatus );
 500  
 501              this.views.set( '.revisions-control-frame', new revisions.view.Controls({
 502                  model: this.model
 503              }) );
 504          },
 505  
 506          render: function() {
 507              wp.Backbone.View.prototype.render.apply( this, arguments );
 508  
 509              $('html').css( 'overflow-y', 'scroll' );
 510              $('#wpbody-content .wrap').append( this.el );
 511              this.updateCompareTwoMode();
 512              this.renderDiff( this.model.diff() );
 513              this.views.ready();
 514  
 515              return this;
 516          },
 517  
 518          renderDiff: function( diff ) {
 519              this.views.set( '.revisions-diff-frame', new revisions.view.Diff({
 520                  model: diff
 521              }) );
 522          },
 523  
 524          updateLoadingStatus: function() {
 525              this.$el.toggleClass( 'loading', this.model.get('loading') );
 526          },
 527  
 528          updateErrorStatus: function() {
 529              this.$el.toggleClass( 'diff-error', this.model.get('error') );
 530          },
 531  
 532          updateCompareTwoMode: function() {
 533              this.$el.toggleClass( 'comparing-two-revisions', this.model.get('compareTwoMode') );
 534          }
 535      });
 536  
 537      // The control view.
 538      // This contains the revision slider, previous/next buttons, the meta info and the compare checkbox.
 539      revisions.view.Controls = wp.Backbone.View.extend({
 540          className: 'revisions-controls',
 541  
 542          initialize: function() {
 543              _.bindAll( this, 'setWidth' );
 544  
 545              // Add the button view
 546              this.views.add( new revisions.view.Buttons({
 547                  model: this.model
 548              }) );
 549  
 550              // Add the checkbox view
 551              this.views.add( new revisions.view.Checkbox({
 552                  model: this.model
 553              }) );
 554  
 555              // Prep the slider model
 556              var slider = new revisions.model.Slider({
 557                  frame: this.model,
 558                  revisions: this.model.revisions
 559              }),
 560  
 561              // Prep the tooltip model
 562              tooltip = new revisions.model.Tooltip({
 563                  frame: this.model,
 564                  revisions: this.model.revisions,
 565                  slider: slider
 566              });
 567  
 568              // Add the tooltip view
 569              this.views.add( new revisions.view.Tooltip({
 570                  model: tooltip
 571              }) );
 572  
 573              // Add the tickmarks view
 574              this.views.add( new revisions.view.Tickmarks({
 575                  model: tooltip
 576              }) );
 577  
 578              // Add the slider view
 579              this.views.add( new revisions.view.Slider({
 580                  model: slider
 581              }) );
 582  
 583              // Add the Metabox view
 584              this.views.add( new revisions.view.Metabox({
 585                  model: this.model
 586              }) );
 587          },
 588  
 589          ready: function() {
 590              this.top = this.$el.offset().top;
 591              this.window = $(window);
 592              this.window.on( 'scroll.wp.revisions', {controls: this}, function(e) {
 593                  var controls  = e.data.controls,
 594                      container = controls.$el.parent(),
 595                      scrolled  = controls.window.scrollTop(),
 596                      frame     = controls.views.parent;
 597  
 598                  if ( scrolled >= controls.top ) {
 599                      if ( ! frame.$el.hasClass('pinned') ) {
 600                          controls.setWidth();
 601                          container.css('height', container.height() + 'px' );
 602                          controls.window.on('resize.wp.revisions.pinning click.wp.revisions.pinning', {controls: controls}, function(e) {
 603                              e.data.controls.setWidth();
 604                          });
 605                      }
 606                      frame.$el.addClass('pinned');
 607                  } else if ( frame.$el.hasClass('pinned') ) {
 608                      controls.window.off('.wp.revisions.pinning');
 609                      controls.$el.css('width', 'auto');
 610                      frame.$el.removeClass('pinned');
 611                      container.css('height', 'auto');
 612                      controls.top = controls.$el.offset().top;
 613                  } else {
 614                      controls.top = controls.$el.offset().top;
 615                  }
 616              });
 617          },
 618  
 619          setWidth: function() {
 620              this.$el.css('width', this.$el.parent().width() + 'px');
 621          }
 622      });
 623  
 624      // The tickmarks view
 625      revisions.view.Tickmarks = wp.Backbone.View.extend({
 626          className: 'revisions-tickmarks',
 627          direction: isRtl ? 'right' : 'left',
 628  
 629          initialize: function() {
 630              this.listenTo( this.model, 'change:revision', this.reportTickPosition );
 631          },
 632  
 633          reportTickPosition: function( model, revision ) {
 634              var offset, thisOffset, parentOffset, tick, index = this.model.revisions.indexOf( revision );
 635              thisOffset = this.$el.allOffsets();
 636              parentOffset = this.$el.parent().allOffsets();
 637              if ( index === this.model.revisions.length - 1 ) {
 638                  // Last one
 639                  offset = {
 640                      rightPlusWidth: thisOffset.left - parentOffset.left + 1,
 641                      leftPlusWidth: thisOffset.right - parentOffset.right + 1
 642                  };
 643              } else {
 644                  // Normal tick
 645                  tick = this.$('div:nth-of-type(' + (index + 1) + ')');
 646                  offset = tick.allPositions();
 647                  _.extend( offset, {
 648                      left: offset.left + thisOffset.left - parentOffset.left,
 649                      right: offset.right + thisOffset.right - parentOffset.right
 650                  });
 651                  _.extend( offset, {
 652                      leftPlusWidth: offset.left + tick.outerWidth(),
 653                      rightPlusWidth: offset.right + tick.outerWidth()
 654                  });
 655              }
 656              this.model.set({ offset: offset });
 657          },
 658  
 659          ready: function() {
 660              var tickCount, tickWidth;
 661              tickCount = this.model.revisions.length - 1;
 662              tickWidth = 1 / tickCount;
 663              this.$el.css('width', ( this.model.revisions.length * 50 ) + 'px');
 664  
 665              _(tickCount).times( function( index ){
 666                  this.$el.append( '<div style="' + this.direction + ': ' + ( 100 * tickWidth * index ) + '%"></div>' );
 667              }, this );
 668          }
 669      });
 670  
 671      // The metabox view
 672      revisions.view.Metabox = wp.Backbone.View.extend({
 673          className: 'revisions-meta',
 674  
 675          initialize: function() {
 676              // Add the 'from' view
 677              this.views.add( new revisions.view.MetaFrom({
 678                  model: this.model,
 679                  className: 'diff-meta diff-meta-from'
 680              }) );
 681  
 682              // Add the 'to' view
 683              this.views.add( new revisions.view.MetaTo({
 684                  model: this.model
 685              }) );
 686          }
 687      });
 688  
 689      // The revision meta view (to be extended)
 690      revisions.view.Meta = wp.Backbone.View.extend({
 691          template: wp.template('revisions-meta'),
 692  
 693          events: {
 694              'click .restore-revision': 'restoreRevision'
 695          },
 696  
 697          initialize: function() {
 698              this.listenTo( this.model, 'update:revisions', this.render );
 699          },
 700  
 701          prepare: function() {
 702              return _.extend( this.model.toJSON()[this.type] || {}, {
 703                  type: this.type
 704              });
 705          },
 706  
 707          restoreRevision: function() {
 708              document.location = this.model.get('to').attributes.restoreUrl;
 709          }
 710      });
 711  
 712      // The revision meta 'from' view
 713      revisions.view.MetaFrom = revisions.view.Meta.extend({
 714          className: 'diff-meta diff-meta-from',
 715          type: 'from'
 716      });
 717  
 718      // The revision meta 'to' view
 719      revisions.view.MetaTo = revisions.view.Meta.extend({
 720          className: 'diff-meta diff-meta-to',
 721          type: 'to'
 722      });
 723  
 724      // The checkbox view.
 725      revisions.view.Checkbox = wp.Backbone.View.extend({
 726          className: 'revisions-checkbox',
 727          template: wp.template('revisions-checkbox'),
 728  
 729          events: {
 730              'click .compare-two-revisions': 'compareTwoToggle'
 731          },
 732  
 733          initialize: function() {
 734              this.listenTo( this.model, 'change:compareTwoMode', this.updateCompareTwoMode );
 735          },
 736  
 737          ready: function() {
 738              if ( this.model.revisions.length < 3 ) {
 739                  $('.revision-toggle-compare-mode').hide();
 740              }
 741          },
 742  
 743          updateCompareTwoMode: function() {
 744              this.$('.compare-two-revisions').prop( 'checked', this.model.get('compareTwoMode') );
 745          },
 746  
 747          // Toggle the compare two mode feature when the compare two checkbox is checked.
 748          compareTwoToggle: function() {
 749              // Activate compare two mode?
 750              this.model.set({ compareTwoMode: $('.compare-two-revisions').prop('checked') });
 751          }
 752      });
 753  
 754      // The tooltip view.
 755      // Encapsulates the tooltip.
 756      revisions.view.Tooltip = wp.Backbone.View.extend({
 757          className: 'revisions-tooltip',
 758          template: wp.template('revisions-meta'),
 759  
 760          initialize: function() {
 761              this.listenTo( this.model, 'change:offset', this.render );
 762              this.listenTo( this.model, 'change:hovering', this.toggleVisibility );
 763              this.listenTo( this.model, 'change:scrubbing', this.toggleVisibility );
 764          },
 765  
 766          prepare: function() {
 767              if ( _.isNull( this.model.get('revision') ) ) {
 768                  return;
 769              } else {
 770                  return _.extend( { type: 'tooltip' }, {
 771                      attributes: this.model.get('revision').toJSON()
 772                  });
 773              }
 774          },
 775  
 776          render: function() {
 777              var otherDirection,
 778                  direction,
 779                  directionVal,
 780                  flipped,
 781                  css      = {},
 782                  position = this.model.revisions.indexOf( this.model.get('revision') ) + 1;
 783  
 784              flipped = ( position / this.model.revisions.length ) > 0.5;
 785              if ( isRtl ) {
 786                  direction = flipped ? 'left' : 'right';
 787                  directionVal = flipped ? 'leftPlusWidth' : direction;
 788              } else {
 789                  direction = flipped ? 'right' : 'left';
 790                  directionVal = flipped ? 'rightPlusWidth' : direction;
 791              }
 792              otherDirection = 'right' === direction ? 'left': 'right';
 793              wp.Backbone.View.prototype.render.apply( this, arguments );
 794              css[direction] = this.model.get('offset')[directionVal] + 'px';
 795              css[otherDirection] = '';
 796              this.$el.toggleClass( 'flipped', flipped ).css( css );
 797          },
 798  
 799          visible: function() {
 800              return this.model.get( 'scrubbing' ) || this.model.get( 'hovering' );
 801          },
 802  
 803          toggleVisibility: function() {
 804              if ( this.visible() ) {
 805                  this.$el.stop().show().fadeTo( 100 - this.el.style.opacity * 100, 1 );
 806              } else {
 807                  this.$el.stop().fadeTo( this.el.style.opacity * 300, 0, function(){ $(this).hide(); } );
 808              }
 809              return;
 810          }
 811      });
 812  
 813      // The buttons view.
 814      // Encapsulates all of the configuration for the previous/next buttons.
 815      revisions.view.Buttons = wp.Backbone.View.extend({
 816          className: 'revisions-buttons',
 817          template: wp.template('revisions-buttons'),
 818  
 819          events: {
 820              'click .revisions-next .button': 'nextRevision',
 821              'click .revisions-previous .button': 'previousRevision'
 822          },
 823  
 824          initialize: function() {
 825              this.listenTo( this.model, 'update:revisions', this.disabledButtonCheck );
 826          },
 827  
 828          ready: function() {
 829              this.disabledButtonCheck();
 830          },
 831  
 832          // Go to a specific model index
 833          gotoModel: function( toIndex ) {
 834              var attributes = {
 835                  to: this.model.revisions.at( toIndex )
 836              };
 837              // If we're at the first revision, unset 'from'.
 838              if ( toIndex ) {
 839                  attributes.from = this.model.revisions.at( toIndex - 1 );
 840              } else {
 841                  this.model.unset('from', { silent: true });
 842              }
 843  
 844              this.model.set( attributes );
 845          },
 846  
 847          // Go to the 'next' revision
 848          nextRevision: function() {
 849              var toIndex = this.model.revisions.indexOf( this.model.get('to') ) + 1;
 850              this.gotoModel( toIndex );
 851          },
 852  
 853          // Go to the 'previous' revision
 854          previousRevision: function() {
 855              var toIndex = this.model.revisions.indexOf( this.model.get('to') ) - 1;
 856              this.gotoModel( toIndex );
 857          },
 858  
 859          // Check to see if the Previous or Next buttons need to be disabled or enabled.
 860          disabledButtonCheck: function() {
 861              var maxVal   = this.model.revisions.length - 1,
 862                  minVal   = 0,
 863                  next     = $('.revisions-next .button'),
 864                  previous = $('.revisions-previous .button'),
 865                  val      = this.model.revisions.indexOf( this.model.get('to') );
 866  
 867              // Disable "Next" button if you're on the last node.
 868              next.prop( 'disabled', ( maxVal === val ) );
 869  
 870              // Disable "Previous" button if you're on the first node.
 871              previous.prop( 'disabled', ( minVal === val ) );
 872          }
 873      });
 874  
 875  
 876      // The slider view.
 877      revisions.view.Slider = wp.Backbone.View.extend({
 878          className: 'wp-slider',
 879          direction: isRtl ? 'right' : 'left',
 880  
 881          events: {
 882              'mousemove' : 'mouseMove'
 883          },
 884  
 885          initialize: function() {
 886              _.bindAll( this, 'start', 'slide', 'stop', 'mouseMove', 'mouseEnter', 'mouseLeave' );
 887              this.listenTo( this.model, 'update:slider', this.applySliderSettings );
 888          },
 889  
 890          ready: function() {
 891              this.$el.css('width', ( this.model.revisions.length * 50 ) + 'px');
 892              this.$el.slider( _.extend( this.model.toJSON(), {
 893                  start: this.start,
 894                  slide: this.slide,
 895                  stop:  this.stop
 896              }) );
 897  
 898              this.$el.hoverIntent({
 899                  over: this.mouseEnter,
 900                  out: this.mouseLeave,
 901                  timeout: 800
 902              });
 903  
 904              this.applySliderSettings();
 905          },
 906  
 907          mouseMove: function( e ) {
 908              var zoneCount         = this.model.revisions.length - 1, // One fewer zone than models
 909                  sliderFrom        = this.$el.allOffsets()[this.direction], // "From" edge of slider
 910                  sliderWidth       = this.$el.width(), // Width of slider
 911                  tickWidth         = sliderWidth / zoneCount, // Calculated width of zone
 912                  actualX           = ( isRtl ? $(window).width() - e.pageX : e.pageX ) - sliderFrom, // Flipped for RTL - sliderFrom;
 913                  currentModelIndex = Math.floor( ( actualX  + ( tickWidth / 2 )  ) / tickWidth ); // Calculate the model index
 914  
 915              // Ensure sane value for currentModelIndex.
 916              if ( currentModelIndex < 0 ) {
 917                  currentModelIndex = 0;
 918              } else if ( currentModelIndex >= this.model.revisions.length ) {
 919                  currentModelIndex = this.model.revisions.length - 1;
 920              }
 921  
 922              // Update the tooltip mode
 923              this.model.set({ hoveredRevision: this.model.revisions.at( currentModelIndex ) });
 924          },
 925  
 926          mouseLeave: function() {
 927              this.model.set({ hovering: false });
 928          },
 929  
 930          mouseEnter: function() {
 931              this.model.set({ hovering: true });
 932          },
 933  
 934          applySliderSettings: function() {
 935              this.$el.slider( _.pick( this.model.toJSON(), 'value', 'values', 'range' ) );
 936              var handles = this.$('a.ui-slider-handle');
 937  
 938              if ( this.model.get('compareTwoMode') ) {
 939                  // in RTL mode the 'left handle' is the second in the slider, 'right' is first
 940                  handles.first()
 941                      .toggleClass( 'to-handle', !! isRtl )
 942                      .toggleClass( 'from-handle', ! isRtl );
 943                  handles.last()
 944                      .toggleClass( 'from-handle', !! isRtl )
 945                      .toggleClass( 'to-handle', ! isRtl );
 946              } else {
 947                  handles.removeClass('from-handle to-handle');
 948              }
 949          },
 950  
 951          start: function( event, ui ) {
 952              this.model.set({ scrubbing: true });
 953  
 954              // Track the mouse position to enable smooth dragging,
 955              // overrides default jQuery UI step behavior.
 956              $( window ).on( 'mousemove.wp.revisions', { view: this }, function( e ) {
 957                  var handles,
 958                      view              = e.data.view,
 959                      leftDragBoundary  = view.$el.offset().left,
 960                      sliderOffset      = leftDragBoundary,
 961                      sliderRightEdge   = leftDragBoundary + view.$el.width(),
 962                      rightDragBoundary = sliderRightEdge,
 963                      leftDragReset     = '0',
 964                      rightDragReset    = '100%',
 965                      handle            = $( ui.handle );
 966  
 967                  // In two handle mode, ensure handles can't be dragged past each other.
 968                  // Adjust left/right boundaries and reset points.
 969                  if ( view.model.get('compareTwoMode') ) {
 970                      handles = handle.parent().find('.ui-slider-handle');
 971                      if ( handle.is( handles.first() ) ) { // We're the left handle
 972                          rightDragBoundary = handles.last().offset().left;
 973                          rightDragReset    = rightDragBoundary - sliderOffset;
 974                      } else { // We're the right handle
 975                          leftDragBoundary = handles.first().offset().left + handles.first().width();
 976                          leftDragReset    = leftDragBoundary - sliderOffset;
 977                      }
 978                  }
 979  
 980                  // Follow mouse movements, as long as handle remains inside slider.
 981                  if ( e.pageX < leftDragBoundary ) {
 982                      handle.css( 'left', leftDragReset ); // Mouse to left of slider.
 983                  } else if ( e.pageX > rightDragBoundary ) {
 984                      handle.css( 'left', rightDragReset ); // Mouse to right of slider.
 985                  } else {
 986                      handle.css( 'left', e.pageX - sliderOffset ); // Mouse in slider.
 987                  }
 988              } );
 989          },
 990  
 991          getPosition: function( position ) {
 992              return isRtl ? this.model.revisions.length - position - 1: position;
 993          },
 994  
 995          // Responds to slide events
 996          slide: function( event, ui ) {
 997              var attributes, movedRevision;
 998              // Compare two revisions mode
 999              if ( this.model.get('compareTwoMode') ) {
1000                  // Prevent sliders from occupying same spot
1001                  if ( ui.values[1] === ui.values[0] ) {
1002                      return false;
1003                  }
1004                  if ( isRtl ) {
1005                      ui.values.reverse();
1006                  }
1007                  attributes = {
1008                      from: this.model.revisions.at( this.getPosition( ui.values[0] ) ),
1009                      to: this.model.revisions.at( this.getPosition( ui.values[1] ) )
1010                  };
1011              } else {
1012                  attributes = {
1013                      to: this.model.revisions.at( this.getPosition( ui.value ) )
1014                  };
1015                  // If we're at the first revision, unset 'from'.
1016                  if ( this.getPosition( ui.value ) > 0 ) {
1017                      attributes.from = this.model.revisions.at( this.getPosition( ui.value ) - 1 );
1018                  } else {
1019                      attributes.from = undefined;
1020                  }
1021              }
1022              movedRevision = this.model.revisions.at( this.getPosition( ui.value ) );
1023  
1024              // If we are scrubbing, a scrub to a revision is considered a hover
1025              if ( this.model.get('scrubbing') ) {
1026                  attributes.hoveredRevision = movedRevision;
1027              }
1028  
1029              this.model.set( attributes );
1030          },
1031  
1032          stop: function() {
1033              $( window ).off('mousemove.wp.revisions');
1034              this.model.updateSliderSettings(); // To snap us back to a tick mark
1035              this.model.set({ scrubbing: false });
1036          }
1037      });
1038  
1039      // The diff view.
1040      // This is the view for the current active diff.
1041      revisions.view.Diff = wp.Backbone.View.extend({
1042          className: 'revisions-diff',
1043          template:  wp.template('revisions-diff'),
1044  
1045          // Generate the options to be passed to the template.
1046          prepare: function() {
1047              return _.extend({ fields: this.model.fields.toJSON() }, this.options );
1048          }
1049      });
1050  
1051      // The revisions router
1052      // takes URLs with #hash fragments and routes them
1053      revisions.Router = Backbone.Router.extend({
1054          initialize: function( options ) {
1055              this.model = options.model;
1056              this.routes = _.object([
1057                  [ this.baseUrl( '?from=:from&to=:to' ), 'handleRoute' ],
1058                  [ this.baseUrl( '?from=:from&to=:to' ), 'handleRoute' ]
1059              ]);
1060              // Maintain state and history when navigating
1061              this.listenTo( this.model, 'update:diff', _.debounce( this.updateUrl, 250 ) );
1062              this.listenTo( this.model, 'change:compareTwoMode', this.updateUrl );
1063          },
1064  
1065          baseUrl: function( url ) {
1066              return this.model.get('baseUrl') + url;
1067          },
1068  
1069          updateUrl: function() {
1070              var from = this.model.has('from') ? this.model.get('from').id : 0,
1071                  to   = this.model.get('to').id;
1072              if ( this.model.get('compareTwoMode' ) ) {
1073                  this.navigate( this.baseUrl( '?from=' + from + '&to=' + to ) );
1074              } else {
1075                  this.navigate( this.baseUrl( '?revision=' + to ) );
1076              }
1077          },
1078  
1079          handleRoute: function( a, b ) {
1080              var compareTwo = _.isUndefined( b );
1081  
1082              if ( ! compareTwo ) {
1083                  b = this.model.revisions.get( a );
1084                  a = this.model.revisions.prev( b );
1085                  b = b ? b.id : 0;
1086                  a = a ? a.id : 0;
1087              }
1088  
1089              this.model.set({
1090                  from: this.model.revisions.get( parseInt( a, 10 ) ),
1091                  to: this.model.revisions.get( parseInt( a, 10 ) ),
1092                  compareTwoMode: compareTwo
1093              });
1094          }
1095      });
1096  
1097      // Initialize the revisions UI.
1098      revisions.init = function() {
1099          revisions.view.frame = new revisions.view.Frame({
1100              model: new revisions.model.FrameState({}, {
1101                  revisions: new revisions.model.Revisions( revisions.settings.revisionData )
1102              })
1103          }).render();
1104      };
1105  
1106      $( revisions.init );
1107  }(jQuery));


Generated: Tue Mar 25 01:41:18 2014 WordPress honlapkészítés: online1.hu