Ticket #24425: 24425.draft.3.diff
File 24425.draft.3.diff, 52.3 KB (added by , 12 years ago) |
---|
-
wp-admin/admin-ajax.php
do_action( 'admin_init' ); 42 42 43 43 $core_actions_get = array( 44 44 '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', 46 46 ); 47 47 48 48 $core_actions_post = array( … … $core_actions_post = array( 56 56 'save-widget', 'set-post-thumbnail', 'date_format', 'time_format', 'wp-fullscreen-save-post', 57 57 'wp-remove-post-lock', 'dismiss-wp-pointer', 'upload-attachment', 'get-attachment', 58 58 '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', 60 60 ); 61 61 62 62 // Register core Ajax calls. -
wp-admin/css/wp-admin.css
td.plugin-title p { 3481 3481 /*------------------------------------------------------------------------------ 3482 3482 11.2 - Post Revisions 3483 3483 ------------------------------------------------------------------------------*/ 3484 .revisions .spinner { 3485 float: none; 3486 margin: 100px auto; 3487 } 3488 3489 .revisions.loading .spinner { 3490 display: block; 3491 } 3492 3493 .revisions-control-frame, 3494 .revisions-diff-frame { 3495 position: relative; 3496 } 3497 3498 .revisions-controls { 3499 height: 20px; 3500 padding: 40px 0 20px; 3501 border-bottom: 1px solid #dfdfdf; 3502 margin-bottom: 10px; 3503 } 3504 3505 .revision-toggle-compare-mode { 3506 position: absolute; 3507 top: 0; 3508 right: 0; 3509 } 3510 3511 .revisions-previous { 3512 float: left; 3513 } 3514 3515 .revisions-next { 3516 float: right; 3517 } 3518 3519 .revisions-slider { 3520 width: 70%; 3521 margin: 6px auto 0; 3522 } 3484 3523 3485 3524 /* Revision meta box */ 3486 3525 .post-revisions li img, … … table.diff .diff-addedline ins { 3527 3566 position: relative; 3528 3567 } 3529 3568 3530 #toggle-revision-compare-mode {3531 position: absolute;3532 top: 0;3533 right: 0;3534 padding: 9px 9px 0 0;3535 }3536 3537 3569 #loading-status { 3538 3570 display: none; 3539 3571 position: absolute; … … table.diff .diff-addedline ins { 3551 3583 padding: 20px 0; 3552 3584 } 3553 3585 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 3572 3586 .comparetwo #diff-slider { 3573 3587 width: 95%; 3574 3588 } -
wp-admin/includes/ajax-actions.php
function wp_ajax_heartbeat() { 2082 2082 wp_send_json($response); 2083 2083 } 2084 2084 2085 function wp_ajax_revisions_data() { 2086 check_ajax_referer( 'revisions-ajax-nonce', 'nonce' ); 2087 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; 2096 2097 $all_the_revisions = array(); 2098 if ( ! $post_id ) 2099 $post_id = $compare_to; 2100 2101 if ( ! current_user_can( 'read_post', $post_id ) ) 2102 continue; 2103 2104 if ( ! $revisions = wp_get_post_revisions( $post_id ) ) 2105 return; 2106 2107 $left_revision = get_post( $compare_to ); 2108 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 ); 2113 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 ); 2085 function wp_ajax_get_revision_diffs() { 2086 require ABSPATH . 'wp-admin/includes/revision.php'; 2140 2087 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 2158 ); 2088 // check_ajax_referer( 'revisions-ajax-nonce', 'nonce' ); 2159 2089 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; 2090 if ( ! $post = get_post( (int) $_REQUEST['post_id'] ) ) 2091 wp_send_json_error(); 2239 2092 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 ); 2093 if ( ! current_user_can( 'read_post', $post->ID ) ) 2094 wp_send_json_error(); 2244 2095 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 } 2096 // Really just pre-loading the cache here. 2097 if ( ! $revisions = wp_get_post_revisions( $post->ID ) ) 2098 wp_send_json_error(); 2251 2099 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 ) ); 2100 $return = array(); 2101 @set_time_limit( count( $_REQUEST['compare'] ) ); 2255 2102 2256 $revision_date_author_short = sprintf( 2257 '%s <strong>%s</strong><br />%s', 2258 $gravatar, 2259 $author, 2260 $date_short 2261 ); 2103 foreach ( $_REQUEST['compare'] as $compare_key ) { 2104 list( $compare_from, $compare_to ) = explode( ':', $compare_key ); // from:to 2262 2105 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}" 2106 $return[] = array( 2107 'id' => $compare_key, 2108 'fields' => wp_get_revision_ui_diff( $post, $compare_from, $compare_to ), 2270 2109 ); 2110 } 2271 2111 2272 // if this is a left handled calculation swap data 2273 if ( 0 != $right_handle_at ) { 2274 $tmp = $revision_from_date_author; 2275 $revision_from_date_author = $revision_date_author; 2276 $revision_date_author = $tmp; 2277 } 2278 2279 if ( ( $compare_two_mode || -1 !== $previous_revision_id ) ) { 2280 $all_the_revisions[] = array ( 2281 'ID' => $revision->ID, 2282 'titleTo' => $revision_date_author, 2283 'titleFrom' => $revision_from_date_author, 2284 'titleTooltip' => $revision_date_author_short, 2285 'restoreLink' => urldecode( $restore_link ), 2286 'previousID' => $previous_revision_id, 2287 'isCurrent' => $is_current_revision, 2288 ); 2289 } 2290 $previous_revision_id = $revision->ID; 2291 2292 endforeach; 2293 2294 // in RTL + single handle mode, reverse the revision direction 2295 if ( is_rtl() && $compare_two_mode ) 2296 $all_the_revisions = array_reverse( $all_the_revisions ); 2297 2298 echo json_encode( $all_the_revisions ); 2299 exit(); 2112 wp_send_json_success( $return ); 2300 2113 } -
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_reverse( array_values( $revisions ) ), + ); +}
1 1 window.wp = window.wp || {}; 2 2 3 3 (function($) { 4 var Revision, Revisions, Diff,revisions;4 var revisions; 5 5 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: {} }; 11 7 12 8 // Link settings. 13 revisions. model.settings = typeof wpRevisionsSettings === 'undefined' ? {} :wpRevisionsSettings;9 revisions.settings = typeof _wpRevisionsSettings === 'undefined' ? {} : _wpRevisionsSettings; 14 10 15 11 16 12 /** 17 13 * ======================================================================== 18 * CONTROLLERS14 * MODELS 19 15 * ======================================================================== 20 16 */ 17 revisions.model.Slider = Backbone.Model.extend({ 18 defaults: { 19 value: 0, 20 min: 0, 21 max: 1, 22 step: 1 23 } 24 }); 21 25 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({}); 58 27 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 }); 64 31 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({}); 70 33 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 }); 112 37 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'); 117 42 118 stopLeftModelLoading: function() {119 this.leftModelLoading = false;120 },43 this.fields = new revisions.model.Fields( fields ); 44 } 45 }); 121 46 122 startRightModelLoading: function(){123 this.rightModelLoading = true;124 $('#revision-diff-container').addClass('right-model-loading');47 revisions.model.Diffs = Backbone.Collection.extend({ 48 initialize: function() { 49 this.requests = {}; 125 50 }, 126 51 127 stopRightModelLoading: function() { 128 this.rightModelLoading = false; 129 }, 52 model: revisions.model.Diff, 130 53 131 stopModelLoadingSpinner: function() {132 $('#revision-diff-container').removeClass('right-model-loading');133 $('#revision-diff-container').removeClass('left-model-loading');134 },54 ensure: function( id, context ) { 55 var diff = this.get( id ); 56 var request = this.requests[ id ]; 57 var deferred = $.Deferred(); 135 58 136 reloadModel: function() { 137 if ( this.singleRevision ) { 138 this.reloadModelSingle(); 59 if ( diff ) { 60 deferred.resolveWith( context, [ diff ] ); 139 61 } else { 140 this.reloadLeftRight(); 62 if ( ! request ) 63 request = this.load([ id ]); 64 65 request.done( _.bind( function() { 66 deferred.resolveWith( context, [ this.get( id ) ] ); 67 }, this ) ); 141 68 } 69 70 return deferred.promise(); 142 71 }, 143 72 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 }); 73 load: function( comparisons ) { 74 return this.fetch({ data: { compare: comparisons } }); 173 75 }, 174 76 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 }); 77 sync: function( method, model, options ) { 78 // Overload the `read` request so Attachment.fetch() functions correctly. 79 if ( 'read' === method ) { 80 options = options || {}; 81 options.context = this; 82 options.data = _.extend( options.data || {}, { 83 action: 'get-revision-diffs', 84 post_id: revisions.settings.postId 85 }); 185 86 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 }, 87 var deferred = wp.xhr.send( options ); 88 var requests = this.requests; 198 89 199 error: function() { 200 self.stopLeftModelLoading(); 90 // Record that we're requesting each diff. 91 if ( options.data.compare ) { 92 _.each( options.data.compare, function( id ) { 93 requests[ id ] = deferred; 94 }); 201 95 } 202 });203 },204 96 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 }); 97 // When the request completes, clear the stored request. 98 deferred.always( function() { 99 if ( options.data.compare ) { 100 _.each( options.data.compare, function( id ) { 101 delete requests[ id ]; 102 }); 103 } 104 }); 215 105 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 }); 106 return deferred; 230 107 231 }, 108 // Otherwise, fall back to `Backbone.sync()`. 109 } else { 110 return Backbone.Model.prototype.sync.apply( this, arguments ); 111 } 112 } 113 }); 232 114 233 /**234 * reloadLeftRight reload models for both the left and right handles235 */236 reloadLeftRight: function() {237 this.startRightModelLoading();238 this.startLeftModelLoading();239 this.reloadLeft();240 this.reloadRight();241 },242 115 243 disabledButtonCheck: function( val ){244 var maxVal = this.revisions.length - 1,245 next = ! isRtl ? $( '#next' ) : $( '#previous' ),246 prev = ! isRtl ? $( '#previous' ) : $( '#next');116 revisions.model.FrameState = Backbone.Model.extend({ 117 initialize: function( attributes, options ) { 118 this.revisions = options.revisions; 119 this.diffs = new revisions.model.Diffs(); 247 120 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 ); 121 this.listenTo( this, 'change:from change:to', this.updateDiffId ); 259 122 }, 260 123 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 124 updateDiffId: function() { 125 var from = this.get('from'); 126 var to = this.get('to'); 127 this.set( 'diffId', (from ? from.id : '0') + ':' + to.id ); 281 128 } 282 129 }); 283 130 … … window.wp = window.wp || {}; 288 135 * ======================================================================== 289 136 */ 290 137 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 } 336 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 }, 138 // The frame view. This contains the entire page. 139 revisions.view.Frame = wp.Backbone.View.extend({ 140 tagName: 'div', 141 className: 'revisions', 142 template: wp.template('revisions-frame'), 428 143 429 option: function( key ) { 430 return $( '#diff-slider' ).slider( 'option', key ); 431 }, 144 initialize: function() { 145 this.model = new revisions.model.FrameState({}, { 146 revisions: this.collection 147 }); 432 148 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 ); 444 } 445 }); 149 this.listenTo( this.model, 'change:diffId', this.updateDiff ); 446 150 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(); 151 this.views.set( '.revisions-control-frame', new revisions.view.Controls({ 152 model: this.model 153 }) ); 476 154 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 } 155 if ( this.model.revisions.length ) { 156 var last = this.model.revisions.last(2); 157 var attributes = { to: last.pop() }; 482 158 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 ); 159 if ( last.length ) 160 attributes.from = last.pop(); 552 161 162 this.model.set( attributes ); 553 163 } 554 self.resetTicks();555 return self;556 }557 } );558 559 /**560 * wp.revisions.view.Interact561 *562 * Next/Prev buttons and the slider563 */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 mode569 events: {570 'click #next': ! isRtl ? 'nextRevision' : 'previousRevision',571 'click #previous': ! isRtl ? 'previousRevision' : 'nextRevision'572 164 }, 573 165 574 166 render: function() { 575 var modelcount; 576 this.$el.html( this.template ); 577 578 modelcount = Diff.revisions.length; 167 wp.Backbone.View.prototype.render.apply( this, arguments ); 579 168 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 } 169 $('#wpbody-content .wrap').append( this.el ); 170 this.views.ready(); 607 171 608 172 return this; 609 173 }, 610 174 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 ); 175 updateDiff: function() { 176 this.model.diffs.ensure( this.model.get('diffId'), this ).done( function( diff ) { 177 this.views.set( '.revisions-diff-frame', new revisions.view.Diff({ 178 model: diff 179 }) ); 180 }); 633 181 } 634 182 }); 635 183 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 }, 651 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' ) ); 184 // The control view. 185 // This contains the revision slider, previous/next buttons, and the compare checkbox. 186 revisions.view.Controls = wp.Backbone.View.extend({ 187 tagName: 'div', 188 className: 'revisions-controls', 189 template: wp.template('revisions-controls'), 190 191 initialize: function() { 192 this.views.set( new revisions.view.Slider({ 193 model: this.model 194 }) ); 195 } 196 }); 685 197 686 return this; 198 // The slider view. 199 // Encapsulates all of the configuration for the jQuery UI slider into a view. 200 revisions.view.Slider = wp.Backbone.View.extend({ 201 tagName: 'div', 202 className: 'wp-slider', 203 204 initialize: function() { 205 _.bindAll( this, 'start', 'slide', 'stop' ); 206 207 // Create the slider model from the provided collection data. 208 // TODO: This should actually pull from the model's `to` key. 209 var latestRevisionIndex = this.model.revisions.length - 1; 210 211 this.settings = new revisions.model.Slider({ 212 max: latestRevisionIndex, 213 value: latestRevisionIndex, 214 start: this.start, 215 slide: this.slide, 216 stop: this.stop 217 }); 687 218 }, 688 219 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 ); 220 ready: function() { 221 this.$el.slider( this.settings.toJSON() ); 222 this.settings.on( 'change', function( model, options ) { 223 // Apply changes to slider settings here. 224 }, this ); 695 225 }, 696 226 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 ; 227 start: function() { 701 228 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 revision705 706 if ( Diff.revisions.length === Diff.rightDiff ) // make sure 'left' handle not in rightmost slot707 Diff.rightDiff = Diff.rightDiff - 1;708 } else {709 if ( 1 === Diff.rightDiff ) // make sure right handle not in leftmost slot710 Diff.rightDiff = 2;711 }712 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 revision716 Diff.reloadLeftRight(); // load diffs for left and right handles717 Diff.revisionView.model = Diff.rightHandleRevisions;718 719 } else { // compare one mode720 Diff.singleRevision = true;721 Diff.revisionView.draggingLeft = false;722 Diff.reloadModelSingle();723 }724 Diff.revisionsInteractions.render();725 Diff.tickmarkView.render();726 229 }, 727 230 728 restore: function() { 729 document.location = $( '#restore-revision' ).data( 'restoreLink' ); 730 } 731 }); 732 733 734 /** 735 * ======================================================================== 736 * MODELS 737 * ======================================================================== 738 */ 231 slide: function( event, ui ) { 232 var attributes = { 233 to: this.model.revisions.at( ui.value ) 234 }; 739 235 740 /**741 * wp.revisions.Revision742 */743 Revision = revisions.model.Revision = Backbone.Model.extend({744 idAttribute: 'ID',236 // If we're at the first revision, unset 'from'. 237 if ( ui.value ) 238 attributes.from = this.model.revisions.at( ui.value - 1 ); 239 else 240 this.model.unset('from', { silent: true }); 745 241 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 242 this.model.set( attributes ); 759 243 }, 760 244 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 } 245 stop: function() { 774 246 775 247 } 776 248 }); 777 249 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 }, 250 // The diff view. 251 // This is the view for the current active diff. 252 revisions.view.Diff = wp.Backbone.View.extend({ 253 tagName: 'div', 254 className: 'revisions-diff', 255 template: wp.template('revisions-diff'), 807 256 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 }); 257 // Generate the options to be passed to the template. 258 prepare: function() { 259 return _.extend({ fields: this.model.fields.toJSON() }, this.options ); 815 260 } 261 }); 816 262 817 } ); 818 819 $( wp.revisions ); 263 // Initialize the revisions UI. 264 revisions.init = function() { 265 revisions.view.frame = new revisions.view.Frame({ 266 collection: new revisions.model.Revisions( revisions.settings.revisionData ) 267 }).render(); 268 }; 820 269 270 $( revisions.init ); 821 271 }(jQuery)); -
wp-admin/revision.php
8 8 9 9 /** WordPress Administration Bootstrap */ 10 10 require_once('./admin.php'); 11 12 require 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 11 17 wp_reset_vars( array( 'revision', 'action' ) ); 12 18 13 19 $revision_id = absint( $revision ); … … else 77 83 $parent_file = $submenu_file = 'edit.php'; 78 84 79 85 wp_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 ); 86 wp_localize_script( 'revisions', '_wpRevisionsSettings', wp_prepare_revisions_for_js( $post ) ); 89 87 90 88 /* Revisions Help Tab */ 91 89 … … require_once( './admin-header.php' ); 114 112 115 113 <div class="wrap"> 116 114 <?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> 119 117 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> 123 123 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> 127 131 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> 129 135 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' ); ?>" /> 131 138 </div> 132 </div> 139 140 <div class="revisions-slider"></div> 141 </script> 133 142 134 143 <script id="tmpl-revisions-diff" type="text/html"> 144 <# _.each( data.fields, function( field ) { #> 145 <h3>{{{ field.name }}}</h3> 146 {{{ field.diff }}} 147 <# }); #> 148 </script> 149 150 <script id="tmpl-revisions-diff-old" type="text/html"> 135 151 <div id="toggle-revision-compare-mode"> 136 152 <label> 137 153 <input type="checkbox" id="compare-two-revisions" /> … … require_once( './admin-header.php' ); 157 173 </div> 158 174 </div> 159 175 160 </div>161 162 176 <div id="diff-table">{{{ data.diff }}}</div> 163 177 </script> 164 178 -
wp-includes/revision.php
function _wp_upgrade_revisions_of_post( $post, $revisions ) { 597 597 598 598 return true; 599 599 } 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 removed604 *605 * @since 3.6.0606 *607 * @see wp_parse_args() Used to change defaults to user defined settings.608 * @uses Text_Diff609 * @uses WP_Text_Diff_Renderer_Table610 *611 * @param string $left_string "old" (left) version of string612 * @param string $right_string "new" (right) version of string613 * @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 }