WordPress.org

Make WordPress Core

Ticket #24425: 24425.draft.2.diff

File 24425.draft.2.diff, 49.7 KB (added by koopersmith, 9 years ago)
  • wp-admin/admin-ajax.php

    do_action( 'admin_init' ); 
    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(
    $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', 'show-post-format-ui',
     59        'send-attachment-to-editor', 'save-attachment-order', 'heartbeat', 'get-revision-diffs',
     60        'show-post-format-ui',
    6061);
    6162
    6263// Register core Ajax calls.
  • wp-admin/css/wp-admin.css

    td.plugin-title p { 
    35283528/*------------------------------------------------------------------------------
    35293529  11.2 - Post Revisions
    35303530------------------------------------------------------------------------------*/
     3531.revisions .spinner {
     3532        float: none;
     3533        margin: 100px auto;
     3534}
     3535
     3536.revisions.loading .spinner {
     3537        display: block;
     3538}
     3539
     3540.revisions-control-frame,
     3541.revisions-diff-frame {
     3542        position: relative;
     3543}
     3544
     3545.revisions-controls {
     3546        height: 20px;
     3547        padding: 40px 0 20px;
     3548        border-bottom: 1px solid #dfdfdf;
     3549        margin-bottom: 10px;
     3550}
     3551
     3552.revision-toggle-compare-mode {
     3553        position: absolute;
     3554        top: 0;
     3555        right: 0;
     3556}
     3557
     3558.revisions-previous {
     3559        float: left;
     3560}
     3561
     3562.revisions-next {
     3563        float: right;
     3564}
     3565
     3566.revisions-slider {
     3567        width: 70%;
     3568        margin: 6px auto 0;
     3569}
    35313570
    35323571/* Revision meta box */
    35333572.post-revisions li img,
    table.diff .diff-addedline ins { 
    35743613        position: relative;
    35753614}
    35763615
    3577 #toggle-revision-compare-mode {
    3578         position: absolute;
    3579         top: 0;
    3580         right: 0;
    3581         padding: 9px 9px 0 0;
    3582 }
    3583 
    35843616#loading-status {
    35853617        display: none;
    35863618        position: absolute;
    table.diff .diff-addedline ins { 
    35983630        padding: 20px 0;
    35993631}
    36003632
    3601 #diff-next-revision,
    3602 #diff-previous-revision {
    3603         margin-top: -.4em; /* Same line as the slider (height: .8em) */
    3604 }
    3605 
    3606 #diff-next-revision {
    3607         float: right;
    3608 }
    3609 
    3610 #diff-previous-revision {
    3611         float: left;
    3612 }
    3613 
    3614 #diff-slider {
    3615         width: 70%;
    3616         margin: 0 auto;
    3617 }
    3618 
    36193633.comparetwo #diff-slider {
    36203634        width: 95%;
    36213635}
  • wp-admin/includes/ajax-actions.php

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

    new file mode 100644
    ---wp-admin/includes/revision.php	(revision 0)n+++wp-admin/includes/revision.php	(revision 0)
    @@ -0,0 +1,92 @@
    +<?php
    +
    +function wp_get_revision_ui_diff( $post, $compare_from, $compare_to ) {
    +	if ( ! $post = get_post( $post ) )
    +		return false;
    +
    +	if ( $compare_from ) {
    +		if ( ! $compare_from = get_post( $compare_from ) )
    +			return false;
    +	} else {
    +		// If we're dealing with the first revision...
    +		$compare_from = false;
    +	}
    +
    +	if ( ! $compare_to = get_post( $compare_to ) )
    +		return false;
    +
    +	// If comparing revisions, make sure we're dealing with the right post parent.
    +	if ( $compare_from && $compare_from->post_parent !== $post->ID )
    +		return false;
    +	if ( $compare_to->post_parent !== $post->ID )
    +		return false;
    +
    +	if ( $compare_from && strtotime( $compare_from->post_date_gmt ) > strtotime( $compare_to->post_date_gmt ) ) {
    +		$temp = $compare_from;
    +		$compare_from = $compare_to;
    +		$compare_to = $temp;
    +	}
    +
    +	$return = array();
    +
    +	foreach ( _wp_post_revision_fields() as $field => $name ) {
    +		$content_from = $compare_from ? apply_filters( "_wp_post_revision_field_$field", $compare_from->$field, $field, $compare_from, 'left' ) : '';
    +		$content_to = apply_filters( "_wp_post_revision_field_$field", $compare_to->$field, $field, $compare_to, 'right' );
    +
    +		$diff = wp_text_diff( $content_from, $content_to, array( 'show_split_view' => true ) );
    +
    +		if ( ! $diff && 'post_title' === $field ) {
    +			// It's a better user experience to still show the Title, even if it didn't change.
    +			// No, you didn't see this.
    +			$diff = "<table class='diff'><col class='ltype' /><col class='content' /><col class='ltype' /><col class='content' /><tbody><tr>";
    +			$diff .= '<td>' . esc_html( $compare_from->post_title ) . '</td><td></td><td>' . esc_html( $compare_to->post_title ) . '</td>';
    +			$diff .= '</tr></tbody>';
    +			$diff .= '</table>';
    +		}
    +
    +		if ( $diff ) {
    +			$return[] = array(
    +				'id' => $field,
    +				'name' => $name,
    +				'diff' => $diff,
    +			);
    +		}
    +	}
    +	return $return;
    +}
    +
    +function wp_prepare_revisions_for_js( $post ) {
    +	$post = get_post( $post );
    +	$revisions = array();
    +	$current = current_time( 'timestamp' );
    +
    +	$revisions = wp_get_post_revisions( $post->ID );
    +
    +	cache_users( wp_list_pluck( $revisions, 'post_author' ) );
    +
    +	foreach ( $revisions as $revision ) {
    +		$modified_gmt = strtotime( $revision->post_modified_gmt );
    +		$revisions[ $revision->ID ] = array(
    +			'id'           => $revision->ID,
    +			'title'        => get_the_title( $post->ID ),
    +			'author' => array(
    +				'id'     => (int) $revision->post_author,
    +				'avatar' => get_avatar( $revision->post_author, 24 ),
    +				'name'   => get_the_author_meta( 'display_name', $revision->post_author ),
    +			),
    +			'date'         => date_i18n( __( 'M j, Y @ G:i' ), $modified_gmt ),
    +			'dateShort'    => date_i18n( _x( 'j M @ G:i', 'revision date short format' ), $modified_gmt ),
    +			'timeAgo'      => human_time_diff( $modified_gmt, $current ),
    +			'autosave'     => wp_is_post_autosave( $revision ),
    +			'current'      => $revision->post_modified_gmt === $post->post_modified_gmt,
    +			'restoreNonce' => wp_create_nonce( 'restore-post_' . $revision->ID ),
    +		);
    +	}
    +
    +	return array(
    +		'postId'       => $post->ID,
    +		'nonce'        => wp_create_nonce( 'revisions-ajax-nonce' ),
    +		'restoreUrl'   => admin_url( 'revision.php?action=restore' ), // &revision=$ID&_wpnonce=$restoreNonce
    +		'revisionData' => array_values( $revisions ),
    +	);
    +}
     
    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         };
    9 
    10         _.extend( revisions, { model: {}, view: {}, controller: {} } );
     6        revisions = wp.revisions = { model: {}, view: {}, controller: {} };
    117
    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
    41 
    42                 constructor: function() {
    43                         var self    = this;
    44                         this.slider = new revisions.view.Slider();
    45 
    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                         }
    57                 },
     26        revisions.model.Revision = Backbone.Model.extend({});
    5827
    59                 loadDiffs: function( models ) {
    60                         var self = this,
    61                                 revisionsToLoad = models.where( { completed: false } ),
    62                                 delay = 0,
    63                                 totalChanges;
     28        revisions.model.Revisions = Backbone.Collection.extend({
     29                model: revisions.model.Revision
     30        });
    6431
    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                         });
     32        revisions.model.Field = Backbone.Model.extend({});
    7033
    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 );
    79 
    80                                                                 // stop spinner when all models are loaded
    81                                                                 if ( 0 === models.where( { completed: false } ).length )
    82                                                                         self.stopModelLoadingSpinner();
    83 
    84                                                                 totalChanges = model.get( 'linesAdded' ) + model.get( 'linesDeleted' ),
    85                                                                         scopeOfChanges = 'vsmall';
    86 
    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
    109                                 }
    110                         );
    111                 },
     34        revisions.model.Fields = Backbone.Collection.extend({
     35                model: revisions.model.Field
     36        });
    11237
    113                 startLeftModelLoading: function() {
    114                         this.leftModelLoading = true;
    115                         $('#revision-diff-container').addClass('left-model-loading');
    116                 },
     38        revisions.model.Diff = Backbone.Model.extend({
     39                initialize: function(attributes, options) {
     40                        var fields = this.get('fields');
     41                        this.unset('fields');
    11742
    118                 stopLeftModelLoading: function() {
    119                         this.leftModelLoading = false;
    120                 },
     43                        this.fields = new revisions.model.Fields( fields );
     44                }
     45        });
    12146
    122                 startRightModelLoading: function() {
    123                         this.rightModelLoading = true;
    124                         $('#revision-diff-container').addClass('right-model-loading');
    125                 },
     47        revisions.model.Diffs = Backbone.Collection.extend({
     48                model: revisions.model.Diff,
    12649
    127                 stopRightModelLoading: function() {
    128                         this.rightModelLoading = false;
     50                load: function( comparisons ) {
     51                        return this.fetch({ data: { compare: comparisons } });
    12952                },
    13053
    131                 stopModelLoadingSpinner: function() {
    132                         $('#revision-diff-container').removeClass('right-model-loading');
    133                         $('#revision-diff-container').removeClass('left-model-loading');
    134                 },
     54                sync: function( method, model, options ) {
     55                        // Overload the `read` request so Attachment.fetch() functions correctly.
     56                        if ( 'read' === method ) {
     57                                options = options || {};
     58                                options.context = this;
     59                                options.data = _.extend( options.data || {}, {
     60                                        action: 'get-revision-diffs',
     61                                        post_id: revisions.settings.postId
     62                                });
     63                                return wp.xhr.send( options );
    13564
    136                 reloadModel: function() {
    137                         if ( this.singleRevision ) {
    138                                 this.reloadModelSingle();
     65                        // Otherwise, fall back to `Backbone.sync()`.
    13966                        } else {
    140                                 this.reloadLeftRight();
     67                                return Backbone.Model.prototype.sync.apply( this, arguments );
    14168                        }
    142                 },
    143 
    144                 // load the models for the single handle mode
    145                 reloadModelSingle: function() {
    146                         var self = this;
    147 
    148                         self.startRightModelLoading();
    149 
    150                         self.revisions.reload({
    151                                 options: {
    152                                 'showAutosaves': self.autosaves,
    153                                 'showSplitView': self.showSplitView
    154                                 },
    155 
    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                         });
    173                 },
    174 
    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                         });
    203                 },
    204 
    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                         });
    215 
    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                                 },
    225 
    226                                 error: function( response ) {
    227                                         self.stopRightModelLoading();
    228                                 }
    229                         });
    230 
    231                 },
    232 
    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                 },
    242 
    243                 disabledButtonCheck: function( val ) {
    244                         var maxVal = this.revisions.length - 1,
    245                                 next = ! isRtl ? $( '#next' ) : $( '#previous' ),
    246                                 prev = ! isRtl ? $( '#previous' ) : $( '#next' );
    247 
    248                         // Disable "Next" button if you're on the last node
    249                         if ( maxVal === val )
    250                                 next.prop( 'disabled', true );
    251                         else
    252                                 next.prop( 'disabled', false );
    253 
    254                         // Disable "Previous" button if you're on the 0 node
    255                         if ( 0 === val )
    256                                 prev.prop( 'disabled', true );
    257                         else
    258                                 prev.prop( 'disabled', false );
    259                 },
    260 
    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
    28169                }
    28270        });
    28371
    window.wp = window.wp || {}; 
    28876         * ========================================================================
    28977         */
    29078
    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,
    299 
    300                 initialize: function( options ) {
    301                         this.options = _.defaults( options || {}, {
    302                                 value: 0,
    303                                 min: 0,
    304                                 max: 1,
    305                                 step: 1
    306                         });
    307                 },
    308 
    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;
    322 
    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;
    327 
    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;
    333 
    334                                         Diff.rightDiff = isRtl ? ui.values[0] : ui.values[1]; // handles are reversed in RTL mode
    335                                 }
     79        // The frame view. This contains the entire page.
     80        revisions.view.Frame = wp.Backbone.View.extend({
     81                tagName: 'div',
     82                className: 'revisions',
     83                template: wp.template('revisions-frame'),
    33684
    337                                 Diff.revisionView.render();
    338                         }
    339                 },
    340 
    341                 /**
    342                  * responds to slider start sliding events
    343                  * in two handle mode stores start position, so if unchanged at stop event no need to reload diffs
    344                  * also swaps in the appropriate models - left handled or right handled
    345                  */
    346                 start: function( event, ui ) {
    347                         // Not needed in one mode
    348                         if ( this.singleRevision )
    349                                 return;
    350 
    351                         if ( $( ui.handle ).hasClass( 'left-handle' ) ) {
    352                                 // Left handler
    353                                 if ( Diff.leftModelLoading ) // left model still loading, prevent sliding left handle
    354                                         return false;
    355 
    356                                 Diff.revisionView.draggingLeft = true;
    357 
    358                                 if ( Diff.revisionView.model !== Diff.leftHandleRevisions &&
    359                                                 null !== Diff.leftHandleRevisions ) {
    360                                         Diff.revisionView.model = Diff.leftHandleRevisions; // use the left handle models
    361                                         Diff.tickmarkView.model = Diff.leftHandleRevisions;
    362                                         Diff.tickmarkView.render();
    363                                 }
    364 
    365                                 Diff.leftDiffStart = isRtl ? ui.values[1] : ui.values[0]; // in RTL mode the 'left handle' is the second in the slider, 'right' is first
    366 
    367                         } else {
    368                                 // Right handler
    369                                 if ( Diff.rightModelLoading || 0 === Diff.rightHandleRevisions.length) // right model still loading, prevent sliding right handle
    370                                         return false;
    371 
    372                                 if ( Diff.revisionView.model !== Diff.rightHandleRevisions &&
    373                                                 null !== Diff.rightHandleRevisions ) {
    374                                         Diff.revisionView.model = Diff.rightHandleRevisions; // use the right handle models
    375                                         Diff.tickmarkView.model = Diff.rightHandleRevisions;
    376                                         Diff.tickmarkView.render();
    377                                 }
    378 
    379                                 Diff.revisionView.draggingLeft = false;
    380                                 Diff.rightDiffStart = isRtl ? ui.values[0] : ui.values[1]; // in RTL mode the 'left handle' is the second in the slider, 'right' is first
    381                         }
    382                 },
    383 
    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;
    393 
    394                         // calculate and generate a diff for comparing to the left handle
    395                         // and the right handle, swap out when dragging
    396                         if ( $( ui.handle ).hasClass( 'left-handle' ) ) {
    397                                 // Left handler
    398                                 if ( Diff.leftDiffStart !== isRtl ? ui.values[1] : ui.values[0] ) // in RTL mode the 'left handle' is the second in the slider, 'right' is first
    399                                         Diff.reloadRight();
    400                         } else {
    401                                 // Right handler
    402                                 if ( Diff.rightDiffStart !== isRtl ? ui.values[0] : ui.values[1] ) // in RTL mode the 'left handle' is the second in the slider, 'right' is first
    403                                         Diff.reloadLeft();
    404                         }
    405                 },
    406 
    407                 addTooltip: function( handle, message ) {
    408                         handle.find( '.ui-slider-tooltip' ).html( message );
    409                 },
    410 
    411                 width: function() {
    412                         return $( '#diff-slider' ).width();
    413                 },
    414 
    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 );
     85                initialize: function() {
     86                        this.views.set( '.revisions-control-frame', new revisions.view.Controls() );
     87                        this.views.set( '.revisions-diff-frame', new revisions.view.Diff() );
    43188                },
    43289
    43390                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 );
    444                 }
    445         });
     91                        wp.Backbone.View.prototype.render.apply( this, arguments );
    44692
    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,
    456 
    457                 resetTicks: function() {
    458                         var sliderMax, sliderWidth, adjustMax, tickWidth, tickCount = 0, aTickWidth, tickMargin, self = this, firstTick, lastTick;
    459                         sliderMax   = Diff.slider.option( 'max' );
    460                         sliderWidth = Diff.slider.width();
    461                         adjustMax   = Diff.singleRevision ? 0 : 1;
    462                         tickWidth   = Math.floor( sliderWidth / ( sliderMax - adjustMax ) );
    463                         tickWidth   = ( tickWidth > 50 ) ? 50 : tickWidth; // set minimum and maximum widths for tick marks
    464                         tickWidth   = ( tickWidth < 6 ) ? 6 : tickWidth;
    465                         sliderWidth = tickWidth * ( sliderMax - adjustMax ); // calculate the slider width
    466                         aTickWidth  = $( '.revision-tick' ).width();
    467 
    468                         if ( tickWidth !== aTickWidth ) { // is the width already set correctly?
    469                                 $( '.revision-tick' ).each( function() {
    470                                         tickMargin = Math.floor( ( tickWidth - $( this ).width() ) / 2 ) + 1;
    471                                         $( this ).css( 'border-left', tickMargin + 'px solid #f7f7f7'); // space the ticks out using margins
    472                                         $( this ).css( 'border-right', ( tickWidth - tickMargin - $( this ).width() ) + 'px solid #f7f7f7'); // space the ticks out using margins
    473                                 });
    474                                 firstTick = $( '.revision-tick' ).first(); //cache selectors for optimization
    475                                 lastTick = $( '.revision-tick' ).last();
    476 
    477                                 sliderWidth = sliderWidth + Math.ceil( ( tickWidth - ( lastTick.outerWidth() - lastTick.innerWidth() ) ) / 2 ); // room for the last tick
    478                                 sliderWidth = sliderWidth + Math.ceil( ( tickWidth - ( firstTick.outerWidth() - firstTick.innerWidth() ) ) / 2 ); // room for the first tick
    479                                 firstTick.css( 'border-left', 'none' ); // first tick gets no left border
    480                                 lastTick.css( 'border-right', 'none' ); // last tick gets no right border
    481                         }
    482 
    483                         /**
    484                          * reset the slider width
    485                          */
    486                         Diff.slider.setWidth( sliderWidth );
    487                         $( '.diff-slider-ticks-wrapper' ).width( sliderWidth );
    488                         $( '#diff-slider-ticks' ).width( sliderWidth );
    489 
    490                         /**
    491                          * go through all ticks, add hover and click interactions
    492                          */
    493                         $( '.revision-tick' ).each( function() {
    494                                 Diff.slider.addTooltip ( $( this ), Diff.revisions.at( tickCount++ ).get( 'titleTooltip' ) );
    495                                 $( this ).hover(
    496                                         function() {
    497                                                 $( this ).find( '.ui-slider-tooltip' ).show().append('<div class="arrow"></div>');
    498                                         },
    499                                         function() {
    500                                                 $( this ).find( '.ui-slider-tooltip' ).hide().find( '.arrow' ).remove();
    501                                         }
    502                                 );
    503 
    504                                 /**
    505                                  * move the slider handle when the tick marks are clicked
    506                                  */
    507                                 $( this ).on( 'click',
    508                                         { tickCount: tickCount }, // pass the tick through so we know where to move the handle
    509                                         function( event ) {
    510                                                 if ( Diff.slider.singleRevision ) { // single handle mode
    511                                                         Diff.rightDiff = event.data.tickCount; // reposition the right handle
    512                                                         Diff.slider.refresh({
    513                                                                 value: Diff.rightDiff - 1
    514                                                         } );
    515                                                 } else { //compare two mode
    516                                                         if ( isRtl ) {
    517                                                                 if ( event.data.tickCount < Diff.leftDiff ) { // click was on the 'left' side
    518                                                                                 Diff.rightDiff = event.data.tickCount; // set the 'right' handle location
    519                                                                                 Diff.reloadLeft(); // reload the left handle comparison models
    520                                                                 } else { // middle or 'right' clicks
    521                                                                         Diff.leftDiff = event.data.tickCount; // set the 'left' handle location
    522                                                                         Diff.reloadRight(); // reload right handle models
    523                                                                 }
    524                                                         } else {
    525                                                                 if ( event.data.tickCount < Diff.leftDiff ) { // click was on the 'left' side
    526                                                                                 Diff.leftDiff = event.data.tickCount; // set the left handle location
    527                                                                                 Diff.reloadRight(); // reload the right handle comparison models
    528                                                                 } else { // middle or 'right' clicks
    529                                                                         Diff.rightDiff = event.data.tickCount; // set the right handle location
    530                                                                         Diff.reloadLeft(); // reload left handle models
    531                                                                 }
    532                                                         }
    533                                                         Diff.slider.refresh( { // set the slider handle positions
    534                                                                 values: [ isRtl ? Diff.rightDiff : Diff.leftDiff, isRtl ? Diff.leftDiff : Diff.rightDiff ]
    535                                                         } );
    536                                                 }
    537                                                 Diff.revisionView.render(); // render the main view
    538                                         } );
    539                         } );
    540                 },
    541 
    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
    569                 events: {
    570                         'click #next':     ! isRtl ? 'nextRevision' : 'previousRevision',
    571                         'click #previous': ! isRtl ? 'previousRevision' : 'nextRevision'
    572                 },
    573 
    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                         }
     93                        $('#wpbody-content .wrap').append( this.el );
     94                        this.views.ready();
    60795
    60896                        return this;
    609                 },
    610 
    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 ;
    615 
    616                         Diff.revisionView.render();
    617 
    618                         Diff.slider.refresh({
    619                                 value: Diff.rightDiff - 1
    620                         }, true );
    621                 },
    622 
    623                 // go to the previous revision
    624                 previousRevision: function() {
    625                         if ( Diff.rightDiff > 1 ) // unless at left boundry
    626                                 Diff.rightDiff = Diff.rightDiff - 1 ;
    627 
    628                         Diff.revisionView.render();
    629 
    630                         Diff.slider.refresh({
    631                                 value: Diff.rightDiff - 1
    632                         }, true );
    63397                }
    63498        });
    63599
    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,
    645 
    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                 },
     100        // The control view.
     101        // This contains the revision slider, previous/next buttons, and the compare checkbox.
     102        revisions.view.Controls = wp.Backbone.View.extend({
     103                tagName: 'div',
     104                className: 'revisions-controls',
     105                template: wp.template('revisions-controls'),
    651106
    652                 // render the revisions
    653                 render: function() {
    654                         var addHtml = '', thediff;
    655 
    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 );
    675 
    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                         }
    680 
    681                         this.toggleCompareTwoCheckbox();
    682 
    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' ) );
     107                initialize: function() {
     108                        this.views.set( new revisions.view.Slider() );
     109                }
     110        });
    685111
    686                         return this;
     112        // The slider view.
     113        // Encapsulates all of the configuration for the jQuery UI slider into a view.
     114        revisions.view.Slider = wp.Backbone.View.extend({
     115                tagName: 'div',
     116                className: 'wp-slider',
     117
     118                initialize: function() {
     119                        // Create the slider model.
     120                        if ( ! this.model )
     121                                this.model = new revisions.model.Slider();
     122
     123                        // Attach the start, slide, and stop methods to the model.
     124                        _.bindAll( this, 'start', 'slide', 'stop' );
     125                        this.model.set({
     126                                start: this.start,
     127                                slide: this.slide,
     128                                stop: this.stop
     129                        });
    687130                },
    688131
    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 );
     132                ready: function() {
     133                        this.$el.slider( this.model.toJSON() );
     134                        this.model.on( 'change', function( model, options ) {
     135                                // Apply changes to slider here.
     136                        }, this );
    695137                },
    696138
    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 ;
    701 
    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;
    708                                 } else {
    709                                         if ( 1 === Diff.rightDiff ) // make sure right handle not in leftmost slot
    710                                                 Diff.rightDiff = 2;
    711                                 }
     139                start: function() {
    712140
    713                                 Diff.revisionView.draggingLeft = false;
    714 
    715                                 revisions.model.settings.revision_id = ''; // reset passed revision id so switching back to one handle mode doesn't re-select revision
    716                                 Diff.reloadLeftRight(); // load diffs for left and right handles
    717                                 Diff.revisionView.model = Diff.rightHandleRevisions;
    718 
    719                         } else { // compare one mode
    720                                 Diff.singleRevision = true;
    721                                 Diff.revisionView.draggingLeft = false;
    722                                 Diff.reloadModelSingle();
    723                         }
    724                         Diff.revisionsInteractions.render();
    725                         Diff.tickmarkView.render();
    726141                },
    727142
    728                 restore: function() {
    729                         document.location = $( '#restore-revision' ).data( 'restoreLink' );
    730                 }
    731         });
    732 
    733 
    734         /**
    735          * ========================================================================
    736          * MODELS
    737          * ========================================================================
    738          */
    739 
    740         /**
    741          * wp.revisions.Revision
    742          */
    743         Revision = revisions.model.Revision = Backbone.Model.extend({
    744                 idAttribute: 'ID',
     143                slide: function() {
    745144
    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
    759145                },
    760146
    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                         }
     147                stop: function() {
    774148
    775149                }
    776150        });
    777151
    778         /**
    779          * wp.revisions.Revisions
    780          */
    781         Revisions = revisions.Revisions = Backbone.Collection.extend({
    782                 model: Revision,
    783 
    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         } );
     152        // The diff view.
     153        // This is the view for the current active diff.
     154        revisions.view.Diff = wp.Backbone.View.extend({
     155                tagName: 'div',
     156                className: 'revisions-diff',
     157                template: wp.template('revisions-diff')
     158        });
    818159
    819         $( wp.revisions );
     160        // Initialize the revisions UI.
     161        revisions.init = function() {
     162                revisions.model.revisions = new revisions.model.Revisions( revisions.settings.revisionData );
     163                revisions.model.diffs = new revisions.model.Diffs();
     164                new revisions.view.Frame().render();
     165        };
    820166
     167        $( revisions.init );
    821168}(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 );
    else 
    7783        $parent_file = $submenu_file = 'edit.php';
    7884
    7985wp_enqueue_script( 'revisions' );
    80 
    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 );
     86wp_localize_script( 'revisions', '_wpRevisionsSettings', wp_prepare_revisions_for_js( $post ) );
    8987
    9088/* Revisions Help Tab */
    9189
    require_once( './admin-header.php' ); 
    114112
    115113<div class="wrap">
    116114        <?php screen_icon(); ?>
    117         <div id="revision-diff-container" class="current-version right-model-loading">
    118                 <h2 class="long-header"><?php echo $h2; ?></h2>
     115        <h2 class="long-header"><?php echo $h2; ?></h2>
     116</div>
    119117
    120                 <div id="loading-status" class="updated message">
    121                         <p><span class="spinner" ></span></p>
    122                 </div>
     118<script id="tmpl-revisions-frame" type="text/html">
     119        <span class="spinner"></span>
     120        <div class="revisions-control-frame"></div>
     121        <div class="revisions-diff-frame"></div>
     122</script>
    123123
    124                 <div class="diff-slider-ticks-wrapper">
    125                         <div id="diff-slider-ticks"></div>
    126                 </div>
     124<script id="tmpl-revisions-controls" type="text/html">
     125        <div class="revision-toggle-compare-mode">
     126                <label>
     127                        <input type="checkbox" class="compare-two-revisions" />
     128                        <?php esc_attr_e( 'Compare two revisions' ); ?>
     129                </label>
     130        </div>
    127131
    128                 <div id="revision-interact"></div>
     132        <div class="revisions-previous">
     133                <input class="button" type="button" id="previous" value="<?php echo esc_attr_x( 'Previous', 'Button label for a previous revision' ); ?>" />
     134        </div>
    129135
    130                 <div id="revisions-diff"></div>
     136        <div class="revisions-next">
     137                <input class="button" type="button" id="next" value="<?php echo esc_attr_x( 'Next', 'Button label for a next revision' ); ?>" />
    131138        </div>
    132 </div>
     139
     140        <div class="revisions-slider"></div>
     141</script>
    133142
    134143<script id="tmpl-revisions-diff" type="text/html">
     144        DIFF
     145</script>
     146
     147<script id="tmpl-revisions-diff-old" type="text/html">
    135148        <div id="toggle-revision-compare-mode">
    136149                <label>
    137150                        <input type="checkbox" id="compare-two-revisions" />
    require_once( './admin-header.php' ); 
    157170                </div>
    158171        </div>
    159172
    160         </div>
    161 
    162173        <div id="diff-table">{{{ data.diff }}}</div>
    163174</script>
    164175
  • wp-includes/revision.php

    function _wp_upgrade_revisions_of_post( $post, $revisions ) { 
    681681
    682682        return true;
    683683}
    684 
    685 /**
    686  * Displays a human readable HTML representation of the difference between two strings.
    687  * similar to wp_text_diff, but tracks and returns could of lines added and removed
    688  *
    689  * @since 3.6.0
    690  *
    691  * @see wp_parse_args() Used to change defaults to user defined settings.
    692  * @uses Text_Diff
    693  * @uses WP_Text_Diff_Renderer_Table
    694  *
    695  * @param string $left_string "old" (left) version of string
    696  * @param string $right_string "new" (right) version of string
    697  * @param string|array $args Optional. Change 'title', 'title_left', and 'title_right' defaults.
    698  * @return array contains html, linesadded & linesdeletd, empty string if strings are equivalent.
    699  */
    700 function wp_text_diff_with_count( $left_string, $right_string, $args = null ) {
    701         $defaults = array( 'title' => '', 'title_left' => '', 'title_right' => '' );
    702         $args = wp_parse_args( $args, $defaults );
    703 
    704         if ( ! class_exists( 'WP_Text_Diff_Renderer_Table' ) )
    705                         require( ABSPATH . WPINC . '/wp-diff.php' );
    706 
    707         $left_string  = normalize_whitespace( $left_string );
    708         $right_string = normalize_whitespace( $right_string );
    709 
    710         $left_lines  = explode( "\n", $left_string );
    711         $right_lines = explode( "\n", $right_string) ;
    712 
    713         $text_diff = new Text_Diff($left_lines, $right_lines  );
    714         $lines_added = $text_diff->countAddedLines();
    715         $lines_deleted = $text_diff->countDeletedLines();
    716 
    717         $renderer  = new WP_Text_Diff_Renderer_Table();
    718         $diff = $renderer->render( $text_diff );
    719 
    720         if ( !$diff )
    721                         return '';
    722 
    723                 $r  = "<table class='diff'>\n";
    724 
    725         if ( ! empty( $args[ 'show_split_view' ] ) ) {
    726                 $r .= "<col class='content diffsplit left' /><col class='content diffsplit middle' /><col class='content diffsplit right' />";
    727         } else {
    728                 $r .= "<col class='content' />";
    729         }
    730 
    731         if ( $args['title'] || $args['title_left'] || $args['title_right'] )
    732                 $r .= "<thead>";
    733         if ( $args['title'] )
    734                 $r .= "<tr class='diff-title'><th colspan='4'>$args[title]</th></tr>\n";
    735         if ( $args['title_left'] || $args['title_right'] ) {
    736                 $r .= "<tr class='diff-sub-title'>\n";
    737                 $r .= "\t<td></td><th>$args[title_left]</th>\n";
    738                 $r .= "\t<td></td><th>$args[title_right]</th>\n";
    739                 $r .= "</tr>\n";
    740         }
    741         if ( $args['title'] || $args['title_left'] || $args['title_right'] )
    742                 $r .= "</thead>\n";
    743 
    744         $r .= "<tbody>\n$diff\n</tbody>\n";
    745         $r .= "</table>";
    746 
    747         return array( 'html' => $r, 'lines_added' => $lines_added, 'lines_deleted' => $lines_deleted );
    748 }