window.wp = window.wp || {}; (function($) { var Revision, Revisions, Diff, l10n, revisions; revisions = wp.revisions = function() { Diff = revisions.Diff = new Diff(); }; _.extend( revisions, { model: {}, view: {}, controller: {} } ); // Link any localized strings. l10n = revisions.model.l10n = typeof wpRevisionsL10n === 'undefined' ? {} : wpRevisionsL10n; // Link any settings. revisions.model.settings = l10n.settings || {}; delete l10n.settings; /** * ======================================================================== * CONTROLLERS * ======================================================================== */ /** * wp.revisions.controller.Diff * * Controlls the diff */ Diff = revisions.controller.Diff = Backbone.Model.extend( { rightDiff: 1, leftDiff: 1, revisions: null, leftHandleRevisions: null, rightHandleRevisions: null, revisionsInteractions: null, autosaves: true, showSplitView: true, singleRevision: true, leftModelLoading: false, // keep track of model loads rightModelLoading: false, // disallow slider interaction, also repeat loads, while loading tickmarkView: null, // the slider tickmarks slider: null, // the slider instance constructor: function() { this.slider = new revisions.view.Slider(); if ( null === this.revisions ) { this.revisions = new Revisions(); // set up collection this.startRightModelLoading(); var self = this; this.revisions.fetch({ // load revision data success: function() { self.stopRightModelLoading(); self.completeApplicationSetup(); } }); } }, loadDiffs: function( models ) { var self = this, revisionsToLoad = models.where( { completed: false } ), delay = 0; // match slider to passed revision_id _.each( revisionsToLoad, function( revision ) { if ( revision.get( 'ID' ) == revisions.model.settings.revision_id ) self.rightDiff = self.revisions.indexOf( revision ) + 1; }); _.each( revisionsToLoad, function( revision ) { _.delay( function() { revision.fetch( { update: true, add: false, remove: false, success: function( model ) { model.set( 'completed', true ); // stop spinner when all models are loaded if ( 0 === models.where( { completed: false } ).length ) self.stopModelLoadingSpinner(); self.tickmarkView.render(); var totalChanges = model.get( 'linesAdded' ) + model.get( 'linesDeleted' ), scopeOfChanges = 'vsmall'; // Note: hard coded scope of changes // TODO change to dynamic based on range of values if ( totalChanges > 1 && totalChanges <= 3 ) { scopeOfChanges = 'small'; } else if ( totalChanges > 3 && totalChanges <= 5 ) { scopeOfChanges = 'med'; } else if ( totalChanges > 5 && totalChanges <= 10 ) { scopeOfChanges = 'large'; } else if ( totalChanges > 10 ) { scopeOfChanges = 'vlarge'; } model.set( 'scopeOfChanges', scopeOfChanges ); if ( 0 !== self.rightDiff && model.get( 'ID' ) === self.revisions.at( self.rightDiff - 1 ).get( 'ID' ) ) { // reload if current model refreshed self.revisionView.render(); } } } ); }, delay ) ; delay = delay + 150; // stagger model loads to avoid hammering server with requests } ); }, startLeftModelLoading: function() { this.leftModelLoading = true; $('#revision-diff-container').addClass('left-model-loading'); }, stopLeftModelLoading: function() { this.leftModelLoading = false; }, startRightModelLoading: function() { this.rightModelLoading = true; $('#revision-diff-container').addClass('right-model-loading'); }, stopRightModelLoading: function() { this.rightModelLoading = false; }, stopModelLoadingSpinner: function() { $('#revision-diff-container').removeClass('right-model-loading'); $('#revision-diff-container').removeClass('left-model-loading'); }, reloadModel: function() { if ( this.singleRevision ) { this.reloadModelSingle(); } else { this.reloadLeftRight(); } }, // load the models for the single handle mode reloadModelSingle: function() { var self = this; self.startRightModelLoading(); self.revisions.reload({ options: { 'showAutosaves': self.autosaves, 'showSplitView': self.showSplitView }, success: function() { var revisionCount = self.revisions.length; self.revisionView.model = self.revisions; self.revisionView.render(); self.loadDiffs( self.revisions ); self.tickmarkView.model = self.revisions; self.tickmarkView.render(); self.slider.refresh({ 'max': revisionCount - 1, 'value': self.rightDiff - 1 }, true); }, error: function() { self.stopRightModelLoading(); } }); }, // load the models for the left handle reloadLeft: function() { var self = this; self.startLeftModelLoading(); self.leftHandleRevisions = new Revisions( {}, { 'compareTo': self.revisions.at( self.rightDiff - 1 ).get( 'ID' ), 'showAutosaves': self.autosaves, 'showSplitView': self.showSplitView, 'rightHandleAt': self.rightDiff }); self.leftHandleRevisions.fetch({ success: function(){ self.stopLeftModelLoading(); self.loadDiffs( self.leftHandleRevisions ); self.tickmarkView.model = self.leftHandleRevisions; self.slider.refresh({ 'max': self.revisions.length }); // ensure right handle not beyond length, in particular if viewing autosaves is switched from on to off // the number of models in the collection might get shorter, this ensures right handle is not beyond last model if ( self.rightDiff > self.revisions.length ) self.rightDiff = self.revisions.length; }, error: function() { self.stopLeftModelLoading(); } }); }, // load the models for the right handle reloadRight: function() { var self = this; self.startRightModelLoading(); self.rightHandleRevisions = new Revisions( {}, { 'compareTo': self.revisions.at( self.leftDiff - 1 ).get( 'ID' ), 'showAutosaves': self.autosaves, 'showSplitView': self.showSplitView, 'leftHandleAt': self.leftDiff }); self.rightHandleRevisions.fetch({ success: function(){ self.stopRightModelLoading(); self.loadDiffs( self.rightHandleRevisions ); self.tickmarkView.model = self.rightHandleRevisions; self.slider.refresh({ 'max': self.revisions.length, 'values': [ self.leftDiff, self.rightDiff] }, true); }, error: function( response ) { self.stopRightModelLoading(); } }); }, reloadLeftRight: function() { this.startRightModelLoading(); this.startLeftModelLoading(); this.reloadLeft(); this.reloadRight(); }, disabledButtonCheck: function( val ) { var maxVal = this.revisions.length - 1, next = $( '#next' ), prev = $( '#previous' ); // Disable "Next" button if you're on the last node if ( maxVal === val ) next.prop( 'disabled', true ); else next.prop( 'disabled', false ); // Disable "Previous" button if you're on the 0 node if ( 0 === val ) prev.prop( 'disabled', true ); else prev.prop( 'disabled', false ); }, completeApplicationSetup: function() { this.revisionView = new revisions.view.Diff({ model: this.revisions }); this.revisionView.render(); this.loadDiffs( this.revisions ); this.revisionsInteractions = new revisions.view.Interact({ model: this.revisions }); this.revisionsInteractions.render(); this.tickmarkView = new revisions.view.Tickmarks({ model: this.revisions }); this.tickmarkView.render(); this.tickmarkView.resetTicks(); } }); /** * ======================================================================== * VIEWS * ======================================================================== */ /** * wp.revisions.view.Slider * * The slider */ revisions.view.Slider = Backbone.View.extend({ el: $( '#diff-slider' ), singleRevision: true, initialize: function( options ) { this.options = _.defaults( options || {}, { value: 0, min: 0, max: 1, step: 1 }); }, slide: function( event, ui ) { if ( this.singleRevision ) { Diff.rightDiff = ( ui.value + 1 ); Diff.revisionView.render(); Diff.disabledButtonCheck( ui.value ); } else { if ( ui.values[0] === ui.values[1] ) // prevent compare to self return false; if ( $( ui.handle ).hasClass( 'left-handle' ) ) { // Left handler if ( Diff.leftModelLoading ) // left model still loading, prevent sliding left handle return false; Diff.leftDiff = ui.values[0]; } else { // Right handler if ( Diff.rightModelLoading ) // right model still loading, prevent sliding right handle return false; Diff.rightDiff = ui.values[1]; } if ( 0 === Diff.leftDiff ) { $( '#revision-diff-container' ).addClass( 'current-version' ); } else { $( '#revision-diff-container' ).removeClass( 'current-version' ); } Diff.revisionView.render(); } }, start: function( event, ui ) { // Not needed in one mode if ( this.singleRevision ) return; if ( $( ui.handle ).hasClass( 'left-handle' ) ) { // Left handler if ( Diff.leftModelLoading ) // left model still loading, prevent sliding left handle return false; Diff.revisionView.draggingLeft = true; if ( Diff.revisionView.model !== Diff.leftHandleRevisions && null !== Diff.leftHandleRevisions ) { Diff.revisionView.model = Diff.leftHandleRevisions; Diff.tickmarkView.model = Diff.leftHandleRevisions; Diff.tickmarkView.render(); } Diff.leftDiffStart = ui.values[ 0 ]; } else { // Right handler if ( Diff.rightModelLoading || 0 === Diff.rightHandleRevisions.length) // right model still loading, prevent sliding right handle return false; if ( Diff.revisionView.model !== Diff.rightHandleRevisions && null !== Diff.rightHandleRevisions ) { Diff.revisionView.model = Diff.rightHandleRevisions; Diff.tickmarkView.model = Diff.rightHandleRevisions; Diff.tickmarkView.render(); } Diff.revisionView.draggingLeft = false; Diff.rightDiffStart = ui.values[1]; } }, stop: function( event, ui ) { // Not needed in one mode if ( this.singleRevision ) return; // calculate and generate a diff for comparing to the left handle // and the right handle, swap out when dragging if ( $( ui.handle ).hasClass( 'left-handle' ) ) { // Left hadnler if ( Diff.leftDiffStart !== ui.values[0] ) Diff.reloadRight(); } else { // Right handler if ( Diff.rightDiffStart !== ui.values[1] ) Diff.reloadLeft(); } }, addTooltip: function( handle, message ) { handle.attr( 'title', '' ).tooltip({ track: false, position: { my: "left-30 top-66", at: "top left", using: function( position, feedback ) { $( this ).css( position ); $( "