Make WordPress Core

Ticket #24425: 24425.draft.9.diff

File 24425.draft.9.diff, 54.1 KB (added by adamsilverstein, 11 years ago)

adds previous/next buttons and functionality back

  • wp-includes/revision.php

     
    597597
    598598        return true;
    599599}
    600 
    601 /**
    602  * Displays a human readable HTML representation of the difference between two strings.
    603  * similar to wp_text_diff, but tracks and returns could of lines added and removed
    604  *
    605  * @since 3.6.0
    606  *
    607  * @see wp_parse_args() Used to change defaults to user defined settings.
    608  * @uses Text_Diff
    609  * @uses WP_Text_Diff_Renderer_Table
    610  *
    611  * @param string $left_string "old" (left) version of string
    612  * @param string $right_string "new" (right) version of string
    613  * @param string|array $args Optional. Change 'title', 'title_left', and 'title_right' defaults.
    614  * @return array contains html, linesadded & linesdeletd, empty string if strings are equivalent.
    615  */
    616 function wp_text_diff_with_count( $left_string, $right_string, $args = null ) {
    617         $defaults = array( 'title' => '', 'title_left' => '', 'title_right' => '' );
    618         $args = wp_parse_args( $args, $defaults );
    619 
    620         if ( ! class_exists( 'WP_Text_Diff_Renderer_Table' ) )
    621                         require( ABSPATH . WPINC . '/wp-diff.php' );
    622 
    623         $left_string  = normalize_whitespace( $left_string );
    624         $right_string = normalize_whitespace( $right_string );
    625 
    626         $left_lines  = explode( "\n", $left_string );
    627         $right_lines = explode( "\n", $right_string) ;
    628 
    629         $text_diff = new Text_Diff($left_lines, $right_lines  );
    630         $lines_added = $text_diff->countAddedLines();
    631         $lines_deleted = $text_diff->countDeletedLines();
    632 
    633         $renderer  = new WP_Text_Diff_Renderer_Table();
    634         $diff = $renderer->render( $text_diff );
    635 
    636         if ( !$diff )
    637                         return '';
    638 
    639                 $r  = "<table class='diff'>\n";
    640 
    641         if ( ! empty( $args[ 'show_split_view' ] ) ) {
    642                 $r .= "<col class='content diffsplit left' /><col class='content diffsplit middle' /><col class='content diffsplit right' />";
    643         } else {
    644                 $r .= "<col class='content' />";
    645         }
    646 
    647         if ( $args['title'] || $args['title_left'] || $args['title_right'] )
    648                 $r .= "<thead>";
    649         if ( $args['title'] )
    650                 $r .= "<tr class='diff-title'><th colspan='4'>$args[title]</th></tr>\n";
    651         if ( $args['title_left'] || $args['title_right'] ) {
    652                 $r .= "<tr class='diff-sub-title'>\n";
    653                 $r .= "\t<td></td><th>$args[title_left]</th>\n";
    654                 $r .= "\t<td></td><th>$args[title_right]</th>\n";
    655                 $r .= "</tr>\n";
    656         }
    657         if ( $args['title'] || $args['title_left'] || $args['title_right'] )
    658                 $r .= "</thead>\n";
    659 
    660         $r .= "<tbody>\n$diff\n</tbody>\n";
    661         $r .= "</table>";
    662 
    663         return array( 'html' => $r, 'lines_added' => $lines_added, 'lines_deleted' => $lines_deleted );
    664 }
  • wp-admin/admin-ajax.php

     
    4242
    4343$core_actions_get = array(
    4444        'fetch-list', 'ajax-tag-search', 'wp-compression-test', 'imgedit-preview', 'oembed-cache',
    45         'autocomplete-user', 'dashboard-widgets', 'logged-in', 'revisions-data'
     45        'autocomplete-user', 'dashboard-widgets', 'logged-in',
    4646);
    4747
    4848$core_actions_post = array(
     
    5656        'save-widget', 'set-post-thumbnail', 'date_format', 'time_format', 'wp-fullscreen-save-post',
    5757        'wp-remove-post-lock', 'dismiss-wp-pointer', 'upload-attachment', 'get-attachment',
    5858        'query-attachments', 'save-attachment', 'save-attachment-compat', 'send-link-to-editor',
    59         'send-attachment-to-editor', 'save-attachment-order', 'heartbeat',
     59        'send-attachment-to-editor', 'save-attachment-order', 'heartbeat', 'get-revision-diffs',
    6060);
    6161
    6262// Register core Ajax calls.
  • wp-admin/includes/ajax-actions.php

     
    20822082        wp_send_json($response);
    20832083}
    20842084
    2085 function wp_ajax_revisions_data() {
    2086         check_ajax_referer( 'revisions-ajax-nonce', 'nonce' );
     2085function wp_ajax_get_revision_diffs() {
     2086        require ABSPATH . 'wp-admin/includes/revision.php';
    20872087
    2088         $compare_to = ! empty( $_GET['compare_to'] ) ? absint( $_GET['compare_to'] ) : 0;
    2089         $show_autosaves = ! empty( $_GET['show_autosaves'] );
    2090         $show_split_view = ! empty( $_GET['show_split_view'] );
    2091         $post_id = ! empty( $_GET['post_id'] ) ? absint( $_GET['post_id'] ) : 0;
    2092         $right_handle_at = ! empty( $_GET['right_handle_at'] ) ? (int) $_GET['right_handle_at'] : 0;
    2093         $left_handle_at = ! empty( $_GET['left_handle_at'] ) ? (int) $_GET['left_handle_at'] : 0;
    2094         $single_revision_id = ! empty( $_GET['single_revision_id'] ) ? absint( $_GET['single_revision_id'] ) : 0;
    2095         $compare_two_mode = (bool) $post_id;
     2088        // check_ajax_referer( 'revisions-ajax-nonce', 'nonce' );
    20962089
    2097         $all_the_revisions = array();
    2098         if ( ! $post_id )
    2099                 $post_id = $compare_to;
     2090        if ( ! $post = get_post( (int) $_REQUEST['post_id'] ) )
     2091                wp_send_json_error();
    21002092
    2101         if ( ! current_user_can( 'read_post', $post_id ) )
    2102                 continue;
     2093        if ( ! current_user_can( 'read_post', $post->ID ) )
     2094                wp_send_json_error();
    21032095
    2104         if ( ! $revisions = wp_get_post_revisions( $post_id ) )
    2105                 return;
     2096        // Really just pre-loading the cache here.
     2097        if ( ! $revisions = wp_get_post_revisions( $post->ID ) )
     2098                wp_send_json_error();
    21062099
    2107         $left_revision = get_post( $compare_to );
     2100        $return = array();
     2101        @set_time_limit( count( $_REQUEST['compare'] ) );
    21082102
    2109         // single model fetch mode
    2110         // return the diff of a single revision comparison
    2111         if ( $single_revision_id ) {
    2112                 $right_revision = get_post( $single_revision_id );
     2103        foreach ( $_REQUEST['compare'] as $compare_key ) {
     2104                list( $compare_from, $compare_to ) = explode( ':', $compare_key ); // from:to
    21132105
    2114                 if ( ! $compare_to )
    2115                         $left_revision = get_post( $post_id );
    2116 
    2117                 // make sure the right revision is the most recent, except on oldest revision
    2118                 if ( $compare_to && $right_revision->post_date < $left_revision->post_date ) {
    2119                         $temp = $left_revision;
    2120                         $left_revision = $right_revision;
    2121                         $right_revision = $temp;
    2122                 }
    2123 
    2124                 $lines_added = $lines_deleted = 0;
    2125                 $content = '';
    2126                 // compare from left to right, passed from application
    2127                 foreach ( _wp_post_revision_fields() as $field => $field_value ) {
    2128                         $left_content = apply_filters( "_wp_post_revision_field_$field", $left_revision->$field, $field, $left_revision, 'left' );
    2129                         $right_content = apply_filters( "_wp_post_revision_field_$field", $right_revision->$field, $field, $right_revision, 'right' );
    2130 
    2131                         add_filter( "_wp_post_revision_field_$field", 'htmlspecialchars' );
    2132 
    2133                         $args = array();
    2134 
    2135                         if ( $show_split_view )
    2136                                  $args = array( 'show_split_view' => true );
    2137 
    2138                         // compare_to == 0 means first revision, so compare to a blank field to show whats changed
    2139                         $diff = wp_text_diff_with_count( ( 0 == $compare_to ) ? '' : $left_content, $right_content, $args );
    2140 
    2141                         if ( isset( $diff[ 'html' ] ) ) {
    2142                                 $content .= sprintf( '<div class="diff-label">%s</div>', $field_value );
    2143                                 $content .= $diff[ 'html' ];
    2144                         }
    2145 
    2146                         if ( isset( $diff[ 'lines_added' ] ) )
    2147                                 $lines_added = $lines_added + $diff[ 'lines_added' ];
    2148 
    2149                         if ( isset( $diff[ 'lines_deleted' ] ) )
    2150                                 $lines_deleted = $lines_deleted + $diff[ 'lines_deleted' ];
    2151                 }
    2152                 $content = '' == $content ? __( 'No difference' ) : $content;
    2153 
    2154                 $all_the_revisions = array (
    2155                         'diff'         => $content,
    2156                         'linesDeleted' => $lines_deleted,
    2157                         'linesAdded'   => $lines_added
     2106                $return[] = array(
     2107                        'id' => $compare_key,
     2108                        'fields' => wp_get_revision_ui_diff( $post, $compare_from, $compare_to ),
    21582109                );
     2110        }
    21592111
    2160                 echo json_encode( $all_the_revisions );
    2161                 exit();
    2162         } // end single model fetch
    2163 
    2164         $count = -1;
    2165 
    2166         // reverse the list to start with oldest revision
    2167         $revisions = array_reverse( $revisions );
    2168 
    2169         $previous_revision_id = 0;
    2170 
    2171         /* translators: revision date format, see http://php.net/date */
    2172         $datef = _x( 'j F, Y @ G:i:s', 'revision date format');
    2173 
    2174         foreach ( $revisions as $revision ) :
    2175                 if ( ! $show_autosaves && wp_is_post_autosave( $revision ) )
    2176                         continue;
    2177 
    2178                 $revision_from_date_author = '';
    2179                 $is_current_revision = false;
    2180                 $count++;
    2181 
    2182                 /**
    2183                 * return blank data for diffs to the left of the left handle (for right handel model)
    2184                 * or to the right of the right handle (for left handel model)
    2185                 * and visa versa in RTL mode
    2186                 */
    2187                 if( ! is_rtl() ) {
    2188                         if ( ( ( 0 != $left_handle_at && $count < $left_handle_at ) ||
    2189                                  ( 0 != $right_handle_at && $count > ( $right_handle_at - 2 ) ) ) ) {
    2190                                 $all_the_revisions[] = array (
    2191                                         'ID' => $revision->ID,
    2192                                 );
    2193                                 continue;
    2194                         }
    2195                 } else { // is_rtl
    2196                         if ( ( 0 != $left_handle_at && $count > ( $left_handle_at - 1 ) ||
    2197                                  ( 0 != $left_handle_at && $count < $right_handle_at ) ) ) {
    2198                                 $all_the_revisions[] = array (
    2199                                         'ID' => $revision->ID,
    2200                                 );
    2201                                 continue;
    2202                         }
    2203                 }
    2204 
    2205                 if ( $compare_two_mode ) {
    2206                         $compare_to_gravatar = get_avatar( $left_revision->post_author, 24 );
    2207                         $compare_to_author = get_the_author_meta( 'display_name', $left_revision->post_author );
    2208                         $compare_to_date = date_i18n( $datef, strtotime( $left_revision->post_modified ) );
    2209 
    2210                         $revision_from_date_author = sprintf(
    2211                                 /* translators: post revision title: 1: author avatar, 2: author name, 3: time ago, 4: date */
    2212                                 _x( '%1$s %2$s, %3$s ago (%4$s)', 'post revision title' ),
    2213                                 $compare_to_gravatar,
    2214                                 $compare_to_author,
    2215                                 human_time_diff( strtotime( $left_revision->post_modified ), current_time( 'timestamp' ) ),
    2216                                 $compare_to_date
    2217                         );
    2218                 }
    2219 
    2220                 $gravatar = get_avatar( $revision->post_author, 24 );
    2221                 $author = get_the_author_meta( 'display_name', $revision->post_author );
    2222                 $date = date_i18n( $datef, strtotime( $revision->post_modified ) );
    2223                 $revision_date_author = sprintf(
    2224                         /* translators: post revision title: 1: author avatar, 2: author name, 3: time ago, 4: date */
    2225                         _x( '%1$s %2$s, %3$s ago (%4$s)', 'post revision title' ),
    2226                         $gravatar,
    2227                         $author,
    2228                         human_time_diff( strtotime( $revision->post_modified ), current_time( 'timestamp' ) ),
    2229                         $date
    2230                 );
    2231 
    2232                 /* translators: 1: date */
    2233                 $autosavef = _x( '%1$s [Autosave]', 'post revision title extra' );
    2234                 /* translators: 1: date */
    2235                 $currentf  = _x( '%1$s [Current Revision]', 'post revision title extra' );
    2236 
    2237                 if ( ! $post = get_post( $post_id ) )
    2238                         continue;
    2239 
    2240                 if ( $left_revision->post_modified === $post->post_modified )
    2241                         $revision_from_date_author = sprintf( $currentf, $revision_from_date_author );
    2242                 elseif ( wp_is_post_autosave( $left_revision ) )
    2243                         $revision_from_date_author = sprintf( $autosavef, $revision_from_date_author );
    2244 
    2245                 if ( $revision->post_modified === $post->post_modified ) {
    2246                         $revision_date_author = sprintf( $currentf, $revision_date_author );
    2247                         $is_current_revision = true;
    2248                 } elseif ( wp_is_post_autosave( $revision ) ) {
    2249                         $revision_date_author = sprintf( $autosavef, $revision_date_author );
    2250                 }
    2251 
    2252                 /* translators: revision date short format, see http://php.net/date */
    2253                 $date_short_format = _x( 'j M @ G:i', 'revision date short format');
    2254                 $date_short = date_i18n( $date_short_format, strtotime( $revision->post_modified ) );
    2255 
    2256                 $revision_date_author_short = sprintf(
    2257                         '%s <strong>%s</strong><br />%s',
    2258                         $gravatar,
    2259                         $author,
    2260                         $date_short
    2261                 );
    2262 
    2263                 $restore_link = wp_nonce_url(
    2264                         add_query_arg(
    2265                                 array( 'revision' => $revision->ID,
    2266                                         'action' => 'restore' ),
    2267                                         admin_url( 'revision.php' )
    2268                         ),
    2269                         "restore-post_{$revision->ID}"
    2270                 );
    2271 
    2272                 // if this is a left handled calculation swap data
    2273                 if ( 0 != $right_handle_at ) {
    2274                         $tmp = $revision_from_date_author;
    2275                         $revision_from_date_author = $revision_date_author;
    2276                         $revision_date_author = $tmp;
    2277                 }
    2278 
    2279                 if ( ( $compare_two_mode || -1 !== $previous_revision_id ) ) {
    2280                         $all_the_revisions[] = array (
    2281                                 'ID'           => $revision->ID,
    2282                                 'titleTo'      => $revision_date_author,
    2283                                 'titleFrom'    => $revision_from_date_author,
    2284                                 'titleTooltip' => $revision_date_author_short,
    2285                                 'restoreLink'  => urldecode( $restore_link ),
    2286                                 'previousID'   => $previous_revision_id,
    2287                                 'isCurrent'    => $is_current_revision,
    2288                         );
    2289                 }
    2290                 $previous_revision_id = $revision->ID;
    2291 
    2292         endforeach;
    2293 
    2294         // in RTL + single handle mode, reverse the revision direction
    2295         if ( is_rtl() && $compare_two_mode )
    2296                 $all_the_revisions = array_reverse( $all_the_revisions );
    2297 
    2298         echo json_encode( $all_the_revisions );
    2299         exit();
     2112        wp_send_json_success( $return );
    23002113}
  • wp-admin/js/revisions.js

     
    11window.wp = window.wp || {};
    22
    33(function($) {
    4         var Revision, Revisions, Diff, revisions;
     4        var revisions;
    55
    6         revisions = wp.revisions = function() {
    7                 Diff = revisions.Diff = new Diff();
    8         };
     6        revisions = wp.revisions = { model: {}, view: {}, controller: {} };
    97
    10         _.extend( revisions, { model: {}, view: {}, controller: {} } );
    11 
    128        // Link settings.
    13         revisions.model.settings = typeof wpRevisionsSettings === 'undefined' ? {} : wpRevisionsSettings;
     9        revisions.settings = typeof _wpRevisionsSettings === 'undefined' ? {} : _wpRevisionsSettings;
    1410
    1511
    1612        /**
    1713         * ========================================================================
    18          * CONTROLLERS
     14         * MODELS
    1915         * ========================================================================
    2016         */
     17        revisions.model.Slider = Backbone.Model.extend({
     18                defaults: {
     19                        value: 0,
     20                        min: 0,
     21                        max: 1,
     22                        step: 1
     23                }
     24        });
    2125
    22         /**
    23          * wp.revisions.controller.Diff
    24          *
    25          * Controlls the diff
    26          */
    27         Diff = revisions.controller.Diff = Backbone.Model.extend( {
    28                 rightDiff: 1,
    29                 leftDiff: 1,
    30                 revisions: null,
    31                 leftHandleRevisions: null,
    32                 rightHandleRevisions: null,
    33                 revisionsInteractions: null,
    34                 autosaves: true,
    35                 showSplitView: true,
    36                 singleRevision: true,
    37                 leftModelLoading: false,        // keep track of model loads
    38                 rightModelLoading: false,       // disallow slider interaction, also repeat loads, while loading
    39                 tickmarkView: null, // the slider tickmarks
    40                 slider: null, // the slider instance
     26        revisions.model.Revision = Backbone.Model.extend({});
    4127
    42                 constructor: function() {
    43                         var self    = this;
    44                         this.slider = new revisions.view.Slider();
     28        revisions.model.Revisions = Backbone.Collection.extend({
     29                model: revisions.model.Revision,
    4530
    46                         if ( null === this.revisions ) {
    47                                 this.revisions = new Revisions(); // set up collection
    48                                 this.startRightModelLoading();
    49 
    50                                 this.revisions.fetch({ // load revision data
    51                                         success: function() {
    52                                                 self.stopRightModelLoading();
    53                                                 self.completeApplicationSetup();
    54                                         }
    55                                 });
    56                         }
     31                comparator: function( revision ) {
     32                        return revision.id;
    5733                },
     34        });
    5835
    59                 loadDiffs: function( models ) {
    60                         var self = this,
    61                                 revisionsToLoad = models.where( { completed: false } ),
    62                                 delay = 0,
    63                                 totalChanges;
     36        revisions.model.Field = Backbone.Model.extend({});
    6437
    65                         // match slider to passed revision_id
    66                         _.each( revisionsToLoad, function( revision ) {
    67                                 if ( revision.get( 'ID' ) == revisions.model.settings.revision_id )
    68                                         self.rightDiff = self.revisions.indexOf( revision ) + 1;
    69                         });
     38        revisions.model.Fields = Backbone.Collection.extend({
     39                model: revisions.model.Field
     40        });
    7041
    71                         _.each( revisionsToLoad, function( revision ) {
    72                                         _.delay( function() {
    73                                                 revision.fetch( {
    74                                                         update: true,
    75                                                         add: false,
    76                                                         remove: false,
    77                                                         success: function( model ) {
    78                                                                 model.set( 'completed', true );
     42        revisions.model.Diff = Backbone.Model.extend({
     43                initialize: function(attributes, options) {
     44                        var fields = this.get('fields');
     45                        this.unset('fields');
    7946
    80                                                                 // stop spinner when all models are loaded
    81                                                                 if ( 0 === models.where( { completed: false } ).length )
    82                                                                         self.stopModelLoadingSpinner();
     47                        this.fields = new revisions.model.Fields( fields );
     48                }
     49        });
    8350
    84                                                                 totalChanges = model.get( 'linesAdded' ) + model.get( 'linesDeleted' ),
    85                                                                         scopeOfChanges = 'vsmall';
     51        revisions.model.Diffs = Backbone.Collection.extend({
     52                initialize: function(models, options) {
     53                        this.revisions = options.revisions;
     54                        this.requests = {};
     55                },
    8656
    87                                                                 // Note: hard coded scope of changes
    88                                                                 // TODO change to dynamic based on range of values
    89                                                                 if ( totalChanges > 1 && totalChanges <= 3 ) {
    90                                                                         scopeOfChanges = 'small';
    91                                                                 } else if ( totalChanges > 3 && totalChanges <= 5 ) {
    92                                                                         scopeOfChanges = 'med';
    93                                                                 } else if ( totalChanges > 5 && totalChanges <= 10 ) {
    94                                                                         scopeOfChanges = 'large';
    95                                                                 } else if ( totalChanges > 10 ) {
    96                                                                         scopeOfChanges = 'vlarge';
    97                                                                 }
    98                                                                 model.set( 'scopeOfChanges', scopeOfChanges );
    99                                                                 if ( 0 !== self.rightDiff &&
    100                                                                         model.get( 'ID' ) === self.revisions.at( self.rightDiff - 1 ).get( 'ID' ) ) {
    101                                                                         // reload if current model refreshed
    102                                                                         self.revisionView.render();
    103                                                                 }
    104                                                                 self.tickmarkView.render();
    105                                                         }
    106                                         } );
    107                                         }, delay ) ;
    108                                         delay = delay + 150; // stagger model loads to avoid hammering server with requests
     57                model: revisions.model.Diff,
     58
     59                ensure: function( id, context ) {
     60                        var diff = this.get( id );
     61                        var request = this.requests[ id ];
     62                        var deferred = $.Deferred();
     63                        var ids = {};
     64
     65                        if ( diff ) {
     66                                deferred.resolveWith( context, [ diff ] );
     67                        } else {
     68                                this.trigger( 'ensure:load', ids );
     69                                _.each( ids, _.bind( function(id) {
     70                                        // Remove anything that has an ongoing request
     71                                        if ( this.requests[ id ] )
     72                                                delete ids[ id ];
     73                                }, this ) );
     74                                if ( ! request ) {
     75                                        // Always include the ID that started this ensure
     76                                        ids[ id ] = true;
     77                                        request = this.load( _.keys( ids ) );
    10978                                }
    110                         );
    111                 },
    11279
    113                 startLeftModelLoading: function() {
    114                         this.leftModelLoading = true;
    115                         $('#revision-diff-container').addClass('left-model-loading');
    116                 },
     80                                request.done( _.bind( function() {
     81                                        deferred.resolveWith( context, [ this.get( id ) ] );
     82                                }, this ) );
     83                        }
    11784
    118                 stopLeftModelLoading: function() {
    119                         this.leftModelLoading = false;
     85                        return deferred.promise();
    12086                },
    12187
    122                 startRightModelLoading: function() {
    123                         this.rightModelLoading = true;
    124                         $('#revision-diff-container').addClass('right-model-loading');
     88                loadNew: function( comparisons ) {
     89                        comparisons = _.object( comparisons, comparisons );
     90                        _.each( comparisons, _.bind( function( id ) {
     91                                // Exists
     92                                if ( this.get( id ) )
     93                                        delete comparisons[ id ];
     94                        }, this ) );
     95                        comparisons = _.toArray( comparisons );
     96                        console.log( 'Loading', comparisons );
     97                        return this.load( comparisons );
    12598                },
    12699
    127                 stopRightModelLoading: function() {
    128                         this.rightModelLoading = false;
     100                load: function( comparisons ) {
     101                        // Our collection should only ever grow, never shrink, so remove: false
     102                        return this.fetch({ data: { compare: comparisons }, remove: false });
    129103                },
    130104
    131                 stopModelLoadingSpinner: function() {
    132                         $('#revision-diff-container').removeClass('right-model-loading');
    133                         $('#revision-diff-container').removeClass('left-model-loading');
     105/**/
     106                loadLast: function( num ) {
     107                        num = num || 1;
     108                        var ids = this.getProximalDiffIds();
     109                        ids = _.last( ids, num );
     110                        if ( ids.length ) {
     111                                console.log( 'Last ' + num, ids );
     112                                return this.loadNew( ids );
     113                        }
    134114                },
    135115
    136                 reloadModel: function() {
    137                         if ( this.singleRevision ) {
    138                                 this.reloadModelSingle();
    139                         } else {
    140                                 this.reloadLeftRight();
     116                loadLastUnloaded: function( num ) {
     117                        num = num || 1;
     118                        var ids = this.getUnloadedProximalDiffIds();
     119                        ids = _.last( ids, num );
     120                        if ( ids.length ) {
     121                                console.log( 'Loading last ' + num );
     122                                return this.loadNew( ids );
    141123                        }
    142124                },
    143125
    144                 // load the models for the single handle mode
    145                 reloadModelSingle: function() {
    146                         var self = this;
     126                getProximalDiffIds: function() {
     127                        var previous = 0, ids = [];
     128                        this.revisions.each( _.bind( function(revision) {
     129                                ids.push( previous + ':' + revision.id );
     130                                previous = revision.id;
     131                        }, this ) );
     132                        return ids;
     133                },
    147134
    148                         self.startRightModelLoading();
     135                getUnloadedProximalDiffIds: function() {
     136                        var comparisons = this.getProximalDiffIds();
     137                        comparisons = _.object( comparisons, comparisons );
     138                        _.each( comparisons, _.bind( function( id ) {
     139                                // Exists
     140                                if ( this.get( id ) )
     141                                        delete comparisons[ id ];
     142                        }, this ) );
     143                        return _.toArray( comparisons );
     144                },
    149145
    150                         self.revisions.reload({
    151                                 options: {
    152                                 'showAutosaves': self.autosaves,
    153                                 'showSplitView': self.showSplitView
    154                                 },
     146                loadAllBy: function( chunkSize ) {
     147                        chunkSize = chunkSize || 20;
     148                        var unloaded = this.getUnloadedProximalDiffIds();
     149                        if ( unloaded.length ) {
     150                                return this.loadLastUnloaded( chunkSize ).always( _.bind( function() {
     151                                        this.loadAllBy( chunkSize );
     152                                }, this ) );
     153                        }
     154                },
    155155
    156                                 success: function() {
    157                                         var revisionCount = self.revisions.length;
    158                                         self.revisionView.model = self.revisions;
    159                                         self.revisionView.render();
    160                                         self.loadDiffs( self.revisions );
    161                                         self.tickmarkView.model = self.revisions;
    162                                         self.tickmarkView.render();
    163                                         self.slider.refresh({
    164                                                 'max': revisionCount - 1, // slider starts at 0 in single handle mode
    165                                                 'value': self.rightDiff - 1 // slider starts at 0 in single handle mode
    166                                         }, true);
    167                                 },
     156/**/
    168157
    169                                 error: function() {
    170                                         self.stopRightModelLoading();
    171                                 }
    172                         });
    173                 },
     158                sync: function( method, model, options ) {
     159                        if ( 'read' === method ) {
     160                                options = options || {};
     161                                options.context = this;
     162                                options.data = _.extend( options.data || {}, {
     163                                        action: 'get-revision-diffs',
     164                                        post_id: revisions.settings.postId
     165                                });
    174166
    175                 // load the models for the left handle (the right handler has moved)
    176                 reloadLeft: function() {
    177                         var self = this;
    178                         self.startLeftModelLoading();
    179                         self.leftHandleRevisions = new Revisions( {}, {
    180                                 'compareTo': self.revisions.at( self.rightDiff - 1 ).get( 'ID' ), // diff and model count off by 1
    181                                 'showAutosaves': self.autosaves,
    182                                 'showSplitView': self.showSplitView,
    183                                 'rightHandleAt': self.rightDiff
    184                         });
     167                                var deferred = wp.xhr.send( options );
     168                                var requests = this.requests;
    185169
    186                         self.leftHandleRevisions.fetch({
    187                                 success: function(){
    188                                         self.stopLeftModelLoading();
    189                                         self.loadDiffs( self.leftHandleRevisions );
    190                                         self.tickmarkView.model = self.leftHandleRevisions;
    191                                         self.slider.refresh({
    192                                                 'max': self.revisions.length
     170                                // Record that we're requesting each diff.
     171                                if ( options.data.compare ) {
     172                                        _.each( options.data.compare, function( id ) {
     173                                                requests[ id ] = deferred;
    193174                                        });
    194                                         // ensure right handle not beyond length
    195                                         if ( self.rightDiff > self.revisions.length )
    196                                                 self.rightDiff = self.revisions.length;
    197                                         },
    198 
    199                                 error: function() {
    200                                         self.stopLeftModelLoading();
    201175                                }
    202                         });
    203                 },
    204176
    205                 // load the models for the right handle (the left handle has moved)
    206                 reloadRight: function() {
    207                         var self = this;
    208                         self.startRightModelLoading();
    209                         self.rightHandleRevisions = new Revisions( {}, {
    210                                 'compareTo': self.revisions.at( self.leftDiff - 1 ).get( 'ID' ), // diff and model count off by 1
    211                                 'showAutosaves': self.autosaves,
    212                                 'showSplitView': self.showSplitView,
    213                                 'leftHandleAt': self.leftDiff
    214                         });
     177                                // When the request completes, clear the stored request.
     178                                deferred.always( function() {
     179                                        if ( options.data.compare ) {
     180                                                _.each( options.data.compare, function( id ) {
     181                                                        delete requests[ id ];
     182                                                });
     183                                        }
     184                                });
    215185
    216                         self.rightHandleRevisions.fetch({
    217                                 success: function(){
    218                                         self.stopRightModelLoading();
    219                                         self.loadDiffs( self.rightHandleRevisions );
    220                                         self.tickmarkView.model = self.rightHandleRevisions;
    221                                         self.slider.refresh({
    222                                                 'max': self.revisions.length
    223                                         }, true);
    224                                 },
     186                                return deferred;
    225187
    226                                 error: function( response ) {
    227                                         self.stopRightModelLoading();
    228                                 }
    229                         });
     188                        // Otherwise, fall back to `Backbone.sync()`.
     189                        } else {
     190                                return Backbone.Model.prototype.sync.apply( this, arguments );
     191                        }
     192                }
     193        });
    230194
    231                 },
    232195
    233                 /**
    234                  * reloadLeftRight reload models for both the left and right handles
    235                  */
    236                 reloadLeftRight: function() {
    237                         this.startRightModelLoading();
    238                         this.startLeftModelLoading();
    239                         this.reloadLeft();
    240                         this.reloadRight();
    241                 },
     196        revisions.model.FrameState = Backbone.Model.extend({
     197                initialize: function( attributes, options ) {
     198                        this.revisions = options.revisions;
     199                        this.diffs = new revisions.model.Diffs( [], {revisions: this.revisions} );
    242200
    243                 disabledButtonCheck: function( val ) {
    244                         var maxVal = this.revisions.length - 1,
    245                                 next = ! isRtl ? $( '#next' ) : $( '#previous' ),
    246                                 prev = ! isRtl ? $( '#previous' ) : $( '#next' );
    247 
    248                         // Disable "Next" button if you're on the last node
    249                         if ( maxVal === val )
    250                                 next.prop( 'disabled', true );
    251                         else
    252                                 next.prop( 'disabled', false );
    253 
    254                         // Disable "Previous" button if you're on the 0 node
    255                         if ( 0 === val )
    256                                 prev.prop( 'disabled', true );
    257                         else
    258                                 prev.prop( 'disabled', false );
     201                        this.listenTo( this, 'change:from change:to', this.updateDiffId );
    259202                },
    260203
    261                 /**
    262                  * completeApplicationSetup finishes loading all views once the initial model load is complete
    263                  */
    264                 completeApplicationSetup: function() {
    265                         this.revisionView = new revisions.view.Diff({
    266                                 model: this.revisions
    267                         });
    268                         this.revisionView.render(); // render the revision view
    269 
    270                         this.loadDiffs( this.revisions ); // get the actual revisions data
    271 
    272                         this.revisionsInteractions = new revisions.view.Interact({
    273                                 model: this.revisions
    274                         });
    275                         this.revisionsInteractions.render(); // render the interaction view
    276 
    277                         this.tickmarkView = new revisions.view.Tickmarks({
    278                                 model: this.revisions
    279                         });
    280                         this.tickmarkView.render(); // render the tickmark view
     204                updateDiffId: function() {
     205                        var from = this.get('from');
     206                        var to = this.get('to');
     207                        this.set( 'diffId', (from ? from.id : '0') + ':' + to.id );
    281208                }
    282209        });
    283210
     
    288215         * ========================================================================
    289216         */
    290217
    291         /**
    292          * wp.revisions.view.Slider
    293          *
    294          * The slider
    295          */
    296         revisions.view.Slider = Backbone.View.extend({
    297                 el: $( '#diff-slider' ),
    298                 singleRevision: true,
     218        // The frame view. This contains the entire page.
     219        revisions.view.Frame = wp.Backbone.View.extend({
     220                tagName: 'div',
     221                className: 'revisions',
     222                template: wp.template('revisions-frame'),
    299223
    300                 initialize: function( options ) {
    301                         this.options = _.defaults( options || {}, {
    302                                 value: 0,
    303                                 min: 0,
    304                                 max: 1,
    305                                 step: 1
     224                initialize: function() {
     225                        this.model = new revisions.model.FrameState({}, {
     226                                revisions: this.collection
    306227                        });
    307                 },
    308228
    309                 /**
    310                  * respond to slider slide events
    311                  * Note: in one handle mode, jQuery UI reports leftmost position as 0
    312                  * in two handle mode, jQuery UI Slider reports leftmost position as 1
    313                  */
    314                 slide: function( event, ui ) {
    315                         if ( this.singleRevision ) {
    316                                 Diff.rightDiff = ( ui.value + 1 );
    317                                 Diff.revisionView.render();
    318                                 Diff.disabledButtonCheck( ui.value );
    319                         } else {
    320                                 if ( ui.values[0] === ui.values[1] ) // prevent compare to self
    321                                         return false;
     229                        this.listenTo( this.model, 'change:diffId', this.updateDiff );
    322230
    323                                 if ( $( ui.handle ).hasClass( 'left-handle' ) ) {
    324                                         // Left handler
    325                                         if ( Diff.leftModelLoading ) // left model still loading, prevent sliding left handle
    326                                                 return false;
     231                        this.views.set( '.revisions-control-frame', new revisions.view.Controls({
     232                                model: this.model
     233                        }) );
    327234
    328                                         Diff.leftDiff = isRtl ? ui.values[1] : ui.values[0]; // handles are reversed in RTL mode
    329                                 } else {
    330                                         // Right handler
    331                                         if ( Diff.rightModelLoading ) // right model still loading, prevent sliding right handle
    332                                                 return false;
     235                        if ( this.model.revisions.length ) {
     236                                var last = this.model.revisions.last(2);
     237                                var attributes = { to: last.pop() };
    333238
    334                                         Diff.rightDiff = isRtl ? ui.values[0] : ui.values[1]; // handles are reversed in RTL mode
    335                                 }
     239                                if ( last.length )
     240                                        attributes.from = last.pop();
    336241
    337                                 Diff.revisionView.render();
    338                         }
    339                 },
     242                                this.model.set( attributes );
    340243
    341                 /**
    342                  * responds to slider start sliding events
    343                  * in two handle mode stores start position, so if unchanged at stop event no need to reload diffs
    344                  * also swaps in the appropriate models - left handled or right handled
    345                  */
    346                 start: function( event, ui ) {
    347                         // Not needed in one mode
    348                         if ( this.singleRevision )
    349                                 return;
    350 
    351                         if ( $( ui.handle ).hasClass( 'left-handle' ) ) {
    352                                 // Left handler
    353                                 if ( Diff.leftModelLoading ) // left model still loading, prevent sliding left handle
    354                                         return false;
    355 
    356                                 Diff.revisionView.draggingLeft = true;
    357 
    358                                 if ( Diff.revisionView.model !== Diff.leftHandleRevisions &&
    359                                                 null !== Diff.leftHandleRevisions ) {
    360                                         Diff.revisionView.model = Diff.leftHandleRevisions; // use the left handle models
    361                                         Diff.tickmarkView.model = Diff.leftHandleRevisions;
    362                                         Diff.tickmarkView.render();
    363                                 }
    364 
    365                                 Diff.leftDiffStart = isRtl ? ui.values[1] : ui.values[0]; // in RTL mode the 'left handle' is the second in the slider, 'right' is first
    366 
    367                         } else {
    368                                 // Right handler
    369                                 if ( Diff.rightModelLoading || 0 === Diff.rightHandleRevisions.length) // right model still loading, prevent sliding right handle
    370                                         return false;
    371 
    372                                 if ( Diff.revisionView.model !== Diff.rightHandleRevisions &&
    373                                                 null !== Diff.rightHandleRevisions ) {
    374                                         Diff.revisionView.model = Diff.rightHandleRevisions; // use the right handle models
    375                                         Diff.tickmarkView.model = Diff.rightHandleRevisions;
    376                                         Diff.tickmarkView.render();
    377                                 }
    378 
    379                                 Diff.revisionView.draggingLeft = false;
    380                                 Diff.rightDiffStart = isRtl ? ui.values[0] : ui.values[1]; // in RTL mode the 'left handle' is the second in the slider, 'right' is first
     244                                // Load the rest: first 10, then the rest by 50
     245                                this.model.diffs.loadLastUnloaded( 10 ).always( _.bind( function() {
     246                                        console.log( 'Loading all by 50' );
     247                                        this.model.diffs.loadAllBy( 50 );
     248                                }, this ) );
    381249                        }
    382250                },
    383251
    384                 /**
    385                  * responds to slider stop events
    386                  * in two handled mode, if the handle that stopped has moved, reload the diffs for the other handle
    387                  * the other handle compares to this handle's position, so if it changes they need to be recalculated
    388                  */
    389                 stop: function( event, ui ) {
    390                         // Not needed in one mode
    391                         if ( this.singleRevision )
    392                                 return;
     252                render: function() {
     253                        wp.Backbone.View.prototype.render.apply( this, arguments );
    393254
    394                         // calculate and generate a diff for comparing to the left handle
    395                         // and the right handle, swap out when dragging
    396                         if ( $( ui.handle ).hasClass( 'left-handle' ) ) {
    397                                 // Left handler
    398                                 if ( Diff.leftDiffStart !== isRtl ? ui.values[1] : ui.values[0] ) // in RTL mode the 'left handle' is the second in the slider, 'right' is first
    399                                         Diff.reloadRight();
    400                         } else {
    401                                 // Right handler
    402                                 if ( Diff.rightDiffStart !== isRtl ? ui.values[0] : ui.values[1] ) // in RTL mode the 'left handle' is the second in the slider, 'right' is first
    403                                         Diff.reloadLeft();
    404                         }
    405                 },
     255                        $('#wpbody-content .wrap').append( this.el );
     256                        this.views.ready();
    406257
    407                 addTooltip: function( handle, message ) {
    408                         handle.find( '.ui-slider-tooltip' ).html( message );
     258                        return this;
    409259                },
    410260
    411                 width: function() {
    412                         return $( '#diff-slider' ).width();
    413                 },
     261                updateDiff: function() {
     262                        this.model.diffs.ensure( this.model.get('diffId'), this ).done( function( diff ) {
     263                                if ( this.model.get('diffId') !== diff.id )
     264                                        return;
     265                                this.views.set( '.revisions-diff-frame', new revisions.view.Diff({
     266                                        model: diff
     267                                }));
     268                        });
     269                }
     270        });
    414271
    415                 setWidth: function( width ) {
    416                         $( '#diff-slider' ).width( width );
    417                 },
     272        // The control view.
     273        // This contains the revision slider, previous/next buttons, and the compare checkbox.
     274        revisions.view.Controls = wp.Backbone.View.extend({
     275                tagName: 'div',
     276                className: 'revisions-controls',
    418277
    419                 refresh: function( options, slide ) {
    420                         $( '#diff-slider' ).slider( 'option', options );
    421 
    422                         // Triggers the slide event
    423                         if ( slide )
    424                                 $( '#diff-slider' ).trigger( 'slide' );
    425 
    426                         Diff.disabledButtonCheck( options.value );
    427                 },
    428 
    429                 option: function( key ) {
    430                         return $( '#diff-slider' ).slider( 'option', key );
    431                 },
    432 
    433                 render: function() {
    434                         var self = this;
    435                         // this.$el doesn't work, why?
    436                         $( '#diff-slider' ).slider( {
    437                                 slide: $.proxy( self.slide, self ),
    438                                 start: $.proxy( self.start, self ),
    439                                 stop:  $.proxy( self.stop, self )
    440                         } );
    441 
    442                         // Set options
    443                         this.refresh( this.options );
     278                initialize: function() {
     279                        //this.$el.html( this.template );
     280                        this.views.add( new revisions.view.Buttons({
     281                                model: this.model
     282                        }));
     283                        this.views.add( new revisions.view.Slider({
     284                                model: this.model
     285                        }) );
    444286                }
    445287        });
    446288
    447         /**
    448          * wp.revisions.view.Tickmarks
    449          *
    450          * The slider tickmarks.
    451          */
    452         revisions.view.Tickmarks = Backbone.View.extend({
    453                 el: $('#diff-slider-ticks'),
    454                 template: wp.template('revision-ticks'),
    455                 model: Revision,
     289        // The buttons view.
     290        // Encapsulates all of the configuration for the previous/next buttons, and the compare checkbox.
     291        revisions.view.Buttons = wp.Backbone.View.extend({
     292                tagName: 'div',
     293                className: 'revisions-buttons',
     294                template: wp.template('revisions-controls'),
    456295
    457                 resetTicks: function() {
    458                         var sliderMax, sliderWidth, adjustMax, tickWidth, tickCount = 0, aTickWidth, tickMargin, self = this, firstTick, lastTick;
    459                         sliderMax   = Diff.slider.option( 'max' );
    460                         sliderWidth = Diff.slider.width();
    461                         adjustMax   = Diff.singleRevision ? 0 : 1;
    462                         tickWidth   = Math.floor( sliderWidth / ( sliderMax - adjustMax ) );
    463                         tickWidth   = ( tickWidth > 50 ) ? 50 : tickWidth; // set minimum and maximum widths for tick marks
    464                         tickWidth   = ( tickWidth < 6 ) ? 6 : tickWidth;
    465                         sliderWidth = tickWidth * ( sliderMax - adjustMax ); // calculate the slider width
    466                         aTickWidth  = $( '.revision-tick' ).width();
    467 
    468                         if ( tickWidth !== aTickWidth ) { // is the width already set correctly?
    469                                 $( '.revision-tick' ).each( function() {
    470                                         tickMargin = Math.floor( ( tickWidth - $( this ).width() ) / 2 ) + 1;
    471                                         $( this ).css( 'border-left', tickMargin + 'px solid #f7f7f7'); // space the ticks out using margins
    472                                         $( this ).css( 'border-right', ( tickWidth - tickMargin - $( this ).width() ) + 'px solid #f7f7f7'); // space the ticks out using margins
    473                                 });
    474                                 firstTick = $( '.revision-tick' ).first(); //cache selectors for optimization
    475                                 lastTick = $( '.revision-tick' ).last();
    476 
    477                                 sliderWidth = sliderWidth + Math.ceil( ( tickWidth - ( lastTick.outerWidth() - lastTick.innerWidth() ) ) / 2 ); // room for the last tick
    478                                 sliderWidth = sliderWidth + Math.ceil( ( tickWidth - ( firstTick.outerWidth() - firstTick.innerWidth() ) ) / 2 ); // room for the first tick
    479                                 firstTick.css( 'border-left', 'none' ); // first tick gets no left border
    480                                 lastTick.css( 'border-right', 'none' ); // last tick gets no right border
    481                         }
    482 
    483                         /**
    484                          * reset the slider width
    485                          */
    486                         Diff.slider.setWidth( sliderWidth );
    487                         $( '.diff-slider-ticks-wrapper' ).width( sliderWidth );
    488                         $( '#diff-slider-ticks' ).width( sliderWidth );
    489 
    490                         /**
    491                          * go through all ticks, add hover and click interactions
    492                          */
    493                         $( '.revision-tick' ).each( function() {
    494                                 Diff.slider.addTooltip ( $( this ), Diff.revisions.at( tickCount++ ).get( 'titleTooltip' ) );
    495                                 $( this ).hover(
    496                                         function() {
    497                                                 $( this ).find( '.ui-slider-tooltip' ).show().append('<div class="arrow"></div>');
    498                                         },
    499                                         function() {
    500                                                 $( this ).find( '.ui-slider-tooltip' ).hide().find( '.arrow' ).remove();
    501                                         }
    502                                 );
    503 
    504                                 /**
    505                                  * move the slider handle when the tick marks are clicked
    506                                  */
    507                                 $( this ).on( 'click',
    508                                         { tickCount: tickCount }, // pass the tick through so we know where to move the handle
    509                                         function( event ) {
    510                                                 if ( Diff.slider.singleRevision ) { // single handle mode
    511                                                         Diff.rightDiff = event.data.tickCount; // reposition the right handle
    512                                                         Diff.slider.refresh({
    513                                                                 value: Diff.rightDiff - 1
    514                                                         } );
    515                                                 } else { //compare two mode
    516                                                         if ( isRtl ) {
    517                                                                 if ( event.data.tickCount < Diff.leftDiff ) { // click was on the 'left' side
    518                                                                                 Diff.rightDiff = event.data.tickCount; // set the 'right' handle location
    519                                                                                 Diff.reloadLeft(); // reload the left handle comparison models
    520                                                                 } else { // middle or 'right' clicks
    521                                                                         Diff.leftDiff = event.data.tickCount; // set the 'left' handle location
    522                                                                         Diff.reloadRight(); // reload right handle models
    523                                                                 }
    524                                                         } else {
    525                                                                 if ( event.data.tickCount < Diff.leftDiff ) { // click was on the 'left' side
    526                                                                                 Diff.leftDiff = event.data.tickCount; // set the left handle location
    527                                                                                 Diff.reloadRight(); // reload the right handle comparison models
    528                                                                 } else { // middle or 'right' clicks
    529                                                                         Diff.rightDiff = event.data.tickCount; // set the right handle location
    530                                                                         Diff.reloadLeft(); // reload left handle models
    531                                                                 }
    532                                                         }
    533                                                         Diff.slider.refresh( { // set the slider handle positions
    534                                                                 values: [ isRtl ? Diff.rightDiff : Diff.leftDiff, isRtl ? Diff.leftDiff : Diff.rightDiff ]
    535                                                         } );
    536                                                 }
    537                                                 Diff.revisionView.render(); // render the main view
    538                                         } );
    539                         } );
     296                initialize: function() {
     297                        this.$el.html( this.template )
    540298                },
    541299
    542                 // render the tick mark view
    543                 render: function() {
    544                         var self = this, addHtml;
    545 
    546                         if ( null !== self.model ) {
    547                                 addHtml = "";
    548                                 _.each ( self.model.models, function( theModel ) {
    549                                         addHtml = addHtml + self.template ( theModel.toJSON() );
    550                                 });
    551                                 self.$el.html( addHtml );
    552 
    553                         }
    554                         self.resetTicks();
    555                         return self;
    556                 }
    557         } );
    558 
    559         /**
    560          * wp.revisions.view.Interact
    561          *
    562          * Next/Prev buttons and the slider
    563          */
    564         revisions.view.Interact = Backbone.View.extend({
    565                 el: $( '#revision-interact' ),
    566                 template: wp.template( 'revision-interact' ),
    567 
    568                 // next and previous buttons, only available in compare one mode
    569300                events: {
    570                         'click #next':     ! isRtl ? 'nextRevision' : 'previousRevision',
    571                         'click #previous': ! isRtl ? 'previousRevision' : 'nextRevision'
     301                        'click #next': 'nextRevision',
     302                        'click #previous': 'previousRevision'
    572303                },
     304               
     305                nextRevision: function() {
     306                        var toIndex = this.model.revisions.indexOf( this.model.get( 'to' ) );
     307                        toIndex = isRtl ? toIndex - 1 : toIndex + 1;
     308                        this.model.set( { 'to': this.model.revisions.at( toIndex ) } );
     309                },
     310               
     311                previousRevision: function() {
     312                        var toIndex = this.model.revisions.indexOf( this.model.get('to') );
     313                        toIndex = isRtl ? toIndex + 1 : toIndex - 1;
     314                        this.model.set( { 'to': this.model.revisions.at( toIndex ) } );
     315                },
    573316
    574                 render: function() {
    575                         var modelcount;
    576                         this.$el.html( this.template );
    577 
    578                         modelcount = Diff.revisions.length;
    579 
    580                         Diff.slider.singleRevision = Diff.singleRevision;
    581                         Diff.slider.render();
    582 
    583                         if ( Diff.singleRevision ) {
    584                                 Diff.slider.refresh({
    585                                         value: Diff.rightDiff - 1, // rightDiff value is off model index by 1
    586                                         min: 0,
    587                                         max: modelcount - 1
    588                                 });
    589 
    590                                 $( '#revision-diff-container' ).removeClass( 'comparing-two-revisions' );
    591 
    592                         } else {
    593                                 Diff.slider.refresh({
    594                                         // in RTL mode the 'left handle' is the second in the slider, 'right' is first
    595                                         values: [ isRtl ? Diff.rightDiff : Diff.leftDiff, isRtl ? Diff.leftDiff : Diff.rightDiff ],
    596                                         min: 1,
    597                                         max: modelcount + 1,
    598                                         range: true
    599                                 });
    600 
    601                                 $( '#revision-diff-container' ).addClass( 'comparing-two-revisions' );
    602                                 // in RTL mode the 'left handle' is the second in the slider, 'right' is first
    603                                 $( '#diff-slider a.ui-slider-handle' ).first().addClass( isRtl ? 'right-handle' : 'left-handle' );
    604                                 $( '#diff-slider a.ui-slider-handle' ).last().addClass( isRtl ? 'left-handle' : 'right-handle' );
    605 
    606                         }
    607 
    608                         return this;
     317                ready: function() {
     318                        this.listenTo( this.model, 'change:diffId', this.disabledButtonCheck );
    609319                },
    610320
    611                 // go to the next revision
    612                 nextRevision: function() {
    613                         if ( Diff.rightDiff < this.model.length ) // unless at right boundry
    614                                 Diff.rightDiff = Diff.rightDiff + 1 ;
     321                // Check to see if the Previous or Next buttons need to be disabled or enabled
     322                disabledButtonCheck: function() {
     323                        var maxVal =   isRtl ? 0 : this.model.revisions.length - 1,
     324                                minVal =   isRtl ? this.model.revisions.length - 1 : 0,
     325                                next =     $( '.revisions-next .button' ),
     326                                previous = $( '.revisions-previous .button' ),
     327                                val =      this.model.revisions.indexOf( this.model.get( 'to' ) );
    615328
    616                         Diff.revisionView.render();
     329                        // Disable "Next" button if you're on the last node
     330                        if ( maxVal === val )
     331                                next.prop( 'disabled', true );
     332                        else
     333                                next.prop( 'disabled', false );
    617334
    618                         Diff.slider.refresh({
    619                                 value: Diff.rightDiff - 1
    620                         }, true );
     335                        // Disable "Previous" button if you're on the first node
     336                        if ( minVal === val )
     337                                previous.prop( 'disabled', true );
     338                        else
     339                                previous.prop( 'disabled', false );
    621340                },
    622341
    623                 // go to the previous revision
    624                 previousRevision: function() {
    625                         if ( Diff.rightDiff > 1 ) // unless at left boundry
    626                                 Diff.rightDiff = Diff.rightDiff - 1 ;
    627342
    628                         Diff.revisionView.render();
    629 
    630                         Diff.slider.refresh({
    631                                 value: Diff.rightDiff - 1
    632                         }, true );
    633                 }
    634343        });
    635344
    636         /**
    637          * wp.revisions.view.Diff
    638          *
    639          * Diff, compare two checkbox and restore button
    640          */
    641         revisions.view.Diff = Backbone.View.extend({
    642                 el: $( '#revisions-diff' ),
    643                 template: wp.template( 'revisions-diff' ),
    644                 draggingLeft: false,
     345        // The slider view.
     346        // Encapsulates all of the configuration for the jQuery UI slider into a view.
     347        revisions.view.Slider = wp.Backbone.View.extend({
     348                tagName: 'div',
     349                className: 'wp-slider',
    645350
    646                 // the compare two button is in this view, add the interaction here
    647                 events: {
    648                         'click #compare-two-revisions': 'compareTwo',
    649                         'click #restore-revision':      'restore'
    650                 },
     351                initialize: function() {
     352                        _.bindAll( this, 'start', 'slide', 'stop' );
    651353
    652                 // render the revisions
    653                 render: function() {
    654                         var addHtml = '', thediff;
     354                        // Create the slider model from the provided collection data.
     355                        // TODO: This should actually pull from the model's `to` key.
     356                        var latestRevisionIndex = this.model.revisions.length - 1;
    655357
    656                         // compare two revisions mode?
    657                         if ( ! Diff.singleRevision ) {
    658                                 if ( this.draggingLeft ) {
    659                                         thediff = Diff.leftDiff - 1; //leftDiff value is off model index by 1
    660                                         if ( this.model.at( thediff ) ) {
    661                                                 addHtml = this.template( this.model.at( thediff ).toJSON() );
    662                                         }
    663                                 } else { // dragging right handle
    664                                         thediff = Diff.rightDiff - 1; // rightDiff value is off model index by 1
    665                                         if ( this.model.at( thediff ) ) {
    666                                                 addHtml = this.template( this.model.at( thediff ).toJSON() );
    667                                         }
    668                                 }
    669                         } else { // end compare two revisions mode, eg only one slider handle
    670                                 if ( this.model.at( Diff.rightDiff - 1 ) ) { // rightDiff value is off model index by 1
    671                                         addHtml = this.template( this.model.at( Diff.rightDiff - 1 ).toJSON() );
    672                                 }
    673                         }
    674                         this.$el.html( addHtml );
     358                        // Find the initially selected revision
     359                        var initiallySelectedRevisionIndex =
     360                                this.model.revisions.indexOf( this.model.revisions.findWhere( { id: Number( revisions.settings.selectedRevision ) } ) );
    675361
    676                         if ( this.model.length < 2 ) {
    677                                 $( '#diff-slider' ).hide(); // don't allow compare two if fewer than three revisions
    678                                 $( '.diff-slider-ticks-wrapper' ).hide();
    679                         }
     362                        this.settings = new revisions.model.Slider({
     363                                max:   latestRevisionIndex,
     364                                value: initiallySelectedRevisionIndex,
     365                                start: this.start,
     366                                slide: this.slide,
     367                                stop:  this.stop
     368                        });
     369                },
    680370
    681                         this.toggleCompareTwoCheckbox();
     371                ready: function() {
     372                        this.$el.slider( this.settings.toJSON() );
     373                        this.settings.on( 'change', function( model, options ) {
     374                                // Apply changes to slider settings here.
     375                                this.$el.slider( { value: this.model.revisions.indexOf( this.model.get( 'to' ) ) } ); // Set handle to current to model
     376                        }, this );
     377                        // Reset to the initially selected revision
     378                        this.slide( '', this.settings.attributes );
    682379
    683                         // hide the restore button when on the last sport/current post data
    684                         $( '#restore-revision' ).toggle( ! Diff.revisions.at( Diff.rightDiff - 1 ).get( 'isCurrent' ) );
     380                        // Listen for changes in the diffId
     381                        this.listenTo( this.model, 'change:diffId', this.diffIdChanged );
    685382
    686                         return this;
    687383                },
    688384
    689                 toggleCompareTwoCheckbox: function() {
    690                         // don't allow compare two if fewer than three revisions
    691                         if ( this.model.length < 3 )
    692                                 $( '#toggle-revision-compare-mode' ).hide();
    693 
    694                         $( '#compare-two-revisions' ).prop( 'checked', ! Diff.singleRevision );
     385                diffIdChanged: function() {
     386                        // Reset the view settings when diffId is changed
     387                        this.settings.set( { 'value': this.model.revisions.indexOf( this.model.get( 'to' ) ) } );
    695388                },
    696389
    697                 // turn on/off the compare two mode
    698                 compareTwo: function() {
    699                         if ( $( '#compare-two-revisions' ).is( ':checked' ) ) { // compare 2 mode
    700                                 Diff.singleRevision = false ;
     390                start: function() {
    701391
    702                                 // in RTL mode handles are swapped, so boundary checks are different;
    703                                 if ( isRtl ){
    704                                         Diff.leftDiff = Diff.revisions.length; // put the left handle at the rightmost position, representing current revision
     392                },
    705393
    706                                         if ( Diff.revisions.length === Diff.rightDiff ) // make sure 'left' handle not in rightmost slot
    707                                                 Diff.rightDiff = Diff.rightDiff - 1;
    708                                 } else {
    709                                         if ( 1 === Diff.rightDiff ) // make sure right handle not in leftmost slot
    710                                                 Diff.rightDiff = 2;
    711                                 }
     394                slide: function( event, ui ) {
     395                        var attributes = {
     396                                to: this.model.revisions.at( ui.value )
     397                        };
    712398
    713                                 Diff.revisionView.draggingLeft = false;
     399                        // If we're at the first revision, unset 'from'.
     400                        if ( ui.value )
     401                                attributes.from = this.model.revisions.at( ui.value - 1 );
     402                        else
     403                                this.model.unset('from', { silent: true });
    714404
    715                                 revisions.model.settings.revision_id = ''; // reset passed revision id so switching back to one handle mode doesn't re-select revision
    716                                 Diff.reloadLeftRight(); // load diffs for left and right handles
    717                                 Diff.revisionView.model = Diff.rightHandleRevisions;
    718 
    719                         } else { // compare one mode
    720                                 Diff.singleRevision = true;
    721                                 Diff.revisionView.draggingLeft = false;
    722                                 Diff.reloadModelSingle();
    723                         }
    724                         Diff.revisionsInteractions.render();
    725                         Diff.tickmarkView.render();
     405                        this.model.set( attributes );
    726406                },
    727407
    728                 restore: function() {
    729                         document.location = $( '#restore-revision' ).data( 'restoreLink' );
     408                stop: function() {
    730409                }
    731410        });
    732411
     412        // The diff view.
     413        // This is the view for the current active diff.
     414        revisions.view.Diff = wp.Backbone.View.extend({
     415                tagName: 'div',
     416                className: 'revisions-diff',
     417                template: wp.template('revisions-diff'),
    733418
    734         /**
    735          * ========================================================================
    736          * MODELS
    737          * ========================================================================
    738          */
    739 
    740         /**
    741          * wp.revisions.Revision
    742          */
    743         Revision = revisions.model.Revision = Backbone.Model.extend({
    744                 idAttribute: 'ID',
    745 
    746                 defaults: {
    747                         ID: 0,
    748                         titleTo: '',
    749                         titleTooltip: '',
    750                         titleFrom: '',
    751                         diff: '<div class="diff-loading"><div class="spinner"></div></div>',
    752                         restoreLink: '',
    753                         completed: false,
    754                         linesAdded: 0,
    755                         linesDeleted: 0,
    756                         scopeOfChanges: 'none',
    757                         previousID: 0,
    758                         isCurrent: false
    759                 },
    760 
    761                 url: function() {
    762                         if ( Diff.singleRevision ) {
    763                                 return ajaxurl +
    764                                         '?action=revisions-data' +
    765                                         '&show_autosaves=true' +
    766                                         '&show_split_view=true' +
    767                                         '&nonce=' + revisions.model.settings.nonce +
    768                                         '&single_revision_id=' + this.id +
    769                                         '&compare_to=' + this.get( 'previousID' ) +
    770                                         '&post_id=' + revisions.model.settings.post_id;
    771                         } else {
    772                                 return this.collection.url() + '&single_revision_id=' + this.id;
    773                         }
    774 
     419                // Generate the options to be passed to the template.
     420                prepare: function() {
     421                        return _.extend({ fields: this.model.fields.toJSON() }, this.options );
    775422                }
    776423        });
    777424
    778         /**
    779          * wp.revisions.Revisions
    780          */
    781         Revisions = revisions.Revisions = Backbone.Collection.extend({
    782                 model: Revision,
     425        // Initialize the revisions UI.
     426        revisions.init = function() {
     427                revisions.view.frame = new revisions.view.Frame({
     428                        collection: new revisions.model.Revisions( revisions.settings.revisionData )
     429                }).render();
     430        };
    783431
    784                 initialize: function( models, options ) {
    785                         this.options = _.defaults( options || {}, {
    786                                 'compareTo': revisions.model.settings.post_id,
    787                                 'post_id': revisions.model.settings.post_id,
    788                                 'showAutosaves': true,
    789                                 'showSplitView': true,
    790                                 'rightHandleAt': 0,
    791                                 'leftHandleAt': 0,
    792                                 'nonce': revisions.model.settings.nonce
    793                         });
    794                 },
    795 
    796                 url: function() {
    797                         return ajaxurl +
    798                                 '?action=revisions-data' +
    799                                 '&compare_to=' + this.options.compareTo + // revision are we comparing to
    800                                 '&post_id=' + this.options.post_id + // the post id
    801                                 '&show_autosaves=' + this.options.showAutosaves + // show or hide autosaves
    802                                 '&show_split_view=' + this.options.showSplitView + // show in split view or single column view
    803                                 '&right_handle_at=' + this.options.rightHandleAt + // mark point for comparison list
    804                                 '&left_handle_at=' + this.options.leftHandleAt + // mark point for comparison list
    805                                 '&nonce=' + this.options.nonce;
    806                 },
    807 
    808                 reload: function( options ) {
    809                         this.options = _.defaults( options.options || {}, this.options );
    810 
    811                         this.fetch({
    812                                 success: options.success || null,
    813                                 error: options.error || null
    814                         });
    815                 }
    816 
    817         } );
    818 
    819         $( wp.revisions );
    820 
     432        $( revisions.init );
    821433}(jQuery));
  • wp-admin/revision.php

     
    88
    99/** WordPress Administration Bootstrap */
    1010require_once('./admin.php');
     11
     12require ABSPATH . 'wp-admin/includes/revision.php';
     13
     14// wp_get_revision_ui_diff( $post, $compare_from, $compare_to )
     15// wp_prepare_revisions_for_js( $post )
     16
    1117wp_reset_vars( array( 'revision', 'action' ) );
    1218
    1319$revision_id = absint( $revision );
     
    7783        $parent_file = $submenu_file = 'edit.php';
    7884
    7985wp_enqueue_script( 'revisions' );
     86wp_localize_script( 'revisions', '_wpRevisionsSettings', wp_prepare_revisions_for_js( $post, $revision_id ) );
    8087
    81 
    82 $settings = array(
    83         'post_id'     => $post->ID,
    84         'nonce'       => wp_create_nonce( 'revisions-ajax-nonce' ),
    85         'revision_id' => $revision_id
    86 );
    87 
    88 wp_localize_script( 'revisions', 'wpRevisionsSettings', $settings );
    89 
    9088/* Revisions Help Tab */
    9189
    9290$revisions_overview  = '<p>' . __( 'This screen is used for managing your content revisions.' ) . '</p>';
     
    114112
    115113<div class="wrap">
    116114        <?php screen_icon(); ?>
    117         <div id="revision-diff-container" class="current-version right-model-loading">
    118                 <h2 class="long-header"><?php echo $h2; ?></h2>
     115        <h2 class="long-header"><?php echo $h2; ?></h2>
     116</div>
    119117
    120                 <div id="loading-status" class="updated message">
    121                         <p><span class="spinner" ></span></p>
    122                 </div>
     118<script id="tmpl-revisions-frame" type="text/html">
     119        <span class="spinner"></span>
     120        <div class="revisions-control-frame"></div>
     121        <div class="revisions-diff-frame"></div>
     122</script>
    123123
    124                 <div class="diff-slider-ticks-wrapper">
    125                         <div id="diff-slider-ticks"></div>
    126                 </div>
     124<script id="tmpl-revisions-controls" type="text/html">
     125        <div class="revision-toggle-compare-mode">
     126                <label>
     127                        <input type="checkbox" class="compare-two-revisions" />
     128                        <?php esc_attr_e( 'Compare two revisions' ); ?>
     129                </label>
     130        </div>
    127131
    128                 <div id="revision-interact"></div>
     132        <div class="revisions-previous">
     133                <input class="button" type="button" id="previous" value="<?php echo esc_attr_x( 'Previous', 'Button label for a previous revision' ); ?>" />
     134        </div>
    129135
    130                 <div id="revisions-diff"></div>
     136        <div class="revisions-next">
     137                <input class="button" type="button" id="next" value="<?php echo esc_attr_x( 'Next', 'Button label for a next revision' ); ?>" />
    131138        </div>
    132 </div>
    133139
     140        <div class="revisions-slider"></div>
     141</script>
     142
    134143<script id="tmpl-revisions-diff" type="text/html">
     144        <# _.each( data.fields, function( field ) { #>
     145                <h3>{{{ field.name }}}</h3>
     146                {{{ field.diff }}}
     147        <# }); #>
     148</script>
     149
     150<script id="tmpl-revisions-diff-old" type="text/html">
    135151        <div id="toggle-revision-compare-mode">
    136152                <label>
    137153                        <input type="checkbox" id="compare-two-revisions" />
     
    157173                </div>
    158174        </div>
    159175
    160         </div>
    161 
    162176        <div id="diff-table">{{{ data.diff }}}</div>
    163177</script>
    164178
    165 <script id="tmpl-revision-interact" type="text/html">
     179<script id="tmpl-revision-interact-old" type="text/html">
    166180        <div id="diff-previous-revision">
    167181                <input class="button" type="button" id="previous" value="<?php echo esc_attr_x( 'Previous', 'Button label for a previous revision' ); ?>" />
    168182        </div>
     
    171185                <input class="button" type="button" id="next" value="<?php echo esc_attr_x( 'Next', 'Button label for a next revision' ); ?>" />
    172186        </div>
    173187
    174         <div id="diff-slider" class="wp-slider"></div>
    175188</script>
    176189
    177190<script id="tmpl-revision-ticks" type="text/html">
  • wp-admin/css/wp-admin.css

     
    34813481/*------------------------------------------------------------------------------
    34823482  11.2 - Post Revisions
    34833483------------------------------------------------------------------------------*/
     3484.revisions .spinner {
     3485        float: none;
     3486        margin: 100px auto;
     3487}
    34843488
     3489.revisions.loading .spinner {
     3490        display: block;
     3491}
     3492
     3493.revisions-control-frame,
     3494.revisions-diff-frame {
     3495        position: relative;
     3496}
     3497
     3498.revisions-controls {
     3499        height: 20px;
     3500        padding: 40px 0 20px;
     3501        border-bottom: 1px solid #dfdfdf;
     3502        margin-bottom: 10px;
     3503}
     3504
     3505.revision-toggle-compare-mode {
     3506        position: absolute;
     3507        top: 0;
     3508        right: 0;
     3509}
     3510
     3511.revisions-previous {
     3512        float: left;
     3513}
     3514
     3515.revisions-next {
     3516        float: right;
     3517}
     3518
     3519.wp-slider {
     3520        width: 70%;
     3521        margin: 6px auto 0;
     3522}
     3523
    34853524/* Revision meta box */
    34863525.post-revisions li img,
    34873526#revisions-meta-restored img {
     
    35273566        position: relative;
    35283567}
    35293568
    3530 #toggle-revision-compare-mode {
    3531         position: absolute;
    3532         top: 0;
    3533         right: 0;
    3534         padding: 9px 9px 0 0;
    3535 }
    3536 
    35373569#loading-status {
    35383570        display: none;
    35393571        position: absolute;
     
    35513583        padding: 20px 0;
    35523584}
    35533585
    3554 #diff-next-revision,
    3555 #diff-previous-revision {
    3556         margin-top: -.4em; /* Same line as the slider (height: .8em) */
    3557 }
    3558 
    3559 #diff-next-revision {
    3560         float: right;
    3561 }
    3562 
    3563 #diff-previous-revision {
    3564         float: left;
    3565 }
    3566 
    3567 #diff-slider {
    3568         width: 70%;
    3569         margin: 0 auto;
    3570 }
    3571 
    35723586.comparetwo #diff-slider {
    35733587        width: 95%;
    35743588}