: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
// A simple wrapper around `updateDiff` to prevent the change event's
// parameters from being passed through.
changeRevisionHandler: function() {
receiveDiff: function( diff ) {
// Did we actually get a diff?
if ( _.isUndefined( diff ) || _.isUndefined( diff.id ) ) {
} else if ( this._diffId === diff.id ) { // Make sure the current diff didn't change.
this.trigger( 'update:diff', diff );
_ensureDiff: function() {
return this.diffs.ensure( this._diffId, this ).always( this.receiveDiff );
* ========================================================================
* ========================================================================
* wp.revisions.view.Frame
* Top level frame that orchestrates the revisions experience.
* @param {object} options The options hash for the view.
* @param {revisions.model.FrameState} options.model The frame state model.
revisions.view.Frame = wp.Backbone.View.extend({
template: wp.template('revisions-frame'),
this.listenTo( this.model, 'update:diff', this.renderDiff );
this.listenTo( this.model, 'change:compareTwoMode', this.updateCompareTwoMode );
this.listenTo( this.model, 'change:loading', this.updateLoadingStatus );
this.listenTo( this.model, 'change:error', this.updateErrorStatus );
this.views.set( '.revisions-control-frame', new revisions.view.Controls({
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() );
renderDiff: function( diff ) {
this.views.set( '.revisions-diff-frame', new revisions.view.Diff({
updateLoadingStatus: function() {
this.$el.toggleClass( 'loading', this.model.get('loading') );
updateErrorStatus: function() {
this.$el.toggleClass( 'diff-error', this.model.get('error') );
updateCompareTwoMode: function() {
this.$el.toggleClass( 'comparing-two-revisions', this.model.get('compareTwoMode') );
* wp.revisions.view.Controls
* Contains the revision slider, previous/next buttons, the meta info and the compare checkbox.
revisions.view.Controls = wp.Backbone.View.extend({
className: 'revisions-controls',
_.bindAll( this, 'setWidth' );
this.views.add( new revisions.view.Buttons({
// Add the checkbox view.
this.views.add( new revisions.view.Checkbox({
// Prep the slider model.
var slider = new revisions.model.Slider({
revisions: this.model.revisions
// Prep the tooltip model.
tooltip = new revisions.model.Tooltip({
revisions: this.model.revisions,
this.views.add( new revisions.view.Tooltip({
// Add the tickmarks view.
this.views.add( new revisions.view.Tickmarks({
this.views.add( new revisions.view.Slider({
this.views.add( new revisions.view.Metabox({
this.top = this.$el.offset().top;
this.window.on( 'scroll.wp.revisions', {controls: this}, function(e) {
var controls = e.data.controls,
container = controls.$el.parent(),
scrolled = controls.window.scrollTop(),
frame = controls.views.parent;
if ( scrolled >= controls.top ) {
if ( ! frame.$el.hasClass('pinned') ) {
container.css('height', container.height() + 'px' );
controls.window.on('resize.wp.revisions.pinning click.wp.revisions.pinning', {controls: controls}, function(e) {
e.data.controls.setWidth();
frame.$el.addClass('pinned');
} else if ( frame.$el.hasClass('pinned') ) {
controls.window.off('.wp.revisions.pinning');
controls.$el.css('width', 'auto');
frame.$el.removeClass('pinned');
container.css('height', 'auto');
controls.top = controls.$el.offset().top;
controls.top = controls.$el.offset().top;
this.$el.css('width', this.$el.parent().width() + 'px');
revisions.view.Tickmarks = wp.Backbone.View.extend({
className: 'revisions-tickmarks',
direction: isRtl ? 'right' : 'left',
this.listenTo( this.model, 'change:revision', this.reportTickPosition );
reportTickPosition: function( model, revision ) {
var offset, thisOffset, parentOffset, tick, index = this.model.revisions.indexOf( revision );
thisOffset = this.$el.allOffsets();
parentOffset = this.$el.parent().allOffsets();
if ( index === this.model.revisions.length - 1 ) {
rightPlusWidth: thisOffset.left - parentOffset.left + 1,
leftPlusWidth: thisOffset.right - parentOffset.right + 1
tick = this.$('div:nth-of-type(' + (index + 1) + ')');
offset = tick.allPositions();
left: offset.left + thisOffset.left - parentOffset.left,
right: offset.right + thisOffset.right - parentOffset.right
leftPlusWidth: offset.left + tick.outerWidth(),
rightPlusWidth: offset.right + tick.outerWidth()
this.model.set({ offset: offset });
var tickCount, tickWidth;
tickCount = this.model.revisions.length - 1;
tickWidth = 1 / tickCount;
this.$el.css('width', ( this.model.revisions.length * 50 ) + 'px');
_(tickCount).times( function( index ){
this.$el.append( '<div style="' + this.direction + ': ' + ( 100 * tickWidth * index ) + '%"></div>' );
revisions.view.Metabox = wp.Backbone.View.extend({
className: 'revisions-meta',
this.views.add( new revisions.view.MetaFrom({
className: 'diff-meta diff-meta-from'
this.views.add( new revisions.view.MetaTo({
// The revision meta view (to be extended).
revisions.view.Meta = wp.Backbone.View.extend({
template: wp.template('revisions-meta'),
'click .restore-revision': 'restoreRevision'
this.listenTo( this.model, 'update:revisions', this.render );
return _.extend( this.model.toJSON()[this.type] || {}, {
restoreRevision: function() {
document.location = this.model.get('to').attributes.restoreUrl;
// The revision meta 'from' view.
revisions.view.MetaFrom = revisions.view.Meta.extend({
className: 'diff-meta diff-meta-from',
// The revision meta 'to' view.
revisions.view.MetaTo = revisions.view.Meta.extend({
className: 'diff-meta diff-meta-to',
revisions.view.Checkbox = wp.Backbone.View.extend({
className: 'revisions-checkbox',
template: wp.template('revisions-checkbox'),
'click .compare-two-revisions': 'compareTwoToggle'
this.listenTo( this.model, 'change:compareTwoMode', this.updateCompareTwoMode );
if ( this.model.revisions.length < 3 ) {
$('.revision-toggle-compare-mode').hide();
updateCompareTwoMode: function() {
this.$('.compare-two-revisions').prop( 'checked', this.model.get('compareTwoMode') );
// Toggle the compare two mode feature when the compare two checkbox is checked.
compareTwoToggle: function() {
// Activate compare two mode?
this.model.set({ compareTwoMode: $('.compare-two-revisions').prop('checked') });
// Encapsulates the tooltip.
revisions.view.Tooltip = wp.Backbone.View.extend({
className: 'revisions-tooltip',
template: wp.template('revisions-meta'),
this.listenTo( this.model, 'change:offset', this.render );
this.listenTo( this.model, 'change:hovering', this.toggleVisibility );
this.listenTo( this.model, 'change:scrubbing', this.toggleVisibility );
if ( _.isNull( this.model.get('revision') ) ) {
return _.extend( { type: 'tooltip' }, {
attributes: this.model.get('revision').toJSON()
position = this.model.revisions.indexOf( this.model.get('revision') ) + 1;
flipped = ( position / this.model.revisions.length ) > 0.5;
direction = flipped ? 'left' : 'right';
directionVal = flipped ? 'leftPlusWidth' : direction;
direction = flipped ? 'right' : 'left';
directionVal = flipped ? 'rightPlusWidth' : direction;
otherDirection = 'right' === direction ? 'left': 'right';
wp.Backbone.View.prototype.render.apply( this, arguments );
css[direction] = this.model.get('offset')[directionVal] + 'px';
css[otherDirection] = '';
this.$el.toggleClass( 'flipped', flipped ).css( css );
return this.model.get( 'scrubbing' ) || this.model.get( 'hovering' );
toggleVisibility: function() {
this.$el.stop().show().fadeTo( 100 - this.el.style.opacity * 100, 1 );
this.$el.stop().fadeTo( this.el.style.opacity * 300, 0, function(){ $(this).hide(); } );
// Encapsulates all of the configuration for the previous/next buttons.
revisions.view.Buttons = wp.Backbone.View.extend({
className: 'revisions-buttons',
template: wp.template('revisions-buttons'),
'click .revisions-next .button': 'nextRevision',
'click .revisions-previous .button': 'previousRevision'
this.listenTo( this.model, 'update:revisions', this.disabledButtonCheck );
this.disabledButtonCheck();
// Go to a specific model index.
gotoModel: function( toIndex ) {
to: this.model.revisions.at( toIndex )
// If we're at the first revision, unset 'from'.
attributes.from = this.model.revisions.at( toIndex - 1 );
this.model.unset('from', { silent: true });
this.model.set( attributes );
// Go to the 'next' revision.
nextRevision: function() {
var toIndex = this.model.revisions.indexOf( this.model.get('to') ) + 1;
this.gotoModel( toIndex );
// Go to the 'previous' revision.
previousRevision: function() {
var toIndex = this.model.revisions.indexOf( this.model.get('to') ) - 1;
this.gotoModel( toIndex );
// Check to see if the Previous or Next buttons need to be disabled or enabled.
disabledButtonCheck: function() {
var maxVal = this.model.revisions.length - 1,
next = $('.revisions-next .button'),
previous = $('.revisions-previous .button'),
val = this.model.revisions.indexOf( this.model.get('to') );
// Disable "Next" button if you're on the last node.
next.prop( 'disabled', ( maxVal === val ) );
// Disable "Previous" button if you're on the first node.
previous.prop( 'disabled', ( minVal === val ) );
revisions.view.Slider = wp.Backbone.View.extend({
direction: isRtl ? 'right' : 'left',
'mousemove' : 'mouseMove'
_.bindAll( this, 'start', 'slide', 'stop', 'mouseMove', 'mouseEnter', 'mouseLeave' );
this.listenTo( this.model, 'update:slider', this.applySliderSettings );
this.$el.css('width', ( this.model.revisions.length * 50 ) + 'px');
this.$el.slider( _.extend( this.model.toJSON(), {
this.applySliderSettings();
mouseMove: function( e ) {
var zoneCount = this.model.revisions.length - 1, // One fewer zone than models.
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 = ( isRtl ? $(window).width() - e.pageX : e.pageX ) - sliderFrom, // Flipped for RTL - sliderFrom.
currentModelIndex = Math.floor( ( actualX + ( tickWidth / 2 ) ) / tickWidth ); // Calculate the model index.
// Ensure sane value for currentModelIndex.
if ( currentModelIndex < 0 ) {
} else if ( currentModelIndex >= this.model.revisions.length ) {
currentModelIndex = this.model.revisions.length - 1;
// Update the tooltip mode.
this.model.set({ hoveredRevision: this.model.revisions.at( currentModelIndex ) });
this.model.set({ hovering: false });
this.model.set({ hovering: true });
applySliderSettings: function() {
this.$el.slider( _.pick( this.model.toJSON(), 'value', 'values', 'range' ) );
var handles = this.$('a.ui-slider-handle');
if ( this.model.get('compareTwoMode') ) {
// In RTL mode the 'left handle' is the second in the slider, 'right' is first.
.toggleClass( 'to-handle', !! isRtl )
.toggleClass( 'from-handle', ! isRtl );
.toggleClass( 'from-handle', !! isRtl )
.toggleClass( 'to-handle', ! isRtl );
handles.removeClass('from-handle to-handle');