Make WordPress Core

Ticket #24425: 24425.draft.15.diff

File 24425.draft.15.diff, 61.1 KB (added by adamsilverstein, 11 years ago)

adds back mouse tracking for handle while dragging

  • 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                );
    2159 
    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();
     2110        }
     2111        wp_send_json_success( $return );
    23002112}
  • wp-admin/includes/revision.php

     
     1<?php
     2
     3function wp_get_revision_ui_diff( $post, $compare_from, $compare_to ) {
     4        if ( ! $post = get_post( $post ) )
     5                return false;
     6
     7        if ( $compare_from ) {
     8                if ( ! $compare_from = get_post( $compare_from ) )
     9                        return false;
     10        } else {
     11                // If we're dealing with the first revision...
     12                $compare_from = false;
     13        }
     14
     15        if ( ! $compare_to = get_post( $compare_to ) )
     16                return false;
     17
     18        // If comparing revisions, make sure we're dealing with the right post parent.
     19        if ( $compare_from && $compare_from->post_parent !== $post->ID )
     20                return false;
     21        if ( $compare_to->post_parent !== $post->ID )
     22                return false;
     23
     24        if ( $compare_from && strtotime( $compare_from->post_date_gmt ) > strtotime( $compare_to->post_date_gmt ) ) {
     25                $temp = $compare_from;
     26                $compare_from = $compare_to;
     27                $compare_to = $temp;
     28        }
     29
     30        $return = array();
     31
     32        foreach ( _wp_post_revision_fields() as $field => $name ) {
     33                $content_from = $compare_from ? apply_filters( "_wp_post_revision_field_$field", $compare_from->$field, $field, $compare_from, 'left' ) : '';
     34                $content_to = apply_filters( "_wp_post_revision_field_$field", $compare_to->$field, $field, $compare_to, 'right' );
     35
     36                $diff = wp_text_diff( $content_from, $content_to, array( 'show_split_view' => true ) );
     37
     38                if ( ! $diff && 'post_title' === $field ) {
     39                        // It's a better user experience to still show the Title, even if it didn't change.
     40                        // No, you didn't see this.
     41                        $diff = "<table class='diff'><col class='ltype' /><col class='content' /><col class='ltype' /><col class='content' /><tbody><tr>";
     42                        $diff .= '<td>' . esc_html( $compare_from->post_title ) . '</td><td></td><td>' . esc_html( $compare_to->post_title ) . '</td>';
     43                        $diff .= '</tr></tbody>';
     44                        $diff .= '</table>';
     45                }
     46
     47                if ( $diff ) {
     48                        $return[] = array(
     49                                'id' => $field,
     50                                'name' => $name,
     51                                'diff' => $diff,
     52                        );
     53                }
     54        }
     55        return $return;
     56}
     57
     58function wp_prepare_revisions_for_js( $post, $selected_revision_id ) {
     59        $post = get_post( $post );
     60        $revisions = array();
     61        $current = current_time( 'timestamp' );
     62
     63        $revisions = wp_get_post_revisions( $post->ID );
     64
     65        cache_users( wp_list_pluck( $revisions, 'post_author' ) );
     66
     67        foreach ( $revisions as $revision ) {
     68                $modified_gmt = strtotime( $revision->post_modified_gmt );
     69                $restore_link = wp_nonce_url(
     70                        add_query_arg(
     71                                array( 'revision' => $revision->ID,
     72                                        'action' => 'restore' ),
     73                                        admin_url( 'revision.php' )
     74                        ),
     75                        "restore-post_{$revision->ID}"
     76                );
     77                $revisions[ $revision->ID ] = array(
     78                        'id'           => $revision->ID,
     79                        'title'        => get_the_title( $post->ID ),
     80                        'author' => array(
     81                                'id'     => (int) $revision->post_author,
     82                                'avatar' => get_avatar( $revision->post_author, 24 ),
     83                                'name'   => get_the_author_meta( 'display_name', $revision->post_author ),
     84                        ),
     85                        'date'         => date_i18n( __( 'M j, Y @ G:i' ), $modified_gmt ),
     86                        'dateShort'    => date_i18n( _x( 'j M @ G:i', 'revision date short format' ), $modified_gmt ),
     87                        'timeAgo'      => human_time_diff( $modified_gmt, $current ),
     88                        'autosave'     => wp_is_post_autosave( $revision ),
     89                        'current'      => $revision->post_modified_gmt === $post->post_modified_gmt,
     90                        'restoreUrl'   => urldecode( $restore_link ),
     91                );
     92        }
     93
     94        return array(
     95                'postId'           => $post->ID,
     96                'nonce'            => wp_create_nonce( 'revisions-ajax-nonce' ),
     97                'revisionData'     => array_values( $revisions ),
     98                'selectedRevision' => $selected_revision_id,
     99        );
     100}
     101 No newline at end of file
  • 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                        return this.load( comparisons );
    12597                },
    12698
    127                 stopRightModelLoading: function() {
    128                         this.rightModelLoading = false;
     99                load: function( comparisons ) {
     100                        // Our collection should only ever grow, never shrink, so remove: false
     101                        return this.fetch({ data: { compare: comparisons }, remove: false });
    129102                },
    130103
    131                 stopModelLoadingSpinner: function() {
    132                         $('#revision-diff-container').removeClass('right-model-loading');
    133                         $('#revision-diff-container').removeClass('left-model-loading');
    134                 },
     104/**/
     105                loadLast: function( num ) {
     106                        num     = num || 1;
     107                        var ids = this.getProximalDiffIds();
     108                        ids     = _.last( ids, num );
    135109
    136                 reloadModel: function() {
    137                         if ( this.singleRevision ) {
    138                                 this.reloadModelSingle();
    139                         } else {
    140                                 this.reloadLeftRight();
     110                        if ( ids.length ) {
     111                                return this.loadNew( ids );
    141112                        }
    142113                },
    143114
    144                 // load the models for the single handle mode
    145                 reloadModelSingle: function() {
    146                         var self = this;
     115                loadLastUnloaded: function( num ) {
     116                        num     = num || 1;
     117                        var ids = this.getUnloadedProximalDiffIds();
     118                        ids     = _.last( ids, num );
    147119
    148                         self.startRightModelLoading();
     120                        if ( ids.length ) {
     121                                return this.loadNew( ids );
     122                        }
     123                },
    149124
    150                         self.revisions.reload({
    151                                 options: {
    152                                 'showAutosaves': self.autosaves,
    153                                 'showSplitView': self.showSplitView
    154                                 },
     125                getProximalDiffIds: function() {
     126                        var previous = 0, ids = [];
     127                        this.revisions.each( _.bind( function(revision) {
     128                                ids.push( previous + ':' + revision.id );
     129                                previous = revision.id;
     130                        }, this ) );
     131                        return ids;
     132                },
    155133
    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                                 },
    168 
    169                                 error: function() {
    170                                         self.stopRightModelLoading();
    171                                 }
    172                         });
     134                getUnloadedProximalDiffIds: function() {
     135                        var comparisons = this.getProximalDiffIds();
     136                        comparisons     = _.object( comparisons, comparisons );
     137                        _.each( comparisons, _.bind( function( id ) {
     138                                // Exists
     139                                if ( this.get( id ) )
     140                                        delete comparisons[ id ];
     141                        }, this ) );
     142                        return _.toArray( comparisons );
    173143                },
    174144
    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                         });
    185 
    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
    193                                         });
    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();
    201                                 }
    202                         });
     145                loadAllBy: function( chunkSize ) {
     146                        chunkSize    = chunkSize || 20;
     147                        var unloaded = this.getUnloadedProximalDiffIds();
     148                        if ( unloaded.length ) {
     149                                return this.loadLastUnloaded( chunkSize ).always( _.bind( function() {
     150                                        this.loadAllBy( chunkSize );
     151                                }, this ) );
     152                        }
    203153                },
    204154
    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                         });
     155                sync: function( method, model, options ) {
     156                        if ( 'read' === method ) {
     157                                options         = options || {};
     158                                options.context = this;
     159                                options.data    = _.extend( options.data || {}, {
     160                                        action: 'get-revision-diffs',
     161                                        post_id: revisions.settings.postId
     162                                });
    215163
    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                                 },
     164                                var deferred = wp.xhr.send( options );
     165                                var requests = this.requests;
    225166
    226                                 error: function( response ) {
    227                                         self.stopRightModelLoading();
     167                                // Record that we're requesting each diff.
     168                                if ( options.data.compare ) {
     169                                        _.each( options.data.compare, function( id ) {
     170                                                requests[ id ] = deferred;
     171                                        });
    228172                                }
    229                         });
    230173
    231                 },
     174                                // When the request completes, clear the stored request.
     175                                deferred.always( function() {
     176                                        if ( options.data.compare ) {
     177                                                _.each( options.data.compare, function( id ) {
     178                                                        delete requests[ id ];
     179                                                });
     180                                        }
     181                                });
    232182
    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                 },
     183                                return deferred;
    242184
    243                 disabledButtonCheck: function( val ) {
    244                         var maxVal = this.revisions.length - 1,
    245                                 next = ! isRtl ? $( '#next' ) : $( '#previous' ),
    246                                 prev = ! isRtl ? $( '#previous' ) : $( '#next' );
     185                        // Otherwise, fall back to `Backbone.sync()`.
     186                        } else {
     187                                return Backbone.Model.prototype.sync.apply( this, arguments );
     188                        }
     189                }
     190        });
    247191
    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 );
    253192
    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 );
     193        revisions.model.FrameState = Backbone.Model.extend({
     194                initialize: function( attributes, options ) {
     195                        this.revisions = options.revisions;
     196                        this.diffs     = new revisions.model.Diffs( [], {revisions: this.revisions} );
     197
     198                        this.listenTo( this, 'change:from change:to', this.updateDiffId );
    259199                },
    260200
    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
     201                updateDiffId: function() {
     202                        var from = this.get( 'from' );
     203                        var to   = this.get( 'to' );
     204                        this.set( 'diffId', (from ? from.id : '0') + ':' + to.id );
    281205                }
    282206        });
    283207
     
    288212         * ========================================================================
    289213         */
    290214
    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,
     215        // The frame view. This contains the entire page.
     216        revisions.view.Frame = wp.Backbone.View.extend({
     217                tagName: 'div',
     218                className: 'revisions',
     219                template: wp.template('revisions-frame'),
    299220
    300                 initialize: function( options ) {
    301                         this.options = _.defaults( options || {}, {
    302                                 value: 0,
    303                                 min: 0,
    304                                 max: 1,
    305                                 step: 1
     221                initialize: function() {
     222                        this.model = new revisions.model.FrameState({}, {
     223                                revisions: this.collection
    306224                        });
    307                 },
    308225
    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;
     226                        this.listenTo( this.model, 'change:diffId', this.updateDiff );
    322227
    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;
     228                        this.views.set( '.revisions-control-frame', new revisions.view.Controls({
     229                                model: this.model
     230                        }) );
    327231
    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;
     232                        if ( this.model.revisions.length ) {
     233                                var last = this.model.revisions.last(2);
     234                                var attributes = { to: last.pop() };
    333235
    334                                         Diff.rightDiff = isRtl ? ui.values[0] : ui.values[1]; // handles are reversed in RTL mode
    335                                 }
     236                                if ( last.length )
     237                                        attributes.from = last.pop();
    336238
    337                                 Diff.revisionView.render();
     239                                this.model.set( attributes );
     240
     241                                // Load the rest: first 10, then the rest by 50
     242                                this.model.diffs.loadLastUnloaded( 10 ).always( _.bind( function() {
     243                                        this.model.diffs.loadAllBy( 50 );
     244                                }, this ) );
    338245                        }
    339246                },
    340247
    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;
     248                render: function() {
     249                        wp.Backbone.View.prototype.render.apply( this, arguments );
    350250
    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;
     251                        $('#wpbody-content .wrap').append( this.el );
     252                        this.views.ready();
    355253
    356                                 Diff.revisionView.draggingLeft = true;
     254                        return this;
     255                },
    357256
    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                                 }
     257                updateDiff: function() {
     258                        this.model.diffs.ensure( this.model.get('diffId'), this ).done( function( diff ) {
     259                                if ( this.model.get('diffId') !== diff.id )
     260                                        return;
     261                                this.views.set( '.revisions-diff-frame', new revisions.view.Diff({
     262                                        model: diff
     263                                }));
     264                        });
     265                }
     266        });
    364267
    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
     268        // The control view.
     269        // This contains the revision slider, previous/next buttons, and the compare checkbox.
     270        revisions.view.Controls = wp.Backbone.View.extend({
     271                tagName: 'div',
     272                className: 'revisions-controls',
    366273
    367                         } else {
    368                                 // Right handler
    369                                 if ( Diff.rightModelLoading || 0 === Diff.rightHandleRevisions.length) // right model still loading, prevent sliding right handle
    370                                         return false;
     274                initialize: function() {
     275                        // Add the button view
     276                        this.views.add( new revisions.view.Buttons({
     277                                model: this.model
     278                        }));
    371279
    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                                 }
     280                        // Add the Slider view
     281                        this.views.add( new revisions.view.Slider({
     282                                model: this.model
     283                        }) );
    378284
    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
    381                         }
    382                 },
     285                        // Add the Meta view
     286                        this.views.add( new revisions.view.Meta({
     287                                model: this.model
     288                        }) );
     289                }
     290        });
    383291
    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;
     292        // The meta view.
     293        // This contains the revision meta, and the restore button.
     294        revisions.view.Meta = wp.Backbone.View.extend({
     295                tagName: 'div',
     296                className: 'revisions-meta',
     297                template: wp.template('revisions-meta'),
    393298
    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                         }
     299                initialize: function() {
     300                        this.listenTo( this.model, 'change:diffId', this.updateMeta );
    405301                },
    406302
    407                 addTooltip: function( handle, message ) {
    408                         handle.find( '.ui-slider-tooltip' ).html( message );
     303                events: {
     304                        'click #restore-revision': 'restoreRevision'
    409305                },
    410306
    411                 width: function() {
    412                         return $( '#diff-slider' ).width();
     307                restoreRevision: function() {
     308                        var restoreUrl    = this.model.get('to').attributes.restoreUrl.replace(/&amp;/g, '&');
     309                        document.location = restoreUrl;
    413310                },
    414311
    415                 setWidth: function( width ) {
    416                         $( '#diff-slider' ).width( width );
    417                 },
    418 
    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 );
     312                updateMeta: function() {
     313                        this.$el.html( this.template( this.model.toJSON() ) );
     314                        if( this.model.get( 'to' ).attributes.current ) {
     315                                $( '#restore-revision' ).prop( 'disabled', true);
     316                        } else {
     317                                $( '#restore-revision' ).prop( 'disabled', false)
     318                        }
    444319                }
    445320        });
    446321
    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,
    456322
    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();
     323        // The buttons view.
     324        // Encapsulates all of the configuration for the previous/next buttons, and the compare checkbox.
     325        revisions.view.Buttons = wp.Backbone.View.extend({
     326                tagName: 'div',
     327                className: 'revisions-buttons',
     328                template: wp.template('revisions-controls'),
    467329
    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                         } );
     330                initialize: function() {
     331                        this.$el.html( this.template() )
    540332                },
    541333
    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
    569334                events: {
    570                         'click #next':     ! isRtl ? 'nextRevision' : 'previousRevision',
    571                         'click #previous': ! isRtl ? 'previousRevision' : 'nextRevision'
     335                        'click #next': 'nextRevision',
     336                        'click #previous': 'previousRevision'
    572337                },
     338               
     339                nextRevision: function() {
     340                        var toIndex = this.model.revisions.indexOf( this.model.get( 'to' ) );
     341                        toIndex     = isRtl ? toIndex - 1 : toIndex + 1;
     342                        this.model.set( { 'to': this.model.revisions.at( toIndex ) } );
     343                },
     344               
     345                previousRevision: function() {
     346                        var toIndex = this.model.revisions.indexOf( this.model.get('to') );
     347                        toIndex     = isRtl ? toIndex + 1 : toIndex - 1;
     348                        this.model.set( { 'to': this.model.revisions.at( toIndex ) } );
     349                },
    573350
    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;
     351                ready: function() {
     352                        this.listenTo( this.model, 'change:diffId', this.disabledButtonCheck );
    609353                },
    610354
    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 ;
     355                // Check to see if the Previous or Next buttons need to be disabled or enabled
     356                disabledButtonCheck: function() {
     357                        var maxVal   = isRtl ? 0 : this.model.revisions.length - 1,
     358                                minVal   = isRtl ? this.model.revisions.length - 1 : 0,
     359                                next     = $( '.revisions-next .button' ),
     360                                previous = $( '.revisions-previous .button' ),
     361                                val      = this.model.revisions.indexOf( this.model.get( 'to' ) );
    615362
    616                         Diff.revisionView.render();
     363                        // Disable "Next" button if you're on the last node
     364                        if ( maxVal === val )
     365                                next.prop( 'disabled', true );
     366                        else
     367                                next.prop( 'disabled', false );
    617368
    618                         Diff.slider.refresh({
    619                                 value: Diff.rightDiff - 1
    620                         }, true );
     369                        // Disable "Previous" button if you're on the first node
     370                        if ( minVal === val )
     371                                previous.prop( 'disabled', true );
     372                        else
     373                                previous.prop( 'disabled', false );
    621374                },
    622375
    623                 // go to the previous revision
    624                 previousRevision: function() {
    625                         if ( Diff.rightDiff > 1 ) // unless at left boundry
    626                                 Diff.rightDiff = Diff.rightDiff - 1 ;
    627376
    628                         Diff.revisionView.render();
    629 
    630                         Diff.slider.refresh({
    631                                 value: Diff.rightDiff - 1
    632                         }, true );
    633                 }
    634377        });
    635378
    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,
     379        // The slider view.
     380        // Encapsulates all of the configuration for the jQuery UI slider into a view.
     381        revisions.view.Slider = wp.Backbone.View.extend({
     382                tagName: 'div',
     383                className: 'wp-slider',
    645384
    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                 },
     385                initialize: function() {
     386                        _.bindAll( this, 'start', 'slide', 'stop' );
    651387
    652                 // render the revisions
    653                 render: function() {
    654                         var addHtml = '', thediff;
     388                        // Create the slider model from the provided collection data.
     389                        // TODO: This should actually pull from the model's `to` key.
     390                        var latestRevisionIndex = this.model.revisions.length - 1;
    655391
    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 );
     392                        // Find the initially selected revision
     393                        var initiallySelectedRevisionIndex =
     394                                this.model.revisions.indexOf(
     395                                        this.model.revisions.findWhere(  { id: Number( revisions.settings.selectedRevision ) } ) );
    675396
    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                         }
     397                        this.settings = new revisions.model.Slider({
     398                                max:   latestRevisionIndex,
     399                                value: initiallySelectedRevisionIndex,
     400                                start: this.start,
     401                                slide: this.slide,
     402                                stop:  this.stop
     403                        });
     404                },
    680405
    681                         this.toggleCompareTwoCheckbox();
     406                ready: function() {
     407                        this.$el.slider( this.settings.toJSON() );
     408                        this.settings.on( 'change', function( model, options ) {
     409                                // Apply changes to slider settings here.
     410                                this.$el.slider( { value: this.model.revisions.indexOf( this.model.get( 'to' ) ) } ); // Set handle to current to model
     411                        }, this );
     412                        // Reset to the initially selected revision
     413                        this.slide( '', this.settings.attributes );
    682414
    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' ) );
     415                        // Listen for changes in the diffId
     416                        this.listenTo( this.model, 'change:diffId', this.diffIdChanged );
    685417
    686                         return this;
    687418                },
    688419
    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 );
     420                diffIdChanged: function() {
     421                        // Reset the view settings when diffId is changed
     422                        this.settings.set( { 'value': this.model.revisions.indexOf( this.model.get( 'to' ) ) } );
    695423                },
    696424
    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 ;
     425                start: function( event, ui ) {
     426                        // Track the mouse position to enable smooth dragging, overrides default jquery ui step behaviour
     427                        $( window ).mousemove( function( e ) {
     428                                var sliderLeft  = $( '.wp-slider' ).offset().left,
     429                                        sliderRight = sliderLeft + $( '.wp-slider' ).width();
    701430
    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
    705 
    706                                         if ( Diff.revisions.length === Diff.rightDiff ) // make sure 'left' handle not in rightmost slot
    707                                                 Diff.rightDiff = Diff.rightDiff - 1;
     431                                // Follow mouse movements, as long as handle remains inside slider
     432                                if ( e.clientX < sliderLeft ) {
     433                                        $( ui.handle ).css( 'left', 0 ); // Mouse to left of slider
     434                                } else if ( e.clientX > sliderRight ) {
     435                                        $( ui.handle ).css( 'left', sliderRight - sliderLeft); // Mouse to right of slider
    708436                                } else {
    709                                         if ( 1 === Diff.rightDiff ) // make sure right handle not in leftmost slot
    710                                                 Diff.rightDiff = 2;
     437                                        $( ui.handle ).css( 'left', e.clientX - sliderLeft ); // Mouse in slider
    711438                                }
     439                        } ); // End mousemove
     440                },
    712441
    713                                 Diff.revisionView.draggingLeft = false;
     442                slide: function( event, ui ) {
     443                        var attributes = {
     444                                to: this.model.revisions.at( ui.value )
     445                        };
    714446
    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;
     447                        // If we're at the first revision, unset 'from'.
     448                        if ( ui.value )
     449                                attributes.from = this.model.revisions.at( ui.value - 1 );
     450                        else
     451                                this.model.unset('from', { silent: true });
    718452
    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();
     453                        this.model.set( attributes );
    726454                },
    727455
    728                 restore: function() {
    729                         document.location = $( '#restore-revision' ).data( 'restoreLink' );
     456                stop: function( event, ui ) {
     457                        $( window ).unbind( 'mousemove' ); // Stop tracking the mouse
     458                        // Reset settings pops handle back to the step position
     459                        this.settings.trigger( 'change' );
    730460                }
    731461        });
    732462
     463        // The diff view.
     464        // This is the view for the current active diff.
     465        revisions.view.Diff = wp.Backbone.View.extend({
     466                tagName: 'div',
     467                className: 'revisions-diff',
     468                template: wp.template('revisions-diff'),
    733469
    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 
     470                // Generate the options to be passed to the template.
     471                prepare: function() {
     472                        return _.extend({ fields: this.model.fields.toJSON() }, this.options );
    775473                }
    776474        });
    777475
    778         /**
    779          * wp.revisions.Revisions
    780          */
    781         Revisions = revisions.Revisions = Backbone.Collection.extend({
    782                 model: Revision,
     476        // Initialize the revisions UI.
     477        revisions.init = function() {
     478                revisions.view.frame = new revisions.view.Frame({
     479                        collection: new revisions.model.Revisions( revisions.settings.revisionData )
     480                }).render();
     481        };
    783482
    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 
     483        $( revisions.init );
    821484}(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 );
     
    2127        if ( ! current_user_can( 'edit_post', $revision->post_parent ) )
    2228                break;
    2329
    24 
    2530        if ( ! $post = get_post( $revision->post_parent ) )
    2631                break;
    2732
     
    7782        $parent_file = $submenu_file = 'edit.php';
    7883
    7984wp_enqueue_script( 'revisions' );
     85wp_localize_script( 'revisions', '_wpRevisionsSettings', wp_prepare_revisions_for_js( $post, $revision_id ) );
    8086
    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 
    9087/* Revisions Help Tab */
    9188
    9289$revisions_overview  = '<p>' . __( 'This screen is used for managing your content revisions.' ) . '</p>';
     
    114111
    115112<div class="wrap">
    116113        <?php screen_icon(); ?>
    117         <div id="revision-diff-container" class="current-version right-model-loading">
    118                 <h2 class="long-header"><?php echo $h2; ?></h2>
     114        <h2 class="long-header"><?php echo $h2; ?></h2>
     115</div>
    119116
    120                 <div id="loading-status" class="updated message">
    121                         <p><span class="spinner" ></span></p>
     117<script id="tmpl-revisions-frame" type="text/html">
     118        <span class="spinner"></span>
     119        <div class="revisions-control-frame"></div>
     120        <div class="revisions-diff-frame"></div>
     121</script>
     122
     123<script id="tmpl-revisions-controls" type="text/html">
     124
     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>
     131
     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>
     135
     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' ); ?>" />
     138        </div>
     139</script>
     140
     141
     142<script id="tmpl-revisions-meta" type="text/html">
     143        <div id="diff-header">
     144                <div id="diff-header-from" class="diff-header">
     145                        <div id="diff-title-from" class="diff-title">
     146                                <strong>
     147                                <?php _ex( 'From:', 'Followed by post revision info' ); ?></strong>
     148                                        <# if ( 'undefined' !== typeof data.from ) { #>
     149                                                {{{ data.from.attributes.author.avatar }}} {{{ data.from.attributes.author.name }}},
     150                                                {{{ data.from.attributes.timeAgo }}} <?php _e( 'ago' ); ?>
     151                                                ({{{ data.from.attributes.dateShort }}})
     152                                        <# } #>
     153
     154                        </div>
     155                        <div class="clear"></div>
    122156                </div>
    123157
    124                 <div class="diff-slider-ticks-wrapper">
    125                         <div id="diff-slider-ticks"></div>
     158                <div id="diff-header-to" class="diff-header">
     159                        <div id="diff-title-to" class="diff-title">
     160                                <strong><?php _ex( 'To:', 'Followed by post revision info' ); ?></strong>
     161                                        <# if ( 'undefined' !== typeof data.to ) { #>
     162                                                {{{ data.to.attributes.author.avatar }}} {{{ data.to.attributes.author.name }}},
     163                                                {{{ data.to.attributes.timeAgo }}} <?php _e( 'ago' ); ?>
     164                                                ({{{ data.to.attributes.dateShort }}})
     165                                        <# } #>
    126166                </div>
    127167
    128                 <div id="revision-interact"></div>
    129 
    130                 <div id="revisions-diff"></div>
     168                        <input type="button" id="restore-revision" class="button button-primary" data-restore-link="{{{ data.restoreLink }}}" value="<?php esc_attr_e( 'Restore This Revision' )?>" />
     169                </div>
    131170        </div>
    132 </div>
     171</script>
    133172
    134173<script id="tmpl-revisions-diff" type="text/html">
     174        <# _.each( data.fields, function( field ) { #>
     175                <h3>{{{ field.name }}}</h3>
     176                {{{ field.diff }}}
     177        <# }); #>
     178</script>
     179
     180<script id="tmpl-revisions-diff-old" type="text/html">
    135181        <div id="toggle-revision-compare-mode">
    136182                <label>
    137183                        <input type="checkbox" id="compare-two-revisions" />
     
    157203                </div>
    158204        </div>
    159205
    160         </div>
    161 
    162206        <div id="diff-table">{{{ data.diff }}}</div>
    163207</script>
    164208
    165 <script id="tmpl-revision-interact" type="text/html">
     209<script id="tmpl-revision-interact-old" type="text/html">
    166210        <div id="diff-previous-revision">
    167211                <input class="button" type="button" id="previous" value="<?php echo esc_attr_x( 'Previous', 'Button label for a previous revision' ); ?>" />
    168212        </div>
     
    171215                <input class="button" type="button" id="next" value="<?php echo esc_attr_x( 'Next', 'Button label for a next revision' ); ?>" />
    172216        </div>
    173217
    174         <div id="diff-slider" class="wp-slider"></div>
    175218</script>
    176219
    177220<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: 60px;
     3500        padding: 40px 0 20px;
     3501        border-bottom: 1px solid #dfdfdf;
     3502        margin-bottom: 10px;
     3503}
     3504
     3505.revisions-meta {
     3506        margin-top: 15px;
     3507}
     3508.revision-toggle-compare-mode {
     3509        position: absolute;
     3510        top: 0;
     3511        right: 0;
     3512}
     3513
     3514.revisions-previous {
     3515        float: left;
     3516}
     3517
     3518.revisions-next {
     3519        float: right;
     3520}
     3521
     3522.wp-slider {
     3523        width: 70%;
     3524        margin: 6px auto 0;
     3525}
     3526
    34853527/* Revision meta box */
    34863528.post-revisions li img,
    34873529#revisions-meta-restored img {
     
    35273569        position: relative;
    35283570}
    35293571
    3530 #toggle-revision-compare-mode {
    3531         position: absolute;
    3532         top: 0;
    3533         right: 0;
    3534         padding: 9px 9px 0 0;
    3535 }
    3536 
    35373572#loading-status {
    35383573        display: none;
    35393574        position: absolute;
     
    35513586        padding: 20px 0;
    35523587}
    35533588
    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 
    35723589.comparetwo #diff-slider {
    35733590        width: 95%;
    35743591}
     
    35883605}
    35893606
    35903607#diff-title-to strong {
    3591         display: none;
     3608        display: inline;
    35923609}
    35933610
    35943611.comparing-two-revisions #diff-title-to strong {
     
    36053622        -webkit-border-radius: 3px;
    36063623        border-radius: 3px;
    36073624        padding: 5px 200px 5px 5px;
     3625        clear: both;
    36083626}
    36093627
    36103628.diff-header {