Index: wp-admin/js/revisions.js =================================================================== --- wp-admin/js/revisions.js (revision 24737) +++ wp-admin/js/revisions.js (working copy) @@ -16,6 +16,15 @@ console.log.apply( console, arguments ); }; + // Handy function to help with positioning + $.fn.allOffsets = function() { + var offset = this.offset() || {top: 0, left: 0}, win = $(window); + return _.extend( offset, { + right: win.width() - offset.left - this.outerWidth(), + bottom: win.height() - offset.top - this.outerHeight() + }); + }; + // wp_localize_script transforms top-level numbers into strings. Undo that. if ( revisions.settings.to ) revisions.settings.to = parseInt( revisions.settings.to, 10 ); @@ -125,19 +134,24 @@ revisions.model.Tooltip = Backbone.Model.extend({ defaults: { revision: null, + offset: {}, hovering: false, // Whether the mouse is hovering scrubbing: false // Whether the mouse is scrubbing }, initialize: function( options ) { + this.frame = options.frame; this.revisions = options.revisions; this.slider = options.slider; this.listenTo( this.slider, 'hovered:revision', this.updateRevision ); this.listenTo( this.slider, 'change:hovering', this.setHovering ); this.listenTo( this.slider, 'change:scrubbing', this.setScrubbing ); + + this.set({ revision: this.frame.diff() }); }, + updateRevision: function( revision ) { this.set({ revision: revision }); }, @@ -475,6 +489,7 @@ render: function() { wp.Backbone.View.prototype.render.apply( this, arguments ); + $('html').css( 'overflow-y', 'scroll' ); $('#wpbody-content .wrap').append( this.el ); this.updateCompareTwoMode(); this.renderDiff( this.model.diff() ); @@ -524,17 +539,21 @@ revisions: this.model.revisions }); + // Prep the tooltip model + var tooltip = new revisions.model.Tooltip({ + frame: this.model, + revisions: this.model.revisions, + slider: slider + }); + // Add the tooltip view this.views.add( new revisions.view.Tooltip({ - model: new revisions.model.Tooltip({ - revisions: this.model.revisions, - slider: slider - }) + model: tooltip }) ); // Add the tickmarks view this.views.add( new revisions.view.Tickmarks({ - model: this.model + model: tooltip }) ); // Add the slider view @@ -554,12 +573,36 @@ revisions.view.Tickmarks = wp.Backbone.View.extend({ className: 'revisions-tickmarks', + initialize: function() { + this.listenTo( this.model, 'change:revision', this.reportTickPosition ); + }, + + reportTickPosition: function( model, revision ) { + var elWidth, offset, tick, index = this.model.revisions.indexOf( revision ); + if ( index === this.model.revisions.length - 1 ) { + // Last one + offset = this.$el.allOffsets(); + elWidth = this.$el.outerWidth(); + // adjust + _.extend( offset, { + right: offset.right + elWidth + 1, + left: offset.left + elWidth + 1 + }); + } else { + // Normal tick + tick = this.$('div:nth-of-type(' + (index + 1) + ')'); + offset = tick.allOffsets(); + } + this.model.set({ offset: offset }); + }, + ready: function() { var tickCount, tickWidth; tickCount = this.model.revisions.length - 1; tickWidth = 1 / tickCount; _(tickCount).times( function(){ this.$el.append( '
' ); }, this ); + this.$('div').css( 'width', ( 100 * tickWidth ) + '%' ); } }); @@ -625,13 +668,27 @@ revisions.view.Tooltip = wp.Backbone.View.extend({ className: 'revisions-tooltip', template: wp.template('revisions-tooltip'), + direction: isRtl ? 'right' : 'left', initialize: function( options ) { - this.listenTo( this.model, 'change:revision', this.render ); + this.listenTo( this.model, 'change:offset', this.render ); this.listenTo( this.model, 'change:hovering', this.toggleVisibility ); this.listenTo( this.model, 'change:scrubbing', this.toggleVisibility ); }, + prepare: function() { + return this.model.get('revision').toJSON(); + }, + + render: function() { + var css = {}; + var top = window.pageYOffset || document.documentElement.scrollTop; // Offset correctly from top when scrolled + wp.Backbone.View.prototype.render.apply( this, arguments ); + css[this.direction] = this.model.get('offset')[this.direction] + 'px'; + css.bottom = ( this.model.get('offset').bottom + top ) + 'px'; + this.$el.css( css ); + }, + visible: function() { return this.model.get( 'scrubbing' ) || this.model.get( 'hovering' ); }, @@ -644,21 +701,14 @@ return; }, - render: function() { - var offset; - // Check if a revision exists. - if ( _.isNull( this.model.get('revision') ) ) - return; + ready: function() { + var self = this; - this.$el.html( this.template( this.model.get('revision').toJSON() ) ); - - // Set the position. - offset = this.model.revisions.indexOf( this.model.get('revision') ) / ( this.model.revisions.length - 1 ); - // 15% to get us to the start of the slider - // 0.7 to convert the slider-relative percentage to a page-relative percentage - // 100 to convert to a percentage - offset = 15 + (0.7 * offset * 100 ); // Now in a percentage - this.$el.css( isRtl ? 'right' : 'left', offset + '%' ); + // If page scrolls, ensure tooltip stays with slider + _.debounce( $(window).scroll(function () { + var top = window.pageYOffset || document.documentElement.scrollTop; + self.$el.css( 'bottom', ( self.model.get('offset').bottom + top ) + 'px' ); + } ), 250 ); } }); @@ -681,31 +731,29 @@ this.disabledButtonCheck(); }, - // Go to a specific modelindex, taking into account RTL mode. + // Go to a specific model index gotoModel: function( toIndex ) { var attributes = { - to: this.model.revisions.at( isRtl ? this.model.revisions.length - toIndex - 1 : toIndex ) // Reverse directions for RTL. + to: this.model.revisions.at( toIndex ) }; // If we're at the first revision, unset 'from'. - if ( isRtl ? this.model.revisions.length - toIndex - 1 : toIndex ) // Reverse directions for RTL - attributes.from = this.model.revisions.at( isRtl ? this.model.revisions.length - toIndex - 2 : toIndex - 1 ); + if ( toIndex ) + attributes.from = this.model.revisions.at( toIndex - 1 ); else this.model.unset('from', { silent: true }); this.model.set( attributes ); }, - // Go to the 'next' revision, direction takes into account RTL mode. + // Go to the 'next' revision nextRevision: function() { - var toIndex = isRtl ? this.model.revisions.length - this.model.revisions.indexOf( this.model.get('to') ) - 1 : this.model.revisions.indexOf( this.model.get('to') ); - toIndex = isRtl ? toIndex - 1 : toIndex + 1; + var toIndex = this.model.revisions.indexOf( this.model.get('to') ) + 1; this.gotoModel( toIndex ); }, - // Go to the 'previous' revision, direction takes into account RTL mode. + // Go to the 'previous' revision previousRevision: function() { - var toIndex = isRtl ? this.model.revisions.length - this.model.revisions.indexOf( this.model.get('to') ) - 1 : this.model.revisions.indexOf( this.model.get('to') ); - toIndex = isRtl ? toIndex + 1 : toIndex - 1; + var toIndex = this.model.revisions.indexOf( this.model.get('to') ) - 1; this.gotoModel( toIndex ); }, @@ -729,6 +777,7 @@ // The slider view. revisions.view.Slider = wp.Backbone.View.extend({ className: 'wp-slider', + direction: isRtl ? 'right' : 'left', events: { 'mousemove' : 'mouseMove' @@ -757,23 +806,20 @@ mouseMove: function( e ) { var zoneCount = this.model.revisions.length - 1, // One fewer zone than models - sliderLeft = this.$el.offset().left, // Left edge of slider + sliderFrom = this.$el.allOffsets()[this.direction], // "From" edge of slider sliderWidth = this.$el.width(), // Width of slider tickWidth = sliderWidth / zoneCount, // Calculated width of zone - actualX = e.clientX - sliderLeft, // Offset of mouse position in slider - currentModelIndex = Math.floor( ( actualX + ( tickWidth / 2 ) ) / tickWidth ); // Calculate the model index + actualX = e.pageX - sliderFrom; // Offset of mouse position in slider + actualX = isRtl ? sliderWidth - actualX : actualX; + var currentModelIndex = Math.floor( ( actualX + ( tickWidth / 2 ) ) / tickWidth ); // Calculate the model index - // Reverse direction in RTL mode. - if ( isRtl ) - currentModelIndex = this.model.revisions.length - currentModelIndex - 1; - // Ensure sane value for currentModelIndex. if ( currentModelIndex < 0 ) currentModelIndex = 0; else if ( currentModelIndex >= this.model.revisions.length ) currentModelIndex = this.model.revisions.length - 1; - // Update the tooltip model + // Update the tooltip mode this.model.set({ hoveredRevision: this.model.revisions.at( currentModelIndex ) }); }, @@ -792,103 +838,84 @@ if ( this.model.get('compareTwoMode') ) { // in RTL mode the 'left handle' is the second in the slider, 'right' is first handles.first() - .toggleClass( 'right-handle', !! isRtl ) - .toggleClass( 'left-handle', ! isRtl ); + .toggleClass( 'to-handle', !! isRtl ) + .toggleClass( 'from-handle', ! isRtl ); handles.last() - .toggleClass( 'left-handle', !! isRtl ) - .toggleClass( 'right-handle', ! isRtl ); + .toggleClass( 'from-handle', !! isRtl ) + .toggleClass( 'to-handle', ! isRtl ); } else { - handles.removeClass('left-handle right-handle'); + handles.removeClass('from-handle to-handle'); } }, - getSliderPosition: function( ui ){ - return isRtl ? this.model.revisions.length - ui.value - 1 : ui.value; - }, - start: function( event, ui ) { this.model.set({ scrubbing: true }); // Track the mouse position to enable smooth dragging, // overrides default jQuery UI step behavior. $( window ).on( 'mousemove.wp.revisions', { view: this }, function( e ) { - var view = e.data.view, - leftDragBoundary = view.$el.offset().left, // Initial left boundary - sliderOffset = leftDragBoundary, - sliderRightEdge = leftDragBoundary + view.$el.width(), - rightDragBoundary = sliderRightEdge, // Initial right boundary - leftDragReset = 0, // Initial left drag reset - rightDragReset = sliderRightEdge - sliderOffset; // Initial right drag reset + var view = e.data.view, + leftDragBoundary = view.$el.offset().left, + sliderOffset = leftDragBoundary, + sliderRightEdge = leftDragBoundary + view.$el.width(), + rightDragBoundary = sliderRightEdge, + leftDragReset = '0', + rightDragReset = '100%', + handle = $( ui.handle ); // In two handle mode, ensure handles can't be dragged past each other. // Adjust left/right boundaries and reset points. if ( view.model.get('compareTwoMode') ) { - var rightHandle = $( ui.handle ).parent().find('.right-handle'), - leftHandle = $( ui.handle ).parent().find('.left-handle'); - - if ( $( ui.handle ).hasClass('left-handle') ) { - // Dragging the left handle, boundary is right handle. - // RTL mode calculations reverse directions. - if ( isRtl ) { - leftDragBoundary = rightHandle.offset().left + rightHandle.width(); - leftDragReset = leftDragBoundary - sliderOffset; - } else { - rightDragBoundary = rightHandle.offset().left; - rightDragReset = rightDragBoundary - sliderOffset; - } - } else { - // Dragging the right handle, boundary is the left handle. - // RTL mode calculations reverse directions. - if ( isRtl ) { - rightDragBoundary = leftHandle.offset().left; - rightDragReset = rightDragBoundary - sliderOffset; - } else { - leftDragBoundary = leftHandle.offset().left + leftHandle.width() ; - leftDragReset = leftDragBoundary - sliderOffset; - } + var handles = handle.parent().find('.ui-slider-handle'); + if ( handle.is( handles.first() ) ) { // We're the left handle + rightDragBoundary = handles.last().offset().left; + rightDragReset = rightDragBoundary - sliderOffset; + } else { // We're the right handle + leftDragBoundary = handles.first().offset().left + handles.first().width(); + leftDragReset = leftDragBoundary - sliderOffset; } } // Follow mouse movements, as long as handle remains inside slider. - if ( e.clientX < leftDragBoundary ) { - $( ui.handle ).css( 'left', leftDragReset ); // Mouse to left of slider. - } else if ( e.clientX > rightDragBoundary ) { - $( ui.handle ).css( 'left', rightDragReset ); // Mouse to right of slider. + if ( e.pageX < leftDragBoundary ) { + handle.css( 'left', leftDragReset ); // Mouse to left of slider. + } else if ( e.pageX > rightDragBoundary ) { + handle.css( 'left', rightDragReset ); // Mouse to right of slider. } else { - $( ui.handle ).css( 'left', e.clientX - sliderOffset ); // Mouse in slider. + handle.css( 'left', e.pageX - sliderOffset ); // Mouse in slider. } } ); }, + getPosition: function( position ) { + return isRtl ? this.model.revisions.length - position - 1: position; + }, + // Responds to slide events slide: function( event, ui ) { - var attributes, movedRevision, sliderPosition; + var attributes, movedRevision; // Compare two revisions mode if ( this.model.get('compareTwoMode') ) { // Prevent sliders from occupying same spot if ( ui.values[1] === ui.values[0] ) return false; - + if ( isRtl ) + ui.values.reverse(); attributes = { - to: this.model.revisions.at( isRtl ? this.model.revisions.length - ui.values[0] - 1 : ui.values[1] ), - from: this.model.revisions.at( isRtl ? this.model.revisions.length - ui.values[1] - 1 : ui.values[0] ) + from: this.model.revisions.at( this.getPosition( ui.values[0] ) ), + to: this.model.revisions.at( this.getPosition( ui.values[1] ) ) }; - if ( isRtl ) - movedRevision = ui.value === ui.values[1] ? attributes.from : attributes.to; - else - movedRevision = ui.value === ui.values[0] ? attributes.from : attributes.to; } else { - sliderPosition = this.getSliderPosition( ui ); attributes = { - to: this.model.revisions.at( sliderPosition ) + to: this.model.revisions.at( this.getPosition( ui.value ) ) }; - movedRevision = attributes.to; // If we're at the first revision, unset 'from'. - if ( sliderPosition ) // Reverse directions for RTL. - attributes.from = this.model.revisions.at( sliderPosition - 1 ); + if ( this.getPosition( ui.value ) > 0 ) + attributes.from = this.model.revisions.at( this.getPosition( ui.value ) - 1 ); else attributes.from = undefined; } + movedRevision = this.model.revisions.at( this.getPosition( ui.value ) ); // If we are scrubbing, a scrub to a revision is considered a hover if ( this.model.get('scrubbing') ) Index: wp-admin/revision.php =================================================================== --- wp-admin/revision.php (revision 24737) +++ wp-admin/revision.php (working copy) @@ -138,7 +138,7 @@ ({{ data.dateShort }}) <# } #> - +