Changeset 24520
- Timestamp:
- 06/26/2013 09:06:50 PM (11 years ago)
- Location:
- trunk
- Files:
-
- 1 added
- 6 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/wp-admin/admin-ajax.php
r24388 r24520 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 … … 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 -
trunk/wp-admin/css/wp-admin.css
r24429 r24520 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: 60px; 3500 padding: 40px 0 20px; 3501 border-bottom: 1px solid #dfdfdf; 3502 margin-bottom: 10px; 3503 } 3504 3505 .revisions-meta { 3506 margin-top: 15px; 3507 } 3508 .revision-toggle-compare-mode { 3509 position: absolute; 3510 top: 0; 3511 right: 0; 3512 } 3513 3514 .revisions-previous { 3515 float: left; 3516 } 3517 3518 .revisions-next { 3519 float: right; 3520 } 3521 3522 .wp-slider { 3523 width: 70%; 3524 margin: 6px auto 0; 3525 } 3484 3526 3485 3527 /* Revision meta box */ … … 3526 3568 #revisions-diff { 3527 3569 position: relative; 3528 }3529 3530 #toggle-revision-compare-mode {3531 position: absolute;3532 top: 0;3533 right: 0;3534 padding: 9px 9px 0 0;3535 3570 } 3536 3571 … … 3550 3585 #revision-interact { 3551 3586 padding: 20px 0; 3552 }3553 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 3587 } 3571 3588 … … 3589 3606 3590 3607 #diff-title-to strong { 3591 display: none;3608 display: inline; 3592 3609 } 3593 3610 … … 3606 3623 border-radius: 3px; 3607 3624 padding: 5px 200px 5px 5px; 3625 clear: both; 3608 3626 } 3609 3627 -
trunk/wp-admin/includes/ajax-actions.php
r24406 r24520 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 ); 2140 2141 if ( isset( $diff[ 'html' ] ) ) { 2142 $content .= sprintf( '<div class="diff-label">%s</div>', $field_value ); 2143 $content .= $diff[ 'html' ]; 2144 } 2145 2146 if ( isset( $diff[ 'lines_added' ] ) ) 2147 $lines_added = $lines_added + $diff[ 'lines_added' ]; 2148 2149 if ( isset( $diff[ 'lines_deleted' ] ) ) 2150 $lines_deleted = $lines_deleted + $diff[ 'lines_deleted' ]; 2151 } 2152 $content = '' == $content ? __( 'No difference' ) : $content; 2153 2154 $all_the_revisions = array ( 2155 'diff' => $content, 2156 'linesDeleted' => $lines_deleted, 2157 'linesAdded' => $lines_added 2085 function wp_ajax_get_revision_diffs() { 2086 require ABSPATH . 'wp-admin/includes/revision.php'; 2087 2088 // check_ajax_referer( 'revisions-ajax-nonce', 'nonce' ); 2089 2090 if ( ! $post = get_post( (int) $_REQUEST['post_id'] ) ) 2091 wp_send_json_error(); 2092 2093 if ( ! current_user_can( 'read_post', $post->ID ) ) 2094 wp_send_json_error(); 2095 2096 // Really just pre-loading the cache here. 2097 if ( ! $revisions = wp_get_post_revisions( $post->ID ) ) 2098 wp_send_json_error(); 2099 2100 $return = array(); 2101 @set_time_limit( count( $_REQUEST['compare'] ) ); 2102 2103 foreach ( $_REQUEST['compare'] as $compare_key ) { 2104 list( $compare_from, $compare_to ) = explode( ':', $compare_key ); // from:to 2105 2106 $return[] = array( 2107 'id' => $compare_key, 2108 'fields' => wp_get_revision_ui_diff( $post, $compare_from, $compare_to ), 2158 2109 ); 2159 2160 echo json_encode( $all_the_revisions ); 2161 exit(); 2162 } // end single model fetch 2163 2164 $count = -1; 2165 2166 // reverse the list to start with oldest revision 2167 $revisions = array_reverse( $revisions ); 2168 2169 $previous_revision_id = 0; 2170 2171 /* translators: revision date format, see http://php.net/date */ 2172 $datef = _x( 'j F, Y @ G:i:s', 'revision date format'); 2173 2174 foreach ( $revisions as $revision ) : 2175 if ( ! $show_autosaves && wp_is_post_autosave( $revision ) ) 2176 continue; 2177 2178 $revision_from_date_author = ''; 2179 $is_current_revision = false; 2180 $count++; 2181 2182 /** 2183 * return blank data for diffs to the left of the left handle (for right handel model) 2184 * or to the right of the right handle (for left handel model) 2185 * and visa versa in RTL mode 2186 */ 2187 if( ! is_rtl() ) { 2188 if ( ( ( 0 != $left_handle_at && $count < $left_handle_at ) || 2189 ( 0 != $right_handle_at && $count > ( $right_handle_at - 2 ) ) ) ) { 2190 $all_the_revisions[] = array ( 2191 'ID' => $revision->ID, 2192 ); 2193 continue; 2194 } 2195 } else { // is_rtl 2196 if ( ( 0 != $left_handle_at && $count > ( $left_handle_at - 1 ) || 2197 ( 0 != $left_handle_at && $count < $right_handle_at ) ) ) { 2198 $all_the_revisions[] = array ( 2199 'ID' => $revision->ID, 2200 ); 2201 continue; 2202 } 2203 } 2204 2205 if ( $compare_two_mode ) { 2206 $compare_to_gravatar = get_avatar( $left_revision->post_author, 24 ); 2207 $compare_to_author = get_the_author_meta( 'display_name', $left_revision->post_author ); 2208 $compare_to_date = date_i18n( $datef, strtotime( $left_revision->post_modified ) ); 2209 2210 $revision_from_date_author = sprintf( 2211 /* translators: post revision title: 1: author avatar, 2: author name, 3: time ago, 4: date */ 2212 _x( '%1$s %2$s, %3$s ago (%4$s)', 'post revision title' ), 2213 $compare_to_gravatar, 2214 $compare_to_author, 2215 human_time_diff( strtotime( $left_revision->post_modified ), current_time( 'timestamp' ) ), 2216 $compare_to_date 2217 ); 2218 } 2219 2220 $gravatar = get_avatar( $revision->post_author, 24 ); 2221 $author = get_the_author_meta( 'display_name', $revision->post_author ); 2222 $date = date_i18n( $datef, strtotime( $revision->post_modified ) ); 2223 $revision_date_author = sprintf( 2224 /* translators: post revision title: 1: author avatar, 2: author name, 3: time ago, 4: date */ 2225 _x( '%1$s %2$s, %3$s ago (%4$s)', 'post revision title' ), 2226 $gravatar, 2227 $author, 2228 human_time_diff( strtotime( $revision->post_modified ), current_time( 'timestamp' ) ), 2229 $date 2230 ); 2231 2232 /* translators: 1: date */ 2233 $autosavef = _x( '%1$s [Autosave]', 'post revision title extra' ); 2234 /* translators: 1: date */ 2235 $currentf = _x( '%1$s [Current Revision]', 'post revision title extra' ); 2236 2237 if ( ! $post = get_post( $post_id ) ) 2238 continue; 2239 2240 if ( $left_revision->post_modified === $post->post_modified ) 2241 $revision_from_date_author = sprintf( $currentf, $revision_from_date_author ); 2242 elseif ( wp_is_post_autosave( $left_revision ) ) 2243 $revision_from_date_author = sprintf( $autosavef, $revision_from_date_author ); 2244 2245 if ( $revision->post_modified === $post->post_modified ) { 2246 $revision_date_author = sprintf( $currentf, $revision_date_author ); 2247 $is_current_revision = true; 2248 } elseif ( wp_is_post_autosave( $revision ) ) { 2249 $revision_date_author = sprintf( $autosavef, $revision_date_author ); 2250 } 2251 2252 /* translators: revision date short format, see http://php.net/date */ 2253 $date_short_format = _x( 'j M @ G:i', 'revision date short format'); 2254 $date_short = date_i18n( $date_short_format, strtotime( $revision->post_modified ) ); 2255 2256 $revision_date_author_short = sprintf( 2257 '%s <strong>%s</strong><br />%s', 2258 $gravatar, 2259 $author, 2260 $date_short 2261 ); 2262 2263 $restore_link = wp_nonce_url( 2264 add_query_arg( 2265 array( 'revision' => $revision->ID, 2266 'action' => 'restore' ), 2267 admin_url( 'revision.php' ) 2268 ), 2269 "restore-post_{$revision->ID}" 2270 ); 2271 2272 // if this is a left handled calculation swap data 2273 if ( 0 != $right_handle_at ) { 2274 $tmp = $revision_from_date_author; 2275 $revision_from_date_author = $revision_date_author; 2276 $revision_date_author = $tmp; 2277 } 2278 2279 if ( ( $compare_two_mode || -1 !== $previous_revision_id ) ) { 2280 $all_the_revisions[] = array ( 2281 'ID' => $revision->ID, 2282 'titleTo' => $revision_date_author, 2283 'titleFrom' => $revision_from_date_author, 2284 'titleTooltip' => $revision_date_author_short, 2285 'restoreLink' => urldecode( $restore_link ), 2286 'previousID' => $previous_revision_id, 2287 'isCurrent' => $is_current_revision, 2288 ); 2289 } 2290 $previous_revision_id = $revision->ID; 2291 2292 endforeach; 2293 2294 // in RTL + single handle mode, reverse the revision direction 2295 if ( is_rtl() && $compare_two_mode ) 2296 $all_the_revisions = array_reverse( $all_the_revisions ); 2297 2298 echo json_encode( $all_the_revisions ); 2299 exit(); 2300 } 2110 } 2111 wp_send_json_success( $return ); 2112 } -
trunk/wp-admin/js/revisions.js
r24254 r24520 2 2 3 3 (function($) { 4 var Revision, Revisions, Diff, revisions; 5 6 revisions = wp.revisions = function() { 7 Diff = revisions.Diff = new Diff(); 8 }; 9 10 _.extend( revisions, { model: {}, view: {}, controller: {} } ); 4 var revisions; 5 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 */ 21 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(); 17 revisions.model.Slider = Backbone.Model.extend({ 18 defaults: { 19 value: 0, 20 min: 0, 21 max: 1, 22 step: 1 23 } 24 }); 25 26 revisions.model.Revision = Backbone.Model.extend({}); 27 28 revisions.model.Revisions = Backbone.Collection.extend({ 29 model: revisions.model.Revision, 30 31 comparator: function( revision ) { 32 return revision.id; 33 }, 34 }); 35 36 revisions.model.Field = Backbone.Model.extend({}); 37 38 revisions.model.Fields = Backbone.Collection.extend({ 39 model: revisions.model.Field 40 }); 41 42 revisions.model.Diff = Backbone.Model.extend({ 43 initialize: function(attributes, options) { 44 var fields = this.get('fields'); 45 this.unset('fields'); 46 47 this.fields = new revisions.model.Fields( fields ); 48 } 49 }); 50 51 revisions.model.Diffs = Backbone.Collection.extend({ 52 initialize: function(models, options) { 53 this.revisions = options.revisions; 54 this.requests = {}; 55 }, 56 57 model: revisions.model.Diff, 58 59 ensure: function( id, context ) { 60 var diff = this.get( id ); 61 var request = this.requests[ id ]; 62 var deferred = $.Deferred(); 63 var ids = {}; 64 65 if ( diff ) { 66 deferred.resolveWith( context, [ diff ] ); 67 } else { 68 this.trigger( 'ensure:load', ids ); 69 _.each( ids, _.bind( function(id) { 70 // Remove anything that has an ongoing request 71 if ( this.requests[ id ] ) 72 delete ids[ id ]; 73 }, this ) ); 74 if ( ! request ) { 75 // Always include the ID that started this ensure 76 ids[ id ] = true; 77 request = this.load( _.keys( ids ) ); 78 } 79 80 request.done( _.bind( function() { 81 deferred.resolveWith( context, [ this.get( id ) ] ); 82 }, this ) ); 83 } 84 85 return deferred.promise(); 86 }, 87 88 loadNew: function( comparisons ) { 89 comparisons = _.object( comparisons, comparisons ); 90 _.each( comparisons, _.bind( function( id ) { 91 // Exists 92 if ( this.get( id ) ) 93 delete comparisons[ id ]; 94 }, this ) ); 95 comparisons = _.toArray( comparisons ); 96 return this.load( comparisons ); 97 }, 98 99 load: function( comparisons ) { 100 // Our collection should only ever grow, never shrink, so remove: false 101 return this.fetch({ data: { compare: comparisons }, remove: false }); 102 }, 103 104 /**/ 105 loadLast: function( num ) { 106 num = num || 1; 107 var ids = this.getProximalDiffIds(); 108 ids = _.last( ids, num ); 109 110 if ( ids.length ) { 111 return this.loadNew( ids ); 112 } 113 }, 114 115 loadLastUnloaded: function( num ) { 116 num = num || 1; 117 var ids = this.getUnloadedProximalDiffIds(); 118 ids = _.last( ids, num ); 119 120 if ( ids.length ) { 121 return this.loadNew( ids ); 122 } 123 }, 124 125 getProximalDiffIds: function() { 126 var previous = 0, ids = []; 127 this.revisions.each( _.bind( function(revision) { 128 ids.push( previous + ':' + revision.id ); 129 previous = revision.id; 130 }, this ) ); 131 return ids; 132 }, 133 134 getUnloadedProximalDiffIds: function() { 135 var comparisons = this.getProximalDiffIds(); 136 comparisons = _.object( comparisons, comparisons ); 137 _.each( comparisons, _.bind( function( id ) { 138 // Exists 139 if ( this.get( id ) ) 140 delete comparisons[ id ]; 141 }, this ) ); 142 return _.toArray( comparisons ); 143 }, 144 145 loadAllBy: function( chunkSize ) { 146 chunkSize = chunkSize || 20; 147 var unloaded = this.getUnloadedProximalDiffIds(); 148 if ( unloaded.length ) { 149 return this.loadLastUnloaded( chunkSize ).always( _.bind( function() { 150 this.loadAllBy( chunkSize ); 151 }, this ) ); 152 } 153 }, 154 155 sync: function( method, model, options ) { 156 if ( 'read' === method ) { 157 options = options || {}; 158 options.context = this; 159 options.data = _.extend( options.data || {}, { 160 action: 'get-revision-diffs', 161 post_id: revisions.settings.postId 162 }); 163 164 var deferred = wp.xhr.send( options ); 165 var requests = this.requests; 166 167 // Record that we're requesting each diff. 168 if ( options.data.compare ) { 169 _.each( options.data.compare, function( id ) { 170 requests[ id ] = deferred; 171 }); 172 } 173 174 // When the request completes, clear the stored request. 175 deferred.always( function() { 176 if ( options.data.compare ) { 177 _.each( options.data.compare, function( id ) { 178 delete requests[ id ]; 179 }); 54 180 } 55 181 }); 56 } 57 }, 58 59 loadDiffs: function( models ) { 60 var self = this, 61 revisionsToLoad = models.where( { completed: false } ), 62 delay = 0, 63 totalChanges; 64 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; 182 183 return deferred; 184 185 // Otherwise, fall back to `Backbone.sync()`. 186 } else { 187 return Backbone.Model.prototype.sync.apply( this, arguments ); 188 } 189 } 190 }); 191 192 193 revisions.model.FrameState = Backbone.Model.extend({ 194 initialize: function( attributes, options ) { 195 this.revisions = options.revisions; 196 this.diffs = new revisions.model.Diffs( [], {revisions: this.revisions} ); 197 198 this.listenTo( this, 'change:from change:to', this.updateDiffId ); 199 }, 200 201 updateDiffId: function() { 202 var from = this.get( 'from' ); 203 var to = this.get( 'to' ); 204 this.set( 'diffId', (from ? from.id : '0') + ':' + to.id ); 205 } 206 }); 207 208 209 /** 210 * ======================================================================== 211 * VIEWS 212 * ======================================================================== 213 */ 214 215 // The frame view. This contains the entire page. 216 revisions.view.Frame = wp.Backbone.View.extend({ 217 tagName: 'div', 218 className: 'revisions', 219 template: wp.template('revisions-frame'), 220 221 initialize: function() { 222 this.model = new revisions.model.FrameState({}, { 223 revisions: this.collection 69 224 }); 70 225 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 }, 112 113 startLeftModelLoading: function() { 114 this.leftModelLoading = true; 115 $('#revision-diff-container').addClass('left-model-loading'); 116 }, 117 118 stopLeftModelLoading: function() { 119 this.leftModelLoading = false; 120 }, 121 122 startRightModelLoading: function() { 123 this.rightModelLoading = true; 124 $('#revision-diff-container').addClass('right-model-loading'); 125 }, 126 127 stopRightModelLoading: function() { 128 this.rightModelLoading = false; 129 }, 130 131 stopModelLoadingSpinner: function() { 132 $('#revision-diff-container').removeClass('right-model-loading'); 133 $('#revision-diff-container').removeClass('left-model-loading'); 134 }, 135 136 reloadModel: function() { 137 if ( this.singleRevision ) { 138 this.reloadModelSingle(); 226 this.listenTo( this.model, 'change:diffId', this.updateDiff ); 227 228 this.views.set( '.revisions-control-frame', new revisions.view.Controls({ 229 model: this.model 230 }) ); 231 232 if ( this.model.revisions.length ) { 233 var last = this.model.revisions.last(2); 234 var attributes = { to: last.pop() }; 235 236 if ( last.length ) 237 attributes.from = last.pop(); 238 239 this.model.set( attributes ); 240 241 // Load the rest: first 10, then the rest by 50 242 this.model.diffs.loadLastUnloaded( 10 ).always( _.bind( function() { 243 this.model.diffs.loadAllBy( 50 ); 244 }, this ) ); 245 } 246 }, 247 248 render: function() { 249 wp.Backbone.View.prototype.render.apply( this, arguments ); 250 251 $('#wpbody-content .wrap').append( this.el ); 252 this.views.ready(); 253 254 return this; 255 }, 256 257 updateDiff: function() { 258 this.model.diffs.ensure( this.model.get('diffId'), this ).done( function( diff ) { 259 if ( this.model.get('diffId') !== diff.id ) 260 return; 261 this.views.set( '.revisions-diff-frame', new revisions.view.Diff({ 262 model: diff 263 })); 264 }); 265 } 266 }); 267 268 // The control view. 269 // This contains the revision slider, previous/next buttons, and the compare checkbox. 270 revisions.view.Controls = wp.Backbone.View.extend({ 271 tagName: 'div', 272 className: 'revisions-controls', 273 274 initialize: function() { 275 // Add the button view 276 this.views.add( new revisions.view.Buttons({ 277 model: this.model 278 })); 279 280 // Add the Slider view 281 this.views.add( new revisions.view.Slider({ 282 model: this.model 283 }) ); 284 285 // Add the Meta view 286 this.views.add( new revisions.view.Meta({ 287 model: this.model 288 }) ); 289 } 290 }); 291 292 // The meta view. 293 // This contains the revision meta, and the restore button. 294 revisions.view.Meta = wp.Backbone.View.extend({ 295 tagName: 'div', 296 className: 'revisions-meta', 297 template: wp.template('revisions-meta'), 298 299 initialize: function() { 300 this.listenTo( this.model, 'change:diffId', this.updateMeta ); 301 }, 302 303 events: { 304 'click #restore-revision': 'restoreRevision' 305 }, 306 307 restoreRevision: function() { 308 var restoreUrl = this.model.get('to').attributes.restoreUrl.replace(/&/g, '&'); 309 document.location = restoreUrl; 310 }, 311 312 updateMeta: function() { 313 this.$el.html( this.template( this.model.toJSON() ) ); 314 if( this.model.get( 'to' ).attributes.current ) { 315 $( '#restore-revision' ).prop( 'disabled', true); 139 316 } else { 140 this.reloadLeftRight(); 141 } 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' ); 317 $( '#restore-revision' ).prop( 'disabled', false) 318 } 319 } 320 }); 321 322 323 // The buttons view. 324 // Encapsulates all of the configuration for the previous/next buttons, and the compare checkbox. 325 revisions.view.Buttons = wp.Backbone.View.extend({ 326 tagName: 'div', 327 className: 'revisions-buttons', 328 template: wp.template('revisions-controls'), 329 330 initialize: function() { 331 this.$el.html( this.template() ) 332 }, 333 334 events: { 335 'click #next': 'nextRevision', 336 'click #previous': 'previousRevision' 337 }, 338 339 gotoModel: function( toIndex ) { 340 var attributes = { 341 to: this.model.revisions.at( isRtl ? this.model.revisions.length - toIndex - 1 : toIndex ) // Reverse directions for Rtl 342 }; 343 // If we're at the first revision, unset 'from'. 344 if ( isRtl ? this.model.revisions.length - toIndex - 1 : toIndex ) // Reverse directions for Rtl 345 attributes.from = this.model.revisions.at( isRtl ? this.model.revisions.length - toIndex - 2 : toIndex - 1 ); 346 else 347 this.model.unset('from', { silent: true }); 348 349 this.model.set( attributes ); 350 }, 351 352 nextRevision: function() { 353 var toIndex = this.model.revisions.indexOf( this.model.get( 'to' ) ); 354 toIndex = isRtl ? toIndex - 1 : toIndex + 1; 355 this.gotoModel( toIndex ); 356 }, 357 358 previousRevision: function() { 359 var toIndex = this.model.revisions.indexOf( this.model.get('to') ); 360 toIndex = isRtl ? toIndex + 1 : toIndex - 1; 361 this.gotoModel( toIndex ); 362 }, 363 364 ready: function() { 365 this.listenTo( this.model, 'change:diffId', this.disabledButtonCheck ); 366 }, 367 368 // Check to see if the Previous or Next buttons need to be disabled or enabled 369 disabledButtonCheck: function() { 370 var maxVal = isRtl ? 0 : this.model.revisions.length - 1, 371 minVal = isRtl ? this.model.revisions.length - 1 : 0, 372 next = $( '.revisions-next .button' ), 373 previous = $( '.revisions-previous .button' ), 374 val = this.model.revisions.indexOf( this.model.get( 'to' ) ); 247 375 248 376 // Disable "Next" button if you're on the last node … … 252 380 next.prop( 'disabled', false ); 253 381 254 // Disable "Previous" button if you're on the 0node255 if ( 0=== val )256 prev .prop( 'disabled', true );382 // Disable "Previous" button if you're on the first node 383 if ( minVal === val ) 384 previous.prop( 'disabled', true ); 257 385 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 386 previous.prop( 'disabled', false ); 387 }, 388 389 390 }); 391 392 // The slider view. 393 // Encapsulates all of the configuration for the jQuery UI slider into a view. 394 revisions.view.Slider = wp.Backbone.View.extend({ 395 tagName: 'div', 396 className: 'wp-slider', 397 398 initialize: function() { 399 _.bindAll( this, 'start', 'slide', 'stop' ); 400 401 // Create the slider model from the provided collection data. 402 // TODO: This should actually pull from the model's `to` key. 403 var latestRevisionIndex = this.model.revisions.length - 1; 404 405 // Find the initially selected revision 406 var initiallySelectedRevisionIndex = 407 this.model.revisions.indexOf( 408 this.model.revisions.findWhere( { id: Number( revisions.settings.selectedRevision ) } ) ); 409 410 this.settings = new revisions.model.Slider({ 411 max: latestRevisionIndex, 412 value: initiallySelectedRevisionIndex, 413 start: this.start, 414 slide: this.slide, 415 stop: this.stop 267 416 }); 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 281 } 282 }); 283 284 285 /** 286 * ======================================================================== 287 * VIEWS 288 * ======================================================================== 289 */ 290 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 */ 417 }, 418 419 ready: function() { 420 this.$el.slider( this.settings.toJSON() ); 421 this.settings.on( 'change', function( model, options ) { 422 // Apply changes to slider settings here. 423 this.$el.slider( { value: this.model.revisions.indexOf( this.model.get( 'to' ) ) } ); // Set handle to current to model 424 }, this ); 425 // Reset to the initially selected revision 426 this.slide( '', this.settings.attributes ); 427 428 // Listen for changes in the diffId 429 this.listenTo( this.model, 'change:diffId', this.diffIdChanged ); 430 431 }, 432 433 diffIdChanged: function() { 434 // Reset the view settings when diffId is changed 435 this.settings.set( { 'value': this.model.revisions.indexOf( this.model.get( 'to' ) ) } ); 436 }, 437 438 start: function( event, ui ) { 439 // Track the mouse position to enable smooth dragging, overrides default jquery ui step behaviour 440 $( window ).mousemove( function( e ) { 441 var sliderLeft = $( '.wp-slider' ).offset().left, 442 sliderRight = sliderLeft + $( '.wp-slider' ).width(); 443 444 // Follow mouse movements, as long as handle remains inside slider 445 if ( e.clientX < sliderLeft ) { 446 $( ui.handle ).css( 'left', 0 ); // Mouse to left of slider 447 } else if ( e.clientX > sliderRight ) { 448 $( ui.handle ).css( 'left', sliderRight - sliderLeft); // Mouse to right of slider 449 } else { 450 $( ui.handle ).css( 'left', e.clientX - sliderLeft ); // Mouse in slider 451 } 452 } ); // End mousemove 453 }, 454 314 455 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 */ 456 var attributes = { 457 to: this.model.revisions.at( isRtl ? this.model.revisions.length - ui.value - 1 : ui.value ) // Reverse directions for Rtl 458 }; 459 460 // If we're at the first revision, unset 'from'. 461 if ( isRtl ? this.model.revisions.length - ui.value - 1 : ui.value ) // Reverse directions for Rtl 462 attributes.from = this.model.revisions.at( isRtl ? this.model.revisions.length - ui.value - 2 : ui.value - 1 ); 463 else 464 this.model.unset('from', { silent: true }); 465 466 this.model.set( attributes ); 467 }, 468 389 469 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 ); 431 }, 432 433 render: function() { 434 var self = this; 435 // this.$el doesn't work, why? 436 $( '#diff-slider' ).slider( { 437 slide: $.proxy( self.slide, self ), 438 start: $.proxy( self.start, self ), 439 stop: $.proxy( self.stop, self ) 440 } ); 441 442 // Set options 443 this.refresh( this.options ); 444 } 445 }); 446 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 } 607 608 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 ); 633 } 634 }); 635 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' ) ); 685 686 return this; 687 }, 688 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 ); 695 }, 696 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 } 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 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(); 726 }, 727 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', 745 746 defaults: { 747 ID: 0, 748 titleTo: '', 749 titleTooltip: '', 750 titleFrom: '', 751 diff: '<div class="diff-loading"><div class="spinner"></div></div>', 752 restoreLink: '', 753 completed: false, 754 linesAdded: 0, 755 linesDeleted: 0, 756 scopeOfChanges: 'none', 757 previousID: 0, 758 isCurrent: false 759 }, 760 761 url: function() { 762 if ( Diff.singleRevision ) { 763 return ajaxurl + 764 '?action=revisions-data' + 765 '&show_autosaves=true' + 766 '&show_split_view=true' + 767 '&nonce=' + revisions.model.settings.nonce + 768 '&single_revision_id=' + this.id + 769 '&compare_to=' + this.get( 'previousID' ) + 770 '&post_id=' + revisions.model.settings.post_id; 771 } else { 772 return this.collection.url() + '&single_revision_id=' + this.id; 773 } 774 775 } 776 }); 777 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 } ); 818 819 $( wp.revisions ); 820 470 $( window ).unbind( 'mousemove' ); // Stop tracking the mouse 471 // Reset settings pops handle back to the step position 472 this.settings.trigger( 'change' ); 473 } 474 }); 475 476 // The diff view. 477 // This is the view for the current active diff. 478 revisions.view.Diff = wp.Backbone.View.extend({ 479 tagName: 'div', 480 className: 'revisions-diff', 481 template: wp.template('revisions-diff'), 482 483 // Generate the options to be passed to the template. 484 prepare: function() { 485 return _.extend({ fields: this.model.fields.toJSON() }, this.options ); 486 } 487 }); 488 489 // Initialize the revisions UI. 490 revisions.init = function() { 491 revisions.view.frame = new revisions.view.Frame({ 492 collection: new revisions.model.Revisions( revisions.settings.revisionData ) 493 }).render(); 494 }; 495 496 $( revisions.init ); 821 497 }(jQuery)); -
trunk/wp-admin/revision.php
r24425 r24520 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 … … 21 27 if ( ! current_user_can( 'edit_post', $revision->post_parent ) ) 22 28 break; 23 24 29 25 30 if ( ! $post = get_post( $revision->post_parent ) ) … … 78 83 79 84 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 ); 85 wp_localize_script( 'revisions', '_wpRevisionsSettings', wp_prepare_revisions_for_js( $post, $revision_id ) ); 89 86 90 87 /* Revisions Help Tab */ … … 115 112 <div class="wrap"> 116 113 <?php screen_icon(); ?> 117 <div id="revision-diff-container" class="current-version right-model-loading"> 118 <h2 class="long-header"><?php echo $h2; ?></h2> 119 120 <div id="loading-status" class="updated message"> 121 <p><span class="spinner" ></span></p> 122 </div> 123 124 <div class="diff-slider-ticks-wrapper"> 125 <div id="diff-slider-ticks"></div> 126 </div> 127 128 <div id="revision-interact"></div> 129 130 <div id="revisions-diff"></div> 131 </div> 114 <h2 class="long-header"><?php echo $h2; ?></h2> 132 115 </div> 133 116 117 <script id="tmpl-revisions-frame" type="text/html"> 118 <span class="spinner"></span> 119 <div class="revisions-control-frame"></div> 120 <div class="revisions-diff-frame"></div> 121 </script> 122 123 <script id="tmpl-revisions-controls" type="text/html"> 124 125 <div class="revision-toggle-compare-mode"> 126 <label> 127 <input type="checkbox" class="compare-two-revisions" /> 128 <?php esc_attr_e( 'Compare two revisions' ); ?> 129 </label> 130 </div> 131 132 <div class="revisions-previous"> 133 <input class="button" type="button" id="previous" value="<?php echo esc_attr_x( 'Previous', 'Button label for a previous revision' ); ?>" /> 134 </div> 135 136 <div class="revisions-next"> 137 <input class="button" type="button" id="next" value="<?php echo esc_attr_x( 'Next', 'Button label for a next revision' ); ?>" /> 138 </div> 139 </script> 140 141 142 <script id="tmpl-revisions-meta" type="text/html"> 143 <div id="diff-header"> 144 <div id="diff-header-from" class="diff-header"> 145 <div id="diff-title-from" class="diff-title"> 146 <strong> 147 <?php _ex( 'From:', 'Followed by post revision info' ); ?></strong> 148 <# if ( 'undefined' !== typeof data.from ) { #> 149 {{{ data.from.attributes.author.avatar }}} {{{ data.from.attributes.author.name }}}, 150 {{{ data.from.attributes.timeAgo }}} <?php _e( 'ago' ); ?> 151 ({{{ data.from.attributes.dateShort }}}) 152 <# } #> 153 154 </div> 155 <div class="clear"></div> 156 </div> 157 158 <div id="diff-header-to" class="diff-header"> 159 <div id="diff-title-to" class="diff-title"> 160 <strong><?php _ex( 'To:', 'Followed by post revision info' ); ?></strong> 161 <# if ( 'undefined' !== typeof data.to ) { #> 162 {{{ data.to.attributes.author.avatar }}} {{{ data.to.attributes.author.name }}}, 163 {{{ data.to.attributes.timeAgo }}} <?php _e( 'ago' ); ?> 164 ({{{ data.to.attributes.dateShort }}}) 165 <# } #> 166 </div> 167 168 <input type="button" id="restore-revision" class="button button-primary" data-restore-link="{{{ data.restoreLink }}}" value="<?php esc_attr_e( 'Restore This Revision' )?>" /> 169 </div> 170 </div> 171 </script> 172 134 173 <script id="tmpl-revisions-diff" type="text/html"> 174 <# _.each( data.fields, function( field ) { #> 175 <h3>{{{ field.name }}}</h3> 176 {{{ field.diff }}} 177 <# }); #> 178 </script> 179 180 <script id="tmpl-revisions-diff-old" type="text/html"> 135 181 <div id="toggle-revision-compare-mode"> 136 182 <label> … … 158 204 </div> 159 205 160 </div>161 162 206 <div id="diff-table">{{{ data.diff }}}</div> 163 207 </script> 164 208 165 <script id="tmpl-revision-interact " type="text/html">209 <script id="tmpl-revision-interact-old" type="text/html"> 166 210 <div id="diff-previous-revision"> 167 211 <input class="button" type="button" id="previous" value="<?php echo esc_attr_x( 'Previous', 'Button label for a previous revision' ); ?>" /> … … 172 216 </div> 173 217 174 <div id="diff-slider" class="wp-slider"></div>175 218 </script> 176 219 -
trunk/wp-includes/revision.php
r24414 r24520 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 }
Note: See TracChangeset
for help on using the changeset viewer.